From 70a2a3993bd0641b06f766806ac68b2df7c621fe Mon Sep 17 00:00:00 2001 From: Simon Larsen Date: Fri, 14 Jun 2024 12:09:53 +0100 Subject: [PATCH] refactor: Update symbol type to use lowercase 'symbol' in ColumnAccessControl files --- .prettierrc.json | 11 - Accounts/Serve.ts | 52 +- Accounts/index.d.ts | 8 +- Accounts/src/App.tsx | 77 +- Accounts/src/Footer.tsx | 30 +- Accounts/src/Index.tsx | 20 +- Accounts/src/Pages/ForgotPassword.tsx | 174 +- Accounts/src/Pages/Login.tsx | 308 +- Accounts/src/Pages/NotFound.tsx | 26 +- Accounts/src/Pages/Register.tsx | 487 +- Accounts/src/Pages/ResetPassword.tsx | 207 +- Accounts/src/Pages/VerifyEmail.tsx | 217 +- Accounts/src/Utils/ApiPaths.ts | 16 +- Accounts/webpack.config.js | 143 +- AdminDashboard/Serve.ts | 52 +- AdminDashboard/index.d.ts | 8 +- AdminDashboard/src/App.tsx | 175 +- .../src/Components/Footer/Footer.tsx | 218 +- .../src/Components/Header/Header.tsx | 72 +- AdminDashboard/src/Components/Header/Help.tsx | 102 +- AdminDashboard/src/Components/Header/Logo.tsx | 40 +- .../src/Components/Header/Notifications.tsx | 58 +- .../src/Components/Header/ProjectPicker.tsx | 531 +- .../src/Components/Header/SearchBox.tsx | 20 +- .../src/Components/Header/UserProfile.tsx | 100 +- .../src/Components/MasterPage/MasterPage.tsx | 52 +- .../src/Components/NavBar/NavBar.tsx | 62 +- AdminDashboard/src/Index.tsx | 20 +- AdminDashboard/src/Pages/Init/Init.tsx | 32 +- AdminDashboard/src/Pages/Logout/Logout.tsx | 86 +- AdminDashboard/src/Pages/Projects/Index.tsx | 478 +- .../src/Pages/Settings/APIKey/Index.tsx | 192 +- .../Pages/Settings/Authentication/Index.tsx | 153 +- .../src/Pages/Settings/CallSMS/Index.tsx | 223 +- .../src/Pages/Settings/Email/Index.tsx | 810 +- .../src/Pages/Settings/Probes/Index.tsx | 464 +- .../src/Pages/Settings/SideMenu.tsx | 132 +- AdminDashboard/src/Pages/Users/Index.tsx | 420 +- AdminDashboard/src/Utils/ModelAPI.ts | 10 +- AdminDashboard/src/Utils/PageMap.ts | 24 +- AdminDashboard/src/Utils/RouteMap.ts | 86 +- AdminDashboard/src/Utils/RouteParams.ts | 4 +- AdminDashboard/webpack.config.js | 142 +- App/FeatureSet/ApiReference/Index.ts | 142 +- .../ApiReference/Service/Authentication.ts | 43 +- .../ApiReference/Service/DataType.ts | 226 +- App/FeatureSet/ApiReference/Service/Errors.ts | 42 +- .../ApiReference/Service/Introduction.ts | 46 +- App/FeatureSet/ApiReference/Service/Model.ts | 460 +- .../ApiReference/Service/PageNotFound.ts | 32 +- .../ApiReference/Service/Pagination.ts | 78 +- .../ApiReference/Service/Permissions.ts | 54 +- App/FeatureSet/ApiReference/Service/Status.ts | 32 +- App/FeatureSet/ApiReference/Utils/Config.ts | 6 +- .../ApiReference/Utils/Resources.ts | 127 +- App/FeatureSet/BaseAPI/Index.ts | 2285 ++-- App/FeatureSet/Docs/Index.ts | 142 +- App/FeatureSet/Docs/Utils/Config.ts | 6 +- App/FeatureSet/Docs/Utils/Nav.ts | 130 +- App/FeatureSet/Docs/Utils/Render.ts | 11 +- App/FeatureSet/Home/API/BlogAPI.ts | 133 +- App/FeatureSet/Home/Index.ts | 2068 ++- App/FeatureSet/Home/Static/js/amplitude.js | 138 +- App/FeatureSet/Home/Static/js/crm.js | 149 +- App/FeatureSet/Home/Static/js/getCookies.js | 6 +- App/FeatureSet/Home/Static/js/resources.js | 112 +- App/FeatureSet/Home/Static/js/switchUrl.js | 33 +- App/FeatureSet/Home/Tests/index.test.ts | 4 +- App/FeatureSet/Home/Utils/BlogPost.ts | 744 +- App/FeatureSet/Home/Utils/Config.ts | 4 +- App/FeatureSet/Home/Utils/NotFound.ts | 24 +- App/FeatureSet/Home/Utils/ProductCompare.ts | 1272 +- App/FeatureSet/Home/Utils/ServerError.ts | 24 +- App/FeatureSet/Identity/API/Authentication.ts | 1083 +- App/FeatureSet/Identity/API/Reseller.ts | 122 +- App/FeatureSet/Identity/API/SSO.ts | 843 +- .../Identity/API/StatusPageAuthentication.ts | 728 +- App/FeatureSet/Identity/API/StatusPageSSO.ts | 557 +- App/FeatureSet/Identity/Index.ts | 40 +- .../Identity/Utils/AuthenticationEmail.ts | 100 +- App/FeatureSet/Identity/Utils/SSO.ts | 408 +- App/FeatureSet/Notification/API/Call.ts | 265 +- App/FeatureSet/Notification/API/Mail.ts | 94 +- App/FeatureSet/Notification/API/SMS.ts | 253 +- App/FeatureSet/Notification/API/SMTPConfig.ts | 176 +- App/FeatureSet/Notification/Config.ts | 296 +- App/FeatureSet/Notification/Index.ts | 32 +- .../Notification/Services/CallService.ts | 681 +- .../Notification/Services/MailService.ts | 992 +- .../Notification/Services/SmsService.ts | 555 +- .../Notification/Utils/Handlebars.ts | 92 +- .../AddAggregationTemporalityToMetric.ts | 52 +- .../AddAttributesColumnToSpanAndLog.ts | 106 +- .../DataMigrations/AddDefaultGlobalConfig.ts | 48 +- .../AddDowntimeMonitorStatusToStatusPage.ts | 132 +- .../AddDurationColumnToSpanTable.ts | 53 +- .../AddEndDateToIncidentStateTimeline.ts | 184 +- .../AddEndDateToMonitorStatusTimeline.ts | 177 +- ...itorStatusTimelineWhereEndDateIsMissing.ts | 192 +- ...ddEndDateToScheduledEventsStateTimeline.ts | 191 +- .../Workers/DataMigrations/AddEndedState.ts | 184 +- .../DataMigrations/AddIsMonotonicToMetric.ts | 52 +- .../AddMonitoringDatesToMonitors.ts | 129 +- .../DataMigrations/AddOwnerInfoToProject.ts | 128 +- .../DataMigrations/AddPointTypeToMetric.ts | 52 +- .../AddPostedAtToPublicNotes.ts | 130 +- .../AddSecretKeyToIncomingRequestMonitor.ts | 80 +- .../AddStartDateToIncidentStateTimeline.ts | 152 +- .../AddStartDateToMonitorStatusTimeline.ts | 152 +- ...StartDateToScheduledEventsStateTimeline.ts | 165 +- .../AddTelemetryServiceColor.ts | 89 +- .../AddUnitColumnToMetricsTable.ts | 64 +- ...geLogSeverityColumnTypeFromTextToNumber.ts | 52 +- .../ChangeMetricColumnTypesToDecimal.ts | 68 +- .../DataMigrations/DataMigrationBase.ts | 34 +- .../GenerateNewCertsForStatusPage.ts | 76 +- .../Workers/DataMigrations/Index.ts | 130 +- .../MigrateDefaultUserNotificationRule.ts | 113 +- .../MigrateDefaultUserSettingNotification.ts | 111 +- .../MigrateToMeteredSubscription.ts | 172 +- ...ersToEnableEmailSubscribersOnStatusPage.ts | 84 +- .../MoveGreenlockCertsToAcmeCerts.ts | 132 +- .../RemoveCanFromPermissions.ts | 214 +- ...dateActiveMonitorCountToBillingProvider.ts | 79 +- .../UpdateGlobalCongfigFromEnv.ts | 110 +- App/FeatureSet/Workers/Index.ts | 150 +- .../SendNotificationToSubscribers.ts | 386 +- .../HardDelete/HardDeleteItemsInDatabase.ts | 157 +- .../Incident/SendNotificationToSubscribers.ts | 468 +- .../SendCreatedResourceNotification.ts | 274 +- .../SendNotePostedNotification.ts | 412 +- .../SendOwnerAddedNotification.ts | 419 +- .../SendStateChangeNotification.ts | 356 +- .../SendNotificationToSubscribers.ts | 522 +- .../SendNotificationToSubscribers.ts | 544 +- .../IncomingRequestMonitor/CheckHeartbeat.ts | 145 +- .../MeteredPlan/ReportTelemetryMeteredPlan.ts | 146 +- .../MonitorMetrics/MonitorMetricsByMinute.ts | 38 +- .../SendCreatedResourceNotification.ts | 238 +- .../SendOwnerAddedNotification.ts | 386 +- .../SendStatusChangeNotification.ts | 274 +- .../ExecutePendingExecutions.ts | 382 +- .../TimeoutStuckExecutions.ts | 90 +- .../CheckSubscriptionStatus.ts | 172 +- .../PopulatePlanNameInProject.ts | 114 +- .../UpdateTeamMembersIfNull.ts | 58 +- .../ChangeStateToEnded.ts | 139 +- .../ChangeStateToOngoing.ts | 150 +- .../SendNotificationToSubscribers.ts | 494 +- .../SendCreatedResourceNotification.ts | 233 +- .../SendNotePostedNotification.ts | 394 +- .../SendOwnerAddedNotification.ts | 441 +- .../SendStateChangeNotification.ts | 275 +- .../SendNotificationToSubscribers.ts | 521 +- .../SendNotificationToSubscribers.ts | 559 +- .../Jobs/ServerMonitor/CheckOnlineStatus.ts | 145 +- .../Jobs/StatusPageCerts/StatusPageCerts.ts | 78 +- .../SendAnnouncementCreatedNotification.ts | 226 +- .../SendCreatedResourceNotification.ts | 233 +- .../SendOwnerAddedNotification.ts | 390 +- .../Jobs/TelemetryService/DeleteOldData.ts | 130 +- .../UserOnCallLog/ExecutePendingExecutions.ts | 329 +- .../UserOnCallLog/TimeoutStuckExecutions.ts | 90 +- .../Workers/Jobs/Workflow/TimeoutJobs.ts | 86 +- .../AnalyticsDatabase/TableManegement.ts | 16 +- App/FeatureSet/Workers/Utils/Cron.ts | 70 +- App/FeatureSet/Workers/Utils/DataMigration.ts | 100 +- App/FeatureSet/Workers/Utils/JobDictionary.ts | 26 +- App/FeatureSet/Workflow/API/ComponentCode.ts | 100 +- App/FeatureSet/Workflow/API/Manual.ts | 70 +- App/FeatureSet/Workflow/API/Workflow.ts | 160 +- App/FeatureSet/Workflow/Index.ts | 108 +- .../Workflow/Services/QueueWorkflow.ts | 367 +- .../Workflow/Services/RunWorkflow.ts | 1043 +- .../Workflow/Utils/ComponentMetadata.ts | 38 +- App/Index.ts | 130 +- Common/AnalyticsModels/BaseModel.ts | 848 +- Common/AnalyticsModels/CommonModel.ts | 462 +- Common/AnalyticsModels/NestedModel.ts | 10 +- Common/Models/AccessControlModel.ts | 20 +- Common/Models/BaseModel.ts | 1378 +- Common/Models/FileModel.ts | 204 +- Common/Models/GlobalConfig.ts | 10 +- Common/Models/TenantModel.ts | 16 +- Common/Models/UserModel.ts | 16 +- Common/ServiceRoute.ts | 34 +- Common/Tests/Types/API/ErrorResponse.test.ts | 28 +- .../Tests/Types/API/HTTPErrorResponse.test.ts | 110 +- Common/Tests/Types/API/HTTPMethod.test.ts | 28 +- Common/Tests/Types/API/Headers.test.ts | 26 +- Common/Tests/Types/API/Hostname.test.ts | 40 +- Common/Tests/Types/API/Protocal.test.ts | 34 +- Common/Tests/Types/API/Response.test.ts | 38 +- Common/Tests/Types/API/ResponseType.test.ts | 22 +- Common/Tests/Types/API/Route.test.ts | 56 +- Common/Tests/Types/API/StatusCode.test.ts | 48 +- Common/Tests/Types/API/URL.test.ts | 78 +- .../Tests/Types/Alerts/AlertEventType.test.ts | 92 +- Common/Tests/Types/Alerts/AlertType.test.ts | 34 +- Common/Tests/Types/AppEnvironment.test.ts | 22 +- .../ApplicationLog/ApplicationLogType.test.ts | 22 +- Common/Tests/Types/ArrayUtil.test.ts | 154 +- .../Types/Billing/SubscriptionPlan.test.ts | 561 +- Common/Tests/Types/BrandColors.test.ts | 242 +- Common/Tests/Types/Char.test.ts | 162 +- Common/Tests/Types/Code/CodeType.test.ts | 22 +- Common/Tests/Types/Color.test.ts | 84 +- .../Tests/Types/Company/CompanySize.test.ts | 34 +- Common/Tests/Types/Company/JobRole.test.ts | 40 +- Common/Tests/Types/Countries.test.ts | 588 +- .../Tests/Types/Database/ColumnLength.test.ts | 82 +- .../Tests/Types/Database/ColumnType.test.ts | 154 +- Common/Tests/Types/Database/Columns.test.ts | 36 +- .../Tests/Types/Database/CompareBase.test.ts | 66 +- Common/Tests/Types/Database/Date.test.ts | 156 +- .../Types/Database/EqualToOrNull.test.ts | 122 +- Common/Tests/Types/Database/InBetween.test.ts | 142 +- Common/Tests/Types/Database/LimitMax.test.ts | 28 +- Common/Tests/Types/Database/NotEqual.test.ts | 30 +- Common/Tests/Types/Database/Search.test.ts | 16 +- Common/Tests/Types/DatabaseType.test.ts | 10 +- Common/Tests/Types/Date.test.ts | 180 +- Common/Tests/Types/Dictionary.test.ts | 44 +- Common/Tests/Types/Domain.test.ts | 72 +- Common/Tests/Types/Email/Email.test.ts | 110 +- Common/Tests/Types/EmailWithName.test.ts | 92 +- .../Tests/Types/EncryptionAlgorithm.test.ts | 10 +- .../Types/Exception/ApiException.test.ts | 25 +- .../Types/Exception/BadDataException.test.ts | 24 +- .../Exception/BadOperationException.test.ts | 25 +- .../Exception/BadRequestException.test.ts | 24 +- .../DatabaseNotConnectedException.test.ts | 20 +- .../Tests/Types/Exception/Exception.test.ts | 32 +- .../Exception/NotImplementedException.test.ts | 20 +- Common/Tests/Types/File.test.ts | 40 +- Common/Tests/Types/HashedString.test.ts | 36 +- Common/Tests/Types/Html.test.ts | 12 +- Common/Tests/Types/IP/IP.test.ts | 132 +- Common/Tests/Types/IP/IPType.test.ts | 16 +- Common/Tests/Types/IP/IPv4.test.ts | 30 +- Common/Tests/Types/IP/IPv6.test.ts | 30 +- Common/Tests/Types/JSON.test.ts | 70 +- Common/Tests/Types/JSONFunctions.test.ts | 85 +- Common/Tests/Types/ListData.test.ts | 66 +- Common/Tests/Types/Name.test.ts | 48 +- Common/Tests/Types/ObjectID.test.ts | 20 +- Common/Tests/Types/Permission.test.ts | 16 +- Common/Tests/Types/Phone.test.ts | 70 +- Common/Tests/Types/Port.test.ts | 66 +- Common/Tests/Types/PositiveNumber.test.ts | 232 +- Common/Tests/Types/SecuritySeverity.test.ts | 28 +- Common/Tests/Types/SerializableObject.test.ts | 71 +- Common/Tests/Types/Sleep.test.ts | 35 +- Common/Tests/Types/Text.test.ts | 14 +- Common/Tests/Types/Timezone.test.ts | 1318 +- Common/Tests/Types/Typeof.test.ts | 28 +- Common/Tests/Types/UserType.test.ts | 28 +- Common/Tests/Types/Version.test.ts | 66 +- Common/Tests/Types/XML.test.ts | 66 +- Common/Tests/Utils/API.test.ts | 651 +- Common/Tests/Utils/Analytics.test.ts | 132 +- Common/Tests/Utils/CronTime.test.ts | 52 +- Common/Tests/Utils/Faker.test.ts | 62 +- Common/Tests/Utils/Slug.test.ts | 40 +- Common/Tests/Utils/UUID.test.ts | 20 +- Common/Types/API/EmptyResponse.ts | 2 +- Common/Types/API/HTTPErrorResponse.ts | 45 +- Common/Types/API/HTTPMethod.ts | 10 +- Common/Types/API/HTTPResponse.ts | 216 +- Common/Types/API/Headers.ts | 2 +- Common/Types/API/Hostname.ts | 200 +- Common/Types/API/Protocol.ts | 12 +- Common/Types/API/ResponseType.ts | 6 +- Common/Types/API/Route.ts | 154 +- Common/Types/API/StatusCode.ts | 73 +- Common/Types/API/URL.ts | 450 +- Common/Types/Alerts/AlertEventType.ts | 20 +- Common/Types/Alerts/AlertType.ts | 10 +- .../AnalyticsDatabase/AnalyticsTableEngine.ts | 2 +- Common/Types/AnalyticsDatabase/TableColumn.ts | 309 +- .../AnalyticsDatabase/TableColumnType.ts | 24 +- Common/Types/AppEnvironment.ts | 6 +- .../ApplicationLog/ApplicationLogType.ts | 6 +- Common/Types/ArrayUtil.ts | 146 +- Common/Types/BaseDatabase/AccessControl.ts | 10 +- .../ColumnBillingAccessControl.ts | 8 +- .../DatabaseCommonInteractionProps.ts | 40 +- .../DatabaseCommonInteractionPropsUtil.ts | 163 +- Common/Types/BaseDatabase/DatabaseType.ts | 4 +- Common/Types/BaseDatabase/EnableWorkflowOn.ts | 8 +- Common/Types/BaseDatabase/EqualToOrNull.ts | 64 +- Common/Types/BaseDatabase/GreaterThan.ts | 38 +- .../Types/BaseDatabase/GreaterThanOrEqual.ts | 38 +- Common/Types/BaseDatabase/InBetween.ts | 128 +- Common/Types/BaseDatabase/Includes.ts | 76 +- Common/Types/BaseDatabase/IsNull.ts | 44 +- Common/Types/BaseDatabase/LessThan.ts | 38 +- Common/Types/BaseDatabase/LessThanOrEqual.ts | 38 +- Common/Types/BaseDatabase/ModelPermission.ts | 62 +- Common/Types/BaseDatabase/NotEqual.ts | 64 +- Common/Types/BaseDatabase/NotNull.ts | 44 +- Common/Types/BaseDatabase/Search.ts | 66 +- Common/Types/BaseDatabase/SortOrder.ts | 4 +- .../BaseDatabase/TableBillingAccessControl.ts | 10 +- Common/Types/Billing/MeteredPlan.ts | 42 +- Common/Types/Billing/SubscriptionPlan.ts | 437 +- Common/Types/Billing/SubscriptionStatus.ts | 46 +- Common/Types/BrandColors.ts | 120 +- Common/Types/BrowserType.ts | 6 +- Common/Types/Calendar/CalendarEvent.ts | 18 +- Common/Types/Call/CallRequest.ts | 38 +- Common/Types/Call/CallStatus.ts | 10 +- Common/Types/CallAndSMS/TwilioConfig.ts | 8 +- Common/Types/Char.ts | 124 +- Common/Types/Code/CodeType.ts | 14 +- .../CodeRepository/CodeRepositoryType.ts | 4 +- Common/Types/CodeRepository/PullRequest.ts | 26 +- .../Types/CodeRepository/PullRequestState.ts | 6 +- Common/Types/Color.ts | 184 +- Common/Types/Company/CompanySize.ts | 10 +- Common/Types/Company/JobRole.ts | 12 +- Common/Types/Copilot/CopilotEventStatus.ts | 4 +- Common/Types/Copilot/CopilotEventType.ts | 4 +- Common/Types/Countries.ts | 486 +- Common/Types/Currency.ts | 60 +- Common/Types/CustomField/CustomFieldType.ts | 6 +- .../AllowAccessIfSubscriptionIsUnpaid.ts | 8 +- .../AccessControl/ColumnAccessControl.ts | 62 +- .../ColumnBillingAccessControl.ts | 66 +- .../AccessControl/TableAccessControl.ts | 32 +- .../TableBillingAccessControl.ts | 32 +- Common/Types/Database/AccessControlColumn.ts | 8 +- .../Database/AllowUserQueryWithoutTenant.ts | 8 +- Common/Types/Database/CanAccessIfCanReadOn.ts | 12 +- Common/Types/Database/ColumnLength.ts | 122 +- Common/Types/Database/ColumnType.ts | 66 +- Common/Types/Database/Columns.ts | 32 +- Common/Types/Database/CompareBase.ts | 62 +- Common/Types/Database/CrudApiEndpoint.ts | 10 +- .../Database/CurrentUserCanAccessRecordBy.ts | 8 +- Common/Types/Database/DatabaseProperty.ts | 90 +- Common/Types/Database/Date.ts | 46 +- Common/Types/Database/EnableDocumentation.ts | 13 +- Common/Types/Database/EnableWorkflow.ts | 10 +- Common/Types/Database/IsPermissionsIf.ts | 24 +- Common/Types/Database/LabelsColumn.ts | 8 +- .../Types/Database/MultiTenentQueryAllowed.ts | 8 +- Common/Types/Database/PartialEntity.ts | 2 +- Common/Types/Database/SlugifyColumn.ts | 10 +- Common/Types/Database/TableColumn.ts | 84 +- Common/Types/Database/TableColumnType.ts | 78 +- Common/Types/Database/TableMetadata.ts | 28 +- Common/Types/Database/TenantColumn.ts | 8 +- Common/Types/Database/TotalItemsBy.ts | 18 +- Common/Types/Database/UniqueColumnBy.ts | 52 +- Common/Types/DatabaseType.ts | 4 +- Common/Types/Date.ts | 1943 ++- Common/Types/Day/DayOfWeek.ts | 48 +- Common/Types/Decimal.ts | 124 +- Common/Types/Dictionary.ts | 2 +- Common/Types/DiskSize.ts | 74 +- Common/Types/Domain.ts | 154 +- Common/Types/Email.ts | 224 +- Common/Types/Email/EmailBody.ts | 4 +- Common/Types/Email/EmailMessage.ts | 16 +- Common/Types/Email/EmailServer.ts | 24 +- Common/Types/Email/EmailTemplate.ts | 10 +- Common/Types/Email/EmailTemplateType.ts | 72 +- Common/Types/EmailWithName.ts | 54 +- Common/Types/EncryptionAlgorithm.ts | 2 +- Common/Types/Events/EventInterval.ts | 10 +- Common/Types/Events/Recurring.ts | 194 +- Common/Types/Exception/ApiException.ts | 10 +- Common/Types/Exception/BadDataException.ts | 10 +- .../Types/Exception/BadOperationException.ts | 10 +- Common/Types/Exception/BadRequestException.ts | 10 +- .../DatabaseNotConnectedException.ts | 16 +- Common/Types/Exception/Exception.ts | 30 +- Common/Types/Exception/ExceptionCode.ts | 34 +- .../Exception/NotAuthenticatedException.ts | 10 +- .../Types/Exception/NotAuthorizedException.ts | 10 +- Common/Types/Exception/NotFoundException.ts | 10 +- .../Exception/NotImplementedException.ts | 16 +- .../Exception/PaymentRequiredException.ts | 10 +- Common/Types/Exception/ServerException.ts | 10 +- .../Exception/SsoAuthorizationException.ts | 24 +- .../Exception/TenantNotFoundException.ts | 10 +- Common/Types/Exception/TimeoutException.ts | 10 +- Common/Types/Exception/UnableToReachServer.ts | 10 +- .../Exception/WebsiteRequestException.ts | 10 +- Common/Types/File.ts | 4 +- Common/Types/File/MimeType.ts | 10 +- Common/Types/Filter/FilterCondition.ts | 4 +- Common/Types/Filter/FilterType.ts | 12 +- Common/Types/HashCode.ts | 16 +- Common/Types/HashedString.ts | 178 +- Common/Types/Html.ts | 26 +- Common/Types/IP/IP.ts | 206 +- Common/Types/IP/IPType.ts | 4 +- Common/Types/IP/IPv4.ts | 14 +- Common/Types/IP/IPv6.ts | 14 +- Common/Types/Icon/IconProp.ts | 244 +- Common/Types/Infrastructure/BasicMetrics.ts | 30 +- Common/Types/Infrastructure/OSType.ts | 8 +- Common/Types/IsolatedVM/ReturnResult.ts | 4 +- Common/Types/JSON.ts | 278 +- Common/Types/JSONFunctions.ts | 710 +- Common/Types/JsonWebTokenData.ts | 22 +- Common/Types/Link.ts | 10 +- Common/Types/ListData.ts | 52 +- Common/Types/Mail/MailStatus.ts | 4 +- Common/Types/MeteredPlan/ProductType.ts | 8 +- Common/Types/Mixins.ts | 2 +- Common/Types/Monitor/CriteriaFilter.ts | 214 +- Common/Types/Monitor/CriteriaIncident.ts | 14 +- .../CustomCodeMonitorResponse.ts | 10 +- .../IncomingMonitor/IncomingMonitorRequest.ts | 20 +- Common/Types/Monitor/MonitorCriteria.ts | 313 +- .../Types/Monitor/MonitorCriteriaInstance.ts | 1306 +- Common/Types/Monitor/MonitorStep.ts | 631 +- Common/Types/Monitor/MonitorSteps.ts | 311 +- Common/Types/Monitor/MonitorType.ts | 319 +- .../Monitor/SSLMonitor/SslMonitorResponse.ts | 24 +- .../ServerMonitor/ServerMonitorResponse.ts | 20 +- .../Monitor/SyntheticMonitors/BrowserType.ts | 2 +- .../SyntheticMonitors/ScreenSizeType.ts | 2 +- .../Monitor/SyntheticMonitors/Screenshot.ts | 2 +- .../SyntheticMonitorResponse.ts | 16 +- Common/Types/Name.ts | 152 +- .../NotificationRule/NotificationRuleType.ts | 6 +- .../NotificationSettingEventType.ts | 36 +- Common/Types/ObjectID.ts | 160 +- Common/Types/OnCallDutyPolicy/Layer.ts | 1782 ++- .../OnCalDutyExecutionLogTimelineStatus.ts | 12 +- .../OnCallDutyPolicyAlertStatus.ts | 6 +- .../OnCallDutyPolicyStatus.ts | 10 +- .../OnCallDutyPolicy/RestrictionTimes.ts | 336 +- Common/Types/Operation/OperationResult.ts | 4 +- Common/Types/Operation/OperationStatus.ts | 10 +- Common/Types/Permission.ts | 6842 +++++----- Common/Types/Phone.ts | 129 +- Common/Types/Port.ts | 142 +- Common/Types/PositiveNumber.ts | 120 +- Common/Types/PricingPlan.ts | 10 +- Common/Types/Probe/ProbeApiIngestResponse.ts | 12 +- Common/Types/Probe/ProbeMonitorResponse.ts | 50 +- Common/Types/Probe/ProbeStatusReport.ts | 8 +- Common/Types/Reflection.ts | 8 +- Common/Types/SMS/SMS.ts | 6 +- Common/Types/SMS/SmsTemplateType.ts | 18 +- Common/Types/SSO/DigestMethod.ts | 8 +- Common/Types/SSO/SignatureMethod.ts | 8 +- .../ScheduledEvent/ScheduledEventState.ts | 8 +- Common/Types/ScreenSizeType.ts | 6 +- Common/Types/SecuritySeverity.ts | 8 +- Common/Types/SerializableObject.ts | 24 +- Common/Types/SerializableObjectDictionary.ts | 116 +- .../Types/ServiceCatalog/ServiceLanguage.ts | 32 +- Common/Types/Sleep.ts | 12 +- Common/Types/SmsStatus.ts | 6 +- .../Types/StatusPage/StatusPageChartType.ts | 12 +- Common/Types/Text.ts | 254 +- Common/Types/Time/StartAndEndTime.ts | 4 +- Common/Types/Timezone.ts | 1184 +- Common/Types/TimezoneCode.ts | 118 +- Common/Types/Typeof.ts | 8 +- .../UserNotificationEventType.ts | 2 +- .../UserNotificationExecutionStatus.ts | 10 +- .../UserNotificationStatus.ts | 10 +- Common/Types/UserType.ts | 8 +- Common/Types/Version.ts | 112 +- Common/Types/WebsiteRequest.ts | 116 +- Common/Types/Workflow/Component.ts | 144 +- Common/Types/Workflow/ComponentID.ts | 32 +- Common/Types/Workflow/Components.ts | 152 +- Common/Types/Workflow/Components/API.ts | 706 +- Common/Types/Workflow/Components/BaseModel.ts | 1167 +- Common/Types/Workflow/Components/Condition.ts | 120 +- Common/Types/Workflow/Components/Email.ts | 205 +- Common/Types/Workflow/Components/JSON.ts | 307 +- .../Types/Workflow/Components/JavaScript.ts | 122 +- Common/Types/Workflow/Components/Log.ts | 78 +- Common/Types/Workflow/Components/Manual.ts | 67 +- .../Workflow/Components/MicrosoftTeams.ts | 123 +- Common/Types/Workflow/Components/Schedule.ts | 64 +- Common/Types/Workflow/Components/Slack.ts | 127 +- Common/Types/Workflow/Components/Webhook.ts | 102 +- Common/Types/Workflow/Components/Workflow.ts | 76 +- Common/Types/Workflow/WorkflowPlan.ts | 8 +- Common/Types/Workflow/WorkflowStatus.ts | 12 +- Common/Types/XML.ts | 32 +- Common/Typings/Index.d.ts | 6 +- Common/Utils/API.ts | 790 +- Common/Utils/Analytics.ts | 72 +- Common/Utils/CronTime.ts | 14 +- Common/Utils/Enum.ts | 22 +- Common/Utils/Faker.ts | 50 +- Common/Utils/ObjectUtil.ts | 10 +- Common/Utils/Realtime.ts | 48 +- Common/Utils/Slug.ts | 34 +- Common/Utils/UUID.ts | 8 +- CommonServer/API/BaseAPI.ts | 791 +- CommonServer/API/BaseAnalyticsAPI.ts | 773 +- CommonServer/API/BillingInvoiceAPI.ts | 286 +- CommonServer/API/BillingPaymentMethodAPI.ts | 168 +- CommonServer/API/CodeRepositoryAPI.ts | 331 +- CommonServer/API/CommonAPI.ts | 120 +- CommonServer/API/FileAPI.ts | 86 +- CommonServer/API/GlobalConfigAPI.ts | 78 +- CommonServer/API/Index.ts | 14 +- CommonServer/API/MonitorGroupAPI.ts | 180 +- CommonServer/API/NotificationAPI.ts | 195 +- CommonServer/API/ProbeAPI.ts | 102 +- CommonServer/API/ProjectAPI.ts | 227 +- CommonServer/API/ProjectSSO.ts | 116 +- CommonServer/API/ResellerPlanAPI.ts | 452 +- CommonServer/API/ShortLinkAPI.ts | 85 +- CommonServer/API/StatusAPI.ts | 211 +- CommonServer/API/StatusPageAPI.ts | 4689 ++++--- CommonServer/API/StatusPageDomainAPI.ts | 423 +- CommonServer/API/StatusPageSubscriberAPI.ts | 86 +- CommonServer/API/UserAPI.ts | 14 +- CommonServer/API/UserCallAPI.ts | 202 +- CommonServer/API/UserEmailAPI.ts | 204 +- CommonServer/API/UserOnCallLogTimelineAPI.ts | 301 +- CommonServer/API/UserSmsAPI.ts | 196 +- CommonServer/API/VersionAPI.ts | 14 +- CommonServer/BillingConfig.ts | 12 +- CommonServer/DatabaseConfig.ts | 112 +- CommonServer/EnvironmentConfig.ts | 175 +- .../Infrastructure/ClickhouseConfig.ts | 62 +- .../Infrastructure/ClickhouseDatabase.ts | 196 +- CommonServer/Infrastructure/GlobalCache.ts | 218 +- CommonServer/Infrastructure/LocalCache.ts | 126 +- .../Postgres/DataSourceOptions.ts | 72 +- .../LocalMigrationGenerationDataSource.ts | 12 +- .../1717605043663-InitialMigration.ts | 10606 ++++++++-------- .../1717678334852-MigrationName.ts | 80 +- .../1717839110671-MigrationName.ts | 124 +- .../1717849921874-MigrationName.ts | 220 +- .../1717955235341-MigrationName.ts | 136 +- .../1718037833516-MigrationName.ts | 92 +- .../1718100824584-MigrationName.ts | 24 +- .../1718101665865-MigrationName.ts | 24 +- .../1718119926223-MigrationName.ts | 24 +- .../1718124277321-MigrationName.ts | 188 +- .../1718126316684-MigrationName.ts | 24 +- .../1718188920011-MigrationName.ts | 36 +- .../1718203144945-MigrationName.ts | 36 +- .../1718285877004-MigrationName.ts | 36 +- .../Postgres/SchemaMigrations/Index.ts | 56 +- .../Postgres/TestDataSourceOptions.ts | 30 +- .../Infrastructure/PostgresDatabase.ts | 160 +- CommonServer/Infrastructure/Queue.ts | 158 +- CommonServer/Infrastructure/QueueWorker.ts | 87 +- CommonServer/Infrastructure/Redis.ts | 246 +- CommonServer/Infrastructure/Semaphore.ts | 87 +- CommonServer/Infrastructure/SocketIO.ts | 54 +- CommonServer/Infrastructure/Status.ts | 58 +- .../Middleware/BearerTokenAuthorization.ts | 67 +- .../Middleware/ClusterKeyAuthorization.ts | 96 +- .../Middleware/NotificationMiddleware.ts | 120 +- .../Middleware/ProjectAuthorization.ts | 349 +- CommonServer/Middleware/UserAuthorization.ts | 687 +- CommonServer/Services/AccessTokenService.ts | 643 +- .../Services/AcmeCertificateService.ts | 12 +- CommonServer/Services/AcmeChallengeService.ts | 12 +- .../Services/AnalyticsDatabaseService.ts | 1673 ++- .../Services/ApiKeyPermissionService.ts | 287 +- CommonServer/Services/ApiKeyService.ts | 30 +- CommonServer/Services/BaseService.ts | 2 +- .../Services/BillingInvoiceService.ts | 168 +- .../Services/BillingPaymentMethodService.ts | 219 +- CommonServer/Services/BillingService.ts | 1603 ++- CommonServer/Services/CallLogService.ts | 14 +- CommonServer/Services/CallService.ts | 102 +- .../Services/CodeRepositoryService.ts | 36 +- CommonServer/Services/CopilotEventService.ts | 12 +- CommonServer/Services/DataMigrationService.ts | 12 +- CommonServer/Services/DatabaseService.ts | 2857 ++--- CommonServer/Services/DomainService.ts | 176 +- CommonServer/Services/EmailLogService.ts | 14 +- .../Services/EmailVerificationTokenService.ts | 12 +- CommonServer/Services/FileService.ts | 76 +- CommonServer/Services/GlobalConfigService.ts | 12 +- .../Services/GreenlockCertificateService.ts | 12 +- .../Services/GreenlockChallengeService.ts | 12 +- .../Services/IncidentCustomFieldService.ts | 12 +- .../Services/IncidentInternalNoteService.ts | 12 +- .../Services/IncidentNoteTemplateService.ts | 12 +- .../Services/IncidentOwnerTeamService.ts | 12 +- .../Services/IncidentOwnerUserService.ts | 12 +- .../Services/IncidentPublicNoteService.ts | 40 +- CommonServer/Services/IncidentService.ts | 1265 +- .../Services/IncidentSeverityService.ts | 284 +- CommonServer/Services/IncidentStateService.ts | 344 +- .../Services/IncidentStateTimelineService.ts | 712 +- .../IncidentTemplateOwnerTeamService.ts | 12 +- .../IncidentTemplateOwnerUserService.ts | 12 +- .../Services/IncidentTemplateService.ts | 158 +- CommonServer/Services/Index.ts | 446 +- CommonServer/Services/LabelService.ts | 68 +- CommonServer/Services/LogService.ts | 12 +- CommonServer/Services/MailService.ts | 112 +- CommonServer/Services/MetricService.ts | 12 +- .../Services/MonitorCustomFieldService.ts | 12 +- .../Services/MonitorGroupOwnerTeamService.ts | 12 +- .../Services/MonitorGroupOwnerUserService.ts | 12 +- .../Services/MonitorGroupResourceService.ts | 12 +- CommonServer/Services/MonitorGroupService.ts | 340 +- .../Services/MonitorMetricsByMinuteService.ts | 18 +- .../Services/MonitorOwnerTeamService.ts | 12 +- .../Services/MonitorOwnerUserService.ts | 12 +- CommonServer/Services/MonitorProbeService.ts | 89 +- CommonServer/Services/MonitorSecretService.ts | 12 +- CommonServer/Services/MonitorService.ts | 958 +- CommonServer/Services/MonitorStatusService.ts | 278 +- .../Services/MonitorStatusTimelineService.ts | 543 +- CommonServer/Services/NotificationService.ts | 355 +- .../OnCallDutyPolicyCustomFieldService.ts | 12 +- ...DutyPolicyEscalationRuleScheduleService.ts | 12 +- .../OnCallDutyPolicyEscalationRuleService.ts | 1401 +- ...CallDutyPolicyEscalationRuleTeamService.ts | 12 +- ...CallDutyPolicyEscalationRuleUserService.ts | 12 +- .../OnCallDutyPolicyExecutionLogService.ts | 179 +- ...llDutyPolicyExecutionLogTimelineService.ts | 12 +- .../OnCallDutyPolicyScheduleLayerService.ts | 221 +- ...nCallDutyPolicyScheduleLayerUserService.ts | 454 +- .../OnCallDutyPolicyScheduleService.ts | 230 +- .../Services/OnCallDutyPolicyService.ts | 132 +- CommonServer/Services/ProbeService.ts | 42 +- .../Services/ProjectCallSMSConfigService.ts | 84 +- CommonServer/Services/ProjectService.ts | 2192 ++-- .../Services/ProjectSmtpConfigService.ts | 112 +- CommonServer/Services/ProjectSsoService.ts | 12 +- CommonServer/Services/PromoCodeService.ts | 12 +- CommonServer/Services/ResellerPlanService.ts | 12 +- CommonServer/Services/ResellerService.ts | 12 +- .../ScheduledMaintenanceCustomFieldService.ts | 12 +- ...ScheduledMaintenanceInternalNoteService.ts | 12 +- ...ScheduledMaintenanceNoteTemplateService.ts | 12 +- .../ScheduledMaintenanceOwnerTeamService.ts | 12 +- .../ScheduledMaintenanceOwnerUserService.ts | 12 +- .../ScheduledMaintenancePublicNoteService.ts | 40 +- .../Services/ScheduledMaintenanceService.ts | 821 +- .../ScheduledMaintenanceStateService.ts | 282 +- ...cheduledMaintenanceStateTimelineService.ts | 914 +- .../ServiceCatalogOwnerTeamService.ts | 12 +- .../ServiceCatalogOwnerUserService.ts | 12 +- .../Services/ServiceCatalogService.ts | 40 +- .../Services/ServiceRepositoryService.ts | 12 +- CommonServer/Services/ShortLinkService.ts | 104 +- CommonServer/Services/SmsLogService.ts | 14 +- CommonServer/Services/SmsService.ts | 104 +- CommonServer/Services/SpanService.ts | 12 +- .../Services/StatusPageAnnouncementService.ts | 12 +- .../Services/StatusPageCertificateService.ts | 102 +- .../Services/StatusPageCustomFieldService.ts | 12 +- .../Services/StatusPageDomainService.ts | 784 +- .../Services/StatusPageFooterLinkService.ts | 427 +- .../Services/StatusPageGroupService.ts | 425 +- .../Services/StatusPageHeaderLinkService.ts | 427 +- ...atusPageHistoryChartBarColorRuleService.ts | 431 +- .../Services/StatusPageOwnerTeamService.ts | 12 +- .../Services/StatusPageOwnerUserService.ts | 12 +- .../Services/StatusPagePrivateUserService.ts | 261 +- .../Services/StatusPageResourceService.ts | 478 +- CommonServer/Services/StatusPageService.ts | 743 +- CommonServer/Services/StatusPageSsoService.ts | 12 +- .../Services/StatusPageSubscriberService.ts | 679 +- CommonServer/Services/TeamMemberService.ts | 882 +- .../Services/TeamPermissionService.ts | 664 +- CommonServer/Services/TeamService.ts | 124 +- .../Services/TelemetryServiceService.ts | 88 +- .../Services/TelemetryUsageBillingService.ts | 296 +- CommonServer/Services/UserCallService.ts | 340 +- CommonServer/Services/UserEmailService.ts | 248 +- .../Services/UserNotificationRuleService.ts | 1476 ++- .../UserNotificationSettingService.ts | 636 +- CommonServer/Services/UserOnCallLogService.ts | 483 +- .../Services/UserOnCallLogTimelineService.ts | 244 +- CommonServer/Services/UserService.ts | 476 +- CommonServer/Services/UserSmsService.ts | 304 +- CommonServer/Services/WorkflowLogService.ts | 14 +- CommonServer/Services/WorkflowService.ts | 142 +- .../Services/WorkflowVariableService.ts | 12 +- CommonServer/Tests/API/BaseAPI.test.ts | 1495 ++- CommonServer/Tests/API/Helpers.ts | 98 +- CommonServer/Tests/API/ProbeAPI.test.ts | 186 +- CommonServer/Tests/API/ProjectAPI.test.ts | 420 +- CommonServer/Tests/API/UserSmsApi.test.ts | 476 +- .../BearerTokenAuthorization.test.ts | 160 +- .../ClusterKeyAuthorization.test.ts | 183 +- .../Middleware/NotificationMiddleware.test.ts | 342 +- .../Middleware/ProjectAuthorization.test.ts | 554 +- .../Middleware/UserAuthorization.test.ts | 1541 ++- .../Services/AnalyticsDatabaseService.test.ts | 469 +- .../Tests/Services/BillingService.test.ts | 2948 +++-- .../Tests/Services/ProbeService.test.ts | 1541 ++- .../ScheduledMaintenanceService.test.ts | 168 +- .../Tests/Services/TeamMemberService.test.ts | 1443 ++- CommonServer/Tests/TestingUtils/Database.ts | 94 +- CommonServer/Tests/TestingUtils/Init.ts | 4 +- .../Tests/TestingUtils/Services/Helpers.ts | 270 +- .../Services/ProjectServiceHelper.ts | 50 +- .../ScheduledMaintenanceServiceHelper.ts | 50 +- .../ScheduledMaintenanceStateServiceHelper.ts | 84 +- .../Services/TeamMemberServiceHelper.ts | 44 +- .../Services/TeamServiceHelper.ts | 36 +- .../Tests/TestingUtils/Services/Types.ts | 78 +- .../Services/UserServiceHelper.ts | 50 +- .../Utils/AnalyticsDatabase/Statement.test.ts | 142 +- .../StatementGenerator.test.ts | 618 +- CommonServer/Tests/Utils/Cookie.test.ts | 190 +- CommonServer/Tests/Utils/CronTab.test.ts | 66 +- CommonServer/Tests/Utils/JsonToCsv.test.ts | 222 +- .../Types/AnalyticsDatabase/CountBy.ts | 20 +- .../Types/AnalyticsDatabase/CreateBy.ts | 8 +- .../Types/AnalyticsDatabase/CreateManyBy.ts | 8 +- .../Types/AnalyticsDatabase/DeleteBy.ts | 10 +- .../Types/AnalyticsDatabase/FindBy.ts | 12 +- .../Types/AnalyticsDatabase/FindOneBy.ts | 22 +- .../Types/AnalyticsDatabase/FindOneByID.ts | 14 +- .../Types/AnalyticsDatabase/GroupBy.ts | 2 +- CommonServer/Types/AnalyticsDatabase/Hooks.ts | 28 +- .../AnalyticsDatabase/ModelPermission.ts | 1398 +- CommonServer/Types/AnalyticsDatabase/Query.ts | 6 +- .../Types/AnalyticsDatabase/QueryHelper.ts | 8 +- .../Types/AnalyticsDatabase/Select.ts | 6 +- CommonServer/Types/AnalyticsDatabase/Sort.ts | 6 +- .../Types/AnalyticsDatabase/UpdateBy.ts | 12 +- .../Types/AnalyticsDatabase/UpdateByID.ts | 8 +- .../Types/BaseDatabase/DatabaseRequestType.ts | 8 +- .../ActiveMonitoringMeteredPlan.ts | 132 +- .../Billing/MeteredPlan/AllMeteredPlans.ts | 72 +- .../Billing/MeteredPlan/ServerMeteredPlan.ts | 50 +- .../MeteredPlan/TelemetryMeteredPlan.ts | 229 +- CommonServer/Types/Database/CountBy.ts | 22 +- CommonServer/Types/Database/CreateBy.ts | 12 +- CommonServer/Types/Database/DeleteBy.ts | 12 +- CommonServer/Types/Database/DeleteById.ts | 12 +- CommonServer/Types/Database/DeleteOneBy.ts | 14 +- CommonServer/Types/Database/FindBy.ts | 12 +- CommonServer/Types/Database/FindOneBy.ts | 22 +- CommonServer/Types/Database/FindOneByID.ts | 14 +- CommonServer/Types/Database/GroupBy.ts | 2 +- CommonServer/Types/Database/HardDeleteBy.ts | 10 +- CommonServer/Types/Database/Hooks.ts | 28 +- .../Permissions/AccessControlPermission.ts | 679 +- .../Database/Permissions/BasePermission.ts | 256 +- .../Database/Permissions/BillingPermission.ts | 179 +- .../Database/Permissions/ColumnPermission.ts | 397 +- .../Database/Permissions/CreatePermission.ts | 86 +- .../Database/Permissions/DeletePermission.ts | 123 +- .../Types/Database/Permissions/Index.ts | 133 +- .../Database/Permissions/PermissionsUtil.ts | 34 +- .../Database/Permissions/PublicPermission.ts | 78 +- .../Database/Permissions/QueryPermission.ts | 315 +- .../Database/Permissions/ReadPermission.ts | 233 +- .../Database/Permissions/SelectPermission.ts | 110 +- .../Database/Permissions/TablePermission.ts | 229 +- .../Database/Permissions/TenantPermission.ts | 195 +- .../Database/Permissions/UpdatePermission.ts | 111 +- .../Database/Permissions/UserPermission.ts | 44 +- CommonServer/Types/Database/Query.ts | 20 +- CommonServer/Types/Database/QueryHelper.ts | 793 +- CommonServer/Types/Database/QueryUtil.ts | 389 +- CommonServer/Types/Database/RelationSelect.ts | 10 +- CommonServer/Types/Database/SearchBy.ts | 20 +- CommonServer/Types/Database/SearchResult.ts | 8 +- CommonServer/Types/Database/Select.ts | 6 +- CommonServer/Types/Database/SelectUtil.ts | 66 +- CommonServer/Types/Database/Sort.ts | 20 +- CommonServer/Types/Database/UpdateBy.ts | 12 +- CommonServer/Types/Database/UpdateByID.ts | 14 +- .../Types/Database/UpdateByIDAndFetch.ts | 10 +- CommonServer/Types/Database/UpdateOneBy.ts | 14 +- CommonServer/Types/Domain.ts | 87 +- CommonServer/Types/FeatureSet.ts | 2 +- CommonServer/Types/Markdown.ts | 208 +- CommonServer/Types/Workflow/ComponentCode.ts | 64 +- .../Types/Workflow/Components/API/Delete.ts | 117 +- .../Types/Workflow/Components/API/Get.ts | 117 +- .../Types/Workflow/Components/API/Post.ts | 117 +- .../Types/Workflow/Components/API/Put.ts | 117 +- .../Types/Workflow/Components/API/Utils.ts | 150 +- .../BaseModel/CreateManyBaseModel.ts | 241 +- .../BaseModel/CreateOneBaseModel.ts | 238 +- .../BaseModel/DeleteManyBaseModel.ts | 288 +- .../BaseModel/DeleteOneBaseModel.ts | 230 +- .../Components/BaseModel/FindManyBaseModel.ts | 351 +- .../Components/BaseModel/FindOneBaseModel.ts | 298 +- .../Components/BaseModel/OnCreateBaseModel.ts | 14 +- .../Components/BaseModel/OnDeleteBaseModel.ts | 14 +- .../BaseModel/OnTriggerBaseModel.ts | 420 +- .../Components/BaseModel/OnUpdateBaseModel.ts | 14 +- .../BaseModel/UpdateManyBaseModel.ts | 334 +- .../BaseModel/UpdateOneBaseModel.ts | 276 +- .../Workflow/Components/Conditions/IfElse.ts | 208 +- .../Types/Workflow/Components/Email.ts | 276 +- .../Types/Workflow/Components/Index.ts | 200 +- .../Workflow/Components/JSON/JsonToText.ts | 144 +- .../Workflow/Components/JSON/MergeJson.ts | 158 +- .../Workflow/Components/JSON/TextToJson.ts | 138 +- .../Types/Workflow/Components/JavaScript.ts | 180 +- CommonServer/Types/Workflow/Components/Log.ts | 74 +- .../Types/Workflow/Components/Manual.ts | 32 +- .../MicrosoftTeams/SendMessageToChannel.ts | 232 +- .../Types/Workflow/Components/Schedule.ts | 259 +- .../Components/Slack/SendMessageToChannel.ts | 205 +- .../Types/Workflow/Components/Webhook.ts | 146 +- CommonServer/Types/Workflow/TriggerCode.ts | 126 +- CommonServer/Types/Workflow/Workflow.ts | 12 +- CommonServer/Utils/Airtable.ts | 64 +- .../Utils/AnalyticsDatabase/Statement.ts | 318 +- .../AnalyticsDatabase/StatementGenerator.ts | 1204 +- CommonServer/Utils/BasicCron.ts | 50 +- .../Utils/CodeRepository/CodeRepository.ts | 634 +- .../CodeRepository/CodeRepositoryFile.ts | 8 +- .../Utils/CodeRepository/GitHub/GitHub.ts | 429 +- .../HostedCodeRepository.ts | 194 +- CommonServer/Utils/Cookie.ts | 112 +- CommonServer/Utils/CronTab.ts | 21 +- CommonServer/Utils/Encryption.ts | 51 +- CommonServer/Utils/Environment.ts | 32 +- CommonServer/Utils/Errors.ts | 45 +- CommonServer/Utils/Execute.ts | 37 +- CommonServer/Utils/Express.ts | 122 +- CommonServer/Utils/Greenlock/Greenlock.ts | 487 +- CommonServer/Utils/JsonToCsv.ts | 24 +- CommonServer/Utils/JsonWebToken.ts | 231 +- CommonServer/Utils/LocalFile.ts | 91 +- CommonServer/Utils/Logger.ts | 155 +- .../Probe/Criteria/APIRequestCriteria.ts | 336 +- .../Utils/Probe/Criteria/CompareCriteria.ts | 886 +- .../Criteria/CustomCodeMonitorCriteria.ts | 206 +- .../Utils/Probe/Criteria/EvaluateOverTime.ts | 201 +- .../Probe/Criteria/IncomingRequestCriteria.ts | 328 +- .../Probe/Criteria/SSLMonitorCriteria.ts | 336 +- .../Probe/Criteria/ServerMonitorCriteria.ts | 537 +- .../Utils/Probe/Criteria/SyntheticMonitor.ts | 78 +- CommonServer/Utils/Probe/DataToProcess.ts | 12 +- .../Utils/Probe/ProbeMonitorResponse.ts | 2249 ++-- CommonServer/Utils/Process.ts | 24 +- CommonServer/Utils/Realtime.ts | 253 +- CommonServer/Utils/Response.ts | 496 +- CommonServer/Utils/StartServer.ts | 416 +- CommonServer/Utils/Stream.ts | 50 +- CommonServer/Utils/Telemetry.ts | 473 +- CommonServer/Utils/VM/VMAPI.ts | 314 +- CommonServer/Utils/VM/VMRunner.ts | 102 +- CommonUI/setupTest.js | 4 +- CommonUI/src/Components/404.tsx | 100 +- .../src/Components/Accordion/Accordion.tsx | 204 +- .../Components/Accordion/AccordionGroup.tsx | 8 +- .../ActionButton/ActionButtonSchema.ts | 28 +- CommonUI/src/Components/Alerts/Alert.tsx | 219 +- .../AuthContainer/AuthContainer.tsx | 8 +- CommonUI/src/Components/Badge/Badge.tsx | 66 +- CommonUI/src/Components/Banner/Banner.tsx | 58 +- .../Components/Breadcrumbs/Breadcrumbs.tsx | 108 +- .../Components/BulkUpdate/BulkUpdateForm.tsx | 579 +- CommonUI/src/Components/Button/Button.tsx | 454 +- CommonUI/src/Components/Button/ButtonTypes.ts | 6 +- CommonUI/src/Components/Calendar/Calendar.tsx | 148 +- CommonUI/src/Components/Card/Card.tsx | 183 +- .../Components/Card/CardButtons/Refresh.ts | 20 +- .../Components/CategoryCheckbox/Category.tsx | 256 +- .../CategoryCheckbox/CategoryCheckboxTypes.ts | 12 +- .../CategoryCheckbox/CheckboxList.tsx | 154 +- .../src/Components/CategoryCheckbox/Index.tsx | 310 +- .../Charts/ChartGroup/ChartGroup.tsx | 119 +- .../src/Components/Charts/Line/LineChart.tsx | 363 +- CommonUI/src/Components/Checkbox/Checkbox.tsx | 164 +- .../Components/Checkbox/CheckboxViewer.tsx | 56 +- .../src/Components/CodeBlock/CodeBlock.tsx | 24 +- .../src/Components/CodeEditor/CodeEditor.tsx | 324 +- .../Components/ColorCircle/ColorCircle.tsx | 32 +- .../ColorSquareCube/ColorSquareCube.tsx | 32 +- .../Components/ColorViewer/ColorViewer.tsx | 74 +- .../src/Components/ComingSoon/ComingSoon.tsx | 22 +- .../ComponentLoader/CompactLoader.tsx | 20 +- .../ComponentLoader/ComponentLoader.tsx | 14 +- .../CopyTextButton/CopyTextButton.tsx | 53 +- .../CopyableButton/CopyableButton.tsx | 80 +- .../CustomFields/CustomFieldsDetail.tsx | 305 +- CommonUI/src/Components/Detail/Detail.tsx | 765 +- CommonUI/src/Components/Detail/Field.ts | 68 +- CommonUI/src/Components/Detail/FieldLabel.tsx | 77 +- .../src/Components/Detail/PlaceholderText.tsx | 8 +- .../src/Components/Dictionary/Dictionary.tsx | 519 +- .../Dictionary/DictionaryOfStingsViewer.tsx | 96 +- .../Dictionary/DictionaryOfStrings.tsx | 57 +- CommonUI/src/Components/Divider/Divider.tsx | 14 +- CommonUI/src/Components/Dropdown/Dropdown.tsx | 378 +- .../DuplicateModel/DuplicateModel.tsx | 283 +- .../src/Components/EmptyState/EmptyState.tsx | 77 +- CommonUI/src/Components/Error/PageError.tsx | 54 +- CommonUI/src/Components/ErrorBoundary.tsx | 61 +- .../Components/ErrorMessage/ErrorMessage.tsx | 46 +- .../EventHistoryList/EventHistoryDayList.tsx | 72 +- .../EventHistoryList/EventHistoryList.tsx | 38 +- .../EventHistoryList/NoEventDay.tsx | 46 +- .../src/Components/EventItem/EventItem.tsx | 635 +- .../Events/RecurringFieldElement.tsx | 143 +- .../src/Components/FilePicker/FilePicker.tsx | 516 +- .../src/Components/Filters/BooleanFilter.tsx | 90 +- .../src/Components/Filters/DateFilter.tsx | 578 +- .../src/Components/Filters/DropdownFilter.tsx | 97 +- .../src/Components/Filters/EntityFilter.tsx | 111 +- .../src/Components/Filters/FilterViewer.tsx | 854 +- .../Components/Filters/FilterViewerItem.tsx | 26 +- .../src/Components/Filters/FiltersForm.tsx | 189 +- .../src/Components/Filters/JSONFilter.tsx | 82 +- .../src/Components/Filters/TextFilter.tsx | 163 +- .../src/Components/Filters/Types/Filter.ts | 14 +- .../Components/Filters/Types/FilterData.ts | 4 +- CommonUI/src/Components/Footer/Footer.tsx | 98 +- .../Components/FormModal/BasicFormModal.tsx | 110 +- CommonUI/src/Components/Forms/BasicForm.tsx | 1334 +- .../src/Components/Forms/BasicModelForm.tsx | 225 +- .../Components/Forms/Fields/ColorPicker.tsx | 241 +- .../Components/Forms/Fields/FieldLabel.tsx | 78 +- .../src/Components/Forms/Fields/FormField.tsx | 1366 +- CommonUI/src/Components/Forms/ModelForm.tsx | 1525 ++- CommonUI/src/Components/Forms/Steps/Step.tsx | 146 +- CommonUI/src/Components/Forms/Steps/Steps.tsx | 108 +- CommonUI/src/Components/Forms/Types/Field.ts | 166 +- CommonUI/src/Components/Forms/Types/Fields.ts | 2 +- .../Forms/Types/FormFieldSchemaType.ts | 70 +- .../Forms/Types/FormFieldsSchema.ts | 12 +- .../src/Components/Forms/Types/FormStep.ts | 14 +- .../src/Components/Forms/Types/FormValues.ts | 8 +- CommonUI/src/Components/Forms/Validation.ts | 641 +- .../FullPageModal/FullPageModal.tsx | 46 +- .../Components/GanttChart/Bar/BarLabel.tsx | 14 +- .../src/Components/GanttChart/Bar/Index.tsx | 188 +- .../Components/GanttChart/ChartContainer.tsx | 54 +- CommonUI/src/Components/GanttChart/Index.tsx | 128 +- .../src/Components/GanttChart/Row/Index.tsx | 24 +- .../src/Components/GanttChart/Row/Row.tsx | 247 +- .../Components/GanttChart/Row/RowLabel.tsx | 22 +- CommonUI/src/Components/GanttChart/Rows.tsx | 80 +- .../Components/GanttChart/Timeline/Index.tsx | 68 +- .../GanttChart/Timeline/TimelineInterval.tsx | 40 +- .../Timeline/TimelineIntervalMarks.tsx | 62 +- .../src/Components/Graphs/DayUptimeGraph.tsx | 478 +- CommonUI/src/Components/Header/Header.tsx | 55 +- .../Header/HeaderIconDropdownButton.tsx | 142 +- .../Header/IconDropdown/IconDropdownItem.tsx | 50 +- .../Header/IconDropdown/IconDropdownMenu.tsx | 26 +- .../Header/IconDropdown/IconDropdownRow.tsx | 8 +- .../Header/Notifications/NotificationItem.tsx | 78 +- .../Header/Notifications/Notifications.tsx | 179 +- .../ProjectPicker/CreateNewProjectButton.tsx | 50 +- .../Header/ProjectPicker/ProjectPicker.tsx | 212 +- .../ProjectPicker/ProjectPickerFilterBox.tsx | 36 +- .../ProjectPicker/ProjectPickerMenu.tsx | 42 +- .../ProjectPicker/ProjectPickerMenuItem.tsx | 68 +- CommonUI/src/Components/Header/SearchBox.tsx | 38 +- .../Header/UserProfile/UserProfile.tsx | 72 +- .../UserProfileDropdownDivider.tsx | 4 +- .../Header/UserProfile/UserProfileMenu.tsx | 40 +- .../UserProfile/UserProfileMenuItem.tsx | 78 +- .../Components/HeaderAlert/HeaderAlert.tsx | 48 +- .../HeaderAlert/HeaderModelAlert.tsx | 148 +- .../src/Components/HiddenText/HiddenText.tsx | 90 +- .../HorizontalRule/HorizontalRule.tsx | 16 +- .../src/Components/Icon/CircularIconImage.tsx | 52 +- CommonUI/src/Components/Icon/Icon.tsx | 2141 ++-- CommonUI/src/Components/Image/Image.tsx | 94 +- .../src/Components/ImageTiles/ImageTiles.tsx | 93 +- CommonUI/src/Components/InfoCard/InfoCard.tsx | 36 +- .../src/Components/InlineCode/InlineCode.tsx | 16 +- CommonUI/src/Components/Input/Input.tsx | 419 +- CommonUI/src/Components/Link/Link.tsx | 116 +- CommonUI/src/Components/List/List.tsx | 277 +- CommonUI/src/Components/List/ListBody.tsx | 114 +- CommonUI/src/Components/List/ListRow.tsx | 305 +- CommonUI/src/Components/Loader/Loader.tsx | 72 +- CommonUI/src/Components/Loader/PageLoader.tsx | 30 +- .../src/Components/LogsViewer/LogItem.tsx | 462 +- .../src/Components/LogsViewer/LogsFilters.tsx | 482 +- .../src/Components/LogsViewer/LogsViewer.tsx | 160 +- .../Markdown.tsx/LazyMarkdownViewer.tsx | 30 +- .../Markdown.tsx/MarkdownEditor.tsx | 46 +- .../Markdown.tsx/MarkdownViewer.tsx | 176 +- .../src/Components/MasterPage/MasterPage.tsx | 98 +- .../src/Components/Modal/ConfirmModal.tsx | 90 +- CommonUI/src/Components/Modal/Modal.tsx | 336 +- CommonUI/src/Components/Modal/ModalBody.tsx | 24 +- CommonUI/src/Components/Modal/ModalFooter.tsx | 116 +- .../Components/ModelDelete/ModelDelete.tsx | 168 +- .../ModelDetail/CardModelDetail.tsx | 238 +- CommonUI/src/Components/ModelDetail/Field.ts | 10 +- .../Components/ModelDetail/ModelDetail.tsx | 464 +- CommonUI/src/Components/ModelFilter/Filter.ts | 44 +- .../ModelFormModal/ModelFormModal.tsx | 205 +- .../src/Components/ModelList/ModelList.tsx | 569 +- .../Components/ModelList/StaticModelList.tsx | 380 +- .../ModelListModal/ModelListModal.tsx | 88 +- .../ModelProgress/ModelProgress.tsx | 116 +- .../ModelTable/AnalyticsModelTable.tsx | 192 +- .../Components/ModelTable/BaseModelTable.tsx | 3371 +++-- CommonUI/src/Components/ModelTable/Column.ts | 57 +- CommonUI/src/Components/ModelTable/Columns.ts | 6 +- .../src/Components/ModelTable/ModelTable.tsx | 345 +- .../src/Components/MonitorGraphs/Uptime.tsx | 155 +- .../Components/MonitorGraphs/UptimeUtil.ts | 592 +- CommonUI/src/Components/Navbar/NavBar.tsx | 34 +- CommonUI/src/Components/Navbar/NavBarItem.tsx | 126 +- CommonUI/src/Components/Navbar/NavBarMenu.tsx | 84 +- .../src/Components/Navbar/NavBarMenuItem.tsx | 56 +- .../Components/Navbar/NavBarMenuSubItem.tsx | 22 +- .../src/Components/ObjectID/IDGenerator.tsx | 144 +- .../src/Components/OrderedStatesList/Item.tsx | 203 +- .../OrderedStatesList/OrderedStatesList.tsx | 307 +- CommonUI/src/Components/Page/ModelPage.tsx | 122 +- CommonUI/src/Components/Page/Page.tsx | 158 +- .../src/Components/Pagination/Pagination.tsx | 444 +- CommonUI/src/Components/Pill/Pill.tsx | 103 +- CommonUI/src/Components/Probe/Probe.tsx | 108 +- .../Components/ProgressBar/ProgressBar.tsx | 104 +- CommonUI/src/Components/Radio/Radio.tsx | 103 +- .../RadioButtons/BasicRadioButtons.tsx | 148 +- .../RadioButtons/GroupRadioButtons.tsx | 218 +- .../ResetObjectID/ResetObjectID.tsx | 248 +- .../src/Components/ShortcutKey/Shortcut.tsx | 22 +- .../src/Components/ShortcutKey/ShortcutKey.ts | 8 +- .../SideMenu/CountModelSideMenuItem.tsx | 124 +- CommonUI/src/Components/SideMenu/SideMenu.tsx | 34 +- .../src/Components/SideMenu/SideMenuItem.tsx | 324 +- .../Components/SideMenu/SideMenuSection.tsx | 20 +- CommonUI/src/Components/SideOver/SideOver.tsx | 212 +- .../SimpleLogViewer/SimpleLogViewer.tsx | 16 +- .../Components/StatusBubble/StatusBubble.tsx | 86 +- CommonUI/src/Components/Table/Table.tsx | 534 +- CommonUI/src/Components/Table/TableBody.tsx | 158 +- CommonUI/src/Components/Table/TableCard.tsx | 38 +- CommonUI/src/Components/Table/TableHeader.tsx | 226 +- CommonUI/src/Components/Table/TableRow.tsx | 592 +- CommonUI/src/Components/Table/Types/Column.ts | 34 +- .../src/Components/Table/Types/Columns.ts | 4 +- .../TableColumnListComponent.tsx | 132 +- CommonUI/src/Components/Tabs/Tab.tsx | 102 +- CommonUI/src/Components/Tabs/Tabs.tsx | 78 +- CommonUI/src/Components/Template/Template.tsx | 8 +- CommonUI/src/Components/TextArea/TextArea.tsx | 172 +- CommonUI/src/Components/Toast/Toast.tsx | 155 +- CommonUI/src/Components/Toggle/Toggle.tsx | 205 +- CommonUI/src/Components/Tooltip/Tooltip.tsx | 28 +- CommonUI/src/Components/TopAlert/TopAlert.tsx | 48 +- .../src/Components/TopSection/TopSection.tsx | 28 +- CommonUI/src/Components/Types/FieldType.ts | 68 +- .../src/Components/Workflow/ArgumentsForm.tsx | 347 +- .../src/Components/Workflow/Component.tsx | 620 +- .../Workflow/ComponentArgumentsViewer.tsx | 115 +- .../Workflow/ComponentPortViewer.tsx | 98 +- .../Workflow/ComponentReturnValueViewer.tsx | 113 +- .../Workflow/ComponentSettingsModal.tsx | 294 +- .../Workflow/ComponentValuePickerModal.tsx | 389 +- .../Components/Workflow/ComponentsModal.tsx | 393 +- .../Workflow/DocumentationViewer.tsx | 128 +- CommonUI/src/Components/Workflow/RunForm.tsx | 200 +- CommonUI/src/Components/Workflow/RunModal.tsx | 217 +- CommonUI/src/Components/Workflow/Utils.ts | 454 +- .../src/Components/Workflow/VariableModal.tsx | 90 +- CommonUI/src/Components/Workflow/Workflow.tsx | 924 +- .../Components/Workflow/WorkflowStatus.tsx | 50 +- CommonUI/src/Config.ts | 198 +- CommonUI/src/Container.tsx | 14 +- CommonUI/src/Tests/Components/404.test.tsx | 114 +- CommonUI/src/Tests/Components/Alert.test.tsx | 182 +- CommonUI/src/Tests/Components/Badge.test.tsx | 116 +- .../src/Tests/Components/BasicForm.test.tsx | 248 +- .../src/Tests/Components/Breadcrumbs.test.tsx | 152 +- CommonUI/src/Tests/Components/Button.test.tsx | 346 +- CommonUI/src/Tests/Components/Card.test.tsx | 178 +- .../src/Tests/Components/ColorViewer.test.tsx | 96 +- .../Tests/Components/ComponentsModal.test.tsx | 747 +- .../Tests/Components/ConfirmModal.test.tsx | 141 +- .../Components/DictionaryOfStrings.test.tsx | 225 +- .../src/Tests/Components/Dropdown.test.tsx | 404 +- .../Tests/Components/DuplicateModel.test.tsx | 616 +- .../Components/EmptyState/EmptyState.test.tsx | 84 +- .../Tests/Components/ErrorBoundary.test.tsx | 76 +- .../src/Tests/Components/FilePicker.test.tsx | 680 +- .../src/Tests/Components/HiddenText.test.tsx | 98 +- CommonUI/src/Tests/Components/Input.test.tsx | 422 +- CommonUI/src/Tests/Components/Item.test.tsx | 130 +- CommonUI/src/Tests/Components/List.test.tsx | 170 +- CommonUI/src/Tests/Components/Loader.test.tsx | 56 +- .../src/Tests/Components/MasterPage.test.tsx | 95 +- CommonUI/src/Tests/Components/Modal.test.tsx | 448 +- CommonUI/src/Tests/Components/NavBar.test.tsx | 78 +- .../Components/OrderedStatesList.test.tsx | 252 +- .../src/Tests/Components/Pagination.test.tsx | 304 +- CommonUI/src/Tests/Components/Pill.test.tsx | 98 +- CommonUI/src/Tests/Components/Probe.test.tsx | 106 +- .../src/Tests/Components/ProgressBar.test.tsx | 84 +- .../Tests/Components/RadioButtons.test.tsx | 182 +- .../Tests/Components/SideMenuItem.test.tsx | 256 +- .../src/Tests/Components/SideOver.test.tsx | 158 +- CommonUI/src/Tests/Components/Tabs.test.tsx | 126 +- .../Components/Template/Template.test.tsx | 28 +- .../src/Tests/Components/TextArea.test.tsx | 248 +- CommonUI/src/Tests/Components/Toast.test.tsx | 245 +- CommonUI/src/Tests/Components/Toggle.test.tsx | 274 +- .../src/Tests/Components/TopSection.test.tsx | 71 +- CommonUI/src/Tests/Index.tsx | 2 +- CommonUI/src/Types/AlignItem.ts | 6 +- CommonUI/src/Types/EntityFieldType.ts | 32 +- CommonUI/src/Types/FunctionTypes.ts | 2 +- CommonUI/src/Types/HtmlEvents.ts | 2 +- CommonUI/src/Types/RequiredEntityFields.ts | 8 +- CommonUI/src/Types/SelectEntityField.ts | 8 +- .../src/Types/UseComponentOutsideClick.ts | 60 +- CommonUI/src/Utils/API/API.ts | 248 +- CommonUI/src/Utils/API/ApiDocsAPI.ts | 12 +- CommonUI/src/Utils/API/DashboardAPI.ts | 12 +- .../src/Utils/API/DashboardFrontendAPI.ts | 10 +- CommonUI/src/Utils/API/HelmAPI.ts | 10 +- CommonUI/src/Utils/API/IdentityAPI.ts | 12 +- CommonUI/src/Utils/API/IntegrationAPI.ts | 10 +- CommonUI/src/Utils/Analytics.ts | 4 +- .../AnalyticsModelAPI/AnalyticsModelAPI.ts | 917 +- CommonUI/src/Utils/BaseDatabase/GroupBy.ts | 8 +- CommonUI/src/Utils/BaseDatabase/ListResult.ts | 16 +- CommonUI/src/Utils/BaseDatabase/Query.ts | 38 +- .../src/Utils/BaseDatabase/RequestOptions.ts | 8 +- CommonUI/src/Utils/BaseDatabase/Select.ts | 8 +- CommonUI/src/Utils/BaseDatabase/Sort.ts | 10 +- CommonUI/src/Utils/Clipboard.ts | 6 +- CommonUI/src/Utils/Cookie.ts | 120 +- CommonUI/src/Utils/Dropdown.ts | 100 +- CommonUI/src/Utils/File.ts | 16 +- CommonUI/src/Utils/GlobalEvents.ts | 48 +- CommonUI/src/Utils/History.ts | 2 +- CommonUI/src/Utils/JsonWebToken.ts | 20 +- CommonUI/src/Utils/LocalStorage.ts | 70 +- CommonUI/src/Utils/Logger.ts | 24 +- CommonUI/src/Utils/Login.ts | 54 +- CommonUI/src/Utils/ModelAPI/ModelAPI.ts | 821 +- CommonUI/src/Utils/Navigation.ts | 438 +- CommonUI/src/Utils/Permission.ts | 146 +- CommonUI/src/Utils/PricingPlan.ts | 222 +- CommonUI/src/Utils/Project.ts | 88 +- CommonUI/src/Utils/Realtime.ts | 205 +- CommonUI/src/Utils/StatusPage.ts | 210 +- CommonUI/src/Utils/Tailwind.ts | 6 +- CommonUI/src/Utils/Telemetry.ts | 88 +- CommonUI/src/Utils/User.ts | 251 +- Copilot/Config.ts | 24 +- Copilot/Index.ts | 88 +- Copilot/Utils/CodeRepository.ts | 759 +- Copilot/Utils/Init.ts | 82 +- Copilot/Utils/ServiceFileTypes.ts | 307 +- Copilot/Utils/ServiceRepository.ts | 64 +- Dashboard/Serve.ts | 52 +- Dashboard/index.d.ts | 8 +- Dashboard/src/App.tsx | 850 +- .../Components/CallSMS/CallSMSConfigTable.tsx | 819 +- .../Components/CustomSMTP/CustomSMTPTable.tsx | 645 +- .../Components/CustomSMTP/CustomSMTPView.tsx | 52 +- Dashboard/src/Components/Footer/Footer.tsx | 218 +- .../Form/Monitor/CriteriaFilter.tsx | 718 +- .../Form/Monitor/CriteriaFilters.tsx | 182 +- .../Form/Monitor/MonitorCriteria.tsx | 305 +- .../Monitor/MonitorCriteriaIncidentForm.tsx | 179 +- .../Monitor/MonitorCriteriaIncidentsForm.tsx | 120 +- .../Form/Monitor/MonitorCriteriaInstance.tsx | 711 +- .../Components/Form/Monitor/MonitorStep.tsx | 1010 +- .../Components/Form/Monitor/MonitorSteps.tsx | 532 +- Dashboard/src/Components/Header/Header.tsx | 406 +- Dashboard/src/Components/Header/Help.tsx | 102 +- Dashboard/src/Components/Header/Logo.tsx | 40 +- .../src/Components/Header/Notifications.tsx | 58 +- .../src/Components/Header/ProjectPicker.tsx | 622 +- Dashboard/src/Components/Header/SearchBox.tsx | 20 +- Dashboard/src/Components/Header/Upgrade.tsx | 286 +- .../src/Components/Header/UserProfile.tsx | 192 +- .../src/Components/Incident/ChangeState.tsx | 423 +- .../src/Components/Incident/Incident.tsx | 54 +- .../Components/Incident/IncidentsTable.tsx | 1311 +- Dashboard/src/Components/Label/Label.tsx | 30 +- Dashboard/src/Components/Label/Labels.tsx | 44 +- Dashboard/src/Components/Loader/Loader.tsx | 4 +- Dashboard/src/Components/Logs/LogsViewer.tsx | 298 +- .../src/Components/MasterPage/MasterPage.tsx | 202 +- .../src/Components/Metrics/MetricVIew.tsx | 168 +- .../Components/Monitor/DisabledWarning.tsx | 124 +- .../IncomingMonitorLink.tsx | 67 +- Dashboard/src/Components/Monitor/Monitor.tsx | 77 +- .../Monitor/MonitorCharts/MonitorChart.tsx | 659 +- .../MonitorCharts/MonitorChartTooltip.tsx | 145 +- .../Monitor/MonitorSteps/CriteriaFilter.tsx | 48 +- .../Monitor/MonitorSteps/CriteriaFilters.tsx | 78 +- .../Monitor/MonitorSteps/MonitorCriteria.tsx | 115 +- .../MonitorSteps/MonitorCriteriaIncident.tsx | 220 +- .../MonitorSteps/MonitorCriteriaIncidents.tsx | 46 +- .../MonitorSteps/MonitorCriteriaInstance.tsx | 238 +- .../Monitor/MonitorSteps/MonitorStep.tsx | 371 +- .../Monitor/MonitorSteps/MonitorSteps.tsx | 348 +- .../src/Components/Monitor/MonitorTable.tsx | 848 +- .../Monitor/MonitoringIntervalElement.tsx | 34 +- Dashboard/src/Components/Monitor/Monitors.tsx | 44 +- .../Monitor/ServerMonitor/Documentation.tsx | 48 +- .../SummaryView/CustomMonitorSummaryView.tsx | 262 +- .../IncomingRequestMonitorSummaryView.tsx | 170 +- .../Monitor/SummaryView/PingMonitorView.tsx | 105 +- .../Monitor/SummaryView/ProbeNowButton.tsx | 4 +- .../Monitor/SummaryView/ProbePicker.tsx | 91 +- .../SummaryView/SSLCertificateMonitorView.tsx | 319 +- .../Monitor/SummaryView/Screenshot.tsx | 40 +- .../Monitor/SummaryView/ScreenshotGroup.tsx | 72 +- .../Monitor/SummaryView/Summary.tsx | 146 +- .../Monitor/SummaryView/SummaryInfo.tsx | 239 +- .../SummaryView/SyntheticMonitorItemView.tsx | 80 +- .../SummaryView/SyntheticMonitorView.tsx | 89 +- .../SummaryView/WebsiteMonitorView.tsx | 236 +- .../Components/MonitorGroup/CurrentStatus.tsx | 116 +- .../MonitorGroup/MonitorGroupElement.tsx | 77 +- .../MonitorStatus/MonitorStatusElement.tsx | 10 +- .../MonitorStatus/MonitorStatusesElement.tsx | 46 +- Dashboard/src/Components/NavBar/NavBar.tsx | 399 +- .../Components/NotificationMethods/Call.tsx | 538 +- .../Components/NotificationMethods/Email.tsx | 532 +- .../NotificationMethod.tsx | 79 +- .../Components/NotificationMethods/SMS.tsx | 532 +- .../NotifyAfterMinutesDropdownOptions.ts | 50 +- .../OnCallDutySchedule/ScheduleElement.tsx | 64 +- .../OnCallDutySchedule/SchedulesElement.tsx | 50 +- .../EscalationRule/EscalationRule.tsx | 58 +- .../EscalationRule/OnCallScheduleView.tsx | 121 +- .../OnCallPolicy/EscalationRule/TeamView.tsx | 115 +- .../OnCallPolicy/EscalationRule/UserView.tsx | 119 +- .../ExecutionLogs/ExecutionLogsTable.tsx | 470 +- .../ExecutionLogsTimelineTable.tsx | 579 +- .../OnCallPolicy/OnCallPolicies.tsx | 52 +- .../Components/OnCallPolicy/OnCallPolicy.tsx | 54 +- .../OnCallScheduleLayer/Layer.tsx | 231 +- .../OnCallScheduleLayer/LayerBasicInfo.tsx | 78 +- .../OnCallScheduleLayer/LayerPreview.tsx | 34 +- .../LayerRestrictionTimes.tsx | 94 +- .../OnCallScheduleLayer/LayerRotation.tsx | 108 +- .../OnCallScheduleLayer/LayerStartTime.tsx | 62 +- .../OnCallScheduleLayer/LayerUser.tsx | 233 +- .../OnCallScheduleLayer/Layers.tsx | 585 +- .../OnCallScheduleLayer/LayersPreview.tsx | 234 +- .../RestrictionTimesFieldElement.tsx | 802 +- .../src/Components/Probe/ProbeStatus.tsx | 48 +- Dashboard/src/Components/Project/Project.tsx | 42 +- .../ProjectCallSMSConfig.tsx | 10 +- .../ProjectSMTPConfig/ProjectSMTPConfig.tsx | 10 +- .../ScheduledMaintenance/ChangeState.tsx | 415 +- .../ScheduledMaintenanceTable.tsx | 930 +- .../ServiceCatalog/ServiceElement.tsx | 94 +- .../src/Components/Span/SpanStatusElement.tsx | 65 +- Dashboard/src/Components/Span/SpanViewer.tsx | 1039 +- .../Components/StatusPage/StatusPageLabel.tsx | 64 +- .../StatusPage/StatusPagesLabel.tsx | 42 +- Dashboard/src/Components/Team/Team.tsx | 60 +- .../src/Components/Team/TeamsElement.tsx | 48 +- .../Components/Telemetry/Documentation.tsx | 238 +- .../TelemetryServiceElement.tsx | 94 +- Dashboard/src/Components/User/User.tsx | 221 +- Dashboard/src/Components/User/Users.tsx | 60 +- .../Components/Workflow/WorkflowElement.tsx | 62 +- Dashboard/src/Index.tsx | 20 +- .../AICopilot/CodeRepository/View/Delete.tsx | 48 +- .../AICopilot/CodeRepository/View/Index.tsx | 406 +- .../AICopilot/CodeRepository/View/Layout.tsx | 52 +- .../CodeRepository/View/Services.tsx | 339 +- .../CodeRepository/View/Settings.tsx | 38 +- .../CodeRepository/View/SideMenu.tsx | 132 +- Dashboard/src/Pages/AICopilot/Index.tsx | 410 +- Dashboard/src/Pages/Global/NewIncidents.tsx | 397 +- .../src/Pages/Global/ProjectInvitations.tsx | 310 +- .../src/Pages/Global/UserProfile/Index.tsx | 210 +- .../src/Pages/Global/UserProfile/Password.tsx | 193 +- .../src/Pages/Global/UserProfile/Picture.tsx | 192 +- .../src/Pages/Global/UserProfile/SideMenu.tsx | 76 +- Dashboard/src/Pages/Home/Home.tsx | 206 +- .../src/Pages/Home/NotOperationalMonitors.tsx | 102 +- .../Home/OngoingScheduledMaintenance.tsx | 96 +- Dashboard/src/Pages/Home/SideMenu.tsx | 222 +- Dashboard/src/Pages/Incidents/Incidents.tsx | 28 +- Dashboard/src/Pages/Incidents/Layout.tsx | 38 +- Dashboard/src/Pages/Incidents/SideMenu.tsx | 92 +- Dashboard/src/Pages/Incidents/Unresolved.tsx | 48 +- .../src/Pages/Incidents/View/CustomFields.tsx | 42 +- Dashboard/src/Pages/Incidents/View/Delete.tsx | 49 +- Dashboard/src/Pages/Incidents/View/Index.tsx | 1177 +- .../src/Pages/Incidents/View/InternalNote.tsx | 617 +- Dashboard/src/Pages/Incidents/View/Layout.tsx | 52 +- Dashboard/src/Pages/Incidents/View/Owners.tsx | 433 +- .../src/Pages/Incidents/View/PublicNote.tsx | 742 +- .../src/Pages/Incidents/View/SideMenu.tsx | 188 +- .../Pages/Incidents/View/StateTimeline.tsx | 550 +- Dashboard/src/Pages/Init/Init.tsx | 62 +- Dashboard/src/Pages/Logout/Logout.tsx | 86 +- .../src/Pages/Monitor/DisabledMonitors.tsx | 40 +- Dashboard/src/Pages/Monitor/Layout.tsx | 44 +- Dashboard/src/Pages/Monitor/Monitors.tsx | 54 +- .../Pages/Monitor/NotOperationalMonitors.tsx | 44 +- Dashboard/src/Pages/Monitor/SideMenu.tsx | 152 +- Dashboard/src/Pages/Monitor/View/Criteria.tsx | 323 +- .../src/Pages/Monitor/View/CustomFields.tsx | 50 +- Dashboard/src/Pages/Monitor/View/Delete.tsx | 57 +- .../src/Pages/Monitor/View/Documentation.tsx | 163 +- .../src/Pages/Monitor/View/Incidents.tsx | 48 +- Dashboard/src/Pages/Monitor/View/Index.tsx | 1050 +- Dashboard/src/Pages/Monitor/View/Interval.tsx | 303 +- Dashboard/src/Pages/Monitor/View/Layout.tsx | 135 +- Dashboard/src/Pages/Monitor/View/Owners.tsx | 429 +- Dashboard/src/Pages/Monitor/View/Probes.tsx | 542 +- Dashboard/src/Pages/Monitor/View/Settings.tsx | 447 +- Dashboard/src/Pages/Monitor/View/SideMenu.tsx | 324 +- .../src/Pages/Monitor/View/StatusTimeline.tsx | 514 +- .../src/Pages/MonitorGroup/MonitorGroups.tsx | 322 +- .../src/Pages/MonitorGroup/View/Delete.tsx | 54 +- .../src/Pages/MonitorGroup/View/Incidents.tsx | 142 +- .../src/Pages/MonitorGroup/View/Index.tsx | 574 +- .../src/Pages/MonitorGroup/View/Layout.tsx | 52 +- .../src/Pages/MonitorGroup/View/Monitors.tsx | 417 +- .../src/Pages/MonitorGroup/View/Owners.tsx | 435 +- .../src/Pages/MonitorGroup/View/SideMenu.tsx | 152 +- Dashboard/src/Pages/OnCallDuty/Layout.tsx | 56 +- .../OnCallDuty/OnCallDutyExecutionLogView.tsx | 16 +- .../OnCallDuty/OnCallDutyExecutionLogs.tsx | 10 +- .../Pages/OnCallDuty/OnCallDutyPolicies.tsx | 289 +- .../OnCallDutyPolicy/CustomFields.tsx | 46 +- .../OnCallDuty/OnCallDutyPolicy/Delete.tsx | 46 +- .../OnCallDutyPolicy/Escalation.tsx | 658 +- .../OnCallDutyPolicy/ExecutionLogView.tsx | 44 +- .../OnCallDutyPolicy/ExecutionLogs.tsx | 34 +- .../OnCallDuty/OnCallDutyPolicy/Index.tsx | 239 +- .../OnCallDuty/OnCallDutyPolicy/Layout.tsx | 52 +- .../OnCallDuty/OnCallDutyPolicy/SideMenu.tsx | 184 +- .../OnCallDuty/OnCallDutySchedule/Delete.tsx | 46 +- .../OnCallDuty/OnCallDutySchedule/Index.tsx | 239 +- .../OnCallDuty/OnCallDutySchedule/Layers.tsx | 48 +- .../OnCallDuty/OnCallDutySchedule/Layout.tsx | 52 +- .../OnCallDutySchedule/SideMenu.tsx | 106 +- .../Pages/OnCallDuty/OnCallDutySchedules.tsx | 275 +- Dashboard/src/Pages/OnCallDuty/SideMenu.tsx | 122 +- Dashboard/src/Pages/Onboarding/SSO.tsx | 136 +- Dashboard/src/Pages/Onboarding/Welcome.tsx | 72 +- Dashboard/src/Pages/PageComponentProps.ts | 10 +- .../src/Pages/PageNotFound/PageNotFound.tsx | 30 +- .../ScheduledMaintenanceEvents/Layout.tsx | 38 +- .../ScheduledMaintenanceEvents/Ongoing.tsx | 46 +- .../ScheduledMaintenanceEvents.tsx | 34 +- .../ScheduledMaintenanceEvents/SideMenu.tsx | 96 +- .../View/CustomFields.tsx | 46 +- .../View/Delete.tsx | 48 +- .../ScheduledMaintenanceEvents/View/Index.tsx | 907 +- .../View/InternalNote.tsx | 644 +- .../View/Layout.tsx | 52 +- .../View/Owners.tsx | 441 +- .../View/PublicNote.tsx | 768 +- .../View/SideMenu.tsx | 203 +- .../View/StateTimeline.tsx | 366 +- .../Pages/ServiceCatalog/ServiceCatalog.tsx | 348 +- .../src/Pages/ServiceCatalog/View/Delete.tsx | 46 +- .../src/Pages/ServiceCatalog/View/Index.tsx | 284 +- .../src/Pages/ServiceCatalog/View/Layout.tsx | 52 +- .../src/Pages/ServiceCatalog/View/Owners.tsx | 433 +- .../Pages/ServiceCatalog/View/Settings.tsx | 100 +- .../Pages/ServiceCatalog/View/SideMenu.tsx | 126 +- Dashboard/src/Pages/Settings/APIKeyView.tsx | 698 +- Dashboard/src/Pages/Settings/APIKeys.tsx | 230 +- .../Settings/Base/CustomFieldsPageBase.tsx | 267 +- Dashboard/src/Pages/Settings/Billing.tsx | 1134 +- .../Settings/BillingPaymentMethodForm.tsx | 106 +- Dashboard/src/Pages/Settings/CallLog.tsx | 429 +- Dashboard/src/Pages/Settings/DangerZone.tsx | 118 +- Dashboard/src/Pages/Settings/Domains.tsx | 408 +- Dashboard/src/Pages/Settings/EmailLog.tsx | 415 +- Dashboard/src/Pages/Settings/FeatureFlags.tsx | 112 +- .../Pages/Settings/IncidentCustomFields.tsx | 24 +- .../Settings/IncidentNoteTemplateView.tsx | 261 +- .../Pages/Settings/IncidentNoteTemplates.tsx | 232 +- .../src/Pages/Settings/IncidentSeverity.tsx | 229 +- .../src/Pages/Settings/IncidentState.tsx | 275 +- .../src/Pages/Settings/IncidentTemplates.tsx | 541 +- .../Pages/Settings/IncidentTemplatesView.tsx | 1005 +- Dashboard/src/Pages/Settings/Invoices.tsx | 480 +- Dashboard/src/Pages/Settings/Labels.tsx | 221 +- Dashboard/src/Pages/Settings/Layout.tsx | 38 +- .../Pages/Settings/MonitorCustomFields.tsx | 24 +- .../src/Pages/Settings/MonitorSecrets.tsx | 460 +- .../src/Pages/Settings/MonitorStatus.tsx | 285 +- .../Pages/Settings/NotificationSettings.tsx | 721 +- .../Settings/OnCallDutyPolicyCustomFields.tsx | 24 +- Dashboard/src/Pages/Settings/Probes.tsx | 600 +- .../src/Pages/Settings/ProjectSettings.tsx | 114 +- Dashboard/src/Pages/Settings/SSO.tsx | 738 +- .../ScheduledMaintenanceCusomFields.tsx | 24 +- .../ScheduledMaintenanceNoteTemplateView.tsx | 262 +- .../ScheduledMaintenanceNoteTemplates.tsx | 233 +- .../Settings/ScheduledMaintenanceState.tsx | 292 +- Dashboard/src/Pages/Settings/SideMenu.tsx | 645 +- Dashboard/src/Pages/Settings/SmsLog.tsx | 410 +- .../Pages/Settings/StatusPageCustomFields.tsx | 24 +- Dashboard/src/Pages/Settings/TeamView.tsx | 832 +- Dashboard/src/Pages/Settings/Teams.tsx | 177 +- Dashboard/src/Pages/Settings/UsageHistory.tsx | 344 +- .../src/Pages/StatusPages/StatusPages.tsx | 294 +- .../StatusPages/View/AdvancedOptions.tsx | 107 +- .../Pages/StatusPages/View/Announcements.tsx | 356 +- .../View/AuthenticationSettings.tsx | 99 +- .../src/Pages/StatusPages/View/Branding.tsx | 212 +- .../Pages/StatusPages/View/CustomFields.tsx | 46 +- .../Pages/StatusPages/View/CustomHtmlCss.tsx | 330 +- .../src/Pages/StatusPages/View/Delete.tsx | 46 +- .../src/Pages/StatusPages/View/Domains.tsx | 746 +- .../StatusPages/View/EmailSubscribers.tsx | 539 +- .../src/Pages/StatusPages/View/Embedded.tsx | 46 +- .../Pages/StatusPages/View/FooterStyle.tsx | 282 +- .../src/Pages/StatusPages/View/Groups.tsx | 224 +- .../Pages/StatusPages/View/HeaderStyle.tsx | 328 +- .../src/Pages/StatusPages/View/Index.tsx | 240 +- .../src/Pages/StatusPages/View/Layout.tsx | 52 +- .../Pages/StatusPages/View/NavBarStyle.tsx | 16 +- .../StatusPages/View/OverviewPageBranding.tsx | 475 +- .../src/Pages/StatusPages/View/Owners.tsx | 437 +- .../Pages/StatusPages/View/PrivateUser.tsx | 177 +- .../src/Pages/StatusPages/View/Resources.tsx | 806 +- .../Pages/StatusPages/View/SMSSubscribers.tsx | 541 +- Dashboard/src/Pages/StatusPages/View/SSO.tsx | 704 +- .../src/Pages/StatusPages/View/SideMenu.tsx | 500 +- .../View/StatusPagePreviewLink.tsx | 58 +- .../StatusPages/View/StatusPageSettings.tsx | 373 +- .../StatusPages/View/SubscriberSettings.tsx | 445 +- .../StatusPages/View/WebhookSubscribers.tsx | 187 +- Dashboard/src/Pages/Telemetry/Services.tsx | 337 +- .../Services/View/Dashboard/Index.tsx | 18 +- .../Pages/Telemetry/Services/View/Delete.tsx | 46 +- .../Telemetry/Services/View/Documentation.tsx | 18 +- .../Pages/Telemetry/Services/View/Index.tsx | 252 +- .../Pages/Telemetry/Services/View/Layout.tsx | 52 +- .../Telemetry/Services/View/Logs/Index.tsx | 34 +- .../Telemetry/Services/View/Metrics/Index.tsx | 146 +- .../Services/View/Metrics/View/Index.tsx | 18 +- .../Telemetry/Services/View/Settings.tsx | 196 +- .../Telemetry/Services/View/SideMenu.tsx | 218 +- .../Telemetry/Services/View/Traces/Index.tsx | 275 +- .../Services/View/Traces/View/Index.tsx | 1004 +- Dashboard/src/Pages/Telemetry/SideMenu.tsx | 46 +- Dashboard/src/Pages/UserSettings/Layout.tsx | 38 +- .../UserSettings/NotificationMethods.tsx | 26 +- .../UserSettings/NotificationSettings.tsx | 369 +- .../src/Pages/UserSettings/OnCallLogs.tsx | 511 +- .../Pages/UserSettings/OnCallLogsTimeline.tsx | 387 +- .../src/Pages/UserSettings/OnCallRules.tsx | 684 +- Dashboard/src/Pages/UserSettings/SideMenu.tsx | 142 +- Dashboard/src/Pages/Workflow/Layout.tsx | 38 +- Dashboard/src/Pages/Workflow/Logs.tsx | 366 +- Dashboard/src/Pages/Workflow/SideMenu.tsx | 76 +- Dashboard/src/Pages/Workflow/Variable.tsx | 276 +- Dashboard/src/Pages/Workflow/View/Builder.tsx | 680 +- Dashboard/src/Pages/Workflow/View/Delete.tsx | 54 +- Dashboard/src/Pages/Workflow/View/Index.tsx | 262 +- Dashboard/src/Pages/Workflow/View/Layout.tsx | 52 +- Dashboard/src/Pages/Workflow/View/Logs.tsx | 360 +- .../src/Pages/Workflow/View/Settings.tsx | 86 +- .../src/Pages/Workflow/View/SideMenu.tsx | 160 +- .../src/Pages/Workflow/View/Variable.tsx | 292 +- Dashboard/src/Pages/Workflow/Workflows.tsx | 422 +- Dashboard/src/Routes/AICopilotRoutes.tsx | 229 +- Dashboard/src/Routes/IncidentsRoutes.tsx | 340 +- Dashboard/src/Routes/InitRoutes.tsx | 76 +- Dashboard/src/Routes/MonitorGroupRoutes.tsx | 242 +- Dashboard/src/Routes/MonitorsRoutes.tsx | 504 +- Dashboard/src/Routes/OnCallDutyRoutes.tsx | 570 +- .../Routes/ScheduleMaintenaceEventsRoutes.tsx | 404 +- Dashboard/src/Routes/ServiceCatalogRoutes.tsx | 218 +- Dashboard/src/Routes/SettingsRoutes.tsx | 1179 +- Dashboard/src/Routes/StatusPagesRoutes.tsx | 903 +- Dashboard/src/Routes/TelemetryRoutes.tsx | 454 +- Dashboard/src/Routes/UserSettingsRoutes.tsx | 260 +- Dashboard/src/Routes/WorkflowRoutes.tsx | 326 +- Dashboard/src/Types/RoutesProps.tsx | 8 +- Dashboard/src/Utils/BillingProvider.ts | 14 +- .../Utils/Breadcrumbs/AICopilotBreadcrumbs.ts | 54 +- Dashboard/src/Utils/Breadcrumbs/Helper.ts | 42 +- .../Utils/Breadcrumbs/IncidentBreadcrumbs.ts | 114 +- .../Utils/Breadcrumbs/MonitorBreadcrumbs.ts | 157 +- .../Breadcrumbs/MonitorGroupBreadcrumbs.ts | 74 +- .../Breadcrumbs/OnCallDutyBreadcrumbs.ts | 176 +- .../ScheduledMaintenanceBreadcrumbs.ts | 149 +- .../Breadcrumbs/ServiceCatalogBreadcrumbs.ts | 62 +- .../Utils/Breadcrumbs/SettingsBreadcrumbs.ts | 348 +- .../Breadcrumbs/StatusPagesBreadcrumbs.ts | 278 +- .../Utils/Breadcrumbs/TelemetryBreadcrumbs.ts | 114 +- .../Breadcrumbs/UserSettingsBreadcrumbs.ts | 52 +- .../Utils/Breadcrumbs/WorkflowsBreadcrumbs.ts | 112 +- Dashboard/src/Utils/Breadcrumbs/index.ts | 22 +- Dashboard/src/Utils/EventName.tsx | 6 +- .../src/Utils/Form/Monitor/CriteriaFilter.ts | 1035 +- Dashboard/src/Utils/IncidentState.ts | 70 +- .../Utils/MonitorIntervalDropdownOptions.ts | 66 +- Dashboard/src/Utils/MonitorType.ts | 28 +- Dashboard/src/Utils/Navigation.ts | 16 +- Dashboard/src/Utils/PageMap.ts | 414 +- Dashboard/src/Utils/Probe.ts | 78 +- Dashboard/src/Utils/ProjectUser.ts | 68 +- Dashboard/src/Utils/RouteMap.ts | 2405 ++-- Dashboard/src/Utils/RouteParams.ts | 6 +- Dashboard/src/Utils/SpanUtil.ts | 285 +- Dashboard/webpack.config.js | 148 +- E2E/Config.ts | 24 +- E2E/Tests/Accounts/Login.spec.ts | 88 +- E2E/Tests/Accounts/Register.spec.ts | 86 +- E2E/Tests/App/StatusCheck.spec.ts | 60 +- E2E/Tests/Home/Landing.spec.ts | 64 +- E2E/Tests/Home/Navigation.spec.ts | 126 +- E2E/Tests/Home/SignIn.spec.ts | 26 +- E2E/Tests/Home/SignUp.spec.ts | 34 +- E2E/Tests/Ingestor/StatusCheck.spec.ts | 62 +- E2E/Tests/StatusPage/Basic.spec.ts | 36 +- E2E/playwright.config.ts | 138 +- Examples/fluentd/index.js | 24 +- Haraka/plugins/email_parser.js | 9 +- Ingestor/API/Alive.ts | 42 +- Ingestor/API/FluentIngest.ts | 135 +- Ingestor/API/IncomingRequest.ts | 188 +- Ingestor/API/Monitor.ts | 596 +- Ingestor/API/OTelIngest.ts | 1223 +- Ingestor/API/Probe.ts | 481 +- Ingestor/API/Register.ts | 156 +- Ingestor/API/ServerMonitor.ts | 198 +- Ingestor/Index.ts | 126 +- Ingestor/Middleware/ProbeAuthorization.ts | 130 +- Ingestor/Middleware/TelemetryIngest.ts | 154 +- Ingestor/Service/OTelIngest.ts | 195 +- Ingestor/Types/Request.ts | 6 +- Ingestor/Utils/Monitor.ts | 312 +- IsolatedVM/API/VM.ts | 116 +- IsolatedVM/Index.ts | 60 +- Model/AnalyticsModels/Index.ts | 18 +- Model/AnalyticsModels/Log.ts | 692 +- Model/AnalyticsModels/Metric.ts | 1286 +- .../AnalyticsModels/MonitorMetricsByMinute.ts | 319 +- .../NestedModels/KeyValueNestedModel.ts | 96 +- Model/AnalyticsModels/Span.ts | 1184 +- Model/Models/AcmeCertificate.ts | 254 +- Model/Models/AcmeChallenge.ts | 210 +- Model/Models/ApiKey.ts | 726 +- Model/Models/ApiKeyPermission.ts | 822 +- Model/Models/BillingInvoice.ts | 630 +- Model/Models/BillingPaymentMethod.ts | 624 +- Model/Models/CallLog.ts | 566 +- Model/Models/CodeRepository.ts | 1156 +- Model/Models/CopilotEvent.ts | 965 +- Model/Models/DataMigration.ts | 336 +- Model/Models/Domain.ts | 672 +- Model/Models/EmailLog.ts | 633 +- Model/Models/EmailVerificationToken.ts | 294 +- Model/Models/File.ts | 38 +- Model/Models/GlobalConfig.ts | 680 +- Model/Models/GreenlockCertificate.ts | 208 +- Model/Models/GreenlockChallenge.ts | 210 +- Model/Models/Incident.ts | 1953 ++- Model/Models/IncidentCustomField.ts | 642 +- Model/Models/IncidentInternalNote.ts | 702 +- Model/Models/IncidentNoteTemplate.ts | 650 +- Model/Models/IncidentOwnerTeam.ts | 792 +- Model/Models/IncidentOwnerUser.ts | 790 +- Model/Models/IncidentPublicNote.ts | 859 +- Model/Models/IncidentSeverity.ts | 806 +- Model/Models/IncidentState.ts | 1000 +- Model/Models/IncidentStateTimeline.ts | 1105 +- Model/Models/IncidentTemplate.ts | 1432 ++- Model/Models/IncidentTemplateOwnerTeam.ts | 794 +- Model/Models/IncidentTemplateOwnerUser.ts | 816 +- Model/Models/Index.ts | 428 +- Model/Models/Label.ts | 718 +- Model/Models/Monitor.ts | 1777 ++- Model/Models/MonitorCustomField.ts | 642 +- Model/Models/MonitorGroup.ts | 774 +- Model/Models/MonitorGroupOwnerTeam.ts | 806 +- Model/Models/MonitorGroupOwnerUser.ts | 804 +- Model/Models/MonitorGroupResource.ts | 735 +- Model/Models/MonitorOwnerTeam.ts | 804 +- Model/Models/MonitorOwnerUser.ts | 802 +- Model/Models/MonitorProbe.ts | 908 +- Model/Models/MonitorSecret.ts | 734 +- Model/Models/MonitorStatus.ts | 936 +- Model/Models/MonitorStatusTimeline.ts | 1006 +- Model/Models/OnCallDutyPolicy.ts | 954 +- Model/Models/OnCallDutyPolicyCustomField.ts | 642 +- .../Models/OnCallDutyPolicyEscalationRule.ts | 838 +- .../OnCallDutyPolicyEscalationRuleSchedule.ts | 838 +- .../OnCallDutyPolicyEscalationRuleTeam.ts | 835 +- .../OnCallDutyPolicyEscalationRuleUser.ts | 833 +- Model/Models/OnCallDutyPolicyExecutionLog.ts | 1260 +- .../OnCallDutyPolicyExecutionLogTimeline.ts | 1383 +- Model/Models/OnCallDutyPolicySchedule.ts | 764 +- Model/Models/OnCallDutyPolicyScheduleLayer.ts | 1064 +- .../OnCallDutyPolicyScheduleLayerUser.ts | 945 +- Model/Models/Probe.ts | 894 +- Model/Models/Project.ts | 2123 ++-- Model/Models/ProjectCallSMSConfig.ts | 816 +- Model/Models/ProjectSmtpConfig.ts | 992 +- Model/Models/ProjectSso.ts | 1100 +- Model/Models/PromoCode.ts | 781 +- Model/Models/Reseller.ts | 517 +- Model/Models/ResellerPlan.ts | 573 +- Model/Models/ScheduledMaintenance.ts | 1693 ++- .../Models/ScheduledMaintenanceCustomField.ts | 642 +- .../ScheduledMaintenanceInternalNote.ts | 704 +- .../ScheduledMaintenanceNoteTemplate.ts | 652 +- Model/Models/ScheduledMaintenanceOwnerTeam.ts | 795 +- Model/Models/ScheduledMaintenanceOwnerUser.ts | 793 +- .../Models/ScheduledMaintenancePublicNote.ts | 861 +- Model/Models/ScheduledMaintenanceState.ts | 1064 +- .../ScheduledMaintenanceStateTimeline.ts | 1005 +- Model/Models/ServiceCatalog.ts | 942 +- Model/Models/ServiceCatalogOwnerTeam.ts | 748 +- Model/Models/ServiceCatalogOwnerUser.ts | 746 +- Model/Models/ServiceRepository.ts | 1036 +- Model/Models/ShortLink.ts | 214 +- Model/Models/SmsLog.ts | 566 +- Model/Models/StatusPage.ts | 3184 +++-- Model/Models/StatusPageAnnouncement.ts | 970 +- Model/Models/StatusPageCustomField.ts | 642 +- Model/Models/StatusPageDomain.ts | 1031 +- Model/Models/StatusPageFooterLink.ts | 797 +- Model/Models/StatusPageGroup.ts | 909 +- Model/Models/StatusPageHeaderLink.ts | 797 +- .../StatusPageHistoryChartBarColorRule.ts | 791 +- Model/Models/StatusPageOwnerTeam.ts | 804 +- Model/Models/StatusPageOwnerUser.ts | 802 +- Model/Models/StatusPagePrivateUser.ts | 873 +- Model/Models/StatusPageResource.ts | 1554 ++- Model/Models/StatusPageSso.ts | 1095 +- Model/Models/StatusPageSubscriber.ts | 1106 +- Model/Models/Team.ts | 828 +- Model/Models/TeamMember.ts | 794 +- Model/Models/TeamPermission.ts | 833 +- Model/Models/TelemetryService.ts | 995 +- Model/Models/TelemetryUsageBilling.ts | 783 +- Model/Models/User.ts | 1384 +- Model/Models/UserCall.ts | 528 +- Model/Models/UserEmail.ts | 528 +- Model/Models/UserNotificationRule.ts | 857 +- Model/Models/UserNotificationSetting.ts | 516 +- Model/Models/UserOnCallLog.ts | 1318 +- Model/Models/UserOnCallLogTimeline.ts | 1611 ++- Model/Models/UserSMS.ts | 528 +- Model/Models/Workflow.ts | 996 +- Model/Models/WorkflowLog.ts | 568 +- Model/Models/WorkflowVariable.ts | 812 +- Model/Tests/File.test.ts | 16 +- Nginx/Index.ts | 82 +- Nginx/Jobs/AcmeWriteCertificates.ts | 110 +- Nginx/Jobs/FetchCertificates.ts | 181 +- Nginx/Utils/SelfSignedSSL.ts | 12 +- Probe/Config.ts | 80 +- Probe/Index.ts | 128 +- Probe/Jobs/Alive.ts | 66 +- Probe/Jobs/Monitor/FetchList.ts | 196 +- Probe/Services/Register.ts | 266 +- Probe/Tests/Utils/PingMonitor.test.skip.ts | 116 +- Probe/Utils/Monitors/Monitor.ts | 689 +- .../Utils/Monitors/MonitorTypes/ApiMonitor.ts | 374 +- .../MonitorTypes/CustomCodeMonitor.ts | 128 +- .../Monitors/MonitorTypes/PingMonitor.ts | 265 +- .../Monitors/MonitorTypes/PortMonitor.ts | 373 +- .../Utils/Monitors/MonitorTypes/SslMonitor.ts | 453 +- .../Monitors/MonitorTypes/SyntheticMonitor.ts | 433 +- .../Monitors/MonitorTypes/WebsiteMonitor.ts | 361 +- Probe/Utils/OnlineCheck.ts | 136 +- Probe/Utils/Probe.ts | 23 +- Probe/Utils/ProbeAPIRequest.ts | 18 +- Scripts/Install/MergeEnvTemplate.js | 62 +- Scripts/Install/ReplaceValueInConfig.js | 76 +- .../PaymentProvider/CouponCodeGenerator.ts | 32 +- StatusPage/Serve.ts | 52 +- StatusPage/index.d.ts | 8 +- StatusPage/src/App.tsx | 1117 +- StatusPage/src/Components/Banner/Banner.tsx | 40 +- StatusPage/src/Components/Footer/Footer.tsx | 48 +- StatusPage/src/Components/Header/Header.tsx | 138 +- StatusPage/src/Components/Logo/Logo.tsx | 34 +- .../src/Components/MasterPage/MasterPage.tsx | 663 +- .../Components/Monitor/MonitorOverview.tsx | 265 +- StatusPage/src/Components/NavBar/NavBar.tsx | 192 +- StatusPage/src/Components/Page/Page.tsx | 17 +- StatusPage/src/Components/Section/Section.tsx | 18 +- StatusPage/src/Index.tsx | 20 +- .../src/Pages/Accounts/ForgotPassword.tsx | 283 +- StatusPage/src/Pages/Accounts/Login.tsx | 421 +- StatusPage/src/Pages/Accounts/Logout.tsx | 76 +- .../src/Pages/Accounts/ResetPassword.tsx | 331 +- StatusPage/src/Pages/Accounts/SSO.tsx | 241 +- StatusPage/src/Pages/Announcement/Detail.tsx | 367 +- StatusPage/src/Pages/Announcement/List.tsx | 421 +- StatusPage/src/Pages/Incidents/Detail.tsx | 781 +- StatusPage/src/Pages/Incidents/List.tsx | 534 +- .../src/Pages/NotFound/PageNotFound.tsx | 32 +- StatusPage/src/Pages/Overview/Overview.tsx | 1684 ++- StatusPage/src/Pages/PageComponentProps.ts | 6 +- .../src/Pages/ScheduledEvent/Detail.tsx | 883 +- StatusPage/src/Pages/ScheduledEvent/List.tsx | 690 +- .../src/Pages/Subscribe/EmailSubscribe.tsx | 410 +- StatusPage/src/Pages/Subscribe/SideMenu.tsx | 96 +- .../src/Pages/Subscribe/SmsSubscribe.tsx | 408 +- .../src/Pages/Subscribe/SubscribePageUtils.ts | 8 +- .../Pages/Subscribe/UpdateSubscription.tsx | 429 +- StatusPage/src/Types/IncidentGroup.ts | 30 +- .../src/Types/ScheduledMaintenanceGroup.ts | 26 +- StatusPage/src/Utils/API.ts | 40 +- StatusPage/src/Utils/ApiPaths.ts | 18 +- StatusPage/src/Utils/Config.ts | 30 +- StatusPage/src/Utils/Login.ts | 50 +- StatusPage/src/Utils/PageMap.ts | 74 +- StatusPage/src/Utils/RouteMap.ts | 182 +- StatusPage/src/Utils/RouteParams.ts | 4 +- StatusPage/src/Utils/StatusPage.ts | 127 +- StatusPage/src/Utils/User.ts | 156 +- StatusPage/webpack.config.js | 147 +- TestServer/API/Main.ts | 138 +- TestServer/API/Settings.ts | 112 +- TestServer/Index.ts | 62 +- babel.config.ts | 8 +- eslint.config.js | 31 +- 1729 files changed, 217499 insertions(+), 225678 deletions(-) delete mode 100644 .prettierrc.json diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 38e0e65fe5..0000000000 --- a/.prettierrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "trailingComma": "es5", - "tabWidth": 4, - "semi": true, - "singleQuote": true, - "bracketSpacing": true, - "arrowParens": "avoid", - "importOrderSeparation": true, - "importOrderSortSpecifiers": true, - "importOrderParserPlugins": ["typescript", "decorators", "dynamicImport", "jsx"] -} \ No newline at end of file diff --git a/Accounts/Serve.ts b/Accounts/Serve.ts index d95bb7d1e6..55eb05660b 100755 --- a/Accounts/Serve.ts +++ b/Accounts/Serve.ts @@ -1,37 +1,37 @@ -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import App from 'CommonServer/Utils/StartServer'; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import App from "CommonServer/Utils/StartServer"; -export const APP_NAME: string = 'accounts'; +export const APP_NAME: string = "accounts"; const app: ExpressApplication = Express.getExpressApp(); const init: PromiseVoidFunction = async (): Promise => { - try { - // init the app - await App.init({ - appName: APP_NAME, - port: undefined, - isFrontendApp: true, - statusOptions: { - liveCheck: async () => {}, - readyCheck: async () => {}, - }, - }); - // add default routes - await App.addDefaultRoutes(); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } + try { + // init the app + await App.init({ + appName: APP_NAME, + port: undefined, + isFrontendApp: true, + statusOptions: { + liveCheck: async () => {}, + readyCheck: async () => {}, + }, + }); + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); export default app; diff --git a/Accounts/index.d.ts b/Accounts/index.d.ts index cf14fde98c..a0ad0eb5c4 100644 --- a/Accounts/index.d.ts +++ b/Accounts/index.d.ts @@ -1,4 +1,4 @@ -declare module '*.png'; -declare module '*.svg'; -declare module '*.jpg'; -declare module '*.gif'; +declare module "*.png"; +declare module "*.svg"; +declare module "*.jpg"; +declare module "*.gif"; diff --git a/Accounts/src/App.tsx b/Accounts/src/App.tsx index 1286c04747..25b0b0ead3 100644 --- a/Accounts/src/App.tsx +++ b/Accounts/src/App.tsx @@ -1,47 +1,44 @@ -import ForgotPasswordPage from './Pages/ForgotPassword'; -import LoginPage from './Pages/Login'; -import NotFound from './Pages/NotFound'; -import RegisterPage from './Pages/Register'; -import ResetPasswordPage from './Pages/ResetPassword'; -import VerifyEmail from './Pages/VerifyEmail'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { ReactElement } from 'react'; +import ForgotPasswordPage from "./Pages/ForgotPassword"; +import LoginPage from "./Pages/Login"; +import NotFound from "./Pages/NotFound"; +import RegisterPage from "./Pages/Register"; +import ResetPasswordPage from "./Pages/ResetPassword"; +import VerifyEmail from "./Pages/VerifyEmail"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { ReactElement } from "react"; import { - Route, - Routes, - useLocation, - useNavigate, - useParams, -} from 'react-router-dom'; + Route, + Routes, + useLocation, + useNavigate, + useParams, +} from "react-router-dom"; function App(): ReactElement { - Navigation.setNavigateHook(useNavigate()); - Navigation.setLocation(useLocation()); - Navigation.setParams(useParams()); + Navigation.setNavigateHook(useNavigate()); + Navigation.setLocation(useLocation()); + Navigation.setParams(useParams()); - return ( -
- - } /> - } /> - } - /> - } - /> - } /> - } - /> - {/* 👇️ only match this when no other routes match */} - } /> - -
- ); + return ( +
+ + } /> + } /> + } + /> + } + /> + } /> + } /> + {/* 👇️ only match this when no other routes match */} + } /> + +
+ ); } export default App; diff --git a/Accounts/src/Footer.tsx b/Accounts/src/Footer.tsx index d415015c42..10e6814522 100644 --- a/Accounts/src/Footer.tsx +++ b/Accounts/src/Footer.tsx @@ -1,20 +1,20 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; +import React from "react"; +import { Link } from "react-router-dom"; const Footer: () => JSX.Element = () => { - return ( -
-

- © OneUptime -

-

- Contact -

-

- Privacy & terms -

-
- ); + return ( +
+

+ © OneUptime +

+

+ Contact +

+

+ Privacy & terms +

+
+ ); }; export default Footer; diff --git a/Accounts/src/Index.tsx b/Accounts/src/Index.tsx index 5bf03b73ff..9ccfacf65f 100644 --- a/Accounts/src/Index.tsx +++ b/Accounts/src/Index.tsx @@ -1,19 +1,19 @@ -import App from './App'; -import Telemetry from 'CommonUI/src/Utils/Telemetry'; -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { BrowserRouter } from 'react-router-dom'; +import App from "./App"; +import Telemetry from "CommonUI/src/Utils/Telemetry"; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; Telemetry.init({ - serviceName: 'Accounts', + serviceName: "Accounts", }); const root: any = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById("root") as HTMLElement, ); root.render( - - - + + + , ); diff --git a/Accounts/src/Pages/ForgotPassword.tsx b/Accounts/src/Pages/ForgotPassword.tsx index 692aab7620..f6a91c46e0 100644 --- a/Accounts/src/Pages/ForgotPassword.tsx +++ b/Accounts/src/Pages/ForgotPassword.tsx @@ -1,99 +1,99 @@ -import { FORGOT_PASSWORD_API_URL } from '../Utils/ApiPaths'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Link from 'CommonUI/src/Components/Link/Link'; -import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg'; -import User from 'Model/Models/User'; -import React, { useState } from 'react'; +import { FORGOT_PASSWORD_API_URL } from "../Utils/ApiPaths"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Link from "CommonUI/src/Components/Link/Link"; +import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg"; +import User from "Model/Models/User"; +import React, { useState } from "react"; const ForgotPassword: () => JSX.Element = () => { - const apiUrl: URL = FORGOT_PASSWORD_API_URL; + const apiUrl: URL = FORGOT_PASSWORD_API_URL; - const [isSuccess, setIsSuccess] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); - return ( -
-
- Your Company -

- Forgot your password -

+ return ( +
+
+ Your Company +

+ Forgot your password +

- {!isSuccess && ( -

- Please enter your email and the password reset link will - be sent to you. -

- )} + {!isSuccess && ( +

+ Please enter your email and the password reset link will be sent to + you. +

+ )} - {isSuccess && ( -

- We have emailed you the password reset link. Please do - not forget to check spam. -

- )} -
+ {isSuccess && ( +

+ We have emailed you the password reset link. Please do not forget to + check spam. +

+ )} +
-
- {!isSuccess && ( -
- - modelType={User} - name="Forgot Password" - id="login-form" - createOrUpdateApiUrl={apiUrl} - fields={[ - { - field: { - email: true, - }, - title: 'Email', - fieldType: FormFieldSchemaType.Email, - required: true, - }, - ]} - onSuccess={() => { - setIsSuccess(true); - }} - submitButtonText={'Send Password Reset Link'} - formType={FormType.Create} - maxPrimaryButtonWidth={true} - footer={ -
-

- - Return to Sign in. - -

-
- } - /> -
- )} - -
-

- Remember your password?{' '} - - Login. - -

+
+ {!isSuccess && ( +
+ + modelType={User} + name="Forgot Password" + id="login-form" + createOrUpdateApiUrl={apiUrl} + fields={[ + { + field: { + email: true, + }, + title: "Email", + fieldType: FormFieldSchemaType.Email, + required: true, + }, + ]} + onSuccess={() => { + setIsSuccess(true); + }} + submitButtonText={"Send Password Reset Link"} + formType={FormType.Create} + maxPrimaryButtonWidth={true} + footer={ +
+

+ + Return to Sign in. + +

-
+ } + /> +
+ )} + +
+

+ Remember your password?{" "} + + Login. + +

- ); +
+
+ ); }; export default ForgotPassword; diff --git a/Accounts/src/Pages/Login.tsx b/Accounts/src/Pages/Login.tsx index add98029c1..376756e27d 100644 --- a/Accounts/src/Pages/Login.tsx +++ b/Accounts/src/Pages/Login.tsx @@ -1,167 +1,161 @@ -import { LOGIN_API_URL } from '../Utils/ApiPaths'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { JSONObject } from 'Common/Types/JSON'; -import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Link from 'CommonUI/src/Components/Link/Link'; -import { DASHBOARD_URL } from 'CommonUI/src/Config'; -import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg'; -import UiAnalytics from 'CommonUI/src/Utils/Analytics'; -import LoginUtil from 'CommonUI/src/Utils/Login'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import UserUtil from 'CommonUI/src/Utils/User'; -import User from 'Model/Models/User'; -import React, { useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import { LOGIN_API_URL } from "../Utils/ApiPaths"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { JSONObject } from "Common/Types/JSON"; +import Alert, { AlertType } from "CommonUI/src/Components/Alerts/Alert"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Link from "CommonUI/src/Components/Link/Link"; +import { DASHBOARD_URL } from "CommonUI/src/Config"; +import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg"; +import UiAnalytics from "CommonUI/src/Utils/Analytics"; +import LoginUtil from "CommonUI/src/Utils/Login"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import UserUtil from "CommonUI/src/Utils/User"; +import User from "Model/Models/User"; +import React, { useState } from "react"; +import useAsyncEffect from "use-async-effect"; const LoginPage: () => JSX.Element = () => { - const apiUrl: URL = LOGIN_API_URL; + const apiUrl: URL = LOGIN_API_URL; - if (UserUtil.isLoggedIn()) { - Navigation.navigate(DASHBOARD_URL); + if (UserUtil.isLoggedIn()) { + Navigation.navigate(DASHBOARD_URL); + } + + const showSsoMessage: boolean = Boolean( + Navigation.getQueryStringByName("sso"), + ); + + const [showSsoTip, setShowSSOTip] = useState(false); + + const [initialValues, setInitialValues] = React.useState({}); + + useAsyncEffect(async () => { + if (Navigation.getQueryStringByName("email")) { + setInitialValues({ + email: Navigation.getQueryStringByName("email"), + }); } + }, []); - const showSsoMessage: boolean = Boolean( - Navigation.getQueryStringByName('sso') - ); + return ( +
+
+ OneUptime +

+ Sign in to your account +

+

+ Join thousands of business that use OneUptime to help them stay online + all the time. +

+
- const [showSsoTip, setShowSSOTip] = useState(false); - - const [initialValues, setInitialValues] = React.useState({}); - - useAsyncEffect(async () => { - if (Navigation.getQueryStringByName('email')) { - setInitialValues({ - email: Navigation.getQueryStringByName('email'), - }); - } - }, []); - - return ( -
-
- OneUptime -

- Sign in to your account -

-

- Join thousands of business that use OneUptime to help them - stay online all the time. -

-
- - {showSsoMessage && ( -
- {' '} - {' '} -
- )} - -
-
- - modelType={User} - id="login-form" - name="Login" - fields={[ - { - field: { - email: true, - }, - fieldType: FormFieldSchemaType.Email, - placeholder: 'jeff@example.com', - required: true, - disabled: Boolean( - initialValues && initialValues['email'] - ), - title: 'Email', - dataTestId: 'email', - }, - { - field: { - password: true, - }, - title: 'Password', - required: true, - validation: { - minLength: 6, - }, - fieldType: FormFieldSchemaType.Password, - sideLink: { - text: 'Forgot password?', - url: new Route('/accounts/forgot-password'), - openLinkInNewTab: false, - }, - dataTestId: 'password', - }, - ]} - createOrUpdateApiUrl={apiUrl} - formType={FormType.Create} - submitButtonText={'Login'} - onSuccess={( - value: User, - miscData: JSONObject | undefined - ) => { - if (value && value.email) { - UiAnalytics.userAuth(value.email); - UiAnalytics.capture('accounts/login'); - } - - LoginUtil.login({ - user: value, - token: miscData ? miscData['token'] : undefined, - }); - }} - maxPrimaryButtonWidth={true} - footer={ -
-
- {!showSsoTip && ( -
{ - setShowSSOTip(true); - }} - className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm" - > - Use single sign-on (SSO) instead -
- )} - - {showSsoTip && ( -
- Please sign in with your SSO - provider like Okta, Auth0, Entra ID - or any other SAML 2.0 provider. -
- )} -
-
- } - /> -
-
-
- Don't have an account?{' '} - - Register. - -
-
-
+ {showSsoMessage && ( +
+ {" "} + {" "}
- ); + )} + +
+
+ + modelType={User} + id="login-form" + name="Login" + fields={[ + { + field: { + email: true, + }, + fieldType: FormFieldSchemaType.Email, + placeholder: "jeff@example.com", + required: true, + disabled: Boolean(initialValues && initialValues["email"]), + title: "Email", + dataTestId: "email", + }, + { + field: { + password: true, + }, + title: "Password", + required: true, + validation: { + minLength: 6, + }, + fieldType: FormFieldSchemaType.Password, + sideLink: { + text: "Forgot password?", + url: new Route("/accounts/forgot-password"), + openLinkInNewTab: false, + }, + dataTestId: "password", + }, + ]} + createOrUpdateApiUrl={apiUrl} + formType={FormType.Create} + submitButtonText={"Login"} + onSuccess={(value: User, miscData: JSONObject | undefined) => { + if (value && value.email) { + UiAnalytics.userAuth(value.email); + UiAnalytics.capture("accounts/login"); + } + + LoginUtil.login({ + user: value, + token: miscData ? miscData["token"] : undefined, + }); + }} + maxPrimaryButtonWidth={true} + footer={ +
+
+ {!showSsoTip && ( +
{ + setShowSSOTip(true); + }} + className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm" + > + Use single sign-on (SSO) instead +
+ )} + + {showSsoTip && ( +
+ Please sign in with your SSO provider like Okta, Auth0, + Entra ID or any other SAML 2.0 provider. +
+ )} +
+
+ } + /> +
+
+
+ Don't have an account?{" "} + + Register. + +
+
+
+
+ ); }; export default LoginPage; diff --git a/Accounts/src/Pages/NotFound.tsx b/Accounts/src/Pages/NotFound.tsx index 34aca8d95e..05b7ac033e 100644 --- a/Accounts/src/Pages/NotFound.tsx +++ b/Accounts/src/Pages/NotFound.tsx @@ -1,18 +1,18 @@ -import React from 'react'; +import React from "react"; const LoginPage: () => JSX.Element = () => { - return ( -
-
-

- Page not found -

-

- Page you are looking for does not exist. -

-
-
- ); + return ( +
+
+

+ Page not found +

+

+ Page you are looking for does not exist. +

+
+
+ ); }; export default LoginPage; diff --git a/Accounts/src/Pages/Register.tsx b/Accounts/src/Pages/Register.tsx index 84abcf16cc..89a0bf7a45 100644 --- a/Accounts/src/Pages/Register.tsx +++ b/Accounts/src/Pages/Register.tsx @@ -1,276 +1,267 @@ -import { SIGNUP_API_URL } from '../Utils/ApiPaths'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import { JSONObject } from 'Common/Types/JSON'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import Fields from 'CommonUI/src/Components/Forms/Types/Fields'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Link from 'CommonUI/src/Components/Link/Link'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import { BILLING_ENABLED, DASHBOARD_URL } from 'CommonUI/src/Config'; -import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg'; -import BaseAPI from 'CommonUI/src/Utils/API/API'; -import UiAnalytics from 'CommonUI/src/Utils/Analytics'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import LoginUtil from 'CommonUI/src/Utils/Login'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import UserUtil from 'CommonUI/src/Utils/User'; -import Reseller from 'Model/Models/Reseller'; -import User from 'Model/Models/User'; -import React, { useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import { SIGNUP_API_URL } from "../Utils/ApiPaths"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import { JSONObject } from "Common/Types/JSON"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import Fields from "CommonUI/src/Components/Forms/Types/Fields"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Link from "CommonUI/src/Components/Link/Link"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import { BILLING_ENABLED, DASHBOARD_URL } from "CommonUI/src/Config"; +import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg"; +import BaseAPI from "CommonUI/src/Utils/API/API"; +import UiAnalytics from "CommonUI/src/Utils/Analytics"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import LoginUtil from "CommonUI/src/Utils/Login"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import UserUtil from "CommonUI/src/Utils/User"; +import Reseller from "Model/Models/Reseller"; +import User from "Model/Models/User"; +import React, { useState } from "react"; +import useAsyncEffect from "use-async-effect"; const RegisterPage: () => JSX.Element = () => { - const apiUrl: URL = SIGNUP_API_URL; + const apiUrl: URL = SIGNUP_API_URL; - const [initialValues, setInitialValues] = React.useState({}); + const [initialValues, setInitialValues] = React.useState({}); - const [error, setError] = useState(''); + const [error, setError] = useState(""); - const [isLoading, setIsLoading] = React.useState(false); + const [isLoading, setIsLoading] = React.useState(false); - const [reseller, setResller] = React.useState( - undefined - ); + const [reseller, setResller] = React.useState( + undefined, + ); - if (UserUtil.isLoggedIn()) { - Navigation.navigate(DASHBOARD_URL); + if (UserUtil.isLoggedIn()) { + Navigation.navigate(DASHBOARD_URL); + } + + type FetchResellerFunction = (resellerId: string) => Promise; + + const fetchReseller: FetchResellerFunction = async ( + resellerId: string, + ): Promise => { + setIsLoading(true); + + try { + const reseller: ListResult = await ModelAPI.getList({ + modelType: Reseller, + query: { + resellerId: resellerId, + }, + limit: 1, + skip: 0, + select: { + hidePhoneNumberOnSignup: true, + }, + sort: {}, + requestOptions: {}, + }); + + if (reseller.data.length > 0) { + setResller(reseller.data[0]); + } + } catch (err) { + setError(BaseAPI.getFriendlyMessage(err)); } - type FetchResellerFunction = (resellerId: string) => Promise; + setIsLoading(false); + }; - const fetchReseller: FetchResellerFunction = async ( - resellerId: string - ): Promise => { - setIsLoading(true); - - try { - const reseller: ListResult = - await ModelAPI.getList({ - modelType: Reseller, - query: { - resellerId: resellerId, - }, - limit: 1, - skip: 0, - select: { - hidePhoneNumberOnSignup: true, - }, - sort: {}, - requestOptions: {}, - }); - - if (reseller.data.length > 0) { - setResller(reseller.data[0]); - } - } catch (err) { - setError(BaseAPI.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - useAsyncEffect(async () => { - // if promo code is found, please save it in localstorage. - if (Navigation.getQueryStringByName('promoCode')) { - LocalStorage.setItem( - 'promoCode', - Navigation.getQueryStringByName('promoCode') - ); - } - - if (Navigation.getQueryStringByName('email')) { - setInitialValues({ - email: Navigation.getQueryStringByName('email'), - }); - } - - // if promo code is found, please save it in localstorage. - if (Navigation.getQueryStringByName('partnerId')) { - await fetchReseller(Navigation.getQueryStringByName('partnerId')!); - } - }, []); - - let formFields: Fields = [ - { - field: { - email: true, - }, - fieldType: FormFieldSchemaType.Email, - placeholder: 'jeff@example.com', - required: true, - disabled: Boolean(initialValues && initialValues['email']), - title: 'Email', - dataTestId: 'email', - }, - { - field: { - name: true, - }, - fieldType: FormFieldSchemaType.Text, - placeholder: 'Jeff Smith', - required: true, - title: 'Full Name', - dataTestId: 'name', - }, - ]; - - if (BILLING_ENABLED) { - formFields = formFields.concat([ - { - field: { - companyName: true, - }, - fieldType: FormFieldSchemaType.Text, - placeholder: 'Acme, Inc.', - required: true, - title: 'Company Name', - dataTestId: 'companyName', - }, - ]); - - // If reseller wants to hide phone number on sign up, we hide it. - if (!reseller || !reseller.hidePhoneNumberOnSignup) { - formFields.push({ - field: { - companyPhoneNumber: true, - }, - fieldType: FormFieldSchemaType.Phone, - required: true, - placeholder: '+11234567890', - title: 'Phone Number', - dataTestId: 'companyPhoneNumber', - }); - } + useAsyncEffect(async () => { + // if promo code is found, please save it in localstorage. + if (Navigation.getQueryStringByName("promoCode")) { + LocalStorage.setItem( + "promoCode", + Navigation.getQueryStringByName("promoCode"), + ); } + if (Navigation.getQueryStringByName("email")) { + setInitialValues({ + email: Navigation.getQueryStringByName("email"), + }); + } + + // if promo code is found, please save it in localstorage. + if (Navigation.getQueryStringByName("partnerId")) { + await fetchReseller(Navigation.getQueryStringByName("partnerId")!); + } + }, []); + + let formFields: Fields = [ + { + field: { + email: true, + }, + fieldType: FormFieldSchemaType.Email, + placeholder: "jeff@example.com", + required: true, + disabled: Boolean(initialValues && initialValues["email"]), + title: "Email", + dataTestId: "email", + }, + { + field: { + name: true, + }, + fieldType: FormFieldSchemaType.Text, + placeholder: "Jeff Smith", + required: true, + title: "Full Name", + dataTestId: "name", + }, + ]; + + if (BILLING_ENABLED) { formFields = formFields.concat([ - { - field: { - password: true, - }, - fieldType: FormFieldSchemaType.Password, - validation: { - minLength: 6, - }, - placeholder: 'Password', - title: 'Password', - required: true, - dataTestId: 'password', - }, - { - field: { - confirmPassword: true, - } as any, - validation: { - minLength: 6, - toMatchField: 'password', - }, - fieldType: FormFieldSchemaType.Password, - placeholder: 'Confirm Password', - title: 'Confirm Password', - overrideFieldKey: 'confirmPassword', - required: true, - showEvenIfPermissionDoesNotExist: true, - dataTestId: 'confirmPassword', + { + field: { + companyName: true, }, + fieldType: FormFieldSchemaType.Text, + placeholder: "Acme, Inc.", + required: true, + title: "Company Name", + dataTestId: "companyName", + }, ]); - if (error) { - return ; + // If reseller wants to hide phone number on sign up, we hide it. + if (!reseller || !reseller.hidePhoneNumberOnSignup) { + formFields.push({ + field: { + companyPhoneNumber: true, + }, + fieldType: FormFieldSchemaType.Phone, + required: true, + placeholder: "+11234567890", + title: "Phone Number", + dataTestId: "companyPhoneNumber", + }); } + } - if (isLoading) { - return ; - } + formFields = formFields.concat([ + { + field: { + password: true, + }, + fieldType: FormFieldSchemaType.Password, + validation: { + minLength: 6, + }, + placeholder: "Password", + title: "Password", + required: true, + dataTestId: "password", + }, + { + field: { + confirmPassword: true, + } as any, + validation: { + minLength: 6, + toMatchField: "password", + }, + fieldType: FormFieldSchemaType.Password, + placeholder: "Confirm Password", + title: "Confirm Password", + overrideFieldKey: "confirmPassword", + required: true, + showEvenIfPermissionDoesNotExist: true, + dataTestId: "confirmPassword", + }, + ]); - return ( -
-
- OneUptime -

- Create your OneUptime account -

-

- Join thousands of business that use OneUptime to help them - stay online all the time. -

-

- No credit card required. -

-
+ if (error) { + return ; + } -
-
- - modelType={User} - id="register-form" - showAsColumns={reseller ? 1 : 2} - name="Register" - initialValues={initialValues} - maxPrimaryButtonWidth={true} - fields={formFields} - createOrUpdateApiUrl={apiUrl} - onBeforeCreate={(item: User): Promise => { - const utmParams: Dictionary = - UserUtil.getUtmParams(); + if (isLoading) { + return ; + } - if ( - utmParams && - Object.keys(utmParams).length > 0 - ) { - item.utmSource = utmParams['utmSource'] || ''; - item.utmMedium = utmParams['utmMedium'] || ''; - item.utmCampaign = - utmParams['utmCampaign'] || ''; - item.utmTerm = utmParams['utmTerm'] || ''; - item.utmContent = utmParams['utmContent'] || ''; - item.utmUrl = utmParams['utmUrl'] || ''; + return ( +
+
+ OneUptime +

+ Create your OneUptime account +

+

+ Join thousands of business that use OneUptime to help them stay online + all the time. +

+

+ No credit card required. +

+
- UiAnalytics.capture('utm_event', utmParams); - } +
+
+ + modelType={User} + id="register-form" + showAsColumns={reseller ? 1 : 2} + name="Register" + initialValues={initialValues} + maxPrimaryButtonWidth={true} + fields={formFields} + createOrUpdateApiUrl={apiUrl} + onBeforeCreate={(item: User): Promise => { + const utmParams: Dictionary = UserUtil.getUtmParams(); - return Promise.resolve(item); - }} - formType={FormType.Create} - submitButtonText={'Sign Up'} - onSuccess={( - value: User, - miscData: JSONObject | undefined - ) => { - if (value && value.email) { - UiAnalytics.userAuth(value.email); - UiAnalytics.capture('accounts/register'); - } + if (utmParams && Object.keys(utmParams).length > 0) { + item.utmSource = utmParams["utmSource"] || ""; + item.utmMedium = utmParams["utmMedium"] || ""; + item.utmCampaign = utmParams["utmCampaign"] || ""; + item.utmTerm = utmParams["utmTerm"] || ""; + item.utmContent = utmParams["utmContent"] || ""; + item.utmUrl = utmParams["utmUrl"] || ""; - LoginUtil.login({ - user: value, - token: miscData ? miscData['token'] : undefined, - }); - }} - /> -
-
-

- Already have an account?{' '} - - Log in. - -

-
-
+ UiAnalytics.capture("utm_event", utmParams); + } + + return Promise.resolve(item); + }} + formType={FormType.Create} + submitButtonText={"Sign Up"} + onSuccess={(value: User, miscData: JSONObject | undefined) => { + if (value && value.email) { + UiAnalytics.userAuth(value.email); + UiAnalytics.capture("accounts/register"); + } + + LoginUtil.login({ + user: value, + token: miscData ? miscData["token"] : undefined, + }); + }} + />
- ); +
+

+ Already have an account?{" "} + + Log in. + +

+
+
+
+ ); }; export default RegisterPage; diff --git a/Accounts/src/Pages/ResetPassword.tsx b/Accounts/src/Pages/ResetPassword.tsx index dcadff2dfd..c14abc9836 100644 --- a/Accounts/src/Pages/ResetPassword.tsx +++ b/Accounts/src/Pages/ResetPassword.tsx @@ -1,115 +1,114 @@ -import { RESET_PASSWORD_API_URL } from '../Utils/ApiPaths'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Link from 'CommonUI/src/Components/Link/Link'; -import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import User from 'Model/Models/User'; -import React, { useState } from 'react'; +import { RESET_PASSWORD_API_URL } from "../Utils/ApiPaths"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Link from "CommonUI/src/Components/Link/Link"; +import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import User from "Model/Models/User"; +import React, { useState } from "react"; const RegisterPage: () => JSX.Element = () => { - const apiUrl: URL = RESET_PASSWORD_API_URL; - const [isSuccess, setIsSuccess] = useState(false); + const apiUrl: URL = RESET_PASSWORD_API_URL; + const [isSuccess, setIsSuccess] = useState(false); - return ( -
-
- Your Company -

- Reset your password -

+ return ( +
+
+ Your Company +

+ Reset your password +

- {!isSuccess && ( -

- Please enter your new password and we will have it - updated.{' '} -

- )} + {!isSuccess && ( +

+ Please enter your new password and we will have it updated.{" "} +

+ )} - {isSuccess && ( -

- Your password has been updated. Please log in. -

- )} -
+ {isSuccess && ( +

+ Your password has been updated. Please log in. +

+ )} +
-
- {!isSuccess && ( -
- - modelType={User} - id="register-form" - name="Reset Password" - onBeforeCreate={(item: User): Promise => { - item.resetPasswordToken = - Navigation.getLastParam() - ?.toString() - .replace('/', '') - .toString() || ''; - return Promise.resolve(item); - }} - showAsColumns={1} - maxPrimaryButtonWidth={true} - fields={[ - { - field: { - password: true, - }, - fieldType: FormFieldSchemaType.Password, - validation: { - minLength: 6, - }, - placeholder: 'New Password', - title: 'New Password', - required: true, - showEvenIfPermissionDoesNotExist: true, - }, - { - field: { - confirmPassword: true, - } as any, - validation: { - minLength: 6, - toMatchField: 'password', - }, - fieldType: FormFieldSchemaType.Password, - placeholder: 'Confirm Password', - title: 'Confirm Password', - overrideFieldKey: 'confirmPassword', - required: true, - showEvenIfPermissionDoesNotExist: true, - }, - ]} - createOrUpdateApiUrl={apiUrl} - formType={FormType.Create} - submitButtonText={'Reset Password'} - onSuccess={() => { - setIsSuccess(true); - }} - /> -
- )} +
+ {!isSuccess && ( +
+ + modelType={User} + id="register-form" + name="Reset Password" + onBeforeCreate={(item: User): Promise => { + item.resetPasswordToken = + Navigation.getLastParam() + ?.toString() + .replace("/", "") + .toString() || ""; + return Promise.resolve(item); + }} + showAsColumns={1} + maxPrimaryButtonWidth={true} + fields={[ + { + field: { + password: true, + }, + fieldType: FormFieldSchemaType.Password, + validation: { + minLength: 6, + }, + placeholder: "New Password", + title: "New Password", + required: true, + showEvenIfPermissionDoesNotExist: true, + }, + { + field: { + confirmPassword: true, + } as any, + validation: { + minLength: 6, + toMatchField: "password", + }, + fieldType: FormFieldSchemaType.Password, + placeholder: "Confirm Password", + title: "Confirm Password", + overrideFieldKey: "confirmPassword", + required: true, + showEvenIfPermissionDoesNotExist: true, + }, + ]} + createOrUpdateApiUrl={apiUrl} + formType={FormType.Create} + submitButtonText={"Reset Password"} + onSuccess={() => { + setIsSuccess(true); + }} + /> +
+ )} -
-

- Know your password?{' '} - - Log in. - -

-
-
+
+

+ Know your password?{" "} + + Log in. + +

- ); +
+
+ ); }; export default RegisterPage; diff --git a/Accounts/src/Pages/VerifyEmail.tsx b/Accounts/src/Pages/VerifyEmail.tsx index 61ec49bbd3..6b34740303 100644 --- a/Accounts/src/Pages/VerifyEmail.tsx +++ b/Accounts/src/Pages/VerifyEmail.tsx @@ -1,132 +1,121 @@ -import { VERIFY_EMAIL_API_URL } from '../Utils/ApiPaths'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import Link from 'CommonUI/src/Components/Link/Link'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import EmailVerificationToken from 'Model/Models/EmailVerificationToken'; -import React, { useEffect, useState } from 'react'; +import { VERIFY_EMAIL_API_URL } from "../Utils/ApiPaths"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import Link from "CommonUI/src/Components/Link/Link"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import EmailVerificationToken from "Model/Models/EmailVerificationToken"; +import React, { useEffect, useState } from "react"; const VerifyEmail: () => JSX.Element = () => { - const apiUrl: URL = VERIFY_EMAIL_API_URL; - const [error, setError] = useState(''); - const [isLoading, setIsLoading] = useState(true); + const apiUrl: URL = VERIFY_EMAIL_API_URL; + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(true); - const init: PromiseVoidFunction = async (): Promise => { - // Ping an API here. - setError(''); - setIsLoading(true); + const init: PromiseVoidFunction = async (): Promise => { + // Ping an API here. + setError(""); + setIsLoading(true); - try { - // strip data. - const emailverificationToken: EmailVerificationToken = - new EmailVerificationToken(); - emailverificationToken.token = new ObjectID( - Navigation.getLastParam()?.toString().replace('/', '') || '' - ); + try { + // strip data. + const emailverificationToken: EmailVerificationToken = + new EmailVerificationToken(); + emailverificationToken.token = new ObjectID( + Navigation.getLastParam()?.toString().replace("/", "") || "", + ); - await ModelAPI.createOrUpdate({ - model: emailverificationToken, - modelType: EmailVerificationToken, - formType: FormType.Create, - miscDataProps: {}, - requestOptions: { - overrideRequestUrl: apiUrl, - }, - }); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - useEffect(() => { - init().catch((err: Error) => { - setError(err.toString()); - }); - }, []); - - if (isLoading) { - return ; + await ModelAPI.createOrUpdate({ + model: emailverificationToken, + modelType: EmailVerificationToken, + formType: FormType.Create, + miscDataProps: {}, + requestOptions: { + overrideRequestUrl: apiUrl, + }, + }); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - return ( -
-
-
-
+ setIsLoading(false); + }; -
-
-
-
-
-
- -
- {!error && ( -
-
- Your email is verified. -
-

- Thank you for verifying your - email. You can now log in to - OneUptime.{' '} -

-
- )} + useEffect(() => { + init().catch((err: Error) => { + setError(err.toString()); + }); + }, []); - {error && ( -
-
- Sorry, something went wrong! -
-

- {error} -

-
- )} + if (isLoading) { + return ; + } -
-

- Return to sign in?{' '} - - Login. - -

-
-
-
-
-
+ return ( +
+
+
+
+ +
+
+
+
+
+
+
+ {!error && ( +
+
Your email is verified.
+

+ Thank you for verifying your email. You can now log in + to OneUptime.{" "} +

+
+ )} -
+ {error && ( +
+
Sorry, something went wrong!
+

{error}

+
+ )} + +
+

+ Return to sign in?{" "} + + Login. + +

+
+
+
+
+ +
- ); +
+
+ ); }; export default VerifyEmail; diff --git a/Accounts/src/Utils/ApiPaths.ts b/Accounts/src/Utils/ApiPaths.ts index f4e92f688a..8f9615e007 100644 --- a/Accounts/src/Utils/ApiPaths.ts +++ b/Accounts/src/Utils/ApiPaths.ts @@ -1,22 +1,22 @@ -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { IDENTITY_URL } from 'CommonUI/src/Config'; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { IDENTITY_URL } from "CommonUI/src/Config"; export const SIGNUP_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute( - new Route('/signup') + new Route("/signup"), ); export const LOGIN_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute( - new Route('/login') + new Route("/login"), ); export const FORGOT_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute( - new Route('/forgot-password') + new Route("/forgot-password"), ); export const VERIFY_EMAIL_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute( - new Route('/verify-email') + new Route("/verify-email"), ); export const RESET_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute( - new Route('/reset-password') + new Route("/reset-password"), ); diff --git a/Accounts/webpack.config.js b/Accounts/webpack.config.js index b6103f38e8..3ce880bfbf 100644 --- a/Accounts/webpack.config.js +++ b/Accounts/webpack.config.js @@ -1,83 +1,84 @@ -require('ts-loader'); -require('file-loader'); -require('style-loader'); -require('css-loader'); -require('sass-loader'); +require("ts-loader"); +require("file-loader"); +require("style-loader"); +require("css-loader"); +require("sass-loader"); const path = require("path"); const webpack = require("webpack"); -const dotenv = require('dotenv'); -const express = require('express'); +const dotenv = require("dotenv"); +const express = require("express"); const readEnvFile = (pathToFile) => { + const parsed = dotenv.config({ path: pathToFile }).parsed; - const parsed = dotenv.config({ path: pathToFile }).parsed; + const env = {}; - const env = { - }; + for (const key in parsed) { + env[key] = JSON.stringify(parsed[key]); + } - for (const key in parsed) { - env[key] = JSON.stringify(parsed[key]); - } - - return env; -} + return env; +}; module.exports = { - entry: "./src/Index.tsx", - mode: "development", - output: { - filename: "bundle.js", - path: path.resolve(__dirname, "public", "dist"), - publicPath: "/accounts/dist/", + entry: "./src/Index.tsx", + mode: "development", + output: { + filename: "bundle.js", + path: path.resolve(__dirname, "public", "dist"), + publicPath: "/accounts/dist/", + }, + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"], + alias: { + react: path.resolve("./node_modules/react"), }, - resolve: { - extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'], - alias: { - react: path.resolve('./node_modules/react'), - } - }, - externals: { - 'react-native-sqlite-storage': 'react-native-sqlite-storage' - }, - plugins: [ - new webpack.DefinePlugin({ - 'process': { - 'env': { - ...readEnvFile('/usr/src/app/dev-env/.env') - } - } - }), - ], - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - use: 'ts-loader' - }, - { - test: /\.s[ac]ss$/i, - use: ['style-loader', 'css-loader', "sass-loader"] - }, - { - test: /\.css$/i, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.(jpe?g|png|gif|svg)$/i, - loader: 'file-loader' - } - ], - }, - devServer: { - historyApiFallback: true, - devMiddleware: { - writeToDisk: true, + }, + externals: { + "react-native-sqlite-storage": "react-native-sqlite-storage", + }, + plugins: [ + new webpack.DefinePlugin({ + process: { + env: { + ...readEnvFile("/usr/src/app/dev-env/.env"), }, - allowedHosts: "all", - setupMiddlewares: (middlewares, devServer) => { - devServer.app.use('/accounts/assets', express.static(path.resolve(__dirname, 'public', 'assets'))); - return middlewares; - } + }, + }), + ], + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + use: "ts-loader", + }, + { + test: /\.s[ac]ss$/i, + use: ["style-loader", "css-loader", "sass-loader"], + }, + { + test: /\.css$/i, + use: ["style-loader", "css-loader"], + }, + { + test: /\.(jpe?g|png|gif|svg)$/i, + loader: "file-loader", + }, + ], + }, + devServer: { + historyApiFallback: true, + devMiddleware: { + writeToDisk: true, }, - devtool: 'eval-source-map', -} \ No newline at end of file + allowedHosts: "all", + setupMiddlewares: (middlewares, devServer) => { + devServer.app.use( + "/accounts/assets", + express.static(path.resolve(__dirname, "public", "assets")), + ); + return middlewares; + }, + }, + devtool: "eval-source-map", +}; diff --git a/AdminDashboard/Serve.ts b/AdminDashboard/Serve.ts index d6eac8f3d0..0464bbb8a3 100755 --- a/AdminDashboard/Serve.ts +++ b/AdminDashboard/Serve.ts @@ -1,38 +1,38 @@ -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import App from 'CommonServer/Utils/StartServer'; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import App from "CommonServer/Utils/StartServer"; -export const APP_NAME: string = 'admin'; +export const APP_NAME: string = "admin"; const app: ExpressApplication = Express.getExpressApp(); const init: PromiseVoidFunction = async (): Promise => { - try { - // init the app - await App.init({ - appName: APP_NAME, - port: undefined, - isFrontendApp: true, - statusOptions: { - liveCheck: async () => {}, - readyCheck: async () => {}, - }, - }); + try { + // init the app + await App.init({ + appName: APP_NAME, + port: undefined, + isFrontendApp: true, + statusOptions: { + liveCheck: async () => {}, + readyCheck: async () => {}, + }, + }); - // add default routes - await App.addDefaultRoutes(); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); export default app; diff --git a/AdminDashboard/index.d.ts b/AdminDashboard/index.d.ts index cf14fde98c..a0ad0eb5c4 100644 --- a/AdminDashboard/index.d.ts +++ b/AdminDashboard/index.d.ts @@ -1,4 +1,4 @@ -declare module '*.png'; -declare module '*.svg'; -declare module '*.jpg'; -declare module '*.gif'; +declare module "*.png"; +declare module "*.svg"; +declare module "*.jpg"; +declare module "*.gif"; diff --git a/AdminDashboard/src/App.tsx b/AdminDashboard/src/App.tsx index 7d5384ff30..ec56a4b201 100644 --- a/AdminDashboard/src/App.tsx +++ b/AdminDashboard/src/App.tsx @@ -1,112 +1,103 @@ -import MasterPage from './Components/MasterPage/MasterPage'; -import Init from './Pages/Init/Init'; -import Logout from './Pages/Logout/Logout'; -import Projects from './Pages/Projects/Index'; -import SettingsAPIKey from './Pages/Settings/APIKey/Index'; -import SettingsAuthentication from './Pages/Settings/Authentication/Index'; -import SettingsCallSMS from './Pages/Settings/CallSMS/Index'; +import MasterPage from "./Components/MasterPage/MasterPage"; +import Init from "./Pages/Init/Init"; +import Logout from "./Pages/Logout/Logout"; +import Projects from "./Pages/Projects/Index"; +import SettingsAPIKey from "./Pages/Settings/APIKey/Index"; +import SettingsAuthentication from "./Pages/Settings/Authentication/Index"; +import SettingsCallSMS from "./Pages/Settings/CallSMS/Index"; // Settings Pages. -import SettingsEmail from './Pages/Settings/Email/Index'; -import SettingsProbes from './Pages/Settings/Probes/Index'; -import Users from './Pages/Users/Index'; -import PageMap from './Utils/PageMap'; -import RouteMap from './Utils/RouteMap'; -import URL from 'Common/Types/API/URL'; -import { ACCOUNTS_URL, DASHBOARD_URL } from 'CommonUI/src/Config'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import User from 'CommonUI/src/Utils/User'; -import React from 'react'; +import SettingsEmail from "./Pages/Settings/Email/Index"; +import SettingsProbes from "./Pages/Settings/Probes/Index"; +import Users from "./Pages/Users/Index"; +import PageMap from "./Utils/PageMap"; +import RouteMap from "./Utils/RouteMap"; +import URL from "Common/Types/API/URL"; +import { ACCOUNTS_URL, DASHBOARD_URL } from "CommonUI/src/Config"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import User from "CommonUI/src/Utils/User"; +import React from "react"; import { - Route as PageRoute, - Routes, - useLocation, - useNavigate, - useParams, -} from 'react-router-dom'; + Route as PageRoute, + Routes, + useLocation, + useNavigate, + useParams, +} from "react-router-dom"; const App: () => JSX.Element = () => { - Navigation.setNavigateHook(useNavigate()); - Navigation.setLocation(useLocation()); - Navigation.setParams(useParams()); + Navigation.setNavigateHook(useNavigate()); + Navigation.setLocation(useLocation()); + Navigation.setParams(useParams()); - if (!User.isLoggedIn()) { - if (Navigation.getQueryStringByName('sso_token')) { - Navigation.navigate( - URL.fromString(ACCOUNTS_URL.toString()).addQueryParam( - 'sso', - 'true' - ) - ); - } else { - Navigation.navigate(URL.fromString(ACCOUNTS_URL.toString())); - } + if (!User.isLoggedIn()) { + if (Navigation.getQueryStringByName("sso_token")) { + Navigation.navigate( + URL.fromString(ACCOUNTS_URL.toString()).addQueryParam("sso", "true"), + ); + } else { + Navigation.navigate(URL.fromString(ACCOUNTS_URL.toString())); } + } - if (!User.isMasterAdmin()) { - Navigation.navigate(URL.fromString(DASHBOARD_URL.toString())); - } + if (!User.isMasterAdmin()) { + Navigation.navigate(URL.fromString(DASHBOARD_URL.toString())); + } - return ( - - - } - /> + return ( + + + } + /> - } - /> + } + /> - } - /> + } + /> - } - /> + } + /> - } - /> + } + /> - } - /> + } + /> - } - /> + } + /> - } - /> + } + /> - } - /> + } + /> - } - /> - - - ); + } + /> + + + ); }; export default App; diff --git a/AdminDashboard/src/Components/Footer/Footer.tsx b/AdminDashboard/src/Components/Footer/Footer.tsx index bb1218635e..30bc4fdc14 100644 --- a/AdminDashboard/src/Components/Footer/Footer.tsx +++ b/AdminDashboard/src/Components/Footer/Footer.tsx @@ -1,122 +1,114 @@ -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { JSONObject } from 'Common/Types/JSON'; -import API from 'Common/Utils/API'; -import Footer from 'CommonUI/src/Components/Footer/Footer'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import { HOST, HTTP_PROTOCOL } from 'CommonUI/src/Config'; -import React from 'react'; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { JSONObject } from "Common/Types/JSON"; +import API from "Common/Utils/API"; +import Footer from "CommonUI/src/Components/Footer/Footer"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import { HOST, HTTP_PROTOCOL } from "CommonUI/src/Config"; +import React from "react"; const DashboardFooter: () => JSX.Element = () => { - const [showAboutModal, setShowAboutModal] = React.useState(false); - const [isAboutModalLoading, setIsAboutModalLoading] = - React.useState(false); - const [versionText, setVersionText] = React.useState>( - {} + const [showAboutModal, setShowAboutModal] = React.useState(false); + const [isAboutModalLoading, setIsAboutModalLoading] = + React.useState(false); + const [versionText, setVersionText] = React.useState>({}); + + const fetchVersions: PromiseVoidFunction = async (): Promise => { + setIsAboutModalLoading(true); + + try { + const verText: Dictionary = {}; + const apps: Array<{ + name: string; + path: string; + }> = [ + { + name: "API", + path: "/api", + }, + { + name: "Dashboard", + path: "/dashboard", + }, + ]; + + for (const app of apps) { + const version: JSONObject = await fetchAppVersion(app.path); + verText[app.name] = + `${app.name}: ${version["version"]} (${version["commit"]})`; + } + + setVersionText(verText); + } catch (err) { + setVersionText({ + error: "Version data is not available: " + (err as Error).message, + }); + } + + setIsAboutModalLoading(false); + }; + + const fetchAppVersion: (appName: string) => Promise = async ( + appName: string, + ): Promise => { + const response: HTTPResponse = await API.get( + URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`), ); - const fetchVersions: PromiseVoidFunction = async (): Promise => { - setIsAboutModalLoading(true); + if (response.data) { + return response.data as JSONObject; + } + throw new BadDataException("Version data is not available"); + }; - try { - const verText: Dictionary = {}; - const apps: Array<{ - name: string; - path: string; - }> = [ - { - name: 'API', - path: '/api', - }, - { - name: 'Dashboard', - path: '/dashboard', - }, - ]; + return ( + <> +
{ + setShowAboutModal(true); + await fetchVersions(); + }, + }, + ]} + /> - for (const app of apps) { - const version: JSONObject = await fetchAppVersion(app.path); - verText[ - app.name - ] = `${app.name}: ${version['version']} (${version['commit']})`; - } - - setVersionText(verText); - } catch (err) { - setVersionText({ - error: - 'Version data is not available: ' + (err as Error).message, - }); - } - - setIsAboutModalLoading(false); - }; - - const fetchAppVersion: (appName: string) => Promise = async ( - appName: string - ): Promise => { - const response: HTTPResponse = await API.get( - URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`) - ); - - if (response.data) { - return response.data as JSONObject; - } - throw new BadDataException('Version data is not available'); - }; - - return ( - <> -
{ - setShowAboutModal(true); - await fetchVersions(); - }, - }, - ]} - /> - - {showAboutModal ? ( - - {Object.keys(versionText).map( - (key: string, i: number) => { - return ( -
{versionText[key]}
- ); - } - )} -
- } - isLoading={isAboutModalLoading} - submitButtonText={'Close'} - onSubmit={() => { - return setShowAboutModal(false); - }} - /> - ) : ( - <> - )} - - ); + {showAboutModal ? ( + + {Object.keys(versionText).map((key: string, i: number) => { + return
{versionText[key]}
; + })} +
+ } + isLoading={isAboutModalLoading} + submitButtonText={"Close"} + onSubmit={() => { + return setShowAboutModal(false); + }} + /> + ) : ( + <> + )} + + ); }; export default DashboardFooter; diff --git a/AdminDashboard/src/Components/Header/Header.tsx b/AdminDashboard/src/Components/Header/Header.tsx index 4464145cdf..9d33fa0863 100644 --- a/AdminDashboard/src/Components/Header/Header.tsx +++ b/AdminDashboard/src/Components/Header/Header.tsx @@ -1,46 +1,46 @@ -import Help from './Help'; -import Logo from './Logo'; -import UserProfile from './UserProfile'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Header from 'CommonUI/src/Components/Header/Header'; -import { DASHBOARD_URL } from 'CommonUI/src/Config'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Help from "./Help"; +import Logo from "./Logo"; +import UserProfile from "./UserProfile"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Header from "CommonUI/src/Components/Header/Header"; +import { DASHBOARD_URL } from "CommonUI/src/Config"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; const DashboardHeader: FunctionComponent = (): ReactElement => { - return ( - <> -
- {}} /> - - } - centerComponents={ - <> - {/* +
+ {}} /> + + } + centerComponents={ + <> + {/* { }} />{' '} */} - - } - rightComponents={ - <> -
- } - submitButtonText={'Close'} - submitButtonType={ButtonStyleType.NORMAL} - onSubmit={async () => { - setShowKeyModal(false); - }} + return ( + - ) : ( - <> - )} - - ); + ); + }, + }, + ]} + /> + + {showKeyModal && currentProbe ? ( + + Here is your probe key. Please keep this a secret. +
+
+ + Probe ID: {currentProbe["_id"]?.toString()} + +
+
+ + Probe Key: {currentProbe["key"]?.toString()} + +
+ } + submitButtonText={"Close"} + submitButtonType={ButtonStyleType.NORMAL} + onSubmit={async () => { + setShowKeyModal(false); + }} + /> + ) : ( + <> + )} + + ); }; export default Settings; diff --git a/AdminDashboard/src/Pages/Settings/SideMenu.tsx b/AdminDashboard/src/Pages/Settings/SideMenu.tsx index bf9e2f78e0..bc7ff16e44 100644 --- a/AdminDashboard/src/Pages/Settings/SideMenu.tsx +++ b/AdminDashboard/src/Pages/Settings/SideMenu.tsx @@ -1,17 +1,17 @@ -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 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { ReactElement } from 'react'; +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 "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { ReactElement } from "react"; const DashboardSideMenu: () => JSX.Element = (): ReactElement => { - return ( - - - {/* + + {/* JSX.Element = (): ReactElement => { }} icon={IconProp.Globe} /> */} - - + + - - - - + + + + - - - - - - - - ); + + + + + + + + ); }; export default DashboardSideMenu; diff --git a/AdminDashboard/src/Pages/Users/Index.tsx b/AdminDashboard/src/Pages/Users/Index.tsx index 5a9344de46..f71d7f0331 100644 --- a/AdminDashboard/src/Pages/Users/Index.tsx +++ b/AdminDashboard/src/Pages/Users/Index.tsx @@ -1,222 +1,218 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import { ErrorFunction } from 'Common/Types/FunctionTypes'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Page from 'CommonUI/src/Components/Page/Page'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import User from 'Model/Models/User'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import { ErrorFunction } from "Common/Types/FunctionTypes"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Page from "CommonUI/src/Components/Page/Page"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import User from "Model/Models/User"; +import React, { FunctionComponent, ReactElement, useState } from "react"; const Users: FunctionComponent = (): ReactElement => { - const [showConfirmVerifyEmailModal, setShowConfirmVerifyEmailModal] = - useState(false); - const [selectedUser, setSelectedUser] = useState(null); - const [error, setError] = useState(null); + const [showConfirmVerifyEmailModal, setShowConfirmVerifyEmailModal] = + useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [error, setError] = useState(null); - const [isConfimModalLoading, setIsConfirmModalLoading] = - useState(false); + const [isConfimModalLoading, setIsConfirmModalLoading] = + useState(false); - const [refreshItemsTrigger, setRefreshItemsTrigger] = - useState(false); + const [refreshItemsTrigger, setRefreshItemsTrigger] = + useState(false); - return ( - + + modelType={User} + id="users-table" + isDeleteable={false} + isEditable={false} + showViewIdButton={true} + refreshToggle={refreshItemsTrigger} + isCreateable={true} + name="Users" + isViewable={false} + cardProps={{ + title: "Users", + description: "Here is a list of users in OneUptime.", + }} + actionButtons={[ + { + title: "Verify Email", + buttonStyleType: ButtonStyleType.NORMAL, + isVisible: (item: User) => { + return !item.isEmailVerified; + }, + onClick: async ( + item: User, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setSelectedUser(item); + setShowConfirmVerifyEmailModal(true); + + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + noItemsMessage={"No users found."} + formFields={[ + { + field: { + email: true, + }, + title: "Email", + fieldType: FormFieldSchemaType.Email, + required: true, + placeholder: "email@company.com", + }, + { + field: { + password: true, + }, + title: "Password", + fieldType: FormFieldSchemaType.Password, + required: true, + placeholder: "Password", + }, + { + field: { + name: true, + }, + title: "Full Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "John Smith", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + name: true, + }, + title: "Full Name", + type: FieldType.Text, + }, + { + field: { + email: true, + }, + title: "Email", + type: FieldType.Email, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + type: FieldType.DateTime, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Full Name", + type: FieldType.Text, + }, + { + field: { + email: true, + }, + title: "Email", + type: FieldType.Email, + }, + { + field: { + isEmailVerified: true, + }, + title: "Email Verified", + type: FieldType.Boolean, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + type: FieldType.DateTime, + }, + ]} + /> + + {error ? ( + { + setError(null); + }} + submitButtonType={ButtonStyleType.NORMAL} + /> + ) : ( + <> + )} + + {showConfirmVerifyEmailModal && selectedUser ? ( + { + setShowConfirmVerifyEmailModal(false); + setSelectedUser(null); + }} + onSubmit={async () => { + try { + setIsConfirmModalLoading(true); + await ModelAPI.updateById({ + modelType: User, + id: selectedUser.id!, + data: { + isEmailVerified: true, }, - { - title: 'Users', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.USERS] as Route - ), - }, - ]} - > - - modelType={User} - id="users-table" - isDeleteable={false} - isEditable={false} - showViewIdButton={true} - refreshToggle={refreshItemsTrigger} - isCreateable={true} - name="Users" - isViewable={false} - cardProps={{ - title: 'Users', - description: 'Here is a list of users in OneUptime.', - }} - actionButtons={[ - { - title: 'Verify Email', - buttonStyleType: ButtonStyleType.NORMAL, - isVisible: (item: User) => { - return !item.isEmailVerified; - }, - onClick: async ( - item: User, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setSelectedUser(item); - setShowConfirmVerifyEmailModal(true); + }); + } catch (err) { + setError(API.getFriendlyMessage(err as Error)); + } - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - noItemsMessage={'No users found.'} - formFields={[ - { - field: { - email: true, - }, - title: 'Email', - fieldType: FormFieldSchemaType.Email, - required: true, - placeholder: 'email@company.com', - }, - { - field: { - password: true, - }, - title: 'Password', - fieldType: FormFieldSchemaType.Password, - required: true, - placeholder: 'Password', - }, - { - field: { - name: true, - }, - title: 'Full Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'John Smith', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - name: true, - }, - title: 'Full Name', - type: FieldType.Text, - }, - { - field: { - email: true, - }, - title: 'Email', - type: FieldType.Email, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - type: FieldType.DateTime, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Full Name', - type: FieldType.Text, - }, - { - field: { - email: true, - }, - title: 'Email', - type: FieldType.Email, - }, - { - field: { - isEmailVerified: true, - }, - title: 'Email Verified', - type: FieldType.Boolean, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - type: FieldType.DateTime, - }, - ]} - /> - - {error ? ( - { - setError(null); - }} - submitButtonType={ButtonStyleType.NORMAL} - /> - ) : ( - <> - )} - - {showConfirmVerifyEmailModal && selectedUser ? ( - { - setShowConfirmVerifyEmailModal(false); - setSelectedUser(null); - }} - onSubmit={async () => { - try { - setIsConfirmModalLoading(true); - await ModelAPI.updateById({ - modelType: User, - id: selectedUser.id!, - data: { - isEmailVerified: true, - }, - }); - } catch (err) { - setError(API.getFriendlyMessage(err as Error)); - } - - setRefreshItemsTrigger(!refreshItemsTrigger); - setIsConfirmModalLoading(false); - setShowConfirmVerifyEmailModal(false); - }} - /> - ) : ( - <> - )} - - ); + setRefreshItemsTrigger(!refreshItemsTrigger); + setIsConfirmModalLoading(false); + setShowConfirmVerifyEmailModal(false); + }} + /> + ) : ( + <> + )} + + ); }; export default Users; diff --git a/AdminDashboard/src/Utils/ModelAPI.ts b/AdminDashboard/src/Utils/ModelAPI.ts index 12cedbb33d..1c5116d90c 100644 --- a/AdminDashboard/src/Utils/ModelAPI.ts +++ b/AdminDashboard/src/Utils/ModelAPI.ts @@ -1,8 +1,8 @@ -import Dictionary from 'Common/Types/Dictionary'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; +import Dictionary from "Common/Types/Dictionary"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; export default class AdminModelAPI extends ModelAPI { - public static override getCommonHeaders(): Dictionary { - return {}; - } + public static override getCommonHeaders(): Dictionary { + return {}; + } } diff --git a/AdminDashboard/src/Utils/PageMap.ts b/AdminDashboard/src/Utils/PageMap.ts index a8b3338ef9..cc7e7dfa17 100644 --- a/AdminDashboard/src/Utils/PageMap.ts +++ b/AdminDashboard/src/Utils/PageMap.ts @@ -1,17 +1,17 @@ enum PageMap { - INIT = 'INIT', - HOME = 'HOME', - LOGOUT = 'LOGOUT', - SETTINGS = 'SETTINGS', - USERS = 'USERS', - PROJECTS = 'PROJECTS', + INIT = "INIT", + HOME = "HOME", + LOGOUT = "LOGOUT", + SETTINGS = "SETTINGS", + USERS = "USERS", + PROJECTS = "PROJECTS", - SETTINGS_HOST = 'SETTINGS_HOST', - SETTINGS_SMTP = 'SETTINGS_SMTP', - SETTINGS_CALL_AND_SMS = 'SETTINGS_CALL_AND_SMS', - SETTINGS_PROBES = 'SETTINGS_PROBES', - SETTINGS_AUTHENTICATION = 'SETTINGS_AUTHENTICATION', - SETTINGS_API_KEY = 'SETTINGS_API_KEY', + SETTINGS_HOST = "SETTINGS_HOST", + SETTINGS_SMTP = "SETTINGS_SMTP", + SETTINGS_CALL_AND_SMS = "SETTINGS_CALL_AND_SMS", + SETTINGS_PROBES = "SETTINGS_PROBES", + SETTINGS_AUTHENTICATION = "SETTINGS_AUTHENTICATION", + SETTINGS_API_KEY = "SETTINGS_API_KEY", } export default PageMap; diff --git a/AdminDashboard/src/Utils/RouteMap.ts b/AdminDashboard/src/Utils/RouteMap.ts index 1bb45ac7a6..dd8978321a 100644 --- a/AdminDashboard/src/Utils/RouteMap.ts +++ b/AdminDashboard/src/Utils/RouteMap.ts @@ -1,54 +1,54 @@ -import PageMap from './PageMap'; -import RouteParams from './RouteParams'; -import Route from 'Common/Types/API/Route'; -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; +import PageMap from "./PageMap"; +import RouteParams from "./RouteParams"; +import Route from "Common/Types/API/Route"; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; const RouteMap: Dictionary = { - [PageMap.INIT]: new Route(`/admin`), - [PageMap.HOME]: new Route(`/admin`), - [PageMap.LOGOUT]: new Route(`/admin/logout`), - [PageMap.SETTINGS]: new Route(`/admin/settings/host`), - [PageMap.PROJECTS]: new Route(`/admin/projects`), - [PageMap.USERS]: new Route(`/admin/users`), - [PageMap.SETTINGS_HOST]: new Route(`/admin/settings/host`), - [PageMap.SETTINGS_SMTP]: new Route(`/admin/settings/smtp`), - [PageMap.SETTINGS_CALL_AND_SMS]: new Route(`/admin/settings/call-and-sms`), - [PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`), - [PageMap.SETTINGS_AUTHENTICATION]: new Route( - `/admin/settings/authentication` - ), - [PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`), + [PageMap.INIT]: new Route(`/admin`), + [PageMap.HOME]: new Route(`/admin`), + [PageMap.LOGOUT]: new Route(`/admin/logout`), + [PageMap.SETTINGS]: new Route(`/admin/settings/host`), + [PageMap.PROJECTS]: new Route(`/admin/projects`), + [PageMap.USERS]: new Route(`/admin/users`), + [PageMap.SETTINGS_HOST]: new Route(`/admin/settings/host`), + [PageMap.SETTINGS_SMTP]: new Route(`/admin/settings/smtp`), + [PageMap.SETTINGS_CALL_AND_SMS]: new Route(`/admin/settings/call-and-sms`), + [PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`), + [PageMap.SETTINGS_AUTHENTICATION]: new Route( + `/admin/settings/authentication`, + ), + [PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`), }; export class RouteUtil { - public static populateRouteParams( - route: Route, - props?: { - modelId?: ObjectID; - subModelId?: ObjectID; - } - ): Route { - // populate projectid + public static populateRouteParams( + route: Route, + props?: { + modelId?: ObjectID; + subModelId?: ObjectID; + }, + ): Route { + // populate projectid - const tempRoute: Route = new Route(route.toString()); + const tempRoute: Route = new Route(route.toString()); - if (props && props.modelId) { - route = tempRoute.addRouteParam( - RouteParams.ModelID, - props.modelId.toString() - ); - } - - if (props && props.subModelId) { - route = tempRoute.addRouteParam( - RouteParams.SubModelID, - props.subModelId.toString() - ); - } - - return tempRoute; + if (props && props.modelId) { + route = tempRoute.addRouteParam( + RouteParams.ModelID, + props.modelId.toString(), + ); } + + if (props && props.subModelId) { + route = tempRoute.addRouteParam( + RouteParams.SubModelID, + props.subModelId.toString(), + ); + } + + return tempRoute; + } } export default RouteMap; diff --git a/AdminDashboard/src/Utils/RouteParams.ts b/AdminDashboard/src/Utils/RouteParams.ts index 99412ad48a..47a490ed5b 100644 --- a/AdminDashboard/src/Utils/RouteParams.ts +++ b/AdminDashboard/src/Utils/RouteParams.ts @@ -1,6 +1,6 @@ enum RouteParams { - ModelID = ':id', - SubModelID = ':subModelId', + ModelID = ":id", + SubModelID = ":subModelId", } export default RouteParams; diff --git a/AdminDashboard/webpack.config.js b/AdminDashboard/webpack.config.js index e76e8a035a..bc8ab1d21d 100644 --- a/AdminDashboard/webpack.config.js +++ b/AdminDashboard/webpack.config.js @@ -1,82 +1,84 @@ -require('ts-loader'); -require('file-loader'); -require('style-loader'); -require('css-loader'); -require('sass-loader'); +require("ts-loader"); +require("file-loader"); +require("style-loader"); +require("css-loader"); +require("sass-loader"); const path = require("path"); const webpack = require("webpack"); -const dotenv = require('dotenv'); -const express = require('express'); +const dotenv = require("dotenv"); +const express = require("express"); const readEnvFile = (pathToFile) => { + const parsed = dotenv.config({ path: pathToFile }).parsed; - const parsed = dotenv.config({ path: pathToFile }).parsed; + const env = {}; - const env = {}; + for (const key in parsed) { + env[key] = JSON.stringify(parsed[key]); + } - for (const key in parsed) { - env[key] = JSON.stringify(parsed[key]); - } - - return env; -} + return env; +}; module.exports = { - entry: "./src/Index.tsx", - mode: "development", - output: { - filename: "bundle.js", - path: path.resolve(__dirname, "public", "dist"), - publicPath: "/admin/dist/", + entry: "./src/Index.tsx", + mode: "development", + output: { + filename: "bundle.js", + path: path.resolve(__dirname, "public", "dist"), + publicPath: "/admin/dist/", + }, + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"], + alias: { + react: path.resolve("./node_modules/react"), }, - resolve: { - extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'], - alias: { - react: path.resolve('./node_modules/react'), - } - }, - externals: { - 'react-native-sqlite-storage': 'react-native-sqlite-storage' - }, - plugins: [ - new webpack.DefinePlugin({ - 'process': { - 'env': { - ...readEnvFile('/usr/src/app/dev-env/.env') - } - } - }), - ], - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - use: 'ts-loader' - }, - { - test: /\.s[ac]ss$/i, - use: ['style-loader', 'css-loader', "sass-loader"] - }, - { - test: /\.css$/i, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.(jpe?g|png|gif|svg)$/i, - loader: 'file-loader' - } - ], - }, - devServer: { - historyApiFallback: true, - devMiddleware: { - writeToDisk: true, + }, + externals: { + "react-native-sqlite-storage": "react-native-sqlite-storage", + }, + plugins: [ + new webpack.DefinePlugin({ + process: { + env: { + ...readEnvFile("/usr/src/app/dev-env/.env"), }, - allowedHosts: "all", - setupMiddlewares: (middlewares, devServer) => { - devServer.app.use('/admin/assets', express.static(path.resolve(__dirname, 'public', 'assets'))); - return middlewares; - } + }, + }), + ], + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + use: "ts-loader", + }, + { + test: /\.s[ac]ss$/i, + use: ["style-loader", "css-loader", "sass-loader"], + }, + { + test: /\.css$/i, + use: ["style-loader", "css-loader"], + }, + { + test: /\.(jpe?g|png|gif|svg)$/i, + loader: "file-loader", + }, + ], + }, + devServer: { + historyApiFallback: true, + devMiddleware: { + writeToDisk: true, }, - devtool: 'eval-source-map', -} \ No newline at end of file + allowedHosts: "all", + setupMiddlewares: (middlewares, devServer) => { + devServer.app.use( + "/admin/assets", + express.static(path.resolve(__dirname, "public", "assets")), + ); + return middlewares; + }, + }, + devtool: "eval-source-map", +}; diff --git a/App/FeatureSet/ApiReference/Index.ts b/App/FeatureSet/ApiReference/Index.ts index 3321e60f1e..7250ceb3ec 100755 --- a/App/FeatureSet/ApiReference/Index.ts +++ b/App/FeatureSet/ApiReference/Index.ts @@ -1,89 +1,83 @@ -import AuthenticationServiceHandler from './Service/Authentication'; -import DataTypeServiceHandler from './Service/DataType'; -import ErrorServiceHandler from './Service/Errors'; -import IntroductionServiceHandler from './Service/Introduction'; -import ModelServiceHandler from './Service/Model'; -import PageNotFoundServiceHandler from './Service/PageNotFound'; -import PaginationServiceHandler from './Service/Pagination'; -import PermissionServiceHandler from './Service/Permissions'; -import StatusServiceHandler from './Service/Status'; -import { StaticPath } from './Utils/Config'; -import ResourceUtil, { ModelDocumentation } from './Utils/Resources'; -import Dictionary from 'Common/Types/Dictionary'; -import FeatureSet from 'CommonServer/Types/FeatureSet'; +import AuthenticationServiceHandler from "./Service/Authentication"; +import DataTypeServiceHandler from "./Service/DataType"; +import ErrorServiceHandler from "./Service/Errors"; +import IntroductionServiceHandler from "./Service/Introduction"; +import ModelServiceHandler from "./Service/Model"; +import PageNotFoundServiceHandler from "./Service/PageNotFound"; +import PaginationServiceHandler from "./Service/Pagination"; +import PermissionServiceHandler from "./Service/Permissions"; +import StatusServiceHandler from "./Service/Status"; +import { StaticPath } from "./Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "./Utils/Resources"; +import Dictionary from "Common/Types/Dictionary"; +import FeatureSet from "CommonServer/Types/FeatureSet"; import Express, { - ExpressApplication, - ExpressRequest, - ExpressResponse, - ExpressStatic, -} from 'CommonServer/Utils/Express'; + ExpressApplication, + ExpressRequest, + ExpressResponse, + ExpressStatic, +} from "CommonServer/Utils/Express"; const APIReferenceFeatureSet: FeatureSet = { - init: async (): Promise => { - const ResourceDictionary: Dictionary = - ResourceUtil.getResourceDictionaryByPath(); + init: async (): Promise => { + const ResourceDictionary: Dictionary = + ResourceUtil.getResourceDictionaryByPath(); - const app: ExpressApplication = Express.getExpressApp(); + const app: ExpressApplication = Express.getExpressApp(); - app.use('/reference', ExpressStatic(StaticPath, { maxAge: 2592000 })); + app.use("/reference", ExpressStatic(StaticPath, { maxAge: 2592000 })); - // Index page - app.get( - ['/reference'], - (_req: ExpressRequest, res: ExpressResponse) => { - return res.redirect('/reference/introduction'); - } - ); + // Index page + app.get(["/reference"], (_req: ExpressRequest, res: ExpressResponse) => { + return res.redirect("/reference/introduction"); + }); - app.get( - ['/reference/page-not-found'], - (req: ExpressRequest, res: ExpressResponse) => { - return PageNotFoundServiceHandler.executeResponse(req, res); - } - ); + app.get( + ["/reference/page-not-found"], + (req: ExpressRequest, res: ExpressResponse) => { + return PageNotFoundServiceHandler.executeResponse(req, res); + }, + ); - // All Pages - app.get( - ['/reference/:page'], - (req: ExpressRequest, res: ExpressResponse) => { - const page: string | undefined = req.params['page']; + // All Pages + app.get( + ["/reference/:page"], + (req: ExpressRequest, res: ExpressResponse) => { + const page: string | undefined = req.params["page"]; - if (!page) { - return PageNotFoundServiceHandler.executeResponse(req, res); - } + if (!page) { + return PageNotFoundServiceHandler.executeResponse(req, res); + } - const currentResource: ModelDocumentation | undefined = - ResourceDictionary[page]; + const currentResource: ModelDocumentation | undefined = + ResourceDictionary[page]; - if (req.params['page'] === 'permissions') { - return PermissionServiceHandler.executeResponse(req, res); - } else if (req.params['page'] === 'authentication') { - return AuthenticationServiceHandler.executeResponse( - req, - res - ); - } else if (req.params['page'] === 'pagination') { - return PaginationServiceHandler.executeResponse(req, res); - } else if (req.params['page'] === 'errors') { - return ErrorServiceHandler.executeResponse(req, res); - } else if (req.params['page'] === 'introduction') { - return IntroductionServiceHandler.executeResponse(req, res); - } else if (req.params['page'] === 'status') { - return StatusServiceHandler.executeResponse(req, res); - } else if (req.params['page'] === 'data-types') { - return DataTypeServiceHandler.executeResponse(req, res); - } else if (currentResource) { - return ModelServiceHandler.executeResponse(req, res); - } - // page not found - return PageNotFoundServiceHandler.executeResponse(req, res); - } - ); + if (req.params["page"] === "permissions") { + return PermissionServiceHandler.executeResponse(req, res); + } else if (req.params["page"] === "authentication") { + return AuthenticationServiceHandler.executeResponse(req, res); + } else if (req.params["page"] === "pagination") { + return PaginationServiceHandler.executeResponse(req, res); + } else if (req.params["page"] === "errors") { + return ErrorServiceHandler.executeResponse(req, res); + } else if (req.params["page"] === "introduction") { + return IntroductionServiceHandler.executeResponse(req, res); + } else if (req.params["page"] === "status") { + return StatusServiceHandler.executeResponse(req, res); + } else if (req.params["page"] === "data-types") { + return DataTypeServiceHandler.executeResponse(req, res); + } else if (currentResource) { + return ModelServiceHandler.executeResponse(req, res); + } + // page not found + return PageNotFoundServiceHandler.executeResponse(req, res); + }, + ); - app.get('/reference/*', (req: ExpressRequest, res: ExpressResponse) => { - return PageNotFoundServiceHandler.executeResponse(req, res); - }); - }, + app.get("/reference/*", (req: ExpressRequest, res: ExpressResponse) => { + return PageNotFoundServiceHandler.executeResponse(req, res); + }); + }, }; export default APIReferenceFeatureSet; diff --git a/App/FeatureSet/ApiReference/Service/Authentication.ts b/App/FeatureSet/ApiReference/Service/Authentication.ts index 1f3ffdfa53..75a3270771 100644 --- a/App/FeatureSet/ApiReference/Service/Authentication.ts +++ b/App/FeatureSet/ApiReference/Service/Authentication.ts @@ -1,29 +1,28 @@ -import { ViewsPath } from '../Utils/Config'; -import ResourceUtil, { ModelDocumentation } from '../Utils/Resources'; -import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express'; +import { ViewsPath } from "../Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "../Utils/Resources"; +import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express"; const Resources: Array = ResourceUtil.getResources(); export default class ServiceHandler { - public static async executeResponse( - req: ExpressRequest, - res: ExpressResponse - ): Promise { - let pageTitle: string = ''; - let pageDescription: string = ''; - const page: string | undefined = req.params['page']; - const pageData: any = {}; + public static async executeResponse( + req: ExpressRequest, + res: ExpressResponse, + ): Promise { + let pageTitle: string = ""; + let pageDescription: string = ""; + const page: string | undefined = req.params["page"]; + const pageData: any = {}; - pageTitle = 'Authentication'; - pageDescription = - 'Learn how to authenticate requests with OneUptime API'; + pageTitle = "Authentication"; + pageDescription = "Learn how to authenticate requests with OneUptime API"; - return res.render(`${ViewsPath}/pages/index`, { - page: page, - resources: Resources, - pageTitle: pageTitle, - pageDescription: pageDescription, - pageData: pageData, - }); - } + return res.render(`${ViewsPath}/pages/index`, { + page: page, + resources: Resources, + pageTitle: pageTitle, + pageDescription: pageDescription, + pageData: pageData, + }); + } } diff --git a/App/FeatureSet/ApiReference/Service/DataType.ts b/App/FeatureSet/ApiReference/Service/DataType.ts index cd70bcfe3d..cf6a16aaea 100644 --- a/App/FeatureSet/ApiReference/Service/DataType.ts +++ b/App/FeatureSet/ApiReference/Service/DataType.ts @@ -1,136 +1,126 @@ -import { CodeExamplesPath, ViewsPath } from '../Utils/Config'; -import ResourceUtil, { ModelDocumentation } from '../Utils/Resources'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; -import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express'; -import LocalFile from 'CommonServer/Utils/LocalFile'; +import { CodeExamplesPath, ViewsPath } from "../Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "../Utils/Resources"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; +import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express"; +import LocalFile from "CommonServer/Utils/LocalFile"; const Resources: Array = ResourceUtil.getResources(); export default class ServiceHandler { - public static async executeResponse( - _req: ExpressRequest, - res: ExpressResponse - ): Promise { - const pageData: any = {}; + public static async executeResponse( + _req: ExpressRequest, + res: ExpressResponse, + ): Promise { + const pageData: any = {}; - pageData.selectCode = await LocalCache.getOrSetString( - 'data-type', - 'select', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/Select.md` - ); - } + pageData.selectCode = await LocalCache.getOrSetString( + "data-type", + "select", + async () => { + return await LocalFile.read(`${CodeExamplesPath}/DataTypes/Select.md`); + }, + ); + + pageData.sortCode = await LocalCache.getOrSetString( + "data-type", + "sort", + async () => { + return await LocalFile.read(`${CodeExamplesPath}/DataTypes/Sort.md`); + }, + ); + + pageData.equalToCode = await LocalCache.getOrSetString( + "data-type", + "equal-to", + async () => { + return await LocalFile.read(`${CodeExamplesPath}/DataTypes/EqualTo.md`); + }, + ); + + pageData.equalToOrNullCode = await LocalCache.getOrSetString( + "data-type", + "equal-to-or-null", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/DataTypes/EqualToOrNull.md`, ); + }, + ); - pageData.sortCode = await LocalCache.getOrSetString( - 'data-type', - 'sort', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/Sort.md` - ); - } + pageData.greaterThanCode = await LocalCache.getOrSetString( + "data-type", + "greater-than", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/DataTypes/GreaterThan.md`, ); + }, + ); - pageData.equalToCode = await LocalCache.getOrSetString( - 'data-type', - 'equal-to', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/EqualTo.md` - ); - } + pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString( + "data-type", + "greater-than-or-equal", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/DataTypes/GreaterThanOrEqual.md`, ); + }, + ); - pageData.equalToOrNullCode = await LocalCache.getOrSetString( - 'data-type', - 'equal-to-or-null', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/EqualToOrNull.md` - ); - } + pageData.lessThanCode = await LocalCache.getOrSetString( + "data-type", + "less-than", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/DataTypes/LessThan.md`, ); + }, + ); - pageData.greaterThanCode = await LocalCache.getOrSetString( - 'data-type', - 'greater-than', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/GreaterThan.md` - ); - } + pageData.lessThanOrEqualCode = await LocalCache.getOrSetString( + "data-type", + "less-than-or-equal", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/DataTypes/LessThanOrEqual.md`, ); + }, + ); - pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString( - 'data-type', - 'greater-than-or-equal', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/GreaterThanOrEqual.md` - ); - } + pageData.isNullCode = await LocalCache.getOrSetString( + "data-type", + "is-null", + async () => { + return await LocalFile.read(`${CodeExamplesPath}/DataTypes/IsNull.md`); + }, + ); + + pageData.notNullCode = await LocalCache.getOrSetString( + "data-type", + "not-null", + async () => { + return await LocalFile.read(`${CodeExamplesPath}/DataTypes/NotNull.md`); + }, + ); + + pageData.notEqualToCode = await LocalCache.getOrSetString( + "data-type", + "not-equals", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/DataTypes/NotEqualTo.md`, ); + }, + ); - pageData.lessThanCode = await LocalCache.getOrSetString( - 'data-type', - 'less-than', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/LessThan.md` - ); - } - ); - - pageData.lessThanOrEqualCode = await LocalCache.getOrSetString( - 'data-type', - 'less-than-or-equal', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/LessThanOrEqual.md` - ); - } - ); - - pageData.isNullCode = await LocalCache.getOrSetString( - 'data-type', - 'is-null', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/IsNull.md` - ); - } - ); - - pageData.notNullCode = await LocalCache.getOrSetString( - 'data-type', - 'not-null', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/NotNull.md` - ); - } - ); - - pageData.notEqualToCode = await LocalCache.getOrSetString( - 'data-type', - 'not-equals', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/DataTypes/NotEqualTo.md` - ); - } - ); - - res.status(200); - return res.render(`${ViewsPath}/pages/index`, { - page: 'data-types', - pageTitle: 'Data Types', - pageDescription: - 'Data Types that can be used to interact with OneUptime API', - resources: Resources, - pageData: pageData, - }); - } + res.status(200); + return res.render(`${ViewsPath}/pages/index`, { + page: "data-types", + pageTitle: "Data Types", + pageDescription: + "Data Types that can be used to interact with OneUptime API", + resources: Resources, + pageData: pageData, + }); + } } diff --git a/App/FeatureSet/ApiReference/Service/Errors.ts b/App/FeatureSet/ApiReference/Service/Errors.ts index bb8b9a3261..f5b50b0235 100644 --- a/App/FeatureSet/ApiReference/Service/Errors.ts +++ b/App/FeatureSet/ApiReference/Service/Errors.ts @@ -1,28 +1,28 @@ -import { ViewsPath } from '../Utils/Config'; -import ResourceUtil, { ModelDocumentation } from '../Utils/Resources'; -import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express'; +import { ViewsPath } from "../Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "../Utils/Resources"; +import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express"; const Resources: Array = ResourceUtil.getResources(); export default class ServiceHandler { - public static async executeResponse( - req: ExpressRequest, - res: ExpressResponse - ): Promise { - let pageTitle: string = ''; - let pageDescription: string = ''; - const page: string | undefined = req.params['page']; - const pageData: any = {}; + public static async executeResponse( + req: ExpressRequest, + res: ExpressResponse, + ): Promise { + let pageTitle: string = ""; + let pageDescription: string = ""; + const page: string | undefined = req.params["page"]; + const pageData: any = {}; - pageTitle = 'Errors'; - pageDescription = 'Learn more about how we return errors from API'; + pageTitle = "Errors"; + pageDescription = "Learn more about how we return errors from API"; - return res.render(`${ViewsPath}/pages/index`, { - page: page, - resources: Resources, - pageTitle: pageTitle, - pageDescription: pageDescription, - pageData: pageData, - }); - } + return res.render(`${ViewsPath}/pages/index`, { + page: page, + resources: Resources, + pageTitle: pageTitle, + pageDescription: pageDescription, + pageData: pageData, + }); + } } diff --git a/App/FeatureSet/ApiReference/Service/Introduction.ts b/App/FeatureSet/ApiReference/Service/Introduction.ts index dde1a675d8..1ceeec6061 100644 --- a/App/FeatureSet/ApiReference/Service/Introduction.ts +++ b/App/FeatureSet/ApiReference/Service/Introduction.ts @@ -1,31 +1,31 @@ -import { ViewsPath } from '../Utils/Config'; -import ResourceUtil, { ModelDocumentation } from '../Utils/Resources'; -import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express'; +import { ViewsPath } from "../Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "../Utils/Resources"; +import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express"; const Resources: Array = ResourceUtil.getResources(); const FeaturedResources: Array = - ResourceUtil.getFeaturedResources(); + ResourceUtil.getFeaturedResources(); export default class ServiceHandler { - public static async executeResponse( - req: ExpressRequest, - res: ExpressResponse - ): Promise { - let pageTitle: string = ''; - let pageDescription: string = ''; - const page: string | undefined = req.params['page']; - const pageData: any = {}; + public static async executeResponse( + req: ExpressRequest, + res: ExpressResponse, + ): Promise { + let pageTitle: string = ""; + let pageDescription: string = ""; + const page: string | undefined = req.params["page"]; + const pageData: any = {}; - pageData.featuredResources = FeaturedResources; - pageTitle = 'Introduction'; - pageDescription = 'API Reference for OneUptime'; + pageData.featuredResources = FeaturedResources; + pageTitle = "Introduction"; + pageDescription = "API Reference for OneUptime"; - return res.render(`${ViewsPath}/pages/index`, { - page: page, - resources: Resources, - pageTitle: pageTitle, - pageDescription: pageDescription, - pageData: pageData, - }); - } + return res.render(`${ViewsPath}/pages/index`, { + page: page, + resources: Resources, + pageTitle: pageTitle, + pageDescription: pageDescription, + pageData: pageData, + }); + } } diff --git a/App/FeatureSet/ApiReference/Service/Model.ts b/App/FeatureSet/ApiReference/Service/Model.ts index eb3fe74fec..a7822440a0 100644 --- a/App/FeatureSet/ApiReference/Service/Model.ts +++ b/App/FeatureSet/ApiReference/Service/Model.ts @@ -1,244 +1,238 @@ -import { CodeExamplesPath, ViewsPath } from '../Utils/Config'; -import ResourceUtil, { ModelDocumentation } from '../Utils/Resources'; -import PageNotFoundServiceHandler from './PageNotFound'; -import { AppApiRoute } from 'Common/ServiceRoute'; -import { ColumnAccessControl } from 'Common/Types/BaseDatabase/AccessControl'; -import { getTableColumns } from 'Common/Types/Database/TableColumn'; -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; +import { CodeExamplesPath, ViewsPath } from "../Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "../Utils/Resources"; +import PageNotFoundServiceHandler from "./PageNotFound"; +import { AppApiRoute } from "Common/ServiceRoute"; +import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl"; +import { getTableColumns } from "Common/Types/Database/TableColumn"; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; import Permission, { - PermissionHelper, - PermissionProps, -} from 'Common/Types/Permission'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; -import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express'; -import LocalFile from 'CommonServer/Utils/LocalFile'; + PermissionHelper, + PermissionProps, +} from "Common/Types/Permission"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; +import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express"; +import LocalFile from "CommonServer/Utils/LocalFile"; const Resources: Array = ResourceUtil.getResources(); const ResourceDictionary: Dictionary = - ResourceUtil.getResourceDictionaryByPath(); + ResourceUtil.getResourceDictionaryByPath(); const PermissionDictionary: Dictionary = - PermissionHelper.getAllPermissionPropsAsDictionary(); + PermissionHelper.getAllPermissionPropsAsDictionary(); export default class ServiceHandler { - public static async executeResponse( - req: ExpressRequest, - res: ExpressResponse - ): Promise { - let pageTitle: string = ''; - let pageDescription: string = ''; - let page: string | undefined = req.params['page']; - const pageData: any = {}; + public static async executeResponse( + req: ExpressRequest, + res: ExpressResponse, + ): Promise { + let pageTitle: string = ""; + let pageDescription: string = ""; + let page: string | undefined = req.params["page"]; + const pageData: any = {}; - if (!page) { - return PageNotFoundServiceHandler.executeResponse(req, res); - } - - const currentResource: ModelDocumentation | undefined = - ResourceDictionary[page]; - - if (!currentResource) { - return PageNotFoundServiceHandler.executeResponse(req, res); - } - - // Resource Page. - pageTitle = currentResource.name; - pageDescription = currentResource.description; - - page = 'model'; - - const tableColumns: any = getTableColumns(currentResource.model); - - for (const key in tableColumns) { - const accessControl: ColumnAccessControl | null = - currentResource.model.getColumnAccessControlFor(key); - - if (!accessControl) { - // remove columns with no access - delete tableColumns[key]; - continue; - } - - if ( - accessControl?.create.length === 0 && - accessControl?.read.length === 0 && - accessControl?.update.length === 0 - ) { - // remove columns with no access - delete tableColumns[key]; - continue; - } - - tableColumns[key].permissions = accessControl; - } - - delete tableColumns['deletedAt']; - delete tableColumns['deletedByUserId']; - delete tableColumns['deletedByUser']; - delete tableColumns['version']; - - pageData.title = currentResource.model.singularName; - pageData.description = currentResource.model.tableDescription; - pageData.columns = tableColumns; - pageData.tablePermissions = { - read: currentResource.model.readRecordPermissions.map( - (permission: Permission) => { - return PermissionDictionary[permission]; - } - ), - update: currentResource.model.updateRecordPermissions.map( - (permission: Permission) => { - return PermissionDictionary[permission]; - } - ), - delete: currentResource.model.deleteRecordPermissions.map( - (permission: Permission) => { - return PermissionDictionary[permission]; - } - ), - create: currentResource.model.createRecordPermissions.map( - (permission: Permission) => { - return PermissionDictionary[permission]; - } - ), - }; - - pageData.listRequest = await LocalCache.getOrSetString( - 'model', - 'list-request', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/ListRequest.md` - ); - } - ); - - pageData.itemRequest = await LocalCache.getOrSetString( - 'model', - 'item-request', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/ItemRequest.md` - ); - } - ); - - pageData.itemResponse = await LocalCache.getOrSetString( - 'model', - 'item-response', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/ItemResponse.md` - ); - } - ); - - pageData.countRequest = await LocalCache.getOrSetString( - 'model', - 'count-request', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/CountRequest.md` - ); - } - ); - - pageData.countResponse = await LocalCache.getOrSetString( - 'model', - 'count-response', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/CountResponse.md` - ); - } - ); - - pageData.updateRequest = await LocalCache.getOrSetString( - 'model', - 'update-request', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/UpdateRequest.md` - ); - } - ); - - pageData.updateResponse = await LocalCache.getOrSetString( - 'model', - 'update-response', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/UpdateResponse.md` - ); - } - ); - - pageData.createRequest = await LocalCache.getOrSetString( - 'model', - 'create-request', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/CreateRequest.md` - ); - } - ); - - pageData.createResponse = await LocalCache.getOrSetString( - 'model', - 'create-response', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/CreateResponse.md` - ); - } - ); - - pageData.deleteRequest = await LocalCache.getOrSetString( - 'model', - 'delete-request', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/DeleteRequest.md` - ); - } - ); - - pageData.deleteResponse = await LocalCache.getOrSetString( - 'model', - 'delete-response', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/DeleteResponse.md` - ); - } - ); - - pageData.listResponse = await LocalCache.getOrSetString( - 'model', - 'list-response', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Model/ListResponse.md` - ); - } - ); - - pageData.exampleObjectID = ObjectID.generate(); - - pageData.apiPath = - AppApiRoute.toString() + - currentResource.model.crudApiPath?.toString(); - - pageData.isMasterAdminApiDocs = - currentResource.model.isMasterAdminApiDocs; - - return res.render(`${ViewsPath}/pages/index`, { - page: page, - resources: Resources, - pageTitle: pageTitle, - pageDescription: pageDescription, - pageData: pageData, - }); + if (!page) { + return PageNotFoundServiceHandler.executeResponse(req, res); } + + const currentResource: ModelDocumentation | undefined = + ResourceDictionary[page]; + + if (!currentResource) { + return PageNotFoundServiceHandler.executeResponse(req, res); + } + + // Resource Page. + pageTitle = currentResource.name; + pageDescription = currentResource.description; + + page = "model"; + + const tableColumns: any = getTableColumns(currentResource.model); + + for (const key in tableColumns) { + const accessControl: ColumnAccessControl | null = + currentResource.model.getColumnAccessControlFor(key); + + if (!accessControl) { + // remove columns with no access + delete tableColumns[key]; + continue; + } + + if ( + accessControl?.create.length === 0 && + accessControl?.read.length === 0 && + accessControl?.update.length === 0 + ) { + // remove columns with no access + delete tableColumns[key]; + continue; + } + + tableColumns[key].permissions = accessControl; + } + + delete tableColumns["deletedAt"]; + delete tableColumns["deletedByUserId"]; + delete tableColumns["deletedByUser"]; + delete tableColumns["version"]; + + pageData.title = currentResource.model.singularName; + pageData.description = currentResource.model.tableDescription; + pageData.columns = tableColumns; + pageData.tablePermissions = { + read: currentResource.model.readRecordPermissions.map( + (permission: Permission) => { + return PermissionDictionary[permission]; + }, + ), + update: currentResource.model.updateRecordPermissions.map( + (permission: Permission) => { + return PermissionDictionary[permission]; + }, + ), + delete: currentResource.model.deleteRecordPermissions.map( + (permission: Permission) => { + return PermissionDictionary[permission]; + }, + ), + create: currentResource.model.createRecordPermissions.map( + (permission: Permission) => { + return PermissionDictionary[permission]; + }, + ), + }; + + pageData.listRequest = await LocalCache.getOrSetString( + "model", + "list-request", + async () => { + return await LocalFile.read(`${CodeExamplesPath}/Model/ListRequest.md`); + }, + ); + + pageData.itemRequest = await LocalCache.getOrSetString( + "model", + "item-request", + async () => { + return await LocalFile.read(`${CodeExamplesPath}/Model/ItemRequest.md`); + }, + ); + + pageData.itemResponse = await LocalCache.getOrSetString( + "model", + "item-response", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/ItemResponse.md`, + ); + }, + ); + + pageData.countRequest = await LocalCache.getOrSetString( + "model", + "count-request", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/CountRequest.md`, + ); + }, + ); + + pageData.countResponse = await LocalCache.getOrSetString( + "model", + "count-response", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/CountResponse.md`, + ); + }, + ); + + pageData.updateRequest = await LocalCache.getOrSetString( + "model", + "update-request", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/UpdateRequest.md`, + ); + }, + ); + + pageData.updateResponse = await LocalCache.getOrSetString( + "model", + "update-response", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/UpdateResponse.md`, + ); + }, + ); + + pageData.createRequest = await LocalCache.getOrSetString( + "model", + "create-request", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/CreateRequest.md`, + ); + }, + ); + + pageData.createResponse = await LocalCache.getOrSetString( + "model", + "create-response", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/CreateResponse.md`, + ); + }, + ); + + pageData.deleteRequest = await LocalCache.getOrSetString( + "model", + "delete-request", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/DeleteRequest.md`, + ); + }, + ); + + pageData.deleteResponse = await LocalCache.getOrSetString( + "model", + "delete-response", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/DeleteResponse.md`, + ); + }, + ); + + pageData.listResponse = await LocalCache.getOrSetString( + "model", + "list-response", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Model/ListResponse.md`, + ); + }, + ); + + pageData.exampleObjectID = ObjectID.generate(); + + pageData.apiPath = + AppApiRoute.toString() + currentResource.model.crudApiPath?.toString(); + + pageData.isMasterAdminApiDocs = currentResource.model.isMasterAdminApiDocs; + + return res.render(`${ViewsPath}/pages/index`, { + page: page, + resources: Resources, + pageTitle: pageTitle, + pageDescription: pageDescription, + pageData: pageData, + }); + } } diff --git a/App/FeatureSet/ApiReference/Service/PageNotFound.ts b/App/FeatureSet/ApiReference/Service/PageNotFound.ts index be1c236b0b..506e540992 100644 --- a/App/FeatureSet/ApiReference/Service/PageNotFound.ts +++ b/App/FeatureSet/ApiReference/Service/PageNotFound.ts @@ -1,21 +1,21 @@ -import { ViewsPath } from '../Utils/Config'; -import ResourceUtil, { ModelDocumentation } from '../Utils/Resources'; -import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express'; +import { ViewsPath } from "../Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "../Utils/Resources"; +import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express"; const Resources: Array = ResourceUtil.getResources(); export default class ServiceHandler { - public static async executeResponse( - _req: ExpressRequest, - res: ExpressResponse - ): Promise { - res.status(404); - return res.render(`${ViewsPath}/pages/index`, { - page: '404', - pageTitle: 'Page Not Found', - pageDescription: "Page you're looking for is not found.", - resources: Resources, - pageData: {}, - }); - } + public static async executeResponse( + _req: ExpressRequest, + res: ExpressResponse, + ): Promise { + res.status(404); + return res.render(`${ViewsPath}/pages/index`, { + page: "404", + pageTitle: "Page Not Found", + pageDescription: "Page you're looking for is not found.", + resources: Resources, + pageData: {}, + }); + } } diff --git a/App/FeatureSet/ApiReference/Service/Pagination.ts b/App/FeatureSet/ApiReference/Service/Pagination.ts index 013d32f1e8..e9588e7582 100644 --- a/App/FeatureSet/ApiReference/Service/Pagination.ts +++ b/App/FeatureSet/ApiReference/Service/Pagination.ts @@ -1,50 +1,50 @@ -import { CodeExamplesPath, ViewsPath } from '../Utils/Config'; -import ResourceUtil, { ModelDocumentation } from '../Utils/Resources'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; -import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express'; -import LocalFile from 'CommonServer/Utils/LocalFile'; +import { CodeExamplesPath, ViewsPath } from "../Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "../Utils/Resources"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; +import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express"; +import LocalFile from "CommonServer/Utils/LocalFile"; const Resources: Array = ResourceUtil.getResources(); export default class ServiceHandler { - public static async executeResponse( - req: ExpressRequest, - res: ExpressResponse - ): Promise { - let pageTitle: string = ''; - let pageDescription: string = ''; - const page: string | undefined = req.params['page']; - const pageData: any = {}; + public static async executeResponse( + req: ExpressRequest, + res: ExpressResponse, + ): Promise { + let pageTitle: string = ""; + let pageDescription: string = ""; + const page: string | undefined = req.params["page"]; + const pageData: any = {}; - pageTitle = 'Pagination'; - pageDescription = 'Learn how to paginate requests with OneUptime API'; + pageTitle = "Pagination"; + pageDescription = "Learn how to paginate requests with OneUptime API"; - pageData.responseCode = await LocalCache.getOrSetString( - 'pagination', - 'response', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Pagination/Response.md` - ); - } + pageData.responseCode = await LocalCache.getOrSetString( + "pagination", + "response", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Pagination/Response.md`, ); + }, + ); - pageData.requestCode = await LocalCache.getOrSetString( - 'pagination', - 'request', - async () => { - return await LocalFile.read( - `${CodeExamplesPath}/Pagination/Request.md` - ); - } + pageData.requestCode = await LocalCache.getOrSetString( + "pagination", + "request", + async () => { + return await LocalFile.read( + `${CodeExamplesPath}/Pagination/Request.md`, ); + }, + ); - return res.render(`${ViewsPath}/pages/index`, { - page: page, - resources: Resources, - pageTitle: pageTitle, - pageDescription: pageDescription, - pageData: pageData, - }); - } + return res.render(`${ViewsPath}/pages/index`, { + page: page, + resources: Resources, + pageTitle: pageTitle, + pageDescription: pageDescription, + pageData: pageData, + }); + } } diff --git a/App/FeatureSet/ApiReference/Service/Permissions.ts b/App/FeatureSet/ApiReference/Service/Permissions.ts index b0f93738b9..3e359fc052 100644 --- a/App/FeatureSet/ApiReference/Service/Permissions.ts +++ b/App/FeatureSet/ApiReference/Service/Permissions.ts @@ -1,35 +1,35 @@ -import { ViewsPath } from '../Utils/Config'; -import ResourceUtil, { ModelDocumentation } from '../Utils/Resources'; -import { PermissionHelper, PermissionProps } from 'Common/Types/Permission'; -import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express'; +import { ViewsPath } from "../Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "../Utils/Resources"; +import { PermissionHelper, PermissionProps } from "Common/Types/Permission"; +import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express"; const Resources: Array = ResourceUtil.getResources(); export default class ServiceHandler { - public static async executeResponse( - req: ExpressRequest, - res: ExpressResponse - ): Promise { - let pageTitle: string = ''; - let pageDescription: string = ''; - const page: string | undefined = req.params['page']; - const pageData: any = {}; + public static async executeResponse( + req: ExpressRequest, + res: ExpressResponse, + ): Promise { + let pageTitle: string = ""; + let pageDescription: string = ""; + const page: string | undefined = req.params["page"]; + const pageData: any = {}; - pageTitle = 'Permissions'; - pageDescription = 'Learn how permissions work with OneUptime'; + pageTitle = "Permissions"; + pageDescription = "Learn how permissions work with OneUptime"; - pageData.permissions = PermissionHelper.getAllPermissionProps().filter( - (i: PermissionProps) => { - return i.isAssignableToTenant; - } - ); + pageData.permissions = PermissionHelper.getAllPermissionProps().filter( + (i: PermissionProps) => { + return i.isAssignableToTenant; + }, + ); - return res.render(`${ViewsPath}/pages/index`, { - page: page, - resources: Resources, - pageTitle: pageTitle, - pageDescription: pageDescription, - pageData: pageData, - }); - } + return res.render(`${ViewsPath}/pages/index`, { + page: page, + resources: Resources, + pageTitle: pageTitle, + pageDescription: pageDescription, + pageData: pageData, + }); + } } diff --git a/App/FeatureSet/ApiReference/Service/Status.ts b/App/FeatureSet/ApiReference/Service/Status.ts index 541cf08964..2d73856fc4 100644 --- a/App/FeatureSet/ApiReference/Service/Status.ts +++ b/App/FeatureSet/ApiReference/Service/Status.ts @@ -1,21 +1,21 @@ -import { ViewsPath } from '../Utils/Config'; -import ResourceUtil, { ModelDocumentation } from '../Utils/Resources'; -import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express'; +import { ViewsPath } from "../Utils/Config"; +import ResourceUtil, { ModelDocumentation } from "../Utils/Resources"; +import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express"; const Resources: Array = ResourceUtil.getResources(); export default class ServiceHandler { - public static async executeResponse( - _req: ExpressRequest, - res: ExpressResponse - ): Promise { - res.status(200); - return res.render(`${ViewsPath}/pages/index`, { - page: 'status', - pageTitle: 'Status', - pageDescription: '200 - Success', - resources: Resources, - pageData: {}, - }); - } + public static async executeResponse( + _req: ExpressRequest, + res: ExpressResponse, + ): Promise { + res.status(200); + return res.render(`${ViewsPath}/pages/index`, { + page: "status", + pageTitle: "Status", + pageDescription: "200 - Success", + resources: Resources, + pageData: {}, + }); + } } diff --git a/App/FeatureSet/ApiReference/Utils/Config.ts b/App/FeatureSet/ApiReference/Utils/Config.ts index d9c9a30ac7..0b7160c7ab 100644 --- a/App/FeatureSet/ApiReference/Utils/Config.ts +++ b/App/FeatureSet/ApiReference/Utils/Config.ts @@ -1,4 +1,4 @@ -export const ViewsPath: string = '/usr/src/app/FeatureSet/ApiReference/views'; -export const StaticPath: string = '/usr/src/app/FeatureSet/ApiReference/Static'; +export const ViewsPath: string = "/usr/src/app/FeatureSet/ApiReference/views"; +export const StaticPath: string = "/usr/src/app/FeatureSet/ApiReference/Static"; export const CodeExamplesPath: string = - '/usr/src/app/FeatureSet/ApiReference/CodeExamples'; + "/usr/src/app/FeatureSet/ApiReference/CodeExamples"; diff --git a/App/FeatureSet/ApiReference/Utils/Resources.ts b/App/FeatureSet/ApiReference/Utils/Resources.ts index dabfa2f0e9..c57e287a13 100644 --- a/App/FeatureSet/ApiReference/Utils/Resources.ts +++ b/App/FeatureSet/ApiReference/Utils/Resources.ts @@ -1,74 +1,73 @@ -import BaseModel from 'Common/Models/BaseModel'; -import ArrayUtil from 'Common/Types/ArrayUtil'; -import Dictionary from 'Common/Types/Dictionary'; -import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig'; -import Models from 'Model/Models/Index'; +import BaseModel from "Common/Models/BaseModel"; +import ArrayUtil from "Common/Types/ArrayUtil"; +import Dictionary from "Common/Types/Dictionary"; +import { IsBillingEnabled } from "CommonServer/EnvironmentConfig"; +import Models from "Model/Models/Index"; export interface ModelDocumentation { - name: string; - path: string; - model: BaseModel; - description: string; + name: string; + path: string; + model: BaseModel; + description: string; } export default class ResourceUtil { - public static getResources(): Array { - const resources: Array = Models.filter( - (model: typeof BaseModel) => { - const modelInstance: BaseModel = new model(); - let showDocs: boolean = modelInstance.enableDocumentation; + public static getResources(): Array { + const resources: Array = Models.filter( + (model: typeof BaseModel) => { + const modelInstance: BaseModel = new model(); + let showDocs: boolean = modelInstance.enableDocumentation; - if (modelInstance.isMasterAdminApiDocs && IsBillingEnabled) { - showDocs = false; - } - - return showDocs; - } - ) - .map((model: typeof BaseModel) => { - const modelInstance: BaseModel = new model(); - - return { - name: modelInstance.singularName!, - path: modelInstance.getAPIDocumentationPath(), - model: modelInstance, - description: modelInstance.tableDescription!, - }; - }) - .sort(ArrayUtil.sortByFieldName('name')); - - return resources; - } - - public static getFeaturedResources(): Array { - const featuredResources: Array = [ - 'Monitor', - 'Scheduled Maintenance Event', - 'Status Page', - 'Incident', - 'Team', - 'On-Call Duty', - 'Label', - 'Team Member', - ]; - - return ResourceUtil.getResources().filter( - (resource: ModelDocumentation) => { - return featuredResources.includes(resource.name); - } - ); - } - - public static getResourceDictionaryByPath(): Dictionary { - const dict: Dictionary = {}; - - const resources: Array = - ResourceUtil.getResources(); - - for (const resource of resources) { - dict[resource.path] = resource; + if (modelInstance.isMasterAdminApiDocs && IsBillingEnabled) { + showDocs = false; } - return dict; + return showDocs; + }, + ) + .map((model: typeof BaseModel) => { + const modelInstance: BaseModel = new model(); + + return { + name: modelInstance.singularName!, + path: modelInstance.getAPIDocumentationPath(), + model: modelInstance, + description: modelInstance.tableDescription!, + }; + }) + .sort(ArrayUtil.sortByFieldName("name")); + + return resources; + } + + public static getFeaturedResources(): Array { + const featuredResources: Array = [ + "Monitor", + "Scheduled Maintenance Event", + "Status Page", + "Incident", + "Team", + "On-Call Duty", + "Label", + "Team Member", + ]; + + return ResourceUtil.getResources().filter( + (resource: ModelDocumentation) => { + return featuredResources.includes(resource.name); + }, + ); + } + + public static getResourceDictionaryByPath(): Dictionary { + const dict: Dictionary = {}; + + const resources: Array = ResourceUtil.getResources(); + + for (const resource of resources) { + dict[resource.path] = resource; } + + return dict; + } } diff --git a/App/FeatureSet/BaseAPI/Index.ts b/App/FeatureSet/BaseAPI/Index.ts index de89263eb7..1780bdede7 100644 --- a/App/FeatureSet/BaseAPI/Index.ts +++ b/App/FeatureSet/BaseAPI/Index.ts @@ -1,1262 +1,1217 @@ -import BaseAPI from 'CommonServer/API/BaseAPI'; -import BaseAnalyticsAPI from 'CommonServer/API/BaseAnalyticsAPI'; -import BillingInvoiceAPI from 'CommonServer/API/BillingInvoiceAPI'; -import BillingPaymentMethodAPI from 'CommonServer/API/BillingPaymentMethodAPI'; -import CodeRepositoryAPI from 'CommonServer/API/CodeRepositoryAPI'; -import FileAPI from 'CommonServer/API/FileAPI'; -import GlobalConfigAPI from 'CommonServer/API/GlobalConfigAPI'; -import MonitorGroupAPI from 'CommonServer/API/MonitorGroupAPI'; -import NotificationAPI from 'CommonServer/API/NotificationAPI'; -import Ingestor from 'CommonServer/API/ProbeAPI'; -import ProjectAPI from 'CommonServer/API/ProjectAPI'; -import ProjectSsoAPI from 'CommonServer/API/ProjectSSO'; +import BaseAPI from "CommonServer/API/BaseAPI"; +import BaseAnalyticsAPI from "CommonServer/API/BaseAnalyticsAPI"; +import BillingInvoiceAPI from "CommonServer/API/BillingInvoiceAPI"; +import BillingPaymentMethodAPI from "CommonServer/API/BillingPaymentMethodAPI"; +import CodeRepositoryAPI from "CommonServer/API/CodeRepositoryAPI"; +import FileAPI from "CommonServer/API/FileAPI"; +import GlobalConfigAPI from "CommonServer/API/GlobalConfigAPI"; +import MonitorGroupAPI from "CommonServer/API/MonitorGroupAPI"; +import NotificationAPI from "CommonServer/API/NotificationAPI"; +import Ingestor from "CommonServer/API/ProbeAPI"; +import ProjectAPI from "CommonServer/API/ProjectAPI"; +import ProjectSsoAPI from "CommonServer/API/ProjectSSO"; // Import API -import ResellerPlanAPI from 'CommonServer/API/ResellerPlanAPI'; -import ShortLinkAPI from 'CommonServer/API/ShortLinkAPI'; -import StatusPageAPI from 'CommonServer/API/StatusPageAPI'; -import StatusPageDomainAPI from 'CommonServer/API/StatusPageDomainAPI'; -import StatusPageSubscriberAPI from 'CommonServer/API/StatusPageSubscriberAPI'; -import UserCallAPI from 'CommonServer/API/UserCallAPI'; +import ResellerPlanAPI from "CommonServer/API/ResellerPlanAPI"; +import ShortLinkAPI from "CommonServer/API/ShortLinkAPI"; +import StatusPageAPI from "CommonServer/API/StatusPageAPI"; +import StatusPageDomainAPI from "CommonServer/API/StatusPageDomainAPI"; +import StatusPageSubscriberAPI from "CommonServer/API/StatusPageSubscriberAPI"; +import UserCallAPI from "CommonServer/API/UserCallAPI"; // User Notification methods. -import UserEmailAPI from 'CommonServer/API/UserEmailAPI'; -import UserNotificationLogTimelineAPI from 'CommonServer/API/UserOnCallLogTimelineAPI'; -import UserSMSAPI from 'CommonServer/API/UserSmsAPI'; +import UserEmailAPI from "CommonServer/API/UserEmailAPI"; +import UserNotificationLogTimelineAPI from "CommonServer/API/UserOnCallLogTimelineAPI"; +import UserSMSAPI from "CommonServer/API/UserSmsAPI"; import ApiKeyPermissionService, { - Service as ApiKeyPermissionServiceType, -} from 'CommonServer/Services/ApiKeyPermissionService'; + Service as ApiKeyPermissionServiceType, +} from "CommonServer/Services/ApiKeyPermissionService"; import ApiKeyService, { - Service as ApiKeyServiceType, -} from 'CommonServer/Services/ApiKeyService'; + Service as ApiKeyServiceType, +} from "CommonServer/Services/ApiKeyService"; import CallLogService, { - Service as CallLogServiceType, -} from 'CommonServer/Services/CallLogService'; + Service as CallLogServiceType, +} from "CommonServer/Services/CallLogService"; import CopilotEventService, { - Service as CopilotEventServiceType, -} from 'CommonServer/Services/CopilotEventService'; + Service as CopilotEventServiceType, +} from "CommonServer/Services/CopilotEventService"; import DomainService, { - Service as DomainServiceType, -} from 'CommonServer/Services/DomainService'; + Service as DomainServiceType, +} from "CommonServer/Services/DomainService"; import EmailLogService, { - Service as EmailLogServiceType, -} from 'CommonServer/Services/EmailLogService'; + Service as EmailLogServiceType, +} from "CommonServer/Services/EmailLogService"; import EmailVerificationTokenService, { - Service as EmailVerificationTokenServiceType, -} from 'CommonServer/Services/EmailVerificationTokenService'; + Service as EmailVerificationTokenServiceType, +} from "CommonServer/Services/EmailVerificationTokenService"; import IncidentCustomFieldService, { - Service as IncidentCustomFieldServiceType, -} from 'CommonServer/Services/IncidentCustomFieldService'; + Service as IncidentCustomFieldServiceType, +} from "CommonServer/Services/IncidentCustomFieldService"; import IncidentInternalNoteService, { - Service as IncidentInternalNoteServiceType, -} from 'CommonServer/Services/IncidentInternalNoteService'; + Service as IncidentInternalNoteServiceType, +} from "CommonServer/Services/IncidentInternalNoteService"; import IncidentNoteTemplateService, { - Service as IncidentNoteTemplateServiceType, -} from 'CommonServer/Services/IncidentNoteTemplateService'; + Service as IncidentNoteTemplateServiceType, +} from "CommonServer/Services/IncidentNoteTemplateService"; import IncidentOwnerTeamService, { - Service as IncidentOwnerTeamServiceType, -} from 'CommonServer/Services/IncidentOwnerTeamService'; + Service as IncidentOwnerTeamServiceType, +} from "CommonServer/Services/IncidentOwnerTeamService"; import IncidentOwnerUserService, { - Service as IncidentOwnerUserServiceType, -} from 'CommonServer/Services/IncidentOwnerUserService'; + Service as IncidentOwnerUserServiceType, +} from "CommonServer/Services/IncidentOwnerUserService"; import IncidentPublicNoteService, { - Service as IncidentPublicNoteServiceType, -} from 'CommonServer/Services/IncidentPublicNoteService'; + Service as IncidentPublicNoteServiceType, +} from "CommonServer/Services/IncidentPublicNoteService"; import IncidentService, { - Service as IncidentServiceType, -} from 'CommonServer/Services/IncidentService'; + Service as IncidentServiceType, +} from "CommonServer/Services/IncidentService"; import IncidentSeverityService, { - Service as IncidentSeverityServiceType, -} from 'CommonServer/Services/IncidentSeverityService'; + Service as IncidentSeverityServiceType, +} from "CommonServer/Services/IncidentSeverityService"; import IncidentStateService, { - Service as IncidentStateServiceType, -} from 'CommonServer/Services/IncidentStateService'; + Service as IncidentStateServiceType, +} from "CommonServer/Services/IncidentStateService"; import IncidentStateTimelineService, { - Service as IncidentStateTimelineServiceType, -} from 'CommonServer/Services/IncidentStateTimelineService'; + Service as IncidentStateTimelineServiceType, +} from "CommonServer/Services/IncidentStateTimelineService"; import IncidentTemplateOwnerTeamService, { - Service as IncidentTemplateOwnerTeamServiceType, -} from 'CommonServer/Services/IncidentTemplateOwnerTeamService'; + Service as IncidentTemplateOwnerTeamServiceType, +} from "CommonServer/Services/IncidentTemplateOwnerTeamService"; import IncidentTemplateOwnerUserService, { - Service as IncidentTemplateOwnerUserServiceType, -} from 'CommonServer/Services/IncidentTemplateOwnerUserService'; + Service as IncidentTemplateOwnerUserServiceType, +} from "CommonServer/Services/IncidentTemplateOwnerUserService"; import IncidentTemplateService, { - Service as IncidentTemplateServiceType, -} from 'CommonServer/Services/IncidentTemplateService'; + Service as IncidentTemplateServiceType, +} from "CommonServer/Services/IncidentTemplateService"; import LabelService, { - Service as LabelServiceType, -} from 'CommonServer/Services/LabelService'; + Service as LabelServiceType, +} from "CommonServer/Services/LabelService"; import LogService, { - LogService as LogServiceType, -} from 'CommonServer/Services/LogService'; + LogService as LogServiceType, +} from "CommonServer/Services/LogService"; import MetricService, { - MetricService as MetricServiceType, -} from 'CommonServer/Services/MetricService'; + MetricService as MetricServiceType, +} from "CommonServer/Services/MetricService"; import MonitorCustomFieldService, { - Service as MonitorCustomFieldServiceType, -} from 'CommonServer/Services/MonitorCustomFieldService'; + Service as MonitorCustomFieldServiceType, +} from "CommonServer/Services/MonitorCustomFieldService"; import MonitorGroupOwnerTeamService, { - Service as MonitorGroupOwnerTeamServiceType, -} from 'CommonServer/Services/MonitorGroupOwnerTeamService'; + Service as MonitorGroupOwnerTeamServiceType, +} from "CommonServer/Services/MonitorGroupOwnerTeamService"; import MonitorGroupOwnerUserService, { - Service as MonitorGroupOwnerUserServiceType, -} from 'CommonServer/Services/MonitorGroupOwnerUserService'; + Service as MonitorGroupOwnerUserServiceType, +} from "CommonServer/Services/MonitorGroupOwnerUserService"; import MonitorGroupResourceService, { - Service as MonitorGroupResourceServiceType, -} from 'CommonServer/Services/MonitorGroupResourceService'; + Service as MonitorGroupResourceServiceType, +} from "CommonServer/Services/MonitorGroupResourceService"; import MonitorMetricsByMinuteService, { - MonitorMetricsByMinuteService as MonitorMetricsByMinuteServiceType, -} from 'CommonServer/Services/MonitorMetricsByMinuteService'; + MonitorMetricsByMinuteService as MonitorMetricsByMinuteServiceType, +} from "CommonServer/Services/MonitorMetricsByMinuteService"; import MonitorOwnerTeamService, { - Service as MonitorOwnerTeamServiceType, -} from 'CommonServer/Services/MonitorOwnerTeamService'; + Service as MonitorOwnerTeamServiceType, +} from "CommonServer/Services/MonitorOwnerTeamService"; import MonitorOwnerUserService, { - Service as MonitorOwnerUserServiceType, -} from 'CommonServer/Services/MonitorOwnerUserService'; + Service as MonitorOwnerUserServiceType, +} from "CommonServer/Services/MonitorOwnerUserService"; import MonitorProbeService, { - Service as MonitorProbeServiceType, -} from 'CommonServer/Services/MonitorProbeService'; + Service as MonitorProbeServiceType, +} from "CommonServer/Services/MonitorProbeService"; import MonitorSecretService, { - Service as MonitorSecretServiceType, -} from 'CommonServer/Services/MonitorSecretService'; + Service as MonitorSecretServiceType, +} from "CommonServer/Services/MonitorSecretService"; import MonitorService, { - Service as MonitorServiceType, -} from 'CommonServer/Services/MonitorService'; + Service as MonitorServiceType, +} from "CommonServer/Services/MonitorService"; import MonitorStatusService, { - Service as MonitorStatusServiceType, -} from 'CommonServer/Services/MonitorStatusService'; + Service as MonitorStatusServiceType, +} from "CommonServer/Services/MonitorStatusService"; import MonitorTimelineStatusService, { - Service as MonitorTimelineStatusServiceType, -} from 'CommonServer/Services/MonitorStatusTimelineService'; + Service as MonitorTimelineStatusServiceType, +} from "CommonServer/Services/MonitorStatusTimelineService"; import OnCallDutyPolicyCustomFieldService, { - Service as OnCallDutyPolicyCustomFieldServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyCustomFieldService'; + Service as OnCallDutyPolicyCustomFieldServiceType, +} from "CommonServer/Services/OnCallDutyPolicyCustomFieldService"; import OnCallDutyPolicyEscalationRuleScheduleService, { - Service as OnCallDutyPolicyEscalationRuleScheduleServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyEscalationRuleScheduleService'; + Service as OnCallDutyPolicyEscalationRuleScheduleServiceType, +} from "CommonServer/Services/OnCallDutyPolicyEscalationRuleScheduleService"; import OnCallDutyPolicyEscalationRuleService, { - Service as OnCallDutyPolicyEscalationRuleServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyEscalationRuleService'; + Service as OnCallDutyPolicyEscalationRuleServiceType, +} from "CommonServer/Services/OnCallDutyPolicyEscalationRuleService"; import OnCallDutyPolicyEscalationRuleTeamService, { - Service as OnCallDutyPolicyEscalationRuleTeamServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyEscalationRuleTeamService'; + Service as OnCallDutyPolicyEscalationRuleTeamServiceType, +} from "CommonServer/Services/OnCallDutyPolicyEscalationRuleTeamService"; import OnCallDutyPolicyEscalationRuleUserService, { - Service as OnCallDutyPolicyEscalationRuleUserServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyEscalationRuleUserService'; + Service as OnCallDutyPolicyEscalationRuleUserServiceType, +} from "CommonServer/Services/OnCallDutyPolicyEscalationRuleUserService"; import OnCallDutyPolicyExecutionLogService, { - Service as OnCallDutyPolicyExecutionLogServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyExecutionLogService'; + Service as OnCallDutyPolicyExecutionLogServiceType, +} from "CommonServer/Services/OnCallDutyPolicyExecutionLogService"; import OnCallDutyPolicyExecutionLogTimelineService, { - Service as OnCallDutyPolicyExecutionLogTimelineServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyExecutionLogTimelineService'; + Service as OnCallDutyPolicyExecutionLogTimelineServiceType, +} from "CommonServer/Services/OnCallDutyPolicyExecutionLogTimelineService"; import OnCallDutyPolicyScheduleLayerService, { - Service as OnCallDutyPolicyScheduleLayerServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyScheduleLayerService'; + Service as OnCallDutyPolicyScheduleLayerServiceType, +} from "CommonServer/Services/OnCallDutyPolicyScheduleLayerService"; import OnCallDutyPolicyScheduleLayerUserService, { - Service as OnCallDutyPolicyScheduleLayerUserServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyScheduleLayerUserService'; + Service as OnCallDutyPolicyScheduleLayerUserServiceType, +} from "CommonServer/Services/OnCallDutyPolicyScheduleLayerUserService"; import OnCallDutyPolicyScheduleService, { - Service as OnCallDutyPolicyScheduleServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyScheduleService'; + Service as OnCallDutyPolicyScheduleServiceType, +} from "CommonServer/Services/OnCallDutyPolicyScheduleService"; import OnCallDutyPolicyService, { - Service as OnCallDutyPolicyServiceType, -} from 'CommonServer/Services/OnCallDutyPolicyService'; + Service as OnCallDutyPolicyServiceType, +} from "CommonServer/Services/OnCallDutyPolicyService"; import ProjectCallSMSConfigService, { - Service as ProjectCallSMSConfigServiceType, -} from 'CommonServer/Services/ProjectCallSMSConfigService'; + Service as ProjectCallSMSConfigServiceType, +} from "CommonServer/Services/ProjectCallSMSConfigService"; import ProjectSmtpConfigService, { - Service as ProjectSMTPConfigServiceType, -} from 'CommonServer/Services/ProjectSmtpConfigService'; + Service as ProjectSMTPConfigServiceType, +} from "CommonServer/Services/ProjectSmtpConfigService"; import PromoCodeService, { - Service as PromoCodeServiceType, -} from 'CommonServer/Services/PromoCodeService'; + Service as PromoCodeServiceType, +} from "CommonServer/Services/PromoCodeService"; import ResellerService, { - Service as ResellerServiceType, -} from 'CommonServer/Services/ResellerService'; + Service as ResellerServiceType, +} from "CommonServer/Services/ResellerService"; import ScheduledMaintenanceCustomFieldService, { - Service as ScheduledMaintenanceCustomFieldServiceType, -} from 'CommonServer/Services/ScheduledMaintenanceCustomFieldService'; + Service as ScheduledMaintenanceCustomFieldServiceType, +} from "CommonServer/Services/ScheduledMaintenanceCustomFieldService"; import ScheduledMaintenanceInternalNoteService, { - Service as ScheduledMaintenanceInternalNoteServiceType, -} from 'CommonServer/Services/ScheduledMaintenanceInternalNoteService'; + Service as ScheduledMaintenanceInternalNoteServiceType, +} from "CommonServer/Services/ScheduledMaintenanceInternalNoteService"; import ScheduledMaintenanceNoteTemplateService, { - Service as ScheduledMaintenanceNoteTemplateServiceType, -} from 'CommonServer/Services/ScheduledMaintenanceNoteTemplateService'; + Service as ScheduledMaintenanceNoteTemplateServiceType, +} from "CommonServer/Services/ScheduledMaintenanceNoteTemplateService"; import ScheduledMaintenanceOwnerTeamService, { - Service as ScheduledMaintenanceOwnerTeamServiceType, -} from 'CommonServer/Services/ScheduledMaintenanceOwnerTeamService'; + Service as ScheduledMaintenanceOwnerTeamServiceType, +} from "CommonServer/Services/ScheduledMaintenanceOwnerTeamService"; import ScheduledMaintenanceOwnerUserService, { - Service as ScheduledMaintenanceOwnerUserServiceType, -} from 'CommonServer/Services/ScheduledMaintenanceOwnerUserService'; + Service as ScheduledMaintenanceOwnerUserServiceType, +} from "CommonServer/Services/ScheduledMaintenanceOwnerUserService"; import ScheduledMaintenancePublicNoteService, { - Service as ScheduledMaintenancePublicNoteServiceType, -} from 'CommonServer/Services/ScheduledMaintenancePublicNoteService'; + Service as ScheduledMaintenancePublicNoteServiceType, +} from "CommonServer/Services/ScheduledMaintenancePublicNoteService"; import ScheduledMaintenanceService, { - Service as ScheduledMaintenanceServiceType, -} from 'CommonServer/Services/ScheduledMaintenanceService'; + Service as ScheduledMaintenanceServiceType, +} from "CommonServer/Services/ScheduledMaintenanceService"; import ScheduledMaintenanceStateService, { - Service as ScheduledMaintenanceStateServiceType, -} from 'CommonServer/Services/ScheduledMaintenanceStateService'; + Service as ScheduledMaintenanceStateServiceType, +} from "CommonServer/Services/ScheduledMaintenanceStateService"; import ScheduledMaintenanceStateTimelineService, { - Service as ScheduledMaintenanceStateTimelineServiceType, -} from 'CommonServer/Services/ScheduledMaintenanceStateTimelineService'; + Service as ScheduledMaintenanceStateTimelineServiceType, +} from "CommonServer/Services/ScheduledMaintenanceStateTimelineService"; import ServiceCatalogOwnerTeamService, { - Service as ServiceCatalogOwnerTeamServiceType, -} from 'CommonServer/Services/ServiceCatalogOwnerTeamService'; + Service as ServiceCatalogOwnerTeamServiceType, +} from "CommonServer/Services/ServiceCatalogOwnerTeamService"; import ServiceCatalogOwnerUserService, { - Service as ServiceCatalogOwnerUserServiceType, -} from 'CommonServer/Services/ServiceCatalogOwnerUserService'; + Service as ServiceCatalogOwnerUserServiceType, +} from "CommonServer/Services/ServiceCatalogOwnerUserService"; import ServiceCatalogService, { - Service as ServiceCatalogServiceType, -} from 'CommonServer/Services/ServiceCatalogService'; + Service as ServiceCatalogServiceType, +} from "CommonServer/Services/ServiceCatalogService"; import ServiceRepositoryService, { - Service as ServiceRepositoryType, -} from 'CommonServer/Services/ServiceRepositoryService'; + Service as ServiceRepositoryType, +} from "CommonServer/Services/ServiceRepositoryService"; import ShortLinkService, { - Service as ShortLinkServiceType, -} from 'CommonServer/Services/ShortLinkService'; + Service as ShortLinkServiceType, +} from "CommonServer/Services/ShortLinkService"; import SmsLogService, { - Service as SmsLogServiceType, -} from 'CommonServer/Services/SmsLogService'; + Service as SmsLogServiceType, +} from "CommonServer/Services/SmsLogService"; import SpanService, { - SpanService as SpanServiceType, -} from 'CommonServer/Services/SpanService'; + SpanService as SpanServiceType, +} from "CommonServer/Services/SpanService"; import StatusPageAnnouncementService, { - Service as StatusPageAnnouncementServiceType, -} from 'CommonServer/Services/StatusPageAnnouncementService'; + Service as StatusPageAnnouncementServiceType, +} from "CommonServer/Services/StatusPageAnnouncementService"; import StatusPageCustomFieldService, { - Service as StatusPageCustomFieldServiceType, -} from 'CommonServer/Services/StatusPageCustomFieldService'; + Service as StatusPageCustomFieldServiceType, +} from "CommonServer/Services/StatusPageCustomFieldService"; import StatusPageFooterLinkService, { - Service as StatusPageFooterLinkServiceType, -} from 'CommonServer/Services/StatusPageFooterLinkService'; + Service as StatusPageFooterLinkServiceType, +} from "CommonServer/Services/StatusPageFooterLinkService"; import StatusPageGroupService, { - Service as StatusPageGroupServiceType, -} from 'CommonServer/Services/StatusPageGroupService'; + Service as StatusPageGroupServiceType, +} from "CommonServer/Services/StatusPageGroupService"; import StatusPageHeaderLinkService, { - Service as StatusPageHeaderLinkServiceType, -} from 'CommonServer/Services/StatusPageHeaderLinkService'; + Service as StatusPageHeaderLinkServiceType, +} from "CommonServer/Services/StatusPageHeaderLinkService"; import StatusPageHistoryChartBarColorRuleService, { - Service as StatusPageHistoryChartBarColorRuleServiceType, -} from 'CommonServer/Services/StatusPageHistoryChartBarColorRuleService'; + Service as StatusPageHistoryChartBarColorRuleServiceType, +} from "CommonServer/Services/StatusPageHistoryChartBarColorRuleService"; import StatusPageOwnerTeamService, { - Service as StatusPageOwnerTeamServiceType, -} from 'CommonServer/Services/StatusPageOwnerTeamService'; + Service as StatusPageOwnerTeamServiceType, +} from "CommonServer/Services/StatusPageOwnerTeamService"; import StatusPageOwnerUserService, { - Service as StatusPageOwnerUserServiceType, -} from 'CommonServer/Services/StatusPageOwnerUserService'; + Service as StatusPageOwnerUserServiceType, +} from "CommonServer/Services/StatusPageOwnerUserService"; import StatusPagePrivateUserService, { - Service as StatusPagePrivateUserServiceType, -} from 'CommonServer/Services/StatusPagePrivateUserService'; + Service as StatusPagePrivateUserServiceType, +} from "CommonServer/Services/StatusPagePrivateUserService"; import StatusPageResourceService, { - Service as StatusPageResourceServiceType, -} from 'CommonServer/Services/StatusPageResourceService'; + Service as StatusPageResourceServiceType, +} from "CommonServer/Services/StatusPageResourceService"; import StatusPageSSOService, { - Service as StatusPageSSOServiceType, -} from 'CommonServer/Services/StatusPageSsoService'; + Service as StatusPageSSOServiceType, +} from "CommonServer/Services/StatusPageSsoService"; import TeamMemberService, { - TeamMemberService as TeamMemberServiceType, -} from 'CommonServer/Services/TeamMemberService'; + TeamMemberService as TeamMemberServiceType, +} from "CommonServer/Services/TeamMemberService"; import TeamPermissionService, { - Service as TeamPermissionServiceType, -} from 'CommonServer/Services/TeamPermissionService'; + Service as TeamPermissionServiceType, +} from "CommonServer/Services/TeamPermissionService"; import TeamService, { - Service as TeamServiceType, -} from 'CommonServer/Services/TeamService'; + Service as TeamServiceType, +} from "CommonServer/Services/TeamService"; import TelemetryServiceService, { - Service as TelemetryServiceServiceType, -} from 'CommonServer/Services/TelemetryServiceService'; + Service as TelemetryServiceServiceType, +} from "CommonServer/Services/TelemetryServiceService"; import TelemetryUsageBillingService, { - Service as TelemetryUsageBillingServiceType, -} from 'CommonServer/Services/TelemetryUsageBillingService'; + Service as TelemetryUsageBillingServiceType, +} from "CommonServer/Services/TelemetryUsageBillingService"; import UserNotificationRuleService, { - Service as UserNotificationRuleServiceType, -} from 'CommonServer/Services/UserNotificationRuleService'; + Service as UserNotificationRuleServiceType, +} from "CommonServer/Services/UserNotificationRuleService"; import UserNotificationSettingService, { - Service as UserNotificationSettingServiceType, -} from 'CommonServer/Services/UserNotificationSettingService'; + Service as UserNotificationSettingServiceType, +} from "CommonServer/Services/UserNotificationSettingService"; import UserOnCallLogService, { - Service as UserNotificationLogServiceType, -} from 'CommonServer/Services/UserOnCallLogService'; + Service as UserNotificationLogServiceType, +} from "CommonServer/Services/UserOnCallLogService"; import UserService, { - Service as UserServiceType, -} from 'CommonServer/Services/UserService'; + Service as UserServiceType, +} from "CommonServer/Services/UserService"; import WorkflowLogService, { - Service as WorkflowLogServiceType, -} from 'CommonServer/Services/WorkflowLogService'; + Service as WorkflowLogServiceType, +} from "CommonServer/Services/WorkflowLogService"; import WorkflowService, { - Service as WorkflowServiceType, -} from 'CommonServer/Services/WorkflowService'; + Service as WorkflowServiceType, +} from "CommonServer/Services/WorkflowService"; import WorkflowVariableService, { - Service as WorkflowVariableServiceType, -} from 'CommonServer/Services/WorkflowVariableService'; -import FeatureSet from 'CommonServer/Types/FeatureSet'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import Log from 'Model/AnalyticsModels/Log'; -import Metric from 'Model/AnalyticsModels/Metric'; -import MonitorMetricsByMinute from 'Model/AnalyticsModels/MonitorMetricsByMinute'; -import Span from 'Model/AnalyticsModels/Span'; -import ApiKey from 'Model/Models/ApiKey'; -import ApiKeyPermission from 'Model/Models/ApiKeyPermission'; -import CallLog from 'Model/Models/CallLog'; -import CopilotEvent from 'Model/Models/CopilotEvent'; -import Domain from 'Model/Models/Domain'; -import EmailLog from 'Model/Models/EmailLog'; -import EmailVerificationToken from 'Model/Models/EmailVerificationToken'; -import Incident from 'Model/Models/Incident'; -import IncidentCustomField from 'Model/Models/IncidentCustomField'; -import IncidentInternalNote from 'Model/Models/IncidentInternalNote'; -import IncidentNoteTemplate from 'Model/Models/IncidentNoteTemplate'; -import IncidentOwnerTeam from 'Model/Models/IncidentOwnerTeam'; -import IncidentOwnerUser from 'Model/Models/IncidentOwnerUser'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import IncidentTemplate from 'Model/Models/IncidentTemplate'; -import IncidentTemplateOwnerTeam from 'Model/Models/IncidentTemplateOwnerTeam'; -import IncidentTemplateOwnerUser from 'Model/Models/IncidentTemplateOwnerUser'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; -import MonitorCustomField from 'Model/Models/MonitorCustomField'; -import MonitorGroupOwnerTeam from 'Model/Models/MonitorGroupOwnerTeam'; -import MonitorGroupOwnerUser from 'Model/Models/MonitorGroupOwnerUser'; -import MonitorGroupResource from 'Model/Models/MonitorGroupResource'; -import MonitorOwnerTeam from 'Model/Models/MonitorOwnerTeam'; -import MonitorOwnerUser from 'Model/Models/MonitorOwnerUser'; -import MonitorProbe from 'Model/Models/MonitorProbe'; -import MonitorSecret from 'Model/Models/MonitorSecret'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorTimelineStatus from 'Model/Models/MonitorStatusTimeline'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import OnCallDutyPolicyCustomField from 'Model/Models/OnCallDutyPolicyCustomField'; -import OnCallDutyPolicyEscalationRule from 'Model/Models/OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicyEscalationRuleSchedule from 'Model/Models/OnCallDutyPolicyEscalationRuleSchedule'; -import OnCallDutyPolicyEscalationRuleTeam from 'Model/Models/OnCallDutyPolicyEscalationRuleTeam'; -import OnCallDutyPolicyEscalationRuleUser from 'Model/Models/OnCallDutyPolicyEscalationRuleUser'; -import OnCallDutyPolicyExecutionLog from 'Model/Models/OnCallDutyPolicyExecutionLog'; -import OnCallDutyPolicyExecutionLogTimeline from 'Model/Models/OnCallDutyPolicyExecutionLogTimeline'; -import OnCallDutyPolicySchedule from 'Model/Models/OnCallDutyPolicySchedule'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import OnCallDutyPolicyScheduleLayerUser from 'Model/Models/OnCallDutyPolicyScheduleLayerUser'; -import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig'; -import ProjectSmtpConfig from 'Model/Models/ProjectSmtpConfig'; -import PromoCode from 'Model/Models/PromoCode'; -import Reseller from 'Model/Models/Reseller'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceCustomField from 'Model/Models/ScheduledMaintenanceCustomField'; -import ScheduledMaintenanceInternalNote from 'Model/Models/ScheduledMaintenanceInternalNote'; -import ScheduledMaintenanceNoteTemplate from 'Model/Models/ScheduledMaintenanceNoteTemplate'; -import ScheduledMaintenanceOwnerTeam from 'Model/Models/ScheduledMaintenanceOwnerTeam'; -import ScheduledMaintenanceOwnerUser from 'Model/Models/ScheduledMaintenanceOwnerUser'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import ServiceCatalog from 'Model/Models/ServiceCatalog'; -import ServiceCatalogOwnerTeam from 'Model/Models/ServiceCatalogOwnerTeam'; -import ServiceCatalogOwnerUser from 'Model/Models/ServiceCatalogOwnerUser'; -import ServiceRepository from 'Model/Models/ServiceRepository'; -import ShortLink from 'Model/Models/ShortLink'; -import SmsLog from 'Model/Models/SmsLog'; -import StatusPageAnnouncement from 'Model/Models/StatusPageAnnouncement'; + Service as WorkflowVariableServiceType, +} from "CommonServer/Services/WorkflowVariableService"; +import FeatureSet from "CommonServer/Types/FeatureSet"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import Log from "Model/AnalyticsModels/Log"; +import Metric from "Model/AnalyticsModels/Metric"; +import MonitorMetricsByMinute from "Model/AnalyticsModels/MonitorMetricsByMinute"; +import Span from "Model/AnalyticsModels/Span"; +import ApiKey from "Model/Models/ApiKey"; +import ApiKeyPermission from "Model/Models/ApiKeyPermission"; +import CallLog from "Model/Models/CallLog"; +import CopilotEvent from "Model/Models/CopilotEvent"; +import Domain from "Model/Models/Domain"; +import EmailLog from "Model/Models/EmailLog"; +import EmailVerificationToken from "Model/Models/EmailVerificationToken"; +import Incident from "Model/Models/Incident"; +import IncidentCustomField from "Model/Models/IncidentCustomField"; +import IncidentInternalNote from "Model/Models/IncidentInternalNote"; +import IncidentNoteTemplate from "Model/Models/IncidentNoteTemplate"; +import IncidentOwnerTeam from "Model/Models/IncidentOwnerTeam"; +import IncidentOwnerUser from "Model/Models/IncidentOwnerUser"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import IncidentTemplate from "Model/Models/IncidentTemplate"; +import IncidentTemplateOwnerTeam from "Model/Models/IncidentTemplateOwnerTeam"; +import IncidentTemplateOwnerUser from "Model/Models/IncidentTemplateOwnerUser"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; +import MonitorCustomField from "Model/Models/MonitorCustomField"; +import MonitorGroupOwnerTeam from "Model/Models/MonitorGroupOwnerTeam"; +import MonitorGroupOwnerUser from "Model/Models/MonitorGroupOwnerUser"; +import MonitorGroupResource from "Model/Models/MonitorGroupResource"; +import MonitorOwnerTeam from "Model/Models/MonitorOwnerTeam"; +import MonitorOwnerUser from "Model/Models/MonitorOwnerUser"; +import MonitorProbe from "Model/Models/MonitorProbe"; +import MonitorSecret from "Model/Models/MonitorSecret"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorTimelineStatus from "Model/Models/MonitorStatusTimeline"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import OnCallDutyPolicyCustomField from "Model/Models/OnCallDutyPolicyCustomField"; +import OnCallDutyPolicyEscalationRule from "Model/Models/OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicyEscalationRuleSchedule from "Model/Models/OnCallDutyPolicyEscalationRuleSchedule"; +import OnCallDutyPolicyEscalationRuleTeam from "Model/Models/OnCallDutyPolicyEscalationRuleTeam"; +import OnCallDutyPolicyEscalationRuleUser from "Model/Models/OnCallDutyPolicyEscalationRuleUser"; +import OnCallDutyPolicyExecutionLog from "Model/Models/OnCallDutyPolicyExecutionLog"; +import OnCallDutyPolicyExecutionLogTimeline from "Model/Models/OnCallDutyPolicyExecutionLogTimeline"; +import OnCallDutyPolicySchedule from "Model/Models/OnCallDutyPolicySchedule"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import OnCallDutyPolicyScheduleLayerUser from "Model/Models/OnCallDutyPolicyScheduleLayerUser"; +import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig"; +import ProjectSmtpConfig from "Model/Models/ProjectSmtpConfig"; +import PromoCode from "Model/Models/PromoCode"; +import Reseller from "Model/Models/Reseller"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceCustomField from "Model/Models/ScheduledMaintenanceCustomField"; +import ScheduledMaintenanceInternalNote from "Model/Models/ScheduledMaintenanceInternalNote"; +import ScheduledMaintenanceNoteTemplate from "Model/Models/ScheduledMaintenanceNoteTemplate"; +import ScheduledMaintenanceOwnerTeam from "Model/Models/ScheduledMaintenanceOwnerTeam"; +import ScheduledMaintenanceOwnerUser from "Model/Models/ScheduledMaintenanceOwnerUser"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import ServiceCatalog from "Model/Models/ServiceCatalog"; +import ServiceCatalogOwnerTeam from "Model/Models/ServiceCatalogOwnerTeam"; +import ServiceCatalogOwnerUser from "Model/Models/ServiceCatalogOwnerUser"; +import ServiceRepository from "Model/Models/ServiceRepository"; +import ShortLink from "Model/Models/ShortLink"; +import SmsLog from "Model/Models/SmsLog"; +import StatusPageAnnouncement from "Model/Models/StatusPageAnnouncement"; // Custom Fields API -import StatusPageCustomField from 'Model/Models/StatusPageCustomField'; -import StatusPageFooterLink from 'Model/Models/StatusPageFooterLink'; -import StatusPageGroup from 'Model/Models/StatusPageGroup'; -import StatusPageHeaderLink from 'Model/Models/StatusPageHeaderLink'; -import StatusPageHistoryChartBarColorRule from 'Model/Models/StatusPageHistoryChartBarColorRule'; -import StatusPageOwnerTeam from 'Model/Models/StatusPageOwnerTeam'; -import StatusPageOwnerUser from 'Model/Models/StatusPageOwnerUser'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import StatusPageResource from 'Model/Models/StatusPageResource'; -import StatusPageSSO from 'Model/Models/StatusPageSso'; -import Team from 'Model/Models/Team'; -import TeamMember from 'Model/Models/TeamMember'; -import TeamPermission from 'Model/Models/TeamPermission'; -import TelemetryService from 'Model/Models/TelemetryService'; -import TelemetryUsageBilling from 'Model/Models/TelemetryUsageBilling'; -import User from 'Model/Models/User'; -import UserNotificationRule from 'Model/Models/UserNotificationRule'; -import UserNotificationSetting from 'Model/Models/UserNotificationSetting'; -import UserOnCallLog from 'Model/Models/UserOnCallLog'; -import Workflow from 'Model/Models/Workflow'; -import WorkflowLog from 'Model/Models/WorkflowLog'; -import WorkflowVariable from 'Model/Models/WorkflowVariable'; +import StatusPageCustomField from "Model/Models/StatusPageCustomField"; +import StatusPageFooterLink from "Model/Models/StatusPageFooterLink"; +import StatusPageGroup from "Model/Models/StatusPageGroup"; +import StatusPageHeaderLink from "Model/Models/StatusPageHeaderLink"; +import StatusPageHistoryChartBarColorRule from "Model/Models/StatusPageHistoryChartBarColorRule"; +import StatusPageOwnerTeam from "Model/Models/StatusPageOwnerTeam"; +import StatusPageOwnerUser from "Model/Models/StatusPageOwnerUser"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import StatusPageResource from "Model/Models/StatusPageResource"; +import StatusPageSSO from "Model/Models/StatusPageSso"; +import Team from "Model/Models/Team"; +import TeamMember from "Model/Models/TeamMember"; +import TeamPermission from "Model/Models/TeamPermission"; +import TelemetryService from "Model/Models/TelemetryService"; +import TelemetryUsageBilling from "Model/Models/TelemetryUsageBilling"; +import User from "Model/Models/User"; +import UserNotificationRule from "Model/Models/UserNotificationRule"; +import UserNotificationSetting from "Model/Models/UserNotificationSetting"; +import UserOnCallLog from "Model/Models/UserOnCallLog"; +import Workflow from "Model/Models/Workflow"; +import WorkflowLog from "Model/Models/WorkflowLog"; +import WorkflowVariable from "Model/Models/WorkflowVariable"; const BaseAPIFeatureSet: FeatureSet = { - init: async (): Promise => { - const app: ExpressApplication = Express.getExpressApp(); - - const APP_NAME: string = 'api'; - - //attach api's - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI(User, UserService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAnalyticsAPI( - Log, - LogService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAnalyticsAPI( - Metric, - MetricService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAnalyticsAPI< - MonitorMetricsByMinute, - MonitorMetricsByMinuteServiceType - >(MonitorMetricsByMinute, MonitorMetricsByMinuteService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAnalyticsAPI( - Span, - SpanService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - TelemetryUsageBilling, - TelemetryUsageBillingServiceType - >(TelemetryUsageBilling, TelemetryUsageBillingService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ShortLink, - ShortLinkService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - StatusPageHistoryChartBarColorRule, - StatusPageHistoryChartBarColorRuleServiceType - >( - StatusPageHistoryChartBarColorRule, - StatusPageHistoryChartBarColorRuleService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - MonitorProbe, - MonitorProbeService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - MonitorSecret, - MonitorSecretService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - StatusPageAnnouncement, - StatusPageAnnouncementServiceType - >(StatusPageAnnouncement, StatusPageAnnouncementService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI(Team, TeamService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ServiceRepository, - ServiceRepositoryService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - MonitorGroupOwnerUser, - MonitorGroupOwnerUserServiceType - >(MonitorGroupOwnerUser, MonitorGroupOwnerUserService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ServiceCatalog, - ServiceCatalogService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ServiceCatalogOwnerTeam, - ServiceCatalogOwnerTeamServiceType - >( - ServiceCatalogOwnerTeam, - ServiceCatalogOwnerTeamService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - CopilotEvent, - CopilotEventService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ServiceCatalogOwnerUser, - ServiceCatalogOwnerUserServiceType - >( - ServiceCatalogOwnerUser, - ServiceCatalogOwnerUserService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicySchedule, - OnCallDutyPolicyScheduleServiceType - >( - OnCallDutyPolicySchedule, - OnCallDutyPolicyScheduleService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicyScheduleLayer, - OnCallDutyPolicyScheduleLayerServiceType - >( - OnCallDutyPolicyScheduleLayer, - OnCallDutyPolicyScheduleLayerService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicyScheduleLayerUser, - OnCallDutyPolicyScheduleLayerUserServiceType - >( - OnCallDutyPolicyScheduleLayerUser, - OnCallDutyPolicyScheduleLayerUserService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - MonitorGroupOwnerTeam, - MonitorGroupOwnerTeamServiceType - >(MonitorGroupOwnerTeam, MonitorGroupOwnerTeamService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ProjectCallSMSConfig, - ProjectCallSMSConfigService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - MonitorGroupResource, - MonitorGroupResourceService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - TeamMember, - TeamMemberService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - TeamPermission, - TeamPermissionService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - MonitorStatus, - MonitorStatusService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - IncidentState, - IncidentStateService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ScheduledMaintenanceState, - ScheduledMaintenanceStateServiceType - >( - ScheduledMaintenanceState, - ScheduledMaintenanceStateService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - StatusPageResource, - StatusPageResourceService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - Workflow, - WorkflowService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - TelemetryService, - TelemetryServiceService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - WorkflowVariable, - WorkflowVariableService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - WorkflowLog, - WorkflowLogService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - Domain, - DomainService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - StatusPageGroup, - StatusPageGroupService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - IncidentStateTimeline, - IncidentStateTimelineServiceType - >(IncidentStateTimeline, IncidentStateTimelineService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ScheduledMaintenanceStateTimeline, - ScheduledMaintenanceStateTimelineServiceType - >( - ScheduledMaintenanceStateTimeline, - ScheduledMaintenanceStateTimelineService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - StatusPagePrivateUser, - StatusPagePrivateUserServiceType - >(StatusPagePrivateUser, StatusPagePrivateUserService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - Incident, - IncidentService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ScheduledMaintenance, - ScheduledMaintenanceService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ApiKey, - ApiKeyService - ).getRouter() - ); - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ApiKeyPermission, - ApiKeyPermissionService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - StatusPageHeaderLink, - StatusPageHeaderLinkService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - UserNotificationRule, - UserNotificationRuleService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - StatusPageFooterLink, - StatusPageFooterLinkService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ApiKey, - ApiKeyService - ).getRouter() - ); - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ApiKeyPermission, - ApiKeyPermissionService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - IncidentSeverity, - IncidentSeverityService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - IncidentOwnerUser, - IncidentOwnerUserService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - IncidentOwnerTeam, - IncidentOwnerTeamService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - IncidentTemplate, - IncidentTemplateService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - IncidentNoteTemplate, - IncidentNoteTemplateService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ScheduledMaintenanceNoteTemplate, - ScheduledMaintenanceNoteTemplateServiceType - >( - ScheduledMaintenanceNoteTemplate, - ScheduledMaintenanceNoteTemplateService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - IncidentTemplateOwnerTeam, - IncidentTemplateOwnerTeamServiceType - >( - IncidentTemplateOwnerTeam, - IncidentTemplateOwnerTeamService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - IncidentTemplateOwnerUser, - IncidentTemplateOwnerUserServiceType - >( - IncidentTemplateOwnerUser, - IncidentTemplateOwnerUserService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - MonitorOwnerUser, - MonitorOwnerUserService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - MonitorOwnerTeam, - MonitorOwnerTeamService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ScheduledMaintenanceOwnerUser, - ScheduledMaintenanceOwnerUserServiceType - >( - ScheduledMaintenanceOwnerUser, - ScheduledMaintenanceOwnerUserService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ScheduledMaintenanceOwnerTeam, - ScheduledMaintenanceOwnerTeamServiceType - >( - ScheduledMaintenanceOwnerTeam, - ScheduledMaintenanceOwnerTeamService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - StatusPageOwnerUser, - StatusPageOwnerUserService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - StatusPageOwnerTeam, - StatusPageOwnerTeamService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - Label, - LabelService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - EmailVerificationToken, - EmailVerificationTokenServiceType - >(EmailVerificationToken, EmailVerificationTokenService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - UserOnCallLog, - UserOnCallLogService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - UserNotificationSetting, - UserNotificationSettingServiceType - >( - UserNotificationSetting, - UserNotificationSettingService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicyCustomField, - OnCallDutyPolicyCustomFieldServiceType - >( - OnCallDutyPolicyCustomField, - OnCallDutyPolicyCustomFieldService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - ProjectSmtpConfig, - ProjectSmtpConfigService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - Monitor, - MonitorService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - SmsLog, - SmsLogService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - EmailLog, - EmailLogService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - Reseller, - ResellerService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - CallLog, - CallLogService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - StatusPageSSO, - StatusPageSSOService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - MonitorTimelineStatus, - MonitorTimelineStatusServiceType - >(MonitorTimelineStatus, MonitorTimelineStatusService).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new ShortLinkAPI().getRouter() - ); - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new StatusPageAPI().getRouter() - ); - app.use(`/${APP_NAME.toLocaleLowerCase()}`, new FileAPI().getRouter()); - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new MonitorGroupAPI().getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new StatusPageDomainAPI().getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new ProjectSsoAPI().getRouter() - ); - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new ResellerPlanAPI().getRouter() - ); - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new GlobalConfigAPI().getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new CodeRepositoryAPI().getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new UserNotificationLogTimelineAPI().getRouter() - ); - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new UserCallAPI().getRouter() - ); - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new UserEmailAPI().getRouter() - ); - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new UserSMSAPI().getRouter() - ); - app.use(`/${APP_NAME.toLocaleLowerCase()}`, new Ingestor().getRouter()); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new StatusPageSubscriberAPI().getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BillingPaymentMethodAPI().getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new ProjectAPI().getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BillingInvoiceAPI().getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ScheduledMaintenancePublicNote, - ScheduledMaintenancePublicNoteServiceType - >( - ScheduledMaintenancePublicNote, - ScheduledMaintenancePublicNoteService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ScheduledMaintenanceInternalNote, - ScheduledMaintenanceInternalNoteServiceType - >( - ScheduledMaintenanceInternalNote, - ScheduledMaintenanceInternalNoteService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - IncidentPublicNote, - IncidentPublicNoteService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - IncidentInternalNote, - IncidentInternalNoteService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - OnCallDutyPolicy, - OnCallDutyPolicyService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - ScheduledMaintenanceCustomField, - ScheduledMaintenanceCustomFieldServiceType - >( - ScheduledMaintenanceCustomField, - ScheduledMaintenanceCustomFieldService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicyEscalationRuleUser, - OnCallDutyPolicyEscalationRuleUserServiceType - >( - OnCallDutyPolicyEscalationRuleUser, - OnCallDutyPolicyEscalationRuleUserService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicyEscalationRuleTeam, - OnCallDutyPolicyEscalationRuleTeamServiceType - >( - OnCallDutyPolicyEscalationRuleTeam, - OnCallDutyPolicyEscalationRuleTeamService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicyEscalationRuleSchedule, - OnCallDutyPolicyEscalationRuleScheduleServiceType - >( - OnCallDutyPolicyEscalationRuleSchedule, - OnCallDutyPolicyEscalationRuleScheduleService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicyExecutionLog, - OnCallDutyPolicyExecutionLogServiceType - >( - OnCallDutyPolicyExecutionLog, - OnCallDutyPolicyExecutionLogService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - PromoCode, - PromoCodeService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicyExecutionLogTimeline, - OnCallDutyPolicyExecutionLogTimelineServiceType - >( - OnCallDutyPolicyExecutionLogTimeline, - OnCallDutyPolicyExecutionLogTimelineService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - IncidentCustomField, - IncidentCustomFieldService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - OnCallDutyPolicyEscalationRule, - OnCallDutyPolicyEscalationRuleServiceType - >( - OnCallDutyPolicyEscalationRule, - OnCallDutyPolicyEscalationRuleService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI( - MonitorCustomField, - MonitorCustomFieldService - ).getRouter() - ); - - app.use( - `/${APP_NAME.toLocaleLowerCase()}`, - new BaseAPI< - StatusPageCustomField, - StatusPageCustomFieldServiceType - >(StatusPageCustomField, StatusPageCustomFieldService).getRouter() - ); - - app.use(`/${APP_NAME.toLocaleLowerCase()}`, NotificationAPI); - }, + init: async (): Promise => { + const app: ExpressApplication = Express.getExpressApp(); + + const APP_NAME: string = "api"; + + //attach api's + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI(User, UserService).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAnalyticsAPI(Log, LogService).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAnalyticsAPI( + Metric, + MetricService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAnalyticsAPI< + MonitorMetricsByMinute, + MonitorMetricsByMinuteServiceType + >(MonitorMetricsByMinute, MonitorMetricsByMinuteService).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAnalyticsAPI( + Span, + SpanService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + TelemetryUsageBilling, + TelemetryUsageBillingService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ShortLink, + ShortLinkService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + StatusPageHistoryChartBarColorRule, + StatusPageHistoryChartBarColorRuleServiceType + >( + StatusPageHistoryChartBarColorRule, + StatusPageHistoryChartBarColorRuleService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorProbe, + MonitorProbeService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorSecret, + MonitorSecretService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPageAnnouncement, + StatusPageAnnouncementService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI(Team, TeamService).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ServiceRepository, + ServiceRepositoryService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorGroupOwnerUser, + MonitorGroupOwnerUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ServiceCatalog, + ServiceCatalogService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ServiceCatalogOwnerTeam, + ServiceCatalogOwnerTeamService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + CopilotEvent, + CopilotEventService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ServiceCatalogOwnerUser, + ServiceCatalogOwnerUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicySchedule, + OnCallDutyPolicyScheduleServiceType + >(OnCallDutyPolicySchedule, OnCallDutyPolicyScheduleService).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicyScheduleLayer, + OnCallDutyPolicyScheduleLayerServiceType + >( + OnCallDutyPolicyScheduleLayer, + OnCallDutyPolicyScheduleLayerService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicyScheduleLayerUser, + OnCallDutyPolicyScheduleLayerUserServiceType + >( + OnCallDutyPolicyScheduleLayerUser, + OnCallDutyPolicyScheduleLayerUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorGroupOwnerTeam, + MonitorGroupOwnerTeamService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ProjectCallSMSConfig, + ProjectCallSMSConfigService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorGroupResource, + MonitorGroupResourceService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + TeamMember, + TeamMemberService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + TeamPermission, + TeamPermissionService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorStatus, + MonitorStatusService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentState, + IncidentStateService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + ScheduledMaintenanceState, + ScheduledMaintenanceStateServiceType + >( + ScheduledMaintenanceState, + ScheduledMaintenanceStateService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPageResource, + StatusPageResourceService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + Workflow, + WorkflowService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + TelemetryService, + TelemetryServiceService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + WorkflowVariable, + WorkflowVariableService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + WorkflowLog, + WorkflowLogService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI(Domain, DomainService).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPageGroup, + StatusPageGroupService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentStateTimeline, + IncidentStateTimelineService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + ScheduledMaintenanceStateTimeline, + ScheduledMaintenanceStateTimelineServiceType + >( + ScheduledMaintenanceStateTimeline, + ScheduledMaintenanceStateTimelineService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPagePrivateUser, + StatusPagePrivateUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + Incident, + IncidentService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ScheduledMaintenance, + ScheduledMaintenanceService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI(ApiKey, ApiKeyService).getRouter(), + ); + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ApiKeyPermission, + ApiKeyPermissionService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPageHeaderLink, + StatusPageHeaderLinkService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + UserNotificationRule, + UserNotificationRuleService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPageFooterLink, + StatusPageFooterLinkService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI(ApiKey, ApiKeyService).getRouter(), + ); + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ApiKeyPermission, + ApiKeyPermissionService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentSeverity, + IncidentSeverityService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentOwnerUser, + IncidentOwnerUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentOwnerTeam, + IncidentOwnerTeamService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentTemplate, + IncidentTemplateService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentNoteTemplate, + IncidentNoteTemplateService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + ScheduledMaintenanceNoteTemplate, + ScheduledMaintenanceNoteTemplateServiceType + >( + ScheduledMaintenanceNoteTemplate, + ScheduledMaintenanceNoteTemplateService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + IncidentTemplateOwnerTeam, + IncidentTemplateOwnerTeamServiceType + >( + IncidentTemplateOwnerTeam, + IncidentTemplateOwnerTeamService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + IncidentTemplateOwnerUser, + IncidentTemplateOwnerUserServiceType + >( + IncidentTemplateOwnerUser, + IncidentTemplateOwnerUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorOwnerUser, + MonitorOwnerUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorOwnerTeam, + MonitorOwnerTeamService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + ScheduledMaintenanceOwnerUser, + ScheduledMaintenanceOwnerUserServiceType + >( + ScheduledMaintenanceOwnerUser, + ScheduledMaintenanceOwnerUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + ScheduledMaintenanceOwnerTeam, + ScheduledMaintenanceOwnerTeamServiceType + >( + ScheduledMaintenanceOwnerTeam, + ScheduledMaintenanceOwnerTeamService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPageOwnerUser, + StatusPageOwnerUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPageOwnerTeam, + StatusPageOwnerTeamService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI(Label, LabelService).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + EmailVerificationToken, + EmailVerificationTokenService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + UserOnCallLog, + UserOnCallLogService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + UserNotificationSetting, + UserNotificationSettingService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicyCustomField, + OnCallDutyPolicyCustomFieldServiceType + >( + OnCallDutyPolicyCustomField, + OnCallDutyPolicyCustomFieldService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + ProjectSmtpConfig, + ProjectSmtpConfigService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + Monitor, + MonitorService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI(SmsLog, SmsLogService).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + EmailLog, + EmailLogService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + Reseller, + ResellerService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + CallLog, + CallLogService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPageSSO, + StatusPageSSOService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorTimelineStatus, + MonitorTimelineStatusService, + ).getRouter(), + ); + + app.use(`/${APP_NAME.toLocaleLowerCase()}`, new ShortLinkAPI().getRouter()); + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new StatusPageAPI().getRouter(), + ); + app.use(`/${APP_NAME.toLocaleLowerCase()}`, new FileAPI().getRouter()); + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new MonitorGroupAPI().getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new StatusPageDomainAPI().getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new ProjectSsoAPI().getRouter(), + ); + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new ResellerPlanAPI().getRouter(), + ); + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new GlobalConfigAPI().getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new CodeRepositoryAPI().getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new UserNotificationLogTimelineAPI().getRouter(), + ); + app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserCallAPI().getRouter()); + app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserEmailAPI().getRouter()); + app.use(`/${APP_NAME.toLocaleLowerCase()}`, new UserSMSAPI().getRouter()); + app.use(`/${APP_NAME.toLocaleLowerCase()}`, new Ingestor().getRouter()); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new StatusPageSubscriberAPI().getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BillingPaymentMethodAPI().getRouter(), + ); + + app.use(`/${APP_NAME.toLocaleLowerCase()}`, new ProjectAPI().getRouter()); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BillingInvoiceAPI().getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + ScheduledMaintenancePublicNote, + ScheduledMaintenancePublicNoteServiceType + >( + ScheduledMaintenancePublicNote, + ScheduledMaintenancePublicNoteService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + ScheduledMaintenanceInternalNote, + ScheduledMaintenanceInternalNoteServiceType + >( + ScheduledMaintenanceInternalNote, + ScheduledMaintenanceInternalNoteService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentPublicNote, + IncidentPublicNoteService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentInternalNote, + IncidentInternalNoteService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + OnCallDutyPolicy, + OnCallDutyPolicyService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + ScheduledMaintenanceCustomField, + ScheduledMaintenanceCustomFieldServiceType + >( + ScheduledMaintenanceCustomField, + ScheduledMaintenanceCustomFieldService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicyEscalationRuleUser, + OnCallDutyPolicyEscalationRuleUserServiceType + >( + OnCallDutyPolicyEscalationRuleUser, + OnCallDutyPolicyEscalationRuleUserService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicyEscalationRuleTeam, + OnCallDutyPolicyEscalationRuleTeamServiceType + >( + OnCallDutyPolicyEscalationRuleTeam, + OnCallDutyPolicyEscalationRuleTeamService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicyEscalationRuleSchedule, + OnCallDutyPolicyEscalationRuleScheduleServiceType + >( + OnCallDutyPolicyEscalationRuleSchedule, + OnCallDutyPolicyEscalationRuleScheduleService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicyExecutionLog, + OnCallDutyPolicyExecutionLogServiceType + >( + OnCallDutyPolicyExecutionLog, + OnCallDutyPolicyExecutionLogService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + PromoCode, + PromoCodeService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicyExecutionLogTimeline, + OnCallDutyPolicyExecutionLogTimelineServiceType + >( + OnCallDutyPolicyExecutionLogTimeline, + OnCallDutyPolicyExecutionLogTimelineService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + IncidentCustomField, + IncidentCustomFieldService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI< + OnCallDutyPolicyEscalationRule, + OnCallDutyPolicyEscalationRuleServiceType + >( + OnCallDutyPolicyEscalationRule, + OnCallDutyPolicyEscalationRuleService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + MonitorCustomField, + MonitorCustomFieldService, + ).getRouter(), + ); + + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + StatusPageCustomField, + StatusPageCustomFieldService, + ).getRouter(), + ); + + app.use(`/${APP_NAME.toLocaleLowerCase()}`, NotificationAPI); + }, }; export default BaseAPIFeatureSet; diff --git a/App/FeatureSet/Docs/Index.ts b/App/FeatureSet/Docs/Index.ts index a4f15ca6ff..b548232aca 100755 --- a/App/FeatureSet/Docs/Index.ts +++ b/App/FeatureSet/Docs/Index.ts @@ -1,93 +1,85 @@ -import { ContentPath, StaticPath, ViewsPath } from './Utils/Config'; -import DocsNav, { NavGroup, NavLink } from './Utils/Nav'; -import DocsRender from './Utils/Render'; -import FeatureSet from 'CommonServer/Types/FeatureSet'; +import { ContentPath, StaticPath, ViewsPath } from "./Utils/Config"; +import DocsNav, { NavGroup, NavLink } from "./Utils/Nav"; +import DocsRender from "./Utils/Render"; +import FeatureSet from "CommonServer/Types/FeatureSet"; import Express, { - ExpressApplication, - ExpressRequest, - ExpressResponse, - ExpressStatic, -} from 'CommonServer/Utils/Express'; -import LocalFile from 'CommonServer/Utils/LocalFile'; -import logger from 'CommonServer/Utils/Logger'; -import 'ejs'; + ExpressApplication, + ExpressRequest, + ExpressResponse, + ExpressStatic, +} from "CommonServer/Utils/Express"; +import LocalFile from "CommonServer/Utils/LocalFile"; +import logger from "CommonServer/Utils/Logger"; +import "ejs"; const DocsFeatureSet: FeatureSet = { - init: async (): Promise => { - const app: ExpressApplication = Express.getExpressApp(); + init: async (): Promise => { + const app: ExpressApplication = Express.getExpressApp(); - app.get('/docs', (_req: ExpressRequest, res: ExpressResponse) => { - res.redirect('/docs/introduction/getting-started'); - }); + app.get("/docs", (_req: ExpressRequest, res: ExpressResponse) => { + res.redirect("/docs/introduction/getting-started"); + }); - app.get( - '/docs/:categorypath/:pagepath', - async (_req: ExpressRequest, res: ExpressResponse) => { - try { - const fullPath: string = - `${_req.params['categorypath']}/${_req.params['pagepath']}`.toLowerCase(); + app.get( + "/docs/:categorypath/:pagepath", + async (_req: ExpressRequest, res: ExpressResponse) => { + try { + const fullPath: string = + `${_req.params["categorypath"]}/${_req.params["pagepath"]}`.toLowerCase(); - // read file from Content folder. - let contentInMarkdown: string = await LocalFile.read( - `${ContentPath}/${fullPath}.md` - ); + // read file from Content folder. + let contentInMarkdown: string = await LocalFile.read( + `${ContentPath}/${fullPath}.md`, + ); - // remove first line from content because we dont want to show title in content. Title is already in nav. + // remove first line from content because we dont want to show title in content. Title is already in nav. - contentInMarkdown = contentInMarkdown - .split('\n') - .slice(1) - .join('\n'); + contentInMarkdown = contentInMarkdown.split("\n").slice(1).join("\n"); - const renderedContent: string = await DocsRender.render( - contentInMarkdown - ); + const renderedContent: string = + await DocsRender.render(contentInMarkdown); - const currentCategory: NavGroup | undefined = DocsNav.find( - (category: NavGroup) => { - return category.links.find((link: NavLink) => { - return link.url - .toLocaleLowerCase() - .includes(fullPath); - }); - } - ); + const currentCategory: NavGroup | undefined = DocsNav.find( + (category: NavGroup) => { + return category.links.find((link: NavLink) => { + return link.url.toLocaleLowerCase().includes(fullPath); + }); + }, + ); - const currrentNavLink: NavLink | undefined = - currentCategory?.links.find((link: NavLink) => { - return link.url - .toLocaleLowerCase() - .includes(fullPath); - }); + const currrentNavLink: NavLink | undefined = + currentCategory?.links.find((link: NavLink) => { + return link.url.toLocaleLowerCase().includes(fullPath); + }); - if (!currentCategory || !currrentNavLink) { - // render not found. + if (!currentCategory || !currrentNavLink) { + // render not found. - res.status(404); - return res.render(`${ViewsPath}/NotFound`, { - nav: DocsNav, - }); - } + res.status(404); + return res.render(`${ViewsPath}/NotFound`, { + nav: DocsNav, + }); + } - res.render(`${ViewsPath}/Index`, { - nav: DocsNav, - content: renderedContent, - category: currentCategory, - link: currrentNavLink, - githubPath: fullPath, - }); - } catch (err) { - logger.error(err); - res.status(500); - return res.render(`${ViewsPath}/ServerError`, { - nav: DocsNav, - }); - } - } - ); + res.render(`${ViewsPath}/Index`, { + nav: DocsNav, + content: renderedContent, + category: currentCategory, + link: currrentNavLink, + githubPath: fullPath, + }); + } catch (err) { + logger.error(err); + res.status(500); + return res.render(`${ViewsPath}/ServerError`, { + nav: DocsNav, + }); + } + }, + ); - app.use('/docs/static', ExpressStatic(StaticPath)); - }, + app.use("/docs/static", ExpressStatic(StaticPath)); + }, }; export default DocsFeatureSet; diff --git a/App/FeatureSet/Docs/Utils/Config.ts b/App/FeatureSet/Docs/Utils/Config.ts index 0988324d26..6fe8fb36d4 100644 --- a/App/FeatureSet/Docs/Utils/Config.ts +++ b/App/FeatureSet/Docs/Utils/Config.ts @@ -1,3 +1,3 @@ -export const ViewsPath: string = '/usr/src/app/FeatureSet/Docs/Views'; -export const StaticPath: string = '/usr/src/app/FeatureSet/Docs/Static'; -export const ContentPath: string = '/usr/src/app/FeatureSet/Docs/Content'; +export const ViewsPath: string = "/usr/src/app/FeatureSet/Docs/Views"; +export const StaticPath: string = "/usr/src/app/FeatureSet/Docs/Static"; +export const ContentPath: string = "/usr/src/app/FeatureSet/Docs/Content"; diff --git a/App/FeatureSet/Docs/Utils/Nav.ts b/App/FeatureSet/Docs/Utils/Nav.ts index a65739fd54..325c24b761 100644 --- a/App/FeatureSet/Docs/Utils/Nav.ts +++ b/App/FeatureSet/Docs/Utils/Nav.ts @@ -1,75 +1,75 @@ export interface NavLink { - title: string; - url: string; + title: string; + url: string; } export interface NavGroup { - title: string; - links: NavLink[]; + title: string; + links: NavLink[]; } const DocsNav: NavGroup[] = [ - { - title: 'Introduction', - links: [ - { - title: 'Getting Started', - url: '/docs/introduction/getting-started', - }, - ], - }, - { - title: 'Installation', - links: [ - { - title: 'Local Development', - url: '/docs/installation/local-development', - }, - { - title: 'Docker Compose', - url: '/docs/installation/docker-compose', - }, - { - title: 'Kubernetes and Helm', - url: 'https://artifacthub.io/packages/helm/oneuptime/oneuptime', - }, - ], - }, - { - title: 'Monitor', - links: [ - { - title: 'Custom Code Monitor', - url: '/docs/monitor/custom-code-monitor', - }, - { - title: 'Synthetic Monitor', - url: '/docs/monitor/synthetic-monitor', - }, - { - title: 'JavaScript Expressions', - url: '/docs/monitor/javascript-expression', - }, - { - title: 'Monitor Secrets', - url: '/docs/monitor/monitor-secrets', - }, - ], - }, - { - title: 'Probe', - links: [ - { title: 'Custom Probes', url: '/docs/probe/custom-probe' }, - { title: 'IP Addresses', url: '/docs/probe/ip-address' }, - ], - }, - { - title: 'Telemetry', - links: [ - { title: 'OpenTelemetry', url: '/docs/telemetry/open-telemetry' }, - { title: 'Fluentd', url: '/docs/telemetry/fluentd' }, - ], - }, + { + title: "Introduction", + links: [ + { + title: "Getting Started", + url: "/docs/introduction/getting-started", + }, + ], + }, + { + title: "Installation", + links: [ + { + title: "Local Development", + url: "/docs/installation/local-development", + }, + { + title: "Docker Compose", + url: "/docs/installation/docker-compose", + }, + { + title: "Kubernetes and Helm", + url: "https://artifacthub.io/packages/helm/oneuptime/oneuptime", + }, + ], + }, + { + title: "Monitor", + links: [ + { + title: "Custom Code Monitor", + url: "/docs/monitor/custom-code-monitor", + }, + { + title: "Synthetic Monitor", + url: "/docs/monitor/synthetic-monitor", + }, + { + title: "JavaScript Expressions", + url: "/docs/monitor/javascript-expression", + }, + { + title: "Monitor Secrets", + url: "/docs/monitor/monitor-secrets", + }, + ], + }, + { + title: "Probe", + links: [ + { title: "Custom Probes", url: "/docs/probe/custom-probe" }, + { title: "IP Addresses", url: "/docs/probe/ip-address" }, + ], + }, + { + title: "Telemetry", + links: [ + { title: "OpenTelemetry", url: "/docs/telemetry/open-telemetry" }, + { title: "Fluentd", url: "/docs/telemetry/fluentd" }, + ], + }, ]; export default DocsNav; diff --git a/App/FeatureSet/Docs/Utils/Render.ts b/App/FeatureSet/Docs/Utils/Render.ts index a96642e5e2..567002b52c 100644 --- a/App/FeatureSet/Docs/Utils/Render.ts +++ b/App/FeatureSet/Docs/Utils/Render.ts @@ -1,10 +1,7 @@ -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; export default class DocsRender { - public static async render(markdownContent: string): Promise { - return Markdown.convertToHTML( - markdownContent, - MarkdownContentType.Docs - ); - } + public static async render(markdownContent: string): Promise { + return Markdown.convertToHTML(markdownContent, MarkdownContentType.Docs); + } } diff --git a/App/FeatureSet/Home/API/BlogAPI.ts b/App/FeatureSet/Home/API/BlogAPI.ts index 7785a68903..c0cd659322 100644 --- a/App/FeatureSet/Home/API/BlogAPI.ts +++ b/App/FeatureSet/Home/API/BlogAPI.ts @@ -1,89 +1,88 @@ -import BlogPostUtil, { BlogPost, BlogPostHeader } from '../Utils/BlogPost'; -import { ViewsPath } from '../Utils/Config'; -import NotFoundUtil from '../Utils/NotFound'; -import ServerErrorUtil from '../Utils/ServerError'; -import Text from 'Common/Types/Text'; +import BlogPostUtil, { BlogPost, BlogPostHeader } from "../Utils/BlogPost"; +import { ViewsPath } from "../Utils/Config"; +import NotFoundUtil from "../Utils/NotFound"; +import ServerErrorUtil from "../Utils/ServerError"; +import Text from "Common/Types/Text"; import Express, { - ExpressApplication, - ExpressRequest, - ExpressResponse, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; + ExpressApplication, + ExpressRequest, + ExpressResponse, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; const app: ExpressApplication = Express.getExpressApp(); app.get( - '/blog/post/:file', - async (req: ExpressRequest, res: ExpressResponse) => { - try { - const fileName: string = req.params['file'] as string; + "/blog/post/:file", + async (req: ExpressRequest, res: ExpressResponse) => { + try { + const fileName: string = req.params["file"] as string; - const blogPost: BlogPost | null = await BlogPostUtil.getBlogPost( - fileName - ); + const blogPost: BlogPost | null = + await BlogPostUtil.getBlogPost(fileName); - if (!blogPost) { - return NotFoundUtil.renderNotFound(res); - } + if (!blogPost) { + return NotFoundUtil.renderNotFound(res); + } - res.render(`${ViewsPath}/Blog/Post`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - requestDemoCta: false, - blogPost: blogPost, - }); - } catch (e) { - logger.error(e); - return ServerErrorUtil.renderServerError(res); - } + res.render(`${ViewsPath}/Blog/Post`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + requestDemoCta: false, + blogPost: blogPost, + }); + } catch (e) { + logger.error(e); + return ServerErrorUtil.renderServerError(res); } + }, ); // List all blog posts with tag app.get( - '/blog/tag/:tagName', - async (req: ExpressRequest, res: ExpressResponse) => { - try { - const tagName: string = req.params['tagName'] as string; + "/blog/tag/:tagName", + async (req: ExpressRequest, res: ExpressResponse) => { + try { + const tagName: string = req.params["tagName"] as string; - const blogPosts: Array = - await BlogPostUtil.getBlogPostList(tagName); + const blogPosts: Array = + await BlogPostUtil.getBlogPostList(tagName); - res.render(`${ViewsPath}/Blog/ListByTag`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - requestDemoCta: false, - blogPosts: blogPosts, - tagName: Text.fromDashesToPascalCase(tagName), - }); - } catch (e) { - logger.error(e); - return ServerErrorUtil.renderServerError(res); - } + res.render(`${ViewsPath}/Blog/ListByTag`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + requestDemoCta: false, + blogPosts: blogPosts, + tagName: Text.fromDashesToPascalCase(tagName), + }); + } catch (e) { + logger.error(e); + return ServerErrorUtil.renderServerError(res); } + }, ); // main blog page -app.get('/blog', async (_req: ExpressRequest, res: ExpressResponse) => { - try { - const blogPosts: Array = - await BlogPostUtil.getBlogPostList(); +app.get("/blog", async (_req: ExpressRequest, res: ExpressResponse) => { + try { + const blogPosts: Array = + await BlogPostUtil.getBlogPostList(); - res.render(`${ViewsPath}/Blog/List`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - requestDemoCta: false, - blogPosts: blogPosts, - }); - } catch (e) { - logger.error(e); - return ServerErrorUtil.renderServerError(res); - } + res.render(`${ViewsPath}/Blog/List`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + requestDemoCta: false, + blogPosts: blogPosts, + }); + } catch (e) { + logger.error(e); + return ServerErrorUtil.renderServerError(res); + } }); diff --git a/App/FeatureSet/Home/Index.ts b/App/FeatureSet/Home/Index.ts index d7496b0183..7481c84fff 100755 --- a/App/FeatureSet/Home/Index.ts +++ b/App/FeatureSet/Home/Index.ts @@ -1,1068 +1,1020 @@ // improt API -import './API/BlogAPI'; -import { StaticPath, ViewsPath } from './Utils/Config'; -import NotFoundUtil from './Utils/NotFound'; -import ProductCompare, { Product } from './Utils/ProductCompare'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import { JSONObject } from 'Common/Types/JSON'; -import API from 'Common/Utils/API'; -import FeatureSet from 'CommonServer/Types/FeatureSet'; +import "./API/BlogAPI"; +import { StaticPath, ViewsPath } from "./Utils/Config"; +import NotFoundUtil from "./Utils/NotFound"; +import ProductCompare, { Product } from "./Utils/ProductCompare"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import { JSONObject } from "Common/Types/JSON"; +import API from "Common/Utils/API"; +import FeatureSet from "CommonServer/Types/FeatureSet"; import Express, { - ExpressApplication, - ExpressRequest, - ExpressResponse, - ExpressStatic, -} from 'CommonServer/Utils/Express'; -import 'ejs'; -import builder from 'xmlbuilder2'; -import { XMLBuilder } from 'xmlbuilder2/lib/interfaces'; + ExpressApplication, + ExpressRequest, + ExpressResponse, + ExpressStatic, +} from "CommonServer/Utils/Express"; +import "ejs"; +import builder from "xmlbuilder2"; +import { XMLBuilder } from "xmlbuilder2/lib/interfaces"; const HomeFeatureSet: FeatureSet = { - init: async (): Promise => { - const app: ExpressApplication = Express.getExpressApp(); + init: async (): Promise => { + const app: ExpressApplication = Express.getExpressApp(); - //Routes - app.get('/', (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/index`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - requestDemoCta: false, - }); + //Routes + app.get("/", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/index`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + requestDemoCta: false, + }); + }); + + app.get( + "/infrastructure-agent/install.sh", + (_req: ExpressRequest, res: ExpressResponse) => { + // fetch the file from https://raw.githubusercontent.com/oneuptime/infrastructure-agent/release/Scripts/Install/Linux.sh and send it as response + res.redirect( + "https://raw.githubusercontent.com/OneUptime/oneuptime/release/InfrastructureAgent/Scripts/Install/Linux.sh", + ); + }, + ); + + app.get("/support", async (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/support`); + }); + + app.get("/pricing", (_req: ExpressRequest, res: ExpressResponse) => { + const pricing: Array = [ + { + name: "Status Page", + data: [ + { + name: "Public Status Page", + plans: { + free: "Unlimited", + growth: "Unlimited", + scale: "Unlimited", + enterprise: "Unlimited", + }, + }, + { + name: "Subscribers", + plans: { + free: "Unlimited", + growth: "Unlimited", + scale: "Unlimited", + enterprise: "Unlimited", + }, + }, + { + name: "Custom Branding", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "SSL Certificate", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Custom Domain", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Private Status Page", + plans: { + free: false, + growth: "Unlimited", + scale: "Unlimited", + enterprise: "Unlimited", + }, + }, + { + name: "Private Status Page Users", + plans: { + free: false, + growth: "Unlimited", + scale: "Unlimited", + enterprise: "Unlimited", + }, + }, + ], + }, + { + name: "Incident Management", + data: [ + { + name: "Basic Incident Management", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Public Postmortem Notes", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Private Postmortem Notes", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Incident Workflows", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Custom Incident State", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Custom Incident Severity", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + ], + }, + { + name: "Monitoring", + data: [ + { + name: "Static / Manual Monitors", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Website Monitoring", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "API Monitoring", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + + { + name: "IPv4 Monitoring", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "IPv6 Monitoring", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Inbound Webhook / Heartbeat Monitoring", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "VM or Server Monitoring", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Container Monitoring", + plans: { + free: "Coming Soon", + growth: "Coming Soon", + scale: "Coming Soon", + enterprise: "Coming Soon", + }, + }, + + { + name: "Network Monitoring", + plans: { + free: "Coming Soon", + growth: "Coming Soon", + scale: "Coming Soon", + enterprise: "Coming Soon", + }, + }, + { + name: "Kubernetes Cluster Monitoring", + plans: { + free: "Coming Soon", + growth: "Coming Soon", + scale: "Coming Soon", + enterprise: "Coming Soon", + }, + }, + ], + }, + { + name: "On-Call and Alerts", + data: [ + { + name: "Phone Alerts", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "SMS Alerts", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Email Alerts", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "On-Call Escalation", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Advanced Workflows", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "On-Call Rotation", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Logs and Events", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Webhook Alerts", + plans: { + free: "Coming Soon", + growth: "Coming Soon", + scale: "Coming Soon", + enterprise: "Coming Soon", + }, + }, + + { + name: "Vacation and OOO Policy", + plans: { + free: "Coming Soon", + growth: "Coming Soon", + scale: "Coming Soon", + enterprise: "Coming Soon", + }, + }, + + { + name: "On-Call Pay", + plans: { + free: "Coming Soon", + growth: "Coming Soon", + scale: "Coming Soon", + enterprise: "Coming Soon", + }, + }, + + { + name: "Reports", + plans: { + free: "Coming Soon", + growth: "Coming Soon", + scale: "Coming Soon", + enterprise: "Coming Soon", + }, + }, + ], + }, + { + name: "Support and More", + data: [ + { + name: "Support", + plans: { + free: "Community Support", + growth: "Email Support", + scale: "Email and Chat Support", + enterprise: "Email, Chat, Phone Support", + }, + }, + { + name: "Support SLA", + plans: { + free: false, + growth: "1 business day", + scale: "6 hours", + enterprise: "1 hour priority", + }, + }, + { + name: "Service SLA", + plans: { + free: false, + growth: "99.90%", + scale: "99.95%", + enterprise: "99.99%", + }, + }, + ], + }, + { + name: "Advanced Features", + data: [ + { + name: "API Access", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + { + name: "Advanced Workflows", + plans: { + free: false, + growth: "500 Runs / month", + scale: "2000 Runs /month", + enterprise: "Unlimited Runs", + }, + }, + { + name: "5000+ Integrations", + plans: { + free: false, + growth: true, + scale: true, + enterprise: true, + }, + }, + ], + }, + { + name: "Billing", + data: [ + { + name: "Billing Period", + plans: { + free: "Free", + growth: "Monthly or Yearly", + scale: "Monthly or Yearly", + enterprise: "Custom", + }, + }, + { + name: "Payment Method", + plans: { + free: false, + growth: "Visa / Mastercard / Amex / Bitcoin", + scale: "Visa / Mastercard / Amex / Bitcoin", + enterprise: + "Visa / Mastercard / Amex / ACH / Invoices / Bitcoin", + }, + }, + { + name: "Cancel Anytime", + plans: { + free: true, + growth: true, + scale: true, + enterprise: true, + }, + }, + ], + }, + ]; + + res.render(`${ViewsPath}/pricing`, { + pricing, + }); + }); + + app.get( + "/enterprise/demo", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/demo`, { + support: false, + footerCards: false, + cta: false, + blackLogo: true, + requestDemoCta: false, + }); + }, + ); + + app.get( + "/product/status-page", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/status-page`); + }, + ); + + app.get( + "/product/logs-management", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/logs-management`); + }, + ); + + app.get("/product/apm", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/apm`); + }); + + app.get("/status-page", (_req: ExpressRequest, res: ExpressResponse) => { + res.redirect("/product/status-page"); + }); + + app.get( + "/logs-manageemnt", + (_req: ExpressRequest, res: ExpressResponse) => { + res.redirect("/product/logs-manageemnt"); + }, + ); + + let gitHubContributors: Array = []; + let gitHubBasicInfo: JSONObject | null = null; + let gitHubCommits: string = "-"; + + app.get("/about", async (_req: ExpressRequest, res: ExpressResponse) => { + if (gitHubContributors.length === 0) { + let contributors: Array = []; + + let hasMoreContributors: boolean = true; + + let pageNumber: number = 1; + + while (hasMoreContributors) { + const response: HTTPResponse> | HTTPErrorResponse = + await API.get>( + URL.fromString( + "https://api.github.com/repos/oneuptime/oneuptime/contributors?page=" + + pageNumber, + ), + ); + pageNumber++; + if ((response.data as Array).length < 30) { + hasMoreContributors = false; + } + + contributors = contributors.concat( + response.data as Array, + ); + } + + //cache it. + gitHubContributors = [...contributors]; + } + + const response: HTTPResponse = await API.get( + URL.fromString( + "https://api.github.com/repos/oneuptime/oneuptime/commits?sha=master&per_page=1&page=1", + ), + ); + + if (gitHubCommits === "-") { + // this is of type: '; rel="next", ; rel="last"', + const link: string | undefined = response.headers["link"]; + const urlString: string | undefined = link + ?.split(",")[1] + ?.split(";")[0] + ?.replace("<", "") + .replace(">", "") + .trim(); + const url: URL = URL.fromString(urlString!); + const commits: string = Number.parseInt( + url.getQueryParam("page") as string, + ).toLocaleString(); + + if (!gitHubBasicInfo) { + const basicInfo: HTTPResponse = await API.get( + URL.fromString("https://api.github.com/repos/oneuptime/oneuptime"), + ); + + gitHubBasicInfo = basicInfo.data as JSONObject; + } + + gitHubCommits = commits; + } + + res.render(`${ViewsPath}/about`, { + contributors: gitHubContributors, + basicInfo: gitHubBasicInfo, + commits: gitHubCommits, + }); + }); + + app.get( + "/product/status-page", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/status-page`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + requestDemoCta: false, + footerCtaText: + "Start with Status Pages, expand into everything else. Sign up today.", + }); + }, + ); + + app.get("/status-page", (_req: ExpressRequest, res: ExpressResponse) => { + res.redirect("/product/status-page"); + }); + + app.get("/workflows", (_req: ExpressRequest, res: ExpressResponse) => { + res.redirect("/product/workflows"); + }); + + app.get("/on-call", (_req: ExpressRequest, res: ExpressResponse) => { + res.redirect("/product/on-call"); + }); + + app.get( + "/product/monitoring", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/monitoring`); + }, + ); + + app.get( + "/product/on-call", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/on-call`); + }, + ); + + app.get( + "/product/workflows", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/workflows`); + }, + ); + + app.get( + "/product/incident-management", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/incident-management`); + }, + ); + + app.get( + "/incident-management", + (_req: ExpressRequest, res: ExpressResponse) => { + res.redirect("/product/incident-management"); + }, + ); + + app.get( + "/enterprise/overview", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/enterprise-overview.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + requestDemoCta: true, + }); + }, + ); + + app.get("/legal", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "terms", + requestDemoCta: false, + }); + }); + + app.get("/legal/terms", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "terms", + requestDemoCta: false, + }); + }); + + app.get("/legal/privacy", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "privacy", + requestDemoCta: false, + }); + }); + + app.get("/legal/contact", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "contact", + requestDemoCta: false, + }); + }); + + app.get( + "/legal/subprocessors", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "subprocessors", + requestDemoCta: false, + }); + }, + ); + + app.get("/legal/ccpa", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "ccpa", + requestDemoCta: false, + }); + }); + + app.get("/legal/hipaa", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "hipaa", + requestDemoCta: false, + }); + }); + + app.get("/legal/dmca", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "dmca", + requestDemoCta: false, + }); + }); + + app.get("/legal/pci", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "pci", + requestDemoCta: false, + }); + }); + + app.get( + "/legal/iso-27001", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + section: "iso-27001", + requestDemoCta: false, + }); + }, + ); + + app.get( + "/legal/iso-27017", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + footerCards: true, + support: false, + cta: true, + blackLogo: false, + section: "iso-27017", + requestDemoCta: false, + }); + }, + ); + + app.get( + "/legal/iso-27018", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + footerCards: true, + support: false, + cta: true, + blackLogo: false, + section: "iso-27018", + requestDemoCta: false, + }); + }, + ); + + app.get( + "/legal/iso-27017", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + footerCards: true, + support: false, + cta: true, + blackLogo: false, + section: "iso-27017", + requestDemoCta: false, + }); + }, + ); + + app.get( + "/legal/iso-27018", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + footerCards: true, + support: false, + cta: true, + blackLogo: false, + section: "iso-27018", + requestDemoCta: false, + }); + }, + ); + + app.get("/legal/soc-2", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + footerCards: true, + support: false, + cta: true, + blackLogo: false, + section: "soc-2", + requestDemoCta: false, + }); + }); + + app.get("/legal/soc-3", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + footerCards: true, + support: false, + cta: true, + blackLogo: false, + section: "soc-3", + requestDemoCta: false, + }); + }); + + app.get( + "/legal/data-residency", + (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + footerCards: true, + support: false, + cta: true, + blackLogo: false, + section: "data-residency", + requestDemoCta: false, + }); + }, + ); + + app.get("/legal/gdpr", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + footerCards: true, + support: false, + cta: true, + blackLogo: false, + section: "gdpr", + requestDemoCta: false, + }); + }); + + app.get("/legal/sla", (_req: ExpressRequest, res: ExpressResponse) => { + res.render(`${ViewsPath}/legal.ejs`, { + footerCards: true, + support: false, + cta: true, + blackLogo: false, + section: "sla", + requestDemoCta: false, + }); + }); + + app.get( + "/compare/:product", + (req: ExpressRequest, res: ExpressResponse) => { + const productConfig: Product = ProductCompare( + req.params["product"] as string, + ); + + if (!productConfig) { + return NotFoundUtil.renderNotFound(res); + } + res.render(`${ViewsPath}/product-compare.ejs`, { + support: false, + footerCards: true, + cta: true, + blackLogo: false, + requestDemoCta: false, + productConfig, + onlyShowCompareTable: false, + }); + }, + ); + + // Generate sitemap + app.get( + "/sitemap.xml", + async (_req: ExpressRequest, res: ExpressResponse) => { + const siteUrls: Array = [ + URL.fromString("https://oneuptime.com/"), + URL.fromString("https://oneuptime.com/pricing"), + URL.fromString("https://oneuptime.com/support"), + URL.fromString("https://oneuptime.com/about"), + URL.fromString("https://oneuptime.com/product/status-page"), + URL.fromString("https://oneuptime.com/product/incident-management"), + URL.fromString("https://oneuptime.com/product/on-call"), + URL.fromString("https://oneuptime.com/enterprise/overview"), + URL.fromString("https://oneuptime.com/enterprise/demo"), + URL.fromString("https://oneuptime.com/legal/terms"), + URL.fromString("https://oneuptime.com/legal/privacy"), + URL.fromString("https://oneuptime.com/legal/gdpr"), + URL.fromString("https://oneuptime.com/legal/ccpa"), + URL.fromString("https://oneuptime.com/legal"), + URL.fromString("https://oneuptime.com/compare/pagerduty"), + URL.fromString("https://oneuptime.com/compare/pingdom"), + URL.fromString("https://oneuptime.com/compare/status-page.io"), + URL.fromString("https://oneuptime.com/compare/incident.io"), + URL.fromString("https://oneuptime.com/legal/soc-2"), + URL.fromString("https://oneuptime.com/legal/soc-3"), + URL.fromString("https://oneuptime.com/legal/iso-27017"), + URL.fromString("https://oneuptime.com/legal/iso-27018"), + URL.fromString("https://oneuptime.com/legal/hipaa"), + URL.fromString("https://oneuptime.com/legal/pci"), + URL.fromString("https://oneuptime.com/legal/sla"), + URL.fromString("https://oneuptime.com/legal/iso-27001"), + URL.fromString("https://oneuptime.com/legal/data-residency"), + URL.fromString("https://oneuptime.com/legal/dmca"), + URL.fromString("https://oneuptime.com/legal/subprocessors"), + URL.fromString("https://oneuptime.com/legal/contact"), + ]; + + // Build xml + const urlsetAttr: Dictionary = { + xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9", + "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "xsi:schemaLocation": + "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd", + }; + + // Get previous day's date/timestamp + const today: Date = OneUptimeDate.getOneDayAgo(); + const timestamp: string = today.toISOString(); + + const urlset: XMLBuilder = builder.create().ele("urlset"); + + // Apply attributes to root element + for (const key in urlsetAttr) { + urlset.att({ key: urlsetAttr[key] }); + } + + //Append urls to root element + siteUrls.forEach((url: URL) => { + const urlElement: XMLBuilder = urlset.ele("url"); + urlElement.ele("loc").txt(url.toString()); + urlElement.ele("lastmod").txt(timestamp); }); - app.get( - '/infrastructure-agent/install.sh', - (_req: ExpressRequest, res: ExpressResponse) => { - // fetch the file from https://raw.githubusercontent.com/oneuptime/infrastructure-agent/release/Scripts/Install/Linux.sh and send it as response - res.redirect( - 'https://raw.githubusercontent.com/OneUptime/oneuptime/release/InfrastructureAgent/Scripts/Install/Linux.sh' - ); - } - ); - - app.get( - '/support', - async (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/support`); - } - ); - - app.get('/pricing', (_req: ExpressRequest, res: ExpressResponse) => { - const pricing: Array = [ - { - name: 'Status Page', - data: [ - { - name: 'Public Status Page', - plans: { - free: 'Unlimited', - growth: 'Unlimited', - scale: 'Unlimited', - enterprise: 'Unlimited', - }, - }, - { - name: 'Subscribers', - plans: { - free: 'Unlimited', - growth: 'Unlimited', - scale: 'Unlimited', - enterprise: 'Unlimited', - }, - }, - { - name: 'Custom Branding', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'SSL Certificate', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Custom Domain', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Private Status Page', - plans: { - free: false, - growth: 'Unlimited', - scale: 'Unlimited', - enterprise: 'Unlimited', - }, - }, - { - name: 'Private Status Page Users', - plans: { - free: false, - growth: 'Unlimited', - scale: 'Unlimited', - enterprise: 'Unlimited', - }, - }, - ], - }, - { - name: 'Incident Management', - data: [ - { - name: 'Basic Incident Management', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Public Postmortem Notes', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Private Postmortem Notes', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Incident Workflows', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Custom Incident State', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Custom Incident Severity', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - ], - }, - { - name: 'Monitoring', - data: [ - { - name: 'Static / Manual Monitors', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Website Monitoring', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'API Monitoring', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - - { - name: 'IPv4 Monitoring', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'IPv6 Monitoring', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Inbound Webhook / Heartbeat Monitoring', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'VM or Server Monitoring', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Container Monitoring', - plans: { - free: 'Coming Soon', - growth: 'Coming Soon', - scale: 'Coming Soon', - enterprise: 'Coming Soon', - }, - }, - - { - name: 'Network Monitoring', - plans: { - free: 'Coming Soon', - growth: 'Coming Soon', - scale: 'Coming Soon', - enterprise: 'Coming Soon', - }, - }, - { - name: 'Kubernetes Cluster Monitoring', - plans: { - free: 'Coming Soon', - growth: 'Coming Soon', - scale: 'Coming Soon', - enterprise: 'Coming Soon', - }, - }, - ], - }, - { - name: 'On-Call and Alerts', - data: [ - { - name: 'Phone Alerts', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'SMS Alerts', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Email Alerts', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'On-Call Escalation', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Advanced Workflows', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'On-Call Rotation', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Logs and Events', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Webhook Alerts', - plans: { - free: 'Coming Soon', - growth: 'Coming Soon', - scale: 'Coming Soon', - enterprise: 'Coming Soon', - }, - }, - - { - name: 'Vacation and OOO Policy', - plans: { - free: 'Coming Soon', - growth: 'Coming Soon', - scale: 'Coming Soon', - enterprise: 'Coming Soon', - }, - }, - - { - name: 'On-Call Pay', - plans: { - free: 'Coming Soon', - growth: 'Coming Soon', - scale: 'Coming Soon', - enterprise: 'Coming Soon', - }, - }, - - { - name: 'Reports', - plans: { - free: 'Coming Soon', - growth: 'Coming Soon', - scale: 'Coming Soon', - enterprise: 'Coming Soon', - }, - }, - ], - }, - { - name: 'Support and More', - data: [ - { - name: 'Support', - plans: { - free: 'Community Support', - growth: 'Email Support', - scale: 'Email and Chat Support', - enterprise: 'Email, Chat, Phone Support', - }, - }, - { - name: 'Support SLA', - plans: { - free: false, - growth: '1 business day', - scale: '6 hours', - enterprise: '1 hour priority', - }, - }, - { - name: 'Service SLA', - plans: { - free: false, - growth: '99.90%', - scale: '99.95%', - enterprise: '99.99%', - }, - }, - ], - }, - { - name: 'Advanced Features', - data: [ - { - name: 'API Access', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - { - name: 'Advanced Workflows', - plans: { - free: false, - growth: '500 Runs / month', - scale: '2000 Runs /month', - enterprise: 'Unlimited Runs', - }, - }, - { - name: '5000+ Integrations', - plans: { - free: false, - growth: true, - scale: true, - enterprise: true, - }, - }, - ], - }, - { - name: 'Billing', - data: [ - { - name: 'Billing Period', - plans: { - free: 'Free', - growth: 'Monthly or Yearly', - scale: 'Monthly or Yearly', - enterprise: 'Custom', - }, - }, - { - name: 'Payment Method', - plans: { - free: false, - growth: 'Visa / Mastercard / Amex / Bitcoin', - scale: 'Visa / Mastercard / Amex / Bitcoin', - enterprise: - 'Visa / Mastercard / Amex / ACH / Invoices / Bitcoin', - }, - }, - { - name: 'Cancel Anytime', - plans: { - free: true, - growth: true, - scale: true, - enterprise: true, - }, - }, - ], - }, - ]; - - res.render(`${ViewsPath}/pricing`, { - pricing, - }); - }); - - app.get( - '/enterprise/demo', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/demo`, { - support: false, - footerCards: false, - cta: false, - blackLogo: true, - requestDemoCta: false, - }); - } - ); - - app.get( - '/product/status-page', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/status-page`); - } - ); - - app.get( - '/product/logs-management', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/logs-management`); - } - ); - - app.get( - '/product/apm', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/apm`); - } - ); - - app.get( - '/status-page', - (_req: ExpressRequest, res: ExpressResponse) => { - res.redirect('/product/status-page'); - } - ); - - app.get( - '/logs-manageemnt', - (_req: ExpressRequest, res: ExpressResponse) => { - res.redirect('/product/logs-manageemnt'); - } - ); - - let gitHubContributors: Array = []; - let gitHubBasicInfo: JSONObject | null = null; - let gitHubCommits: string = '-'; - - app.get( - '/about', - async (_req: ExpressRequest, res: ExpressResponse) => { - if (gitHubContributors.length === 0) { - let contributors: Array = []; - - let hasMoreContributors: boolean = true; - - let pageNumber: number = 1; - - while (hasMoreContributors) { - const response: - | HTTPResponse> - | HTTPErrorResponse = await API.get< - Array - >( - URL.fromString( - 'https://api.github.com/repos/oneuptime/oneuptime/contributors?page=' + - pageNumber - ) - ); - pageNumber++; - if ((response.data as Array).length < 30) { - hasMoreContributors = false; - } - - contributors = contributors.concat( - response.data as Array - ); - } - - //cache it. - gitHubContributors = [...contributors]; - } - - const response: HTTPResponse = await API.get( - URL.fromString( - 'https://api.github.com/repos/oneuptime/oneuptime/commits?sha=master&per_page=1&page=1' - ) - ); - - if (gitHubCommits === '-') { - // this is of type: '; rel="next", ; rel="last"', - const link: string | undefined = response.headers['link']; - const urlString: string | undefined = link - ?.split(',')[1] - ?.split(';')[0] - ?.replace('<', '') - .replace('>', '') - .trim(); - const url: URL = URL.fromString(urlString!); - const commits: string = Number.parseInt( - url.getQueryParam('page') as string - ).toLocaleString(); - - if (!gitHubBasicInfo) { - const basicInfo: HTTPResponse = - await API.get( - URL.fromString( - 'https://api.github.com/repos/oneuptime/oneuptime' - ) - ); - - gitHubBasicInfo = basicInfo.data as JSONObject; - } - - gitHubCommits = commits; - } - - res.render(`${ViewsPath}/about`, { - contributors: gitHubContributors, - basicInfo: gitHubBasicInfo, - commits: gitHubCommits, - }); - } - ); - - app.get( - '/product/status-page', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/status-page`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - requestDemoCta: false, - footerCtaText: - 'Start with Status Pages, expand into everything else. Sign up today.', - }); - } - ); - - app.get( - '/status-page', - (_req: ExpressRequest, res: ExpressResponse) => { - res.redirect('/product/status-page'); - } - ); - - app.get('/workflows', (_req: ExpressRequest, res: ExpressResponse) => { - res.redirect('/product/workflows'); - }); - - app.get('/on-call', (_req: ExpressRequest, res: ExpressResponse) => { - res.redirect('/product/on-call'); - }); - - app.get( - '/product/monitoring', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/monitoring`); - } - ); - - app.get( - '/product/on-call', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/on-call`); - } - ); - - app.get( - '/product/workflows', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/workflows`); - } - ); - - app.get( - '/product/incident-management', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/incident-management`); - } - ); - - app.get( - '/incident-management', - (_req: ExpressRequest, res: ExpressResponse) => { - res.redirect('/product/incident-management'); - } - ); - - app.get( - '/enterprise/overview', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/enterprise-overview.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - requestDemoCta: true, - }); - } - ); - - app.get('/legal', (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'terms', - requestDemoCta: false, - }); - }); - - app.get( - '/legal/terms', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'terms', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/privacy', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'privacy', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/contact', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'contact', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/subprocessors', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'subprocessors', - requestDemoCta: false, - }); - } - ); - - app.get('/legal/ccpa', (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'ccpa', - requestDemoCta: false, - }); - }); - - app.get( - '/legal/hipaa', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'hipaa', - requestDemoCta: false, - }); - } - ); - - app.get('/legal/dmca', (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'dmca', - requestDemoCta: false, - }); - }); - - app.get('/legal/pci', (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'pci', - requestDemoCta: false, - }); - }); - - app.get( - '/legal/iso-27001', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - section: 'iso-27001', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/iso-27017', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - footerCards: true, - support: false, - cta: true, - blackLogo: false, - section: 'iso-27017', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/iso-27018', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - footerCards: true, - support: false, - cta: true, - blackLogo: false, - section: 'iso-27018', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/iso-27017', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - footerCards: true, - support: false, - cta: true, - blackLogo: false, - section: 'iso-27017', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/iso-27018', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - footerCards: true, - support: false, - cta: true, - blackLogo: false, - section: 'iso-27018', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/soc-2', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - footerCards: true, - support: false, - cta: true, - blackLogo: false, - section: 'soc-2', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/soc-3', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - footerCards: true, - support: false, - cta: true, - blackLogo: false, - section: 'soc-3', - requestDemoCta: false, - }); - } - ); - - app.get( - '/legal/data-residency', - (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - footerCards: true, - support: false, - cta: true, - blackLogo: false, - section: 'data-residency', - requestDemoCta: false, - }); - } - ); - - app.get('/legal/gdpr', (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - footerCards: true, - support: false, - cta: true, - blackLogo: false, - section: 'gdpr', - requestDemoCta: false, - }); - }); - - app.get('/legal/sla', (_req: ExpressRequest, res: ExpressResponse) => { - res.render(`${ViewsPath}/legal.ejs`, { - footerCards: true, - support: false, - cta: true, - blackLogo: false, - section: 'sla', - requestDemoCta: false, - }); - }); - - app.get( - '/compare/:product', - (req: ExpressRequest, res: ExpressResponse) => { - const productConfig: Product = ProductCompare( - req.params['product'] as string - ); - - if (!productConfig) { - return NotFoundUtil.renderNotFound(res); - } - res.render(`${ViewsPath}/product-compare.ejs`, { - support: false, - footerCards: true, - cta: true, - blackLogo: false, - requestDemoCta: false, - productConfig, - onlyShowCompareTable: false, - }); - } - ); - - // Generate sitemap - app.get( - '/sitemap.xml', - async (_req: ExpressRequest, res: ExpressResponse) => { - const siteUrls: Array = [ - URL.fromString('https://oneuptime.com/'), - URL.fromString('https://oneuptime.com/pricing'), - URL.fromString('https://oneuptime.com/support'), - URL.fromString('https://oneuptime.com/about'), - URL.fromString('https://oneuptime.com/product/status-page'), - URL.fromString( - 'https://oneuptime.com/product/incident-management' - ), - URL.fromString('https://oneuptime.com/product/on-call'), - URL.fromString('https://oneuptime.com/enterprise/overview'), - URL.fromString('https://oneuptime.com/enterprise/demo'), - URL.fromString('https://oneuptime.com/legal/terms'), - URL.fromString('https://oneuptime.com/legal/privacy'), - URL.fromString('https://oneuptime.com/legal/gdpr'), - URL.fromString('https://oneuptime.com/legal/ccpa'), - URL.fromString('https://oneuptime.com/legal'), - URL.fromString('https://oneuptime.com/compare/pagerduty'), - URL.fromString('https://oneuptime.com/compare/pingdom'), - URL.fromString( - 'https://oneuptime.com/compare/status-page.io' - ), - URL.fromString('https://oneuptime.com/compare/incident.io'), - URL.fromString('https://oneuptime.com/legal/soc-2'), - URL.fromString('https://oneuptime.com/legal/soc-3'), - URL.fromString('https://oneuptime.com/legal/iso-27017'), - URL.fromString('https://oneuptime.com/legal/iso-27018'), - URL.fromString('https://oneuptime.com/legal/hipaa'), - URL.fromString('https://oneuptime.com/legal/pci'), - URL.fromString('https://oneuptime.com/legal/sla'), - URL.fromString('https://oneuptime.com/legal/iso-27001'), - URL.fromString( - 'https://oneuptime.com/legal/data-residency' - ), - URL.fromString('https://oneuptime.com/legal/dmca'), - URL.fromString('https://oneuptime.com/legal/subprocessors'), - URL.fromString('https://oneuptime.com/legal/contact'), - ]; - - // Build xml - const urlsetAttr: Dictionary = { - xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9', - 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', - 'xsi:schemaLocation': - 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd', - }; - - // Get previous day's date/timestamp - const today: Date = OneUptimeDate.getOneDayAgo(); - const timestamp: string = today.toISOString(); - - const urlset: XMLBuilder = builder.create().ele('urlset'); - - // Apply attributes to root element - for (const key in urlsetAttr) { - urlset.att({ key: urlsetAttr[key] }); - } - - //Append urls to root element - siteUrls.forEach((url: URL) => { - const urlElement: XMLBuilder = urlset.ele('url'); - urlElement.ele('loc').txt(url.toString()); - urlElement.ele('lastmod').txt(timestamp); - }); - - // Generate xml file - const xml: string = urlset.end({ prettyPrint: true }); - - res.setHeader('Content-Type', 'text/xml'); - res.send(xml); - } - ); - - /* - * Cache policy for static contents - * Loads up the site faster - */ - app.use( - ExpressStatic(StaticPath, { - setHeaders(res: ExpressResponse) { - res.setHeader( - 'Cache-Control', - 'public,max-age=31536000,immutable' - ); - }, - }) - ); - - app.get('/*', (_req: ExpressRequest, res: ExpressResponse) => { - return NotFoundUtil.renderNotFound(res); - }); - }, + // Generate xml file + const xml: string = urlset.end({ prettyPrint: true }); + + res.setHeader("Content-Type", "text/xml"); + res.send(xml); + }, + ); + + /* + * Cache policy for static contents + * Loads up the site faster + */ + app.use( + ExpressStatic(StaticPath, { + setHeaders(res: ExpressResponse) { + res.setHeader("Cache-Control", "public,max-age=31536000,immutable"); + }, + }), + ); + + app.get("/*", (_req: ExpressRequest, res: ExpressResponse) => { + return NotFoundUtil.renderNotFound(res); + }); + }, }; export default HomeFeatureSet; diff --git a/App/FeatureSet/Home/Static/js/amplitude.js b/App/FeatureSet/Home/Static/js/amplitude.js index 5fe1be79e6..9fbc8706fe 100644 --- a/App/FeatureSet/Home/Static/js/amplitude.js +++ b/App/FeatureSet/Home/Static/js/amplitude.js @@ -1,47 +1,99 @@ (function (e, t) { - var n = e.amplitude || { _q: [], _iq: {} }; var r = t.createElement("script") - ; r.type = "text/javascript" - ; r.integrity = "sha384-vYYnQ3LPdp/RkQjoKBTGSq0X5F73gXU3G2QopHaIfna0Ct1JRWzwrmEz115NzOta" - ; r.crossOrigin = "anonymous"; r.async = true - ; r.src = "https://cdn.amplitude.com/libs/amplitude-5.8.0-min.gz.js" - ; r.onload = function () { - if (!e.amplitude.runQueuedFunctions) { - console.log("[Amplitude] Error: could not load SDK") - } - } - ; var i = t.getElementsByTagName("script")[0]; i.parentNode.insertBefore(r, i) - ; function s(e, t) { - e.prototype[t] = function () { - this._q.push([t].concat(Array.prototype.slice.call(arguments, 0))); return this - } - } - var o = function () { this._q = []; return this } - ; var a = ["add", "append", "clearAll", "prepend", "set", "setOnce", "unset"] - ; for (var u = 0; u < a.length; u++) { s(o, a[u]) } n.Identify = o; var c = function () { - this._q = [] - ; return this - } - ; var l = ["setProductId", "setQuantity", "setPrice", "setRevenueType", "setEventProperties"] - ; for (var p = 0; p < l.length; p++) { s(c, l[p]) } n.Revenue = c - ; var d = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "enableTracking", "setGlobalUserProperties", "identify", "clearUserProperties", "setGroup", "logRevenueV2", "regenerateDeviceId", "groupIdentify", "onInit", "logEventWithTimestamp", "logEventWithGroups", "setSessionId", "resetSessionId"] - ; function v(e) { - function t(t) { - e[t] = function () { - e._q.push([t].concat(Array.prototype.slice.call(arguments, 0))) - } - } - for (var n = 0; n < d.length; n++) { t(d[n]) } - } v(n); n.getInstance = function (e) { - e = (!e || e.length === 0 ? "$default_instance" : e).toLowerCase() - ; if (!n._iq.hasOwnProperty(e)) { n._iq[e] = { _q: [] }; v(n._iq[e]) } return n._iq[e] - } - ; e.amplitude = n + var n = e.amplitude || { _q: [], _iq: {} }; + var r = t.createElement("script"); + r.type = "text/javascript"; + r.integrity = + "sha384-vYYnQ3LPdp/RkQjoKBTGSq0X5F73gXU3G2QopHaIfna0Ct1JRWzwrmEz115NzOta"; + r.crossOrigin = "anonymous"; + r.async = true; + r.src = "https://cdn.amplitude.com/libs/amplitude-5.8.0-min.gz.js"; + r.onload = function () { + if (!e.amplitude.runQueuedFunctions) { + console.log("[Amplitude] Error: could not load SDK"); + } + }; + var i = t.getElementsByTagName("script")[0]; + i.parentNode.insertBefore(r, i); + function s(e, t) { + e.prototype[t] = function () { + this._q.push([t].concat(Array.prototype.slice.call(arguments, 0))); + return this; + }; + } + var o = function () { + this._q = []; + return this; + }; + var a = ["add", "append", "clearAll", "prepend", "set", "setOnce", "unset"]; + for (var u = 0; u < a.length; u++) { + s(o, a[u]); + } + n.Identify = o; + var c = function () { + this._q = []; + return this; + }; + var l = [ + "setProductId", + "setQuantity", + "setPrice", + "setRevenueType", + "setEventProperties", + ]; + for (var p = 0; p < l.length; p++) { + s(c, l[p]); + } + n.Revenue = c; + var d = [ + "init", + "logEvent", + "logRevenue", + "setUserId", + "setUserProperties", + "setOptOut", + "setVersionName", + "setDomain", + "setDeviceId", + "enableTracking", + "setGlobalUserProperties", + "identify", + "clearUserProperties", + "setGroup", + "logRevenueV2", + "regenerateDeviceId", + "groupIdentify", + "onInit", + "logEventWithTimestamp", + "logEventWithGroups", + "setSessionId", + "resetSessionId", + ]; + function v(e) { + function t(t) { + e[t] = function () { + e._q.push([t].concat(Array.prototype.slice.call(arguments, 0))); + }; + } + for (var n = 0; n < d.length; n++) { + t(d[n]); + } + } + v(n); + n.getInstance = function (e) { + e = (!e || e.length === 0 ? "$default_instance" : e).toLowerCase(); + if (!n._iq.hasOwnProperty(e)) { + n._iq[e] = { _q: [] }; + v(n._iq[e]); + } + return n._iq[e]; + }; + e.amplitude = n; })(window, document); amplitude.getInstance().init("802d95003af23aad17ed068b6cfdeb2b", null, { - // include referrer information in amplitude. - saveEvents: true, - includeUtm: true, - includeReferrer: true, - includeGclid: true -}); \ No newline at end of file + // include referrer information in amplitude. + saveEvents: true, + includeUtm: true, + includeReferrer: true, + includeGclid: true, +}); diff --git a/App/FeatureSet/Home/Static/js/crm.js b/App/FeatureSet/Home/Static/js/crm.js index 5867838881..d497205a44 100755 --- a/App/FeatureSet/Home/Static/js/crm.js +++ b/App/FeatureSet/Home/Static/js/crm.js @@ -1,94 +1,95 @@ function openTab(evt, tabName) { - // Declare all variables - let i; + // Declare all variables + let i; - // Get all elements with class="tabcontent" and hide them - const tabcontent = document.getElementsByClassName('tabcontent'); - for (i = 0; i < tabcontent.length; i++) { - tabcontent[i].className = tabcontent[i].className.replace(' active', ''); - } + // Get all elements with class="tabcontent" and hide them + const tabcontent = document.getElementsByClassName("tabcontent"); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].className = tabcontent[i].className.replace(" active", ""); + } - // Get all elements with class="tablinks" and remove the class "active" - const tablinks = document.getElementsByClassName('tablinks'); - for (i = 0; i < tablinks.length; i++) { - tablinks[i].className = tablinks[i].className.replace(' active', ''); - } + // Get all elements with class="tablinks" and remove the class "active" + const tablinks = document.getElementsByClassName("tablinks"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } - // Show the current tab, and add an "active" class to the link that opened the tab + // Show the current tab, and add an "active" class to the link that opened the tab - document.getElementById(tabName).className += ' active'; - evt.currentTarget.className += ' active'; + document.getElementById(tabName).className += " active"; + evt.currentTarget.className += " active"; - setTimeout(() => document.getElementById(tabName + '1').parentNode.click(), 200); + setTimeout( + () => document.getElementById(tabName + "1").parentNode.click(), + 200, + ); } function openTooltip(name) { - // Declare all variables - let i; - const element = document.getElementById(name); + // Declare all variables + let i; + const element = document.getElementById(name); - const elclass = element.className; + const elclass = element.className; - const tooltip = document.getElementsByClassName('tooltiptext'); - for (i = 0; i < tooltip.length; i++) { - tooltip[i].className = tooltip[i].className.replace(' active', ''); - } - if (elclass.indexOf('active') > -1) { - - element.className = element.className.replace(' active', ''); - } - else { - - element.classList.add('active'); - } + const tooltip = document.getElementsByClassName("tooltiptext"); + for (i = 0; i < tooltip.length; i++) { + tooltip[i].className = tooltip[i].className.replace(" active", ""); + } + if (elclass.indexOf("active") > -1) { + element.className = element.className.replace(" active", ""); + } else { + element.classList.add("active"); + } } window.onload = function () { - animateHTML().init(); - const tooltext = document.getElementsByClassName('tooltiptext'); - for (let i = 0; i < tooltext.length; i++) { + animateHTML().init(); + const tooltext = document.getElementsByClassName("tooltiptext"); + for (let i = 0; i < tooltext.length; i++) { + tooltext[i].onclick = function (e) { + e.stopPropagation(); + }; + } - tooltext[i].onclick = function (e) { - e.stopPropagation(); - } + document.getElementsByTagName("body")[0].onclick = function (e) { + if ( + e.target.className !== "popover-dot" && + e.target.className !== "tooltiptext" && + e.target.className !== "tablinks active" + ) { + const tooltip = document.getElementsByClassName("tooltiptext"); + for (let i = 0; i < tooltip.length; i++) { + tooltip[i].className = tooltip[i].className.replace(" active", ""); + } } - - document.getElementsByTagName('body')[0].onclick = function (e) { - - if (e.target.className !== 'popover-dot' && e.target.className !== 'tooltiptext' && e.target.className !== 'tablinks active') { - const tooltip = document.getElementsByClassName('tooltiptext'); - for (let i = 0; i < tooltip.length; i++) { - tooltip[i].className = tooltip[i].className.replace(' active', ''); - } - } - } -} + }; +}; const animateHTML = function () { - let elem, windowHeight; - const init = function () { - elem = document.getElementById('Statuspage'); - windowHeight = window.innerHeight; - _addEventHandlers(); + let elem, windowHeight; + const init = function () { + elem = document.getElementById("Statuspage"); + windowHeight = window.innerHeight; + _addEventHandlers(); + }; + const _addEventHandlers = function () { + window.addEventListener("scroll", _checkPosition); + window.addEventListener("resize", init); + }; + const _checkPosition = function () { + if (!elem) { + return; } - const _addEventHandlers = function () { - window.addEventListener('scroll', _checkPosition) - window.addEventListener('resize', init) - } - const _checkPosition = function () { - if (!elem) { - return; - } - const posFromTop = elem.getBoundingClientRect().top; + const posFromTop = elem.getBoundingClientRect().top; - if (posFromTop - windowHeight <= -400) { - - document.getElementById('Statuspage1').parentNode.click(); - window.removeEventListener('scroll', _checkPosition); - window.removeEventListener('resize', init); - return; - } + if (posFromTop - windowHeight <= -400) { + document.getElementById("Statuspage1").parentNode.click(); + window.removeEventListener("scroll", _checkPosition); + window.removeEventListener("resize", init); + return; } - return { - init: init - } -} + }; + return { + init: init, + }; +}; diff --git a/App/FeatureSet/Home/Static/js/getCookies.js b/App/FeatureSet/Home/Static/js/getCookies.js index ae81991e45..d3cd6b49ec 100644 --- a/App/FeatureSet/Home/Static/js/getCookies.js +++ b/App/FeatureSet/Home/Static/js/getCookies.js @@ -1,5 +1,5 @@ // This is basicaly meant to get a cookie by name var getCookiebyName = function (name) { - var pair = document.cookie.match(new RegExp(name + '=([^;]+)')); - return pair ? pair[1] : null; -}; \ No newline at end of file + var pair = document.cookie.match(new RegExp(name + "=([^;]+)")); + return pair ? pair[1] : null; +}; diff --git a/App/FeatureSet/Home/Static/js/resources.js b/App/FeatureSet/Home/Static/js/resources.js index 9870ada6d7..68ecd9020d 100755 --- a/App/FeatureSet/Home/Static/js/resources.js +++ b/App/FeatureSet/Home/Static/js/resources.js @@ -1,69 +1,49 @@ +!(function () { + function n(n, e) { + $(".hidden", n) + .eq(e) + .css({ + transitionDelay: Math.random() + Math.random() + "s", + transitionDuration: 2 * Math.random() + 0.2 + "s", + }), + $(".hidden", n).eq(e).attr("class", "shown"); + } -! function () { - function n(n, e) { - - $('.hidden', n) - .eq(e) - .css({ - transitionDelay: Math.random() + Math.random() + 's', - transitionDuration: 2 * Math.random() + .2 + 's' - - }), $('.hidden', n) - .eq(e) - .attr('class', 'shown') + function e(n, e) { + if (n.hasClass("is-visible")) { + const a = $(".shown", n).eq(e); + a.attr("class", "hidden"), + setTimeout(function () { + a.attr("class", "shown"); + }, 3e3); } - - function e(n, e) { - if (n.hasClass('is-visible')) { - - const a = $('.shown', n) - .eq(e); - a.attr('class', 'hidden'), setTimeout(function () { - a.attr('class', 'shown') - }, 3e3) - } + } + + $(".card").each(function (e, a) { + if (window.IntersectionObserver) + (a.observer = new IntersectionObserver((e) => { + e.forEach((e) => { + if (e.isIntersecting || e.intersectionRatio > 0) { + $(a).addClass("is-visible"); + + for (let t = $(".hidden", a).length; t >= 0; t--) n(a, t); + } else $(a).removeClass("is-visible"); + }); + })), + a.observer.observe(a); + else { + $(a).addClass("is-visible"); + + for (let t = $(".hidden", a).length; t >= 0; t--) n(a, t); } - - $('.card') - .each(function (e, a) { - if (window.IntersectionObserver) a.observer = new IntersectionObserver(e => { - e.forEach(e => { - if (e.isIntersecting || e.intersectionRatio > 0) { - - $(a) - .addClass('is-visible'); - - for (let t = $('.hidden', a) - .length; t >= 0; t--) n(a, t) - - } else $(a) - .removeClass('is-visible') - }) - }), a.observer.observe(a); - else { - - $(a) - .addClass('is-visible'); - - for (let t = $('.hidden', a) - .length; t >= 0; t--) n(a, t) - } - }), setInterval(function () { - - let n = $('.card') - - .eq(Math.floor(Math.random() * $('.card') - .length)); - - e(n, Math.floor(Math.random() * $('.shown', n) - .length)); - - n = $('.card') - - .eq(Math.floor(Math.random() * $('.card') - .length)); - - e(n, Math.floor(Math.random() * $('.shown', n) - .length)) - }, 600) -}(); + }), + setInterval(function () { + let n = $(".card").eq(Math.floor(Math.random() * $(".card").length)); + + e(n, Math.floor(Math.random() * $(".shown", n).length)); + + n = $(".card").eq(Math.floor(Math.random() * $(".card").length)); + + e(n, Math.floor(Math.random() * $(".shown", n).length)); + }, 600); +})(); diff --git a/App/FeatureSet/Home/Static/js/switchUrl.js b/App/FeatureSet/Home/Static/js/switchUrl.js index 7e039b83f1..5d8ac4886c 100755 --- a/App/FeatureSet/Home/Static/js/switchUrl.js +++ b/App/FeatureSet/Home/Static/js/switchUrl.js @@ -1,27 +1,26 @@ - -let accountsUrl = window.location.origin+'/accounts'; -let backendUrl = window.location.hostname==='localhost'? 'http://localhost:3002': window.location.origin+'/api' - +let accountsUrl = window.location.origin + "/accounts"; +let backendUrl = + window.location.hostname === "localhost" + ? "http://localhost:3002" + : window.location.origin + "/api"; //eslint-disable-next-line function loginUrl(extra) { - if (extra) { - window.location.href = `${accountsUrl}/login${extra}`; - } - else { - window.location.href = `${accountsUrl}/login`; - } + if (extra) { + window.location.href = `${accountsUrl}/login${extra}`; + } else { + window.location.href = `${accountsUrl}/login`; + } } //eslint-disable-next-line function registerUrl(params) { - if (params) { - window.location.href = `${accountsUrl}/register${params}`; - } - else { - window.location.href = `${accountsUrl}/register`; - } + if (params) { + window.location.href = `${accountsUrl}/register${params}`; + } else { + window.location.href = `${accountsUrl}/register`; + } } //eslint-disable-next-line function formUrl() { - return `${backendUrl}/lead/`; + return `${backendUrl}/lead/`; } diff --git a/App/FeatureSet/Home/Tests/index.test.ts b/App/FeatureSet/Home/Tests/index.test.ts index 2d530fc802..00e68774d3 100644 --- a/App/FeatureSet/Home/Tests/index.test.ts +++ b/App/FeatureSet/Home/Tests/index.test.ts @@ -1,3 +1,3 @@ -test('two plus two is four', () => { - expect(2 + 2).toBe(4); +test("two plus two is four", () => { + expect(2 + 2).toBe(4); }); diff --git a/App/FeatureSet/Home/Utils/BlogPost.ts b/App/FeatureSet/Home/Utils/BlogPost.ts index 653c6ee7a0..1fe0458806 100644 --- a/App/FeatureSet/Home/Utils/BlogPost.ts +++ b/App/FeatureSet/Home/Utils/BlogPost.ts @@ -1,453 +1,447 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONArray, JSONObject, JSONObjectOrArray } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Text from 'Common/Types/Text'; -import API from 'Common/Utils/API'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONArray, JSONObject, JSONObjectOrArray } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Text from "Common/Types/Text"; +import API from "Common/Utils/API"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; export interface BlogPostAuthor { - username: string; - githubUrl: string; - profileImageUrl: string; - name: string; + username: string; + githubUrl: string; + profileImageUrl: string; + name: string; } export interface BlogPostBaseProps { - title: string; - description: string; + title: string; + description: string; - formattedPostDate: string; - fileName: string; - tags: string[]; - postDate: string; - blogUrl: string; + formattedPostDate: string; + fileName: string; + tags: string[]; + postDate: string; + blogUrl: string; } export interface BlogPostHeader extends BlogPostBaseProps { - authorGitHubUsername: string; + authorGitHubUsername: string; } export interface BlogPost extends BlogPostBaseProps { - htmlBody: string; - markdownBody: string; - socialMediaImageUrl: string; - author: BlogPostAuthor | null; + htmlBody: string; + markdownBody: string; + socialMediaImageUrl: string; + author: BlogPostAuthor | null; } const GitHubRawUrl: string = - 'https://raw.githubusercontent.com/oneuptime/blog/master'; + "https://raw.githubusercontent.com/oneuptime/blog/master"; export default class BlogPostUtil { - public static async getBlogPostList( - tagName?: string | undefined - ): Promise { - const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/Blogs.json`); + public static async getBlogPostList( + tagName?: string | undefined, + ): Promise { + const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/Blogs.json`); - const fileData: - | HTTPResponse< - | JSONObjectOrArray - | BaseModel - | BaseModel[] - | AnalyticsBaseModel - | AnalyticsBaseModel[] - > - | HTTPErrorResponse = await API.get(fileUrl); + const fileData: + | HTTPResponse< + | JSONObjectOrArray + | BaseModel + | BaseModel[] + | AnalyticsBaseModel + | AnalyticsBaseModel[] + > + | HTTPErrorResponse = await API.get(fileUrl); - if (fileData.isFailure()) { - throw fileData as HTTPErrorResponse; - } - - let jsonContent: string | JSONArray = - (fileData.data as string | JSONArray) || []; - - if (typeof jsonContent === 'string') { - jsonContent = JSONFunctions.parseJSONArray(jsonContent); - } - - const blogs: Array = JSONFunctions.deserializeArray( - jsonContent as Array - ).reverse(); // reverse so new content comes first - - const resultList: Array = []; - - for (const blog of blogs) { - const fileName: string = blog['post'] as string; - const formattedPostDate: string = - this.getFormattedPostDateFromFileName(fileName); - const postDate: string = this.getPostDateFromFileName(fileName); - - resultList.push({ - title: blog['title'] as string, - description: blog['description'] as string, - fileName, - formattedPostDate, - postDate, - tags: blog['tags'] as string[], - authorGitHubUsername: blog['authorGitHubUsername'] as string, - blogUrl: `/blog/post/${fileName}`, - }); - } - - if (tagName) { - return resultList.filter((blog: BlogPostHeader) => { - return blog.tags - .map((item: string) => { - return Text.replaceAll(item.toLowerCase(), ' ', '-'); - }) - .includes(tagName); - }); - } - - return resultList; + if (fileData.isFailure()) { + throw fileData as HTTPErrorResponse; } - public static async getBlogPost( - fileName: string - ): Promise { - let blogPost: BlogPost | null = this.getBlogPostFromCache(fileName); + let jsonContent: string | JSONArray = + (fileData.data as string | JSONArray) || []; - // if (blogPost) { - // return Promise.resolve(blogPost); - // } - - blogPost = await this.getBlogPostFromGitHub(fileName); - - // save this to cache - LocalCache.setJSON( - 'blog', - fileName, - JSONFunctions.serialize(blogPost as any) - ); - - return blogPost; + if (typeof jsonContent === "string") { + jsonContent = JSONFunctions.parseJSONArray(jsonContent); } - public static async getNameOfGitHubUser(username: string): Promise { - const fileUrl: URL = URL.fromString( - `https://api.github.com/users/${username}` - ); + const blogs: Array = JSONFunctions.deserializeArray( + jsonContent as Array, + ).reverse(); // reverse so new content comes first - const fileData: - | HTTPResponse< - | JSONObjectOrArray - | BaseModel - | BaseModel[] - | AnalyticsBaseModel - | AnalyticsBaseModel[] - > - | HTTPErrorResponse = await API.get(fileUrl); + const resultList: Array = []; - if (fileData.isFailure()) { - throw fileData as HTTPErrorResponse; - } + for (const blog of blogs) { + const fileName: string = blog["post"] as string; + const formattedPostDate: string = + this.getFormattedPostDateFromFileName(fileName); + const postDate: string = this.getPostDateFromFileName(fileName); - const name: string = - (fileData.data as JSONObject)?.['name']?.toString() || ''; - return name; + resultList.push({ + title: blog["title"] as string, + description: blog["description"] as string, + fileName, + formattedPostDate, + postDate, + tags: blog["tags"] as string[], + authorGitHubUsername: blog["authorGitHubUsername"] as string, + blogUrl: `/blog/post/${fileName}`, + }); } - public static async getGitHubMarkdownFileContent( - githubPath: string - ): Promise { - const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/${githubPath}`); - - const fileData: - | HTTPResponse< - | JSONObjectOrArray - | BaseModel - | BaseModel[] - | AnalyticsBaseModel - | AnalyticsBaseModel[] - > - | HTTPErrorResponse = await API.get(fileUrl); - - if (fileData.isFailure()) { - if ((fileData as HTTPErrorResponse).statusCode === 404) { - return null; - } - - throw fileData as HTTPErrorResponse; - } - - const markdownContent: string = - (fileData.data as JSONObject)?.['data']?.toString() || ''; - return markdownContent; + if (tagName) { + return resultList.filter((blog: BlogPostHeader) => { + return blog.tags + .map((item: string) => { + return Text.replaceAll(item.toLowerCase(), " ", "-"); + }) + .includes(tagName); + }); } - public static async getTags(): Promise { - // check if tags are in cache - let tags: string[] = LocalCache.getJSON( - 'blog-tags', - 'tags' - ) as string[]; + return resultList; + } - if (tags && tags.length > 0) { - return tags; - } + public static async getBlogPost(fileName: string): Promise { + let blogPost: BlogPost | null = this.getBlogPostFromCache(fileName); - tags = await this.getAllTagsFromGitHub(); + // if (blogPost) { + // return Promise.resolve(blogPost); + // } - // save this to cache + blogPost = await this.getBlogPostFromGitHub(fileName); - LocalCache.setJSON( - 'blog-tags', - 'tags', - JSONFunctions.serialize(tags as any) - ); + // save this to cache + LocalCache.setJSON( + "blog", + fileName, + JSONFunctions.serialize(blogPost as any), + ); - return tags; + return blogPost; + } + + public static async getNameOfGitHubUser(username: string): Promise { + const fileUrl: URL = URL.fromString( + `https://api.github.com/users/${username}`, + ); + + const fileData: + | HTTPResponse< + | JSONObjectOrArray + | BaseModel + | BaseModel[] + | AnalyticsBaseModel + | AnalyticsBaseModel[] + > + | HTTPErrorResponse = await API.get(fileUrl); + + if (fileData.isFailure()) { + throw fileData as HTTPErrorResponse; } - public static async getAllTagsFromGitHub(): Promise { - const tagsMarkdownContent: string | null = - await this.getGitHubMarkdownFileContent('Tags.md'); + const name: string = + (fileData.data as JSONObject)?.["name"]?.toString() || ""; + return name; + } - if (!tagsMarkdownContent) { - return []; - } + public static async getGitHubMarkdownFileContent( + githubPath: string, + ): Promise { + const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/${githubPath}`); - const tags: Array = tagsMarkdownContent - .split('\n') - .map((tag: string) => { - return tag.trim(); - }) - .filter((tag: string) => { - return tag.startsWith('-'); - }) - .map((tag: string) => { - return tag.replace('-', '').trim(); - }); + const fileData: + | HTTPResponse< + | JSONObjectOrArray + | BaseModel + | BaseModel[] + | AnalyticsBaseModel + | AnalyticsBaseModel[] + > + | HTTPErrorResponse = await API.get(fileUrl); - return tags; + if (fileData.isFailure()) { + if ((fileData as HTTPErrorResponse).statusCode === 404) { + return null; + } + + throw fileData as HTTPErrorResponse; } - public static async getBlogPostFromGitHub( - fileName: string - ): Promise { - const fileUrl: URL = URL.fromString( - `${GitHubRawUrl}/posts/${fileName}/README.md` - ); + const markdownContent: string = + (fileData.data as JSONObject)?.["data"]?.toString() || ""; + return markdownContent; + } - const postDate: string = this.getPostDateFromFileName(fileName); - const formattedPostDate: string = - this.getFormattedPostDateFromFileName(fileName); + public static async getTags(): Promise { + // check if tags are in cache + let tags: string[] = LocalCache.getJSON("blog-tags", "tags") as string[]; - const fileData: - | HTTPResponse< - | JSONObjectOrArray - | BaseModel - | BaseModel[] - | AnalyticsBaseModel - | AnalyticsBaseModel[] - > - | HTTPErrorResponse = await API.get(fileUrl); - - if (fileData.isFailure()) { - if ((fileData as HTTPErrorResponse).statusCode === 404) { - return null; - } - - throw fileData as HTTPErrorResponse; - } - - let markdownContent: string = - (fileData.data as JSONObject)?.['data']?.toString() || ''; - - const blogPostAuthor: BlogPostAuthor | null = - await this.getAuthorFromFileContent(markdownContent); - - const title: string = this.getTitleFromFileContent(markdownContent); - const description: string = - this.getDescriptionFromFileContent(markdownContent); - const tags: Array = - this.getTagsFromFileContent(markdownContent); - - markdownContent = this.getPostFromMarkdown(markdownContent); - - const htmlBody: string = await Markdown.convertToHTML( - markdownContent, - MarkdownContentType.Blog - ); - - const blogPost: BlogPost = { - title, - description, - author: blogPostAuthor, - htmlBody, - markdownBody: markdownContent, - fileName, - tags, - postDate, - formattedPostDate, - socialMediaImageUrl: `${GitHubRawUrl}/posts/${fileName}/social-media.png`, - blogUrl: `https://oneuptime.com/blog/post/${fileName}`, // this has to be oneuptime.com because its used in twitter cards and faceboomk cards. Please dont change this. - }; - - return blogPost; + if (tags && tags.length > 0) { + return tags; } - private static getPostDateFromFileName(fileName: string): string { - const year: string | undefined = fileName.split('-')[0]; - const month: string | undefined = fileName.split('-')[1]; - const day: string | undefined = fileName.split('-')[2]; + tags = await this.getAllTagsFromGitHub(); - if (!year || !month || !day) { - throw new BadDataException('Invalid file name'); - } + // save this to cache - return `${year}-${month}-${day}`; + LocalCache.setJSON( + "blog-tags", + "tags", + JSONFunctions.serialize(tags as any), + ); + + return tags; + } + + public static async getAllTagsFromGitHub(): Promise { + const tagsMarkdownContent: string | null = + await this.getGitHubMarkdownFileContent("Tags.md"); + + if (!tagsMarkdownContent) { + return []; } - private static getFormattedPostDateFromFileName(fileName: string): string { - // file name is of the format YYYY-MM-DD-Title.md - const year: string | undefined = fileName.split('-')[0]; - const month: string | undefined = fileName.split('-')[1]; - const day: string | undefined = fileName.split('-')[2]; + const tags: Array = tagsMarkdownContent + .split("\n") + .map((tag: string) => { + return tag.trim(); + }) + .filter((tag: string) => { + return tag.startsWith("-"); + }) + .map((tag: string) => { + return tag.replace("-", "").trim(); + }); - if (!year || !month || !day) { - throw new BadDataException('Invalid file name'); - } + return tags; + } - const date: Date = OneUptimeDate.getDateFromYYYYMMDD(year, month, day); - return OneUptimeDate.getDateAsLocalFormattedString(date, true); + public static async getBlogPostFromGitHub( + fileName: string, + ): Promise { + const fileUrl: URL = URL.fromString( + `${GitHubRawUrl}/posts/${fileName}/README.md`, + ); + + const postDate: string = this.getPostDateFromFileName(fileName); + const formattedPostDate: string = + this.getFormattedPostDateFromFileName(fileName); + + const fileData: + | HTTPResponse< + | JSONObjectOrArray + | BaseModel + | BaseModel[] + | AnalyticsBaseModel + | AnalyticsBaseModel[] + > + | HTTPErrorResponse = await API.get(fileUrl); + + if (fileData.isFailure()) { + if ((fileData as HTTPErrorResponse).statusCode === 404) { + return null; + } + + throw fileData as HTTPErrorResponse; } - private static getPostFromMarkdown(markdownContent: string): string { - const authorLine: string | undefined = markdownContent - .split('\n') - .find((line: string) => { - return line.startsWith('Author:'); - }); - const titleLine: string | undefined = markdownContent - .split('\n') - .find((line: string) => { - return line.startsWith('#'); - }); - const descriptionLine: string | undefined = - markdownContent.split('\n').find((line: string) => { - return line.startsWith('Description:'); - }) || ''; + let markdownContent: string = + (fileData.data as JSONObject)?.["data"]?.toString() || ""; - const tagsLine: string | undefined = - markdownContent.split('\n').find((line: string) => { - return line.startsWith('Tags:'); - }) || ''; + const blogPostAuthor: BlogPostAuthor | null = + await this.getAuthorFromFileContent(markdownContent); - if (!authorLine && !titleLine && !descriptionLine && !tagsLine) { - return markdownContent; - } + const title: string = this.getTitleFromFileContent(markdownContent); + const description: string = + this.getDescriptionFromFileContent(markdownContent); + const tags: Array = this.getTagsFromFileContent(markdownContent); - const lines: string[] = markdownContent.split('\n'); + markdownContent = this.getPostFromMarkdown(markdownContent); - if (authorLine) { - const authorLineIndex: number = lines.indexOf(authorLine); - lines.splice(authorLineIndex, 1); - } + const htmlBody: string = await Markdown.convertToHTML( + markdownContent, + MarkdownContentType.Blog, + ); - if (titleLine) { - const titleLineIndex: number = lines.indexOf(titleLine); - lines.splice(titleLineIndex, 1); - } + const blogPost: BlogPost = { + title, + description, + author: blogPostAuthor, + htmlBody, + markdownBody: markdownContent, + fileName, + tags, + postDate, + formattedPostDate, + socialMediaImageUrl: `${GitHubRawUrl}/posts/${fileName}/social-media.png`, + blogUrl: `https://oneuptime.com/blog/post/${fileName}`, // this has to be oneuptime.com because its used in twitter cards and faceboomk cards. Please dont change this. + }; - if (descriptionLine) { - const descriptionLineIndex: number = lines.indexOf(descriptionLine); - lines.splice(descriptionLineIndex, 1); - } + return blogPost; + } - if (tagsLine) { - const tagsLineIndex: number = lines.indexOf(tagsLine); - lines.splice(tagsLineIndex, 1); - } + private static getPostDateFromFileName(fileName: string): string { + const year: string | undefined = fileName.split("-")[0]; + const month: string | undefined = fileName.split("-")[1]; + const day: string | undefined = fileName.split("-")[2]; - return lines.join('\n').trim(); + if (!year || !month || !day) { + throw new BadDataException("Invalid file name"); } - public static getBlogPostFromCache(fileName: string): BlogPost | null { - const blogPost: BlogPost | null = LocalCache.getJSON( - 'blog', - fileName - ) as BlogPost | null; - return blogPost; + return `${year}-${month}-${day}`; + } + + private static getFormattedPostDateFromFileName(fileName: string): string { + // file name is of the format YYYY-MM-DD-Title.md + const year: string | undefined = fileName.split("-")[0]; + const month: string | undefined = fileName.split("-")[1]; + const day: string | undefined = fileName.split("-")[2]; + + if (!year || !month || !day) { + throw new BadDataException("Invalid file name"); } - public static getTitleFromFileContent(fileContent: string): string { - // title is the first line that stars with "#" + const date: Date = OneUptimeDate.getDateFromYYYYMMDD(year, month, day); + return OneUptimeDate.getDateAsLocalFormattedString(date, true); + } - const titleLine: string = - fileContent - .split('\n') - .find((line: string) => { - return line.startsWith('#'); - }) - ?.replace('#', '') || 'OneUptime Blog'; + private static getPostFromMarkdown(markdownContent: string): string { + const authorLine: string | undefined = markdownContent + .split("\n") + .find((line: string) => { + return line.startsWith("Author:"); + }); + const titleLine: string | undefined = markdownContent + .split("\n") + .find((line: string) => { + return line.startsWith("#"); + }); + const descriptionLine: string | undefined = + markdownContent.split("\n").find((line: string) => { + return line.startsWith("Description:"); + }) || ""; - return titleLine; + const tagsLine: string | undefined = + markdownContent.split("\n").find((line: string) => { + return line.startsWith("Tags:"); + }) || ""; + + if (!authorLine && !titleLine && !descriptionLine && !tagsLine) { + return markdownContent; } - public static getTagsFromFileContent(fileContent: string): string[] { - // tags is the first line that starts with "Tags:" + const lines: string[] = markdownContent.split("\n"); - const tagsLine: string | undefined = - fileContent - .split('\n') - .find((line: string) => { - return line.startsWith('Tags:'); - }) - ?.replace('Tags:', '') || ''; - - return tagsLine.split(',').map((tag: string) => { - return tag.trim(); - }); + if (authorLine) { + const authorLineIndex: number = lines.indexOf(authorLine); + lines.splice(authorLineIndex, 1); } - public static getDescriptionFromFileContent(fileContent: string): string { - // description is the first line that starts with ">" - - const descriptionLine: string | undefined = - fileContent - .split('\n') - .find((line: string) => { - return line.startsWith('Description:'); - }) - ?.replace('Description:', '') || ''; - - return descriptionLine; + if (titleLine) { + const titleLineIndex: number = lines.indexOf(titleLine); + lines.splice(titleLineIndex, 1); } - public static async getAuthorFromFileContent( - fileContent: string - ): Promise { - // author line is in this format: Author: [username](githubUrl) - - const authorLine: string | undefined = fileContent - .split('\n') - .find((line: string) => { - return line.startsWith('Author:'); - }); - const authorUsername: string | undefined = authorLine - ?.split('[')[1] - ?.split(']')[0]; - const authorGitHubUrl: string | undefined = authorLine - ?.split('(')[1] - ?.split(')')[0]; - const authorProfileImageUrl: string = `https://avatars.githubusercontent.com/${authorUsername}`; - - if (!authorUsername || !authorGitHubUrl) { - return null; - } - - return { - username: authorUsername, - githubUrl: authorGitHubUrl, - profileImageUrl: authorProfileImageUrl, - name: await this.getNameOfGitHubUser(authorUsername), - }; + if (descriptionLine) { + const descriptionLineIndex: number = lines.indexOf(descriptionLine); + lines.splice(descriptionLineIndex, 1); } + + if (tagsLine) { + const tagsLineIndex: number = lines.indexOf(tagsLine); + lines.splice(tagsLineIndex, 1); + } + + return lines.join("\n").trim(); + } + + public static getBlogPostFromCache(fileName: string): BlogPost | null { + const blogPost: BlogPost | null = LocalCache.getJSON( + "blog", + fileName, + ) as BlogPost | null; + return blogPost; + } + + public static getTitleFromFileContent(fileContent: string): string { + // title is the first line that stars with "#" + + const titleLine: string = + fileContent + .split("\n") + .find((line: string) => { + return line.startsWith("#"); + }) + ?.replace("#", "") || "OneUptime Blog"; + + return titleLine; + } + + public static getTagsFromFileContent(fileContent: string): string[] { + // tags is the first line that starts with "Tags:" + + const tagsLine: string | undefined = + fileContent + .split("\n") + .find((line: string) => { + return line.startsWith("Tags:"); + }) + ?.replace("Tags:", "") || ""; + + return tagsLine.split(",").map((tag: string) => { + return tag.trim(); + }); + } + + public static getDescriptionFromFileContent(fileContent: string): string { + // description is the first line that starts with ">" + + const descriptionLine: string | undefined = + fileContent + .split("\n") + .find((line: string) => { + return line.startsWith("Description:"); + }) + ?.replace("Description:", "") || ""; + + return descriptionLine; + } + + public static async getAuthorFromFileContent( + fileContent: string, + ): Promise { + // author line is in this format: Author: [username](githubUrl) + + const authorLine: string | undefined = fileContent + .split("\n") + .find((line: string) => { + return line.startsWith("Author:"); + }); + const authorUsername: string | undefined = authorLine + ?.split("[")[1] + ?.split("]")[0]; + const authorGitHubUrl: string | undefined = authorLine + ?.split("(")[1] + ?.split(")")[0]; + const authorProfileImageUrl: string = `https://avatars.githubusercontent.com/${authorUsername}`; + + if (!authorUsername || !authorGitHubUrl) { + return null; + } + + return { + username: authorUsername, + githubUrl: authorGitHubUrl, + profileImageUrl: authorProfileImageUrl, + name: await this.getNameOfGitHubUser(authorUsername), + }; + } } diff --git a/App/FeatureSet/Home/Utils/Config.ts b/App/FeatureSet/Home/Utils/Config.ts index c5e48a06b6..db99c3a6f4 100644 --- a/App/FeatureSet/Home/Utils/Config.ts +++ b/App/FeatureSet/Home/Utils/Config.ts @@ -1,2 +1,2 @@ -export const ViewsPath: string = '/usr/src/app/FeatureSet/Home/Views'; -export const StaticPath: string = '/usr/src/app/FeatureSet/Home/Static'; +export const ViewsPath: string = "/usr/src/app/FeatureSet/Home/Views"; +export const StaticPath: string = "/usr/src/app/FeatureSet/Home/Static"; diff --git a/App/FeatureSet/Home/Utils/NotFound.ts b/App/FeatureSet/Home/Utils/NotFound.ts index 17bd54dcc2..7e7f3a6461 100644 --- a/App/FeatureSet/Home/Utils/NotFound.ts +++ b/App/FeatureSet/Home/Utils/NotFound.ts @@ -1,15 +1,15 @@ -import { ViewsPath } from './Config'; -import { ExpressResponse } from 'CommonServer/Utils/Express'; +import { ViewsPath } from "./Config"; +import { ExpressResponse } from "CommonServer/Utils/Express"; export default class NotFoundUtil { - public static renderNotFound(res: ExpressResponse): void { - res.status(404); - res.render(`${ViewsPath}/not-found.ejs`, { - footerCards: false, - support: false, - cta: false, - blackLogo: false, - requestDemoCta: false, - }); - } + public static renderNotFound(res: ExpressResponse): void { + res.status(404); + res.render(`${ViewsPath}/not-found.ejs`, { + footerCards: false, + support: false, + cta: false, + blackLogo: false, + requestDemoCta: false, + }); + } } diff --git a/App/FeatureSet/Home/Utils/ProductCompare.ts b/App/FeatureSet/Home/Utils/ProductCompare.ts index 5e0bc087d0..6829704aa0 100644 --- a/App/FeatureSet/Home/Utils/ProductCompare.ts +++ b/App/FeatureSet/Home/Utils/ProductCompare.ts @@ -1,665 +1,653 @@ -import Dictionary from 'Common/Types/Dictionary'; +import Dictionary from "Common/Types/Dictionary"; export interface FAQ { - question: string; - answer: string; + question: string; + answer: string; } export enum ItemType { - Item = 'item', - Category = 'category', + Item = "item", + Category = "category", } export interface Item { - title: string; - description: string; - productColumn: string; - oneuptimeColumn: string; + title: string; + description: string; + productColumn: string; + oneuptimeColumn: string; } export interface Category { - name: string; - data: Array; + name: string; + data: Array; } export interface Product { - productName: string; - iconUrl: string; - price: string; - oneuptimePrice: string; - description: string; - descriptionLine2: string; - faq: Array; - items: Array; - oneUptimeDescription: string; - productDescription: string; + productName: string; + iconUrl: string; + price: string; + oneuptimePrice: string; + description: string; + descriptionLine2: string; + faq: Array; + items: Array; + oneUptimeDescription: string; + productDescription: string; } export default (product: string): Product => { - const products: Dictionary = { - pagerduty: { - productName: 'PagerDuty', - iconUrl: '/img/pagerduty.jpeg', - price: '$410', - oneuptimePrice: '$0', - productDescription: - 'For 10 users alerts and on-call schedule. PagerDuty is an on-call scheduling solution.', - oneUptimeDescription: - 'You can get alerts, on-call rotation and schedules for free with OneUptime and oa whole lot more.', - description: - 'Check out how we compare with PagerDuty. We do most of what PagerDuty does and a whole lot more.', - descriptionLine2: - "If you're a startup, we're a lot cheaper than PagerDuty which saves you a hundreds today, thousands as you grow.", - faq: [ - { - question: 'How does OneUptime compare with PagerDuty?', - answer: 'PagerDuty is an incident management and on-call tool whereas OneUptime is a complete Observability platform. OneUptime offers mostly everything that PagerDuty offers, but a lot more like monitoring, StatusPage, security, performance-monitoring and more. Please check detailed comparision above for more info.', - }, - { - question: - 'Do I need to buy a monitoring solution to monitor my resources?', - answer: 'PagerDuty needs a separate monitoring solution that you need to buy which then sends data to PagerDuty for on-call and incident management. OneUptime has a built in monitoring solution as well. You use one product, your team has one dashboard, save time, simplify ops.', - }, - { - question: - 'I have already bought an external monitoring solution. Will OneUptime work with it?', - answer: 'Yes! We integrate with every single monitoring solution in the market - like Incident.io, UptimeRobot, DataDog, Site 24x7 and more.', - }, - ], - items: [ - { - name: 'On-Call Scheduling', - data: [ - { - title: 'Alerts by Email, SMS, Call and Push Notifications', - description: - 'Have your team alerted by any of the channels including Slack and Microsoft Teams', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - { - title: 'On-Call Rotations', - description: - 'Rotate your on-call team daily, weekly or monthly. We also support custom rotations.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - { - title: 'Vacation Policy', - description: - "Have vacation policy built into your company's on-call schedule.", - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - { - title: 'Sick Policy', - description: - "Have sick policy built into your company's on-call schedule.", - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - { - title: 'On-call for Geo-distributed teams', - description: - 'Support on-call schedules for teams in multiple timezones who are geo-distributed.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - ], - }, - { - name: 'Monitoring', - data: [ - { - title: 'Monitor anything', - description: - "Server, Containers, API's, Websites, IoT and more.", - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Uptime Check', - description: - 'How often we check uptime of your resources.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Probe Locations', - description: - 'We check your uptime from different locations around the world.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Public Status Pages', - description: - 'Public Status Page for your customers.', - productColumn: 'Every 1 second', - oneuptimeColumn: 'US, Canada, EU & Australia.', - }, - ], - }, - { - name: 'Status Page', - data: [ - { - title: 'Public Status Pages', - description: - 'Public Status Page for your customers.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Unlimited Subscribers', - description: - 'You can have unlimited customer subscribers and have them alerted by Email, SMS, RSS or more.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Scheduled Events', - description: - 'You can show scheduled maintenance window on your status page.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Private Status Page', - description: - 'Private status pages for your internal team.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - ], - }, - { - name: 'More', - data: [ - { - title: 'Integrations', - description: - 'Integrate OneUptime with more than 2000+ apps.', - productColumn: 'Integrates with 350+ apps', - oneuptimeColumn: 'Integrates with 2000+ apps', - }, - { - title: 'API Access', - description: - 'Build custom integrations with unlimited API access.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - ], - }, - ], + const products: Dictionary = { + pagerduty: { + productName: "PagerDuty", + iconUrl: "/img/pagerduty.jpeg", + price: "$410", + oneuptimePrice: "$0", + productDescription: + "For 10 users alerts and on-call schedule. PagerDuty is an on-call scheduling solution.", + oneUptimeDescription: + "You can get alerts, on-call rotation and schedules for free with OneUptime and oa whole lot more.", + description: + "Check out how we compare with PagerDuty. We do most of what PagerDuty does and a whole lot more.", + descriptionLine2: + "If you're a startup, we're a lot cheaper than PagerDuty which saves you a hundreds today, thousands as you grow.", + faq: [ + { + question: "How does OneUptime compare with PagerDuty?", + answer: + "PagerDuty is an incident management and on-call tool whereas OneUptime is a complete Observability platform. OneUptime offers mostly everything that PagerDuty offers, but a lot more like monitoring, StatusPage, security, performance-monitoring and more. Please check detailed comparision above for more info.", }, - 'statuspage.io': { - productName: 'StatusPage.io', - iconUrl: '/img/statuspagelogo.png', - price: '$99', - oneuptimePrice: '$0', - productDescription: - 'For their status pages with 1000 subscribers. ', - oneUptimeDescription: - 'OneUptime offers unlimited status pages with unlimited subscribers for free.', - description: - 'Check out how we compare with StatusPage.io. We do most of what StatusPage.io does and a whole lot more.', - descriptionLine2: - "If you're a startup, we're a lot cheaper than StatusPage.io which saves you a hundreds today, thousands as you grow.", - faq: [ - { - question: 'How does OneUptime compare with StatusPage.io?', - answer: 'StatusPage.io is a status page tool whereas OneUptime is a complete Observability platform. OneUptime offers mostly everything that StatusPage.io offers, but a lot more like monitoring, incident management, on-call scheduling, security, performance-monitoring and more. Please check detailed comparision above for more info.', - }, - { - question: - 'Do I need to buy a monitoring solution to monitor my resources?', - answer: 'StatusPage.io needs a separate monitoring solution that you need to buy which then sends data to StatusPage.io. OneUptime has a built in monitoring solution as well. You use one product, your team has one dashboard, save time, simplify ops.', - }, - { - question: - 'I have already bought an external monitoring solution. Will OneUptime work with it?', - answer: 'Yes! We integrate with every single monitoring solution in the market - like Pingdom, UptimeRobot, DataDog, Site 24x7 and more.', - }, - ], - items: [ - { - name: 'On-Call Scheduling', - data: [ - { - title: 'Alerts by Email, SMS, Call and Push Notifications', - description: - 'Have your team alerted by any of the channels including Slack and Microsoft Teams', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'On-Call Rotations', - description: - 'Rotate your on-call team daily, weekly or monthly. We also support custom rotations.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Vacation Policy', - description: - "Have vacation policy built into your company's on-call schedule.", - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Sick Policy', - description: - "Have sick policy built into your company's on-call schedule.", - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'On-call for Geo-distributed teams', - description: - 'Support on-call schedules for teams in multiple timezones who are geo-distributed.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - ], - }, - - { - name: 'Monitoring', - data: [ - { - title: 'Monitor anything', - description: - "Server, Containers, API's, Websites, IoT and more.", - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Uptime Check', - description: - 'How often we check uptime of your resources.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Probe Locations', - description: - 'We check your uptime from different locations around the world.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Public Status Pages', - description: - 'Public Status Page for your customers.', - productColumn: '', - oneuptimeColumn: 'US, Canada, EU & Australia.', - }, - ], - }, - { - name: 'Status Pages', - data: [ - { - title: 'Public Status Pages', - description: - 'Public Status Page for your customers.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - { - title: 'Subscribers', - description: - 'You can have customer subscribers and have them alerted by Email, SMS, RSS or more.', - productColumn: '250 Subscribers', - oneuptimeColumn: 'Unlimited Subscribers', - }, - { - title: 'Scheduled Events', - description: - 'You can show scheduled maintenance window on your status page.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - { - title: 'Private Status Page', - description: - 'Private status pages for your internal team.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - ], - }, - - { - name: 'Misc', - data: [ - { - title: 'Integrations', - description: - 'Integrate OneUptime with more than 2000+ apps.', - productColumn: 'Integrates with 80+ apps', - oneuptimeColumn: 'Integrates with 2000+ apps', - }, - { - title: 'API Access', - description: - 'Build custom integrations with unlimited API access.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - ], - }, - ], + { + question: + "Do I need to buy a monitoring solution to monitor my resources?", + answer: + "PagerDuty needs a separate monitoring solution that you need to buy which then sends data to PagerDuty for on-call and incident management. OneUptime has a built in monitoring solution as well. You use one product, your team has one dashboard, save time, simplify ops.", }, - pingdom: { - productName: 'Pingdom', - iconUrl: '/img/pingdom.svg', - price: '$49', - productDescription: - 'Pingdom charges you $49/mo for 50 uptime monitors.', - oneUptimeDescription: - 'OneUptime offers unlimited monitoring with unlimited alerts for free.', - oneuptimePrice: '$0', - description: - 'Check out how we compare with Pingdom. We do most of what Pingdom does and a whole lot more.', - descriptionLine2: - "If you're a startup, we're a lot cheaper than Pingdom which saves you a hundreds today, thousands as you grow.", - faq: [ - { - question: 'How does OneUptime compare with Pingdom?', - answer: 'Pingdom is an monitoring tool whereas OneUptime is a complete Observability platform. OneUptime offers mostly everything that Pingdom offers, but a lot more like monitoring, StatusPage, security, performance-monitoring and more. Please check detailed comparision above for more info.', - }, - { - question: - 'Do I need to buy an incident management and on-call solution for alerts?', - answer: 'Pingdom is a monitoring solution and you need to buy an on-call solution and incident management solution which Pingdom sends data to. OneUptime has a built in monitoring, on-call and incident management. You use one product, your team has one dashboard, save time, simplify ops.', - }, - { - question: - 'I have already bought an external on-call and incident management solution. Will OneUptime work with it?', - answer: 'Yes! We integrate with every single on-call and incident management solution in the market - like PagerDuty, OpsGenie and more.', - }, - ], - items: [ - { - name: 'On-Call Scheduling', - data: [ - { - title: 'Alerts by Email, SMS, Call and Push Notifications', - description: - 'Have your team alerted by any of the channels including Slack and Microsoft Teams', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'On-Call Rotations', - description: - 'Rotate your on-call team daily, weekly or monthly. We also support custom rotations.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Vacation Policy', - description: - "Have vacation policy built into your company's on-call schedule.", - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Sick Policy', - description: - "Have sick policy built into your company's on-call schedule.", - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'On-call for Geo-distributed teams', - description: - 'Support on-call schedules for teams in multiple timezones who are geo-distributed.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - ], - }, - - { - name: 'Monitoring', - data: [ - { - title: 'Monitor anything', - description: - "Server, Containers, API's, Websites, IoT and more.", - productColumn: 'Monitors only API and Websites.', - oneuptimeColumn: 'tick', - }, - { - title: 'Uptime Check', - description: - 'How often we check uptime of your resources.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - { - title: 'Probe Locations', - description: - 'We check your uptime from different locations around the world.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - ], - }, - - { - name: 'Status Page', - data: [ - { - title: 'Public Status Pages', - description: - 'Public Status Page for your customers.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - { - title: 'Unlimited Subscribers', - description: - 'You can have unlimited customer subscribers and have them alerted by Email, SMS, RSS or more.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Scheduled Events', - description: - 'You can show scheduled maintenance window on your status page.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Private Status Page', - description: - 'Private status pages for your internal team.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - ], - }, - - { - name: 'Misc', - data: [ - { - title: 'Integrations', - description: - 'Integrate OneUptime with more than 2000+ apps.', - productColumn: 'Integrates with 100+ apps', - oneuptimeColumn: 'Integrates with 2000+ apps', - }, - { - title: 'API Access', - description: - 'Build custom integrations with unlimited API access.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - ], - }, - ], + { + question: + "I have already bought an external monitoring solution. Will OneUptime work with it?", + answer: + "Yes! We integrate with every single monitoring solution in the market - like Incident.io, UptimeRobot, DataDog, Site 24x7 and more.", }, - 'incident.io': { - productName: 'Incident.io', - iconUrl: '/img/pingdom.svg', - price: '$160', - productDescription: - 'For 10 teammates on the platform, responding to incidents.', - oneUptimeDescription: - 'OneUptime offers unlimited monitoring and alerting. Post incidents directly on Status Page (included).', - oneuptimePrice: '$0', - description: - 'Check out how we compare with Incident.io. We do most of what Incident.io does and a whole lot more.', - descriptionLine2: - "If you're a startup, we're a lot cheaper than Incident.io which saves you a hundreds today, thousands as you grow.", - faq: [ - { - question: 'How does OneUptime compare with Incident.io?', - answer: 'Incident.io is just an incident management platform whereas OneUptime is a complete Observability platform. OneUptime offers mostly everything that Incident.io offers, but a lot more like monitoring, status-page, security, performance-monitoring and more. Please check detailed comparision above for more info.', - }, - { - question: - 'Do I need to buy an incident management and on-call solution for alerts?', - answer: 'Incident.io is a incident management solution and you need to buy an on-call solution and monitoring solution which Incident.io receives data from. OneUptime has a built in monitoring, on-call and incident management. You use one product, your team has one dashboard, save time, simplify ops.', - }, - { - question: - 'I have already bought an external on-call and monitoring solution. Will OneUptime work with it?', - answer: 'Yes! We integrate with every single on-call and monitoring solution in the market - like PagerDuty, Pingdom and more.', - }, - ], - items: [ - { - name: 'On-Call Scheduling', - data: [ - { - title: 'Alerts by Email, SMS, Call and Push Notifications', - description: - 'Have your team alerted by any of the channels including Slack and Microsoft Teams', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'On-Call Rotations', - description: - 'Rotate your on-call team daily, weekly or monthly. We also support custom rotations.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Vacation Policy', - description: - "Have vacation policy built into your company's on-call schedule.", - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Sick Policy', - description: - "Have sick policy built into your company's on-call schedule.", - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'On-call for Geo-distributed teams', - description: - 'Support on-call schedules for teams in multiple timezones who are geo-distributed.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - ], - }, - - { - name: 'Monitoring', - data: [ - { - title: 'Monitor anything', - description: - "Server, Containers, API's, Websites, IoT and more.", - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Uptime Check', - description: - 'How often we check uptime of your resources.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Probe Locations', - description: - 'We check your uptime from different locations around the world.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - ], - }, - - { - name: 'Status Page', - data: [ - { - title: 'Public Status Pages', - description: - 'Public Status Page for your customers.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Unlimited Subscribers', - description: - 'You can have unlimited customer subscribers and have them alerted by Email, SMS, RSS or more.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Scheduled Events', - description: - 'You can show scheduled maintenance window on your status page.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - { - title: 'Private Status Page', - description: - 'Private status pages for your internal team.', - productColumn: '', - oneuptimeColumn: 'tick', - }, - ], - }, - - { - name: 'Misc', - data: [ - { - title: 'Integrations', - description: - 'Integrate OneUptime with more than 2000+ apps.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - { - title: 'API Access', - description: - 'Build custom integrations with unlimited API access.', - productColumn: 'tick', - oneuptimeColumn: 'tick', - }, - ], - }, - ], + ], + items: [ + { + name: "On-Call Scheduling", + data: [ + { + title: "Alerts by Email, SMS, Call and Push Notifications", + description: + "Have your team alerted by any of the channels including Slack and Microsoft Teams", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + { + title: "On-Call Rotations", + description: + "Rotate your on-call team daily, weekly or monthly. We also support custom rotations.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + { + title: "Vacation Policy", + description: + "Have vacation policy built into your company's on-call schedule.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + { + title: "Sick Policy", + description: + "Have sick policy built into your company's on-call schedule.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + { + title: "On-call for Geo-distributed teams", + description: + "Support on-call schedules for teams in multiple timezones who are geo-distributed.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + ], + }, + { + name: "Monitoring", + data: [ + { + title: "Monitor anything", + description: "Server, Containers, API's, Websites, IoT and more.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Uptime Check", + description: "How often we check uptime of your resources.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Probe Locations", + description: + "We check your uptime from different locations around the world.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Public Status Pages", + description: "Public Status Page for your customers.", + productColumn: "Every 1 second", + oneuptimeColumn: "US, Canada, EU & Australia.", + }, + ], + }, + { + name: "Status Page", + data: [ + { + title: "Public Status Pages", + description: "Public Status Page for your customers.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Unlimited Subscribers", + description: + "You can have unlimited customer subscribers and have them alerted by Email, SMS, RSS or more.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Scheduled Events", + description: + "You can show scheduled maintenance window on your status page.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Private Status Page", + description: "Private status pages for your internal team.", + productColumn: "", + oneuptimeColumn: "tick", + }, + ], + }, + { + name: "More", + data: [ + { + title: "Integrations", + description: "Integrate OneUptime with more than 2000+ apps.", + productColumn: "Integrates with 350+ apps", + oneuptimeColumn: "Integrates with 2000+ apps", + }, + { + title: "API Access", + description: + "Build custom integrations with unlimited API access.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + ], + }, + ], + }, + "statuspage.io": { + productName: "StatusPage.io", + iconUrl: "/img/statuspagelogo.png", + price: "$99", + oneuptimePrice: "$0", + productDescription: "For their status pages with 1000 subscribers. ", + oneUptimeDescription: + "OneUptime offers unlimited status pages with unlimited subscribers for free.", + description: + "Check out how we compare with StatusPage.io. We do most of what StatusPage.io does and a whole lot more.", + descriptionLine2: + "If you're a startup, we're a lot cheaper than StatusPage.io which saves you a hundreds today, thousands as you grow.", + faq: [ + { + question: "How does OneUptime compare with StatusPage.io?", + answer: + "StatusPage.io is a status page tool whereas OneUptime is a complete Observability platform. OneUptime offers mostly everything that StatusPage.io offers, but a lot more like monitoring, incident management, on-call scheduling, security, performance-monitoring and more. Please check detailed comparision above for more info.", + }, + { + question: + "Do I need to buy a monitoring solution to monitor my resources?", + answer: + "StatusPage.io needs a separate monitoring solution that you need to buy which then sends data to StatusPage.io. OneUptime has a built in monitoring solution as well. You use one product, your team has one dashboard, save time, simplify ops.", + }, + { + question: + "I have already bought an external monitoring solution. Will OneUptime work with it?", + answer: + "Yes! We integrate with every single monitoring solution in the market - like Pingdom, UptimeRobot, DataDog, Site 24x7 and more.", + }, + ], + items: [ + { + name: "On-Call Scheduling", + data: [ + { + title: "Alerts by Email, SMS, Call and Push Notifications", + description: + "Have your team alerted by any of the channels including Slack and Microsoft Teams", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "On-Call Rotations", + description: + "Rotate your on-call team daily, weekly or monthly. We also support custom rotations.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Vacation Policy", + description: + "Have vacation policy built into your company's on-call schedule.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Sick Policy", + description: + "Have sick policy built into your company's on-call schedule.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "On-call for Geo-distributed teams", + description: + "Support on-call schedules for teams in multiple timezones who are geo-distributed.", + productColumn: "", + oneuptimeColumn: "tick", + }, + ], }, - }; - return products[product] as Product; + { + name: "Monitoring", + data: [ + { + title: "Monitor anything", + description: "Server, Containers, API's, Websites, IoT and more.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Uptime Check", + description: "How often we check uptime of your resources.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Probe Locations", + description: + "We check your uptime from different locations around the world.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Public Status Pages", + description: "Public Status Page for your customers.", + productColumn: "", + oneuptimeColumn: "US, Canada, EU & Australia.", + }, + ], + }, + { + name: "Status Pages", + data: [ + { + title: "Public Status Pages", + description: "Public Status Page for your customers.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + { + title: "Subscribers", + description: + "You can have customer subscribers and have them alerted by Email, SMS, RSS or more.", + productColumn: "250 Subscribers", + oneuptimeColumn: "Unlimited Subscribers", + }, + { + title: "Scheduled Events", + description: + "You can show scheduled maintenance window on your status page.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + { + title: "Private Status Page", + description: "Private status pages for your internal team.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + ], + }, + + { + name: "Misc", + data: [ + { + title: "Integrations", + description: "Integrate OneUptime with more than 2000+ apps.", + productColumn: "Integrates with 80+ apps", + oneuptimeColumn: "Integrates with 2000+ apps", + }, + { + title: "API Access", + description: + "Build custom integrations with unlimited API access.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + ], + }, + ], + }, + pingdom: { + productName: "Pingdom", + iconUrl: "/img/pingdom.svg", + price: "$49", + productDescription: "Pingdom charges you $49/mo for 50 uptime monitors.", + oneUptimeDescription: + "OneUptime offers unlimited monitoring with unlimited alerts for free.", + oneuptimePrice: "$0", + description: + "Check out how we compare with Pingdom. We do most of what Pingdom does and a whole lot more.", + descriptionLine2: + "If you're a startup, we're a lot cheaper than Pingdom which saves you a hundreds today, thousands as you grow.", + faq: [ + { + question: "How does OneUptime compare with Pingdom?", + answer: + "Pingdom is an monitoring tool whereas OneUptime is a complete Observability platform. OneUptime offers mostly everything that Pingdom offers, but a lot more like monitoring, StatusPage, security, performance-monitoring and more. Please check detailed comparision above for more info.", + }, + { + question: + "Do I need to buy an incident management and on-call solution for alerts?", + answer: + "Pingdom is a monitoring solution and you need to buy an on-call solution and incident management solution which Pingdom sends data to. OneUptime has a built in monitoring, on-call and incident management. You use one product, your team has one dashboard, save time, simplify ops.", + }, + { + question: + "I have already bought an external on-call and incident management solution. Will OneUptime work with it?", + answer: + "Yes! We integrate with every single on-call and incident management solution in the market - like PagerDuty, OpsGenie and more.", + }, + ], + items: [ + { + name: "On-Call Scheduling", + data: [ + { + title: "Alerts by Email, SMS, Call and Push Notifications", + description: + "Have your team alerted by any of the channels including Slack and Microsoft Teams", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "On-Call Rotations", + description: + "Rotate your on-call team daily, weekly or monthly. We also support custom rotations.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Vacation Policy", + description: + "Have vacation policy built into your company's on-call schedule.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Sick Policy", + description: + "Have sick policy built into your company's on-call schedule.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "On-call for Geo-distributed teams", + description: + "Support on-call schedules for teams in multiple timezones who are geo-distributed.", + productColumn: "", + oneuptimeColumn: "tick", + }, + ], + }, + + { + name: "Monitoring", + data: [ + { + title: "Monitor anything", + description: "Server, Containers, API's, Websites, IoT and more.", + productColumn: "Monitors only API and Websites.", + oneuptimeColumn: "tick", + }, + { + title: "Uptime Check", + description: "How often we check uptime of your resources.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + { + title: "Probe Locations", + description: + "We check your uptime from different locations around the world.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + ], + }, + + { + name: "Status Page", + data: [ + { + title: "Public Status Pages", + description: "Public Status Page for your customers.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + { + title: "Unlimited Subscribers", + description: + "You can have unlimited customer subscribers and have them alerted by Email, SMS, RSS or more.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Scheduled Events", + description: + "You can show scheduled maintenance window on your status page.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Private Status Page", + description: "Private status pages for your internal team.", + productColumn: "", + oneuptimeColumn: "tick", + }, + ], + }, + + { + name: "Misc", + data: [ + { + title: "Integrations", + description: "Integrate OneUptime with more than 2000+ apps.", + productColumn: "Integrates with 100+ apps", + oneuptimeColumn: "Integrates with 2000+ apps", + }, + { + title: "API Access", + description: + "Build custom integrations with unlimited API access.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + ], + }, + ], + }, + "incident.io": { + productName: "Incident.io", + iconUrl: "/img/pingdom.svg", + price: "$160", + productDescription: + "For 10 teammates on the platform, responding to incidents.", + oneUptimeDescription: + "OneUptime offers unlimited monitoring and alerting. Post incidents directly on Status Page (included).", + oneuptimePrice: "$0", + description: + "Check out how we compare with Incident.io. We do most of what Incident.io does and a whole lot more.", + descriptionLine2: + "If you're a startup, we're a lot cheaper than Incident.io which saves you a hundreds today, thousands as you grow.", + faq: [ + { + question: "How does OneUptime compare with Incident.io?", + answer: + "Incident.io is just an incident management platform whereas OneUptime is a complete Observability platform. OneUptime offers mostly everything that Incident.io offers, but a lot more like monitoring, status-page, security, performance-monitoring and more. Please check detailed comparision above for more info.", + }, + { + question: + "Do I need to buy an incident management and on-call solution for alerts?", + answer: + "Incident.io is a incident management solution and you need to buy an on-call solution and monitoring solution which Incident.io receives data from. OneUptime has a built in monitoring, on-call and incident management. You use one product, your team has one dashboard, save time, simplify ops.", + }, + { + question: + "I have already bought an external on-call and monitoring solution. Will OneUptime work with it?", + answer: + "Yes! We integrate with every single on-call and monitoring solution in the market - like PagerDuty, Pingdom and more.", + }, + ], + items: [ + { + name: "On-Call Scheduling", + data: [ + { + title: "Alerts by Email, SMS, Call and Push Notifications", + description: + "Have your team alerted by any of the channels including Slack and Microsoft Teams", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "On-Call Rotations", + description: + "Rotate your on-call team daily, weekly or monthly. We also support custom rotations.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Vacation Policy", + description: + "Have vacation policy built into your company's on-call schedule.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Sick Policy", + description: + "Have sick policy built into your company's on-call schedule.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "On-call for Geo-distributed teams", + description: + "Support on-call schedules for teams in multiple timezones who are geo-distributed.", + productColumn: "", + oneuptimeColumn: "tick", + }, + ], + }, + + { + name: "Monitoring", + data: [ + { + title: "Monitor anything", + description: "Server, Containers, API's, Websites, IoT and more.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Uptime Check", + description: "How often we check uptime of your resources.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Probe Locations", + description: + "We check your uptime from different locations around the world.", + productColumn: "", + oneuptimeColumn: "tick", + }, + ], + }, + + { + name: "Status Page", + data: [ + { + title: "Public Status Pages", + description: "Public Status Page for your customers.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Unlimited Subscribers", + description: + "You can have unlimited customer subscribers and have them alerted by Email, SMS, RSS or more.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Scheduled Events", + description: + "You can show scheduled maintenance window on your status page.", + productColumn: "", + oneuptimeColumn: "tick", + }, + { + title: "Private Status Page", + description: "Private status pages for your internal team.", + productColumn: "", + oneuptimeColumn: "tick", + }, + ], + }, + + { + name: "Misc", + data: [ + { + title: "Integrations", + description: "Integrate OneUptime with more than 2000+ apps.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + { + title: "API Access", + description: + "Build custom integrations with unlimited API access.", + productColumn: "tick", + oneuptimeColumn: "tick", + }, + ], + }, + ], + }, + }; + + return products[product] as Product; }; diff --git a/App/FeatureSet/Home/Utils/ServerError.ts b/App/FeatureSet/Home/Utils/ServerError.ts index 266faa4de3..3c39a66f1c 100644 --- a/App/FeatureSet/Home/Utils/ServerError.ts +++ b/App/FeatureSet/Home/Utils/ServerError.ts @@ -1,15 +1,15 @@ -import { ViewsPath } from './Config'; -import { ExpressResponse } from 'CommonServer/Utils/Express'; +import { ViewsPath } from "./Config"; +import { ExpressResponse } from "CommonServer/Utils/Express"; export default class ServerErrorUtil { - public static renderServerError(res: ExpressResponse): void { - res.status(500); - res.render(`${ViewsPath}/server-error.ejs`, { - footerCards: false, - support: false, - cta: false, - blackLogo: false, - requestDemoCta: false, - }); - } + public static renderServerError(res: ExpressResponse): void { + res.status(500); + res.render(`${ViewsPath}/server-error.ejs`, { + footerCards: false, + support: false, + cta: false, + blackLogo: false, + requestDemoCta: false, + }); + } } diff --git a/App/FeatureSet/Identity/API/Authentication.ts b/App/FeatureSet/Identity/API/Authentication.ts index 883ef4267f..97f1009f42 100644 --- a/App/FeatureSet/Identity/API/Authentication.ts +++ b/App/FeatureSet/Identity/API/Authentication.ts @@ -1,648 +1,611 @@ -import AuthenticationEmail from '../Utils/AuthenticationEmail'; -import BaseModel from 'Common/Models/BaseModel'; -import { AccountsRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Email from 'Common/Types/Email'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import BadRequestException from 'Common/Types/Exception/BadRequestException'; -import { JSONObject } from 'Common/Types/JSON'; -import Name from 'Common/Types/Name'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; +import AuthenticationEmail from "../Utils/AuthenticationEmail"; +import BaseModel from "Common/Models/BaseModel"; +import { AccountsRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Email from "Common/Types/Email"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import BadRequestException from "Common/Types/Exception/BadRequestException"; +import { JSONObject } from "Common/Types/JSON"; +import Name from "Common/Types/Name"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; import { - EncryptionSecret, - IsBillingEnabled, -} from 'CommonServer/EnvironmentConfig'; -import AccessTokenService from 'CommonServer/Services/AccessTokenService'; -import EmailVerificationTokenService from 'CommonServer/Services/EmailVerificationTokenService'; -import MailService from 'CommonServer/Services/MailService'; -import UserService from 'CommonServer/Services/UserService'; -import CookieUtil from 'CommonServer/Utils/Cookie'; + EncryptionSecret, + IsBillingEnabled, +} from "CommonServer/EnvironmentConfig"; +import AccessTokenService from "CommonServer/Services/AccessTokenService"; +import EmailVerificationTokenService from "CommonServer/Services/EmailVerificationTokenService"; +import MailService from "CommonServer/Services/MailService"; +import UserService from "CommonServer/Services/UserService"; +import CookieUtil from "CommonServer/Utils/Cookie"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import JSONWebToken from 'CommonServer/Utils/JsonWebToken'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import EmailVerificationToken from 'Model/Models/EmailVerificationToken'; -import User from 'Model/Models/User'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import JSONWebToken from "CommonServer/Utils/JsonWebToken"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import EmailVerificationToken from "Model/Models/EmailVerificationToken"; +import User from "Model/Models/User"; const router: ExpressRouter = Express.getRouter(); router.post( - '/signup', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - if (await DatabaseConfig.shouldDisableSignup()) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException( - 'Sign up is disabled on this OneUptime Server. Please contact your server admin to enable it.' - ) - ); - } + "/signup", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + if (await DatabaseConfig.shouldDisableSignup()) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException( + "Sign up is disabled on this OneUptime Server. Please contact your server admin to enable it.", + ), + ); + } - const data: JSONObject = req.body['data']; + const data: JSONObject = req.body["data"]; - /* Creating a type that is a partial of the TBaseModel type. */ - const partialUser: User = BaseModel.fromJSON( - data as JSONObject, - User - ) as User; + /* Creating a type that is a partial of the TBaseModel type. */ + const partialUser: User = BaseModel.fromJSON( + data as JSONObject, + User, + ) as User; - if (IsBillingEnabled) { - //ALERT: Delete data.role so user don't accidently sign up as master-admin from the API. - partialUser.isMasterAdmin = false; - partialUser.isEmailVerified = false; - } else { - // IF its not a saas service then we will make the email verified. + if (IsBillingEnabled) { + //ALERT: Delete data.role so user don't accidently sign up as master-admin from the API. + partialUser.isMasterAdmin = false; + partialUser.isEmailVerified = false; + } else { + // IF its not a saas service then we will make the email verified. - // check if there are more than one user and if there is then we will not make the user master admin. + // check if there are more than one user and if there is then we will not make the user master admin. - const userCount: PositiveNumber = await UserService.countBy({ - props: { - isRoot: true, - }, - query: {}, - }); + const userCount: PositiveNumber = await UserService.countBy({ + props: { + isRoot: true, + }, + query: {}, + }); - partialUser.isMasterAdmin = userCount.isZero(); // if the user count is 0 then make the first user master admin. - partialUser.isEmailVerified = true; - } + partialUser.isMasterAdmin = userCount.isZero(); // if the user count is 0 then make the first user master admin. + partialUser.isEmailVerified = true; + } - const alreadySavedUser: User | null = await UserService.findOneBy({ - query: { email: partialUser.email as Email }, - select: { - _id: true, - password: true, - }, - props: { - isRoot: true, - }, - }); + const alreadySavedUser: User | null = await UserService.findOneBy({ + query: { email: partialUser.email as Email }, + select: { + _id: true, + password: true, + }, + props: { + isRoot: true, + }, + }); - if (alreadySavedUser && alreadySavedUser.password) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - `User with email ${partialUser.email} already exists.` - ) - ); - } + if (alreadySavedUser && alreadySavedUser.password) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + `User with email ${partialUser.email} already exists.`, + ), + ); + } - let savedUser: User | null = null; + let savedUser: User | null = null; - if (alreadySavedUser) { - savedUser = await UserService.updateOneByIdAndFetch({ - id: alreadySavedUser.id!, - data: { - password: partialUser.password!, - name: partialUser.name!, - companyPhoneNumber: partialUser.companyPhoneNumber!, - companyName: partialUser.companyName!, - }, - select: { - email: true, - _id: true, - name: true, - isMasterAdmin: true, - }, - props: { - isRoot: true, - }, - }); - } else { - const user: User = partialUser; + if (alreadySavedUser) { + savedUser = await UserService.updateOneByIdAndFetch({ + id: alreadySavedUser.id!, + data: { + password: partialUser.password!, + name: partialUser.name!, + companyPhoneNumber: partialUser.companyPhoneNumber!, + companyName: partialUser.companyName!, + }, + select: { + email: true, + _id: true, + name: true, + isMasterAdmin: true, + }, + props: { + isRoot: true, + }, + }); + } else { + const user: User = partialUser; - savedUser = await UserService.create({ - data: user, - props: { - isRoot: true, - }, - }); - } + savedUser = await UserService.create({ + data: user, + props: { + isRoot: true, + }, + }); + } - const generatedToken: ObjectID = ObjectID.generate(); + const generatedToken: ObjectID = ObjectID.generate(); - const emailVerificationToken: EmailVerificationToken = - new EmailVerificationToken(); - emailVerificationToken.userId = savedUser?.id as ObjectID; - emailVerificationToken.email = savedUser?.email as Email; - emailVerificationToken.token = generatedToken; - emailVerificationToken.expires = OneUptimeDate.getOneDayAfter(); + const emailVerificationToken: EmailVerificationToken = + new EmailVerificationToken(); + emailVerificationToken.userId = savedUser?.id as ObjectID; + emailVerificationToken.email = savedUser?.email as Email; + emailVerificationToken.token = generatedToken; + emailVerificationToken.expires = OneUptimeDate.getOneDayAfter(); - await EmailVerificationTokenService.create({ - data: emailVerificationToken, - props: { - isRoot: true, - }, - }); + await EmailVerificationTokenService.create({ + data: emailVerificationToken, + props: { + isRoot: true, + }, + }); - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - MailService.sendMail({ - toEmail: partialUser.email as Email, - subject: 'Welcome to OneUptime. Please verify your email.', - templateType: EmailTemplateType.SignupWelcomeEmail, - vars: { - name: (partialUser.name! as Name).toString(), - tokenVerifyUrl: new URL( - httpProtocol, - host, - new Route(AccountsRoute.toString()).addRoute( - '/verify-email/' + generatedToken.toString() - ) - ).toString(), - homeUrl: new URL(httpProtocol, host).toString(), - }, - }).catch((err: Error) => { - logger.error(err); - }); + MailService.sendMail({ + toEmail: partialUser.email as Email, + subject: "Welcome to OneUptime. Please verify your email.", + templateType: EmailTemplateType.SignupWelcomeEmail, + vars: { + name: (partialUser.name! as Name).toString(), + tokenVerifyUrl: new URL( + httpProtocol, + host, + new Route(AccountsRoute.toString()).addRoute( + "/verify-email/" + generatedToken.toString(), + ), + ).toString(), + homeUrl: new URL(httpProtocol, host).toString(), + }, + }).catch((err: Error) => { + logger.error(err); + }); - if (savedUser) { - // Refresh Permissions for this user here. - await AccessTokenService.refreshUserAllPermissions( - savedUser.id! - ); + if (savedUser) { + // Refresh Permissions for this user here. + await AccessTokenService.refreshUserAllPermissions(savedUser.id!); - const token: string = JSONWebToken.signUserLoginToken({ - tokenData: { - userId: savedUser.id!, - email: savedUser.email!, - name: savedUser.name!, - isMasterAdmin: savedUser.isMasterAdmin!, - isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO. - }, - expiresInSeconds: OneUptimeDate.getSecondsInDays( - new PositiveNumber(30) - ), - }); + const token: string = JSONWebToken.signUserLoginToken({ + tokenData: { + userId: savedUser.id!, + email: savedUser.email!, + name: savedUser.name!, + isMasterAdmin: savedUser.isMasterAdmin!, + isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO. + }, + expiresInSeconds: OneUptimeDate.getSecondsInDays( + new PositiveNumber(30), + ), + }); - // Set a cookie with token. - CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), token, { - maxAge: OneUptimeDate.getMillisecondsInDays( - new PositiveNumber(30) - ), - httpOnly: true, - }); + // Set a cookie with token. + CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), token, { + maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)), + httpOnly: true, + }); - logger.info('User signed up: ' + savedUser.email?.toString()); + logger.info("User signed up: " + savedUser.email?.toString()); - return Response.sendEntityResponse(req, res, savedUser, User); - } + return Response.sendEntityResponse(req, res, savedUser, User); + } - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Failed to create a user') - ); - } catch (err) { - return next(err); - } + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Failed to create a user"), + ); + } catch (err) { + return next(err); } + }, ); router.post( - '/forgot-password', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - const data: JSONObject = req.body['data']; + "/forgot-password", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + const data: JSONObject = req.body["data"]; - const user: User = BaseModel.fromJSON( - data as JSONObject, - User - ) as User; + const user: User = BaseModel.fromJSON(data as JSONObject, User) as User; - const alreadySavedUser: User | null = await UserService.findOneBy({ - query: { email: user.email! }, - select: { - _id: true, - password: true, - name: true, - email: true, - isMasterAdmin: true, - }, - props: { - isRoot: true, - }, - }); + const alreadySavedUser: User | null = await UserService.findOneBy({ + query: { email: user.email! }, + select: { + _id: true, + password: true, + name: true, + email: true, + isMasterAdmin: true, + }, + props: { + isRoot: true, + }, + }); - if (alreadySavedUser && alreadySavedUser.password) { - const token: string = ObjectID.generate().toString(); - await UserService.updateOneBy({ - query: { - _id: alreadySavedUser._id!, - }, - data: { - resetPasswordToken: token, - resetPasswordExpires: OneUptimeDate.getOneDayAfter(), - }, - props: { - isRoot: true, - }, - }); + if (alreadySavedUser && alreadySavedUser.password) { + const token: string = ObjectID.generate().toString(); + await UserService.updateOneBy({ + query: { + _id: alreadySavedUser._id!, + }, + data: { + resetPasswordToken: token, + resetPasswordExpires: OneUptimeDate.getOneDayAfter(), + }, + props: { + isRoot: true, + }, + }); - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - const tokenVerifyUrl: string = new URL( - httpProtocol, - host, - new Route(AccountsRoute.toString()).addRoute( - '/reset-password/' + token - ) - ).toString(); + const tokenVerifyUrl: string = new URL( + httpProtocol, + host, + new Route(AccountsRoute.toString()).addRoute( + "/reset-password/" + token, + ), + ).toString(); - logger.info('User forgot password: ' + user.email?.toString()); - logger.info('Reset Password URL: ' + tokenVerifyUrl); + logger.info("User forgot password: " + user.email?.toString()); + logger.info("Reset Password URL: " + tokenVerifyUrl); - MailService.sendMail({ - toEmail: user.email!, - subject: 'Password Reset Request for OneUptime', - templateType: EmailTemplateType.ForgotPassword, - vars: { - homeURL: new URL(httpProtocol, host).toString(), - tokenVerifyUrl: tokenVerifyUrl, - }, - }).catch((err: Error) => { - logger.error(err); - }); + MailService.sendMail({ + toEmail: user.email!, + subject: "Password Reset Request for OneUptime", + templateType: EmailTemplateType.ForgotPassword, + vars: { + homeURL: new URL(httpProtocol, host).toString(), + tokenVerifyUrl: tokenVerifyUrl, + }, + }).catch((err: Error) => { + logger.error(err); + }); - return Response.sendEmptySuccessResponse(req, res); - } + return Response.sendEmptySuccessResponse(req, res); + } - return Response.sendErrorResponse( - req, - res, - new BadDataException( - `No user is registered with ${user.email?.toString()}. Please sign up for a new account.` - ) - ); - } catch (err) { - return next(err); - } + return Response.sendErrorResponse( + req, + res, + new BadDataException( + `No user is registered with ${user.email?.toString()}. Please sign up for a new account.`, + ), + ); + } catch (err) { + return next(err); } + }, ); router.post( - '/verify-email', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - const data: JSONObject = req.body['data']; + "/verify-email", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + const data: JSONObject = req.body["data"]; - const token: EmailVerificationToken = BaseModel.fromJSON( - data as JSONObject, - EmailVerificationToken - ) as EmailVerificationToken; + const token: EmailVerificationToken = BaseModel.fromJSON( + data as JSONObject, + EmailVerificationToken, + ) as EmailVerificationToken; - const alreadySavedToken: EmailVerificationToken | null = - await EmailVerificationTokenService.findOneBy({ - query: { token: token.token! }, - select: { - _id: true, - userId: true, - email: true, - expires: true, - }, - props: { - isRoot: true, - }, - }); + const alreadySavedToken: EmailVerificationToken | null = + await EmailVerificationTokenService.findOneBy({ + query: { token: token.token! }, + select: { + _id: true, + userId: true, + email: true, + expires: true, + }, + props: { + isRoot: true, + }, + }); - if (!alreadySavedToken) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Invalid link. Please try to log in and we will resend you another link which you should be able to verify email with.' - ) - ); - } + if (!alreadySavedToken) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Invalid link. Please try to log in and we will resend you another link which you should be able to verify email with.", + ), + ); + } - if (OneUptimeDate.hasExpired(alreadySavedToken.expires!)) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Link expired. Please try to log in and we will resend you another link which you should be able to verify email with.' - ) - ); - } + if (OneUptimeDate.hasExpired(alreadySavedToken.expires!)) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Link expired. Please try to log in and we will resend you another link which you should be able to verify email with.", + ), + ); + } - const user: User | null = await UserService.findOneBy({ - query: { - email: alreadySavedToken.email!, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - email: true, - }, - }); + const user: User | null = await UserService.findOneBy({ + query: { + email: alreadySavedToken.email!, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + email: true, + }, + }); - if (!user) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Invalid link. Please try to log in and we will resend you another link which you should be able to verify email with.' - ) - ); - } + if (!user) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Invalid link. Please try to log in and we will resend you another link which you should be able to verify email with.", + ), + ); + } - await UserService.updateOneBy({ - query: { - _id: user._id!, - }, - data: { - isEmailVerified: true, - }, - props: { - isRoot: true, - }, - }); + await UserService.updateOneBy({ + query: { + _id: user._id!, + }, + data: { + isEmailVerified: true, + }, + props: { + isRoot: true, + }, + }); - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - MailService.sendMail({ - toEmail: user.email!, - subject: 'Email Verified.', - templateType: EmailTemplateType.EmailVerified, - vars: { - homeURL: new URL(httpProtocol, host).toString(), - }, - }).catch((err: Error) => { - logger.error(err); - }); + MailService.sendMail({ + toEmail: user.email!, + subject: "Email Verified.", + templateType: EmailTemplateType.EmailVerified, + vars: { + homeURL: new URL(httpProtocol, host).toString(), + }, + }).catch((err: Error) => { + logger.error(err); + }); - logger.info('User email verified: ' + user.email?.toString()); + logger.info("User email verified: " + user.email?.toString()); - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); - } + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); router.post( - '/reset-password', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - const data: JSONObject = req.body['data']; + "/reset-password", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + const data: JSONObject = req.body["data"]; - const user: User = BaseModel.fromJSON( - data as JSONObject, - User - ) as User; + const user: User = BaseModel.fromJSON(data as JSONObject, User) as User; - await user.password?.hashValue(EncryptionSecret); + await user.password?.hashValue(EncryptionSecret); - const alreadySavedUser: User | null = await UserService.findOneBy({ - query: { - resetPasswordToken: - (user.resetPasswordToken as string) || '', - }, - select: { - _id: true, - password: true, - name: true, - email: true, - isMasterAdmin: true, - resetPasswordExpires: true, - }, - props: { - isRoot: true, - }, - }); + const alreadySavedUser: User | null = await UserService.findOneBy({ + query: { + resetPasswordToken: (user.resetPasswordToken as string) || "", + }, + select: { + _id: true, + password: true, + name: true, + email: true, + isMasterAdmin: true, + resetPasswordExpires: true, + }, + props: { + isRoot: true, + }, + }); - if (!alreadySavedUser) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Invalid link. Please go to forgot password page again and request a new link.' - ) - ); - } + if (!alreadySavedUser) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Invalid link. Please go to forgot password page again and request a new link.", + ), + ); + } - if ( - alreadySavedUser && - OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!) - ) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Expired link. Please go to forgot password page again and request a new link.' - ) - ); - } + if ( + alreadySavedUser && + OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!) + ) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Expired link. Please go to forgot password page again and request a new link.", + ), + ); + } - await UserService.updateOneById({ - id: alreadySavedUser.id!, - data: { - password: user.password!, - resetPasswordToken: null!, - resetPasswordExpires: null!, - }, - props: { - isRoot: true, - }, - }); + await UserService.updateOneById({ + id: alreadySavedUser.id!, + data: { + password: user.password!, + resetPasswordToken: null!, + resetPasswordExpires: null!, + }, + props: { + isRoot: true, + }, + }); - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - MailService.sendMail({ - toEmail: alreadySavedUser.email!, - subject: 'Password Changed.', - templateType: EmailTemplateType.PasswordChanged, - vars: { - homeURL: new URL(httpProtocol, host).toString(), - }, - }).catch((err: Error) => { - logger.error(err); - }); + MailService.sendMail({ + toEmail: alreadySavedUser.email!, + subject: "Password Changed.", + templateType: EmailTemplateType.PasswordChanged, + vars: { + homeURL: new URL(httpProtocol, host).toString(), + }, + }).catch((err: Error) => { + logger.error(err); + }); - logger.info( - 'User password reset: ' + alreadySavedUser.email?.toString() - ); + logger.info("User password reset: " + alreadySavedUser.email?.toString()); - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); - } + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); router.post( - '/logout', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - CookieUtil.removeAllCookies(req, res); + "/logout", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + CookieUtil.removeAllCookies(req, res); - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); - } + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); router.post( - '/login', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - const data: JSONObject = req.body['data']; + "/login", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + const data: JSONObject = req.body["data"]; - const user: User = BaseModel.fromJSON( - data as JSONObject, - User - ) as User; + const user: User = BaseModel.fromJSON(data as JSONObject, User) as User; - await user.password?.hashValue(EncryptionSecret); + await user.password?.hashValue(EncryptionSecret); - const alreadySavedUser: User | null = await UserService.findOneBy({ - query: { email: user.email! }, - select: { - _id: true, - password: true, - name: true, - email: true, - isMasterAdmin: true, - isEmailVerified: true, - profilePictureId: true, - }, - props: { - isRoot: true, - }, - }); + const alreadySavedUser: User | null = await UserService.findOneBy({ + query: { email: user.email! }, + select: { + _id: true, + password: true, + name: true, + email: true, + isMasterAdmin: true, + isEmailVerified: true, + profilePictureId: true, + }, + props: { + isRoot: true, + }, + }); - if (alreadySavedUser) { - if (!alreadySavedUser.password) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'You have not signed up so far. Please go to the registration page to sign up.' - ) - ); - } - - if (!alreadySavedUser.isEmailVerified) { - await AuthenticationEmail.sendVerificationEmail( - alreadySavedUser - ); - - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.' - ) - ); - } - - // Refresh Permissions for this user here. - await AccessTokenService.refreshUserAllPermissions( - alreadySavedUser.id! - ); - - if ( - alreadySavedUser.password.toString() === - user.password!.toString() - ) { - logger.info( - 'User logged in: ' + alreadySavedUser.email?.toString() - ); - - const token: string = JSONWebToken.signUserLoginToken({ - tokenData: { - userId: alreadySavedUser.id!, - email: alreadySavedUser.email!, - name: alreadySavedUser.name!, - isMasterAdmin: alreadySavedUser.isMasterAdmin!, - isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO. - }, - expiresInSeconds: OneUptimeDate.getSecondsInDays( - new PositiveNumber(30) - ), - }); - - // Set a cookie with token. - CookieUtil.setCookie( - res, - CookieUtil.getUserTokenKey(), - token, - { - maxAge: OneUptimeDate.getMillisecondsInDays( - new PositiveNumber(30) - ), - httpOnly: true, - } - ); - - return Response.sendEntityResponse( - req, - res, - alreadySavedUser, - User - ); - } - } - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Invalid login: Email or password does not match.' - ) - ); - } catch (err) { - return next(err); + if (alreadySavedUser) { + if (!alreadySavedUser.password) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "You have not signed up so far. Please go to the registration page to sign up.", + ), + ); } + + if (!alreadySavedUser.isEmailVerified) { + await AuthenticationEmail.sendVerificationEmail(alreadySavedUser); + + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.", + ), + ); + } + + // Refresh Permissions for this user here. + await AccessTokenService.refreshUserAllPermissions( + alreadySavedUser.id!, + ); + + if ( + alreadySavedUser.password.toString() === user.password!.toString() + ) { + logger.info("User logged in: " + alreadySavedUser.email?.toString()); + + const token: string = JSONWebToken.signUserLoginToken({ + tokenData: { + userId: alreadySavedUser.id!, + email: alreadySavedUser.email!, + name: alreadySavedUser.name!, + isMasterAdmin: alreadySavedUser.isMasterAdmin!, + isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO. + }, + expiresInSeconds: OneUptimeDate.getSecondsInDays( + new PositiveNumber(30), + ), + }); + + // Set a cookie with token. + CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), token, { + maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)), + httpOnly: true, + }); + + return Response.sendEntityResponse(req, res, alreadySavedUser, User); + } + } + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Invalid login: Email or password does not match.", + ), + ); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/App/FeatureSet/Identity/API/Reseller.ts b/App/FeatureSet/Identity/API/Reseller.ts index 11de5a3bf9..f77fb3acff 100644 --- a/App/FeatureSet/Identity/API/Reseller.ts +++ b/App/FeatureSet/Identity/API/Reseller.ts @@ -1,79 +1,79 @@ -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ResellerService from 'CommonServer/Services/ResellerService'; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ResellerService from "CommonServer/Services/ResellerService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import JSONWebToken from 'CommonServer/Utils/JsonWebToken'; -import Response from 'CommonServer/Utils/Response'; -import Reseller from 'Model/Models/Reseller'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import JSONWebToken from "CommonServer/Utils/JsonWebToken"; +import Response from "CommonServer/Utils/Response"; +import Reseller from "Model/Models/Reseller"; const router: ExpressRouter = Express.getRouter(); router.post( - '/reseller/auth/:resellerid', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - const resellerId: string | undefined = req.params['resellerid']; + "/reseller/auth/:resellerid", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + const resellerId: string | undefined = req.params["resellerid"]; - if (!resellerId) { - throw new BadDataException('Reseller ID not found'); - } + if (!resellerId) { + throw new BadDataException("Reseller ID not found"); + } - const username: string = req.body['username']; - const password: string = req.body['password']; + const username: string = req.body["username"]; + const password: string = req.body["password"]; - if (!username) { - throw new BadDataException('Username not found'); - } + if (!username) { + throw new BadDataException("Username not found"); + } - if (!password) { - throw new BadDataException('Password not found'); - } + if (!password) { + throw new BadDataException("Password not found"); + } - // get the reseller user. - const reseller: Reseller | null = await ResellerService.findOneBy({ - query: { - resellerId: resellerId, - username: username, - password: password, - }, - select: { - _id: true, - resellerId: true, - }, - props: { - isRoot: true, - }, - }); + // get the reseller user. + const reseller: Reseller | null = await ResellerService.findOneBy({ + query: { + resellerId: resellerId, + username: username, + password: password, + }, + select: { + _id: true, + resellerId: true, + }, + props: { + isRoot: true, + }, + }); - if (!reseller) { - throw new BadDataException( - 'Reseller not found or username and password is incorrect' - ); - } + if (!reseller) { + throw new BadDataException( + "Reseller not found or username and password is incorrect", + ); + } - // if found then generate a token and return it. + // if found then generate a token and return it. - const token: string = JSONWebToken.sign({ - data: { resellerId: resellerId }, - expiresInSeconds: OneUptimeDate.getDayInSeconds(365), - }); + const token: string = JSONWebToken.sign({ + data: { resellerId: resellerId }, + expiresInSeconds: OneUptimeDate.getDayInSeconds(365), + }); - return Response.sendJsonObjectResponse(req, res, { - access: token, - }); - } catch (err) { - return next(err); - } + return Response.sendJsonObjectResponse(req, res, { + access: token, + }); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/App/FeatureSet/Identity/API/SSO.ts b/App/FeatureSet/Identity/API/SSO.ts index ee64605e34..bff5ec659c 100644 --- a/App/FeatureSet/Identity/API/SSO.ts +++ b/App/FeatureSet/Identity/API/SSO.ts @@ -1,471 +1,436 @@ -import AuthenticationEmail from '../Utils/AuthenticationEmail'; -import SSOUtil from '../Utils/SSO'; -import { DashboardRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Email from 'Common/Types/Email'; -import BadRequestException from 'Common/Types/Exception/BadRequestException'; -import Exception from 'Common/Types/Exception/Exception'; -import ServerException from 'Common/Types/Exception/ServerException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig'; -import AccessTokenService from 'CommonServer/Services/AccessTokenService'; -import ProjectSSOService from 'CommonServer/Services/ProjectSsoService'; -import TeamMemberService from 'CommonServer/Services/TeamMemberService'; -import UserService from 'CommonServer/Services/UserService'; -import CookieUtil from 'CommonServer/Utils/Cookie'; +import AuthenticationEmail from "../Utils/AuthenticationEmail"; +import SSOUtil from "../Utils/SSO"; +import { DashboardRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Email from "Common/Types/Email"; +import BadRequestException from "Common/Types/Exception/BadRequestException"; +import Exception from "Common/Types/Exception/Exception"; +import ServerException from "Common/Types/Exception/ServerException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import { Host, HttpProtocol } from "CommonServer/EnvironmentConfig"; +import AccessTokenService from "CommonServer/Services/AccessTokenService"; +import ProjectSSOService from "CommonServer/Services/ProjectSsoService"; +import TeamMemberService from "CommonServer/Services/TeamMemberService"; +import UserService from "CommonServer/Services/UserService"; +import CookieUtil from "CommonServer/Utils/Cookie"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import JSONWebToken from 'CommonServer/Utils/JsonWebToken'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import ProjectSSO from 'Model/Models/ProjectSso'; -import TeamMember from 'Model/Models/TeamMember'; -import User from 'Model/Models/User'; -import xml2js from 'xml2js'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import JSONWebToken from "CommonServer/Utils/JsonWebToken"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import ProjectSSO from "Model/Models/ProjectSso"; +import TeamMember from "Model/Models/TeamMember"; +import User from "Model/Models/User"; +import xml2js from "xml2js"; const router: ExpressRouter = Express.getRouter(); router.get( - '/sso/:projectId/:projectSsoId', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - if (!req.params['projectId']) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Project ID not found') - ); - } + "/sso/:projectId/:projectSsoId", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + if (!req.params["projectId"]) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Project ID not found"), + ); + } - if (!req.params['projectSsoId']) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Project SSO ID not found') - ); - } + if (!req.params["projectSsoId"]) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Project SSO ID not found"), + ); + } - const projectSSO: ProjectSSO | null = - await ProjectSSOService.findOneBy({ - query: { - projectId: new ObjectID(req.params['projectId']), - _id: req.params['projectSsoId'], - isEnabled: true, - }, - select: { - _id: true, - signOnURL: true, - issuerURL: true, - projectId: true, - }, - props: { - isRoot: true, - }, - }); + const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy({ + query: { + projectId: new ObjectID(req.params["projectId"]), + _id: req.params["projectSsoId"], + isEnabled: true, + }, + select: { + _id: true, + signOnURL: true, + issuerURL: true, + projectId: true, + }, + props: { + isRoot: true, + }, + }); - if (!projectSSO) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('SSO Config not found') - ); - } + if (!projectSSO) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("SSO Config not found"), + ); + } - // redirect to Identity Provider. + // redirect to Identity Provider. - if (!projectSSO.signOnURL) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Sign On URL not found') - ); - } + if (!projectSSO.signOnURL) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Sign On URL not found"), + ); + } - if (!projectSSO.issuerURL) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Issuer not found') - ); - } + if (!projectSSO.issuerURL) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Issuer not found"), + ); + } - const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({ - acsUrl: URL.fromString( - `${HttpProtocol}${Host}/identity/idp-login/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}` - ), - signOnUrl: projectSSO.signOnURL!, - issuerUrl: URL.fromString( - `${HttpProtocol}${Host}/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}` - ), - }); + const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({ + acsUrl: URL.fromString( + `${HttpProtocol}${Host}/identity/idp-login/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`, + ), + signOnUrl: projectSSO.signOnURL!, + issuerUrl: URL.fromString( + `${HttpProtocol}${Host}/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`, + ), + }); - return Response.redirect(req, res, samlRequestUrl); - } catch (err) { - return next(err); - } + return Response.redirect(req, res, samlRequestUrl); + } catch (err) { + return next(err); } + }, ); router.get( - '/idp-login/:projectId/:projectSsoId', - async (req: ExpressRequest, res: ExpressResponse): Promise => { - return await loginUserWithSso(req, res); - } + "/idp-login/:projectId/:projectSsoId", + async (req: ExpressRequest, res: ExpressResponse): Promise => { + return await loginUserWithSso(req, res); + }, ); router.post( - '/idp-login/:projectId/:projectSsoId', - async (req: ExpressRequest, res: ExpressResponse): Promise => { - return await loginUserWithSso(req, res); - } + "/idp-login/:projectId/:projectSsoId", + async (req: ExpressRequest, res: ExpressResponse): Promise => { + return await loginUserWithSso(req, res); + }, ); type LoginUserWithSsoFunction = ( - req: ExpressRequest, - res: ExpressResponse + req: ExpressRequest, + res: ExpressResponse, ) => Promise; const loginUserWithSso: LoginUserWithSsoFunction = async ( - req: ExpressRequest, - res: ExpressResponse + req: ExpressRequest, + res: ExpressResponse, ): Promise => { - try { - const samlResponseBase64: string = req.body.SAMLResponse; + try { + const samlResponseBase64: string = req.body.SAMLResponse; - if (!samlResponseBase64) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('SAMLResponse not found') - ); - } - - const samlResponse: string = Buffer.from( - samlResponseBase64, - 'base64' - ).toString(); - - const response: JSONObject = await xml2js.parseStringPromise( - samlResponse - ); - - let issuerUrl: string = ''; - let email: Email | null = null; - - if (!req.params['projectId']) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Project ID not found') - ); - } - - if (!req.params['projectSsoId']) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Project SSO ID not found') - ); - } - - const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy( - { - query: { - projectId: new ObjectID(req.params['projectId']), - _id: req.params['projectSsoId'], - isEnabled: true, - }, - select: { - signOnURL: true, - issuerURL: true, - publicCertificate: true, - teams: { - _id: true, - }, - }, - props: { - isRoot: true, - }, - } - ); - - if (!projectSSO) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('SSO Config not found') - ); - } - - // redirect to Identity Provider. - - if (!projectSSO.issuerURL) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Issuer URL not found') - ); - } - - // redirect to Identity Provider. - - if (!projectSSO.signOnURL) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Sign on URL not found') - ); - } - - if (!projectSSO.publicCertificate) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Public Certificate not found') - ); - } - - try { - SSOUtil.isPayloadValid(response); - - if ( - !SSOUtil.isSignatureValid( - samlResponse, - projectSSO.publicCertificate - ) - ) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException( - 'Signature is not valid or Public Certificate configured with this SSO provider is not valid' - ) - ); - } - - issuerUrl = SSOUtil.getIssuer(response); - email = SSOUtil.getEmail(response); - } catch (err: unknown) { - if (err instanceof Exception) { - return Response.sendErrorResponse(req, res, err); - } - return Response.sendErrorResponse(req, res, new ServerException()); - } - - if (projectSSO.issuerURL.toString() !== issuerUrl) { - logger.error( - 'Issuer URL does not match. It should be ' + - projectSSO.issuerURL.toString() + - ' but it is ' + - issuerUrl.toString() - ); - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Issuer URL does not match') - ); - } - - // Check if he already belongs to the project, If he does - then log in. - - let alreadySavedUser: User | null = await UserService.findOneBy({ - query: { email: email }, - select: { - _id: true, - name: true, - email: true, - isMasterAdmin: true, - isEmailVerified: true, - profilePictureId: true, - }, - props: { - isRoot: true, - }, - }); - - let isNewUser: boolean = false; - - if (!alreadySavedUser) { - // this should never happen because user is logged in before he signs in with SSO UNLESS he initiates the login though the IDP. - - /// Create a user. - - alreadySavedUser = await UserService.createByEmail({ - email, - isEmailVerified: true, - generateRandomPassword: true, - props: { - isRoot: true, - }, - }); - - isNewUser = true; - } - - // If he does not then add him to teams that he should belong and log in. - // This should never happen because email is verified before he logs in with SSO. - if (!alreadySavedUser.isEmailVerified && !isNewUser) { - await AuthenticationEmail.sendVerificationEmail(alreadySavedUser!); - - return Response.render( - req, - res, - '/usr/src/app/FeatureSet/Identity/Views/Message.ejs', - { - title: 'Email not verified.', - message: - 'Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.', - } - ); - } - - // check if the user already belongs to the project - const teamMemberCount: PositiveNumber = await TeamMemberService.countBy( - { - query: { - projectId: new ObjectID(req.params['projectId'] as string), - userId: alreadySavedUser!.id!, - }, - props: { - isRoot: true, - }, - } - ); - - if (teamMemberCount.toNumber() === 0) { - // user not in project, add him to default teams. - - if (!projectSSO.teams || projectSSO.teams.length === 0) { - return Response.render( - req, - res, - '/usr/src/app/FeatureSet/Identity/Views/Message.ejs', - { - title: 'No teams added.', - message: - 'No teams have been added to this SSO config. Please contact your admin and have default teams added.', - } - ); - } - - for (const team of projectSSO.teams) { - // add user to team - let teamMember: TeamMember = new TeamMember(); - teamMember.projectId = new ObjectID( - req.params['projectId'] as string - ); - teamMember.userId = alreadySavedUser.id!; - teamMember.hasAcceptedInvitation = true; - teamMember.invitationAcceptedAt = - OneUptimeDate.getCurrentDate(); - teamMember.teamId = team.id!; - - teamMember = await TeamMemberService.create({ - data: teamMember, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - } - } - - const projectId: ObjectID = new ObjectID( - req.params['projectId'] as string - ); - - const ssoToken: string = JSONWebToken.sign({ - data: { - userId: alreadySavedUser.id!, - projectId: projectId, - name: alreadySavedUser.name!, - email: email, - isMasterAdmin: false, - isGeneralLogin: false, - }, - expiresInSeconds: OneUptimeDate.getSecondsInDays( - new PositiveNumber(30) - ), - }); - - const oneUptimeToken: string = JSONWebToken.signUserLoginToken({ - tokenData: { - userId: alreadySavedUser.id!, - email: alreadySavedUser.email!, - name: alreadySavedUser.name!, - isMasterAdmin: alreadySavedUser.isMasterAdmin!, - isGlobalLogin: false, // This is a general login without SSO. So, we will set this to false. This will give access to all the projects that dont require SSO. - }, - expiresInSeconds: OneUptimeDate.getSecondsInDays( - new PositiveNumber(30) - ), - }); - - // Set a cookie with token. - CookieUtil.setCookie( - res, - CookieUtil.getUserTokenKey(), - oneUptimeToken, - { - maxAge: OneUptimeDate.getMillisecondsInDays( - new PositiveNumber(30) - ), - httpOnly: true, - } - ); - - CookieUtil.setCookie( - res, - CookieUtil.getUserSSOKey(projectId), - ssoToken, - { - maxAge: OneUptimeDate.getMillisecondsInDays( - new PositiveNumber(30) - ), - httpOnly: true, - } - ); - - // Refresh Permissions for this user here. - await AccessTokenService.refreshUserAllPermissions( - alreadySavedUser.id! - ); - - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - - logger.info('User logged in with SSO' + email.toString()); - - return Response.redirect( - req, - res, - new URL( - httpProtocol, - host, - new Route(DashboardRoute.toString()).addRoute( - '/' + req.params['projectId'] - ) - ) - ); - } catch (err) { - logger.error(err); - Response.sendErrorResponse(req, res, new ServerException()); + if (!samlResponseBase64) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("SAMLResponse not found"), + ); } + + const samlResponse: string = Buffer.from( + samlResponseBase64, + "base64", + ).toString(); + + const response: JSONObject = await xml2js.parseStringPromise(samlResponse); + + let issuerUrl: string = ""; + let email: Email | null = null; + + if (!req.params["projectId"]) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Project ID not found"), + ); + } + + if (!req.params["projectSsoId"]) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Project SSO ID not found"), + ); + } + + const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy({ + query: { + projectId: new ObjectID(req.params["projectId"]), + _id: req.params["projectSsoId"], + isEnabled: true, + }, + select: { + signOnURL: true, + issuerURL: true, + publicCertificate: true, + teams: { + _id: true, + }, + }, + props: { + isRoot: true, + }, + }); + + if (!projectSSO) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("SSO Config not found"), + ); + } + + // redirect to Identity Provider. + + if (!projectSSO.issuerURL) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Issuer URL not found"), + ); + } + + // redirect to Identity Provider. + + if (!projectSSO.signOnURL) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Sign on URL not found"), + ); + } + + if (!projectSSO.publicCertificate) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Public Certificate not found"), + ); + } + + try { + SSOUtil.isPayloadValid(response); + + if ( + !SSOUtil.isSignatureValid(samlResponse, projectSSO.publicCertificate) + ) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException( + "Signature is not valid or Public Certificate configured with this SSO provider is not valid", + ), + ); + } + + issuerUrl = SSOUtil.getIssuer(response); + email = SSOUtil.getEmail(response); + } catch (err: unknown) { + if (err instanceof Exception) { + return Response.sendErrorResponse(req, res, err); + } + return Response.sendErrorResponse(req, res, new ServerException()); + } + + if (projectSSO.issuerURL.toString() !== issuerUrl) { + logger.error( + "Issuer URL does not match. It should be " + + projectSSO.issuerURL.toString() + + " but it is " + + issuerUrl.toString(), + ); + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Issuer URL does not match"), + ); + } + + // Check if he already belongs to the project, If he does - then log in. + + let alreadySavedUser: User | null = await UserService.findOneBy({ + query: { email: email }, + select: { + _id: true, + name: true, + email: true, + isMasterAdmin: true, + isEmailVerified: true, + profilePictureId: true, + }, + props: { + isRoot: true, + }, + }); + + let isNewUser: boolean = false; + + if (!alreadySavedUser) { + // this should never happen because user is logged in before he signs in with SSO UNLESS he initiates the login though the IDP. + + /// Create a user. + + alreadySavedUser = await UserService.createByEmail({ + email, + isEmailVerified: true, + generateRandomPassword: true, + props: { + isRoot: true, + }, + }); + + isNewUser = true; + } + + // If he does not then add him to teams that he should belong and log in. + // This should never happen because email is verified before he logs in with SSO. + if (!alreadySavedUser.isEmailVerified && !isNewUser) { + await AuthenticationEmail.sendVerificationEmail(alreadySavedUser!); + + return Response.render( + req, + res, + "/usr/src/app/FeatureSet/Identity/Views/Message.ejs", + { + title: "Email not verified.", + message: + "Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.", + }, + ); + } + + // check if the user already belongs to the project + const teamMemberCount: PositiveNumber = await TeamMemberService.countBy({ + query: { + projectId: new ObjectID(req.params["projectId"] as string), + userId: alreadySavedUser!.id!, + }, + props: { + isRoot: true, + }, + }); + + if (teamMemberCount.toNumber() === 0) { + // user not in project, add him to default teams. + + if (!projectSSO.teams || projectSSO.teams.length === 0) { + return Response.render( + req, + res, + "/usr/src/app/FeatureSet/Identity/Views/Message.ejs", + { + title: "No teams added.", + message: + "No teams have been added to this SSO config. Please contact your admin and have default teams added.", + }, + ); + } + + for (const team of projectSSO.teams) { + // add user to team + let teamMember: TeamMember = new TeamMember(); + teamMember.projectId = new ObjectID(req.params["projectId"] as string); + teamMember.userId = alreadySavedUser.id!; + teamMember.hasAcceptedInvitation = true; + teamMember.invitationAcceptedAt = OneUptimeDate.getCurrentDate(); + teamMember.teamId = team.id!; + + teamMember = await TeamMemberService.create({ + data: teamMember, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + } + } + + const projectId: ObjectID = new ObjectID(req.params["projectId"] as string); + + const ssoToken: string = JSONWebToken.sign({ + data: { + userId: alreadySavedUser.id!, + projectId: projectId, + name: alreadySavedUser.name!, + email: email, + isMasterAdmin: false, + isGeneralLogin: false, + }, + expiresInSeconds: OneUptimeDate.getSecondsInDays(new PositiveNumber(30)), + }); + + const oneUptimeToken: string = JSONWebToken.signUserLoginToken({ + tokenData: { + userId: alreadySavedUser.id!, + email: alreadySavedUser.email!, + name: alreadySavedUser.name!, + isMasterAdmin: alreadySavedUser.isMasterAdmin!, + isGlobalLogin: false, // This is a general login without SSO. So, we will set this to false. This will give access to all the projects that dont require SSO. + }, + expiresInSeconds: OneUptimeDate.getSecondsInDays(new PositiveNumber(30)), + }); + + // Set a cookie with token. + CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), oneUptimeToken, { + maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)), + httpOnly: true, + }); + + CookieUtil.setCookie(res, CookieUtil.getUserSSOKey(projectId), ssoToken, { + maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)), + httpOnly: true, + }); + + // Refresh Permissions for this user here. + await AccessTokenService.refreshUserAllPermissions(alreadySavedUser.id!); + + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + logger.info("User logged in with SSO" + email.toString()); + + return Response.redirect( + req, + res, + new URL( + httpProtocol, + host, + new Route(DashboardRoute.toString()).addRoute( + "/" + req.params["projectId"], + ), + ), + ); + } catch (err) { + logger.error(err); + Response.sendErrorResponse(req, res, new ServerException()); + } }; export default router; diff --git a/App/FeatureSet/Identity/API/StatusPageAuthentication.ts b/App/FeatureSet/Identity/API/StatusPageAuthentication.ts index e17712f7be..f74a3067ab 100644 --- a/App/FeatureSet/Identity/API/StatusPageAuthentication.ts +++ b/App/FeatureSet/Identity/API/StatusPageAuthentication.ts @@ -1,432 +1,422 @@ -import BaseModel from 'Common/Models/BaseModel'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import { EncryptionSecret } from 'CommonServer/EnvironmentConfig'; -import MailService from 'CommonServer/Services/MailService'; -import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivateUserService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import CookieUtil from 'CommonServer/Utils/Cookie'; +import BaseModel from "Common/Models/BaseModel"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import { EncryptionSecret } from "CommonServer/EnvironmentConfig"; +import MailService from "CommonServer/Services/MailService"; +import StatusPagePrivateUserService from "CommonServer/Services/StatusPagePrivateUserService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import CookieUtil from "CommonServer/Utils/Cookie"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import JSONWebToken from 'CommonServer/Utils/JsonWebToken'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import JSONWebToken from "CommonServer/Utils/JsonWebToken"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; const router: ExpressRouter = Express.getRouter(); router.post( - '/logout/:statuspageid', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - if (!req.params['statuspageid']) { - throw new BadDataException('Status Page ID is required.'); - } + "/logout/:statuspageid", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + if (!req.params["statuspageid"]) { + throw new BadDataException("Status Page ID is required."); + } - const statusPageId: ObjectID = new ObjectID( - req.params['statuspageid'].toString() - ); + const statusPageId: ObjectID = new ObjectID( + req.params["statuspageid"].toString(), + ); - CookieUtil.removeCookie( - res, - CookieUtil.getUserTokenKey(statusPageId) - ); // remove the cookie. + CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId)); // remove the cookie. - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); - } + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); router.post( - '/forgot-password', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - const data: JSONObject = req.body['data']; + "/forgot-password", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + const data: JSONObject = req.body["data"]; - if (!data['email']) { - throw new BadDataException('Email is required.'); - } + if (!data["email"]) { + throw new BadDataException("Email is required."); + } - const user: StatusPagePrivateUser = BaseModel.fromJSON( - data as JSONObject, - StatusPagePrivateUser - ) as StatusPagePrivateUser; + const user: StatusPagePrivateUser = BaseModel.fromJSON( + data as JSONObject, + StatusPagePrivateUser, + ) as StatusPagePrivateUser; - if (!user.statusPageId) { - throw new BadDataException('Status Page ID is required.'); - } + if (!user.statusPageId) { + throw new BadDataException("Status Page ID is required."); + } - const statusPage: StatusPage | null = - await StatusPageService.findOneById({ - id: user.statusPageId!, - props: { - isRoot: true, - ignoreHooks: true, - }, - select: { - _id: true, - name: true, - pageTitle: true, - logoFileId: true, - requireSsoForLogin: true, - projectId: true, - }, - }); + const statusPage: StatusPage | null = await StatusPageService.findOneById( + { + id: user.statusPageId!, + props: { + isRoot: true, + ignoreHooks: true, + }, + select: { + _id: true, + name: true, + pageTitle: true, + logoFileId: true, + requireSsoForLogin: true, + projectId: true, + }, + }, + ); - if (!statusPage) { - throw new BadDataException('Status Page not found'); - } + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } - if (statusPage.requireSsoForLogin) { - throw new BadDataException( - 'Status Page supports authentication by SSO. You cannot use email and password for authentication.' - ); - } + if (statusPage.requireSsoForLogin) { + throw new BadDataException( + "Status Page supports authentication by SSO. You cannot use email and password for authentication.", + ); + } - const statusPageName: string | undefined = - statusPage.pageTitle || statusPage.name; + const statusPageName: string | undefined = + statusPage.pageTitle || statusPage.name; - const statusPageURL: string = - await StatusPageService.getStatusPageURL(statusPage.id!); + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statusPage.id!, + ); - const alreadySavedUser: StatusPagePrivateUser | null = - await StatusPagePrivateUserService.findOneBy({ - query: { - email: user.email!, - statusPageId: user.statusPageId!, - }, - select: { - _id: true, - password: true, - email: true, - }, - props: { - isRoot: true, - }, - }); + const alreadySavedUser: StatusPagePrivateUser | null = + await StatusPagePrivateUserService.findOneBy({ + query: { + email: user.email!, + statusPageId: user.statusPageId!, + }, + select: { + _id: true, + password: true, + email: true, + }, + props: { + isRoot: true, + }, + }); - if (alreadySavedUser) { - const token: string = ObjectID.generate().toString(); - await StatusPagePrivateUserService.updateOneBy({ - query: { - _id: alreadySavedUser._id!, - }, - data: { - resetPasswordToken: token, - resetPasswordExpires: OneUptimeDate.getOneDayAfter(), - }, - props: { - isRoot: true, - }, - }); + if (alreadySavedUser) { + const token: string = ObjectID.generate().toString(); + await StatusPagePrivateUserService.updateOneBy({ + query: { + _id: alreadySavedUser._id!, + }, + data: { + resetPasswordToken: token, + resetPasswordExpires: OneUptimeDate.getOneDayAfter(), + }, + props: { + isRoot: true, + }, + }); - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - MailService.sendMail( - { - toEmail: user.email!, - subject: 'Password Reset Request for ' + statusPageName, - templateType: - EmailTemplateType.StatusPageForgotPassword, - vars: { - statusPageName: statusPageName!, - logoUrl: statusPage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute( - '/image/' + statusPage.logoFileId - ) - .toString() - : '', - homeURL: statusPageURL, - tokenVerifyUrl: URL.fromString(statusPageURL) - .addRoute('/reset-password/' + token) - .toString(), - }, - }, - { - projectId: statusPage.projectId!, - } - ).catch((err: Error) => { - logger.error(err); - }); + MailService.sendMail( + { + toEmail: user.email!, + subject: "Password Reset Request for " + statusPageName, + templateType: EmailTemplateType.StatusPageForgotPassword, + vars: { + statusPageName: statusPageName!, + logoUrl: statusPage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statusPage.logoFileId) + .toString() + : "", + homeURL: statusPageURL, + tokenVerifyUrl: URL.fromString(statusPageURL) + .addRoute("/reset-password/" + token) + .toString(), + }, + }, + { + projectId: statusPage.projectId!, + }, + ).catch((err: Error) => { + logger.error(err); + }); - return Response.sendEmptySuccessResponse(req, res); - } + return Response.sendEmptySuccessResponse(req, res); + } - throw new BadDataException( - `No user is registered with ${user.email?.toString()}` - ); - } catch (err) { - return next(err); - } + throw new BadDataException( + `No user is registered with ${user.email?.toString()}`, + ); + } catch (err) { + return next(err); } + }, ); router.post( - '/reset-password', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - const data: JSONObject = JSONFunctions.deserialize( - req.body['data'] - ); + "/reset-password", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + const data: JSONObject = JSONFunctions.deserialize(req.body["data"]); - if (!data['statusPageId']) { - throw new BadDataException('Status Page ID is required.'); - } + if (!data["statusPageId"]) { + throw new BadDataException("Status Page ID is required."); + } - const user: StatusPagePrivateUser = BaseModel.fromJSON( - data as JSONObject, - StatusPagePrivateUser - ) as StatusPagePrivateUser; + const user: StatusPagePrivateUser = BaseModel.fromJSON( + data as JSONObject, + StatusPagePrivateUser, + ) as StatusPagePrivateUser; - await user.password?.hashValue(EncryptionSecret); + await user.password?.hashValue(EncryptionSecret); - const alreadySavedUser: StatusPagePrivateUser | null = - await StatusPagePrivateUserService.findOneBy({ - query: { - statusPageId: new ObjectID( - data['statusPageId'].toString() - ), - resetPasswordToken: - (user.resetPasswordToken as string) || '', - }, - select: { - _id: true, - password: true, - email: true, - resetPasswordExpires: true, - }, - props: { - isRoot: true, - }, - }); + const alreadySavedUser: StatusPagePrivateUser | null = + await StatusPagePrivateUserService.findOneBy({ + query: { + statusPageId: new ObjectID(data["statusPageId"].toString()), + resetPasswordToken: (user.resetPasswordToken as string) || "", + }, + select: { + _id: true, + password: true, + email: true, + resetPasswordExpires: true, + }, + props: { + isRoot: true, + }, + }); - if (!alreadySavedUser) { - throw new BadDataException( - 'Invalid link. Please go to forgot password page again and request a new link.' - ); - } + if (!alreadySavedUser) { + throw new BadDataException( + "Invalid link. Please go to forgot password page again and request a new link.", + ); + } - if ( - alreadySavedUser && - OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!) - ) { - throw new BadDataException( - 'Expired link. Please go to forgot password page again and request a new link.' - ); - } + if ( + alreadySavedUser && + OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!) + ) { + throw new BadDataException( + "Expired link. Please go to forgot password page again and request a new link.", + ); + } - const statusPage: StatusPage | null = - await StatusPageService.findOneById({ - id: new ObjectID(data['statusPageId'].toString()), - props: { - isRoot: true, - ignoreHooks: true, - }, - select: { - _id: true, - name: true, - pageTitle: true, - logoFileId: true, - requireSsoForLogin: true, - projectId: true, - }, - }); + const statusPage: StatusPage | null = await StatusPageService.findOneById( + { + id: new ObjectID(data["statusPageId"].toString()), + props: { + isRoot: true, + ignoreHooks: true, + }, + select: { + _id: true, + name: true, + pageTitle: true, + logoFileId: true, + requireSsoForLogin: true, + projectId: true, + }, + }, + ); - if (!statusPage) { - throw new BadDataException('Status Page not found'); - } + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } - if (statusPage.requireSsoForLogin) { - throw new BadDataException( - 'Status Page supports authentication by SSO. You cannot use email and password for authentication.' - ); - } + if (statusPage.requireSsoForLogin) { + throw new BadDataException( + "Status Page supports authentication by SSO. You cannot use email and password for authentication.", + ); + } - const statusPageName: string | undefined = - statusPage.pageTitle || statusPage.name; + const statusPageName: string | undefined = + statusPage.pageTitle || statusPage.name; - const statusPageURL: string = - await StatusPageService.getStatusPageURL(statusPage.id!); + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statusPage.id!, + ); - await StatusPagePrivateUserService.updateOneById({ - id: alreadySavedUser.id!, - data: { - password: user.password!, - resetPasswordToken: null!, - resetPasswordExpires: null!, - }, - props: { - isRoot: true, - }, - }); + await StatusPagePrivateUserService.updateOneById({ + id: alreadySavedUser.id!, + data: { + password: user.password!, + resetPasswordToken: null!, + resetPasswordExpires: null!, + }, + props: { + isRoot: true, + }, + }); - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - MailService.sendMail( - { - toEmail: alreadySavedUser.email!, - subject: 'Password Changed.', - templateType: EmailTemplateType.StatusPagePasswordChanged, - vars: { - homeURL: statusPageURL, - statusPageName: statusPageName || '', - logoUrl: statusPage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute('/image/' + statusPage.logoFileId) - .toString() - : '', - }, - }, - { - projectId: statusPage.projectId!, - } - ).catch((err: Error) => { - logger.error(err); - }); + MailService.sendMail( + { + toEmail: alreadySavedUser.email!, + subject: "Password Changed.", + templateType: EmailTemplateType.StatusPagePasswordChanged, + vars: { + homeURL: statusPageURL, + statusPageName: statusPageName || "", + logoUrl: statusPage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statusPage.logoFileId) + .toString() + : "", + }, + }, + { + projectId: statusPage.projectId!, + }, + ).catch((err: Error) => { + logger.error(err); + }); - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); - } + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); router.post( - '/login', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - const data: JSONObject = req.body['data']; + "/login", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + const data: JSONObject = req.body["data"]; - const user: StatusPagePrivateUser = BaseModel.fromJSON( - data as JSONObject, - StatusPagePrivateUser - ) as StatusPagePrivateUser; + const user: StatusPagePrivateUser = BaseModel.fromJSON( + data as JSONObject, + StatusPagePrivateUser, + ) as StatusPagePrivateUser; - if (!user.statusPageId) { - throw new BadDataException('Status Page ID not found'); - } + if (!user.statusPageId) { + throw new BadDataException("Status Page ID not found"); + } - const statusPage: StatusPage | null = - await StatusPageService.findOneById({ - id: user.statusPageId, - props: { - isRoot: true, - ignoreHooks: true, - }, - select: { - requireSsoForLogin: true, - }, - }); + const statusPage: StatusPage | null = await StatusPageService.findOneById( + { + id: user.statusPageId, + props: { + isRoot: true, + ignoreHooks: true, + }, + select: { + requireSsoForLogin: true, + }, + }, + ); - if (!statusPage) { - throw new BadDataException('Status Page not found'); - } + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } - if (statusPage.requireSsoForLogin) { - throw new BadDataException( - 'Status Page supports authentication by SSO. You cannot use email and password for authentication.' - ); - } + if (statusPage.requireSsoForLogin) { + throw new BadDataException( + "Status Page supports authentication by SSO. You cannot use email and password for authentication.", + ); + } - await user.password?.hashValue(EncryptionSecret); + await user.password?.hashValue(EncryptionSecret); - const alreadySavedUser: StatusPagePrivateUser | null = - await StatusPagePrivateUserService.findOneBy({ - query: { - email: user.email!, - password: user.password!, - statusPageId: user.statusPageId!, - }, - select: { - _id: true, - password: true, - email: true, - statusPageId: true, - }, - props: { - isRoot: true, - }, - }); + const alreadySavedUser: StatusPagePrivateUser | null = + await StatusPagePrivateUserService.findOneBy({ + query: { + email: user.email!, + password: user.password!, + statusPageId: user.statusPageId!, + }, + select: { + _id: true, + password: true, + email: true, + statusPageId: true, + }, + props: { + isRoot: true, + }, + }); - if (alreadySavedUser) { - const token: string = JSONWebToken.sign({ - data: alreadySavedUser, - expiresInSeconds: OneUptimeDate.getSecondsInDays( - new PositiveNumber(30) - ), - }); + if (alreadySavedUser) { + const token: string = JSONWebToken.sign({ + data: alreadySavedUser, + expiresInSeconds: OneUptimeDate.getSecondsInDays( + new PositiveNumber(30), + ), + }); - CookieUtil.setCookie( - res, - CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!), - token, - { - httpOnly: true, - maxAge: OneUptimeDate.getMillisecondsInDays( - new PositiveNumber(30) - ), - } - ); + CookieUtil.setCookie( + res, + CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!), + token, + { + httpOnly: true, + maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)), + }, + ); - return Response.sendEntityResponse( - req, - res, - alreadySavedUser, - StatusPagePrivateUser, - { - miscData: { - token: token, - }, - } - ); - } - throw new BadDataException( - 'Invalid login: Email or password does not match.' - ); - } catch (err) { - return next(err); - } + return Response.sendEntityResponse( + req, + res, + alreadySavedUser, + StatusPagePrivateUser, + { + miscData: { + token: token, + }, + }, + ); + } + throw new BadDataException( + "Invalid login: Email or password does not match.", + ); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/App/FeatureSet/Identity/API/StatusPageSSO.ts b/App/FeatureSet/Identity/API/StatusPageSSO.ts index fadc6e6a28..5e18a79c87 100644 --- a/App/FeatureSet/Identity/API/StatusPageSSO.ts +++ b/App/FeatureSet/Identity/API/StatusPageSSO.ts @@ -1,323 +1,312 @@ -import SSOUtil from '../Utils/SSO'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Email from 'Common/Types/Email'; -import BadRequestException from 'Common/Types/Exception/BadRequestException'; -import Exception from 'Common/Types/Exception/Exception'; -import ServerException from 'Common/Types/Exception/ServerException'; -import HashedString from 'Common/Types/HashedString'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig'; -import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivateUserService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import StatusPageSsoService from 'CommonServer/Services/StatusPageSsoService'; -import CookieUtil from 'CommonServer/Utils/Cookie'; +import SSOUtil from "../Utils/SSO"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Email from "Common/Types/Email"; +import BadRequestException from "Common/Types/Exception/BadRequestException"; +import Exception from "Common/Types/Exception/Exception"; +import ServerException from "Common/Types/Exception/ServerException"; +import HashedString from "Common/Types/HashedString"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import { Host, HttpProtocol } from "CommonServer/EnvironmentConfig"; +import StatusPagePrivateUserService from "CommonServer/Services/StatusPagePrivateUserService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import StatusPageSsoService from "CommonServer/Services/StatusPageSsoService"; +import CookieUtil from "CommonServer/Utils/Cookie"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import JSONWebToken from 'CommonServer/Utils/JsonWebToken'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import StatusPageSSO from 'Model/Models/StatusPageSso'; -import xml2js from 'xml2js'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import JSONWebToken from "CommonServer/Utils/JsonWebToken"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import StatusPageSSO from "Model/Models/StatusPageSso"; +import xml2js from "xml2js"; const router: ExpressRouter = Express.getRouter(); router.get( - '/status-page-sso/:statusPageId/:statusPageSsoId', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise => { - try { - if (!req.params['statusPageId']) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Status Page ID not found') - ); - } + "/status-page-sso/:statusPageId/:statusPageSsoId", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise => { + try { + if (!req.params["statusPageId"]) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Status Page ID not found"), + ); + } - if (!req.params['statusPageSsoId']) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Status Page SSO ID not found') - ); - } + if (!req.params["statusPageSsoId"]) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Status Page SSO ID not found"), + ); + } - const statusPageId: ObjectID = new ObjectID( - req.params['statusPageId'] - ); + const statusPageId: ObjectID = new ObjectID(req.params["statusPageId"]); - const statusPageSSO: StatusPageSSO | null = - await StatusPageSsoService.findOneBy({ - query: { - statusPageId: statusPageId, - _id: req.params['statusPageSsoId'], - isEnabled: true, - }, - select: { - signOnURL: true, - statusPageId: true, - _id: true, - }, - props: { - isRoot: true, - }, - }); + const statusPageSSO: StatusPageSSO | null = + await StatusPageSsoService.findOneBy({ + query: { + statusPageId: statusPageId, + _id: req.params["statusPageSsoId"], + isEnabled: true, + }, + select: { + signOnURL: true, + statusPageId: true, + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (!statusPageSSO) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('SSO Config not found') - ); - } + if (!statusPageSSO) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("SSO Config not found"), + ); + } - // redirect to Identity Provider. + // redirect to Identity Provider. - if (!statusPageSSO.signOnURL) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Sign On URL not found') - ); - } + if (!statusPageSSO.signOnURL) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Sign On URL not found"), + ); + } - const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({ - acsUrl: URL.fromString( - `${HttpProtocol}${Host}/identity/status-page-idp-login/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}` - ), - signOnUrl: statusPageSSO.signOnURL!, - issuerUrl: URL.fromString( - `${HttpProtocol}${Host}/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}` - ), - }); + const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({ + acsUrl: URL.fromString( + `${HttpProtocol}${Host}/identity/status-page-idp-login/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`, + ), + signOnUrl: statusPageSSO.signOnURL!, + issuerUrl: URL.fromString( + `${HttpProtocol}${Host}/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`, + ), + }); - return Response.redirect(req, res, samlRequestUrl); - } catch (err) { - return next(err); - } + return Response.redirect(req, res, samlRequestUrl); + } catch (err) { + return next(err); } + }, ); router.post( - '/status-page-idp-login/:statusPageId/:statusPageSsoId', - async (req: ExpressRequest, res: ExpressResponse): Promise => { - try { - const samlResponseBase64: string = req.body.SAMLResponse; + "/status-page-idp-login/:statusPageId/:statusPageSsoId", + async (req: ExpressRequest, res: ExpressResponse): Promise => { + try { + const samlResponseBase64: string = req.body.SAMLResponse; - const samlResponse: string = Buffer.from( - samlResponseBase64, - 'base64' - ).toString(); + const samlResponse: string = Buffer.from( + samlResponseBase64, + "base64", + ).toString(); - const response: JSONObject = await xml2js.parseStringPromise( - samlResponse - ); + const response: JSONObject = + await xml2js.parseStringPromise(samlResponse); - let issuerUrl: string = ''; - let email: Email | null = null; + let issuerUrl: string = ""; + let email: Email | null = null; - if (!req.params['statusPageId']) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Status Page ID not found') - ); - } + if (!req.params["statusPageId"]) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Status Page ID not found"), + ); + } - if (!req.params['statusPageSsoId']) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Status Page SSO ID not found') - ); - } + if (!req.params["statusPageSsoId"]) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Status Page SSO ID not found"), + ); + } - const statusPageId: ObjectID = new ObjectID( - req.params['statusPageId'] - ); + const statusPageId: ObjectID = new ObjectID(req.params["statusPageId"]); - const statusPageSSO: StatusPageSSO | null = - await StatusPageSsoService.findOneBy({ - query: { - statusPageId: statusPageId, - _id: req.params['statusPageSsoId'], - isEnabled: true, - }, - select: { - signOnURL: true, - issuerURL: true, - publicCertificate: true, - projectId: true, - }, - props: { - isRoot: true, - }, - }); + const statusPageSSO: StatusPageSSO | null = + await StatusPageSsoService.findOneBy({ + query: { + statusPageId: statusPageId, + _id: req.params["statusPageSsoId"], + isEnabled: true, + }, + select: { + signOnURL: true, + issuerURL: true, + publicCertificate: true, + projectId: true, + }, + props: { + isRoot: true, + }, + }); - if (!statusPageSSO) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('SSO Config not found') - ); - } + if (!statusPageSSO) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("SSO Config not found"), + ); + } - if (!statusPageSSO.projectId) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('SSO Config Project ID not found') - ); - } + if (!statusPageSSO.projectId) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("SSO Config Project ID not found"), + ); + } - const projectId: ObjectID = statusPageSSO.projectId; + const projectId: ObjectID = statusPageSSO.projectId; - // redirect to Identity Provider. + // redirect to Identity Provider. - if (!statusPageSSO.issuerURL) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Issuer URL not found') - ); - } + if (!statusPageSSO.issuerURL) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Issuer URL not found"), + ); + } - // redirect to Identity Provider. + // redirect to Identity Provider. - if (!statusPageSSO.signOnURL) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Sign on URL not found') - ); - } + if (!statusPageSSO.signOnURL) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Sign on URL not found"), + ); + } - if (!statusPageSSO.publicCertificate) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Public Certificate not found') - ); - } + if (!statusPageSSO.publicCertificate) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Public Certificate not found"), + ); + } - try { - SSOUtil.isPayloadValid(response); + try { + SSOUtil.isPayloadValid(response); - if ( - !SSOUtil.isSignatureValid( - samlResponse, - statusPageSSO.publicCertificate - ) - ) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Signature is not valid') - ); - } - - issuerUrl = SSOUtil.getIssuer(response); - - email = SSOUtil.getEmail(response); - } catch (err: unknown) { - if (err instanceof Exception) { - return Response.sendErrorResponse(req, res, err); - } - return Response.sendErrorResponse( - req, - res, - new ServerException() - ); - } - - if (statusPageSSO.issuerURL.toString() !== issuerUrl) { - return Response.sendErrorResponse( - req, - res, - new BadRequestException('Issuer URL does not match') - ); - } - - // Check if he already belongs to the project, If he does - then log in. - - let alreadySavedUser: StatusPagePrivateUser | null = - await StatusPagePrivateUserService.findOneBy({ - query: { email: email, statusPageId: statusPageId }, - select: { - _id: true, - email: true, - statusPageId: true, - projectId: true, - }, - props: { - isRoot: true, - }, - }); - - if (!alreadySavedUser) { - /// Create a user. - - alreadySavedUser = new StatusPagePrivateUser(); - alreadySavedUser.projectId = projectId; - alreadySavedUser.statusPageId = statusPageId; - alreadySavedUser.email = email; - alreadySavedUser.password = new HashedString( - ObjectID.generate().toString() - ); - alreadySavedUser.isSsoUser = true; - - alreadySavedUser = await StatusPagePrivateUserService.create({ - data: alreadySavedUser, - props: { isRoot: true }, - }); - } - - const token: string = JSONWebToken.sign({ - data: alreadySavedUser, - expiresInSeconds: OneUptimeDate.getSecondsInDays( - new PositiveNumber(30) - ), - }); - - CookieUtil.setCookie( - res, - CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!), - token, - { - httpOnly: true, - maxAge: OneUptimeDate.getMillisecondsInDays( - new PositiveNumber(30) - ), - } - ); - - // get status page URL. - const statusPageURL: string = - await StatusPageService.getStatusPageFirstURL(statusPageId); - - return Response.redirect( - req, - res, - URL.fromString(statusPageURL).addQueryParams({ - token: token, - }) - ); - } catch (err) { - logger.error(err); - Response.sendErrorResponse(req, res, new ServerException()); + if ( + !SSOUtil.isSignatureValid( + samlResponse, + statusPageSSO.publicCertificate, + ) + ) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Signature is not valid"), + ); } + + issuerUrl = SSOUtil.getIssuer(response); + + email = SSOUtil.getEmail(response); + } catch (err: unknown) { + if (err instanceof Exception) { + return Response.sendErrorResponse(req, res, err); + } + return Response.sendErrorResponse(req, res, new ServerException()); + } + + if (statusPageSSO.issuerURL.toString() !== issuerUrl) { + return Response.sendErrorResponse( + req, + res, + new BadRequestException("Issuer URL does not match"), + ); + } + + // Check if he already belongs to the project, If he does - then log in. + + let alreadySavedUser: StatusPagePrivateUser | null = + await StatusPagePrivateUserService.findOneBy({ + query: { email: email, statusPageId: statusPageId }, + select: { + _id: true, + email: true, + statusPageId: true, + projectId: true, + }, + props: { + isRoot: true, + }, + }); + + if (!alreadySavedUser) { + /// Create a user. + + alreadySavedUser = new StatusPagePrivateUser(); + alreadySavedUser.projectId = projectId; + alreadySavedUser.statusPageId = statusPageId; + alreadySavedUser.email = email; + alreadySavedUser.password = new HashedString( + ObjectID.generate().toString(), + ); + alreadySavedUser.isSsoUser = true; + + alreadySavedUser = await StatusPagePrivateUserService.create({ + data: alreadySavedUser, + props: { isRoot: true }, + }); + } + + const token: string = JSONWebToken.sign({ + data: alreadySavedUser, + expiresInSeconds: OneUptimeDate.getSecondsInDays( + new PositiveNumber(30), + ), + }); + + CookieUtil.setCookie( + res, + CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!), + token, + { + httpOnly: true, + maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)), + }, + ); + + // get status page URL. + const statusPageURL: string = + await StatusPageService.getStatusPageFirstURL(statusPageId); + + return Response.redirect( + req, + res, + URL.fromString(statusPageURL).addQueryParams({ + token: token, + }), + ); + } catch (err) { + logger.error(err); + Response.sendErrorResponse(req, res, new ServerException()); } + }, ); export default router; diff --git a/App/FeatureSet/Identity/Index.ts b/App/FeatureSet/Identity/Index.ts index 2f0769cbf4..f0e67db2a0 100644 --- a/App/FeatureSet/Identity/Index.ts +++ b/App/FeatureSet/Identity/Index.ts @@ -1,31 +1,31 @@ -import AuthenticationAPI from './API/Authentication'; -import ResellerAPI from './API/Reseller'; -import SsoAPI from './API/SSO'; -import StatusPageAuthenticationAPI from './API/StatusPageAuthentication'; -import StatusPageSsoAPI from './API/StatusPageSSO'; -import FeatureSet from 'CommonServer/Types/FeatureSet'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import 'ejs'; +import AuthenticationAPI from "./API/Authentication"; +import ResellerAPI from "./API/Reseller"; +import SsoAPI from "./API/SSO"; +import StatusPageAuthenticationAPI from "./API/StatusPageAuthentication"; +import StatusPageSsoAPI from "./API/StatusPageSSO"; +import FeatureSet from "CommonServer/Types/FeatureSet"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import "ejs"; const IdentityFeatureSet: FeatureSet = { - init: async (): Promise => { - const app: ExpressApplication = Express.getExpressApp(); + init: async (): Promise => { + const app: ExpressApplication = Express.getExpressApp(); - const APP_NAME: string = 'api/identity'; + const APP_NAME: string = "api/identity"; - app.use([`/${APP_NAME}`, '/'], AuthenticationAPI); + app.use([`/${APP_NAME}`, "/"], AuthenticationAPI); - app.use([`/${APP_NAME}`, '/'], ResellerAPI); + app.use([`/${APP_NAME}`, "/"], ResellerAPI); - app.use([`/${APP_NAME}`, '/'], SsoAPI); + app.use([`/${APP_NAME}`, "/"], SsoAPI); - app.use([`/${APP_NAME}`, '/'], StatusPageSsoAPI); + app.use([`/${APP_NAME}`, "/"], StatusPageSsoAPI); - app.use( - [`/${APP_NAME}/status-page`, '/status-page'], - StatusPageAuthenticationAPI - ); - }, + app.use( + [`/${APP_NAME}/status-page`, "/status-page"], + StatusPageAuthenticationAPI, + ); + }, }; export default IdentityFeatureSet; diff --git a/App/FeatureSet/Identity/Utils/AuthenticationEmail.ts b/App/FeatureSet/Identity/Utils/AuthenticationEmail.ts index 7e5109abb6..3c64b2e8da 100644 --- a/App/FeatureSet/Identity/Utils/AuthenticationEmail.ts +++ b/App/FeatureSet/Identity/Utils/AuthenticationEmail.ts @@ -1,57 +1,57 @@ -import { AccountsRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Email from 'Common/Types/Email'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import ObjectID from 'Common/Types/ObjectID'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import EmailVerificationTokenService from 'CommonServer/Services/EmailVerificationTokenService'; -import MailService from 'CommonServer/Services/MailService'; -import logger from 'CommonServer/Utils/Logger'; -import EmailVerificationToken from 'Model/Models/EmailVerificationToken'; -import User from 'Model/Models/User'; +import { AccountsRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Email from "Common/Types/Email"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import ObjectID from "Common/Types/ObjectID"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import EmailVerificationTokenService from "CommonServer/Services/EmailVerificationTokenService"; +import MailService from "CommonServer/Services/MailService"; +import logger from "CommonServer/Utils/Logger"; +import EmailVerificationToken from "Model/Models/EmailVerificationToken"; +import User from "Model/Models/User"; export default class AuthenticationEmail { - public static async sendVerificationEmail(user: User): Promise { - const generatedToken: ObjectID = ObjectID.generate(); + public static async sendVerificationEmail(user: User): Promise { + const generatedToken: ObjectID = ObjectID.generate(); - const emailVerificationToken: EmailVerificationToken = - new EmailVerificationToken(); - emailVerificationToken.userId = user?.id as ObjectID; - emailVerificationToken.email = user?.email as Email; - emailVerificationToken.token = generatedToken; - emailVerificationToken.expires = OneUptimeDate.getOneDayAfter(); + const emailVerificationToken: EmailVerificationToken = + new EmailVerificationToken(); + emailVerificationToken.userId = user?.id as ObjectID; + emailVerificationToken.email = user?.email as Email; + emailVerificationToken.token = generatedToken; + emailVerificationToken.expires = OneUptimeDate.getOneDayAfter(); - await EmailVerificationTokenService.create({ - data: emailVerificationToken, - props: { - isRoot: true, - }, - }); + await EmailVerificationTokenService.create({ + data: emailVerificationToken, + props: { + isRoot: true, + }, + }); - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - MailService.sendMail({ - toEmail: user.email!, - subject: 'Please verify email.', - templateType: EmailTemplateType.SignupWelcomeEmail, - vars: { - name: user.name?.toString() || '', - tokenVerifyUrl: new URL( - httpProtocol, - host, - new Route(AccountsRoute.toString()).addRoute( - '/verify-email/' + generatedToken.toString() - ) - ).toString(), - homeUrl: new URL(httpProtocol, host).toString(), - }, - }).catch((err: Error) => { - logger.error(err); - }); - } + MailService.sendMail({ + toEmail: user.email!, + subject: "Please verify email.", + templateType: EmailTemplateType.SignupWelcomeEmail, + vars: { + name: user.name?.toString() || "", + tokenVerifyUrl: new URL( + httpProtocol, + host, + new Route(AccountsRoute.toString()).addRoute( + "/verify-email/" + generatedToken.toString(), + ), + ).toString(), + homeUrl: new URL(httpProtocol, host).toString(), + }, + }).catch((err: Error) => { + logger.error(err); + }); + } } diff --git a/App/FeatureSet/Identity/Utils/SSO.ts b/App/FeatureSet/Identity/Utils/SSO.ts index d279823da8..f0978b8a6d 100644 --- a/App/FeatureSet/Identity/Utils/SSO.ts +++ b/App/FeatureSet/Identity/Utils/SSO.ts @@ -1,230 +1,220 @@ -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Email from 'Common/Types/Email'; -import BadRequestException from 'Common/Types/Exception/BadRequestException'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import Text from 'Common/Types/Text'; -import logger from 'CommonServer/Utils/Logger'; -import xmlCrypto, { FileKeyInfo } from 'xml-crypto'; -import xmldom from 'xmldom'; -import zlib from 'zlib'; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Email from "Common/Types/Email"; +import BadRequestException from "Common/Types/Exception/BadRequestException"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import Text from "Common/Types/Text"; +import logger from "CommonServer/Utils/Logger"; +import xmlCrypto, { FileKeyInfo } from "xml-crypto"; +import xmldom from "xmldom"; +import zlib from "zlib"; export default class SSOUtil { - public static createSAMLRequestUrl(data: { - acsUrl: URL; - signOnUrl: URL; - issuerUrl: URL; - }): URL { - const { acsUrl, signOnUrl } = data; + public static createSAMLRequestUrl(data: { + acsUrl: URL; + signOnUrl: URL; + issuerUrl: URL; + }): URL { + const { acsUrl, signOnUrl } = data; - const samlRequest: string = `${data.issuerUrl.toString()}`; + const samlRequest: string = `${data.issuerUrl.toString()}`; - const deflated: Buffer = zlib.deflateRawSync(samlRequest); + const deflated: Buffer = zlib.deflateRawSync(samlRequest); - const base64Encoded: string = deflated.toString('base64'); + const base64Encoded: string = deflated.toString("base64"); - return URL.fromString(signOnUrl.toString()).addQueryParam( - 'SAMLRequest', - base64Encoded, - true - ); + return URL.fromString(signOnUrl.toString()).addQueryParam( + "SAMLRequest", + base64Encoded, + true, + ); + } + + public static isPayloadValid(payload: JSONObject): void { + if ( + !payload["saml2p:Response"] && + !payload["samlp:Response"] && + !payload["samlp:Response"] + ) { + throw new BadRequestException("SAML Response not found."); } - public static isPayloadValid(payload: JSONObject): void { - if ( - !payload['saml2p:Response'] && - !payload['samlp:Response'] && - !payload['samlp:Response'] - ) { - throw new BadRequestException('SAML Response not found.'); - } + payload = + (payload["saml2p:Response"] as JSONObject) || + (payload["samlp:Response"] as JSONObject) || + (payload["samlp:Response"] as JSONObject) || + (payload["Response"] as JSONObject); - payload = - (payload['saml2p:Response'] as JSONObject) || - (payload['samlp:Response'] as JSONObject) || - (payload['samlp:Response'] as JSONObject) || - (payload['Response'] as JSONObject); + const issuers: JSONArray = + (payload["saml2:Issuer"] as JSONArray) || + (payload["saml:Issuer"] as JSONArray) || + (payload["Issuer"] as JSONArray); - const issuers: JSONArray = - (payload['saml2:Issuer'] as JSONArray) || - (payload['saml:Issuer'] as JSONArray) || - (payload['Issuer'] as JSONArray); - - if (issuers.length === 0) { - throw new BadRequestException('Issuers not found'); - } - - const issuer: JSONObject | string | undefined = issuers[0]; - - if (typeof issuer === 'string') { - return issuer; - } - if (!issuer) { - throw new BadRequestException('Issuer not found'); - } - - const issuerUrl: string = issuer['_'] as string; - - if (!issuerUrl) { - throw new BadRequestException( - 'Issuer URL not found in SAML response' - ); - } - - const samlAssertion: JSONArray = - (payload['saml2:Assertion'] as JSONArray) || - (payload['saml:Assertion'] as JSONArray) || - (payload['Assertion'] as JSONArray); - - if (!samlAssertion || samlAssertion.length === 0) { - throw new BadRequestException('SAML Assertion not found'); - } - - const samlSubject: JSONArray = - ((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) || - ((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray) || - ((samlAssertion[0] as JSONObject)['Subject'] as JSONArray); - - if (!samlSubject || samlSubject.length === 0) { - throw new BadRequestException('SAML Subject not found'); - } - - const samlNameId: JSONArray = - ((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) || - ((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray) || - ((samlSubject[0] as JSONObject)['NameID'] as JSONArray); - - if (!samlNameId || samlNameId.length === 0) { - throw new BadRequestException('SAML NAME ID not found'); - } - - const emailString: string = (samlNameId[0] as JSONObject)[ - '_' - ] as string; - - if (!emailString) { - if (!samlNameId || samlNameId.length === 0) { - throw new BadRequestException('SAML Email not found'); - } - } + if (issuers.length === 0) { + throw new BadRequestException("Issuers not found"); } - public static isSignatureValid( - samlPayload: string, - certificate: string - ): boolean { - try { - const dom: Document = new xmldom.DOMParser().parseFromString( - samlPayload - ); - const signature: Element | undefined = dom.getElementsByTagNameNS( - 'http://www.w3.org/2000/09/xmldsig#', - 'Signature' - )[0]; - const sig: xmlCrypto.SignedXml = new xmlCrypto.SignedXml(); + const issuer: JSONObject | string | undefined = issuers[0]; - sig.keyInfoProvider = { - getKeyInfo: function (_key: any) { - return `${certificate}`; - }, - getKey: function () { - return certificate; - } as any, - } as FileKeyInfo; - - sig.loadSignature(signature!.toString()); - const res: boolean = sig.checkSignature(samlPayload); - - return res; - } catch (err) { - logger.error(err); - return false; - } + if (typeof issuer === "string") { + return issuer; + } + if (!issuer) { + throw new BadRequestException("Issuer not found"); } - public static getEmail(payload: JSONObject): Email { - if (!payload['saml2p:Response'] && !payload['samlp:Response']) { - throw new BadRequestException('SAML Response not found.'); - } + const issuerUrl: string = issuer["_"] as string; - payload = - (payload['saml2p:Response'] as JSONObject) || - (payload['samlp:Response'] as JSONObject) || - (payload['Response'] as JSONObject); - - const samlAssertion: JSONArray = - (payload['saml2:Assertion'] as JSONArray) || - (payload['saml:Assertion'] as JSONArray) || - (payload['Assertion'] as JSONArray); - - if (!samlAssertion || samlAssertion.length === 0) { - throw new BadRequestException('SAML Assertion not found'); - } - - const samlSubject: JSONArray = - ((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) || - ((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray) || - ((samlAssertion[0] as JSONObject)['Subject'] as JSONArray); - - if (!samlSubject || samlSubject.length === 0) { - throw new BadRequestException('SAML Subject not found'); - } - - const samlNameId: JSONArray = - ((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) || - ((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray) || - ((samlSubject[0] as JSONObject)['NameID'] as JSONArray); - - if (!samlNameId || samlNameId.length === 0) { - throw new BadRequestException('SAML NAME ID not found'); - } - - const emailString: string = (samlNameId[0] as JSONObject)[ - '_' - ] as string; - - return new Email(emailString.trim()); + if (!issuerUrl) { + throw new BadRequestException("Issuer URL not found in SAML response"); } - public static getIssuer(payload: JSONObject): string { - if (!payload['saml2p:Response'] && !payload['samlp:Response']) { - throw new BadRequestException('SAML Response not found.'); - } + const samlAssertion: JSONArray = + (payload["saml2:Assertion"] as JSONArray) || + (payload["saml:Assertion"] as JSONArray) || + (payload["Assertion"] as JSONArray); - payload = - (payload['saml2p:Response'] as JSONObject) || - (payload['samlp:Response'] as JSONObject) || - (payload['Response'] as JSONObject); - - const issuers: JSONArray = - (payload['saml2:Issuer'] as JSONArray) || - (payload['saml:Issuer'] as JSONArray) || - (payload['Issuer'] as JSONArray); - - if (issuers.length === 0) { - throw new BadRequestException('Issuers not found'); - } - - const issuer: JSONObject | string | undefined = issuers[0]; - - if (typeof issuer === 'string') { - return issuer; - } - - if (!issuer) { - throw new BadRequestException('Issuer not found'); - } - - const issuerUrl: string = issuer['_'] as string; - - if (!issuerUrl) { - throw new BadRequestException( - 'Issuer URL not found in SAML response' - ); - } - - return issuerUrl.trim(); + if (!samlAssertion || samlAssertion.length === 0) { + throw new BadRequestException("SAML Assertion not found"); } + + const samlSubject: JSONArray = + ((samlAssertion[0] as JSONObject)["saml2:Subject"] as JSONArray) || + ((samlAssertion[0] as JSONObject)["saml:Subject"] as JSONArray) || + ((samlAssertion[0] as JSONObject)["Subject"] as JSONArray); + + if (!samlSubject || samlSubject.length === 0) { + throw new BadRequestException("SAML Subject not found"); + } + + const samlNameId: JSONArray = + ((samlSubject[0] as JSONObject)["saml2:NameID"] as JSONArray) || + ((samlSubject[0] as JSONObject)["saml:NameID"] as JSONArray) || + ((samlSubject[0] as JSONObject)["NameID"] as JSONArray); + + if (!samlNameId || samlNameId.length === 0) { + throw new BadRequestException("SAML NAME ID not found"); + } + + const emailString: string = (samlNameId[0] as JSONObject)["_"] as string; + + if (!emailString) { + if (!samlNameId || samlNameId.length === 0) { + throw new BadRequestException("SAML Email not found"); + } + } + } + + public static isSignatureValid( + samlPayload: string, + certificate: string, + ): boolean { + try { + const dom: Document = new xmldom.DOMParser().parseFromString(samlPayload); + const signature: Element | undefined = dom.getElementsByTagNameNS( + "http://www.w3.org/2000/09/xmldsig#", + "Signature", + )[0]; + const sig: xmlCrypto.SignedXml = new xmlCrypto.SignedXml(); + + sig.keyInfoProvider = { + getKeyInfo: function (_key: any) { + return `${certificate}`; + }, + getKey: function () { + return certificate; + } as any, + } as FileKeyInfo; + + sig.loadSignature(signature!.toString()); + const res: boolean = sig.checkSignature(samlPayload); + + return res; + } catch (err) { + logger.error(err); + return false; + } + } + + public static getEmail(payload: JSONObject): Email { + if (!payload["saml2p:Response"] && !payload["samlp:Response"]) { + throw new BadRequestException("SAML Response not found."); + } + + payload = + (payload["saml2p:Response"] as JSONObject) || + (payload["samlp:Response"] as JSONObject) || + (payload["Response"] as JSONObject); + + const samlAssertion: JSONArray = + (payload["saml2:Assertion"] as JSONArray) || + (payload["saml:Assertion"] as JSONArray) || + (payload["Assertion"] as JSONArray); + + if (!samlAssertion || samlAssertion.length === 0) { + throw new BadRequestException("SAML Assertion not found"); + } + + const samlSubject: JSONArray = + ((samlAssertion[0] as JSONObject)["saml2:Subject"] as JSONArray) || + ((samlAssertion[0] as JSONObject)["saml:Subject"] as JSONArray) || + ((samlAssertion[0] as JSONObject)["Subject"] as JSONArray); + + if (!samlSubject || samlSubject.length === 0) { + throw new BadRequestException("SAML Subject not found"); + } + + const samlNameId: JSONArray = + ((samlSubject[0] as JSONObject)["saml2:NameID"] as JSONArray) || + ((samlSubject[0] as JSONObject)["saml:NameID"] as JSONArray) || + ((samlSubject[0] as JSONObject)["NameID"] as JSONArray); + + if (!samlNameId || samlNameId.length === 0) { + throw new BadRequestException("SAML NAME ID not found"); + } + + const emailString: string = (samlNameId[0] as JSONObject)["_"] as string; + + return new Email(emailString.trim()); + } + + public static getIssuer(payload: JSONObject): string { + if (!payload["saml2p:Response"] && !payload["samlp:Response"]) { + throw new BadRequestException("SAML Response not found."); + } + + payload = + (payload["saml2p:Response"] as JSONObject) || + (payload["samlp:Response"] as JSONObject) || + (payload["Response"] as JSONObject); + + const issuers: JSONArray = + (payload["saml2:Issuer"] as JSONArray) || + (payload["saml:Issuer"] as JSONArray) || + (payload["Issuer"] as JSONArray); + + if (issuers.length === 0) { + throw new BadRequestException("Issuers not found"); + } + + const issuer: JSONObject | string | undefined = issuers[0]; + + if (typeof issuer === "string") { + return issuer; + } + + if (!issuer) { + throw new BadRequestException("Issuer not found"); + } + + const issuerUrl: string = issuer["_"] as string; + + if (!issuerUrl) { + throw new BadRequestException("Issuer URL not found in SAML response"); + } + + return issuerUrl.trim(); + } } diff --git a/App/FeatureSet/Notification/API/Call.ts b/App/FeatureSet/Notification/API/Call.ts index 4752a44668..b838ce1ccf 100644 --- a/App/FeatureSet/Notification/API/Call.ts +++ b/App/FeatureSet/Notification/API/Call.ts @@ -1,144 +1,143 @@ -import CallService from '../Services/CallService'; -import CallRequest from 'Common/Types/Call/CallRequest'; -import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import Phone from 'Common/Types/Phone'; -import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization'; -import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService'; +import CallService from "../Services/CallService"; +import CallRequest from "Common/Types/Call/CallRequest"; +import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import Phone from "Common/Types/Phone"; +import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization"; +import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig'; + ExpressRequest, + ExpressResponse, + ExpressRouter, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig"; const router: ExpressRouter = Express.getRouter(); router.post( - '/make-call', - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - const body: JSONObject = JSONFunctions.deserialize(req.body); + "/make-call", + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + const body: JSONObject = JSONFunctions.deserialize(req.body); - await CallService.makeCall(body['callRequest'] as CallRequest, { - projectId: body['projectId'] as ObjectID, - isSensitive: (body['isSensitive'] as boolean) || false, - userOnCallLogTimelineId: - (body['userOnCallLogTimelineId'] as ObjectID) || undefined, - customTwilioConfig: body['customTwilioConfig'] as any, - }); - - return Response.sendEmptySuccessResponse(req, res); - } -); - -router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => { - const body: JSONObject = req.body; - - const callSMSConfigId: ObjectID = new ObjectID( - body['callSMSConfigId'] as string - ); - - const config: ProjectCallSMSConfig | null = - await ProjectCallSMSConfigService.findOneById({ - id: callSMSConfigId, - props: { - isRoot: true, - }, - select: { - _id: true, - twilioAccountSID: true, - twilioAuthToken: true, - twilioPhoneNumber: true, - projectId: true, - }, - }); - - if (!config) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'call and sms config not found for id' + - callSMSConfigId.toString() - ) - ); - } - - const toPhone: Phone = new Phone(body['toPhone'] as string); - - if (!toPhone) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('toPhone is required') - ); - } - - // if any of the twilio config is missing, we will not send make the call - - if (!config.twilioAccountSID) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('twilioAccountSID is required') - ); - } - - if (!config.twilioAuthToken) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('twilioAuthToken is required') - ); - } - - if (!config.twilioPhoneNumber) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('twilioPhoneNumber is required') - ); - } - - const twilioConfig: TwilioConfig | undefined = - ProjectCallSMSConfigService.toTwilioConfig(config); - - try { - if (!twilioConfig) { - throw new BadDataException('twilioConfig is undefined'); - } - - const testCallRequest: CallRequest = { - data: [ - { - sayMessage: 'This is a test call from OneUptime.', - }, - ], - to: toPhone, - }; - - await CallService.makeCall(testCallRequest, { - projectId: config.projectId, - customTwilioConfig: twilioConfig, - }); - } catch (err) { - logger.error(err); - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Error making test call. Please check the twilio logs for more details' - ) - ); - } + await CallService.makeCall(body["callRequest"] as CallRequest, { + projectId: body["projectId"] as ObjectID, + isSensitive: (body["isSensitive"] as boolean) || false, + userOnCallLogTimelineId: + (body["userOnCallLogTimelineId"] as ObjectID) || undefined, + customTwilioConfig: body["customTwilioConfig"] as any, + }); return Response.sendEmptySuccessResponse(req, res); + }, +); + +router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => { + const body: JSONObject = req.body; + + const callSMSConfigId: ObjectID = new ObjectID( + body["callSMSConfigId"] as string, + ); + + const config: ProjectCallSMSConfig | null = + await ProjectCallSMSConfigService.findOneById({ + id: callSMSConfigId, + props: { + isRoot: true, + }, + select: { + _id: true, + twilioAccountSID: true, + twilioAuthToken: true, + twilioPhoneNumber: true, + projectId: true, + }, + }); + + if (!config) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "call and sms config not found for id" + callSMSConfigId.toString(), + ), + ); + } + + const toPhone: Phone = new Phone(body["toPhone"] as string); + + if (!toPhone) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("toPhone is required"), + ); + } + + // if any of the twilio config is missing, we will not send make the call + + if (!config.twilioAccountSID) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("twilioAccountSID is required"), + ); + } + + if (!config.twilioAuthToken) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("twilioAuthToken is required"), + ); + } + + if (!config.twilioPhoneNumber) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("twilioPhoneNumber is required"), + ); + } + + const twilioConfig: TwilioConfig | undefined = + ProjectCallSMSConfigService.toTwilioConfig(config); + + try { + if (!twilioConfig) { + throw new BadDataException("twilioConfig is undefined"); + } + + const testCallRequest: CallRequest = { + data: [ + { + sayMessage: "This is a test call from OneUptime.", + }, + ], + to: toPhone, + }; + + await CallService.makeCall(testCallRequest, { + projectId: config.projectId, + customTwilioConfig: twilioConfig, + }); + } catch (err) { + logger.error(err); + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Error making test call. Please check the twilio logs for more details", + ), + ); + } + + return Response.sendEmptySuccessResponse(req, res); }); export default router; diff --git a/App/FeatureSet/Notification/API/Mail.ts b/App/FeatureSet/Notification/API/Mail.ts index d954804b17..223c17fb1d 100644 --- a/App/FeatureSet/Notification/API/Mail.ts +++ b/App/FeatureSet/Notification/API/Mail.ts @@ -1,65 +1,65 @@ -import MailService from '../Services/MailService'; -import Dictionary from 'Common/Types/Dictionary'; -import Email from 'Common/Types/Email'; -import EmailMessage from 'Common/Types/Email/EmailMessage'; -import EmailServer from 'Common/Types/Email/EmailServer'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization'; +import MailService from "../Services/MailService"; +import Dictionary from "Common/Types/Dictionary"; +import Email from "Common/Types/Email"; +import EmailMessage from "Common/Types/Email/EmailMessage"; +import EmailServer from "Common/Types/Email/EmailServer"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, -} from 'CommonServer/Utils/Express'; -import Response from 'CommonServer/Utils/Response'; + ExpressRequest, + ExpressResponse, + ExpressRouter, +} from "CommonServer/Utils/Express"; +import Response from "CommonServer/Utils/Response"; const router: ExpressRouter = Express.getRouter(); router.post( - '/send', - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - const body: JSONObject = req.body; + "/send", + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + const body: JSONObject = req.body; - const mail: EmailMessage = { - templateType: body['templateType'] as EmailTemplateType, - toEmail: new Email(body['toEmail'] as string), - subject: body['subject'] as string, - vars: body['vars'] as Dictionary, - body: (body['body'] as string) || '', - }; + const mail: EmailMessage = { + templateType: body["templateType"] as EmailTemplateType, + toEmail: new Email(body["toEmail"] as string), + subject: body["subject"] as string, + vars: body["vars"] as Dictionary, + body: (body["body"] as string) || "", + }; - let mailServer: EmailServer | undefined = undefined; + let mailServer: EmailServer | undefined = undefined; - if (hasMailServerSettingsInBody(body)) { - mailServer = MailService.getEmailServer(req.body); - } - - await MailService.send(mail, { - projectId: body['projectId'] - ? new ObjectID(body['projectId'] as string) - : undefined, - emailServer: mailServer, - userOnCallLogTimelineId: - (body['userOnCallLogTimelineId'] as ObjectID) || undefined, - }); - - return Response.sendEmptySuccessResponse(req, res); + if (hasMailServerSettingsInBody(body)) { + mailServer = MailService.getEmailServer(req.body); } + + await MailService.send(mail, { + projectId: body["projectId"] + ? new ObjectID(body["projectId"] as string) + : undefined, + emailServer: mailServer, + userOnCallLogTimelineId: + (body["userOnCallLogTimelineId"] as ObjectID) || undefined, + }); + + return Response.sendEmptySuccessResponse(req, res); + }, ); type HasMailServerSettingsInBody = (body: JSONObject) => boolean; const hasMailServerSettingsInBody: HasMailServerSettingsInBody = ( - body: JSONObject + body: JSONObject, ): boolean => { - return ( - body && - Object.keys(body).filter((key: string) => { - return key.startsWith('SMTP_'); - }).length > 0 - ); + return ( + body && + Object.keys(body).filter((key: string) => { + return key.startsWith("SMTP_"); + }).length > 0 + ); }; export default router; diff --git a/App/FeatureSet/Notification/API/SMS.ts b/App/FeatureSet/Notification/API/SMS.ts index 7f0c49321a..7c2819f902 100644 --- a/App/FeatureSet/Notification/API/SMS.ts +++ b/App/FeatureSet/Notification/API/SMS.ts @@ -1,142 +1,133 @@ -import SmsService from '../Services/SmsService'; -import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import Phone from 'Common/Types/Phone'; -import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization'; -import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService'; +import SmsService from "../Services/SmsService"; +import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import Phone from "Common/Types/Phone"; +import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization"; +import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig'; + ExpressRequest, + ExpressResponse, + ExpressRouter, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig"; const router: ExpressRouter = Express.getRouter(); router.post( - '/send', - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - const body: JSONObject = JSONFunctions.deserialize(req.body); + "/send", + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + const body: JSONObject = JSONFunctions.deserialize(req.body); - await SmsService.sendSms( - body['to'] as Phone, - body['message'] as string, - { - projectId: body['projectId'] as ObjectID, - isSensitive: (body['isSensitive'] as boolean) || false, - userOnCallLogTimelineId: - (body['userOnCallLogTimelineId'] as ObjectID) || undefined, - customTwilioConfig: body['customTwilioConfig'] as any, - } - ); - - return Response.sendEmptySuccessResponse(req, res); - } -); - -router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => { - const body: JSONObject = req.body; - - const callSMSConfigId: ObjectID = new ObjectID( - body['callSMSConfigId'] as string - ); - - const config: ProjectCallSMSConfig | null = - await ProjectCallSMSConfigService.findOneById({ - id: callSMSConfigId, - props: { - isRoot: true, - }, - select: { - _id: true, - twilioAccountSID: true, - twilioAuthToken: true, - twilioPhoneNumber: true, - projectId: true, - }, - }); - - if (!config) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'call and sms config not found for id' + - callSMSConfigId.toString() - ) - ); - } - - const toPhone: Phone = new Phone(body['toPhone'] as string); - - if (!toPhone) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('toPhone is required') - ); - } - - // if any of the twilio config is missing, we will not send make the call - - if (!config.twilioAccountSID) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('twilioAccountSID is required') - ); - } - - if (!config.twilioAuthToken) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('twilioAuthToken is required') - ); - } - - if (!config.twilioPhoneNumber) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('twilioPhoneNumber is required') - ); - } - - const twilioConfig: TwilioConfig | undefined = - ProjectCallSMSConfigService.toTwilioConfig(config); - - try { - if (!twilioConfig) { - throw new BadDataException('twilioConfig is undefined'); - } - - await SmsService.sendSms( - toPhone, - 'This is a test SMS from OneUptime.', - { - projectId: config.projectId, - customTwilioConfig: twilioConfig, - } - ); - } catch (err) { - logger.error(err); - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Failed to send test SMS. Please check the twilio logs for more details.' - ) - ); - } + await SmsService.sendSms(body["to"] as Phone, body["message"] as string, { + projectId: body["projectId"] as ObjectID, + isSensitive: (body["isSensitive"] as boolean) || false, + userOnCallLogTimelineId: + (body["userOnCallLogTimelineId"] as ObjectID) || undefined, + customTwilioConfig: body["customTwilioConfig"] as any, + }); return Response.sendEmptySuccessResponse(req, res); + }, +); + +router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => { + const body: JSONObject = req.body; + + const callSMSConfigId: ObjectID = new ObjectID( + body["callSMSConfigId"] as string, + ); + + const config: ProjectCallSMSConfig | null = + await ProjectCallSMSConfigService.findOneById({ + id: callSMSConfigId, + props: { + isRoot: true, + }, + select: { + _id: true, + twilioAccountSID: true, + twilioAuthToken: true, + twilioPhoneNumber: true, + projectId: true, + }, + }); + + if (!config) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "call and sms config not found for id" + callSMSConfigId.toString(), + ), + ); + } + + const toPhone: Phone = new Phone(body["toPhone"] as string); + + if (!toPhone) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("toPhone is required"), + ); + } + + // if any of the twilio config is missing, we will not send make the call + + if (!config.twilioAccountSID) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("twilioAccountSID is required"), + ); + } + + if (!config.twilioAuthToken) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("twilioAuthToken is required"), + ); + } + + if (!config.twilioPhoneNumber) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("twilioPhoneNumber is required"), + ); + } + + const twilioConfig: TwilioConfig | undefined = + ProjectCallSMSConfigService.toTwilioConfig(config); + + try { + if (!twilioConfig) { + throw new BadDataException("twilioConfig is undefined"); + } + + await SmsService.sendSms(toPhone, "This is a test SMS from OneUptime.", { + projectId: config.projectId, + customTwilioConfig: twilioConfig, + }); + } catch (err) { + logger.error(err); + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Failed to send test SMS. Please check the twilio logs for more details.", + ), + ); + } + + return Response.sendEmptySuccessResponse(req, res); }); export default router; diff --git a/App/FeatureSet/Notification/API/SMTPConfig.ts b/App/FeatureSet/Notification/API/SMTPConfig.ts index 36c5e50091..8ac1abfbcb 100644 --- a/App/FeatureSet/Notification/API/SMTPConfig.ts +++ b/App/FeatureSet/Notification/API/SMTPConfig.ts @@ -1,104 +1,104 @@ -import MailService from '../Services/MailService'; -import Email from 'Common/Types/Email'; -import EmailMessage from 'Common/Types/Email/EmailMessage'; -import EmailServer from 'Common/Types/Email/EmailServer'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import ProjectSMTPConfigService from 'CommonServer/Services/ProjectSmtpConfigService'; +import MailService from "../Services/MailService"; +import Email from "Common/Types/Email"; +import EmailMessage from "Common/Types/Email/EmailMessage"; +import EmailServer from "Common/Types/Email/EmailServer"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import ProjectSMTPConfigService from "CommonServer/Services/ProjectSmtpConfigService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import ProjectSmtpConfig from 'Model/Models/ProjectSmtpConfig'; + ExpressRequest, + ExpressResponse, + ExpressRouter, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import ProjectSmtpConfig from "Model/Models/ProjectSmtpConfig"; const router: ExpressRouter = Express.getRouter(); -router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => { - const body: JSONObject = req.body; +router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => { + const body: JSONObject = req.body; - const smtpConfigId: ObjectID = new ObjectID(body['smtpConfigId'] as string); + const smtpConfigId: ObjectID = new ObjectID(body["smtpConfigId"] as string); - const config: ProjectSmtpConfig | null = - await ProjectSMTPConfigService.findOneById({ - id: smtpConfigId, - props: { - isRoot: true, - }, - select: { - _id: true, - hostname: true, - port: true, - username: true, - password: true, - fromEmail: true, - fromName: true, - secure: true, - projectId: true, - }, - }); + const config: ProjectSmtpConfig | null = + await ProjectSMTPConfigService.findOneById({ + id: smtpConfigId, + props: { + isRoot: true, + }, + select: { + _id: true, + hostname: true, + port: true, + username: true, + password: true, + fromEmail: true, + fromName: true, + secure: true, + projectId: true, + }, + }); - if (!config) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'smtp-config not found for id' + smtpConfigId.toString() - ) - ); - } + if (!config) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "smtp-config not found for id" + smtpConfigId.toString(), + ), + ); + } - const toEmail: Email = new Email(body['toEmail'] as string); + const toEmail: Email = new Email(body["toEmail"] as string); - if (!toEmail) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('toEmail is required') - ); - } + if (!toEmail) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("toEmail is required"), + ); + } - const mail: EmailMessage = { - templateType: EmailTemplateType.SMTPTest, - toEmail: new Email(body['toEmail'] as string), - subject: 'Test Email from OneUptime', - vars: {}, - body: '', - }; + const mail: EmailMessage = { + templateType: EmailTemplateType.SMTPTest, + toEmail: new Email(body["toEmail"] as string), + subject: "Test Email from OneUptime", + vars: {}, + body: "", + }; - const mailServer: EmailServer = { - id: config.id!, - host: config.hostname!, - port: config.port!, - username: config.username!, - password: config.password!, - fromEmail: config.fromEmail!, - fromName: config.fromName!, - secure: Boolean(config.secure), - }; + const mailServer: EmailServer = { + id: config.id!, + host: config.hostname!, + port: config.port!, + username: config.username!, + password: config.password!, + fromEmail: config.fromEmail!, + fromName: config.fromName!, + secure: Boolean(config.secure), + }; - try { - await MailService.send(mail, { - emailServer: mailServer, - projectId: config.projectId!, - timeout: 4000, - }); - } catch (err) { - logger.error(err); - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.' - ) - ); - } + try { + await MailService.send(mail, { + emailServer: mailServer, + projectId: config.projectId!, + timeout: 4000, + }); + } catch (err) { + logger.error(err); + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.", + ), + ); + } - return Response.sendEmptySuccessResponse(req, res); + return Response.sendEmptySuccessResponse(req, res); }); export default router; diff --git a/App/FeatureSet/Notification/Config.ts b/App/FeatureSet/Notification/Config.ts index b3aa05bbd9..9ce5a37938 100644 --- a/App/FeatureSet/Notification/Config.ts +++ b/App/FeatureSet/Notification/Config.ts @@ -1,18 +1,18 @@ -import Hostname from 'Common/Types/API/Hostname'; -import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig'; -import Email from 'Common/Types/Email'; -import EmailServer from 'Common/Types/Email/EmailServer'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Port from 'Common/Types/Port'; -import GlobalConfigService from 'CommonServer/Services/GlobalConfigService'; -import GlobalConfig, { EmailServerType } from 'Model/Models/GlobalConfig'; +import Hostname from "Common/Types/API/Hostname"; +import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig"; +import Email from "Common/Types/Email"; +import EmailServer from "Common/Types/Email/EmailServer"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Port from "Common/Types/Port"; +import GlobalConfigService from "CommonServer/Services/GlobalConfigService"; +import GlobalConfig, { EmailServerType } from "Model/Models/GlobalConfig"; export const InternalSmtpPassword: string = - process.env['INTERNAL_SMTP_PASSWORD'] || ''; + process.env["INTERNAL_SMTP_PASSWORD"] || ""; export const InternalSmtpHost: Hostname = new Hostname( - process.env['INTERNAL_SMTP_HOST'] || 'haraka' + process.env["INTERNAL_SMTP_HOST"] || "haraka", ); export const InternalSmtpPort: Port = new Port(2525); @@ -20,187 +20,187 @@ export const InternalSmtpPort: Port = new Port(2525); export const InternalSmtpSecure: boolean = false; export const InternalSmtpEmail: Email = new Email( - process.env['INTERNAL_SMTP_EMAIL'] || 'noreply@oneuptime.com' + process.env["INTERNAL_SMTP_EMAIL"] || "noreply@oneuptime.com", ); export const InternalSmtpFromName: string = - process.env['INTERNAL_SMTP_FROM_NAME'] || 'OneUptime'; + process.env["INTERNAL_SMTP_FROM_NAME"] || "OneUptime"; type GetGlobalSMTPConfig = () => Promise; export const getGlobalSMTPConfig: GetGlobalSMTPConfig = - async (): Promise => { - const globalConfig: GlobalConfig | null = - await GlobalConfigService.findOneBy({ - query: { - _id: ObjectID.getZeroObjectID().toString(), - }, - props: { - isRoot: true, - }, - select: { - smtpFromEmail: true, - smtpHost: true, - smtpPort: true, - smtpUsername: true, - smtpPassword: true, - isSMTPSecure: true, - smtpFromName: true, - }, - }); + async (): Promise => { + const globalConfig: GlobalConfig | null = + await GlobalConfigService.findOneBy({ + query: { + _id: ObjectID.getZeroObjectID().toString(), + }, + props: { + isRoot: true, + }, + select: { + smtpFromEmail: true, + smtpHost: true, + smtpPort: true, + smtpUsername: true, + smtpPassword: true, + isSMTPSecure: true, + smtpFromName: true, + }, + }); - if (!globalConfig) { - throw new BadDataException('Global Config not found'); - } + if (!globalConfig) { + throw new BadDataException("Global Config not found"); + } - if ( - !globalConfig.smtpFromEmail || - !globalConfig.smtpHost || - !globalConfig.smtpPort || - !globalConfig.smtpUsername || - !globalConfig.smtpPassword || - !globalConfig.smtpFromName - ) { - return null; - } + if ( + !globalConfig.smtpFromEmail || + !globalConfig.smtpHost || + !globalConfig.smtpPort || + !globalConfig.smtpUsername || + !globalConfig.smtpPassword || + !globalConfig.smtpFromName + ) { + return null; + } - return { - host: globalConfig.smtpHost, - port: globalConfig.smtpPort, - username: globalConfig.smtpUsername, - password: globalConfig.smtpPassword, - secure: globalConfig.isSMTPSecure || false, - fromEmail: globalConfig.smtpFromEmail, - fromName: globalConfig.smtpFromName, - }; + return { + host: globalConfig.smtpHost, + port: globalConfig.smtpPort, + username: globalConfig.smtpUsername, + password: globalConfig.smtpPassword, + secure: globalConfig.isSMTPSecure || false, + fromEmail: globalConfig.smtpFromEmail, + fromName: globalConfig.smtpFromName, }; + }; type GetEmailServerTypeFunction = () => Promise; export const getEmailServerType: GetEmailServerTypeFunction = - async (): Promise => { - const globalConfig: GlobalConfig | null = - await GlobalConfigService.findOneBy({ - query: { - _id: ObjectID.getZeroObjectID().toString(), - }, - props: { - isRoot: true, - }, - select: { - emailServerType: true, - }, - }); + async (): Promise => { + const globalConfig: GlobalConfig | null = + await GlobalConfigService.findOneBy({ + query: { + _id: ObjectID.getZeroObjectID().toString(), + }, + props: { + isRoot: true, + }, + select: { + emailServerType: true, + }, + }); - if (!globalConfig) { - return EmailServerType.Internal; - } + if (!globalConfig) { + return EmailServerType.Internal; + } - return globalConfig.emailServerType || EmailServerType.Internal; - }; + return globalConfig.emailServerType || EmailServerType.Internal; + }; export interface SendGridConfig { - apiKey: string; - fromName: string; - fromEmail: Email; + apiKey: string; + fromName: string; + fromEmail: Email; } type GetSendgridConfigFunction = () => Promise; export const getSendgridConfig: GetSendgridConfigFunction = - async (): Promise => { - const globalConfig: GlobalConfig | null = - await GlobalConfigService.findOneBy({ - query: { - _id: ObjectID.getZeroObjectID().toString(), - }, - props: { - isRoot: true, - }, - select: { - sendgridApiKey: true, - sendgridFromEmail: true, - sendgridFromName: true, - }, - }); + async (): Promise => { + const globalConfig: GlobalConfig | null = + await GlobalConfigService.findOneBy({ + query: { + _id: ObjectID.getZeroObjectID().toString(), + }, + props: { + isRoot: true, + }, + select: { + sendgridApiKey: true, + sendgridFromEmail: true, + sendgridFromName: true, + }, + }); - if (!globalConfig) { - return null; - } + if (!globalConfig) { + return null; + } - if ( - globalConfig.sendgridApiKey && - globalConfig.sendgridFromEmail && - globalConfig.sendgridFromName - ) { - return { - apiKey: globalConfig.sendgridApiKey, - fromName: globalConfig.sendgridFromName, - fromEmail: globalConfig.sendgridFromEmail, - }; - } + if ( + globalConfig.sendgridApiKey && + globalConfig.sendgridFromEmail && + globalConfig.sendgridFromName + ) { + return { + apiKey: globalConfig.sendgridApiKey, + fromName: globalConfig.sendgridFromName, + fromEmail: globalConfig.sendgridFromEmail, + }; + } - return null; - }; + return null; + }; type GetTwilioConfigFunction = () => Promise; export const getTwilioConfig: GetTwilioConfigFunction = - async (): Promise => { - const globalConfig: GlobalConfig | null = - await GlobalConfigService.findOneBy({ - query: { - _id: ObjectID.getZeroObjectID().toString(), - }, - props: { - isRoot: true, - }, - select: { - twilioAccountSID: true, - twilioAuthToken: true, - twilioPhoneNumber: true, - }, - }); + async (): Promise => { + const globalConfig: GlobalConfig | null = + await GlobalConfigService.findOneBy({ + query: { + _id: ObjectID.getZeroObjectID().toString(), + }, + props: { + isRoot: true, + }, + select: { + twilioAccountSID: true, + twilioAuthToken: true, + twilioPhoneNumber: true, + }, + }); - if (!globalConfig) { - throw new BadDataException('Global Config not found'); - } + if (!globalConfig) { + throw new BadDataException("Global Config not found"); + } - if ( - !globalConfig.twilioAccountSID || - !globalConfig.twilioAuthToken || - !globalConfig.twilioPhoneNumber - ) { - return null; - } + if ( + !globalConfig.twilioAccountSID || + !globalConfig.twilioAuthToken || + !globalConfig.twilioPhoneNumber + ) { + return null; + } - return { - accountSid: globalConfig.twilioAccountSID, - authToken: globalConfig.twilioAuthToken, - phoneNumber: globalConfig.twilioPhoneNumber, - }; + return { + accountSid: globalConfig.twilioAccountSID, + authToken: globalConfig.twilioAuthToken, + phoneNumber: globalConfig.twilioPhoneNumber, }; + }; export const SMSDefaultCostInCents: number = process.env[ - 'SMS_DEFAULT_COST_IN_CENTS' + "SMS_DEFAULT_COST_IN_CENTS" ] - ? parseInt(process.env['SMS_DEFAULT_COST_IN_CENTS']) - : 0; + ? parseInt(process.env["SMS_DEFAULT_COST_IN_CENTS"]) + : 0; export const SMSHighRiskCostInCents: number = process.env[ - 'SMS_HIGH_RISK_COST_IN_CENTS' + "SMS_HIGH_RISK_COST_IN_CENTS" ] - ? parseInt(process.env['SMS_HIGH_RISK_COST_IN_CENTS']) - : 0; + ? parseInt(process.env["SMS_HIGH_RISK_COST_IN_CENTS"]) + : 0; export const CallHighRiskCostInCentsPerMinute: number = process.env[ - 'CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE' + "CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE" ] - ? parseInt(process.env['CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE']) - : 0; + ? parseInt(process.env["CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE"]) + : 0; export const CallDefaultCostInCentsPerMinute: number = process.env[ - 'CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE' + "CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE" ] - ? parseInt(process.env['CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE']) - : 0; + ? parseInt(process.env["CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE"]) + : 0; diff --git a/App/FeatureSet/Notification/Index.ts b/App/FeatureSet/Notification/Index.ts index 0cc3455e54..8708c312e8 100644 --- a/App/FeatureSet/Notification/Index.ts +++ b/App/FeatureSet/Notification/Index.ts @@ -1,23 +1,23 @@ -import CallAPI from './API/Call'; +import CallAPI from "./API/Call"; // API -import MailAPI from './API/Mail'; -import SmsAPI from './API/SMS'; -import SMTPConfigAPI from './API/SMTPConfig'; -import './Utils/Handlebars'; -import FeatureSet from 'CommonServer/Types/FeatureSet'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import 'ejs'; +import MailAPI from "./API/Mail"; +import SmsAPI from "./API/SMS"; +import SMTPConfigAPI from "./API/SMTPConfig"; +import "./Utils/Handlebars"; +import FeatureSet from "CommonServer/Types/FeatureSet"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import "ejs"; const NotificationFeatureSet: FeatureSet = { - init: async (): Promise => { - const APP_NAME: string = 'api/notification'; - const app: ExpressApplication = Express.getExpressApp(); + init: async (): Promise => { + const APP_NAME: string = "api/notification"; + const app: ExpressApplication = Express.getExpressApp(); - app.use([`/${APP_NAME}/email`, '/email'], MailAPI); - app.use([`/${APP_NAME}/sms`, '/sms'], SmsAPI); - app.use([`/${APP_NAME}/call`, '/call'], CallAPI); - app.use([`/${APP_NAME}/smtp-config`, '/smtp-config'], SMTPConfigAPI); - }, + app.use([`/${APP_NAME}/email`, "/email"], MailAPI); + app.use([`/${APP_NAME}/sms`, "/sms"], SmsAPI); + app.use([`/${APP_NAME}/call`, "/call"], CallAPI); + app.use([`/${APP_NAME}/smtp-config`, "/smtp-config"], SMTPConfigAPI); + }, }; export default NotificationFeatureSet; diff --git a/App/FeatureSet/Notification/Services/CallService.ts b/App/FeatureSet/Notification/Services/CallService.ts index 522c2c7a53..9706972731 100644 --- a/App/FeatureSet/Notification/Services/CallService.ts +++ b/App/FeatureSet/Notification/Services/CallService.ts @@ -1,383 +1,364 @@ import { - CallDefaultCostInCentsPerMinute, - CallHighRiskCostInCentsPerMinute, - getTwilioConfig, -} from '../Config'; + CallDefaultCostInCentsPerMinute, + CallHighRiskCostInCentsPerMinute, + getTwilioConfig, +} from "../Config"; import CallRequest, { - GatherInput, - Say, - isHighRiskPhoneNumber, -} from 'Common/Types/Call/CallRequest'; -import CallStatus from 'Common/Types/Call/CallStatus'; -import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import UserNotificationStatus from 'Common/Types/UserNotification/UserNotificationStatus'; -import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig'; -import CallLogService from 'CommonServer/Services/CallLogService'; -import NotificationService from 'CommonServer/Services/NotificationService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import UserOnCallLogTimelineService from 'CommonServer/Services/UserOnCallLogTimelineService'; -import JSONWebToken from 'CommonServer/Utils/JsonWebToken'; -import logger from 'CommonServer/Utils/Logger'; -import CallLog from 'Model/Models/CallLog'; -import Project from 'Model/Models/Project'; -import Twilio from 'twilio'; -import { CallInstance } from 'twilio/lib/rest/api/v2010/account/call'; + GatherInput, + Say, + isHighRiskPhoneNumber, +} from "Common/Types/Call/CallRequest"; +import CallStatus from "Common/Types/Call/CallStatus"; +import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import UserNotificationStatus from "Common/Types/UserNotification/UserNotificationStatus"; +import { IsBillingEnabled } from "CommonServer/EnvironmentConfig"; +import CallLogService from "CommonServer/Services/CallLogService"; +import NotificationService from "CommonServer/Services/NotificationService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import UserOnCallLogTimelineService from "CommonServer/Services/UserOnCallLogTimelineService"; +import JSONWebToken from "CommonServer/Utils/JsonWebToken"; +import logger from "CommonServer/Utils/Logger"; +import CallLog from "Model/Models/CallLog"; +import Project from "Model/Models/Project"; +import Twilio from "twilio"; +import { CallInstance } from "twilio/lib/rest/api/v2010/account/call"; export default class CallService { - public static async makeCall( - callRequest: CallRequest, - options: { - projectId?: ObjectID | undefined; // project id for sms log - isSensitive?: boolean; // if true, message will not be logged - userOnCallLogTimelineId?: ObjectID | undefined; // user notification log timeline id - customTwilioConfig?: TwilioConfig | undefined; + public static async makeCall( + callRequest: CallRequest, + options: { + projectId?: ObjectID | undefined; // project id for sms log + isSensitive?: boolean; // if true, message will not be logged + userOnCallLogTimelineId?: ObjectID | undefined; // user notification log timeline id + customTwilioConfig?: TwilioConfig | undefined; + }, + ): Promise { + let callError: Error | null = null; + const callLog: CallLog = new CallLog(); + + try { + logger.debug("Call Request received."); + + let callCost: number = 0; + + // is no custom twilio config is provided, use default twilio config and charge for call. + const shouldChargeForCall: boolean = + IsBillingEnabled && !options.customTwilioConfig; + + if (shouldChargeForCall) { + callCost = CallDefaultCostInCentsPerMinute / 100; + if (isHighRiskPhoneNumber(callRequest.to)) { + callCost = CallHighRiskCostInCentsPerMinute / 100; } - ): Promise { - let callError: Error | null = null; - const callLog: CallLog = new CallLog(); + } - try { - logger.debug('Call Request received.'); + logger.debug("Call Cost: " + callCost); - let callCost: number = 0; + const twilioConfig: TwilioConfig | null = + options.customTwilioConfig || (await getTwilioConfig()); - // is no custom twilio config is provided, use default twilio config and charge for call. - const shouldChargeForCall: boolean = - IsBillingEnabled && !options.customTwilioConfig; + if (!twilioConfig) { + throw new BadDataException("Twilio Config not found"); + } - if (shouldChargeForCall) { - callCost = CallDefaultCostInCentsPerMinute / 100; - if (isHighRiskPhoneNumber(callRequest.to)) { - callCost = CallHighRiskCostInCentsPerMinute / 100; - } - } + const client: Twilio.Twilio = Twilio( + twilioConfig.accountSid, + twilioConfig.authToken, + ); - logger.debug('Call Cost: ' + callCost); + callLog.toNumber = callRequest.to; + callLog.fromNumber = twilioConfig.phoneNumber; + callLog.callData = + options && options.isSensitive + ? { message: "This call is sensitive and is not logged" } + : JSON.parse(JSON.stringify(callRequest)); + callLog.callCostInUSDCents = 0; - const twilioConfig: TwilioConfig | null = - options.customTwilioConfig || (await getTwilioConfig()); + if (options.projectId) { + callLog.projectId = options.projectId; + } - if (!twilioConfig) { - throw new BadDataException('Twilio Config not found'); - } + let project: Project | null = null; - const client: Twilio.Twilio = Twilio( - twilioConfig.accountSid, - twilioConfig.authToken + // make sure project has enough balance. + + if (options.projectId) { + project = await ProjectService.findOneById({ + id: options.projectId, + select: { + smsOrCallCurrentBalanceInUSDCents: true, + enableCallNotifications: true, + lowCallAndSMSBalanceNotificationSentToOwners: true, + name: true, + notEnabledSmsOrCallNotificationSentToOwners: true, + }, + props: { + isRoot: true, + }, + }); + + logger.debug("Project found."); + + if (!project) { + callLog.status = CallStatus.Error; + callLog.statusMessage = `Project ${options.projectId.toString()} not found.`; + logger.error(callLog.statusMessage); + await CallLogService.create({ + data: callLog, + props: { + isRoot: true, + }, + }); + return; + } + + if (!project.enableCallNotifications) { + callLog.status = CallStatus.Error; + callLog.statusMessage = `Call notifications are not enabled for this project. Please enable Call notifications in Project Settings.`; + logger.error(callLog.statusMessage); + await CallLogService.create({ + data: callLog, + props: { + isRoot: true, + }, + }); + + if (!project.notEnabledSmsOrCallNotificationSentToOwners) { + await ProjectService.updateOneById({ + data: { + notEnabledSmsOrCallNotificationSentToOwners: true, + }, + id: project.id!, + props: { + isRoot: true, + }, + }); + await ProjectService.sendEmailToProjectOwners( + project.id!, + "Call notifications not enabled for " + (project.name || ""), + `We tried to make a call to ${callRequest.to.toString()}.

This Call was not sent because call notifications are not enabled for this project. Please enable call notifications in Project Settings.`, ); + } + return; + } - callLog.toNumber = callRequest.to; - callLog.fromNumber = twilioConfig.phoneNumber; - callLog.callData = - options && options.isSensitive - ? { message: 'This call is sensitive and is not logged' } - : JSON.parse(JSON.stringify(callRequest)); - callLog.callCostInUSDCents = 0; + if (shouldChargeForCall) { + // check if auto recharge is enabled and current balance is low. + let updatedBalance: number = + project.smsOrCallCurrentBalanceInUSDCents!; + try { + updatedBalance = await NotificationService.rechargeIfBalanceIsLow( + project.id!, + ); + } catch (err) { + logger.error(err); + } - if (options.projectId) { - callLog.projectId = options.projectId; - } + project.smsOrCallCurrentBalanceInUSDCents = updatedBalance; - let project: Project | null = null; - - // make sure project has enough balance. - - if (options.projectId) { - project = await ProjectService.findOneById({ - id: options.projectId, - select: { - smsOrCallCurrentBalanceInUSDCents: true, - enableCallNotifications: true, - lowCallAndSMSBalanceNotificationSentToOwners: true, - name: true, - notEnabledSmsOrCallNotificationSentToOwners: true, - }, - props: { - isRoot: true, - }, - }); - - logger.debug('Project found.'); - - if (!project) { - callLog.status = CallStatus.Error; - callLog.statusMessage = `Project ${options.projectId.toString()} not found.`; - logger.error(callLog.statusMessage); - await CallLogService.create({ - data: callLog, - props: { - isRoot: true, - }, - }); - return; - } - - if (!project.enableCallNotifications) { - callLog.status = CallStatus.Error; - callLog.statusMessage = `Call notifications are not enabled for this project. Please enable Call notifications in Project Settings.`; - logger.error(callLog.statusMessage); - await CallLogService.create({ - data: callLog, - props: { - isRoot: true, - }, - }); - - if (!project.notEnabledSmsOrCallNotificationSentToOwners) { - await ProjectService.updateOneById({ - data: { - notEnabledSmsOrCallNotificationSentToOwners: - true, - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - await ProjectService.sendEmailToProjectOwners( - project.id!, - 'Call notifications not enabled for ' + - (project.name || ''), - `We tried to make a call to ${callRequest.to.toString()}.

This Call was not sent because call notifications are not enabled for this project. Please enable call notifications in Project Settings.` - ); - } - return; - } - - if (shouldChargeForCall) { - // check if auto recharge is enabled and current balance is low. - let updatedBalance: number = - project.smsOrCallCurrentBalanceInUSDCents!; - try { - updatedBalance = - await NotificationService.rechargeIfBalanceIsLow( - project.id! - ); - } catch (err) { - logger.error(err); - } - - project.smsOrCallCurrentBalanceInUSDCents = updatedBalance; - - if (!project.smsOrCallCurrentBalanceInUSDCents) { - callLog.status = CallStatus.LowBalance; - callLog.statusMessage = `Project ${options.projectId.toString()} does not have enough Call balance.`; - logger.error(callLog.statusMessage); - await CallLogService.create({ - data: callLog, - props: { - isRoot: true, - }, - }); - - if ( - !project.lowCallAndSMSBalanceNotificationSentToOwners - ) { - await ProjectService.updateOneById({ - data: { - lowCallAndSMSBalanceNotificationSentToOwners: - true, - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - await ProjectService.sendEmailToProjectOwners( - project.id!, - 'Low SMS and Call Balance for ' + - (project.name || ''), - `We tried to make a call to ${callRequest.to.toString()}. This call was not made because project does not have enough balance to make calls. Current balance is ${ - (project.smsOrCallCurrentBalanceInUSDCents || - 0) / 100 - } USD. Required balance to send this SMS should is ${callCost} USD. Please enable auto recharge or recharge manually.` - ); - } - return; - } - - if ( - project.smsOrCallCurrentBalanceInUSDCents < - callCost * 100 - ) { - callLog.status = CallStatus.LowBalance; - callLog.statusMessage = `Project does not have enough balance to make this call. Current balance is ${ - project.smsOrCallCurrentBalanceInUSDCents / 100 - } USD. Required balance is ${callCost} USD to make this call.`; - logger.error(callLog.statusMessage); - await CallLogService.create({ - data: callLog, - props: { - isRoot: true, - }, - }); - if ( - !project.lowCallAndSMSBalanceNotificationSentToOwners - ) { - await ProjectService.updateOneById({ - data: { - lowCallAndSMSBalanceNotificationSentToOwners: - true, - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - await ProjectService.sendEmailToProjectOwners( - project.id!, - 'Low SMS and Call Balance for ' + - (project.name || ''), - `We tried to make a call to ${callRequest.to.toString()}. This call was not made because project does not have enough balance to make a call. Current balance is ${ - project.smsOrCallCurrentBalanceInUSDCents / - 100 - } USD. Required balance is ${callCost} USD to make this call. Please enable auto recharge or recharge manually.` - ); - } - return; - } - } - } - - logger.debug('Sending Call Request.'); - - const twillioCall: CallInstance = await client.calls.create({ - twiml: this.generateTwimlForCall(callRequest), - to: callRequest.to.toString(), - from: twilioConfig.phoneNumber.toString(), // From a valid Twilio number - }); - - logger.debug('Call Request sent successfully.'); - - callLog.status = CallStatus.Success; - callLog.statusMessage = 'Call ID: ' + twillioCall.sid; - - logger.debug('Call ID: ' + twillioCall.sid); - logger.debug(callLog.statusMessage); - - if (shouldChargeForCall && project) { - logger.debug('Updating Project Balance.'); - - callLog.callCostInUSDCents = callCost * 100; - - if (twillioCall && parseInt(twillioCall.duration) > 60) { - callLog.callCostInUSDCents = Math.ceil( - Math.ceil(parseInt(twillioCall.duration) / 60) * - (callCost * 100) - ); - } - - logger.debug('Call Cost: ' + callLog.callCostInUSDCents); - - project.smsOrCallCurrentBalanceInUSDCents = Math.floor( - project.smsOrCallCurrentBalanceInUSDCents! - callCost * 100 - ); - - await ProjectService.updateOneById({ - data: { - smsOrCallCurrentBalanceInUSDCents: - project.smsOrCallCurrentBalanceInUSDCents, - notEnabledSmsOrCallNotificationSentToOwners: false, // reset this flag - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - - logger.debug("Project's current balance updated."); - logger.debug( - 'Current Balance: ' + - project.smsOrCallCurrentBalanceInUSDCents - ); - } - } catch (e: any) { - callLog.callCostInUSDCents = 0; - callLog.status = CallStatus.Error; - callLog.statusMessage = - e && e.message ? e.message.toString() : e.toString(); - - logger.error('Call Request failed.'); + if (!project.smsOrCallCurrentBalanceInUSDCents) { + callLog.status = CallStatus.LowBalance; + callLog.statusMessage = `Project ${options.projectId.toString()} does not have enough Call balance.`; logger.error(callLog.statusMessage); - callError = e; - } - - logger.debug('Saving Call Log if project id is provided.'); - - if (options.projectId) { - logger.debug('Saving Call Log.'); await CallLogService.create({ - data: callLog, - props: { - isRoot: true, - }, + data: callLog, + props: { + isRoot: true, + }, }); - logger.debug('Call Log saved.'); - } else { - logger.debug('Project Id is not provided. Call Log not saved.'); - } - if (options.userOnCallLogTimelineId) { - await UserOnCallLogTimelineService.updateOneById({ + if (!project.lowCallAndSMSBalanceNotificationSentToOwners) { + await ProjectService.updateOneById({ data: { - status: - callLog.status === CallStatus.Success - ? UserNotificationStatus.Sent - : UserNotificationStatus.Error, - statusMessage: callLog.statusMessage!, + lowCallAndSMSBalanceNotificationSentToOwners: true, }, - id: options.userOnCallLogTimelineId, + id: project.id!, props: { - isRoot: true, + isRoot: true, }, + }); + await ProjectService.sendEmailToProjectOwners( + project.id!, + "Low SMS and Call Balance for " + (project.name || ""), + `We tried to make a call to ${callRequest.to.toString()}. This call was not made because project does not have enough balance to make calls. Current balance is ${ + (project.smsOrCallCurrentBalanceInUSDCents || 0) / 100 + } USD. Required balance to send this SMS should is ${callCost} USD. Please enable auto recharge or recharge manually.`, + ); + } + return; + } + + if (project.smsOrCallCurrentBalanceInUSDCents < callCost * 100) { + callLog.status = CallStatus.LowBalance; + callLog.statusMessage = `Project does not have enough balance to make this call. Current balance is ${ + project.smsOrCallCurrentBalanceInUSDCents / 100 + } USD. Required balance is ${callCost} USD to make this call.`; + logger.error(callLog.statusMessage); + await CallLogService.create({ + data: callLog, + props: { + isRoot: true, + }, }); + if (!project.lowCallAndSMSBalanceNotificationSentToOwners) { + await ProjectService.updateOneById({ + data: { + lowCallAndSMSBalanceNotificationSentToOwners: true, + }, + id: project.id!, + props: { + isRoot: true, + }, + }); + await ProjectService.sendEmailToProjectOwners( + project.id!, + "Low SMS and Call Balance for " + (project.name || ""), + `We tried to make a call to ${callRequest.to.toString()}. This call was not made because project does not have enough balance to make a call. Current balance is ${ + project.smsOrCallCurrentBalanceInUSDCents / 100 + } USD. Required balance is ${callCost} USD to make this call. Please enable auto recharge or recharge manually.`, + ); + } + return; + } + } + } + + logger.debug("Sending Call Request."); + + const twillioCall: CallInstance = await client.calls.create({ + twiml: this.generateTwimlForCall(callRequest), + to: callRequest.to.toString(), + from: twilioConfig.phoneNumber.toString(), // From a valid Twilio number + }); + + logger.debug("Call Request sent successfully."); + + callLog.status = CallStatus.Success; + callLog.statusMessage = "Call ID: " + twillioCall.sid; + + logger.debug("Call ID: " + twillioCall.sid); + logger.debug(callLog.statusMessage); + + if (shouldChargeForCall && project) { + logger.debug("Updating Project Balance."); + + callLog.callCostInUSDCents = callCost * 100; + + if (twillioCall && parseInt(twillioCall.duration) > 60) { + callLog.callCostInUSDCents = Math.ceil( + Math.ceil(parseInt(twillioCall.duration) / 60) * (callCost * 100), + ); } - if (callError) { - throw callError; - } + logger.debug("Call Cost: " + callLog.callCostInUSDCents); + + project.smsOrCallCurrentBalanceInUSDCents = Math.floor( + project.smsOrCallCurrentBalanceInUSDCents! - callCost * 100, + ); + + await ProjectService.updateOneById({ + data: { + smsOrCallCurrentBalanceInUSDCents: + project.smsOrCallCurrentBalanceInUSDCents, + notEnabledSmsOrCallNotificationSentToOwners: false, // reset this flag + }, + id: project.id!, + props: { + isRoot: true, + }, + }); + + logger.debug("Project's current balance updated."); + logger.debug( + "Current Balance: " + project.smsOrCallCurrentBalanceInUSDCents, + ); + } + } catch (e: any) { + callLog.callCostInUSDCents = 0; + callLog.status = CallStatus.Error; + callLog.statusMessage = + e && e.message ? e.message.toString() : e.toString(); + + logger.error("Call Request failed."); + logger.error(callLog.statusMessage); + callError = e; } - public static generateTwimlForCall(callRequest: CallRequest): string { - const response: Twilio.twiml.VoiceResponse = - new Twilio.twiml.VoiceResponse(); + logger.debug("Saving Call Log if project id is provided."); - for (const item of callRequest.data) { - if ((item as Say).sayMessage) { - response.say((item as Say).sayMessage); - } - - if ((item as GatherInput) && (item as GatherInput).numDigits > 0) { - response.say((item as GatherInput).introMessage); - - response.gather({ - numDigits: (item as GatherInput).numDigits, - timeout: (item as GatherInput).timeoutInSeconds || 5, - action: (item as GatherInput).responseUrl - .addQueryParam( - 'token', - JSONWebToken.signJsonPayload( - JSONFunctions.serialize( - (item as GatherInput) - .onInputCallRequest as any - ), - OneUptimeDate.getDayInSeconds() - ) - ) - .toString(), - method: 'POST', - }); - - response.say((item as GatherInput).noInputMessage); - } - } - - response.hangup(); - - return response.toString(); + if (options.projectId) { + logger.debug("Saving Call Log."); + await CallLogService.create({ + data: callLog, + props: { + isRoot: true, + }, + }); + logger.debug("Call Log saved."); + } else { + logger.debug("Project Id is not provided. Call Log not saved."); } + + if (options.userOnCallLogTimelineId) { + await UserOnCallLogTimelineService.updateOneById({ + data: { + status: + callLog.status === CallStatus.Success + ? UserNotificationStatus.Sent + : UserNotificationStatus.Error, + statusMessage: callLog.statusMessage!, + }, + id: options.userOnCallLogTimelineId, + props: { + isRoot: true, + }, + }); + } + + if (callError) { + throw callError; + } + } + + public static generateTwimlForCall(callRequest: CallRequest): string { + const response: Twilio.twiml.VoiceResponse = + new Twilio.twiml.VoiceResponse(); + + for (const item of callRequest.data) { + if ((item as Say).sayMessage) { + response.say((item as Say).sayMessage); + } + + if ((item as GatherInput) && (item as GatherInput).numDigits > 0) { + response.say((item as GatherInput).introMessage); + + response.gather({ + numDigits: (item as GatherInput).numDigits, + timeout: (item as GatherInput).timeoutInSeconds || 5, + action: (item as GatherInput).responseUrl + .addQueryParam( + "token", + JSONWebToken.signJsonPayload( + JSONFunctions.serialize( + (item as GatherInput).onInputCallRequest as any, + ), + OneUptimeDate.getDayInSeconds(), + ), + ) + .toString(), + method: "POST", + }); + + response.say((item as GatherInput).noInputMessage); + } + } + + response.hangup(); + + return response.toString(); + } } diff --git a/App/FeatureSet/Notification/Services/MailService.ts b/App/FeatureSet/Notification/Services/MailService.ts index 552f212cd8..d6f3302b5d 100755 --- a/App/FeatureSet/Notification/Services/MailService.ts +++ b/App/FeatureSet/Notification/Services/MailService.ts @@ -1,529 +1,511 @@ import { - InternalSmtpEmail, - InternalSmtpFromName, - InternalSmtpHost, - InternalSmtpPassword, - InternalSmtpPort, - InternalSmtpSecure, - SendGridConfig, - getEmailServerType, - getGlobalSMTPConfig, - getSendgridConfig, -} from '../Config'; -import SendgridMail, { MailDataRequired } from '@sendgrid/mail'; -import Hostname from 'Common/Types/API/Hostname'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import Email from 'Common/Types/Email'; -import EmailMessage from 'Common/Types/Email/EmailMessage'; -import EmailServer from 'Common/Types/Email/EmailServer'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import MailStatus from 'Common/Types/Mail/MailStatus'; -import ObjectID from 'Common/Types/ObjectID'; -import Port from 'Common/Types/Port'; -import UserNotificationStatus from 'Common/Types/UserNotification/UserNotificationStatus'; -import { IsDevelopment } from 'CommonServer/EnvironmentConfig'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; -import EmailLogService from 'CommonServer/Services/EmailLogService'; -import UserOnCallLogTimelineService from 'CommonServer/Services/UserOnCallLogTimelineService'; -import logger from 'CommonServer/Utils/Logger'; -import EmailLog from 'Model/Models/EmailLog'; -import { EmailServerType } from 'Model/Models/GlobalConfig'; -import fsp from 'fs/promises'; -import Handlebars from 'handlebars'; -import nodemailer, { Transporter } from 'nodemailer'; -import Path from 'path'; + InternalSmtpEmail, + InternalSmtpFromName, + InternalSmtpHost, + InternalSmtpPassword, + InternalSmtpPort, + InternalSmtpSecure, + SendGridConfig, + getEmailServerType, + getGlobalSMTPConfig, + getSendgridConfig, +} from "../Config"; +import SendgridMail, { MailDataRequired } from "@sendgrid/mail"; +import Hostname from "Common/Types/API/Hostname"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import Email from "Common/Types/Email"; +import EmailMessage from "Common/Types/Email/EmailMessage"; +import EmailServer from "Common/Types/Email/EmailServer"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import MailStatus from "Common/Types/Mail/MailStatus"; +import ObjectID from "Common/Types/ObjectID"; +import Port from "Common/Types/Port"; +import UserNotificationStatus from "Common/Types/UserNotification/UserNotificationStatus"; +import { IsDevelopment } from "CommonServer/EnvironmentConfig"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; +import EmailLogService from "CommonServer/Services/EmailLogService"; +import UserOnCallLogTimelineService from "CommonServer/Services/UserOnCallLogTimelineService"; +import logger from "CommonServer/Utils/Logger"; +import EmailLog from "Model/Models/EmailLog"; +import { EmailServerType } from "Model/Models/GlobalConfig"; +import fsp from "fs/promises"; +import Handlebars from "handlebars"; +import nodemailer, { Transporter } from "nodemailer"; +import Path from "path"; export default class MailService { - public static isSMTPConfigValid(obj: JSONObject): boolean { - if (!obj['SMTP_USERNAME']) { - logger.error('SMTP_USERNAME env var not found'); - return false; - } - - if (!obj['SMTP_EMAIL']) { - logger.error('SMTP_EMAIL env var not found'); - return false; - } - - if (!Email.isValid(obj['SMTP_EMAIL'].toString())) { - logger.error( - 'SMTP_EMAIL env var ' + - obj['SMTP_EMAIL'] + - ' is not a valid email' - ); - return false; - } - - if (!obj['SMTP_FROM_NAME']) { - logger.error('SMTP_FROM_NAME env var not found'); - return false; - } - - if (!obj['SMTP_PORT']) { - logger.error('SMTP_PORT env var not found'); - return false; - } - - if (!Port.isValid(obj['SMTP_PORT'].toString())) { - logger.error( - 'SMTP_PORT ' + obj['SMTP_HOST'] + ' env var not valid' - ); - return false; - } - - if (!obj['SMTP_HOST']) { - logger.error('SMTP_HOST env var not found'); - return false; - } - - if (!Hostname.isValid(obj['SMTP_HOST'].toString())) { - logger.error( - 'SMTP_HOST env var ' + obj['SMTP_HOST'] + ' not valid' - ); - return false; - } - - if (!obj['SMTP_PASSWORD']) { - logger.error('SMTP_PASSWORD env var not found'); - return false; - } - - return true; + public static isSMTPConfigValid(obj: JSONObject): boolean { + if (!obj["SMTP_USERNAME"]) { + logger.error("SMTP_USERNAME env var not found"); + return false; } - public static getEmailServer(obj: JSONObject): EmailServer { - if (!this.isSMTPConfigValid(obj)) { - throw new BadDataException('SMTP Config is not valid'); + if (!obj["SMTP_EMAIL"]) { + logger.error("SMTP_EMAIL env var not found"); + return false; + } + + if (!Email.isValid(obj["SMTP_EMAIL"].toString())) { + logger.error( + "SMTP_EMAIL env var " + obj["SMTP_EMAIL"] + " is not a valid email", + ); + return false; + } + + if (!obj["SMTP_FROM_NAME"]) { + logger.error("SMTP_FROM_NAME env var not found"); + return false; + } + + if (!obj["SMTP_PORT"]) { + logger.error("SMTP_PORT env var not found"); + return false; + } + + if (!Port.isValid(obj["SMTP_PORT"].toString())) { + logger.error("SMTP_PORT " + obj["SMTP_HOST"] + " env var not valid"); + return false; + } + + if (!obj["SMTP_HOST"]) { + logger.error("SMTP_HOST env var not found"); + return false; + } + + if (!Hostname.isValid(obj["SMTP_HOST"].toString())) { + logger.error("SMTP_HOST env var " + obj["SMTP_HOST"] + " not valid"); + return false; + } + + if (!obj["SMTP_PASSWORD"]) { + logger.error("SMTP_PASSWORD env var not found"); + return false; + } + + return true; + } + + public static getEmailServer(obj: JSONObject): EmailServer { + if (!this.isSMTPConfigValid(obj)) { + throw new BadDataException("SMTP Config is not valid"); + } + + return { + id: + obj && obj["SMTP_ID"] + ? new ObjectID(obj["SMTP_ID"].toString()) + : undefined, + username: obj["SMTP_USERNAME"]?.toString() || undefined, + password: obj["SMTP_PASSWORD"]?.toString() || undefined, + host: new Hostname(obj["SMTP_HOST"]?.toString() as string), + port: new Port(obj["SMTP_PORT"]?.toString() as string), + fromEmail: new Email(obj["SMTP_EMAIL"]?.toString() as string), + fromName: obj["SMTP_FROM_NAME"]?.toString() as string, + secure: + obj["SMTP_IS_SECURE"] === "true" || obj["SMTP_IS_SECURE"] === true, + }; + } + + public static getInternalEmailServer(): EmailServer { + return { + id: undefined, + username: InternalSmtpEmail.toString(), + password: InternalSmtpPassword, + host: InternalSmtpHost, + port: InternalSmtpPort, + fromEmail: InternalSmtpEmail, + fromName: InternalSmtpFromName, + secure: InternalSmtpSecure, + }; + } + + public static async getGlobalFromEmail(): Promise { + const emailServer: EmailServer | null = await this.getGlobalSmtpSettings(); + + if (!emailServer) { + throw new BadDataException("Global SMTP Config not found"); + } + + return emailServer.fromEmail; + } + + private static async getGlobalSmtpSettings(): Promise { + return await getGlobalSMTPConfig(); + } + + private static async updateUserNotificationLogTimelineAsSent( + timelineId: ObjectID, + ): Promise { + if (timelineId) { + await UserOnCallLogTimelineService.updateOneById({ + data: { + status: UserNotificationStatus.Sent, + statusMessage: + "Email sent successfully. This does not mean the email was delivered. We do not track email delivery. If the email was not delivered - it is likely due to the email address being invalid, user has blocked sending domain, or it could have landed in spam.", + }, + id: timelineId, + props: { + isRoot: true, + }, + }); + } + } + + private static async compileEmailBody( + emailTemplateType: EmailTemplateType, + vars: Dictionary, + ): Promise { + // Localcache templates, so we don't read from disk all the time. + + let templateData: string; + if ( + LocalCache.hasValue("email-templates", emailTemplateType) && + !IsDevelopment + ) { + templateData = LocalCache.getString("email-templates", emailTemplateType); + } else { + templateData = await fsp.readFile( + Path.resolve( + process.cwd(), + "FeatureSet", + "Notification", + "Templates", + `${emailTemplateType}`, + ), + { encoding: "utf8", flag: "r" }, + ); + LocalCache.setString( + "email-templates", + emailTemplateType, + templateData as string, + ); + } + + const emailBody: Handlebars.TemplateDelegate = + Handlebars.compile(templateData); + return emailBody(vars).toString(); + } + + private static compileText( + subject: string, + vars: Dictionary, + ): string { + const subjectHandlebars: Handlebars.TemplateDelegate = + Handlebars.compile(subject); + return subjectHandlebars(vars).toString(); + } + + private static createMailer( + emailServer: EmailServer, + options: { + timeout?: number | undefined; + }, + ): Transporter { + const privateMailer: Transporter = nodemailer.createTransport({ + host: emailServer.host.toString(), + port: emailServer.port.toNumber(), + secure: emailServer.secure, + auth: + emailServer.username && emailServer.password + ? { + user: emailServer.username, + pass: emailServer.password, + } + : undefined, + connectionTimeout: options.timeout || undefined, + }); + + return privateMailer; + } + + private static async transportMail( + mail: EmailMessage, + options: { + emailServer: EmailServer; + projectId?: ObjectID | undefined; + timeout?: number | undefined; + }, + ): Promise { + const mailer: Transporter = this.createMailer(options.emailServer, { + timeout: options.timeout, + }); + await mailer.sendMail({ + from: `${options.emailServer.fromName.toString()} <${options.emailServer.fromEmail.toString()}>`, + to: mail.toEmail.toString(), + subject: mail.subject, + html: mail.body, + }); + } + + public static async send( + mail: EmailMessage, + options?: + | { + projectId?: ObjectID | undefined; + emailServer?: EmailServer | undefined; + userOnCallLogTimelineId?: ObjectID | undefined; + timeout?: number | undefined; } + | undefined, + ): Promise { + let emailLog: EmailLog | undefined = undefined; - return { - id: - obj && obj['SMTP_ID'] - ? new ObjectID(obj['SMTP_ID'].toString()) - : undefined, - username: obj['SMTP_USERNAME']?.toString() || undefined, - password: obj['SMTP_PASSWORD']?.toString() || undefined, - host: new Hostname(obj['SMTP_HOST']?.toString() as string), - port: new Port(obj['SMTP_PORT']?.toString() as string), - fromEmail: new Email(obj['SMTP_EMAIL']?.toString() as string), - fromName: obj['SMTP_FROM_NAME']?.toString() as string, - secure: - obj['SMTP_IS_SECURE'] === 'true' || - obj['SMTP_IS_SECURE'] === true, - }; + if (options && options.projectId) { + emailLog = new EmailLog(); + emailLog.projectId = options.projectId; + emailLog.toEmail = mail.toEmail; + emailLog.subject = mail.subject; + + if (options.emailServer?.id) { + emailLog.projectSmtpConfigId = options.emailServer?.id; + } } - public static getInternalEmailServer(): EmailServer { - return { - id: undefined, - username: InternalSmtpEmail.toString(), - password: InternalSmtpPassword, - host: InternalSmtpHost, - port: InternalSmtpPort, - fromEmail: InternalSmtpEmail, - fromName: InternalSmtpFromName, - secure: InternalSmtpSecure, - }; + // default vars. + if (!mail.vars) { + mail.vars = {}; } - public static async getGlobalFromEmail(): Promise { - const emailServer: EmailServer | null = - await this.getGlobalSmtpSettings(); - - if (!emailServer) { - throw new BadDataException('Global SMTP Config not found'); - } - - return emailServer.fromEmail; + if (!mail.vars["year"]) { + mail.vars["year"] = OneUptimeDate.getCurrentYear().toString(); } - private static async getGlobalSmtpSettings(): Promise { - return await getGlobalSMTPConfig(); - } + mail.body = mail.templateType + ? await this.compileEmailBody(mail.templateType, mail.vars) + : this.compileText(mail.body || "", mail.vars); + mail.subject = this.compileText(mail.subject, mail.vars); - private static async updateUserNotificationLogTimelineAsSent( - timelineId: ObjectID - ): Promise { - if (timelineId) { - await UserOnCallLogTimelineService.updateOneById({ - data: { - status: UserNotificationStatus.Sent, - statusMessage: - 'Email sent successfully. This does not mean the email was delivered. We do not track email delivery. If the email was not delivered - it is likely due to the email address being invalid, user has blocked sending domain, or it could have landed in spam.', - }, - id: timelineId, - props: { - isRoot: true, - }, + const emailServerType: EmailServerType = await getEmailServerType(); + + try { + if ( + (!options || !options.emailServer) && + emailServerType === EmailServerType.Sendgrid + ) { + const sendgridConfig: SendGridConfig | null = await getSendgridConfig(); + + if (!sendgridConfig) { + if (emailLog) { + emailLog.status = MailStatus.Error; + emailLog.statusMessage = + "Email is configured to use Sendgrid, but Sendgrid Settings is not configured."; + + await EmailLogService.create({ + data: emailLog, + props: { + isRoot: true, + }, }); - } - } + } - private static async compileEmailBody( - emailTemplateType: EmailTemplateType, - vars: Dictionary - ): Promise { - // Localcache templates, so we don't read from disk all the time. - - let templateData: string; - if ( - LocalCache.hasValue('email-templates', emailTemplateType) && - !IsDevelopment - ) { - templateData = LocalCache.getString( - 'email-templates', - emailTemplateType - ); - } else { - templateData = await fsp.readFile( - Path.resolve( - process.cwd(), - 'FeatureSet', - 'Notification', - 'Templates', - `${emailTemplateType}` - ), - { encoding: 'utf8', flag: 'r' } - ); - LocalCache.setString( - 'email-templates', - emailTemplateType, - templateData as string - ); + throw new BadDataException("Sendgrid Config not found"); } - const emailBody: Handlebars.TemplateDelegate = - Handlebars.compile(templateData); - return emailBody(vars).toString(); - } + if (!sendgridConfig.apiKey) { + if (emailLog) { + emailLog.status = MailStatus.Error; + emailLog.statusMessage = + "Email is configured to use Sendgrid, but Sendgrid API key is not configured."; - private static compileText( - subject: string, - vars: Dictionary - ): string { - const subjectHandlebars: Handlebars.TemplateDelegate = - Handlebars.compile(subject); - return subjectHandlebars(vars).toString(); - } - - private static createMailer( - emailServer: EmailServer, - options: { - timeout?: number | undefined; - } - ): Transporter { - const privateMailer: Transporter = nodemailer.createTransport({ - host: emailServer.host.toString(), - port: emailServer.port.toNumber(), - secure: emailServer.secure, - auth: - emailServer.username && emailServer.password - ? { - user: emailServer.username, - pass: emailServer.password, - } - : undefined, - connectionTimeout: options.timeout || undefined, - }); - - return privateMailer; - } - - private static async transportMail( - mail: EmailMessage, - options: { - emailServer: EmailServer; - projectId?: ObjectID | undefined; - timeout?: number | undefined; - } - ): Promise { - const mailer: Transporter = this.createMailer(options.emailServer, { - timeout: options.timeout, - }); - await mailer.sendMail({ - from: `${options.emailServer.fromName.toString()} <${options.emailServer.fromEmail.toString()}>`, - to: mail.toEmail.toString(), - subject: mail.subject, - html: mail.body, - }); - } - - public static async send( - mail: EmailMessage, - options?: - | { - projectId?: ObjectID | undefined; - emailServer?: EmailServer | undefined; - userOnCallLogTimelineId?: ObjectID | undefined; - timeout?: number | undefined; - } - | undefined - ): Promise { - let emailLog: EmailLog | undefined = undefined; - - if (options && options.projectId) { - emailLog = new EmailLog(); - emailLog.projectId = options.projectId; - emailLog.toEmail = mail.toEmail; - emailLog.subject = mail.subject; - - if (options.emailServer?.id) { - emailLog.projectSmtpConfigId = options.emailServer?.id; - } - } - - // default vars. - if (!mail.vars) { - mail.vars = {}; - } - - if (!mail.vars['year']) { - mail.vars['year'] = OneUptimeDate.getCurrentYear().toString(); - } - - mail.body = mail.templateType - ? await this.compileEmailBody(mail.templateType, mail.vars) - : this.compileText(mail.body || '', mail.vars); - mail.subject = this.compileText(mail.subject, mail.vars); - - const emailServerType: EmailServerType = await getEmailServerType(); - - try { - if ( - (!options || !options.emailServer) && - emailServerType === EmailServerType.Sendgrid - ) { - const sendgridConfig: SendGridConfig | null = - await getSendgridConfig(); - - if (!sendgridConfig) { - if (emailLog) { - emailLog.status = MailStatus.Error; - emailLog.statusMessage = - 'Email is configured to use Sendgrid, but Sendgrid Settings is not configured.'; - - await EmailLogService.create({ - data: emailLog, - props: { - isRoot: true, - }, - }); - } - - throw new BadDataException('Sendgrid Config not found'); - } - - if (!sendgridConfig.apiKey) { - if (emailLog) { - emailLog.status = MailStatus.Error; - emailLog.statusMessage = - 'Email is configured to use Sendgrid, but Sendgrid API key is not configured.'; - - await EmailLogService.create({ - data: emailLog, - props: { - isRoot: true, - }, - }); - } - - throw new BadDataException( - 'Sendgrid API key not configured' - ); - } - - if (!sendgridConfig.fromEmail) { - if (emailLog) { - emailLog.status = MailStatus.Error; - emailLog.statusMessage = - 'Email is configured to use Sendgrid, but Sendgrid From Email is not configured.'; - - await EmailLogService.create({ - data: emailLog, - props: { - isRoot: true, - }, - }); - } - - throw new BadDataException( - 'Sendgrid From Email not configured' - ); - } - - if (!sendgridConfig.fromName) { - if (emailLog) { - emailLog.status = MailStatus.Error; - emailLog.statusMessage = - 'Email is configured to use Sendgrid, but Sendgrid From Name is not configured.'; - - await EmailLogService.create({ - data: emailLog, - props: { - isRoot: true, - }, - }); - } - - throw new BadDataException( - 'Sendgrid From Name not configured' - ); - } - - SendgridMail.setApiKey(sendgridConfig.apiKey); - - const msg: MailDataRequired = { - to: mail.toEmail.toString(), - from: `${ - sendgridConfig.fromName || 'OneUptime' - } <${sendgridConfig.fromEmail.toString()}>`, - subject: mail.subject, - html: mail.body, - }; - - if (emailLog) { - emailLog.fromEmail = sendgridConfig.fromEmail; - } - - await SendgridMail.send(msg); - - if (emailLog) { - emailLog.status = MailStatus.Success; - emailLog.statusMessage = - 'Email sent successfully. This does not mean the email was delivered. We do not track email delivery. If the email was not delivered - it is likely due to the email address being invalid, user has blocked sending domain, or it could have landed in spam.'; - - await EmailLogService.create({ - data: emailLog, - props: { - isRoot: true, - }, - }); - } - - if (options?.userOnCallLogTimelineId) { - await this.updateUserNotificationLogTimelineAsSent( - options?.userOnCallLogTimelineId - ); - } - return; - } - - if ( - (!options || !options.emailServer) && - emailServerType === EmailServerType.CustomSMTP - ) { - if (!options) { - options = {}; - } - - const globalEmailServer: EmailServer | null = - await this.getGlobalSmtpSettings(); - - if (!globalEmailServer) { - if (emailLog) { - emailLog.status = MailStatus.Error; - emailLog.statusMessage = - 'Email is configured to use SMTP, but SMTP settings are not configured.'; - - await EmailLogService.create({ - data: emailLog, - props: { - isRoot: true, - }, - }); - } - - throw new BadDataException('Global SMTP Config not found'); - } - - options.emailServer = globalEmailServer; - } - - if ( - emailServerType === EmailServerType.Internal && - (!options || !options.emailServer) - ) { - if (!options) { - options = {}; - } - - options.emailServer = this.getInternalEmailServer(); - } - - if (options && options.emailServer && emailLog) { - emailLog.fromEmail = options.emailServer.fromEmail; - } - - if (!options || !options.emailServer) { - throw new BadDataException('Email server not found'); - } - - await this.transportMail(mail, { - emailServer: options.emailServer, - projectId: options.projectId, - timeout: options.timeout, + await EmailLogService.create({ + data: emailLog, + props: { + isRoot: true, + }, }); + } - if (emailLog) { - emailLog.status = MailStatus.Success; - emailLog.statusMessage = - 'Email sent successfully. This does not mean the email was delivered. We do not track email delivery. If the email was not delivered - it is likely due to the email address being invalid, user has blocked sending domain, or it could have landed in spam.'; - - await EmailLogService.create({ - data: emailLog, - props: { - isRoot: true, - }, - }); - } - - if (options?.userOnCallLogTimelineId) { - await this.updateUserNotificationLogTimelineAsSent( - options?.userOnCallLogTimelineId - ); - } - } catch (err: any) { - let message: string | undefined = err.message; - - if (message === 'Unexpected socket close') { - message = - 'Email failed to send. Unexpected socket close. This could mean various things, such as your SMTP server is unreachble, username and password is incorrect, your SMTP server is not configured to accept connections from this IP address, or TLS/SSL is not configured correctly, or ports are not configured correctly.'; - } - - if (!message) { - message = 'Email failed to send. Unknown error.'; - } - - logger.error(err); - if (options?.userOnCallLogTimelineId) { - await UserOnCallLogTimelineService.updateOneById({ - data: { - status: UserNotificationStatus.Error, - statusMessage: message, - }, - id: options.userOnCallLogTimelineId, - props: { - isRoot: true, - }, - }); - } - - if (emailLog) { - emailLog.status = MailStatus.Error; - emailLog.statusMessage = message; - - await EmailLogService.create({ - data: emailLog, - props: { - isRoot: true, - }, - }); - } - - throw err; + throw new BadDataException("Sendgrid API key not configured"); } + + if (!sendgridConfig.fromEmail) { + if (emailLog) { + emailLog.status = MailStatus.Error; + emailLog.statusMessage = + "Email is configured to use Sendgrid, but Sendgrid From Email is not configured."; + + await EmailLogService.create({ + data: emailLog, + props: { + isRoot: true, + }, + }); + } + + throw new BadDataException("Sendgrid From Email not configured"); + } + + if (!sendgridConfig.fromName) { + if (emailLog) { + emailLog.status = MailStatus.Error; + emailLog.statusMessage = + "Email is configured to use Sendgrid, but Sendgrid From Name is not configured."; + + await EmailLogService.create({ + data: emailLog, + props: { + isRoot: true, + }, + }); + } + + throw new BadDataException("Sendgrid From Name not configured"); + } + + SendgridMail.setApiKey(sendgridConfig.apiKey); + + const msg: MailDataRequired = { + to: mail.toEmail.toString(), + from: `${ + sendgridConfig.fromName || "OneUptime" + } <${sendgridConfig.fromEmail.toString()}>`, + subject: mail.subject, + html: mail.body, + }; + + if (emailLog) { + emailLog.fromEmail = sendgridConfig.fromEmail; + } + + await SendgridMail.send(msg); + + if (emailLog) { + emailLog.status = MailStatus.Success; + emailLog.statusMessage = + "Email sent successfully. This does not mean the email was delivered. We do not track email delivery. If the email was not delivered - it is likely due to the email address being invalid, user has blocked sending domain, or it could have landed in spam."; + + await EmailLogService.create({ + data: emailLog, + props: { + isRoot: true, + }, + }); + } + + if (options?.userOnCallLogTimelineId) { + await this.updateUserNotificationLogTimelineAsSent( + options?.userOnCallLogTimelineId, + ); + } + return; + } + + if ( + (!options || !options.emailServer) && + emailServerType === EmailServerType.CustomSMTP + ) { + if (!options) { + options = {}; + } + + const globalEmailServer: EmailServer | null = + await this.getGlobalSmtpSettings(); + + if (!globalEmailServer) { + if (emailLog) { + emailLog.status = MailStatus.Error; + emailLog.statusMessage = + "Email is configured to use SMTP, but SMTP settings are not configured."; + + await EmailLogService.create({ + data: emailLog, + props: { + isRoot: true, + }, + }); + } + + throw new BadDataException("Global SMTP Config not found"); + } + + options.emailServer = globalEmailServer; + } + + if ( + emailServerType === EmailServerType.Internal && + (!options || !options.emailServer) + ) { + if (!options) { + options = {}; + } + + options.emailServer = this.getInternalEmailServer(); + } + + if (options && options.emailServer && emailLog) { + emailLog.fromEmail = options.emailServer.fromEmail; + } + + if (!options || !options.emailServer) { + throw new BadDataException("Email server not found"); + } + + await this.transportMail(mail, { + emailServer: options.emailServer, + projectId: options.projectId, + timeout: options.timeout, + }); + + if (emailLog) { + emailLog.status = MailStatus.Success; + emailLog.statusMessage = + "Email sent successfully. This does not mean the email was delivered. We do not track email delivery. If the email was not delivered - it is likely due to the email address being invalid, user has blocked sending domain, or it could have landed in spam."; + + await EmailLogService.create({ + data: emailLog, + props: { + isRoot: true, + }, + }); + } + + if (options?.userOnCallLogTimelineId) { + await this.updateUserNotificationLogTimelineAsSent( + options?.userOnCallLogTimelineId, + ); + } + } catch (err: any) { + let message: string | undefined = err.message; + + if (message === "Unexpected socket close") { + message = + "Email failed to send. Unexpected socket close. This could mean various things, such as your SMTP server is unreachble, username and password is incorrect, your SMTP server is not configured to accept connections from this IP address, or TLS/SSL is not configured correctly, or ports are not configured correctly."; + } + + if (!message) { + message = "Email failed to send. Unknown error."; + } + + logger.error(err); + if (options?.userOnCallLogTimelineId) { + await UserOnCallLogTimelineService.updateOneById({ + data: { + status: UserNotificationStatus.Error, + statusMessage: message, + }, + id: options.userOnCallLogTimelineId, + props: { + isRoot: true, + }, + }); + } + + if (emailLog) { + emailLog.status = MailStatus.Error; + emailLog.statusMessage = message; + + await EmailLogService.create({ + data: emailLog, + props: { + isRoot: true, + }, + }); + } + + throw err; } + } } diff --git a/App/FeatureSet/Notification/Services/SmsService.ts b/App/FeatureSet/Notification/Services/SmsService.ts index 8207500414..1da3f68df1 100644 --- a/App/FeatureSet/Notification/Services/SmsService.ts +++ b/App/FeatureSet/Notification/Services/SmsService.ts @@ -1,319 +1,302 @@ import { - SMSDefaultCostInCents, - SMSHighRiskCostInCents, - getTwilioConfig, -} from '../Config'; -import { isHighRiskPhoneNumber } from 'Common/Types/Call/CallRequest'; -import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Phone from 'Common/Types/Phone'; -import SmsStatus from 'Common/Types/SmsStatus'; -import Text from 'Common/Types/Text'; -import UserNotificationStatus from 'Common/Types/UserNotification/UserNotificationStatus'; -import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig'; -import NotificationService from 'CommonServer/Services/NotificationService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import SmsLogService from 'CommonServer/Services/SmsLogService'; -import UserOnCallLogTimelineService from 'CommonServer/Services/UserOnCallLogTimelineService'; -import logger from 'CommonServer/Utils/Logger'; -import Project from 'Model/Models/Project'; -import SmsLog from 'Model/Models/SmsLog'; -import Twilio from 'twilio'; -import { MessageInstance } from 'twilio/lib/rest/api/v2010/account/message'; + SMSDefaultCostInCents, + SMSHighRiskCostInCents, + getTwilioConfig, +} from "../Config"; +import { isHighRiskPhoneNumber } from "Common/Types/Call/CallRequest"; +import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Phone from "Common/Types/Phone"; +import SmsStatus from "Common/Types/SmsStatus"; +import Text from "Common/Types/Text"; +import UserNotificationStatus from "Common/Types/UserNotification/UserNotificationStatus"; +import { IsBillingEnabled } from "CommonServer/EnvironmentConfig"; +import NotificationService from "CommonServer/Services/NotificationService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import SmsLogService from "CommonServer/Services/SmsLogService"; +import UserOnCallLogTimelineService from "CommonServer/Services/UserOnCallLogTimelineService"; +import logger from "CommonServer/Utils/Logger"; +import Project from "Model/Models/Project"; +import SmsLog from "Model/Models/SmsLog"; +import Twilio from "twilio"; +import { MessageInstance } from "twilio/lib/rest/api/v2010/account/message"; export default class SmsService { - public static async sendSms( - to: Phone, - message: string, - options: { - projectId?: ObjectID | undefined; // project id for sms log - customTwilioConfig?: TwilioConfig | undefined; - isSensitive?: boolean; // if true, message will not be logged - userOnCallLogTimelineId?: ObjectID | undefined; + public static async sendSms( + to: Phone, + message: string, + options: { + projectId?: ObjectID | undefined; // project id for sms log + customTwilioConfig?: TwilioConfig | undefined; + isSensitive?: boolean; // if true, message will not be logged + userOnCallLogTimelineId?: ObjectID | undefined; + }, + ): Promise { + let smsError: Error | null = null; + const smsLog: SmsLog = new SmsLog(); + + try { + // check number of sms to send for this entire messages to send. Each sms can have 160 characters. + const smsSegments: number = Math.ceil(message.length / 160); + + message = Text.trimLines(message); + + let smsCost: number = 0; + + const shouldChargeForSMS: boolean = + IsBillingEnabled && !options.customTwilioConfig; + + if (shouldChargeForSMS) { + smsCost = SMSDefaultCostInCents / 100; + + if (isHighRiskPhoneNumber(to)) { + smsCost = SMSHighRiskCostInCents / 100; } - ): Promise { - let smsError: Error | null = null; - const smsLog: SmsLog = new SmsLog(); + } - try { - // check number of sms to send for this entire messages to send. Each sms can have 160 characters. - const smsSegments: number = Math.ceil(message.length / 160); + if (smsSegments > 1) { + smsCost = smsCost * smsSegments; + } - message = Text.trimLines(message); + smsLog.toNumber = to; - let smsCost: number = 0; + smsLog.smsText = + options && options.isSensitive + ? "This message is sensitive and is not logged" + : message; + smsLog.smsCostInUSDCents = 0; - const shouldChargeForSMS: boolean = - IsBillingEnabled && !options.customTwilioConfig; + if (options.projectId) { + smsLog.projectId = options.projectId; + } - if (shouldChargeForSMS) { - smsCost = SMSDefaultCostInCents / 100; + const twilioConfig: TwilioConfig | null = + options.customTwilioConfig || (await getTwilioConfig()); - if (isHighRiskPhoneNumber(to)) { - smsCost = SMSHighRiskCostInCents / 100; - } - } + if (!twilioConfig) { + throw new BadDataException("Twilio Config not found"); + } - if (smsSegments > 1) { - smsCost = smsCost * smsSegments; - } + const client: Twilio.Twilio = Twilio( + twilioConfig.accountSid, + twilioConfig.authToken, + ); - smsLog.toNumber = to; + smsLog.fromNumber = twilioConfig.phoneNumber; - smsLog.smsText = - options && options.isSensitive - ? 'This message is sensitive and is not logged' - : message; - smsLog.smsCostInUSDCents = 0; + let project: Project | null = null; - if (options.projectId) { - smsLog.projectId = options.projectId; - } + // make sure project has enough balance. - const twilioConfig: TwilioConfig | null = - options.customTwilioConfig || (await getTwilioConfig()); + if (options.projectId) { + project = await ProjectService.findOneById({ + id: options.projectId, + select: { + smsOrCallCurrentBalanceInUSDCents: true, + enableSmsNotifications: true, + lowCallAndSMSBalanceNotificationSentToOwners: true, + name: true, + notEnabledSmsOrCallNotificationSentToOwners: true, + }, + props: { + isRoot: true, + }, + }); - if (!twilioConfig) { - throw new BadDataException('Twilio Config not found'); - } + if (!project) { + smsLog.status = SmsStatus.Error; + smsLog.statusMessage = `Project ${options.projectId.toString()} not found.`; + logger.error(smsLog.statusMessage); + await SmsLogService.create({ + data: smsLog, + props: { + isRoot: true, + }, + }); + return; + } - const client: Twilio.Twilio = Twilio( - twilioConfig.accountSid, - twilioConfig.authToken + if (!project.enableSmsNotifications) { + smsLog.status = SmsStatus.Error; + smsLog.statusMessage = `SMS notifications are not enabled for this project. Please enable SMS notifications in Project Settings.`; + logger.error(smsLog.statusMessage); + await SmsLogService.create({ + data: smsLog, + props: { + isRoot: true, + }, + }); + if (!project.notEnabledSmsOrCallNotificationSentToOwners) { + await ProjectService.updateOneById({ + data: { + notEnabledSmsOrCallNotificationSentToOwners: true, + }, + id: project.id!, + props: { + isRoot: true, + }, + }); + await ProjectService.sendEmailToProjectOwners( + project.id!, + "SMS notifications not enabled for " + (project.name || ""), + `We tried to send an SMS to ${to.toString()} with message:

${message}

This SMS was not sent because SMS notifications are not enabled for this project. Please enable SMS notifications in Project Settings.`, ); + } + return; + } - smsLog.fromNumber = twilioConfig.phoneNumber; + if (shouldChargeForSMS) { + // check if auto recharge is enabled and current balance is low. + let updatedBalance: number = + project.smsOrCallCurrentBalanceInUSDCents!; + try { + updatedBalance = await NotificationService.rechargeIfBalanceIsLow( + project.id!, + ); + } catch (err) { + logger.error(err); + } - let project: Project | null = null; + project.smsOrCallCurrentBalanceInUSDCents = updatedBalance; - // make sure project has enough balance. - - if (options.projectId) { - project = await ProjectService.findOneById({ - id: options.projectId, - select: { - smsOrCallCurrentBalanceInUSDCents: true, - enableSmsNotifications: true, - lowCallAndSMSBalanceNotificationSentToOwners: true, - name: true, - notEnabledSmsOrCallNotificationSentToOwners: true, - }, - props: { - isRoot: true, - }, - }); - - if (!project) { - smsLog.status = SmsStatus.Error; - smsLog.statusMessage = `Project ${options.projectId.toString()} not found.`; - logger.error(smsLog.statusMessage); - await SmsLogService.create({ - data: smsLog, - props: { - isRoot: true, - }, - }); - return; - } - - if (!project.enableSmsNotifications) { - smsLog.status = SmsStatus.Error; - smsLog.statusMessage = `SMS notifications are not enabled for this project. Please enable SMS notifications in Project Settings.`; - logger.error(smsLog.statusMessage); - await SmsLogService.create({ - data: smsLog, - props: { - isRoot: true, - }, - }); - if (!project.notEnabledSmsOrCallNotificationSentToOwners) { - await ProjectService.updateOneById({ - data: { - notEnabledSmsOrCallNotificationSentToOwners: - true, - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - await ProjectService.sendEmailToProjectOwners( - project.id!, - 'SMS notifications not enabled for ' + - (project.name || ''), - `We tried to send an SMS to ${to.toString()} with message:

${message}

This SMS was not sent because SMS notifications are not enabled for this project. Please enable SMS notifications in Project Settings.` - ); - } - return; - } - - if (shouldChargeForSMS) { - // check if auto recharge is enabled and current balance is low. - let updatedBalance: number = - project.smsOrCallCurrentBalanceInUSDCents!; - try { - updatedBalance = - await NotificationService.rechargeIfBalanceIsLow( - project.id! - ); - } catch (err) { - logger.error(err); - } - - project.smsOrCallCurrentBalanceInUSDCents = updatedBalance; - - if (!project.smsOrCallCurrentBalanceInUSDCents) { - smsLog.status = SmsStatus.LowBalance; - smsLog.statusMessage = `Project ${options.projectId.toString()} does not have enough SMS balance.`; - logger.error(smsLog.statusMessage); - await SmsLogService.create({ - data: smsLog, - props: { - isRoot: true, - }, - }); - - if ( - !project.lowCallAndSMSBalanceNotificationSentToOwners - ) { - await ProjectService.updateOneById({ - data: { - lowCallAndSMSBalanceNotificationSentToOwners: - true, - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - await ProjectService.sendEmailToProjectOwners( - project.id!, - 'Low SMS and Call Balance for ' + - (project.name || ''), - `We tried to send an SMS to ${to.toString()} with message:

${message}
This SMS was not sent because project does not have enough balance to send SMS. Current balance is ${ - (project.smsOrCallCurrentBalanceInUSDCents || - 0) / 100 - } USD cents. Required balance to send this SMS should is ${smsCost} USD. Please enable auto recharge or recharge manually.` - ); - } - return; - } - - if ( - project.smsOrCallCurrentBalanceInUSDCents < - smsCost * 100 - ) { - smsLog.status = SmsStatus.LowBalance; - smsLog.statusMessage = `Project does not have enough balance to send SMS. Current balance is ${ - project.smsOrCallCurrentBalanceInUSDCents / 100 - } USD. Required balance is ${smsCost} USD to send this SMS.`; - logger.error(smsLog.statusMessage); - await SmsLogService.create({ - data: smsLog, - props: { - isRoot: true, - }, - }); - if ( - !project.lowCallAndSMSBalanceNotificationSentToOwners - ) { - await ProjectService.updateOneById({ - data: { - lowCallAndSMSBalanceNotificationSentToOwners: - true, - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - await ProjectService.sendEmailToProjectOwners( - project.id!, - 'Low SMS and Call Balance for ' + - (project.name || ''), - `We tried to send an SMS to ${to.toString()} with message:

${message}

This SMS was not sent because project does not have enough balance to send SMS. Current balance is ${ - project.smsOrCallCurrentBalanceInUSDCents / - 100 - } USD. Required balance is ${smsCost} USD to send this SMS. Please enable auto recharge or recharge manually.` - ); - } - return; - } - } - } - - const twillioMessage: MessageInstance = - await client.messages.create({ - body: message, - to: to.toString(), - from: twilioConfig.phoneNumber.toString(), // From a valid Twilio number - }); - - smsLog.status = SmsStatus.Success; - smsLog.statusMessage = 'Message ID: ' + twillioMessage.sid; - - logger.debug('SMS message sent successfully.'); - logger.debug(smsLog.statusMessage); - - if (shouldChargeForSMS && project) { - smsLog.smsCostInUSDCents = smsCost * 100; - - project.smsOrCallCurrentBalanceInUSDCents = Math.floor( - project.smsOrCallCurrentBalanceInUSDCents! - smsCost * 100 - ); - - await ProjectService.updateOneById({ - data: { - smsOrCallCurrentBalanceInUSDCents: - project.smsOrCallCurrentBalanceInUSDCents, - notEnabledSmsOrCallNotificationSentToOwners: false, // reset this flag - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - } - } catch (e: any) { - smsLog.smsCostInUSDCents = 0; - smsLog.status = SmsStatus.Error; - smsLog.statusMessage = - e && e.message ? e.message.toString() : e.toString(); - - logger.error('SMS message failed to send.'); + if (!project.smsOrCallCurrentBalanceInUSDCents) { + smsLog.status = SmsStatus.LowBalance; + smsLog.statusMessage = `Project ${options.projectId.toString()} does not have enough SMS balance.`; logger.error(smsLog.statusMessage); - - smsError = e; - } - - if (options.projectId) { await SmsLogService.create({ - data: smsLog, - props: { - isRoot: true, - }, + data: smsLog, + props: { + isRoot: true, + }, }); - } - if (options.userOnCallLogTimelineId) { - await UserOnCallLogTimelineService.updateOneById({ + if (!project.lowCallAndSMSBalanceNotificationSentToOwners) { + await ProjectService.updateOneById({ data: { - status: - smsLog.status === SmsStatus.Success - ? UserNotificationStatus.Sent - : UserNotificationStatus.Error, - statusMessage: smsLog.statusMessage!, + lowCallAndSMSBalanceNotificationSentToOwners: true, }, - id: options.userOnCallLogTimelineId, + id: project.id!, props: { - isRoot: true, + isRoot: true, }, - }); - } + }); + await ProjectService.sendEmailToProjectOwners( + project.id!, + "Low SMS and Call Balance for " + (project.name || ""), + `We tried to send an SMS to ${to.toString()} with message:

${message}
This SMS was not sent because project does not have enough balance to send SMS. Current balance is ${ + (project.smsOrCallCurrentBalanceInUSDCents || 0) / 100 + } USD cents. Required balance to send this SMS should is ${smsCost} USD. Please enable auto recharge or recharge manually.`, + ); + } + return; + } - if (smsError) { - throw smsError; + if (project.smsOrCallCurrentBalanceInUSDCents < smsCost * 100) { + smsLog.status = SmsStatus.LowBalance; + smsLog.statusMessage = `Project does not have enough balance to send SMS. Current balance is ${ + project.smsOrCallCurrentBalanceInUSDCents / 100 + } USD. Required balance is ${smsCost} USD to send this SMS.`; + logger.error(smsLog.statusMessage); + await SmsLogService.create({ + data: smsLog, + props: { + isRoot: true, + }, + }); + if (!project.lowCallAndSMSBalanceNotificationSentToOwners) { + await ProjectService.updateOneById({ + data: { + lowCallAndSMSBalanceNotificationSentToOwners: true, + }, + id: project.id!, + props: { + isRoot: true, + }, + }); + await ProjectService.sendEmailToProjectOwners( + project.id!, + "Low SMS and Call Balance for " + (project.name || ""), + `We tried to send an SMS to ${to.toString()} with message:

${message}

This SMS was not sent because project does not have enough balance to send SMS. Current balance is ${ + project.smsOrCallCurrentBalanceInUSDCents / 100 + } USD. Required balance is ${smsCost} USD to send this SMS. Please enable auto recharge or recharge manually.`, + ); + } + return; + } } + } + + const twillioMessage: MessageInstance = await client.messages.create({ + body: message, + to: to.toString(), + from: twilioConfig.phoneNumber.toString(), // From a valid Twilio number + }); + + smsLog.status = SmsStatus.Success; + smsLog.statusMessage = "Message ID: " + twillioMessage.sid; + + logger.debug("SMS message sent successfully."); + logger.debug(smsLog.statusMessage); + + if (shouldChargeForSMS && project) { + smsLog.smsCostInUSDCents = smsCost * 100; + + project.smsOrCallCurrentBalanceInUSDCents = Math.floor( + project.smsOrCallCurrentBalanceInUSDCents! - smsCost * 100, + ); + + await ProjectService.updateOneById({ + data: { + smsOrCallCurrentBalanceInUSDCents: + project.smsOrCallCurrentBalanceInUSDCents, + notEnabledSmsOrCallNotificationSentToOwners: false, // reset this flag + }, + id: project.id!, + props: { + isRoot: true, + }, + }); + } + } catch (e: any) { + smsLog.smsCostInUSDCents = 0; + smsLog.status = SmsStatus.Error; + smsLog.statusMessage = + e && e.message ? e.message.toString() : e.toString(); + + logger.error("SMS message failed to send."); + logger.error(smsLog.statusMessage); + + smsError = e; } + + if (options.projectId) { + await SmsLogService.create({ + data: smsLog, + props: { + isRoot: true, + }, + }); + } + + if (options.userOnCallLogTimelineId) { + await UserOnCallLogTimelineService.updateOneById({ + data: { + status: + smsLog.status === SmsStatus.Success + ? UserNotificationStatus.Sent + : UserNotificationStatus.Error, + statusMessage: smsLog.statusMessage!, + }, + id: options.userOnCallLogTimelineId, + props: { + isRoot: true, + }, + }); + } + + if (smsError) { + throw smsError; + } + } } diff --git a/App/FeatureSet/Notification/Utils/Handlebars.ts b/App/FeatureSet/Notification/Utils/Handlebars.ts index 3ef5561508..754220d8c1 100644 --- a/App/FeatureSet/Notification/Utils/Handlebars.ts +++ b/App/FeatureSet/Notification/Utils/Handlebars.ts @@ -1,64 +1,64 @@ -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import logger from 'CommonServer/Utils/Logger'; -import fsp from 'fs/promises'; -import Handlebars from 'handlebars'; -import Path from 'path'; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import logger from "CommonServer/Utils/Logger"; +import fsp from "fs/promises"; +import Handlebars from "handlebars"; +import Path from "path"; const loadPartials: PromiseVoidFunction = async (): Promise => { - // get all partials in the partial folder and comile then and register then as partials in handlebars. - const partialsDir: string = Path.resolve( - process.cwd(), - 'FeatureSet', - 'Notification', - 'Templates', - 'Partials' + // get all partials in the partial folder and comile then and register then as partials in handlebars. + const partialsDir: string = Path.resolve( + process.cwd(), + "FeatureSet", + "Notification", + "Templates", + "Partials", + ); + const filenames: string[] = await fsp.readdir(partialsDir); + filenames.forEach(async (filename: string) => { + const matches: RegExpMatchArray | null = filename.match(/^(.*)\.hbs$/); + if (!matches) { + return; + } + + const name: string = matches[1]!; + const template: string = await fsp.readFile( + Path.resolve(partialsDir, filename), + { encoding: "utf8", flag: "r" }, ); - const filenames: string[] = await fsp.readdir(partialsDir); - filenames.forEach(async (filename: string) => { - const matches: RegExpMatchArray | null = filename.match(/^(.*)\.hbs$/); - if (!matches) { - return; - } - const name: string = matches[1]!; - const template: string = await fsp.readFile( - Path.resolve(partialsDir, filename), - { encoding: 'utf8', flag: 'r' } - ); + const partialTemplate: Handlebars.TemplateDelegate = + Handlebars.compile(template); - const partialTemplate: Handlebars.TemplateDelegate = - Handlebars.compile(template); + Handlebars.registerPartial(name, partialTemplate); - Handlebars.registerPartial(name, partialTemplate); - - logger.debug(`Loaded partial ${name}`); - }); + logger.debug(`Loaded partial ${name}`); + }); }; loadPartials().catch((err: Error) => { - logger.error('Error loading partials'); - logger.error(err); + logger.error("Error loading partials"); + logger.error(err); }); -Handlebars.registerHelper('ifCond', function (v1, v2, options) { - if (v1 === v2) { - //@ts-ignore - return options.fn(this); - } +Handlebars.registerHelper("ifCond", function (v1, v2, options) { + if (v1 === v2) { //@ts-ignore - return options.inverse(this); + return options.fn(this); + } + //@ts-ignore + return options.inverse(this); }); -Handlebars.registerHelper('concat', (v1: any, v2: any) => { - // contact v1 and v2 - return v1 + v2; +Handlebars.registerHelper("concat", (v1: any, v2: any) => { + // contact v1 and v2 + return v1 + v2; }); -Handlebars.registerHelper('ifNotCond', function (v1, v2, options) { - if (v1 !== v2) { - //@ts-ignore - return options.fn(this); - } +Handlebars.registerHelper("ifNotCond", function (v1, v2, options) { + if (v1 !== v2) { //@ts-ignore - return options.inverse(this); + return options.fn(this); + } + //@ts-ignore + return options.inverse(this); }); diff --git a/App/FeatureSet/Workers/DataMigrations/AddAggregationTemporalityToMetric.ts b/App/FeatureSet/Workers/DataMigrations/AddAggregationTemporalityToMetric.ts index ec90791341..cc5fa27921 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddAggregationTemporalityToMetric.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddAggregationTemporalityToMetric.ts @@ -1,34 +1,34 @@ -import DataMigrationBase from './DataMigrationBase'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import MetricService from 'CommonServer/Services/MetricService'; -import Metric from 'Model/AnalyticsModels/Metric'; +import DataMigrationBase from "./DataMigrationBase"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import MetricService from "CommonServer/Services/MetricService"; +import Metric from "Model/AnalyticsModels/Metric"; export default class AddAggregationTemporalityToMetric extends DataMigrationBase { - public constructor() { - super('AddAggregationTemporalityToMetric'); + public constructor() { + super("AddAggregationTemporalityToMetric"); + } + + public override async migrate(): Promise { + const column: AnalyticsTableColumn | undefined = + new Metric().tableColumns.find((column: AnalyticsTableColumn) => { + return column.key === "aggregationTemporality"; + }); + + if (!column) { + return; } - public override async migrate(): Promise { - const column: AnalyticsTableColumn | undefined = - new Metric().tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === 'aggregationTemporality'; - }); + const columnType: TableColumnType | null = + await MetricService.getColumnTypeInDatabase(column); - if (!column) { - return; - } - - const columnType: TableColumnType | null = - await MetricService.getColumnTypeInDatabase(column); - - if (!columnType) { - await MetricService.dropColumnInDatabase('aggregationTemporality'); - await MetricService.addColumnInDatabase(column); - } + if (!columnType) { + await MetricService.dropColumnInDatabase("aggregationTemporality"); + await MetricService.addColumnInDatabase(column); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddAttributesColumnToSpanAndLog.ts b/App/FeatureSet/Workers/DataMigrations/AddAttributesColumnToSpanAndLog.ts index c0690f6076..fe0e6715e1 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddAttributesColumnToSpanAndLog.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddAttributesColumnToSpanAndLog.ts @@ -1,63 +1,63 @@ -import DataMigrationBase from './DataMigrationBase'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import LogService from 'CommonServer/Services/LogService'; -import SpanService from 'CommonServer/Services/SpanService'; -import Log from 'Model/AnalyticsModels/Log'; -import Span from 'Model/AnalyticsModels/Span'; +import DataMigrationBase from "./DataMigrationBase"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import LogService from "CommonServer/Services/LogService"; +import SpanService from "CommonServer/Services/SpanService"; +import Log from "Model/AnalyticsModels/Log"; +import Span from "Model/AnalyticsModels/Span"; export default class AddAttributeColumnToSpanAndLog extends DataMigrationBase { - public constructor() { - super('AddAttributeColumnToSpanAndLog'); + public constructor() { + super("AddAttributeColumnToSpanAndLog"); + } + + public override async migrate(): Promise { + await this.addAttributesColumnToLog(); + await this.addAttributesColumnToSpan(); + } + + public async addAttributesColumnToLog(): Promise { + // logs + const logsAttributesColumn: AnalyticsTableColumn | undefined = + new Log().tableColumns.find((column: AnalyticsTableColumn) => { + return column.key === "attributes"; + }); + + if (!logsAttributesColumn) { + return; } - public override async migrate(): Promise { - await this.addAttributesColumnToLog(); - await this.addAttributesColumnToSpan(); + const columnType: TableColumnType | null = + await LogService.getColumnTypeInDatabase(logsAttributesColumn); + + if (!columnType) { + await LogService.dropColumnInDatabase("attributes"); + await LogService.addColumnInDatabase(logsAttributesColumn); + } + } + + public async addAttributesColumnToSpan(): Promise { + // spans + + const spansAttributesColumn: AnalyticsTableColumn | undefined = + new Span().tableColumns.find((column: AnalyticsTableColumn) => { + return column.key === "attributes"; + }); + + if (!spansAttributesColumn) { + return; } - public async addAttributesColumnToLog(): Promise { - // logs - const logsAttributesColumn: AnalyticsTableColumn | undefined = - new Log().tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === 'attributes'; - }); + const spansColumnType: TableColumnType | null = + await SpanService.getColumnTypeInDatabase(spansAttributesColumn); - if (!logsAttributesColumn) { - return; - } - - const columnType: TableColumnType | null = - await LogService.getColumnTypeInDatabase(logsAttributesColumn); - - if (!columnType) { - await LogService.dropColumnInDatabase('attributes'); - await LogService.addColumnInDatabase(logsAttributesColumn); - } + if (!spansColumnType) { + await SpanService.dropColumnInDatabase("attributes"); + await SpanService.addColumnInDatabase(spansAttributesColumn); } + } - public async addAttributesColumnToSpan(): Promise { - // spans - - const spansAttributesColumn: AnalyticsTableColumn | undefined = - new Span().tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === 'attributes'; - }); - - if (!spansAttributesColumn) { - return; - } - - const spansColumnType: TableColumnType | null = - await SpanService.getColumnTypeInDatabase(spansAttributesColumn); - - if (!spansColumnType) { - await SpanService.dropColumnInDatabase('attributes'); - await SpanService.addColumnInDatabase(spansAttributesColumn); - } - } - - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddDefaultGlobalConfig.ts b/App/FeatureSet/Workers/DataMigrations/AddDefaultGlobalConfig.ts index cb65921baa..6cea3c9443 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddDefaultGlobalConfig.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddDefaultGlobalConfig.ts @@ -1,31 +1,31 @@ -import DataMigrationBase from './DataMigrationBase'; -import ObjectID from 'Common/Types/ObjectID'; -import GlobalConfigService from 'CommonServer/Services/GlobalConfigService'; -import GlobalConfig, { EmailServerType } from 'Model/Models/GlobalConfig'; +import DataMigrationBase from "./DataMigrationBase"; +import ObjectID from "Common/Types/ObjectID"; +import GlobalConfigService from "CommonServer/Services/GlobalConfigService"; +import GlobalConfig, { EmailServerType } from "Model/Models/GlobalConfig"; export default class AddDefaultGlobalConfig extends DataMigrationBase { - public constructor() { - super('AddDefaultGlobalConfig'); - } + public constructor() { + super("AddDefaultGlobalConfig"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const globalConfig: GlobalConfig = new GlobalConfig(); - globalConfig.id = ObjectID.getZeroObjectID(); - globalConfig.emailServerType = EmailServerType.Internal; - globalConfig.sendgridFromName = 'OneUptime'; - globalConfig.smtpFromName = 'OneUptime'; + const globalConfig: GlobalConfig = new GlobalConfig(); + globalConfig.id = ObjectID.getZeroObjectID(); + globalConfig.emailServerType = EmailServerType.Internal; + globalConfig.sendgridFromName = "OneUptime"; + globalConfig.smtpFromName = "OneUptime"; - await GlobalConfigService.create({ - data: globalConfig, - props: { - isRoot: true, - }, - }); - } + await GlobalConfigService.create({ + data: globalConfig, + props: { + isRoot: true, + }, + }); + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddDowntimeMonitorStatusToStatusPage.ts b/App/FeatureSet/Workers/DataMigrations/AddDowntimeMonitorStatusToStatusPage.ts index df5c2e8f38..60ac71a9cc 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddDowntimeMonitorStatusToStatusPage.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddDowntimeMonitorStatusToStatusPage.ts @@ -1,76 +1,76 @@ -import DataMigrationBase from './DataMigrationBase'; -import { Green } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import MonitorStatusService from 'CommonServer/Services/MonitorStatusService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import StatusPage from 'Model/Models/StatusPage'; +import DataMigrationBase from "./DataMigrationBase"; +import { Green } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import MonitorStatusService from "CommonServer/Services/MonitorStatusService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import StatusPage from "Model/Models/StatusPage"; export default class AddDowntimeMonitorStatusToStatusPage extends DataMigrationBase { - public constructor() { - super('AddDowntimeMonitorStatusToStatusPage'); - } + public constructor() { + super("AddDowntimeMonitorStatusToStatusPage"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const statusPages: Array = await StatusPageService.findBy({ - query: {}, - select: { - _id: true, - projectId: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, + const statusPages: Array = await StatusPageService.findBy({ + query: {}, + select: { + _id: true, + projectId: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const statusPage of statusPages) { + // add ended scheduled maintenance state for each of these projects. + // first fetch resolved state. Ended state order is -1 of resolved state. + + if (!statusPage.projectId) { + continue; + } + + const monitorStatuses: Array = + await MonitorStatusService.findBy({ + query: { + projectId: statusPage.projectId, + }, + select: { + _id: true, + isOperationalState: true, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, }); - for (const statusPage of statusPages) { - // add ended scheduled maintenance state for each of these projects. - // first fetch resolved state. Ended state order is -1 of resolved state. + const getNonOperationStatuses: Array = + monitorStatuses.filter((monitorStatus: MonitorStatus) => { + return !monitorStatus.isOperationalState; + }); - if (!statusPage.projectId) { - continue; - } - - const monitorStatuses: Array = - await MonitorStatusService.findBy({ - query: { - projectId: statusPage.projectId, - }, - select: { - _id: true, - isOperationalState: true, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - }); - - const getNonOperationStatuses: Array = - monitorStatuses.filter((monitorStatus: MonitorStatus) => { - return !monitorStatus.isOperationalState; - }); - - await StatusPageService.updateOneById({ - id: statusPage.id!, - data: { - downtimeMonitorStatuses: getNonOperationStatuses as any, - defaultBarColor: new Color(Green.toString()) as any, - }, - props: { - isRoot: true, - }, - }); - } + await StatusPageService.updateOneById({ + id: statusPage.id!, + data: { + downtimeMonitorStatuses: getNonOperationStatuses as any, + defaultBarColor: new Color(Green.toString()) as any, + }, + props: { + isRoot: true, + }, + }); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddDurationColumnToSpanTable.ts b/App/FeatureSet/Workers/DataMigrations/AddDurationColumnToSpanTable.ts index fbe497403b..7e635e23a3 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddDurationColumnToSpanTable.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddDurationColumnToSpanTable.ts @@ -1,33 +1,34 @@ -import DataMigrationBase from './DataMigrationBase'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import SpanService from 'CommonServer/Services/SpanService'; -import Span from 'Model/AnalyticsModels/Span'; +import DataMigrationBase from "./DataMigrationBase"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import SpanService from "CommonServer/Services/SpanService"; +import Span from "Model/AnalyticsModels/Span"; export default class AddDurationColumnToSpanTable extends DataMigrationBase { - public constructor() { - super('AddDurationColumnToSpanTable'); + public constructor() { + super("AddDurationColumnToSpanTable"); + } + + public override async migrate(): Promise { + const hasDurationColumn: boolean = + await SpanService.doesColumnExistInDatabase("durationUnixNano"); + + const durationColumn: AnalyticsTableColumn = new Span().tableColumns.find( + (column: AnalyticsTableColumn) => { + return column.key === "durationUnixNano"; + }, + )!; + + if (!hasDurationColumn && durationColumn) { + await SpanService.addColumnInDatabase(durationColumn); } + } - public override async migrate(): Promise { - const hasDurationColumn: boolean = - await SpanService.doesColumnExistInDatabase('durationUnixNano'); + public override async rollback(): Promise { + const hasDurationColumn: boolean = + await SpanService.doesColumnExistInDatabase("durationUnixNano"); - const durationColumn: AnalyticsTableColumn = - new Span().tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === 'durationUnixNano'; - })!; - - if (!hasDurationColumn && durationColumn) { - await SpanService.addColumnInDatabase(durationColumn); - } - } - - public override async rollback(): Promise { - const hasDurationColumn: boolean = - await SpanService.doesColumnExistInDatabase('durationUnixNano'); - - if (hasDurationColumn) { - await SpanService.dropColumnInDatabase('durationUnixNano'); - } + if (hasDurationColumn) { + await SpanService.dropColumnInDatabase("durationUnixNano"); } + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddEndDateToIncidentStateTimeline.ts b/App/FeatureSet/Workers/DataMigrations/AddEndDateToIncidentStateTimeline.ts index a6376f4204..f2ccca50a7 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddEndDateToIncidentStateTimeline.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddEndDateToIncidentStateTimeline.ts @@ -1,111 +1,107 @@ -import DataMigrationBase from './DataMigrationBase'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import IncidentStateTimelineService from 'CommonServer/Services/IncidentStateTimelineService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Incident from 'Model/Models/Incident'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import Project from 'Model/Models/Project'; +import DataMigrationBase from "./DataMigrationBase"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import IncidentService from "CommonServer/Services/IncidentService"; +import IncidentStateTimelineService from "CommonServer/Services/IncidentStateTimelineService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Incident from "Model/Models/Incident"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import Project from "Model/Models/Project"; export default class AddEndDateToIncidentStateTimeline extends DataMigrationBase { - public constructor() { - super('AddEndDateToIncidentStateTimeline'); - } + public constructor() { + super("AddEndDateToIncidentStateTimeline"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const projects: Array = await ProjectService.findBy({ - query: {}, + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + // add ended scheduled maintenance state for each of these projects. + // first fetch resolved state. Ended state order is -1 of resolved state. + + const incidents: Array = await IncidentService.findBy({ + query: { + projectId: project.id!, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const incident of incidents) { + const incidentStateTimelines: Array = + await IncidentStateTimelineService.findBy({ + query: { + incidentId: incident.id!, + endsAt: QueryHelper.isNull(), + }, select: { - _id: true, + _id: true, + createdAt: true, }, skip: 0, limit: LIMIT_MAX, props: { - isRoot: true, + isRoot: true, }, - }); + sort: { + createdAt: SortOrder.Ascending, + }, + }); - for (const project of projects) { - // add ended scheduled maintenance state for each of these projects. - // first fetch resolved state. Ended state order is -1 of resolved state. + for (let i: number = 0; i < incidentStateTimelines.length; i++) { + const statusTimeline: IncidentStateTimeline | undefined = + incidentStateTimelines[i]; - const incidents: Array = await IncidentService.findBy({ - query: { - projectId: project.id!, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, + if (!statusTimeline) { + continue; + } + + let endDate: Date | null = null; + + if ( + incidentStateTimelines[i + 1] && + incidentStateTimelines[i + 1]?.createdAt + ) { + endDate = incidentStateTimelines[i + 1]!.createdAt!; + } + + if (endDate) { + await IncidentStateTimelineService.updateOneById({ + id: statusTimeline!.id!, + data: { + endsAt: endDate, + }, + props: { + isRoot: true, + }, }); - - for (const incident of incidents) { - const incidentStateTimelines: Array = - await IncidentStateTimelineService.findBy({ - query: { - incidentId: incident.id!, - endsAt: QueryHelper.isNull(), - }, - select: { - _id: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - sort: { - createdAt: SortOrder.Ascending, - }, - }); - - for ( - let i: number = 0; - i < incidentStateTimelines.length; - i++ - ) { - const statusTimeline: IncidentStateTimeline | undefined = - incidentStateTimelines[i]; - - if (!statusTimeline) { - continue; - } - - let endDate: Date | null = null; - - if ( - incidentStateTimelines[i + 1] && - incidentStateTimelines[i + 1]?.createdAt - ) { - endDate = incidentStateTimelines[i + 1]!.createdAt!; - } - - if (endDate) { - await IncidentStateTimelineService.updateOneById({ - id: statusTimeline!.id!, - data: { - endsAt: endDate, - }, - props: { - isRoot: true, - }, - }); - } - } - } + } } + } } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddEndDateToMonitorStatusTimeline.ts b/App/FeatureSet/Workers/DataMigrations/AddEndDateToMonitorStatusTimeline.ts index 897b3272e0..18d5a78588 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddEndDateToMonitorStatusTimeline.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddEndDateToMonitorStatusTimeline.ts @@ -1,107 +1,104 @@ -import DataMigrationBase from './DataMigrationBase'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import MonitorService from 'CommonServer/Services/MonitorService'; -import MonitorStatusTimelineService from 'CommonServer/Services/MonitorStatusTimelineService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import Project from 'Model/Models/Project'; +import DataMigrationBase from "./DataMigrationBase"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import MonitorService from "CommonServer/Services/MonitorService"; +import MonitorStatusTimelineService from "CommonServer/Services/MonitorStatusTimelineService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import Project from "Model/Models/Project"; export default class AddEndDateToMonitorStatusTimeline extends DataMigrationBase { - public constructor() { - super('AddEndDateToMonitorStatusTimeline'); - } + public constructor() { + super("AddEndDateToMonitorStatusTimeline"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const projects: Array = await ProjectService.findBy({ - query: {}, + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + // add ended scheduled maintenance state for each of these projects. + // first fetch resolved state. Ended state order is -1 of resolved state. + + const monitors: Array = await MonitorService.findBy({ + query: { + projectId: project.id!, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const monitor of monitors) { + const statusTimelines: Array = + await MonitorStatusTimelineService.findBy({ + query: { + monitorId: monitor.id!, + endsAt: QueryHelper.isNull(), + }, select: { - _id: true, + _id: true, + createdAt: true, }, skip: 0, limit: LIMIT_MAX, props: { - isRoot: true, + isRoot: true, }, - }); + sort: { + createdAt: SortOrder.Ascending, + }, + }); - for (const project of projects) { - // add ended scheduled maintenance state for each of these projects. - // first fetch resolved state. Ended state order is -1 of resolved state. + for (let i: number = 0; i < statusTimelines.length; i++) { + const statusTimeline: MonitorStatusTimeline | undefined = + statusTimelines[i]; - const monitors: Array = await MonitorService.findBy({ - query: { - projectId: project.id!, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, + if (!statusTimeline) { + continue; + } + + let endDate: Date | null = null; + + if (statusTimelines[i + 1] && statusTimelines[i + 1]?.createdAt) { + endDate = statusTimelines[i + 1]!.createdAt!; + } + + if (endDate) { + await MonitorStatusTimelineService.updateOneById({ + id: statusTimeline!.id!, + data: { + endsAt: endDate, + }, + props: { + isRoot: true, + }, }); - - for (const monitor of monitors) { - const statusTimelines: Array = - await MonitorStatusTimelineService.findBy({ - query: { - monitorId: monitor.id!, - endsAt: QueryHelper.isNull(), - }, - select: { - _id: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - sort: { - createdAt: SortOrder.Ascending, - }, - }); - - for (let i: number = 0; i < statusTimelines.length; i++) { - const statusTimeline: MonitorStatusTimeline | undefined = - statusTimelines[i]; - - if (!statusTimeline) { - continue; - } - - let endDate: Date | null = null; - - if ( - statusTimelines[i + 1] && - statusTimelines[i + 1]?.createdAt - ) { - endDate = statusTimelines[i + 1]!.createdAt!; - } - - if (endDate) { - await MonitorStatusTimelineService.updateOneById({ - id: statusTimeline!.id!, - data: { - endsAt: endDate, - }, - props: { - isRoot: true, - }, - }); - } - } - } + } } + } } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing.ts b/App/FeatureSet/Workers/DataMigrations/AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing.ts index afbbf9db16..207a80e82a 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing.ts @@ -1,113 +1,113 @@ -import DataMigrationBase from './DataMigrationBase'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import MonitorService from 'CommonServer/Services/MonitorService'; -import MonitorStatusTimelineService from 'CommonServer/Services/MonitorStatusTimelineService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import Project from 'Model/Models/Project'; +import DataMigrationBase from "./DataMigrationBase"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import MonitorService from "CommonServer/Services/MonitorService"; +import MonitorStatusTimelineService from "CommonServer/Services/MonitorStatusTimelineService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import Project from "Model/Models/Project"; export default class AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing extends DataMigrationBase { - public constructor() { - super('AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing'); - } + public constructor() { + super("AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const projects: Array = await ProjectService.findBy({ - query: {}, + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + // add ended scheduled maintenance state for each of these projects. + // first fetch resolved state. Ended state order is -1 of resolved state. + + const monitors: Array = await MonitorService.findBy({ + query: { + projectId: project.id!, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const monitor of monitors) { + let statusTimelines: Array = + await MonitorStatusTimelineService.findBy({ + query: { + monitorId: monitor.id!, + }, select: { - _id: true, + _id: true, + createdAt: true, }, skip: 0, limit: LIMIT_MAX, props: { - isRoot: true, + isRoot: true, }, - }); + sort: { + createdAt: SortOrder.Descending, + }, + }); - for (const project of projects) { - // add ended scheduled maintenance state for each of these projects. - // first fetch resolved state. Ended state order is -1 of resolved state. + // reverse the status timelines + statusTimelines = statusTimelines.reverse(); - const monitors: Array = await MonitorService.findBy({ - query: { - projectId: project.id!, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, + for (let i: number = 0; i < statusTimelines.length; i++) { + const statusTimeline: MonitorStatusTimeline | undefined = + statusTimelines[i]; + + if (!statusTimeline) { + continue; + } + + if (statusTimeline.endsAt) { + continue; + } + + let endDate: Date | null = statusTimeline.endsAt || null; + + if ( + !endDate && + statusTimelines[i + 1] && + statusTimelines[i + 1]?.createdAt + ) { + endDate = statusTimelines[i + 1]!.createdAt!; + } + + if (endDate) { + await MonitorStatusTimelineService.updateOneById({ + id: statusTimeline!.id!, + data: { + endsAt: endDate, + }, + props: { + isRoot: true, + }, }); - - for (const monitor of monitors) { - let statusTimelines: Array = - await MonitorStatusTimelineService.findBy({ - query: { - monitorId: monitor.id!, - }, - select: { - _id: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - sort: { - createdAt: SortOrder.Descending, - }, - }); - - // reverse the status timelines - statusTimelines = statusTimelines.reverse(); - - for (let i: number = 0; i < statusTimelines.length; i++) { - const statusTimeline: MonitorStatusTimeline | undefined = - statusTimelines[i]; - - if (!statusTimeline) { - continue; - } - - if (statusTimeline.endsAt) { - continue; - } - - let endDate: Date | null = statusTimeline.endsAt || null; - - if ( - !endDate && - statusTimelines[i + 1] && - statusTimelines[i + 1]?.createdAt - ) { - endDate = statusTimelines[i + 1]!.createdAt!; - } - - if (endDate) { - await MonitorStatusTimelineService.updateOneById({ - id: statusTimeline!.id!, - data: { - endsAt: endDate, - }, - props: { - isRoot: true, - }, - }); - } - } - } + } } + } } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddEndDateToScheduledEventsStateTimeline.ts b/App/FeatureSet/Workers/DataMigrations/AddEndDateToScheduledEventsStateTimeline.ts index eab48d49e0..b3bdeed663 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddEndDateToScheduledEventsStateTimeline.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddEndDateToScheduledEventsStateTimeline.ts @@ -1,117 +1,112 @@ -import DataMigrationBase from './DataMigrationBase'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import ScheduledMaintenanceStateTimelineService from 'CommonServer/Services/ScheduledMaintenanceStateTimelineService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Project from 'Model/Models/Project'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; +import DataMigrationBase from "./DataMigrationBase"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import ProjectService from "CommonServer/Services/ProjectService"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import ScheduledMaintenanceStateTimelineService from "CommonServer/Services/ScheduledMaintenanceStateTimelineService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Project from "Model/Models/Project"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; export default class AddEndDateToScheduledEventsStateTimeline extends DataMigrationBase { - public constructor() { - super('AddEndDateToScheduledEventsStateTimeline'); - } + public constructor() { + super("AddEndDateToScheduledEventsStateTimeline"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const projects: Array = await ProjectService.findBy({ - query: {}, + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + // add ended scheduled maintenance state for each of these projects. + // first fetch resolved state. Ended state order is -1 of resolved state. + + const scheduledEvents: Array = + await ScheduledMaintenanceService.findBy({ + query: { + projectId: project.id!, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const scheduledEvent of scheduledEvents) { + const scheduledMaintenanceStateTimelines: Array = + await ScheduledMaintenanceStateTimelineService.findBy({ + query: { + scheduledMaintenanceId: scheduledEvent.id!, + endsAt: QueryHelper.isNull(), + }, select: { - _id: true, + _id: true, + createdAt: true, }, skip: 0, limit: LIMIT_MAX, props: { - isRoot: true, + isRoot: true, }, - }); + sort: { + createdAt: SortOrder.Ascending, + }, + }); - for (const project of projects) { - // add ended scheduled maintenance state for each of these projects. - // first fetch resolved state. Ended state order is -1 of resolved state. + for ( + let i: number = 0; + i < scheduledMaintenanceStateTimelines.length; + i++ + ) { + const statusTimeline: ScheduledMaintenanceStateTimeline | undefined = + scheduledMaintenanceStateTimelines[i]; - const scheduledEvents: Array = - await ScheduledMaintenanceService.findBy({ - query: { - projectId: project.id!, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); + if (!statusTimeline) { + continue; + } - for (const scheduledEvent of scheduledEvents) { - const scheduledMaintenanceStateTimelines: Array = - await ScheduledMaintenanceStateTimelineService.findBy({ - query: { - scheduledMaintenanceId: scheduledEvent.id!, - endsAt: QueryHelper.isNull(), - }, - select: { - _id: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - sort: { - createdAt: SortOrder.Ascending, - }, - }); + let endDate: Date | null = null; - for ( - let i: number = 0; - i < scheduledMaintenanceStateTimelines.length; - i++ - ) { - const statusTimeline: - | ScheduledMaintenanceStateTimeline - | undefined = scheduledMaintenanceStateTimelines[i]; + if ( + scheduledMaintenanceStateTimelines[i + 1] && + scheduledMaintenanceStateTimelines[i + 1]?.createdAt + ) { + endDate = scheduledMaintenanceStateTimelines[i + 1]!.createdAt!; + } - if (!statusTimeline) { - continue; - } - - let endDate: Date | null = null; - - if ( - scheduledMaintenanceStateTimelines[i + 1] && - scheduledMaintenanceStateTimelines[i + 1]?.createdAt - ) { - endDate = - scheduledMaintenanceStateTimelines[i + 1]! - .createdAt!; - } - - if (endDate) { - await ScheduledMaintenanceStateTimelineService.updateOneById( - { - id: statusTimeline!.id!, - data: { - endsAt: endDate, - }, - props: { - isRoot: true, - }, - } - ); - } - } - } + if (endDate) { + await ScheduledMaintenanceStateTimelineService.updateOneById({ + id: statusTimeline!.id!, + data: { + endsAt: endDate, + }, + props: { + isRoot: true, + }, + }); + } } + } } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddEndedState.ts b/App/FeatureSet/Workers/DataMigrations/AddEndedState.ts index 9794e55224..d213e28dba 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddEndedState.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddEndedState.ts @@ -1,107 +1,107 @@ -import DataMigrationBase from './DataMigrationBase'; -import Color from 'Common/Types/Color'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import ScheduledMaintenanceStateService from 'CommonServer/Services/ScheduledMaintenanceStateService'; -import Project from 'Model/Models/Project'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; +import DataMigrationBase from "./DataMigrationBase"; +import Color from "Common/Types/Color"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import ProjectService from "CommonServer/Services/ProjectService"; +import ScheduledMaintenanceStateService from "CommonServer/Services/ScheduledMaintenanceStateService"; +import Project from "Model/Models/Project"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; export default class AddEndedState extends DataMigrationBase { - public constructor() { - super('AddEndedState'); - } + public constructor() { + super("AddEndedState"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const projects: Array = await ProjectService.findBy({ - query: {}, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + // add ended scheduled maintenance state for each of these projects. + // first fetch resolved state. Ended state order is -1 of resolved state. + + const resolvedState: ScheduledMaintenanceState | null = + await ScheduledMaintenanceStateService.findOneBy({ + query: { + projectId: project.id!, + isResolvedState: true, + }, + select: { + order: true, + }, + props: { + isRoot: true, + }, }); - for (const project of projects) { - // add ended scheduled maintenance state for each of these projects. - // first fetch resolved state. Ended state order is -1 of resolved state. + if (!resolvedState) { + continue; + } - const resolvedState: ScheduledMaintenanceState | null = - await ScheduledMaintenanceStateService.findOneBy({ - query: { - projectId: project.id!, - isResolvedState: true, - }, - select: { - order: true, - }, - props: { - isRoot: true, - }, - }); + let endedState: ScheduledMaintenanceState | null = + await ScheduledMaintenanceStateService.findOneBy({ + query: { + projectId: project.id!, + isEndedState: true, + }, + select: { + order: true, + }, + props: { + isRoot: true, + }, + }); - if (!resolvedState) { - continue; - } + if (endedState) { + continue; + } - let endedState: ScheduledMaintenanceState | null = - await ScheduledMaintenanceStateService.findOneBy({ - query: { - projectId: project.id!, - isEndedState: true, - }, - select: { - order: true, - }, - props: { - isRoot: true, - }, - }); + endedState = await ScheduledMaintenanceStateService.findOneBy({ + query: { + projectId: project.id!, + name: "Ended", + }, + select: { + order: true, + }, + props: { + isRoot: true, + }, + }); - if (endedState) { - continue; - } + if (endedState) { + continue; + } - endedState = await ScheduledMaintenanceStateService.findOneBy({ - query: { - projectId: project.id!, - name: 'Ended', - }, - select: { - order: true, - }, - props: { - isRoot: true, - }, - }); + endedState = new ScheduledMaintenanceState(); + endedState.projectId = project.id!; + endedState.name = "Ended"; + endedState.description = + "Scheduled maintenance events switch to this state when they end."; + endedState.order = resolvedState.order!; + endedState.isEndedState = true; + endedState.color = new Color("#4A4A4A"); - if (endedState) { - continue; - } - - endedState = new ScheduledMaintenanceState(); - endedState.projectId = project.id!; - endedState.name = 'Ended'; - endedState.description = - 'Scheduled maintenance events switch to this state when they end.'; - endedState.order = resolvedState.order!; - endedState.isEndedState = true; - endedState.color = new Color('#4A4A4A'); - - await ScheduledMaintenanceStateService.create({ - data: endedState, - props: { - isRoot: true, - }, - }); - } + await ScheduledMaintenanceStateService.create({ + data: endedState, + props: { + isRoot: true, + }, + }); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddIsMonotonicToMetric.ts b/App/FeatureSet/Workers/DataMigrations/AddIsMonotonicToMetric.ts index 284b99db23..0b4cee884f 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddIsMonotonicToMetric.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddIsMonotonicToMetric.ts @@ -1,34 +1,34 @@ -import DataMigrationBase from './DataMigrationBase'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import MetricService from 'CommonServer/Services/MetricService'; -import Metric from 'Model/AnalyticsModels/Metric'; +import DataMigrationBase from "./DataMigrationBase"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import MetricService from "CommonServer/Services/MetricService"; +import Metric from "Model/AnalyticsModels/Metric"; export default class AddIsMonotonicToMetric extends DataMigrationBase { - public constructor() { - super('AddIsMonotonicToMetric'); + public constructor() { + super("AddIsMonotonicToMetric"); + } + + public override async migrate(): Promise { + const column: AnalyticsTableColumn | undefined = + new Metric().tableColumns.find((column: AnalyticsTableColumn) => { + return column.key === "isMonotonic"; + }); + + if (!column) { + return; } - public override async migrate(): Promise { - const column: AnalyticsTableColumn | undefined = - new Metric().tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === 'isMonotonic'; - }); + const columnType: TableColumnType | null = + await MetricService.getColumnTypeInDatabase(column); - if (!column) { - return; - } - - const columnType: TableColumnType | null = - await MetricService.getColumnTypeInDatabase(column); - - if (!columnType) { - await MetricService.dropColumnInDatabase('isMonotonic'); - await MetricService.addColumnInDatabase(column); - } + if (!columnType) { + await MetricService.dropColumnInDatabase("isMonotonic"); + await MetricService.addColumnInDatabase(column); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddMonitoringDatesToMonitors.ts b/App/FeatureSet/Workers/DataMigrations/AddMonitoringDatesToMonitors.ts index 12011719be..c9a6243340 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddMonitoringDatesToMonitors.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddMonitoringDatesToMonitors.ts @@ -1,73 +1,72 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import MonitorProbeService from 'CommonServer/Services/MonitorProbeService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import MonitorProbe from 'Model/Models/MonitorProbe'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import MonitorProbeService from "CommonServer/Services/MonitorProbeService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import MonitorProbe from "Model/Models/MonitorProbe"; export default class AddMonitoringDatesToMonitor extends DataMigrationBase { - public constructor() { - super('AddMonitoringDatesToMonitor'); + public constructor() { + super("AddMonitoringDatesToMonitor"); + } + + public override async migrate(): Promise { + // get all the users with email isVerified true. + + let probeMonitors: Array = await MonitorProbeService.findBy({ + query: { + nextPingAt: QueryHelper.isNull(), + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const probeMonitor of probeMonitors) { + await MonitorProbeService.updateOneById({ + id: probeMonitor.id!, + data: { + nextPingAt: OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); } - public override async migrate(): Promise { - // get all the users with email isVerified true. + probeMonitors = await MonitorProbeService.findBy({ + query: { + lastPingAt: QueryHelper.isNull(), + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); - let probeMonitors: Array = - await MonitorProbeService.findBy({ - query: { - nextPingAt: QueryHelper.isNull(), - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const probeMonitor of probeMonitors) { - await MonitorProbeService.updateOneById({ - id: probeMonitor.id!, - data: { - nextPingAt: OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); - } - - probeMonitors = await MonitorProbeService.findBy({ - query: { - lastPingAt: QueryHelper.isNull(), - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const probeMonitor of probeMonitors) { - await MonitorProbeService.updateOneById({ - id: probeMonitor.id!, - data: { - lastPingAt: OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); - } + for (const probeMonitor of probeMonitors) { + await MonitorProbeService.updateOneById({ + id: probeMonitor.id!, + data: { + lastPingAt: OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddOwnerInfoToProject.ts b/App/FeatureSet/Workers/DataMigrations/AddOwnerInfoToProject.ts index 972fe274d3..881b8785fd 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddOwnerInfoToProject.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddOwnerInfoToProject.ts @@ -1,75 +1,73 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import UserService from 'CommonServer/Services/UserService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Project from 'Model/Models/Project'; -import User from 'Model/Models/User'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import ProjectService from "CommonServer/Services/ProjectService"; +import UserService from "CommonServer/Services/UserService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Project from "Model/Models/Project"; +import User from "Model/Models/User"; export default class AddOwnerInfoToProjects extends DataMigrationBase { - public constructor() { - super('AddOwnerInfoToProjects'); - } + public constructor() { + super("AddOwnerInfoToProjects"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const projects: Array = await ProjectService.findBy({ - query: { - createdOwnerEmail: QueryHelper.isNull(), - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, + const projects: Array = await ProjectService.findBy({ + query: { + createdOwnerEmail: QueryHelper.isNull(), + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + const owners: Array = await ProjectService.getOwners(project.id!); + + if (owners.length > 0) { + const ownerUser: User | null = owners[0]!; + + const user: User | null = await UserService.findOneById({ + id: ownerUser.id!, + select: { + email: true, + name: true, + companyName: true, + companyPhoneNumber: true, + }, + props: { + isRoot: true, + }, }); - for (const project of projects) { - const owners: Array = await ProjectService.getOwners( - project.id! - ); - - if (owners.length > 0) { - const ownerUser: User | null = owners[0]!; - - const user: User | null = await UserService.findOneById({ - id: ownerUser.id!, - select: { - email: true, - name: true, - companyName: true, - companyPhoneNumber: true, - }, - props: { - isRoot: true, - }, - }); - - if (!user) { - continue; - } - - await ProjectService.updateOneById({ - id: project.id!, - data: { - createdOwnerEmail: user.email!, - createdOwnerPhone: user.companyPhoneNumber!, - createdOwnerName: user.name!, - createdOwnerCompanyName: user.companyName!, - }, - props: { - isRoot: true, - }, - }); - } + if (!user) { + continue; } - } - public override async rollback(): Promise { - return; + await ProjectService.updateOneById({ + id: project.id!, + data: { + createdOwnerEmail: user.email!, + createdOwnerPhone: user.companyPhoneNumber!, + createdOwnerName: user.name!, + createdOwnerCompanyName: user.companyName!, + }, + props: { + isRoot: true, + }, + }); + } } + } + + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddPointTypeToMetric.ts b/App/FeatureSet/Workers/DataMigrations/AddPointTypeToMetric.ts index 33a83c03bf..5634767d55 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddPointTypeToMetric.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddPointTypeToMetric.ts @@ -1,34 +1,34 @@ -import DataMigrationBase from './DataMigrationBase'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import MetricService from 'CommonServer/Services/MetricService'; -import Metric from 'Model/AnalyticsModels/Metric'; +import DataMigrationBase from "./DataMigrationBase"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import MetricService from "CommonServer/Services/MetricService"; +import Metric from "Model/AnalyticsModels/Metric"; export default class AddPointTypeToMetric extends DataMigrationBase { - public constructor() { - super('AddPointTypeToMetric'); + public constructor() { + super("AddPointTypeToMetric"); + } + + public override async migrate(): Promise { + const column: AnalyticsTableColumn | undefined = + new Metric().tableColumns.find((column: AnalyticsTableColumn) => { + return column.key === "metricPointType"; + }); + + if (!column) { + return; } - public override async migrate(): Promise { - const column: AnalyticsTableColumn | undefined = - new Metric().tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === 'metricPointType'; - }); + const columnType: TableColumnType | null = + await MetricService.getColumnTypeInDatabase(column); - if (!column) { - return; - } - - const columnType: TableColumnType | null = - await MetricService.getColumnTypeInDatabase(column); - - if (!columnType) { - await MetricService.dropColumnInDatabase('metricPointType'); - await MetricService.addColumnInDatabase(column); - } + if (!columnType) { + await MetricService.dropColumnInDatabase("metricPointType"); + await MetricService.addColumnInDatabase(column); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddPostedAtToPublicNotes.ts b/App/FeatureSet/Workers/DataMigrations/AddPostedAtToPublicNotes.ts index de0a0afa8e..399ac4fa46 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddPostedAtToPublicNotes.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddPostedAtToPublicNotes.ts @@ -1,74 +1,74 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import IncidentPublicNoteService from 'CommonServer/Services/IncidentPublicNoteService'; -import ScheduledMaintenancePublicNoteService from 'CommonServer/Services/ScheduledMaintenancePublicNoteService'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import IncidentPublicNoteService from "CommonServer/Services/IncidentPublicNoteService"; +import ScheduledMaintenancePublicNoteService from "CommonServer/Services/ScheduledMaintenancePublicNoteService"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; export default class AddPostedAtToPublicNotes extends DataMigrationBase { - public constructor() { - super('AddPostedAtToPublicNotes'); + public constructor() { + super("AddPostedAtToPublicNotes"); + } + + public override async migrate(): Promise { + // get all the users with email isVerified true. + + const incidentPublicNotes: Array = + await IncidentPublicNoteService.findBy({ + query: {}, + select: { + _id: true, + createdAt: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const publicNote of incidentPublicNotes) { + await IncidentPublicNoteService.updateOneById({ + id: publicNote.id!, + data: { + postedAt: publicNote.createdAt!, + }, + props: { + isRoot: true, + }, + }); } - public override async migrate(): Promise { - // get all the users with email isVerified true. + // do the same for scheduledeventpublic notes. - const incidentPublicNotes: Array = - await IncidentPublicNoteService.findBy({ - query: {}, - select: { - _id: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); + const eventPublicNotes: Array = + await ScheduledMaintenancePublicNoteService.findBy({ + query: {}, + select: { + _id: true, + createdAt: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); - for (const publicNote of incidentPublicNotes) { - await IncidentPublicNoteService.updateOneById({ - id: publicNote.id!, - data: { - postedAt: publicNote.createdAt!, - }, - props: { - isRoot: true, - }, - }); - } - - // do the same for scheduledeventpublic notes. - - const eventPublicNotes: Array = - await ScheduledMaintenancePublicNoteService.findBy({ - query: {}, - select: { - _id: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const publicNote of eventPublicNotes) { - await ScheduledMaintenancePublicNoteService.updateOneById({ - id: publicNote.id!, - data: { - postedAt: publicNote.createdAt!, - }, - props: { - isRoot: true, - }, - }); - } + for (const publicNote of eventPublicNotes) { + await ScheduledMaintenancePublicNoteService.updateOneById({ + id: publicNote.id!, + data: { + postedAt: publicNote.createdAt!, + }, + props: { + isRoot: true, + }, + }); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddSecretKeyToIncomingRequestMonitor.ts b/App/FeatureSet/Workers/DataMigrations/AddSecretKeyToIncomingRequestMonitor.ts index 90f4a24f46..b74a86b5c8 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddSecretKeyToIncomingRequestMonitor.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddSecretKeyToIncomingRequestMonitor.ts @@ -1,48 +1,48 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import MonitorService from 'CommonServer/Services/MonitorService'; -import Monitor from 'Model/Models/Monitor'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import MonitorService from "CommonServer/Services/MonitorService"; +import Monitor from "Model/Models/Monitor"; export default class AddSecretKeyToIncomingRequestMonitor extends DataMigrationBase { - public constructor() { - super('AddSecretKeyToIncomingRequestMonitor'); - } + public constructor() { + super("AddSecretKeyToIncomingRequestMonitor"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const monitors: Array = await MonitorService.findBy({ - query: { - monitorType: MonitorType.IncomingRequest, - }, - select: { - _id: true, - incomingRequestSecretKey: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, + const monitors: Array = await MonitorService.findBy({ + query: { + monitorType: MonitorType.IncomingRequest, + }, + select: { + _id: true, + incomingRequestSecretKey: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const monitor of monitors) { + if (!monitor.incomingRequestSecretKey) { + await MonitorService.updateOneById({ + id: monitor.id!, + data: { + incomingRequestSecretKey: monitor.id!, //same as id for backward compatibility + }, + props: { + isRoot: true, + }, }); - - for (const monitor of monitors) { - if (!monitor.incomingRequestSecretKey) { - await MonitorService.updateOneById({ - id: monitor.id!, - data: { - incomingRequestSecretKey: monitor.id!, //same as id for backward compatibility - }, - props: { - isRoot: true, - }, - }); - } - } + } } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddStartDateToIncidentStateTimeline.ts b/App/FeatureSet/Workers/DataMigrations/AddStartDateToIncidentStateTimeline.ts index e5589868b9..946b5975c4 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddStartDateToIncidentStateTimeline.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddStartDateToIncidentStateTimeline.ts @@ -1,93 +1,89 @@ -import DataMigrationBase from './DataMigrationBase'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import IncidentStateTimelineService from 'CommonServer/Services/IncidentStateTimelineService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Incident from 'Model/Models/Incident'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import Project from 'Model/Models/Project'; +import DataMigrationBase from "./DataMigrationBase"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import IncidentService from "CommonServer/Services/IncidentService"; +import IncidentStateTimelineService from "CommonServer/Services/IncidentStateTimelineService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Incident from "Model/Models/Incident"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import Project from "Model/Models/Project"; export default class AddStartDateToIncidentStateTimeline extends DataMigrationBase { - public constructor() { - super('AddStartDateToIncidentStateTimeline'); - } + public constructor() { + super("AddStartDateToIncidentStateTimeline"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const projects: Array = await ProjectService.findBy({ - query: {}, + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + // add ended scheduled maintenance state for each of these projects. + // first fetch resolved state. Ended state order is -1 of resolved state. + + const incidents: Array = await IncidentService.findBy({ + query: { + projectId: project.id!, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const incident of incidents) { + const incidentStateTimelines: Array = + await IncidentStateTimelineService.findBy({ + query: { + incidentId: incident.id!, + startsAt: QueryHelper.isNull(), + }, select: { - _id: true, + _id: true, + createdAt: true, }, skip: 0, limit: LIMIT_MAX, props: { - isRoot: true, + isRoot: true, }, - }); + sort: { + createdAt: SortOrder.Ascending, + }, + }); - for (const project of projects) { - // add ended scheduled maintenance state for each of these projects. - // first fetch resolved state. Ended state order is -1 of resolved state. - - const incidents: Array = await IncidentService.findBy({ - query: { - projectId: project.id!, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const incident of incidents) { - const incidentStateTimelines: Array = - await IncidentStateTimelineService.findBy({ - query: { - incidentId: incident.id!, - startsAt: QueryHelper.isNull(), - }, - select: { - _id: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - sort: { - createdAt: SortOrder.Ascending, - }, - }); - - for ( - let i: number = 0; - i < incidentStateTimelines.length; - i++ - ) { - await IncidentStateTimelineService.updateOneById({ - id: incidentStateTimelines[i]!.id!, - data: { - startsAt: incidentStateTimelines[i]!.createdAt!, - }, - props: { - isRoot: true, - }, - }); - } - } + for (let i: number = 0; i < incidentStateTimelines.length; i++) { + await IncidentStateTimelineService.updateOneById({ + id: incidentStateTimelines[i]!.id!, + data: { + startsAt: incidentStateTimelines[i]!.createdAt!, + }, + props: { + isRoot: true, + }, + }); } + } } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddStartDateToMonitorStatusTimeline.ts b/App/FeatureSet/Workers/DataMigrations/AddStartDateToMonitorStatusTimeline.ts index ce2b382d40..746185a880 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddStartDateToMonitorStatusTimeline.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddStartDateToMonitorStatusTimeline.ts @@ -1,92 +1,92 @@ -import DataMigrationBase from './DataMigrationBase'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import MonitorService from 'CommonServer/Services/MonitorService'; -import MonitorStatusTimelineService from 'CommonServer/Services/MonitorStatusTimelineService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import Project from 'Model/Models/Project'; +import DataMigrationBase from "./DataMigrationBase"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import MonitorService from "CommonServer/Services/MonitorService"; +import MonitorStatusTimelineService from "CommonServer/Services/MonitorStatusTimelineService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import Project from "Model/Models/Project"; export default class AddStartDateToMonitorStatusTimeline extends DataMigrationBase { - public constructor() { - super('AddStartDateToMonitorStatusTimeline'); - } + public constructor() { + super("AddStartDateToMonitorStatusTimeline"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const projects: Array = await ProjectService.findBy({ - query: {}, + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + // add ended scheduled maintenance state for each of these projects. + // first fetch resolved state. Ended state order is -1 of resolved state. + + const monitors: Array = await MonitorService.findBy({ + query: { + projectId: project.id!, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const monitor of monitors) { + const statusTimelines: Array = + await MonitorStatusTimelineService.findBy({ + query: { + monitorId: monitor.id!, + startsAt: QueryHelper.isNull(), + }, select: { - _id: true, + _id: true, + createdAt: true, }, skip: 0, limit: LIMIT_MAX, props: { - isRoot: true, + isRoot: true, }, - }); + sort: { + createdAt: SortOrder.Ascending, + }, + }); - for (const project of projects) { - // add ended scheduled maintenance state for each of these projects. - // first fetch resolved state. Ended state order is -1 of resolved state. + for (let i: number = 0; i < statusTimelines.length; i++) { + const statusTimeline: MonitorStatusTimeline | undefined = + statusTimelines[i]; - const monitors: Array = await MonitorService.findBy({ - query: { - projectId: project.id!, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const monitor of monitors) { - const statusTimelines: Array = - await MonitorStatusTimelineService.findBy({ - query: { - monitorId: monitor.id!, - startsAt: QueryHelper.isNull(), - }, - select: { - _id: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - sort: { - createdAt: SortOrder.Ascending, - }, - }); - - for (let i: number = 0; i < statusTimelines.length; i++) { - const statusTimeline: MonitorStatusTimeline | undefined = - statusTimelines[i]; - - await MonitorStatusTimelineService.updateOneById({ - id: statusTimeline!.id!, - data: { - startsAt: statusTimeline!.createdAt!, - }, - props: { - isRoot: true, - }, - }); - } - } + await MonitorStatusTimelineService.updateOneById({ + id: statusTimeline!.id!, + data: { + startsAt: statusTimeline!.createdAt!, + }, + props: { + isRoot: true, + }, + }); } + } } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddStartDateToScheduledEventsStateTimeline.ts b/App/FeatureSet/Workers/DataMigrations/AddStartDateToScheduledEventsStateTimeline.ts index f1b62eaf80..0e8ac477c8 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddStartDateToScheduledEventsStateTimeline.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddStartDateToScheduledEventsStateTimeline.ts @@ -1,100 +1,97 @@ -import DataMigrationBase from './DataMigrationBase'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import ScheduledMaintenanceStateTimelineService from 'CommonServer/Services/ScheduledMaintenanceStateTimelineService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Project from 'Model/Models/Project'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; +import DataMigrationBase from "./DataMigrationBase"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import ProjectService from "CommonServer/Services/ProjectService"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import ScheduledMaintenanceStateTimelineService from "CommonServer/Services/ScheduledMaintenanceStateTimelineService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Project from "Model/Models/Project"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; export default class AddStartDateToScheduledEventsStateTimeline extends DataMigrationBase { - public constructor() { - super('AddStartDateToScheduledEventsStateTimeline'); - } + public constructor() { + super("AddStartDateToScheduledEventsStateTimeline"); + } - public override async migrate(): Promise { - // get all the users with email isVerified true. + public override async migrate(): Promise { + // get all the users with email isVerified true. - const projects: Array = await ProjectService.findBy({ - query: {}, + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + // add ended scheduled maintenance state for each of these projects. + // first fetch resolved state. Ended state order is -1 of resolved state. + + const scheduledEvents: Array = + await ScheduledMaintenanceService.findBy({ + query: { + projectId: project.id!, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const scheduledEvent of scheduledEvents) { + const scheduledMaintenanceStateTimelines: Array = + await ScheduledMaintenanceStateTimelineService.findBy({ + query: { + scheduledMaintenanceId: scheduledEvent.id!, + startsAt: QueryHelper.isNull(), + }, select: { - _id: true, + _id: true, + createdAt: true, }, skip: 0, limit: LIMIT_MAX, props: { - isRoot: true, + isRoot: true, }, - }); + sort: { + createdAt: SortOrder.Ascending, + }, + }); - for (const project of projects) { - // add ended scheduled maintenance state for each of these projects. - // first fetch resolved state. Ended state order is -1 of resolved state. + for ( + let i: number = 0; + i < scheduledMaintenanceStateTimelines.length; + i++ + ) { + const statusTimeline: ScheduledMaintenanceStateTimeline | undefined = + scheduledMaintenanceStateTimelines[i]; - const scheduledEvents: Array = - await ScheduledMaintenanceService.findBy({ - query: { - projectId: project.id!, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const scheduledEvent of scheduledEvents) { - const scheduledMaintenanceStateTimelines: Array = - await ScheduledMaintenanceStateTimelineService.findBy({ - query: { - scheduledMaintenanceId: scheduledEvent.id!, - startsAt: QueryHelper.isNull(), - }, - select: { - _id: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - sort: { - createdAt: SortOrder.Ascending, - }, - }); - - for ( - let i: number = 0; - i < scheduledMaintenanceStateTimelines.length; - i++ - ) { - const statusTimeline: - | ScheduledMaintenanceStateTimeline - | undefined = scheduledMaintenanceStateTimelines[i]; - - await ScheduledMaintenanceStateTimelineService.updateOneById( - { - id: statusTimeline!.id!, - data: { - startsAt: statusTimeline!.createdAt!, - }, - props: { - isRoot: true, - }, - } - ); - } - } + await ScheduledMaintenanceStateTimelineService.updateOneById({ + id: statusTimeline!.id!, + data: { + startsAt: statusTimeline!.createdAt!, + }, + props: { + isRoot: true, + }, + }); } + } } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddTelemetryServiceColor.ts b/App/FeatureSet/Workers/DataMigrations/AddTelemetryServiceColor.ts index f4415fe550..e213f2e1d1 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddTelemetryServiceColor.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddTelemetryServiceColor.ts @@ -1,50 +1,49 @@ -import DataMigrationBase from './DataMigrationBase'; -import ArrayUtil from 'Common/Types/ArrayUtil'; -import { BrightColors } from 'Common/Types/BrandColors'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import TelemetryServiceService from 'CommonServer/Services/TelemetryServiceService'; -import TelemetryService from 'Model/Models/TelemetryService'; +import DataMigrationBase from "./DataMigrationBase"; +import ArrayUtil from "Common/Types/ArrayUtil"; +import { BrightColors } from "Common/Types/BrandColors"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import TelemetryServiceService from "CommonServer/Services/TelemetryServiceService"; +import TelemetryService from "Model/Models/TelemetryService"; export default class AddTelemetryServiceColor extends DataMigrationBase { - public constructor() { - super('AddTelemetryServiceColor'); + public constructor() { + super("AddTelemetryServiceColor"); + } + + public override async migrate(): Promise { + // get all the users with email isVerified true. + + const services: Array = + await TelemetryServiceService.findBy({ + query: {}, + select: { + _id: true, + serviceColor: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const service of services) { + if (!service.serviceColor) { + (service.serviceColor = ArrayUtil.selectItemByRandom(BrightColors)), + await TelemetryServiceService.updateOneById({ + id: service.id!, + data: { + serviceColor: service.serviceColor, + }, + props: { + isRoot: true, + }, + }); + } } + } - public override async migrate(): Promise { - // get all the users with email isVerified true. - - const services: Array = - await TelemetryServiceService.findBy({ - query: {}, - select: { - _id: true, - serviceColor: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const service of services) { - if (!service.serviceColor) { - (service.serviceColor = - ArrayUtil.selectItemByRandom(BrightColors)), - await TelemetryServiceService.updateOneById({ - id: service.id!, - data: { - serviceColor: service.serviceColor, - }, - props: { - isRoot: true, - }, - }); - } - } - } - - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/AddUnitColumnToMetricsTable.ts b/App/FeatureSet/Workers/DataMigrations/AddUnitColumnToMetricsTable.ts index 1896d664dd..b612767a48 100644 --- a/App/FeatureSet/Workers/DataMigrations/AddUnitColumnToMetricsTable.ts +++ b/App/FeatureSet/Workers/DataMigrations/AddUnitColumnToMetricsTable.ts @@ -1,39 +1,39 @@ -import DataMigrationBase from './DataMigrationBase'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import MetricService from 'CommonServer/Services/MetricService'; -import Metric from 'Model/AnalyticsModels/Metric'; +import DataMigrationBase from "./DataMigrationBase"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import MetricService from "CommonServer/Services/MetricService"; +import Metric from "Model/AnalyticsModels/Metric"; export default class AddUnitColumnToMetricsTable extends DataMigrationBase { - public constructor() { - super('AddUnitColumnToMetricsTable'); + public constructor() { + super("AddUnitColumnToMetricsTable"); + } + + public override async migrate(): Promise { + await this.addUnitColumnToMetricsTable(); + } + + public async addUnitColumnToMetricsTable(): Promise { + // logs + const unitColumn: AnalyticsTableColumn | undefined = + new Metric().tableColumns.find((column: AnalyticsTableColumn) => { + return column.key === "unit"; + }); + + if (!unitColumn) { + return; } - public override async migrate(): Promise { - await this.addUnitColumnToMetricsTable(); + const columnType: TableColumnType | null = + await MetricService.getColumnTypeInDatabase(unitColumn); + + if (!columnType) { + await MetricService.dropColumnInDatabase("unit"); + await MetricService.addColumnInDatabase(unitColumn); } + } - public async addUnitColumnToMetricsTable(): Promise { - // logs - const unitColumn: AnalyticsTableColumn | undefined = - new Metric().tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === 'unit'; - }); - - if (!unitColumn) { - return; - } - - const columnType: TableColumnType | null = - await MetricService.getColumnTypeInDatabase(unitColumn); - - if (!columnType) { - await MetricService.dropColumnInDatabase('unit'); - await MetricService.addColumnInDatabase(unitColumn); - } - } - - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/ChangeLogSeverityColumnTypeFromTextToNumber.ts b/App/FeatureSet/Workers/DataMigrations/ChangeLogSeverityColumnTypeFromTextToNumber.ts index c48da5f4b0..3240ed0ca9 100644 --- a/App/FeatureSet/Workers/DataMigrations/ChangeLogSeverityColumnTypeFromTextToNumber.ts +++ b/App/FeatureSet/Workers/DataMigrations/ChangeLogSeverityColumnTypeFromTextToNumber.ts @@ -1,34 +1,34 @@ -import DataMigrationBase from './DataMigrationBase'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import LogService from 'CommonServer/Services/LogService'; -import Log from 'Model/AnalyticsModels/Log'; +import DataMigrationBase from "./DataMigrationBase"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import LogService from "CommonServer/Services/LogService"; +import Log from "Model/AnalyticsModels/Log"; export default class ChangeLogSeverityColumnTypeFromTextToNumber extends DataMigrationBase { - public constructor() { - super('ChangeLogSeverityColumnTypeFromTextToNumber'); + public constructor() { + super("ChangeLogSeverityColumnTypeFromTextToNumber"); + } + + public override async migrate(): Promise { + const logSeverityNumberColumn: AnalyticsTableColumn | undefined = + new Log().tableColumns.find((column: AnalyticsTableColumn) => { + return column.key === "severityNumber"; + }); + + if (!logSeverityNumberColumn) { + return; } - public override async migrate(): Promise { - const logSeverityNumberColumn: AnalyticsTableColumn | undefined = - new Log().tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === 'severityNumber'; - }); + const columnType: TableColumnType | null = + await LogService.getColumnTypeInDatabase(logSeverityNumberColumn); - if (!logSeverityNumberColumn) { - return; - } - - const columnType: TableColumnType | null = - await LogService.getColumnTypeInDatabase(logSeverityNumberColumn); - - if (!columnType || columnType === TableColumnType.Text) { - await LogService.dropColumnInDatabase('severityNumber'); - await LogService.addColumnInDatabase(logSeverityNumberColumn); - } + if (!columnType || columnType === TableColumnType.Text) { + await LogService.dropColumnInDatabase("severityNumber"); + await LogService.addColumnInDatabase(logSeverityNumberColumn); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/ChangeMetricColumnTypesToDecimal.ts b/App/FeatureSet/Workers/DataMigrations/ChangeMetricColumnTypesToDecimal.ts index 7da49f4876..f38e9bd6ad 100644 --- a/App/FeatureSet/Workers/DataMigrations/ChangeMetricColumnTypesToDecimal.ts +++ b/App/FeatureSet/Workers/DataMigrations/ChangeMetricColumnTypesToDecimal.ts @@ -1,42 +1,42 @@ -import DataMigrationBase from './DataMigrationBase'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import MetricService from 'CommonServer/Services/MetricService'; -import Metric from 'Model/AnalyticsModels/Metric'; +import DataMigrationBase from "./DataMigrationBase"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import MetricService from "CommonServer/Services/MetricService"; +import Metric from "Model/AnalyticsModels/Metric"; export default class ChangeMetricColumnTypeToDecimal extends DataMigrationBase { - public constructor() { - super('ChangeMetricColumnTypeToDecimal'); + public constructor() { + super("ChangeMetricColumnTypeToDecimal"); + } + + public override async migrate(): Promise { + await this.dropAndCreateColumn("value"); + await this.dropAndCreateColumn("sum"); + await this.dropAndCreateColumn("min"); + await this.dropAndCreateColumn("max"); + } + + public async dropAndCreateColumn(columnName: string): Promise { + const column: AnalyticsTableColumn | undefined = + new Metric().tableColumns.find((column: AnalyticsTableColumn) => { + return column.key === columnName; + }); + + if (!column) { + return; } - public override async migrate(): Promise { - await this.dropAndCreateColumn('value'); - await this.dropAndCreateColumn('sum'); - await this.dropAndCreateColumn('min'); - await this.dropAndCreateColumn('max'); + const columnType: TableColumnType | null = + await MetricService.getColumnTypeInDatabase(column); + + if (columnType) { + await MetricService.dropColumnInDatabase(columnName); } - public async dropAndCreateColumn(columnName: string): Promise { - const column: AnalyticsTableColumn | undefined = - new Metric().tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === columnName; - }); + await MetricService.addColumnInDatabase(column); + } - if (!column) { - return; - } - - const columnType: TableColumnType | null = - await MetricService.getColumnTypeInDatabase(column); - - if (columnType) { - await MetricService.dropColumnInDatabase(columnName); - } - - await MetricService.addColumnInDatabase(column); - } - - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/DataMigrationBase.ts b/App/FeatureSet/Workers/DataMigrations/DataMigrationBase.ts index b4194bbb38..70c8e1ac98 100644 --- a/App/FeatureSet/Workers/DataMigrations/DataMigrationBase.ts +++ b/App/FeatureSet/Workers/DataMigrations/DataMigrationBase.ts @@ -1,23 +1,23 @@ -import NotImplementedException from 'Common/Types/Exception/NotImplementedException'; +import NotImplementedException from "Common/Types/Exception/NotImplementedException"; export default class DataMigrationBase { - private _name: string = ''; - public get name(): string { - return this._name; - } - public set name(v: string) { - this._name = v; - } + private _name: string = ""; + public get name(): string { + return this._name; + } + public set name(v: string) { + this._name = v; + } - public constructor(name: string) { - this.name = name; - } + public constructor(name: string) { + this.name = name; + } - public async migrate(): Promise { - throw new NotImplementedException(); - } + public async migrate(): Promise { + throw new NotImplementedException(); + } - public async rollback(): Promise { - throw new NotImplementedException(); - } + public async rollback(): Promise { + throw new NotImplementedException(); + } } diff --git a/App/FeatureSet/Workers/DataMigrations/GenerateNewCertsForStatusPage.ts b/App/FeatureSet/Workers/DataMigrations/GenerateNewCertsForStatusPage.ts index 23fc8c49cb..ad307e55d1 100644 --- a/App/FeatureSet/Workers/DataMigrations/GenerateNewCertsForStatusPage.ts +++ b/App/FeatureSet/Workers/DataMigrations/GenerateNewCertsForStatusPage.ts @@ -1,43 +1,43 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService'; -import logger from 'CommonServer/Utils/Logger'; -import StatusPageDomain from 'Model/Models/StatusPageDomain'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import StatusPageDomainService from "CommonServer/Services/StatusPageDomainService"; +import logger from "CommonServer/Utils/Logger"; +import StatusPageDomain from "Model/Models/StatusPageDomain"; export default class GenerateNewCertsForStatusPage extends DataMigrationBase { - public constructor() { - super('GenerateNewCertsForStatusPage'); + public constructor() { + super("GenerateNewCertsForStatusPage"); + } + + public override async migrate(): Promise { + // get all domains in greenlock certs. + const statusPageDomains: Array = + await StatusPageDomainService.findBy({ + query: {}, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + }, + }); + + // now order these domains + + for (const statusPageDomain of statusPageDomains) { + // get status page domain. + + try { + await StatusPageDomainService.orderCert(statusPageDomain); + } catch (e) { + logger.error(e); + } } + } - public override async migrate(): Promise { - // get all domains in greenlock certs. - const statusPageDomains: Array = - await StatusPageDomainService.findBy({ - query: {}, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - }, - }); - - // now order these domains - - for (const statusPageDomain of statusPageDomains) { - // get status page domain. - - try { - await StatusPageDomainService.orderCert(statusPageDomain); - } catch (e) { - logger.error(e); - } - } - } - - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/Index.ts b/App/FeatureSet/Workers/DataMigrations/Index.ts index 2d275c525b..c3835efeff 100644 --- a/App/FeatureSet/Workers/DataMigrations/Index.ts +++ b/App/FeatureSet/Workers/DataMigrations/Index.ts @@ -1,72 +1,72 @@ -import AddAggregationTemporalityToMetric from './AddAggregationTemporalityToMetric'; -import AddAttributeColumnToSpanAndLog from './AddAttributesColumnToSpanAndLog'; -import AddDefaultGlobalConfig from './AddDefaultGlobalConfig'; -import AddDowntimeMonitorStatusToStatusPage from './AddDowntimeMonitorStatusToStatusPage'; -import AddDurationColumnToSpanTable from './AddDurationColumnToSpanTable'; -import AddEndDateToIncidentStateTimeline from './AddEndDateToIncidentStateTimeline'; -import AddEndDateToMonitorStatusTimeline from './AddEndDateToMonitorStatusTimeline'; -import AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing from './AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing'; -import AddEndDateToScheduledEventsStateTimeline from './AddEndDateToScheduledEventsStateTimeline'; -import AddEndedState from './AddEndedState'; -import AddIsMonotonicToMetric from './AddIsMonotonicToMetric'; -import AddMonitoringDatesToMonitor from './AddMonitoringDatesToMonitors'; -import AddOwnerInfoToProjects from './AddOwnerInfoToProject'; -import AddPointTypeToMetric from './AddPointTypeToMetric'; -import AddPostedAtToPublicNotes from './AddPostedAtToPublicNotes'; -import AddSecretKeyToIncomingRequestMonitor from './AddSecretKeyToIncomingRequestMonitor'; -import AddStartDateToIncidentStateTimeline from './AddStartDateToIncidentStateTimeline'; -import AddStartDateToMonitorStatusTimeline from './AddStartDateToMonitorStatusTimeline'; -import AddStartDateToScheduledEventsStateTimeline from './AddStartDateToScheduledEventsStateTimeline'; -import AddTelemetryServiceColor from './AddTelemetryServiceColor'; -import AddUnitColumnToMetricsTable from './AddUnitColumnToMetricsTable'; -import ChangeLogSeverityColumnTypeFromTextToNumber from './ChangeLogSeverityColumnTypeFromTextToNumber'; -import ChangeMetricColumnTypeToDecimal from './ChangeMetricColumnTypesToDecimal'; -import DataMigrationBase from './DataMigrationBase'; -import GenerateNewCertsForStatusPage from './GenerateNewCertsForStatusPage'; -import MigrateDefaultUserNotificationRule from './MigrateDefaultUserNotificationRule'; -import MigrateDefaultUserNotificationSetting from './MigrateDefaultUserSettingNotification'; -import MigrateToMeteredSubscription from './MigrateToMeteredSubscription'; -import MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage from './MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage'; -import MoveGreenlockCertsToAcmeCerts from './MoveGreenlockCertsToAcmeCerts'; -import RemoveCanFromPermissions from './RemoveCanFromPermissions'; -import UpdateActiveMonitorCountToBillingProvider from './UpdateActiveMonitorCountToBillingProvider'; -import UpdateGlobalConfigFromEnv from './UpdateGlobalCongfigFromEnv'; +import AddAggregationTemporalityToMetric from "./AddAggregationTemporalityToMetric"; +import AddAttributeColumnToSpanAndLog from "./AddAttributesColumnToSpanAndLog"; +import AddDefaultGlobalConfig from "./AddDefaultGlobalConfig"; +import AddDowntimeMonitorStatusToStatusPage from "./AddDowntimeMonitorStatusToStatusPage"; +import AddDurationColumnToSpanTable from "./AddDurationColumnToSpanTable"; +import AddEndDateToIncidentStateTimeline from "./AddEndDateToIncidentStateTimeline"; +import AddEndDateToMonitorStatusTimeline from "./AddEndDateToMonitorStatusTimeline"; +import AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing from "./AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing"; +import AddEndDateToScheduledEventsStateTimeline from "./AddEndDateToScheduledEventsStateTimeline"; +import AddEndedState from "./AddEndedState"; +import AddIsMonotonicToMetric from "./AddIsMonotonicToMetric"; +import AddMonitoringDatesToMonitor from "./AddMonitoringDatesToMonitors"; +import AddOwnerInfoToProjects from "./AddOwnerInfoToProject"; +import AddPointTypeToMetric from "./AddPointTypeToMetric"; +import AddPostedAtToPublicNotes from "./AddPostedAtToPublicNotes"; +import AddSecretKeyToIncomingRequestMonitor from "./AddSecretKeyToIncomingRequestMonitor"; +import AddStartDateToIncidentStateTimeline from "./AddStartDateToIncidentStateTimeline"; +import AddStartDateToMonitorStatusTimeline from "./AddStartDateToMonitorStatusTimeline"; +import AddStartDateToScheduledEventsStateTimeline from "./AddStartDateToScheduledEventsStateTimeline"; +import AddTelemetryServiceColor from "./AddTelemetryServiceColor"; +import AddUnitColumnToMetricsTable from "./AddUnitColumnToMetricsTable"; +import ChangeLogSeverityColumnTypeFromTextToNumber from "./ChangeLogSeverityColumnTypeFromTextToNumber"; +import ChangeMetricColumnTypeToDecimal from "./ChangeMetricColumnTypesToDecimal"; +import DataMigrationBase from "./DataMigrationBase"; +import GenerateNewCertsForStatusPage from "./GenerateNewCertsForStatusPage"; +import MigrateDefaultUserNotificationRule from "./MigrateDefaultUserNotificationRule"; +import MigrateDefaultUserNotificationSetting from "./MigrateDefaultUserSettingNotification"; +import MigrateToMeteredSubscription from "./MigrateToMeteredSubscription"; +import MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage from "./MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage"; +import MoveGreenlockCertsToAcmeCerts from "./MoveGreenlockCertsToAcmeCerts"; +import RemoveCanFromPermissions from "./RemoveCanFromPermissions"; +import UpdateActiveMonitorCountToBillingProvider from "./UpdateActiveMonitorCountToBillingProvider"; +import UpdateGlobalConfigFromEnv from "./UpdateGlobalCongfigFromEnv"; // This is the order in which the migrations will be run. Add new migrations to the end of the array. const DataMigrations: Array = [ - new MigrateDefaultUserNotificationRule(), - new AddOwnerInfoToProjects(), - new MigrateDefaultUserNotificationSetting(), - new MigrateToMeteredSubscription(), - new UpdateActiveMonitorCountToBillingProvider(), - new AddMonitoringDatesToMonitor(), - new AddEndedState(), - new AddDefaultGlobalConfig(), - new UpdateGlobalConfigFromEnv(), - new AddPostedAtToPublicNotes(), - new MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage(), - new AddDowntimeMonitorStatusToStatusPage(), - new AddEndDateToMonitorStatusTimeline(), - new AddEndDateToScheduledEventsStateTimeline(), - new AddEndDateToIncidentStateTimeline(), - new AddStartDateToIncidentStateTimeline(), - new AddStartDateToMonitorStatusTimeline(), - new AddStartDateToScheduledEventsStateTimeline(), - new AddDurationColumnToSpanTable(), - new ChangeLogSeverityColumnTypeFromTextToNumber(), - new AddAttributeColumnToSpanAndLog(), - new AddSecretKeyToIncomingRequestMonitor(), - new AddTelemetryServiceColor(), - new MoveGreenlockCertsToAcmeCerts(), - new GenerateNewCertsForStatusPage(), - new AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing(), - new RemoveCanFromPermissions(), - new AddUnitColumnToMetricsTable(), - new ChangeMetricColumnTypeToDecimal(), - new AddAggregationTemporalityToMetric(), - new AddPointTypeToMetric(), - new AddIsMonotonicToMetric(), + new MigrateDefaultUserNotificationRule(), + new AddOwnerInfoToProjects(), + new MigrateDefaultUserNotificationSetting(), + new MigrateToMeteredSubscription(), + new UpdateActiveMonitorCountToBillingProvider(), + new AddMonitoringDatesToMonitor(), + new AddEndedState(), + new AddDefaultGlobalConfig(), + new UpdateGlobalConfigFromEnv(), + new AddPostedAtToPublicNotes(), + new MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage(), + new AddDowntimeMonitorStatusToStatusPage(), + new AddEndDateToMonitorStatusTimeline(), + new AddEndDateToScheduledEventsStateTimeline(), + new AddEndDateToIncidentStateTimeline(), + new AddStartDateToIncidentStateTimeline(), + new AddStartDateToMonitorStatusTimeline(), + new AddStartDateToScheduledEventsStateTimeline(), + new AddDurationColumnToSpanTable(), + new ChangeLogSeverityColumnTypeFromTextToNumber(), + new AddAttributeColumnToSpanAndLog(), + new AddSecretKeyToIncomingRequestMonitor(), + new AddTelemetryServiceColor(), + new MoveGreenlockCertsToAcmeCerts(), + new GenerateNewCertsForStatusPage(), + new AddEndDateToMonitorStatusTimelineWhereEndDateIsMissing(), + new RemoveCanFromPermissions(), + new AddUnitColumnToMetricsTable(), + new ChangeMetricColumnTypeToDecimal(), + new AddAggregationTemporalityToMetric(), + new AddPointTypeToMetric(), + new AddIsMonotonicToMetric(), ]; export default DataMigrations; diff --git a/App/FeatureSet/Workers/DataMigrations/MigrateDefaultUserNotificationRule.ts b/App/FeatureSet/Workers/DataMigrations/MigrateDefaultUserNotificationRule.ts index b2b219eac2..512b427730 100644 --- a/App/FeatureSet/Workers/DataMigrations/MigrateDefaultUserNotificationRule.ts +++ b/App/FeatureSet/Workers/DataMigrations/MigrateDefaultUserNotificationRule.ts @@ -1,62 +1,61 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import TeamMemberService from 'CommonServer/Services/TeamMemberService'; -import UserNotificationRuleService from 'CommonServer/Services/UserNotificationRuleService'; -import UserService from 'CommonServer/Services/UserService'; -import TeamMember from 'Model/Models/TeamMember'; -import User from 'Model/Models/User'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import TeamMemberService from "CommonServer/Services/TeamMemberService"; +import UserNotificationRuleService from "CommonServer/Services/UserNotificationRuleService"; +import UserService from "CommonServer/Services/UserService"; +import TeamMember from "Model/Models/TeamMember"; +import User from "Model/Models/User"; export default class MigrateDefaultUserNotificationRule extends DataMigrationBase { - public constructor() { - super('MigrateDefaultUserNotificationRule'); + public constructor() { + super("MigrateDefaultUserNotificationRule"); + } + + public override async migrate(): Promise { + // get all the users with email isVerified true. + + const users: Array = await UserService.findBy({ + query: { + isEmailVerified: true, + }, + select: { + email: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const user of users) { + // then get all the projects the user belongs to. + const teamMembers: Array = await TeamMemberService.findBy({ + query: { + userId: user.id!, + hasAcceptedInvitation: true, + }, + select: { + projectId: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const teamMember of teamMembers) { + await UserNotificationRuleService.addDefaultNotificationRuleForUser( + teamMember.projectId!, + user.id!, + user.email!, + ); + } } + } - public override async migrate(): Promise { - // get all the users with email isVerified true. - - const users: Array = await UserService.findBy({ - query: { - isEmailVerified: true, - }, - select: { - email: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const user of users) { - // then get all the projects the user belongs to. - const teamMembers: Array = - await TeamMemberService.findBy({ - query: { - userId: user.id!, - hasAcceptedInvitation: true, - }, - select: { - projectId: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const teamMember of teamMembers) { - await UserNotificationRuleService.addDefaultNotificationRuleForUser( - teamMember.projectId!, - user.id!, - user.email! - ); - } - } - } - - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/MigrateDefaultUserSettingNotification.ts b/App/FeatureSet/Workers/DataMigrations/MigrateDefaultUserSettingNotification.ts index 7a681c59ec..31af5affda 100644 --- a/App/FeatureSet/Workers/DataMigrations/MigrateDefaultUserSettingNotification.ts +++ b/App/FeatureSet/Workers/DataMigrations/MigrateDefaultUserSettingNotification.ts @@ -1,61 +1,60 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import TeamMemberService from 'CommonServer/Services/TeamMemberService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import UserService from 'CommonServer/Services/UserService'; -import TeamMember from 'Model/Models/TeamMember'; -import User from 'Model/Models/User'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import TeamMemberService from "CommonServer/Services/TeamMemberService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import UserService from "CommonServer/Services/UserService"; +import TeamMember from "Model/Models/TeamMember"; +import User from "Model/Models/User"; export default class MigrateDefaultUserNotificationSetting extends DataMigrationBase { - public constructor() { - super('MigrateDefaultUserNotificationSetting'); + public constructor() { + super("MigrateDefaultUserNotificationSetting"); + } + + public override async migrate(): Promise { + // get all the users with email isVerified true. + + const users: Array = await UserService.findBy({ + query: { + isEmailVerified: true, + }, + select: { + email: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const user of users) { + // then get all the projects the user belongs to. + const teamMembers: Array = await TeamMemberService.findBy({ + query: { + userId: user.id!, + hasAcceptedInvitation: true, + }, + select: { + projectId: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const teamMember of teamMembers) { + await UserNotificationSettingService.addDefaultNotificationSettingsForUser( + user.id!, + teamMember.projectId!, + ); + } } + } - public override async migrate(): Promise { - // get all the users with email isVerified true. - - const users: Array = await UserService.findBy({ - query: { - isEmailVerified: true, - }, - select: { - email: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const user of users) { - // then get all the projects the user belongs to. - const teamMembers: Array = - await TeamMemberService.findBy({ - query: { - userId: user.id!, - hasAcceptedInvitation: true, - }, - select: { - projectId: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const teamMember of teamMembers) { - await UserNotificationSettingService.addDefaultNotificationSettingsForUser( - user.id!, - teamMember.projectId! - ); - } - } - } - - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/MigrateToMeteredSubscription.ts b/App/FeatureSet/Workers/DataMigrations/MigrateToMeteredSubscription.ts index d5a047752e..d4853529b4 100644 --- a/App/FeatureSet/Workers/DataMigrations/MigrateToMeteredSubscription.ts +++ b/App/FeatureSet/Workers/DataMigrations/MigrateToMeteredSubscription.ts @@ -1,99 +1,97 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Sleep from 'Common/Types/Sleep'; -import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Sleep from "Common/Types/Sleep"; +import { IsBillingEnabled } from "CommonServer/EnvironmentConfig"; import BillingService, { - SubscriptionItem, -} from 'CommonServer/Services/BillingService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import AllMeteredPlans from 'CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Project from 'Model/Models/Project'; + SubscriptionItem, +} from "CommonServer/Services/BillingService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import AllMeteredPlans from "CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Project from "Model/Models/Project"; export default class MigrateToMeteredSubscription extends DataMigrationBase { - public constructor() { - super('MigrateToMeteredSubscription'); + public constructor() { + super("MigrateToMeteredSubscription"); + } + + public override async migrate(): Promise { + if (!IsBillingEnabled) { + return; } - public override async migrate(): Promise { - if (!IsBillingEnabled) { - return; + const projects: Array = await ProjectService.findBy({ + query: { + paymentProviderMeteredSubscriptionId: QueryHelper.isNull(), + }, + select: { + _id: true, + paymentProviderSubscriptionId: true, + paymentProviderCustomerId: true, + trialEndsAt: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + if (!project.paymentProviderSubscriptionId) { + continue; + } + + if (!project.paymentProviderCustomerId) { + continue; + } + + // remove subscription item. + const subscriptionItems: Array = + await BillingService.getSubscriptionItems( + project.paymentProviderSubscriptionId, + ); + + for (const subscriptionItem of subscriptionItems) { + if ( + subscriptionItem.plan.id === "price_1N6Cg9ANuQdJ93r7veN7YgsH" || + subscriptionItem.plan.id === "price_1N6B9EANuQdJ93r7fj3bhcWP" + ) { + await BillingService.removeSubscriptionItem( + project.paymentProviderSubscriptionId, + subscriptionItem.id, + true, + ); } + } - const projects: Array = await ProjectService.findBy({ - query: { - paymentProviderMeteredSubscriptionId: QueryHelper.isNull(), - }, - select: { - _id: true, - paymentProviderSubscriptionId: true, - paymentProviderCustomerId: true, - trialEndsAt: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); + // add metered subscription item and update metered quantity. + const meteredPlan: { + meteredSubscriptionId: string; + } = await BillingService.subscribeToMeteredPlan({ + projectId: project.id!, + customerId: project.paymentProviderCustomerId!, + serverMeteredPlans: AllMeteredPlans, + trialDate: project.trialEndsAt || null, + }); - for (const project of projects) { - if (!project.paymentProviderSubscriptionId) { - continue; - } + // update project with metered subscription id. + await ProjectService.updateOneById({ + id: project.id!, + data: { + paymentProviderMeteredSubscriptionId: + meteredPlan.meteredSubscriptionId, + }, + props: { + isRoot: true, + }, + }); - if (!project.paymentProviderCustomerId) { - continue; - } - - // remove subscription item. - const subscriptionItems: Array = - await BillingService.getSubscriptionItems( - project.paymentProviderSubscriptionId - ); - - for (const subscriptionItem of subscriptionItems) { - if ( - subscriptionItem.plan.id === - 'price_1N6Cg9ANuQdJ93r7veN7YgsH' || - subscriptionItem.plan.id === - 'price_1N6B9EANuQdJ93r7fj3bhcWP' - ) { - await BillingService.removeSubscriptionItem( - project.paymentProviderSubscriptionId, - subscriptionItem.id, - true - ); - } - } - - // add metered subscription item and update metered quantity. - const meteredPlan: { - meteredSubscriptionId: string; - } = await BillingService.subscribeToMeteredPlan({ - projectId: project.id!, - customerId: project.paymentProviderCustomerId!, - serverMeteredPlans: AllMeteredPlans, - trialDate: project.trialEndsAt || null, - }); - - // update project with metered subscription id. - await ProjectService.updateOneById({ - id: project.id!, - data: { - paymentProviderMeteredSubscriptionId: - meteredPlan.meteredSubscriptionId, - }, - props: { - isRoot: true, - }, - }); - - await Sleep.sleep(500); - } + await Sleep.sleep(500); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage.ts b/App/FeatureSet/Workers/DataMigrations/MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage.ts index 9a8a3e0943..9a3474ea0f 100644 --- a/App/FeatureSet/Workers/DataMigrations/MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage.ts +++ b/App/FeatureSet/Workers/DataMigrations/MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage.ts @@ -1,50 +1,50 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import StatusPage from 'Model/Models/StatusPage'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import StatusPage from "Model/Models/StatusPage"; export default class MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage extends DataMigrationBase { - public constructor() { - super('AddPostedAtToPublicNotes'); + public constructor() { + super("AddPostedAtToPublicNotes"); + } + + public override async migrate(): Promise { + // get all the users with email isVerified true. + + const tempStatusPage: StatusPage = new StatusPage(); + + if (!tempStatusPage.getTableColumnMetadata("enableSubscribers")) { + // this column does not exist, so we can skip this migration. + return; } - public override async migrate(): Promise { - // get all the users with email isVerified true. + const statusPages: Array = await StatusPageService.findBy({ + query: {}, + select: { + _id: true, + enableSubscribers: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); - const tempStatusPage: StatusPage = new StatusPage(); - - if (!tempStatusPage.getTableColumnMetadata('enableSubscribers')) { - // this column does not exist, so we can skip this migration. - return; - } - - const statusPages: Array = await StatusPageService.findBy({ - query: {}, - select: { - _id: true, - enableSubscribers: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const statusPage of statusPages) { - await StatusPageService.updateOneById({ - id: statusPage.id!, - data: { - enableEmailSubscribers: statusPage.enableSubscribers!, - }, - props: { - isRoot: true, - }, - }); - } + for (const statusPage of statusPages) { + await StatusPageService.updateOneById({ + id: statusPage.id!, + data: { + enableEmailSubscribers: statusPage.enableSubscribers!, + }, + props: { + isRoot: true, + }, + }); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/MoveGreenlockCertsToAcmeCerts.ts b/App/FeatureSet/Workers/DataMigrations/MoveGreenlockCertsToAcmeCerts.ts index 1c9986f565..8cb6d8c15b 100644 --- a/App/FeatureSet/Workers/DataMigrations/MoveGreenlockCertsToAcmeCerts.ts +++ b/App/FeatureSet/Workers/DataMigrations/MoveGreenlockCertsToAcmeCerts.ts @@ -1,77 +1,77 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import GreenlockCertificateService from 'CommonServer/Services/GreenlockCertificateService'; -import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService'; -import logger from 'CommonServer/Utils/Logger'; -import GreenlockCertificate from 'Model/Models/GreenlockCertificate'; -import StatusPageDomain from 'Model/Models/StatusPageDomain'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import GreenlockCertificateService from "CommonServer/Services/GreenlockCertificateService"; +import StatusPageDomainService from "CommonServer/Services/StatusPageDomainService"; +import logger from "CommonServer/Utils/Logger"; +import GreenlockCertificate from "Model/Models/GreenlockCertificate"; +import StatusPageDomain from "Model/Models/StatusPageDomain"; export default class MoveGreenlockCertsToAcmeCerts extends DataMigrationBase { - public constructor() { - super('MoveGreenlockCertsToAcmeCerts'); + public constructor() { + super("MoveGreenlockCertsToAcmeCerts"); + } + + public override async migrate(): Promise { + const allDomains: Array = []; + + // get all domains in greenlock certs. + const greenlockCerts: GreenlockCertificate[] = + await GreenlockCertificateService.findBy({ + query: {}, + select: { + key: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const greenlockCert of greenlockCerts) { + if (!greenlockCert.key) { + continue; + } + + if (allDomains.includes(greenlockCert.key!)) { + // this domain already exists in acme certs. + continue; + } + + allDomains.push(greenlockCert.key!); } - public override async migrate(): Promise { - const allDomains: Array = []; + // now order these domains - // get all domains in greenlock certs. - const greenlockCerts: GreenlockCertificate[] = - await GreenlockCertificateService.findBy({ - query: {}, - select: { - key: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); + for (const domain of allDomains) { + // get status page domain. - for (const greenlockCert of greenlockCerts) { - if (!greenlockCert.key) { - continue; - } + const statusPageDomain: StatusPageDomain | null = + await StatusPageDomainService.findOneBy({ + query: { + fullDomain: domain, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); - if (allDomains.includes(greenlockCert.key!)) { - // this domain already exists in acme certs. - continue; - } + if (!statusPageDomain) { + continue; + } - allDomains.push(greenlockCert.key!); - } - - // now order these domains - - for (const domain of allDomains) { - // get status page domain. - - const statusPageDomain: StatusPageDomain | null = - await StatusPageDomainService.findOneBy({ - query: { - fullDomain: domain, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - if (!statusPageDomain) { - continue; - } - - try { - await StatusPageDomainService.orderCert(statusPageDomain); - } catch (e) { - logger.error(e); - } - } + try { + await StatusPageDomainService.orderCert(statusPageDomain); + } catch (e) { + logger.error(e); + } } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/RemoveCanFromPermissions.ts b/App/FeatureSet/Workers/DataMigrations/RemoveCanFromPermissions.ts index 02d273976f..fd5beed4f1 100644 --- a/App/FeatureSet/Workers/DataMigrations/RemoveCanFromPermissions.ts +++ b/App/FeatureSet/Workers/DataMigrations/RemoveCanFromPermissions.ts @@ -1,114 +1,114 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Permission from 'Common/Types/Permission'; -import ApiKeyPermissionService from 'CommonServer/Services/ApiKeyPermissionService'; -import TeamPermissionService from 'CommonServer/Services/TeamPermissionService'; -import APIKeyPermission from 'Model/Models/ApiKeyPermission'; -import TeamPermission from 'Model/Models/TeamPermission'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Permission from "Common/Types/Permission"; +import ApiKeyPermissionService from "CommonServer/Services/ApiKeyPermissionService"; +import TeamPermissionService from "CommonServer/Services/TeamPermissionService"; +import APIKeyPermission from "Model/Models/ApiKeyPermission"; +import TeamPermission from "Model/Models/TeamPermission"; export default class RemoveCanFromPermissions extends DataMigrationBase { - public constructor() { - super('RemoveCanFromPermissions'); + public constructor() { + super("RemoveCanFromPermissions"); + } + + public override async migrate(): Promise { + await this.removeCanFromTeamPermissions(); + await this.removeCanFromAPIPermissions(); + } + + public async removeCanFromTeamPermissions(): Promise { + const teamPermissions: Array = + await TeamPermissionService.findBy({ + query: {}, + select: { + permission: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + // check the permission that starts with "Can" and remove the "Can" prefix and save it. + + for (const teamPermission of teamPermissions) { + if (!teamPermission.permission) { + continue; + } + + if (!teamPermission.permission.startsWith("Can")) { + continue; + } + + if (!teamPermission.id) { + continue; + } + + teamPermission.permission = teamPermission.permission.substring( + 3, + ) as Permission; + // update this permission in the database + + await TeamPermissionService.updateOneById({ + id: teamPermission.id, + data: { + permission: teamPermission.permission, + }, + props: { + isRoot: true, + }, + }); } + } - public override async migrate(): Promise { - await this.removeCanFromTeamPermissions(); - await this.removeCanFromAPIPermissions(); + public async removeCanFromAPIPermissions(): Promise { + const apiPermissions: Array = + await ApiKeyPermissionService.findBy({ + query: {}, + select: { + permission: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + // check the permission that starts with "Can" and remove the "Can" prefix and save it. + + for (const apiPermission of apiPermissions) { + if (!apiPermission.permission) { + continue; + } + + if (!apiPermission.permission.startsWith("Can")) { + continue; + } + + if (!apiPermission.id) { + continue; + } + + apiPermission.permission = apiPermission.permission.substring( + 3, + ) as Permission; + // update this permission in the database + + await ApiKeyPermissionService.updateOneById({ + id: apiPermission.id, + data: { + permission: apiPermission.permission, + }, + props: { + isRoot: true, + }, + }); } + } - public async removeCanFromTeamPermissions(): Promise { - const teamPermissions: Array = - await TeamPermissionService.findBy({ - query: {}, - select: { - permission: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - // check the permission that starts with "Can" and remove the "Can" prefix and save it. - - for (const teamPermission of teamPermissions) { - if (!teamPermission.permission) { - continue; - } - - if (!teamPermission.permission.startsWith('Can')) { - continue; - } - - if (!teamPermission.id) { - continue; - } - - teamPermission.permission = teamPermission.permission.substring( - 3 - ) as Permission; - // update this permission in the database - - await TeamPermissionService.updateOneById({ - id: teamPermission.id, - data: { - permission: teamPermission.permission, - }, - props: { - isRoot: true, - }, - }); - } - } - - public async removeCanFromAPIPermissions(): Promise { - const apiPermissions: Array = - await ApiKeyPermissionService.findBy({ - query: {}, - select: { - permission: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - // check the permission that starts with "Can" and remove the "Can" prefix and save it. - - for (const apiPermission of apiPermissions) { - if (!apiPermission.permission) { - continue; - } - - if (!apiPermission.permission.startsWith('Can')) { - continue; - } - - if (!apiPermission.id) { - continue; - } - - apiPermission.permission = apiPermission.permission.substring( - 3 - ) as Permission; - // update this permission in the database - - await ApiKeyPermissionService.updateOneById({ - id: apiPermission.id, - data: { - permission: apiPermission.permission, - }, - props: { - isRoot: true, - }, - }); - } - } - - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/UpdateActiveMonitorCountToBillingProvider.ts b/App/FeatureSet/Workers/DataMigrations/UpdateActiveMonitorCountToBillingProvider.ts index 9e162a7242..675fdf97b4 100644 --- a/App/FeatureSet/Workers/DataMigrations/UpdateActiveMonitorCountToBillingProvider.ts +++ b/App/FeatureSet/Workers/DataMigrations/UpdateActiveMonitorCountToBillingProvider.ts @@ -1,50 +1,47 @@ -import DataMigrationBase from './DataMigrationBase'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Sleep from 'Common/Types/Sleep'; -import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import AllMeteredPlans from 'CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Project from 'Model/Models/Project'; +import DataMigrationBase from "./DataMigrationBase"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Sleep from "Common/Types/Sleep"; +import { IsBillingEnabled } from "CommonServer/EnvironmentConfig"; +import ProjectService from "CommonServer/Services/ProjectService"; +import AllMeteredPlans from "CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Project from "Model/Models/Project"; export default class UpdateActiveMonitorCountToBillingProvider extends DataMigrationBase { - public constructor() { - super('UpdateActiveMonitorCountToBillingProvider'); + public constructor() { + super("UpdateActiveMonitorCountToBillingProvider"); + } + + public override async migrate(): Promise { + if (!IsBillingEnabled) { + return; } - public override async migrate(): Promise { - if (!IsBillingEnabled) { - return; - } + const projects: Array = await ProjectService.findBy({ + query: { + paymentProviderMeteredSubscriptionId: QueryHelper.notNull(), + }, + select: { + _id: true, + paymentProviderMeteredSubscriptionId: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); - const projects: Array = await ProjectService.findBy({ - query: { - paymentProviderMeteredSubscriptionId: QueryHelper.notNull(), - }, - select: { - _id: true, - paymentProviderMeteredSubscriptionId: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); + for (const project of projects) { + for (const meteredPlan of AllMeteredPlans) { + await meteredPlan.reportQuantityToBillingProvider(project.id!, {}); + } - for (const project of projects) { - for (const meteredPlan of AllMeteredPlans) { - await meteredPlan.reportQuantityToBillingProvider( - project.id!, - {} - ); - } - - await Sleep.sleep(100); - } + await Sleep.sleep(100); } + } - public override async rollback(): Promise { - return; - } + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/DataMigrations/UpdateGlobalCongfigFromEnv.ts b/App/FeatureSet/Workers/DataMigrations/UpdateGlobalCongfigFromEnv.ts index 897b234323..c3f60afab8 100644 --- a/App/FeatureSet/Workers/DataMigrations/UpdateGlobalCongfigFromEnv.ts +++ b/App/FeatureSet/Workers/DataMigrations/UpdateGlobalCongfigFromEnv.ts @@ -1,63 +1,61 @@ -import DataMigrationBase from './DataMigrationBase'; -import Hostname from 'Common/Types/API/Hostname'; -import ObjectID from 'Common/Types/ObjectID'; -import Port from 'Common/Types/Port'; -import GlobalConfigService from 'CommonServer/Services/GlobalConfigService'; -import { EmailServerType } from 'Model/Models/GlobalConfig'; +import DataMigrationBase from "./DataMigrationBase"; +import Hostname from "Common/Types/API/Hostname"; +import ObjectID from "Common/Types/ObjectID"; +import Port from "Common/Types/Port"; +import GlobalConfigService from "CommonServer/Services/GlobalConfigService"; +import { EmailServerType } from "Model/Models/GlobalConfig"; export default class UpdateGlobalConfigFromEnv extends DataMigrationBase { - public constructor() { - super('UpdateGlobalConfigFromEnv'); + public constructor() { + super("UpdateGlobalConfigFromEnv"); + } + + public override async migrate(): Promise { + // get all the users with email isVerified true. + + let emailServerType: EmailServerType = EmailServerType.Internal; + + if (process.env["USE_INTERNAL_SMTP"] !== "true") { + emailServerType = EmailServerType.CustomSMTP; } - public override async migrate(): Promise { - // get all the users with email isVerified true. - - let emailServerType: EmailServerType = EmailServerType.Internal; - - if (process.env['USE_INTERNAL_SMTP'] !== 'true') { - emailServerType = EmailServerType.CustomSMTP; - } - - if (process.env['SENDGRID_API_KEY']) { - emailServerType = EmailServerType.Sendgrid; - } - - await GlobalConfigService.updateOneById({ - id: ObjectID.getZeroObjectID(), - data: { - // Update Twilio - - twilioAccountSID: process.env['TWILIO_ACCOUNT_SID'] || '', - twilioAuthToken: process.env['TWILIO_AUTH_TOKEN'] || '', - twilioPhoneNumber: process.env['TWILIO_PHONE_NUMBER'] || '', - - // Update SMTP - smtpUsername: process.env['SMTP_USERNAME'] || '', - smtpPassword: process.env['SMTP_PASSWORD'] || '', - smtpHost: Hostname.fromString(process.env['SMTP_HOST'] || ''), - smtpPort: new Port(process.env['SMTP_PORT'] || '25'), - isSMTPSecure: process.env['SMTP_IS_SECURE'] === 'true', - smtpFromEmail: - process.env['SMTP_FROM_EMAIL'] || - process.env['SMTP_EMAIL'] || - '', - smtpFromName: process.env['SMTP_FROM_NAME'] || '', - - emailServerType: emailServerType, - - // diable signup - disableSignup: process.env['DISABLE_SIGNUP'] === 'true', - - sendgridApiKey: process.env['SENDGRID_API_KEY'] || '', - }, - props: { - isRoot: true, - }, - }); + if (process.env["SENDGRID_API_KEY"]) { + emailServerType = EmailServerType.Sendgrid; } - public override async rollback(): Promise { - return; - } + await GlobalConfigService.updateOneById({ + id: ObjectID.getZeroObjectID(), + data: { + // Update Twilio + + twilioAccountSID: process.env["TWILIO_ACCOUNT_SID"] || "", + twilioAuthToken: process.env["TWILIO_AUTH_TOKEN"] || "", + twilioPhoneNumber: process.env["TWILIO_PHONE_NUMBER"] || "", + + // Update SMTP + smtpUsername: process.env["SMTP_USERNAME"] || "", + smtpPassword: process.env["SMTP_PASSWORD"] || "", + smtpHost: Hostname.fromString(process.env["SMTP_HOST"] || ""), + smtpPort: new Port(process.env["SMTP_PORT"] || "25"), + isSMTPSecure: process.env["SMTP_IS_SECURE"] === "true", + smtpFromEmail: + process.env["SMTP_FROM_EMAIL"] || process.env["SMTP_EMAIL"] || "", + smtpFromName: process.env["SMTP_FROM_NAME"] || "", + + emailServerType: emailServerType, + + // diable signup + disableSignup: process.env["DISABLE_SIGNUP"] === "true", + + sendgridApiKey: process.env["SENDGRID_API_KEY"] || "", + }, + props: { + isRoot: true, + }, + }); + } + + public override async rollback(): Promise { + return; + } } diff --git a/App/FeatureSet/Workers/Index.ts b/App/FeatureSet/Workers/Index.ts index 5a11585a09..59dc719323 100644 --- a/App/FeatureSet/Workers/Index.ts +++ b/App/FeatureSet/Workers/Index.ts @@ -1,101 +1,101 @@ // Announcements. -import './Jobs/Announcement/SendNotificationToSubscribers'; +import "./Jobs/Announcement/SendNotificationToSubscribers"; // Hard Delete -import './Jobs/HardDelete/HardDeleteItemsInDatabase'; +import "./Jobs/HardDelete/HardDeleteItemsInDatabase"; // Incidents -import './Jobs/Incident/SendNotificationToSubscribers'; +import "./Jobs/Incident/SendNotificationToSubscribers"; // Incident Owners -import './Jobs/IncidentOwners/SendCreatedResourceNotification'; -import './Jobs/IncidentOwners/SendNotePostedNotification'; -import './Jobs/IncidentOwners/SendOwnerAddedNotification'; -import './Jobs/IncidentOwners/SendStateChangeNotification'; +import "./Jobs/IncidentOwners/SendCreatedResourceNotification"; +import "./Jobs/IncidentOwners/SendNotePostedNotification"; +import "./Jobs/IncidentOwners/SendOwnerAddedNotification"; +import "./Jobs/IncidentOwners/SendStateChangeNotification"; // Incident Notes -import './Jobs/IncidentPublicNote/SendNotificationToSubscribers'; -import './Jobs/IncidentStateTimeline/SendNotificationToSubscribers'; -import './Jobs/IncomingRequestMonitor/CheckHeartbeat'; -import './Jobs/MeteredPlan/ReportTelemetryMeteredPlan'; +import "./Jobs/IncidentPublicNote/SendNotificationToSubscribers"; +import "./Jobs/IncidentStateTimeline/SendNotificationToSubscribers"; +import "./Jobs/IncomingRequestMonitor/CheckHeartbeat"; +import "./Jobs/MeteredPlan/ReportTelemetryMeteredPlan"; // Monitor Metrics -import './Jobs/MonitorMetrics/MonitorMetricsByMinute'; +import "./Jobs/MonitorMetrics/MonitorMetricsByMinute"; // Monitor Owners -import './Jobs/MonitorOwners/SendCreatedResourceNotification'; -import './Jobs/MonitorOwners/SendOwnerAddedNotification'; -import './Jobs/MonitorOwners/SendStatusChangeNotification'; +import "./Jobs/MonitorOwners/SendCreatedResourceNotification"; +import "./Jobs/MonitorOwners/SendOwnerAddedNotification"; +import "./Jobs/MonitorOwners/SendStatusChangeNotification"; // On-Call Duty Policy Executions. -import './Jobs/OnCallDutyPolicyExecutionLog/ExecutePendingExecutions'; -import './Jobs/OnCallDutyPolicyExecutionLog/TimeoutStuckExecutions'; +import "./Jobs/OnCallDutyPolicyExecutionLog/ExecutePendingExecutions"; +import "./Jobs/OnCallDutyPolicyExecutionLog/TimeoutStuckExecutions"; // Payments. -import './Jobs/PaymentProvider/CheckSubscriptionStatus'; -import './Jobs/PaymentProvider/PopulatePlanNameInProject'; -import './Jobs/PaymentProvider/UpdateTeamMembersIfNull'; -import './Jobs/ScheduledMaintenance/ChangeStateToEnded'; +import "./Jobs/PaymentProvider/CheckSubscriptionStatus"; +import "./Jobs/PaymentProvider/PopulatePlanNameInProject"; +import "./Jobs/PaymentProvider/UpdateTeamMembersIfNull"; +import "./Jobs/ScheduledMaintenance/ChangeStateToEnded"; // Scheduled Event -import './Jobs/ScheduledMaintenance/ChangeStateToOngoing'; -import './Jobs/ScheduledMaintenance/SendNotificationToSubscribers'; +import "./Jobs/ScheduledMaintenance/ChangeStateToOngoing"; +import "./Jobs/ScheduledMaintenance/SendNotificationToSubscribers"; // Scheduled Event Owners -import './Jobs/ScheduledMaintenanceOwners/SendCreatedResourceNotification'; -import './Jobs/ScheduledMaintenanceOwners/SendNotePostedNotification'; -import './Jobs/ScheduledMaintenanceOwners/SendOwnerAddedNotification'; -import './Jobs/ScheduledMaintenanceOwners/SendStateChangeNotification'; +import "./Jobs/ScheduledMaintenanceOwners/SendCreatedResourceNotification"; +import "./Jobs/ScheduledMaintenanceOwners/SendNotePostedNotification"; +import "./Jobs/ScheduledMaintenanceOwners/SendOwnerAddedNotification"; +import "./Jobs/ScheduledMaintenanceOwners/SendStateChangeNotification"; // Scheduled Event Notes -import './Jobs/ScheduledMaintenancePublicNote/SendNotificationToSubscribers'; -import './Jobs/ScheduledMaintenanceStateTimeline/SendNotificationToSubscribers'; -import './Jobs/ServerMonitor/CheckOnlineStatus'; +import "./Jobs/ScheduledMaintenancePublicNote/SendNotificationToSubscribers"; +import "./Jobs/ScheduledMaintenanceStateTimeline/SendNotificationToSubscribers"; +import "./Jobs/ServerMonitor/CheckOnlineStatus"; // Certs Routers -import './Jobs/StatusPageCerts/StatusPageCerts'; -import './Jobs/StatusPageOwners/SendAnnouncementCreatedNotification'; +import "./Jobs/StatusPageCerts/StatusPageCerts"; +import "./Jobs/StatusPageOwners/SendAnnouncementCreatedNotification"; // Status Page Owners -import './Jobs/StatusPageOwners/SendCreatedResourceNotification'; -import './Jobs/StatusPageOwners/SendOwnerAddedNotification'; +import "./Jobs/StatusPageOwners/SendCreatedResourceNotification"; +import "./Jobs/StatusPageOwners/SendOwnerAddedNotification"; // Telemetry Service -import './Jobs/TelemetryService/DeleteOldData'; +import "./Jobs/TelemetryService/DeleteOldData"; // User Notifications Log -import './Jobs/UserOnCallLog/ExecutePendingExecutions'; -import './Jobs/UserOnCallLog/TimeoutStuckExecutions'; -import './Jobs/Workflow/TimeoutJobs'; -import AnalyticsTableManagement from './Utils/AnalyticsDatabase/TableManegement'; -import RunDatabaseMigrations from './Utils/DataMigration'; -import JobDictionary from './Utils/JobDictionary'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { QueueJob, QueueName } from 'CommonServer/Infrastructure/Queue'; -import QueueWorker from 'CommonServer/Infrastructure/QueueWorker'; -import FeatureSet from 'CommonServer/Types/FeatureSet'; -import logger from 'CommonServer/Utils/Logger'; +import "./Jobs/UserOnCallLog/ExecutePendingExecutions"; +import "./Jobs/UserOnCallLog/TimeoutStuckExecutions"; +import "./Jobs/Workflow/TimeoutJobs"; +import AnalyticsTableManagement from "./Utils/AnalyticsDatabase/TableManegement"; +import RunDatabaseMigrations from "./Utils/DataMigration"; +import JobDictionary from "./Utils/JobDictionary"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { QueueJob, QueueName } from "CommonServer/Infrastructure/Queue"; +import QueueWorker from "CommonServer/Infrastructure/QueueWorker"; +import FeatureSet from "CommonServer/Types/FeatureSet"; +import logger from "CommonServer/Utils/Logger"; const WorkersFeatureSet: FeatureSet = { - init: async (): Promise => { - try { - // run async database migrations - RunDatabaseMigrations().catch((err: Error) => { - logger.error('Error running database migrations'); - logger.error(err); - }); + init: async (): Promise => { + try { + // run async database migrations + RunDatabaseMigrations().catch((err: Error) => { + logger.error("Error running database migrations"); + logger.error(err); + }); - // create tables in analytics database - await AnalyticsTableManagement.createTables(); + // create tables in analytics database + await AnalyticsTableManagement.createTables(); - // Job process. - QueueWorker.getWorker( - QueueName.Worker, - async (job: QueueJob) => { - const name: string = job.name; + // Job process. + QueueWorker.getWorker( + QueueName.Worker, + async (job: QueueJob) => { + const name: string = job.name; - logger.debug('Running Job: ' + name); + logger.debug("Running Job: " + name); - const funcToRun: PromiseVoidFunction = - JobDictionary.getJobFunction(name); + const funcToRun: PromiseVoidFunction = + JobDictionary.getJobFunction(name); - if (funcToRun) { - await funcToRun(); - } - }, - { concurrency: 10 } - ); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } - }, + if (funcToRun) { + await funcToRun(); + } + }, + { concurrency: 10 }, + ); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } + }, }; export default WorkersFeatureSet; diff --git a/App/FeatureSet/Workers/Jobs/Announcement/SendNotificationToSubscribers.ts b/App/FeatureSet/Workers/Jobs/Announcement/SendNotificationToSubscribers.ts index 7d12f20731..68306651e7 100644 --- a/App/FeatureSet/Workers/Jobs/Announcement/SendNotificationToSubscribers.ts +++ b/App/FeatureSet/Workers/Jobs/Announcement/SendNotificationToSubscribers.ts @@ -1,224 +1,216 @@ -import RunCron from '../../Utils/Cron'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import SMS from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import MailService from 'CommonServer/Services/MailService'; -import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService'; -import ProjectSMTPConfigService from 'CommonServer/Services/ProjectSmtpConfigService'; -import SmsService from 'CommonServer/Services/SmsService'; -import StatusPageAnnouncementService from 'CommonServer/Services/StatusPageAnnouncementService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import StatusPageSubscriberService from 'CommonServer/Services/StatusPageSubscriberService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import logger from 'CommonServer/Utils/Logger'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageAnnouncement from 'Model/Models/StatusPageAnnouncement'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; +import RunCron from "../../Utils/Cron"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import SMS from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import MailService from "CommonServer/Services/MailService"; +import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService"; +import ProjectSMTPConfigService from "CommonServer/Services/ProjectSmtpConfigService"; +import SmsService from "CommonServer/Services/SmsService"; +import StatusPageAnnouncementService from "CommonServer/Services/StatusPageAnnouncementService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import StatusPageSubscriberService from "CommonServer/Services/StatusPageSubscriberService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import logger from "CommonServer/Utils/Logger"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageAnnouncement from "Model/Models/StatusPageAnnouncement"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; RunCron( - 'Announcement:SendNotificationToSubscribers', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. - const announcements: Array = - await StatusPageAnnouncementService.findBy({ - query: { - isStatusPageSubscribersNotified: false, - shouldStatusPageSubscribersBeNotified: true, - showAnnouncementAt: QueryHelper.lessThan( - OneUptimeDate.getCurrentDate() - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - title: true, - description: true, - statusPages: { - _id: true, - }, - }, - }); + "Announcement:SendNotificationToSubscribers", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. + const announcements: Array = + await StatusPageAnnouncementService.findBy({ + query: { + isStatusPageSubscribersNotified: false, + shouldStatusPageSubscribersBeNotified: true, + showAnnouncementAt: QueryHelper.lessThan( + OneUptimeDate.getCurrentDate(), + ), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + title: true, + description: true, + statusPages: { + _id: true, + }, + }, + }); - // change their state to Ongoing. + // change their state to Ongoing. - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - for (const announcement of announcements) { - if (!announcement.statusPages) { - continue; - } + for (const announcement of announcements) { + if (!announcement.statusPages) { + continue; + } - const statusPages: Array = - await StatusPageService.findBy({ - query: { - _id: QueryHelper.any( - announcement.statusPages.map((sp: StatusPage) => { - return sp.id!; - }) - ), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - _id: true, - name: true, - pageTitle: true, - projectId: true, - isPublicStatusPage: true, - logoFileId: true, - smtpConfig: { - _id: true, - hostname: true, - port: true, - username: true, - password: true, - fromEmail: true, - fromName: true, - secure: true, - }, - callSmsConfig: { - _id: true, - twilioAccountSID: true, - twilioAuthToken: true, - twilioPhoneNumber: true, - }, - }, - }); + const statusPages: Array = await StatusPageService.findBy({ + query: { + _id: QueryHelper.any( + announcement.statusPages.map((sp: StatusPage) => { + return sp.id!; + }), + ), + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + _id: true, + name: true, + pageTitle: true, + projectId: true, + isPublicStatusPage: true, + logoFileId: true, + smtpConfig: { + _id: true, + hostname: true, + port: true, + username: true, + password: true, + fromEmail: true, + fromName: true, + secure: true, + }, + callSmsConfig: { + _id: true, + twilioAccountSID: true, + twilioAuthToken: true, + twilioPhoneNumber: true, + }, + }, + }); - await StatusPageAnnouncementService.updateOneById({ - id: announcement.id!, - data: { - isStatusPageSubscribersNotified: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + await StatusPageAnnouncementService.updateOneById({ + id: announcement.id!, + data: { + isStatusPageSubscribersNotified: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); - for (const statuspage of statusPages) { - if (!statuspage.id) { - continue; - } + for (const statuspage of statusPages) { + if (!statuspage.id) { + continue; + } - const subscribers: Array = - await StatusPageSubscriberService.getSubscribersByStatusPage( - statuspage.id!, - { - isRoot: true, - ignoreHooks: true, - } - ); + const subscribers: Array = + await StatusPageSubscriberService.getSubscribersByStatusPage( + statuspage.id!, + { + isRoot: true, + ignoreHooks: true, + }, + ); - const statusPageURL: string = - await StatusPageService.getStatusPageURL(statuspage.id); - const statusPageName: string = - statuspage.pageTitle || statuspage.name || 'Status Page'; + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statuspage.id, + ); + const statusPageName: string = + statuspage.pageTitle || statuspage.name || "Status Page"; - // Send email to Email subscribers. + // Send email to Email subscribers. - for (const subscriber of subscribers) { - if (!subscriber._id) { - continue; - } + for (const subscriber of subscribers) { + if (!subscriber._id) { + continue; + } - const unsubscribeUrl: string = - StatusPageSubscriberService.getUnsubscribeLink( - URL.fromString(statusPageURL), - subscriber.id! - ).toString(); + const unsubscribeUrl: string = + StatusPageSubscriberService.getUnsubscribeLink( + URL.fromString(statusPageURL), + subscriber.id!, + ).toString(); - if (subscriber.subscriberPhone) { - const sms: SMS = { - message: ` + if (subscriber.subscriberPhone) { + const sms: SMS = { + message: ` Announcement - ${statusPageName} - ${announcement.title || ''} + ${announcement.title || ""} To view this announcement, visit ${statusPageURL} To update notification preferences or unsubscribe, visit ${unsubscribeUrl} `, - to: subscriber.subscriberPhone, - }; + to: subscriber.subscriberPhone, + }; - // send sms here. - SmsService.sendSms(sms, { - projectId: statuspage.projectId, - customTwilioConfig: - ProjectCallSMSConfigService.toTwilioConfig( - statuspage.callSmsConfig - ), - }).catch((err: Error) => { - logger.error(err); - }); - } + // send sms here. + SmsService.sendSms(sms, { + projectId: statuspage.projectId, + customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig( + statuspage.callSmsConfig, + ), + }).catch((err: Error) => { + logger.error(err); + }); + } - if (subscriber.subscriberEmail) { - // send email here. + if (subscriber.subscriberEmail) { + // send email here. - MailService.sendMail( - { - toEmail: subscriber.subscriberEmail, - templateType: - EmailTemplateType.SubscriberAnnouncementCreated, - vars: { - statusPageName: statusPageName, - statusPageUrl: statusPageURL, - logoUrl: statuspage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute( - '/image/' + - statuspage.logoFileId - ) - .toString() - : '', - isPublicStatusPage: - statuspage.isPublicStatusPage - ? 'true' - : 'false', - announcementTitle: announcement.title || '', - announcementDescription: - await Markdown.convertToHTML( - announcement.description || '', - MarkdownContentType.Email - ), - unsubscribeUrl: unsubscribeUrl, - }, - subject: '[Announcement] ' + statusPageName, - }, - { - mailServer: - ProjectSMTPConfigService.toEmailServer( - statuspage.smtpConfig - ), - projectId: statuspage.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - } + MailService.sendMail( + { + toEmail: subscriber.subscriberEmail, + templateType: EmailTemplateType.SubscriberAnnouncementCreated, + vars: { + statusPageName: statusPageName, + statusPageUrl: statusPageURL, + logoUrl: statuspage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statuspage.logoFileId) + .toString() + : "", + isPublicStatusPage: statuspage.isPublicStatusPage + ? "true" + : "false", + announcementTitle: announcement.title || "", + announcementDescription: await Markdown.convertToHTML( + announcement.description || "", + MarkdownContentType.Email, + ), + unsubscribeUrl: unsubscribeUrl, + }, + subject: "[Announcement] " + statusPageName, + }, + { + mailServer: ProjectSMTPConfigService.toEmailServer( + statuspage.smtpConfig, + ), + projectId: statuspage.projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/HardDelete/HardDeleteItemsInDatabase.ts b/App/FeatureSet/Workers/Jobs/HardDelete/HardDeleteItemsInDatabase.ts index 9424a412d6..d6bd66b3e9 100644 --- a/App/FeatureSet/Workers/Jobs/HardDelete/HardDeleteItemsInDatabase.ts +++ b/App/FeatureSet/Workers/Jobs/HardDelete/HardDeleteItemsInDatabase.ts @@ -1,90 +1,87 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import { EVERY_DAY, EVERY_MINUTE } from 'Common/Utils/CronTime'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import { EVERY_DAY, EVERY_MINUTE } from "Common/Utils/CronTime"; import { - IsBillingEnabled, - IsDevelopment, -} from 'CommonServer/EnvironmentConfig'; -import DatabaseService from 'CommonServer/Services/DatabaseService'; -import Services from 'CommonServer/Services/Index'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import logger from 'CommonServer/Utils/Logger'; + IsBillingEnabled, + IsDevelopment, +} from "CommonServer/EnvironmentConfig"; +import DatabaseService from "CommonServer/Services/DatabaseService"; +import Services from "CommonServer/Services/Index"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import logger from "CommonServer/Utils/Logger"; RunCron( - 'HardDelete:HardDeleteItemsInDatabase', - { schedule: IsDevelopment ? EVERY_MINUTE : EVERY_DAY, runOnStartup: false }, - async () => { - if (!IsBillingEnabled) { - logger.debug( - 'HardDelete:HardDeleteItemsInDatabase: Billing is not enabled. Skipping hard delete.' - ); - return; - } - - for (const service of Services) { - if (service instanceof DatabaseService) { - if (service.doNotAllowDelete) { - // marked as do not delete. skip. - continue; - } - - try { - // Retain data for 30 days for accidental deletion, and then hard delete. - await service.hardDeleteBy({ - query: { - deletedAt: QueryHelper.lessThan( - OneUptimeDate.getSomeDaysAgo(30) - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); - } catch (err) { - logger.error(err); - } - } - } + "HardDelete:HardDeleteItemsInDatabase", + { schedule: IsDevelopment ? EVERY_MINUTE : EVERY_DAY, runOnStartup: false }, + async () => { + if (!IsBillingEnabled) { + logger.debug( + "HardDelete:HardDeleteItemsInDatabase: Billing is not enabled. Skipping hard delete.", + ); + return; } + + for (const service of Services) { + if (service instanceof DatabaseService) { + if (service.doNotAllowDelete) { + // marked as do not delete. skip. + continue; + } + + try { + // Retain data for 30 days for accidental deletion, and then hard delete. + await service.hardDeleteBy({ + query: { + deletedAt: QueryHelper.lessThan(OneUptimeDate.getSomeDaysAgo(30)), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); + } catch (err) { + logger.error(err); + } + } + } + }, ); RunCron( - 'HardDelete:HardDeleteOlderItemsInDatabase', - { schedule: IsDevelopment ? EVERY_MINUTE : EVERY_DAY, runOnStartup: false }, - async () => { - for (const service of Services) { - if (service instanceof DatabaseService) { - if ( - !service.hardDeleteItemByColumnName || - !service.hardDeleteItemsOlderThanDays - ) { - continue; - } - - try { - // Retain data for 30 days for accidental deletion, and then hard delete. - await service.hardDeleteBy({ - query: { - [service.hardDeleteItemByColumnName]: - QueryHelper.lessThan( - OneUptimeDate.getSomeDaysAgo( - service.hardDeleteItemsOlderThanDays - ) - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); - } catch (err) { - logger.error(err); - } - } + "HardDelete:HardDeleteOlderItemsInDatabase", + { schedule: IsDevelopment ? EVERY_MINUTE : EVERY_DAY, runOnStartup: false }, + async () => { + for (const service of Services) { + if (service instanceof DatabaseService) { + if ( + !service.hardDeleteItemByColumnName || + !service.hardDeleteItemsOlderThanDays + ) { + continue; } + + try { + // Retain data for 30 days for accidental deletion, and then hard delete. + await service.hardDeleteBy({ + query: { + [service.hardDeleteItemByColumnName]: QueryHelper.lessThan( + OneUptimeDate.getSomeDaysAgo( + service.hardDeleteItemsOlderThanDays, + ), + ), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); + } catch (err) { + logger.error(err); + } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/Incident/SendNotificationToSubscribers.ts b/App/FeatureSet/Workers/Jobs/Incident/SendNotificationToSubscribers.ts index 89bd7dd2f7..18005fd99d 100644 --- a/App/FeatureSet/Workers/Jobs/Incident/SendNotificationToSubscribers.ts +++ b/App/FeatureSet/Workers/Jobs/Incident/SendNotificationToSubscribers.ts @@ -1,244 +1,233 @@ -import RunCron from '../../Utils/Cron'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import ObjectID from 'Common/Types/ObjectID'; -import SMS from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import MailService from 'CommonServer/Services/MailService'; -import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService'; -import ProjectSMTPConfigService from 'CommonServer/Services/ProjectSmtpConfigService'; -import SmsService from 'CommonServer/Services/SmsService'; -import StatusPageResourceService from 'CommonServer/Services/StatusPageResourceService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import StatusPageSubscriberService from 'CommonServer/Services/StatusPageSubscriberService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import logger from 'CommonServer/Utils/Logger'; -import Incident from 'Model/Models/Incident'; -import Monitor from 'Model/Models/Monitor'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageResource from 'Model/Models/StatusPageResource'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; +import RunCron from "../../Utils/Cron"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import ObjectID from "Common/Types/ObjectID"; +import SMS from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import IncidentService from "CommonServer/Services/IncidentService"; +import MailService from "CommonServer/Services/MailService"; +import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService"; +import ProjectSMTPConfigService from "CommonServer/Services/ProjectSmtpConfigService"; +import SmsService from "CommonServer/Services/SmsService"; +import StatusPageResourceService from "CommonServer/Services/StatusPageResourceService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import StatusPageSubscriberService from "CommonServer/Services/StatusPageSubscriberService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import logger from "CommonServer/Utils/Logger"; +import Incident from "Model/Models/Incident"; +import Monitor from "Model/Models/Monitor"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageResource from "Model/Models/StatusPageResource"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; RunCron( - 'Incident:SendNotificationToSubscribers', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. - const incidents: Array = await IncidentService.findBy({ - query: { - isStatusPageSubscribersNotifiedOnIncidentCreated: false, - shouldStatusPageSubscribersBeNotifiedOnIncidentCreated: true, - createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - title: true, - description: true, - monitors: { - _id: true, - }, - incidentSeverity: { - name: true, - }, - }, + "Incident:SendNotificationToSubscribers", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. + const incidents: Array = await IncidentService.findBy({ + query: { + isStatusPageSubscribersNotifiedOnIncidentCreated: false, + shouldStatusPageSubscribersBeNotifiedOnIncidentCreated: true, + createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + title: true, + description: true, + monitors: { + _id: true, + }, + incidentSeverity: { + name: true, + }, + }, + }); + + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + for (const incident of incidents) { + if (!incident.monitors || incident.monitors.length === 0) { + continue; + } + + await IncidentService.updateOneById({ + id: incident.id!, + data: { + isStatusPageSubscribersNotifiedOnIncidentCreated: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + // get status page resources from monitors. + + const statusPageResources: Array = + await StatusPageResourceService.findBy({ + query: { + monitorId: QueryHelper.any( + incident.monitors + .filter((m: Monitor) => { + return m._id; + }) + .map((m: Monitor) => { + return new ObjectID(m._id!); + }), + ), + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + _id: true, + displayName: true, + statusPageId: true, + }, }); - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + const statusPageToResources: Dictionary> = {}; - for (const incident of incidents) { - if (!incident.monitors || incident.monitors.length === 0) { - continue; - } + for (const resource of statusPageResources) { + if (!resource.statusPageId) { + continue; + } - await IncidentService.updateOneById({ - id: incident.id!, - data: { - isStatusPageSubscribersNotifiedOnIncidentCreated: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, + if (!statusPageToResources[resource.statusPageId?.toString()]) { + statusPageToResources[resource.statusPageId?.toString()] = []; + } + + statusPageToResources[resource.statusPageId?.toString()]?.push( + resource, + ); + } + + const statusPages: Array = + await StatusPageSubscriberService.getStatusPagesToSendNotification( + Object.keys(statusPageToResources).map((i: string) => { + return new ObjectID(i); + }), + ); + + for (const statuspage of statusPages) { + if (!statuspage.id) { + continue; + } + + const subscribers: Array = + await StatusPageSubscriberService.getSubscribersByStatusPage( + statuspage.id!, + { + isRoot: true, + ignoreHooks: true, + }, + ); + + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statuspage.id, + ); + const statusPageName: string = + statuspage.pageTitle || statuspage.name || "Status Page"; + + // Send email to Email subscribers. + + const resourcesAffectedString: string = + statusPageToResources[statuspage._id!] + ?.map((r: StatusPageResource) => { + return r.displayName; + }) + .join(", ") || "None"; + + for (const subscriber of subscribers) { + if (!subscriber._id) { + continue; + } + + const shouldNotifySubscriber: boolean = + StatusPageSubscriberService.shouldSendNotification({ + subscriber: subscriber, + statusPageResources: statusPageToResources[statuspage._id!] || [], + statusPage: statuspage, }); - // get status page resources from monitors. + if (!shouldNotifySubscriber) { + continue; + } - const statusPageResources: Array = - await StatusPageResourceService.findBy({ - query: { - monitorId: QueryHelper.any( - incident.monitors - .filter((m: Monitor) => { - return m._id; - }) - .map((m: Monitor) => { - return new ObjectID(m._id!); - }) - ), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - _id: true, - displayName: true, - statusPageId: true, - }, - }); + const unsubscribeUrl: string = + StatusPageSubscriberService.getUnsubscribeLink( + URL.fromString(statusPageURL), + subscriber.id!, + ).toString(); - const statusPageToResources: Dictionary> = - {}; + if (subscriber.subscriberEmail) { + // send email here. - for (const resource of statusPageResources) { - if (!resource.statusPageId) { - continue; - } + MailService.sendMail( + { + toEmail: subscriber.subscriberEmail, + templateType: EmailTemplateType.SubscriberIncidentCreated, + vars: { + statusPageName: statusPageName, + statusPageUrl: statusPageURL, + logoUrl: statuspage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statuspage.logoFileId) + .toString() + : "", + isPublicStatusPage: statuspage.isPublicStatusPage + ? "true" + : "false", + resourcesAffected: resourcesAffectedString, + incidentSeverity: incident.incidentSeverity?.name || " - ", + incidentTitle: incident.title || "", + incidentDescription: await Markdown.convertToHTML( + incident.description || "", + MarkdownContentType.Email, + ), + unsubscribeUrl: unsubscribeUrl, + }, + subject: "[Incident] " + statusPageName, + }, + { + mailServer: ProjectSMTPConfigService.toEmailServer( + statuspage.smtpConfig, + ), + projectId: statuspage.projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } - if (!statusPageToResources[resource.statusPageId?.toString()]) { - statusPageToResources[resource.statusPageId?.toString()] = - []; - } - - statusPageToResources[resource.statusPageId?.toString()]?.push( - resource - ); - } - - const statusPages: Array = - await StatusPageSubscriberService.getStatusPagesToSendNotification( - Object.keys(statusPageToResources).map((i: string) => { - return new ObjectID(i); - }) - ); - - for (const statuspage of statusPages) { - if (!statuspage.id) { - continue; - } - - const subscribers: Array = - await StatusPageSubscriberService.getSubscribersByStatusPage( - statuspage.id!, - { - isRoot: true, - ignoreHooks: true, - } - ); - - const statusPageURL: string = - await StatusPageService.getStatusPageURL(statuspage.id); - const statusPageName: string = - statuspage.pageTitle || statuspage.name || 'Status Page'; - - // Send email to Email subscribers. - - const resourcesAffectedString: string = - statusPageToResources[statuspage._id!] - ?.map((r: StatusPageResource) => { - return r.displayName; - }) - .join(', ') || 'None'; - - for (const subscriber of subscribers) { - if (!subscriber._id) { - continue; - } - - const shouldNotifySubscriber: boolean = - StatusPageSubscriberService.shouldSendNotification({ - subscriber: subscriber, - statusPageResources: - statusPageToResources[statuspage._id!] || [], - statusPage: statuspage, - }); - - if (!shouldNotifySubscriber) { - continue; - } - - const unsubscribeUrl: string = - StatusPageSubscriberService.getUnsubscribeLink( - URL.fromString(statusPageURL), - subscriber.id! - ).toString(); - - if (subscriber.subscriberEmail) { - // send email here. - - MailService.sendMail( - { - toEmail: subscriber.subscriberEmail, - templateType: - EmailTemplateType.SubscriberIncidentCreated, - vars: { - statusPageName: statusPageName, - statusPageUrl: statusPageURL, - logoUrl: statuspage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute( - '/image/' + - statuspage.logoFileId - ) - .toString() - : '', - isPublicStatusPage: - statuspage.isPublicStatusPage - ? 'true' - : 'false', - resourcesAffected: resourcesAffectedString, - incidentSeverity: - incident.incidentSeverity?.name || - ' - ', - incidentTitle: incident.title || '', - incidentDescription: - await Markdown.convertToHTML( - incident.description || '', - MarkdownContentType.Email - ), - unsubscribeUrl: unsubscribeUrl, - }, - subject: '[Incident] ' + statusPageName, - }, - { - mailServer: - ProjectSMTPConfigService.toEmailServer( - statuspage.smtpConfig - ), - projectId: statuspage.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - - if (subscriber.subscriberPhone) { - const sms: SMS = { - message: ` + if (subscriber.subscriberPhone) { + const sms: SMS = { + message: ` Incident - ${statusPageName} - Title: ${incident.title || ''} + Title: ${incident.title || ""} Severity: ${ - incident.incidentSeverity?.name || ' - ' + incident.incidentSeverity?.name || " - " } Resources Affected: ${resourcesAffectedString} @@ -247,22 +236,21 @@ RunCron( To update notification preferences or unsubscribe, visit ${unsubscribeUrl} `, - to: subscriber.subscriberPhone, - }; + to: subscriber.subscriberPhone, + }; - // send sms here. - SmsService.sendSms(sms, { - projectId: statuspage.projectId, - customTwilioConfig: - ProjectCallSMSConfigService.toTwilioConfig( - statuspage.callSmsConfig - ), - }).catch((err: Error) => { - logger.error(err); - }); - } - } - } + // send sms here. + SmsService.sendSms(sms, { + projectId: statuspage.projectId, + customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig( + statuspage.callSmsConfig, + ), + }).catch((err: Error) => { + logger.error(err); + }); + } } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/IncidentOwners/SendCreatedResourceNotification.ts b/App/FeatureSet/Workers/Jobs/IncidentOwners/SendCreatedResourceNotification.ts index d4704b95e4..739dcccfd9 100644 --- a/App/FeatureSet/Workers/Jobs/IncidentOwners/SendCreatedResourceNotification.ts +++ b/App/FeatureSet/Workers/Jobs/IncidentOwners/SendCreatedResourceNotification.ts @@ -1,145 +1,141 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import Incident from 'Model/Models/Incident'; -import Monitor from 'Model/Models/Monitor'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import IncidentService from "CommonServer/Services/IncidentService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import Incident from "Model/Models/Incident"; +import Monitor from "Model/Models/Monitor"; +import User from "Model/Models/User"; RunCron( - 'IncidentOwner:SendCreatedResourceEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. - const incidents: Array = await IncidentService.findBy({ - query: { - isOwnerNotifiedOfResourceCreation: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - title: true, - description: true, - projectId: true, - project: { - name: true, - }, - currentIncidentState: { - name: true, - }, - incidentSeverity: { - name: true, - }, - rootCause: true, - monitors: { - name: true, - }, + "IncidentOwner:SendCreatedResourceEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. + const incidents: Array = await IncidentService.findBy({ + query: { + isOwnerNotifiedOfResourceCreation: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + title: true, + description: true, + projectId: true, + project: { + name: true, + }, + currentIncidentState: { + name: true, + }, + incidentSeverity: { + name: true, + }, + rootCause: true, + monitors: { + name: true, + }, + }, + }); + + for (const incident of incidents) { + await IncidentService.updateOneById({ + id: incident.id!, + data: { + isOwnerNotifiedOfResourceCreation: true, + }, + props: { + isRoot: true, + }, + }); + + // now find owners. + + let doesResourceHasOwners: boolean = true; + + let owners: Array = await IncidentService.findOwners(incident.id!); + + if (owners.length === 0) { + doesResourceHasOwners = false; + + // find project owners. + owners = await ProjectService.getOwners(incident.projectId!); + } + + if (owners.length === 0) { + continue; + } + + const vars: Dictionary = { + incidentTitle: incident.title!, + projectName: incident.project!.name!, + currentState: incident.currentIncidentState!.name!, + incidentDescription: await Markdown.convertToHTML( + incident.description! || "", + MarkdownContentType.Email, + ), + resourcesAffected: + incident + .monitors!.map((monitor: Monitor) => { + return monitor.name!; + }) + .join(", ") || "None", + incidentSeverity: incident.incidentSeverity!.name!, + rootCause: + incident.rootCause || "No root cause identified for this incident", + incidentViewLink: ( + await IncidentService.getIncidentLinkInDashboard( + incident.projectId!, + incident.id!, + ) + ).toString(), + }; + + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } + + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.IncidentOwnerResourceCreated, + vars: vars, + subject: "[Incident] " + incident.title!, + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. New incident created: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. New incident created: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: incident.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION, }); - - for (const incident of incidents) { - await IncidentService.updateOneById({ - id: incident.id!, - data: { - isOwnerNotifiedOfResourceCreation: true, - }, - props: { - isRoot: true, - }, - }); - - // now find owners. - - let doesResourceHasOwners: boolean = true; - - let owners: Array = await IncidentService.findOwners( - incident.id! - ); - - if (owners.length === 0) { - doesResourceHasOwners = false; - - // find project owners. - owners = await ProjectService.getOwners(incident.projectId!); - } - - if (owners.length === 0) { - continue; - } - - const vars: Dictionary = { - incidentTitle: incident.title!, - projectName: incident.project!.name!, - currentState: incident.currentIncidentState!.name!, - incidentDescription: await Markdown.convertToHTML( - incident.description! || '', - MarkdownContentType.Email - ), - resourcesAffected: - incident - .monitors!.map((monitor: Monitor) => { - return monitor.name!; - }) - .join(', ') || 'None', - incidentSeverity: incident.incidentSeverity!.name!, - rootCause: - incident.rootCause || - 'No root cause identified for this incident', - incidentViewLink: ( - await IncidentService.getIncidentLinkInDashboard( - incident.projectId!, - incident.id! - ) - ).toString(), - }; - - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } - - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: - EmailTemplateType.IncidentOwnerResourceCreated, - vars: vars, - subject: '[Incident] ' + incident.title!, - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. New incident created: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. New incident created: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: incident.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION, - }); - } - } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/IncidentOwners/SendNotePostedNotification.ts b/App/FeatureSet/Workers/Jobs/IncidentOwners/SendNotePostedNotification.ts index 3f02a21f95..50cef7c131 100644 --- a/App/FeatureSet/Workers/Jobs/IncidentOwners/SendNotePostedNotification.ts +++ b/App/FeatureSet/Workers/Jobs/IncidentOwners/SendNotePostedNotification.ts @@ -1,214 +1,212 @@ -import RunCron from '../../Utils/Cron'; -import BaseModel from 'Common/Models/BaseModel'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import ObjectID from 'Common/Types/ObjectID'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import IncidentInternalNoteService from 'CommonServer/Services/IncidentInternalNoteService'; -import IncidentPublicNoteService from 'CommonServer/Services/IncidentPublicNoteService'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import Incident from 'Model/Models/Incident'; -import IncidentInternalNote from 'Model/Models/IncidentInternalNote'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import Monitor from 'Model/Models/Monitor'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import BaseModel from "Common/Models/BaseModel"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import ObjectID from "Common/Types/ObjectID"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import IncidentInternalNoteService from "CommonServer/Services/IncidentInternalNoteService"; +import IncidentPublicNoteService from "CommonServer/Services/IncidentPublicNoteService"; +import IncidentService from "CommonServer/Services/IncidentService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import Incident from "Model/Models/Incident"; +import IncidentInternalNote from "Model/Models/IncidentInternalNote"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import Monitor from "Model/Models/Monitor"; +import User from "Model/Models/User"; RunCron( - 'IncidentOwner:SendsNotePostedEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const publicNotes: Array = - await IncidentPublicNoteService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - note: true, - incidentId: true, - projectId: true, - }, - }); + "IncidentOwner:SendsNotePostedEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const publicNotes: Array = + await IncidentPublicNoteService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + note: true, + incidentId: true, + projectId: true, + }, + }); - const privateNotes: Array = - await IncidentInternalNoteService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - note: true, - incidentId: true, - projectId: true, - }, - }); + const privateNotes: Array = + await IncidentInternalNoteService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + note: true, + incidentId: true, + projectId: true, + }, + }); - const privateNoteIds: Array = privateNotes.map( - (note: IncidentInternalNote) => { - return note._id!; - } - ); + const privateNoteIds: Array = privateNotes.map( + (note: IncidentInternalNote) => { + return note._id!; + }, + ); - for (const note of publicNotes) { - await IncidentPublicNoteService.updateOneById({ - id: note.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - for (const note of privateNotes) { - await IncidentInternalNoteService.updateOneById({ - id: note.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - const notes: Array = [...publicNotes, ...privateNotes]; - - for (const noteObject of notes) { - const note: BaseModel = noteObject as BaseModel; - - // get all scheduled events of all the projects. - const incident: Incident | null = await IncidentService.findOneById( - { - id: note.getColumnValue('incidentId')! as ObjectID, - props: { - isRoot: true, - }, - select: { - _id: true, - title: true, - description: true, - projectId: true, - project: { - name: true, - }, - currentIncidentState: { - name: true, - }, - incidentSeverity: { - name: true, - }, - monitors: { - name: true, - }, - }, - } - ); - - if (!incident) { - continue; - } - - // now find owners. - - let doesResourceHasOwners: boolean = true; - - let owners: Array = await IncidentService.findOwners( - note.getColumnValue('incidentId')! as ObjectID - ); - - if (owners.length === 0) { - doesResourceHasOwners = false; - - // find project owners. - owners = await ProjectService.getOwners( - note.getColumnValue('projectId') as ObjectID - ); - } - - if (owners.length === 0) { - continue; - } - - const vars: Dictionary = { - incidentTitle: incident.title!, - projectName: incident.project!.name!, - currentState: incident.currentIncidentState!.name!, - note: await Markdown.convertToHTML( - (note.getColumnValue('note')! as string) || '', - MarkdownContentType.Email - ), - resourcesAffected: - incident - .monitors!.map((monitor: Monitor) => { - return monitor.name!; - }) - .join(', ') || 'None', - incidentSeverity: incident.incidentSeverity!.name!, - incidentViewLink: ( - await IncidentService.getIncidentLinkInDashboard( - incident.projectId!, - incident.id! - ) - ).toString(), - }; - - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } - - if (privateNoteIds.includes(note._id!)) { - vars['isPrivateNote'] = 'true'; - } - - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: EmailTemplateType.IncidentOwnerNotePosted, - vars: vars, - subject: '[Incident Update] ' + incident.title, - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. New note posted on incident: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. New note posted on incident ${incident.title}. To see the note, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: incident.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_INCIDENT_NOTE_POSTED_OWNER_NOTIFICATION, - }); - } - } + for (const note of publicNotes) { + await IncidentPublicNoteService.updateOneById({ + id: note.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); } + + for (const note of privateNotes) { + await IncidentInternalNoteService.updateOneById({ + id: note.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); + } + + const notes: Array = [...publicNotes, ...privateNotes]; + + for (const noteObject of notes) { + const note: BaseModel = noteObject as BaseModel; + + // get all scheduled events of all the projects. + const incident: Incident | null = await IncidentService.findOneById({ + id: note.getColumnValue("incidentId")! as ObjectID, + props: { + isRoot: true, + }, + select: { + _id: true, + title: true, + description: true, + projectId: true, + project: { + name: true, + }, + currentIncidentState: { + name: true, + }, + incidentSeverity: { + name: true, + }, + monitors: { + name: true, + }, + }, + }); + + if (!incident) { + continue; + } + + // now find owners. + + let doesResourceHasOwners: boolean = true; + + let owners: Array = await IncidentService.findOwners( + note.getColumnValue("incidentId")! as ObjectID, + ); + + if (owners.length === 0) { + doesResourceHasOwners = false; + + // find project owners. + owners = await ProjectService.getOwners( + note.getColumnValue("projectId") as ObjectID, + ); + } + + if (owners.length === 0) { + continue; + } + + const vars: Dictionary = { + incidentTitle: incident.title!, + projectName: incident.project!.name!, + currentState: incident.currentIncidentState!.name!, + note: await Markdown.convertToHTML( + (note.getColumnValue("note")! as string) || "", + MarkdownContentType.Email, + ), + resourcesAffected: + incident + .monitors!.map((monitor: Monitor) => { + return monitor.name!; + }) + .join(", ") || "None", + incidentSeverity: incident.incidentSeverity!.name!, + incidentViewLink: ( + await IncidentService.getIncidentLinkInDashboard( + incident.projectId!, + incident.id!, + ) + ).toString(), + }; + + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } + + if (privateNoteIds.includes(note._id!)) { + vars["isPrivateNote"] = "true"; + } + + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.IncidentOwnerNotePosted, + vars: vars, + subject: "[Incident Update] " + incident.title, + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. New note posted on incident: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. New note posted on incident ${incident.title}. To see the note, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: incident.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_INCIDENT_NOTE_POSTED_OWNER_NOTIFICATION, + }); + } + } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/IncidentOwners/SendOwnerAddedNotification.ts b/App/FeatureSet/Workers/Jobs/IncidentOwners/SendOwnerAddedNotification.ts index b4415996ce..bdeda404b5 100644 --- a/App/FeatureSet/Workers/Jobs/IncidentOwners/SendOwnerAddedNotification.ts +++ b/App/FeatureSet/Workers/Jobs/IncidentOwners/SendOwnerAddedNotification.ts @@ -1,224 +1,215 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import ObjectID from 'Common/Types/ObjectID'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import IncidentOwnerTeamService from 'CommonServer/Services/IncidentOwnerTeamService'; -import IncidentOwnerUserService from 'CommonServer/Services/IncidentOwnerUserService'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import TeamMemberService from 'CommonServer/Services/TeamMemberService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import Incident from 'Model/Models/Incident'; -import IncidentOwnerTeam from 'Model/Models/IncidentOwnerTeam'; -import IncidentOwnerUser from 'Model/Models/IncidentOwnerUser'; -import Monitor from 'Model/Models/Monitor'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import ObjectID from "Common/Types/ObjectID"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import IncidentOwnerTeamService from "CommonServer/Services/IncidentOwnerTeamService"; +import IncidentOwnerUserService from "CommonServer/Services/IncidentOwnerUserService"; +import IncidentService from "CommonServer/Services/IncidentService"; +import TeamMemberService from "CommonServer/Services/TeamMemberService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import Incident from "Model/Models/Incident"; +import IncidentOwnerTeam from "Model/Models/IncidentOwnerTeam"; +import IncidentOwnerUser from "Model/Models/IncidentOwnerUser"; +import Monitor from "Model/Models/Monitor"; +import User from "Model/Models/User"; RunCron( - 'IncidentOwner:SendOwnerAddedEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const incidentOwnerTeams: Array = - await IncidentOwnerTeamService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - incidentId: true, - teamId: true, - }, - }); + "IncidentOwner:SendOwnerAddedEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const incidentOwnerTeams: Array = + await IncidentOwnerTeamService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + incidentId: true, + teamId: true, + }, + }); - const incidentOwnersMap: Dictionary> = {}; + const incidentOwnersMap: Dictionary> = {}; - for (const incidentOwnerTeam of incidentOwnerTeams) { - const incidentId: ObjectID = incidentOwnerTeam.incidentId!; - const teamId: ObjectID = incidentOwnerTeam.teamId!; + for (const incidentOwnerTeam of incidentOwnerTeams) { + const incidentId: ObjectID = incidentOwnerTeam.incidentId!; + const teamId: ObjectID = incidentOwnerTeam.teamId!; - const users: Array = await TeamMemberService.getUsersInTeams([ - teamId, - ]); + const users: Array = await TeamMemberService.getUsersInTeams([ + teamId, + ]); - if (incidentOwnersMap[incidentId.toString()] === undefined) { - incidentOwnersMap[incidentId.toString()] = []; - } + if (incidentOwnersMap[incidentId.toString()] === undefined) { + incidentOwnersMap[incidentId.toString()] = []; + } - for (const user of users) { - (incidentOwnersMap[incidentId.toString()] as Array).push( - user - ); - } + for (const user of users) { + (incidentOwnersMap[incidentId.toString()] as Array).push(user); + } - // mark this as notified. - await IncidentOwnerTeamService.updateOneById({ - id: incidentOwnerTeam.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - const incidentOwnerUsers: Array = - await IncidentOwnerUserService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - incidentId: true, - userId: true, - user: { - email: true, - name: true, - }, - }, - }); - - for (const incidentOwnerUser of incidentOwnerUsers) { - const incidentId: ObjectID = incidentOwnerUser.incidentId!; - const user: User = incidentOwnerUser.user!; - - if (incidentOwnersMap[incidentId.toString()] === undefined) { - incidentOwnersMap[incidentId.toString()] = []; - } - - (incidentOwnersMap[incidentId.toString()] as Array).push( - user - ); - - // mark this as notified. - await IncidentOwnerUserService.updateOneById({ - id: incidentOwnerUser.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - // send email to all of these users. - - for (const incidentId in incidentOwnersMap) { - if (!incidentOwnersMap[incidentId]) { - continue; - } - - if ((incidentOwnersMap[incidentId] as Array).length === 0) { - continue; - } - - const users: Array = incidentOwnersMap[ - incidentId - ] as Array; - - // get all scheduled events of all the projects. - const incident: Incident | null = await IncidentService.findOneById( - { - id: new ObjectID(incidentId), - props: { - isRoot: true, - }, - - select: { - _id: true, - title: true, - description: true, - projectId: true, - project: { - name: true, - }, - currentIncidentState: { - name: true, - }, - incidentSeverity: { - name: true, - }, - monitors: { - name: true, - }, - }, - } - ); - - if (!incident) { - continue; - } - - const vars: Dictionary = { - incidentTitle: incident.title!, - projectName: incident.project!.name!, - currentState: incident.currentIncidentState!.name!, - incidentDescription: await Markdown.convertToHTML( - incident.description! || '', - MarkdownContentType.Email - ), - resourcesAffected: - incident - .monitors!.map((monitor: Monitor) => { - return monitor.name!; - }) - .join(', ') || 'None', - incidentSeverity: incident.incidentSeverity!.name!, - incidentViewLink: ( - await IncidentService.getIncidentLinkInDashboard( - incident.projectId!, - incident.id! - ) - ).toString(), - }; - - for (const user of users) { - const emailMessage: EmailEnvelope = { - templateType: EmailTemplateType.IncidentOwnerAdded, - vars: vars, - subject: - 'You have been added as the owner of the incident.', - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. You have been added as the owner of the incident: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. You have been added as the owner of the incident: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: incident.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_INCIDENT_OWNER_ADDED_NOTIFICATION, - }); - } - } + // mark this as notified. + await IncidentOwnerTeamService.updateOneById({ + id: incidentOwnerTeam.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); } + + const incidentOwnerUsers: Array = + await IncidentOwnerUserService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + incidentId: true, + userId: true, + user: { + email: true, + name: true, + }, + }, + }); + + for (const incidentOwnerUser of incidentOwnerUsers) { + const incidentId: ObjectID = incidentOwnerUser.incidentId!; + const user: User = incidentOwnerUser.user!; + + if (incidentOwnersMap[incidentId.toString()] === undefined) { + incidentOwnersMap[incidentId.toString()] = []; + } + + (incidentOwnersMap[incidentId.toString()] as Array).push(user); + + // mark this as notified. + await IncidentOwnerUserService.updateOneById({ + id: incidentOwnerUser.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); + } + + // send email to all of these users. + + for (const incidentId in incidentOwnersMap) { + if (!incidentOwnersMap[incidentId]) { + continue; + } + + if ((incidentOwnersMap[incidentId] as Array).length === 0) { + continue; + } + + const users: Array = incidentOwnersMap[incidentId] as Array; + + // get all scheduled events of all the projects. + const incident: Incident | null = await IncidentService.findOneById({ + id: new ObjectID(incidentId), + props: { + isRoot: true, + }, + + select: { + _id: true, + title: true, + description: true, + projectId: true, + project: { + name: true, + }, + currentIncidentState: { + name: true, + }, + incidentSeverity: { + name: true, + }, + monitors: { + name: true, + }, + }, + }); + + if (!incident) { + continue; + } + + const vars: Dictionary = { + incidentTitle: incident.title!, + projectName: incident.project!.name!, + currentState: incident.currentIncidentState!.name!, + incidentDescription: await Markdown.convertToHTML( + incident.description! || "", + MarkdownContentType.Email, + ), + resourcesAffected: + incident + .monitors!.map((monitor: Monitor) => { + return monitor.name!; + }) + .join(", ") || "None", + incidentSeverity: incident.incidentSeverity!.name!, + incidentViewLink: ( + await IncidentService.getIncidentLinkInDashboard( + incident.projectId!, + incident.id!, + ) + ).toString(), + }; + + for (const user of users) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.IncidentOwnerAdded, + vars: vars, + subject: "You have been added as the owner of the incident.", + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. You have been added as the owner of the incident: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. You have been added as the owner of the incident: ${incident.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: incident.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_INCIDENT_OWNER_ADDED_NOTIFICATION, + }); + } + } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/IncidentOwners/SendStateChangeNotification.ts b/App/FeatureSet/Workers/Jobs/IncidentOwners/SendStateChangeNotification.ts index ab29758d3e..5e1960fdfa 100644 --- a/App/FeatureSet/Workers/Jobs/IncidentOwners/SendStateChangeNotification.ts +++ b/App/FeatureSet/Workers/Jobs/IncidentOwners/SendStateChangeNotification.ts @@ -1,207 +1,201 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import ObjectID from 'Common/Types/ObjectID'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import Text from 'Common/Types/Text'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import IncidentStateTimelineService from 'CommonServer/Services/IncidentStateTimelineService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import Incident from 'Model/Models/Incident'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import Monitor from 'Model/Models/Monitor'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import ObjectID from "Common/Types/ObjectID"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import Text from "Common/Types/Text"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import IncidentService from "CommonServer/Services/IncidentService"; +import IncidentStateTimelineService from "CommonServer/Services/IncidentStateTimelineService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import Incident from "Model/Models/Incident"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import Monitor from "Model/Models/Monitor"; +import User from "Model/Models/User"; RunCron( - 'IncidentOwner:SendStateChangeEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. + "IncidentOwner:SendStateChangeEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. - const incidentStateTimelines: Array = - await IncidentStateTimelineService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - createdAt: true, - projectId: true, - project: { - name: true, - }, - incidentId: true, - incidentState: { - name: true, - }, - }, - }); + const incidentStateTimelines: Array = + await IncidentStateTimelineService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + createdAt: true, + projectId: true, + project: { + name: true, + }, + incidentId: true, + incidentState: { + name: true, + }, + }, + }); - for (const incidentStateTimeline of incidentStateTimelines) { - const incidentId: ObjectID = incidentStateTimeline.incidentId!; + for (const incidentStateTimeline of incidentStateTimelines) { + const incidentId: ObjectID = incidentStateTimeline.incidentId!; - if (!incidentId) { - continue; - } + if (!incidentId) { + continue; + } - // get incident + // get incident - const incident: Incident | null = await IncidentService.findOneById( - { - id: incidentId, - props: { - isRoot: true, - }, - select: { - _id: true, - title: true, - description: true, - monitors: { - name: true, - }, - }, - } - ); + const incident: Incident | null = await IncidentService.findOneById({ + id: incidentId, + props: { + isRoot: true, + }, + select: { + _id: true, + title: true, + description: true, + monitors: { + name: true, + }, + }, + }); - if (!incident) { - continue; - } + if (!incident) { + continue; + } - const incidentState: IncidentState = - incidentStateTimeline.incidentState!; + const incidentState: IncidentState = incidentStateTimeline.incidentState!; - // get incident severity - const incidentWithSeverity: Incident | null = - await IncidentService.findOneById({ - id: incident.id!, - props: { - isRoot: true, - }, - select: { - _id: true, - incidentSeverity: { - name: true, - }, - }, - }); + // get incident severity + const incidentWithSeverity: Incident | null = + await IncidentService.findOneById({ + id: incident.id!, + props: { + isRoot: true, + }, + select: { + _id: true, + incidentSeverity: { + name: true, + }, + }, + }); - if (!incidentWithSeverity) { - continue; - } + if (!incidentWithSeverity) { + continue; + } - await IncidentStateTimelineService.updateOneById({ - id: incidentStateTimeline.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); + await IncidentStateTimelineService.updateOneById({ + id: incidentStateTimeline.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); - // now find owners. + // now find owners. - let doesResourceHasOwners: boolean = true; + let doesResourceHasOwners: boolean = true; - let owners: Array = await IncidentService.findOwners( - incident.id! - ); + let owners: Array = await IncidentService.findOwners(incident.id!); - if (owners.length === 0) { - doesResourceHasOwners = false; + if (owners.length === 0) { + doesResourceHasOwners = false; - // find project owners. - owners = await ProjectService.getOwners( - incidentStateTimeline.projectId! - ); - } + // find project owners. + owners = await ProjectService.getOwners( + incidentStateTimeline.projectId!, + ); + } - if (owners.length === 0) { - continue; - } + if (owners.length === 0) { + continue; + } - const vars: Dictionary = { - incidentTitle: incident.title!, - projectName: incidentStateTimeline.project!.name!, - currentState: incidentState!.name!, - incidentDescription: await Markdown.convertToHTML( - incident.description! || '', - MarkdownContentType.Email - ), - resourcesAffected: - incident - .monitors!.map((monitor: Monitor) => { - return monitor.name!; - }) - .join(', ') || 'None', - stateChangedAt: - OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones( - incidentStateTimeline.createdAt! - ), - incidentSeverity: incidentWithSeverity.incidentSeverity!.name!, - incidentViewLink: ( - await IncidentService.getIncidentLinkInDashboard( - incidentStateTimeline.projectId!, - incident.id! - ) - ).toString(), - }; + const vars: Dictionary = { + incidentTitle: incident.title!, + projectName: incidentStateTimeline.project!.name!, + currentState: incidentState!.name!, + incidentDescription: await Markdown.convertToHTML( + incident.description! || "", + MarkdownContentType.Email, + ), + resourcesAffected: + incident + .monitors!.map((monitor: Monitor) => { + return monitor.name!; + }) + .join(", ") || "None", + stateChangedAt: OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones( + incidentStateTimeline.createdAt!, + ), + incidentSeverity: incidentWithSeverity.incidentSeverity!.name!, + incidentViewLink: ( + await IncidentService.getIncidentLinkInDashboard( + incidentStateTimeline.projectId!, + incident.id!, + ) + ).toString(), + }; - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: EmailTemplateType.IncidentOwnerStateChanged, - vars: vars, - subject: `[Incident ${Text.uppercaseFirstLetter( - incidentState!.name! - )}] ${incident.title!}`, - }; + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.IncidentOwnerStateChanged, + vars: vars, + subject: `[Incident ${Text.uppercaseFirstLetter( + incidentState!.name!, + )}] ${incident.title!}`, + }; - const sms: SMSMessage = { - message: `This is a message from OneUptime. Incident: ${ - incident.title - } - state changed to ${incidentState! - .name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; + const sms: SMSMessage = { + message: `This is a message from OneUptime. Incident: ${ + incident.title + } - state changed to ${incidentState! + .name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. Incident ${ - incident.title - } state changed to ${incidentState! - .name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. Incident ${ + incident.title + } state changed to ${incidentState! + .name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: incidentStateTimeline.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION, - }); - } - } + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: incidentStateTimeline.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION, + }); + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/IncidentPublicNote/SendNotificationToSubscribers.ts b/App/FeatureSet/Workers/Jobs/IncidentPublicNote/SendNotificationToSubscribers.ts index 86cd58fd83..1ae7029ec5 100644 --- a/App/FeatureSet/Workers/Jobs/IncidentPublicNote/SendNotificationToSubscribers.ts +++ b/App/FeatureSet/Workers/Jobs/IncidentPublicNote/SendNotificationToSubscribers.ts @@ -1,292 +1,276 @@ -import RunCron from '../../Utils/Cron'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import ObjectID from 'Common/Types/ObjectID'; -import SMS from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import IncidentPublicNoteService from 'CommonServer/Services/IncidentPublicNoteService'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import MailService from 'CommonServer/Services/MailService'; -import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService'; -import ProjectSmtpConfigService from 'CommonServer/Services/ProjectSmtpConfigService'; -import SmsService from 'CommonServer/Services/SmsService'; -import StatusPageResourceService from 'CommonServer/Services/StatusPageResourceService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import StatusPageSubscriberService from 'CommonServer/Services/StatusPageSubscriberService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import logger from 'CommonServer/Utils/Logger'; -import Incident from 'Model/Models/Incident'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import Monitor from 'Model/Models/Monitor'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageResource from 'Model/Models/StatusPageResource'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; +import RunCron from "../../Utils/Cron"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import ObjectID from "Common/Types/ObjectID"; +import SMS from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import IncidentPublicNoteService from "CommonServer/Services/IncidentPublicNoteService"; +import IncidentService from "CommonServer/Services/IncidentService"; +import MailService from "CommonServer/Services/MailService"; +import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService"; +import ProjectSmtpConfigService from "CommonServer/Services/ProjectSmtpConfigService"; +import SmsService from "CommonServer/Services/SmsService"; +import StatusPageResourceService from "CommonServer/Services/StatusPageResourceService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import StatusPageSubscriberService from "CommonServer/Services/StatusPageSubscriberService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import logger from "CommonServer/Utils/Logger"; +import Incident from "Model/Models/Incident"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import Monitor from "Model/Models/Monitor"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageResource from "Model/Models/StatusPageResource"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; RunCron( - 'IncidentPublicNote:SendNotificationToSubscribers', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all incident notes of all the projects + "IncidentPublicNote:SendNotificationToSubscribers", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all incident notes of all the projects - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - const incidentPublicNoteNotes: Array = - await IncidentPublicNoteService.findBy({ - query: { - isStatusPageSubscribersNotifiedOnNoteCreated: false, - shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true, - createdAt: QueryHelper.lessThan( - OneUptimeDate.getCurrentDate() - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - note: true, - incidentId: true, - }, + const incidentPublicNoteNotes: Array = + await IncidentPublicNoteService.findBy({ + query: { + isStatusPageSubscribersNotifiedOnNoteCreated: false, + shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true, + createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + note: true, + incidentId: true, + }, + }); + + for (const incidentPublicNote of incidentPublicNoteNotes) { + if (!incidentPublicNote.incidentId) { + continue; // skip if incidentId is not set + } + + // get all scheduled events of all the projects. + const incident: Incident | null = await IncidentService.findOneById({ + id: incidentPublicNote.incidentId!, + props: { + isRoot: true, + }, + select: { + _id: true, + title: true, + description: true, + monitors: { + _id: true, + }, + incidentSeverity: { + name: true, + }, + }, + }); + + if (!incident) { + continue; + } + + if (!incident.monitors || incident.monitors.length === 0) { + continue; + } + + await IncidentPublicNoteService.updateOneById({ + id: incidentPublicNote.id!, + data: { + isStatusPageSubscribersNotifiedOnNoteCreated: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + // get status page resources from monitors. + + const statusPageResources: Array = + await StatusPageResourceService.findBy({ + query: { + monitorId: QueryHelper.any( + incident.monitors + .filter((m: Monitor) => { + return m._id; + }) + .map((m: Monitor) => { + return new ObjectID(m._id!); + }), + ), + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + _id: true, + displayName: true, + statusPageId: true, + }, + }); + + const statusPageToResources: Dictionary> = {}; + + for (const resource of statusPageResources) { + if (!resource.statusPageId) { + continue; + } + + if (!statusPageToResources[resource.statusPageId?.toString()]) { + statusPageToResources[resource.statusPageId?.toString()] = []; + } + + statusPageToResources[resource.statusPageId?.toString()]?.push( + resource, + ); + } + + const statusPages: Array = + await StatusPageSubscriberService.getStatusPagesToSendNotification( + Object.keys(statusPageToResources).map((i: string) => { + return new ObjectID(i); + }), + ); + + for (const statuspage of statusPages) { + if (!statuspage.id) { + continue; + } + + const subscribers: Array = + await StatusPageSubscriberService.getSubscribersByStatusPage( + statuspage.id!, + { + isRoot: true, + ignoreHooks: true, + }, + ); + + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statuspage.id, + ); + const statusPageName: string = + statuspage.pageTitle || statuspage.name || "Status Page"; + + // Send email to Email subscribers. + + for (const subscriber of subscribers) { + if (!subscriber._id) { + continue; + } + + const shouldNotifySubscriber: boolean = + StatusPageSubscriberService.shouldSendNotification({ + subscriber: subscriber, + statusPageResources: statusPageToResources[statuspage._id!] || [], + statusPage: statuspage, }); - for (const incidentPublicNote of incidentPublicNoteNotes) { - if (!incidentPublicNote.incidentId) { - continue; // skip if incidentId is not set - } + if (!shouldNotifySubscriber) { + continue; + } - // get all scheduled events of all the projects. - const incident: Incident | null = await IncidentService.findOneById( - { - id: incidentPublicNote.incidentId!, - props: { - isRoot: true, - }, - select: { - _id: true, - title: true, - description: true, - monitors: { - _id: true, - }, - incidentSeverity: { - name: true, - }, - }, - } - ); + const unsubscribeUrl: string = + StatusPageSubscriberService.getUnsubscribeLink( + URL.fromString(statusPageURL), + subscriber.id!, + ).toString(); - if (!incident) { - continue; - } - - if (!incident.monitors || incident.monitors.length === 0) { - continue; - } - - await IncidentPublicNoteService.updateOneById({ - id: incidentPublicNote.id!, - data: { - isStatusPageSubscribersNotifiedOnNoteCreated: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - // get status page resources from monitors. - - const statusPageResources: Array = - await StatusPageResourceService.findBy({ - query: { - monitorId: QueryHelper.any( - incident.monitors - .filter((m: Monitor) => { - return m._id; - }) - .map((m: Monitor) => { - return new ObjectID(m._id!); - }) - ), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - _id: true, - displayName: true, - statusPageId: true, - }, - }); - - const statusPageToResources: Dictionary> = - {}; - - for (const resource of statusPageResources) { - if (!resource.statusPageId) { - continue; - } - - if (!statusPageToResources[resource.statusPageId?.toString()]) { - statusPageToResources[resource.statusPageId?.toString()] = - []; - } - - statusPageToResources[resource.statusPageId?.toString()]?.push( - resource - ); - } - - const statusPages: Array = - await StatusPageSubscriberService.getStatusPagesToSendNotification( - Object.keys(statusPageToResources).map((i: string) => { - return new ObjectID(i); - }) - ); - - for (const statuspage of statusPages) { - if (!statuspage.id) { - continue; - } - - const subscribers: Array = - await StatusPageSubscriberService.getSubscribersByStatusPage( - statuspage.id!, - { - isRoot: true, - ignoreHooks: true, - } - ); - - const statusPageURL: string = - await StatusPageService.getStatusPageURL(statuspage.id); - const statusPageName: string = - statuspage.pageTitle || statuspage.name || 'Status Page'; - - // Send email to Email subscribers. - - for (const subscriber of subscribers) { - if (!subscriber._id) { - continue; - } - - const shouldNotifySubscriber: boolean = - StatusPageSubscriberService.shouldSendNotification({ - subscriber: subscriber, - statusPageResources: - statusPageToResources[statuspage._id!] || [], - statusPage: statuspage, - }); - - if (!shouldNotifySubscriber) { - continue; - } - - const unsubscribeUrl: string = - StatusPageSubscriberService.getUnsubscribeLink( - URL.fromString(statusPageURL), - subscriber.id! - ).toString(); - - if (subscriber.subscriberPhone) { - const sms: SMS = { - message: ` + if (subscriber.subscriberPhone) { + const sms: SMS = { + message: ` Incident Update - ${statusPageName} New note has been added to an incident. - Incident Title: ${incident.title || ' - '} + Incident Title: ${incident.title || " - "} To view this note, visit ${statusPageURL} To update notification preferences or unsubscribe, visit ${unsubscribeUrl} `, - to: subscriber.subscriberPhone, - }; + to: subscriber.subscriberPhone, + }; - // send sms here. - SmsService.sendSms(sms, { - projectId: statuspage.projectId, - customTwilioConfig: - ProjectCallSMSConfigService.toTwilioConfig( - statuspage.callSmsConfig - ), - }).catch((err: Error) => { - logger.error(err); - }); - } + // send sms here. + SmsService.sendSms(sms, { + projectId: statuspage.projectId, + customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig( + statuspage.callSmsConfig, + ), + }).catch((err: Error) => { + logger.error(err); + }); + } - if (subscriber.subscriberEmail) { - // send email here. + if (subscriber.subscriberEmail) { + // send email here. - MailService.sendMail( - { - toEmail: subscriber.subscriberEmail, - templateType: - EmailTemplateType.SubscriberIncidentNoteCreated, - vars: { - note: await Markdown.convertToHTML( - incidentPublicNote.note!, - MarkdownContentType.Email - ), - statusPageName: statusPageName, - statusPageUrl: statusPageURL, - logoUrl: statuspage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute( - '/image/' + - statuspage.logoFileId - ) - .toString() - : '', - isPublicStatusPage: - statuspage.isPublicStatusPage - ? 'true' - : 'false', - resourcesAffected: - statusPageToResources[statuspage._id!] - ?.map((r: StatusPageResource) => { - return r.displayName; - }) - .join(', ') || 'None', - incidentSeverity: - incident.incidentSeverity?.name || - ' - ', - incidentTitle: incident.title || '', - incidentDescription: - incident.description || '', - unsubscribeUrl: unsubscribeUrl, - }, - subject: '[Incident Update] ' + statusPageName, - }, - { - mailServer: - ProjectSmtpConfigService.toEmailServer( - statuspage.smtpConfig - ), - projectId: statuspage.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - } + MailService.sendMail( + { + toEmail: subscriber.subscriberEmail, + templateType: EmailTemplateType.SubscriberIncidentNoteCreated, + vars: { + note: await Markdown.convertToHTML( + incidentPublicNote.note!, + MarkdownContentType.Email, + ), + statusPageName: statusPageName, + statusPageUrl: statusPageURL, + logoUrl: statuspage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statuspage.logoFileId) + .toString() + : "", + isPublicStatusPage: statuspage.isPublicStatusPage + ? "true" + : "false", + resourcesAffected: + statusPageToResources[statuspage._id!] + ?.map((r: StatusPageResource) => { + return r.displayName; + }) + .join(", ") || "None", + incidentSeverity: incident.incidentSeverity?.name || " - ", + incidentTitle: incident.title || "", + incidentDescription: incident.description || "", + unsubscribeUrl: unsubscribeUrl, + }, + subject: "[Incident Update] " + statusPageName, + }, + { + mailServer: ProjectSmtpConfigService.toEmailServer( + statuspage.smtpConfig, + ), + projectId: statuspage.projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/IncidentStateTimeline/SendNotificationToSubscribers.ts b/App/FeatureSet/Workers/Jobs/IncidentStateTimeline/SendNotificationToSubscribers.ts index cafcdaa108..f607c6b5a1 100644 --- a/App/FeatureSet/Workers/Jobs/IncidentStateTimeline/SendNotificationToSubscribers.ts +++ b/App/FeatureSet/Workers/Jobs/IncidentStateTimeline/SendNotificationToSubscribers.ts @@ -1,301 +1,283 @@ -import RunCron from '../../Utils/Cron'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import ObjectID from 'Common/Types/ObjectID'; -import SMS from 'Common/Types/SMS/SMS'; -import Text from 'Common/Types/Text'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import IncidentStateTimelineService from 'CommonServer/Services/IncidentStateTimelineService'; -import MailService from 'CommonServer/Services/MailService'; -import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService'; -import ProjectSMTPConfigService from 'CommonServer/Services/ProjectSmtpConfigService'; -import SmsService from 'CommonServer/Services/SmsService'; -import StatusPageResourceService from 'CommonServer/Services/StatusPageResourceService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import StatusPageSubscriberService from 'CommonServer/Services/StatusPageSubscriberService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import logger from 'CommonServer/Utils/Logger'; -import Incident from 'Model/Models/Incident'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import Monitor from 'Model/Models/Monitor'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageResource from 'Model/Models/StatusPageResource'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; +import RunCron from "../../Utils/Cron"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import ObjectID from "Common/Types/ObjectID"; +import SMS from "Common/Types/SMS/SMS"; +import Text from "Common/Types/Text"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import IncidentService from "CommonServer/Services/IncidentService"; +import IncidentStateTimelineService from "CommonServer/Services/IncidentStateTimelineService"; +import MailService from "CommonServer/Services/MailService"; +import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService"; +import ProjectSMTPConfigService from "CommonServer/Services/ProjectSmtpConfigService"; +import SmsService from "CommonServer/Services/SmsService"; +import StatusPageResourceService from "CommonServer/Services/StatusPageResourceService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import StatusPageSubscriberService from "CommonServer/Services/StatusPageSubscriberService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import logger from "CommonServer/Utils/Logger"; +import Incident from "Model/Models/Incident"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import Monitor from "Model/Models/Monitor"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageResource from "Model/Models/StatusPageResource"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; RunCron( - 'IncidentStateTimeline:SendNotificationToSubscribers', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const incidentStateTimelines: Array = - await IncidentStateTimelineService.findBy({ - query: { - isStatusPageSubscribersNotified: false, - shouldStatusPageSubscribersBeNotified: true, - createdAt: QueryHelper.lessThan( - OneUptimeDate.getCurrentDate() - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - incidentId: true, - incidentStateId: true, - incidentState: { - name: true, - }, - }, + "IncidentStateTimeline:SendNotificationToSubscribers", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const incidentStateTimelines: Array = + await IncidentStateTimelineService.findBy({ + query: { + isStatusPageSubscribersNotified: false, + shouldStatusPageSubscribersBeNotified: true, + createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + incidentId: true, + incidentStateId: true, + incidentState: { + name: true, + }, + }, + }); + + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + for (const incidentStateTimeline of incidentStateTimelines) { + await IncidentStateTimelineService.updateOneById({ + id: incidentStateTimeline.id!, + data: { + isStatusPageSubscribersNotified: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + if ( + !incidentStateTimeline.incidentId || + !incidentStateTimeline.incidentStateId + ) { + continue; + } + + if (!incidentStateTimeline.incidentState?.name) { + continue; + } + + // get all scheduled events of all the projects. + const incident: Incident | null = await IncidentService.findOneById({ + id: incidentStateTimeline.incidentId!, + props: { + isRoot: true, + }, + + select: { + _id: true, + title: true, + description: true, + monitors: { + _id: true, + }, + incidentSeverity: { + name: true, + }, + }, + }); + + if (!incident) { + continue; + } + + if (!incident.monitors || incident.monitors.length === 0) { + continue; + } + + // get status page resources from monitors. + + const statusPageResources: Array = + await StatusPageResourceService.findBy({ + query: { + monitorId: QueryHelper.any( + incident.monitors + .filter((m: Monitor) => { + return m._id; + }) + .map((m: Monitor) => { + return new ObjectID(m._id!); + }), + ), + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + _id: true, + displayName: true, + statusPageId: true, + }, + }); + + const statusPageToResources: Dictionary> = {}; + + for (const resource of statusPageResources) { + if (!resource.statusPageId) { + continue; + } + + if (!statusPageToResources[resource.statusPageId?.toString()]) { + statusPageToResources[resource.statusPageId?.toString()] = []; + } + + statusPageToResources[resource.statusPageId?.toString()]?.push( + resource, + ); + } + + const statusPages: Array = + await StatusPageSubscriberService.getStatusPagesToSendNotification( + Object.keys(statusPageToResources).map((i: string) => { + return new ObjectID(i); + }), + ); + + for (const statuspage of statusPages) { + if (!statuspage.id) { + continue; + } + + const subscribers: Array = + await StatusPageSubscriberService.getSubscribersByStatusPage( + statuspage.id!, + { + isRoot: true, + ignoreHooks: true, + }, + ); + + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statuspage.id, + ); + const statusPageName: string = + statuspage.pageTitle || statuspage.name || "Status Page"; + + // Send email to Email subscribers. + + for (const subscriber of subscribers) { + if (!subscriber._id) { + continue; + } + + const shouldNotifySubscriber: boolean = + StatusPageSubscriberService.shouldSendNotification({ + subscriber: subscriber, + statusPageResources: statusPageToResources[statuspage._id!] || [], + statusPage: statuspage, }); - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + if (!shouldNotifySubscriber) { + continue; + } - for (const incidentStateTimeline of incidentStateTimelines) { - await IncidentStateTimelineService.updateOneById({ - id: incidentStateTimeline.id!, - data: { - isStatusPageSubscribersNotified: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + const unsubscribeUrl: string = + StatusPageSubscriberService.getUnsubscribeLink( + URL.fromString(statusPageURL), + subscriber.id!, + ).toString(); - if ( - !incidentStateTimeline.incidentId || - !incidentStateTimeline.incidentStateId - ) { - continue; - } - - if (!incidentStateTimeline.incidentState?.name) { - continue; - } - - // get all scheduled events of all the projects. - const incident: Incident | null = await IncidentService.findOneById( - { - id: incidentStateTimeline.incidentId!, - props: { - isRoot: true, - }, - - select: { - _id: true, - title: true, - description: true, - monitors: { - _id: true, - }, - incidentSeverity: { - name: true, - }, - }, - } - ); - - if (!incident) { - continue; - } - - if (!incident.monitors || incident.monitors.length === 0) { - continue; - } - - // get status page resources from monitors. - - const statusPageResources: Array = - await StatusPageResourceService.findBy({ - query: { - monitorId: QueryHelper.any( - incident.monitors - .filter((m: Monitor) => { - return m._id; - }) - .map((m: Monitor) => { - return new ObjectID(m._id!); - }) - ), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - _id: true, - displayName: true, - statusPageId: true, - }, - }); - - const statusPageToResources: Dictionary> = - {}; - - for (const resource of statusPageResources) { - if (!resource.statusPageId) { - continue; - } - - if (!statusPageToResources[resource.statusPageId?.toString()]) { - statusPageToResources[resource.statusPageId?.toString()] = - []; - } - - statusPageToResources[resource.statusPageId?.toString()]?.push( - resource - ); - } - - const statusPages: Array = - await StatusPageSubscriberService.getStatusPagesToSendNotification( - Object.keys(statusPageToResources).map((i: string) => { - return new ObjectID(i); - }) - ); - - for (const statuspage of statusPages) { - if (!statuspage.id) { - continue; - } - - const subscribers: Array = - await StatusPageSubscriberService.getSubscribersByStatusPage( - statuspage.id!, - { - isRoot: true, - ignoreHooks: true, - } - ); - - const statusPageURL: string = - await StatusPageService.getStatusPageURL(statuspage.id); - const statusPageName: string = - statuspage.pageTitle || statuspage.name || 'Status Page'; - - // Send email to Email subscribers. - - for (const subscriber of subscribers) { - if (!subscriber._id) { - continue; - } - - const shouldNotifySubscriber: boolean = - StatusPageSubscriberService.shouldSendNotification({ - subscriber: subscriber, - statusPageResources: - statusPageToResources[statuspage._id!] || [], - statusPage: statuspage, - }); - - if (!shouldNotifySubscriber) { - continue; - } - - const unsubscribeUrl: string = - StatusPageSubscriberService.getUnsubscribeLink( - URL.fromString(statusPageURL), - subscriber.id! - ).toString(); - - if (subscriber.subscriberPhone) { - const sms: SMS = { - message: ` + if (subscriber.subscriberPhone) { + const sms: SMS = { + message: ` Incident ${Text.uppercaseFirstLetter( - incidentStateTimeline.incidentState.name + incidentStateTimeline.incidentState.name, )} - ${statusPageName} To view this incident, visit ${statusPageURL} To update notification preferences or unsubscribe, visit ${unsubscribeUrl} `, - to: subscriber.subscriberPhone, - }; + to: subscriber.subscriberPhone, + }; - // send sms here. - SmsService.sendSms(sms, { - projectId: statuspage.projectId, - customTwilioConfig: - ProjectCallSMSConfigService.toTwilioConfig( - statuspage.callSmsConfig - ), - }).catch((err: Error) => { - logger.error(err); - }); - } + // send sms here. + SmsService.sendSms(sms, { + projectId: statuspage.projectId, + customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig( + statuspage.callSmsConfig, + ), + }).catch((err: Error) => { + logger.error(err); + }); + } - if (subscriber.subscriberEmail) { - // send email here. + if (subscriber.subscriberEmail) { + // send email here. - MailService.sendMail( - { - toEmail: subscriber.subscriberEmail, - templateType: - EmailTemplateType.SubscriberIncidentStateChanged, - vars: { - statusPageName: statusPageName, - statusPageUrl: statusPageURL, - logoUrl: statuspage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute( - '/image/' + - statuspage.logoFileId - ) - .toString() - : '', - isPublicStatusPage: - statuspage.isPublicStatusPage - ? 'true' - : 'false', - resourcesAffected: - statusPageToResources[statuspage._id!] - ?.map((r: StatusPageResource) => { - return r.displayName; - }) - .join(', ') || 'None', - incidentSeverity: - incident.incidentSeverity?.name || - ' - ', - incidentTitle: incident.title || '', - incidentDescription: - incident.description || '', + MailService.sendMail( + { + toEmail: subscriber.subscriberEmail, + templateType: EmailTemplateType.SubscriberIncidentStateChanged, + vars: { + statusPageName: statusPageName, + statusPageUrl: statusPageURL, + logoUrl: statuspage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statuspage.logoFileId) + .toString() + : "", + isPublicStatusPage: statuspage.isPublicStatusPage + ? "true" + : "false", + resourcesAffected: + statusPageToResources[statuspage._id!] + ?.map((r: StatusPageResource) => { + return r.displayName; + }) + .join(", ") || "None", + incidentSeverity: incident.incidentSeverity?.name || " - ", + incidentTitle: incident.title || "", + incidentDescription: incident.description || "", - incidentState: - incidentStateTimeline.incidentState - .name, - unsubscribeUrl: unsubscribeUrl, - }, - subject: `[Incident ${Text.uppercaseFirstLetter( - incidentStateTimeline.incidentState.name - )}] ${statusPageName}`, - }, - { - mailServer: - ProjectSMTPConfigService.toEmailServer( - statuspage.smtpConfig - ), - projectId: statuspage.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - } + incidentState: incidentStateTimeline.incidentState.name, + unsubscribeUrl: unsubscribeUrl, + }, + subject: `[Incident ${Text.uppercaseFirstLetter( + incidentStateTimeline.incidentState.name, + )}] ${statusPageName}`, + }, + { + mailServer: ProjectSMTPConfigService.toEmailServer( + statuspage.smtpConfig, + ), + projectId: statuspage.projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/IncomingRequestMonitor/CheckHeartbeat.ts b/App/FeatureSet/Workers/Jobs/IncomingRequestMonitor/CheckHeartbeat.ts index 37bb431067..a0883a3785 100644 --- a/App/FeatureSet/Workers/Jobs/IncomingRequestMonitor/CheckHeartbeat.ts +++ b/App/FeatureSet/Workers/Jobs/IncomingRequestMonitor/CheckHeartbeat.ts @@ -1,96 +1,95 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import { CheckOn } from 'Common/Types/Monitor/CriteriaFilter'; -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import MonitorService from 'CommonServer/Services/MonitorService'; -import ProbeMonitorResponseService from 'CommonServer/Utils/Probe/ProbeMonitorResponse'; -import Monitor from 'Model/Models/Monitor'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import { CheckOn } from "Common/Types/Monitor/CriteriaFilter"; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import MonitorService from "CommonServer/Services/MonitorService"; +import ProbeMonitorResponseService from "CommonServer/Utils/Probe/ProbeMonitorResponse"; +import Monitor from "Model/Models/Monitor"; RunCron( - 'IncomingRequestMonitor:CheckHeartbeat', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const incomingRequestMonitors: Array = - await MonitorService.findBy({ - query: { - monitorType: MonitorType.IncomingRequest, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - monitorSteps: true, - incomingRequestReceivedAt: true, - createdAt: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); + "IncomingRequestMonitor:CheckHeartbeat", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const incomingRequestMonitors: Array = await MonitorService.findBy( + { + query: { + monitorType: MonitorType.IncomingRequest, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + monitorSteps: true, + incomingRequestReceivedAt: true, + createdAt: true, + }, + limit: LIMIT_MAX, + skip: 0, + }, + ); - for (const monitor of incomingRequestMonitors) { - if (!monitor.monitorSteps) { - continue; - } + for (const monitor of incomingRequestMonitors) { + if (!monitor.monitorSteps) { + continue; + } - const processRequest: boolean = shouldProcessRequest(monitor); + const processRequest: boolean = shouldProcessRequest(monitor); - if (!processRequest) { - continue; - } + if (!processRequest) { + continue; + } - const incomingRequest: IncomingMonitorRequest = { - monitorId: monitor.id!, - requestHeaders: undefined, - requestBody: undefined, - requestMethod: undefined, - incomingRequestReceivedAt: - monitor.incomingRequestReceivedAt || monitor.createdAt!, - onlyCheckForIncomingRequestReceivedAt: true, - }; + const incomingRequest: IncomingMonitorRequest = { + monitorId: monitor.id!, + requestHeaders: undefined, + requestBody: undefined, + requestMethod: undefined, + incomingRequestReceivedAt: + monitor.incomingRequestReceivedAt || monitor.createdAt!, + onlyCheckForIncomingRequestReceivedAt: true, + }; - await ProbeMonitorResponseService.processProbeResponse( - incomingRequest - ); - } + await ProbeMonitorResponseService.processProbeResponse(incomingRequest); } + }, ); type ShouldProcessRequestFunction = (monitor: Monitor) => boolean; const shouldProcessRequest: ShouldProcessRequestFunction = ( - monitor: Monitor + monitor: Monitor, ): boolean => { - // check if any criteria has request time step. If yes, then process the request. If no then skip the request. - // We dont want Incoming Request Monitor to process the request if there is no criteria that checks for incoming request. - // Those monitors criteria should be checked if the request is receievd from the API and not through the worker. + // check if any criteria has request time step. If yes, then process the request. If no then skip the request. + // We dont want Incoming Request Monitor to process the request if there is no criteria that checks for incoming request. + // Those monitors criteria should be checked if the request is receievd from the API and not through the worker. - let shouldWeProcessRequest: boolean = false; + let shouldWeProcessRequest: boolean = false; - for (const steps of monitor.monitorSteps?.data?.monitorStepsInstanceArray || - []) { - if (steps.data?.monitorCriteria.data?.monitorCriteriaInstanceArray) { - for (const criteria of steps.data?.monitorCriteria.data - ?.monitorCriteriaInstanceArray || []) { - for (const filters of criteria.data?.filters || []) { - if (filters.checkOn === CheckOn.IncomingRequest) { - shouldWeProcessRequest = true; - break; - } - } - - if (shouldWeProcessRequest) { - break; - } - } + for (const steps of monitor.monitorSteps?.data?.monitorStepsInstanceArray || + []) { + if (steps.data?.monitorCriteria.data?.monitorCriteriaInstanceArray) { + for (const criteria of steps.data?.monitorCriteria.data + ?.monitorCriteriaInstanceArray || []) { + for (const filters of criteria.data?.filters || []) { + if (filters.checkOn === CheckOn.IncomingRequest) { + shouldWeProcessRequest = true; + break; + } } if (shouldWeProcessRequest) { - break; + break; } + } } - return shouldWeProcessRequest; + if (shouldWeProcessRequest) { + break; + } + } + + return shouldWeProcessRequest; }; diff --git a/App/FeatureSet/Workers/Jobs/MeteredPlan/ReportTelemetryMeteredPlan.ts b/App/FeatureSet/Workers/Jobs/MeteredPlan/ReportTelemetryMeteredPlan.ts index c1a38808ad..3c3e81687a 100644 --- a/App/FeatureSet/Workers/Jobs/MeteredPlan/ReportTelemetryMeteredPlan.ts +++ b/App/FeatureSet/Workers/Jobs/MeteredPlan/ReportTelemetryMeteredPlan.ts @@ -1,79 +1,79 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Sleep from 'Common/Types/Sleep'; -import { EVERY_DAY, EVERY_FIVE_MINUTE } from 'Common/Utils/CronTime'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Sleep from "Common/Types/Sleep"; +import { EVERY_DAY, EVERY_FIVE_MINUTE } from "Common/Utils/CronTime"; import { - IsBillingEnabled, - IsDevelopment, -} from 'CommonServer/EnvironmentConfig'; -import ProjectService from 'CommonServer/Services/ProjectService'; + IsBillingEnabled, + IsDevelopment, +} from "CommonServer/EnvironmentConfig"; +import ProjectService from "CommonServer/Services/ProjectService"; import { - ActiveMonitoringMeteredPlan, - LogDataIngestMeteredPlan, - MetricsDataIngestMeteredPlan, - TracesDataIngestMetredPlan, -} from 'CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans'; -import logger from 'CommonServer/Utils/Logger'; -import Project from 'Model/Models/Project'; + ActiveMonitoringMeteredPlan, + LogDataIngestMeteredPlan, + MetricsDataIngestMeteredPlan, + TracesDataIngestMetredPlan, +} from "CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans"; +import logger from "CommonServer/Utils/Logger"; +import Project from "Model/Models/Project"; RunCron( - 'MeteredPlan:ReportTelemetryMeteredPlan', - { - schedule: IsDevelopment ? EVERY_FIVE_MINUTE : EVERY_DAY, - runOnStartup: true, - }, - async () => { - if (!IsBillingEnabled) { - logger.debug( - 'MeteredPlan:ReportTelemetryMeteredPlan Billing is not enabled. Skipping job.' - ); - return; - } - - const projects: Array = await ProjectService.findBy({ - query: {}, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - for (const project of projects) { - try { - if (project.id) { - await LogDataIngestMeteredPlan.reportQuantityToBillingProvider( - project.id - ); - - await Sleep.sleep(1000); - - await MetricsDataIngestMeteredPlan.reportQuantityToBillingProvider( - project.id - ); - - await Sleep.sleep(1000); - - await TracesDataIngestMetredPlan.reportQuantityToBillingProvider( - project.id - ); - - await Sleep.sleep(1000); - - await ActiveMonitoringMeteredPlan.reportQuantityToBillingProvider( - project.id! - ); - - await Sleep.sleep(1000); - } - } catch (error) { - logger.error( - `MeteredPlan:ReportTelemetryMeteredPlan Error while reporting telemetry for project ${project.id}: ${error}` - ); - } - } + "MeteredPlan:ReportTelemetryMeteredPlan", + { + schedule: IsDevelopment ? EVERY_FIVE_MINUTE : EVERY_DAY, + runOnStartup: true, + }, + async () => { + if (!IsBillingEnabled) { + logger.debug( + "MeteredPlan:ReportTelemetryMeteredPlan Billing is not enabled. Skipping job.", + ); + return; } + + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const project of projects) { + try { + if (project.id) { + await LogDataIngestMeteredPlan.reportQuantityToBillingProvider( + project.id, + ); + + await Sleep.sleep(1000); + + await MetricsDataIngestMeteredPlan.reportQuantityToBillingProvider( + project.id, + ); + + await Sleep.sleep(1000); + + await TracesDataIngestMetredPlan.reportQuantityToBillingProvider( + project.id, + ); + + await Sleep.sleep(1000); + + await ActiveMonitoringMeteredPlan.reportQuantityToBillingProvider( + project.id!, + ); + + await Sleep.sleep(1000); + } + } catch (error) { + logger.error( + `MeteredPlan:ReportTelemetryMeteredPlan Error while reporting telemetry for project ${project.id}: ${error}`, + ); + } + } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/MonitorMetrics/MonitorMetricsByMinute.ts b/App/FeatureSet/Workers/Jobs/MonitorMetrics/MonitorMetricsByMinute.ts index 02500c1a6b..ed003e263e 100644 --- a/App/FeatureSet/Workers/Jobs/MonitorMetrics/MonitorMetricsByMinute.ts +++ b/App/FeatureSet/Workers/Jobs/MonitorMetrics/MonitorMetricsByMinute.ts @@ -1,24 +1,24 @@ -import RunCron from '../../Utils/Cron'; -import LessThan from 'Common/Types/BaseDatabase/LessThan'; -import OneUptimeDate from 'Common/Types/Date'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import MonitorMetricsByMinuteService from 'CommonServer/Services/MonitorMetricsByMinuteService'; +import RunCron from "../../Utils/Cron"; +import LessThan from "Common/Types/BaseDatabase/LessThan"; +import OneUptimeDate from "Common/Types/Date"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import MonitorMetricsByMinuteService from "CommonServer/Services/MonitorMetricsByMinuteService"; RunCron( - 'MonitorMetrics:HardDeleteMonitorMetricsByMinute', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const oneHourAgo: Date = OneUptimeDate.getSomeMinutesAgo(60); + "MonitorMetrics:HardDeleteMonitorMetricsByMinute", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const oneHourAgo: Date = OneUptimeDate.getSomeMinutesAgo(60); - // Delete all monitor metrics older than one hour + // Delete all monitor metrics older than one hour - await MonitorMetricsByMinuteService.deleteBy({ - query: { - createdAt: new LessThan(oneHourAgo), - }, - props: { - isRoot: true, - }, - }); - } + await MonitorMetricsByMinuteService.deleteBy({ + query: { + createdAt: new LessThan(oneHourAgo), + }, + props: { + isRoot: true, + }, + }); + }, ); diff --git a/App/FeatureSet/Workers/Jobs/MonitorOwners/SendCreatedResourceNotification.ts b/App/FeatureSet/Workers/Jobs/MonitorOwners/SendCreatedResourceNotification.ts index c8cd429b47..b25961dbe2 100644 --- a/App/FeatureSet/Workers/Jobs/MonitorOwners/SendCreatedResourceNotification.ts +++ b/App/FeatureSet/Workers/Jobs/MonitorOwners/SendCreatedResourceNotification.ts @@ -1,126 +1,124 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import MonitorService from 'CommonServer/Services/MonitorService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import Monitor from 'Model/Models/Monitor'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import MonitorService from "CommonServer/Services/MonitorService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import Monitor from "Model/Models/Monitor"; +import User from "Model/Models/User"; RunCron( - 'MonitorOwner:SendCreatedResourceEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. - const monitors: Array = await MonitorService.findBy({ - query: { - isOwnerNotifiedOfResourceCreation: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - name: true, - description: true, - projectId: true, - project: { - name: true, - }, - currentMonitorStatus: { - name: true, - }, + "MonitorOwner:SendCreatedResourceEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. + const monitors: Array = await MonitorService.findBy({ + query: { + isOwnerNotifiedOfResourceCreation: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + name: true, + description: true, + projectId: true, + project: { + name: true, + }, + currentMonitorStatus: { + name: true, + }, + }, + }); + + for (const monitor of monitors) { + await MonitorService.updateOneById({ + id: monitor.id!, + data: { + isOwnerNotifiedOfResourceCreation: true, + }, + props: { + isRoot: true, + }, + }); + + // now find owners. + + let doesResourceHasOwners: boolean = true; + + let owners: Array = await MonitorService.findOwners(monitor.id!); + + if (owners.length === 0) { + doesResourceHasOwners = false; + + // find project owners. + owners = await ProjectService.getOwners(monitor.projectId!); + } + + if (owners.length === 0) { + continue; + } + + const vars: Dictionary = { + monitorName: monitor.name!, + projectName: monitor.project!.name!, + currentStatus: monitor.currentMonitorStatus!.name!, + monitorDescription: await Markdown.convertToHTML( + monitor.description! || "", + MarkdownContentType.Email, + ), + monitorViewLink: ( + await MonitorService.getMonitorLinkInDashboard( + monitor.projectId!, + monitor.id!, + ) + ).toString(), + }; + + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } + + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.MonitorOwnerResourceCreated, + vars: vars, + subject: "[Monitor Created] " + monitor.name!, + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. New monitor created - ${monitor.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. New monitor was created ${monitor.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: monitor.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_MONITOR_CREATED_OWNER_NOTIFICATION, }); - - for (const monitor of monitors) { - await MonitorService.updateOneById({ - id: monitor.id!, - data: { - isOwnerNotifiedOfResourceCreation: true, - }, - props: { - isRoot: true, - }, - }); - - // now find owners. - - let doesResourceHasOwners: boolean = true; - - let owners: Array = await MonitorService.findOwners( - monitor.id! - ); - - if (owners.length === 0) { - doesResourceHasOwners = false; - - // find project owners. - owners = await ProjectService.getOwners(monitor.projectId!); - } - - if (owners.length === 0) { - continue; - } - - const vars: Dictionary = { - monitorName: monitor.name!, - projectName: monitor.project!.name!, - currentStatus: monitor.currentMonitorStatus!.name!, - monitorDescription: await Markdown.convertToHTML( - monitor.description! || '', - MarkdownContentType.Email - ), - monitorViewLink: ( - await MonitorService.getMonitorLinkInDashboard( - monitor.projectId!, - monitor.id! - ) - ).toString(), - }; - - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } - - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: EmailTemplateType.MonitorOwnerResourceCreated, - vars: vars, - subject: '[Monitor Created] ' + monitor.name!, - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. New monitor created - ${monitor.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. New monitor was created ${monitor.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: monitor.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_MONITOR_CREATED_OWNER_NOTIFICATION, - }); - } - } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/MonitorOwners/SendOwnerAddedNotification.ts b/App/FeatureSet/Workers/Jobs/MonitorOwners/SendOwnerAddedNotification.ts index 92f95e4c10..d48343288b 100644 --- a/App/FeatureSet/Workers/Jobs/MonitorOwners/SendOwnerAddedNotification.ts +++ b/App/FeatureSet/Workers/Jobs/MonitorOwners/SendOwnerAddedNotification.ts @@ -1,205 +1,201 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import ObjectID from 'Common/Types/ObjectID'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import MonitorOwnerTeamService from 'CommonServer/Services/MonitorOwnerTeamService'; -import MonitorOwnerUserService from 'CommonServer/Services/MonitorOwnerUserService'; -import MonitorService from 'CommonServer/Services/MonitorService'; -import TeamMemberService from 'CommonServer/Services/TeamMemberService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import Monitor from 'Model/Models/Monitor'; -import MonitorOwnerTeam from 'Model/Models/MonitorOwnerTeam'; -import MonitorOwnerUser from 'Model/Models/MonitorOwnerUser'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import ObjectID from "Common/Types/ObjectID"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import MonitorOwnerTeamService from "CommonServer/Services/MonitorOwnerTeamService"; +import MonitorOwnerUserService from "CommonServer/Services/MonitorOwnerUserService"; +import MonitorService from "CommonServer/Services/MonitorService"; +import TeamMemberService from "CommonServer/Services/TeamMemberService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import Monitor from "Model/Models/Monitor"; +import MonitorOwnerTeam from "Model/Models/MonitorOwnerTeam"; +import MonitorOwnerUser from "Model/Models/MonitorOwnerUser"; +import User from "Model/Models/User"; RunCron( - 'MonitorOwner:SendOwnerAddedEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const monitorOwnerTeams: Array = - await MonitorOwnerTeamService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - monitorId: true, - teamId: true, - }, - }); + "MonitorOwner:SendOwnerAddedEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const monitorOwnerTeams: Array = + await MonitorOwnerTeamService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + monitorId: true, + teamId: true, + }, + }); - const monitorOwnersMap: Dictionary> = {}; + const monitorOwnersMap: Dictionary> = {}; - for (const monitorOwnerTeam of monitorOwnerTeams) { - const monitorId: ObjectID = monitorOwnerTeam.monitorId!; - const teamId: ObjectID = monitorOwnerTeam.teamId!; + for (const monitorOwnerTeam of monitorOwnerTeams) { + const monitorId: ObjectID = monitorOwnerTeam.monitorId!; + const teamId: ObjectID = monitorOwnerTeam.teamId!; - const users: Array = await TeamMemberService.getUsersInTeams([ - teamId, - ]); + const users: Array = await TeamMemberService.getUsersInTeams([ + teamId, + ]); - if (monitorOwnersMap[monitorId.toString()] === undefined) { - monitorOwnersMap[monitorId.toString()] = []; - } + if (monitorOwnersMap[monitorId.toString()] === undefined) { + monitorOwnersMap[monitorId.toString()] = []; + } - for (const user of users) { - (monitorOwnersMap[monitorId.toString()] as Array).push( - user - ); - } + for (const user of users) { + (monitorOwnersMap[monitorId.toString()] as Array).push(user); + } - // mark this as notified. - await MonitorOwnerTeamService.updateOneById({ - id: monitorOwnerTeam.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - const monitorOwnerUsers: Array = - await MonitorOwnerUserService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - monitorId: true, - userId: true, - user: { - email: true, - name: true, - }, - }, - }); - - for (const monitorOwnerUser of monitorOwnerUsers) { - const monitorId: ObjectID = monitorOwnerUser.monitorId!; - const user: User = monitorOwnerUser.user!; - - if (monitorOwnersMap[monitorId.toString()] === undefined) { - monitorOwnersMap[monitorId.toString()] = []; - } - - (monitorOwnersMap[monitorId.toString()] as Array).push(user); - - // mark this as notified. - await MonitorOwnerUserService.updateOneById({ - id: monitorOwnerUser.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - // send email to all of these users. - - for (const monitorId in monitorOwnersMap) { - if (!monitorOwnersMap[monitorId]) { - continue; - } - - if ((monitorOwnersMap[monitorId] as Array).length === 0) { - continue; - } - - const users: Array = monitorOwnersMap[ - monitorId - ] as Array; - - // get all scheduled events of all the projects. - const monitor: Monitor | null = await MonitorService.findOneById({ - id: new ObjectID(monitorId), - props: { - isRoot: true, - }, - - select: { - _id: true, - name: true, - description: true, - projectId: true, - project: { - name: true, - }, - currentMonitorStatus: { - name: true, - }, - }, - }); - - if (!monitor) { - continue; - } - - const vars: Dictionary = { - monitorName: monitor.name!, - projectName: monitor.project!.name!, - currentStatus: monitor.currentMonitorStatus!.name!, - monitorDescription: await Markdown.convertToHTML( - monitor.description! || '', - MarkdownContentType.Email - ), - monitorViewLink: ( - await MonitorService.getMonitorLinkInDashboard( - monitor.projectId!, - monitor.id! - ) - ).toString(), - }; - - for (const user of users) { - const emailMessage: EmailEnvelope = { - templateType: EmailTemplateType.MonitorOwnerAdded, - vars: vars, - subject: 'You have been added as the owner of the monitor.', - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. You have been added as the owner of the monitor - ${monitor.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. You have been added as the owner of the monitor ${monitor.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: monitor.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_MONITOR_OWNER_ADDED_NOTIFICATION, - }); - } - } + // mark this as notified. + await MonitorOwnerTeamService.updateOneById({ + id: monitorOwnerTeam.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); } + + const monitorOwnerUsers: Array = + await MonitorOwnerUserService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + monitorId: true, + userId: true, + user: { + email: true, + name: true, + }, + }, + }); + + for (const monitorOwnerUser of monitorOwnerUsers) { + const monitorId: ObjectID = monitorOwnerUser.monitorId!; + const user: User = monitorOwnerUser.user!; + + if (monitorOwnersMap[monitorId.toString()] === undefined) { + monitorOwnersMap[monitorId.toString()] = []; + } + + (monitorOwnersMap[monitorId.toString()] as Array).push(user); + + // mark this as notified. + await MonitorOwnerUserService.updateOneById({ + id: monitorOwnerUser.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); + } + + // send email to all of these users. + + for (const monitorId in monitorOwnersMap) { + if (!monitorOwnersMap[monitorId]) { + continue; + } + + if ((monitorOwnersMap[monitorId] as Array).length === 0) { + continue; + } + + const users: Array = monitorOwnersMap[monitorId] as Array; + + // get all scheduled events of all the projects. + const monitor: Monitor | null = await MonitorService.findOneById({ + id: new ObjectID(monitorId), + props: { + isRoot: true, + }, + + select: { + _id: true, + name: true, + description: true, + projectId: true, + project: { + name: true, + }, + currentMonitorStatus: { + name: true, + }, + }, + }); + + if (!monitor) { + continue; + } + + const vars: Dictionary = { + monitorName: monitor.name!, + projectName: monitor.project!.name!, + currentStatus: monitor.currentMonitorStatus!.name!, + monitorDescription: await Markdown.convertToHTML( + monitor.description! || "", + MarkdownContentType.Email, + ), + monitorViewLink: ( + await MonitorService.getMonitorLinkInDashboard( + monitor.projectId!, + monitor.id!, + ) + ).toString(), + }; + + for (const user of users) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.MonitorOwnerAdded, + vars: vars, + subject: "You have been added as the owner of the monitor.", + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. You have been added as the owner of the monitor - ${monitor.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. You have been added as the owner of the monitor ${monitor.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: monitor.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_MONITOR_OWNER_ADDED_NOTIFICATION, + }); + } + } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/MonitorOwners/SendStatusChangeNotification.ts b/App/FeatureSet/Workers/Jobs/MonitorOwners/SendStatusChangeNotification.ts index 6e88ff4b5c..64d3c629fd 100644 --- a/App/FeatureSet/Workers/Jobs/MonitorOwners/SendStatusChangeNotification.ts +++ b/App/FeatureSet/Workers/Jobs/MonitorOwners/SendStatusChangeNotification.ts @@ -1,158 +1,154 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import MonitorService from 'CommonServer/Services/MonitorService'; -import MonitorStatusTimelineService from 'CommonServer/Services/MonitorStatusTimelineService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import MonitorService from "CommonServer/Services/MonitorService"; +import MonitorStatusTimelineService from "CommonServer/Services/MonitorStatusTimelineService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import User from "Model/Models/User"; RunCron( - 'MonitorOwner:SendStatusChangeEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. + "MonitorOwner:SendStatusChangeEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. - const monitorStatusTimelines: Array = - await MonitorStatusTimelineService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - projectId: true, - createdAt: true, - project: { - name: true, - }, - monitor: { - _id: true, - name: true, - description: true, - }, - monitorStatus: { - name: true, - }, - rootCause: true, - }, - }); + const monitorStatusTimelines: Array = + await MonitorStatusTimelineService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + projectId: true, + createdAt: true, + project: { + name: true, + }, + monitor: { + _id: true, + name: true, + description: true, + }, + monitorStatus: { + name: true, + }, + rootCause: true, + }, + }); - for (const monitorStatusTimeline of monitorStatusTimelines) { - const monitor: Monitor = monitorStatusTimeline.monitor!; - const monitorStatus: MonitorStatus = - monitorStatusTimeline.monitorStatus!; + for (const monitorStatusTimeline of monitorStatusTimelines) { + const monitor: Monitor = monitorStatusTimeline.monitor!; + const monitorStatus: MonitorStatus = monitorStatusTimeline.monitorStatus!; - await MonitorStatusTimelineService.updateOneById({ - id: monitorStatusTimeline.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); + await MonitorStatusTimelineService.updateOneById({ + id: monitorStatusTimeline.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); - // now find owners. + // now find owners. - let doesResourceHasOwners: boolean = true; + let doesResourceHasOwners: boolean = true; - let owners: Array = await MonitorService.findOwners( - monitor.id! - ); + let owners: Array = await MonitorService.findOwners(monitor.id!); - if (owners.length === 0) { - doesResourceHasOwners = false; + if (owners.length === 0) { + doesResourceHasOwners = false; - // find project owners. - owners = await ProjectService.getOwners( - monitorStatusTimeline.projectId! - ); - } + // find project owners. + owners = await ProjectService.getOwners( + monitorStatusTimeline.projectId!, + ); + } - if (owners.length === 0) { - continue; - } + if (owners.length === 0) { + continue; + } - const vars: Dictionary = { - monitorName: monitor.name!, - projectName: monitorStatusTimeline.project!.name!, - currentStatus: monitorStatus!.name!, - monitorDescription: await Markdown.convertToHTML( - monitor.description! || '', - MarkdownContentType.Email - ), - statusChangedAt: - OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones( - monitorStatusTimeline.createdAt! - ), - monitorViewLink: ( - await MonitorService.getMonitorLinkInDashboard( - monitorStatusTimeline.projectId!, - monitor.id! - ) - ).toString(), - rootCause: - monitorStatusTimeline.rootCause || - 'No root cause identified.', - }; + const vars: Dictionary = { + monitorName: monitor.name!, + projectName: monitorStatusTimeline.project!.name!, + currentStatus: monitorStatus!.name!, + monitorDescription: await Markdown.convertToHTML( + monitor.description! || "", + MarkdownContentType.Email, + ), + statusChangedAt: + OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones( + monitorStatusTimeline.createdAt!, + ), + monitorViewLink: ( + await MonitorService.getMonitorLinkInDashboard( + monitorStatusTimeline.projectId!, + monitor.id!, + ) + ).toString(), + rootCause: + monitorStatusTimeline.rootCause || "No root cause identified.", + }; - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: EmailTemplateType.MonitorOwnerStatusChanged, - vars: vars, - subject: `[Monitor] ${ - monitor.name || 'Monitor' - } is ${monitorStatus!.name!}`, - }; + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.MonitorOwnerStatusChanged, + vars: vars, + subject: `[Monitor] ${ + monitor.name || "Monitor" + } is ${monitorStatus!.name!}`, + }; - const sms: SMSMessage = { - message: `This is a message from OneUptime. ${ - monitor.name || 'Monitor' - } status changed to ${monitorStatus! - .name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; + const sms: SMSMessage = { + message: `This is a message from OneUptime. ${ + monitor.name || "Monitor" + } status changed to ${monitorStatus! + .name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. ${ - monitor.name || 'Monitor' - } status changed to ${monitorStatus! - .name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. ${ + monitor.name || "Monitor" + } status changed to ${monitorStatus! + .name!}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: monitorStatusTimeline.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION, - }); - } - } + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: monitorStatusTimeline.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION, + }); + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/OnCallDutyPolicyExecutionLog/ExecutePendingExecutions.ts b/App/FeatureSet/Workers/Jobs/OnCallDutyPolicyExecutionLog/ExecutePendingExecutions.ts index 27bde2a59e..b86a0849ba 100644 --- a/App/FeatureSet/Workers/Jobs/OnCallDutyPolicyExecutionLog/ExecutePendingExecutions.ts +++ b/App/FeatureSet/Workers/Jobs/OnCallDutyPolicyExecutionLog/ExecutePendingExecutions.ts @@ -1,218 +1,214 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import OnCallDutyPolicyStatus from 'Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import OnCallDutyPolicyEscalationRuleService from 'CommonServer/Services/OnCallDutyPolicyEscalationRuleService'; -import OnCallDutyPolicyExecutionLogService from 'CommonServer/Services/OnCallDutyPolicyExecutionLogService'; -import logger from 'CommonServer/Utils/Logger'; -import OnCallDutyPolicyEscalationRule from 'Model/Models/OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicyExecutionLog from 'Model/Models/OnCallDutyPolicyExecutionLog'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import OnCallDutyPolicyStatus from "Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import OnCallDutyPolicyEscalationRuleService from "CommonServer/Services/OnCallDutyPolicyEscalationRuleService"; +import OnCallDutyPolicyExecutionLogService from "CommonServer/Services/OnCallDutyPolicyExecutionLogService"; +import logger from "CommonServer/Utils/Logger"; +import OnCallDutyPolicyEscalationRule from "Model/Models/OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicyExecutionLog from "Model/Models/OnCallDutyPolicyExecutionLog"; RunCron( - 'OnCallDutyPolicyExecutionLog:ExecutePendingExecutions', - { - schedule: EVERY_MINUTE, - runOnStartup: false, - }, - async () => { - // get all pending on-call executions and execute them all at once. + "OnCallDutyPolicyExecutionLog:ExecutePendingExecutions", + { + schedule: EVERY_MINUTE, + runOnStartup: false, + }, + async () => { + // get all pending on-call executions and execute them all at once. - const pendingExecutions: Array = - await OnCallDutyPolicyExecutionLogService.findBy({ - query: { - status: OnCallDutyPolicyStatus.Executing, - }, - select: { - _id: true, - projectId: true, - onCallDutyPolicyId: true, - lastEscalationRuleExecutedAt: true, - lastExecutedEscalationRuleId: true, - lastExecutedEscalationRuleOrder: true, - executeNextEscalationRuleInMinutes: true, - userNotificationEventType: true, - triggeredByIncidentId: true, - createdAt: true, - onCallDutyPolicy: { - repeatPolicyIfNoOneAcknowledgesNoOfTimes: true, - }, - onCallPolicyExecutionRepeatCount: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); + const pendingExecutions: Array = + await OnCallDutyPolicyExecutionLogService.findBy({ + query: { + status: OnCallDutyPolicyStatus.Executing, + }, + select: { + _id: true, + projectId: true, + onCallDutyPolicyId: true, + lastEscalationRuleExecutedAt: true, + lastExecutedEscalationRuleId: true, + lastExecutedEscalationRuleOrder: true, + executeNextEscalationRuleInMinutes: true, + userNotificationEventType: true, + triggeredByIncidentId: true, + createdAt: true, + onCallDutyPolicy: { + repeatPolicyIfNoOneAcknowledgesNoOfTimes: true, + }, + onCallPolicyExecutionRepeatCount: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); - const promises: Array> = []; + const promises: Array> = []; - for (const executionLog of pendingExecutions) { - promises.push(executeOnCallPolicy(executionLog)); - } - - await Promise.allSettled(promises); + for (const executionLog of pendingExecutions) { + promises.push(executeOnCallPolicy(executionLog)); } + + await Promise.allSettled(promises); + }, ); type ExecuteOnCallPolicyFunction = ( - executionLog: OnCallDutyPolicyExecutionLog + executionLog: OnCallDutyPolicyExecutionLog, ) => Promise; const executeOnCallPolicy: ExecuteOnCallPolicyFunction = async ( - executionLog: OnCallDutyPolicyExecutionLog + executionLog: OnCallDutyPolicyExecutionLog, ): Promise => { - try { - // check if this execution needs to be executed. + try { + // check if this execution needs to be executed. - const currentDate: Date = OneUptimeDate.getCurrentDate(); + const currentDate: Date = OneUptimeDate.getCurrentDate(); - const lastExecutedAt: Date = - executionLog.lastEscalationRuleExecutedAt || - executionLog.createdAt!; + const lastExecutedAt: Date = + executionLog.lastEscalationRuleExecutedAt || executionLog.createdAt!; - const getDifferenceInMinutes: number = - OneUptimeDate.getDifferenceInMinutes(lastExecutedAt, currentDate); + const getDifferenceInMinutes: number = OneUptimeDate.getDifferenceInMinutes( + lastExecutedAt, + currentDate, + ); - if ( - getDifferenceInMinutes < - (executionLog.executeNextEscalationRuleInMinutes || 0) - ) { - return; + if ( + getDifferenceInMinutes < + (executionLog.executeNextEscalationRuleInMinutes || 0) + ) { + return; + } + + // get the next escalation rule to execute. + const nextEscalationRule: OnCallDutyPolicyEscalationRule | null = + await OnCallDutyPolicyEscalationRuleService.findOneBy({ + query: { + projectId: executionLog.projectId!, + onCallDutyPolicyId: executionLog.onCallDutyPolicyId!, + order: executionLog.lastExecutedEscalationRuleOrder! + 1, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + if (!nextEscalationRule) { + // check if we need to repeat this execution. + + if ( + executionLog.onCallPolicyExecutionRepeatCount && + executionLog.onCallPolicyExecutionRepeatCount < + executionLog.onCallDutyPolicy! + .repeatPolicyIfNoOneAcknowledgesNoOfTimes! + ) { + // repeating execution + + const newRepeatCount: number = + executionLog.onCallPolicyExecutionRepeatCount + 1; + + await OnCallDutyPolicyExecutionLogService.updateOneById({ + id: executionLog.id!, + data: { + onCallPolicyExecutionRepeatCount: newRepeatCount, + }, + props: { + isRoot: true, + }, + }); + + // get first escalation rule. + + const firstEscalationRule: OnCallDutyPolicyEscalationRule | null = + await OnCallDutyPolicyEscalationRuleService.findOneBy({ + query: { + projectId: executionLog.projectId!, + onCallDutyPolicyId: executionLog.onCallDutyPolicyId!, + order: 1, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + if (!firstEscalationRule) { + // mark this as complete. + await OnCallDutyPolicyExecutionLogService.updateOneById({ + id: executionLog.id!, + data: { + status: OnCallDutyPolicyStatus.Completed, + statusMessage: "Execution completed.", + }, + props: { + isRoot: true, + }, + }); + + return; } - // get the next escalation rule to execute. - const nextEscalationRule: OnCallDutyPolicyEscalationRule | null = - await OnCallDutyPolicyEscalationRuleService.findOneBy({ - query: { - projectId: executionLog.projectId!, - onCallDutyPolicyId: executionLog.onCallDutyPolicyId!, - order: executionLog.lastExecutedEscalationRuleOrder! + 1, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - if (!nextEscalationRule) { - // check if we need to repeat this execution. - - if ( - executionLog.onCallPolicyExecutionRepeatCount && - executionLog.onCallPolicyExecutionRepeatCount < - executionLog.onCallDutyPolicy! - .repeatPolicyIfNoOneAcknowledgesNoOfTimes! - ) { - // repeating execution - - const newRepeatCount: number = - executionLog.onCallPolicyExecutionRepeatCount + 1; - - await OnCallDutyPolicyExecutionLogService.updateOneById({ - id: executionLog.id!, - data: { - onCallPolicyExecutionRepeatCount: newRepeatCount, - }, - props: { - isRoot: true, - }, - }); - - // get first escalation rule. - - const firstEscalationRule: OnCallDutyPolicyEscalationRule | null = - await OnCallDutyPolicyEscalationRuleService.findOneBy({ - query: { - projectId: executionLog.projectId!, - onCallDutyPolicyId: - executionLog.onCallDutyPolicyId!, - order: 1, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - if (!firstEscalationRule) { - // mark this as complete. - await OnCallDutyPolicyExecutionLogService.updateOneById({ - id: executionLog.id!, - data: { - status: OnCallDutyPolicyStatus.Completed, - statusMessage: 'Execution completed.', - }, - props: { - isRoot: true, - }, - }); - - return; - } - - // update the execution log. - await OnCallDutyPolicyEscalationRuleService.startRuleExecution( - firstEscalationRule.id!, - { - projectId: executionLog.projectId!, - triggeredByIncidentId: - executionLog.triggeredByIncidentId, - userNotificationEventType: - executionLog.userNotificationEventType!, - onCallPolicyExecutionLogId: executionLog.id!, - onCallPolicyId: executionLog.onCallDutyPolicyId!, - } - ); - - return; - } - // mark this as complete as we have no rules to execute. - await OnCallDutyPolicyExecutionLogService.updateOneById({ - id: executionLog.id!, - data: { - status: OnCallDutyPolicyStatus.Completed, - statusMessage: 'Execution completed.', - }, - props: { - isRoot: true, - }, - }); - return; - } + // update the execution log. await OnCallDutyPolicyEscalationRuleService.startRuleExecution( - nextEscalationRule!.id!, - { - projectId: executionLog.projectId!, - triggeredByIncidentId: executionLog.triggeredByIncidentId, - userNotificationEventType: - executionLog.userNotificationEventType!, - onCallPolicyExecutionLogId: executionLog.id!, - onCallPolicyId: executionLog.onCallDutyPolicyId!, - } + firstEscalationRule.id!, + { + projectId: executionLog.projectId!, + triggeredByIncidentId: executionLog.triggeredByIncidentId, + userNotificationEventType: executionLog.userNotificationEventType!, + onCallPolicyExecutionLogId: executionLog.id!, + onCallPolicyId: executionLog.onCallDutyPolicyId!, + }, ); return; - } catch (err: any) { - logger.error(err); - - // update this log with error message. - await OnCallDutyPolicyExecutionLogService.updateOneById({ - id: executionLog.id!, - data: { - status: OnCallDutyPolicyStatus.Error, - statusMessage: - err.message || - 'Error occurred while executing the on-call policy.', - }, - props: { - isRoot: true, - }, - }); + } + // mark this as complete as we have no rules to execute. + await OnCallDutyPolicyExecutionLogService.updateOneById({ + id: executionLog.id!, + data: { + status: OnCallDutyPolicyStatus.Completed, + statusMessage: "Execution completed.", + }, + props: { + isRoot: true, + }, + }); + return; } + await OnCallDutyPolicyEscalationRuleService.startRuleExecution( + nextEscalationRule!.id!, + { + projectId: executionLog.projectId!, + triggeredByIncidentId: executionLog.triggeredByIncidentId, + userNotificationEventType: executionLog.userNotificationEventType!, + onCallPolicyExecutionLogId: executionLog.id!, + onCallPolicyId: executionLog.onCallDutyPolicyId!, + }, + ); + + return; + } catch (err: any) { + logger.error(err); + + // update this log with error message. + await OnCallDutyPolicyExecutionLogService.updateOneById({ + id: executionLog.id!, + data: { + status: OnCallDutyPolicyStatus.Error, + statusMessage: + err.message || "Error occurred while executing the on-call policy.", + }, + props: { + isRoot: true, + }, + }); + } }; diff --git a/App/FeatureSet/Workers/Jobs/OnCallDutyPolicyExecutionLog/TimeoutStuckExecutions.ts b/App/FeatureSet/Workers/Jobs/OnCallDutyPolicyExecutionLog/TimeoutStuckExecutions.ts index 007cb73521..eb7437a120 100644 --- a/App/FeatureSet/Workers/Jobs/OnCallDutyPolicyExecutionLog/TimeoutStuckExecutions.ts +++ b/App/FeatureSet/Workers/Jobs/OnCallDutyPolicyExecutionLog/TimeoutStuckExecutions.ts @@ -1,55 +1,55 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import OnCallDutyPolicyStatus from 'Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import { IsDevelopment } from 'CommonServer/EnvironmentConfig'; -import OnCallDutyPolicyExecutionLogService from 'CommonServer/Services/OnCallDutyPolicyExecutionLogService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import OnCallDutyPolicyExecutionLog from 'Model/Models/OnCallDutyPolicyExecutionLog'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import OnCallDutyPolicyStatus from "Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import { IsDevelopment } from "CommonServer/EnvironmentConfig"; +import OnCallDutyPolicyExecutionLogService from "CommonServer/Services/OnCallDutyPolicyExecutionLogService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import OnCallDutyPolicyExecutionLog from "Model/Models/OnCallDutyPolicyExecutionLog"; /** * Jobs move from Started to Executing in seconds. If it takes more than 5 minutes, it's stuck. So, mark them as error */ RunCron( - 'OnCallDutyPolicyExecutionLog:TimeoutStuckExecutions', - { - schedule: IsDevelopment ? EVERY_MINUTE : EVERY_MINUTE, - runOnStartup: false, - }, - async () => { - // get all pending on-call executions and execute them all at once. - const fiveMinsAgo: Date = OneUptimeDate.getSomeMinutesAgo(5); + "OnCallDutyPolicyExecutionLog:TimeoutStuckExecutions", + { + schedule: IsDevelopment ? EVERY_MINUTE : EVERY_MINUTE, + runOnStartup: false, + }, + async () => { + // get all pending on-call executions and execute them all at once. + const fiveMinsAgo: Date = OneUptimeDate.getSomeMinutesAgo(5); - const stuckExecutions: Array = - await OnCallDutyPolicyExecutionLogService.findBy({ - query: { - status: OnCallDutyPolicyStatus.Started, - createdAt: QueryHelper.lessThan(fiveMinsAgo), - }, - select: { - _id: true, - createdAt: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); + const stuckExecutions: Array = + await OnCallDutyPolicyExecutionLogService.findBy({ + query: { + status: OnCallDutyPolicyStatus.Started, + createdAt: QueryHelper.lessThan(fiveMinsAgo), + }, + select: { + _id: true, + createdAt: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); - for (const executionLog of stuckExecutions) { - await OnCallDutyPolicyExecutionLogService.updateOneById({ - id: executionLog.id!, - data: { - status: OnCallDutyPolicyStatus.Error, - statusMessage: 'Policy Execution timed out.', - }, - props: { - isRoot: true, - }, - }); - } + for (const executionLog of stuckExecutions) { + await OnCallDutyPolicyExecutionLogService.updateOneById({ + id: executionLog.id!, + data: { + status: OnCallDutyPolicyStatus.Error, + statusMessage: "Policy Execution timed out.", + }, + props: { + isRoot: true, + }, + }); } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/PaymentProvider/CheckSubscriptionStatus.ts b/App/FeatureSet/Workers/Jobs/PaymentProvider/CheckSubscriptionStatus.ts index 0925137a2d..81dc015e2e 100644 --- a/App/FeatureSet/Workers/Jobs/PaymentProvider/CheckSubscriptionStatus.ts +++ b/App/FeatureSet/Workers/Jobs/PaymentProvider/CheckSubscriptionStatus.ts @@ -1,92 +1,90 @@ -import RunCron from '../../Utils/Cron'; -import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Sleep from 'Common/Types/Sleep'; -import { EVERY_DAY, EVERY_MINUTE } from 'Common/Utils/CronTime'; +import RunCron from "../../Utils/Cron"; +import SubscriptionStatus from "Common/Types/Billing/SubscriptionStatus"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Sleep from "Common/Types/Sleep"; +import { EVERY_DAY, EVERY_MINUTE } from "Common/Utils/CronTime"; import { - IsBillingEnabled, - IsDevelopment, -} from 'CommonServer/EnvironmentConfig'; -import BillingService from 'CommonServer/Services/BillingService'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import logger from 'CommonServer/Utils/Logger'; -import Project from 'Model/Models/Project'; + IsBillingEnabled, + IsDevelopment, +} from "CommonServer/EnvironmentConfig"; +import BillingService from "CommonServer/Services/BillingService"; +import ProjectService from "CommonServer/Services/ProjectService"; +import logger from "CommonServer/Utils/Logger"; +import Project from "Model/Models/Project"; RunCron( - 'PaymentProvider:CheckSubscriptionStatus', - { schedule: IsDevelopment ? EVERY_MINUTE : EVERY_DAY, runOnStartup: true }, - async () => { - // get all projects. - if (!IsBillingEnabled) { - return; - } - - const projects: Array = await ProjectService.findBy({ - query: {}, - select: { - _id: true, - paymentProviderSubscriptionId: true, - paymentProviderMeteredSubscriptionId: true, - paymentProviderCustomerId: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - for (const project of projects) { - try { - if (project.paymentProviderSubscriptionId) { - // get subscription detail. - const subscriptionState: SubscriptionStatus = - await BillingService.getSubscriptionStatus( - project.paymentProviderSubscriptionId as string - ); - - await ProjectService.updateOneById({ - id: project.id!, - data: { - paymentProviderSubscriptionStatus: - subscriptionState, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - // after every subscription fetch, sleep for a 5 seconds to avoid stripe rate limit. - await Sleep.sleep(1000); - } - - if (project.paymentProviderMeteredSubscriptionId) { - // get subscription detail. - const subscriptionState: SubscriptionStatus = - await BillingService.getSubscriptionStatus( - project.paymentProviderMeteredSubscriptionId as string - ); - - await ProjectService.updateOneById({ - id: project.id!, - data: { - paymentProviderMeteredSubscriptionStatus: - subscriptionState, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - // after every subscription fetch, sleep for a 5 seconds to avoid stripe rate limit. - await Sleep.sleep(1000); - } - } catch (err) { - logger.error(err); - } - } + "PaymentProvider:CheckSubscriptionStatus", + { schedule: IsDevelopment ? EVERY_MINUTE : EVERY_DAY, runOnStartup: true }, + async () => { + // get all projects. + if (!IsBillingEnabled) { + return; } + + const projects: Array = await ProjectService.findBy({ + query: {}, + select: { + _id: true, + paymentProviderSubscriptionId: true, + paymentProviderMeteredSubscriptionId: true, + paymentProviderCustomerId: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + for (const project of projects) { + try { + if (project.paymentProviderSubscriptionId) { + // get subscription detail. + const subscriptionState: SubscriptionStatus = + await BillingService.getSubscriptionStatus( + project.paymentProviderSubscriptionId as string, + ); + + await ProjectService.updateOneById({ + id: project.id!, + data: { + paymentProviderSubscriptionStatus: subscriptionState, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + // after every subscription fetch, sleep for a 5 seconds to avoid stripe rate limit. + await Sleep.sleep(1000); + } + + if (project.paymentProviderMeteredSubscriptionId) { + // get subscription detail. + const subscriptionState: SubscriptionStatus = + await BillingService.getSubscriptionStatus( + project.paymentProviderMeteredSubscriptionId as string, + ); + + await ProjectService.updateOneById({ + id: project.id!, + data: { + paymentProviderMeteredSubscriptionStatus: subscriptionState, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + // after every subscription fetch, sleep for a 5 seconds to avoid stripe rate limit. + await Sleep.sleep(1000); + } + } catch (err) { + logger.error(err); + } + } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/PaymentProvider/PopulatePlanNameInProject.ts b/App/FeatureSet/Workers/Jobs/PaymentProvider/PopulatePlanNameInProject.ts index 4a43a2e9c0..ede7e72ca7 100644 --- a/App/FeatureSet/Workers/Jobs/PaymentProvider/PopulatePlanNameInProject.ts +++ b/App/FeatureSet/Workers/Jobs/PaymentProvider/PopulatePlanNameInProject.ts @@ -1,62 +1,62 @@ -import RunCron from '../../Utils/Cron'; +import RunCron from "../../Utils/Cron"; import SubscriptionPlan, { - PlanSelect, -} from 'Common/Types/Billing/SubscriptionPlan'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import { EVERY_WEEK } from 'Common/Utils/CronTime'; -import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import logger from 'CommonServer/Utils/Logger'; -import Project from 'Model/Models/Project'; + PlanSelect, +} from "Common/Types/Billing/SubscriptionPlan"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import { EVERY_WEEK } from "Common/Utils/CronTime"; +import { IsBillingEnabled } from "CommonServer/EnvironmentConfig"; +import ProjectService from "CommonServer/Services/ProjectService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import logger from "CommonServer/Utils/Logger"; +import Project from "Model/Models/Project"; RunCron( - 'PaymentProvider:PopulatePlanNameInProject', - { schedule: EVERY_WEEK, runOnStartup: true }, - async () => { - // get all projects. - if (!IsBillingEnabled) { - return; - } - - const projects: Array = await ProjectService.findBy({ - query: { - planName: QueryHelper.isNull(), - }, - select: { - _id: true, - paymentProviderPlanId: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - for (const project of projects) { - try { - if (project.paymentProviderPlanId) { - // get subscription detail. - const planName: PlanSelect = SubscriptionPlan.getPlanSelect( - project.paymentProviderPlanId as string - ); - - await ProjectService.updateOneById({ - id: project.id!, - data: { - planName: planName, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - } - } catch (err) { - logger.error(err); - } - } + "PaymentProvider:PopulatePlanNameInProject", + { schedule: EVERY_WEEK, runOnStartup: true }, + async () => { + // get all projects. + if (!IsBillingEnabled) { + return; } + + const projects: Array = await ProjectService.findBy({ + query: { + planName: QueryHelper.isNull(), + }, + select: { + _id: true, + paymentProviderPlanId: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + for (const project of projects) { + try { + if (project.paymentProviderPlanId) { + // get subscription detail. + const planName: PlanSelect = SubscriptionPlan.getPlanSelect( + project.paymentProviderPlanId as string, + ); + + await ProjectService.updateOneById({ + id: project.id!, + data: { + planName: planName, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + } + } catch (err) { + logger.error(err); + } + } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/PaymentProvider/UpdateTeamMembersIfNull.ts b/App/FeatureSet/Workers/Jobs/PaymentProvider/UpdateTeamMembersIfNull.ts index 51f186e6bc..fe31856d48 100644 --- a/App/FeatureSet/Workers/Jobs/PaymentProvider/UpdateTeamMembersIfNull.ts +++ b/App/FeatureSet/Workers/Jobs/PaymentProvider/UpdateTeamMembersIfNull.ts @@ -1,34 +1,34 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import { EVERY_DAY, EVERY_MINUTE } from 'Common/Utils/CronTime'; -import { IsDevelopment } from 'CommonServer/EnvironmentConfig'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import TeamMemberService from 'CommonServer/Services/TeamMemberService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Project from 'Model/Models/Project'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import { EVERY_DAY, EVERY_MINUTE } from "Common/Utils/CronTime"; +import { IsDevelopment } from "CommonServer/EnvironmentConfig"; +import ProjectService from "CommonServer/Services/ProjectService"; +import TeamMemberService from "CommonServer/Services/TeamMemberService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Project from "Model/Models/Project"; RunCron( - 'PaymentProvider:UpdateTeamMembersIfNull', - { schedule: IsDevelopment ? EVERY_MINUTE : EVERY_DAY, runOnStartup: true }, - async () => { - const projects: Array = await ProjectService.findBy({ - query: { - paymentProviderSubscriptionSeats: QueryHelper.isNull(), - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); + "PaymentProvider:UpdateTeamMembersIfNull", + { schedule: IsDevelopment ? EVERY_MINUTE : EVERY_DAY, runOnStartup: true }, + async () => { + const projects: Array = await ProjectService.findBy({ + query: { + paymentProviderSubscriptionSeats: QueryHelper.isNull(), + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); - for (const project of projects) { - await TeamMemberService.updateSubscriptionSeatsByUniqueTeamMembersInProject( - project.id! - ); - } + for (const project of projects) { + await TeamMemberService.updateSubscriptionSeatsByUniqueTeamMembersInProject( + project.id!, + ); } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/ChangeStateToEnded.ts b/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/ChangeStateToEnded.ts index d9a2739b2a..52ae2fe61e 100644 --- a/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/ChangeStateToEnded.ts +++ b/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/ChangeStateToEnded.ts @@ -1,79 +1,76 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import ScheduledMaintenanceStateService from 'CommonServer/Services/ScheduledMaintenanceStateService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import ScheduledMaintenanceStateService from "CommonServer/Services/ScheduledMaintenanceStateService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; RunCron( - 'ScheduledMaintenance:ChangeStateToEnded', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. - const events: Array = - await ScheduledMaintenanceService.findBy({ - query: { - currentScheduledMaintenanceState: { - isOngoingState: true, - } as any, - endsAt: QueryHelper.lessThan( - OneUptimeDate.getCurrentDate() - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - projectId: true, - changeMonitorStatusToId: true, - shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded: - true, - monitors: { - _id: true, - }, - }, - }); + "ScheduledMaintenance:ChangeStateToEnded", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. + const events: Array = + await ScheduledMaintenanceService.findBy({ + query: { + currentScheduledMaintenanceState: { + isOngoingState: true, + } as any, + endsAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + projectId: true, + changeMonitorStatusToId: true, + shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded: true, + monitors: { + _id: true, + }, + }, + }); - // change their state to Ongoing. + // change their state to Ongoing. - for (const event of events) { - const scheduledMaintenanceState: ScheduledMaintenanceState | null = - await ScheduledMaintenanceStateService.findOneBy({ - query: { - projectId: event.projectId!, - isEndedState: true, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); + for (const event of events) { + const scheduledMaintenanceState: ScheduledMaintenanceState | null = + await ScheduledMaintenanceStateService.findOneBy({ + query: { + projectId: event.projectId!, + isEndedState: true, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (!scheduledMaintenanceState || !scheduledMaintenanceState.id) { - continue; - } + if (!scheduledMaintenanceState || !scheduledMaintenanceState.id) { + continue; + } - await ScheduledMaintenanceService.changeScheduledMaintenanceState({ - projectId: event.projectId!, - scheduledMaintenanceId: event.id!, - scheduledMaintenanceStateId: scheduledMaintenanceState.id, - shouldNotifyStatusPageSubscribers: Boolean( - event.shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded - ), - isSubscribersNotified: false, - notifyOwners: true, - props: { - isRoot: true, - }, - }); - } + await ScheduledMaintenanceService.changeScheduledMaintenanceState({ + projectId: event.projectId!, + scheduledMaintenanceId: event.id!, + scheduledMaintenanceStateId: scheduledMaintenanceState.id, + shouldNotifyStatusPageSubscribers: Boolean( + event.shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded, + ), + isSubscribersNotified: false, + notifyOwners: true, + props: { + isRoot: true, + }, + }); } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/ChangeStateToOngoing.ts b/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/ChangeStateToOngoing.ts index fd6d393e35..1cdc379588 100644 --- a/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/ChangeStateToOngoing.ts +++ b/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/ChangeStateToOngoing.ts @@ -1,87 +1,81 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import ScheduledMaintenanceStateService from 'CommonServer/Services/ScheduledMaintenanceStateService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import ScheduledMaintenanceStateService from "CommonServer/Services/ScheduledMaintenanceStateService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; RunCron( - 'ScheduledMaintenance:ChangeStateToOngoing', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. - const events: Array = - await ScheduledMaintenanceService.findBy({ - query: { - currentScheduledMaintenanceState: { - isScheduledState: true, - } as any, - startsAt: QueryHelper.lessThan( - OneUptimeDate.getCurrentDate() - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - projectId: true, - changeMonitorStatusToId: true, - shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing: - true, - monitors: { - _id: true, - }, - }, - }); + "ScheduledMaintenance:ChangeStateToOngoing", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. + const events: Array = + await ScheduledMaintenanceService.findBy({ + query: { + currentScheduledMaintenanceState: { + isScheduledState: true, + } as any, + startsAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + projectId: true, + changeMonitorStatusToId: true, + shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing: true, + monitors: { + _id: true, + }, + }, + }); - // change their state to Ongoing. + // change their state to Ongoing. - for (const event of events) { - const scheduledMaintenanceState: ScheduledMaintenanceState | null = - await ScheduledMaintenanceStateService.findOneBy({ - query: { - projectId: event.projectId!, - isOngoingState: true, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); + for (const event of events) { + const scheduledMaintenanceState: ScheduledMaintenanceState | null = + await ScheduledMaintenanceStateService.findOneBy({ + query: { + projectId: event.projectId!, + isOngoingState: true, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (!scheduledMaintenanceState || !scheduledMaintenanceState.id) { - continue; - } + if (!scheduledMaintenanceState || !scheduledMaintenanceState.id) { + continue; + } - await ScheduledMaintenanceService.changeScheduledMaintenanceState({ - projectId: event.projectId!, - scheduledMaintenanceId: event.id!, - scheduledMaintenanceStateId: scheduledMaintenanceState.id, - shouldNotifyStatusPageSubscribers: Boolean( - event.shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing - ), - isSubscribersNotified: false, - notifyOwners: true, - props: { - isRoot: true, - }, - }); + await ScheduledMaintenanceService.changeScheduledMaintenanceState({ + projectId: event.projectId!, + scheduledMaintenanceId: event.id!, + scheduledMaintenanceStateId: scheduledMaintenanceState.id, + shouldNotifyStatusPageSubscribers: Boolean( + event.shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing, + ), + isSubscribersNotified: false, + notifyOwners: true, + props: { + isRoot: true, + }, + }); - // change attached monitor states. - await ScheduledMaintenanceService.changeAttachedMonitorStates( - event, - { - isRoot: true, - } - ); - } + // change attached monitor states. + await ScheduledMaintenanceService.changeAttachedMonitorStates(event, { + isRoot: true, + }); } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/SendNotificationToSubscribers.ts b/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/SendNotificationToSubscribers.ts index a70a593b58..3385b4b054 100644 --- a/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/SendNotificationToSubscribers.ts +++ b/App/FeatureSet/Workers/Jobs/ScheduledMaintenance/SendNotificationToSubscribers.ts @@ -1,276 +1,264 @@ -import RunCron from '../../Utils/Cron'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import ObjectID from 'Common/Types/ObjectID'; -import SMS from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import MailService from 'CommonServer/Services/MailService'; -import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService'; -import ProjectSmtpConfigService from 'CommonServer/Services/ProjectSmtpConfigService'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import SmsService from 'CommonServer/Services/SmsService'; -import StatusPageResourceService from 'CommonServer/Services/StatusPageResourceService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import StatusPageSubscriberService from 'CommonServer/Services/StatusPageSubscriberService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import logger from 'CommonServer/Utils/Logger'; -import Monitor from 'Model/Models/Monitor'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageResource from 'Model/Models/StatusPageResource'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; +import RunCron from "../../Utils/Cron"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import ObjectID from "Common/Types/ObjectID"; +import SMS from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import MailService from "CommonServer/Services/MailService"; +import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService"; +import ProjectSmtpConfigService from "CommonServer/Services/ProjectSmtpConfigService"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import SmsService from "CommonServer/Services/SmsService"; +import StatusPageResourceService from "CommonServer/Services/StatusPageResourceService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import StatusPageSubscriberService from "CommonServer/Services/StatusPageSubscriberService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import logger from "CommonServer/Utils/Logger"; +import Monitor from "Model/Models/Monitor"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageResource from "Model/Models/StatusPageResource"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; RunCron( - 'ScheduledMaintenance:SendNotificationToSubscribers', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + "ScheduledMaintenance:SendNotificationToSubscribers", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - // get all scheduled events of all the projects. - const scheduledEvents: Array = - await ScheduledMaintenanceService.findBy({ - query: { - isStatusPageSubscribersNotifiedOnEventScheduled: false, - shouldStatusPageSubscribersBeNotifiedOnEventCreated: true, - createdAt: QueryHelper.lessThan( - OneUptimeDate.getCurrentDate() - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - title: true, - description: true, - startsAt: true, - monitors: { - _id: true, - }, - statusPages: { - _id: true, - }, - }, + // get all scheduled events of all the projects. + const scheduledEvents: Array = + await ScheduledMaintenanceService.findBy({ + query: { + isStatusPageSubscribersNotifiedOnEventScheduled: false, + shouldStatusPageSubscribersBeNotifiedOnEventCreated: true, + createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + title: true, + description: true, + startsAt: true, + monitors: { + _id: true, + }, + statusPages: { + _id: true, + }, + }, + }); + + for (const event of scheduledEvents) { + // update the flag. + + await ScheduledMaintenanceService.updateOneById({ + id: event.id!, + data: { + isStatusPageSubscribersNotifiedOnEventScheduled: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + // get status page resources from monitors. + + let statusPageResources: Array = []; + + if (event.monitors && event.monitors.length > 0) { + statusPageResources = await StatusPageResourceService.findBy({ + query: { + monitorId: QueryHelper.any( + event.monitors + .filter((m: Monitor) => { + return m._id; + }) + .map((m: Monitor) => { + return new ObjectID(m._id!); + }), + ), + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + _id: true, + displayName: true, + statusPageId: true, + }, + }); + } + + const statusPageToResources: Dictionary> = {}; + + for (const resource of statusPageResources) { + if (!resource.statusPageId) { + continue; + } + + if (!statusPageToResources[resource.statusPageId?.toString()]) { + statusPageToResources[resource.statusPageId?.toString()] = []; + } + + statusPageToResources[resource.statusPageId?.toString()]?.push( + resource, + ); + } + + const statusPages: Array = + await StatusPageSubscriberService.getStatusPagesToSendNotification( + event.statusPages?.map((i: StatusPage) => { + return i.id!; + }) || [], + ); + + for (const statuspage of statusPages) { + if (!statuspage.id) { + continue; + } + + const subscribers: Array = + await StatusPageSubscriberService.getSubscribersByStatusPage( + statuspage.id!, + { + isRoot: true, + ignoreHooks: true, + }, + ); + + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statuspage.id, + ); + + const statusPageName: string = + statuspage.pageTitle || statuspage.name || "Status Page"; + + // Send email to Email subscribers. + + const resourcesAffected: string = + statusPageToResources[statuspage._id!] + ?.map((r: StatusPageResource) => { + return r.displayName; + }) + .join(", ") || ""; + + for (const subscriber of subscribers) { + if (!subscriber._id) { + continue; + } + + const shouldNotifySubscriber: boolean = + StatusPageSubscriberService.shouldSendNotification({ + subscriber: subscriber, + statusPageResources: statusPageToResources[statuspage._id!] || [], + statusPage: statuspage, }); - for (const event of scheduledEvents) { - // update the flag. + if (!shouldNotifySubscriber) { + continue; + } - await ScheduledMaintenanceService.updateOneById({ - id: event.id!, - data: { - isStatusPageSubscribersNotifiedOnEventScheduled: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + const unsubscribeUrl: string = + StatusPageSubscriberService.getUnsubscribeLink( + URL.fromString(statusPageURL), + subscriber.id!, + ).toString(); - // get status page resources from monitors. - - let statusPageResources: Array = []; - - if (event.monitors && event.monitors.length > 0) { - statusPageResources = await StatusPageResourceService.findBy({ - query: { - monitorId: QueryHelper.any( - event.monitors - .filter((m: Monitor) => { - return m._id; - }) - .map((m: Monitor) => { - return new ObjectID(m._id!); - }) - ), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - _id: true, - displayName: true, - statusPageId: true, - }, - }); - } - - const statusPageToResources: Dictionary> = - {}; - - for (const resource of statusPageResources) { - if (!resource.statusPageId) { - continue; - } - - if (!statusPageToResources[resource.statusPageId?.toString()]) { - statusPageToResources[resource.statusPageId?.toString()] = - []; - } - - statusPageToResources[resource.statusPageId?.toString()]?.push( - resource - ); - } - - const statusPages: Array = - await StatusPageSubscriberService.getStatusPagesToSendNotification( - event.statusPages?.map((i: StatusPage) => { - return i.id!; - }) || [] - ); - - for (const statuspage of statusPages) { - if (!statuspage.id) { - continue; - } - - const subscribers: Array = - await StatusPageSubscriberService.getSubscribersByStatusPage( - statuspage.id!, - { - isRoot: true, - ignoreHooks: true, - } - ); - - const statusPageURL: string = - await StatusPageService.getStatusPageURL(statuspage.id); - - const statusPageName: string = - statuspage.pageTitle || statuspage.name || 'Status Page'; - - // Send email to Email subscribers. - - const resourcesAffected: string = - statusPageToResources[statuspage._id!] - ?.map((r: StatusPageResource) => { - return r.displayName; - }) - .join(', ') || ''; - - for (const subscriber of subscribers) { - if (!subscriber._id) { - continue; - } - - const shouldNotifySubscriber: boolean = - StatusPageSubscriberService.shouldSendNotification({ - subscriber: subscriber, - statusPageResources: - statusPageToResources[statuspage._id!] || [], - statusPage: statuspage, - }); - - if (!shouldNotifySubscriber) { - continue; - } - - const unsubscribeUrl: string = - StatusPageSubscriberService.getUnsubscribeLink( - URL.fromString(statusPageURL), - subscriber.id! - ).toString(); - - if (subscriber.subscriberPhone) { - const sms: SMS = { - message: ` + if (subscriber.subscriberPhone) { + const sms: SMS = { + message: ` Scheduled Maintenance - ${statusPageName} - ${event.title || ''} + ${event.title || ""} ${ - resourcesAffected - ? 'Resources Affected: ' + resourcesAffected - : '' + resourcesAffected + ? "Resources Affected: " + resourcesAffected + : "" } To view this event, visit ${statusPageURL} To update notification preferences or unsubscribe, visit ${unsubscribeUrl} `, - to: subscriber.subscriberPhone, - }; + to: subscriber.subscriberPhone, + }; - // send sms here. - SmsService.sendSms(sms, { - projectId: statuspage.projectId, - customTwilioConfig: - ProjectCallSMSConfigService.toTwilioConfig( - statuspage.callSmsConfig - ), - }).catch((err: Error) => { - logger.error(err); - }); - } + // send sms here. + SmsService.sendSms(sms, { + projectId: statuspage.projectId, + customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig( + statuspage.callSmsConfig, + ), + }).catch((err: Error) => { + logger.error(err); + }); + } - if (subscriber.subscriberEmail) { - // send email here. + if (subscriber.subscriberEmail) { + // send email here. - MailService.sendMail( - { - toEmail: subscriber.subscriberEmail, - templateType: - EmailTemplateType.SubscriberScheduledMaintenanceEventCreated, - vars: { - statusPageName: statusPageName, - statusPageUrl: statusPageURL, - logoUrl: statuspage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute( - '/image/' + - statuspage.logoFileId - ) - .toString() - : '', - isPublicStatusPage: - statuspage.isPublicStatusPage - ? 'true' - : 'false', - resourcesAffected: resourcesAffected, - scheduledAt: - OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones( - event.startsAt! - ), - eventTitle: event.title || '', - eventDescription: - await Markdown.convertToHTML( - event.description || '', - MarkdownContentType.Email - ), - unsubscribeUrl: unsubscribeUrl, - }, - subject: - '[Scheduled Maintenance] ' + statusPageName, - }, - { - mailServer: - ProjectSmtpConfigService.toEmailServer( - statuspage.smtpConfig - ), - projectId: statuspage.projectId!, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - } + MailService.sendMail( + { + toEmail: subscriber.subscriberEmail, + templateType: + EmailTemplateType.SubscriberScheduledMaintenanceEventCreated, + vars: { + statusPageName: statusPageName, + statusPageUrl: statusPageURL, + logoUrl: statuspage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statuspage.logoFileId) + .toString() + : "", + isPublicStatusPage: statuspage.isPublicStatusPage + ? "true" + : "false", + resourcesAffected: resourcesAffected, + scheduledAt: + OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones( + event.startsAt!, + ), + eventTitle: event.title || "", + eventDescription: await Markdown.convertToHTML( + event.description || "", + MarkdownContentType.Email, + ), + unsubscribeUrl: unsubscribeUrl, + }, + subject: "[Scheduled Maintenance] " + statusPageName, + }, + { + mailServer: ProjectSmtpConfigService.toEmailServer( + statuspage.smtpConfig, + ), + projectId: statuspage.projectId!, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendCreatedResourceNotification.ts b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendCreatedResourceNotification.ts index 0dfae7bfa4..ee48864e5c 100644 --- a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendCreatedResourceNotification.ts +++ b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendCreatedResourceNotification.ts @@ -1,135 +1,132 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import ProjectService from "CommonServer/Services/ProjectService"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import User from "Model/Models/User"; RunCron( - 'ScheduledMaintenanceOwner:SendCreatedResourceEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. - const scheduledMaintenances: Array = - await ScheduledMaintenanceService.findBy({ - query: { - isOwnerNotifiedOfResourceCreation: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - title: true, - description: true, - projectId: true, - project: { - name: true, - }, - currentScheduledMaintenanceState: { - name: true, - }, - }, - }); + "ScheduledMaintenanceOwner:SendCreatedResourceEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. + const scheduledMaintenances: Array = + await ScheduledMaintenanceService.findBy({ + query: { + isOwnerNotifiedOfResourceCreation: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + title: true, + description: true, + projectId: true, + project: { + name: true, + }, + currentScheduledMaintenanceState: { + name: true, + }, + }, + }); - for (const scheduledMaintenance of scheduledMaintenances) { - await ScheduledMaintenanceService.updateOneById({ - id: scheduledMaintenance.id!, - data: { - isOwnerNotifiedOfResourceCreation: true, - }, - props: { - isRoot: true, - }, - }); + for (const scheduledMaintenance of scheduledMaintenances) { + await ScheduledMaintenanceService.updateOneById({ + id: scheduledMaintenance.id!, + data: { + isOwnerNotifiedOfResourceCreation: true, + }, + props: { + isRoot: true, + }, + }); - // now find owners. + // now find owners. - let doesResourceHasOwners: boolean = true; + let doesResourceHasOwners: boolean = true; - let owners: Array = - await ScheduledMaintenanceService.findOwners( - scheduledMaintenance.id! - ); + let owners: Array = await ScheduledMaintenanceService.findOwners( + scheduledMaintenance.id!, + ); - if (owners.length === 0) { - doesResourceHasOwners = false; + if (owners.length === 0) { + doesResourceHasOwners = false; - // find project owners. - owners = await ProjectService.getOwners( - scheduledMaintenance.projectId! - ); - } + // find project owners. + owners = await ProjectService.getOwners( + scheduledMaintenance.projectId!, + ); + } - if (owners.length === 0) { - continue; - } + if (owners.length === 0) { + continue; + } - const vars: Dictionary = { - scheduledMaintenanceTitle: scheduledMaintenance.title!, - projectName: scheduledMaintenance.project!.name!, - currentState: - scheduledMaintenance.currentScheduledMaintenanceState! - .name!, - scheduledMaintenanceDescription: await Markdown.convertToHTML( - scheduledMaintenance.description! || '', - MarkdownContentType.Email - ), - scheduledMaintenanceViewLink: ( - await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard( - scheduledMaintenance.projectId!, - scheduledMaintenance.id! - ) - ).toString(), - }; + const vars: Dictionary = { + scheduledMaintenanceTitle: scheduledMaintenance.title!, + projectName: scheduledMaintenance.project!.name!, + currentState: + scheduledMaintenance.currentScheduledMaintenanceState!.name!, + scheduledMaintenanceDescription: await Markdown.convertToHTML( + scheduledMaintenance.description! || "", + MarkdownContentType.Email, + ), + scheduledMaintenanceViewLink: ( + await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard( + scheduledMaintenance.projectId!, + scheduledMaintenance.id!, + ) + ).toString(), + }; - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: - EmailTemplateType.ScheduledMaintenanceOwnerResourceCreated, - vars: vars, - subject: - '[Scheduled Maintenance Created] ' + - scheduledMaintenance.title!, - }; + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: + EmailTemplateType.ScheduledMaintenanceOwnerResourceCreated, + vars: vars, + subject: + "[Scheduled Maintenance Created] " + scheduledMaintenance.title!, + }; - const sms: SMSMessage = { - message: `This is a message from OneUptime. New scheduled maintenance event created - ${scheduledMaintenance.title}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; + const sms: SMSMessage = { + message: `This is a message from OneUptime. New scheduled maintenance event created - ${scheduledMaintenance.title}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. New scheduled maintenance event created ${scheduledMaintenance.title}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. New scheduled maintenance event created ${scheduledMaintenance.title}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: scheduledMaintenance.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_CREATED_OWNER_NOTIFICATION, - }); - } - } + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: scheduledMaintenance.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_CREATED_OWNER_NOTIFICATION, + }); + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendNotePostedNotification.ts b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendNotePostedNotification.ts index 1378456bbc..5ff9f44c11 100644 --- a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendNotePostedNotification.ts +++ b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendNotePostedNotification.ts @@ -1,207 +1,201 @@ -import RunCron from '../../Utils/Cron'; -import BaseModel from 'Common/Models/BaseModel'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import ObjectID from 'Common/Types/ObjectID'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import ScheduledMaintenanceInternalNoteService from 'CommonServer/Services/ScheduledMaintenanceInternalNoteService'; -import ScheduledMaintenancePublicNoteService from 'CommonServer/Services/ScheduledMaintenancePublicNoteService'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceInternalNote from 'Model/Models/ScheduledMaintenanceInternalNote'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import BaseModel from "Common/Models/BaseModel"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import ObjectID from "Common/Types/ObjectID"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import ProjectService from "CommonServer/Services/ProjectService"; +import ScheduledMaintenanceInternalNoteService from "CommonServer/Services/ScheduledMaintenanceInternalNoteService"; +import ScheduledMaintenancePublicNoteService from "CommonServer/Services/ScheduledMaintenancePublicNoteService"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceInternalNote from "Model/Models/ScheduledMaintenanceInternalNote"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import User from "Model/Models/User"; RunCron( - 'ScheduledMaintenanceOwner:SendsNotePostedEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const publicNotes: Array = - await ScheduledMaintenancePublicNoteService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - note: true, - scheduledMaintenanceId: true, - projectId: true, - }, - }); + "ScheduledMaintenanceOwner:SendsNotePostedEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const publicNotes: Array = + await ScheduledMaintenancePublicNoteService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + note: true, + scheduledMaintenanceId: true, + projectId: true, + }, + }); - const privateNotes: Array = - await ScheduledMaintenanceInternalNoteService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - note: true, - scheduledMaintenanceId: true, - projectId: true, - }, - }); + const privateNotes: Array = + await ScheduledMaintenanceInternalNoteService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + note: true, + scheduledMaintenanceId: true, + projectId: true, + }, + }); - const privateNoteIds: Array = privateNotes.map( - (note: ScheduledMaintenancePublicNote) => { - return note._id!; - } - ); + const privateNoteIds: Array = privateNotes.map( + (note: ScheduledMaintenancePublicNote) => { + return note._id!; + }, + ); - for (const note of publicNotes) { - await ScheduledMaintenancePublicNoteService.updateOneById({ - id: note.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - for (const note of privateNotes) { - await ScheduledMaintenanceInternalNoteService.updateOneById({ - id: note.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - const notes: Array = [...publicNotes, ...privateNotes]; - - for (const noteObject of notes) { - const note: BaseModel = noteObject as BaseModel; - - // get all scheduled events of all the projects. - const scheduledMaintenance: ScheduledMaintenance | null = - await ScheduledMaintenanceService.findOneById({ - id: note.getColumnValue( - 'scheduledMaintenanceId' - )! as ObjectID, - props: { - isRoot: true, - }, - select: { - _id: true, - title: true, - description: true, - projectId: true, - project: { - name: true, - }, - currentScheduledMaintenanceState: { - name: true, - }, - }, - }); - - if (!scheduledMaintenance) { - continue; - } - - // now find owners. - - let doesResourceHasOwners: boolean = true; - - let owners: Array = - await ScheduledMaintenanceService.findOwners( - note.getColumnValue('scheduledMaintenanceId')! as ObjectID - ); - - if (owners.length === 0) { - doesResourceHasOwners = false; - - // find project owners. - owners = await ProjectService.getOwners( - note.getColumnValue('projectId') as ObjectID - ); - } - - if (owners.length === 0) { - continue; - } - - const vars: Dictionary = { - scheduledMaintenanceTitle: scheduledMaintenance.title!, - projectName: scheduledMaintenance.project!.name!, - currentState: - scheduledMaintenance.currentScheduledMaintenanceState! - .name!, - note: await Markdown.convertToHTML( - (note.getColumnValue('note')! as string) || '', - MarkdownContentType.Email - ), - scheduledMaintenanceViewLink: ( - await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard( - scheduledMaintenance.projectId!, - scheduledMaintenance.id! - ) - ).toString(), - }; - - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } - - if (privateNoteIds.includes(note._id!)) { - vars['isPrivateNote'] = 'true'; - } - - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: - EmailTemplateType.ScheduledMaintenanceOwnerNotePosted, - vars: vars, - subject: - '[Scheduled Maintenance Update] ' + - scheduledMaintenance.title, - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. New note posted on scheduled maintenance event - ${scheduledMaintenance.title}. To view this note, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. New note posted on scheduled maintenance event ${scheduledMaintenance.title}. To view this note, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: scheduledMaintenance.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_NOTE_POSTED_OWNER_NOTIFICATION, - }); - } - } + for (const note of publicNotes) { + await ScheduledMaintenancePublicNoteService.updateOneById({ + id: note.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); } + + for (const note of privateNotes) { + await ScheduledMaintenanceInternalNoteService.updateOneById({ + id: note.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); + } + + const notes: Array = [...publicNotes, ...privateNotes]; + + for (const noteObject of notes) { + const note: BaseModel = noteObject as BaseModel; + + // get all scheduled events of all the projects. + const scheduledMaintenance: ScheduledMaintenance | null = + await ScheduledMaintenanceService.findOneById({ + id: note.getColumnValue("scheduledMaintenanceId")! as ObjectID, + props: { + isRoot: true, + }, + select: { + _id: true, + title: true, + description: true, + projectId: true, + project: { + name: true, + }, + currentScheduledMaintenanceState: { + name: true, + }, + }, + }); + + if (!scheduledMaintenance) { + continue; + } + + // now find owners. + + let doesResourceHasOwners: boolean = true; + + let owners: Array = await ScheduledMaintenanceService.findOwners( + note.getColumnValue("scheduledMaintenanceId")! as ObjectID, + ); + + if (owners.length === 0) { + doesResourceHasOwners = false; + + // find project owners. + owners = await ProjectService.getOwners( + note.getColumnValue("projectId") as ObjectID, + ); + } + + if (owners.length === 0) { + continue; + } + + const vars: Dictionary = { + scheduledMaintenanceTitle: scheduledMaintenance.title!, + projectName: scheduledMaintenance.project!.name!, + currentState: + scheduledMaintenance.currentScheduledMaintenanceState!.name!, + note: await Markdown.convertToHTML( + (note.getColumnValue("note")! as string) || "", + MarkdownContentType.Email, + ), + scheduledMaintenanceViewLink: ( + await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard( + scheduledMaintenance.projectId!, + scheduledMaintenance.id!, + ) + ).toString(), + }; + + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } + + if (privateNoteIds.includes(note._id!)) { + vars["isPrivateNote"] = "true"; + } + + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.ScheduledMaintenanceOwnerNotePosted, + vars: vars, + subject: + "[Scheduled Maintenance Update] " + scheduledMaintenance.title, + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. New note posted on scheduled maintenance event - ${scheduledMaintenance.title}. To view this note, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. New note posted on scheduled maintenance event ${scheduledMaintenance.title}. To view this note, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: scheduledMaintenance.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_NOTE_POSTED_OWNER_NOTIFICATION, + }); + } + } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendOwnerAddedNotification.ts b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendOwnerAddedNotification.ts index 8e50d7ca49..7ed4c3b3b4 100644 --- a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendOwnerAddedNotification.ts +++ b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendOwnerAddedNotification.ts @@ -1,236 +1,225 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import ObjectID from 'Common/Types/ObjectID'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import ScheduledMaintenanceOwnerTeamService from 'CommonServer/Services/ScheduledMaintenanceOwnerTeamService'; -import ScheduledMaintenanceOwnerUserService from 'CommonServer/Services/ScheduledMaintenanceOwnerUserService'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import TeamMemberService from 'CommonServer/Services/TeamMemberService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceOwnerTeam from 'Model/Models/ScheduledMaintenanceOwnerTeam'; -import ScheduledMaintenanceOwnerUser from 'Model/Models/ScheduledMaintenanceOwnerUser'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import ObjectID from "Common/Types/ObjectID"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import ScheduledMaintenanceOwnerTeamService from "CommonServer/Services/ScheduledMaintenanceOwnerTeamService"; +import ScheduledMaintenanceOwnerUserService from "CommonServer/Services/ScheduledMaintenanceOwnerUserService"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import TeamMemberService from "CommonServer/Services/TeamMemberService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceOwnerTeam from "Model/Models/ScheduledMaintenanceOwnerTeam"; +import ScheduledMaintenanceOwnerUser from "Model/Models/ScheduledMaintenanceOwnerUser"; +import User from "Model/Models/User"; RunCron( - 'ScheduledMaintenanceOwner:SendOwnerAddedEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const scheduledMaintenanceOwnerTeams: Array = - await ScheduledMaintenanceOwnerTeamService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - scheduledMaintenanceId: true, - teamId: true, - }, - }); + "ScheduledMaintenanceOwner:SendOwnerAddedEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const scheduledMaintenanceOwnerTeams: Array = + await ScheduledMaintenanceOwnerTeamService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + scheduledMaintenanceId: true, + teamId: true, + }, + }); - const scheduledMaintenanceOwnersMap: Dictionary> = {}; + const scheduledMaintenanceOwnersMap: Dictionary> = {}; - for (const scheduledMaintenanceOwnerTeam of scheduledMaintenanceOwnerTeams) { - const scheduledMaintenanceId: ObjectID = - scheduledMaintenanceOwnerTeam.scheduledMaintenanceId!; - const teamId: ObjectID = scheduledMaintenanceOwnerTeam.teamId!; + for (const scheduledMaintenanceOwnerTeam of scheduledMaintenanceOwnerTeams) { + const scheduledMaintenanceId: ObjectID = + scheduledMaintenanceOwnerTeam.scheduledMaintenanceId!; + const teamId: ObjectID = scheduledMaintenanceOwnerTeam.teamId!; - const users: Array = await TeamMemberService.getUsersInTeams([ - teamId, - ]); + const users: Array = await TeamMemberService.getUsersInTeams([ + teamId, + ]); - if ( - scheduledMaintenanceOwnersMap[ - scheduledMaintenanceId.toString() - ] === undefined - ) { - scheduledMaintenanceOwnersMap[ - scheduledMaintenanceId.toString() - ] = []; - } + if ( + scheduledMaintenanceOwnersMap[scheduledMaintenanceId.toString()] === + undefined + ) { + scheduledMaintenanceOwnersMap[scheduledMaintenanceId.toString()] = []; + } - for (const user of users) { - ( - scheduledMaintenanceOwnersMap[ - scheduledMaintenanceId.toString() - ] as Array - ).push(user); - } + for (const user of users) { + ( + scheduledMaintenanceOwnersMap[ + scheduledMaintenanceId.toString() + ] as Array + ).push(user); + } - // mark this as notified. - await ScheduledMaintenanceOwnerTeamService.updateOneById({ - id: scheduledMaintenanceOwnerTeam.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - const scheduledMaintenanceOwnerUsers: Array = - await ScheduledMaintenanceOwnerUserService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - scheduledMaintenanceId: true, - userId: true, - user: { - email: true, - name: true, - }, - }, - }); - - for (const scheduledMaintenanceOwnerUser of scheduledMaintenanceOwnerUsers) { - const scheduledMaintenanceId: ObjectID = - scheduledMaintenanceOwnerUser.scheduledMaintenanceId!; - const user: User = scheduledMaintenanceOwnerUser.user!; - - if ( - scheduledMaintenanceOwnersMap[ - scheduledMaintenanceId.toString() - ] === undefined - ) { - scheduledMaintenanceOwnersMap[ - scheduledMaintenanceId.toString() - ] = []; - } - - ( - scheduledMaintenanceOwnersMap[ - scheduledMaintenanceId.toString() - ] as Array - ).push(user); - - // mark this as notified. - await ScheduledMaintenanceOwnerUserService.updateOneById({ - id: scheduledMaintenanceOwnerUser.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - // send email to all of these users. - - for (const scheduledMaintenanceId in scheduledMaintenanceOwnersMap) { - if (!scheduledMaintenanceOwnersMap[scheduledMaintenanceId]) { - continue; - } - - if ( - ( - scheduledMaintenanceOwnersMap[ - scheduledMaintenanceId - ] as Array - ).length === 0 - ) { - continue; - } - - const users: Array = scheduledMaintenanceOwnersMap[ - scheduledMaintenanceId - ] as Array; - - // get all scheduled events of all the projects. - const scheduledMaintenance: ScheduledMaintenance | null = - await ScheduledMaintenanceService.findOneById({ - id: new ObjectID(scheduledMaintenanceId), - props: { - isRoot: true, - }, - - select: { - _id: true, - title: true, - description: true, - projectId: true, - project: { - name: true, - }, - currentScheduledMaintenanceState: { - name: true, - }, - }, - }); - - if (!scheduledMaintenance) { - continue; - } - - const vars: Dictionary = { - scheduledMaintenanceTitle: scheduledMaintenance.title!, - projectName: scheduledMaintenance.project!.name!, - currentState: - scheduledMaintenance.currentScheduledMaintenanceState! - .name!, - scheduledMaintenanceDescription: await Markdown.convertToHTML( - scheduledMaintenance.description! || '', - MarkdownContentType.Email - ), - scheduledMaintenanceViewLink: ( - await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard( - scheduledMaintenance.projectId!, - scheduledMaintenance.id! - ) - ).toString(), - }; - - for (const user of users) { - const emailMessage: EmailEnvelope = { - templateType: - EmailTemplateType.ScheduledMaintenanceOwnerAdded, - vars: vars, - subject: - 'You have been added as the owner of the scheduled maintenance event.', - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. You have been added as the owner of the scheduled maintenance event - ${scheduledMaintenance.title}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime.You have been added as the owner of the scheduled maintenance event ${scheduledMaintenance.title}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: scheduledMaintenance.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_OWNER_ADDED_NOTIFICATION, - }); - } - } + // mark this as notified. + await ScheduledMaintenanceOwnerTeamService.updateOneById({ + id: scheduledMaintenanceOwnerTeam.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); } + + const scheduledMaintenanceOwnerUsers: Array = + await ScheduledMaintenanceOwnerUserService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + scheduledMaintenanceId: true, + userId: true, + user: { + email: true, + name: true, + }, + }, + }); + + for (const scheduledMaintenanceOwnerUser of scheduledMaintenanceOwnerUsers) { + const scheduledMaintenanceId: ObjectID = + scheduledMaintenanceOwnerUser.scheduledMaintenanceId!; + const user: User = scheduledMaintenanceOwnerUser.user!; + + if ( + scheduledMaintenanceOwnersMap[scheduledMaintenanceId.toString()] === + undefined + ) { + scheduledMaintenanceOwnersMap[scheduledMaintenanceId.toString()] = []; + } + + ( + scheduledMaintenanceOwnersMap[ + scheduledMaintenanceId.toString() + ] as Array + ).push(user); + + // mark this as notified. + await ScheduledMaintenanceOwnerUserService.updateOneById({ + id: scheduledMaintenanceOwnerUser.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); + } + + // send email to all of these users. + + for (const scheduledMaintenanceId in scheduledMaintenanceOwnersMap) { + if (!scheduledMaintenanceOwnersMap[scheduledMaintenanceId]) { + continue; + } + + if ( + (scheduledMaintenanceOwnersMap[scheduledMaintenanceId] as Array) + .length === 0 + ) { + continue; + } + + const users: Array = scheduledMaintenanceOwnersMap[ + scheduledMaintenanceId + ] as Array; + + // get all scheduled events of all the projects. + const scheduledMaintenance: ScheduledMaintenance | null = + await ScheduledMaintenanceService.findOneById({ + id: new ObjectID(scheduledMaintenanceId), + props: { + isRoot: true, + }, + + select: { + _id: true, + title: true, + description: true, + projectId: true, + project: { + name: true, + }, + currentScheduledMaintenanceState: { + name: true, + }, + }, + }); + + if (!scheduledMaintenance) { + continue; + } + + const vars: Dictionary = { + scheduledMaintenanceTitle: scheduledMaintenance.title!, + projectName: scheduledMaintenance.project!.name!, + currentState: + scheduledMaintenance.currentScheduledMaintenanceState!.name!, + scheduledMaintenanceDescription: await Markdown.convertToHTML( + scheduledMaintenance.description! || "", + MarkdownContentType.Email, + ), + scheduledMaintenanceViewLink: ( + await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard( + scheduledMaintenance.projectId!, + scheduledMaintenance.id!, + ) + ).toString(), + }; + + for (const user of users) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.ScheduledMaintenanceOwnerAdded, + vars: vars, + subject: + "You have been added as the owner of the scheduled maintenance event.", + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. You have been added as the owner of the scheduled maintenance event - ${scheduledMaintenance.title}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime.You have been added as the owner of the scheduled maintenance event ${scheduledMaintenance.title}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: scheduledMaintenance.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_OWNER_ADDED_NOTIFICATION, + }); + } + } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendStateChangeNotification.ts b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendStateChangeNotification.ts index a78279405a..9cff2f7dfe 100644 --- a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendStateChangeNotification.ts +++ b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceOwners/SendStateChangeNotification.ts @@ -1,158 +1,155 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import Text from 'Common/Types/Text'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import ScheduledMaintenanceStateTimelineService from 'CommonServer/Services/ScheduledMaintenanceStateTimelineService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import Text from "Common/Types/Text"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import ProjectService from "CommonServer/Services/ProjectService"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import ScheduledMaintenanceStateTimelineService from "CommonServer/Services/ScheduledMaintenanceStateTimelineService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import User from "Model/Models/User"; RunCron( - 'ScheduledMaintenanceOwner:SendStateChangeEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. + "ScheduledMaintenanceOwner:SendStateChangeEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. - const scheduledMaintenanceStateTimelines: Array = - await ScheduledMaintenanceStateTimelineService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - createdAt: true, - projectId: true, - project: { - name: true, - }, - scheduledMaintenance: { - _id: true, - title: true, - description: true, - }, - scheduledMaintenanceState: { - name: true, - }, - }, - }); + const scheduledMaintenanceStateTimelines: Array = + await ScheduledMaintenanceStateTimelineService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + createdAt: true, + projectId: true, + project: { + name: true, + }, + scheduledMaintenance: { + _id: true, + title: true, + description: true, + }, + scheduledMaintenanceState: { + name: true, + }, + }, + }); - for (const scheduledMaintenanceStateTimeline of scheduledMaintenanceStateTimelines) { - const scheduledMaintenance: ScheduledMaintenance = - scheduledMaintenanceStateTimeline.scheduledMaintenance!; - const scheduledMaintenanceState: ScheduledMaintenanceState = - scheduledMaintenanceStateTimeline.scheduledMaintenanceState!; + for (const scheduledMaintenanceStateTimeline of scheduledMaintenanceStateTimelines) { + const scheduledMaintenance: ScheduledMaintenance = + scheduledMaintenanceStateTimeline.scheduledMaintenance!; + const scheduledMaintenanceState: ScheduledMaintenanceState = + scheduledMaintenanceStateTimeline.scheduledMaintenanceState!; - await ScheduledMaintenanceStateTimelineService.updateOneById({ - id: scheduledMaintenanceStateTimeline.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); + await ScheduledMaintenanceStateTimelineService.updateOneById({ + id: scheduledMaintenanceStateTimeline.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); - // now find owners. + // now find owners. - let doesResourceHasOwners: boolean = true; + let doesResourceHasOwners: boolean = true; - let owners: Array = - await ScheduledMaintenanceService.findOwners( - scheduledMaintenance.id! - ); + let owners: Array = await ScheduledMaintenanceService.findOwners( + scheduledMaintenance.id!, + ); - if (owners.length === 0) { - doesResourceHasOwners = false; + if (owners.length === 0) { + doesResourceHasOwners = false; - // find project owners. - owners = await ProjectService.getOwners( - scheduledMaintenanceStateTimeline.projectId! - ); - } + // find project owners. + owners = await ProjectService.getOwners( + scheduledMaintenanceStateTimeline.projectId!, + ); + } - if (owners.length === 0) { - continue; - } + if (owners.length === 0) { + continue; + } - const vars: Dictionary = { - scheduledMaintenanceTitle: scheduledMaintenance.title!, - projectName: scheduledMaintenanceStateTimeline.project!.name!, - currentState: scheduledMaintenanceState!.name!, - scheduledMaintenanceDescription: await Markdown.convertToHTML( - scheduledMaintenance.description! || '', - MarkdownContentType.Email - ), - stateChangedAt: - OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones( - scheduledMaintenanceStateTimeline.createdAt! - ), - scheduledMaintenanceViewLink: ( - await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard( - scheduledMaintenanceStateTimeline.projectId!, - scheduledMaintenance.id! - ) - ).toString(), - }; + const vars: Dictionary = { + scheduledMaintenanceTitle: scheduledMaintenance.title!, + projectName: scheduledMaintenanceStateTimeline.project!.name!, + currentState: scheduledMaintenanceState!.name!, + scheduledMaintenanceDescription: await Markdown.convertToHTML( + scheduledMaintenance.description! || "", + MarkdownContentType.Email, + ), + stateChangedAt: OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones( + scheduledMaintenanceStateTimeline.createdAt!, + ), + scheduledMaintenanceViewLink: ( + await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard( + scheduledMaintenanceStateTimeline.projectId!, + scheduledMaintenance.id!, + ) + ).toString(), + }; - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: - EmailTemplateType.ScheduledMaintenanceOwnerStateChanged, - vars: vars, - subject: `[Scheduled Maintenance ${Text.uppercaseFirstLetter( - scheduledMaintenanceState!.name! - )}] - ${scheduledMaintenance.title}`, - }; + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.ScheduledMaintenanceOwnerStateChanged, + vars: vars, + subject: `[Scheduled Maintenance ${Text.uppercaseFirstLetter( + scheduledMaintenanceState!.name!, + )}] - ${scheduledMaintenance.title}`, + }; - const sms: SMSMessage = { - message: `This is a message from OneUptime. Scheduled maintenance event - ${ - scheduledMaintenance.title - }, state changed to ${scheduledMaintenanceState! - .name!}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; + const sms: SMSMessage = { + message: `This is a message from OneUptime. Scheduled maintenance event - ${ + scheduledMaintenance.title + }, state changed to ${scheduledMaintenanceState! + .name!}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. Scheduled maintenance event ${ - scheduledMaintenance.title - } state changed to ${scheduledMaintenanceState! - .name!}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. Scheduled maintenance event ${ + scheduledMaintenance.title + } state changed to ${scheduledMaintenanceState! + .name!}. To view this event, go to OneUptime Dashboard. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: scheduledMaintenanceStateTimeline.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_STATE_CHANGED_OWNER_NOTIFICATION, - }); - } - } + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: scheduledMaintenanceStateTimeline.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_STATE_CHANGED_OWNER_NOTIFICATION, + }); + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ScheduledMaintenancePublicNote/SendNotificationToSubscribers.ts b/App/FeatureSet/Workers/Jobs/ScheduledMaintenancePublicNote/SendNotificationToSubscribers.ts index 460acb0647..80dd881f74 100644 --- a/App/FeatureSet/Workers/Jobs/ScheduledMaintenancePublicNote/SendNotificationToSubscribers.ts +++ b/App/FeatureSet/Workers/Jobs/ScheduledMaintenancePublicNote/SendNotificationToSubscribers.ts @@ -1,289 +1,276 @@ -import RunCron from '../../Utils/Cron'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import ObjectID from 'Common/Types/ObjectID'; -import SMS from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import MailService from 'CommonServer/Services/MailService'; -import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService'; -import ProjectSmtpConfigService from 'CommonServer/Services/ProjectSmtpConfigService'; -import ScheduledMaintenancePublicNoteService from 'CommonServer/Services/ScheduledMaintenancePublicNoteService'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import SmsService from 'CommonServer/Services/SmsService'; -import StatusPageResourceService from 'CommonServer/Services/StatusPageResourceService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import StatusPageSubscriberService from 'CommonServer/Services/StatusPageSubscriberService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import logger from 'CommonServer/Utils/Logger'; -import Monitor from 'Model/Models/Monitor'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageResource from 'Model/Models/StatusPageResource'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; +import RunCron from "../../Utils/Cron"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import ObjectID from "Common/Types/ObjectID"; +import SMS from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import MailService from "CommonServer/Services/MailService"; +import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService"; +import ProjectSmtpConfigService from "CommonServer/Services/ProjectSmtpConfigService"; +import ScheduledMaintenancePublicNoteService from "CommonServer/Services/ScheduledMaintenancePublicNoteService"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import SmsService from "CommonServer/Services/SmsService"; +import StatusPageResourceService from "CommonServer/Services/StatusPageResourceService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import StatusPageSubscriberService from "CommonServer/Services/StatusPageSubscriberService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import logger from "CommonServer/Utils/Logger"; +import Monitor from "Model/Models/Monitor"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageResource from "Model/Models/StatusPageResource"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; RunCron( - 'ScheduledMaintenancePublicNote:SendNotificationToSubscribers', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all incident notes of all the projects + "ScheduledMaintenancePublicNote:SendNotificationToSubscribers", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all incident notes of all the projects - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - const publicNotes: Array = - await ScheduledMaintenancePublicNoteService.findBy({ - query: { - isStatusPageSubscribersNotifiedOnNoteCreated: false, - shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true, - createdAt: QueryHelper.lessThan( - OneUptimeDate.getCurrentDate() - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - note: true, - scheduledMaintenanceId: true, - }, + const publicNotes: Array = + await ScheduledMaintenancePublicNoteService.findBy({ + query: { + isStatusPageSubscribersNotifiedOnNoteCreated: false, + shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true, + createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + note: true, + scheduledMaintenanceId: true, + }, + }); + + for (const publicNote of publicNotes) { + // get all scheduled events of all the projects. + const event: ScheduledMaintenance | null = + await ScheduledMaintenanceService.findOneById({ + id: publicNote.scheduledMaintenanceId!, + props: { + isRoot: true, + }, + select: { + _id: true, + title: true, + description: true, + startsAt: true, + monitors: { + _id: true, + }, + statusPages: { + _id: true, + }, + }, + }); + + if (!event) { + continue; + } + + await ScheduledMaintenancePublicNoteService.updateOneById({ + id: publicNote.id!, + data: { + isStatusPageSubscribersNotifiedOnNoteCreated: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + // get status page resources from monitors. + + let statusPageResources: Array = []; + + if (event.monitors && event.monitors.length > 0) { + statusPageResources = await StatusPageResourceService.findBy({ + query: { + monitorId: QueryHelper.any( + event.monitors + .filter((m: Monitor) => { + return m._id; + }) + .map((m: Monitor) => { + return new ObjectID(m._id!); + }), + ), + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + _id: true, + displayName: true, + statusPageId: true, + }, + }); + } + + const statusPageToResources: Dictionary> = {}; + + for (const resource of statusPageResources) { + if (!resource.statusPageId) { + continue; + } + + if (!statusPageToResources[resource.statusPageId?.toString()]) { + statusPageToResources[resource.statusPageId?.toString()] = []; + } + + statusPageToResources[resource.statusPageId?.toString()]?.push( + resource, + ); + } + + const statusPages: Array = + await StatusPageSubscriberService.getStatusPagesToSendNotification( + event.statusPages?.map((i: StatusPage) => { + return i.id!; + }) || [], + ); + + for (const statuspage of statusPages) { + if (!statuspage.id) { + continue; + } + + const subscribers: Array = + await StatusPageSubscriberService.getSubscribersByStatusPage( + statuspage.id!, + { + isRoot: true, + ignoreHooks: true, + }, + ); + + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statuspage.id, + ); + + const statusPageName: string = + statuspage.pageTitle || statuspage.name || "Status Page"; + + // Send email to Email subscribers. + + for (const subscriber of subscribers) { + if (!subscriber._id) { + continue; + } + + const shouldNotifySubscriber: boolean = + StatusPageSubscriberService.shouldSendNotification({ + subscriber: subscriber, + statusPageResources: statusPageToResources[statuspage._id!] || [], + statusPage: statuspage, }); - for (const publicNote of publicNotes) { - // get all scheduled events of all the projects. - const event: ScheduledMaintenance | null = - await ScheduledMaintenanceService.findOneById({ - id: publicNote.scheduledMaintenanceId!, - props: { - isRoot: true, - }, - select: { - _id: true, - title: true, - description: true, - startsAt: true, - monitors: { - _id: true, - }, - statusPages: { - _id: true, - }, - }, - }); + if (!shouldNotifySubscriber) { + continue; + } - if (!event) { - continue; - } + const unsubscribeUrl: string = + StatusPageSubscriberService.getUnsubscribeLink( + URL.fromString(statusPageURL), + subscriber.id!, + ).toString(); - await ScheduledMaintenancePublicNoteService.updateOneById({ - id: publicNote.id!, - data: { - isStatusPageSubscribersNotifiedOnNoteCreated: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - // get status page resources from monitors. - - let statusPageResources: Array = []; - - if (event.monitors && event.monitors.length > 0) { - statusPageResources = await StatusPageResourceService.findBy({ - query: { - monitorId: QueryHelper.any( - event.monitors - .filter((m: Monitor) => { - return m._id; - }) - .map((m: Monitor) => { - return new ObjectID(m._id!); - }) - ), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - _id: true, - displayName: true, - statusPageId: true, - }, - }); - } - - const statusPageToResources: Dictionary> = - {}; - - for (const resource of statusPageResources) { - if (!resource.statusPageId) { - continue; - } - - if (!statusPageToResources[resource.statusPageId?.toString()]) { - statusPageToResources[resource.statusPageId?.toString()] = - []; - } - - statusPageToResources[resource.statusPageId?.toString()]?.push( - resource - ); - } - - const statusPages: Array = - await StatusPageSubscriberService.getStatusPagesToSendNotification( - event.statusPages?.map((i: StatusPage) => { - return i.id!; - }) || [] - ); - - for (const statuspage of statusPages) { - if (!statuspage.id) { - continue; - } - - const subscribers: Array = - await StatusPageSubscriberService.getSubscribersByStatusPage( - statuspage.id!, - { - isRoot: true, - ignoreHooks: true, - } - ); - - const statusPageURL: string = - await StatusPageService.getStatusPageURL(statuspage.id); - - const statusPageName: string = - statuspage.pageTitle || statuspage.name || 'Status Page'; - - // Send email to Email subscribers. - - for (const subscriber of subscribers) { - if (!subscriber._id) { - continue; - } - - const shouldNotifySubscriber: boolean = - StatusPageSubscriberService.shouldSendNotification({ - subscriber: subscriber, - statusPageResources: - statusPageToResources[statuspage._id!] || [], - statusPage: statuspage, - }); - - if (!shouldNotifySubscriber) { - continue; - } - - const unsubscribeUrl: string = - StatusPageSubscriberService.getUnsubscribeLink( - URL.fromString(statusPageURL), - subscriber.id! - ).toString(); - - if (subscriber.subscriberPhone) { - const sms: SMS = { - message: ` + if (subscriber.subscriberPhone) { + const sms: SMS = { + message: ` ${statusPageName} - New note has been posted to maintenance event. - ${event.title || ''} + ${event.title || ""} To view this note, visit ${statusPageURL} To update notification preferences or unsubscribe, visit ${unsubscribeUrl} `, - to: subscriber.subscriberPhone, - }; + to: subscriber.subscriberPhone, + }; - // send sms here. - SmsService.sendSms(sms, { - projectId: statuspage.projectId, - customTwilioConfig: - ProjectCallSMSConfigService.toTwilioConfig( - statuspage.callSmsConfig - ), - }).catch((err: Error) => { - logger.error(err); - }); - } + // send sms here. + SmsService.sendSms(sms, { + projectId: statuspage.projectId, + customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig( + statuspage.callSmsConfig, + ), + }).catch((err: Error) => { + logger.error(err); + }); + } - if (subscriber.subscriberEmail) { - // send email here. + if (subscriber.subscriberEmail) { + // send email here. - MailService.sendMail( - { - toEmail: subscriber.subscriberEmail, - templateType: - EmailTemplateType.SubscriberScheduledMaintenanceEventNoteCreated, - vars: { - note: await Markdown.convertToHTML( - publicNote.note!, - MarkdownContentType.Email - ), - statusPageName: statusPageName, - statusPageUrl: statusPageURL, - logoUrl: statuspage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute( - '/image/' + - statuspage.logoFileId - ) - .toString() - : '', - isPublicStatusPage: - statuspage.isPublicStatusPage - ? 'true' - : 'false', - resourcesAffected: - statusPageToResources[statuspage._id!] - ?.map((r: StatusPageResource) => { - return r.displayName; - }) - .join(', ') || '', + MailService.sendMail( + { + toEmail: subscriber.subscriberEmail, + templateType: + EmailTemplateType.SubscriberScheduledMaintenanceEventNoteCreated, + vars: { + note: await Markdown.convertToHTML( + publicNote.note!, + MarkdownContentType.Email, + ), + statusPageName: statusPageName, + statusPageUrl: statusPageURL, + logoUrl: statuspage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statuspage.logoFileId) + .toString() + : "", + isPublicStatusPage: statuspage.isPublicStatusPage + ? "true" + : "false", + resourcesAffected: + statusPageToResources[statuspage._id!] + ?.map((r: StatusPageResource) => { + return r.displayName; + }) + .join(", ") || "", - scheduledAt: - OneUptimeDate.getDateAsFormattedString( - event.startsAt! - ), - eventTitle: event.title || '', - eventDescription: event.description || '', - unsubscribeUrl: unsubscribeUrl, - }, - subject: - '[Scheduled Maintenance Update] ' + - statusPageName, - }, - { - mailServer: - ProjectSmtpConfigService.toEmailServer( - statuspage.smtpConfig - ), - projectId: statuspage.projectId!, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - } + scheduledAt: OneUptimeDate.getDateAsFormattedString( + event.startsAt!, + ), + eventTitle: event.title || "", + eventDescription: event.description || "", + unsubscribeUrl: unsubscribeUrl, + }, + subject: "[Scheduled Maintenance Update] " + statusPageName, + }, + { + mailServer: ProjectSmtpConfigService.toEmailServer( + statuspage.smtpConfig, + ), + projectId: statuspage.projectId!, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceStateTimeline/SendNotificationToSubscribers.ts b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceStateTimeline/SendNotificationToSubscribers.ts index 7dd08607ab..d1080eb479 100644 --- a/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceStateTimeline/SendNotificationToSubscribers.ts +++ b/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceStateTimeline/SendNotificationToSubscribers.ts @@ -1,307 +1,294 @@ -import RunCron from '../../Utils/Cron'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import ObjectID from 'Common/Types/ObjectID'; -import SMS from 'Common/Types/SMS/SMS'; -import Text from 'Common/Types/Text'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import DatabaseConfig from 'CommonServer/DatabaseConfig'; -import MailService from 'CommonServer/Services/MailService'; -import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService'; -import ProjectSmtpConfigService from 'CommonServer/Services/ProjectSmtpConfigService'; -import ScheduledMaintenanceService from 'CommonServer/Services/ScheduledMaintenanceService'; -import ScheduledMaintenanceStateTimelineService from 'CommonServer/Services/ScheduledMaintenanceStateTimelineService'; -import SmsService from 'CommonServer/Services/SmsService'; -import StatusPageResourceService from 'CommonServer/Services/StatusPageResourceService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import StatusPageSubscriberService from 'CommonServer/Services/StatusPageSubscriberService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import logger from 'CommonServer/Utils/Logger'; -import Monitor from 'Model/Models/Monitor'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageResource from 'Model/Models/StatusPageResource'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; +import RunCron from "../../Utils/Cron"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import ObjectID from "Common/Types/ObjectID"; +import SMS from "Common/Types/SMS/SMS"; +import Text from "Common/Types/Text"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import DatabaseConfig from "CommonServer/DatabaseConfig"; +import MailService from "CommonServer/Services/MailService"; +import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService"; +import ProjectSmtpConfigService from "CommonServer/Services/ProjectSmtpConfigService"; +import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService"; +import ScheduledMaintenanceStateTimelineService from "CommonServer/Services/ScheduledMaintenanceStateTimelineService"; +import SmsService from "CommonServer/Services/SmsService"; +import StatusPageResourceService from "CommonServer/Services/StatusPageResourceService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import StatusPageSubscriberService from "CommonServer/Services/StatusPageSubscriberService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import logger from "CommonServer/Utils/Logger"; +import Monitor from "Model/Models/Monitor"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageResource from "Model/Models/StatusPageResource"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; RunCron( - 'ScheduledMaintenanceStateTimeline:SendNotificationToSubscribers', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + "ScheduledMaintenanceStateTimeline:SendNotificationToSubscribers", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - const scheduledEventStateTimelines: Array = - await ScheduledMaintenanceStateTimelineService.findBy({ - query: { - isStatusPageSubscribersNotified: false, - shouldStatusPageSubscribersBeNotified: true, - createdAt: QueryHelper.lessThan( - OneUptimeDate.getCurrentDate() - ), - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - scheduledMaintenanceId: true, - scheduledMaintenanceStateId: true, - scheduledMaintenanceState: { - name: true, - }, - }, + const scheduledEventStateTimelines: Array = + await ScheduledMaintenanceStateTimelineService.findBy({ + query: { + isStatusPageSubscribersNotified: false, + shouldStatusPageSubscribersBeNotified: true, + createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()), + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + scheduledMaintenanceId: true, + scheduledMaintenanceStateId: true, + scheduledMaintenanceState: { + name: true, + }, + }, + }); + + for (const scheduledEventStateTimeline of scheduledEventStateTimelines) { + await ScheduledMaintenanceStateTimelineService.updateOneById({ + id: scheduledEventStateTimeline.id!, + data: { + isStatusPageSubscribersNotified: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + if ( + !scheduledEventStateTimeline.scheduledMaintenanceId || + !scheduledEventStateTimeline.scheduledMaintenanceStateId + ) { + continue; + } + + if (!scheduledEventStateTimeline.scheduledMaintenanceState?.name) { + continue; + } + + // get all scheduled events of all the projects. + const event: ScheduledMaintenance | null = + await ScheduledMaintenanceService.findOneById({ + id: scheduledEventStateTimeline.scheduledMaintenanceId!, + props: { + isRoot: true, + }, + + select: { + _id: true, + title: true, + description: true, + startsAt: true, + monitors: { + _id: true, + }, + statusPages: { + _id: true, + }, + }, + }); + + if (!event) { + continue; + } + + // get status page resources from monitors. + + let statusPageResources: Array = []; + + if (event.monitors && event.monitors.length > 0) { + statusPageResources = await StatusPageResourceService.findBy({ + query: { + monitorId: QueryHelper.any( + event.monitors + .filter((m: Monitor) => { + return m._id; + }) + .map((m: Monitor) => { + return new ObjectID(m._id!); + }), + ), + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + _id: true, + displayName: true, + statusPageId: true, + }, + }); + } + + const statusPageToResources: Dictionary> = {}; + + for (const resource of statusPageResources) { + if (!resource.statusPageId) { + continue; + } + + if (!statusPageToResources[resource.statusPageId?.toString()]) { + statusPageToResources[resource.statusPageId?.toString()] = []; + } + + statusPageToResources[resource.statusPageId?.toString()]?.push( + resource, + ); + } + + const statusPages: Array = + await StatusPageSubscriberService.getStatusPagesToSendNotification( + event.statusPages?.map((i: StatusPage) => { + return i.id!; + }) || [], + ); + + for (const statuspage of statusPages) { + if (!statuspage.id) { + continue; + } + + const subscribers: Array = + await StatusPageSubscriberService.getSubscribersByStatusPage( + statuspage.id!, + { + isRoot: true, + ignoreHooks: true, + }, + ); + + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statuspage.id, + ); + + const statusPageName: string = + statuspage.pageTitle || statuspage.name || "Status Page"; + + // Send email to Email subscribers. + + for (const subscriber of subscribers) { + if (!subscriber._id) { + continue; + } + + const shouldNotifySubscriber: boolean = + StatusPageSubscriberService.shouldSendNotification({ + subscriber: subscriber, + statusPageResources: statusPageToResources[statuspage._id!] || [], + statusPage: statuspage, }); - for (const scheduledEventStateTimeline of scheduledEventStateTimelines) { - await ScheduledMaintenanceStateTimelineService.updateOneById({ - id: scheduledEventStateTimeline.id!, - data: { - isStatusPageSubscribersNotified: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + if (!shouldNotifySubscriber) { + continue; + } - if ( - !scheduledEventStateTimeline.scheduledMaintenanceId || - !scheduledEventStateTimeline.scheduledMaintenanceStateId - ) { - continue; - } + const unsubscribeUrl: string = + StatusPageSubscriberService.getUnsubscribeLink( + URL.fromString(statusPageURL), + subscriber.id!, + ).toString(); - if (!scheduledEventStateTimeline.scheduledMaintenanceState?.name) { - continue; - } - - // get all scheduled events of all the projects. - const event: ScheduledMaintenance | null = - await ScheduledMaintenanceService.findOneById({ - id: scheduledEventStateTimeline.scheduledMaintenanceId!, - props: { - isRoot: true, - }, - - select: { - _id: true, - title: true, - description: true, - startsAt: true, - monitors: { - _id: true, - }, - statusPages: { - _id: true, - }, - }, - }); - - if (!event) { - continue; - } - - // get status page resources from monitors. - - let statusPageResources: Array = []; - - if (event.monitors && event.monitors.length > 0) { - statusPageResources = await StatusPageResourceService.findBy({ - query: { - monitorId: QueryHelper.any( - event.monitors - .filter((m: Monitor) => { - return m._id; - }) - .map((m: Monitor) => { - return new ObjectID(m._id!); - }) - ), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - _id: true, - displayName: true, - statusPageId: true, - }, - }); - } - - const statusPageToResources: Dictionary> = - {}; - - for (const resource of statusPageResources) { - if (!resource.statusPageId) { - continue; - } - - if (!statusPageToResources[resource.statusPageId?.toString()]) { - statusPageToResources[resource.statusPageId?.toString()] = - []; - } - - statusPageToResources[resource.statusPageId?.toString()]?.push( - resource - ); - } - - const statusPages: Array = - await StatusPageSubscriberService.getStatusPagesToSendNotification( - event.statusPages?.map((i: StatusPage) => { - return i.id!; - }) || [] - ); - - for (const statuspage of statusPages) { - if (!statuspage.id) { - continue; - } - - const subscribers: Array = - await StatusPageSubscriberService.getSubscribersByStatusPage( - statuspage.id!, - { - isRoot: true, - ignoreHooks: true, - } - ); - - const statusPageURL: string = - await StatusPageService.getStatusPageURL(statuspage.id); - - const statusPageName: string = - statuspage.pageTitle || statuspage.name || 'Status Page'; - - // Send email to Email subscribers. - - for (const subscriber of subscribers) { - if (!subscriber._id) { - continue; - } - - const shouldNotifySubscriber: boolean = - StatusPageSubscriberService.shouldSendNotification({ - subscriber: subscriber, - statusPageResources: - statusPageToResources[statuspage._id!] || [], - statusPage: statuspage, - }); - - if (!shouldNotifySubscriber) { - continue; - } - - const unsubscribeUrl: string = - StatusPageSubscriberService.getUnsubscribeLink( - URL.fromString(statusPageURL), - subscriber.id! - ).toString(); - - if (subscriber.subscriberPhone) { - const sms: SMS = { - message: ` + if (subscriber.subscriberPhone) { + const sms: SMS = { + message: ` ${statusPageName} - Scheduled maintenance event - ${ - event.title || '' - } - state changed to ${ - scheduledEventStateTimeline - .scheduledMaintenanceState?.name - } + event.title || "" + } - state changed to ${ + scheduledEventStateTimeline + .scheduledMaintenanceState?.name + } To view this note, visit ${statusPageURL} To update notification preferences or unsubscribe, visit ${unsubscribeUrl} `, - to: subscriber.subscriberPhone, - }; + to: subscriber.subscriberPhone, + }; - // send sms here. - SmsService.sendSms(sms, { - projectId: statuspage.projectId, - customTwilioConfig: - ProjectCallSMSConfigService.toTwilioConfig( - statuspage.callSmsConfig - ), - }).catch((err: Error) => { - logger.error(err); - }); - } + // send sms here. + SmsService.sendSms(sms, { + projectId: statuspage.projectId, + customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig( + statuspage.callSmsConfig, + ), + }).catch((err: Error) => { + logger.error(err); + }); + } - if (subscriber.subscriberEmail) { - // send email here. + if (subscriber.subscriberEmail) { + // send email here. - MailService.sendMail( - { - toEmail: subscriber.subscriberEmail, - templateType: - EmailTemplateType.SubscriberScheduledMaintenanceEventStateChanged, - vars: { - statusPageName: statusPageName, - statusPageUrl: statusPageURL, - logoUrl: statuspage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute( - '/image/' + - statuspage.logoFileId - ) - .toString() - : '', - isPublicStatusPage: - statuspage.isPublicStatusPage - ? 'true' - : 'false', - resourcesAffected: - statusPageToResources[statuspage._id!] - ?.map((r: StatusPageResource) => { - return r.displayName; - }) - .join(', ') || '', + MailService.sendMail( + { + toEmail: subscriber.subscriberEmail, + templateType: + EmailTemplateType.SubscriberScheduledMaintenanceEventStateChanged, + vars: { + statusPageName: statusPageName, + statusPageUrl: statusPageURL, + logoUrl: statuspage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statuspage.logoFileId) + .toString() + : "", + isPublicStatusPage: statuspage.isPublicStatusPage + ? "true" + : "false", + resourcesAffected: + statusPageToResources[statuspage._id!] + ?.map((r: StatusPageResource) => { + return r.displayName; + }) + .join(", ") || "", - eventState: - scheduledEventStateTimeline - .scheduledMaintenanceState?.name || - '', + eventState: + scheduledEventStateTimeline.scheduledMaintenanceState + ?.name || "", - scheduledAt: - OneUptimeDate.getDateAsFormattedString( - event.startsAt! - ), - eventTitle: event.title || '', - eventDescription: event.description || '', - unsubscribeUrl: unsubscribeUrl, - }, - subject: `[Scheduled Maintenance ${Text.uppercaseFirstLetter( - scheduledEventStateTimeline - .scheduledMaintenanceState?.name - )}] ${statusPageName}`, - }, - { - mailServer: - ProjectSmtpConfigService.toEmailServer( - statuspage.smtpConfig - ), - projectId: statuspage.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - } + scheduledAt: OneUptimeDate.getDateAsFormattedString( + event.startsAt!, + ), + eventTitle: event.title || "", + eventDescription: event.description || "", + unsubscribeUrl: unsubscribeUrl, + }, + subject: `[Scheduled Maintenance ${Text.uppercaseFirstLetter( + scheduledEventStateTimeline.scheduledMaintenanceState?.name, + )}] ${statusPageName}`, + }, + { + mailServer: ProjectSmtpConfigService.toEmailServer( + statuspage.smtpConfig, + ), + projectId: statuspage.projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/ServerMonitor/CheckOnlineStatus.ts b/App/FeatureSet/Workers/Jobs/ServerMonitor/CheckOnlineStatus.ts index 58d3c5e39a..4f8beacba4 100644 --- a/App/FeatureSet/Workers/Jobs/ServerMonitor/CheckOnlineStatus.ts +++ b/App/FeatureSet/Workers/Jobs/ServerMonitor/CheckOnlineStatus.ts @@ -1,97 +1,96 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import { CheckOn } from 'Common/Types/Monitor/CriteriaFilter'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ServerMonitorResponse from 'Common/Types/Monitor/ServerMonitor/ServerMonitorResponse'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import MonitorService from 'CommonServer/Services/MonitorService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import ProbeMonitorResponseService from 'CommonServer/Utils/Probe/ProbeMonitorResponse'; -import Monitor from 'Model/Models/Monitor'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import { CheckOn } from "Common/Types/Monitor/CriteriaFilter"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ServerMonitorResponse from "Common/Types/Monitor/ServerMonitor/ServerMonitorResponse"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import MonitorService from "CommonServer/Services/MonitorService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import ProbeMonitorResponseService from "CommonServer/Utils/Probe/ProbeMonitorResponse"; +import Monitor from "Model/Models/Monitor"; RunCron( - 'ServerMonitor:CheckOnlineStatus', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const twoMinsAgo: Date = OneUptimeDate.getSomeMinutesAgo(2); + "ServerMonitor:CheckOnlineStatus", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const twoMinsAgo: Date = OneUptimeDate.getSomeMinutesAgo(2); - const serverMonitors: Array = await MonitorService.findBy({ - query: { - monitorType: MonitorType.Server, - serverMonitorRequestReceivedAt: - QueryHelper.lessThanEqualToOrNull(twoMinsAgo), - }, - props: { - isRoot: true, - }, - select: { - _id: true, - monitorSteps: true, - serverMonitorRequestReceivedAt: true, - createdAt: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); + const serverMonitors: Array = await MonitorService.findBy({ + query: { + monitorType: MonitorType.Server, + serverMonitorRequestReceivedAt: + QueryHelper.lessThanEqualToOrNull(twoMinsAgo), + }, + props: { + isRoot: true, + }, + select: { + _id: true, + monitorSteps: true, + serverMonitorRequestReceivedAt: true, + createdAt: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); - for (const monitor of serverMonitors) { - if (!monitor.monitorSteps) { - continue; - } + for (const monitor of serverMonitors) { + if (!monitor.monitorSteps) { + continue; + } - const processRequest: boolean = shouldProcessRequest(monitor); + const processRequest: boolean = shouldProcessRequest(monitor); - if (!processRequest) { - continue; - } + if (!processRequest) { + continue; + } - const serverMonitorResponse: ServerMonitorResponse = { - monitorId: monitor.id!, - onlyCheckRequestReceivedAt: true, - requestReceivedAt: - monitor.serverMonitorRequestReceivedAt || - monitor.createdAt!, - }; + const serverMonitorResponse: ServerMonitorResponse = { + monitorId: monitor.id!, + onlyCheckRequestReceivedAt: true, + requestReceivedAt: + monitor.serverMonitorRequestReceivedAt || monitor.createdAt!, + }; - await ProbeMonitorResponseService.processProbeResponse( - serverMonitorResponse - ); - } + await ProbeMonitorResponseService.processProbeResponse( + serverMonitorResponse, + ); } + }, ); type ShouldProcessRequestFunction = (monitor: Monitor) => boolean; const shouldProcessRequest: ShouldProcessRequestFunction = ( - monitor: Monitor + monitor: Monitor, ): boolean => { - // check if any criteria has Is Online step. If yes, then process the request. If no then skip the request. + // check if any criteria has Is Online step. If yes, then process the request. If no then skip the request. - let shouldWeProcessRequest: boolean = false; + let shouldWeProcessRequest: boolean = false; - for (const steps of monitor.monitorSteps?.data?.monitorStepsInstanceArray || - []) { - if (steps.data?.monitorCriteria.data?.monitorCriteriaInstanceArray) { - for (const criteria of steps.data?.monitorCriteria.data - ?.monitorCriteriaInstanceArray || []) { - for (const filters of criteria.data?.filters || []) { - if (filters.checkOn === CheckOn.IsOnline) { - shouldWeProcessRequest = true; - break; - } - } - - if (shouldWeProcessRequest) { - break; - } - } + for (const steps of monitor.monitorSteps?.data?.monitorStepsInstanceArray || + []) { + if (steps.data?.monitorCriteria.data?.monitorCriteriaInstanceArray) { + for (const criteria of steps.data?.monitorCriteria.data + ?.monitorCriteriaInstanceArray || []) { + for (const filters of criteria.data?.filters || []) { + if (filters.checkOn === CheckOn.IsOnline) { + shouldWeProcessRequest = true; + break; + } } if (shouldWeProcessRequest) { - break; + break; } + } } - return shouldWeProcessRequest; + if (shouldWeProcessRequest) { + break; + } + } + + return shouldWeProcessRequest; }; diff --git a/App/FeatureSet/Workers/Jobs/StatusPageCerts/StatusPageCerts.ts b/App/FeatureSet/Workers/Jobs/StatusPageCerts/StatusPageCerts.ts index 7e66721010..9f63f1788d 100644 --- a/App/FeatureSet/Workers/Jobs/StatusPageCerts/StatusPageCerts.ts +++ b/App/FeatureSet/Workers/Jobs/StatusPageCerts/StatusPageCerts.ts @@ -1,51 +1,51 @@ -import RunCron from '../../Utils/Cron'; -import { EVERY_FIFTEEN_MINUTE } from 'Common/Utils/CronTime'; -import { IsDevelopment } from 'CommonServer/EnvironmentConfig'; -import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService'; -import logger from 'CommonServer/Utils/Logger'; +import RunCron from "../../Utils/Cron"; +import { EVERY_FIFTEEN_MINUTE } from "Common/Utils/CronTime"; +import { IsDevelopment } from "CommonServer/EnvironmentConfig"; +import StatusPageDomainService from "CommonServer/Services/StatusPageDomainService"; +import logger from "CommonServer/Utils/Logger"; RunCron( - 'StatusPageCerts:RenewCerts', - { - schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE, - runOnStartup: true, - }, - async () => { - logger.debug('Renewing Certs...'); - await StatusPageDomainService.renewCertsWhichAreExpiringSoon(); - logger.debug('Renew Completed...'); - } + "StatusPageCerts:RenewCerts", + { + schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE, + runOnStartup: true, + }, + async () => { + logger.debug("Renewing Certs..."); + await StatusPageDomainService.renewCertsWhichAreExpiringSoon(); + logger.debug("Renew Completed..."); + }, ); RunCron( - 'StatusPageCerts:CheckSslProvisioningStatus', - { - schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE, - runOnStartup: true, - }, - async () => { - await StatusPageDomainService.updateSslProvisioningStatusForAllDomains(); - } + "StatusPageCerts:CheckSslProvisioningStatus", + { + schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE, + runOnStartup: true, + }, + async () => { + await StatusPageDomainService.updateSslProvisioningStatusForAllDomains(); + }, ); RunCron( - 'StatusPageCerts:OrderSSL', - { - schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE, - runOnStartup: true, - }, - async () => { - await StatusPageDomainService.orderSSLForDomainsWhichAreNotOrderedYet(); - } + "StatusPageCerts:OrderSSL", + { + schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE, + runOnStartup: true, + }, + async () => { + await StatusPageDomainService.orderSSLForDomainsWhichAreNotOrderedYet(); + }, ); RunCron( - 'StatusPageCerts:VerifyCnameWhoseCnameisNotVerified', - { - schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE, - runOnStartup: true, - }, - async () => { - await StatusPageDomainService.verifyCnameWhoseCnameisNotVerified(); - } + "StatusPageCerts:VerifyCnameWhoseCnameisNotVerified", + { + schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE, + runOnStartup: true, + }, + async () => { + await StatusPageDomainService.verifyCnameWhoseCnameisNotVerified(); + }, ); diff --git a/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendAnnouncementCreatedNotification.ts b/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendAnnouncementCreatedNotification.ts index f6ee3d4f3b..67a56c5cdc 100644 --- a/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendAnnouncementCreatedNotification.ts +++ b/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendAnnouncementCreatedNotification.ts @@ -1,127 +1,123 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import StatusPageAnnouncementService from 'CommonServer/Services/StatusPageAnnouncementService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageAnnouncement from 'Model/Models/StatusPageAnnouncement'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import ProjectService from "CommonServer/Services/ProjectService"; +import StatusPageAnnouncementService from "CommonServer/Services/StatusPageAnnouncementService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageAnnouncement from "Model/Models/StatusPageAnnouncement"; +import User from "Model/Models/User"; RunCron( - 'StatusPageOwner:SendAnnouncementCreatedEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const announcements: Array = - await StatusPageAnnouncementService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - title: true, - description: true, - projectId: true, - statusPages: { - _id: true, - name: true, - }, - }, - }); + "StatusPageOwner:SendAnnouncementCreatedEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const announcements: Array = + await StatusPageAnnouncementService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + title: true, + description: true, + projectId: true, + statusPages: { + _id: true, + name: true, + }, + }, + }); - for (const announcement of announcements) { - await StatusPageAnnouncementService.updateOneById({ - id: announcement.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); + for (const announcement of announcements) { + await StatusPageAnnouncementService.updateOneById({ + id: announcement.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); - const statusPages: Array = - announcement.statusPages || []; + const statusPages: Array = announcement.statusPages || []; - for (const statusPage of statusPages) { - // now find owners. + for (const statusPage of statusPages) { + // now find owners. - let doesResourceHasOwners: boolean = true; + let doesResourceHasOwners: boolean = true; - let owners: Array = await StatusPageService.findOwners( - statusPage.id! - ); + let owners: Array = await StatusPageService.findOwners( + statusPage.id!, + ); - if (owners.length === 0) { - doesResourceHasOwners = false; + if (owners.length === 0) { + doesResourceHasOwners = false; - // find project owners. - owners = await ProjectService.getOwners( - announcement.projectId! - ); - } - - if (owners.length === 0) { - continue; - } - - const vars: Dictionary = { - statusPageName: statusPage.name!, - announcementTitle: announcement.title!, - announcementDescription: await Markdown.convertToHTML( - announcement.description!, - MarkdownContentType.Email - ), - }; - - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } - - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: - EmailTemplateType.StatusPageOwnerAnnouncementPosted, - vars: vars, - subject: `[Announcement] ${announcement.title!}`, - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. New announcement posted on Status Page ${statusPage.name} - ${announcement.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. New announcement posted on Status Page ${statusPage.name}, ${announcement.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: announcement.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_STATUS_PAGE_ANNOUNCEMENT_CREATED_OWNER_NOTIFICATION, - }); - } - } + // find project owners. + owners = await ProjectService.getOwners(announcement.projectId!); } + + if (owners.length === 0) { + continue; + } + + const vars: Dictionary = { + statusPageName: statusPage.name!, + announcementTitle: announcement.title!, + announcementDescription: await Markdown.convertToHTML( + announcement.description!, + MarkdownContentType.Email, + ), + }; + + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } + + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.StatusPageOwnerAnnouncementPosted, + vars: vars, + subject: `[Announcement] ${announcement.title!}`, + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. New announcement posted on Status Page ${statusPage.name} - ${announcement.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. New announcement posted on Status Page ${statusPage.name}, ${announcement.title}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: announcement.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_STATUS_PAGE_ANNOUNCEMENT_CREATED_OWNER_NOTIFICATION, + }); + } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendCreatedResourceNotification.ts b/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendCreatedResourceNotification.ts index 221ce95acc..8f8436965b 100644 --- a/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendCreatedResourceNotification.ts +++ b/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendCreatedResourceNotification.ts @@ -1,123 +1,122 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import StatusPage from 'Model/Models/StatusPage'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import ProjectService from "CommonServer/Services/ProjectService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import StatusPage from "Model/Models/StatusPage"; +import User from "Model/Models/User"; RunCron( - 'StatusPageOwner:SendCreatedResourceEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // get all scheduled events of all the projects. - const statusPages: Array = await StatusPageService.findBy({ - query: { - isOwnerNotifiedOfResourceCreation: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - name: true, - description: true, - projectId: true, - project: { - name: true, - }, + "StatusPageOwner:SendCreatedResourceEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // get all scheduled events of all the projects. + const statusPages: Array = await StatusPageService.findBy({ + query: { + isOwnerNotifiedOfResourceCreation: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + name: true, + description: true, + projectId: true, + project: { + name: true, + }, + }, + }); + + for (const statusPage of statusPages) { + await StatusPageService.updateOneById({ + id: statusPage.id!, + data: { + isOwnerNotifiedOfResourceCreation: true, + }, + props: { + isRoot: true, + }, + }); + + // now find owners. + + let doesResourceHasOwners: boolean = true; + + let owners: Array = await StatusPageService.findOwners( + statusPage.id!, + ); + + if (owners.length === 0) { + doesResourceHasOwners = false; + + // find project owners. + owners = await ProjectService.getOwners(statusPage.projectId!); + } + + if (owners.length === 0) { + continue; + } + + const vars: Dictionary = { + statusPageName: statusPage.name!, + projectName: statusPage.project!.name!, + statusPageDescription: await Markdown.convertToHTML( + statusPage.description! || "", + MarkdownContentType.Email, + ), + statusPageViewLink: ( + await StatusPageService.getStatusPageLinkInDashboard( + statusPage.projectId!, + statusPage.id!, + ) + ).toString(), + }; + + if (doesResourceHasOwners === true) { + vars["isOwner"] = "true"; + } + + for (const user of owners) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.StatusPageOwnerResourceCreated, + vars: vars, + subject: "[Status Page Created]" + statusPage.name!, + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. New status page created - ${statusPage.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. New status page created ${statusPage.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: statusPage.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_STATUS_PAGE_CREATED_OWNER_NOTIFICATION, }); - - for (const statusPage of statusPages) { - await StatusPageService.updateOneById({ - id: statusPage.id!, - data: { - isOwnerNotifiedOfResourceCreation: true, - }, - props: { - isRoot: true, - }, - }); - - // now find owners. - - let doesResourceHasOwners: boolean = true; - - let owners: Array = await StatusPageService.findOwners( - statusPage.id! - ); - - if (owners.length === 0) { - doesResourceHasOwners = false; - - // find project owners. - owners = await ProjectService.getOwners(statusPage.projectId!); - } - - if (owners.length === 0) { - continue; - } - - const vars: Dictionary = { - statusPageName: statusPage.name!, - projectName: statusPage.project!.name!, - statusPageDescription: await Markdown.convertToHTML( - statusPage.description! || '', - MarkdownContentType.Email - ), - statusPageViewLink: ( - await StatusPageService.getStatusPageLinkInDashboard( - statusPage.projectId!, - statusPage.id! - ) - ).toString(), - }; - - if (doesResourceHasOwners === true) { - vars['isOwner'] = 'true'; - } - - for (const user of owners) { - const emailMessage: EmailEnvelope = { - templateType: - EmailTemplateType.StatusPageOwnerResourceCreated, - vars: vars, - subject: '[Status Page Created]' + statusPage.name!, - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. New status page created - ${statusPage.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. New status page created ${statusPage.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: statusPage.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_STATUS_PAGE_CREATED_OWNER_NOTIFICATION, - }); - } - } + } } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendOwnerAddedNotification.ts b/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendOwnerAddedNotification.ts index dbdb0cb959..db2de0c2fe 100644 --- a/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendOwnerAddedNotification.ts +++ b/App/FeatureSet/Workers/Jobs/StatusPageOwners/SendOwnerAddedNotification.ts @@ -1,207 +1,203 @@ -import RunCron from '../../Utils/Cron'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import ObjectID from 'Common/Types/ObjectID'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import StatusPageOwnerTeamService from 'CommonServer/Services/StatusPageOwnerTeamService'; -import StatusPageOwnerUserService from 'CommonServer/Services/StatusPageOwnerUserService'; -import StatusPageService from 'CommonServer/Services/StatusPageService'; -import TeamMemberService from 'CommonServer/Services/TeamMemberService'; -import UserNotificationSettingService from 'CommonServer/Services/UserNotificationSettingService'; -import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageOwnerTeam from 'Model/Models/StatusPageOwnerTeam'; -import StatusPageOwnerUser from 'Model/Models/StatusPageOwnerUser'; -import User from 'Model/Models/User'; +import RunCron from "../../Utils/Cron"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import ObjectID from "Common/Types/ObjectID"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import StatusPageOwnerTeamService from "CommonServer/Services/StatusPageOwnerTeamService"; +import StatusPageOwnerUserService from "CommonServer/Services/StatusPageOwnerUserService"; +import StatusPageService from "CommonServer/Services/StatusPageService"; +import TeamMemberService from "CommonServer/Services/TeamMemberService"; +import UserNotificationSettingService from "CommonServer/Services/UserNotificationSettingService"; +import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageOwnerTeam from "Model/Models/StatusPageOwnerTeam"; +import StatusPageOwnerUser from "Model/Models/StatusPageOwnerUser"; +import User from "Model/Models/User"; RunCron( - 'StatusPageOwner:SendOwnerAddedEmail', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - const statusPageOwnerTeams: Array = - await StatusPageOwnerTeamService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - statusPageId: true, - teamId: true, - }, - }); + "StatusPageOwner:SendOwnerAddedEmail", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + const statusPageOwnerTeams: Array = + await StatusPageOwnerTeamService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + statusPageId: true, + teamId: true, + }, + }); - const statusPageOwnersMap: Dictionary> = {}; + const statusPageOwnersMap: Dictionary> = {}; - for (const statusPageOwnerTeam of statusPageOwnerTeams) { - const statusPageId: ObjectID = statusPageOwnerTeam.statusPageId!; - const teamId: ObjectID = statusPageOwnerTeam.teamId!; + for (const statusPageOwnerTeam of statusPageOwnerTeams) { + const statusPageId: ObjectID = statusPageOwnerTeam.statusPageId!; + const teamId: ObjectID = statusPageOwnerTeam.teamId!; - const users: Array = await TeamMemberService.getUsersInTeams([ - teamId, - ]); + const users: Array = await TeamMemberService.getUsersInTeams([ + teamId, + ]); - if (statusPageOwnersMap[statusPageId.toString()] === undefined) { - statusPageOwnersMap[statusPageId.toString()] = []; - } + if (statusPageOwnersMap[statusPageId.toString()] === undefined) { + statusPageOwnersMap[statusPageId.toString()] = []; + } - for (const user of users) { - ( - statusPageOwnersMap[statusPageId.toString()] as Array - ).push(user); - } + for (const user of users) { + (statusPageOwnersMap[statusPageId.toString()] as Array).push( + user, + ); + } - // mark this as notified. - await StatusPageOwnerTeamService.updateOneById({ - id: statusPageOwnerTeam.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - const statusPageOwnerUsers: Array = - await StatusPageOwnerUserService.findBy({ - query: { - isOwnerNotified: false, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - statusPageId: true, - userId: true, - user: { - email: true, - name: true, - }, - }, - }); - - for (const statusPageOwnerUser of statusPageOwnerUsers) { - const statusPageId: ObjectID = statusPageOwnerUser.statusPageId!; - const user: User = statusPageOwnerUser.user!; - - if (statusPageOwnersMap[statusPageId.toString()] === undefined) { - statusPageOwnersMap[statusPageId.toString()] = []; - } - - (statusPageOwnersMap[statusPageId.toString()] as Array).push( - user - ); - - // mark this as notified. - await StatusPageOwnerUserService.updateOneById({ - id: statusPageOwnerUser.id!, - data: { - isOwnerNotified: true, - }, - props: { - isRoot: true, - }, - }); - } - - // send email to all of these users. - - for (const statusPageId in statusPageOwnersMap) { - if (!statusPageOwnersMap[statusPageId]) { - continue; - } - - if ( - (statusPageOwnersMap[statusPageId] as Array).length === 0 - ) { - continue; - } - - const users: Array = statusPageOwnersMap[ - statusPageId - ] as Array; - - // get all scheduled events of all the projects. - const statusPage: StatusPage | null = - await StatusPageService.findOneById({ - id: new ObjectID(statusPageId), - props: { - isRoot: true, - }, - - select: { - _id: true, - name: true, - description: true, - projectId: true, - project: { - name: true, - }, - }, - }); - - if (!statusPage) { - continue; - } - - const vars: Dictionary = { - statusPageName: statusPage.name!, - projectName: statusPage.project!.name!, - statusPageDescription: await Markdown.convertToHTML( - statusPage.description! || '', - MarkdownContentType.Email - ), - statusPageViewLink: ( - await StatusPageService.getStatusPageLinkInDashboard( - statusPage.projectId!, - statusPage.id! - ) - ).toString(), - }; - - for (const user of users) { - const emailMessage: EmailEnvelope = { - templateType: EmailTemplateType.StatusPageOwnerAdded, - vars: vars, - subject: - 'You have been added as the owner of the status page.', - }; - - const sms: SMSMessage = { - message: `This is a message from OneUptime. You have been added as the owner of the status page. Status Page Name: ${statusPage.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, - }; - - const callMessage: CallRequestMessage = { - data: [ - { - sayMessage: `This is a message from OneUptime. You have been added as the owner of the status page. Status Page ${statusPage.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, - }, - ], - }; - - await UserNotificationSettingService.sendUserNotification({ - userId: user.id!, - projectId: statusPage.projectId!, - emailEnvelope: emailMessage, - smsMessage: sms, - callRequestMessage: callMessage, - eventType: - NotificationSettingEventType.SEND_STATUS_PAGE_OWNER_ADDED_NOTIFICATION, - }); - } - } + // mark this as notified. + await StatusPageOwnerTeamService.updateOneById({ + id: statusPageOwnerTeam.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); } + + const statusPageOwnerUsers: Array = + await StatusPageOwnerUserService.findBy({ + query: { + isOwnerNotified: false, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + statusPageId: true, + userId: true, + user: { + email: true, + name: true, + }, + }, + }); + + for (const statusPageOwnerUser of statusPageOwnerUsers) { + const statusPageId: ObjectID = statusPageOwnerUser.statusPageId!; + const user: User = statusPageOwnerUser.user!; + + if (statusPageOwnersMap[statusPageId.toString()] === undefined) { + statusPageOwnersMap[statusPageId.toString()] = []; + } + + (statusPageOwnersMap[statusPageId.toString()] as Array).push(user); + + // mark this as notified. + await StatusPageOwnerUserService.updateOneById({ + id: statusPageOwnerUser.id!, + data: { + isOwnerNotified: true, + }, + props: { + isRoot: true, + }, + }); + } + + // send email to all of these users. + + for (const statusPageId in statusPageOwnersMap) { + if (!statusPageOwnersMap[statusPageId]) { + continue; + } + + if ((statusPageOwnersMap[statusPageId] as Array).length === 0) { + continue; + } + + const users: Array = statusPageOwnersMap[ + statusPageId + ] as Array; + + // get all scheduled events of all the projects. + const statusPage: StatusPage | null = await StatusPageService.findOneById( + { + id: new ObjectID(statusPageId), + props: { + isRoot: true, + }, + + select: { + _id: true, + name: true, + description: true, + projectId: true, + project: { + name: true, + }, + }, + }, + ); + + if (!statusPage) { + continue; + } + + const vars: Dictionary = { + statusPageName: statusPage.name!, + projectName: statusPage.project!.name!, + statusPageDescription: await Markdown.convertToHTML( + statusPage.description! || "", + MarkdownContentType.Email, + ), + statusPageViewLink: ( + await StatusPageService.getStatusPageLinkInDashboard( + statusPage.projectId!, + statusPage.id!, + ) + ).toString(), + }; + + for (const user of users) { + const emailMessage: EmailEnvelope = { + templateType: EmailTemplateType.StatusPageOwnerAdded, + vars: vars, + subject: "You have been added as the owner of the status page.", + }; + + const sms: SMSMessage = { + message: `This is a message from OneUptime. You have been added as the owner of the status page. Status Page Name: ${statusPage.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard.`, + }; + + const callMessage: CallRequestMessage = { + data: [ + { + sayMessage: `This is a message from OneUptime. You have been added as the owner of the status page. Status Page ${statusPage.name}. To unsubscribe from this notification go to User Settings in OneUptime Dashboard. Good bye.`, + }, + ], + }; + + await UserNotificationSettingService.sendUserNotification({ + userId: user.id!, + projectId: statusPage.projectId!, + emailEnvelope: emailMessage, + smsMessage: sms, + callRequestMessage: callMessage, + eventType: + NotificationSettingEventType.SEND_STATUS_PAGE_OWNER_ADDED_NOTIFICATION, + }); + } + } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/TelemetryService/DeleteOldData.ts b/App/FeatureSet/Workers/Jobs/TelemetryService/DeleteOldData.ts index 94ca19860e..aaec1dd396 100644 --- a/App/FeatureSet/Workers/Jobs/TelemetryService/DeleteOldData.ts +++ b/App/FeatureSet/Workers/Jobs/TelemetryService/DeleteOldData.ts @@ -1,77 +1,77 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import { EVERY_HOUR, EVERY_MINUTE } from 'Common/Utils/CronTime'; -import { IsDevelopment } from 'CommonServer/EnvironmentConfig'; -import LogService from 'CommonServer/Services/LogService'; -import SpanService from 'CommonServer/Services/SpanService'; -import TelemetryServiceService from 'CommonServer/Services/TelemetryServiceService'; -import QueryHelper from 'CommonServer/Types/AnalyticsDatabase/QueryHelper'; -import TelemetryService from 'Model/Models/TelemetryService'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import { EVERY_HOUR, EVERY_MINUTE } from "Common/Utils/CronTime"; +import { IsDevelopment } from "CommonServer/EnvironmentConfig"; +import LogService from "CommonServer/Services/LogService"; +import SpanService from "CommonServer/Services/SpanService"; +import TelemetryServiceService from "CommonServer/Services/TelemetryServiceService"; +import QueryHelper from "CommonServer/Types/AnalyticsDatabase/QueryHelper"; +import TelemetryService from "Model/Models/TelemetryService"; RunCron( - 'TelemetryService:DeleteOldData', - { - schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, - runOnStartup: false, - }, - async () => { - // get a list of all the telemetry services. + "TelemetryService:DeleteOldData", + { + schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, + runOnStartup: false, + }, + async () => { + // get a list of all the telemetry services. - const telemetryService: Array = - await TelemetryServiceService.findBy({ - query: {}, - limit: LIMIT_MAX, - skip: 0, - select: { - retainTelemetryDataForDays: true, - projectId: true, - _id: true, - }, - props: { - isRoot: true, - }, - }); + const telemetryService: Array = + await TelemetryServiceService.findBy({ + query: {}, + limit: LIMIT_MAX, + skip: 0, + select: { + retainTelemetryDataForDays: true, + projectId: true, + _id: true, + }, + props: { + isRoot: true, + }, + }); - for (const service of telemetryService) { - let dataRententionDays: number | undefined = - service.retainTelemetryDataForDays; + for (const service of telemetryService) { + let dataRententionDays: number | undefined = + service.retainTelemetryDataForDays; - if (!dataRententionDays) { - dataRententionDays = 15; // default to 15 days. - } + if (!dataRententionDays) { + dataRententionDays = 15; // default to 15 days. + } - // delete logs + // delete logs - await LogService.deleteBy({ - query: { - createdAt: QueryHelper.lessThan( - OneUptimeDate.getSomeDaysAgo(dataRententionDays) - ), - serviceId: service.id, - projectId: service.projectId, - }, - props: { - isRoot: true, - }, - }); + await LogService.deleteBy({ + query: { + createdAt: QueryHelper.lessThan( + OneUptimeDate.getSomeDaysAgo(dataRententionDays), + ), + serviceId: service.id, + projectId: service.projectId, + }, + props: { + isRoot: true, + }, + }); - // delete spans + // delete spans - await SpanService.deleteBy({ - query: { - createdAt: QueryHelper.lessThan( - OneUptimeDate.getSomeDaysAgo(dataRententionDays) - ), - serviceId: service.id, - projectId: service.projectId, - }, - props: { - isRoot: true, - }, - }); + await SpanService.deleteBy({ + query: { + createdAt: QueryHelper.lessThan( + OneUptimeDate.getSomeDaysAgo(dataRententionDays), + ), + serviceId: service.id, + projectId: service.projectId, + }, + props: { + isRoot: true, + }, + }); - // TOOD: delete metrics. - } + // TOOD: delete metrics. } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/UserOnCallLog/ExecutePendingExecutions.ts b/App/FeatureSet/Workers/Jobs/UserOnCallLog/ExecutePendingExecutions.ts index 120b34344b..547c2a19dd 100644 --- a/App/FeatureSet/Workers/Jobs/UserOnCallLog/ExecutePendingExecutions.ts +++ b/App/FeatureSet/Workers/Jobs/UserOnCallLog/ExecutePendingExecutions.ts @@ -1,187 +1,178 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import NotificationRuleType from 'Common/Types/NotificationRule/NotificationRuleType'; -import ObjectID from 'Common/Types/ObjectID'; -import UserNotificationExecutionStatus from 'Common/Types/UserNotification/UserNotificationExecutionStatus'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import { IsDevelopment } from 'CommonServer/EnvironmentConfig'; -import IncidentService from 'CommonServer/Services/IncidentService'; -import UserNotificationRuleService from 'CommonServer/Services/UserNotificationRuleService'; -import UserOnCallLogService from 'CommonServer/Services/UserOnCallLogService'; -import logger from 'CommonServer/Utils/Logger'; -import Incident from 'Model/Models/Incident'; -import UserNotificationRule from 'Model/Models/UserNotificationRule'; -import UserOnCallLog from 'Model/Models/UserOnCallLog'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import NotificationRuleType from "Common/Types/NotificationRule/NotificationRuleType"; +import ObjectID from "Common/Types/ObjectID"; +import UserNotificationExecutionStatus from "Common/Types/UserNotification/UserNotificationExecutionStatus"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import { IsDevelopment } from "CommonServer/EnvironmentConfig"; +import IncidentService from "CommonServer/Services/IncidentService"; +import UserNotificationRuleService from "CommonServer/Services/UserNotificationRuleService"; +import UserOnCallLogService from "CommonServer/Services/UserOnCallLogService"; +import logger from "CommonServer/Utils/Logger"; +import Incident from "Model/Models/Incident"; +import UserNotificationRule from "Model/Models/UserNotificationRule"; +import UserOnCallLog from "Model/Models/UserOnCallLog"; RunCron( - 'UserOnCallLog:ExecutePendingExecutions', - { - schedule: IsDevelopment ? EVERY_MINUTE : EVERY_MINUTE, - runOnStartup: false, - }, - async () => { - const pendingNotificationLogs: Array = - await UserOnCallLogService.findBy({ - query: { - status: UserNotificationExecutionStatus.Executing, - }, - select: { - _id: true, - projectId: true, - createdAt: true, - executedNotificationRules: true, - userId: true, - userNotificationEventType: true, - triggeredByIncidentId: true, - onCallDutyPolicyEscalationRuleId: true, - onCallDutyPolicyExecutionLogTimelineId: true, - onCallDutyPolicyExecutionLogId: true, - onCallDutyPolicyId: true, - userBelongsToTeamId: true, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_MAX, - }); + "UserOnCallLog:ExecutePendingExecutions", + { + schedule: IsDevelopment ? EVERY_MINUTE : EVERY_MINUTE, + runOnStartup: false, + }, + async () => { + const pendingNotificationLogs: Array = + await UserOnCallLogService.findBy({ + query: { + status: UserNotificationExecutionStatus.Executing, + }, + select: { + _id: true, + projectId: true, + createdAt: true, + executedNotificationRules: true, + userId: true, + userNotificationEventType: true, + triggeredByIncidentId: true, + onCallDutyPolicyEscalationRuleId: true, + onCallDutyPolicyExecutionLogTimelineId: true, + onCallDutyPolicyExecutionLogId: true, + onCallDutyPolicyId: true, + userBelongsToTeamId: true, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_MAX, + }); - const promises: Array> = []; + const promises: Array> = []; - for (const pendingNotificationLog of pendingNotificationLogs) { - promises.push( - executePendingNotificationLog(pendingNotificationLog) - ); - } - - await Promise.allSettled(promises); + for (const pendingNotificationLog of pendingNotificationLogs) { + promises.push(executePendingNotificationLog(pendingNotificationLog)); } + + await Promise.allSettled(promises); + }, ); type ExecutePendingNotificationLogFunction = ( - pendingNotificationLog: UserOnCallLog + pendingNotificationLog: UserOnCallLog, ) => Promise; const executePendingNotificationLog: ExecutePendingNotificationLogFunction = - async (pendingNotificationLog: UserOnCallLog): Promise => { - try { - const ruleType: NotificationRuleType = - UserOnCallLogService.getNotificationRuleType( - pendingNotificationLog.userNotificationEventType! - ); + async (pendingNotificationLog: UserOnCallLog): Promise => { + try { + const ruleType: NotificationRuleType = + UserOnCallLogService.getNotificationRuleType( + pendingNotificationLog.userNotificationEventType!, + ); - const incident: Incident | null = await IncidentService.findOneById( - { - id: pendingNotificationLog.triggeredByIncidentId!, - props: { - isRoot: true, - }, - select: { - incidentSeverityId: true, - }, - } - ); + const incident: Incident | null = await IncidentService.findOneById({ + id: pendingNotificationLog.triggeredByIncidentId!, + props: { + isRoot: true, + }, + select: { + incidentSeverityId: true, + }, + }); - const notificationRules: Array = - await UserNotificationRuleService.findBy({ - query: { - projectId: pendingNotificationLog.projectId!, - userId: pendingNotificationLog.userId!, - ruleType: ruleType, - incidentSeverityId: - incident?.incidentSeverityId as ObjectID, - }, - select: { - _id: true, - notifyAfterMinutes: true, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - }); + const notificationRules: Array = + await UserNotificationRuleService.findBy({ + query: { + projectId: pendingNotificationLog.projectId!, + userId: pendingNotificationLog.userId!, + ruleType: ruleType, + incidentSeverityId: incident?.incidentSeverityId as ObjectID, + }, + select: { + _id: true, + notifyAfterMinutes: true, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + }); - let isAllExecuted: boolean = true; + let isAllExecuted: boolean = true; - const minutesSinceExecutionStarted: number = - OneUptimeDate.getDifferenceInMinutes( - pendingNotificationLog.createdAt!, - OneUptimeDate.getCurrentDate() - ); + const minutesSinceExecutionStarted: number = + OneUptimeDate.getDifferenceInMinutes( + pendingNotificationLog.createdAt!, + OneUptimeDate.getCurrentDate(), + ); - for (const notificationRule of notificationRules) { - // check if this rule is already executed. - const isAlreadyExecuted: boolean = Object.keys( - pendingNotificationLog.executedNotificationRules! || {} - ).includes(notificationRule.id?.toString() || ''); + for (const notificationRule of notificationRules) { + // check if this rule is already executed. + const isAlreadyExecuted: boolean = Object.keys( + pendingNotificationLog.executedNotificationRules! || {}, + ).includes(notificationRule.id?.toString() || ""); - if (isAlreadyExecuted) { - continue; - } - - isAllExecuted = false; - - if ( - notificationRule.notifyAfterMinutes! > - minutesSinceExecutionStarted - ) { - continue; - } - - // execute this rule. - - await UserNotificationRuleService.executeNotificationRuleItem( - notificationRule.id!, - { - userNotificationLogId: pendingNotificationLog.id!, - projectId: pendingNotificationLog.projectId!, - triggeredByIncidentId: - pendingNotificationLog.triggeredByIncidentId, - userNotificationEventType: - pendingNotificationLog.userNotificationEventType!, - onCallPolicyExecutionLogId: - pendingNotificationLog.onCallDutyPolicyExecutionLogId, - onCallPolicyId: - pendingNotificationLog.onCallDutyPolicyId, - onCallPolicyEscalationRuleId: - pendingNotificationLog.onCallDutyPolicyEscalationRuleId, - userBelongsToTeamId: - pendingNotificationLog.userBelongsToTeamId, - onCallDutyPolicyExecutionLogTimelineId: - pendingNotificationLog.onCallDutyPolicyExecutionLogTimelineId, - } - ); - } - - if (isAllExecuted) { - // mark this log as complete. - await UserOnCallLogService.updateOneById({ - id: pendingNotificationLog.id!, - data: { - status: UserNotificationExecutionStatus.Completed, - }, - props: { - isRoot: true, - }, - }); - } - } catch (err: any) { - logger.error( - `Error executing pending notification log: ${pendingNotificationLog._id}` - ); - logger.error(err); - - await UserOnCallLogService.updateOneById({ - id: pendingNotificationLog.id!, - data: { - status: UserNotificationExecutionStatus.Error, - statusMessage: err.message ? err.message : 'Unknown error', - }, - props: { - isRoot: true, - }, - }); + if (isAlreadyExecuted) { + continue; } - }; + + isAllExecuted = false; + + if ( + notificationRule.notifyAfterMinutes! > minutesSinceExecutionStarted + ) { + continue; + } + + // execute this rule. + + await UserNotificationRuleService.executeNotificationRuleItem( + notificationRule.id!, + { + userNotificationLogId: pendingNotificationLog.id!, + projectId: pendingNotificationLog.projectId!, + triggeredByIncidentId: pendingNotificationLog.triggeredByIncidentId, + userNotificationEventType: + pendingNotificationLog.userNotificationEventType!, + onCallPolicyExecutionLogId: + pendingNotificationLog.onCallDutyPolicyExecutionLogId, + onCallPolicyId: pendingNotificationLog.onCallDutyPolicyId, + onCallPolicyEscalationRuleId: + pendingNotificationLog.onCallDutyPolicyEscalationRuleId, + userBelongsToTeamId: pendingNotificationLog.userBelongsToTeamId, + onCallDutyPolicyExecutionLogTimelineId: + pendingNotificationLog.onCallDutyPolicyExecutionLogTimelineId, + }, + ); + } + + if (isAllExecuted) { + // mark this log as complete. + await UserOnCallLogService.updateOneById({ + id: pendingNotificationLog.id!, + data: { + status: UserNotificationExecutionStatus.Completed, + }, + props: { + isRoot: true, + }, + }); + } + } catch (err: any) { + logger.error( + `Error executing pending notification log: ${pendingNotificationLog._id}`, + ); + logger.error(err); + + await UserOnCallLogService.updateOneById({ + id: pendingNotificationLog.id!, + data: { + status: UserNotificationExecutionStatus.Error, + statusMessage: err.message ? err.message : "Unknown error", + }, + props: { + isRoot: true, + }, + }); + } + }; diff --git a/App/FeatureSet/Workers/Jobs/UserOnCallLog/TimeoutStuckExecutions.ts b/App/FeatureSet/Workers/Jobs/UserOnCallLog/TimeoutStuckExecutions.ts index dfaea92ede..6513f2b6ed 100644 --- a/App/FeatureSet/Workers/Jobs/UserOnCallLog/TimeoutStuckExecutions.ts +++ b/App/FeatureSet/Workers/Jobs/UserOnCallLog/TimeoutStuckExecutions.ts @@ -1,55 +1,55 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import UserNotificationExecutionStatus from 'Common/Types/UserNotification/UserNotificationExecutionStatus'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import { IsDevelopment } from 'CommonServer/EnvironmentConfig'; -import UserOnCallLogService from 'CommonServer/Services/UserOnCallLogService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import UserOnCallLog from 'Model/Models/UserOnCallLog'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import UserNotificationExecutionStatus from "Common/Types/UserNotification/UserNotificationExecutionStatus"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import { IsDevelopment } from "CommonServer/EnvironmentConfig"; +import UserOnCallLogService from "CommonServer/Services/UserOnCallLogService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import UserOnCallLog from "Model/Models/UserOnCallLog"; /** * Jobs move from Started to Executing in seconds. If it takes more than 5 minutes, it's stuck. So, mark them as error */ RunCron( - 'UserOnCallLog:TimeoutStuckExecutions', - { - schedule: IsDevelopment ? EVERY_MINUTE : EVERY_MINUTE, - runOnStartup: false, - }, - async () => { - // get all pending on-call executions and execute them all at once. - const fiveMinsAgo: Date = OneUptimeDate.getSomeMinutesAgo(5); + "UserOnCallLog:TimeoutStuckExecutions", + { + schedule: IsDevelopment ? EVERY_MINUTE : EVERY_MINUTE, + runOnStartup: false, + }, + async () => { + // get all pending on-call executions and execute them all at once. + const fiveMinsAgo: Date = OneUptimeDate.getSomeMinutesAgo(5); - const stuckExecutions: Array = - await UserOnCallLogService.findBy({ - query: { - status: UserNotificationExecutionStatus.Started, - createdAt: QueryHelper.lessThan(fiveMinsAgo), - }, - select: { - _id: true, - createdAt: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); + const stuckExecutions: Array = + await UserOnCallLogService.findBy({ + query: { + status: UserNotificationExecutionStatus.Started, + createdAt: QueryHelper.lessThan(fiveMinsAgo), + }, + select: { + _id: true, + createdAt: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); - for (const executionLog of stuckExecutions) { - await UserOnCallLogService.updateOneById({ - id: executionLog.id!, - data: { - status: UserNotificationExecutionStatus.Error, - statusMessage: 'Rule execution timed out.', - }, - props: { - isRoot: true, - }, - }); - } + for (const executionLog of stuckExecutions) { + await UserOnCallLogService.updateOneById({ + id: executionLog.id!, + data: { + status: UserNotificationExecutionStatus.Error, + statusMessage: "Rule execution timed out.", + }, + props: { + isRoot: true, + }, + }); } + }, ); diff --git a/App/FeatureSet/Workers/Jobs/Workflow/TimeoutJobs.ts b/App/FeatureSet/Workers/Jobs/Workflow/TimeoutJobs.ts index f356c268b5..2c55724f04 100644 --- a/App/FeatureSet/Workers/Jobs/Workflow/TimeoutJobs.ts +++ b/App/FeatureSet/Workers/Jobs/Workflow/TimeoutJobs.ts @@ -1,49 +1,47 @@ -import RunCron from '../../Utils/Cron'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import WorkflowStatus from 'Common/Types/Workflow/WorkflowStatus'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import WorkflowLogService from 'CommonServer/Services/WorkflowLogService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import WorkflowLog from 'Model/Models/WorkflowLog'; +import RunCron from "../../Utils/Cron"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import WorkflowStatus from "Common/Types/Workflow/WorkflowStatus"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import WorkflowLogService from "CommonServer/Services/WorkflowLogService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import WorkflowLog from "Model/Models/WorkflowLog"; RunCron( - 'Workflow:TimeoutJobs', - { schedule: EVERY_MINUTE, runOnStartup: false }, - async () => { - // Timeout all workflows that have been scheduled for more than 5 minutes - const stalledWorkflowLogs: Array = - await WorkflowLogService.findBy({ - query: { - createdAt: QueryHelper.lessThan( - OneUptimeDate.getSomeMinutesAgo(5) - ), - workflowStatus: WorkflowStatus.Scheduled, - }, - limit: LIMIT_MAX, - select: { - logs: true, - _id: true, - }, - skip: 0, - props: { - isRoot: true, - }, - }); + "Workflow:TimeoutJobs", + { schedule: EVERY_MINUTE, runOnStartup: false }, + async () => { + // Timeout all workflows that have been scheduled for more than 5 minutes + const stalledWorkflowLogs: Array = + await WorkflowLogService.findBy({ + query: { + createdAt: QueryHelper.lessThan(OneUptimeDate.getSomeMinutesAgo(5)), + workflowStatus: WorkflowStatus.Scheduled, + }, + limit: LIMIT_MAX, + select: { + logs: true, + _id: true, + }, + skip: 0, + props: { + isRoot: true, + }, + }); - for (const stalledWorkflowLog of stalledWorkflowLogs) { - await WorkflowLogService.updateOneById({ - id: stalledWorkflowLog.id!, - data: { - workflowStatus: WorkflowStatus.Error, - logs: `${ - stalledWorkflowLog.logs - } \n ${OneUptimeDate.getCurrentDateAsFormattedString()}: Workflow was not picked up by the runner and has timed out.`, - }, - props: { - isRoot: true, - }, - }); - } + for (const stalledWorkflowLog of stalledWorkflowLogs) { + await WorkflowLogService.updateOneById({ + id: stalledWorkflowLog.id!, + data: { + workflowStatus: WorkflowStatus.Error, + logs: `${ + stalledWorkflowLog.logs + } \n ${OneUptimeDate.getCurrentDateAsFormattedString()}: Workflow was not picked up by the runner and has timed out.`, + }, + props: { + isRoot: true, + }, + }); } + }, ); diff --git a/App/FeatureSet/Workers/Utils/AnalyticsDatabase/TableManegement.ts b/App/FeatureSet/Workers/Utils/AnalyticsDatabase/TableManegement.ts index b3a95be647..30eaac4c89 100644 --- a/App/FeatureSet/Workers/Utils/AnalyticsDatabase/TableManegement.ts +++ b/App/FeatureSet/Workers/Utils/AnalyticsDatabase/TableManegement.ts @@ -1,12 +1,12 @@ -import { AnalyticsServices } from 'CommonServer/Services/Index'; +import { AnalyticsServices } from "CommonServer/Services/Index"; export default class AnalyticsTableManagement { - public static async createTables(): Promise { - for (const service of AnalyticsServices) { - // create a table if it does not exist - await service.execute( - service.statementGenerator.toTableCreateStatement() - ); - } + public static async createTables(): Promise { + for (const service of AnalyticsServices) { + // create a table if it does not exist + await service.execute( + service.statementGenerator.toTableCreateStatement(), + ); } + } } diff --git a/App/FeatureSet/Workers/Utils/Cron.ts b/App/FeatureSet/Workers/Utils/Cron.ts index 2d8b9f32f6..69b148df51 100644 --- a/App/FeatureSet/Workers/Utils/Cron.ts +++ b/App/FeatureSet/Workers/Utils/Cron.ts @@ -1,48 +1,46 @@ -import JobDictionary from './JobDictionary'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Queue, { QueueName } from 'CommonServer/Infrastructure/Queue'; -import logger from 'CommonServer/Utils/Logger'; +import JobDictionary from "./JobDictionary"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Queue, { QueueName } from "CommonServer/Infrastructure/Queue"; +import logger from "CommonServer/Utils/Logger"; type RunCronFunction = ( - jobName: string, - options: { - schedule: string; - runOnStartup: boolean; - }, - runFunction: PromiseVoidFunction + jobName: string, + options: { + schedule: string; + runOnStartup: boolean; + }, + runFunction: PromiseVoidFunction, ) => void; const RunCron: RunCronFunction = ( - jobName: string, - options: { - schedule: string; - runOnStartup: boolean; - }, - runFunction: PromiseVoidFunction + jobName: string, + options: { + schedule: string; + runOnStartup: boolean; + }, + runFunction: PromiseVoidFunction, ): void => { - JobDictionary.setJobFunction(jobName, runFunction); + JobDictionary.setJobFunction(jobName, runFunction); - logger.debug('Adding job to the queue: ' + jobName); + logger.debug("Adding job to the queue: " + jobName); - Queue.addJob( - QueueName.Worker, - jobName, - jobName, - {}, - { - scheduleAt: options.schedule, - } - ).catch((err: Error) => { - return logger.error(err); + Queue.addJob( + QueueName.Worker, + jobName, + jobName, + {}, + { + scheduleAt: options.schedule, + }, + ).catch((err: Error) => { + return logger.error(err); + }); + + if (options.runOnStartup) { + Queue.addJob(QueueName.Worker, jobName, jobName, {}).catch((err: Error) => { + return logger.error(err); }); - - if (options.runOnStartup) { - Queue.addJob(QueueName.Worker, jobName, jobName, {}).catch( - (err: Error) => { - return logger.error(err); - } - ); - } + } }; export default RunCron; diff --git a/App/FeatureSet/Workers/Utils/DataMigration.ts b/App/FeatureSet/Workers/Utils/DataMigration.ts index 615d4929b4..65e009b96e 100644 --- a/App/FeatureSet/Workers/Utils/DataMigration.ts +++ b/App/FeatureSet/Workers/Utils/DataMigration.ts @@ -1,65 +1,63 @@ -import DataMigrations from '../DataMigrations/Index'; -import OneUptimeDate from 'Common/Types/Date'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import DataMigrationService from 'CommonServer/Services/DataMigrationService'; -import logger from 'CommonServer/Utils/Logger'; -import DataMigration from 'Model/Models/DataMigration'; +import DataMigrations from "../DataMigrations/Index"; +import OneUptimeDate from "Common/Types/Date"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import DataMigrationService from "CommonServer/Services/DataMigrationService"; +import logger from "CommonServer/Utils/Logger"; +import DataMigration from "Model/Models/DataMigration"; const RunDatabaseMigrations: PromiseVoidFunction = async (): Promise => { - for (const migration of DataMigrations) { - try { - // check if this migration has already been run - const existingMigration: DataMigration | null = - await DataMigrationService.findOneBy({ - query: { - name: migration.name, - executed: true, - }, - props: { - isRoot: true, - }, - }); + for (const migration of DataMigrations) { + try { + // check if this migration has already been run + const existingMigration: DataMigration | null = + await DataMigrationService.findOneBy({ + query: { + name: migration.name, + executed: true, + }, + props: { + isRoot: true, + }, + }); - if (existingMigration) { - logger.debug('Skipping Database Migration:' + migration.name); - continue; - } + if (existingMigration) { + logger.debug("Skipping Database Migration:" + migration.name); + continue; + } - logger.debug('Running Database Migration:' + migration.name); + logger.debug("Running Database Migration:" + migration.name); - await migration.migrate(); + await migration.migrate(); - logger.debug('Database Migration Complete:' + migration.name); + logger.debug("Database Migration Complete:" + migration.name); - // add it to the database. - const dataMigration: DataMigration = new DataMigration(); - dataMigration.name = migration.name; - dataMigration.executed = true; - dataMigration.executedAt = OneUptimeDate.getCurrentDate(); + // add it to the database. + const dataMigration: DataMigration = new DataMigration(); + dataMigration.name = migration.name; + dataMigration.executed = true; + dataMigration.executedAt = OneUptimeDate.getCurrentDate(); - await DataMigrationService.create({ - data: dataMigration, - props: { - isRoot: true, - }, - }); - } catch (err) { - logger.error('Database Migration Failed:' + migration.name); - logger.error(err); - logger.debug('Rolling back Database Migration:' + migration.name); + await DataMigrationService.create({ + data: dataMigration, + props: { + isRoot: true, + }, + }); + } catch (err) { + logger.error("Database Migration Failed:" + migration.name); + logger.error(err); + logger.debug("Rolling back Database Migration:" + migration.name); - try { - await migration.rollback(); - } catch (err) { - logger.error( - 'Database Migration Rollback Failed:' + migration.name - ); - logger.error(err); - } + try { + await migration.rollback(); + } catch (err) { + logger.error("Database Migration Rollback Failed:" + migration.name); + logger.error(err); + } - break; // Stop running migrations - } + break; // Stop running migrations } + } }; export default RunDatabaseMigrations; diff --git a/App/FeatureSet/Workers/Utils/JobDictionary.ts b/App/FeatureSet/Workers/Utils/JobDictionary.ts index b9b84c92ae..7b3c48d86a 100644 --- a/App/FeatureSet/Workers/Utils/JobDictionary.ts +++ b/App/FeatureSet/Workers/Utils/JobDictionary.ts @@ -1,19 +1,19 @@ -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; export default class JobDictionary { - private static dictionary: Dictionary = {}; + private static dictionary: Dictionary = {}; - public static getJobFunction(name: string): PromiseVoidFunction { - if (this.dictionary[name]) { - return this.dictionary[name] as PromiseVoidFunction; - } - - throw new BadDataException('No job found with name: ' + name); + public static getJobFunction(name: string): PromiseVoidFunction { + if (this.dictionary[name]) { + return this.dictionary[name] as PromiseVoidFunction; } - public static setJobFunction(name: string, job: PromiseVoidFunction): void { - this.dictionary[name] = job; - } + throw new BadDataException("No job found with name: " + name); + } + + public static setJobFunction(name: string, job: PromiseVoidFunction): void { + this.dictionary[name] = job; + } } diff --git a/App/FeatureSet/Workflow/API/ComponentCode.ts b/App/FeatureSet/Workflow/API/ComponentCode.ts index 36977e1231..df88bba686 100644 --- a/App/FeatureSet/Workflow/API/ComponentCode.ts +++ b/App/FeatureSet/Workflow/API/ComponentCode.ts @@ -1,59 +1,59 @@ -import QueueWorkflow from '../Services/QueueWorkflow'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentCode from 'CommonServer/Types/Workflow/ComponentCode'; -import Components from 'CommonServer/Types/Workflow/Components/Index'; +import QueueWorkflow from "../Services/QueueWorkflow"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentCode from "CommonServer/Types/Workflow/ComponentCode"; +import Components from "CommonServer/Types/Workflow/Components/Index"; import TriggerCode, { - ExecuteWorkflowType, -} from 'CommonServer/Types/Workflow/TriggerCode'; -import Express, { ExpressRouter } from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; + ExecuteWorkflowType, +} from "CommonServer/Types/Workflow/TriggerCode"; +import Express, { ExpressRouter } from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; export default class ComponentCodeAPI { - public router!: ExpressRouter; + public router!: ExpressRouter; - public constructor() { - this.router = Express.getRouter(); + public constructor() { + this.router = Express.getRouter(); + } + + public init(): void { + // init all component code. + /// Get all the components. + for (const key in Components) { + const ComponentCode: ComponentCode | undefined = Components[key]; + + if (ComponentCode instanceof TriggerCode) { + const instance: TriggerCode = ComponentCode; + instance + .setupComponent({ + router: this.router, + scheduleWorkflow: this.scheduleWorkflow, + executeWorkflow: this.executeWorkflow, + removeWorkflow: this.removeWorkflow, + }) + .catch((err: Error) => { + logger.error(err); + }); + } } + } - public init(): void { - // init all component code. - /// Get all the components. - for (const key in Components) { - const ComponentCode: ComponentCode | undefined = Components[key]; + public async scheduleWorkflow( + executeWorkflow: ExecuteWorkflowType, + scheduleAt: string, + ): Promise { + /// add to queue. + await QueueWorkflow.addWorkflowToQueue(executeWorkflow, scheduleAt); + } - if (ComponentCode instanceof TriggerCode) { - const instance: TriggerCode = ComponentCode; - instance - .setupComponent({ - router: this.router, - scheduleWorkflow: this.scheduleWorkflow, - executeWorkflow: this.executeWorkflow, - removeWorkflow: this.removeWorkflow, - }) - .catch((err: Error) => { - logger.error(err); - }); - } - } - } + public async executeWorkflow( + executeWorkflow: ExecuteWorkflowType, + ): Promise { + // add to queue. + await QueueWorkflow.addWorkflowToQueue(executeWorkflow); + } - public async scheduleWorkflow( - executeWorkflow: ExecuteWorkflowType, - scheduleAt: string - ): Promise { - /// add to queue. - await QueueWorkflow.addWorkflowToQueue(executeWorkflow, scheduleAt); - } - - public async executeWorkflow( - executeWorkflow: ExecuteWorkflowType - ): Promise { - // add to queue. - await QueueWorkflow.addWorkflowToQueue(executeWorkflow); - } - - public async removeWorkflow(workflowId: ObjectID): Promise { - // add to queue. - await QueueWorkflow.removeWorkflow(workflowId); - } + public async removeWorkflow(workflowId: ObjectID): Promise { + // add to queue. + await QueueWorkflow.removeWorkflow(workflowId); + } } diff --git a/App/FeatureSet/Workflow/API/Manual.ts b/App/FeatureSet/Workflow/API/Manual.ts index 2a16fe9259..c7d62ef53e 100644 --- a/App/FeatureSet/Workflow/API/Manual.ts +++ b/App/FeatureSet/Workflow/API/Manual.ts @@ -1,45 +1,45 @@ -import QueueWorkflow from '../Services/QueueWorkflow'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; +import QueueWorkflow from "../Services/QueueWorkflow"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, -} from 'CommonServer/Utils/Express'; -import Response from 'CommonServer/Utils/Response'; + ExpressRequest, + ExpressResponse, + ExpressRouter, +} from "CommonServer/Utils/Express"; +import Response from "CommonServer/Utils/Response"; export default class ManualAPI { - public router!: ExpressRouter; + public router!: ExpressRouter; - public constructor() { - this.router = Express.getRouter(); + public constructor() { + this.router = Express.getRouter(); - this.router.get(`/run/:workflowId`, this.manuallyRunWorkflow); + this.router.get(`/run/:workflowId`, this.manuallyRunWorkflow); - this.router.post(`/run/:workflowId`, this.manuallyRunWorkflow); + this.router.post(`/run/:workflowId`, this.manuallyRunWorkflow); + } + + public async manuallyRunWorkflow( + req: ExpressRequest, + res: ExpressResponse, + ): Promise { + // add this workflow to the run queue and return the 200 response. + + if (!req.params["workflowId"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("workflowId not found in URL"), + ); } - public async manuallyRunWorkflow( - req: ExpressRequest, - res: ExpressResponse - ): Promise { - // add this workflow to the run queue and return the 200 response. + await QueueWorkflow.addWorkflowToQueue({ + workflowId: new ObjectID(req.params["workflowId"] as string), + returnValues: req.body.data || {}, + }); - if (!req.params['workflowId']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('workflowId not found in URL') - ); - } - - await QueueWorkflow.addWorkflowToQueue({ - workflowId: new ObjectID(req.params['workflowId'] as string), - returnValues: req.body.data || {}, - }); - - return Response.sendJsonObjectResponse(req, res, { - status: 'Scheduled', - }); - } + return Response.sendJsonObjectResponse(req, res, { + status: "Scheduled", + }); + } } diff --git a/App/FeatureSet/Workflow/API/Workflow.ts b/App/FeatureSet/Workflow/API/Workflow.ts index d7808a65bc..40328069ec 100644 --- a/App/FeatureSet/Workflow/API/Workflow.ts +++ b/App/FeatureSet/Workflow/API/Workflow.ts @@ -1,91 +1,91 @@ -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization'; -import WorkflowService from 'CommonServer/Services/WorkflowService'; -import ComponentCode from 'CommonServer/Types/Workflow/ComponentCode'; -import Components from 'CommonServer/Types/Workflow/Components/Index'; -import TriggerCode from 'CommonServer/Types/Workflow/TriggerCode'; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization"; +import WorkflowService from "CommonServer/Services/WorkflowService"; +import ComponentCode from "CommonServer/Types/Workflow/ComponentCode"; +import Components from "CommonServer/Types/Workflow/Components/Index"; +import TriggerCode from "CommonServer/Types/Workflow/TriggerCode"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, -} from 'CommonServer/Utils/Express'; -import Response from 'CommonServer/Utils/Response'; -import Workflow from 'Model/Models/Workflow'; + ExpressRequest, + ExpressResponse, + ExpressRouter, +} from "CommonServer/Utils/Express"; +import Response from "CommonServer/Utils/Response"; +import Workflow from "Model/Models/Workflow"; export default class WorkflowAPI { - public router!: ExpressRouter; + public router!: ExpressRouter; - public constructor() { - this.router = Express.getRouter(); + public constructor() { + this.router = Express.getRouter(); - this.router.get( - `/update/:workflowId`, - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - this.updateWorkflow - ); + this.router.get( + `/update/:workflowId`, + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + this.updateWorkflow, + ); - this.router.post( - `/update/:workflowId`, - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - this.updateWorkflow - ); + this.router.post( + `/update/:workflowId`, + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + this.updateWorkflow, + ); + } + + public async updateWorkflow( + req: ExpressRequest, + res: ExpressResponse, + ): Promise { + // add this workflow to the run queue and return the 200 response. + + if (!req.params["workflowId"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("workflowId not found in URL"), + ); } - public async updateWorkflow( - req: ExpressRequest, - res: ExpressResponse - ): Promise { - // add this workflow to the run queue and return the 200 response. + const workflow: Workflow | null = await WorkflowService.findOneById({ + id: new ObjectID(req.params["workflowId"]), + select: { + _id: true, + triggerId: true, + }, + props: { + isRoot: true, + }, + }); - if (!req.params['workflowId']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('workflowId not found in URL') - ); - } - - const workflow: Workflow | null = await WorkflowService.findOneById({ - id: new ObjectID(req.params['workflowId']), - select: { - _id: true, - triggerId: true, - }, - props: { - isRoot: true, - }, - }); - - if (!workflow) { - return Response.sendJsonObjectResponse(req, res, { - status: 'Workflow not found', - }); - } - - if (!workflow.triggerId) { - return Response.sendJsonObjectResponse(req, res, { - status: 'Trigger not found in workflow', - }); - } - - const componentCode: ComponentCode | undefined = - Components[workflow.triggerId]; - - if (!componentCode) { - return Response.sendJsonObjectResponse(req, res, { - status: 'Component not found', - }); - } - - if (componentCode instanceof TriggerCode) { - await componentCode.update({ - workflowId: workflow.id!, - }); - } - - return Response.sendJsonObjectResponse(req, res, { - status: 'Updated', - }); + if (!workflow) { + return Response.sendJsonObjectResponse(req, res, { + status: "Workflow not found", + }); } + + if (!workflow.triggerId) { + return Response.sendJsonObjectResponse(req, res, { + status: "Trigger not found in workflow", + }); + } + + const componentCode: ComponentCode | undefined = + Components[workflow.triggerId]; + + if (!componentCode) { + return Response.sendJsonObjectResponse(req, res, { + status: "Component not found", + }); + } + + if (componentCode instanceof TriggerCode) { + await componentCode.update({ + workflowId: workflow.id!, + }); + } + + return Response.sendJsonObjectResponse(req, res, { + status: "Updated", + }); + } } diff --git a/App/FeatureSet/Workflow/Index.ts b/App/FeatureSet/Workflow/Index.ts index 32da362fed..2321bdf712 100644 --- a/App/FeatureSet/Workflow/Index.ts +++ b/App/FeatureSet/Workflow/Index.ts @@ -1,68 +1,66 @@ -import ComponentCodeAPI from './API/ComponentCode'; -import ManualAPI from './API/Manual'; -import WorkflowAPI from './API/Workflow'; -import RunWorkflow from './Services/RunWorkflow'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { QueueJob, QueueName } from 'CommonServer/Infrastructure/Queue'; -import QueueWorker from 'CommonServer/Infrastructure/QueueWorker'; -import FeatureSet from 'CommonServer/Types/FeatureSet'; +import ComponentCodeAPI from "./API/ComponentCode"; +import ManualAPI from "./API/Manual"; +import WorkflowAPI from "./API/Workflow"; +import RunWorkflow from "./Services/RunWorkflow"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { QueueJob, QueueName } from "CommonServer/Infrastructure/Queue"; +import QueueWorker from "CommonServer/Infrastructure/QueueWorker"; +import FeatureSet from "CommonServer/Types/FeatureSet"; import Express, { - ExpressApplication, - ExpressRequest, - ExpressResponse, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; + ExpressApplication, + ExpressRequest, + ExpressResponse, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; -const APP_NAME: string = 'api/workflow'; +const APP_NAME: string = "api/workflow"; const WorkflowFeatureSet: FeatureSet = { - init: async (): Promise => { - try { - const app: ExpressApplication = Express.getExpressApp(); + init: async (): Promise => { + try { + const app: ExpressApplication = Express.getExpressApp(); - app.use(`/${APP_NAME}/manual`, new ManualAPI().router); + app.use(`/${APP_NAME}/manual`, new ManualAPI().router); - app.use(`/${APP_NAME}`, new WorkflowAPI().router); + app.use(`/${APP_NAME}`, new WorkflowAPI().router); - app.get( - `/${APP_NAME}/docs/:componentName`, - (req: ExpressRequest, res: ExpressResponse) => { - res.sendFile( - '/usr/src/app/FeatureSet/Workflow/Docs/ComponentDocumentation/' + - req.params['componentName'] - ); - } - ); + app.get( + `/${APP_NAME}/docs/:componentName`, + (req: ExpressRequest, res: ExpressResponse) => { + res.sendFile( + "/usr/src/app/FeatureSet/Workflow/Docs/ComponentDocumentation/" + + req.params["componentName"], + ); + }, + ); - const componentCodeAPI: ComponentCodeAPI = new ComponentCodeAPI(); - componentCodeAPI.init(); + const componentCodeAPI: ComponentCodeAPI = new ComponentCodeAPI(); + componentCodeAPI.init(); - app.use(`/${APP_NAME}`, componentCodeAPI.router); + app.use(`/${APP_NAME}`, componentCodeAPI.router); - // Job process. - QueueWorker.getWorker( - QueueName.Workflow, - async (job: QueueJob) => { - await new RunWorkflow().runWorkflow({ - workflowId: new ObjectID( - job.data['workflowId'] as string - ), - workflowLogId: job.data['workflowLogId'] - ? new ObjectID(job.data['workflowLogId'] as string) - : null, - arguments: job.data.data as JSONObject, - timeout: 5000, - }); - }, - { concurrency: 10 } - ); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } - }, + // Job process. + QueueWorker.getWorker( + QueueName.Workflow, + async (job: QueueJob) => { + await new RunWorkflow().runWorkflow({ + workflowId: new ObjectID(job.data["workflowId"] as string), + workflowLogId: job.data["workflowLogId"] + ? new ObjectID(job.data["workflowLogId"] as string) + : null, + arguments: job.data.data as JSONObject, + timeout: 5000, + }); + }, + { concurrency: 10 }, + ); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } + }, }; export default WorkflowFeatureSet; diff --git a/App/FeatureSet/Workflow/Services/QueueWorkflow.ts b/App/FeatureSet/Workflow/Services/QueueWorkflow.ts index cde34f7c5a..4257f5f82a 100644 --- a/App/FeatureSet/Workflow/Services/QueueWorkflow.ts +++ b/App/FeatureSet/Workflow/Services/QueueWorkflow.ts @@ -1,210 +1,209 @@ -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import WorkflowPlan from 'Common/Types/Workflow/WorkflowPlan'; -import WorkflowStatus from 'Common/Types/Workflow/WorkflowStatus'; -import Queue, { QueueName } from 'CommonServer/Infrastructure/Queue'; -import ProjectService from 'CommonServer/Services/ProjectService'; -import WorkflowLogService from 'CommonServer/Services/WorkflowLogService'; -import WorkflowService from 'CommonServer/Services/WorkflowService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import { ExecuteWorkflowType } from 'CommonServer/Types/Workflow/TriggerCode'; -import Workflow from 'Model/Models/Workflow'; -import WorkflowLog from 'Model/Models/WorkflowLog'; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import WorkflowPlan from "Common/Types/Workflow/WorkflowPlan"; +import WorkflowStatus from "Common/Types/Workflow/WorkflowStatus"; +import Queue, { QueueName } from "CommonServer/Infrastructure/Queue"; +import ProjectService from "CommonServer/Services/ProjectService"; +import WorkflowLogService from "CommonServer/Services/WorkflowLogService"; +import WorkflowService from "CommonServer/Services/WorkflowService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import { ExecuteWorkflowType } from "CommonServer/Types/Workflow/TriggerCode"; +import Workflow from "Model/Models/Workflow"; +import WorkflowLog from "Model/Models/WorkflowLog"; export default class QueueWorkflow { - public static async removeWorkflow(workflowId: ObjectID): Promise { - // get workflow to see if its enabled. - const workflow: Workflow | null = await WorkflowService.findOneById({ - id: workflowId, - select: { - projectId: true, - repeatableJobKey: true, - }, - props: { - isRoot: true, - }, - }); + public static async removeWorkflow(workflowId: ObjectID): Promise { + // get workflow to see if its enabled. + const workflow: Workflow | null = await WorkflowService.findOneById({ + id: workflowId, + select: { + projectId: true, + repeatableJobKey: true, + }, + props: { + isRoot: true, + }, + }); - if (!workflow) { - throw new BadDataException('Workflow not found'); - } - - if (!workflow.projectId) { - throw new BadDataException( - 'This workflow does not belong to a project and cannot be run' - ); - } - - await Queue.removeJob(QueueName.Workflow, workflow.repeatableJobKey!); - - // update workflow. - await WorkflowService.updateOneById({ - id: workflow.id!, - data: { - repeatableJobKey: null!, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + if (!workflow) { + throw new BadDataException("Workflow not found"); } - public static async addWorkflowToQueue( - executeWorkflow: ExecuteWorkflowType, - scheduleAt?: string - ): Promise { - const workflowId: ObjectID = executeWorkflow.workflowId; + if (!workflow.projectId) { + throw new BadDataException( + "This workflow does not belong to a project and cannot be run", + ); + } - // get workflow to see if its enabled. - const workflow: Workflow | null = await WorkflowService.findOneById({ - id: workflowId, - select: { - isEnabled: true, - projectId: true, - repeatableJobKey: true, - }, - props: { - isRoot: true, - }, - }); + await Queue.removeJob(QueueName.Workflow, workflow.repeatableJobKey!); - if (!workflow) { - throw new BadDataException('Workflow not found'); - } + // update workflow. + await WorkflowService.updateOneById({ + id: workflow.id!, + data: { + repeatableJobKey: null!, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + } - if (!workflow.isEnabled) { - throw new BadDataException('This workflow is not enabled'); - } + public static async addWorkflowToQueue( + executeWorkflow: ExecuteWorkflowType, + scheduleAt?: string, + ): Promise { + const workflowId: ObjectID = executeWorkflow.workflowId; - if (!workflow.projectId) { - throw new BadDataException( - 'This workflow does not belong to a project and cannot be run' - ); - } + // get workflow to see if its enabled. + const workflow: Workflow | null = await WorkflowService.findOneById({ + id: workflowId, + select: { + isEnabled: true, + projectId: true, + repeatableJobKey: true, + }, + props: { + isRoot: true, + }, + }); - //check project and plan - const projectPlan: { - plan: PlanSelect | null; - isSubscriptionUnpaid: boolean; - } = await ProjectService.getCurrentPlan(workflow.projectId); + if (!workflow) { + throw new BadDataException("Workflow not found"); + } - if (projectPlan.isSubscriptionUnpaid) { - // Add Workflow Run Log. + if (!workflow.isEnabled) { + throw new BadDataException("This workflow is not enabled"); + } - const runLog: WorkflowLog = new WorkflowLog(); - runLog.workflowId = workflowId; - runLog.projectId = workflow.projectId; - runLog.workflowStatus = WorkflowStatus.WorkflowCountExceeded; - runLog.logs = - OneUptimeDate.getCurrentDateAsFormattedString() + - ': Workflow cannot run because subscription is unpaid.'; + if (!workflow.projectId) { + throw new BadDataException( + "This workflow does not belong to a project and cannot be run", + ); + } - await WorkflowLogService.create({ - data: runLog, - props: { - isRoot: true, - }, - }); + //check project and plan + const projectPlan: { + plan: PlanSelect | null; + isSubscriptionUnpaid: boolean; + } = await ProjectService.getCurrentPlan(workflow.projectId); - return; - } + if (projectPlan.isSubscriptionUnpaid) { + // Add Workflow Run Log. - if (projectPlan.plan) { - const startDate: Date = OneUptimeDate.getSomeDaysAgo(30); - const endDate: Date = OneUptimeDate.getCurrentDate(); + const runLog: WorkflowLog = new WorkflowLog(); + runLog.workflowId = workflowId; + runLog.projectId = workflow.projectId; + runLog.workflowStatus = WorkflowStatus.WorkflowCountExceeded; + runLog.logs = + OneUptimeDate.getCurrentDateAsFormattedString() + + ": Workflow cannot run because subscription is unpaid."; - const workflowCount: PositiveNumber = - await WorkflowLogService.countBy({ - query: { - projectId: workflow.projectId, - createdAt: QueryHelper.inBetween(startDate, endDate), - }, - props: { - isRoot: true, - }, - }); + await WorkflowLogService.create({ + data: runLog, + props: { + isRoot: true, + }, + }); - if (workflowCount.toNumber() > WorkflowPlan[projectPlan.plan]) { - // Add Workflow Run Log. + return; + } - const runLog: WorkflowLog = new WorkflowLog(); - runLog.workflowId = workflowId; - runLog.projectId = workflow.projectId; - runLog.workflowStatus = WorkflowStatus.WorkflowCountExceeded; - runLog.logs = - OneUptimeDate.getCurrentDateAsFormattedString() + - `: Workflow cannot run because it already ran ${workflowCount.toNumber()} in the last 30 days. Your current plan limit is ${ - WorkflowPlan[projectPlan.plan] - }`; + if (projectPlan.plan) { + const startDate: Date = OneUptimeDate.getSomeDaysAgo(30); + const endDate: Date = OneUptimeDate.getCurrentDate(); - await WorkflowLogService.create({ - data: runLog, - props: { - isRoot: true, - }, - }); - - return; - } - } + const workflowCount: PositiveNumber = await WorkflowLogService.countBy({ + query: { + projectId: workflow.projectId, + createdAt: QueryHelper.inBetween(startDate, endDate), + }, + props: { + isRoot: true, + }, + }); + if (workflowCount.toNumber() > WorkflowPlan[projectPlan.plan]) { // Add Workflow Run Log. - let workflowLog: WorkflowLog | null = null; - if (!scheduleAt) { - // if the workflow is to be run immediately. - const runLog: WorkflowLog = new WorkflowLog(); - runLog.workflowId = workflowId; - runLog.projectId = workflow.projectId; - runLog.workflowStatus = WorkflowStatus.Scheduled; - runLog.logs = - OneUptimeDate.getCurrentDateAsFormattedString() + - `: Workflow ${workflowId.toString()} Scheduled.`; - workflowLog = await WorkflowLogService.create({ - data: runLog, - props: { - isRoot: true, - }, - }); - } + const runLog: WorkflowLog = new WorkflowLog(); + runLog.workflowId = workflowId; + runLog.projectId = workflow.projectId; + runLog.workflowStatus = WorkflowStatus.WorkflowCountExceeded; + runLog.logs = + OneUptimeDate.getCurrentDateAsFormattedString() + + `: Workflow cannot run because it already ran ${workflowCount.toNumber()} in the last 30 days. Your current plan limit is ${ + WorkflowPlan[projectPlan.plan] + }`; - const job: any = await Queue.addJob( - QueueName.Workflow, - workflowLog - ? (workflowLog._id?.toString() as string) - : (workflow._id?.toString() as string), - workflowLog - ? (workflowLog._id?.toString() as string) - : (workflow._id?.toString() as string), - { - data: executeWorkflow.returnValues, - workflowLogId: workflowLog?._id || null, - workflowId: workflow._id, - }, - { - scheduleAt: scheduleAt, - repeatableKey: workflow.repeatableJobKey || undefined, - } - ); + await WorkflowLogService.create({ + data: runLog, + props: { + isRoot: true, + }, + }); - // update workflow with repeatable key. - - if (job.repeatJobKey) { - // update workflow. - await WorkflowService.updateOneById({ - id: workflow.id!, - data: { - repeatableJobKey: job.repeatJobKey, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - } + return; + } } + + // Add Workflow Run Log. + let workflowLog: WorkflowLog | null = null; + if (!scheduleAt) { + // if the workflow is to be run immediately. + const runLog: WorkflowLog = new WorkflowLog(); + runLog.workflowId = workflowId; + runLog.projectId = workflow.projectId; + runLog.workflowStatus = WorkflowStatus.Scheduled; + runLog.logs = + OneUptimeDate.getCurrentDateAsFormattedString() + + `: Workflow ${workflowId.toString()} Scheduled.`; + + workflowLog = await WorkflowLogService.create({ + data: runLog, + props: { + isRoot: true, + }, + }); + } + + const job: any = await Queue.addJob( + QueueName.Workflow, + workflowLog + ? (workflowLog._id?.toString() as string) + : (workflow._id?.toString() as string), + workflowLog + ? (workflowLog._id?.toString() as string) + : (workflow._id?.toString() as string), + { + data: executeWorkflow.returnValues, + workflowLogId: workflowLog?._id || null, + workflowId: workflow._id, + }, + { + scheduleAt: scheduleAt, + repeatableKey: workflow.repeatableJobKey || undefined, + }, + ); + + // update workflow with repeatable key. + + if (job.repeatJobKey) { + // update workflow. + await WorkflowService.updateOneById({ + id: workflow.id!, + data: { + repeatableJobKey: job.repeatJobKey, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + } + } } diff --git a/App/FeatureSet/Workflow/Services/RunWorkflow.ts b/App/FeatureSet/Workflow/Services/RunWorkflow.ts index f62790dc30..f75fcc7ede 100644 --- a/App/FeatureSet/Workflow/Services/RunWorkflow.ts +++ b/App/FeatureSet/Workflow/Services/RunWorkflow.ts @@ -1,601 +1,574 @@ -import { loadAllComponentMetadata } from '../Utils/ComponentMetadata'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Exception from 'Common/Types/Exception/Exception'; -import TimeoutException from 'Common/Types/Exception/TimeoutException'; -import { JSONArray, JSONObject, JSONValue } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; +import { loadAllComponentMetadata } from "../Utils/ComponentMetadata"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Exception from "Common/Types/Exception/Exception"; +import TimeoutException from "Common/Types/Exception/TimeoutException"; +import { JSONArray, JSONObject, JSONValue } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, - NodeDataProp, - NodeType, - Port, -} from 'Common/Types/Workflow/Component'; -import WorkflowStatus from 'Common/Types/Workflow/WorkflowStatus'; -import WorkflowLogService from 'CommonServer/Services/WorkflowLogService'; -import WorkflowService from 'CommonServer/Services/WorkflowService'; -import WorkflowVariableService from 'CommonServer/Services/WorkflowVariableService'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; + ComponentInputType, + ComponentType, + NodeDataProp, + NodeType, + Port, +} from "Common/Types/Workflow/Component"; +import WorkflowStatus from "Common/Types/Workflow/WorkflowStatus"; +import WorkflowLogService from "CommonServer/Services/WorkflowLogService"; +import WorkflowService from "CommonServer/Services/WorkflowService"; +import WorkflowVariableService from "CommonServer/Services/WorkflowVariableService"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; import ComponentCode, { - RunReturnType, -} from 'CommonServer/Types/Workflow/ComponentCode'; -import Components from 'CommonServer/Types/Workflow/Components/Index'; -import { RunProps } from 'CommonServer/Types/Workflow/Workflow'; -import logger from 'CommonServer/Utils/Logger'; -import VMAPI from 'CommonServer/Utils/VM/VMAPI'; -import Workflow from 'Model/Models/Workflow'; -import WorkflowLog from 'Model/Models/WorkflowLog'; -import WorkflowVariable from 'Model/Models/WorkflowVariable'; + RunReturnType, +} from "CommonServer/Types/Workflow/ComponentCode"; +import Components from "CommonServer/Types/Workflow/Components/Index"; +import { RunProps } from "CommonServer/Types/Workflow/Workflow"; +import logger from "CommonServer/Utils/Logger"; +import VMAPI from "CommonServer/Utils/VM/VMAPI"; +import Workflow from "Model/Models/Workflow"; +import WorkflowLog from "Model/Models/WorkflowLog"; +import WorkflowVariable from "Model/Models/WorkflowVariable"; const AllComponents: Dictionary = loadAllComponentMetadata(); export interface StorageMap { - local: { - variables: Dictionary; - components: { - [x: string]: { - returnValues: JSONObject; - }; - }; - }; - global: { - variables: Dictionary; + local: { + variables: Dictionary; + components: { + [x: string]: { + returnValues: JSONObject; + }; }; + }; + global: { + variables: Dictionary; + }; } export interface RunStackItem { - node: NodeDataProp; - outPorts: Dictionary>; // portId <-> [ComponentIds] + node: NodeDataProp; + outPorts: Dictionary>; // portId <-> [ComponentIds] } export interface RunStack { - stack: Dictionary; - startWithComponentId: string; + stack: Dictionary; + startWithComponentId: string; } export default class RunWorkflow { - private logs: Array = []; - private workflowId: ObjectID | null = null; - private projectId: ObjectID | null = null; - private workflowLogId: ObjectID | null = null; + private logs: Array = []; + private workflowId: ObjectID | null = null; + private projectId: ObjectID | null = null; + private workflowLogId: ObjectID | null = null; - public async runWorkflow(runProps: RunProps): Promise { - // get nodes and edges. + public async runWorkflow(runProps: RunProps): Promise { + // get nodes and edges. - let variables: Array = []; + let variables: Array = []; - try { - this.workflowId = runProps.workflowId; - this.workflowLogId = runProps.workflowLogId; + try { + this.workflowId = runProps.workflowId; + this.workflowLogId = runProps.workflowLogId; - let didWorkflowTimeOut: boolean = false; - let didWorkflowErrorOut: boolean = false; + let didWorkflowTimeOut: boolean = false; + let didWorkflowErrorOut: boolean = false; - setTimeout(() => { - didWorkflowTimeOut = true; - }, runProps.timeout); + setTimeout(() => { + didWorkflowTimeOut = true; + }, runProps.timeout); - const workflow: Workflow | null = await WorkflowService.findOneById( - { - id: runProps.workflowId, - select: { - graph: true, - projectId: true, - }, - props: { - isRoot: true, - }, - } - ); + const workflow: Workflow | null = await WorkflowService.findOneById({ + id: runProps.workflowId, + select: { + graph: true, + projectId: true, + }, + props: { + isRoot: true, + }, + }); - if (!workflow) { - throw new BadDataException('Workflow not found'); - } + if (!workflow) { + throw new BadDataException("Workflow not found"); + } - if (!workflow.graph) { - throw new BadDataException('Workflow graph not found'); - } + if (!workflow.graph) { + throw new BadDataException("Workflow graph not found"); + } - this.projectId = workflow.projectId || null; + this.projectId = workflow.projectId || null; - if (!runProps.workflowLogId) { - // create a new workflow log here. - // if the workflow is to be run immediately. - const runLog: WorkflowLog = new WorkflowLog(); - runLog.workflowId = runProps.workflowId; - runLog.projectId = workflow.projectId!; - runLog.workflowStatus = WorkflowStatus.Scheduled; - runLog.logs = - OneUptimeDate.getCurrentDateAsFormattedString() + - `: Workflow ${runProps.workflowId.toString()} Scheduled.`; + if (!runProps.workflowLogId) { + // create a new workflow log here. + // if the workflow is to be run immediately. + const runLog: WorkflowLog = new WorkflowLog(); + runLog.workflowId = runProps.workflowId; + runLog.projectId = workflow.projectId!; + runLog.workflowStatus = WorkflowStatus.Scheduled; + runLog.logs = + OneUptimeDate.getCurrentDateAsFormattedString() + + `: Workflow ${runProps.workflowId.toString()} Scheduled.`; - runProps.workflowLogId = ( - await WorkflowLogService.create({ - data: runLog, - props: { - isRoot: true, - }, - }) - ).id!; - } + runProps.workflowLogId = ( + await WorkflowLogService.create({ + data: runLog, + props: { + isRoot: true, + }, + }) + ).id!; + } - // update workflow log. - await WorkflowLogService.updateOneById({ - id: runProps.workflowLogId, - data: { - workflowStatus: WorkflowStatus.Running, - startedAt: OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); + // update workflow log. + await WorkflowLogService.updateOneById({ + id: runProps.workflowLogId, + data: { + workflowStatus: WorkflowStatus.Running, + startedAt: OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); - // form a run stack. + // form a run stack. - const runStack: RunStack = await this.makeRunStack(workflow.graph); + const runStack: RunStack = await this.makeRunStack(workflow.graph); - const getVariableResult: { - storageMap: StorageMap; - variables: Array; - } = await this.getVariables(workflow.projectId!, workflow.id!); + const getVariableResult: { + storageMap: StorageMap; + variables: Array; + } = await this.getVariables(workflow.projectId!, workflow.id!); - // get storage map with variables. - const storageMap: StorageMap = getVariableResult.storageMap; - variables = getVariableResult.variables; + // get storage map with variables. + const storageMap: StorageMap = getVariableResult.storageMap; + variables = getVariableResult.variables; - // start execute different components. - let executeComponentId: string = runStack.startWithComponentId; + // start execute different components. + let executeComponentId: string = runStack.startWithComponentId; - const fifoStackOfComponentsPendingExecution: Array = [ - executeComponentId, - ]; - const componentsExecuted: Array = []; + const fifoStackOfComponentsPendingExecution: Array = [ + executeComponentId, + ]; + const componentsExecuted: Array = []; - const setDidErrorOut: VoidFunction = () => { - didWorkflowErrorOut = true; - }; - // make variable map + const setDidErrorOut: VoidFunction = () => { + didWorkflowErrorOut = true; + }; + // make variable map - while (fifoStackOfComponentsPendingExecution.length > 0) { - if (didWorkflowTimeOut) { - throw new TimeoutException( - 'Workflow execution time was more than ' + - runProps.timeout + - 'ms and workflow timed-out.' - ); - } - - // get component. - // and remove that component from the stack. - executeComponentId = - fifoStackOfComponentsPendingExecution.shift()!; - - if (componentsExecuted.includes(executeComponentId)) { - throw new BadDataException( - 'Cyclic Workflow Detected. Cannot execute ' + - executeComponentId + - ' when it has already been executed.' - ); - } - - componentsExecuted.push(executeComponentId); - - this.log('Executing Component: ' + executeComponentId); - - const stackItem: RunStackItem | undefined = - runStack.stack[executeComponentId]; - - if (!stackItem) { - throw new BadDataException( - 'Component with ID ' + - executeComponentId + - ' not found.' - ); - } - - // now actually run this component. - - let args: JSONObject = this.getComponentArguments( - storageMap, - stackItem.node - ); - - if (stackItem.node.componentType === ComponentType.Trigger) { - // If this is the trigger. Then pass workflow argument to this component as args to execute. - args = { - ...args, - ...runProps.arguments, - }; - } - - this.log('Component Args:'); - this.log(args); - this.log('Component Logs: ' + executeComponentId); - - const result: RunReturnType = await this.runComponent( - args, - stackItem.node, - setDidErrorOut - ); - - if (didWorkflowErrorOut) { - throw new BadDataException( - 'Workflow stopped because of an error' - ); - } - - this.log( - 'Completed Execution Component: ' + executeComponentId - ); - this.log('Data Returned'); - this.log(result.returnValues); - this.log( - 'Executing Port: ' + result.executePort?.title || '' - ); - - storageMap.local.components[stackItem.node.id] = { - returnValues: result.returnValues, - }; - - const portToBeExecuted: Port | undefined = result.executePort; - - if (!portToBeExecuted) { - break; // stop the workflow, the process has ended. - } - - const nodesToBeExecuted: Array | undefined = - stackItem.outPorts[portToBeExecuted.id]; - - if (nodesToBeExecuted && nodesToBeExecuted.length > 0) { - nodesToBeExecuted.forEach((item: string) => { - // if its not in the stack, then add it to execution stack. - if ( - !fifoStackOfComponentsPendingExecution.includes( - item - ) - ) { - fifoStackOfComponentsPendingExecution.push(item); - } - }); - } - } - - // collect logs and update status. - this.cleanLogs(variables); - // update workflow log. - await WorkflowLogService.updateOneById({ - id: runProps.workflowLogId, - data: { - workflowStatus: WorkflowStatus.Success, - logs: this.logs.join('\n'), - completedAt: OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); - } catch (err: any) { - logger.error(err); - this.log(err.message || err.toString()); - - if (!runProps.workflowLogId) { - return; - } - - this.cleanLogs(variables); - - if (err instanceof TimeoutException) { - this.log('Workflow Timed out.'); - - // update workflow log. - await WorkflowLogService.updateOneById({ - id: runProps.workflowLogId, - data: { - workflowStatus: WorkflowStatus.Timeout, - logs: this.logs.join('\n'), - completedAt: OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); - } else { - // update workflow log. - await WorkflowLogService.updateOneById({ - id: runProps.workflowLogId, - data: { - workflowStatus: WorkflowStatus.Error, - logs: this.logs.join('\n'), - completedAt: OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); - } - } - } - - public cleanLogs(variables: Array): void { - for (let i: number = 0; i < this.logs.length; i++) { - if (!this.logs[i]) { - continue; - } - - for (const variable of variables) { - if (variable.isSecret) { - if (this.logs[i]!.includes(variable.content!)) { - this.logs[i] = this.logs[i]!.replace( - variable.content!, - 'xxxxxxxxxxxxxxx' - ); - } - } - } - } - } - - public getComponentArguments( - storageMap: StorageMap, - component: NodeDataProp - ): JSONObject { - // pick arguments from storage map. - const argumentObj: JSONObject = {}; - - for (const argument of component.metadata.arguments) { - if (!component.arguments) { - component.arguments = {}; - } - - if (!component.arguments[argument.id]) { - continue; - } - - let argumentContent: JSONValue | undefined = - component.arguments[argument.id]; - - if (!argumentContent) { - continue; - } - - argumentContent = VMAPI.replaceValueInPlace( - storageMap as any, - argumentContent as string, - argument.type === ComponentInputType.JSON - ); - - if ( - typeof argumentContent === 'string' && - (argument.type === ComponentInputType.JSON || - argument.type === ComponentInputType.Query || - argument.type === ComponentInputType.Select) - ) { - try { - argumentContent = JSON.parse(argumentContent); - } catch (err: any) { - throw new BadDataException( - 'Invalid JSON provided for argument ' + - argument.id + - '. JSON parse error: ' + - err.message + - '. JSON: ' + - argumentContent - ); - } - } - - argumentObj[argument.id] = argumentContent; + while (fifoStackOfComponentsPendingExecution.length > 0) { + if (didWorkflowTimeOut) { + throw new TimeoutException( + "Workflow execution time was more than " + + runProps.timeout + + "ms and workflow timed-out.", + ); } - return argumentObj; - } + // get component. + // and remove that component from the stack. + executeComponentId = fifoStackOfComponentsPendingExecution.shift()!; - public async runComponent( - args: JSONObject, - node: NodeDataProp, - onError: VoidFunction - ): Promise { - // takes in args and returns values. - const ComponentCode: ComponentCode | undefined = - Components[node.metadata.id]; - - if (ComponentCode) { - const instance: ComponentCode = ComponentCode; - return await instance.run(args, { - log: ( - data: string | JSONObject | JSONArray | Error | JSONValue - ) => { - this.log(data); - }, - workflowId: this.workflowId!, - workflowLogId: this.workflowLogId!, - projectId: this.projectId!, - onError: (exception: Exception) => { - this.log(exception); - onError(); - return exception; - }, - }); + if (componentsExecuted.includes(executeComponentId)) { + throw new BadDataException( + "Cyclic Workflow Detected. Cannot execute " + + executeComponentId + + " when it has already been executed.", + ); } - throw new BadDataException( - 'Component ' + node.metadata.id + ' not found' + componentsExecuted.push(executeComponentId); + + this.log("Executing Component: " + executeComponentId); + + const stackItem: RunStackItem | undefined = + runStack.stack[executeComponentId]; + + if (!stackItem) { + throw new BadDataException( + "Component with ID " + executeComponentId + " not found.", + ); + } + + // now actually run this component. + + let args: JSONObject = this.getComponentArguments( + storageMap, + stackItem.node, ); - } - public async getVariables( - projectId: ObjectID, - workflowId: ObjectID - ): Promise<{ storageMap: StorageMap; variables: Array }> { - /// get local and global variables. - const localVariables: Array = - await WorkflowVariableService.findBy({ - query: { - workflowId: workflowId, - }, - select: { - name: true, - content: true, - isSecret: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); + if (stackItem.node.componentType === ComponentType.Trigger) { + // If this is the trigger. Then pass workflow argument to this component as args to execute. + args = { + ...args, + ...runProps.arguments, + }; + } - const globalVariables: Array = - await WorkflowVariableService.findBy({ - query: { - workflowId: QueryHelper.isNull(), - projectId: projectId, - }, - select: { - name: true, - content: true, - isSecret: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); + this.log("Component Args:"); + this.log(args); + this.log("Component Logs: " + executeComponentId); - const newStorageMap: StorageMap = { - local: { - variables: {}, - components: {}, - }, - global: { - variables: {}, - }, + const result: RunReturnType = await this.runComponent( + args, + stackItem.node, + setDidErrorOut, + ); + + if (didWorkflowErrorOut) { + throw new BadDataException("Workflow stopped because of an error"); + } + + this.log("Completed Execution Component: " + executeComponentId); + this.log("Data Returned"); + this.log(result.returnValues); + this.log("Executing Port: " + result.executePort?.title || ""); + + storageMap.local.components[stackItem.node.id] = { + returnValues: result.returnValues, }; - for (const variable of localVariables) { - newStorageMap.local.variables[variable.name as string] = - variable.content as string; + const portToBeExecuted: Port | undefined = result.executePort; + + if (!portToBeExecuted) { + break; // stop the workflow, the process has ended. } - for (const variable of globalVariables) { - newStorageMap.global.variables[variable.name as string] = - variable.content as string; - } + const nodesToBeExecuted: Array | undefined = + stackItem.outPorts[portToBeExecuted.id]; - return { - storageMap: newStorageMap, - variables: [...localVariables, ...globalVariables], - }; + if (nodesToBeExecuted && nodesToBeExecuted.length > 0) { + nodesToBeExecuted.forEach((item: string) => { + // if its not in the stack, then add it to execution stack. + if (!fifoStackOfComponentsPendingExecution.includes(item)) { + fifoStackOfComponentsPendingExecution.push(item); + } + }); + } + } + + // collect logs and update status. + this.cleanLogs(variables); + // update workflow log. + await WorkflowLogService.updateOneById({ + id: runProps.workflowLogId, + data: { + workflowStatus: WorkflowStatus.Success, + logs: this.logs.join("\n"), + completedAt: OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); + } catch (err: any) { + logger.error(err); + this.log(err.message || err.toString()); + + if (!runProps.workflowLogId) { + return; + } + + this.cleanLogs(variables); + + if (err instanceof TimeoutException) { + this.log("Workflow Timed out."); + + // update workflow log. + await WorkflowLogService.updateOneById({ + id: runProps.workflowLogId, + data: { + workflowStatus: WorkflowStatus.Timeout, + logs: this.logs.join("\n"), + completedAt: OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); + } else { + // update workflow log. + await WorkflowLogService.updateOneById({ + id: runProps.workflowLogId, + data: { + workflowStatus: WorkflowStatus.Error, + logs: this.logs.join("\n"), + completedAt: OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); + } + } + } + + public cleanLogs(variables: Array): void { + for (let i: number = 0; i < this.logs.length; i++) { + if (!this.logs[i]) { + continue; + } + + for (const variable of variables) { + if (variable.isSecret) { + if (this.logs[i]!.includes(variable.content!)) { + this.logs[i] = this.logs[i]!.replace( + variable.content!, + "xxxxxxxxxxxxxxx", + ); + } + } + } + } + } + + public getComponentArguments( + storageMap: StorageMap, + component: NodeDataProp, + ): JSONObject { + // pick arguments from storage map. + const argumentObj: JSONObject = {}; + + for (const argument of component.metadata.arguments) { + if (!component.arguments) { + component.arguments = {}; + } + + if (!component.arguments[argument.id]) { + continue; + } + + let argumentContent: JSONValue | undefined = + component.arguments[argument.id]; + + if (!argumentContent) { + continue; + } + + argumentContent = VMAPI.replaceValueInPlace( + storageMap as any, + argumentContent as string, + argument.type === ComponentInputType.JSON, + ); + + if ( + typeof argumentContent === "string" && + (argument.type === ComponentInputType.JSON || + argument.type === ComponentInputType.Query || + argument.type === ComponentInputType.Select) + ) { + try { + argumentContent = JSON.parse(argumentContent); + } catch (err: any) { + throw new BadDataException( + "Invalid JSON provided for argument " + + argument.id + + ". JSON parse error: " + + err.message + + ". JSON: " + + argumentContent, + ); + } + } + + argumentObj[argument.id] = argumentContent; } - public log( - data: string | JSONObject | JSONArray | Error | JSONValue - ): void { - if (!this.logs) { - this.logs = []; - } + return argumentObj; + } - if (data instanceof Exception) { - data = data.getMessage(); - } + public async runComponent( + args: JSONObject, + node: NodeDataProp, + onError: VoidFunction, + ): Promise { + // takes in args and returns values. + const ComponentCode: ComponentCode | undefined = + Components[node.metadata.id]; - if (typeof data === 'string') { - this.logs.push( - OneUptimeDate.getCurrentDateAsFormattedString() + ': ' + data - ); - } else { - this.logs.push( - OneUptimeDate.getCurrentDateAsFormattedString() + - ': ' + - JSON.stringify(data) - ); - } + if (ComponentCode) { + const instance: ComponentCode = ComponentCode; + return await instance.run(args, { + log: (data: string | JSONObject | JSONArray | Error | JSONValue) => { + this.log(data); + }, + workflowId: this.workflowId!, + workflowLogId: this.workflowLogId!, + projectId: this.projectId!, + onError: (exception: Exception) => { + this.log(exception); + onError(); + return exception; + }, + }); } - public async makeRunStack(graph: JSONObject): Promise { - const nodes: Array = graph['nodes'] as Array; + throw new BadDataException("Component " + node.metadata.id + " not found"); + } - const edges: Array = graph['edges'] as Array; + public async getVariables( + projectId: ObjectID, + workflowId: ObjectID, + ): Promise<{ storageMap: StorageMap; variables: Array }> { + /// get local and global variables. + const localVariables: Array = + await WorkflowVariableService.findBy({ + query: { + workflowId: workflowId, + }, + select: { + name: true, + content: true, + isSecret: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); - if (nodes.length === 0) { - return { - startWithComponentId: '', - stack: {}, - }; + const globalVariables: Array = + await WorkflowVariableService.findBy({ + query: { + workflowId: QueryHelper.isNull(), + projectId: projectId, + }, + select: { + name: true, + content: true, + isSecret: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const newStorageMap: StorageMap = { + local: { + variables: {}, + components: {}, + }, + global: { + variables: {}, + }, + }; + + for (const variable of localVariables) { + newStorageMap.local.variables[variable.name as string] = + variable.content as string; + } + + for (const variable of globalVariables) { + newStorageMap.global.variables[variable.name as string] = + variable.content as string; + } + + return { + storageMap: newStorageMap, + variables: [...localVariables, ...globalVariables], + }; + } + + public log(data: string | JSONObject | JSONArray | Error | JSONValue): void { + if (!this.logs) { + this.logs = []; + } + + if (data instanceof Exception) { + data = data.getMessage(); + } + + if (typeof data === "string") { + this.logs.push( + OneUptimeDate.getCurrentDateAsFormattedString() + ": " + data, + ); + } else { + this.logs.push( + OneUptimeDate.getCurrentDateAsFormattedString() + + ": " + + JSON.stringify(data), + ); + } + } + + public async makeRunStack(graph: JSONObject): Promise { + const nodes: Array = graph["nodes"] as Array; + + const edges: Array = graph["edges"] as Array; + + if (nodes.length === 0) { + return { + startWithComponentId: "", + stack: {}, + }; + } + + const runStackItems: Dictionary = {}; + + for (const node of nodes) { + if ((node.data as NodeDataProp).nodeType === NodeType.PlaceholderNode) { + continue; + } + + const item: RunStackItem = { + outPorts: {}, + node: node.data as NodeDataProp, + }; + + if (!AllComponents[item.node.metadataId]) { + // metadata not found. + throw new BadDataException( + "Metadata not found for " + item.node.metadataId, + ); + } + + item.node.metadata = AllComponents[ + item.node.metadataId + ] as ComponentMetadata; + + // check other components connected to this component. + + const thisComponentId: string = node.id; + + for (const edge of edges) { + if (edge.source !== thisComponentId) { + // this edge does not connect to this component. + continue; } - const runStackItems: Dictionary = {}; - - for (const node of nodes) { - if ( - (node.data as NodeDataProp).nodeType === - NodeType.PlaceholderNode - ) { - continue; - } - - const item: RunStackItem = { - outPorts: {}, - node: node.data as NodeDataProp, - }; - - if (!AllComponents[item.node.metadataId]) { - // metadata not found. - throw new BadDataException( - 'Metadata not found for ' + item.node.metadataId - ); - } - - item.node.metadata = AllComponents[ - item.node.metadataId - ] as ComponentMetadata; - - // check other components connected to this component. - - const thisComponentId: string = node.id; - - for (const edge of edges) { - if (edge.source !== thisComponentId) { - // this edge does not connect to this component. - continue; - } - - if (!item.outPorts[edge['sourceHandle']]) { - item.outPorts[edge['sourceHandle']] = []; - } - - const connectedNode: any = nodes.find((n: any) => { - return n.id === edge.target; - }); - - if (connectedNode) { - item.outPorts[edge['sourceHandle']]?.push( - (connectedNode.data as NodeDataProp).id - ); - } - } - - runStackItems[node.data.id] = item; + if (!item.outPorts[edge["sourceHandle"]]) { + item.outPorts[edge["sourceHandle"]] = []; } - const trigger: any | undefined = nodes.find((n: any) => { - return ( - (n.data as NodeDataProp).componentType === - ComponentType.Trigger && - (n.data as NodeDataProp).nodeType === NodeType.Node - ); + const connectedNode: any = nodes.find((n: any) => { + return n.id === edge.target; }); - return { - stack: runStackItems, - startWithComponentId: trigger - ? (trigger.data as NodeDataProp).id - : '', - }; + if (connectedNode) { + item.outPorts[edge["sourceHandle"]]?.push( + (connectedNode.data as NodeDataProp).id, + ); + } + } + + runStackItems[node.data.id] = item; } + + const trigger: any | undefined = nodes.find((n: any) => { + return ( + (n.data as NodeDataProp).componentType === ComponentType.Trigger && + (n.data as NodeDataProp).nodeType === NodeType.Node + ); + }); + + return { + stack: runStackItems, + startWithComponentId: trigger ? (trigger.data as NodeDataProp).id : "", + }; + } } diff --git a/App/FeatureSet/Workflow/Utils/ComponentMetadata.ts b/App/FeatureSet/Workflow/Utils/ComponentMetadata.ts index 3ffa9d6150..44a542216c 100644 --- a/App/FeatureSet/Workflow/Utils/ComponentMetadata.ts +++ b/App/FeatureSet/Workflow/Utils/ComponentMetadata.ts @@ -1,27 +1,27 @@ -import Dictionary from 'Common/Types/Dictionary'; -import ComponentMetadata from 'Common/Types/Workflow/Component'; -import Components from 'Common/Types/Workflow/Components'; -import BaseModelComponentFactory from 'Common/Types/Workflow/Components/BaseModel'; -import Entities from 'Model/Models/Index'; +import Dictionary from "Common/Types/Dictionary"; +import ComponentMetadata from "Common/Types/Workflow/Component"; +import Components from "Common/Types/Workflow/Components"; +import BaseModelComponentFactory from "Common/Types/Workflow/Components/BaseModel"; +import Entities from "Model/Models/Index"; type LoadAllComponentMetadataFunction = () => Dictionary; export const loadAllComponentMetadata: LoadAllComponentMetadataFunction = - (): Dictionary => { - const initComponents: Dictionary = {}; + (): Dictionary => { + const initComponents: Dictionary = {}; - for (const componentMetadata of Components) { - initComponents[componentMetadata.id] = componentMetadata; - } + for (const componentMetadata of Components) { + initComponents[componentMetadata.id] = componentMetadata; + } - for (const model of Entities) { - const baseModelComponentMetadata: Array = - BaseModelComponentFactory.getComponents(new model()); + for (const model of Entities) { + const baseModelComponentMetadata: Array = + BaseModelComponentFactory.getComponents(new model()); - for (const componentMetadata of baseModelComponentMetadata) { - initComponents[componentMetadata.id] = componentMetadata; - } - } + for (const componentMetadata of baseModelComponentMetadata) { + initComponents[componentMetadata.id] = componentMetadata; + } + } - return initComponents; - }; + return initComponents; + }; diff --git a/App/Index.ts b/App/Index.ts index 6499930d20..fe545ad587 100755 --- a/App/Index.ts +++ b/App/Index.ts @@ -1,81 +1,81 @@ -import APIReferenceRoutes from './FeatureSet/ApiReference/Index'; -import BaseAPIRoutes from './FeatureSet/BaseAPI/Index'; -import DocsRoutes from './FeatureSet/Docs/Index'; -import HomeRoutes from './FeatureSet/Home/Index'; +import APIReferenceRoutes from "./FeatureSet/ApiReference/Index"; +import BaseAPIRoutes from "./FeatureSet/BaseAPI/Index"; +import DocsRoutes from "./FeatureSet/Docs/Index"; +import HomeRoutes from "./FeatureSet/Home/Index"; // import FeatureSets. -import IdentityRoutes from './FeatureSet/Identity/Index'; -import NotificationRoutes from './FeatureSet/Notification/Index'; -import Workers from './FeatureSet/Workers/Index'; -import Workflow from './FeatureSet/Workflow/Index'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { ClickhouseAppInstance } from 'CommonServer/Infrastructure/ClickhouseDatabase'; -import { PostgresAppInstance } from 'CommonServer/Infrastructure/PostgresDatabase'; -import Redis from 'CommonServer/Infrastructure/Redis'; -import InfrastructureStatus from 'CommonServer/Infrastructure/Status'; -import logger from 'CommonServer/Utils/Logger'; -import Realtime from 'CommonServer/Utils/Realtime'; -import App from 'CommonServer/Utils/StartServer'; -import 'CommonServer/Utils/Telemetry'; -import 'ejs'; +import IdentityRoutes from "./FeatureSet/Identity/Index"; +import NotificationRoutes from "./FeatureSet/Notification/Index"; +import Workers from "./FeatureSet/Workers/Index"; +import Workflow from "./FeatureSet/Workflow/Index"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { ClickhouseAppInstance } from "CommonServer/Infrastructure/ClickhouseDatabase"; +import { PostgresAppInstance } from "CommonServer/Infrastructure/PostgresDatabase"; +import Redis from "CommonServer/Infrastructure/Redis"; +import InfrastructureStatus from "CommonServer/Infrastructure/Status"; +import logger from "CommonServer/Utils/Logger"; +import Realtime from "CommonServer/Utils/Realtime"; +import App from "CommonServer/Utils/StartServer"; +import "CommonServer/Utils/Telemetry"; +import "ejs"; -process.env['SERVICE_NAME'] = 'app'; +process.env["SERVICE_NAME"] = "app"; const init: PromiseVoidFunction = async (): Promise => { - try { - const statusCheck: PromiseVoidFunction = async (): Promise => { - return await InfrastructureStatus.checkStatus({ - checkClickhouseStatus: true, - checkPostgresStatus: true, - checkRedisStatus: true, - }); - }; + try { + const statusCheck: PromiseVoidFunction = async (): Promise => { + return await InfrastructureStatus.checkStatus({ + checkClickhouseStatus: true, + checkPostgresStatus: true, + checkRedisStatus: true, + }); + }; - // init the app - await App.init({ - appName: process.env['SERVICE_NAME'] || 'app', - statusOptions: { - liveCheck: statusCheck, - readyCheck: statusCheck, - }, - }); + // init the app + await App.init({ + appName: process.env["SERVICE_NAME"] || "app", + statusOptions: { + liveCheck: statusCheck, + readyCheck: statusCheck, + }, + }); - // connect to the database. - await PostgresAppInstance.connect( - PostgresAppInstance.getDatasourceOptions() - ); + // connect to the database. + await PostgresAppInstance.connect( + PostgresAppInstance.getDatasourceOptions(), + ); - // connect redis - await Redis.connect(); + // connect redis + await Redis.connect(); - await ClickhouseAppInstance.connect( - ClickhouseAppInstance.getDatasourceOptions() - ); + await ClickhouseAppInstance.connect( + ClickhouseAppInstance.getDatasourceOptions(), + ); - await Realtime.init(); + await Realtime.init(); - // init featuresets - await IdentityRoutes.init(); - await NotificationRoutes.init(); - await DocsRoutes.init(); - await BaseAPIRoutes.init(); - await APIReferenceRoutes.init(); - await Workers.init(); - await Workflow.init(); + // init featuresets + await IdentityRoutes.init(); + await NotificationRoutes.init(); + await DocsRoutes.init(); + await BaseAPIRoutes.init(); + await APIReferenceRoutes.init(); + await Workers.init(); + await Workflow.init(); - // home should be in the end because it has the catch all route. - await HomeRoutes.init(); + // home should be in the end because it has the catch all route. + await HomeRoutes.init(); - // add default routes - await App.addDefaultRoutes(); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); diff --git a/Common/AnalyticsModels/BaseModel.ts b/Common/AnalyticsModels/BaseModel.ts index e7195251c7..04fc0c230b 100644 --- a/Common/AnalyticsModels/BaseModel.ts +++ b/Common/AnalyticsModels/BaseModel.ts @@ -1,468 +1,462 @@ -import Route from '../Types/API/Route'; -import AnalyticsTableEngine from '../Types/AnalyticsDatabase/AnalyticsTableEngine'; -import AnalyticsTableColumn from '../Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from '../Types/AnalyticsDatabase/TableColumnType'; +import Route from "../Types/API/Route"; +import AnalyticsTableEngine from "../Types/AnalyticsDatabase/AnalyticsTableEngine"; +import AnalyticsTableColumn from "../Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "../Types/AnalyticsDatabase/TableColumnType"; import { - ColumnAccessControl, - TableAccessControl, -} from '../Types/BaseDatabase/AccessControl'; -import ColumnBillingAccessControl from '../Types/BaseDatabase/ColumnBillingAccessControl'; -import EnableWorkflowOn from '../Types/BaseDatabase/EnableWorkflowOn'; -import ModelPermission from '../Types/BaseDatabase/ModelPermission'; -import TableBillingAccessControl from '../Types/BaseDatabase/TableBillingAccessControl'; -import { PlanSelect } from '../Types/Billing/SubscriptionPlan'; -import Dictionary from '../Types/Dictionary'; -import BadDataException from '../Types/Exception/BadDataException'; -import { JSONValue } from '../Types/JSON'; -import ObjectID from '../Types/ObjectID'; -import Permission, { UserTenantAccessPermission } from '../Types/Permission'; -import Text from '../Types/Text'; -import { EnableRealtimeEventsOn } from '../Utils/Realtime'; -import CommonModel from './CommonModel'; + ColumnAccessControl, + TableAccessControl, +} from "../Types/BaseDatabase/AccessControl"; +import ColumnBillingAccessControl from "../Types/BaseDatabase/ColumnBillingAccessControl"; +import EnableWorkflowOn from "../Types/BaseDatabase/EnableWorkflowOn"; +import ModelPermission from "../Types/BaseDatabase/ModelPermission"; +import TableBillingAccessControl from "../Types/BaseDatabase/TableBillingAccessControl"; +import { PlanSelect } from "../Types/Billing/SubscriptionPlan"; +import Dictionary from "../Types/Dictionary"; +import BadDataException from "../Types/Exception/BadDataException"; +import { JSONValue } from "../Types/JSON"; +import ObjectID from "../Types/ObjectID"; +import Permission, { UserTenantAccessPermission } from "../Types/Permission"; +import Text from "../Types/Text"; +import { EnableRealtimeEventsOn } from "../Utils/Realtime"; +import CommonModel from "./CommonModel"; export type AnalyticsBaseModelType = { new (): AnalyticsBaseModel }; export default class AnalyticsBaseModel extends CommonModel { - public constructor(data: { - tableName: string; - singularName: string; - pluralName: string; - tableEngine?: AnalyticsTableEngine | undefined; - tableColumns: Array; - crudApiPath: Route; - allowAccessIfSubscriptionIsUnpaid?: boolean | undefined; - tableBillingAccessControl?: TableBillingAccessControl | undefined; - accessControl?: TableAccessControl | undefined; - primaryKeys: Array; // this should be the subset of tableColumns - enableWorkflowOn?: EnableWorkflowOn | undefined; - enableRealtimeEventsOn?: EnableRealtimeEventsOn | undefined; - }) { - super({ - tableColumns: data.tableColumns, - }); + public constructor(data: { + tableName: string; + singularName: string; + pluralName: string; + tableEngine?: AnalyticsTableEngine | undefined; + tableColumns: Array; + crudApiPath: Route; + allowAccessIfSubscriptionIsUnpaid?: boolean | undefined; + tableBillingAccessControl?: TableBillingAccessControl | undefined; + accessControl?: TableAccessControl | undefined; + primaryKeys: Array; // this should be the subset of tableColumns + enableWorkflowOn?: EnableWorkflowOn | undefined; + enableRealtimeEventsOn?: EnableRealtimeEventsOn | undefined; + }) { + super({ + tableColumns: data.tableColumns, + }); - this.tableName = data.tableName; + this.tableName = data.tableName; - const columns: Array = [...data.tableColumns]; + const columns: Array = [...data.tableColumns]; - if (data.tableEngine) { - this.tableEngine = data.tableEngine; - } + if (data.tableEngine) { + this.tableEngine = data.tableEngine; + } - columns.push( - new AnalyticsTableColumn({ - key: '_id', - title: 'ID', - description: 'ID of this object', - required: true, - type: TableColumnType.ObjectID, - }) + columns.push( + new AnalyticsTableColumn({ + key: "_id", + title: "ID", + description: "ID of this object", + required: true, + type: TableColumnType.ObjectID, + }), + ); + + columns.push( + new AnalyticsTableColumn({ + key: "createdAt", + title: "Created", + description: "Date and Time when the object was created.", + required: true, + type: TableColumnType.Date, + }), + ); + + columns.push( + new AnalyticsTableColumn({ + key: "updatedAt", + title: "Updated", + description: "Date and Time when the object was updated.", + required: true, + type: TableColumnType.Date, + }), + ); + + if (!data.primaryKeys || data.primaryKeys.length === 0) { + throw new BadDataException("Primary keys are required"); + } + + // check if primary keys are subset of tableColumns + + data.primaryKeys.forEach((primaryKey: string) => { + const column: AnalyticsTableColumn | undefined = columns.find( + (column: AnalyticsTableColumn) => { + return column.key === primaryKey; + }, + ); + + if (!column) { + throw new BadDataException( + "Primary key " + primaryKey + " is not part of tableColumns", ); + } - columns.push( - new AnalyticsTableColumn({ - key: 'createdAt', - title: 'Created', - description: 'Date and Time when the object was created.', - required: true, - type: TableColumnType.Date, - }) + if (!column.required) { + throw new BadDataException( + "Primary key " + + primaryKey + + " is not required. Primary keys must be required.", ); + } + }); - columns.push( - new AnalyticsTableColumn({ - key: 'updatedAt', - title: 'Updated', - description: 'Date and Time when the object was updated.', - required: true, - type: TableColumnType.Date, - }) - ); + this.primaryKeys = data.primaryKeys; + this.tableColumns = columns; + this.singularName = data.singularName; + this.pluralName = data.pluralName; + this.tableBillingAccessControl = data.tableBillingAccessControl; + this.allowAccessIfSubscriptionIsUnpaid = + data.allowAccessIfSubscriptionIsUnpaid || false; + this.accessControl = data.accessControl; + this.enableWorkflowOn = data.enableWorkflowOn; + this.crudApiPath = data.crudApiPath; + this.enableRealtimeEventsOn = data.enableRealtimeEventsOn; - if (!data.primaryKeys || data.primaryKeys.length === 0) { - throw new BadDataException('Primary keys are required'); - } + // initialize Arrays. + for (const column of this.tableColumns) { + if (column.type === TableColumnType.NestedModel) { + this.setColumnValue(column.key, []); + } + } + } - // check if primary keys are subset of tableColumns + private _enableWorkflowOn: EnableWorkflowOn | undefined; + public get enableWorkflowOn(): EnableWorkflowOn | undefined { + return this._enableWorkflowOn; + } + public set enableWorkflowOn(v: EnableWorkflowOn | undefined) { + this._enableWorkflowOn = v; + } - data.primaryKeys.forEach((primaryKey: string) => { - const column: AnalyticsTableColumn | undefined = columns.find( - (column: AnalyticsTableColumn) => { - return column.key === primaryKey; - } - ); + private _accessControl: TableAccessControl | undefined; + public get accessControl(): TableAccessControl | undefined { + return this._accessControl; + } + public set accessControl(v: TableAccessControl | undefined) { + this._accessControl = v; + } - if (!column) { - throw new BadDataException( - 'Primary key ' + primaryKey + ' is not part of tableColumns' - ); - } + private _tableEngine: AnalyticsTableEngine = AnalyticsTableEngine.MergeTree; + public get tableEngine(): AnalyticsTableEngine { + return this._tableEngine; + } + public set tableEngine(v: AnalyticsTableEngine) { + this._tableEngine = v; + } - if (!column.required) { - throw new BadDataException( - 'Primary key ' + - primaryKey + - ' is not required. Primary keys must be required.' - ); - } - }); + private _enableRealtimeEventsOn: EnableRealtimeEventsOn | undefined; + public get enableRealtimeEventsOn(): EnableRealtimeEventsOn | undefined { + return this._enableRealtimeEventsOn; + } + public set enableRealtimeEventsOn(v: EnableRealtimeEventsOn | undefined) { + this._enableRealtimeEventsOn = v; + } - this.primaryKeys = data.primaryKeys; - this.tableColumns = columns; - this.singularName = data.singularName; - this.pluralName = data.pluralName; - this.tableBillingAccessControl = data.tableBillingAccessControl; - this.allowAccessIfSubscriptionIsUnpaid = - data.allowAccessIfSubscriptionIsUnpaid || false; - this.accessControl = data.accessControl; - this.enableWorkflowOn = data.enableWorkflowOn; - this.crudApiPath = data.crudApiPath; - this.enableRealtimeEventsOn = data.enableRealtimeEventsOn; + private _primaryKeys: Array = []; + public get primaryKeys(): Array { + return this._primaryKeys; + } + public set primaryKeys(v: Array) { + this._primaryKeys = v; + } - // initialize Arrays. - for (const column of this.tableColumns) { - if (column.type === TableColumnType.NestedModel) { - this.setColumnValue(column.key, []); - } - } + private _singularName: string = ""; + public get singularName(): string { + return this._singularName; + } + public set singularName(v: string) { + this._singularName = v; + } + + private _pluralName: string = ""; + public get pluralName(): string { + return this._pluralName; + } + public set pluralName(v: string) { + this._pluralName = v; + } + + private _tableBillingAccessControl: TableBillingAccessControl | undefined; + public get tableBillingAccessControl(): + | TableBillingAccessControl + | undefined { + return this._tableBillingAccessControl; + } + public set tableBillingAccessControl( + v: TableBillingAccessControl | undefined, + ) { + this._tableBillingAccessControl = v; + } + + private _allowAccessIfSubscriptionIsUnpaid: boolean = false; + public get allowAccessIfSubscriptionIsUnpaid(): boolean { + return this._allowAccessIfSubscriptionIsUnpaid; + } + public set allowAccessIfSubscriptionIsUnpaid(v: boolean) { + this._allowAccessIfSubscriptionIsUnpaid = v; + } + + private _tableName: string = ""; + public get tableName(): string { + return this._tableName; + } + public set tableName(v: string) { + this._tableName = v; + } + + private _crudApiPath!: Route; + public get crudApiPath(): Route { + return this._crudApiPath; + } + public set crudApiPath(v: Route) { + this._crudApiPath = v; + } + + public getTenantColumn(): AnalyticsTableColumn | null { + const column: AnalyticsTableColumn | undefined = this.tableColumns.find( + (column: AnalyticsTableColumn) => { + return column.isTenantId; + }, + ); + + if (!column) { + return null; } - private _enableWorkflowOn: EnableWorkflowOn | undefined; - public get enableWorkflowOn(): EnableWorkflowOn | undefined { - return this._enableWorkflowOn; - } - public set enableWorkflowOn(v: EnableWorkflowOn | undefined) { - this._enableWorkflowOn = v; + return column; + } + + public getTenantColumnValue(): ObjectID | null { + const column: AnalyticsTableColumn | null = this.getTenantColumn(); + + if (!column) { + return null; } - private _accessControl: TableAccessControl | undefined; - public get accessControl(): TableAccessControl | undefined { - return this._accessControl; - } - public set accessControl(v: TableAccessControl | undefined) { - this._accessControl = v; + return this.getColumnValue(column.key) as ObjectID | null; + } + + public getRequiredColumns(): Array { + return this.tableColumns.filter((column: AnalyticsTableColumn) => { + return column.required; + }); + } + + public isDefaultValueColumn(columnName: string): boolean { + const column: AnalyticsTableColumn | null = this.getTableColumn(columnName); + + if (!column) { + return false; } - private _tableEngine: AnalyticsTableEngine = AnalyticsTableEngine.MergeTree; - public get tableEngine(): AnalyticsTableEngine { - return this._tableEngine; - } - public set tableEngine(v: AnalyticsTableEngine) { - this._tableEngine = v; + return column.isDefaultValueColumn; + } + + public getDefaultValueForColumn(columnName: string): JSONValue { + const column: AnalyticsTableColumn | null = this.getTableColumn(columnName); + + if (!column) { + throw new BadDataException("Column " + columnName + " not found"); } - private _enableRealtimeEventsOn: EnableRealtimeEventsOn | undefined; - public get enableRealtimeEventsOn(): EnableRealtimeEventsOn | undefined { - return this._enableRealtimeEventsOn; - } - public set enableRealtimeEventsOn(v: EnableRealtimeEventsOn | undefined) { - this._enableRealtimeEventsOn = v; + return column.defaultValue; + } + + public getColumnBillingAccessControl( + columnName: string, + ): ColumnBillingAccessControl | null { + const column: AnalyticsTableColumn | null = this.getTableColumn(columnName); + + if (!column) { + return null; } - private _primaryKeys: Array = []; - public get primaryKeys(): Array { - return this._primaryKeys; - } - public set primaryKeys(v: Array) { - this._primaryKeys = v; + return column.billingAccessControl || null; + } + + public get id(): ObjectID | undefined { + return this.getColumnValue("_id") as ObjectID | undefined; + } + public set id(v: ObjectID | undefined) { + this.setColumnValue("_id", v); + } + + public get _id(): ObjectID | undefined { + return this.getColumnValue("_id") as ObjectID | undefined; + } + public set _id(v: ObjectID | undefined) { + this.setColumnValue("_id", v); + } + + public get createdAt(): Date | undefined { + return this.getColumnValue("createdAt") as Date | undefined; + } + + public set createdAt(v: Date | undefined) { + this.setColumnValue("createdAt", v); + } + + public get updatedAt(): Date | undefined { + return this.getColumnValue("updatedAt") as Date | undefined; + } + + public set updatedAt(v: Date | undefined) { + this.setColumnValue("updatedAt", v); + } + + public getAPIDocumentationPath(): string { + return Text.pascalCaseToDashes(this.tableName); + } + + public getColumnAccessControlFor( + columnName: string, + ): ColumnAccessControl | null { + const tableColumn: AnalyticsTableColumn | undefined = + this.tableColumns.find((column: AnalyticsTableColumn) => { + return column.key === columnName; + }); + + if (!tableColumn || !tableColumn.accessControl) { + return null; } - private _singularName: string = ''; - public get singularName(): string { - return this._singularName; - } - public set singularName(v: string) { - this._singularName = v; + return tableColumn.accessControl; + } + + public getColumnAccessControlForAllColumns(): Dictionary { + const dictionary: Dictionary = {}; + + for (const column of this.tableColumns) { + if (column.accessControl) { + dictionary[column.key] = column.accessControl; + } } - private _pluralName: string = ''; - public get pluralName(): string { - return this._pluralName; - } - public set pluralName(v: string) { - this._pluralName = v; + return dictionary; + } + + public getReadPermissions(): Array { + return this.accessControl?.read || []; + } + + public getCreatePermissions(): Array { + return this.accessControl?.create || []; + } + + public getUpdatePermissions(): Array { + return this.accessControl?.update || []; + } + + public getDeletePermissions(): Array { + return this.accessControl?.delete || []; + } + + public getReadBillingPlan(): PlanSelect | null { + return this.tableBillingAccessControl?.read || null; + } + + public getCreateBillingPlan(): PlanSelect | null { + return this.tableBillingAccessControl?.create || null; + } + + public getUpdateBillingPlan(): PlanSelect | null { + return this.tableBillingAccessControl?.update || null; + } + + public getDeleteBillingPlan(): PlanSelect | null { + return this.tableBillingAccessControl?.delete || null; + } + + public isEntityColumn(_columnName: string): boolean { + // Analytics model does not suppprt entity columns. + return false; + } + + public hasColumn(columnName: string): boolean { + return this.tableColumns.some((column: AnalyticsTableColumn) => { + return column.key === columnName; + }); + } + + public isFileColumn(_columnName: string): boolean { + // Analytics model does not suppprt file columns. + return false; + } + + public hasCreatePermissions( + userProjectPermissions: UserTenantAccessPermission | Array, + columnName?: string, + ): boolean { + let modelPermission: Array = this.accessControl?.create || []; + + if (columnName) { + const columnAccessControl: ColumnAccessControl | null = + this.getColumnAccessControlFor(columnName); + if (columnAccessControl) { + modelPermission = columnAccessControl.create; + } } - private _tableBillingAccessControl: TableBillingAccessControl | undefined; - public get tableBillingAccessControl(): - | TableBillingAccessControl - | undefined { - return this._tableBillingAccessControl; - } - public set tableBillingAccessControl( - v: TableBillingAccessControl | undefined - ) { - this._tableBillingAccessControl = v; + return ModelPermission.hasPermissions( + userProjectPermissions, + modelPermission, + ); + } + + public hasReadPermissions( + userProjectPermissions: UserTenantAccessPermission | Array, + columnName?: string, + ): boolean { + let modelPermission: Array = this.accessControl?.read || []; + + if (columnName) { + const columnAccessControl: ColumnAccessControl | null = + this.getColumnAccessControlFor(columnName); + if (columnAccessControl) { + modelPermission = columnAccessControl.read; + } } - private _allowAccessIfSubscriptionIsUnpaid: boolean = false; - public get allowAccessIfSubscriptionIsUnpaid(): boolean { - return this._allowAccessIfSubscriptionIsUnpaid; - } - public set allowAccessIfSubscriptionIsUnpaid(v: boolean) { - this._allowAccessIfSubscriptionIsUnpaid = v; + return ModelPermission.hasPermissions( + userProjectPermissions, + modelPermission, + ); + } + + public hasDeletePermissions( + userProjectPermissions: UserTenantAccessPermission | Array, + ): boolean { + const modelPermission: Array = this.accessControl?.delete || []; + return ModelPermission.hasPermissions( + userProjectPermissions, + modelPermission, + ); + } + + public hasUpdatePermissions( + userProjectPermissions: UserTenantAccessPermission | Array, + columnName?: string, + ): boolean { + let modelPermission: Array = this.accessControl?.update || []; + + if (columnName) { + const columnAccessControl: ColumnAccessControl | null = + this.getColumnAccessControlFor(columnName); + if (columnAccessControl) { + modelPermission = columnAccessControl.update; + } } - private _tableName: string = ''; - public get tableName(): string { - return this._tableName; - } - public set tableName(v: string) { - this._tableName = v; - } - - private _crudApiPath!: Route; - public get crudApiPath(): Route { - return this._crudApiPath; - } - public set crudApiPath(v: Route) { - this._crudApiPath = v; - } - - public getTenantColumn(): AnalyticsTableColumn | null { - const column: AnalyticsTableColumn | undefined = this.tableColumns.find( - (column: AnalyticsTableColumn) => { - return column.isTenantId; - } - ); - - if (!column) { - return null; - } - - return column; - } - - public getTenantColumnValue(): ObjectID | null { - const column: AnalyticsTableColumn | null = this.getTenantColumn(); - - if (!column) { - return null; - } - - return this.getColumnValue(column.key) as ObjectID | null; - } - - public getRequiredColumns(): Array { - return this.tableColumns.filter((column: AnalyticsTableColumn) => { - return column.required; - }); - } - - public isDefaultValueColumn(columnName: string): boolean { - const column: AnalyticsTableColumn | null = - this.getTableColumn(columnName); - - if (!column) { - return false; - } - - return column.isDefaultValueColumn; - } - - public getDefaultValueForColumn(columnName: string): JSONValue { - const column: AnalyticsTableColumn | null = - this.getTableColumn(columnName); - - if (!column) { - throw new BadDataException('Column ' + columnName + ' not found'); - } - - return column.defaultValue; - } - - public getColumnBillingAccessControl( - columnName: string - ): ColumnBillingAccessControl | null { - const column: AnalyticsTableColumn | null = - this.getTableColumn(columnName); - - if (!column) { - return null; - } - - return column.billingAccessControl || null; - } - - public get id(): ObjectID | undefined { - return this.getColumnValue('_id') as ObjectID | undefined; - } - public set id(v: ObjectID | undefined) { - this.setColumnValue('_id', v); - } - - public get _id(): ObjectID | undefined { - return this.getColumnValue('_id') as ObjectID | undefined; - } - public set _id(v: ObjectID | undefined) { - this.setColumnValue('_id', v); - } - - public get createdAt(): Date | undefined { - return this.getColumnValue('createdAt') as Date | undefined; - } - - public set createdAt(v: Date | undefined) { - this.setColumnValue('createdAt', v); - } - - public get updatedAt(): Date | undefined { - return this.getColumnValue('updatedAt') as Date | undefined; - } - - public set updatedAt(v: Date | undefined) { - this.setColumnValue('updatedAt', v); - } - - public getAPIDocumentationPath(): string { - return Text.pascalCaseToDashes(this.tableName); - } - - public getColumnAccessControlFor( - columnName: string - ): ColumnAccessControl | null { - const tableColumn: AnalyticsTableColumn | undefined = - this.tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === columnName; - }); - - if (!tableColumn || !tableColumn.accessControl) { - return null; - } - - return tableColumn.accessControl; - } - - public getColumnAccessControlForAllColumns(): Dictionary { - const dictionary: Dictionary = {}; - - for (const column of this.tableColumns) { - if (column.accessControl) { - dictionary[column.key] = column.accessControl; - } - } - - return dictionary; - } - - public getReadPermissions(): Array { - return this.accessControl?.read || []; - } - - public getCreatePermissions(): Array { - return this.accessControl?.create || []; - } - - public getUpdatePermissions(): Array { - return this.accessControl?.update || []; - } - - public getDeletePermissions(): Array { - return this.accessControl?.delete || []; - } - - public getReadBillingPlan(): PlanSelect | null { - return this.tableBillingAccessControl?.read || null; - } - - public getCreateBillingPlan(): PlanSelect | null { - return this.tableBillingAccessControl?.create || null; - } - - public getUpdateBillingPlan(): PlanSelect | null { - return this.tableBillingAccessControl?.update || null; - } - - public getDeleteBillingPlan(): PlanSelect | null { - return this.tableBillingAccessControl?.delete || null; - } - - public isEntityColumn(_columnName: string): boolean { - // Analytics model does not suppprt entity columns. - return false; - } - - public hasColumn(columnName: string): boolean { - return this.tableColumns.some((column: AnalyticsTableColumn) => { - return column.key === columnName; - }); - } - - public isFileColumn(_columnName: string): boolean { - // Analytics model does not suppprt file columns. - return false; - } - - public hasCreatePermissions( - userProjectPermissions: UserTenantAccessPermission | Array, - columnName?: string - ): boolean { - let modelPermission: Array = - this.accessControl?.create || []; - - if (columnName) { - const columnAccessControl: ColumnAccessControl | null = - this.getColumnAccessControlFor(columnName); - if (columnAccessControl) { - modelPermission = columnAccessControl.create; - } - } - - return ModelPermission.hasPermissions( - userProjectPermissions, - modelPermission - ); - } - - public hasReadPermissions( - userProjectPermissions: UserTenantAccessPermission | Array, - columnName?: string - ): boolean { - let modelPermission: Array = this.accessControl?.read || []; - - if (columnName) { - const columnAccessControl: ColumnAccessControl | null = - this.getColumnAccessControlFor(columnName); - if (columnAccessControl) { - modelPermission = columnAccessControl.read; - } - } - - return ModelPermission.hasPermissions( - userProjectPermissions, - modelPermission - ); - } - - public hasDeletePermissions( - userProjectPermissions: UserTenantAccessPermission | Array - ): boolean { - const modelPermission: Array = - this.accessControl?.delete || []; - return ModelPermission.hasPermissions( - userProjectPermissions, - modelPermission - ); - } - - public hasUpdatePermissions( - userProjectPermissions: UserTenantAccessPermission | Array, - columnName?: string - ): boolean { - let modelPermission: Array = - this.accessControl?.update || []; - - if (columnName) { - const columnAccessControl: ColumnAccessControl | null = - this.getColumnAccessControlFor(columnName); - if (columnAccessControl) { - modelPermission = columnAccessControl.update; - } - } - - return ModelPermission.hasPermissions( - userProjectPermissions, - modelPermission - ); - } + return ModelPermission.hasPermissions( + userProjectPermissions, + modelPermission, + ); + } } diff --git a/Common/AnalyticsModels/CommonModel.ts b/Common/AnalyticsModels/CommonModel.ts index d620aeff08..30106e8427 100644 --- a/Common/AnalyticsModels/CommonModel.ts +++ b/Common/AnalyticsModels/CommonModel.ts @@ -1,266 +1,256 @@ // This model will be extended by BaseModel and Nested Mdoel -import AnalyticsTableColumn from '../Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from '../Types/AnalyticsDatabase/TableColumnType'; -import GreaterThan from '../Types/BaseDatabase/GreaterThan'; -import GreaterThanOrEqual from '../Types/BaseDatabase/GreaterThanOrEqual'; -import InBetween from '../Types/BaseDatabase/InBetween'; -import Includes from '../Types/BaseDatabase/Includes'; -import LessThan from '../Types/BaseDatabase/LessThan'; -import LessThanOrEqual from '../Types/BaseDatabase/LessThanOrEqual'; -import NotEqual from '../Types/BaseDatabase/NotEqual'; -import Search from '../Types/BaseDatabase/Search'; -import OneUptimeDate from '../Types/Date'; -import BadDataException from '../Types/Exception/BadDataException'; -import { JSONArray, JSONObject, JSONValue } from '../Types/JSON'; -import JSONFunctions from '../Types/JSONFunctions'; -import ObjectID from '../Types/ObjectID'; +import AnalyticsTableColumn from "../Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "../Types/AnalyticsDatabase/TableColumnType"; +import GreaterThan from "../Types/BaseDatabase/GreaterThan"; +import GreaterThanOrEqual from "../Types/BaseDatabase/GreaterThanOrEqual"; +import InBetween from "../Types/BaseDatabase/InBetween"; +import Includes from "../Types/BaseDatabase/Includes"; +import LessThan from "../Types/BaseDatabase/LessThan"; +import LessThanOrEqual from "../Types/BaseDatabase/LessThanOrEqual"; +import NotEqual from "../Types/BaseDatabase/NotEqual"; +import Search from "../Types/BaseDatabase/Search"; +import OneUptimeDate from "../Types/Date"; +import BadDataException from "../Types/Exception/BadDataException"; +import { JSONArray, JSONObject, JSONValue } from "../Types/JSON"; +import JSONFunctions from "../Types/JSONFunctions"; +import ObjectID from "../Types/ObjectID"; export type RecordValue = - | ObjectID - | string - | number - | boolean - | Date - | Search - | NotEqual - | GreaterThan - | InBetween - | Includes - | Date - | LessThan - | LessThanOrEqual - | GreaterThanOrEqual - | Array - | Array - | Array - | Array - | JSONObject - | CommonModel; + | ObjectID + | string + | number + | boolean + | Date + | Search + | NotEqual + | GreaterThan + | InBetween + | Includes + | Date + | LessThan + | LessThanOrEqual + | GreaterThanOrEqual + | Array + | Array + | Array + | Array + | JSONObject + | CommonModel; export type Record = Array; export default class CommonModel { - protected data: JSONObject = {}; + protected data: JSONObject = {}; - private _tableColumns: Array = []; - public get tableColumns(): Array { - return this._tableColumns; - } - public set tableColumns(v: Array) { - this._tableColumns = v; - } + private _tableColumns: Array = []; + public get tableColumns(): Array { + return this._tableColumns; + } + public set tableColumns(v: Array) { + this._tableColumns = v; + } - public setColumnValue( - columnName: string, - value: JSONValue | Array - ): void { - const column: AnalyticsTableColumn | null = - this.getTableColumn(columnName); + public setColumnValue( + columnName: string, + value: JSONValue | Array, + ): void { + const column: AnalyticsTableColumn | null = this.getTableColumn(columnName); - if (column) { - if ( - column.type === TableColumnType.ObjectID && - (typeof value === 'string' || typeof value === 'object') - ) { - value = new ObjectID(value as string | JSONObject); - } + if (column) { + if ( + column.type === TableColumnType.ObjectID && + (typeof value === "string" || typeof value === "object") + ) { + value = new ObjectID(value as string | JSONObject); + } - if ( - column.type === TableColumnType.Date && - typeof value === 'string' - ) { - value = OneUptimeDate.fromString(value); - } + if (column.type === TableColumnType.Date && typeof value === "string") { + value = OneUptimeDate.fromString(value); + } - if ( - column.type === TableColumnType.JSON && - typeof value === 'string' - ) { - try { - value = JSONFunctions.parse(value); - } catch (e) { - value = {}; - } - } - - if ( - column.type === TableColumnType.JSONArray && - typeof value === 'string' - ) { - try { - value = JSONFunctions.parseJSONArray(value); - - if (!Array.isArray(value)) { - throw new BadDataException('Not an array'); - } - } catch (e) { - value = []; - } - } - - if ( - column.type === TableColumnType.Number && - typeof value === 'string' - ) { - value = parseInt(value); - } - - // decimal - if ( - column.type === TableColumnType.Decimal && - typeof value === 'string' - ) { - value = parseFloat(value); - } - - // long number - if ( - column.type === TableColumnType.LongNumber && - typeof value === 'string' - ) { - value = parseInt(value); - } - - return (this.data[columnName] = value as any); + if (column.type === TableColumnType.JSON && typeof value === "string") { + try { + value = JSONFunctions.parse(value); + } catch (e) { + value = {}; } - throw new BadDataException('Column ' + columnName + ' does not exist'); + } + + if ( + column.type === TableColumnType.JSONArray && + typeof value === "string" + ) { + try { + value = JSONFunctions.parseJSONArray(value); + + if (!Array.isArray(value)) { + throw new BadDataException("Not an array"); + } + } catch (e) { + value = []; + } + } + + if (column.type === TableColumnType.Number && typeof value === "string") { + value = parseInt(value); + } + + // decimal + if ( + column.type === TableColumnType.Decimal && + typeof value === "string" + ) { + value = parseFloat(value); + } + + // long number + if ( + column.type === TableColumnType.LongNumber && + typeof value === "string" + ) { + value = parseInt(value); + } + + return (this.data[columnName] = value as any); + } + throw new BadDataException("Column " + columnName + " does not exist"); + } + + public constructor(data: { tableColumns: Array }) { + this.tableColumns = data.tableColumns; + } + + public getColumnValue( + columnName: string, + ): T | undefined { + if (this.getTableColumn(columnName)) { + return this.data[columnName] as T; } - public constructor(data: { tableColumns: Array }) { - this.tableColumns = data.tableColumns; + return undefined; + } + + public getTableColumn(name: string): AnalyticsTableColumn | null { + const column: AnalyticsTableColumn | undefined = this.tableColumns.find( + (column: AnalyticsTableColumn) => { + return column.key === name; + }, + ); + + if (!column) { + return null; } - public getColumnValue( - columnName: string - ): T | undefined { - if (this.getTableColumn(columnName)) { - return this.data[columnName] as T; + return column; + } + + public getTableColumns(): Array { + return this.tableColumns; + } + + public static fromJSON( + json: JSONObject | JSONArray | CommonModel | Array, + type: { new (): T }, + ): T | Array { + if (Array.isArray(json)) { + const arr: Array = []; + + for (const item of json) { + if (item instanceof CommonModel) { + arr.push(item as T); + continue; } - return undefined; + arr.push(new type().fromJSON(item) as T); + } + + return arr; } - public getTableColumn(name: string): AnalyticsTableColumn | null { - const column: AnalyticsTableColumn | undefined = this.tableColumns.find( - (column: AnalyticsTableColumn) => { - return column.key === name; - } - ); + if (json instanceof CommonModel) { + return json as T; + } - if (!column) { - return null; + return new type().fromJSON(json) as T; + } + + public static toJSON( + model: T, + _modelType: { new (): T }, + ): JSONObject { + return model.toJSON(); + } + + public fromJSON(json: JSONObject): CommonModel { + for (const key in json) { + this.setColumnValue(key, json[key]); + } + + return this; + } + + public toJSON(): JSONObject { + const json: JSONObject = {}; + + this.tableColumns.forEach((column: AnalyticsTableColumn) => { + const recordValue: RecordValue | undefined = this.getColumnValue( + column.key, + ); + + if (recordValue instanceof CommonModel) { + json[column.key] = recordValue.toJSON(); + return; + } + + if (recordValue instanceof Array) { + if (recordValue.length > 0 && column.nestedModelType) { + json[column.key] = CommonModel.toJSONArray( + recordValue as Array, + column.nestedModelType, + ); + } else { + json[column.key] = recordValue; } - return column; + return; + } + + json[column.key] = recordValue; + }); + + return JSONFunctions.serialize(json); + } + + public static fromJSONArray( + jsonArray: Array, + modelType: { new (): CommonModel }, + ): Array { + const models: Array = []; + + for (const json of jsonArray) { + if (json instanceof CommonModel) { + models.push(json); + continue; + } + + const model: CommonModel = new modelType(); + model.fromJSON(json); + models.push(model); } - public getTableColumns(): Array { - return this.tableColumns; - } + return models as Array; + } - public static fromJSON( - json: JSONObject | JSONArray | CommonModel | Array, - type: { new (): T } - ): T | Array { - if (Array.isArray(json)) { - const arr: Array = []; + public static toJSONArray( + models: Array, + modelType: { new (): CommonModel }, + ): Array { + const json: Array = []; - for (const item of json) { - if (item instanceof CommonModel) { - arr.push(item as T); - continue; - } + models.forEach((model: CommonModel) => { + json.push(this.toJSON(model, modelType)); + }); - arr.push(new type().fromJSON(item) as T); - } - - return arr; - } - - if (json instanceof CommonModel) { - return json as T; - } - - return new type().fromJSON(json) as T; - } - - public static toJSON( - model: T, - _modelType: { new (): T } - ): JSONObject { - return model.toJSON(); - } - - public fromJSON(json: JSONObject): CommonModel { - for (const key in json) { - this.setColumnValue(key, json[key]); - } - - return this; - } - - public toJSON(): JSONObject { - const json: JSONObject = {}; - - this.tableColumns.forEach((column: AnalyticsTableColumn) => { - const recordValue: RecordValue | undefined = this.getColumnValue( - column.key - ); - - if (recordValue instanceof CommonModel) { - json[column.key] = recordValue.toJSON(); - return; - } - - if (recordValue instanceof Array) { - if (recordValue.length > 0 && column.nestedModelType) { - json[column.key] = CommonModel.toJSONArray( - recordValue as Array, - column.nestedModelType - ); - } else { - json[column.key] = recordValue; - } - - return; - } - - json[column.key] = recordValue; - }); - - return JSONFunctions.serialize(json); - } - - public static fromJSONArray( - jsonArray: Array, - modelType: { new (): CommonModel } - ): Array { - const models: Array = []; - - for (const json of jsonArray) { - if (json instanceof CommonModel) { - models.push(json); - continue; - } - - const model: CommonModel = new modelType(); - model.fromJSON(json); - models.push(model); - } - - return models as Array; - } - - public static toJSONArray( - models: Array, - modelType: { new (): CommonModel } - ): Array { - const json: Array = []; - - models.forEach((model: CommonModel) => { - json.push(this.toJSON(model, modelType)); - }); - - return json; - } + return json; + } } diff --git a/Common/AnalyticsModels/NestedModel.ts b/Common/AnalyticsModels/NestedModel.ts index c17c989abe..25737cf3f4 100644 --- a/Common/AnalyticsModels/NestedModel.ts +++ b/Common/AnalyticsModels/NestedModel.ts @@ -1,8 +1,8 @@ -import AnalyticsTableColumn from '../Types/AnalyticsDatabase/TableColumn'; -import CommonModel from './CommonModel'; +import AnalyticsTableColumn from "../Types/AnalyticsDatabase/TableColumn"; +import CommonModel from "./CommonModel"; export default class NestedModel extends CommonModel { - public constructor(data: { tableColumns: Array }) { - super(data); - } + public constructor(data: { tableColumns: Array }) { + super(data); + } } diff --git a/Common/Models/AccessControlModel.ts b/Common/Models/AccessControlModel.ts index 07bbf60b20..a95dd4e0f8 100644 --- a/Common/Models/AccessControlModel.ts +++ b/Common/Models/AccessControlModel.ts @@ -1,15 +1,15 @@ -import ObjectID from '../Types/ObjectID'; -import BaseModel from './BaseModel'; +import ObjectID from "../Types/ObjectID"; +import BaseModel from "./BaseModel"; export default class AccessControlModel extends BaseModel { - // Please override this property in the child class - public name?: string = undefined; + // Please override this property in the child class + public name?: string = undefined; - public constructor(id?: ObjectID) { - super(id); - } + public constructor(id?: ObjectID) { + super(id); + } - public override isAccessControlModel(): boolean { - return true; - } + public override isAccessControlModel(): boolean { + return true; + } } diff --git a/Common/Models/BaseModel.ts b/Common/Models/BaseModel.ts index 2bc9650511..ddc791d428 100644 --- a/Common/Models/BaseModel.ts +++ b/Common/Models/BaseModel.ts @@ -1,739 +1,731 @@ -import Route from '../Types/API/Route'; -import { ColumnAccessControl } from '../Types/BaseDatabase/AccessControl'; -import ColumnBillingAccessControl from '../Types/BaseDatabase/ColumnBillingAccessControl'; -import EnableWorkflowOn from '../Types/BaseDatabase/EnableWorkflowOn'; -import ModelPermission from '../Types/BaseDatabase/ModelPermission'; -import { PlanSelect } from '../Types/Billing/SubscriptionPlan'; -import { getColumnAccessControlForAllColumns } from '../Types/Database/AccessControl/ColumnAccessControl'; -import { getColumnBillingAccessControlForAllColumns } from '../Types/Database/AccessControl/ColumnBillingAccessControl'; -import Columns from '../Types/Database/Columns'; +import Route from "../Types/API/Route"; +import { ColumnAccessControl } from "../Types/BaseDatabase/AccessControl"; +import ColumnBillingAccessControl from "../Types/BaseDatabase/ColumnBillingAccessControl"; +import EnableWorkflowOn from "../Types/BaseDatabase/EnableWorkflowOn"; +import ModelPermission from "../Types/BaseDatabase/ModelPermission"; +import { PlanSelect } from "../Types/Billing/SubscriptionPlan"; +import { getColumnAccessControlForAllColumns } from "../Types/Database/AccessControl/ColumnAccessControl"; +import { getColumnBillingAccessControlForAllColumns } from "../Types/Database/AccessControl/ColumnBillingAccessControl"; +import Columns from "../Types/Database/Columns"; import TableColumn, { - TableColumnMetadata, - getTableColumn, - getTableColumns, -} from '../Types/Database/TableColumn'; -import TableColumnType from '../Types/Database/TableColumnType'; -import Dictionary from '../Types/Dictionary'; -import Email from '../Types/Email'; -import BadDataException from '../Types/Exception/BadDataException'; -import HashedString from '../Types/HashedString'; -import IconProp from '../Types/Icon/IconProp'; -import { JSONArray, JSONObject, JSONValue } from '../Types/JSON'; -import JSONFunctions from '../Types/JSONFunctions'; -import ObjectID from '../Types/ObjectID'; -import Permission, { UserTenantAccessPermission } from '../Types/Permission'; -import Phone from '../Types/Phone'; -import PositiveNumber from '../Types/PositiveNumber'; -import Text from '../Types/Text'; + TableColumnMetadata, + getTableColumn, + getTableColumns, +} from "../Types/Database/TableColumn"; +import TableColumnType from "../Types/Database/TableColumnType"; +import Dictionary from "../Types/Dictionary"; +import Email from "../Types/Email"; +import BadDataException from "../Types/Exception/BadDataException"; +import HashedString from "../Types/HashedString"; +import IconProp from "../Types/Icon/IconProp"; +import { JSONArray, JSONObject, JSONValue } from "../Types/JSON"; +import JSONFunctions from "../Types/JSONFunctions"; +import ObjectID from "../Types/ObjectID"; +import Permission, { UserTenantAccessPermission } from "../Types/Permission"; +import Phone from "../Types/Phone"; +import PositiveNumber from "../Types/PositiveNumber"; +import Text from "../Types/Text"; import { - BaseEntity, - CreateDateColumn, - DeleteDateColumn, - PrimaryGeneratedColumn, - UpdateDateColumn, - VersionColumn, -} from 'typeorm'; + BaseEntity, + CreateDateColumn, + DeleteDateColumn, + PrimaryGeneratedColumn, + UpdateDateColumn, + VersionColumn, +} from "typeorm"; export type BaseModelType = { new (): BaseModel }; export type DbTypes = - | string - | number - | PositiveNumber - | Email - | HashedString - | URL - | Phone - | JSONObject - | ObjectID - | JSONArray - | Buffer; + | string + | number + | PositiveNumber + | Email + | HashedString + | URL + | Phone + | JSONObject + | ObjectID + | JSONArray + | Buffer; export default class BaseModel extends BaseEntity { - @TableColumn({ - title: 'ID', - type: TableColumnType.ObjectID, - description: 'ID of this object', - }) - @PrimaryGeneratedColumn('uuid') - public _id?: string = undefined; + @TableColumn({ + title: "ID", + type: TableColumnType.ObjectID, + description: "ID of this object", + }) + @PrimaryGeneratedColumn("uuid") + public _id?: string = undefined; - @TableColumn({ - title: 'Created', - type: TableColumnType.Date, - description: 'Date and Time when the object was created.', - }) - @CreateDateColumn() - public createdAt?: Date = undefined; + @TableColumn({ + title: "Created", + type: TableColumnType.Date, + description: "Date and Time when the object was created.", + }) + @CreateDateColumn() + public createdAt?: Date = undefined; - @TableColumn({ - title: 'Updated', - type: TableColumnType.Date, - description: 'Date and Time when the object was updated.', - }) - @UpdateDateColumn() - public updatedAt?: Date = undefined; + @TableColumn({ + title: "Updated", + type: TableColumnType.Date, + description: "Date and Time when the object was updated.", + }) + @UpdateDateColumn() + public updatedAt?: Date = undefined; - @TableColumn({ - title: 'Deleted', - type: TableColumnType.Date, - description: 'Date and Time when the object was deleted.', - }) - @DeleteDateColumn() - public deletedAt?: Date = undefined; + @TableColumn({ + title: "Deleted", + type: TableColumnType.Date, + description: "Date and Time when the object was deleted.", + }) + @DeleteDateColumn() + public deletedAt?: Date = undefined; - @TableColumn({ - title: 'Version', - type: TableColumnType.Version, - description: 'Object version', - }) - @VersionColumn() - public version?: number = undefined; + @TableColumn({ + title: "Version", + type: TableColumnType.Version, + description: "Object version", + }) + @VersionColumn() + public version?: number = undefined; - public createRecordPermissions!: Array; - public readRecordPermissions!: Array; - public deleteRecordPermissions!: Array; - public updateRecordPermissions!: Array; + public createRecordPermissions!: Array; + public readRecordPermissions!: Array; + public deleteRecordPermissions!: Array; + public updateRecordPermissions!: Array; - // Billing Plans. - public createBillingPlan!: PlanSelect | null; - public readBillingPlan!: PlanSelect | null; - public updateBillingPlan!: PlanSelect | null; - public deleteBillingPlan!: PlanSelect | null; + // Billing Plans. + public createBillingPlan!: PlanSelect | null; + public readBillingPlan!: PlanSelect | null; + public updateBillingPlan!: PlanSelect | null; + public deleteBillingPlan!: PlanSelect | null; - public allowAccessIfSubscriptionIsUnpaid!: boolean; + public allowAccessIfSubscriptionIsUnpaid!: boolean; - public enableWorkflowOn!: EnableWorkflowOn; + public enableWorkflowOn!: EnableWorkflowOn; - public enableDocumentation!: boolean; - public isMasterAdminApiDocs!: boolean; + public enableDocumentation!: boolean; + public isMasterAdminApiDocs!: boolean; - public currentUserCanAccessColumnBy!: string | null; - public labelsColumn!: string | null; - public slugifyColumn!: string | null; - public saveSlugToColumn!: string | null; - public singularName!: string | null; - public pluralName!: string | null; + public currentUserCanAccessColumnBy!: string | null; + public labelsColumn!: string | null; + public slugifyColumn!: string | null; + public saveSlugToColumn!: string | null; + public singularName!: string | null; + public pluralName!: string | null; - // total items by - public totalItemsByColumnName!: string | null; - public totalItemsNumber!: number | null; - public totalItemsErrorMessage!: string | null; + // total items by + public totalItemsByColumnName!: string | null; + public totalItemsNumber!: number | null; + public totalItemsErrorMessage!: string | null; - public isPermissionIf: Dictionary = {}; + public isPermissionIf: Dictionary = {}; - public isMultiTenantRequestAllowed!: boolean | null; - public allowUserQueryWithoutTenant!: boolean | null; + public isMultiTenantRequestAllowed!: boolean | null; + public allowUserQueryWithoutTenant!: boolean | null; - public crudApiPath!: Route | null; + public crudApiPath!: Route | null; - // If this resource is by projectId, which column does projectId belong to? - public tenantColumn!: string | null; + // If this resource is by projectId, which column does projectId belong to? + public tenantColumn!: string | null; - public accessControlColumn!: string | null; + public accessControlColumn!: string | null; - public icon!: IconProp | null; + public icon!: IconProp | null; - public tableDescription!: string | null; + public tableDescription!: string | null; - public tableName!: string | null; + public tableName!: string | null; - public canAccessIfCanReadOn!: string | null; + public canAccessIfCanReadOn!: string | null; - public constructor(id?: ObjectID) { - super(); - if (id) { - this.id = id; - } + public constructor(id?: ObjectID) { + super(); + if (id) { + this.id = id; + } + } + + public getHashedColumns(): Columns { + const dictionary: Dictionary = getTableColumns(this); + const columns: Array = []; + for (const key in dictionary) { + if (dictionary[key]?.hashed) { + columns.push(key); + } } - public getHashedColumns(): Columns { - const dictionary: Dictionary = - getTableColumns(this); - const columns: Array = []; - for (const key in dictionary) { - if (dictionary[key]?.hashed) { - columns.push(key); - } + return new Columns(columns); + } + + public getDisplayColumnPlaceholderAs(columnName: string): string | null { + return getTableColumn(this, columnName)?.placeholder || null; + } + + public getDisplayColumnTitleAs(columnName: string): string | null { + return getTableColumn(this, columnName)?.title || null; + } + + public getDisplayColumnDescriptionAs(columnName: string): string | null { + return getTableColumn(this, columnName)?.description || null; + } + + public getEncryptedColumns(): Columns { + const dictionary: Dictionary = getTableColumns(this); + const columns: Array = []; + for (const key in dictionary) { + if (dictionary[key]?.encrypted) { + columns.push(key); + } + } + + return new Columns(columns); + } + + public getTableColumns(): Columns { + return new Columns(Object.keys(getTableColumns(this))); + } + + public canQueryMultiTenant(): boolean { + return Boolean(this.isMultiTenantRequestAllowed); + } + + public isUserQueryWithoutTenantAllowed(): boolean { + return Boolean(this.allowUserQueryWithoutTenant); + } + + public getTableColumnMetadata(columnName: string): TableColumnMetadata { + const dictionary: Dictionary = getTableColumns(this); + return dictionary[columnName] as TableColumnMetadata; + } + + public hasColumn(columnName: string): boolean { + return Boolean(getTableColumn(this, columnName)); + } + + public getColumnBillingAccessControl( + columnName: string, + ): ColumnBillingAccessControl { + const dictionary: Dictionary = + getColumnBillingAccessControlForAllColumns(this); + return dictionary[columnName] as ColumnBillingAccessControl; + } + + public getColumnAccessControlFor( + columnName: string, + ): ColumnAccessControl | null { + return this.getColumnAccessControlForAllColumns()[columnName] || null; + } + + public getColumnAccessControlForAllColumns(): Dictionary { + const dictionary: Dictionary = + getColumnAccessControlForAllColumns(this); + + const defaultColumns: Array = [ + "_id", + "createdAt", + "deletedAt", + "updatedAt", + ]; + + for (const key of defaultColumns) { + dictionary[key] = { + read: this.readRecordPermissions, + create: this.createRecordPermissions, + update: this.updateRecordPermissions, + }; + } + + return dictionary; + } + + public hasValue(columnName: string): boolean { + return Boolean((this as any)[columnName]); + } + + public getValue(columnName: string): T { + return (this as any)[columnName] as T; + } + + public setValue(columnName: string, value: T): void { + (this as any)[columnName] = value; + } + + public removeValue(columnName: string): void { + (this as any)[columnName] = undefined; + } + + public doesPermissionHaveConditions( + permission: Permission, + ): JSONObject | null { + return this.isPermissionIf[permission] + ? (this.isPermissionIf[permission] as JSONObject) + : null; + } + + public getUniqueColumns(): Columns { + const dictionary: Dictionary = getTableColumns(this); + const columns: Array = []; + for (const key in dictionary) { + if (dictionary[key]?.unique) { + columns.push(key); + } + } + + return new Columns(columns); + } + + public setSlugifyColumn(columnName: string): void { + this.slugifyColumn = columnName; + } + + public getTotalItemsByColumnName(): string | null { + return this.totalItemsByColumnName; + } + + public getTotalItemsByErrorMessage(): string | null { + return this.totalItemsErrorMessage; + } + + public getTotalItemsNumber(): number | null { + return this.totalItemsNumber; + } + + public getRequiredColumns(): Columns { + const dictionary: Dictionary = getTableColumns(this); + const columns: Array = []; + for (const key in dictionary) { + if (dictionary[key]?.required) { + columns.push(key); + } + } + + return new Columns(columns); + } + + public getSlugifyColumn(): string | null { + return this.slugifyColumn; + } + + public getCrudApiPath(): Route | null { + return this.crudApiPath; + } + + public getSaveSlugToColumn(): string | null { + return this.saveSlugToColumn; + } + + public getTenantColumn(): string | null { + return this.tenantColumn; + } + + public getAccessControlColumn(): string | null { + return this.accessControlColumn; + } + + public getUserColumn(): string | null { + return this.currentUserCanAccessColumnBy; + } + + public getLabelsColumn(): string | null { + return this.labelsColumn; + } + + public get id(): ObjectID | null { + return this._id ? new ObjectID(this._id) : null; + } + + public set id(value: ObjectID | null) { + if (value) { + this._id = value.toString(); + } + } + + public isDefaultValueColumn(columnName: string): boolean { + return Boolean(getTableColumn(this, columnName).isDefaultValueColumn); + } + + public getColumnValue( + columnName: string, + ): JSONValue | BaseModel | Array | null { + if (getTableColumn(this, columnName) && (this as any)[columnName]) { + return (this as any)[columnName] as JSONValue; + } + + return null; + } + + public setColumnValue( + columnName: string, + value: JSONValue | BaseModel | Array, + ): void { + if (getTableColumn(this, columnName)) { + return ((this as any)[columnName] = value as any); + } + } + + public isTableColumn(columnName: string): boolean { + return Boolean(getTableColumn(this, columnName)); + } + + public isEntityColumn(columnName: string): boolean { + const tableColumnType: TableColumnMetadata = getTableColumn( + this, + columnName, + ); + + if (!tableColumnType) { + throw new BadDataException( + "TableColumnMetadata not found for " + columnName + " column", + ); + } + + return Boolean( + tableColumnType.type === TableColumnType.Entity || + tableColumnType.type === TableColumnType.EntityArray, + ); + } + + public isHashedStringColumn(columnName: string): boolean { + const tableColumnType: TableColumnMetadata = getTableColumn( + this, + columnName, + ); + + if (!tableColumnType) { + throw new BadDataException( + "TableColumnMetadata not found for " + columnName + " column", + ); + } + + return Boolean(tableColumnType.type === TableColumnType.HashedString); + } + + public isFileColumn(columnName: string): boolean { + const tableColumnType: TableColumnMetadata = getTableColumn( + this, + columnName, + ); + + if (!tableColumnType || !tableColumnType.modelType) { + return false; + } + + const fileModel: BaseModel = new tableColumnType.modelType(); + + if (fileModel.isFileModel()) { + return true; + } + + return false; + } + + public hasPermission(_permissions: Array): boolean { + return false; + } + + public isTenantModel(): boolean { + return false; + } + + public isFileModel(): boolean { + return false; + } + + public isAccessControlModel(): boolean { + return false; + } + + public isUserModel(): boolean { + return false; + } + + public hasCreatePermissions( + userProjectPermissions: UserTenantAccessPermission | Array, + columnName?: string, + ): boolean { + let modelPermission: Array = this.createRecordPermissions; + + if (columnName) { + const columnAccessControl: ColumnAccessControl | null = + this.getColumnAccessControlFor(columnName); + if (columnAccessControl) { + modelPermission = columnAccessControl.create; + } + } + + return ModelPermission.hasPermissions( + userProjectPermissions, + modelPermission, + ); + } + + public getReadPermissions(): Array { + return this.readRecordPermissions; + } + + public getReadBillingPlan(): PlanSelect | null { + return this.readBillingPlan; + } + + public getCreateBillingPlan(): PlanSelect | null { + return this.createBillingPlan; + } + + public getUpdateBillingPlan(): PlanSelect | null { + return this.updateBillingPlan; + } + + public getDeleteBillingPlan(): PlanSelect | null { + return this.deleteBillingPlan; + } + + public getCreatePermissions(): Array { + return this.createRecordPermissions; + } + + public getUpdatePermissions(): Array { + return this.updateRecordPermissions; + } + + public getDeletePermissions(): Array { + return this.deleteRecordPermissions; + } + + public hasReadPermissions( + userProjectPermissions: UserTenantAccessPermission | Array, + columnName?: string, + ): boolean { + let modelPermission: Array = this.readRecordPermissions; + + if (columnName) { + const columnAccessControl: ColumnAccessControl | null = + this.getColumnAccessControlFor(columnName); + if (columnAccessControl) { + modelPermission = columnAccessControl.read; + } + } + + return ModelPermission.hasPermissions( + userProjectPermissions, + modelPermission, + ); + } + + public hasDeletePermissions( + userProjectPermissions: UserTenantAccessPermission | Array, + ): boolean { + const modelPermission: Array = this.deleteRecordPermissions; + return ModelPermission.hasPermissions( + userProjectPermissions, + modelPermission, + ); + } + + public hasUpdatePermissions( + userProjectPermissions: UserTenantAccessPermission | Array, + columnName?: string, + ): boolean { + let modelPermission: Array = this.updateRecordPermissions; + + if (columnName) { + const columnAccessControl: ColumnAccessControl | null = + this.getColumnAccessControlFor(columnName); + if (columnAccessControl) { + modelPermission = columnAccessControl.update; + } + } + + return ModelPermission.hasPermissions( + userProjectPermissions, + modelPermission, + ); + } + + public getAPIDocumentationPath(): string { + return Text.pascalCaseToDashes(this.tableName as string); + } + + public static toJSON(model: BaseModel, modelType: BaseModelType): JSONObject { + const json: JSONObject = this.toJSONObject(model, modelType); + return JSONFunctions.serialize(json); + } + + public static toJSONObject( + model: BaseModel, + modelType: BaseModelType, + ): JSONObject { + const json: JSONObject = {}; + + const vanillaModel: BaseModel = new modelType(); + + for (const key of vanillaModel.getTableColumns().columns) { + if ((model as any)[key] === undefined) { + continue; + } + + const tableColumnMetadata: TableColumnMetadata = + vanillaModel.getTableColumnMetadata(key); + + if (tableColumnMetadata) { + if ( + (model as any)[key] && + tableColumnMetadata.modelType && + tableColumnMetadata.type === TableColumnType.Entity && + (model as any)[key] instanceof BaseModel + ) { + (json as any)[key] = this.toJSONObject( + (model as any)[key], + tableColumnMetadata.modelType, + ); + } else if ( + (model as any)[key] && + Array.isArray((model as any)[key]) && + (model as any)[key].length > 0 && + tableColumnMetadata.modelType && + tableColumnMetadata.type === TableColumnType.EntityArray + ) { + (json as any)[key] = this.toJSONObjectArray( + (model as any)[key] as Array, + tableColumnMetadata.modelType, + ); + } else { + (json as any)[key] = (model as any)[key]; + } + } + } + + return json; + } + + public static toJSONObjectArray( + list: Array, + modelType: BaseModelType, + ): JSONArray { + const array: JSONArray = []; + + for (const item of list) { + array.push(this.toJSONObject(item, modelType)); + } + + return array; + } + + public static toJSONArray( + list: Array, + modelType: BaseModelType, + ): JSONArray { + const array: JSONArray = []; + + for (const item of list) { + array.push(this.toJSON(item, modelType)); + } + + return array; + } + + private static _fromJSON( + json: JSONObject | T, + type: { new (): T }, + ): T { + if (json instanceof BaseModel) { + return json; + } + + json = JSONFunctions.deserialize(json); + const baseModel: T = new type(); + + for (const key of Object.keys(json)) { + const tableColumnMetadata: TableColumnMetadata = + baseModel.getTableColumnMetadata(key); + if (tableColumnMetadata) { + if ( + json[key] && + tableColumnMetadata.modelType && + tableColumnMetadata.type === TableColumnType.Entity + ) { + if ( + json[key] && + Array.isArray(json[key]) && + (json[key] as Array).length > 0 + ) { + json[key] = (json[key] as Array)[0]; + } + + (baseModel as any)[key] = this.fromJSON( + json[key] as JSONObject, + tableColumnMetadata.modelType, + ); + } else if ( + json[key] && + tableColumnMetadata.modelType && + tableColumnMetadata.type === TableColumnType.EntityArray + ) { + if (json[key] && !Array.isArray(json[key])) { + json[key] = [json[key]]; + } + + (baseModel as any)[key] = this.fromJSONArray( + json[key] as JSONArray, + tableColumnMetadata.modelType, + ); + } else { + (baseModel as any)[key] = json[key]; + } + } + } + + return baseModel as T; + } + + public static fromJSON( + json: JSONObject | JSONArray | BaseModel | Array, + type: { new (): T }, + ): T | Array { + if (Array.isArray(json)) { + const arr: Array = []; + + for (const item of json) { + if (item instanceof BaseModel) { + arr.push(item as T); + continue; } - return new Columns(columns); - } - - public getDisplayColumnPlaceholderAs(columnName: string): string | null { - return getTableColumn(this, columnName)?.placeholder || null; - } - - public getDisplayColumnTitleAs(columnName: string): string | null { - return getTableColumn(this, columnName)?.title || null; - } - - public getDisplayColumnDescriptionAs(columnName: string): string | null { - return getTableColumn(this, columnName)?.description || null; - } - - public getEncryptedColumns(): Columns { - const dictionary: Dictionary = - getTableColumns(this); - const columns: Array = []; - for (const key in dictionary) { - if (dictionary[key]?.encrypted) { - columns.push(key); - } - } - - return new Columns(columns); - } - - public getTableColumns(): Columns { - return new Columns(Object.keys(getTableColumns(this))); - } - - public canQueryMultiTenant(): boolean { - return Boolean(this.isMultiTenantRequestAllowed); - } - - public isUserQueryWithoutTenantAllowed(): boolean { - return Boolean(this.allowUserQueryWithoutTenant); - } - - public getTableColumnMetadata(columnName: string): TableColumnMetadata { - const dictionary: Dictionary = - getTableColumns(this); - return dictionary[columnName] as TableColumnMetadata; - } - - public hasColumn(columnName: string): boolean { - return Boolean(getTableColumn(this, columnName)); - } - - public getColumnBillingAccessControl( - columnName: string - ): ColumnBillingAccessControl { - const dictionary: Dictionary = - getColumnBillingAccessControlForAllColumns(this); - return dictionary[columnName] as ColumnBillingAccessControl; - } - - public getColumnAccessControlFor( - columnName: string - ): ColumnAccessControl | null { - return this.getColumnAccessControlForAllColumns()[columnName] || null; - } - - public getColumnAccessControlForAllColumns(): Dictionary { - const dictionary: Dictionary = - getColumnAccessControlForAllColumns(this); - - const defaultColumns: Array = [ - '_id', - 'createdAt', - 'deletedAt', - 'updatedAt', - ]; - - for (const key of defaultColumns) { - dictionary[key] = { - read: this.readRecordPermissions, - create: this.createRecordPermissions, - update: this.updateRecordPermissions, - }; - } - - return dictionary; - } - - public hasValue(columnName: string): boolean { - return Boolean((this as any)[columnName]); - } - - public getValue(columnName: string): T { - return (this as any)[columnName] as T; - } - - public setValue(columnName: string, value: T): void { - (this as any)[columnName] = value; - } - - public removeValue(columnName: string): void { - (this as any)[columnName] = undefined; - } - - public doesPermissionHaveConditions( - permission: Permission - ): JSONObject | null { - return this.isPermissionIf[permission] - ? (this.isPermissionIf[permission] as JSONObject) - : null; - } - - public getUniqueColumns(): Columns { - const dictionary: Dictionary = - getTableColumns(this); - const columns: Array = []; - for (const key in dictionary) { - if (dictionary[key]?.unique) { - columns.push(key); - } - } - - return new Columns(columns); - } - - public setSlugifyColumn(columnName: string): void { - this.slugifyColumn = columnName; - } - - public getTotalItemsByColumnName(): string | null { - return this.totalItemsByColumnName; - } - - public getTotalItemsByErrorMessage(): string | null { - return this.totalItemsErrorMessage; - } - - public getTotalItemsNumber(): number | null { - return this.totalItemsNumber; - } - - public getRequiredColumns(): Columns { - const dictionary: Dictionary = - getTableColumns(this); - const columns: Array = []; - for (const key in dictionary) { - if (dictionary[key]?.required) { - columns.push(key); - } - } - - return new Columns(columns); - } - - public getSlugifyColumn(): string | null { - return this.slugifyColumn; - } - - public getCrudApiPath(): Route | null { - return this.crudApiPath; - } - - public getSaveSlugToColumn(): string | null { - return this.saveSlugToColumn; - } - - public getTenantColumn(): string | null { - return this.tenantColumn; - } - - public getAccessControlColumn(): string | null { - return this.accessControlColumn; - } - - public getUserColumn(): string | null { - return this.currentUserCanAccessColumnBy; - } - - public getLabelsColumn(): string | null { - return this.labelsColumn; - } - - public get id(): ObjectID | null { - return this._id ? new ObjectID(this._id) : null; - } - - public set id(value: ObjectID | null) { - if (value) { - this._id = value.toString(); - } - } - - public isDefaultValueColumn(columnName: string): boolean { - return Boolean(getTableColumn(this, columnName).isDefaultValueColumn); - } - - public getColumnValue( - columnName: string - ): JSONValue | BaseModel | Array | null { - if (getTableColumn(this, columnName) && (this as any)[columnName]) { - return (this as any)[columnName] as JSONValue; - } - - return null; - } - - public setColumnValue( - columnName: string, - value: JSONValue | BaseModel | Array - ): void { - if (getTableColumn(this, columnName)) { - return ((this as any)[columnName] = value as any); - } - } - - public isTableColumn(columnName: string): boolean { - return Boolean(getTableColumn(this, columnName)); - } - - public isEntityColumn(columnName: string): boolean { - const tableColumnType: TableColumnMetadata = getTableColumn( - this, - columnName - ); + arr.push(this._fromJSON(item, type)); + } - if (!tableColumnType) { - throw new BadDataException( - 'TableColumnMetadata not found for ' + columnName + ' column' - ); - } - - return Boolean( - tableColumnType.type === TableColumnType.Entity || - tableColumnType.type === TableColumnType.EntityArray - ); + return arr; } - public isHashedStringColumn(columnName: string): boolean { - const tableColumnType: TableColumnMetadata = getTableColumn( - this, - columnName - ); - - if (!tableColumnType) { - throw new BadDataException( - 'TableColumnMetadata not found for ' + columnName + ' column' - ); - } - - return Boolean(tableColumnType.type === TableColumnType.HashedString); + if (json instanceof BaseModel) { + return json as T; } - public isFileColumn(columnName: string): boolean { - const tableColumnType: TableColumnMetadata = getTableColumn( - this, - columnName - ); - - if (!tableColumnType || !tableColumnType.modelType) { - return false; - } - - const fileModel: BaseModel = new tableColumnType.modelType(); - - if (fileModel.isFileModel()) { - return true; - } + return this._fromJSON(json, type); + } - return false; + public static fromJSONObject( + json: JSONObject | T, + type: { new (): T }, + ): T { + if (json instanceof BaseModel) { + return json; } - public hasPermission(_permissions: Array): boolean { - return false; - } + return this.fromJSON(json, type) as T; + } - public isTenantModel(): boolean { - return false; - } + public static fromJSONArray( + json: Array, + type: { new (): T }, + ): Array { + const arr: Array = []; - public isFileModel(): boolean { - return false; + for (const item of json) { + arr.push(this._fromJSON(item, type)); } - public isAccessControlModel(): boolean { - return false; - } - - public isUserModel(): boolean { - return false; - } - - public hasCreatePermissions( - userProjectPermissions: UserTenantAccessPermission | Array, - columnName?: string - ): boolean { - let modelPermission: Array = this.createRecordPermissions; - - if (columnName) { - const columnAccessControl: ColumnAccessControl | null = - this.getColumnAccessControlFor(columnName); - if (columnAccessControl) { - modelPermission = columnAccessControl.create; - } - } - - return ModelPermission.hasPermissions( - userProjectPermissions, - modelPermission - ); - } - - public getReadPermissions(): Array { - return this.readRecordPermissions; - } - - public getReadBillingPlan(): PlanSelect | null { - return this.readBillingPlan; - } - - public getCreateBillingPlan(): PlanSelect | null { - return this.createBillingPlan; - } - - public getUpdateBillingPlan(): PlanSelect | null { - return this.updateBillingPlan; - } - - public getDeleteBillingPlan(): PlanSelect | null { - return this.deleteBillingPlan; - } - - public getCreatePermissions(): Array { - return this.createRecordPermissions; - } - - public getUpdatePermissions(): Array { - return this.updateRecordPermissions; - } - - public getDeletePermissions(): Array { - return this.deleteRecordPermissions; - } - - public hasReadPermissions( - userProjectPermissions: UserTenantAccessPermission | Array, - columnName?: string - ): boolean { - let modelPermission: Array = this.readRecordPermissions; - - if (columnName) { - const columnAccessControl: ColumnAccessControl | null = - this.getColumnAccessControlFor(columnName); - if (columnAccessControl) { - modelPermission = columnAccessControl.read; - } - } - - return ModelPermission.hasPermissions( - userProjectPermissions, - modelPermission - ); - } - - public hasDeletePermissions( - userProjectPermissions: UserTenantAccessPermission | Array - ): boolean { - const modelPermission: Array = this.deleteRecordPermissions; - return ModelPermission.hasPermissions( - userProjectPermissions, - modelPermission - ); - } - - public hasUpdatePermissions( - userProjectPermissions: UserTenantAccessPermission | Array, - columnName?: string - ): boolean { - let modelPermission: Array = this.updateRecordPermissions; - - if (columnName) { - const columnAccessControl: ColumnAccessControl | null = - this.getColumnAccessControlFor(columnName); - if (columnAccessControl) { - modelPermission = columnAccessControl.update; - } - } - - return ModelPermission.hasPermissions( - userProjectPermissions, - modelPermission - ); - } - - public getAPIDocumentationPath(): string { - return Text.pascalCaseToDashes(this.tableName as string); - } - - public static toJSON( - model: BaseModel, - modelType: BaseModelType - ): JSONObject { - const json: JSONObject = this.toJSONObject(model, modelType); - return JSONFunctions.serialize(json); - } - - public static toJSONObject( - model: BaseModel, - modelType: BaseModelType - ): JSONObject { - const json: JSONObject = {}; - - const vanillaModel: BaseModel = new modelType(); - - for (const key of vanillaModel.getTableColumns().columns) { - if ((model as any)[key] === undefined) { - continue; - } - - const tableColumnMetadata: TableColumnMetadata = - vanillaModel.getTableColumnMetadata(key); - - if (tableColumnMetadata) { - if ( - (model as any)[key] && - tableColumnMetadata.modelType && - tableColumnMetadata.type === TableColumnType.Entity && - (model as any)[key] instanceof BaseModel - ) { - (json as any)[key] = this.toJSONObject( - (model as any)[key], - tableColumnMetadata.modelType - ); - } else if ( - (model as any)[key] && - Array.isArray((model as any)[key]) && - (model as any)[key].length > 0 && - tableColumnMetadata.modelType && - tableColumnMetadata.type === TableColumnType.EntityArray - ) { - (json as any)[key] = this.toJSONObjectArray( - (model as any)[key] as Array, - tableColumnMetadata.modelType - ); - } else { - (json as any)[key] = (model as any)[key]; - } - } - } - - return json; - } - - public static toJSONObjectArray( - list: Array, - modelType: BaseModelType - ): JSONArray { - const array: JSONArray = []; - - for (const item of list) { - array.push(this.toJSONObject(item, modelType)); - } - - return array; - } - - public static toJSONArray( - list: Array, - modelType: BaseModelType - ): JSONArray { - const array: JSONArray = []; - - for (const item of list) { - array.push(this.toJSON(item, modelType)); - } - - return array; - } - - private static _fromJSON( - json: JSONObject | T, - type: { new (): T } - ): T { - if (json instanceof BaseModel) { - return json; - } - - json = JSONFunctions.deserialize(json); - const baseModel: T = new type(); - - for (const key of Object.keys(json)) { - const tableColumnMetadata: TableColumnMetadata = - baseModel.getTableColumnMetadata(key); - if (tableColumnMetadata) { - if ( - json[key] && - tableColumnMetadata.modelType && - tableColumnMetadata.type === TableColumnType.Entity - ) { - if ( - json[key] && - Array.isArray(json[key]) && - (json[key] as Array).length > 0 - ) { - json[key] = (json[key] as Array)[0]; - } - - (baseModel as any)[key] = this.fromJSON( - json[key] as JSONObject, - tableColumnMetadata.modelType - ); - } else if ( - json[key] && - tableColumnMetadata.modelType && - tableColumnMetadata.type === TableColumnType.EntityArray - ) { - if (json[key] && !Array.isArray(json[key])) { - json[key] = [json[key]]; - } - - (baseModel as any)[key] = this.fromJSONArray( - json[key] as JSONArray, - tableColumnMetadata.modelType - ); - } else { - (baseModel as any)[key] = json[key]; - } - } - } - - return baseModel as T; - } - - public static fromJSON( - json: JSONObject | JSONArray | BaseModel | Array, - type: { new (): T } - ): T | Array { - if (Array.isArray(json)) { - const arr: Array = []; - - for (const item of json) { - if (item instanceof BaseModel) { - arr.push(item as T); - continue; - } - - arr.push(this._fromJSON(item, type)); - } - - return arr; - } - - if (json instanceof BaseModel) { - return json as T; - } - - return this._fromJSON(json, type); - } - - public static fromJSONObject( - json: JSONObject | T, - type: { new (): T } - ): T { - if (json instanceof BaseModel) { - return json; - } - - return this.fromJSON(json, type) as T; - } - - public static fromJSONArray( - json: Array, - type: { new (): T } - ): Array { - const arr: Array = []; - - for (const item of json) { - arr.push(this._fromJSON(item, type)); - } - - return arr; - } + return arr; + } } diff --git a/Common/Models/FileModel.ts b/Common/Models/FileModel.ts index 751525a573..0a9597e9c9 100644 --- a/Common/Models/FileModel.ts +++ b/Common/Models/FileModel.ts @@ -1,111 +1,111 @@ -import ColumnAccessControl from '../Types/Database/AccessControl/ColumnAccessControl'; -import ColumnLength from '../Types/Database/ColumnLength'; -import ColumnType from '../Types/Database/ColumnType'; -import SlugifyColumn from '../Types/Database/SlugifyColumn'; -import TableColumn from '../Types/Database/TableColumn'; -import TableColumnType from '../Types/Database/TableColumnType'; -import MimeType from '../Types/File/MimeType'; -import ObjectID from '../Types/ObjectID'; -import Permission from '../Types/Permission'; -import BaseModel from './BaseModel'; -import { Column } from 'typeorm'; +import ColumnAccessControl from "../Types/Database/AccessControl/ColumnAccessControl"; +import ColumnLength from "../Types/Database/ColumnLength"; +import ColumnType from "../Types/Database/ColumnType"; +import SlugifyColumn from "../Types/Database/SlugifyColumn"; +import TableColumn from "../Types/Database/TableColumn"; +import TableColumnType from "../Types/Database/TableColumnType"; +import MimeType from "../Types/File/MimeType"; +import ObjectID from "../Types/ObjectID"; +import Permission from "../Types/Permission"; +import BaseModel from "./BaseModel"; +import { Column } from "typeorm"; -@SlugifyColumn('name', 'slug') +@SlugifyColumn("name", "slug") export default class FileModel extends BaseModel { - public constructor(id?: ObjectID) { - super(id); - } + public constructor(id?: ObjectID) { + super(id); + } - public override isFileModel(): boolean { - return true; - } + public override isFileModel(): boolean { + return true; + } - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.File, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.File, - }) - public file?: Buffer = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.File, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.File, + }) + public file?: Buffer = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name of this object', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name of this object", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public type?: MimeType = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public type?: MimeType = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Slug, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - default: true, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public isPublic?: boolean = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Slug, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + default: true, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public isPublic?: boolean = undefined; } diff --git a/Common/Models/GlobalConfig.ts b/Common/Models/GlobalConfig.ts index 8f358edeed..1980b3f3fa 100644 --- a/Common/Models/GlobalConfig.ts +++ b/Common/Models/GlobalConfig.ts @@ -1,8 +1,8 @@ -import ObjectID from '../Types/ObjectID'; -import BaseModel from './BaseModel'; +import ObjectID from "../Types/ObjectID"; +import BaseModel from "./BaseModel"; export default class GlobalConfig extends BaseModel { - public constructor(id?: ObjectID) { - super(id); - } + public constructor(id?: ObjectID) { + super(id); + } } diff --git a/Common/Models/TenantModel.ts b/Common/Models/TenantModel.ts index ca6eb618f3..e1dbf3e41d 100644 --- a/Common/Models/TenantModel.ts +++ b/Common/Models/TenantModel.ts @@ -1,12 +1,12 @@ -import ObjectID from '../Types/ObjectID'; -import BaseModel from './BaseModel'; +import ObjectID from "../Types/ObjectID"; +import BaseModel from "./BaseModel"; export default class TenantModel extends BaseModel { - public constructor(id?: ObjectID) { - super(id); - } + public constructor(id?: ObjectID) { + super(id); + } - public override isTenantModel(): boolean { - return true; - } + public override isTenantModel(): boolean { + return true; + } } diff --git a/Common/Models/UserModel.ts b/Common/Models/UserModel.ts index a35ad98176..9c1280d6b0 100644 --- a/Common/Models/UserModel.ts +++ b/Common/Models/UserModel.ts @@ -1,12 +1,12 @@ -import ObjectID from '../Types/ObjectID'; -import BaseModel from './BaseModel'; +import ObjectID from "../Types/ObjectID"; +import BaseModel from "./BaseModel"; export default class UserModel extends BaseModel { - public constructor(id?: ObjectID) { - super(id); - } + public constructor(id?: ObjectID) { + super(id); + } - public override isUserModel(): boolean { - return true; - } + public override isUserModel(): boolean { + return true; + } } diff --git a/Common/ServiceRoute.ts b/Common/ServiceRoute.ts index 71cddb3417..1fc6a9d12e 100644 --- a/Common/ServiceRoute.ts +++ b/Common/ServiceRoute.ts @@ -1,33 +1,33 @@ -import Route from './Types/API/Route'; +import Route from "./Types/API/Route"; -export const HomeRoute: Route = new Route('/'); +export const HomeRoute: Route = new Route("/"); -export const AppApiRoute: Route = new Route('/api'); +export const AppApiRoute: Route = new Route("/api"); -export const IdentityRoute: Route = new Route('/identity'); +export const IdentityRoute: Route = new Route("/identity"); -export const FileRoute: Route = new Route('/file'); +export const FileRoute: Route = new Route("/file"); -export const StatusPageRoute: Route = new Route('/status-page'); +export const StatusPageRoute: Route = new Route("/status-page"); -export const LinkShortenerRoute: Route = new Route('/l'); +export const LinkShortenerRoute: Route = new Route("/l"); -export const DashboardRoute: Route = new Route('/dashboard'); +export const DashboardRoute: Route = new Route("/dashboard"); -export const IntegrationRoute: Route = new Route('/integration'); +export const IntegrationRoute: Route = new Route("/integration"); -export const NotificationRoute: Route = new Route('/notification'); +export const NotificationRoute: Route = new Route("/notification"); -export const AccountsRoute: Route = new Route('/accounts'); +export const AccountsRoute: Route = new Route("/accounts"); -export const WorkflowRoute: Route = new Route('/workflow'); +export const WorkflowRoute: Route = new Route("/workflow"); -export const ApiReferenceRoute: Route = new Route('/reference'); +export const ApiReferenceRoute: Route = new Route("/reference"); -export const AdminDashboardRoute: Route = new Route('/admin'); +export const AdminDashboardRoute: Route = new Route("/admin"); -export const IngestorRoute: Route = new Route('/ingestor'); +export const IngestorRoute: Route = new Route("/ingestor"); -export const RealtimeRoute: Route = new Route('/realtime/socket'); +export const RealtimeRoute: Route = new Route("/realtime/socket"); -export const DocsRoute: Route = new Route('/docs'); +export const DocsRoute: Route = new Route("/docs"); diff --git a/Common/Tests/Types/API/ErrorResponse.test.ts b/Common/Tests/Types/API/ErrorResponse.test.ts index 4fb3a7e73a..aebe94ea03 100644 --- a/Common/Tests/Types/API/ErrorResponse.test.ts +++ b/Common/Tests/Types/API/ErrorResponse.test.ts @@ -1,17 +1,17 @@ -import ErrorResponse from '../../../Types/API/HTTPErrorResponse'; +import ErrorResponse from "../../../Types/API/HTTPErrorResponse"; -describe('ErrorResponse', () => { - test('should return a valid error response object', () => { - const errorResponseObject: ErrorResponse = new ErrorResponse( - 500, - { - error: 'Internal Server Error', - }, - {} - ); - expect(errorResponseObject.statusCode).toBe(500); - expect(errorResponseObject.data).toEqual({ - error: 'Internal Server Error', - }); +describe("ErrorResponse", () => { + test("should return a valid error response object", () => { + const errorResponseObject: ErrorResponse = new ErrorResponse( + 500, + { + error: "Internal Server Error", + }, + {}, + ); + expect(errorResponseObject.statusCode).toBe(500); + expect(errorResponseObject.data).toEqual({ + error: "Internal Server Error", }); + }); }); diff --git a/Common/Tests/Types/API/HTTPErrorResponse.test.ts b/Common/Tests/Types/API/HTTPErrorResponse.test.ts index 2108b0d105..fb7220081c 100644 --- a/Common/Tests/Types/API/HTTPErrorResponse.test.ts +++ b/Common/Tests/Types/API/HTTPErrorResponse.test.ts @@ -1,62 +1,62 @@ -import HTTPErrorResponse from '../../../Types/API/HTTPErrorResponse'; -import { describe, expect, it } from '@jest/globals'; +import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse"; +import { describe, expect, it } from "@jest/globals"; -describe('HTTPErrorResponse', () => { - it('should return an empty string when data is null', () => { - const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( - 404, - { data: null }, - {} - ); - expect(httpResponse.message).toBe(''); - }); +describe("HTTPErrorResponse", () => { + it("should return an empty string when data is null", () => { + const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( + 404, + { data: null }, + {}, + ); + expect(httpResponse.message).toBe(""); + }); - it('should return the message from the "data" property if present', () => { - const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( - 200, - { data: 'Data message' }, - {} - ); - expect(httpResponse.message).toBe('Data message'); - }); + it('should return the message from the "data" property if present', () => { + const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( + 200, + { data: "Data message" }, + {}, + ); + expect(httpResponse.message).toBe("Data message"); + }); - it('should return the message from the "message" property if present', () => { - const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( - 200, - { message: 'Message message' }, - {} - ); - expect(httpResponse.message).toBe('Message message'); - }); + it('should return the message from the "message" property if present', () => { + const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( + 200, + { message: "Message message" }, + {}, + ); + expect(httpResponse.message).toBe("Message message"); + }); - it('should return the message from the "error" property if no other message properties are present', () => { - const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( - 500, - { error: 'Error message' }, - {} - ); - expect(httpResponse.message).toBe('Error message'); - }); + it('should return the message from the "error" property if no other message properties are present', () => { + const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( + 500, + { error: "Error message" }, + {}, + ); + expect(httpResponse.message).toBe("Error message"); + }); - it('should return an empty string when no relevant message properties are present', () => { - const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( - 204, - { otherProperty: 'Other message' }, - {} - ); - expect(httpResponse.message).toBe(''); - }); + it("should return an empty string when no relevant message properties are present", () => { + const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( + 204, + { otherProperty: "Other message" }, + {}, + ); + expect(httpResponse.message).toBe(""); + }); - it('should prioritize "data" > "message" > "error" when multiple message properties are present', () => { - const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( - 201, - { - data: 'Data message', - message: 'Message message', - error: 'Error message', - }, - {} - ); - expect(httpResponse.message).toBe('Data message'); - }); + it('should prioritize "data" > "message" > "error" when multiple message properties are present', () => { + const httpResponse: HTTPErrorResponse = new HTTPErrorResponse( + 201, + { + data: "Data message", + message: "Message message", + error: "Error message", + }, + {}, + ); + expect(httpResponse.message).toBe("Data message"); + }); }); diff --git a/Common/Tests/Types/API/HTTPMethod.test.ts b/Common/Tests/Types/API/HTTPMethod.test.ts index d8f36c9995..dde618dcb0 100644 --- a/Common/Tests/Types/API/HTTPMethod.test.ts +++ b/Common/Tests/Types/API/HTTPMethod.test.ts @@ -1,16 +1,16 @@ -import HTTPMethod from '../../../Types/API/HTTPMethod'; +import HTTPMethod from "../../../Types/API/HTTPMethod"; -describe('HTTPMethod', () => { - test('HTTPMethod.GET should be GET', () => { - expect(HTTPMethod.GET).toBe('GET'); - }); - test('HTTPMethod.POST should be POST', () => { - expect(HTTPMethod.POST).toBe('POST'); - }); - test('HTTPMethod.PUT should be PUT', () => { - expect(HTTPMethod.PUT).toBe('PUT'); - }); - test('HTTPMethod.DELETE should be DELETE', () => { - expect(HTTPMethod.DELETE).toBe('DELETE'); - }); +describe("HTTPMethod", () => { + test("HTTPMethod.GET should be GET", () => { + expect(HTTPMethod.GET).toBe("GET"); + }); + test("HTTPMethod.POST should be POST", () => { + expect(HTTPMethod.POST).toBe("POST"); + }); + test("HTTPMethod.PUT should be PUT", () => { + expect(HTTPMethod.PUT).toBe("PUT"); + }); + test("HTTPMethod.DELETE should be DELETE", () => { + expect(HTTPMethod.DELETE).toBe("DELETE"); + }); }); diff --git a/Common/Tests/Types/API/Headers.test.ts b/Common/Tests/Types/API/Headers.test.ts index f786728433..43c6cefcb1 100644 --- a/Common/Tests/Types/API/Headers.test.ts +++ b/Common/Tests/Types/API/Headers.test.ts @@ -1,15 +1,15 @@ -import Headers from '../../../Types/API/Headers'; -import Faker from '../../../Utils/Faker'; +import Headers from "../../../Types/API/Headers"; +import Faker from "../../../Utils/Faker"; -describe('Headers', () => { - test('should compile', () => { - const apiKey: string = Faker.randomNumbers(16); - const headers: Headers = { - accept: 'application/json', - 'x-api-key': apiKey, - }; - expect(headers['accept']).toBe('application/json'); - expect(headers['x-api-key']).toEqual(apiKey); - expect(headers['undefined']).toBe(undefined); - }); +describe("Headers", () => { + test("should compile", () => { + const apiKey: string = Faker.randomNumbers(16); + const headers: Headers = { + accept: "application/json", + "x-api-key": apiKey, + }; + expect(headers["accept"]).toBe("application/json"); + expect(headers["x-api-key"]).toEqual(apiKey); + expect(headers["undefined"]).toBe(undefined); + }); }); diff --git a/Common/Tests/Types/API/Hostname.test.ts b/Common/Tests/Types/API/Hostname.test.ts index 009d84d24a..4fcaaaa88f 100644 --- a/Common/Tests/Types/API/Hostname.test.ts +++ b/Common/Tests/Types/API/Hostname.test.ts @@ -1,22 +1,22 @@ -import Hostname from '../../../Types/API/Hostname'; -import BadDataException from '../../../Types/Exception/BadDataException'; +import Hostname from "../../../Types/API/Hostname"; +import BadDataException from "../../../Types/Exception/BadDataException"; -describe('Hostname', () => { - test('new Hostname(hostname) should throw an error if invalid hostname is given', () => { - expect(() => { - return new Hostname('undefined undefined'); - }).toThrowError(BadDataException); - expect(() => { - return new Hostname('localhost 5000'); - }).toThrow(BadDataException); - expect(() => { - new Hostname('localhost:5000').hostname = 'localhost 6000'; - }).toThrow(BadDataException); - }); - test('new Hostname(hostname) should return a valid object', () => { - const hostnameObject: Hostname = new Hostname('localhost:5000'); - expect(hostnameObject.hostname).toBeTruthy(); - expect(hostnameObject.hostname).toBe('localhost:5000'); - expect(hostnameObject.toString()).toBe('localhost:5000'); - }); +describe("Hostname", () => { + test("new Hostname(hostname) should throw an error if invalid hostname is given", () => { + expect(() => { + return new Hostname("undefined undefined"); + }).toThrowError(BadDataException); + expect(() => { + return new Hostname("localhost 5000"); + }).toThrow(BadDataException); + expect(() => { + new Hostname("localhost:5000").hostname = "localhost 6000"; + }).toThrow(BadDataException); + }); + test("new Hostname(hostname) should return a valid object", () => { + const hostnameObject: Hostname = new Hostname("localhost:5000"); + expect(hostnameObject.hostname).toBeTruthy(); + expect(hostnameObject.hostname).toBe("localhost:5000"); + expect(hostnameObject.toString()).toBe("localhost:5000"); + }); }); diff --git a/Common/Tests/Types/API/Protocal.test.ts b/Common/Tests/Types/API/Protocal.test.ts index 8abc8ab9bd..585a4fc4cc 100644 --- a/Common/Tests/Types/API/Protocal.test.ts +++ b/Common/Tests/Types/API/Protocal.test.ts @@ -1,19 +1,19 @@ -import Protocol from '../../../Types/API/Protocol'; +import Protocol from "../../../Types/API/Protocol"; -describe('Protocol', () => { - test('Protocol.HTTPS should be https://', () => { - expect(Protocol.HTTPS).toBe('https://'); - }); - test('Protocol.HTTP should be https://', () => { - expect(Protocol.HTTP).toBe('http://'); - }); - test('Protocol.WS should be ws://', () => { - expect(Protocol.WS).toBe('ws://'); - }); - test('Protocol.WSS should be ws://', () => { - expect(Protocol.WSS).toBe('wss://'); - }); - test('Protocol.MONGO_DB should be mongodb://', () => { - expect(Protocol.MONGO_DB).toBe('mongodb://'); - }); +describe("Protocol", () => { + test("Protocol.HTTPS should be https://", () => { + expect(Protocol.HTTPS).toBe("https://"); + }); + test("Protocol.HTTP should be https://", () => { + expect(Protocol.HTTP).toBe("http://"); + }); + test("Protocol.WS should be ws://", () => { + expect(Protocol.WS).toBe("ws://"); + }); + test("Protocol.WSS should be ws://", () => { + expect(Protocol.WSS).toBe("wss://"); + }); + test("Protocol.MONGO_DB should be mongodb://", () => { + expect(Protocol.MONGO_DB).toBe("mongodb://"); + }); }); diff --git a/Common/Tests/Types/API/Response.test.ts b/Common/Tests/Types/API/Response.test.ts index 0a017b9830..4f3d37fd72 100644 --- a/Common/Tests/Types/API/Response.test.ts +++ b/Common/Tests/Types/API/Response.test.ts @@ -1,21 +1,21 @@ -import Response from '../../../Types/API/HTTPResponse'; -import { JSONObject } from '../../../Types/JSON'; +import Response from "../../../Types/API/HTTPResponse"; +import { JSONObject } from "../../../Types/JSON"; -describe('Response()', () => { - test('should return a valid response object', () => { - const responseObject: Response = new Response( - 200, - { - welcome: 'here', - }, - {} - ); - expect(responseObject.statusCode).toBe(200); - expect(responseObject.data).toEqual({ welcome: 'here' }); - const responseObjectArray: Response> = new Response< - Array - >(200, [{ welcome: 'here' }], {}); - expect(responseObjectArray.statusCode).toBe(200); - expect(responseObjectArray.data).toEqual([{ welcome: 'here' }]); - }); +describe("Response()", () => { + test("should return a valid response object", () => { + const responseObject: Response = new Response( + 200, + { + welcome: "here", + }, + {}, + ); + expect(responseObject.statusCode).toBe(200); + expect(responseObject.data).toEqual({ welcome: "here" }); + const responseObjectArray: Response> = new Response< + Array + >(200, [{ welcome: "here" }], {}); + expect(responseObjectArray.statusCode).toBe(200); + expect(responseObjectArray.data).toEqual([{ welcome: "here" }]); + }); }); diff --git a/Common/Tests/Types/API/ResponseType.test.ts b/Common/Tests/Types/API/ResponseType.test.ts index c8fb6eaa42..10d831d155 100644 --- a/Common/Tests/Types/API/ResponseType.test.ts +++ b/Common/Tests/Types/API/ResponseType.test.ts @@ -1,13 +1,13 @@ -import ResponseType from '../../../Types/API/ResponseType'; +import ResponseType from "../../../Types/API/ResponseType"; -describe('ResponseType', () => { - test('ResponseType.CSV to be csv', () => { - expect(ResponseType.CSV).toBe('csv'); - }); - test('ResponseType.HTML to be json', () => { - expect(ResponseType.HTML).toBe('html'); - }); - test('ResponseType.HTML to be', () => { - expect(ResponseType.JSON).toBe('json'); - }); +describe("ResponseType", () => { + test("ResponseType.CSV to be csv", () => { + expect(ResponseType.CSV).toBe("csv"); + }); + test("ResponseType.HTML to be json", () => { + expect(ResponseType.HTML).toBe("html"); + }); + test("ResponseType.HTML to be", () => { + expect(ResponseType.JSON).toBe("json"); + }); }); diff --git a/Common/Tests/Types/API/Route.test.ts b/Common/Tests/Types/API/Route.test.ts index 9288d3bc84..cac5a97ffc 100644 --- a/Common/Tests/Types/API/Route.test.ts +++ b/Common/Tests/Types/API/Route.test.ts @@ -1,30 +1,30 @@ -import Route from '../../../Types/API/Route'; -import BadDataException from '../../../Types/Exception/BadDataException'; +import Route from "../../../Types/API/Route"; +import BadDataException from "../../../Types/Exception/BadDataException"; -describe('Route', () => { - test('new Route() should throw an error if invalid route is passed', () => { - expect(() => { - return new Route('api test'); - }).toThrowError(BadDataException); - expect(() => { - return new Route('api\test'); - }).toThrowError(BadDataException); - expect(() => { - return new Route('api`test'); - }).toThrowError(BadDataException); - expect(() => { - return new Route('/api|test'); - }).toThrowError(BadDataException); - }); - test('new Route() should throw an error if invalid route is passed', () => { - const route: Route = new Route('/api/test'); - expect(() => { - route.route = 'api`test'; - }).toThrowError(BadDataException); - }); - test('Route.toString() should return valid string', () => { - expect(new Route('/api/test').toString()).toBe('/api/test'); - expect(new Route('/api#test').toString()).toBe('/api#test'); - expect(new Route('/api-test').toString()).toBe('/api-test'); - }); +describe("Route", () => { + test("new Route() should throw an error if invalid route is passed", () => { + expect(() => { + return new Route("api test"); + }).toThrowError(BadDataException); + expect(() => { + return new Route("api\test"); + }).toThrowError(BadDataException); + expect(() => { + return new Route("api`test"); + }).toThrowError(BadDataException); + expect(() => { + return new Route("/api|test"); + }).toThrowError(BadDataException); + }); + test("new Route() should throw an error if invalid route is passed", () => { + const route: Route = new Route("/api/test"); + expect(() => { + route.route = "api`test"; + }).toThrowError(BadDataException); + }); + test("Route.toString() should return valid string", () => { + expect(new Route("/api/test").toString()).toBe("/api/test"); + expect(new Route("/api#test").toString()).toBe("/api#test"); + expect(new Route("/api-test").toString()).toBe("/api-test"); + }); }); diff --git a/Common/Tests/Types/API/StatusCode.test.ts b/Common/Tests/Types/API/StatusCode.test.ts index cba401aeb8..080c46d365 100644 --- a/Common/Tests/Types/API/StatusCode.test.ts +++ b/Common/Tests/Types/API/StatusCode.test.ts @@ -1,26 +1,26 @@ -import StatusCode from '../../../Types/API/StatusCode'; -import BadDataException from '../../../Types/Exception/BadDataException'; +import StatusCode from "../../../Types/API/StatusCode"; +import BadDataException from "../../../Types/Exception/BadDataException"; -describe('Route', () => { - test('new StatusCode should throw if number is not passed', () => { - expect(() => { - return new StatusCode('invalid'); - }).toThrowError(BadDataException); - expect(() => { - return new StatusCode(-400); - }).toThrowError(BadDataException); - expect(() => { - return new StatusCode('-400'); - }).toThrowError(BadDataException); - }); - test('Route.toString() should return valid statuscode string', () => { - expect(new StatusCode('200').toString()).toEqual('200'); - expect(new StatusCode('100').toString()).toEqual('100'); - expect(new StatusCode(400).toString()).toEqual('400'); - }); - test('Route.toNumber() should return valid statuscode number', () => { - expect(new StatusCode('200').toNumber()).toEqual(200); - expect(new StatusCode('100').toNumber()).toEqual(100); - expect(new StatusCode('500').toNumber()).toEqual(500); - }); +describe("Route", () => { + test("new StatusCode should throw if number is not passed", () => { + expect(() => { + return new StatusCode("invalid"); + }).toThrowError(BadDataException); + expect(() => { + return new StatusCode(-400); + }).toThrowError(BadDataException); + expect(() => { + return new StatusCode("-400"); + }).toThrowError(BadDataException); + }); + test("Route.toString() should return valid statuscode string", () => { + expect(new StatusCode("200").toString()).toEqual("200"); + expect(new StatusCode("100").toString()).toEqual("100"); + expect(new StatusCode(400).toString()).toEqual("400"); + }); + test("Route.toNumber() should return valid statuscode number", () => { + expect(new StatusCode("200").toNumber()).toEqual(200); + expect(new StatusCode("100").toNumber()).toEqual(100); + expect(new StatusCode("500").toNumber()).toEqual(500); + }); }); diff --git a/Common/Tests/Types/API/URL.test.ts b/Common/Tests/Types/API/URL.test.ts index b1bc28b70d..59dc5962db 100644 --- a/Common/Tests/Types/API/URL.test.ts +++ b/Common/Tests/Types/API/URL.test.ts @@ -1,42 +1,42 @@ -import Hostname from '../../../Types/API/Hostname'; -import Protocol from '../../../Types/API/Protocol'; -import Route from '../../../Types/API/Route'; -import URL from '../../../Types/API/URL'; +import Hostname from "../../../Types/API/Hostname"; +import Protocol from "../../../Types/API/Protocol"; +import Route from "../../../Types/API/Route"; +import URL from "../../../Types/API/URL"; -describe('URL', () => { - test('new URL() should return a valid object', () => { - const url: URL = new URL( - Protocol.HTTPS, - new Hostname('localhost:5000'), - new Route('/api/test') - ); - expect(url.hostname).toBeInstanceOf(Hostname); - expect(url.protocol).toBe('https://'); - expect(url.route).toBeInstanceOf(Route); - expect(url.isHttps()).toBe(true); - expect(url.toString).toBeTruthy(); - }); - test('URL.fromString should create URL object', () => { - let url: URL = URL.fromString('https://localhost:5000/api/test'); - expect(url).toBeInstanceOf(URL); - expect(url.protocol).toBe('https://'); - url = URL.fromString('mongodb://localhost:27017/test'); - expect(url).toBeInstanceOf(URL); - expect(url.protocol).toBe('mongodb://'); - url = URL.fromString('ws://localhost:5000/api/test'); - expect(url).toBeInstanceOf(URL); - expect(url.protocol).toBe('ws://'); - url = URL.fromString('wss://localhost:5000/api/test'); - expect(url).toBeInstanceOf(URL); - expect(url.protocol).toBe('wss://'); - }); +describe("URL", () => { + test("new URL() should return a valid object", () => { + const url: URL = new URL( + Protocol.HTTPS, + new Hostname("localhost:5000"), + new Route("/api/test"), + ); + expect(url.hostname).toBeInstanceOf(Hostname); + expect(url.protocol).toBe("https://"); + expect(url.route).toBeInstanceOf(Route); + expect(url.isHttps()).toBe(true); + expect(url.toString).toBeTruthy(); + }); + test("URL.fromString should create URL object", () => { + let url: URL = URL.fromString("https://localhost:5000/api/test"); + expect(url).toBeInstanceOf(URL); + expect(url.protocol).toBe("https://"); + url = URL.fromString("mongodb://localhost:27017/test"); + expect(url).toBeInstanceOf(URL); + expect(url.protocol).toBe("mongodb://"); + url = URL.fromString("ws://localhost:5000/api/test"); + expect(url).toBeInstanceOf(URL); + expect(url.protocol).toBe("ws://"); + url = URL.fromString("wss://localhost:5000/api/test"); + expect(url).toBeInstanceOf(URL); + expect(url.protocol).toBe("wss://"); + }); - test('URL.toString should return a valid URL', () => { - const url: URL = new URL( - Protocol.HTTPS, - new Hostname('localhost:5000'), - new Route('/api/test') - ); - expect(url.toString()).toBe('https://localhost:5000/api/test'); - }); + test("URL.toString should return a valid URL", () => { + const url: URL = new URL( + Protocol.HTTPS, + new Hostname("localhost:5000"), + new Route("/api/test"), + ); + expect(url.toString()).toBe("https://localhost:5000/api/test"); + }); }); diff --git a/Common/Tests/Types/Alerts/AlertEventType.test.ts b/Common/Tests/Types/Alerts/AlertEventType.test.ts index 40f213dd09..a90ac4f396 100644 --- a/Common/Tests/Types/Alerts/AlertEventType.test.ts +++ b/Common/Tests/Types/Alerts/AlertEventType.test.ts @@ -1,49 +1,49 @@ -import AlertEventType from '../../../Types/Alerts/AlertEventType'; +import AlertEventType from "../../../Types/Alerts/AlertEventType"; -describe('AlertEventType', () => { - test('AlertEventType.Identified to be Identified', () => { - expect(AlertEventType.Identified).toBe('Identified'); - }); - test('AlertEventType.Acknowledged to Acknowledged', () => { - expect(AlertEventType.Acknowledged).toBe('Acknowledged'); - }); - test('AlertEventType.Resolved to Resolved', () => { - expect(AlertEventType.Resolved).toBe('Resolved'); - }); - test('AlertEventType.InvestigationNoteCreated to Investigation note created', () => { - expect(AlertEventType.InvestigationNoteCreated).toBe( - 'Investigation note created' - ); - }); - test('AlertEventType.InvestigationNoteUpdated to Investigation note updated', () => { - expect(AlertEventType.InvestigationNoteUpdated).toBe( - 'Investigation note updated' - ); - }); - test('AlertEventType.ScheduledMaintenanceCreated to Scheduled maintenance created', () => { - expect(AlertEventType.ScheduledMaintenanceCreated).toBe( - 'Scheduled maintenance created' - ); - }); - test('AlertEventType.ScheduledMaintenanceNoteCreated to Scheduled maintenance note Created', () => { - expect(AlertEventType.ScheduledMaintenanceNoteCreated).toBe( - 'Scheduled maintenance note created' - ); - }); +describe("AlertEventType", () => { + test("AlertEventType.Identified to be Identified", () => { + expect(AlertEventType.Identified).toBe("Identified"); + }); + test("AlertEventType.Acknowledged to Acknowledged", () => { + expect(AlertEventType.Acknowledged).toBe("Acknowledged"); + }); + test("AlertEventType.Resolved to Resolved", () => { + expect(AlertEventType.Resolved).toBe("Resolved"); + }); + test("AlertEventType.InvestigationNoteCreated to Investigation note created", () => { + expect(AlertEventType.InvestigationNoteCreated).toBe( + "Investigation note created", + ); + }); + test("AlertEventType.InvestigationNoteUpdated to Investigation note updated", () => { + expect(AlertEventType.InvestigationNoteUpdated).toBe( + "Investigation note updated", + ); + }); + test("AlertEventType.ScheduledMaintenanceCreated to Scheduled maintenance created", () => { + expect(AlertEventType.ScheduledMaintenanceCreated).toBe( + "Scheduled maintenance created", + ); + }); + test("AlertEventType.ScheduledMaintenanceNoteCreated to Scheduled maintenance note Created", () => { + expect(AlertEventType.ScheduledMaintenanceNoteCreated).toBe( + "Scheduled maintenance note created", + ); + }); - test('AlertEventType.ScheduledMaintenanceResolved to scheduled maintenance resolved', () => { - expect(AlertEventType.ScheduledMaintenanceResolved).toBe( - 'Scheduled maintenance resolved' - ); - }); - test('AlertEventType.Acknowledged to Scheduled maintenance cancelled', () => { - expect(AlertEventType.ScheduledMaintenanceCancelled).toBe( - 'Scheduled maintenance cancelled' - ); - }); - test('AlertEventType.AnnouncementNotificationCreated to Announcement notification created', () => { - expect(AlertEventType.AnnouncementNotificationCreated).toBe( - 'Announcement notification created' - ); - }); + test("AlertEventType.ScheduledMaintenanceResolved to scheduled maintenance resolved", () => { + expect(AlertEventType.ScheduledMaintenanceResolved).toBe( + "Scheduled maintenance resolved", + ); + }); + test("AlertEventType.Acknowledged to Scheduled maintenance cancelled", () => { + expect(AlertEventType.ScheduledMaintenanceCancelled).toBe( + "Scheduled maintenance cancelled", + ); + }); + test("AlertEventType.AnnouncementNotificationCreated to Announcement notification created", () => { + expect(AlertEventType.AnnouncementNotificationCreated).toBe( + "Announcement notification created", + ); + }); }); diff --git a/Common/Tests/Types/Alerts/AlertType.test.ts b/Common/Tests/Types/Alerts/AlertType.test.ts index 7217665d78..43106969b0 100644 --- a/Common/Tests/Types/Alerts/AlertType.test.ts +++ b/Common/Tests/Types/Alerts/AlertType.test.ts @@ -1,19 +1,19 @@ -import AlertType from '../../../Types/Alerts/AlertType'; +import AlertType from "../../../Types/Alerts/AlertType"; -describe('AlertType', () => { - test('AlertType.Email to be Email', () => { - expect(AlertType.Webhook).toBe('Webhook'); - }); - test('AlertType.Acknowledged to Acknowledged', () => { - expect(AlertType.Email).toBe('Email'); - }); - test('AlertType.SMS to SMS', () => { - expect(AlertType.SMS).toBe('SMS'); - }); - test('AlertType.Call to Call', () => { - expect(AlertType.Call).toBe('Call'); - }); - test('AlertType.PushNotification to PushNotification', () => { - expect(AlertType.PushNotification).toBe('PushNotification'); - }); +describe("AlertType", () => { + test("AlertType.Email to be Email", () => { + expect(AlertType.Webhook).toBe("Webhook"); + }); + test("AlertType.Acknowledged to Acknowledged", () => { + expect(AlertType.Email).toBe("Email"); + }); + test("AlertType.SMS to SMS", () => { + expect(AlertType.SMS).toBe("SMS"); + }); + test("AlertType.Call to Call", () => { + expect(AlertType.Call).toBe("Call"); + }); + test("AlertType.PushNotification to PushNotification", () => { + expect(AlertType.PushNotification).toBe("PushNotification"); + }); }); diff --git a/Common/Tests/Types/AppEnvironment.test.ts b/Common/Tests/Types/AppEnvironment.test.ts index cd25bd6254..2c0783ae23 100644 --- a/Common/Tests/Types/AppEnvironment.test.ts +++ b/Common/Tests/Types/AppEnvironment.test.ts @@ -1,13 +1,13 @@ -import AppEnvironment from '../../Types/AppEnvironment'; +import AppEnvironment from "../../Types/AppEnvironment"; -describe('AppEnvironment', () => { - test('AppEnvironment.Production should be production', () => { - expect(AppEnvironment.Production).toEqual('production'); - }); - test('AppEnvironment.Development should be production', () => { - expect(AppEnvironment.Development).toEqual('development'); - }); - test('AppEnvironment.Test should be test', () => { - expect(AppEnvironment.Test).toEqual('test'); - }); +describe("AppEnvironment", () => { + test("AppEnvironment.Production should be production", () => { + expect(AppEnvironment.Production).toEqual("production"); + }); + test("AppEnvironment.Development should be production", () => { + expect(AppEnvironment.Development).toEqual("development"); + }); + test("AppEnvironment.Test should be test", () => { + expect(AppEnvironment.Test).toEqual("test"); + }); }); diff --git a/Common/Tests/Types/ApplicationLog/ApplicationLogType.test.ts b/Common/Tests/Types/ApplicationLog/ApplicationLogType.test.ts index 81adfdf24b..138a1ec925 100644 --- a/Common/Tests/Types/ApplicationLog/ApplicationLogType.test.ts +++ b/Common/Tests/Types/ApplicationLog/ApplicationLogType.test.ts @@ -1,13 +1,13 @@ -import ApplicationLogType from '../../../Types/ApplicationLog/ApplicationLogType'; +import ApplicationLogType from "../../../Types/ApplicationLog/ApplicationLogType"; -describe('ApplicationLogType', () => { - test('ApplicationLogType.Info to be Info', () => { - expect(ApplicationLogType.Info).toBe('Info'); - }); - test('ApplicationLogType.Error to Error', () => { - expect(ApplicationLogType.Error).toBe('Error'); - }); - test('ApplicationLogType.Warning to Warning', () => { - expect(ApplicationLogType.Warning).toBe('Warning'); - }); +describe("ApplicationLogType", () => { + test("ApplicationLogType.Info to be Info", () => { + expect(ApplicationLogType.Info).toBe("Info"); + }); + test("ApplicationLogType.Error to Error", () => { + expect(ApplicationLogType.Error).toBe("Error"); + }); + test("ApplicationLogType.Warning to Warning", () => { + expect(ApplicationLogType.Warning).toBe("Warning"); + }); }); diff --git a/Common/Tests/Types/ArrayUtil.test.ts b/Common/Tests/Types/ArrayUtil.test.ts index 61cf332e11..1228e7964b 100644 --- a/Common/Tests/Types/ArrayUtil.test.ts +++ b/Common/Tests/Types/ArrayUtil.test.ts @@ -1,90 +1,90 @@ -import ArrayUtil from '../../Types/ArrayUtil'; +import ArrayUtil from "../../Types/ArrayUtil"; -describe('ArrayUtil.isEqual', () => { - const equalArrays: Array<[Array, Array]> = [ - [[], []], - [[1], [1]], - [ - [1, 2], - [1, 2], - ], - [ - [1, 2, 3], - [3, 2, 1], // different order - ], - [ - [undefined, function () {}], - [null, null], - ], - [ - [true, true, false], - [true, false, true], - ], - [[new Map([['foo', 'bar']])], [{}]], - [[[1]], [[1]]], - [[{ a: 1 }], [{ a: 1 }]], - [[{ a: undefined, b: function () {} }], [{}]], - [[new Date(2000, 1, 1)], [new Date(2000, 1, 1)]], - ]; +describe("ArrayUtil.isEqual", () => { + const equalArrays: Array<[Array, Array]> = [ + [[], []], + [[1], [1]], + [ + [1, 2], + [1, 2], + ], + [ + [1, 2, 3], + [3, 2, 1], // different order + ], + [ + [undefined, function () {}], + [null, null], + ], + [ + [true, true, false], + [true, false, true], + ], + [[new Map([["foo", "bar"]])], [{}]], + [[[1]], [[1]]], + [[{ a: 1 }], [{ a: 1 }]], + [[{ a: undefined, b: function () {} }], [{}]], + [[new Date(2000, 1, 1)], [new Date(2000, 1, 1)]], + ]; - test.each(equalArrays)('isEqual(%p, %p) is true', (a: any[], b: any[]) => { - expect(ArrayUtil.isEqual(a, b)).toBe(true); - expect(ArrayUtil.isEqual(b, a)).toBe(true); - }); + test.each(equalArrays)("isEqual(%p, %p) is true", (a: any[], b: any[]) => { + expect(ArrayUtil.isEqual(a, b)).toBe(true); + expect(ArrayUtil.isEqual(b, a)).toBe(true); + }); - const nonEqualArrays: Array<[Array, Array]> = [ - [[1], []], - [[1], [2]], - [ - [1, 2], - [2, 3], - ], - [[[1]], [[2]]], - [ - [true, true, false], - [true, false, false], - ], - [[{ a: 1 }], [{ a: 2 }]], - [[{ a: 1, b: 2 }], [{ b: 2, a: 1 }]], // JSON.stringify maintains key order - [[new Date(2000, 1, 1)], [new Date(2000, 1, 2)]], - ]; + const nonEqualArrays: Array<[Array, Array]> = [ + [[1], []], + [[1], [2]], + [ + [1, 2], + [2, 3], + ], + [[[1]], [[2]]], + [ + [true, true, false], + [true, false, false], + ], + [[{ a: 1 }], [{ a: 2 }]], + [[{ a: 1, b: 2 }], [{ b: 2, a: 1 }]], // JSON.stringify maintains key order + [[new Date(2000, 1, 1)], [new Date(2000, 1, 2)]], + ]; - test.each(nonEqualArrays)( - 'isEqual(%p, %p) is false', - (a: any[], b: any[]) => { - expect(ArrayUtil.isEqual(a, b)).toBe(false); - expect(ArrayUtil.isEqual(b, a)).toBe(false); - } - ); + test.each(nonEqualArrays)( + "isEqual(%p, %p) is false", + (a: any[], b: any[]) => { + expect(ArrayUtil.isEqual(a, b)).toBe(false); + expect(ArrayUtil.isEqual(b, a)).toBe(false); + }, + ); - test('should not mutate arguments', () => { - const a: Array = [3, 1, 2]; - const b: Array = [1, 3, 2]; + test("should not mutate arguments", () => { + const a: Array = [3, 1, 2]; + const b: Array = [1, 3, 2]; - expect(ArrayUtil.isEqual(a, b)).toBe(true); + expect(ArrayUtil.isEqual(a, b)).toBe(true); - // Jest does not sort elements before performing the equality test - expect(a).toEqual([3, 1, 2]); - expect(b).toEqual([1, 3, 2]); - }); + // Jest does not sort elements before performing the equality test + expect(a).toEqual([3, 1, 2]); + expect(b).toEqual([1, 3, 2]); + }); }); -describe('ArrayUtil.sortByFieldName', () => { - type compareFn = (a: any, b: any) => number; +describe("ArrayUtil.sortByFieldName", () => { + type compareFn = (a: any, b: any) => number; - test('should return a compare function that sorts an array of objects by field name', () => { - const sortByFoo: compareFn = ArrayUtil.sortByFieldName('foo'); - type Foo = { - foo: number; - }; + test("should return a compare function that sorts an array of objects by field name", () => { + const sortByFoo: compareFn = ArrayUtil.sortByFieldName("foo"); + type Foo = { + foo: number; + }; - const items: Array = [{ foo: 42 }, { foo: 1 }, { foo: 13 }]; - items.sort(sortByFoo); + const items: Array = [{ foo: 42 }, { foo: 1 }, { foo: 13 }]; + items.sort(sortByFoo); - expect( - items.map((v: Foo) => { - return v.foo; - }) - ).toEqual([1, 13, 42]); - }); + expect( + items.map((v: Foo) => { + return v.foo; + }), + ).toEqual([1, 13, 42]); + }); }); diff --git a/Common/Tests/Types/Billing/SubscriptionPlan.test.ts b/Common/Tests/Types/Billing/SubscriptionPlan.test.ts index cb6a006ff6..9887fcd297 100644 --- a/Common/Tests/Types/Billing/SubscriptionPlan.test.ts +++ b/Common/Tests/Types/Billing/SubscriptionPlan.test.ts @@ -1,299 +1,284 @@ import SubscriptionPlan, { - PlanSelect, -} from '../../../Types/Billing/SubscriptionPlan'; -import BadDataException from '../../../Types/Exception/BadDataException'; -import { JSONObject } from '../../../Types/JSON'; -import { describe, expect, it } from '@jest/globals'; + PlanSelect, +} from "../../../Types/Billing/SubscriptionPlan"; +import BadDataException from "../../../Types/Exception/BadDataException"; +import { JSONObject } from "../../../Types/JSON"; +import { describe, expect, it } from "@jest/globals"; -describe('SubscriptionPlan', () => { - const monthlyPlanId: string = 'monthly_plan_id'; - const yearlyPlanId: string = 'yearly_plan_id'; - const name: string = 'Test Plan'; - const monthlySubscriptionAmountInUSD: number = 0; - const yearlySubscriptionAmountInUSD: number = 0; - const order: number = 1; - const trialPeriodInDays: number = 30; - const env: JSONObject = { - SUBSCRIPTION_PLAN_1: 'Free,monthly_plan_id,yearly_plan_id,0,0,1,7', +describe("SubscriptionPlan", () => { + const monthlyPlanId: string = "monthly_plan_id"; + const yearlyPlanId: string = "yearly_plan_id"; + const name: string = "Test Plan"; + const monthlySubscriptionAmountInUSD: number = 0; + const yearlySubscriptionAmountInUSD: number = 0; + const order: number = 1; + const trialPeriodInDays: number = 30; + const env: JSONObject = { + SUBSCRIPTION_PLAN_1: "Free,monthly_plan_id,yearly_plan_id,0,0,1,7", + SUBSCRIPTION_PLAN_2: + "Growth,growth_monthly_plan_id,growth_yearly_plan_id,9,99,2,14", + }; + + describe("constructor", () => { + it("should create a new SubscriptionPlan object", () => { + const plan: SubscriptionPlan = new SubscriptionPlan( + monthlyPlanId, + yearlyPlanId, + name, + monthlySubscriptionAmountInUSD, + yearlySubscriptionAmountInUSD, + order, + trialPeriodInDays, + ); + expect(plan.getMonthlyPlanId()).toEqual(monthlyPlanId); + expect(plan.getYearlyPlanId()).toEqual(yearlyPlanId); + expect(plan.getName()).toEqual(name); + expect(plan.getPlanOrder()).toEqual(order); + expect(plan.getTrialPeriod()).toEqual(trialPeriodInDays); + }); + }); + + describe("getMonthlyPlanId", () => { + it("should return the monthly plan ID", () => { + const getMonthlyPlanId: string = "monthly_plan_id"; + expect(getMonthlyPlanId).toEqual(monthlyPlanId); + }); + }); + + describe("getYearlyPlanId", () => { + it("should return the yearly plan ID", () => { + const getYearlyPlanId: string = "yearly_plan_id"; + expect(getYearlyPlanId).toEqual(yearlyPlanId); + }); + }); + + describe("getPlanOrder", () => { + it("should return the plan order", () => { + const getPlanOrder: number = 1; + expect(getPlanOrder).toEqual(order); + }); + }); + + describe("getTrialPeriod", () => { + it("should return the trial period in days", () => { + const getTrialPeriod: number = 30; + expect(getTrialPeriod).toEqual(trialPeriodInDays); + }); + }); + + describe("getName", () => { + it("should return the plan name", () => { + const getName: string = "Test Plan"; + expect(getName).toEqual(name); + }); + }); + + describe("isFreePlan", () => { + it("should return true if plan is free with monthlyId", () => { + const isFreePlan: boolean = SubscriptionPlan.isFreePlan( + "monthly_plan_id", + env, + ); + expect(isFreePlan).toBe(true); + }); + it("should return true if plan is free with yearlyId", () => { + const isFreePlan: boolean = SubscriptionPlan.isFreePlan( + "yearly_plan_id", + env, + ); + expect(isFreePlan).toBe(true); + }); + }); + describe("isCustomPricingPlan", () => { + it("should return false if plan is not custom pricing", () => { + const isCustomPricingPlan: boolean = SubscriptionPlan.isCustomPricingPlan( + monthlyPlanId, + env, + ); + expect(isCustomPricingPlan).toBe(false); + }); + }); + + describe("getSubscriptionPlans", () => { + it("should return an array of SubscriptionPlan objects", () => { + const subscriptionPlans: SubscriptionPlan[] = + SubscriptionPlan.getSubscriptionPlans(env); + + expect(subscriptionPlans.length).toBe(2); + expect(subscriptionPlans?.[0]?.getName()).toBe("Free"); + expect(subscriptionPlans?.[0]?.getYearlyPlanId()).toBe("yearly_plan_id"); + }); + }); + describe("isValidPlanId", () => { + it("should return true if plan ID is valid", () => { + const isValidPlanId: boolean = SubscriptionPlan.isValidPlanId( + "growth_monthly_plan_id", + env, + ); + expect(isValidPlanId).toBe(true); + }); + }); + describe("getPlanSelect", () => { + it("should return the plan name if valid planId is passed", () => { + new SubscriptionPlan( + monthlyPlanId, + "yearly_plan_id", + PlanSelect.Free, + 0, + 0, + 2, + 30, + ); + const result: PlanSelect = SubscriptionPlan.getPlanSelect( + monthlyPlanId, + env, + ); + expect(result).toBe(PlanSelect.Free); + }); + it("should throw an error if invalid PlanId is passed", () => { + SubscriptionPlan.getSubscriptionPlanById = jest + .fn() + .mockReturnValue(undefined); + expect(() => { + SubscriptionPlan.getPlanSelect("invalid-plan-id", env); + }).toThrow(BadDataException); + }); + }); + describe("getYearlySubscriptionAmountInUSD", () => { + it("should return the yearly subscription amount", () => { + const getYearlySubscriptionAmountInUSD: number = 0; + expect(getYearlySubscriptionAmountInUSD).toEqual( + yearlySubscriptionAmountInUSD, + ); + }); + }); + describe("getMonthlySubscriptionAmountInUSD", () => { + it("should return the yearly subscription amount", () => { + const getMonthlySubscriptionAmountInUSD: number = 0; + expect(getMonthlySubscriptionAmountInUSD).toEqual( + monthlySubscriptionAmountInUSD, + ); + }); + }); + describe("isFeatureAccessibleOnCurrentPlan", () => { + it("should return false if the feature is not accessible on current plan", () => { + const env: JSONObject = { + SUBSCRIPTION_PLAN_1: "Free,monthly_plan_id,yearly_plan_id,0,0,1,7", SUBSCRIPTION_PLAN_2: - 'Growth,growth_monthly_plan_id,growth_yearly_plan_id,9,99,2,14', - }; - - describe('constructor', () => { - it('should create a new SubscriptionPlan object', () => { - const plan: SubscriptionPlan = new SubscriptionPlan( - monthlyPlanId, - yearlyPlanId, - name, - monthlySubscriptionAmountInUSD, - yearlySubscriptionAmountInUSD, - order, - trialPeriodInDays - ); - expect(plan.getMonthlyPlanId()).toEqual(monthlyPlanId); - expect(plan.getYearlyPlanId()).toEqual(yearlyPlanId); - expect(plan.getName()).toEqual(name); - expect(plan.getPlanOrder()).toEqual(order); - expect(plan.getTrialPeriod()).toEqual(trialPeriodInDays); - }); + "Growth,growth_monthly_plan_id,growth_yearly_plan_id,9,99,2,14", + }; + const featureSubscriptionPlan: SubscriptionPlan = new SubscriptionPlan( + "growth_monthly_plan_id", + "growth_yearly_plan_id", + PlanSelect.Growth, + 9, + 99, + 2, + 14, + ); + const currentSubscriptionPlan: SubscriptionPlan = new SubscriptionPlan( + "monthly_plan_id", + "yearly_plan_id", + PlanSelect.Free, + 0, + 0, + 1, + 7, + ); + const result: boolean = SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + PlanSelect.Growth, + PlanSelect.Free, + env, + ); + expect(featureSubscriptionPlan.getPlanOrder()).toBeGreaterThan( + currentSubscriptionPlan.getPlanOrder(), + ); + expect(result).toBe(false); }); - - describe('getMonthlyPlanId', () => { - it('should return the monthly plan ID', () => { - const getMonthlyPlanId: string = 'monthly_plan_id'; - expect(getMonthlyPlanId).toEqual(monthlyPlanId); - }); + it("should return true if the feature is on the current plan", () => { + const env: JSONObject = { + SUBSCRIPTION_PLAN_1: "Free,monthly_plan_id,yearly_plan_id,0,0,3,7", + SUBSCRIPTION_PLAN_2: + "Growth,growth_monthly_plan_id,growth_yearly_plan_id,9,99,2,14", + }; + const featureSubscriptionPlan: SubscriptionPlan = new SubscriptionPlan( + "growth_monthly_plan_id", + "growth_yearly_plan_id", + PlanSelect.Growth, + 9, + 99, + 2, + 14, + ); + const currentSubscriptionPlan: SubscriptionPlan = new SubscriptionPlan( + monthlyPlanId, + "yearly_plan_id", + PlanSelect.Free, + 0, + 0, + 3, + 7, + ); + const result: boolean = SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + PlanSelect.Growth, + PlanSelect.Free, + env, + ); + expect(featureSubscriptionPlan.getPlanOrder()).toBeLessThan( + currentSubscriptionPlan.getPlanOrder(), + ); + expect(result).toBe(true); }); - - describe('getYearlyPlanId', () => { - it('should return the yearly plan ID', () => { - const getYearlyPlanId: string = 'yearly_plan_id'; - expect(getYearlyPlanId).toEqual(yearlyPlanId); - }); + }); + describe("getSubscriptionPlanFromPlanSelect", () => { + it("should return the correct SubscriptionPlan when a valid planSelect is provided", () => { + const plan: SubscriptionPlan = + SubscriptionPlan.getSubscriptionPlanFromPlanSelect( + PlanSelect.Growth, + env, + ); + expect(plan).toEqual(plan); + expect(plan.getName()).toEqual(PlanSelect.Growth); }); - - describe('getPlanOrder', () => { - it('should return the plan order', () => { - const getPlanOrder: number = 1; - expect(getPlanOrder).toEqual(order); - }); + it("should throw a BadDataException when an invalid planSelect is provided", () => { + const planSelect: PlanSelect = PlanSelect.Scale; + SubscriptionPlan.getSubscriptionPlans = jest.fn().mockReturnValue([]); + expect(() => { + SubscriptionPlan.getSubscriptionPlanFromPlanSelect(planSelect, env); + }).toThrow(BadDataException); }); - - describe('getTrialPeriod', () => { - it('should return the trial period in days', () => { - const getTrialPeriod: number = 30; - expect(getTrialPeriod).toEqual(trialPeriodInDays); - }); + }); + describe("isYearlyPlan", () => { + it("should return true if yearly plan exists", () => { + const planId: string = "growth_yearly_plan_id"; + const plan: SubscriptionPlan = new SubscriptionPlan( + "monthly-plan-id", + planId, + "Growth", + 10, + 100, + 2, + 7, + ); + SubscriptionPlan.getSubscriptionPlanById(planId, env); + expect(plan?.getYearlyPlanId()).toBe(planId); }); - - describe('getName', () => { - it('should return the plan name', () => { - const getName: string = 'Test Plan'; - expect(getName).toEqual(name); - }); + }); + describe("isUnpaid", () => { + it("should return true if the subscription status is unpaid", () => { + const subscriptionStatus: string = + "incomplete" || + "incomplete_expired" || + "past_due" || + "canceled" || + "unpaid"; + const result: boolean = SubscriptionPlan.isUnpaid(subscriptionStatus); + expect(result).toBe(true); }); - - describe('isFreePlan', () => { - it('should return true if plan is free with monthlyId', () => { - const isFreePlan: boolean = SubscriptionPlan.isFreePlan( - 'monthly_plan_id', - env - ); - expect(isFreePlan).toBe(true); - }); - it('should return true if plan is free with yearlyId', () => { - const isFreePlan: boolean = SubscriptionPlan.isFreePlan( - 'yearly_plan_id', - env - ); - expect(isFreePlan).toBe(true); - }); - }); - describe('isCustomPricingPlan', () => { - it('should return false if plan is not custom pricing', () => { - const isCustomPricingPlan: boolean = - SubscriptionPlan.isCustomPricingPlan(monthlyPlanId, env); - expect(isCustomPricingPlan).toBe(false); - }); - }); - - describe('getSubscriptionPlans', () => { - it('should return an array of SubscriptionPlan objects', () => { - const subscriptionPlans: SubscriptionPlan[] = - SubscriptionPlan.getSubscriptionPlans(env); - - expect(subscriptionPlans.length).toBe(2); - expect(subscriptionPlans?.[0]?.getName()).toBe('Free'); - expect(subscriptionPlans?.[0]?.getYearlyPlanId()).toBe( - 'yearly_plan_id' - ); - }); - }); - describe('isValidPlanId', () => { - it('should return true if plan ID is valid', () => { - const isValidPlanId: boolean = SubscriptionPlan.isValidPlanId( - 'growth_monthly_plan_id', - env - ); - expect(isValidPlanId).toBe(true); - }); - }); - describe('getPlanSelect', () => { - it('should return the plan name if valid planId is passed', () => { - new SubscriptionPlan( - monthlyPlanId, - 'yearly_plan_id', - PlanSelect.Free, - 0, - 0, - 2, - 30 - ); - const result: PlanSelect = SubscriptionPlan.getPlanSelect( - monthlyPlanId, - env - ); - expect(result).toBe(PlanSelect.Free); - }); - it('should throw an error if invalid PlanId is passed', () => { - SubscriptionPlan.getSubscriptionPlanById = jest - .fn() - .mockReturnValue(undefined); - expect(() => { - SubscriptionPlan.getPlanSelect('invalid-plan-id', env); - }).toThrow(BadDataException); - }); - }); - describe('getYearlySubscriptionAmountInUSD', () => { - it('should return the yearly subscription amount', () => { - const getYearlySubscriptionAmountInUSD: number = 0; - expect(getYearlySubscriptionAmountInUSD).toEqual( - yearlySubscriptionAmountInUSD - ); - }); - }); - describe('getMonthlySubscriptionAmountInUSD', () => { - it('should return the yearly subscription amount', () => { - const getMonthlySubscriptionAmountInUSD: number = 0; - expect(getMonthlySubscriptionAmountInUSD).toEqual( - monthlySubscriptionAmountInUSD - ); - }); - }); - describe('isFeatureAccessibleOnCurrentPlan', () => { - it('should return false if the feature is not accessible on current plan', () => { - const env: JSONObject = { - SUBSCRIPTION_PLAN_1: - 'Free,monthly_plan_id,yearly_plan_id,0,0,1,7', - SUBSCRIPTION_PLAN_2: - 'Growth,growth_monthly_plan_id,growth_yearly_plan_id,9,99,2,14', - }; - const featureSubscriptionPlan: SubscriptionPlan = - new SubscriptionPlan( - 'growth_monthly_plan_id', - 'growth_yearly_plan_id', - PlanSelect.Growth, - 9, - 99, - 2, - 14 - ); - const currentSubscriptionPlan: SubscriptionPlan = - new SubscriptionPlan( - 'monthly_plan_id', - 'yearly_plan_id', - PlanSelect.Free, - 0, - 0, - 1, - 7 - ); - const result: boolean = - SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - PlanSelect.Growth, - PlanSelect.Free, - env - ); - expect(featureSubscriptionPlan.getPlanOrder()).toBeGreaterThan( - currentSubscriptionPlan.getPlanOrder() - ); - expect(result).toBe(false); - }); - it('should return true if the feature is on the current plan', () => { - const env: JSONObject = { - SUBSCRIPTION_PLAN_1: - 'Free,monthly_plan_id,yearly_plan_id,0,0,3,7', - SUBSCRIPTION_PLAN_2: - 'Growth,growth_monthly_plan_id,growth_yearly_plan_id,9,99,2,14', - }; - const featureSubscriptionPlan: SubscriptionPlan = - new SubscriptionPlan( - 'growth_monthly_plan_id', - 'growth_yearly_plan_id', - PlanSelect.Growth, - 9, - 99, - 2, - 14 - ); - const currentSubscriptionPlan: SubscriptionPlan = - new SubscriptionPlan( - monthlyPlanId, - 'yearly_plan_id', - PlanSelect.Free, - 0, - 0, - 3, - 7 - ); - const result: boolean = - SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - PlanSelect.Growth, - PlanSelect.Free, - env - ); - expect(featureSubscriptionPlan.getPlanOrder()).toBeLessThan( - currentSubscriptionPlan.getPlanOrder() - ); - expect(result).toBe(true); - }); - }); - describe('getSubscriptionPlanFromPlanSelect', () => { - it('should return the correct SubscriptionPlan when a valid planSelect is provided', () => { - const plan: SubscriptionPlan = - SubscriptionPlan.getSubscriptionPlanFromPlanSelect( - PlanSelect.Growth, - env - ); - expect(plan).toEqual(plan); - expect(plan.getName()).toEqual(PlanSelect.Growth); - }); - it('should throw a BadDataException when an invalid planSelect is provided', () => { - const planSelect: PlanSelect = PlanSelect.Scale; - SubscriptionPlan.getSubscriptionPlans = jest - .fn() - .mockReturnValue([]); - expect(() => { - SubscriptionPlan.getSubscriptionPlanFromPlanSelect( - planSelect, - env - ); - }).toThrow(BadDataException); - }); - }); - describe('isYearlyPlan', () => { - it('should return true if yearly plan exists', () => { - const planId: string = 'growth_yearly_plan_id'; - const plan: SubscriptionPlan = new SubscriptionPlan( - 'monthly-plan-id', - planId, - 'Growth', - 10, - 100, - 2, - 7 - ); - SubscriptionPlan.getSubscriptionPlanById(planId, env); - expect(plan?.getYearlyPlanId()).toBe(planId); - }); - }); - describe('isUnpaid', () => { - it('should return true if the subscription status is unpaid', () => { - const subscriptionStatus: string = - 'incomplete' || - 'incomplete_expired' || - 'past_due' || - 'canceled' || - 'unpaid'; - const result: boolean = - SubscriptionPlan.isUnpaid(subscriptionStatus); - expect(result).toBe(true); - }); - it('should return false if the subscription status is active', () => { - const subscriptionStatus: string = 'active'; - const result: boolean = - SubscriptionPlan.isUnpaid(subscriptionStatus); - expect(result).toBe(false); - }); + it("should return false if the subscription status is active", () => { + const subscriptionStatus: string = "active"; + const result: boolean = SubscriptionPlan.isUnpaid(subscriptionStatus); + expect(result).toBe(false); }); + }); }); diff --git a/Common/Tests/Types/BrandColors.test.ts b/Common/Tests/Types/BrandColors.test.ts index 4b211dc5b0..1a6aca10bf 100644 --- a/Common/Tests/Types/BrandColors.test.ts +++ b/Common/Tests/Types/BrandColors.test.ts @@ -1,141 +1,141 @@ import { - Black, - Blue500, - Cyan500, - Gray500, - Green500, - LightGray, - Moroon500, - Orange500, - Pink500, - Purple500, - Red500, - Slate500, - Teal500, - VeryLightGray, - White, - Yellow500, -} from '../../Types/BrandColors'; -import Color from '../../Types/Color'; -import { describe, expect, it } from '@jest/globals'; + Black, + Blue500, + Cyan500, + Gray500, + Green500, + LightGray, + Moroon500, + Orange500, + Pink500, + Purple500, + Red500, + Slate500, + Teal500, + VeryLightGray, + White, + Yellow500, +} from "../../Types/BrandColors"; +import Color from "../../Types/Color"; +import { describe, expect, it } from "@jest/globals"; -describe('Color', () => { - describe('Verify color hex', () => { - it('should create a new instance with the given hex code', () => { - const color: Color = new Color('#123456'); - expect(color).toBe(color); - }); +describe("Color", () => { + describe("Verify color hex", () => { + it("should create a new instance with the given hex code", () => { + const color: Color = new Color("#123456"); + expect(color).toBe(color); }); - describe('Black', () => { - it('should be an instance with the hex code of Black', () => { - const color: Color = Black; - expect(Black).toBe(color); - expect(Black.color).toBe('#000000'); - }); + }); + describe("Black", () => { + it("should be an instance with the hex code of Black", () => { + const color: Color = Black; + expect(Black).toBe(color); + expect(Black.color).toBe("#000000"); }); - describe('White', () => { - it('should be an instance with the hex code of White', () => { - const color: Color = White; - expect(White).toBe(color); - expect(White.color).toBe('#ffffff'); - }); + }); + describe("White", () => { + it("should be an instance with the hex code of White", () => { + const color: Color = White; + expect(White).toBe(color); + expect(White.color).toBe("#ffffff"); }); - describe('slate', () => { - it('should be an instance with the hex code of slate', () => { - const color: Color = Slate500; - expect(Slate500).toBe(color); - expect(Slate500.color).toBe('#64748b'); - }); + }); + describe("slate", () => { + it("should be an instance with the hex code of slate", () => { + const color: Color = Slate500; + expect(Slate500).toBe(color); + expect(Slate500.color).toBe("#64748b"); }); - describe('Purple500', () => { - it('should be an instance with the hex code of Purple500', () => { - const color: Color = Purple500; - expect(Purple500).toBe(color); - expect(Purple500.color).toBe('#a855f7'); - }); + }); + describe("Purple500", () => { + it("should be an instance with the hex code of Purple500", () => { + const color: Color = Purple500; + expect(Purple500).toBe(color); + expect(Purple500.color).toBe("#a855f7"); }); - describe('Pink500', () => { - it('should be an instance with the hex code of Pink500', () => { - const color: Color = Pink500; - expect(Pink500).toBe(color); - expect(Pink500.color).toBe('#ec4899'); - }); + }); + describe("Pink500", () => { + it("should be an instance with the hex code of Pink500", () => { + const color: Color = Pink500; + expect(Pink500).toBe(color); + expect(Pink500.color).toBe("#ec4899"); }); - describe('Red500', () => { - it('should be an instance with the hex code of Red500', () => { - const color: Color = Red500; - expect(Red500).toBe(color); - expect(Red500.color).toBe('#ef4444'); - }); + }); + describe("Red500", () => { + it("should be an instance with the hex code of Red500", () => { + const color: Color = Red500; + expect(Red500).toBe(color); + expect(Red500.color).toBe("#ef4444"); }); - describe('Orange500', () => { - it('should be an instance with the hex code of Orange500', () => { - const color: Color = Orange500; - expect(Orange500).toBe(color); - expect(Orange500.color).toBe('#f97316'); - }); + }); + describe("Orange500", () => { + it("should be an instance with the hex code of Orange500", () => { + const color: Color = Orange500; + expect(Orange500).toBe(color); + expect(Orange500.color).toBe("#f97316"); }); - describe('Yellow500', () => { - it('should be an instance with the hex code of Yellow500', () => { - const color: Color = Yellow500; - expect(Yellow500).toBe(color); - expect(Yellow500.color).toBe('#ffbf53'); - }); + }); + describe("Yellow500", () => { + it("should be an instance with the hex code of Yellow500", () => { + const color: Color = Yellow500; + expect(Yellow500).toBe(color); + expect(Yellow500.color).toBe("#ffbf53"); }); - describe('Green500', () => { - it('should be an instance with the hex code of Green500', () => { - const color: Color = Green500; - expect(Green500).toBe(color); - expect(Green500.color).toBe('#22c55e'); - }); + }); + describe("Green500", () => { + it("should be an instance with the hex code of Green500", () => { + const color: Color = Green500; + expect(Green500).toBe(color); + expect(Green500.color).toBe("#22c55e"); }); - describe('Teal500', () => { - it('should be an instance with the hex code of Teal500', () => { - const color: Color = Teal500; - expect(Teal500).toBe(color); - expect(Teal500.color).toBe('#14b8a6'); - }); + }); + describe("Teal500", () => { + it("should be an instance with the hex code of Teal500", () => { + const color: Color = Teal500; + expect(Teal500).toBe(color); + expect(Teal500.color).toBe("#14b8a6"); }); - describe('Cyan500', () => { - it('should be an instance with the hex code of Cyan500', () => { - const color: Color = Cyan500; - expect(Cyan500).toBe(color); - expect(Cyan500.color).toBe('#06b6d4'); - }); + }); + describe("Cyan500", () => { + it("should be an instance with the hex code of Cyan500", () => { + const color: Color = Cyan500; + expect(Cyan500).toBe(color); + expect(Cyan500.color).toBe("#06b6d4"); }); - describe('VeryLightGray', () => { - it('should be an instance with the hex code of VeryLightGray', () => { - const color: Color = VeryLightGray; - expect(VeryLightGray).toBe(color); - expect(VeryLightGray.color).toBe('#c2c2c2'); - }); + }); + describe("VeryLightGray", () => { + it("should be an instance with the hex code of VeryLightGray", () => { + const color: Color = VeryLightGray; + expect(VeryLightGray).toBe(color); + expect(VeryLightGray.color).toBe("#c2c2c2"); }); - describe('Gray500', () => { - it('should be an instance with the hex code of Gray500', () => { - const color: Color = Gray500; - expect(Gray500).toBe(color); - expect(Gray500.color).toBe('#6b7280'); - }); + }); + describe("Gray500", () => { + it("should be an instance with the hex code of Gray500", () => { + const color: Color = Gray500; + expect(Gray500).toBe(color); + expect(Gray500.color).toBe("#6b7280"); }); - describe('LightGray', () => { - it('should be an instance with the hex code of LightGray', () => { - const color: Color = LightGray; - expect(LightGray).toBe(color); - expect(LightGray.color).toBe('#908B8B'); - }); + }); + describe("LightGray", () => { + it("should be an instance with the hex code of LightGray", () => { + const color: Color = LightGray; + expect(LightGray).toBe(color); + expect(LightGray.color).toBe("#908B8B"); }); - describe('Moroon500', () => { - it('should be an instance with the hex code of Moroon500', () => { - const color: Color = Moroon500; - expect(Moroon500).toBe(color); - expect(Moroon500.color).toBe('#b70400'); - }); + }); + describe("Moroon500", () => { + it("should be an instance with the hex code of Moroon500", () => { + const color: Color = Moroon500; + expect(Moroon500).toBe(color); + expect(Moroon500.color).toBe("#b70400"); }); - describe('Blue500', () => { - it('should be an instance with the hex code of Blue500', () => { - const color: Color = Blue500; - expect(Blue500).toBe(color); - expect(Blue500.color).toBe('#3b82f6'); - }); + }); + describe("Blue500", () => { + it("should be an instance with the hex code of Blue500", () => { + const color: Color = Blue500; + expect(Blue500).toBe(color); + expect(Blue500.color).toBe("#3b82f6"); }); + }); }); diff --git a/Common/Tests/Types/Char.test.ts b/Common/Tests/Types/Char.test.ts index ed6f420498..ed5b613dfd 100644 --- a/Common/Tests/Types/Char.test.ts +++ b/Common/Tests/Types/Char.test.ts @@ -1,83 +1,83 @@ -import Char from '../../Types/Char'; -import Dictionary from '../../Types/Dictionary'; +import Char from "../../Types/Char"; +import Dictionary from "../../Types/Dictionary"; -describe('type Char', () => { - test('Char can used be type', () => { - let char: Char; - expect((char = 'a')).toBe('a'); - expect((char = 'b')).toBe('b'); - expect((char = 'c')).toBe('c'); - expect((char = 'd')).toEqual('d'); - expect((char = 'e')).toEqual('e'); - expect((char = 'f')).toEqual('f'); - expect((char = 'g')).toEqual('g'); - expect((char = 'h')).toEqual('h'); - expect((char = 'i')).toEqual('i'); - expect((char = 'j')).toEqual('j'); - expect((char = 'k')).toEqual('k'); - expect((char = 'l')).toEqual('l'); - expect((char = 'm')).toEqual('m'); - expect((char = 'n')).toEqual('n'); - expect((char = 'o')).toEqual('o'); - expect((char = 'p')).toEqual('p'); - expect((char = 'q')).toEqual('q'); - expect((char = 'r')).toEqual('r'); - expect((char = 's')).toEqual('s'); - expect((char = 't')).toEqual('t'); - expect((char = 'u')).toEqual('u'); - expect((char = 'v')).toEqual('v'); - expect((char = 'w')).toEqual('w'); - expect((char = 'x')).toEqual('x'); - expect((char = 'y')).toEqual('y'); - expect((char = 'z')).toEqual('z'); - expect((char = 'A')).toEqual('A'); - expect((char = 'B')).toEqual('B'); - expect((char = 'C')).toEqual('C'); - expect((char = 'D')).toEqual('D'); - expect((char = 'E')).toEqual('E'); - expect((char = 'F')).toEqual('F'); - expect((char = 'G')).toEqual('G'); - expect((char = 'H')).toEqual('H'); - expect((char = 'I')).toEqual('I'); - expect((char = 'J')).toEqual('J'); - expect((char = 'K')).toEqual('K'); - expect((char = 'L')).toEqual('L'); - expect((char = 'M')).toEqual('M'); - expect((char = 'N')).toEqual('N'); - expect((char = 'O')).toEqual('O'); - expect((char = 'P')).toEqual('P'); - expect((char = 'Q')).toEqual('Q'); - expect((char = 'R')).toEqual('R'); - expect((char = 'S')).toEqual('S'); - expect((char = 'T')).toEqual('T'); - expect((char = 'U')).toEqual('U'); - expect((char = 'V')).toEqual('V'); - expect((char = 'W')).toEqual('W'); - expect((char = 'X')).toEqual('X'); - expect((char = 'Y')).toEqual('Y'); - expect((char = 'Z')).toEqual('Z'); - expect((char = '0')).toEqual('0'); - expect((char = '1')).toEqual('1'); - expect((char = '2')).toEqual('2'); - expect((char = '3')).toEqual('3'); - expect((char = '4')).toEqual('4'); - expect((char = '5')).toEqual('5'); - expect((char = '6')).toEqual('6'); - expect((char = '7')).toEqual('7'); - expect((char = '8')).toEqual('8'); - expect((char = '9')).toEqual('9'); - expect(char).toEqual('9'); - }); - test('type Char can be used in array', () => { - const characters: Array = ['a', 'A', '1']; - expect(characters).toBeDefined(); - expect(characters).toEqual(['a', 'A', '1']); - }); - test('type Char to be used in dictionary', () => { - const characterDictionary: Dictionary = { - a: 'a', - A: 'A', - }; - expect(characterDictionary['a']).toEqual('a'); - }); +describe("type Char", () => { + test("Char can used be type", () => { + let char: Char; + expect((char = "a")).toBe("a"); + expect((char = "b")).toBe("b"); + expect((char = "c")).toBe("c"); + expect((char = "d")).toEqual("d"); + expect((char = "e")).toEqual("e"); + expect((char = "f")).toEqual("f"); + expect((char = "g")).toEqual("g"); + expect((char = "h")).toEqual("h"); + expect((char = "i")).toEqual("i"); + expect((char = "j")).toEqual("j"); + expect((char = "k")).toEqual("k"); + expect((char = "l")).toEqual("l"); + expect((char = "m")).toEqual("m"); + expect((char = "n")).toEqual("n"); + expect((char = "o")).toEqual("o"); + expect((char = "p")).toEqual("p"); + expect((char = "q")).toEqual("q"); + expect((char = "r")).toEqual("r"); + expect((char = "s")).toEqual("s"); + expect((char = "t")).toEqual("t"); + expect((char = "u")).toEqual("u"); + expect((char = "v")).toEqual("v"); + expect((char = "w")).toEqual("w"); + expect((char = "x")).toEqual("x"); + expect((char = "y")).toEqual("y"); + expect((char = "z")).toEqual("z"); + expect((char = "A")).toEqual("A"); + expect((char = "B")).toEqual("B"); + expect((char = "C")).toEqual("C"); + expect((char = "D")).toEqual("D"); + expect((char = "E")).toEqual("E"); + expect((char = "F")).toEqual("F"); + expect((char = "G")).toEqual("G"); + expect((char = "H")).toEqual("H"); + expect((char = "I")).toEqual("I"); + expect((char = "J")).toEqual("J"); + expect((char = "K")).toEqual("K"); + expect((char = "L")).toEqual("L"); + expect((char = "M")).toEqual("M"); + expect((char = "N")).toEqual("N"); + expect((char = "O")).toEqual("O"); + expect((char = "P")).toEqual("P"); + expect((char = "Q")).toEqual("Q"); + expect((char = "R")).toEqual("R"); + expect((char = "S")).toEqual("S"); + expect((char = "T")).toEqual("T"); + expect((char = "U")).toEqual("U"); + expect((char = "V")).toEqual("V"); + expect((char = "W")).toEqual("W"); + expect((char = "X")).toEqual("X"); + expect((char = "Y")).toEqual("Y"); + expect((char = "Z")).toEqual("Z"); + expect((char = "0")).toEqual("0"); + expect((char = "1")).toEqual("1"); + expect((char = "2")).toEqual("2"); + expect((char = "3")).toEqual("3"); + expect((char = "4")).toEqual("4"); + expect((char = "5")).toEqual("5"); + expect((char = "6")).toEqual("6"); + expect((char = "7")).toEqual("7"); + expect((char = "8")).toEqual("8"); + expect((char = "9")).toEqual("9"); + expect(char).toEqual("9"); + }); + test("type Char can be used in array", () => { + const characters: Array = ["a", "A", "1"]; + expect(characters).toBeDefined(); + expect(characters).toEqual(["a", "A", "1"]); + }); + test("type Char to be used in dictionary", () => { + const characterDictionary: Dictionary = { + a: "a", + A: "A", + }; + expect(characterDictionary["a"]).toEqual("a"); + }); }); diff --git a/Common/Tests/Types/Code/CodeType.test.ts b/Common/Tests/Types/Code/CodeType.test.ts index 81adfdf24b..138a1ec925 100644 --- a/Common/Tests/Types/Code/CodeType.test.ts +++ b/Common/Tests/Types/Code/CodeType.test.ts @@ -1,13 +1,13 @@ -import ApplicationLogType from '../../../Types/ApplicationLog/ApplicationLogType'; +import ApplicationLogType from "../../../Types/ApplicationLog/ApplicationLogType"; -describe('ApplicationLogType', () => { - test('ApplicationLogType.Info to be Info', () => { - expect(ApplicationLogType.Info).toBe('Info'); - }); - test('ApplicationLogType.Error to Error', () => { - expect(ApplicationLogType.Error).toBe('Error'); - }); - test('ApplicationLogType.Warning to Warning', () => { - expect(ApplicationLogType.Warning).toBe('Warning'); - }); +describe("ApplicationLogType", () => { + test("ApplicationLogType.Info to be Info", () => { + expect(ApplicationLogType.Info).toBe("Info"); + }); + test("ApplicationLogType.Error to Error", () => { + expect(ApplicationLogType.Error).toBe("Error"); + }); + test("ApplicationLogType.Warning to Warning", () => { + expect(ApplicationLogType.Warning).toBe("Warning"); + }); }); diff --git a/Common/Tests/Types/Color.test.ts b/Common/Tests/Types/Color.test.ts index 3200616bf2..26928e9058 100644 --- a/Common/Tests/Types/Color.test.ts +++ b/Common/Tests/Types/Color.test.ts @@ -1,44 +1,44 @@ -import Color, { RGB } from '../../Types/Color'; -import BadDataException from '../../Types/Exception/BadDataException'; +import Color, { RGB } from "../../Types/Color"; +import BadDataException from "../../Types/Exception/BadDataException"; -describe('Color', () => { - test('should return the color string', () => { - const color: Color = new Color('#807149'); - expect(color.toString()).toBe('#807149'); - }); - test('should return the string value of color ', () => { - const color: Color = new Color('#807149'); - expect(Color.toDatabase(color)).toBe('#807149'); - }); - test('should return instance of Color', () => { - expect(Color.fromDatabase('#807149')).toBeInstanceOf(Color); - }); - test('should return null', () => { - expect(Color.fromDatabase('')).toBeNull(); - }); - test('should return object of type RGB', () => { - const color: Color = new Color('#807149'); - const rgb: RGB = Color.colorToRgb(color); - expect(typeof rgb.red).toEqual('number'); - expect(typeof rgb.green).toEqual('number'); - expect(typeof rgb.blue).toEqual('number'); - }); - test('should return correct RGB values ', () => { - const color: Color = new Color('#807149'); - const rgb: RGB = Color.colorToRgb(color); - expect(rgb.red).toEqual(128); - expect(rgb.green).toEqual(113); - expect(rgb.blue).toEqual(73); - }); - test('should throw exception', () => { - const color: Color = new Color('-1'); - expect(() => { - Color.colorToRgb(color); - }).toThrow(BadDataException); - }); - test('should return instance of Color', () => { - const color: Color = new Color('#807149'); - const rgb: RGB = Color.colorToRgb(color); - expect(Color.rgbToColor(rgb)).toBeInstanceOf(Color); - }); +describe("Color", () => { + test("should return the color string", () => { + const color: Color = new Color("#807149"); + expect(color.toString()).toBe("#807149"); + }); + test("should return the string value of color ", () => { + const color: Color = new Color("#807149"); + expect(Color.toDatabase(color)).toBe("#807149"); + }); + test("should return instance of Color", () => { + expect(Color.fromDatabase("#807149")).toBeInstanceOf(Color); + }); + test("should return null", () => { + expect(Color.fromDatabase("")).toBeNull(); + }); + test("should return object of type RGB", () => { + const color: Color = new Color("#807149"); + const rgb: RGB = Color.colorToRgb(color); + expect(typeof rgb.red).toEqual("number"); + expect(typeof rgb.green).toEqual("number"); + expect(typeof rgb.blue).toEqual("number"); + }); + test("should return correct RGB values ", () => { + const color: Color = new Color("#807149"); + const rgb: RGB = Color.colorToRgb(color); + expect(rgb.red).toEqual(128); + expect(rgb.green).toEqual(113); + expect(rgb.blue).toEqual(73); + }); + test("should throw exception", () => { + const color: Color = new Color("-1"); + expect(() => { + Color.colorToRgb(color); + }).toThrow(BadDataException); + }); + test("should return instance of Color", () => { + const color: Color = new Color("#807149"); + const rgb: RGB = Color.colorToRgb(color); + expect(Color.rgbToColor(rgb)).toBeInstanceOf(Color); + }); }); diff --git a/Common/Tests/Types/Company/CompanySize.test.ts b/Common/Tests/Types/Company/CompanySize.test.ts index 6f38a06d98..289da4cc03 100644 --- a/Common/Tests/Types/Company/CompanySize.test.ts +++ b/Common/Tests/Types/Company/CompanySize.test.ts @@ -1,21 +1,21 @@ -import CompanySize from '../../../Types/Company/CompanySize'; +import CompanySize from "../../../Types/Company/CompanySize"; export default CompanySize; -describe('CompanySize', () => { - test('should be in range of 1 to 10', () => { - expect(CompanySize.OneToTen).toBe('1 to 10'); - }); - test('should be in range of 11 to 50', () => { - expect(CompanySize.ElevenToFifty).toBe('11 to 50'); - }); - test('should be in range of 51 to 200', () => { - expect(CompanySize.FiftyToTwoHundred).toBe('51 to 200'); - }); - test('should be in range of 201 to 500', () => { - expect(CompanySize.TwoHundredToFiveHundred).toBe('201 to 500'); - }); - test('should be greater than 500', () => { - expect(CompanySize.FiveHundredAndMore).toBe('500+'); - }); +describe("CompanySize", () => { + test("should be in range of 1 to 10", () => { + expect(CompanySize.OneToTen).toBe("1 to 10"); + }); + test("should be in range of 11 to 50", () => { + expect(CompanySize.ElevenToFifty).toBe("11 to 50"); + }); + test("should be in range of 51 to 200", () => { + expect(CompanySize.FiftyToTwoHundred).toBe("51 to 200"); + }); + test("should be in range of 201 to 500", () => { + expect(CompanySize.TwoHundredToFiveHundred).toBe("201 to 500"); + }); + test("should be greater than 500", () => { + expect(CompanySize.FiveHundredAndMore).toBe("500+"); + }); }); diff --git a/Common/Tests/Types/Company/JobRole.test.ts b/Common/Tests/Types/Company/JobRole.test.ts index 3c1b89e4b3..7bcbcfad22 100644 --- a/Common/Tests/Types/Company/JobRole.test.ts +++ b/Common/Tests/Types/Company/JobRole.test.ts @@ -1,22 +1,22 @@ -import JobRole from '../../../Types/Company/JobRole'; +import JobRole from "../../../Types/Company/JobRole"; -describe('JobRole', () => { - test('should have a role of CEO', () => { - expect(JobRole.CEO).toBe('CEO'); - }); - test('should have a role of CEO', () => { - expect(JobRole.CTO).toBe('CTO'); - }); - test('should have a role of CEO', () => { - expect(JobRole.CIO).toBe('CIO'); - }); - test('should have a role of executive', () => { - expect(JobRole.Executive).toBe('Executive'); - }); - test('should have a role of developer', () => { - expect(JobRole.Developer).toBe('Developer'); - }); - test('should have a role of engineering manager', () => { - expect(JobRole.EngineeringManager).toBe('Engineering Manager'); - }); +describe("JobRole", () => { + test("should have a role of CEO", () => { + expect(JobRole.CEO).toBe("CEO"); + }); + test("should have a role of CEO", () => { + expect(JobRole.CTO).toBe("CTO"); + }); + test("should have a role of CEO", () => { + expect(JobRole.CIO).toBe("CIO"); + }); + test("should have a role of executive", () => { + expect(JobRole.Executive).toBe("Executive"); + }); + test("should have a role of developer", () => { + expect(JobRole.Developer).toBe("Developer"); + }); + test("should have a role of engineering manager", () => { + expect(JobRole.EngineeringManager).toBe("Engineering Manager"); + }); }); diff --git a/Common/Tests/Types/Countries.test.ts b/Common/Tests/Types/Countries.test.ts index 7b2958cd45..75f1d77ecb 100644 --- a/Common/Tests/Types/Countries.test.ts +++ b/Common/Tests/Types/Countries.test.ts @@ -1,303 +1,289 @@ -import Countries from '../../Types/Countries'; +import Countries from "../../Types/Countries"; -describe('enum Countries', () => { - test('each country should have a valid corresponding name ', () => { - expect(Countries['Afghanistan']).toEqual('Afghanistan'); - expect(Countries['Åland Islands']).toEqual('Åland Islands'); - expect(Countries['Albania']).toEqual('Albania'); - expect(Countries['Algeria']).toEqual('Algeria'); - expect(Countries['American Samoa']).toEqual('American Samoa'); - expect(Countries['Andorra']).toEqual('Andorra'); - expect(Countries['Angola']).toEqual('Angola'); - expect(Countries['Anguilla']).toEqual('Anguilla'); - expect(Countries['Antarctica']).toEqual('Antarctica'); - expect(Countries['Antigua and Barbuda']).toEqual('Antigua and Barbuda'); - expect(Countries['Argentina']).toEqual('Argentina'); - expect(Countries['Armenia']).toEqual('Armenia'); - expect(Countries['Aruba']).toEqual('Aruba'); - expect(Countries['Australia']).toEqual('Australia'); - expect(Countries['Austria']).toEqual('Austria'); - expect(Countries['Azerbaijan']).toEqual('Azerbaijan'); - expect(Countries['Bahamas']).toEqual('Bahamas'); - expect(Countries['Bahrain']).toEqual('Bahrain'); - expect(Countries['Bangladesh']).toEqual('Bangladesh'); - expect(Countries['Barbados']).toEqual('Barbados'); - expect(Countries['Belarus']).toEqual('Belarus'); - expect(Countries['Belgium']).toEqual('Belgium'); - expect(Countries['Belize']).toEqual('Belize'); - expect(Countries['Benin']).toEqual('Benin'); - expect(Countries['Bermuda']).toEqual('Bermuda'); - expect(Countries['Bhutan']).toEqual('Bhutan'); - expect(Countries['Bolivia']).toEqual('Bolivia'); - expect(Countries['Bosnia and Herzegovina']).toEqual( - 'Bosnia and Herzegovina' - ); - expect(Countries['Botswana']).toEqual('Botswana'); - expect(Countries['Bouvet Island']).toEqual('Bouvet Island'); - expect(Countries['Brazil']).toEqual('Brazil'); - expect(Countries['British Indian Ocean Territory']).toEqual( - 'British Indian Ocean Territory' - ); - expect(Countries['Brunei Darussalam']).toEqual('Brunei Darussalam'); - expect(Countries['Bulgaria']).toEqual('Bulgaria'); - expect(Countries['Burkina Faso']).toEqual('Burkina Faso'); - expect(Countries['Burundi']).toEqual('Burundi'); - expect(Countries['Cambodia']).toEqual('Cambodia'); - expect(Countries['Cameroon']).toEqual('Cameroon'); - expect(Countries['Canada']).toEqual('Canada'); - expect(Countries['Cape Verde']).toEqual('Cape Verde'); - expect(Countries['Cayman Islands']).toEqual('Cayman Islands'); - expect(Countries['Central African Republic']).toEqual( - 'Central African Republic' - ); - expect(Countries['Chad']).toEqual('Chad'); - expect(Countries['Chile']).toEqual('Chile'); - expect(Countries['China']).toEqual('China'); - expect(Countries['Christmas Island']).toEqual('Christmas Island'); - expect(Countries['Cocos (Keeling) Islands']).toEqual( - 'Cocos (Keeling) Islands' - ); - expect(Countries['Colombia']).toEqual('Colombia'); - expect(Countries['Comoros']).toEqual('Comoros'); - expect(Countries['Congo']).toEqual('Congo'); - expect(Countries['Congo, The Democratic Republic of the']).toEqual( - 'Congo, The Democratic Republic of the' - ); - expect(Countries['Cook Islands']).toEqual('Cook Islands'); - expect(Countries['Costa Rica']).toEqual('Costa Rica'); - expect(Countries["Cote d'Ivoire"]).toEqual("Cote d'Ivoire"); - expect(Countries['Croatia']).toEqual('Croatia'); - expect(Countries['Cuba']).toEqual('Cuba'); - expect(Countries['Cyprus']).toEqual('Cyprus'); - expect(Countries['Czech Republic']).toEqual('Czech Republic'); - expect(Countries['Denmark']).toEqual('Denmark'); - expect(Countries['Djibouti']).toEqual('Djibouti'); - expect(Countries['Dominica']).toEqual('Dominica'); - expect(Countries['Dominican Republic']).toEqual('Dominican Republic'); - expect(Countries['Ecuador']).toEqual('Ecuador'); - expect(Countries['Egypt']).toEqual('Egypt'); - expect(Countries['El Salvador']).toEqual('El Salvador'); - expect(Countries['Equatorial Guinea']).toEqual('Equatorial Guinea'); - expect(Countries['Eritrea']).toEqual('Eritrea'); - expect(Countries['Estonia']).toEqual('Estonia'); - expect(Countries['Ethiopia']).toEqual('Ethiopia'); - expect(Countries['Falkland Islands (Malvinas)']).toEqual( - 'Falkland Islands (Malvinas)' - ); - expect(Countries['Faroe Islands']).toEqual('Faroe Islands'); - expect(Countries['Fiji']).toEqual('Fiji'); - expect(Countries['Finland']).toEqual('Finland'); - expect(Countries['France']).toEqual('France'); - expect(Countries['French Guiana']).toEqual('French Guiana'); - expect(Countries['French Polynesia']).toEqual('French Polynesia'); - expect(Countries['French Southern Territories']).toEqual( - 'French Southern Territories' - ); - expect(Countries['Gabon']).toEqual('Gabon'); - expect(Countries['Gambia']).toEqual('Gambia'); - expect(Countries['Georgia']).toEqual('Georgia'); - expect(Countries['Germany']).toEqual('Germany'); - expect(Countries['Ghana']).toEqual('Ghana'); - expect(Countries['Gibraltar']).toEqual('Gibraltar'); - expect(Countries['Greece']).toEqual('Greece'); - expect(Countries['Greenland']).toEqual('Greenland'); - expect(Countries['Grenada']).toEqual('Grenada'); - expect(Countries['Guadeloupe']).toEqual('Guadeloupe'); - expect(Countries['Guam']).toEqual('Guam'); - expect(Countries['Guatemala']).toEqual('Guatemala'); - expect(Countries['Guernsey']).toEqual('Guernsey'); - expect(Countries['Guinea']).toEqual('Guinea'); - expect(Countries['Guinea-Bissau']).toEqual('Guinea-Bissau'); - expect(Countries['Guyana']).toEqual('Guyana'); - expect(Countries['Haiti']).toEqual('Haiti'); - expect(Countries['Heard Island and Mcdonald Islands']).toEqual( - 'Heard Island and Mcdonald Islands' - ); - expect(Countries['Holy See (Vatican City State)']).toEqual( - 'Holy See (Vatican City State)' - ); - expect(Countries['Honduras']).toEqual('Honduras'); - expect(Countries['Hong Kong']).toEqual('Hong Kong'); - expect(Countries['Hungary']).toEqual('Hungary'); - expect(Countries['Iceland']).toEqual('Iceland'); - expect(Countries['India']).toEqual('India'); - expect(Countries['Indonesia']).toEqual('Indonesia'); - expect(Countries['Iran']).toEqual('Iran'); - expect(Countries['Iraq']).toEqual('Iraq'); - expect(Countries['Ireland']).toEqual('Ireland'); - expect(Countries['Isle of Man']).toEqual('Isle of Man'); - expect(Countries['Israel']).toEqual('Israel'); - expect(Countries['Italy']).toEqual('Italy'); - expect(Countries['Jamaica']).toEqual('Jamaica'); - expect(Countries['Japan']).toEqual('Japan'); - expect(Countries['Jersey']).toEqual('Jersey'); - expect(Countries['Jordan']).toEqual('Jordan'); - expect(Countries['Kazakhstan']).toEqual('Kazakhstan'); - expect(Countries['Kenya']).toEqual('Kenya'); - expect(Countries['Kiribati']).toEqual('Kiribati'); - expect(Countries['South Korea']).toEqual('South Korea'); - expect(Countries['North Korea']).toEqual('North Korea'); - expect(Countries['Kuwait']).toEqual('Kuwait'); - expect(Countries['Kyrgyzstan']).toEqual('Kyrgyzstan'); - expect(Countries["Lao People's Democratic Republic"]).toEqual( - "Lao People's Democratic Republic" - ); - expect(Countries['Latvia']).toEqual('Latvia'); - expect(Countries['Lebanon']).toEqual('Lebanon'); - expect(Countries['Lesotho']).toEqual('Lesotho'); - expect(Countries['Liberia']).toEqual('Liberia'); - expect(Countries['Libyan Arab Jamahiriya']).toEqual( - 'Libyan Arab Jamahiriya' - ); - expect(Countries['Liechtenstein']).toEqual('Liechtenstein'); - expect(Countries['Lithuania']).toEqual('Lithuania'); - expect(Countries['Luxembourg']).toEqual('Luxembourg'); - expect(Countries['Macao']).toEqual('Macao'); - expect(Countries['Macedonia']).toEqual('Macedonia'); - expect(Countries['Madagascar']).toEqual('Madagascar'); - expect(Countries['Malawi']).toEqual('Malawi'); - expect(Countries['Malaysia']).toEqual('Malaysia'); - expect(Countries['Maldives']).toEqual('Maldives'); - expect(Countries['Mali']).toEqual('Mali'); - expect(Countries['Malta']).toEqual('Malta'); - expect(Countries['Marshall Islands']).toEqual('Marshall Islands'); - expect(Countries['Martinique']).toEqual('Martinique'); - expect(Countries['Mauritania']).toEqual('Mauritania'); - expect(Countries['Mauritius']).toEqual('Mauritius'); - expect(Countries['Mayotte']).toEqual('Mayotte'); - expect(Countries['Mexico']).toEqual('Mexico'); - expect(Countries['Micronesia']).toEqual('Micronesia'); - expect(Countries['Moldova']).toEqual('Moldova'); - expect(Countries['Monaco']).toEqual('Monaco'); - expect(Countries['Mongolia']).toEqual('Mongolia'); - expect(Countries['Montserrat']).toEqual('Montserrat'); - expect(Countries['Morocco']).toEqual('Morocco'); - expect(Countries['Mozambique']).toEqual('Mozambique'); - expect(Countries['Myanmar']).toEqual('Myanmar'); - expect(Countries['Namibia']).toEqual('Namibia'); - expect(Countries['Nauru']).toEqual('Nauru'); - expect(Countries['Nepal']).toEqual('Nepal'); - expect(Countries['Netherlands']).toEqual('Netherlands'); - expect(Countries['Netherlands Antilles']).toEqual( - 'Netherlands Antilles' - ); - expect(Countries['New Caledonia']).toEqual('New Caledonia'); - expect(Countries['New Zealand']).toEqual('New Zealand'); - expect(Countries['Nicaragua']).toEqual('Nicaragua'); - expect(Countries['Niger']).toEqual('Niger'); - expect(Countries['Nigeria']).toEqual('Nigeria'); - expect(Countries['Niue']).toEqual('Niue'); - expect(Countries['Norfolk Island']).toEqual('Norfolk Island'); - expect(Countries['Northern Mariana Islands']).toEqual( - 'Northern Mariana Islands' - ); - expect(Countries['Norway']).toEqual('Norway'); - expect(Countries['Oman']).toEqual('Oman'); - expect(Countries['Pakistan']).toEqual('Pakistan'); - expect(Countries['Palau']).toEqual('Palau'); - expect(Countries['Palestine State']).toEqual('Palestine State'); - expect(Countries['Panama']).toEqual('Panama'); - expect(Countries['Papua New Guinea']).toEqual('Papua New Guinea'); - expect(Countries['Paraguay']).toEqual('Paraguay'); - expect(Countries['Peru']).toEqual('Peru'); - expect(Countries['Philippines']).toEqual('Philippines'); - expect(Countries['Pitcairn']).toEqual('Pitcairn'); - expect(Countries['Poland']).toEqual('Poland'); - expect(Countries['Portugal']).toEqual('Portugal'); - expect(Countries['Puerto Rico']).toEqual('Puerto Rico'); - expect(Countries['Qatar']).toEqual('Qatar'); - expect(Countries['Reunion']).toEqual('Reunion'); - expect(Countries['Romania']).toEqual('Romania'); - expect(Countries['Russian Federation']).toEqual('Russian Federation'); - expect(Countries['Rwanda']).toEqual('Rwanda'); - expect(Countries['Saint Helena']).toEqual('Saint Helena'); - expect(Countries['Saint Kitts and Nevis']).toEqual( - 'Saint Kitts and Nevis' - ); - expect(Countries['Saint Lucia']).toEqual('Saint Lucia'); - expect(Countries['Saint Pierre and Miquelon']).toEqual( - 'Saint Pierre and Miquelon' - ); - expect(Countries['Saint Vincent and the Grenadines']).toEqual( - 'Saint Vincent and the Grenadines' - ); - expect(Countries['Samoa']).toEqual('Samoa'); - expect(Countries['San Marino']).toEqual('San Marino'); - expect(Countries['Sao Tome and Principe']).toEqual( - 'Sao Tome and Principe' - ); - expect(Countries['Saudi Arabia']).toEqual('Saudi Arabia'); - expect(Countries['Senegal']).toEqual('Senegal'); - expect(Countries['Serbia and Montenegro']).toEqual( - 'Serbia and Montenegro' - ); - expect(Countries['Seychelles']).toEqual('Seychelles'); - expect(Countries['Sierra Leone']).toEqual('Sierra Leone'); - expect(Countries['Singapore']).toEqual('Singapore'); - expect(Countries['Slovakia']).toEqual('Slovakia'); - expect(Countries['Slovenia']).toEqual('Slovenia'); - expect(Countries['Solomon Islands']).toEqual('Solomon Islands'); - expect(Countries['Somalia']).toEqual('Somalia'); - expect(Countries['South Africa']).toEqual('South Africa'); - expect( - Countries['South Georgia and the South Sandwich Islands'] - ).toEqual('South Georgia and the South Sandwich Islands'); - expect(Countries['Spain']).toEqual('Spain'); - expect(Countries['Sri Lanka']).toEqual('Sri Lanka'); - expect(Countries['Sudan']).toEqual('Sudan'); - expect(Countries['Suriname']).toEqual('Suriname'); - expect(Countries['Svalbard and Jan Mayen']).toEqual( - 'Svalbard and Jan Mayen' - ); - expect(Countries['Swaziland']).toEqual('Swaziland'); - expect(Countries['Sweden']).toEqual('Sweden'); - expect(Countries['Switzerland']).toEqual('Switzerland'); - expect(Countries['Syrian Arab Republic']).toEqual( - 'Syrian Arab Republic' - ); - expect(Countries['Taiwan, Province of China']).toEqual( - 'Taiwan, Province of China' - ); - expect(Countries['Tajikistan']).toEqual('Tajikistan'); - expect(Countries['Tanzania']).toEqual('Tanzania'); - expect(Countries['Thailand']).toEqual('Thailand'); - expect(Countries['Timor-Leste']).toEqual('Timor-Leste'); - expect(Countries['Togo']).toEqual('Togo'); - expect(Countries['Tokelau']).toEqual('Tokelau'); - expect(Countries['Tonga']).toEqual('Tonga'); - expect(Countries['Trinidad and Tobago']).toEqual('Trinidad and Tobago'); - expect(Countries['Tunisia']).toEqual('Tunisia'); - expect(Countries['Turkey']).toEqual('Turkey'); - expect(Countries['Turkmenistan']).toEqual('Turkmenistan'); - expect(Countries['Turks and Caicos Islands']).toEqual( - 'Turks and Caicos Islands' - ); - expect(Countries['Tuvalu']).toEqual('Tuvalu'); - expect(Countries['Uganda']).toEqual('Uganda'); - expect(Countries['Ukraine']).toEqual('Ukraine'); - expect(Countries['United Arab Emirates']).toEqual( - 'United Arab Emirates' - ); - expect(Countries['United Kingdom']).toEqual('United Kingdom'); - expect(Countries['United States']).toEqual('United States'); - expect(Countries['United States Minor Outlying Islands']).toEqual( - 'United States Minor Outlying Islands' - ); - expect(Countries['Uruguay']).toEqual('Uruguay'); - expect(Countries['Uzbekistan']).toEqual('Uzbekistan'); - expect(Countries['Vanuatu']).toEqual('Vanuatu'); - expect(Countries['Venezuela']).toEqual('Venezuela'); - expect(Countries['Viet Nam']).toEqual('Viet Nam'); - expect(Countries['Virgin Islands, British']).toEqual( - 'Virgin Islands, British' - ); - expect(Countries['Virgin Islands, U.S.']).toEqual( - 'Virgin Islands, U.S.' - ); - expect(Countries['Wallis and Futuna']).toEqual('Wallis and Futuna'); - expect(Countries['Western Sahara']).toEqual('Western Sahara'); - expect(Countries['Yemen']).toEqual('Yemen'); - expect(Countries['Zambia']).toEqual('Zambia'); - expect(Countries['Zimbabwe']).toEqual('Zimbabwe'); - }); +describe("enum Countries", () => { + test("each country should have a valid corresponding name ", () => { + expect(Countries["Afghanistan"]).toEqual("Afghanistan"); + expect(Countries["Åland Islands"]).toEqual("Åland Islands"); + expect(Countries["Albania"]).toEqual("Albania"); + expect(Countries["Algeria"]).toEqual("Algeria"); + expect(Countries["American Samoa"]).toEqual("American Samoa"); + expect(Countries["Andorra"]).toEqual("Andorra"); + expect(Countries["Angola"]).toEqual("Angola"); + expect(Countries["Anguilla"]).toEqual("Anguilla"); + expect(Countries["Antarctica"]).toEqual("Antarctica"); + expect(Countries["Antigua and Barbuda"]).toEqual("Antigua and Barbuda"); + expect(Countries["Argentina"]).toEqual("Argentina"); + expect(Countries["Armenia"]).toEqual("Armenia"); + expect(Countries["Aruba"]).toEqual("Aruba"); + expect(Countries["Australia"]).toEqual("Australia"); + expect(Countries["Austria"]).toEqual("Austria"); + expect(Countries["Azerbaijan"]).toEqual("Azerbaijan"); + expect(Countries["Bahamas"]).toEqual("Bahamas"); + expect(Countries["Bahrain"]).toEqual("Bahrain"); + expect(Countries["Bangladesh"]).toEqual("Bangladesh"); + expect(Countries["Barbados"]).toEqual("Barbados"); + expect(Countries["Belarus"]).toEqual("Belarus"); + expect(Countries["Belgium"]).toEqual("Belgium"); + expect(Countries["Belize"]).toEqual("Belize"); + expect(Countries["Benin"]).toEqual("Benin"); + expect(Countries["Bermuda"]).toEqual("Bermuda"); + expect(Countries["Bhutan"]).toEqual("Bhutan"); + expect(Countries["Bolivia"]).toEqual("Bolivia"); + expect(Countries["Bosnia and Herzegovina"]).toEqual( + "Bosnia and Herzegovina", + ); + expect(Countries["Botswana"]).toEqual("Botswana"); + expect(Countries["Bouvet Island"]).toEqual("Bouvet Island"); + expect(Countries["Brazil"]).toEqual("Brazil"); + expect(Countries["British Indian Ocean Territory"]).toEqual( + "British Indian Ocean Territory", + ); + expect(Countries["Brunei Darussalam"]).toEqual("Brunei Darussalam"); + expect(Countries["Bulgaria"]).toEqual("Bulgaria"); + expect(Countries["Burkina Faso"]).toEqual("Burkina Faso"); + expect(Countries["Burundi"]).toEqual("Burundi"); + expect(Countries["Cambodia"]).toEqual("Cambodia"); + expect(Countries["Cameroon"]).toEqual("Cameroon"); + expect(Countries["Canada"]).toEqual("Canada"); + expect(Countries["Cape Verde"]).toEqual("Cape Verde"); + expect(Countries["Cayman Islands"]).toEqual("Cayman Islands"); + expect(Countries["Central African Republic"]).toEqual( + "Central African Republic", + ); + expect(Countries["Chad"]).toEqual("Chad"); + expect(Countries["Chile"]).toEqual("Chile"); + expect(Countries["China"]).toEqual("China"); + expect(Countries["Christmas Island"]).toEqual("Christmas Island"); + expect(Countries["Cocos (Keeling) Islands"]).toEqual( + "Cocos (Keeling) Islands", + ); + expect(Countries["Colombia"]).toEqual("Colombia"); + expect(Countries["Comoros"]).toEqual("Comoros"); + expect(Countries["Congo"]).toEqual("Congo"); + expect(Countries["Congo, The Democratic Republic of the"]).toEqual( + "Congo, The Democratic Republic of the", + ); + expect(Countries["Cook Islands"]).toEqual("Cook Islands"); + expect(Countries["Costa Rica"]).toEqual("Costa Rica"); + expect(Countries["Cote d'Ivoire"]).toEqual("Cote d'Ivoire"); + expect(Countries["Croatia"]).toEqual("Croatia"); + expect(Countries["Cuba"]).toEqual("Cuba"); + expect(Countries["Cyprus"]).toEqual("Cyprus"); + expect(Countries["Czech Republic"]).toEqual("Czech Republic"); + expect(Countries["Denmark"]).toEqual("Denmark"); + expect(Countries["Djibouti"]).toEqual("Djibouti"); + expect(Countries["Dominica"]).toEqual("Dominica"); + expect(Countries["Dominican Republic"]).toEqual("Dominican Republic"); + expect(Countries["Ecuador"]).toEqual("Ecuador"); + expect(Countries["Egypt"]).toEqual("Egypt"); + expect(Countries["El Salvador"]).toEqual("El Salvador"); + expect(Countries["Equatorial Guinea"]).toEqual("Equatorial Guinea"); + expect(Countries["Eritrea"]).toEqual("Eritrea"); + expect(Countries["Estonia"]).toEqual("Estonia"); + expect(Countries["Ethiopia"]).toEqual("Ethiopia"); + expect(Countries["Falkland Islands (Malvinas)"]).toEqual( + "Falkland Islands (Malvinas)", + ); + expect(Countries["Faroe Islands"]).toEqual("Faroe Islands"); + expect(Countries["Fiji"]).toEqual("Fiji"); + expect(Countries["Finland"]).toEqual("Finland"); + expect(Countries["France"]).toEqual("France"); + expect(Countries["French Guiana"]).toEqual("French Guiana"); + expect(Countries["French Polynesia"]).toEqual("French Polynesia"); + expect(Countries["French Southern Territories"]).toEqual( + "French Southern Territories", + ); + expect(Countries["Gabon"]).toEqual("Gabon"); + expect(Countries["Gambia"]).toEqual("Gambia"); + expect(Countries["Georgia"]).toEqual("Georgia"); + expect(Countries["Germany"]).toEqual("Germany"); + expect(Countries["Ghana"]).toEqual("Ghana"); + expect(Countries["Gibraltar"]).toEqual("Gibraltar"); + expect(Countries["Greece"]).toEqual("Greece"); + expect(Countries["Greenland"]).toEqual("Greenland"); + expect(Countries["Grenada"]).toEqual("Grenada"); + expect(Countries["Guadeloupe"]).toEqual("Guadeloupe"); + expect(Countries["Guam"]).toEqual("Guam"); + expect(Countries["Guatemala"]).toEqual("Guatemala"); + expect(Countries["Guernsey"]).toEqual("Guernsey"); + expect(Countries["Guinea"]).toEqual("Guinea"); + expect(Countries["Guinea-Bissau"]).toEqual("Guinea-Bissau"); + expect(Countries["Guyana"]).toEqual("Guyana"); + expect(Countries["Haiti"]).toEqual("Haiti"); + expect(Countries["Heard Island and Mcdonald Islands"]).toEqual( + "Heard Island and Mcdonald Islands", + ); + expect(Countries["Holy See (Vatican City State)"]).toEqual( + "Holy See (Vatican City State)", + ); + expect(Countries["Honduras"]).toEqual("Honduras"); + expect(Countries["Hong Kong"]).toEqual("Hong Kong"); + expect(Countries["Hungary"]).toEqual("Hungary"); + expect(Countries["Iceland"]).toEqual("Iceland"); + expect(Countries["India"]).toEqual("India"); + expect(Countries["Indonesia"]).toEqual("Indonesia"); + expect(Countries["Iran"]).toEqual("Iran"); + expect(Countries["Iraq"]).toEqual("Iraq"); + expect(Countries["Ireland"]).toEqual("Ireland"); + expect(Countries["Isle of Man"]).toEqual("Isle of Man"); + expect(Countries["Israel"]).toEqual("Israel"); + expect(Countries["Italy"]).toEqual("Italy"); + expect(Countries["Jamaica"]).toEqual("Jamaica"); + expect(Countries["Japan"]).toEqual("Japan"); + expect(Countries["Jersey"]).toEqual("Jersey"); + expect(Countries["Jordan"]).toEqual("Jordan"); + expect(Countries["Kazakhstan"]).toEqual("Kazakhstan"); + expect(Countries["Kenya"]).toEqual("Kenya"); + expect(Countries["Kiribati"]).toEqual("Kiribati"); + expect(Countries["South Korea"]).toEqual("South Korea"); + expect(Countries["North Korea"]).toEqual("North Korea"); + expect(Countries["Kuwait"]).toEqual("Kuwait"); + expect(Countries["Kyrgyzstan"]).toEqual("Kyrgyzstan"); + expect(Countries["Lao People's Democratic Republic"]).toEqual( + "Lao People's Democratic Republic", + ); + expect(Countries["Latvia"]).toEqual("Latvia"); + expect(Countries["Lebanon"]).toEqual("Lebanon"); + expect(Countries["Lesotho"]).toEqual("Lesotho"); + expect(Countries["Liberia"]).toEqual("Liberia"); + expect(Countries["Libyan Arab Jamahiriya"]).toEqual( + "Libyan Arab Jamahiriya", + ); + expect(Countries["Liechtenstein"]).toEqual("Liechtenstein"); + expect(Countries["Lithuania"]).toEqual("Lithuania"); + expect(Countries["Luxembourg"]).toEqual("Luxembourg"); + expect(Countries["Macao"]).toEqual("Macao"); + expect(Countries["Macedonia"]).toEqual("Macedonia"); + expect(Countries["Madagascar"]).toEqual("Madagascar"); + expect(Countries["Malawi"]).toEqual("Malawi"); + expect(Countries["Malaysia"]).toEqual("Malaysia"); + expect(Countries["Maldives"]).toEqual("Maldives"); + expect(Countries["Mali"]).toEqual("Mali"); + expect(Countries["Malta"]).toEqual("Malta"); + expect(Countries["Marshall Islands"]).toEqual("Marshall Islands"); + expect(Countries["Martinique"]).toEqual("Martinique"); + expect(Countries["Mauritania"]).toEqual("Mauritania"); + expect(Countries["Mauritius"]).toEqual("Mauritius"); + expect(Countries["Mayotte"]).toEqual("Mayotte"); + expect(Countries["Mexico"]).toEqual("Mexico"); + expect(Countries["Micronesia"]).toEqual("Micronesia"); + expect(Countries["Moldova"]).toEqual("Moldova"); + expect(Countries["Monaco"]).toEqual("Monaco"); + expect(Countries["Mongolia"]).toEqual("Mongolia"); + expect(Countries["Montserrat"]).toEqual("Montserrat"); + expect(Countries["Morocco"]).toEqual("Morocco"); + expect(Countries["Mozambique"]).toEqual("Mozambique"); + expect(Countries["Myanmar"]).toEqual("Myanmar"); + expect(Countries["Namibia"]).toEqual("Namibia"); + expect(Countries["Nauru"]).toEqual("Nauru"); + expect(Countries["Nepal"]).toEqual("Nepal"); + expect(Countries["Netherlands"]).toEqual("Netherlands"); + expect(Countries["Netherlands Antilles"]).toEqual("Netherlands Antilles"); + expect(Countries["New Caledonia"]).toEqual("New Caledonia"); + expect(Countries["New Zealand"]).toEqual("New Zealand"); + expect(Countries["Nicaragua"]).toEqual("Nicaragua"); + expect(Countries["Niger"]).toEqual("Niger"); + expect(Countries["Nigeria"]).toEqual("Nigeria"); + expect(Countries["Niue"]).toEqual("Niue"); + expect(Countries["Norfolk Island"]).toEqual("Norfolk Island"); + expect(Countries["Northern Mariana Islands"]).toEqual( + "Northern Mariana Islands", + ); + expect(Countries["Norway"]).toEqual("Norway"); + expect(Countries["Oman"]).toEqual("Oman"); + expect(Countries["Pakistan"]).toEqual("Pakistan"); + expect(Countries["Palau"]).toEqual("Palau"); + expect(Countries["Palestine State"]).toEqual("Palestine State"); + expect(Countries["Panama"]).toEqual("Panama"); + expect(Countries["Papua New Guinea"]).toEqual("Papua New Guinea"); + expect(Countries["Paraguay"]).toEqual("Paraguay"); + expect(Countries["Peru"]).toEqual("Peru"); + expect(Countries["Philippines"]).toEqual("Philippines"); + expect(Countries["Pitcairn"]).toEqual("Pitcairn"); + expect(Countries["Poland"]).toEqual("Poland"); + expect(Countries["Portugal"]).toEqual("Portugal"); + expect(Countries["Puerto Rico"]).toEqual("Puerto Rico"); + expect(Countries["Qatar"]).toEqual("Qatar"); + expect(Countries["Reunion"]).toEqual("Reunion"); + expect(Countries["Romania"]).toEqual("Romania"); + expect(Countries["Russian Federation"]).toEqual("Russian Federation"); + expect(Countries["Rwanda"]).toEqual("Rwanda"); + expect(Countries["Saint Helena"]).toEqual("Saint Helena"); + expect(Countries["Saint Kitts and Nevis"]).toEqual("Saint Kitts and Nevis"); + expect(Countries["Saint Lucia"]).toEqual("Saint Lucia"); + expect(Countries["Saint Pierre and Miquelon"]).toEqual( + "Saint Pierre and Miquelon", + ); + expect(Countries["Saint Vincent and the Grenadines"]).toEqual( + "Saint Vincent and the Grenadines", + ); + expect(Countries["Samoa"]).toEqual("Samoa"); + expect(Countries["San Marino"]).toEqual("San Marino"); + expect(Countries["Sao Tome and Principe"]).toEqual("Sao Tome and Principe"); + expect(Countries["Saudi Arabia"]).toEqual("Saudi Arabia"); + expect(Countries["Senegal"]).toEqual("Senegal"); + expect(Countries["Serbia and Montenegro"]).toEqual("Serbia and Montenegro"); + expect(Countries["Seychelles"]).toEqual("Seychelles"); + expect(Countries["Sierra Leone"]).toEqual("Sierra Leone"); + expect(Countries["Singapore"]).toEqual("Singapore"); + expect(Countries["Slovakia"]).toEqual("Slovakia"); + expect(Countries["Slovenia"]).toEqual("Slovenia"); + expect(Countries["Solomon Islands"]).toEqual("Solomon Islands"); + expect(Countries["Somalia"]).toEqual("Somalia"); + expect(Countries["South Africa"]).toEqual("South Africa"); + expect(Countries["South Georgia and the South Sandwich Islands"]).toEqual( + "South Georgia and the South Sandwich Islands", + ); + expect(Countries["Spain"]).toEqual("Spain"); + expect(Countries["Sri Lanka"]).toEqual("Sri Lanka"); + expect(Countries["Sudan"]).toEqual("Sudan"); + expect(Countries["Suriname"]).toEqual("Suriname"); + expect(Countries["Svalbard and Jan Mayen"]).toEqual( + "Svalbard and Jan Mayen", + ); + expect(Countries["Swaziland"]).toEqual("Swaziland"); + expect(Countries["Sweden"]).toEqual("Sweden"); + expect(Countries["Switzerland"]).toEqual("Switzerland"); + expect(Countries["Syrian Arab Republic"]).toEqual("Syrian Arab Republic"); + expect(Countries["Taiwan, Province of China"]).toEqual( + "Taiwan, Province of China", + ); + expect(Countries["Tajikistan"]).toEqual("Tajikistan"); + expect(Countries["Tanzania"]).toEqual("Tanzania"); + expect(Countries["Thailand"]).toEqual("Thailand"); + expect(Countries["Timor-Leste"]).toEqual("Timor-Leste"); + expect(Countries["Togo"]).toEqual("Togo"); + expect(Countries["Tokelau"]).toEqual("Tokelau"); + expect(Countries["Tonga"]).toEqual("Tonga"); + expect(Countries["Trinidad and Tobago"]).toEqual("Trinidad and Tobago"); + expect(Countries["Tunisia"]).toEqual("Tunisia"); + expect(Countries["Turkey"]).toEqual("Turkey"); + expect(Countries["Turkmenistan"]).toEqual("Turkmenistan"); + expect(Countries["Turks and Caicos Islands"]).toEqual( + "Turks and Caicos Islands", + ); + expect(Countries["Tuvalu"]).toEqual("Tuvalu"); + expect(Countries["Uganda"]).toEqual("Uganda"); + expect(Countries["Ukraine"]).toEqual("Ukraine"); + expect(Countries["United Arab Emirates"]).toEqual("United Arab Emirates"); + expect(Countries["United Kingdom"]).toEqual("United Kingdom"); + expect(Countries["United States"]).toEqual("United States"); + expect(Countries["United States Minor Outlying Islands"]).toEqual( + "United States Minor Outlying Islands", + ); + expect(Countries["Uruguay"]).toEqual("Uruguay"); + expect(Countries["Uzbekistan"]).toEqual("Uzbekistan"); + expect(Countries["Vanuatu"]).toEqual("Vanuatu"); + expect(Countries["Venezuela"]).toEqual("Venezuela"); + expect(Countries["Viet Nam"]).toEqual("Viet Nam"); + expect(Countries["Virgin Islands, British"]).toEqual( + "Virgin Islands, British", + ); + expect(Countries["Virgin Islands, U.S."]).toEqual("Virgin Islands, U.S."); + expect(Countries["Wallis and Futuna"]).toEqual("Wallis and Futuna"); + expect(Countries["Western Sahara"]).toEqual("Western Sahara"); + expect(Countries["Yemen"]).toEqual("Yemen"); + expect(Countries["Zambia"]).toEqual("Zambia"); + expect(Countries["Zimbabwe"]).toEqual("Zimbabwe"); + }); }); diff --git a/Common/Tests/Types/Database/ColumnLength.test.ts b/Common/Tests/Types/Database/ColumnLength.test.ts index 1c663b810e..611de5e828 100644 --- a/Common/Tests/Types/Database/ColumnLength.test.ts +++ b/Common/Tests/Types/Database/ColumnLength.test.ts @@ -1,55 +1,55 @@ -import ColumnLength from '../../../Types/Database/ColumnLength'; +import ColumnLength from "../../../Types/Database/ColumnLength"; -describe('enum ColumnLength', () => { - test('ColumnLength.Version', () => { - expect(ColumnLength.Version).toEqual(30); - }); +describe("enum ColumnLength", () => { + test("ColumnLength.Version", () => { + expect(ColumnLength.Version).toEqual(30); + }); - test('ColumnLength.Slug', () => { - expect(ColumnLength.Slug).toEqual(100); - }); + test("ColumnLength.Slug", () => { + expect(ColumnLength.Slug).toEqual(100); + }); - test('ColumnLength.Email', () => { - expect(ColumnLength.Email).toEqual(100); - }); + test("ColumnLength.Email", () => { + expect(ColumnLength.Email).toEqual(100); + }); - test('ColumnLength.Color', () => { - expect(ColumnLength.Color).toEqual(7); - }); + test("ColumnLength.Color", () => { + expect(ColumnLength.Color).toEqual(7); + }); - test('ColumnLength.Name', () => { - expect(ColumnLength.Name).toEqual(50); - }); + test("ColumnLength.Name", () => { + expect(ColumnLength.Name).toEqual(50); + }); - test('ColumnLength.Description', () => { - expect(ColumnLength.Description).toEqual(500); - }); + test("ColumnLength.Description", () => { + expect(ColumnLength.Description).toEqual(500); + }); - test('ColumnLength.LongText', () => { - expect(ColumnLength.LongText).toEqual(500); - }); + test("ColumnLength.LongText", () => { + expect(ColumnLength.LongText).toEqual(500); + }); - test('ColumnLength.Password', () => { - expect(ColumnLength.Password).toEqual(500); - }); + test("ColumnLength.Password", () => { + expect(ColumnLength.Password).toEqual(500); + }); - test('ColumnLength.ShortURL', () => { - expect(ColumnLength.ShortURL).toEqual(100); - }); + test("ColumnLength.ShortURL", () => { + expect(ColumnLength.ShortURL).toEqual(100); + }); - test('ColumnLength.ShortText', () => { - expect(ColumnLength.ShortText).toEqual(100); - }); + test("ColumnLength.ShortText", () => { + expect(ColumnLength.ShortText).toEqual(100); + }); - test('ColumnLength.HashedString', () => { - expect(ColumnLength.HashedString).toEqual(64); - }); + test("ColumnLength.HashedString", () => { + expect(ColumnLength.HashedString).toEqual(64); + }); - test('ColumnLength.Phone', () => { - expect(ColumnLength.Phone).toEqual(30); - }); + test("ColumnLength.Phone", () => { + expect(ColumnLength.Phone).toEqual(30); + }); - test('ColumnLength.OTP', () => { - expect(ColumnLength.OTP).toEqual(8); - }); + test("ColumnLength.OTP", () => { + expect(ColumnLength.OTP).toEqual(8); + }); }); diff --git a/Common/Tests/Types/Database/ColumnType.test.ts b/Common/Tests/Types/Database/ColumnType.test.ts index 54685db5df..928e16c48c 100644 --- a/Common/Tests/Types/Database/ColumnType.test.ts +++ b/Common/Tests/Types/Database/ColumnType.test.ts @@ -1,103 +1,103 @@ -import ColumnType from '../../../Types/Database/ColumnType'; +import ColumnType from "../../../Types/Database/ColumnType"; -describe('enum ColumnType', () => { - test('ColumnType.Color should be varchar', () => { - expect(ColumnType.Color).toEqual('varchar'); - }); +describe("enum ColumnType", () => { + test("ColumnType.Color should be varchar", () => { + expect(ColumnType.Color).toEqual("varchar"); + }); - test('ColumnType.Version', () => { - expect(ColumnType.Version).toEqual('varchar'); - }); + test("ColumnType.Version", () => { + expect(ColumnType.Version).toEqual("varchar"); + }); - test('ColumnType.Phone', () => { - expect(ColumnType.Phone).toEqual('varchar'); - }); + test("ColumnType.Phone", () => { + expect(ColumnType.Phone).toEqual("varchar"); + }); - test('ColumnType.HashedString', () => { - expect(ColumnType.HashedString).toEqual('varchar'); - }); + test("ColumnType.HashedString", () => { + expect(ColumnType.HashedString).toEqual("varchar"); + }); - test('ColumnType.Password', () => { - expect(ColumnType.Password).toEqual('varchar'); - }); + test("ColumnType.Password", () => { + expect(ColumnType.Password).toEqual("varchar"); + }); - test('ColumnType.Email', () => { - expect(ColumnType.Email).toEqual('varchar'); - }); + test("ColumnType.Email", () => { + expect(ColumnType.Email).toEqual("varchar"); + }); - test('ColumnType.Slug', () => { - expect(ColumnType.Slug).toEqual('varchar'); - }); + test("ColumnType.Slug", () => { + expect(ColumnType.Slug).toEqual("varchar"); + }); - test('ColumnType.Name', () => { - expect(ColumnType.Name).toEqual('varchar'); - }); + test("ColumnType.Name", () => { + expect(ColumnType.Name).toEqual("varchar"); + }); - test('ColumnType.Description', () => { - expect(ColumnType.Description).toEqual('varchar'); - }); + test("ColumnType.Description", () => { + expect(ColumnType.Description).toEqual("varchar"); + }); - test('ColumnType.ObjectID', () => { - expect(ColumnType.ObjectID).toEqual('uuid'); - }); + test("ColumnType.ObjectID", () => { + expect(ColumnType.ObjectID).toEqual("uuid"); + }); - test('ColumnType.ShortURL', () => { - expect(ColumnType.ShortURL).toEqual('varchar'); - }); + test("ColumnType.ShortURL", () => { + expect(ColumnType.ShortURL).toEqual("varchar"); + }); - test('ColumnType.LongURL', () => { - expect(ColumnType.LongURL).toEqual('text'); - }); + test("ColumnType.LongURL", () => { + expect(ColumnType.LongURL).toEqual("text"); + }); - test('ColumnType.ShortText', () => { - expect(ColumnType.ShortText).toEqual('varchar'); - }); + test("ColumnType.ShortText", () => { + expect(ColumnType.ShortText).toEqual("varchar"); + }); - test('ColumnType.OTP', () => { - expect(ColumnType.OTP).toEqual('varchar'); - }); + test("ColumnType.OTP", () => { + expect(ColumnType.OTP).toEqual("varchar"); + }); - test('ColumnType.LongText', () => { - expect(ColumnType.LongText).toEqual('varchar'); - }); + test("ColumnType.LongText", () => { + expect(ColumnType.LongText).toEqual("varchar"); + }); - test('ColumnType.VeryLongText', () => { - expect(ColumnType.VeryLongText).toEqual('text'); - }); + test("ColumnType.VeryLongText", () => { + expect(ColumnType.VeryLongText).toEqual("text"); + }); - test('ColumnType.Date', () => { - expect(ColumnType.Date).toEqual('timestamptz'); - }); + test("ColumnType.Date", () => { + expect(ColumnType.Date).toEqual("timestamptz"); + }); - test('ColumnType.Boolean', () => { - expect(ColumnType.Boolean).toEqual('boolean'); - }); + test("ColumnType.Boolean", () => { + expect(ColumnType.Boolean).toEqual("boolean"); + }); - test('ColumnType.Array', () => { - expect(ColumnType.Array).toEqual('simple-array'); - }); + test("ColumnType.Array", () => { + expect(ColumnType.Array).toEqual("simple-array"); + }); - test('ColumnType.SmallPositiveNumber', () => { - expect(ColumnType.SmallPositiveNumber).toEqual('smallint'); - }); + test("ColumnType.SmallPositiveNumber", () => { + expect(ColumnType.SmallPositiveNumber).toEqual("smallint"); + }); - test('ColumnType.PositiveNumber', () => { - expect(ColumnType.PositiveNumber).toEqual('integer'); - }); + test("ColumnType.PositiveNumber", () => { + expect(ColumnType.PositiveNumber).toEqual("integer"); + }); - test('ColumnType.BigPositiveNumber', () => { - expect(ColumnType.BigPositiveNumber).toEqual('bigint'); - }); + test("ColumnType.BigPositiveNumber", () => { + expect(ColumnType.BigPositiveNumber).toEqual("bigint"); + }); - test('ColumnType.SmallNumber', () => { - expect(ColumnType.SmallNumber).toEqual('smallint'); - }); + test("ColumnType.SmallNumber", () => { + expect(ColumnType.SmallNumber).toEqual("smallint"); + }); - test('ColumnType.Number', () => { - expect(ColumnType.Number).toEqual('integer'); - }); + test("ColumnType.Number", () => { + expect(ColumnType.Number).toEqual("integer"); + }); - test('ColumnType.BigNumber', () => { - expect(ColumnType.BigNumber).toEqual('bigint'); - }); + test("ColumnType.BigNumber", () => { + expect(ColumnType.BigNumber).toEqual("bigint"); + }); }); diff --git a/Common/Tests/Types/Database/Columns.test.ts b/Common/Tests/Types/Database/Columns.test.ts index b35f1c1bd5..5e73b73b11 100644 --- a/Common/Tests/Types/Database/Columns.test.ts +++ b/Common/Tests/Types/Database/Columns.test.ts @@ -1,22 +1,22 @@ -import Columns from '../../../Types/Database/Columns'; +import Columns from "../../../Types/Database/Columns"; -describe('class Columns', () => { - test('it should return a valid object if Columns is valid', () => { - expect(new Columns(['col1', 'col2'])).toBeInstanceOf(Columns); - expect(new Columns(['col1', 'col2']).columns).toStrictEqual([ - 'col1', - 'col2', - ]); - }); +describe("class Columns", () => { + test("it should return a valid object if Columns is valid", () => { + expect(new Columns(["col1", "col2"])).toBeInstanceOf(Columns); + expect(new Columns(["col1", "col2"]).columns).toStrictEqual([ + "col1", + "col2", + ]); + }); - test('it should add column', () => { - const cols: Array = ['col1', 'col2']; - new Columns(cols).addColumn('col3'); - expect(new Columns(cols).columns).toContain('col3'); - }); + test("it should add column", () => { + const cols: Array = ["col1", "col2"]; + new Columns(cols).addColumn("col3"); + expect(new Columns(cols).columns).toContain("col3"); + }); - test('it should return true if column is included', () => { - const cols: Array = ['col1', 'col2']; - expect(new Columns(cols).hasColumn('col2')).toBeTruthy(); - }); + test("it should return true if column is included", () => { + const cols: Array = ["col1", "col2"]; + expect(new Columns(cols).hasColumn("col2")).toBeTruthy(); + }); }); diff --git a/Common/Tests/Types/Database/CompareBase.test.ts b/Common/Tests/Types/Database/CompareBase.test.ts index 3029fd2d1e..6b0e0a26af 100644 --- a/Common/Tests/Types/Database/CompareBase.test.ts +++ b/Common/Tests/Types/Database/CompareBase.test.ts @@ -1,41 +1,41 @@ -import CompareBase from '../../../Types/Database/CompareBase'; -import BadDataException from '../../../Types/Exception/BadDataException'; -import { describe, expect, it } from '@jest/globals'; +import CompareBase from "../../../Types/Database/CompareBase"; +import BadDataException from "../../../Types/Exception/BadDataException"; +import { describe, expect, it } from "@jest/globals"; -describe('CompareBase', () => { - describe('toString', () => { - it('should return string representation of value', () => { - const compareBase: CompareBase = new CompareBase(10); - expect(compareBase.toString()).toBe('10'); - }); +describe("CompareBase", () => { + describe("toString", () => { + it("should return string representation of value", () => { + const compareBase: CompareBase = new CompareBase(10); + expect(compareBase.toString()).toBe("10"); + }); + }); + + describe("toNumber", () => { + it("should return value as number if it is a number", () => { + const compareBase: CompareBase = new CompareBase(10); + expect(compareBase.toNumber()).toBe(10); }); - describe('toNumber', () => { - it('should return value as number if it is a number', () => { - const compareBase: CompareBase = new CompareBase(10); - expect(compareBase.toNumber()).toBe(10); - }); + it("should throw BadDataException if value is not a number", () => { + const compareBase: CompareBase = new CompareBase(new Date()); + expect(() => { + return compareBase.toNumber(); + }).toThrow(BadDataException); + }); + }); - it('should throw BadDataException if value is not a number', () => { - const compareBase: CompareBase = new CompareBase(new Date()); - expect(() => { - return compareBase.toNumber(); - }).toThrow(BadDataException); - }); + describe("toDate", () => { + it("should return value as date object if it is a date object", () => { + const date: Date = new Date(); + const compareBase: CompareBase = new CompareBase(date); + expect(compareBase.toDate()).toBe(date); }); - describe('toDate', () => { - it('should return value as date object if it is a date object', () => { - const date: Date = new Date(); - const compareBase: CompareBase = new CompareBase(date); - expect(compareBase.toDate()).toBe(date); - }); - - it('should throw BadDataException if value is not a date object', () => { - const compareBase: CompareBase = new CompareBase(10); - expect(() => { - return compareBase.toDate(); - }).toThrow(BadDataException); - }); + it("should throw BadDataException if value is not a date object", () => { + const compareBase: CompareBase = new CompareBase(10); + expect(() => { + return compareBase.toDate(); + }).toThrow(BadDataException); }); + }); }); diff --git a/Common/Tests/Types/Database/Date.test.ts b/Common/Tests/Types/Database/Date.test.ts index 952163ba2d..916f00b4a3 100644 --- a/Common/Tests/Types/Database/Date.test.ts +++ b/Common/Tests/Types/Database/Date.test.ts @@ -1,83 +1,81 @@ -import InBetween from '../../../Types/BaseDatabase/InBetween'; -import DatabaseDate from '../../../Types/Database/Date'; -import { JSONObject } from '../../../Types/JSON'; -import { describe, expect, it } from '@jest/globals'; -import moment from 'moment'; +import InBetween from "../../../Types/BaseDatabase/InBetween"; +import DatabaseDate from "../../../Types/Database/Date"; +import { JSONObject } from "../../../Types/JSON"; +import { describe, expect, it } from "@jest/globals"; +import moment from "moment"; -describe('DatabaseDate', () => { - describe('asDateStartOfTheDayEndOfTheDayForDatabaseQuery', () => { - it('should return InBetween object for a valid Date input', () => { - const inputDate: Date = new Date('2023-10-24T12:00:00Z'); - const result: JSONObject = - DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( - inputDate - ).toJSON(); +describe("DatabaseDate", () => { + describe("asDateStartOfTheDayEndOfTheDayForDatabaseQuery", () => { + it("should return InBetween object for a valid Date input", () => { + const inputDate: Date = new Date("2023-10-24T12:00:00Z"); + const result: JSONObject = + DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( + inputDate, + ).toJSON(); - const expectedStart: string = moment(inputDate) - .startOf('day') - .format('YYYY-MM-DD HH:mm:ss'); - const expectedEnd: string = moment(inputDate) - .endOf('day') - .format('YYYY-MM-DD HH:mm:ss'); - expect(result).toEqual({ - startValue: expectedStart, - endValue: expectedEnd, - _type: 'InBetween', - }); - }); - - it('should return InBetween object for a valid Date string input', () => { - const inputDate: string = '2023-10-24T12:00:00Z'; - const result: JSONObject = - DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( - inputDate - ).toJSON(); - const expectedStart: string = moment(inputDate) - .startOf('day') - .format('YYYY-MM-DD HH:mm:ss'); - const expectedEnd: string = moment(inputDate) - .endOf('day') - .format('YYYY-MM-DD HH:mm:ss'); - expect(result).toEqual({ - startValue: expectedStart, - endValue: expectedEnd, - _type: 'InBetween', - }); - }); - - it('should handle invalid date string gracefully', () => { - const inputDate: string = 'invalid-date'; - const result: JSONObject = - DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( - inputDate - ).toJSON(); - expect(result).toEqual({ - startValue: 'Invalid date', - endValue: 'Invalid date', - _type: 'InBetween', - }); - }); - - it('should handle empty string input gracefully', () => { - const inputDate: string = ''; - const result: JSONObject = - DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( - inputDate - ).toJSON(); - expect(result).toEqual({ - startValue: 'Invalid date', - endValue: 'Invalid date', - _type: 'InBetween', - }); - }); - - it('should be a type of InBetween', () => { - const inputDate: string = '2023-10-24T12:00:00Z'; - const result: InBetween = - DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( - inputDate - ); - expect(result).toBeInstanceOf(InBetween); - }); + const expectedStart: string = moment(inputDate) + .startOf("day") + .format("YYYY-MM-DD HH:mm:ss"); + const expectedEnd: string = moment(inputDate) + .endOf("day") + .format("YYYY-MM-DD HH:mm:ss"); + expect(result).toEqual({ + startValue: expectedStart, + endValue: expectedEnd, + _type: "InBetween", + }); }); + + it("should return InBetween object for a valid Date string input", () => { + const inputDate: string = "2023-10-24T12:00:00Z"; + const result: JSONObject = + DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( + inputDate, + ).toJSON(); + const expectedStart: string = moment(inputDate) + .startOf("day") + .format("YYYY-MM-DD HH:mm:ss"); + const expectedEnd: string = moment(inputDate) + .endOf("day") + .format("YYYY-MM-DD HH:mm:ss"); + expect(result).toEqual({ + startValue: expectedStart, + endValue: expectedEnd, + _type: "InBetween", + }); + }); + + it("should handle invalid date string gracefully", () => { + const inputDate: string = "invalid-date"; + const result: JSONObject = + DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( + inputDate, + ).toJSON(); + expect(result).toEqual({ + startValue: "Invalid date", + endValue: "Invalid date", + _type: "InBetween", + }); + }); + + it("should handle empty string input gracefully", () => { + const inputDate: string = ""; + const result: JSONObject = + DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( + inputDate, + ).toJSON(); + expect(result).toEqual({ + startValue: "Invalid date", + endValue: "Invalid date", + _type: "InBetween", + }); + }); + + it("should be a type of InBetween", () => { + const inputDate: string = "2023-10-24T12:00:00Z"; + const result: InBetween = + DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery(inputDate); + expect(result).toBeInstanceOf(InBetween); + }); + }); }); diff --git a/Common/Tests/Types/Database/EqualToOrNull.test.ts b/Common/Tests/Types/Database/EqualToOrNull.test.ts index 73493dc783..32e33c1d0d 100644 --- a/Common/Tests/Types/Database/EqualToOrNull.test.ts +++ b/Common/Tests/Types/Database/EqualToOrNull.test.ts @@ -1,71 +1,71 @@ -import EqualToOrNull from '../../../Types/BaseDatabase/EqualToOrNull'; -import BadDataException from '../../../Types/Exception/BadDataException'; -import { JSONObject } from '../../../Types/JSON'; -import { describe, expect, it } from '@jest/globals'; +import EqualToOrNull from "../../../Types/BaseDatabase/EqualToOrNull"; +import BadDataException from "../../../Types/Exception/BadDataException"; +import { JSONObject } from "../../../Types/JSON"; +import { describe, expect, it } from "@jest/globals"; -describe('EqualToOrNull', () => { - it('should create an EqualToOrNull object with a valid value', () => { - const value: string = 'oneuptime'; - const equalObj: EqualToOrNull = new EqualToOrNull(value); - expect(equalObj.value).toBe(value); - }); +describe("EqualToOrNull", () => { + it("should create an EqualToOrNull object with a valid value", () => { + const value: string = "oneuptime"; + const equalObj: EqualToOrNull = new EqualToOrNull(value); + expect(equalObj.value).toBe(value); + }); - it('should get the value property of an EqualToOrNull object', () => { - const value: string = 'oneuptime'; - const equalObj: EqualToOrNull = new EqualToOrNull(value); - expect(equalObj.value).toBe(value); - }); + it("should get the value property of an EqualToOrNull object", () => { + const value: string = "oneuptime"; + const equalObj: EqualToOrNull = new EqualToOrNull(value); + expect(equalObj.value).toBe(value); + }); - it('should set the value property of an EqualToOrNull object', () => { - const equalObj: EqualToOrNull = new EqualToOrNull('oldValue'); - equalObj.value = 'newValue'; - expect(equalObj.value).toBe('newValue'); - }); + it("should set the value property of an EqualToOrNull object", () => { + const equalObj: EqualToOrNull = new EqualToOrNull("oldValue"); + equalObj.value = "newValue"; + expect(equalObj.value).toBe("newValue"); + }); - it('should return the correct string representation using toString method', () => { - const equalObj: EqualToOrNull = new EqualToOrNull('oneuptime'); - expect(equalObj.toString()).toBe('oneuptime'); - }); + it("should return the correct string representation using toString method", () => { + const equalObj: EqualToOrNull = new EqualToOrNull("oneuptime"); + expect(equalObj.toString()).toBe("oneuptime"); + }); - it('should generate the correct JSON representation using toJSON method', () => { - const equalObj: EqualToOrNull = new EqualToOrNull('oneuptime'); - const expectedJSON: JSONObject = { - _type: 'EqualToOrNull', - value: 'oneuptime', - }; - expect(equalObj.toJSON()).toEqual(expectedJSON); - }); + it("should generate the correct JSON representation using toJSON method", () => { + const equalObj: EqualToOrNull = new EqualToOrNull("oneuptime"); + const expectedJSON: JSONObject = { + _type: "EqualToOrNull", + value: "oneuptime", + }; + expect(equalObj.toJSON()).toEqual(expectedJSON); + }); - it('should create an EqualToOrNull object from valid JSON input', () => { - const jsonInput: JSONObject = { - _type: 'EqualToOrNull', - value: 'oneuptime', - }; - const equalObj: EqualToOrNull = EqualToOrNull.fromJSON(jsonInput); - expect(equalObj.value).toBe('oneuptime'); - }); + it("should create an EqualToOrNull object from valid JSON input", () => { + const jsonInput: JSONObject = { + _type: "EqualToOrNull", + value: "oneuptime", + }; + const equalObj: EqualToOrNull = EqualToOrNull.fromJSON(jsonInput); + expect(equalObj.value).toBe("oneuptime"); + }); - it('should throw a BadDataException when using invalid JSON input', () => { - const jsonInput: JSONObject = { - _type: 'InvalidType', - value: 'oneuptime', - }; - expect(() => { - return EqualToOrNull.fromJSON(jsonInput); - }).toThrow(BadDataException); - }); + it("should throw a BadDataException when using invalid JSON input", () => { + const jsonInput: JSONObject = { + _type: "InvalidType", + value: "oneuptime", + }; + expect(() => { + return EqualToOrNull.fromJSON(jsonInput); + }).toThrow(BadDataException); + }); - it('should be a type of EqualToOrNull', () => { - const equalObj: EqualToOrNull = new EqualToOrNull('oneuptime'); - expect(equalObj).toBeInstanceOf(EqualToOrNull); - }); + it("should be a type of EqualToOrNull", () => { + const equalObj: EqualToOrNull = new EqualToOrNull("oneuptime"); + expect(equalObj).toBeInstanceOf(EqualToOrNull); + }); - it('should handle null value when using fromJSON method', () => { - const jsonInput: JSONObject = { - _type: 'EqualToOrNull', - value: null, - }; - const equalObj: EqualToOrNull = EqualToOrNull.fromJSON(jsonInput); - expect(equalObj.value).toBeNull(); - }); + it("should handle null value when using fromJSON method", () => { + const jsonInput: JSONObject = { + _type: "EqualToOrNull", + value: null, + }; + const equalObj: EqualToOrNull = EqualToOrNull.fromJSON(jsonInput); + expect(equalObj.value).toBeNull(); + }); }); diff --git a/Common/Tests/Types/Database/InBetween.test.ts b/Common/Tests/Types/Database/InBetween.test.ts index 97daa0d9ab..c1a6f7c8a1 100644 --- a/Common/Tests/Types/Database/InBetween.test.ts +++ b/Common/Tests/Types/Database/InBetween.test.ts @@ -1,81 +1,81 @@ -import InBetween from '../../../Types/BaseDatabase/InBetween'; -import BadDataException from '../../../Types/Exception/BadDataException'; -import { JSONObject } from '../../../Types/JSON'; -import { describe, expect, it } from '@jest/globals'; +import InBetween from "../../../Types/BaseDatabase/InBetween"; +import BadDataException from "../../../Types/Exception/BadDataException"; +import { JSONObject } from "../../../Types/JSON"; +import { describe, expect, it } from "@jest/globals"; -describe('InBetween', () => { - it('should create an InBetween object with valid start and end values', () => { - const startValue: number = 10; - const endValue: number = 20; - const betweenObj: InBetween = new InBetween(startValue, endValue); - expect(betweenObj.startValue).toBe(10); - expect(betweenObj.endValue).toBe(20); - }); +describe("InBetween", () => { + it("should create an InBetween object with valid start and end values", () => { + const startValue: number = 10; + const endValue: number = 20; + const betweenObj: InBetween = new InBetween(startValue, endValue); + expect(betweenObj.startValue).toBe(10); + expect(betweenObj.endValue).toBe(20); + }); - it('should generate the correct JSON representation using toJSON method', () => { - const startValue: number = 10; - const endValue: number = 20; - const betweenObj: InBetween = new InBetween(startValue, endValue); - const expectedJSON: JSONObject = { - _type: 'InBetween', - startValue: 10, - endValue: 20, - }; - expect(betweenObj.toJSON()).toEqual(expectedJSON); - }); + it("should generate the correct JSON representation using toJSON method", () => { + const startValue: number = 10; + const endValue: number = 20; + const betweenObj: InBetween = new InBetween(startValue, endValue); + const expectedJSON: JSONObject = { + _type: "InBetween", + startValue: 10, + endValue: 20, + }; + expect(betweenObj.toJSON()).toEqual(expectedJSON); + }); - it('should create an InBetween object from valid JSON input', () => { - const jsonInput: JSONObject = { - _type: 'InBetween', - startValue: 10, - endValue: 20, - }; - const betweenObj: InBetween = InBetween.fromJSON(jsonInput); - expect(betweenObj.startValue).toBe(10); - expect(betweenObj.endValue).toBe(20); - }); + it("should create an InBetween object from valid JSON input", () => { + const jsonInput: JSONObject = { + _type: "InBetween", + startValue: 10, + endValue: 20, + }; + const betweenObj: InBetween = InBetween.fromJSON(jsonInput); + expect(betweenObj.startValue).toBe(10); + expect(betweenObj.endValue).toBe(20); + }); - it('should throw a BadDataException when using invalid JSON input', () => { - const jsonInput: JSONObject = { - _type: 'InvalidType', - startValue: 10, - endValue: 20, - }; - expect(() => { - return InBetween.fromJSON(jsonInput); - }).toThrow(BadDataException); - }); + it("should throw a BadDataException when using invalid JSON input", () => { + const jsonInput: JSONObject = { + _type: "InvalidType", + startValue: 10, + endValue: 20, + }; + expect(() => { + return InBetween.fromJSON(jsonInput); + }).toThrow(BadDataException); + }); - it('should return a string with start and end values matching', () => { - const startValue: number = 15; - const endValue: number = 15; - const betweenObj: InBetween = new InBetween(startValue, endValue); - expect(betweenObj.toString()).toBe('15'); - }); + it("should return a string with start and end values matching", () => { + const startValue: number = 15; + const endValue: number = 15; + const betweenObj: InBetween = new InBetween(startValue, endValue); + expect(betweenObj.toString()).toBe("15"); + }); - it('should return a string with start and end values different', () => { - const startValue: number = 10; - const endValue: number = 20; - const betweenObj: InBetween = new InBetween(startValue, endValue); - expect(betweenObj.toString()).toBe('10 - 20'); - }); + it("should return a string with start and end values different", () => { + const startValue: number = 10; + const endValue: number = 20; + const betweenObj: InBetween = new InBetween(startValue, endValue); + expect(betweenObj.toString()).toBe("10 - 20"); + }); - it('should return the start value as a string', () => { - const startValue: number = 10; - const endValue: number = 20; - const betweenObj: InBetween = new InBetween(startValue, endValue); - expect(betweenObj.toStartValueString()).toBe('10'); - }); + it("should return the start value as a string", () => { + const startValue: number = 10; + const endValue: number = 20; + const betweenObj: InBetween = new InBetween(startValue, endValue); + expect(betweenObj.toStartValueString()).toBe("10"); + }); - it('should return the end value as a string', () => { - const startValue: number = 10; - const endValue: number = 20; - const betweenObj: InBetween = new InBetween(startValue, endValue); - expect(betweenObj.toEndValueString()).toBe('20'); - }); + it("should return the end value as a string", () => { + const startValue: number = 10; + const endValue: number = 20; + const betweenObj: InBetween = new InBetween(startValue, endValue); + expect(betweenObj.toEndValueString()).toBe("20"); + }); - it('should be a type of InBetween', () => { - const inBetweenObj: InBetween = new InBetween(10, 15); - expect(inBetweenObj).toBeInstanceOf(InBetween); - }); + it("should be a type of InBetween", () => { + const inBetweenObj: InBetween = new InBetween(10, 15); + expect(inBetweenObj).toBeInstanceOf(InBetween); + }); }); diff --git a/Common/Tests/Types/Database/LimitMax.test.ts b/Common/Tests/Types/Database/LimitMax.test.ts index b37b04e7b8..5c0d263c47 100644 --- a/Common/Tests/Types/Database/LimitMax.test.ts +++ b/Common/Tests/Types/Database/LimitMax.test.ts @@ -1,20 +1,20 @@ -import LIMIT_MAX, { LIMIT_PER_PROJECT } from '../../../Types/Database/LimitMax'; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax"; -describe('LIMIT_MAX', () => { - test('it should be number', () => { - expect(typeof LIMIT_MAX).toBe('number'); - }); +describe("LIMIT_MAX", () => { + test("it should be number", () => { + expect(typeof LIMIT_MAX).toBe("number"); + }); }); -describe('LIMIT_MAX', () => { - test('it be positive', () => { - expect(LIMIT_MAX).toBeGreaterThan(0); - }); +describe("LIMIT_MAX", () => { + test("it be positive", () => { + expect(LIMIT_MAX).toBeGreaterThan(0); + }); }); -describe('LIMIT_PER_PROJECT', () => { - test('should be positive number', () => { - expect(typeof LIMIT_PER_PROJECT).toBe('number'); - expect(LIMIT_PER_PROJECT).toBeGreaterThan(0); - }); +describe("LIMIT_PER_PROJECT", () => { + test("should be positive number", () => { + expect(typeof LIMIT_PER_PROJECT).toBe("number"); + expect(LIMIT_PER_PROJECT).toBeGreaterThan(0); + }); }); diff --git a/Common/Tests/Types/Database/NotEqual.test.ts b/Common/Tests/Types/Database/NotEqual.test.ts index f15780a706..f484cc8baa 100644 --- a/Common/Tests/Types/Database/NotEqual.test.ts +++ b/Common/Tests/Types/Database/NotEqual.test.ts @@ -1,19 +1,19 @@ -import NotEqual from '../../../Types/BaseDatabase/NotEqual'; -import { describe, expect, it } from '@jest/globals'; +import NotEqual from "../../../Types/BaseDatabase/NotEqual"; +import { describe, expect, it } from "@jest/globals"; -describe('NotEqual', () => { - describe('constructor', () => { - it('should set the value property', () => { - const value: string = 'test'; - const notEqual: NotEqual = new NotEqual(value); - expect(notEqual.value).toBe(value); - }); +describe("NotEqual", () => { + describe("constructor", () => { + it("should set the value property", () => { + const value: string = "test"; + const notEqual: NotEqual = new NotEqual(value); + expect(notEqual.value).toBe(value); }); - describe('toString', () => { - it('should return the string value', () => { - const value: string = 'test'; - const notEqual: NotEqual = new NotEqual(value); - expect(notEqual.toString()).toBe(value); - }); + }); + describe("toString", () => { + it("should return the string value", () => { + const value: string = "test"; + const notEqual: NotEqual = new NotEqual(value); + expect(notEqual.toString()).toBe(value); }); + }); }); diff --git a/Common/Tests/Types/Database/Search.test.ts b/Common/Tests/Types/Database/Search.test.ts index 41332699ed..7c632d2ceb 100644 --- a/Common/Tests/Types/Database/Search.test.ts +++ b/Common/Tests/Types/Database/Search.test.ts @@ -1,10 +1,10 @@ -import Search from '../../../Types/BaseDatabase/Search'; +import Search from "../../../Types/BaseDatabase/Search"; -describe('Search', () => { - test('new Search() should return a valid object', () => { - expect(new Search('Demo').toString()).toBe('Demo'); - }); - test('value should return a valid string', () => { - expect(new Search('Item1').value).toBe('Item1'); - }); +describe("Search", () => { + test("new Search() should return a valid object", () => { + expect(new Search("Demo").toString()).toBe("Demo"); + }); + test("value should return a valid string", () => { + expect(new Search("Item1").value).toBe("Item1"); + }); }); diff --git a/Common/Tests/Types/DatabaseType.test.ts b/Common/Tests/Types/DatabaseType.test.ts index 9877fd67cf..efbc781f3a 100644 --- a/Common/Tests/Types/DatabaseType.test.ts +++ b/Common/Tests/Types/DatabaseType.test.ts @@ -1,7 +1,7 @@ -import DatabaseType from '../../Types/DatabaseType'; +import DatabaseType from "../../Types/DatabaseType"; -describe('enum DatabaseType', () => { - test('DatabaseType.Postgres should be Postgres', () => { - expect(DatabaseType.Postgres).toEqual('postgres'); - }); +describe("enum DatabaseType", () => { + test("DatabaseType.Postgres should be Postgres", () => { + expect(DatabaseType.Postgres).toEqual("postgres"); + }); }); diff --git a/Common/Tests/Types/Date.test.ts b/Common/Tests/Types/Date.test.ts index 48825c46c8..6ce4e1fe75 100644 --- a/Common/Tests/Types/Date.test.ts +++ b/Common/Tests/Types/Date.test.ts @@ -1,95 +1,89 @@ -import OneUptimeDate from '../../Types/Date'; -import PositiveNumber from '../../Types/PositiveNumber'; -import moment, { isMoment } from 'moment'; +import OneUptimeDate from "../../Types/Date"; +import PositiveNumber from "../../Types/PositiveNumber"; +import moment, { isMoment } from "moment"; -describe('class OneUptimeDate', () => { - test('OneUptimeDate.getCurrentDate should return current date', () => { - expect(OneUptimeDate.getCurrentDate().getFullYear()).toEqual( - new Date().getFullYear() - ); - expect(OneUptimeDate.getCurrentDate().getDay()).toEqual( - new Date().getDay() - ); - expect(OneUptimeDate.getCurrentDate().getDate()).toEqual( - new Date().getDate() - ); - expect(OneUptimeDate.getCurrentDate().getHours()).toEqual( - new Date().getHours() - ); - expect(isMoment(OneUptimeDate.getCurrentDate())).toBeFalsy(); - }); - test('OneUptimeDAte.getCurrentMomentDate() should return moment Date', () => { - expect(isMoment(OneUptimeDate.getCurrentMomentDate())).toBeTruthy(); - }); - test('OneUptimeDAte.getSomeMinutesAgo should return someMinutes ago Date from current time', () => { - expect( - OneUptimeDate.getSomeMinutesAgo(new PositiveNumber(4)).getMinutes() - ).toEqual(moment().add(-4, 'minutes').toDate().getMinutes()); - }); - test('OneUptimeDAte.getOneMinAgo should return one minute age Date from current time', () => { - expect(OneUptimeDate.getOneMinAgo().getMinutes()).toEqual( - moment().add(-1, 'minute').toDate().getMinutes() - ); - }); - test('OneUptimeDAte.getOneDayAgo should return oneDate ago Date', () => { - expect(OneUptimeDate.getOneDayAgo().getDay()).toEqual( - moment().add(-1, 'day').toDate().getDay() - ); - }); - test('OneUptimeDAte.getSomeHoursAgo should return moment Date', () => { - expect( - OneUptimeDate.getSomeHoursAgo(new PositiveNumber(4)).getHours() - ).toEqual(moment().add(-4, 'hours').toDate().getHours()); - }); - test('OneUptimeDAte.getSomeDaysAgo should return moment Date', () => { - expect( - OneUptimeDate.getSomeDaysAgo(new PositiveNumber(4)).getDay() - ).toEqual(moment().add(-4, 'days').toDate().getDay()); - }); - test('OneUptimeDAte.getSomeSecondsAgo should return moment Date', () => { - expect( - OneUptimeDate.getSomeSecondsAgo(new PositiveNumber(4)).getSeconds() - ).toEqual(moment().add(-4, 'seconds').toDate().getSeconds()); - }); - test('OneUptimeDAte.getOneMinAfter should return moment Date', () => { - expect(OneUptimeDate.getOneMinAfter().getMinutes()).toEqual( - moment().add(1, 'minute').toDate().getMinutes() - ); - }); - test('OneUptimeDAte.getOneDayAfter should return moment Date', () => { - expect(OneUptimeDate.getOneDayAfter().getDay()).toEqual( - moment().add(1, 'day').toDate().getDay() - ); - }); - test('OneUptimeDAte.getSomeMinutesAfter should return moment Date', () => { - expect( - OneUptimeDate.getSomeMinutesAfter( - new PositiveNumber(4) - ).getMinutes() - ).toEqual(moment().add(4, 'minutes').toDate().getMinutes()); - }); - test('OneUptimeDAte.getSomeHoursAfter should return moment Date', () => { - expect( - OneUptimeDate.getSomeHoursAfter(new PositiveNumber(4)).getHours() - ).toEqual(moment().add(4, 'hours').toDate().getHours()); - }); - test('OneUptimeDAte.getSomeDaysAfter should return moment Date', () => { - expect( - OneUptimeDate.getSomeDaysAfter(new PositiveNumber(4)).getDay() - ).toEqual(moment().add(4, 'days').toDate().getDay()); - }); - test('OneUptimeDAte.getSomeSecondsAfter should return moment Date', () => { - expect( - OneUptimeDate.getSomeSecondsAfter( - new PositiveNumber(4) - ).getSeconds() - ).toEqual(moment().add(4, 'seconds').toDate().getSeconds()); - }); - test('OneUptimeDAte.getCurrentYear should return the current year', () => { - expect( - OneUptimeDate.getSomeSecondsAfter( - new PositiveNumber(4) - ).getSeconds() - ).toEqual(moment().add(4, 'seconds').toDate().getSeconds()); - }); +describe("class OneUptimeDate", () => { + test("OneUptimeDate.getCurrentDate should return current date", () => { + expect(OneUptimeDate.getCurrentDate().getFullYear()).toEqual( + new Date().getFullYear(), + ); + expect(OneUptimeDate.getCurrentDate().getDay()).toEqual( + new Date().getDay(), + ); + expect(OneUptimeDate.getCurrentDate().getDate()).toEqual( + new Date().getDate(), + ); + expect(OneUptimeDate.getCurrentDate().getHours()).toEqual( + new Date().getHours(), + ); + expect(isMoment(OneUptimeDate.getCurrentDate())).toBeFalsy(); + }); + test("OneUptimeDAte.getCurrentMomentDate() should return moment Date", () => { + expect(isMoment(OneUptimeDate.getCurrentMomentDate())).toBeTruthy(); + }); + test("OneUptimeDAte.getSomeMinutesAgo should return someMinutes ago Date from current time", () => { + expect( + OneUptimeDate.getSomeMinutesAgo(new PositiveNumber(4)).getMinutes(), + ).toEqual(moment().add(-4, "minutes").toDate().getMinutes()); + }); + test("OneUptimeDAte.getOneMinAgo should return one minute age Date from current time", () => { + expect(OneUptimeDate.getOneMinAgo().getMinutes()).toEqual( + moment().add(-1, "minute").toDate().getMinutes(), + ); + }); + test("OneUptimeDAte.getOneDayAgo should return oneDate ago Date", () => { + expect(OneUptimeDate.getOneDayAgo().getDay()).toEqual( + moment().add(-1, "day").toDate().getDay(), + ); + }); + test("OneUptimeDAte.getSomeHoursAgo should return moment Date", () => { + expect( + OneUptimeDate.getSomeHoursAgo(new PositiveNumber(4)).getHours(), + ).toEqual(moment().add(-4, "hours").toDate().getHours()); + }); + test("OneUptimeDAte.getSomeDaysAgo should return moment Date", () => { + expect( + OneUptimeDate.getSomeDaysAgo(new PositiveNumber(4)).getDay(), + ).toEqual(moment().add(-4, "days").toDate().getDay()); + }); + test("OneUptimeDAte.getSomeSecondsAgo should return moment Date", () => { + expect( + OneUptimeDate.getSomeSecondsAgo(new PositiveNumber(4)).getSeconds(), + ).toEqual(moment().add(-4, "seconds").toDate().getSeconds()); + }); + test("OneUptimeDAte.getOneMinAfter should return moment Date", () => { + expect(OneUptimeDate.getOneMinAfter().getMinutes()).toEqual( + moment().add(1, "minute").toDate().getMinutes(), + ); + }); + test("OneUptimeDAte.getOneDayAfter should return moment Date", () => { + expect(OneUptimeDate.getOneDayAfter().getDay()).toEqual( + moment().add(1, "day").toDate().getDay(), + ); + }); + test("OneUptimeDAte.getSomeMinutesAfter should return moment Date", () => { + expect( + OneUptimeDate.getSomeMinutesAfter(new PositiveNumber(4)).getMinutes(), + ).toEqual(moment().add(4, "minutes").toDate().getMinutes()); + }); + test("OneUptimeDAte.getSomeHoursAfter should return moment Date", () => { + expect( + OneUptimeDate.getSomeHoursAfter(new PositiveNumber(4)).getHours(), + ).toEqual(moment().add(4, "hours").toDate().getHours()); + }); + test("OneUptimeDAte.getSomeDaysAfter should return moment Date", () => { + expect( + OneUptimeDate.getSomeDaysAfter(new PositiveNumber(4)).getDay(), + ).toEqual(moment().add(4, "days").toDate().getDay()); + }); + test("OneUptimeDAte.getSomeSecondsAfter should return moment Date", () => { + expect( + OneUptimeDate.getSomeSecondsAfter(new PositiveNumber(4)).getSeconds(), + ).toEqual(moment().add(4, "seconds").toDate().getSeconds()); + }); + test("OneUptimeDAte.getCurrentYear should return the current year", () => { + expect( + OneUptimeDate.getSomeSecondsAfter(new PositiveNumber(4)).getSeconds(), + ).toEqual(moment().add(4, "seconds").toDate().getSeconds()); + }); }); diff --git a/Common/Tests/Types/Dictionary.test.ts b/Common/Tests/Types/Dictionary.test.ts index 8b97137378..5ad538dd8e 100644 --- a/Common/Tests/Types/Dictionary.test.ts +++ b/Common/Tests/Types/Dictionary.test.ts @@ -1,26 +1,26 @@ -import Dictionary from '../../Types/Dictionary'; +import Dictionary from "../../Types/Dictionary"; -describe('Dictionary', () => { - test('should allow basic types compile', () => { - const dictionary: Dictionary = { - property: 'string', - }; - const numberDictionary: Dictionary = { - property: 1, - }; +describe("Dictionary", () => { + test("should allow basic types compile", () => { + const dictionary: Dictionary = { + property: "string", + }; + const numberDictionary: Dictionary = { + property: 1, + }; - expect(dictionary['property']).toEqual('string'); - expect(numberDictionary['property']).toEqual(1); - }); - test('should allow the complex type', () => { - const nestedDictionary: Dictionary<{ nested: string }> = { - property: { nested: 'stringValue' }, - }; - const nestedArrayDictionary: Dictionary<{ nested: Array }> = { - property: { nested: ['stringValue'] }, - }; + expect(dictionary["property"]).toEqual("string"); + expect(numberDictionary["property"]).toEqual(1); + }); + test("should allow the complex type", () => { + const nestedDictionary: Dictionary<{ nested: string }> = { + property: { nested: "stringValue" }, + }; + const nestedArrayDictionary: Dictionary<{ nested: Array }> = { + property: { nested: ["stringValue"] }, + }; - expect(nestedDictionary['property']?.nested).toEqual('stringValue'); - expect(nestedArrayDictionary['property']?.nested.length).toEqual(1); - }); + expect(nestedDictionary["property"]?.nested).toEqual("stringValue"); + expect(nestedArrayDictionary["property"]?.nested.length).toEqual(1); + }); }); diff --git a/Common/Tests/Types/Domain.test.ts b/Common/Tests/Types/Domain.test.ts index 5c4ec073ec..c5db719384 100644 --- a/Common/Tests/Types/Domain.test.ts +++ b/Common/Tests/Types/Domain.test.ts @@ -1,39 +1,39 @@ -import Domain from '../../Types/Domain'; -import BadDataException from '../../Types/Exception/BadDataException'; +import Domain from "../../Types/Domain"; +import BadDataException from "../../Types/Exception/BadDataException"; -describe('class Domain', () => { - test('new Domain() should return a valid object if domain is valid', () => { - expect(new Domain('example.com')).toBeInstanceOf(Domain); - expect(new Domain('example.com').toString()).toBe('example.com'); - expect(new Domain('example.com.ac')).toBeInstanceOf(Domain); - expect(new Domain('example.com.ac').toString()).toBe('example.com.ac'); - expect(new Domain('example.com.ac').domain).toBe('example.com.ac'); - expect(new Domain('example.ac')).toBeInstanceOf(Domain); - expect(new Domain('example.ac').toString()).toBe('example.ac'); - expect(new Domain('example.ac').domain).toBe('example.ac'); - }); - test('new Domain() should throw the BadDataException if domain is invalid', () => { - expect(() => { - return new Domain('example'); - }).toThrowError(BadDataException); - expect(() => { - new Domain('example'); - }).toThrowError(BadDataException); - expect(() => { - new Domain('example@com'); - }).toThrowError(BadDataException); +describe("class Domain", () => { + test("new Domain() should return a valid object if domain is valid", () => { + expect(new Domain("example.com")).toBeInstanceOf(Domain); + expect(new Domain("example.com").toString()).toBe("example.com"); + expect(new Domain("example.com.ac")).toBeInstanceOf(Domain); + expect(new Domain("example.com.ac").toString()).toBe("example.com.ac"); + expect(new Domain("example.com.ac").domain).toBe("example.com.ac"); + expect(new Domain("example.ac")).toBeInstanceOf(Domain); + expect(new Domain("example.ac").toString()).toBe("example.ac"); + expect(new Domain("example.ac").domain).toBe("example.ac"); + }); + test("new Domain() should throw the BadDataException if domain is invalid", () => { + expect(() => { + return new Domain("example"); + }).toThrowError(BadDataException); + expect(() => { + new Domain("example"); + }).toThrowError(BadDataException); + expect(() => { + new Domain("example@com"); + }).toThrowError(BadDataException); - expect(() => { - new Domain('example.invalid'); - }).toThrowError(BadDataException); - expect(() => { - const validDomain: Domain = new Domain('example.valid'); - validDomain.domain = 'example.invalid'; - }).toThrowError(BadDataException); - }); - test('Domain.domain should be mutable', () => { - const domain: Domain = new Domain('example.com'); - domain.domain = 'example.io'; - expect(domain.domain).toEqual('example.io'); - }); + expect(() => { + new Domain("example.invalid"); + }).toThrowError(BadDataException); + expect(() => { + const validDomain: Domain = new Domain("example.valid"); + validDomain.domain = "example.invalid"; + }).toThrowError(BadDataException); + }); + test("Domain.domain should be mutable", () => { + const domain: Domain = new Domain("example.com"); + domain.domain = "example.io"; + expect(domain.domain).toEqual("example.io"); + }); }); diff --git a/Common/Tests/Types/Email/Email.test.ts b/Common/Tests/Types/Email/Email.test.ts index 00e6d2c39e..c9de9183d6 100644 --- a/Common/Tests/Types/Email/Email.test.ts +++ b/Common/Tests/Types/Email/Email.test.ts @@ -1,69 +1,69 @@ -import Email from '../../../Types/Email'; +import Email from "../../../Types/Email"; -describe('Email()', () => { - test('should create an email instance', () => { - expect(new Email('test@test.com')).toBeInstanceOf(Email); - }); +describe("Email()", () => { + test("should create an email instance", () => { + expect(new Email("test@test.com")).toBeInstanceOf(Email); + }); - test('should not create an email with invalid credentials', () => { - const invalidEmail: string = 'invalid email address'; - expect(() => { - new Email(invalidEmail); - }).toThrow(`Email ${invalidEmail} is not in valid format.`); - }); + test("should not create an email with invalid credentials", () => { + const invalidEmail: string = "invalid email address"; + expect(() => { + new Email(invalidEmail); + }).toThrow(`Email ${invalidEmail} is not in valid format.`); + }); - test('should be a business email', () => { - const email: Email = new Email('dev@oneuptime.com'); - expect(email.isBusinessEmail()).toBeTruthy(); - }); + test("should be a business email", () => { + const email: Email = new Email("dev@oneuptime.com"); + expect(email.isBusinessEmail()).toBeTruthy(); + }); - test('should not be a business email', () => { - const email: Email = new Email('dev@yahoo.co.uk'); - expect(email.isBusinessEmail()).toBeFalsy(); - }); + test("should not be a business email", () => { + const email: Email = new Email("dev@yahoo.co.uk"); + expect(email.isBusinessEmail()).toBeFalsy(); + }); - test('should return the domain of the email address', () => { - expect(new Email('hello@oneuptime.com').getEmailDomain().hostname).toBe( - 'oneuptime.com' - ); - }); + test("should return the domain of the email address", () => { + expect(new Email("hello@oneuptime.com").getEmailDomain().hostname).toBe( + "oneuptime.com", + ); + }); - test('value of email instance should be mutable', () => { - const email: Email = new Email('test@test.com'); - email.email = 'new@test.com'; - expect(email.toString()).toBe('new@test.com'); - }); + test("value of email instance should be mutable", () => { + const email: Email = new Email("test@test.com"); + email.email = "new@test.com"; + expect(email.toString()).toBe("new@test.com"); + }); - test('should read the value of email instance', () => { - expect(new Email('test@test.com').email).toBe('test@test.com'); - }); + test("should read the value of email instance", () => { + expect(new Email("test@test.com").email).toBe("test@test.com"); + }); - test('value of email instance should of type string', () => { - expect(typeof new Email('test@test.com').email).toBe('string'); - }); + test("value of email instance should of type string", () => { + expect(typeof new Email("test@test.com").email).toBe("string"); + }); - test('should return a string', () => { - const email: Email = new Email('test@test.com'); - expect(Email.toDatabase(email)).toBe('test@test.com'); - }); + test("should return a string", () => { + const email: Email = new Email("test@test.com"); + expect(Email.toDatabase(email)).toBe("test@test.com"); + }); - test('should be an instance Email', () => { - expect(Email.fromDatabase('test@gmail.com')).toBeInstanceOf(Email); - }); + test("should be an instance Email", () => { + expect(Email.fromDatabase("test@gmail.com")).toBeInstanceOf(Email); + }); - test('should not create an instance of Email', () => { - expect(Email.fromDatabase('')).toBeNull(); - }); + test("should not create an instance of Email", () => { + expect(Email.fromDatabase("")).toBeNull(); + }); - test('should return a string from the transformers to function', () => { - expect(Email.getDatabaseTransformer().to('test@gmail.com')).toBe( - 'test@gmail.com' - ); - }); + test("should return a string from the transformers to function", () => { + expect(Email.getDatabaseTransformer().to("test@gmail.com")).toBe( + "test@gmail.com", + ); + }); - test('should create an instance of Email through the transformer', () => { - expect( - Email.getDatabaseTransformer().from(new Email('test@test.com')) - ).toBeInstanceOf(Email); - }); + test("should create an instance of Email through the transformer", () => { + expect( + Email.getDatabaseTransformer().from(new Email("test@test.com")), + ).toBeInstanceOf(Email); + }); }); diff --git a/Common/Tests/Types/EmailWithName.test.ts b/Common/Tests/Types/EmailWithName.test.ts index bad16f67a1..23c6d580dc 100644 --- a/Common/Tests/Types/EmailWithName.test.ts +++ b/Common/Tests/Types/EmailWithName.test.ts @@ -1,49 +1,49 @@ -import Email from '../../Types/Email'; -import EmailWithName from '../../Types/EmailWithName'; -import BadDataException from '../../Types/Exception/BadDataException'; -import Faker from '../../Utils/Faker'; +import Email from "../../Types/Email"; +import EmailWithName from "../../Types/EmailWithName"; +import BadDataException from "../../Types/Exception/BadDataException"; +import Faker from "../../Utils/Faker"; -describe('class EmailWithName', () => { - test('new EmailWithName() should return valid object if valid name and email is given', () => { - const name: string = Faker.generateName(); - const email: Email = Faker.generateEmail(); - expect(new EmailWithName(name, email).name).toEqual(name); - expect(new EmailWithName(name, email).email).toEqual(email); - }); - test('EmailWithName.name should be mutable', () => { - const newName: string = Faker.generateName(); - const emailWithName: EmailWithName = new EmailWithName( - Faker.generateName(), - Faker.generateEmail().toString() - ); - emailWithName.name = newName; - expect(emailWithName.name).toEqual(newName); - }); - test('EmailWithName.email should be mutable', () => { - const newEmail: Email = Faker.generateEmail(); - const emailWithName: EmailWithName = new EmailWithName( - Faker.generateName(), - Faker.generateEmail() - ); - emailWithName.email = newEmail; - expect(emailWithName.email).toEqual(newEmail); - }); - test('mutating EmailWithName.email with invalid email should throw BadDataException', () => { - const emailWithName: EmailWithName = new EmailWithName( - Faker.generateName(), - Faker.generateEmail() - ); - emailWithName.email = 'janedoe@example.com'; - expect(() => { - emailWithName.email = 'Invalid email'; - }).toThrowError(BadDataException); - }); +describe("class EmailWithName", () => { + test("new EmailWithName() should return valid object if valid name and email is given", () => { + const name: string = Faker.generateName(); + const email: Email = Faker.generateEmail(); + expect(new EmailWithName(name, email).name).toEqual(name); + expect(new EmailWithName(name, email).email).toEqual(email); + }); + test("EmailWithName.name should be mutable", () => { + const newName: string = Faker.generateName(); + const emailWithName: EmailWithName = new EmailWithName( + Faker.generateName(), + Faker.generateEmail().toString(), + ); + emailWithName.name = newName; + expect(emailWithName.name).toEqual(newName); + }); + test("EmailWithName.email should be mutable", () => { + const newEmail: Email = Faker.generateEmail(); + const emailWithName: EmailWithName = new EmailWithName( + Faker.generateName(), + Faker.generateEmail(), + ); + emailWithName.email = newEmail; + expect(emailWithName.email).toEqual(newEmail); + }); + test("mutating EmailWithName.email with invalid email should throw BadDataException", () => { + const emailWithName: EmailWithName = new EmailWithName( + Faker.generateName(), + Faker.generateEmail(), + ); + emailWithName.email = "janedoe@example.com"; + expect(() => { + emailWithName.email = "Invalid email"; + }).toThrowError(BadDataException); + }); - test('EmailWithName.toString() should return the name with email', () => { - const name: string = Faker.generateName(); - const email: Email = Faker.generateEmail(); - expect(new EmailWithName(name, email).toString()).toEqual( - `"${name}" <${email}>` - ); - }); + test("EmailWithName.toString() should return the name with email", () => { + const name: string = Faker.generateName(); + const email: Email = Faker.generateEmail(); + expect(new EmailWithName(name, email).toString()).toEqual( + `"${name}" <${email}>`, + ); + }); }); diff --git a/Common/Tests/Types/EncryptionAlgorithm.test.ts b/Common/Tests/Types/EncryptionAlgorithm.test.ts index 24495cef06..84e7ec1736 100644 --- a/Common/Tests/Types/EncryptionAlgorithm.test.ts +++ b/Common/Tests/Types/EncryptionAlgorithm.test.ts @@ -1,7 +1,7 @@ -import EncryptionAlgorithm from '../../Types/EncryptionAlgorithm'; +import EncryptionAlgorithm from "../../Types/EncryptionAlgorithm"; -describe('enum EncryptionAlgorithm', () => { - test('EncryptionAlgorithm.SHA256 should be SHA-256', () => { - expect(EncryptionAlgorithm.SHA256).toEqual('SHA-256'); - }); +describe("enum EncryptionAlgorithm", () => { + test("EncryptionAlgorithm.SHA256 should be SHA-256", () => { + expect(EncryptionAlgorithm.SHA256).toEqual("SHA-256"); + }); }); diff --git a/Common/Tests/Types/Exception/ApiException.test.ts b/Common/Tests/Types/Exception/ApiException.test.ts index 3d1253a821..6a4fd8cb07 100644 --- a/Common/Tests/Types/Exception/ApiException.test.ts +++ b/Common/Tests/Types/Exception/ApiException.test.ts @@ -1,16 +1,15 @@ -import APIException from '../../../Types/Exception/ApiException'; +import APIException from "../../../Types/Exception/ApiException"; -describe('ApiException', () => { - test('should return error message from ApiException', () => { - expect( - new APIException('Server responded with a status code of 5000') - .message - ).toBe('Server responded with a status code of 5000'); - }); +describe("ApiException", () => { + test("should return error message from ApiException", () => { + expect( + new APIException("Server responded with a status code of 5000").message, + ).toBe("Server responded with a status code of 5000"); + }); - test('should return 2 as the code for ApiException', () => { - expect( - new APIException('Server responded with a status code of 5000').code - ).toBe(2); - }); + test("should return 2 as the code for ApiException", () => { + expect( + new APIException("Server responded with a status code of 5000").code, + ).toBe(2); + }); }); diff --git a/Common/Tests/Types/Exception/BadDataException.test.ts b/Common/Tests/Types/Exception/BadDataException.test.ts index 3939720ef2..1ede5bddd2 100644 --- a/Common/Tests/Types/Exception/BadDataException.test.ts +++ b/Common/Tests/Types/Exception/BadDataException.test.ts @@ -1,15 +1,15 @@ -import BadDataException from '../../../Types/Exception/BadDataException'; +import BadDataException from "../../../Types/Exception/BadDataException"; -describe('BadDataException', () => { - test('should throw a not bad data exception', () => { - expect(() => { - throw new BadDataException('This is not a valid IPv4 address'); - }).toThrow('This is not a valid IPv4 address'); - }); +describe("BadDataException", () => { + test("should throw a not bad data exception", () => { + expect(() => { + throw new BadDataException("This is not a valid IPv4 address"); + }).toThrow("This is not a valid IPv4 address"); + }); - test('should return 400 as the code for BadDataException', () => { - expect( - new BadDataException('This is not a valid IPv4 address').code - ).toBe(400); - }); + test("should return 400 as the code for BadDataException", () => { + expect(new BadDataException("This is not a valid IPv4 address").code).toBe( + 400, + ); + }); }); diff --git a/Common/Tests/Types/Exception/BadOperationException.test.ts b/Common/Tests/Types/Exception/BadOperationException.test.ts index 61e126ec55..923332b534 100644 --- a/Common/Tests/Types/Exception/BadOperationException.test.ts +++ b/Common/Tests/Types/Exception/BadOperationException.test.ts @@ -1,16 +1,15 @@ -import BadOperationException from '../../../Types/Exception/BadOperationException'; +import BadOperationException from "../../../Types/Exception/BadOperationException"; -describe('BadOperationException', () => { - test('should return error message from BadOperationException', () => { - expect( - new BadOperationException('Cannot await a non-thenable code') - .message - ).toBe('Cannot await a non-thenable code'); - }); +describe("BadOperationException", () => { + test("should return error message from BadOperationException", () => { + expect( + new BadOperationException("Cannot await a non-thenable code").message, + ).toBe("Cannot await a non-thenable code"); + }); - test('should return 5 as the code for BadOperationException', () => { - expect( - new BadOperationException('Cannot await a non-thenable code').code - ).toBe(5); - }); + test("should return 5 as the code for BadOperationException", () => { + expect( + new BadOperationException("Cannot await a non-thenable code").code, + ).toBe(5); + }); }); diff --git a/Common/Tests/Types/Exception/BadRequestException.test.ts b/Common/Tests/Types/Exception/BadRequestException.test.ts index d3a96eb10b..33eebc4990 100644 --- a/Common/Tests/Types/Exception/BadRequestException.test.ts +++ b/Common/Tests/Types/Exception/BadRequestException.test.ts @@ -1,15 +1,15 @@ -import BadRequestException from '../../../Types/Exception/BadRequestException'; +import BadRequestException from "../../../Types/Exception/BadRequestException"; -describe('BadRequestException', () => { - test('should throw a bad request exception', () => { - expect(() => { - throw new BadRequestException('Forbidden. A token is needed'); - }).toThrow('Forbidden. A token is needed'); - }); +describe("BadRequestException", () => { + test("should throw a bad request exception", () => { + expect(() => { + throw new BadRequestException("Forbidden. A token is needed"); + }).toThrow("Forbidden. A token is needed"); + }); - test('should return 400 as the code for BadRequestException', () => { - expect( - new BadRequestException('Forbidden. A token is needed').code - ).toBe(400); - }); + test("should return 400 as the code for BadRequestException", () => { + expect(new BadRequestException("Forbidden. A token is needed").code).toBe( + 400, + ); + }); }); diff --git a/Common/Tests/Types/Exception/DatabaseNotConnectedException.test.ts b/Common/Tests/Types/Exception/DatabaseNotConnectedException.test.ts index fa768a2629..43a0cb94a4 100644 --- a/Common/Tests/Types/Exception/DatabaseNotConnectedException.test.ts +++ b/Common/Tests/Types/Exception/DatabaseNotConnectedException.test.ts @@ -1,13 +1,13 @@ -import DatabaseNotConnectedException from '../../../Types/Exception/DatabaseNotConnectedException'; +import DatabaseNotConnectedException from "../../../Types/Exception/DatabaseNotConnectedException"; -describe('DatabaseNotConnectedException', () => { - test('should return the error message set in database exception', () => { - expect(new DatabaseNotConnectedException().message).toBe( - 'Database not connected' - ); - }); +describe("DatabaseNotConnectedException", () => { + test("should return the error message set in database exception", () => { + expect(new DatabaseNotConnectedException().message).toBe( + "Database not connected", + ); + }); - test('should return 3 as the code for DatabaseNotConnectedException', () => { - expect(new DatabaseNotConnectedException().code).toBe(500); - }); + test("should return 3 as the code for DatabaseNotConnectedException", () => { + expect(new DatabaseNotConnectedException().code).toBe(500); + }); }); diff --git a/Common/Tests/Types/Exception/Exception.test.ts b/Common/Tests/Types/Exception/Exception.test.ts index 7689db792a..f7ef43480e 100644 --- a/Common/Tests/Types/Exception/Exception.test.ts +++ b/Common/Tests/Types/Exception/Exception.test.ts @@ -1,21 +1,19 @@ -import Exception from '../../../Types/Exception/Exception'; +import Exception from "../../../Types/Exception/Exception"; -describe('Exception', () => { - test('should throw an exception from exception class', () => { - expect(() => { - throw new Exception(1, 'General exception error message'); - }).toThrow('General exception error message'); - }); +describe("Exception", () => { + test("should throw an exception from exception class", () => { + expect(() => { + throw new Exception(1, "General exception error message"); + }).toThrow("General exception error message"); + }); - test('should return error message', () => { - expect( - new Exception(0, 'This code has not been implemented').message - ).toBe('This code has not been implemented'); - }); + test("should return error message", () => { + expect(new Exception(0, "This code has not been implemented").message).toBe( + "This code has not been implemented", + ); + }); - test('should return 1 as the code for Exception', () => { - expect(new Exception(1, 'This is not a valid IPv4 address').code).toBe( - 1 - ); - }); + test("should return 1 as the code for Exception", () => { + expect(new Exception(1, "This is not a valid IPv4 address").code).toBe(1); + }); }); diff --git a/Common/Tests/Types/Exception/NotImplementedException.test.ts b/Common/Tests/Types/Exception/NotImplementedException.test.ts index c5047185a7..026f77cfa1 100644 --- a/Common/Tests/Types/Exception/NotImplementedException.test.ts +++ b/Common/Tests/Types/Exception/NotImplementedException.test.ts @@ -1,13 +1,13 @@ -import NotImplementedException from '../../../Types/Exception/NotImplementedException'; +import NotImplementedException from "../../../Types/Exception/NotImplementedException"; -describe('NotImplementedException', () => { - test('should throw a not implemented exception', () => { - expect(() => { - throw new NotImplementedException(); - }).toThrow('This code is not implemented'); - }); +describe("NotImplementedException", () => { + test("should throw a not implemented exception", () => { + expect(() => { + throw new NotImplementedException(); + }).toThrow("This code is not implemented"); + }); - test('should return 0 as the code for NotImplementedException', () => { - expect(new NotImplementedException().code).toBe(0); - }); + test("should return 0 as the code for NotImplementedException", () => { + expect(new NotImplementedException().code).toBe(0); + }); }); diff --git a/Common/Tests/Types/File.test.ts b/Common/Tests/Types/File.test.ts index a8a4c9132c..6fcb8d1f53 100644 --- a/Common/Tests/Types/File.test.ts +++ b/Common/Tests/Types/File.test.ts @@ -1,22 +1,22 @@ -import { File } from '../../Types/File'; +import { File } from "../../Types/File"; -describe('interface File', () => { - test('should have name and contentType property', () => { - const file: File = { - name: 'file', - contentType: 'text/html', - }; - expect(file.name).toEqual('file'); - expect(file.contentType).toEqual('text/html'); - }); - test('name and contentType property should be mutable', () => { - const file: File = { - name: 'file', - contentType: 'text/html', - }; - file.name = 'updatedFile'; - file.contentType = 'Text/html'; - expect(file.name).toEqual('updatedFile'); - expect(file.contentType).toEqual('Text/html'); - }); +describe("interface File", () => { + test("should have name and contentType property", () => { + const file: File = { + name: "file", + contentType: "text/html", + }; + expect(file.name).toEqual("file"); + expect(file.contentType).toEqual("text/html"); + }); + test("name and contentType property should be mutable", () => { + const file: File = { + name: "file", + contentType: "text/html", + }; + file.name = "updatedFile"; + file.contentType = "Text/html"; + expect(file.name).toEqual("updatedFile"); + expect(file.contentType).toEqual("Text/html"); + }); }); diff --git a/Common/Tests/Types/HashedString.test.ts b/Common/Tests/Types/HashedString.test.ts index 4a25a87ff5..753a170b10 100644 --- a/Common/Tests/Types/HashedString.test.ts +++ b/Common/Tests/Types/HashedString.test.ts @@ -1,21 +1,21 @@ -import HashedString from '../../Types/HashedString'; -import ObjectID from '../../Types/ObjectID'; +import HashedString from "../../Types/HashedString"; +import ObjectID from "../../Types/ObjectID"; -describe('class HashedString', () => { - test('HashedString.constructor() should return valid hashedString', () => { - const hashedString: HashedString = new HashedString('stringToHash'); - expect(hashedString).toBeInstanceOf(HashedString); - expect(hashedString.isValueHashed()).toBe(false); - expect(hashedString.hashValue(ObjectID.generate())).toBeTruthy(); - }); +describe("class HashedString", () => { + test("HashedString.constructor() should return valid hashedString", () => { + const hashedString: HashedString = new HashedString("stringToHash"); + expect(hashedString).toBeInstanceOf(HashedString); + expect(hashedString.isValueHashed()).toBe(false); + expect(hashedString.hashValue(ObjectID.generate())).toBeTruthy(); + }); - // TODO: Make this test pass. - test.skip('should SHA256 hash', () => { - const hashedString: HashedString = new HashedString('stringToHash'); - expect(hashedString).toBeInstanceOf(HashedString); - expect(hashedString.isValueHashed()).toBe(false); - expect(hashedString.hashValue(null)).toBe( - 'd3cd003df301cb1adf26fd3af623a0d372403f71b23bd099511cee06e7029b37' - ); - }); + // TODO: Make this test pass. + test.skip("should SHA256 hash", () => { + const hashedString: HashedString = new HashedString("stringToHash"); + expect(hashedString).toBeInstanceOf(HashedString); + expect(hashedString.isValueHashed()).toBe(false); + expect(hashedString.hashValue(null)).toBe( + "d3cd003df301cb1adf26fd3af623a0d372403f71b23bd099511cee06e7029b37", + ); + }); }); diff --git a/Common/Tests/Types/Html.test.ts b/Common/Tests/Types/Html.test.ts index 452ad3bbd2..81df034169 100644 --- a/Common/Tests/Types/Html.test.ts +++ b/Common/Tests/Types/Html.test.ts @@ -1,8 +1,8 @@ -import HTML from '../../Types/Html'; +import HTML from "../../Types/Html"; -describe('class HTML', () => { - test('new HTML should return a valid html object a valid html is given', () => { - const html: HTML = new HTML('</head>'); - expect(html.html).toBe('<!DOCTYPE><head><title></head>'); - }); +describe("class HTML", () => { + test("new HTML should return a valid html object a valid html is given", () => { + const html: HTML = new HTML("<!DOCTYPE><head><title></head>"); + expect(html.html).toBe("<!DOCTYPE><head><title></head>"); + }); }); diff --git a/Common/Tests/Types/IP/IP.test.ts b/Common/Tests/Types/IP/IP.test.ts index b2a16a5a1f..f5b1e2169a 100644 --- a/Common/Tests/Types/IP/IP.test.ts +++ b/Common/Tests/Types/IP/IP.test.ts @@ -1,83 +1,81 @@ -import IP from '../../../Types/IP/IP'; +import IP from "../../../Types/IP/IP"; -describe('IP()', () => { - test('expect ip to be defined', () => { - const ip: IP = new IP('196.223.149.8'); - expect(ip.toString()).toBe('196.223.149.8'); - }); +describe("IP()", () => { + test("expect ip to be defined", () => { + const ip: IP = new IP("196.223.149.8"); + expect(ip.toString()).toBe("196.223.149.8"); + }); - test('expects type of ip to be a string', () => { - const ip: IP = new IP('196.223.149.8'); - expect(typeof ip.toString()).toBe('string'); - }); + test("expects type of ip to be a string", () => { + const ip: IP = new IP("196.223.149.8"); + expect(typeof ip.toString()).toBe("string"); + }); - test('expects ip address to be mutable', () => { - const ip: IP = new IP('196.223.149.8'); - const newIp: string = '127.0.0.1'; - ip.ip = newIp; - expect(ip.ip).not.toBe('196.223.149.8'); - expect(ip.ip).toBe('127.0.0.1'); - }); + test("expects ip address to be mutable", () => { + const ip: IP = new IP("196.223.149.8"); + const newIp: string = "127.0.0.1"; + ip.ip = newIp; + expect(ip.ip).not.toBe("196.223.149.8"); + expect(ip.ip).toBe("127.0.0.1"); + }); - test('expects ip address to be 127.0.0.1', () => { - const ip: IP = new IP('196.223.149.8'); - const newIp: string = '127.0.0.1'; - ip.ip = newIp; - expect(ip.ip).toBe('127.0.0.1'); - }); + test("expects ip address to be 127.0.0.1", () => { + const ip: IP = new IP("196.223.149.8"); + const newIp: string = "127.0.0.1"; + ip.ip = newIp; + expect(ip.ip).toBe("127.0.0.1"); + }); - test('is valid IPv6 address', () => { - const ip: IP = new IP('::11.22.33.44'); - expect(ip.isIPv6()).toBeTruthy(); - }); + test("is valid IPv6 address", () => { + const ip: IP = new IP("::11.22.33.44"); + expect(ip.isIPv6()).toBeTruthy(); + }); - test('should throw an error for invalid IP', () => { - expect(() => { - new IP(''); - }).toThrow('IP is not a valid address'); - }); + test("should throw an error for invalid IP", () => { + expect(() => { + new IP(""); + }).toThrow("IP is not a valid address"); + }); - test('should return a string', () => { - expect(IP.toDatabase(new IP('127.0.0.1'))).toBe('127.0.0.1'); - }); + test("should return a string", () => { + expect(IP.toDatabase(new IP("127.0.0.1"))).toBe("127.0.0.1"); + }); - test('should be an instance IP', () => { - expect(IP.fromDatabase('127.0.0.1')).toBeInstanceOf(IP); - }); + test("should be an instance IP", () => { + expect(IP.fromDatabase("127.0.0.1")).toBeInstanceOf(IP); + }); - test('should not create an instance of IP', () => { - expect(IP.fromDatabase('')).toBeNull(); - }); + test("should not create an instance of IP", () => { + expect(IP.fromDatabase("")).toBeNull(); + }); - test('should create an IP of type IPv4 from database', () => { - expect(IP.fromDatabase('127.0.0.1')?.isIPv4()).toBeTruthy(); - }); + test("should create an IP of type IPv4 from database", () => { + expect(IP.fromDatabase("127.0.0.1")?.isIPv4()).toBeTruthy(); + }); - test('should create an IP of type IPv6 from database', () => { - expect( - IP.fromDatabase('2001:0db8:85a3:0000:0000:8a2e:0370:7334')?.isIPv6() - ).toBeTruthy(); - }); + test("should create an IP of type IPv6 from database", () => { + expect( + IP.fromDatabase("2001:0db8:85a3:0000:0000:8a2e:0370:7334")?.isIPv6(), + ).toBeTruthy(); + }); - test('should create an IP of type IPv4 through the transformer', () => { - expect( - IP.getDatabaseTransformer().from('127.0.0.1').isIPv4() - ).toBeTruthy(); - }); + test("should create an IP of type IPv4 through the transformer", () => { + expect(IP.getDatabaseTransformer().from("127.0.0.1").isIPv4()).toBeTruthy(); + }); - test('should create an IP of type IPv6 through the transformer', () => { - expect( - IP.getDatabaseTransformer() - .from('2001:0db8:85a3:0000:0000:8a2e:0370:7334') - .isIPv6() - ).toBeTruthy(); - }); + test("should create an IP of type IPv6 through the transformer", () => { + expect( + IP.getDatabaseTransformer() + .from("2001:0db8:85a3:0000:0000:8a2e:0370:7334") + .isIPv6(), + ).toBeTruthy(); + }); - test('should return a string from the transformers to function', () => { - expect(IP.getDatabaseTransformer().to('127.0.0.1')).toBe('127.0.0.1'); - }); + test("should return a string from the transformers to function", () => { + expect(IP.getDatabaseTransformer().to("127.0.0.1")).toBe("127.0.0.1"); + }); - test('should return null from the transformers to function', () => { - expect(IP.getDatabaseTransformer().to('')).toBe(null); - }); + test("should return null from the transformers to function", () => { + expect(IP.getDatabaseTransformer().to("")).toBe(null); + }); }); diff --git a/Common/Tests/Types/IP/IPType.test.ts b/Common/Tests/Types/IP/IPType.test.ts index 2e9a2674b5..389cb05c29 100644 --- a/Common/Tests/Types/IP/IPType.test.ts +++ b/Common/Tests/Types/IP/IPType.test.ts @@ -1,11 +1,11 @@ -import IPType from '../../../Types/IP/IPType'; +import IPType from "../../../Types/IP/IPType"; -describe('IPType', () => { - test('should have a type of IPv4', () => { - expect(IPType.IPv4).toBe('IPv4'); - }); +describe("IPType", () => { + test("should have a type of IPv4", () => { + expect(IPType.IPv4).toBe("IPv4"); + }); - test('should have a type of IPv6', () => { - expect(IPType.IPv6).toBe('IPv6'); - }); + test("should have a type of IPv6", () => { + expect(IPType.IPv6).toBe("IPv6"); + }); }); diff --git a/Common/Tests/Types/IP/IPv4.test.ts b/Common/Tests/Types/IP/IPv4.test.ts index 928f21a48f..d3673fbed5 100644 --- a/Common/Tests/Types/IP/IPv4.test.ts +++ b/Common/Tests/Types/IP/IPv4.test.ts @@ -1,19 +1,19 @@ -import IP from '../../../Types/IP/IPv4'; +import IP from "../../../Types/IP/IPv4"; -describe('IPv4()', () => { - test('should be IPv4', () => { - const ip: IP = new IP('196.223.149.8'); - expect(ip.isIPv4()).toBeTruthy(); - }); +describe("IPv4()", () => { + test("should be IPv4", () => { + const ip: IP = new IP("196.223.149.8"); + expect(ip.isIPv4()).toBeTruthy(); + }); - test('should not be IPv6', () => { - const ip: IP = new IP('196.223.149.8'); - expect(ip.isIPv6()).toBeFalsy(); - }); + test("should not be IPv6", () => { + const ip: IP = new IP("196.223.149.8"); + expect(ip.isIPv6()).toBeFalsy(); + }); - test('Is not a valid address', () => { - expect(() => { - new IP('Invalid IP'); - }).toThrow('IP is not a valid address'); - }); + test("Is not a valid address", () => { + expect(() => { + new IP("Invalid IP"); + }).toThrow("IP is not a valid address"); + }); }); diff --git a/Common/Tests/Types/IP/IPv6.test.ts b/Common/Tests/Types/IP/IPv6.test.ts index 11b0281018..74545cfef3 100644 --- a/Common/Tests/Types/IP/IPv6.test.ts +++ b/Common/Tests/Types/IP/IPv6.test.ts @@ -1,19 +1,19 @@ -import IP from '../../../Types/IP/IPv6'; +import IP from "../../../Types/IP/IPv6"; -describe('IPv6()', () => { - test('should be IPv6', () => { - const ip: IP = new IP('2001:0db8:85a3:::8a2e:0370:7334'); - expect(ip.isIPv6()).toBeTruthy(); - }); +describe("IPv6()", () => { + test("should be IPv6", () => { + const ip: IP = new IP("2001:0db8:85a3:::8a2e:0370:7334"); + expect(ip.isIPv6()).toBeTruthy(); + }); - test('should not be IPv4', () => { - const ip: IP = new IP('2001:0db8:85a3:0000:0000:8a2e:0370:7334'); - expect(ip.isIPv4()).toBeFalsy(); - }); + test("should not be IPv4", () => { + const ip: IP = new IP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + expect(ip.isIPv4()).toBeFalsy(); + }); - test('Is not a valid address', () => { - expect(() => { - new IP('Invalid Ip'); - }).toThrow('IP is not a valid address'); - }); + test("Is not a valid address", () => { + expect(() => { + new IP("Invalid Ip"); + }).toThrow("IP is not a valid address"); + }); }); diff --git a/Common/Tests/Types/JSON.test.ts b/Common/Tests/Types/JSON.test.ts index f4a4e38379..8ef69682a9 100644 --- a/Common/Tests/Types/JSON.test.ts +++ b/Common/Tests/Types/JSON.test.ts @@ -1,38 +1,38 @@ -import { ObjectType } from '../../Types/JSON'; +import { ObjectType } from "../../Types/JSON"; -describe('ObjectType', () => { - const expectedFields: Array<keyof typeof ObjectType> = [ - 'ObjectID', - 'Name', - 'EqualToOrNull', - 'NotEqual', - 'Email', - 'Phone', - 'Color', - 'Domain', - 'Version', - 'Route', - 'URL', - 'Permission', - 'Search', - 'GreaterThan', - 'GreaterThanOrEqual', - 'LessThan', - 'LessThanOrEqual', - 'Port', - 'Hostname', - 'HashedString', - 'DateTime', - 'Buffer', - 'InBetween', - 'NotNull', - 'IsNull', - ]; +describe("ObjectType", () => { + const expectedFields: Array<keyof typeof ObjectType> = [ + "ObjectID", + "Name", + "EqualToOrNull", + "NotEqual", + "Email", + "Phone", + "Color", + "Domain", + "Version", + "Route", + "URL", + "Permission", + "Search", + "GreaterThan", + "GreaterThanOrEqual", + "LessThan", + "LessThanOrEqual", + "Port", + "Hostname", + "HashedString", + "DateTime", + "Buffer", + "InBetween", + "NotNull", + "IsNull", + ]; - test.each(expectedFields)( - 'ObjectType has %s', - (field: keyof typeof ObjectType) => { - expect(ObjectType[field]).toBe(field); - } - ); + test.each(expectedFields)( + "ObjectType has %s", + (field: keyof typeof ObjectType) => { + expect(ObjectType[field]).toBe(field); + }, + ); }); diff --git a/Common/Tests/Types/JSONFunctions.test.ts b/Common/Tests/Types/JSONFunctions.test.ts index 63364118de..b6d170e740 100644 --- a/Common/Tests/Types/JSONFunctions.test.ts +++ b/Common/Tests/Types/JSONFunctions.test.ts @@ -1,52 +1,49 @@ -import BaseModel from '../../Models/BaseModel'; -import { JSONObject } from '../../Types/JSON'; -import JSONFunctions from '../../Types/JSONFunctions'; +import BaseModel from "../../Models/BaseModel"; +import { JSONObject } from "../../Types/JSON"; +import JSONFunctions from "../../Types/JSONFunctions"; -describe('JSONFunctions Class', () => { - let baseModel: BaseModel; +describe("JSONFunctions Class", () => { + let baseModel: BaseModel; - beforeEach(() => { - baseModel = new BaseModel(); + beforeEach(() => { + baseModel = new BaseModel(); + }); + + describe("isEmptyObject Method", () => { + test("Returns true for an empty object", () => { + const emptyObj: JSONObject = {}; + expect(JSONFunctions.isEmptyObject(emptyObj)).toBe(true); }); - describe('isEmptyObject Method', () => { - test('Returns true for an empty object', () => { - const emptyObj: JSONObject = {}; - expect(JSONFunctions.isEmptyObject(emptyObj)).toBe(true); - }); - - test('Returns false for a non-empty object', () => { - const nonEmptyObj: JSONObject = { key: 'value' }; - expect(JSONFunctions.isEmptyObject(nonEmptyObj)).toBe(false); - }); - - test('Returns true for null or undefined', () => { - expect(JSONFunctions.isEmptyObject(null)).toBe(true); - expect(JSONFunctions.isEmptyObject(undefined)).toBe(true); - }); + test("Returns false for a non-empty object", () => { + const nonEmptyObj: JSONObject = { key: "value" }; + expect(JSONFunctions.isEmptyObject(nonEmptyObj)).toBe(false); }); - describe('toJSON and fromJSON Methods', () => { - test('toJSON returns a valid JSON object', () => { - const json: JSONObject = BaseModel.toJSON(baseModel, BaseModel); - expect(json).toEqual(expect.objectContaining({})); - }); - - test('toJSONObject returns a valid JSON object', () => { - const json: JSONObject = BaseModel.toJSONObject( - baseModel, - BaseModel - ); - expect(json).toEqual(expect.objectContaining({})); - }); - - test('fromJSON returns a BaseModel instance', () => { - const json: JSONObject = { name: 'oneuptime' }; - const result: BaseModel | BaseModel[] = BaseModel.fromJSON( - json, - BaseModel - ); - expect(result).toBeInstanceOf(BaseModel); - }); + test("Returns true for null or undefined", () => { + expect(JSONFunctions.isEmptyObject(null)).toBe(true); + expect(JSONFunctions.isEmptyObject(undefined)).toBe(true); }); + }); + + describe("toJSON and fromJSON Methods", () => { + test("toJSON returns a valid JSON object", () => { + const json: JSONObject = BaseModel.toJSON(baseModel, BaseModel); + expect(json).toEqual(expect.objectContaining({})); + }); + + test("toJSONObject returns a valid JSON object", () => { + const json: JSONObject = BaseModel.toJSONObject(baseModel, BaseModel); + expect(json).toEqual(expect.objectContaining({})); + }); + + test("fromJSON returns a BaseModel instance", () => { + const json: JSONObject = { name: "oneuptime" }; + const result: BaseModel | BaseModel[] = BaseModel.fromJSON( + json, + BaseModel, + ); + expect(result).toBeInstanceOf(BaseModel); + }); + }); }); diff --git a/Common/Tests/Types/ListData.test.ts b/Common/Tests/Types/ListData.test.ts index b1020afad0..31b1555efd 100644 --- a/Common/Tests/Types/ListData.test.ts +++ b/Common/Tests/Types/ListData.test.ts @@ -1,40 +1,40 @@ -import { JSONArray, JSONObject } from '../../Types/JSON'; -import ListData from '../../Types/ListData'; -import PositiveNumber from '../../Types/PositiveNumber'; +import { JSONArray, JSONObject } from "../../Types/JSON"; +import ListData from "../../Types/ListData"; +import PositiveNumber from "../../Types/PositiveNumber"; -describe('ListData', () => { - test('should create a ListData instance', () => { - const data: JSONArray = [{ foo: 1 }, { foo: 2 }]; - const skip: PositiveNumber = new PositiveNumber(0); - const count: PositiveNumber = new PositiveNumber(0); - const limit: PositiveNumber = new PositiveNumber(0); +describe("ListData", () => { + test("should create a ListData instance", () => { + const data: JSONArray = [{ foo: 1 }, { foo: 2 }]; + const skip: PositiveNumber = new PositiveNumber(0); + const count: PositiveNumber = new PositiveNumber(0); + const limit: PositiveNumber = new PositiveNumber(0); - const listData: ListData = new ListData({ - data, - count, - skip, - limit, - }); - expect(listData).toBeInstanceOf(ListData); + const listData: ListData = new ListData({ + data, + count, + skip, + limit, + }); + expect(listData).toBeInstanceOf(ListData); - expect(listData.count.toNumber()).toBe(count.toNumber()); - expect(listData.skip.toNumber()).toBe(skip.toNumber()); - expect(listData.limit.toNumber()).toBe(limit.toNumber()); + expect(listData.count.toNumber()).toBe(count.toNumber()); + expect(listData.skip.toNumber()).toBe(skip.toNumber()); + expect(listData.limit.toNumber()).toBe(limit.toNumber()); + }); + + test("toJSON converts ListData to JSONObject", () => { + const listData: ListData = new ListData({ + data: [{ foo: "bar" }], + count: new PositiveNumber(0), + skip: new PositiveNumber(0), + limit: new PositiveNumber(0), }); - test('toJSON converts ListData to JSONObject', () => { - const listData: ListData = new ListData({ - data: [{ foo: 'bar' }], - count: new PositiveNumber(0), - skip: new PositiveNumber(0), - limit: new PositiveNumber(0), - }); + const jsonObject: JSONObject = listData.toJSON(); - const jsonObject: JSONObject = listData.toJSON(); - - expect(jsonObject['data']).toEqual([{ foo: 'bar' }]); - expect(jsonObject['count']).toEqual(0); - expect(jsonObject['skip']).toEqual(0); - expect(jsonObject['limit']).toEqual(0); - }); + expect(jsonObject["data"]).toEqual([{ foo: "bar" }]); + expect(jsonObject["count"]).toEqual(0); + expect(jsonObject["skip"]).toEqual(0); + expect(jsonObject["limit"]).toEqual(0); + }); }); diff --git a/Common/Tests/Types/Name.test.ts b/Common/Tests/Types/Name.test.ts index ce17814dad..8ee7f68538 100644 --- a/Common/Tests/Types/Name.test.ts +++ b/Common/Tests/Types/Name.test.ts @@ -1,26 +1,26 @@ -import Name from '../../Types/Name'; +import Name from "../../Types/Name"; -describe('class Name', () => { - test('new Name() should return a valid object if valid name is given', () => { - expect(new Name('Dua Lipa').toString()).toBe('Dua Lipa'); - }); - test('should return the First Name', () => { - expect(new Name('Louis Harry Liam').getFirstName()).toBe('Louis'); - }); - test('should return the Last Name', () => { - expect(new Name('Louis Harry Liam').getLastName()).toBe('Liam'); - }); - test('should return the Midlle Name', () => { - expect(new Name('Louis Harry Liam').getMiddleName()).toBe('Harry'); - }); - test('Name should return String', () => { - const nam: Name = new Name('Taylor Swift'); - expect(Name.toDatabase(nam)).toBe('Taylor Swift'); - }); - test('should read the value of Name instance', () => { - expect(new Name('Kriti Sanon').name).toBe('Kriti Sanon'); - }); - test('should not create an instance of Name', () => { - expect(Name.fromDatabase('')).toBeNull(); - }); +describe("class Name", () => { + test("new Name() should return a valid object if valid name is given", () => { + expect(new Name("Dua Lipa").toString()).toBe("Dua Lipa"); + }); + test("should return the First Name", () => { + expect(new Name("Louis Harry Liam").getFirstName()).toBe("Louis"); + }); + test("should return the Last Name", () => { + expect(new Name("Louis Harry Liam").getLastName()).toBe("Liam"); + }); + test("should return the Midlle Name", () => { + expect(new Name("Louis Harry Liam").getMiddleName()).toBe("Harry"); + }); + test("Name should return String", () => { + const nam: Name = new Name("Taylor Swift"); + expect(Name.toDatabase(nam)).toBe("Taylor Swift"); + }); + test("should read the value of Name instance", () => { + expect(new Name("Kriti Sanon").name).toBe("Kriti Sanon"); + }); + test("should not create an instance of Name", () => { + expect(Name.fromDatabase("")).toBeNull(); + }); }); diff --git a/Common/Tests/Types/ObjectID.test.ts b/Common/Tests/Types/ObjectID.test.ts index 0c9fd43950..0d5c06e5b2 100644 --- a/Common/Tests/Types/ObjectID.test.ts +++ b/Common/Tests/Types/ObjectID.test.ts @@ -1,12 +1,12 @@ -import ObjectID from '../../Types/ObjectID'; +import ObjectID from "../../Types/ObjectID"; -describe('class ObjectID', () => { - test('ObjectID.constructor should return a valid ObjectID object', () => { - const objectID: ObjectID = new ObjectID('id'); - expect(objectID.id).toBe('id'); - }); - test('ObjectID.fromString() should create ObjectID', () => { - expect(ObjectID.fromString('id')).toBeInstanceOf(ObjectID); - }); - test('Should create ObjectId through transformer', () => {}); +describe("class ObjectID", () => { + test("ObjectID.constructor should return a valid ObjectID object", () => { + const objectID: ObjectID = new ObjectID("id"); + expect(objectID.id).toBe("id"); + }); + test("ObjectID.fromString() should create ObjectID", () => { + expect(ObjectID.fromString("id")).toBeInstanceOf(ObjectID); + }); + test("Should create ObjectId through transformer", () => {}); }); diff --git a/Common/Tests/Types/Permission.test.ts b/Common/Tests/Types/Permission.test.ts index 918439332f..a8289cd80b 100644 --- a/Common/Tests/Types/Permission.test.ts +++ b/Common/Tests/Types/Permission.test.ts @@ -1,11 +1,11 @@ -import Permission from '../../Types/Permission'; +import Permission from "../../Types/Permission"; -describe('Permission', () => { - test('Permission.ProjectMember should be ProjectMember', () => { - expect(Permission.ProjectMember).toBe('ProjectMember'); - }); +describe("Permission", () => { + test("Permission.ProjectMember should be ProjectMember", () => { + expect(Permission.ProjectMember).toBe("ProjectMember"); + }); - test('Permission.Public should be Public', () => { - expect(Permission.Public).toBe('Public'); - }); + test("Permission.Public should be Public", () => { + expect(Permission.Public).toBe("Public"); + }); }); diff --git a/Common/Tests/Types/Phone.test.ts b/Common/Tests/Types/Phone.test.ts index 83ff2cc3cf..6d56025099 100644 --- a/Common/Tests/Types/Phone.test.ts +++ b/Common/Tests/Types/Phone.test.ts @@ -1,40 +1,40 @@ -import BadDataException from '../../Types/Exception/BadDataException'; -import Phone from '../../Types/Phone'; +import BadDataException from "../../Types/Exception/BadDataException"; +import Phone from "../../Types/Phone"; -describe('Testing Class Phone', () => { - test('Should create a phone if the phone is valid phone number', () => { - expect(new Phone('+251912974103').toString()).toEqual('+251912974103'); - expect(new Phone('961-770-7727').phone).toEqual('961-770-7727'); - expect(new Phone('943-627-0355').phone).toEqual('943-627-0355'); - expect(new Phone('282.652.3201').phone).toEqual('282.652.3201'); - }); +describe("Testing Class Phone", () => { + test("Should create a phone if the phone is valid phone number", () => { + expect(new Phone("+251912974103").toString()).toEqual("+251912974103"); + expect(new Phone("961-770-7727").phone).toEqual("961-770-7727"); + expect(new Phone("943-627-0355").phone).toEqual("943-627-0355"); + expect(new Phone("282.652.3201").phone).toEqual("282.652.3201"); + }); - test('Phone.phone should be mutatable', () => { - const value: Phone = new Phone('+251912974103'); - value.phone = '+251925974121'; - expect(value.phone).toEqual('+251925974121'); - expect(value.toString()).toEqual('+251925974121'); - }); + test("Phone.phone should be mutatable", () => { + const value: Phone = new Phone("+251912974103"); + value.phone = "+251925974121"; + expect(value.phone).toEqual("+251925974121"); + expect(value.toString()).toEqual("+251925974121"); + }); - test('Creating phone number with invalid format should throw BadDataException', () => { - expect(() => { - new Phone('25192599879079074121'); - }).toThrowError(BadDataException); - }); + test("Creating phone number with invalid format should throw BadDataException", () => { + expect(() => { + new Phone("25192599879079074121"); + }).toThrowError(BadDataException); + }); - test('try to mutating Phone.phone with invalid value should throw a BadDataException', () => { - const valid: string = '+251912974103'; - const invalid: string = '278@$90> '; - const value: Phone = new Phone(valid); - expect(() => { - value.phone = invalid; - }).toThrowError(BadDataException); - expect(() => { - value.phone = '278@$90> '; - }).toThrow('Phone is not in valid format: 278@$90>'); - expect(value.phone).toBe(valid); - expect(() => { - value.phone = 'hgjuit879'; - }).toThrowError(BadDataException); - }); + test("try to mutating Phone.phone with invalid value should throw a BadDataException", () => { + const valid: string = "+251912974103"; + const invalid: string = "278@$90> "; + const value: Phone = new Phone(valid); + expect(() => { + value.phone = invalid; + }).toThrowError(BadDataException); + expect(() => { + value.phone = "278@$90> "; + }).toThrow("Phone is not in valid format: 278@$90>"); + expect(value.phone).toBe(valid); + expect(() => { + value.phone = "hgjuit879"; + }).toThrowError(BadDataException); + }); }); diff --git a/Common/Tests/Types/Port.test.ts b/Common/Tests/Types/Port.test.ts index 962f02f85c..0dd88256dd 100644 --- a/Common/Tests/Types/Port.test.ts +++ b/Common/Tests/Types/Port.test.ts @@ -1,39 +1,39 @@ -import BadDataException from '../../Types/Exception/BadDataException'; -import Port from '../../Types/Port'; -import PositiveNumber from '../../Types/PositiveNumber'; +import BadDataException from "../../Types/Exception/BadDataException"; +import Port from "../../Types/Port"; +import PositiveNumber from "../../Types/PositiveNumber"; -describe('Testing class port', () => { - test('should return a posetive number', () => { - const value: Port = new Port(3000); - expect(value.port.positiveNumber).toBeGreaterThanOrEqual(0); - expect(new Port('6000').port.positiveNumber).toEqual(6000); - }); +describe("Testing class port", () => { + test("should return a posetive number", () => { + const value: Port = new Port(3000); + expect(value.port.positiveNumber).toBeGreaterThanOrEqual(0); + expect(new Port("6000").port.positiveNumber).toEqual(6000); + }); - test('should throw exception "Port is not in valid format."', () => { - expect(() => { - new Port(67000); - }).toThrow('Port is not in valid format.'); - }); + test('should throw exception "Port is not in valid format."', () => { + expect(() => { + new Port(67000); + }).toThrow("Port is not in valid format."); + }); - test('Port.port should be mutatable', () => { - const value: Port = new Port(5000); - value.port = new PositiveNumber(7000); - expect(value.port.positiveNumber).toEqual(7000); - expect(value.port.toNumber()).toEqual(7000); - }); + test("Port.port should be mutatable", () => { + const value: Port = new Port(5000); + value.port = new PositiveNumber(7000); + expect(value.port.positiveNumber).toEqual(7000); + expect(value.port.toNumber()).toEqual(7000); + }); - test('try to mutating Port.port with invalid value should throw an BadDataException', () => { - const value: Port = new Port(3000); - expect(() => { - value.port = new PositiveNumber('hj567'); - }).toThrowError(BadDataException); - expect(() => { - value.port = new PositiveNumber(-6000); - }).toThrow(BadDataException); - }); + test("try to mutating Port.port with invalid value should throw an BadDataException", () => { + const value: Port = new Port(3000); + expect(() => { + value.port = new PositiveNumber("hj567"); + }).toThrowError(BadDataException); + expect(() => { + value.port = new PositiveNumber(-6000); + }).toThrow(BadDataException); + }); - test('If the supplied port is string type, is should convert it to number before creating port', () => { - const value: Port = new Port('6000'); - expect(typeof value.port.positiveNumber).toBe('number'); - }); + test("If the supplied port is string type, is should convert it to number before creating port", () => { + const value: Port = new Port("6000"); + expect(typeof value.port.positiveNumber).toBe("number"); + }); }); diff --git a/Common/Tests/Types/PositiveNumber.test.ts b/Common/Tests/Types/PositiveNumber.test.ts index 4c6563b715..7164eed271 100644 --- a/Common/Tests/Types/PositiveNumber.test.ts +++ b/Common/Tests/Types/PositiveNumber.test.ts @@ -1,142 +1,142 @@ -import BadDataException from '../../Types/Exception/BadDataException'; -import PositiveNumber from '../../Types/PositiveNumber'; +import BadDataException from "../../Types/Exception/BadDataException"; +import PositiveNumber from "../../Types/PositiveNumber"; -describe('PositiveNumber constructor', () => { - test('should create PositiveNumber', () => { - const value: number = 42; - const n: PositiveNumber = new PositiveNumber(value); +describe("PositiveNumber constructor", () => { + test("should create PositiveNumber", () => { + const value: number = 42; + const n: PositiveNumber = new PositiveNumber(value); - expect(n).toBeInstanceOf(PositiveNumber); - expect(n.positiveNumber).toBe(value); - }); + expect(n).toBeInstanceOf(PositiveNumber); + expect(n.positiveNumber).toBe(value); + }); - const validNumbers: Array<[number | string, number]> = [ - [0, 0], - [1, 1], - [Infinity, Infinity], - [NaN, NaN], - ['255.0', 255], - ]; + const validNumbers: Array<[number | string, number]> = [ + [0, 0], + [1, 1], + [Infinity, Infinity], + [NaN, NaN], + ["255.0", 255], + ]; - test.each(validNumbers)( - '(new PositiveNumber(%p)).positiveNumber == %p', - (value: number | string, expected: number) => { - const n: PositiveNumber = new PositiveNumber(value); + test.each(validNumbers)( + "(new PositiveNumber(%p)).positiveNumber == %p", + (value: number | string, expected: number) => { + const n: PositiveNumber = new PositiveNumber(value); - expect(n).toBeInstanceOf(PositiveNumber); - expect(n.positiveNumber).toBe(expected); - } - ); + expect(n).toBeInstanceOf(PositiveNumber); + expect(n.positiveNumber).toBe(expected); + }, + ); - const invalidNumbers: Array<number | string> = ['', 'hello', -1, 'NaN']; + const invalidNumbers: Array<number | string> = ["", "hello", -1, "NaN"]; - test.each(invalidNumbers)( - 'should throw error for new PositiveNumber(%p)', - (value: number | string) => { - expect(() => { - new PositiveNumber(value); - }).toThrowError(BadDataException); - } - ); + test.each(invalidNumbers)( + "should throw error for new PositiveNumber(%p)", + (value: number | string) => { + expect(() => { + new PositiveNumber(value); + }).toThrowError(BadDataException); + }, + ); }); interface TestCase { - value: number | string; - stringValue: string; - numberValue: number; - isZero: boolean; - isOne: boolean; + value: number | string; + stringValue: string; + numberValue: number; + isZero: boolean; + isOne: boolean; } const tests: Array<TestCase> = [ - { - value: 0, - stringValue: '0', - numberValue: 0, - isZero: true, - isOne: false, - }, - { - value: 1, - stringValue: '1', - numberValue: 1, - isZero: false, - isOne: true, - }, - { - value: 42, - stringValue: '42', - numberValue: 42, - isZero: false, - isOne: false, - }, - { - value: Infinity, - stringValue: 'Infinity', - numberValue: Infinity, - isZero: false, - isOne: false, - }, - { - value: NaN, - stringValue: 'NaN', - numberValue: NaN, - isZero: false, - isOne: false, - }, - { - value: '255.0', - stringValue: '255', - numberValue: 255, - isZero: false, - isOne: false, - }, + { + value: 0, + stringValue: "0", + numberValue: 0, + isZero: true, + isOne: false, + }, + { + value: 1, + stringValue: "1", + numberValue: 1, + isZero: false, + isOne: true, + }, + { + value: 42, + stringValue: "42", + numberValue: 42, + isZero: false, + isOne: false, + }, + { + value: Infinity, + stringValue: "Infinity", + numberValue: Infinity, + isZero: false, + isOne: false, + }, + { + value: NaN, + stringValue: "NaN", + numberValue: NaN, + isZero: false, + isOne: false, + }, + { + value: "255.0", + stringValue: "255", + numberValue: 255, + isZero: false, + isOne: false, + }, ]; -describe('PositiveNumber.toString', () => { - test.each(tests)( - `(new PositiveNumber($value)).toString() == $stringValue`, - ({ value, stringValue }: TestCase) => { - const n: PositiveNumber = new PositiveNumber(value); +describe("PositiveNumber.toString", () => { + test.each(tests)( + `(new PositiveNumber($value)).toString() == $stringValue`, + ({ value, stringValue }: TestCase) => { + const n: PositiveNumber = new PositiveNumber(value); - expect(n).toBeInstanceOf(PositiveNumber); - expect(n.toString()).toBe(stringValue); - } - ); + expect(n).toBeInstanceOf(PositiveNumber); + expect(n.toString()).toBe(stringValue); + }, + ); }); -describe('PositiveNumber.isZero', () => { - test.each(tests)( - `(new PositiveNumber($value)).isZero() == $isZero`, - ({ value, isZero }: TestCase) => { - const n: PositiveNumber = new PositiveNumber(value); +describe("PositiveNumber.isZero", () => { + test.each(tests)( + `(new PositiveNumber($value)).isZero() == $isZero`, + ({ value, isZero }: TestCase) => { + const n: PositiveNumber = new PositiveNumber(value); - expect(n).toBeInstanceOf(PositiveNumber); - expect(n.isZero()).toBe(isZero); - } - ); + expect(n).toBeInstanceOf(PositiveNumber); + expect(n.isZero()).toBe(isZero); + }, + ); }); -describe('PositiveNumber.isOne', () => { - test.each(tests)( - `(new PositiveNumber($value)).isOne() == $isOne`, - ({ value, isOne }: TestCase) => { - const n: PositiveNumber = new PositiveNumber(value); +describe("PositiveNumber.isOne", () => { + test.each(tests)( + `(new PositiveNumber($value)).isOne() == $isOne`, + ({ value, isOne }: TestCase) => { + const n: PositiveNumber = new PositiveNumber(value); - expect(n).toBeInstanceOf(PositiveNumber); - expect(n.isOne()).toBe(isOne); - } - ); + expect(n).toBeInstanceOf(PositiveNumber); + expect(n.isOne()).toBe(isOne); + }, + ); }); -describe('PositiveNumber.toNumber', () => { - test.each(tests)( - `(new PositiveNumber($value)).toNumber() == $numberValue`, - ({ value, numberValue }: TestCase) => { - const n: PositiveNumber = new PositiveNumber(value); +describe("PositiveNumber.toNumber", () => { + test.each(tests)( + `(new PositiveNumber($value)).toNumber() == $numberValue`, + ({ value, numberValue }: TestCase) => { + const n: PositiveNumber = new PositiveNumber(value); - expect(n).toBeInstanceOf(PositiveNumber); - expect(n.toNumber()).toBe(numberValue); - } - ); + expect(n).toBeInstanceOf(PositiveNumber); + expect(n.toNumber()).toBe(numberValue); + }, + ); }); diff --git a/Common/Tests/Types/SecuritySeverity.test.ts b/Common/Tests/Types/SecuritySeverity.test.ts index 6aca6e63f0..23e77f47e4 100644 --- a/Common/Tests/Types/SecuritySeverity.test.ts +++ b/Common/Tests/Types/SecuritySeverity.test.ts @@ -1,16 +1,16 @@ -import SecuritySeverity from '../../Types/SecuritySeverity'; +import SecuritySeverity from "../../Types/SecuritySeverity"; -describe('enum SecuritySeverity', () => { - test('SecuritySeverity.Critical should be Critical', () => { - expect(SecuritySeverity.Critical).toEqual('Critical'); - }); - test('SecuritySeverity.High should be High', () => { - expect(SecuritySeverity.High).toEqual('High'); - }); - test('SecuritySeverity.Medium should be Medium', () => { - expect(SecuritySeverity.Medium).toEqual('Medium'); - }); - test('SecuritySeverity.Low should be Low', () => { - expect(SecuritySeverity.Low).toEqual('Low'); - }); +describe("enum SecuritySeverity", () => { + test("SecuritySeverity.Critical should be Critical", () => { + expect(SecuritySeverity.Critical).toEqual("Critical"); + }); + test("SecuritySeverity.High should be High", () => { + expect(SecuritySeverity.High).toEqual("High"); + }); + test("SecuritySeverity.Medium should be Medium", () => { + expect(SecuritySeverity.Medium).toEqual("Medium"); + }); + test("SecuritySeverity.Low should be Low", () => { + expect(SecuritySeverity.Low).toEqual("Low"); + }); }); diff --git a/Common/Tests/Types/SerializableObject.test.ts b/Common/Tests/Types/SerializableObject.test.ts index 7476ce459d..e4b410ecfc 100644 --- a/Common/Tests/Types/SerializableObject.test.ts +++ b/Common/Tests/Types/SerializableObject.test.ts @@ -1,44 +1,43 @@ -import NotImplementedException from '../../Types/Exception/NotImplementedException'; -import { JSONObject } from '../../Types/JSON'; -import SerializableObject from '../../Types/SerializableObject'; +import NotImplementedException from "../../Types/Exception/NotImplementedException"; +import { JSONObject } from "../../Types/JSON"; +import SerializableObject from "../../Types/SerializableObject"; -describe('SerializableObject Class', () => { - let serializableObject: SerializableObject; +describe("SerializableObject Class", () => { + let serializableObject: SerializableObject; - beforeEach(() => { - serializableObject = new SerializableObject(); + beforeEach(() => { + serializableObject = new SerializableObject(); + }); + + test("Constructor initializes an instance of SerializableObject", () => { + expect(serializableObject).toBeInstanceOf(SerializableObject); + }); + + describe("toJSON Method", () => { + test("Throws NotImplementedException when called", () => { + expect(() => { + return serializableObject.toJSON(); + }).toThrow(NotImplementedException); }); + }); - test('Constructor initializes an instance of SerializableObject', () => { - expect(serializableObject).toBeInstanceOf(SerializableObject); + describe("fromJSON Method", () => { + test("Throws NotImplementedException when called", () => { + expect(() => { + return SerializableObject.fromJSON({}); + }).toThrow(NotImplementedException); }); + }); - describe('toJSON Method', () => { - test('Throws NotImplementedException when called', () => { - expect(() => { - return serializableObject.toJSON(); - }).toThrow(NotImplementedException); - }); - }); - - describe('fromJSON Method', () => { - test('Throws NotImplementedException when called', () => { - expect(() => { - return SerializableObject.fromJSON({}); - }).toThrow(NotImplementedException); - }); - }); - - describe('fromJSON Instance Method', () => { - test('Returns the result from the static fromJSON method', () => { - const json: JSONObject = { key: 'value' }; - const expectedResult: SerializableObject = new SerializableObject(); - jest.spyOn(SerializableObject, 'fromJSON').mockReturnValue( - expectedResult - ); - const result: SerializableObject = - serializableObject.fromJSON(json); - expect(result).toBe(expectedResult); - }); + describe("fromJSON Instance Method", () => { + test("Returns the result from the static fromJSON method", () => { + const json: JSONObject = { key: "value" }; + const expectedResult: SerializableObject = new SerializableObject(); + jest + .spyOn(SerializableObject, "fromJSON") + .mockReturnValue(expectedResult); + const result: SerializableObject = serializableObject.fromJSON(json); + expect(result).toBe(expectedResult); }); + }); }); diff --git a/Common/Tests/Types/Sleep.test.ts b/Common/Tests/Types/Sleep.test.ts index 1a34d07ed5..ae114cf9ed 100644 --- a/Common/Tests/Types/Sleep.test.ts +++ b/Common/Tests/Types/Sleep.test.ts @@ -1,25 +1,22 @@ -import Sleep from '../../Types/Sleep'; +import Sleep from "../../Types/Sleep"; -describe('Sleep.sleep', () => { - test('should delay by given duration', async () => { - jest.useFakeTimers(); - jest.spyOn(global, 'setTimeout'); +describe("Sleep.sleep", () => { + test("should delay by given duration", async () => { + jest.useFakeTimers(); + jest.spyOn(global, "setTimeout"); - const delay: number = 100; + const delay: number = 100; - // See - https://stackoverflow.com/a/51132058 - Promise.resolve() - .then(() => { - jest.advanceTimersByTime(delay); - }) - .catch(() => {}); + // See - https://stackoverflow.com/a/51132058 + Promise.resolve() + .then(() => { + jest.advanceTimersByTime(delay); + }) + .catch(() => {}); - await Sleep.sleep(delay); + await Sleep.sleep(delay); - expect(setTimeout).toHaveBeenCalledTimes(1); - expect(setTimeout).toHaveBeenLastCalledWith( - expect.any(Function), - delay - ); - }); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), delay); + }); }); diff --git a/Common/Tests/Types/Text.test.ts b/Common/Tests/Types/Text.test.ts index 6d5b53cc2f..18fd119970 100644 --- a/Common/Tests/Types/Text.test.ts +++ b/Common/Tests/Types/Text.test.ts @@ -1,10 +1,8 @@ -import Text from '../../Types/Text'; +import Text from "../../Types/Text"; -describe('class Text', () => { - test('Text.uppercaseFirstLetter should make string first letter Uppercase', () => { - expect(Text.uppercaseFirstLetter('text')).toEqual('Text'); - expect(Text.uppercaseFirstLetter('another test')).toEqual( - 'Another test' - ); - }); +describe("class Text", () => { + test("Text.uppercaseFirstLetter should make string first letter Uppercase", () => { + expect(Text.uppercaseFirstLetter("text")).toEqual("Text"); + expect(Text.uppercaseFirstLetter("another test")).toEqual("Another test"); + }); }); diff --git a/Common/Tests/Types/Timezone.test.ts b/Common/Tests/Types/Timezone.test.ts index 79dc8a203b..f23a27bc3b 100644 --- a/Common/Tests/Types/Timezone.test.ts +++ b/Common/Tests/Types/Timezone.test.ts @@ -1,676 +1,646 @@ -import Timezone from '../../Types/Timezone'; +import Timezone from "../../Types/Timezone"; -describe('enum Timezone', () => { - test('each country should have a corresponding timezone', () => { - expect(Timezone['AfricaAbidjan']).toEqual('Africa/Abidjan'); - expect(Timezone['AfricaAccra']).toEqual('Africa/Accra'); - expect(Timezone['AfricaAddis_Ababa']).toEqual('Africa/Addis_Ababa'); - expect(Timezone['AfricaAlgiers']).toEqual('Africa/Algiers'); - expect(Timezone['AfricaAsmara']).toEqual('Africa/Asmara'); - expect(Timezone['AfricaAsmera']).toEqual('Africa/Asmera'); - expect(Timezone['AfricaBamako']).toEqual('Africa/Bamako'); - expect(Timezone['AfricaBangui']).toEqual('Africa/Bangui'); - expect(Timezone['AfricaBanjul']).toEqual('Africa/Banjul'); - expect(Timezone['AfricaBissau']).toEqual('Africa/Bissau'); - expect(Timezone['AfricaBlantyre']).toEqual('Africa/Blantyre'); - expect(Timezone['AfricaBrazzaville']).toEqual('Africa/Brazzaville'); - expect(Timezone['AfricaBujumbura']).toEqual('Africa/Bujumbura'); - expect(Timezone['AfricaCairo']).toEqual('Africa/Cairo'); - expect(Timezone['AfricaCasablanca']).toEqual('Africa/Casablanca'); - expect(Timezone['AfricaCeuta']).toEqual('Africa/Ceuta'); - expect(Timezone['AfricaConakry']).toEqual('Africa/Conakry'); - expect(Timezone['AfricaDakar']).toEqual('Africa/Dakar'); - expect(Timezone['AfricaDar_es_Salaam']).toEqual('Africa/Dar_es_Salaam'); - expect(Timezone['AfricaDjibouti']).toEqual('Africa/Djibouti'); - expect(Timezone['AfricaDouala']).toEqual('Africa/Douala'); - expect(Timezone['AfricaEl_Aaiun']).toEqual('Africa/El_Aaiun'); - expect(Timezone['AfricaFreetown']).toEqual('Africa/Freetown'); - expect(Timezone['AfricaGaborone']).toEqual('Africa/Gaborone'); - expect(Timezone['AfricaHarare']).toEqual('Africa/Harare'); - expect(Timezone['AfricaJohannesburg']).toEqual('Africa/Johannesburg'); - expect(Timezone['AfricaJuba']).toEqual('Africa/Juba'); - expect(Timezone['AfricaKampala']).toEqual('Africa/Kampala'); - expect(Timezone['AfricaKhartoum']).toEqual('Africa/Khartoum'); - expect(Timezone['AfricaKigali']).toEqual('Africa/Kigali'); - expect(Timezone['AfricaKinshasa']).toEqual('Africa/Kinshasa'); - expect(Timezone['AfricaLagos']).toEqual('Africa/Lagos'); - expect(Timezone['AfricaLibreville']).toEqual('Africa/Libreville'); - expect(Timezone['AfricaLome']).toEqual('Africa/Lome'); - expect(Timezone['AfricaLuanda']).toEqual('Africa/Luanda'); - expect(Timezone['AfricaLubumbashi']).toEqual('Africa/Lubumbashi'); - expect(Timezone['AfricaLusaka']).toEqual('Africa/Lusaka'); - expect(Timezone['AfricaMalabo']).toEqual('Africa/Malabo'); - expect(Timezone['AfricaMaputo']).toEqual('Africa/Maputo'); - expect(Timezone['AfricaMaseru']).toEqual('Africa/Maseru'); - expect(Timezone['AfricaMbabane']).toEqual('Africa/Mbabane'); - expect(Timezone['AfricaMogadishu']).toEqual('Africa/Mogadishu'); - expect(Timezone['AfricaMonrovia']).toEqual('Africa/Monrovia'); - expect(Timezone['AfricaNairobi']).toEqual('Africa/Nairobi'); - expect(Timezone['AfricaNdjamena']).toEqual('Africa/Ndjamena'); - expect(Timezone['AfricaNouakchott']).toEqual('Africa/Nouakchott'); - expect(Timezone['AfricaOuagadougou']).toEqual('Africa/Ouagadougou'); - expect(Timezone['AfricaPortoNovo']).toEqual('Africa/Porto-Novo'); - expect(Timezone['AfricaSao_Tome']).toEqual('Africa/Sao_Tome'); - expect(Timezone['AfricaTimbuktu']).toEqual('Africa/Timbuktu'); - expect(Timezone['AfricaTripoli']).toEqual('Africa/Tripoli'); - expect(Timezone['AfricaTunis']).toEqual('Africa/Tunis'); - expect(Timezone['AfricaWindhoek']).toEqual('Africa/Windhoek'); - expect(Timezone['AmericaAdak']).toEqual('America/Adak'); - expect(Timezone['AmericaAnchorage']).toEqual('America/Anchorage'); - expect(Timezone['AmericaAnguilla']).toEqual('America/Anguilla'); - expect(Timezone['AmericaAntigua']).toEqual('America/Antigua'); - expect(Timezone['AmericaAraguaina']).toEqual('America/Araguaina'); - expect(Timezone['AmericaArgentinaBuenos_Aires']).toEqual( - 'America/Argentina/Buenos_Aires' - ); - expect(Timezone['AmericaArgentinaCatamarca']).toEqual( - 'America/Argentina/Catamarca' - ); - expect(Timezone['AmericaArgentinaComodRivadavia']).toEqual( - 'America/Argentina/ComodRivadavia' - ); - expect(Timezone['AmericaArgentinaCordoba']).toEqual( - 'America/Argentina/Cordoba' - ); - expect(Timezone['AmericaArgentinaJujuy']).toEqual( - 'America/Argentina/Jujuy' - ); - expect(Timezone['AmericaArgentinaLa_Rioja']).toEqual( - 'America/Argentina/La_Rioja' - ); - expect(Timezone['AmericaArgentinaMendoza']).toEqual( - 'America/Argentina/Mendoza' - ); - expect(Timezone['AmericaArgentinaRio_Gallegos']).toEqual( - 'America/Argentina/Rio_Gallegos' - ); - expect(Timezone['AmericaArgentinaSalta']).toEqual( - 'America/Argentina/Salta' - ); - expect(Timezone['AmericaArgentinaSan_Juan']).toEqual( - 'America/Argentina/San_Juan' - ); - expect(Timezone['AmericaArgentinaSan_Luis']).toEqual( - 'America/Argentina/San_Luis' - ); - expect(Timezone['AmericaArgentinaTucuman']).toEqual( - 'America/Argentina/Tucuman' - ); - expect(Timezone['AmericaArgentinaUshuaia']).toEqual( - 'America/Argentina/Ushuaia' - ); - expect(Timezone['AmericaAruba']).toEqual('America/Aruba'); - expect(Timezone['AmericaAsuncion']).toEqual('America/Asuncion'); - expect(Timezone['AmericaAtikokan']).toEqual('America/Atikokan'); - expect(Timezone['AmericaAtka']).toEqual('America/Atka'); - expect(Timezone['AmericaBahia']).toEqual('America/Bahia'); - expect(Timezone['AmericaBahia_Banderas']).toEqual( - 'America/Bahia_Banderas' - ); - expect(Timezone['AmericaBarbados']).toEqual('America/Barbados'); - expect(Timezone['AmericaBelem']).toEqual('America/Belem'); - expect(Timezone['AmericaBelize']).toEqual('America/Belize'); - expect(Timezone['AmericaBlancSablon']).toEqual('America/Blanc-Sablon'); - expect(Timezone['AmericaBoa_Vista']).toEqual('America/Boa_Vista'); - expect(Timezone['AmericaBogota']).toEqual('America/Bogota'); - expect(Timezone['AmericaBoise']).toEqual('America/Boise'); - expect(Timezone['AmericaBuenos_Aires']).toEqual('America/Buenos_Aires'); - expect(Timezone['AmericaCambridge_Bay']).toEqual( - 'America/Cambridge_Bay' - ); - expect(Timezone['AmericaCambridge_Bay']).toEqual( - 'America/Cambridge_Bay' - ); - expect(Timezone['AmericaCambridge_Bay']).toEqual( - 'America/Cambridge_Bay' - ); - expect(Timezone['AmericaCampo_Grande']).toEqual('America/Campo_Grande'); - expect(Timezone['AmericaCancun']).toEqual('America/Cancun'); - expect(Timezone['AmericaCaracas']).toEqual('America/Caracas'); - expect(Timezone['AmericaCatamarca']).toEqual('America/Catamarca'); - expect(Timezone['AmericaCayenne']).toEqual('America/Cayenne'); - expect(Timezone['AmericaCayman']).toEqual('America/Cayman'); - expect(Timezone['AmericaChicago']).toEqual('America/Chicago'); - expect(Timezone['AmericaChihuahua']).toEqual('America/Chihuahua'); - expect(Timezone['AmericaCoral_Harbour']).toEqual( - 'America/Coral_Harbour' - ); - expect(Timezone['AmericaCordoba']).toEqual('America/Cordoba'); - expect(Timezone['AmericaCosta_Rica']).toEqual('America/Costa_Rica'); - expect(Timezone['AmericaCreston']).toEqual('America/Creston'); - expect(Timezone['AmericaCuiaba']).toEqual('America/Cuiaba'); - expect(Timezone['AmericaCuracao']).toEqual('America/Curacao'); - expect(Timezone['AmericaDanmarkshavn']).toEqual('America/Danmarkshavn'); - expect(Timezone['AmericaDawson']).toEqual('America/Dawson'); - expect(Timezone['AmericaDawson_Creek']).toEqual('America/Dawson_Creek'); - expect(Timezone['AmericaDenver']).toEqual('America/Denver'); - expect(Timezone['AmericaDetroit']).toEqual('America/Detroit'); - expect(Timezone['AmericaDominica']).toEqual('America/Dominica'); - expect(Timezone['AmericaEdmonton']).toEqual('America/Edmonton'); - expect(Timezone['AmericaEirunepe']).toEqual('America/Eirunepe'); - expect(Timezone['AmericaEl_Salvador']).toEqual('America/El_Salvador'); - expect(Timezone['AmericaFort_Nelson']).toEqual('America/Fort_Nelson'); - expect(Timezone['AmericaFort_Wayne']).toEqual('America/Fort_Wayne'); - expect(Timezone['AmericaFortaleza']).toEqual('America/Fortaleza'); - expect(Timezone['AmericaGlace_Bay']).toEqual('America/Glace_Bay'); - expect(Timezone['AmericaGodthab']).toEqual('America/Godthab'); - expect(Timezone['AmericaGoose_Bay']).toEqual('America/Goose_Bay'); - expect(Timezone['AmericaGrand_Turk']).toEqual('America/Grand_Turk'); - expect(Timezone['AmericaGrenada']).toEqual('America/Grenada'); - expect(Timezone['AmericaGuadeloupe']).toEqual('America/Guadeloupe'); - expect(Timezone['AmericaGuatemala']).toEqual('America/Guatemala'); - expect(Timezone['AmericaGuayaquil']).toEqual('America/Guayaquil'); - expect(Timezone['AmericaGuyana']).toEqual('America/Guyana'); - expect(Timezone['AmericaHalifax']).toEqual('America/Halifax'); - expect(Timezone['AmericaHavana']).toEqual('America/Havana'); - expect(Timezone['AmericaHermosillo']).toEqual('America/Hermosillo'); - expect(Timezone['AmericaIndianaIndianapolis']).toEqual( - 'America/Indiana/Indianapolis' - ); - expect(Timezone['AmericaIndianaKnox']).toEqual('America/Indiana/Knox'); - expect(Timezone['AmericaIndianaMarengo']).toEqual( - 'America/Indiana/Marengo' - ); - expect(Timezone['AmericaIndianaPetersburg']).toEqual( - 'America/Indiana/Petersburg' - ); - expect(Timezone['AmericaIndianaTell_City']).toEqual( - 'America/Indiana/Tell_City' - ); - expect(Timezone['AmericaIndianaVevay']).toEqual( - 'America/Indiana/Vevay' - ); - expect(Timezone['AmericaIndianaVincennes']).toEqual( - 'America/Indiana/Vincennes' - ); - expect(Timezone['AmericaIndianaWinamac']).toEqual( - 'America/Indiana/Winamac' - ); - expect(Timezone['AmericaIndianapolis']).toEqual('America/Indianapolis'); - expect(Timezone['AmericaInuvik']).toEqual('America/Inuvik'); - expect(Timezone['AmericaIqaluit']).toEqual('America/Iqaluit'); - expect(Timezone['AmericaJamaica']).toEqual('America/Jamaica'); - expect(Timezone['AmericaJujuy']).toEqual('America/Jujuy'); - expect(Timezone['AmericaJuneau']).toEqual('America/Juneau'); - expect(Timezone['AmericaKentuckyLouisville']).toEqual( - 'America/Kentucky/Louisville' - ); - expect(Timezone['AmericaKentuckyMonticello']).toEqual( - 'America/Kentucky/Monticello' - ); - expect(Timezone['AmericaKnox_IN']).toEqual('America/Knox_IN'); - expect(Timezone['AmericaKralendijk']).toEqual('America/Kralendijk'); - expect(Timezone['AmericaLa_Paz']).toEqual('America/La_Paz'); - expect(Timezone['AmericaLos_Angeles']).toEqual('America/Los_Angeles'); - expect(Timezone['AmericaLouisville']).toEqual('America/Louisville'); - expect(Timezone['AmericaLower_Princes']).toEqual( - 'America/Lower_Princes' - ); - expect(Timezone['AmericaMaceio']).toEqual('America/Maceio'); - expect(Timezone['AmericaManagua']).toEqual('America/Managua'); - expect(Timezone['AmericaManaus']).toEqual('America/Manaus'); - expect(Timezone['AmericaMarigot']).toEqual('America/Marigot'); - expect(Timezone['AmericaMartinique']).toEqual('America/Martinique'); - expect(Timezone['AmericaMatamoros']).toEqual('America/Matamoros'); - expect(Timezone['AmericaMazatlan']).toEqual('America/Mazatlan'); - expect(Timezone['AmericaMendoza']).toEqual('America/Mendoza'); - expect(Timezone['AmericaMenominee']).toEqual('America/Menominee'); - expect(Timezone['AmericaMerida']).toEqual('America/Merida'); - expect(Timezone['AmericaMetlakatla']).toEqual('America/Metlakatla'); - expect(Timezone['AmericaMexico_City']).toEqual('America/Mexico_City'); - expect(Timezone['AmericaMiquelon']).toEqual('America/Miquelon'); - expect(Timezone['AmericaMoncton']).toEqual('America/Moncton'); - expect(Timezone['AmericaMonterrey']).toEqual('America/Monterrey'); - expect(Timezone['AmericaMontevideo']).toEqual('America/Montevideo'); - expect(Timezone['AmericaMontreal']).toEqual('America/Montreal'); - expect(Timezone['AmericaMontserrat']).toEqual('America/Montserrat'); - expect(Timezone['AmericaNassau']).toEqual('America/Nassau'); - expect(Timezone['AmericaNew_York']).toEqual('America/New_York'); - expect(Timezone['AmericaNipigon']).toEqual('America/Nipigon'); - expect(Timezone['AmericaNome']).toEqual('America/Nome'); - expect(Timezone['AmericaNoronha']).toEqual('America/Noronha'); - expect(Timezone['AmericaNorth_DakotaBeulah']).toEqual( - 'America/North_Dakota/Beulah' - ); - expect(Timezone['AmericaNorth_DakotaCenter']).toEqual( - 'America/North_Dakota/Center' - ); - expect(Timezone['AmericaNorth_DakotaNew_Salem']).toEqual( - 'America/North_Dakota/New_Salem' - ); - expect(Timezone['AmericaOjinaga']).toEqual('America/Ojinaga'); - expect(Timezone['AmericaPanama']).toEqual('America/Panama'); - expect(Timezone['AmericaPangnirtung']).toEqual('America/Pangnirtung'); - expect(Timezone['AmericaParamaribo']).toEqual('America/Paramaribo'); - expect(Timezone['AmericaPhoenix']).toEqual('America/Phoenix'); - expect(Timezone['AmericaPortAuPrince']).toEqual( - 'America/Port-au-Prince' - ); - expect(Timezone['AmericaPort_of_Spain']).toEqual( - 'America/Port_of_Spain' - ); - expect(Timezone['AmericaPorto_Acre']).toEqual('America/Porto_Acre'); - expect(Timezone['AmericaPorto_Velho']).toEqual('America/Porto_Velho'); - expect(Timezone['AmericaPuerto_Rico']).toEqual('America/Puerto_Rico'); - expect(Timezone['AmericaPunta_Arenas']).toEqual('America/Punta_Arenas'); - expect(Timezone['AmericaRainy_River']).toEqual('America/Rainy_River'); - expect(Timezone['AmericaRankin_Inlet']).toEqual('America/Rankin_Inlet'); - expect(Timezone['AmericaRecife']).toEqual('America/Recife'); - expect(Timezone['AmericaRegina']).toEqual('America/Regina'); - expect(Timezone['AmericaResolute']).toEqual('America/Resolute'); - expect(Timezone['AmericaRio_Branco']).toEqual('America/Rio_Branco'); - expect(Timezone['AmericaRosario']).toEqual('America/Rosario'); - expect(Timezone['AmericaSanta_Isabel']).toEqual('America/Santa_Isabel'); - expect(Timezone['AmericaSantarem']).toEqual('America/Santarem'); - expect(Timezone['AmericaSantiago']).toEqual('America/Santiago'); - expect(Timezone['AmericaSanto_Domingo']).toEqual( - 'America/Santo_Domingo' - ); - expect(Timezone['AmericaSao_Paulo']).toEqual('America/Sao_Paulo'); - expect(Timezone['AmericaScoresbysund']).toEqual('America/Scoresbysund'); - expect(Timezone['AmericaShiprock']).toEqual('America/Shiprock'); - expect(Timezone['AmericaSitka']).toEqual('America/Sitka'); - expect(Timezone['AmericaSt_Barthelemy']).toEqual( - 'America/St_Barthelemy' - ); - expect(Timezone['AmericaSt_Johns']).toEqual('America/St_Johns'); - expect(Timezone['AmericaSt_Kitts']).toEqual('America/St_Kitts'); - expect(Timezone['AmericaSt_Lucia']).toEqual('America/St_Lucia'); - expect(Timezone['AmericaSt_Thomas']).toEqual('America/St_Thomas'); - expect(Timezone['AmericaSt_Vincent']).toEqual('America/St_Vincent'); - expect(Timezone['AmericaSwift_Current']).toEqual( - 'America/Swift_Current' - ); - expect(Timezone['AmericaTegucigalpa']).toEqual('America/Tegucigalpa'); - expect(Timezone['AmericaThule']).toEqual('America/Thule'); - expect(Timezone['AmericaThunder_Bay']).toEqual('America/Thunder_Bay'); - expect(Timezone['AmericaTijuana']).toEqual('America/Tijuana'); - expect(Timezone['AmericaToronto']).toEqual('America/Toronto'); - expect(Timezone['AmericaTortola']).toEqual('America/Tortola'); - expect(Timezone['AmericaVancouver']).toEqual('America/Vancouver'); - expect(Timezone['AmericaVirgin']).toEqual('America/Virgin'); - expect(Timezone['AmericaWhitehorse']).toEqual('America/Whitehorse'); - expect(Timezone['AmericaWinnipeg']).toEqual('America/Winnipeg'); - expect(Timezone['AmericaYakutat']).toEqual('America/Yakutat'); - expect(Timezone['AmericaYellowknife']).toEqual('America/Yellowknife'); - expect(Timezone['AntarcticaCasey']).toEqual('Antarctica/Casey'); - expect(Timezone['AntarcticaDavis']).toEqual('Antarctica/Davis'); - expect(Timezone['AntarcticaDumontDUrville']).toEqual( - 'Antarctica/DumontDUrville' - ); - expect(Timezone['AntarcticaMacquarie']).toEqual('Antarctica/Macquarie'); - expect(Timezone['AntarcticaMawson']).toEqual('Antarctica/Mawson'); - expect(Timezone['AntarcticaMcMurdo']).toEqual('Antarctica/McMurdo'); - expect(Timezone['AntarcticaPalmer']).toEqual('Antarctica/Palmer'); - expect(Timezone['AntarcticaRothera']).toEqual('Antarctica/Rothera'); - expect(Timezone['AntarcticaSouth_Pole']).toEqual( - 'Antarctica/South_Pole' - ); - expect(Timezone['AntarcticaSyowa']).toEqual('Antarctica/Syowa'); - expect(Timezone['AntarcticaTroll']).toEqual('Antarctica/Troll'); - expect(Timezone['AntarcticaVostok']).toEqual('Antarctica/Vostok'); - expect(Timezone['ArcticLongyearbyen']).toEqual('Arctic/Longyearbyen'); - expect(Timezone['AsiaAden']).toEqual('Asia/Aden'); - expect(Timezone['AsiaAlmaty']).toEqual('Asia/Almaty'); - expect(Timezone['AsiaAmman']).toEqual('Asia/Amman'); - expect(Timezone['AsiaAnadyr']).toEqual('Asia/Anadyr'); - expect(Timezone['AsiaAqtau']).toEqual('Asia/Aqtau'); - expect(Timezone['AsiaAqtobe']).toEqual('Asia/Aqtobe'); - expect(Timezone['AsiaAshgabat']).toEqual('Asia/Ashgabat'); - expect(Timezone['AsiaAshkhabad']).toEqual('Asia/Ashkhabad'); - expect(Timezone['AsiaAtyrau']).toEqual('Asia/Atyrau'); - expect(Timezone['AsiaBaghdad']).toEqual('Asia/Baghdad'); - expect(Timezone['AsiaBahrain']).toEqual('Asia/Bahrain'); - expect(Timezone['AsiaBaku']).toEqual('Asia/Baku'); - expect(Timezone['AsiaBangkok']).toEqual('Asia/Bangkok'); - expect(Timezone['AsiaBarnaul']).toEqual('Asia/Barnaul'); - expect(Timezone['AsiaBeirut']).toEqual('Asia/Beirut'); - expect(Timezone['AsiaBishkek']).toEqual('Asia/Bishkek'); - expect(Timezone['AsiaBrunei']).toEqual('Asia/Brunei'); - expect(Timezone['AsiaCalcutta']).toEqual('Asia/Calcutta'); - expect(Timezone['AsiaChita']).toEqual('Asia/Chita'); - expect(Timezone['AsiaChoibalsan']).toEqual('Asia/Choibalsan'); - expect(Timezone['AsiaChongqing']).toEqual('Asia/Chongqing'); - expect(Timezone['AsiaChungking']).toEqual('Asia/Chungking'); - expect(Timezone['AsiaColombo']).toEqual('Asia/Colombo'); - expect(Timezone['AsiaDacca']).toEqual('Asia/Dacca'); - expect(Timezone['AsiaDamascus']).toEqual('Asia/Damascus'); - expect(Timezone['AsiaDhaka']).toEqual('Asia/Dhaka'); - expect(Timezone['AsiaDili']).toEqual('Asia/Dili'); - expect(Timezone['AsiaDubai']).toEqual('Asia/Dubai'); - expect(Timezone['AsiaDushanbe']).toEqual('Asia/Dushanbe'); - expect(Timezone['AsiaFamagusta']).toEqual('Asia/Famagusta'); - expect(Timezone['AsiaGaza']).toEqual('Asia/Gaza'); - expect(Timezone['AsiaHarbin']).toEqual('Asia/Harbin'); - expect(Timezone['AsiaHebron']).toEqual('Asia/Hebron'); - expect(Timezone['AsiaHo_Chi_Minh']).toEqual('Asia/Ho_Chi_Minh'); - expect(Timezone['AsiaHong_Kong']).toEqual('Asia/Hong_Kong'); - expect(Timezone['AsiaHovd']).toEqual('Asia/Hovd'); - expect(Timezone['AsiaIrkutsk']).toEqual('Asia/Irkutsk'); - expect(Timezone['AsiaIstanbul']).toEqual('Asia/Istanbul'); - expect(Timezone['AsiaJakarta']).toEqual('Asia/Jakarta'); - expect(Timezone['AsiaJayapura']).toEqual('Asia/Jayapura'); - expect(Timezone['AsiaJerusalem']).toEqual('Asia/Jerusalem'); - expect(Timezone['AsiaKabul']).toEqual('Asia/Kabul'); - expect(Timezone['AsiaKamchatka']).toEqual('Asia/Kamchatka'); - expect(Timezone['AsiaKarachi']).toEqual('Asia/Karachi'); - expect(Timezone['AsiaKashgar']).toEqual('Asia/Kashgar'); - expect(Timezone['AsiaKathmandu']).toEqual('Asia/Kathmandu'); - expect(Timezone['AsiaKatmandu']).toEqual('Asia/Katmandu'); - expect(Timezone['AsiaKhandyga']).toEqual('Asia/Khandyga'); - expect(Timezone['AsiaKolkata']).toEqual('Asia/Kolkata'); - expect(Timezone['AsiaKrasnoyarsk']).toEqual('Asia/Krasnoyarsk'); - expect(Timezone['AsiaKuala_Lumpur']).toEqual('Asia/Kuala_Lumpur'); - expect(Timezone['AsiaKuching']).toEqual('Asia/Kuching'); - expect(Timezone['AsiaKuwait']).toEqual('Asia/Kuwait'); - expect(Timezone['AsiaMacao']).toEqual('Asia/Macao'); - expect(Timezone['AsiaMacau']).toEqual('Asia/Macau'); - expect(Timezone['AsiaMagadan']).toEqual('Asia/Magadan'); - expect(Timezone['AsiaMakassar']).toEqual('Asia/Makassar'); - expect(Timezone['AsiaManila']).toEqual('Asia/Manila'); - expect(Timezone['AsiaMuscat']).toEqual('Asia/Muscat'); - expect(Timezone['AsiaNicosia']).toEqual('Asia/Nicosia'); - expect(Timezone['AsiaNovokuznetsk']).toEqual('Asia/Novokuznetsk'); - expect(Timezone['AsiaNovosibirsk']).toEqual('Asia/Novosibirsk'); - expect(Timezone['AsiaOmsk']).toEqual('Asia/Omsk'); - expect(Timezone['AsiaOral']).toEqual('Asia/Oral'); - expect(Timezone['AsiaPhnom_Penh']).toEqual('Asia/Phnom_Penh'); - expect(Timezone['AsiaPontianak']).toEqual('Asia/Pontianak'); - expect(Timezone['AsiaPyongyang']).toEqual('Asia/Pyongyang'); - expect(Timezone['AsiaQatar']).toEqual('Asia/Qatar'); - expect(Timezone['AsiaQyzylorda']).toEqual('Asia/Qyzylorda'); - expect(Timezone['AsiaRangoon']).toEqual('Asia/Rangoon'); - expect(Timezone['AsiaRiyadh']).toEqual('Asia/Riyadh'); - expect(Timezone['AsiaSaigon']).toEqual('Asia/Saigon'); - expect(Timezone['AsiaSakhalin']).toEqual('Asia/Sakhalin'); - expect(Timezone['AsiaSeoul']).toEqual('Asia/Seoul'); - expect(Timezone['AsiaShanghai']).toEqual('Asia/Shanghai'); - expect(Timezone['AsiaSingapore']).toEqual('Asia/Singapore'); - expect(Timezone['AsiaSrednekolymsk']).toEqual('Asia/Srednekolymsk'); - expect(Timezone['AsiaTaipei']).toEqual('Asia/Taipei'); - expect(Timezone['AsiaTashkent']).toEqual('Asia/Tashkent'); - expect(Timezone['AsiaTbilisi']).toEqual('Asia/Tbilisi'); - expect(Timezone['AsiaTehran']).toEqual('Asia/Tehran'); - expect(Timezone['AsiaTel_Aviv']).toEqual('Asia/Tel_Aviv'); - expect(Timezone['AsiaThimbu']).toEqual('Asia/Thimbu'); - expect(Timezone['AsiaThimphu']).toEqual('Asia/Thimphu'); - expect(Timezone['AsiaTokyo']).toEqual('Asia/Tokyo'); - expect(Timezone['AsiaTomsk']).toEqual('Asia/Tomsk'); - expect(Timezone['AsiaUjung_Pandang']).toEqual('Asia/Ujung_Pandang'); - expect(Timezone['AsiaUlaanbaatar']).toEqual('Asia/Ulaanbaatar'); - expect(Timezone['AsiaUlan_Bator']).toEqual('Asia/Ulan_Bator'); - expect(Timezone['AsiaUrumqi']).toEqual('Asia/Urumqi'); - expect(Timezone['AsiaUstNera']).toEqual('Asia/Ust-Nera'); - expect(Timezone['AsiaVientiane']).toEqual('Asia/Vientiane'); - expect(Timezone['AsiaVladivostok']).toEqual('Asia/Vladivostok'); - expect(Timezone['AsiaYakutsk']).toEqual('Asia/Yakutsk'); - expect(Timezone['AsiaYangon']).toEqual('Asia/Yangon'); - expect(Timezone['AsiaYekaterinburg']).toEqual('Asia/Yekaterinburg'); - expect(Timezone['AsiaYekaterinburg']).toEqual('Asia/Yekaterinburg'); - expect(Timezone['AsiaYekaterinburg']).toEqual('Asia/Yekaterinburg'); - expect(Timezone['AsiaYekaterinburg']).toEqual('Asia/Yekaterinburg'); - expect(Timezone['AsiaYekaterinburg']).toEqual('Asia/Yekaterinburg'); - expect(Timezone['AsiaYekaterinburg']).toEqual('Asia/Yekaterinburg'); - expect(Timezone['AsiaYekaterinburg']).toEqual('Asia/Yekaterinburg'); - expect(Timezone['AsiaYerevan']).toEqual('Asia/Yerevan'); - expect(Timezone['AtlanticAzores']).toEqual('Atlantic/Azores'); - expect(Timezone['AtlanticBermuda']).toEqual('Atlantic/Bermuda'); - expect(Timezone['AtlanticCanary']).toEqual('Atlantic/Canary'); - expect(Timezone['AtlanticCape_Verde']).toEqual('Atlantic/Cape_Verde'); - expect(Timezone['AtlanticFaeroe']).toEqual('Atlantic/Faeroe'); - expect(Timezone['AtlanticFaroe']).toEqual('Atlantic/Faroe'); - expect(Timezone['AtlanticJan_Mayen']).toEqual('Atlantic/Jan_Mayen'); - expect(Timezone['AtlanticMadeira']).toEqual('Atlantic/Madeira'); - expect(Timezone['AtlanticReykjavik']).toEqual('Atlantic/Reykjavik'); - expect(Timezone['AtlanticSouth_Georgia']).toEqual( - 'Atlantic/South_Georgia' - ); - expect(Timezone['AtlanticSt_Helena']).toEqual('Atlantic/St_Helena'); - expect(Timezone['AtlanticStanley']).toEqual('Atlantic/Stanley'); - expect(Timezone['AustraliaACT']).toEqual('Australia/ACT'); - expect(Timezone['AustraliaAdelaide']).toEqual('Australia/Adelaide'); - expect(Timezone['AustraliaBrisbane']).toEqual('Australia/Brisbane'); - expect(Timezone['AustraliaBroken_Hill']).toEqual( - 'Australia/Broken_Hill' - ); - expect(Timezone['AustraliaCanberra']).toEqual('Australia/Canberra'); - expect(Timezone['AustraliaCurrie']).toEqual('Australia/Currie'); - expect(Timezone['AustraliaDarwin']).toEqual('Australia/Darwin'); - expect(Timezone['AustraliaEucla']).toEqual('Australia/Eucla'); - expect(Timezone['AustraliaHobart']).toEqual('Australia/Hobart'); - expect(Timezone['AustraliaLHI']).toEqual('Australia/LHI'); - expect(Timezone['AustraliaLindeman']).toEqual('Australia/Lindeman'); - expect(Timezone['AustraliaLord_Howe']).toEqual('Australia/Lord_Howe'); - expect(Timezone['AustraliaMelbourne']).toEqual('Australia/Melbourne'); - expect(Timezone['AustraliaNSW']).toEqual('Australia/NSW'); - expect(Timezone['AustraliaNorth']).toEqual('Australia/North'); - expect(Timezone['AustraliaPerth']).toEqual('Australia/Perth'); - expect(Timezone['AustraliaQueensland']).toEqual('Australia/Queensland'); - expect(Timezone['AustraliaSouth']).toEqual('Australia/South'); - expect(Timezone['AustraliaSydney']).toEqual('Australia/Sydney'); - expect(Timezone['AustraliaTasmania']).toEqual('Australia/Tasmania'); - expect(Timezone['AustraliaVictoria']).toEqual('Australia/Victoria'); - expect(Timezone['AustraliaWest']).toEqual('Australia/West'); - expect(Timezone['AustraliaYancowinna']).toEqual('Australia/Yancowinna'); - expect(Timezone['BrazilAcre']).toEqual('Brazil/Acre'); - expect(Timezone['BrazilDeNoronha']).toEqual('Brazil/DeNoronha'); - expect(Timezone['BrazilEast']).toEqual('Brazil/East'); - expect(Timezone['BrazilWest']).toEqual('Brazil/West'); - expect(Timezone['CET']).toEqual('CET'); - expect(Timezone['CST6CDT']).toEqual('CST6CDT'); - expect(Timezone['CanadaAtlantic']).toEqual('Canada/Atlantic'); - expect(Timezone['CanadaCentral']).toEqual('Canada/Central'); - expect(Timezone['CanadaEastern']).toEqual('Canada/Eastern'); - expect(Timezone['CanadaMountain']).toEqual('Canada/Mountain'); - expect(Timezone['CanadaNewfoundland']).toEqual('Canada/Newfoundland'); - expect(Timezone['CanadaPacific']).toEqual('Canada/Pacific'); - expect(Timezone['CanadaSaskatchewan']).toEqual('Canada/Saskatchewan'); - expect(Timezone['CanadaYukon']).toEqual('Canada/Yukon'); - expect(Timezone['ChileContinental']).toEqual('Chile/Continental'); - expect(Timezone['ChileEasterIsland']).toEqual('Chile/EasterIsland'); - expect(Timezone['Cuba']).toEqual('Cuba'); - expect(Timezone['EET']).toEqual('EET'); - expect(Timezone['EST']).toEqual('EST'); - expect(Timezone['EST5EDT']).toEqual('EST5EDT'); - expect(Timezone['Egypt']).toEqual('Egypt'); - expect(Timezone['Eire']).toEqual('Eire'); - expect(Timezone['EtcGMT']).toEqual('Etc/GMT'); - expect(Timezone['EtcGMTPositive0']).toEqual('Etc/GMT+0'); - expect(Timezone['EtcGMTPositive1']).toEqual('Etc/GMT+1'); - expect(Timezone['EtcGMTPositive10']).toEqual('Etc/GMT+10'); - expect(Timezone['EtcGMTPositive11']).toEqual('Etc/GMT+11'); - expect(Timezone['EtcGMTPositive12']).toEqual('Etc/GMT+12'); - expect(Timezone['EtcGMTPositive2']).toEqual('Etc/GMT+2'); - expect(Timezone['EtcGMTPositive3']).toEqual('Etc/GMT+3'); - expect(Timezone['EtcGMTPositive4']).toEqual('Etc/GMT+4'); - expect(Timezone['EtcGMTPositive5']).toEqual('Etc/GMT+5'); - expect(Timezone['EtcGMTPositive6']).toEqual('Etc/GMT+6'); - expect(Timezone['EtcGMTPositive7']).toEqual('Etc/GMT+7'); - expect(Timezone['EtcGMTPositive8']).toEqual('Etc/GMT+8'); - expect(Timezone['EtcGMTPositive9']).toEqual('Etc/GMT+9'); - expect(Timezone['EtcGMTNegative0']).toEqual('Etc/GMT-0'); - expect(Timezone['EtcGMTNegative1']).toEqual('Etc/GMT-1'); - expect(Timezone['EtcGMTNegative10']).toEqual('Etc/GMT-10'); - expect(Timezone['EtcGMTNegative11']).toEqual('Etc/GMT-11'); - expect(Timezone['EtcGMTNegative12']).toEqual('Etc/GMT-12'); - expect(Timezone['EtcGMTNegative13']).toEqual('Etc/GMT-13'); - expect(Timezone['EtcGMTNegative14']).toEqual('Etc/GMT-14'); - expect(Timezone['EtcGMTNegative2']).toEqual('Etc/GMT-2'); - expect(Timezone['EtcGMTNegative3']).toEqual('Etc/GMT-3'); - expect(Timezone['EtcGMTNegative4']).toEqual('Etc/GMT-4'); - expect(Timezone['EtcGMTNegative5']).toEqual('Etc/GMT-5'); - expect(Timezone['EtcGMTNegative6']).toEqual('Etc/GMT-6'); - expect(Timezone['EtcGMTNegative7']).toEqual('Etc/GMT-7'); - expect(Timezone['EtcGMTNegative8']).toEqual('Etc/GMT-8'); - expect(Timezone['EtcGMTNegative9']).toEqual('Etc/GMT-9'); - expect(Timezone['EtcGMT0']).toEqual('Etc/GMT0'); - expect(Timezone['EtcGreenwich']).toEqual('Etc/Greenwich'); - expect(Timezone['EtcUCT']).toEqual('Etc/UCT'); - expect(Timezone['EtcUTC']).toEqual('Etc/UTC'); - expect(Timezone['EtcUniversal']).toEqual('Etc/Universal'); - expect(Timezone['EtcZulu']).toEqual('Etc/Zulu'); - expect(Timezone['EuropeAmsterdam']).toEqual('Europe/Amsterdam'); - expect(Timezone['EuropeAndorra']).toEqual('Europe/Andorra'); - expect(Timezone['EuropeAstrakhan']).toEqual('Europe/Astrakhan'); - expect(Timezone['EuropeAthens']).toEqual('Europe/Athens'); - expect(Timezone['EuropeBelfast']).toEqual('Europe/Belfast'); - expect(Timezone['EuropeBelgrade']).toEqual('Europe/Belgrade'); - expect(Timezone['EuropeBerlin']).toEqual('Europe/Berlin'); - expect(Timezone['EuropeBratislava']).toEqual('Europe/Bratislava'); - expect(Timezone['EuropeBrussels']).toEqual('Europe/Brussels'); - expect(Timezone['EuropeBucharest']).toEqual('Europe/Bucharest'); - expect(Timezone['EuropeBudapest']).toEqual('Europe/Budapest'); - expect(Timezone['EuropeBusingen']).toEqual('Europe/Busingen'); - expect(Timezone['EuropeChisinau']).toEqual('Europe/Chisinau'); - expect(Timezone['EuropeCopenhagen']).toEqual('Europe/Copenhagen'); - expect(Timezone['EuropeDublin']).toEqual('Europe/Dublin'); - expect(Timezone['EuropeGibraltar']).toEqual('Europe/Gibraltar'); - expect(Timezone['EuropeGuernsey']).toEqual('Europe/Guernsey'); - expect(Timezone['EuropeHelsinki']).toEqual('Europe/Helsinki'); - expect(Timezone['EuropeIsle_of_Man']).toEqual('Europe/Isle_of_Man'); - expect(Timezone['EuropeIstanbul']).toEqual('Europe/Istanbul'); - expect(Timezone['EuropeJersey']).toEqual('Europe/Jersey'); - expect(Timezone['EuropeKaliningrad']).toEqual('Europe/Kaliningrad'); - expect(Timezone['EuropeKiev']).toEqual('Europe/Kiev'); - expect(Timezone['EuropeKirov']).toEqual('Europe/Kirov'); - expect(Timezone['EuropeLisbon']).toEqual('Europe/Lisbon'); - expect(Timezone['EuropeLjubljana']).toEqual('Europe/Ljubljana'); - expect(Timezone['EuropeLondon']).toEqual('Europe/London'); - expect(Timezone['EuropeLuxembourg']).toEqual('Europe/Luxembourg'); - expect(Timezone['EuropeMadrid']).toEqual('Europe/Madrid'); - expect(Timezone['EuropeMalta']).toEqual('Europe/Malta'); - expect(Timezone['EuropeMariehamn']).toEqual('Europe/Mariehamn'); - expect(Timezone['EuropeMinsk']).toEqual('Europe/Minsk'); - expect(Timezone['EuropeMonaco']).toEqual('Europe/Monaco'); - expect(Timezone['EuropeMoscow']).toEqual('Europe/Moscow'); - expect(Timezone['EuropeNicosia']).toEqual('Europe/Nicosia'); - expect(Timezone['EuropeOslo']).toEqual('Europe/Oslo'); - expect(Timezone['EuropeParis']).toEqual('Europe/Paris'); - expect(Timezone['EuropePodgorica']).toEqual('Europe/Podgorica'); - expect(Timezone['EuropePrague']).toEqual('Europe/Prague'); - expect(Timezone['EuropeRiga']).toEqual('Europe/Riga'); - expect(Timezone['EuropeRome']).toEqual('Europe/Rome'); - expect(Timezone['EuropeSamara']).toEqual('Europe/Samara'); - expect(Timezone['EuropeSan_Marino']).toEqual('Europe/San_Marino'); - expect(Timezone['EuropeSarajevo']).toEqual('Europe/Sarajevo'); - expect(Timezone['EuropeSaratov']).toEqual('Europe/Saratov'); - expect(Timezone['EuropeSimferopol']).toEqual('Europe/Simferopol'); - expect(Timezone['EuropeSkopje']).toEqual('Europe/Skopje'); - expect(Timezone['EuropeSofia']).toEqual('Europe/Sofia'); - expect(Timezone['EuropeStockholm']).toEqual('Europe/Stockholm'); - expect(Timezone['EuropeTallinn']).toEqual('Europe/Tallinn'); - expect(Timezone['EuropeTirane']).toEqual('Europe/Tirane'); - expect(Timezone['EuropeTiraspol']).toEqual('Europe/Tiraspol'); - expect(Timezone['EuropeUlyanovsk']).toEqual('Europe/Ulyanovsk'); - expect(Timezone['EuropeUzhgorod']).toEqual('Europe/Uzhgorod'); - expect(Timezone['EuropeVaduz']).toEqual('Europe/Vaduz'); - expect(Timezone['EuropeVatican']).toEqual('Europe/Vatican'); - expect(Timezone['EuropeVienna']).toEqual('Europe/Vienna'); - expect(Timezone['EuropeVilnius']).toEqual('Europe/Vilnius'); - expect(Timezone['EuropeVolgograd']).toEqual('Europe/Volgograd'); - expect(Timezone['EuropeWarsaw']).toEqual('Europe/Warsaw'); - expect(Timezone['EuropeZagreb']).toEqual('Europe/Zagreb'); - expect(Timezone['EuropeZaporozhye']).toEqual('Europe/Zaporozhye'); - expect(Timezone['EuropeZurich']).toEqual('Europe/Zurich'); - expect(Timezone['GB']).toEqual('GB'); - expect(Timezone['GBEire']).toEqual('GB-Eire'); - expect(Timezone['HST']).toEqual('HST'); - expect(Timezone['Hongkong']).toEqual('Hongkong'); - expect(Timezone['Iceland']).toEqual('Iceland'); - expect(Timezone['IndianAntananarivo']).toEqual('Indian/Antananarivo'); - expect(Timezone['IndianChagos']).toEqual('Indian/Chagos'); - expect(Timezone['IndianChristmas']).toEqual('Indian/Christmas'); - expect(Timezone['IndianCocos']).toEqual('Indian/Cocos'); - expect(Timezone['IndianComoro']).toEqual('Indian/Comoro'); - expect(Timezone['IndianKerguelen']).toEqual('Indian/Kerguelen'); - expect(Timezone['IndianMahe']).toEqual('Indian/Mahe'); - expect(Timezone['IndianMaldives']).toEqual('Indian/Maldives'); - expect(Timezone['IndianMauritius']).toEqual('Indian/Mauritius'); - expect(Timezone['IndianMayotte']).toEqual('Indian/Mayotte'); - expect(Timezone['IndianReunion']).toEqual('Indian/Reunion'); - expect(Timezone['Iran']).toEqual('Iran'); - expect(Timezone['Israel']).toEqual('Israel'); - expect(Timezone['Jamaica']).toEqual('Jamaica'); - expect(Timezone['Japan']).toEqual('Japan'); - expect(Timezone['Kwajalein']).toEqual('Kwajalein'); - expect(Timezone['Libya']).toEqual('Libya'); - expect(Timezone['MET']).toEqual('MET'); - expect(Timezone['MST']).toEqual('MST'); - expect(Timezone['MST7MDT']).toEqual('MST7MDT'); - expect(Timezone['MexicoBajaNorte']).toEqual('Mexico/BajaNorte'); - expect(Timezone['MexicoBajaSur']).toEqual('Mexico/BajaSur'); - expect(Timezone['MexicoGeneral']).toEqual('Mexico/General'); - expect(Timezone['NZ']).toEqual('NZ'); - expect(Timezone['NZCHAT']).toEqual('NZ-CHAT'); - expect(Timezone['Navajo']).toEqual('Navajo'); - expect(Timezone['PRC']).toEqual('PRC'); - expect(Timezone['PST8PDT']).toEqual('PST8PDT'); - expect(Timezone['PacificApia']).toEqual('Pacific/Apia'); - expect(Timezone['PacificAuckland']).toEqual('Pacific/Auckland'); - expect(Timezone['PacificBougainville']).toEqual('Pacific/Bougainville'); - expect(Timezone['PacificChatham']).toEqual('Pacific/Chatham'); - expect(Timezone['PacificChatham']).toEqual('Pacific/Chatham'); - expect(Timezone['PacificEaster']).toEqual('Pacific/Easter'); - expect(Timezone['PacificEfate']).toEqual('Pacific/Efate'); - expect(Timezone['PacificEnderbury']).toEqual('Pacific/Enderbury'); - expect(Timezone['PacificFakaofo']).toEqual('Pacific/Fakaofo'); - expect(Timezone['PacificFiji']).toEqual('Pacific/Fiji'); - expect(Timezone['PacificFunafuti']).toEqual('Pacific/Funafuti'); - expect(Timezone['PacificGalapagos']).toEqual('Pacific/Galapagos'); - expect(Timezone['PacificGambier']).toEqual('Pacific/Gambier'); - expect(Timezone['PacificGuadalcanal']).toEqual('Pacific/Guadalcanal'); - expect(Timezone['PacificGuam']).toEqual('Pacific/Guam'); - expect(Timezone['PacificHonolulu']).toEqual('Pacific/Honolulu'); - expect(Timezone['PacificJohnston']).toEqual('Pacific/Johnston'); - expect(Timezone['PacificKiritimati']).toEqual('Pacific/Kiritimati'); - expect(Timezone['PacificKosrae']).toEqual('Pacific/Kosrae'); - expect(Timezone['PacificKwajalein']).toEqual('Pacific/Kwajalein'); - expect(Timezone['PacificMajuro']).toEqual('Pacific/Majuro'); - expect(Timezone['PacificMarquesas']).toEqual('Pacific/Marquesas'); - expect(Timezone['PacificMidway']).toEqual('Pacific/Midway'); - expect(Timezone['PacificNauru']).toEqual('Pacific/Nauru'); - expect(Timezone['PacificNiue']).toEqual('Pacific/Niue'); - expect(Timezone['PacificNorfolk']).toEqual('Pacific/Norfolk'); - expect(Timezone['PacificNoumea']).toEqual('Pacific/Noumea'); - expect(Timezone['PacificPago_Pago']).toEqual('Pacific/Pago_Pago'); - expect(Timezone['PacificPalau']).toEqual('Pacific/Palau'); - expect(Timezone['PacificPitcairn']).toEqual('Pacific/Pitcairn'); - expect(Timezone['PacificPohnpei']).toEqual('Pacific/Pohnpei'); - expect(Timezone['PacificPonape']).toEqual('Pacific/Ponape'); - expect(Timezone['PacificPort_Moresby']).toEqual('Pacific/Port_Moresby'); - expect(Timezone['PacificRarotonga']).toEqual('Pacific/Rarotonga'); - expect(Timezone['PacificSaipan']).toEqual('Pacific/Saipan'); - expect(Timezone['PacificSamoa']).toEqual('Pacific/Samoa'); - expect(Timezone['PacificTahiti']).toEqual('Pacific/Tahiti'); - expect(Timezone['PacificTarawa']).toEqual('Pacific/Tarawa'); - expect(Timezone['PacificTongatapu']).toEqual('Pacific/Tongatapu'); - expect(Timezone['PacificTruk']).toEqual('Pacific/Truk'); - expect(Timezone['PacificWake']).toEqual('Pacific/Wake'); - expect(Timezone['PacificWallis']).toEqual('Pacific/Wallis'); - expect(Timezone['PacificYap']).toEqual('Pacific/Yap'); - expect(Timezone['Poland']).toEqual('Poland'); - expect(Timezone['Portugal']).toEqual('Portugal'); - expect(Timezone['ROC']).toEqual('ROC'); - expect(Timezone['ROK']).toEqual('ROK'); - expect(Timezone['Singapore']).toEqual('Singapore'); - expect(Timezone['Turkey']).toEqual('Turkey'); - expect(Timezone['UCT']).toEqual('UCT'); - expect(Timezone['USAlaska']).toEqual('US/Alaska'); - expect(Timezone['USAleutian']).toEqual('US/Aleutian'); - expect(Timezone['USArizona']).toEqual('US/Arizona'); - expect(Timezone['USCentral']).toEqual('US/Central'); - expect(Timezone['USEastIndiana']).toEqual('US/East-Indiana'); - expect(Timezone['USEastern']).toEqual('US/Eastern'); - expect(Timezone['USHawaii']).toEqual('US/Hawaii'); - expect(Timezone['USIndianaStarke']).toEqual('US/Indiana-Starke'); - expect(Timezone['USMichigan']).toEqual('US/Michigan'); - expect(Timezone['USMountain']).toEqual('US/Mountain'); - expect(Timezone['USPacific']).toEqual('US/Pacific'); - expect(Timezone['USSamoa']).toEqual('US/Samoa'); - expect(Timezone['UTC']).toEqual('UTC'); - expect(Timezone['Universal']).toEqual('Universal'); - expect(Timezone['WSU']).toEqual('W-SU'); - expect(Timezone['WET']).toEqual('WET'); - expect(Timezone['Zulu']).toEqual('Zulu'); - }); +describe("enum Timezone", () => { + test("each country should have a corresponding timezone", () => { + expect(Timezone["AfricaAbidjan"]).toEqual("Africa/Abidjan"); + expect(Timezone["AfricaAccra"]).toEqual("Africa/Accra"); + expect(Timezone["AfricaAddis_Ababa"]).toEqual("Africa/Addis_Ababa"); + expect(Timezone["AfricaAlgiers"]).toEqual("Africa/Algiers"); + expect(Timezone["AfricaAsmara"]).toEqual("Africa/Asmara"); + expect(Timezone["AfricaAsmera"]).toEqual("Africa/Asmera"); + expect(Timezone["AfricaBamako"]).toEqual("Africa/Bamako"); + expect(Timezone["AfricaBangui"]).toEqual("Africa/Bangui"); + expect(Timezone["AfricaBanjul"]).toEqual("Africa/Banjul"); + expect(Timezone["AfricaBissau"]).toEqual("Africa/Bissau"); + expect(Timezone["AfricaBlantyre"]).toEqual("Africa/Blantyre"); + expect(Timezone["AfricaBrazzaville"]).toEqual("Africa/Brazzaville"); + expect(Timezone["AfricaBujumbura"]).toEqual("Africa/Bujumbura"); + expect(Timezone["AfricaCairo"]).toEqual("Africa/Cairo"); + expect(Timezone["AfricaCasablanca"]).toEqual("Africa/Casablanca"); + expect(Timezone["AfricaCeuta"]).toEqual("Africa/Ceuta"); + expect(Timezone["AfricaConakry"]).toEqual("Africa/Conakry"); + expect(Timezone["AfricaDakar"]).toEqual("Africa/Dakar"); + expect(Timezone["AfricaDar_es_Salaam"]).toEqual("Africa/Dar_es_Salaam"); + expect(Timezone["AfricaDjibouti"]).toEqual("Africa/Djibouti"); + expect(Timezone["AfricaDouala"]).toEqual("Africa/Douala"); + expect(Timezone["AfricaEl_Aaiun"]).toEqual("Africa/El_Aaiun"); + expect(Timezone["AfricaFreetown"]).toEqual("Africa/Freetown"); + expect(Timezone["AfricaGaborone"]).toEqual("Africa/Gaborone"); + expect(Timezone["AfricaHarare"]).toEqual("Africa/Harare"); + expect(Timezone["AfricaJohannesburg"]).toEqual("Africa/Johannesburg"); + expect(Timezone["AfricaJuba"]).toEqual("Africa/Juba"); + expect(Timezone["AfricaKampala"]).toEqual("Africa/Kampala"); + expect(Timezone["AfricaKhartoum"]).toEqual("Africa/Khartoum"); + expect(Timezone["AfricaKigali"]).toEqual("Africa/Kigali"); + expect(Timezone["AfricaKinshasa"]).toEqual("Africa/Kinshasa"); + expect(Timezone["AfricaLagos"]).toEqual("Africa/Lagos"); + expect(Timezone["AfricaLibreville"]).toEqual("Africa/Libreville"); + expect(Timezone["AfricaLome"]).toEqual("Africa/Lome"); + expect(Timezone["AfricaLuanda"]).toEqual("Africa/Luanda"); + expect(Timezone["AfricaLubumbashi"]).toEqual("Africa/Lubumbashi"); + expect(Timezone["AfricaLusaka"]).toEqual("Africa/Lusaka"); + expect(Timezone["AfricaMalabo"]).toEqual("Africa/Malabo"); + expect(Timezone["AfricaMaputo"]).toEqual("Africa/Maputo"); + expect(Timezone["AfricaMaseru"]).toEqual("Africa/Maseru"); + expect(Timezone["AfricaMbabane"]).toEqual("Africa/Mbabane"); + expect(Timezone["AfricaMogadishu"]).toEqual("Africa/Mogadishu"); + expect(Timezone["AfricaMonrovia"]).toEqual("Africa/Monrovia"); + expect(Timezone["AfricaNairobi"]).toEqual("Africa/Nairobi"); + expect(Timezone["AfricaNdjamena"]).toEqual("Africa/Ndjamena"); + expect(Timezone["AfricaNouakchott"]).toEqual("Africa/Nouakchott"); + expect(Timezone["AfricaOuagadougou"]).toEqual("Africa/Ouagadougou"); + expect(Timezone["AfricaPortoNovo"]).toEqual("Africa/Porto-Novo"); + expect(Timezone["AfricaSao_Tome"]).toEqual("Africa/Sao_Tome"); + expect(Timezone["AfricaTimbuktu"]).toEqual("Africa/Timbuktu"); + expect(Timezone["AfricaTripoli"]).toEqual("Africa/Tripoli"); + expect(Timezone["AfricaTunis"]).toEqual("Africa/Tunis"); + expect(Timezone["AfricaWindhoek"]).toEqual("Africa/Windhoek"); + expect(Timezone["AmericaAdak"]).toEqual("America/Adak"); + expect(Timezone["AmericaAnchorage"]).toEqual("America/Anchorage"); + expect(Timezone["AmericaAnguilla"]).toEqual("America/Anguilla"); + expect(Timezone["AmericaAntigua"]).toEqual("America/Antigua"); + expect(Timezone["AmericaAraguaina"]).toEqual("America/Araguaina"); + expect(Timezone["AmericaArgentinaBuenos_Aires"]).toEqual( + "America/Argentina/Buenos_Aires", + ); + expect(Timezone["AmericaArgentinaCatamarca"]).toEqual( + "America/Argentina/Catamarca", + ); + expect(Timezone["AmericaArgentinaComodRivadavia"]).toEqual( + "America/Argentina/ComodRivadavia", + ); + expect(Timezone["AmericaArgentinaCordoba"]).toEqual( + "America/Argentina/Cordoba", + ); + expect(Timezone["AmericaArgentinaJujuy"]).toEqual( + "America/Argentina/Jujuy", + ); + expect(Timezone["AmericaArgentinaLa_Rioja"]).toEqual( + "America/Argentina/La_Rioja", + ); + expect(Timezone["AmericaArgentinaMendoza"]).toEqual( + "America/Argentina/Mendoza", + ); + expect(Timezone["AmericaArgentinaRio_Gallegos"]).toEqual( + "America/Argentina/Rio_Gallegos", + ); + expect(Timezone["AmericaArgentinaSalta"]).toEqual( + "America/Argentina/Salta", + ); + expect(Timezone["AmericaArgentinaSan_Juan"]).toEqual( + "America/Argentina/San_Juan", + ); + expect(Timezone["AmericaArgentinaSan_Luis"]).toEqual( + "America/Argentina/San_Luis", + ); + expect(Timezone["AmericaArgentinaTucuman"]).toEqual( + "America/Argentina/Tucuman", + ); + expect(Timezone["AmericaArgentinaUshuaia"]).toEqual( + "America/Argentina/Ushuaia", + ); + expect(Timezone["AmericaAruba"]).toEqual("America/Aruba"); + expect(Timezone["AmericaAsuncion"]).toEqual("America/Asuncion"); + expect(Timezone["AmericaAtikokan"]).toEqual("America/Atikokan"); + expect(Timezone["AmericaAtka"]).toEqual("America/Atka"); + expect(Timezone["AmericaBahia"]).toEqual("America/Bahia"); + expect(Timezone["AmericaBahia_Banderas"]).toEqual("America/Bahia_Banderas"); + expect(Timezone["AmericaBarbados"]).toEqual("America/Barbados"); + expect(Timezone["AmericaBelem"]).toEqual("America/Belem"); + expect(Timezone["AmericaBelize"]).toEqual("America/Belize"); + expect(Timezone["AmericaBlancSablon"]).toEqual("America/Blanc-Sablon"); + expect(Timezone["AmericaBoa_Vista"]).toEqual("America/Boa_Vista"); + expect(Timezone["AmericaBogota"]).toEqual("America/Bogota"); + expect(Timezone["AmericaBoise"]).toEqual("America/Boise"); + expect(Timezone["AmericaBuenos_Aires"]).toEqual("America/Buenos_Aires"); + expect(Timezone["AmericaCambridge_Bay"]).toEqual("America/Cambridge_Bay"); + expect(Timezone["AmericaCambridge_Bay"]).toEqual("America/Cambridge_Bay"); + expect(Timezone["AmericaCambridge_Bay"]).toEqual("America/Cambridge_Bay"); + expect(Timezone["AmericaCampo_Grande"]).toEqual("America/Campo_Grande"); + expect(Timezone["AmericaCancun"]).toEqual("America/Cancun"); + expect(Timezone["AmericaCaracas"]).toEqual("America/Caracas"); + expect(Timezone["AmericaCatamarca"]).toEqual("America/Catamarca"); + expect(Timezone["AmericaCayenne"]).toEqual("America/Cayenne"); + expect(Timezone["AmericaCayman"]).toEqual("America/Cayman"); + expect(Timezone["AmericaChicago"]).toEqual("America/Chicago"); + expect(Timezone["AmericaChihuahua"]).toEqual("America/Chihuahua"); + expect(Timezone["AmericaCoral_Harbour"]).toEqual("America/Coral_Harbour"); + expect(Timezone["AmericaCordoba"]).toEqual("America/Cordoba"); + expect(Timezone["AmericaCosta_Rica"]).toEqual("America/Costa_Rica"); + expect(Timezone["AmericaCreston"]).toEqual("America/Creston"); + expect(Timezone["AmericaCuiaba"]).toEqual("America/Cuiaba"); + expect(Timezone["AmericaCuracao"]).toEqual("America/Curacao"); + expect(Timezone["AmericaDanmarkshavn"]).toEqual("America/Danmarkshavn"); + expect(Timezone["AmericaDawson"]).toEqual("America/Dawson"); + expect(Timezone["AmericaDawson_Creek"]).toEqual("America/Dawson_Creek"); + expect(Timezone["AmericaDenver"]).toEqual("America/Denver"); + expect(Timezone["AmericaDetroit"]).toEqual("America/Detroit"); + expect(Timezone["AmericaDominica"]).toEqual("America/Dominica"); + expect(Timezone["AmericaEdmonton"]).toEqual("America/Edmonton"); + expect(Timezone["AmericaEirunepe"]).toEqual("America/Eirunepe"); + expect(Timezone["AmericaEl_Salvador"]).toEqual("America/El_Salvador"); + expect(Timezone["AmericaFort_Nelson"]).toEqual("America/Fort_Nelson"); + expect(Timezone["AmericaFort_Wayne"]).toEqual("America/Fort_Wayne"); + expect(Timezone["AmericaFortaleza"]).toEqual("America/Fortaleza"); + expect(Timezone["AmericaGlace_Bay"]).toEqual("America/Glace_Bay"); + expect(Timezone["AmericaGodthab"]).toEqual("America/Godthab"); + expect(Timezone["AmericaGoose_Bay"]).toEqual("America/Goose_Bay"); + expect(Timezone["AmericaGrand_Turk"]).toEqual("America/Grand_Turk"); + expect(Timezone["AmericaGrenada"]).toEqual("America/Grenada"); + expect(Timezone["AmericaGuadeloupe"]).toEqual("America/Guadeloupe"); + expect(Timezone["AmericaGuatemala"]).toEqual("America/Guatemala"); + expect(Timezone["AmericaGuayaquil"]).toEqual("America/Guayaquil"); + expect(Timezone["AmericaGuyana"]).toEqual("America/Guyana"); + expect(Timezone["AmericaHalifax"]).toEqual("America/Halifax"); + expect(Timezone["AmericaHavana"]).toEqual("America/Havana"); + expect(Timezone["AmericaHermosillo"]).toEqual("America/Hermosillo"); + expect(Timezone["AmericaIndianaIndianapolis"]).toEqual( + "America/Indiana/Indianapolis", + ); + expect(Timezone["AmericaIndianaKnox"]).toEqual("America/Indiana/Knox"); + expect(Timezone["AmericaIndianaMarengo"]).toEqual( + "America/Indiana/Marengo", + ); + expect(Timezone["AmericaIndianaPetersburg"]).toEqual( + "America/Indiana/Petersburg", + ); + expect(Timezone["AmericaIndianaTell_City"]).toEqual( + "America/Indiana/Tell_City", + ); + expect(Timezone["AmericaIndianaVevay"]).toEqual("America/Indiana/Vevay"); + expect(Timezone["AmericaIndianaVincennes"]).toEqual( + "America/Indiana/Vincennes", + ); + expect(Timezone["AmericaIndianaWinamac"]).toEqual( + "America/Indiana/Winamac", + ); + expect(Timezone["AmericaIndianapolis"]).toEqual("America/Indianapolis"); + expect(Timezone["AmericaInuvik"]).toEqual("America/Inuvik"); + expect(Timezone["AmericaIqaluit"]).toEqual("America/Iqaluit"); + expect(Timezone["AmericaJamaica"]).toEqual("America/Jamaica"); + expect(Timezone["AmericaJujuy"]).toEqual("America/Jujuy"); + expect(Timezone["AmericaJuneau"]).toEqual("America/Juneau"); + expect(Timezone["AmericaKentuckyLouisville"]).toEqual( + "America/Kentucky/Louisville", + ); + expect(Timezone["AmericaKentuckyMonticello"]).toEqual( + "America/Kentucky/Monticello", + ); + expect(Timezone["AmericaKnox_IN"]).toEqual("America/Knox_IN"); + expect(Timezone["AmericaKralendijk"]).toEqual("America/Kralendijk"); + expect(Timezone["AmericaLa_Paz"]).toEqual("America/La_Paz"); + expect(Timezone["AmericaLos_Angeles"]).toEqual("America/Los_Angeles"); + expect(Timezone["AmericaLouisville"]).toEqual("America/Louisville"); + expect(Timezone["AmericaLower_Princes"]).toEqual("America/Lower_Princes"); + expect(Timezone["AmericaMaceio"]).toEqual("America/Maceio"); + expect(Timezone["AmericaManagua"]).toEqual("America/Managua"); + expect(Timezone["AmericaManaus"]).toEqual("America/Manaus"); + expect(Timezone["AmericaMarigot"]).toEqual("America/Marigot"); + expect(Timezone["AmericaMartinique"]).toEqual("America/Martinique"); + expect(Timezone["AmericaMatamoros"]).toEqual("America/Matamoros"); + expect(Timezone["AmericaMazatlan"]).toEqual("America/Mazatlan"); + expect(Timezone["AmericaMendoza"]).toEqual("America/Mendoza"); + expect(Timezone["AmericaMenominee"]).toEqual("America/Menominee"); + expect(Timezone["AmericaMerida"]).toEqual("America/Merida"); + expect(Timezone["AmericaMetlakatla"]).toEqual("America/Metlakatla"); + expect(Timezone["AmericaMexico_City"]).toEqual("America/Mexico_City"); + expect(Timezone["AmericaMiquelon"]).toEqual("America/Miquelon"); + expect(Timezone["AmericaMoncton"]).toEqual("America/Moncton"); + expect(Timezone["AmericaMonterrey"]).toEqual("America/Monterrey"); + expect(Timezone["AmericaMontevideo"]).toEqual("America/Montevideo"); + expect(Timezone["AmericaMontreal"]).toEqual("America/Montreal"); + expect(Timezone["AmericaMontserrat"]).toEqual("America/Montserrat"); + expect(Timezone["AmericaNassau"]).toEqual("America/Nassau"); + expect(Timezone["AmericaNew_York"]).toEqual("America/New_York"); + expect(Timezone["AmericaNipigon"]).toEqual("America/Nipigon"); + expect(Timezone["AmericaNome"]).toEqual("America/Nome"); + expect(Timezone["AmericaNoronha"]).toEqual("America/Noronha"); + expect(Timezone["AmericaNorth_DakotaBeulah"]).toEqual( + "America/North_Dakota/Beulah", + ); + expect(Timezone["AmericaNorth_DakotaCenter"]).toEqual( + "America/North_Dakota/Center", + ); + expect(Timezone["AmericaNorth_DakotaNew_Salem"]).toEqual( + "America/North_Dakota/New_Salem", + ); + expect(Timezone["AmericaOjinaga"]).toEqual("America/Ojinaga"); + expect(Timezone["AmericaPanama"]).toEqual("America/Panama"); + expect(Timezone["AmericaPangnirtung"]).toEqual("America/Pangnirtung"); + expect(Timezone["AmericaParamaribo"]).toEqual("America/Paramaribo"); + expect(Timezone["AmericaPhoenix"]).toEqual("America/Phoenix"); + expect(Timezone["AmericaPortAuPrince"]).toEqual("America/Port-au-Prince"); + expect(Timezone["AmericaPort_of_Spain"]).toEqual("America/Port_of_Spain"); + expect(Timezone["AmericaPorto_Acre"]).toEqual("America/Porto_Acre"); + expect(Timezone["AmericaPorto_Velho"]).toEqual("America/Porto_Velho"); + expect(Timezone["AmericaPuerto_Rico"]).toEqual("America/Puerto_Rico"); + expect(Timezone["AmericaPunta_Arenas"]).toEqual("America/Punta_Arenas"); + expect(Timezone["AmericaRainy_River"]).toEqual("America/Rainy_River"); + expect(Timezone["AmericaRankin_Inlet"]).toEqual("America/Rankin_Inlet"); + expect(Timezone["AmericaRecife"]).toEqual("America/Recife"); + expect(Timezone["AmericaRegina"]).toEqual("America/Regina"); + expect(Timezone["AmericaResolute"]).toEqual("America/Resolute"); + expect(Timezone["AmericaRio_Branco"]).toEqual("America/Rio_Branco"); + expect(Timezone["AmericaRosario"]).toEqual("America/Rosario"); + expect(Timezone["AmericaSanta_Isabel"]).toEqual("America/Santa_Isabel"); + expect(Timezone["AmericaSantarem"]).toEqual("America/Santarem"); + expect(Timezone["AmericaSantiago"]).toEqual("America/Santiago"); + expect(Timezone["AmericaSanto_Domingo"]).toEqual("America/Santo_Domingo"); + expect(Timezone["AmericaSao_Paulo"]).toEqual("America/Sao_Paulo"); + expect(Timezone["AmericaScoresbysund"]).toEqual("America/Scoresbysund"); + expect(Timezone["AmericaShiprock"]).toEqual("America/Shiprock"); + expect(Timezone["AmericaSitka"]).toEqual("America/Sitka"); + expect(Timezone["AmericaSt_Barthelemy"]).toEqual("America/St_Barthelemy"); + expect(Timezone["AmericaSt_Johns"]).toEqual("America/St_Johns"); + expect(Timezone["AmericaSt_Kitts"]).toEqual("America/St_Kitts"); + expect(Timezone["AmericaSt_Lucia"]).toEqual("America/St_Lucia"); + expect(Timezone["AmericaSt_Thomas"]).toEqual("America/St_Thomas"); + expect(Timezone["AmericaSt_Vincent"]).toEqual("America/St_Vincent"); + expect(Timezone["AmericaSwift_Current"]).toEqual("America/Swift_Current"); + expect(Timezone["AmericaTegucigalpa"]).toEqual("America/Tegucigalpa"); + expect(Timezone["AmericaThule"]).toEqual("America/Thule"); + expect(Timezone["AmericaThunder_Bay"]).toEqual("America/Thunder_Bay"); + expect(Timezone["AmericaTijuana"]).toEqual("America/Tijuana"); + expect(Timezone["AmericaToronto"]).toEqual("America/Toronto"); + expect(Timezone["AmericaTortola"]).toEqual("America/Tortola"); + expect(Timezone["AmericaVancouver"]).toEqual("America/Vancouver"); + expect(Timezone["AmericaVirgin"]).toEqual("America/Virgin"); + expect(Timezone["AmericaWhitehorse"]).toEqual("America/Whitehorse"); + expect(Timezone["AmericaWinnipeg"]).toEqual("America/Winnipeg"); + expect(Timezone["AmericaYakutat"]).toEqual("America/Yakutat"); + expect(Timezone["AmericaYellowknife"]).toEqual("America/Yellowknife"); + expect(Timezone["AntarcticaCasey"]).toEqual("Antarctica/Casey"); + expect(Timezone["AntarcticaDavis"]).toEqual("Antarctica/Davis"); + expect(Timezone["AntarcticaDumontDUrville"]).toEqual( + "Antarctica/DumontDUrville", + ); + expect(Timezone["AntarcticaMacquarie"]).toEqual("Antarctica/Macquarie"); + expect(Timezone["AntarcticaMawson"]).toEqual("Antarctica/Mawson"); + expect(Timezone["AntarcticaMcMurdo"]).toEqual("Antarctica/McMurdo"); + expect(Timezone["AntarcticaPalmer"]).toEqual("Antarctica/Palmer"); + expect(Timezone["AntarcticaRothera"]).toEqual("Antarctica/Rothera"); + expect(Timezone["AntarcticaSouth_Pole"]).toEqual("Antarctica/South_Pole"); + expect(Timezone["AntarcticaSyowa"]).toEqual("Antarctica/Syowa"); + expect(Timezone["AntarcticaTroll"]).toEqual("Antarctica/Troll"); + expect(Timezone["AntarcticaVostok"]).toEqual("Antarctica/Vostok"); + expect(Timezone["ArcticLongyearbyen"]).toEqual("Arctic/Longyearbyen"); + expect(Timezone["AsiaAden"]).toEqual("Asia/Aden"); + expect(Timezone["AsiaAlmaty"]).toEqual("Asia/Almaty"); + expect(Timezone["AsiaAmman"]).toEqual("Asia/Amman"); + expect(Timezone["AsiaAnadyr"]).toEqual("Asia/Anadyr"); + expect(Timezone["AsiaAqtau"]).toEqual("Asia/Aqtau"); + expect(Timezone["AsiaAqtobe"]).toEqual("Asia/Aqtobe"); + expect(Timezone["AsiaAshgabat"]).toEqual("Asia/Ashgabat"); + expect(Timezone["AsiaAshkhabad"]).toEqual("Asia/Ashkhabad"); + expect(Timezone["AsiaAtyrau"]).toEqual("Asia/Atyrau"); + expect(Timezone["AsiaBaghdad"]).toEqual("Asia/Baghdad"); + expect(Timezone["AsiaBahrain"]).toEqual("Asia/Bahrain"); + expect(Timezone["AsiaBaku"]).toEqual("Asia/Baku"); + expect(Timezone["AsiaBangkok"]).toEqual("Asia/Bangkok"); + expect(Timezone["AsiaBarnaul"]).toEqual("Asia/Barnaul"); + expect(Timezone["AsiaBeirut"]).toEqual("Asia/Beirut"); + expect(Timezone["AsiaBishkek"]).toEqual("Asia/Bishkek"); + expect(Timezone["AsiaBrunei"]).toEqual("Asia/Brunei"); + expect(Timezone["AsiaCalcutta"]).toEqual("Asia/Calcutta"); + expect(Timezone["AsiaChita"]).toEqual("Asia/Chita"); + expect(Timezone["AsiaChoibalsan"]).toEqual("Asia/Choibalsan"); + expect(Timezone["AsiaChongqing"]).toEqual("Asia/Chongqing"); + expect(Timezone["AsiaChungking"]).toEqual("Asia/Chungking"); + expect(Timezone["AsiaColombo"]).toEqual("Asia/Colombo"); + expect(Timezone["AsiaDacca"]).toEqual("Asia/Dacca"); + expect(Timezone["AsiaDamascus"]).toEqual("Asia/Damascus"); + expect(Timezone["AsiaDhaka"]).toEqual("Asia/Dhaka"); + expect(Timezone["AsiaDili"]).toEqual("Asia/Dili"); + expect(Timezone["AsiaDubai"]).toEqual("Asia/Dubai"); + expect(Timezone["AsiaDushanbe"]).toEqual("Asia/Dushanbe"); + expect(Timezone["AsiaFamagusta"]).toEqual("Asia/Famagusta"); + expect(Timezone["AsiaGaza"]).toEqual("Asia/Gaza"); + expect(Timezone["AsiaHarbin"]).toEqual("Asia/Harbin"); + expect(Timezone["AsiaHebron"]).toEqual("Asia/Hebron"); + expect(Timezone["AsiaHo_Chi_Minh"]).toEqual("Asia/Ho_Chi_Minh"); + expect(Timezone["AsiaHong_Kong"]).toEqual("Asia/Hong_Kong"); + expect(Timezone["AsiaHovd"]).toEqual("Asia/Hovd"); + expect(Timezone["AsiaIrkutsk"]).toEqual("Asia/Irkutsk"); + expect(Timezone["AsiaIstanbul"]).toEqual("Asia/Istanbul"); + expect(Timezone["AsiaJakarta"]).toEqual("Asia/Jakarta"); + expect(Timezone["AsiaJayapura"]).toEqual("Asia/Jayapura"); + expect(Timezone["AsiaJerusalem"]).toEqual("Asia/Jerusalem"); + expect(Timezone["AsiaKabul"]).toEqual("Asia/Kabul"); + expect(Timezone["AsiaKamchatka"]).toEqual("Asia/Kamchatka"); + expect(Timezone["AsiaKarachi"]).toEqual("Asia/Karachi"); + expect(Timezone["AsiaKashgar"]).toEqual("Asia/Kashgar"); + expect(Timezone["AsiaKathmandu"]).toEqual("Asia/Kathmandu"); + expect(Timezone["AsiaKatmandu"]).toEqual("Asia/Katmandu"); + expect(Timezone["AsiaKhandyga"]).toEqual("Asia/Khandyga"); + expect(Timezone["AsiaKolkata"]).toEqual("Asia/Kolkata"); + expect(Timezone["AsiaKrasnoyarsk"]).toEqual("Asia/Krasnoyarsk"); + expect(Timezone["AsiaKuala_Lumpur"]).toEqual("Asia/Kuala_Lumpur"); + expect(Timezone["AsiaKuching"]).toEqual("Asia/Kuching"); + expect(Timezone["AsiaKuwait"]).toEqual("Asia/Kuwait"); + expect(Timezone["AsiaMacao"]).toEqual("Asia/Macao"); + expect(Timezone["AsiaMacau"]).toEqual("Asia/Macau"); + expect(Timezone["AsiaMagadan"]).toEqual("Asia/Magadan"); + expect(Timezone["AsiaMakassar"]).toEqual("Asia/Makassar"); + expect(Timezone["AsiaManila"]).toEqual("Asia/Manila"); + expect(Timezone["AsiaMuscat"]).toEqual("Asia/Muscat"); + expect(Timezone["AsiaNicosia"]).toEqual("Asia/Nicosia"); + expect(Timezone["AsiaNovokuznetsk"]).toEqual("Asia/Novokuznetsk"); + expect(Timezone["AsiaNovosibirsk"]).toEqual("Asia/Novosibirsk"); + expect(Timezone["AsiaOmsk"]).toEqual("Asia/Omsk"); + expect(Timezone["AsiaOral"]).toEqual("Asia/Oral"); + expect(Timezone["AsiaPhnom_Penh"]).toEqual("Asia/Phnom_Penh"); + expect(Timezone["AsiaPontianak"]).toEqual("Asia/Pontianak"); + expect(Timezone["AsiaPyongyang"]).toEqual("Asia/Pyongyang"); + expect(Timezone["AsiaQatar"]).toEqual("Asia/Qatar"); + expect(Timezone["AsiaQyzylorda"]).toEqual("Asia/Qyzylorda"); + expect(Timezone["AsiaRangoon"]).toEqual("Asia/Rangoon"); + expect(Timezone["AsiaRiyadh"]).toEqual("Asia/Riyadh"); + expect(Timezone["AsiaSaigon"]).toEqual("Asia/Saigon"); + expect(Timezone["AsiaSakhalin"]).toEqual("Asia/Sakhalin"); + expect(Timezone["AsiaSeoul"]).toEqual("Asia/Seoul"); + expect(Timezone["AsiaShanghai"]).toEqual("Asia/Shanghai"); + expect(Timezone["AsiaSingapore"]).toEqual("Asia/Singapore"); + expect(Timezone["AsiaSrednekolymsk"]).toEqual("Asia/Srednekolymsk"); + expect(Timezone["AsiaTaipei"]).toEqual("Asia/Taipei"); + expect(Timezone["AsiaTashkent"]).toEqual("Asia/Tashkent"); + expect(Timezone["AsiaTbilisi"]).toEqual("Asia/Tbilisi"); + expect(Timezone["AsiaTehran"]).toEqual("Asia/Tehran"); + expect(Timezone["AsiaTel_Aviv"]).toEqual("Asia/Tel_Aviv"); + expect(Timezone["AsiaThimbu"]).toEqual("Asia/Thimbu"); + expect(Timezone["AsiaThimphu"]).toEqual("Asia/Thimphu"); + expect(Timezone["AsiaTokyo"]).toEqual("Asia/Tokyo"); + expect(Timezone["AsiaTomsk"]).toEqual("Asia/Tomsk"); + expect(Timezone["AsiaUjung_Pandang"]).toEqual("Asia/Ujung_Pandang"); + expect(Timezone["AsiaUlaanbaatar"]).toEqual("Asia/Ulaanbaatar"); + expect(Timezone["AsiaUlan_Bator"]).toEqual("Asia/Ulan_Bator"); + expect(Timezone["AsiaUrumqi"]).toEqual("Asia/Urumqi"); + expect(Timezone["AsiaUstNera"]).toEqual("Asia/Ust-Nera"); + expect(Timezone["AsiaVientiane"]).toEqual("Asia/Vientiane"); + expect(Timezone["AsiaVladivostok"]).toEqual("Asia/Vladivostok"); + expect(Timezone["AsiaYakutsk"]).toEqual("Asia/Yakutsk"); + expect(Timezone["AsiaYangon"]).toEqual("Asia/Yangon"); + expect(Timezone["AsiaYekaterinburg"]).toEqual("Asia/Yekaterinburg"); + expect(Timezone["AsiaYekaterinburg"]).toEqual("Asia/Yekaterinburg"); + expect(Timezone["AsiaYekaterinburg"]).toEqual("Asia/Yekaterinburg"); + expect(Timezone["AsiaYekaterinburg"]).toEqual("Asia/Yekaterinburg"); + expect(Timezone["AsiaYekaterinburg"]).toEqual("Asia/Yekaterinburg"); + expect(Timezone["AsiaYekaterinburg"]).toEqual("Asia/Yekaterinburg"); + expect(Timezone["AsiaYekaterinburg"]).toEqual("Asia/Yekaterinburg"); + expect(Timezone["AsiaYerevan"]).toEqual("Asia/Yerevan"); + expect(Timezone["AtlanticAzores"]).toEqual("Atlantic/Azores"); + expect(Timezone["AtlanticBermuda"]).toEqual("Atlantic/Bermuda"); + expect(Timezone["AtlanticCanary"]).toEqual("Atlantic/Canary"); + expect(Timezone["AtlanticCape_Verde"]).toEqual("Atlantic/Cape_Verde"); + expect(Timezone["AtlanticFaeroe"]).toEqual("Atlantic/Faeroe"); + expect(Timezone["AtlanticFaroe"]).toEqual("Atlantic/Faroe"); + expect(Timezone["AtlanticJan_Mayen"]).toEqual("Atlantic/Jan_Mayen"); + expect(Timezone["AtlanticMadeira"]).toEqual("Atlantic/Madeira"); + expect(Timezone["AtlanticReykjavik"]).toEqual("Atlantic/Reykjavik"); + expect(Timezone["AtlanticSouth_Georgia"]).toEqual("Atlantic/South_Georgia"); + expect(Timezone["AtlanticSt_Helena"]).toEqual("Atlantic/St_Helena"); + expect(Timezone["AtlanticStanley"]).toEqual("Atlantic/Stanley"); + expect(Timezone["AustraliaACT"]).toEqual("Australia/ACT"); + expect(Timezone["AustraliaAdelaide"]).toEqual("Australia/Adelaide"); + expect(Timezone["AustraliaBrisbane"]).toEqual("Australia/Brisbane"); + expect(Timezone["AustraliaBroken_Hill"]).toEqual("Australia/Broken_Hill"); + expect(Timezone["AustraliaCanberra"]).toEqual("Australia/Canberra"); + expect(Timezone["AustraliaCurrie"]).toEqual("Australia/Currie"); + expect(Timezone["AustraliaDarwin"]).toEqual("Australia/Darwin"); + expect(Timezone["AustraliaEucla"]).toEqual("Australia/Eucla"); + expect(Timezone["AustraliaHobart"]).toEqual("Australia/Hobart"); + expect(Timezone["AustraliaLHI"]).toEqual("Australia/LHI"); + expect(Timezone["AustraliaLindeman"]).toEqual("Australia/Lindeman"); + expect(Timezone["AustraliaLord_Howe"]).toEqual("Australia/Lord_Howe"); + expect(Timezone["AustraliaMelbourne"]).toEqual("Australia/Melbourne"); + expect(Timezone["AustraliaNSW"]).toEqual("Australia/NSW"); + expect(Timezone["AustraliaNorth"]).toEqual("Australia/North"); + expect(Timezone["AustraliaPerth"]).toEqual("Australia/Perth"); + expect(Timezone["AustraliaQueensland"]).toEqual("Australia/Queensland"); + expect(Timezone["AustraliaSouth"]).toEqual("Australia/South"); + expect(Timezone["AustraliaSydney"]).toEqual("Australia/Sydney"); + expect(Timezone["AustraliaTasmania"]).toEqual("Australia/Tasmania"); + expect(Timezone["AustraliaVictoria"]).toEqual("Australia/Victoria"); + expect(Timezone["AustraliaWest"]).toEqual("Australia/West"); + expect(Timezone["AustraliaYancowinna"]).toEqual("Australia/Yancowinna"); + expect(Timezone["BrazilAcre"]).toEqual("Brazil/Acre"); + expect(Timezone["BrazilDeNoronha"]).toEqual("Brazil/DeNoronha"); + expect(Timezone["BrazilEast"]).toEqual("Brazil/East"); + expect(Timezone["BrazilWest"]).toEqual("Brazil/West"); + expect(Timezone["CET"]).toEqual("CET"); + expect(Timezone["CST6CDT"]).toEqual("CST6CDT"); + expect(Timezone["CanadaAtlantic"]).toEqual("Canada/Atlantic"); + expect(Timezone["CanadaCentral"]).toEqual("Canada/Central"); + expect(Timezone["CanadaEastern"]).toEqual("Canada/Eastern"); + expect(Timezone["CanadaMountain"]).toEqual("Canada/Mountain"); + expect(Timezone["CanadaNewfoundland"]).toEqual("Canada/Newfoundland"); + expect(Timezone["CanadaPacific"]).toEqual("Canada/Pacific"); + expect(Timezone["CanadaSaskatchewan"]).toEqual("Canada/Saskatchewan"); + expect(Timezone["CanadaYukon"]).toEqual("Canada/Yukon"); + expect(Timezone["ChileContinental"]).toEqual("Chile/Continental"); + expect(Timezone["ChileEasterIsland"]).toEqual("Chile/EasterIsland"); + expect(Timezone["Cuba"]).toEqual("Cuba"); + expect(Timezone["EET"]).toEqual("EET"); + expect(Timezone["EST"]).toEqual("EST"); + expect(Timezone["EST5EDT"]).toEqual("EST5EDT"); + expect(Timezone["Egypt"]).toEqual("Egypt"); + expect(Timezone["Eire"]).toEqual("Eire"); + expect(Timezone["EtcGMT"]).toEqual("Etc/GMT"); + expect(Timezone["EtcGMTPositive0"]).toEqual("Etc/GMT+0"); + expect(Timezone["EtcGMTPositive1"]).toEqual("Etc/GMT+1"); + expect(Timezone["EtcGMTPositive10"]).toEqual("Etc/GMT+10"); + expect(Timezone["EtcGMTPositive11"]).toEqual("Etc/GMT+11"); + expect(Timezone["EtcGMTPositive12"]).toEqual("Etc/GMT+12"); + expect(Timezone["EtcGMTPositive2"]).toEqual("Etc/GMT+2"); + expect(Timezone["EtcGMTPositive3"]).toEqual("Etc/GMT+3"); + expect(Timezone["EtcGMTPositive4"]).toEqual("Etc/GMT+4"); + expect(Timezone["EtcGMTPositive5"]).toEqual("Etc/GMT+5"); + expect(Timezone["EtcGMTPositive6"]).toEqual("Etc/GMT+6"); + expect(Timezone["EtcGMTPositive7"]).toEqual("Etc/GMT+7"); + expect(Timezone["EtcGMTPositive8"]).toEqual("Etc/GMT+8"); + expect(Timezone["EtcGMTPositive9"]).toEqual("Etc/GMT+9"); + expect(Timezone["EtcGMTNegative0"]).toEqual("Etc/GMT-0"); + expect(Timezone["EtcGMTNegative1"]).toEqual("Etc/GMT-1"); + expect(Timezone["EtcGMTNegative10"]).toEqual("Etc/GMT-10"); + expect(Timezone["EtcGMTNegative11"]).toEqual("Etc/GMT-11"); + expect(Timezone["EtcGMTNegative12"]).toEqual("Etc/GMT-12"); + expect(Timezone["EtcGMTNegative13"]).toEqual("Etc/GMT-13"); + expect(Timezone["EtcGMTNegative14"]).toEqual("Etc/GMT-14"); + expect(Timezone["EtcGMTNegative2"]).toEqual("Etc/GMT-2"); + expect(Timezone["EtcGMTNegative3"]).toEqual("Etc/GMT-3"); + expect(Timezone["EtcGMTNegative4"]).toEqual("Etc/GMT-4"); + expect(Timezone["EtcGMTNegative5"]).toEqual("Etc/GMT-5"); + expect(Timezone["EtcGMTNegative6"]).toEqual("Etc/GMT-6"); + expect(Timezone["EtcGMTNegative7"]).toEqual("Etc/GMT-7"); + expect(Timezone["EtcGMTNegative8"]).toEqual("Etc/GMT-8"); + expect(Timezone["EtcGMTNegative9"]).toEqual("Etc/GMT-9"); + expect(Timezone["EtcGMT0"]).toEqual("Etc/GMT0"); + expect(Timezone["EtcGreenwich"]).toEqual("Etc/Greenwich"); + expect(Timezone["EtcUCT"]).toEqual("Etc/UCT"); + expect(Timezone["EtcUTC"]).toEqual("Etc/UTC"); + expect(Timezone["EtcUniversal"]).toEqual("Etc/Universal"); + expect(Timezone["EtcZulu"]).toEqual("Etc/Zulu"); + expect(Timezone["EuropeAmsterdam"]).toEqual("Europe/Amsterdam"); + expect(Timezone["EuropeAndorra"]).toEqual("Europe/Andorra"); + expect(Timezone["EuropeAstrakhan"]).toEqual("Europe/Astrakhan"); + expect(Timezone["EuropeAthens"]).toEqual("Europe/Athens"); + expect(Timezone["EuropeBelfast"]).toEqual("Europe/Belfast"); + expect(Timezone["EuropeBelgrade"]).toEqual("Europe/Belgrade"); + expect(Timezone["EuropeBerlin"]).toEqual("Europe/Berlin"); + expect(Timezone["EuropeBratislava"]).toEqual("Europe/Bratislava"); + expect(Timezone["EuropeBrussels"]).toEqual("Europe/Brussels"); + expect(Timezone["EuropeBucharest"]).toEqual("Europe/Bucharest"); + expect(Timezone["EuropeBudapest"]).toEqual("Europe/Budapest"); + expect(Timezone["EuropeBusingen"]).toEqual("Europe/Busingen"); + expect(Timezone["EuropeChisinau"]).toEqual("Europe/Chisinau"); + expect(Timezone["EuropeCopenhagen"]).toEqual("Europe/Copenhagen"); + expect(Timezone["EuropeDublin"]).toEqual("Europe/Dublin"); + expect(Timezone["EuropeGibraltar"]).toEqual("Europe/Gibraltar"); + expect(Timezone["EuropeGuernsey"]).toEqual("Europe/Guernsey"); + expect(Timezone["EuropeHelsinki"]).toEqual("Europe/Helsinki"); + expect(Timezone["EuropeIsle_of_Man"]).toEqual("Europe/Isle_of_Man"); + expect(Timezone["EuropeIstanbul"]).toEqual("Europe/Istanbul"); + expect(Timezone["EuropeJersey"]).toEqual("Europe/Jersey"); + expect(Timezone["EuropeKaliningrad"]).toEqual("Europe/Kaliningrad"); + expect(Timezone["EuropeKiev"]).toEqual("Europe/Kiev"); + expect(Timezone["EuropeKirov"]).toEqual("Europe/Kirov"); + expect(Timezone["EuropeLisbon"]).toEqual("Europe/Lisbon"); + expect(Timezone["EuropeLjubljana"]).toEqual("Europe/Ljubljana"); + expect(Timezone["EuropeLondon"]).toEqual("Europe/London"); + expect(Timezone["EuropeLuxembourg"]).toEqual("Europe/Luxembourg"); + expect(Timezone["EuropeMadrid"]).toEqual("Europe/Madrid"); + expect(Timezone["EuropeMalta"]).toEqual("Europe/Malta"); + expect(Timezone["EuropeMariehamn"]).toEqual("Europe/Mariehamn"); + expect(Timezone["EuropeMinsk"]).toEqual("Europe/Minsk"); + expect(Timezone["EuropeMonaco"]).toEqual("Europe/Monaco"); + expect(Timezone["EuropeMoscow"]).toEqual("Europe/Moscow"); + expect(Timezone["EuropeNicosia"]).toEqual("Europe/Nicosia"); + expect(Timezone["EuropeOslo"]).toEqual("Europe/Oslo"); + expect(Timezone["EuropeParis"]).toEqual("Europe/Paris"); + expect(Timezone["EuropePodgorica"]).toEqual("Europe/Podgorica"); + expect(Timezone["EuropePrague"]).toEqual("Europe/Prague"); + expect(Timezone["EuropeRiga"]).toEqual("Europe/Riga"); + expect(Timezone["EuropeRome"]).toEqual("Europe/Rome"); + expect(Timezone["EuropeSamara"]).toEqual("Europe/Samara"); + expect(Timezone["EuropeSan_Marino"]).toEqual("Europe/San_Marino"); + expect(Timezone["EuropeSarajevo"]).toEqual("Europe/Sarajevo"); + expect(Timezone["EuropeSaratov"]).toEqual("Europe/Saratov"); + expect(Timezone["EuropeSimferopol"]).toEqual("Europe/Simferopol"); + expect(Timezone["EuropeSkopje"]).toEqual("Europe/Skopje"); + expect(Timezone["EuropeSofia"]).toEqual("Europe/Sofia"); + expect(Timezone["EuropeStockholm"]).toEqual("Europe/Stockholm"); + expect(Timezone["EuropeTallinn"]).toEqual("Europe/Tallinn"); + expect(Timezone["EuropeTirane"]).toEqual("Europe/Tirane"); + expect(Timezone["EuropeTiraspol"]).toEqual("Europe/Tiraspol"); + expect(Timezone["EuropeUlyanovsk"]).toEqual("Europe/Ulyanovsk"); + expect(Timezone["EuropeUzhgorod"]).toEqual("Europe/Uzhgorod"); + expect(Timezone["EuropeVaduz"]).toEqual("Europe/Vaduz"); + expect(Timezone["EuropeVatican"]).toEqual("Europe/Vatican"); + expect(Timezone["EuropeVienna"]).toEqual("Europe/Vienna"); + expect(Timezone["EuropeVilnius"]).toEqual("Europe/Vilnius"); + expect(Timezone["EuropeVolgograd"]).toEqual("Europe/Volgograd"); + expect(Timezone["EuropeWarsaw"]).toEqual("Europe/Warsaw"); + expect(Timezone["EuropeZagreb"]).toEqual("Europe/Zagreb"); + expect(Timezone["EuropeZaporozhye"]).toEqual("Europe/Zaporozhye"); + expect(Timezone["EuropeZurich"]).toEqual("Europe/Zurich"); + expect(Timezone["GB"]).toEqual("GB"); + expect(Timezone["GBEire"]).toEqual("GB-Eire"); + expect(Timezone["HST"]).toEqual("HST"); + expect(Timezone["Hongkong"]).toEqual("Hongkong"); + expect(Timezone["Iceland"]).toEqual("Iceland"); + expect(Timezone["IndianAntananarivo"]).toEqual("Indian/Antananarivo"); + expect(Timezone["IndianChagos"]).toEqual("Indian/Chagos"); + expect(Timezone["IndianChristmas"]).toEqual("Indian/Christmas"); + expect(Timezone["IndianCocos"]).toEqual("Indian/Cocos"); + expect(Timezone["IndianComoro"]).toEqual("Indian/Comoro"); + expect(Timezone["IndianKerguelen"]).toEqual("Indian/Kerguelen"); + expect(Timezone["IndianMahe"]).toEqual("Indian/Mahe"); + expect(Timezone["IndianMaldives"]).toEqual("Indian/Maldives"); + expect(Timezone["IndianMauritius"]).toEqual("Indian/Mauritius"); + expect(Timezone["IndianMayotte"]).toEqual("Indian/Mayotte"); + expect(Timezone["IndianReunion"]).toEqual("Indian/Reunion"); + expect(Timezone["Iran"]).toEqual("Iran"); + expect(Timezone["Israel"]).toEqual("Israel"); + expect(Timezone["Jamaica"]).toEqual("Jamaica"); + expect(Timezone["Japan"]).toEqual("Japan"); + expect(Timezone["Kwajalein"]).toEqual("Kwajalein"); + expect(Timezone["Libya"]).toEqual("Libya"); + expect(Timezone["MET"]).toEqual("MET"); + expect(Timezone["MST"]).toEqual("MST"); + expect(Timezone["MST7MDT"]).toEqual("MST7MDT"); + expect(Timezone["MexicoBajaNorte"]).toEqual("Mexico/BajaNorte"); + expect(Timezone["MexicoBajaSur"]).toEqual("Mexico/BajaSur"); + expect(Timezone["MexicoGeneral"]).toEqual("Mexico/General"); + expect(Timezone["NZ"]).toEqual("NZ"); + expect(Timezone["NZCHAT"]).toEqual("NZ-CHAT"); + expect(Timezone["Navajo"]).toEqual("Navajo"); + expect(Timezone["PRC"]).toEqual("PRC"); + expect(Timezone["PST8PDT"]).toEqual("PST8PDT"); + expect(Timezone["PacificApia"]).toEqual("Pacific/Apia"); + expect(Timezone["PacificAuckland"]).toEqual("Pacific/Auckland"); + expect(Timezone["PacificBougainville"]).toEqual("Pacific/Bougainville"); + expect(Timezone["PacificChatham"]).toEqual("Pacific/Chatham"); + expect(Timezone["PacificChatham"]).toEqual("Pacific/Chatham"); + expect(Timezone["PacificEaster"]).toEqual("Pacific/Easter"); + expect(Timezone["PacificEfate"]).toEqual("Pacific/Efate"); + expect(Timezone["PacificEnderbury"]).toEqual("Pacific/Enderbury"); + expect(Timezone["PacificFakaofo"]).toEqual("Pacific/Fakaofo"); + expect(Timezone["PacificFiji"]).toEqual("Pacific/Fiji"); + expect(Timezone["PacificFunafuti"]).toEqual("Pacific/Funafuti"); + expect(Timezone["PacificGalapagos"]).toEqual("Pacific/Galapagos"); + expect(Timezone["PacificGambier"]).toEqual("Pacific/Gambier"); + expect(Timezone["PacificGuadalcanal"]).toEqual("Pacific/Guadalcanal"); + expect(Timezone["PacificGuam"]).toEqual("Pacific/Guam"); + expect(Timezone["PacificHonolulu"]).toEqual("Pacific/Honolulu"); + expect(Timezone["PacificJohnston"]).toEqual("Pacific/Johnston"); + expect(Timezone["PacificKiritimati"]).toEqual("Pacific/Kiritimati"); + expect(Timezone["PacificKosrae"]).toEqual("Pacific/Kosrae"); + expect(Timezone["PacificKwajalein"]).toEqual("Pacific/Kwajalein"); + expect(Timezone["PacificMajuro"]).toEqual("Pacific/Majuro"); + expect(Timezone["PacificMarquesas"]).toEqual("Pacific/Marquesas"); + expect(Timezone["PacificMidway"]).toEqual("Pacific/Midway"); + expect(Timezone["PacificNauru"]).toEqual("Pacific/Nauru"); + expect(Timezone["PacificNiue"]).toEqual("Pacific/Niue"); + expect(Timezone["PacificNorfolk"]).toEqual("Pacific/Norfolk"); + expect(Timezone["PacificNoumea"]).toEqual("Pacific/Noumea"); + expect(Timezone["PacificPago_Pago"]).toEqual("Pacific/Pago_Pago"); + expect(Timezone["PacificPalau"]).toEqual("Pacific/Palau"); + expect(Timezone["PacificPitcairn"]).toEqual("Pacific/Pitcairn"); + expect(Timezone["PacificPohnpei"]).toEqual("Pacific/Pohnpei"); + expect(Timezone["PacificPonape"]).toEqual("Pacific/Ponape"); + expect(Timezone["PacificPort_Moresby"]).toEqual("Pacific/Port_Moresby"); + expect(Timezone["PacificRarotonga"]).toEqual("Pacific/Rarotonga"); + expect(Timezone["PacificSaipan"]).toEqual("Pacific/Saipan"); + expect(Timezone["PacificSamoa"]).toEqual("Pacific/Samoa"); + expect(Timezone["PacificTahiti"]).toEqual("Pacific/Tahiti"); + expect(Timezone["PacificTarawa"]).toEqual("Pacific/Tarawa"); + expect(Timezone["PacificTongatapu"]).toEqual("Pacific/Tongatapu"); + expect(Timezone["PacificTruk"]).toEqual("Pacific/Truk"); + expect(Timezone["PacificWake"]).toEqual("Pacific/Wake"); + expect(Timezone["PacificWallis"]).toEqual("Pacific/Wallis"); + expect(Timezone["PacificYap"]).toEqual("Pacific/Yap"); + expect(Timezone["Poland"]).toEqual("Poland"); + expect(Timezone["Portugal"]).toEqual("Portugal"); + expect(Timezone["ROC"]).toEqual("ROC"); + expect(Timezone["ROK"]).toEqual("ROK"); + expect(Timezone["Singapore"]).toEqual("Singapore"); + expect(Timezone["Turkey"]).toEqual("Turkey"); + expect(Timezone["UCT"]).toEqual("UCT"); + expect(Timezone["USAlaska"]).toEqual("US/Alaska"); + expect(Timezone["USAleutian"]).toEqual("US/Aleutian"); + expect(Timezone["USArizona"]).toEqual("US/Arizona"); + expect(Timezone["USCentral"]).toEqual("US/Central"); + expect(Timezone["USEastIndiana"]).toEqual("US/East-Indiana"); + expect(Timezone["USEastern"]).toEqual("US/Eastern"); + expect(Timezone["USHawaii"]).toEqual("US/Hawaii"); + expect(Timezone["USIndianaStarke"]).toEqual("US/Indiana-Starke"); + expect(Timezone["USMichigan"]).toEqual("US/Michigan"); + expect(Timezone["USMountain"]).toEqual("US/Mountain"); + expect(Timezone["USPacific"]).toEqual("US/Pacific"); + expect(Timezone["USSamoa"]).toEqual("US/Samoa"); + expect(Timezone["UTC"]).toEqual("UTC"); + expect(Timezone["Universal"]).toEqual("Universal"); + expect(Timezone["WSU"]).toEqual("W-SU"); + expect(Timezone["WET"]).toEqual("WET"); + expect(Timezone["Zulu"]).toEqual("Zulu"); + }); }); diff --git a/Common/Tests/Types/Typeof.test.ts b/Common/Tests/Types/Typeof.test.ts index 7ef61068e0..9c5532b83d 100644 --- a/Common/Tests/Types/Typeof.test.ts +++ b/Common/Tests/Types/Typeof.test.ts @@ -1,19 +1,19 @@ -import Typeof from '../../Types/Typeof'; +import Typeof from "../../Types/Typeof"; -describe('Typeof', () => { - test('String should be string', () => { - expect(Typeof.String).toBe('string'); - }); +describe("Typeof", () => { + test("String should be string", () => { + expect(Typeof.String).toBe("string"); + }); - test('Boolean should be boolean', () => { - expect(Typeof.Boolean).toBe('boolean'); - }); + test("Boolean should be boolean", () => { + expect(Typeof.Boolean).toBe("boolean"); + }); - test('Number should be number', () => { - expect(Typeof.Number).toBe('number'); - }); + test("Number should be number", () => { + expect(Typeof.Number).toBe("number"); + }); - test('Object should be object', () => { - expect(Typeof.Object).toBe('object'); - }); + test("Object should be object", () => { + expect(Typeof.Object).toBe("object"); + }); }); diff --git a/Common/Tests/Types/UserType.test.ts b/Common/Tests/Types/UserType.test.ts index 84236a810e..6d35c9a75a 100644 --- a/Common/Tests/Types/UserType.test.ts +++ b/Common/Tests/Types/UserType.test.ts @@ -1,16 +1,16 @@ -import UserType from '../../Types/UserType'; +import UserType from "../../Types/UserType"; -describe('enum UserType', () => { - test('UserType.API should be API', () => { - expect(UserType.API).toEqual('API'); - }); - test('UserType.User should be User', () => { - expect(UserType.User).toEqual('User'); - }); - test('UserType.MasterAdmin should be MasterAdmin', () => { - expect(UserType.MasterAdmin).toEqual('MasterAdmin'); - }); - test('UserType.Public should be Public', () => { - expect(UserType.Public).toEqual('Public'); - }); +describe("enum UserType", () => { + test("UserType.API should be API", () => { + expect(UserType.API).toEqual("API"); + }); + test("UserType.User should be User", () => { + expect(UserType.User).toEqual("User"); + }); + test("UserType.MasterAdmin should be MasterAdmin", () => { + expect(UserType.MasterAdmin).toEqual("MasterAdmin"); + }); + test("UserType.Public should be Public", () => { + expect(UserType.Public).toEqual("Public"); + }); }); diff --git a/Common/Tests/Types/Version.test.ts b/Common/Tests/Types/Version.test.ts index 1453c43527..b3d5f2f4b5 100644 --- a/Common/Tests/Types/Version.test.ts +++ b/Common/Tests/Types/Version.test.ts @@ -1,35 +1,35 @@ -import BadDataException from '../../Types/Exception/BadDataException'; -import Version from '../../Types/Version'; +import BadDataException from "../../Types/Exception/BadDataException"; +import Version from "../../Types/Version"; -describe('class Version', () => { - test('should create a version if version string is valid', () => { - expect(new Version('1.2.1').toString()).toEqual('1.2.1'); - expect(new Version('1.2.1').version).toEqual('1.2.1'); - }); - test('Version.version should be mutatable', () => { - const version: Version = new Version('1.2.1'); - version.version = '1.2.2'; - expect(version.version).toEqual('1.2.2'); - expect(version.toString()).toEqual('1.2.2'); - }); - test('mutating Version.version with invalid data should throw an BadDataException', () => { - const version: Version = new Version('1.0.0'); - expect(() => { - version.version = '1'; - }).toThrowError(BadDataException); - expect(() => { - version.version = '1.1'; - }).toThrow('Version is not in valid format.'); - expect(() => { - version.version = '1.1.0.0'; - }).toThrowError(BadDataException); - }); - test('creating version new Version with invalid data should throw an BadDataException', () => { - expect(() => { - new Version('1'); - }).toThrowError(BadDataException); - expect(() => { - new Version('1.1'); - }).toThrow('Version is not in valid format.'); - }); +describe("class Version", () => { + test("should create a version if version string is valid", () => { + expect(new Version("1.2.1").toString()).toEqual("1.2.1"); + expect(new Version("1.2.1").version).toEqual("1.2.1"); + }); + test("Version.version should be mutatable", () => { + const version: Version = new Version("1.2.1"); + version.version = "1.2.2"; + expect(version.version).toEqual("1.2.2"); + expect(version.toString()).toEqual("1.2.2"); + }); + test("mutating Version.version with invalid data should throw an BadDataException", () => { + const version: Version = new Version("1.0.0"); + expect(() => { + version.version = "1"; + }).toThrowError(BadDataException); + expect(() => { + version.version = "1.1"; + }).toThrow("Version is not in valid format."); + expect(() => { + version.version = "1.1.0.0"; + }).toThrowError(BadDataException); + }); + test("creating version new Version with invalid data should throw an BadDataException", () => { + expect(() => { + new Version("1"); + }).toThrowError(BadDataException); + expect(() => { + new Version("1.1"); + }).toThrow("Version is not in valid format."); + }); }); diff --git a/Common/Tests/Types/XML.test.ts b/Common/Tests/Types/XML.test.ts index cc56df4384..08bcda09f0 100644 --- a/Common/Tests/Types/XML.test.ts +++ b/Common/Tests/Types/XML.test.ts @@ -1,36 +1,36 @@ -import BadDataException from '../../Types/Exception/BadDataException'; -import XML from '../../Types/XML'; +import BadDataException from "../../Types/Exception/BadDataException"; +import XML from "../../Types/XML"; -describe('class XML', () => { - test('new XML should return valid object if it is valid', () => { - const xmlString: string = '<test> <info>Test</info></test>'; - const xml: XML = new XML(xmlString); - expect(xml.toString()).toEqual(xmlString); - expect(xml.xml).toEqual(xmlString); - }); - test('XML.xml should be mutable', () => { - const xmlNewString: string = '<new> <info>Test</info></new>'; - const xml: XML = new XML('<test> <info>Test</info></test>'); - xml.xml = xmlNewString; - expect(xml.toString()).toEqual(xmlNewString); - expect(xml.xml).toEqual(xmlNewString); - }); - test('mutating XML.xml with empty string should throw BadDataException', () => { - const xml: XML = new XML('<test> <info>Test</info></test>'); - expect(() => { - xml.xml = ''; - }).toThrowError(BadDataException); - expect(() => { - xml.xml = ''; - }).toThrow('XML is not in valid format.'); - }); +describe("class XML", () => { + test("new XML should return valid object if it is valid", () => { + const xmlString: string = "<test> <info>Test</info></test>"; + const xml: XML = new XML(xmlString); + expect(xml.toString()).toEqual(xmlString); + expect(xml.xml).toEqual(xmlString); + }); + test("XML.xml should be mutable", () => { + const xmlNewString: string = "<new> <info>Test</info></new>"; + const xml: XML = new XML("<test> <info>Test</info></test>"); + xml.xml = xmlNewString; + expect(xml.toString()).toEqual(xmlNewString); + expect(xml.xml).toEqual(xmlNewString); + }); + test("mutating XML.xml with empty string should throw BadDataException", () => { + const xml: XML = new XML("<test> <info>Test</info></test>"); + expect(() => { + xml.xml = ""; + }).toThrowError(BadDataException); + expect(() => { + xml.xml = ""; + }).toThrow("XML is not in valid format."); + }); - test('new should throw BadDataException if empty string is given', () => { - expect(() => { - new XML(''); - }).toThrowError(BadDataException); - expect(() => { - new XML(''); - }).toThrow('XML is not in valid format.'); - }); + test("new should throw BadDataException if empty string is given", () => { + expect(() => { + new XML(""); + }).toThrowError(BadDataException); + expect(() => { + new XML(""); + }).toThrow("XML is not in valid format."); + }); }); diff --git a/Common/Tests/Utils/API.test.ts b/Common/Tests/Utils/API.test.ts index 6a36fcd0ff..4ce0ea5686 100644 --- a/Common/Tests/Utils/API.test.ts +++ b/Common/Tests/Utils/API.test.ts @@ -1,35 +1,35 @@ -import HTTPErrorResponse from '../../Types/API/HTTPErrorResponse'; -import HTTPMethod from '../../Types/API/HTTPMethod'; -import HTTPResponse from '../../Types/API/HTTPResponse'; -import Headers from '../../Types/API/Headers'; -import Hostname from '../../Types/API/Hostname'; -import Protocol from '../../Types/API/Protocol'; -import Route from '../../Types/API/Route'; -import URL from '../../Types/API/URL'; -import Dictionary from '../../Types/Dictionary'; -import APIException from '../../Types/Exception/ApiException'; -import GenericObject from '../../Types/GenericObject'; -import { JSONObject } from '../../Types/JSON'; -import API from '../../Utils/API'; -import { expect, jest } from '@jest/globals'; +import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse"; +import HTTPMethod from "../../Types/API/HTTPMethod"; +import HTTPResponse from "../../Types/API/HTTPResponse"; +import Headers from "../../Types/API/Headers"; +import Hostname from "../../Types/API/Hostname"; +import Protocol from "../../Types/API/Protocol"; +import Route from "../../Types/API/Route"; +import URL from "../../Types/API/URL"; +import Dictionary from "../../Types/Dictionary"; +import APIException from "../../Types/Exception/ApiException"; +import GenericObject from "../../Types/GenericObject"; +import { JSONObject } from "../../Types/JSON"; +import API from "../../Utils/API"; +import { expect, jest } from "@jest/globals"; import axios, { - AxiosError, - AxiosHeaders, - AxiosRequestConfig, - AxiosResponse, - AxiosStatic, - Method, -} from 'axios'; + AxiosError, + AxiosHeaders, + AxiosRequestConfig, + AxiosResponse, + AxiosStatic, + Method, +} from "axios"; const DEFAULT_HEADERS: Headers = { - 'Access-Control-Allow-Origin': '*', - Accept: 'application/json', - 'Content-Type': 'application/json;charset=UTF-8', + "Access-Control-Allow-Origin": "*", + Accept: "application/json", + "Content-Type": "application/json;charset=UTF-8", }; -jest.mock('axios', () => { - // Use actual axios module exported functions/constants such as axios.isAxiosError() - return Object.assign(jest.fn(), jest.requireActual('axios')); +jest.mock("axios", () => { + // Use actual axios module exported functions/constants such as axios.isAxiosError() + return Object.assign(jest.fn(), jest.requireActual("axios")); }); // Mock axios(config) top level function @@ -37,389 +37,386 @@ jest.mock('axios', () => { // Property 'lastCall' is optional in type MockFunctionState but required in type MockContext // @ts-ignore const mockedAxios: jest.MockedFunctionDeep<typeof axios> = - jest.mocked<AxiosStatic>(axios); + jest.mocked<AxiosStatic>(axios); // Spy on calls to HTTPErrorResponse -jest.mock('../../Types/API/HTTPErrorResponse'); +jest.mock("../../Types/API/HTTPErrorResponse"); const HTTPErrorResponseMock: jest.MockedClass<typeof HTTPErrorResponse> = - HTTPErrorResponse as jest.MockedClass<typeof HTTPErrorResponse>; + HTTPErrorResponse as jest.MockedClass<typeof HTTPErrorResponse>; /** * Create a fake axios response */ function createAxiosResponse<T = any, D = any>( - { - data = [] as T, - status = 200, - statusText = 'OK', - config = { - headers: DEFAULT_HEADERS as AxiosHeaders, - }, - headers = DEFAULT_HEADERS, - }: Partial<AxiosResponse<T, D>> = { - data: [] as T, - status: 200, - statusText: 'OK', - config: { - headers: DEFAULT_HEADERS as AxiosHeaders, - }, - headers: DEFAULT_HEADERS, - } + { + data = [] as T, + status = 200, + statusText = "OK", + config = { + headers: DEFAULT_HEADERS as AxiosHeaders, + }, + headers = DEFAULT_HEADERS, + }: Partial<AxiosResponse<T, D>> = { + data: [] as T, + status: 200, + statusText: "OK", + config: { + headers: DEFAULT_HEADERS as AxiosHeaders, + }, + headers: DEFAULT_HEADERS, + }, ): AxiosResponse<T, D> { - return { - data, - status, - statusText, - config, - headers, - }; + return { + data, + status, + statusText, + config, + headers, + }; } const mergedHeaders: Headers = { - 'Access-Control-Allow-Origin': '*', - Accept: 'application/json', - 'Content-Type': 'application/json', // replace default header - 'X-PoweredBy': 'coffee', // add new header + "Access-Control-Allow-Origin": "*", + Accept: "application/json", + "Content-Type": "application/json", // replace default header + "X-PoweredBy": "coffee", // add new header }; /** * Create a fake axios error */ function createAxiosError<T = any, D = any>( - { - config = { - headers: DEFAULT_HEADERS as AxiosHeaders, - }, - isAxiosError = true, - toJSON = () => { - return {}; - }, - name = 'SOME_ERROR_OCCURRED', - message = 'Something went wrong', - response = createAxiosResponse(), - }: Partial<AxiosError<T, D>> = { - config: { - headers: DEFAULT_HEADERS as AxiosHeaders, - }, - isAxiosError: true, - toJSON: () => { - return {}; - }, - name: '', - message: '', - response: createAxiosResponse(), - } + { + config = { + headers: DEFAULT_HEADERS as AxiosHeaders, + }, + isAxiosError = true, + toJSON = () => { + return {}; + }, + name = "SOME_ERROR_OCCURRED", + message = "Something went wrong", + response = createAxiosResponse(), + }: Partial<AxiosError<T, D>> = { + config: { + headers: DEFAULT_HEADERS as AxiosHeaders, + }, + isAxiosError: true, + toJSON: () => { + return {}; + }, + name: "", + message: "", + response: createAxiosResponse(), + }, ): AxiosError { - return { - config, - isAxiosError, - toJSON, - name, - message, - response, - }; + return { + config, + isAxiosError, + toJSON, + name, + message, + response, + }; } /** * Create fake axios parameters */ function createAxiosParameters( - { - method = 'GET', - url = 'https://catfact.ninja/fact', - headers = { ...mergedHeaders }, - data = undefined, - }: { - method?: Method; - url?: string; - headers?: Dictionary<string>; - data?: any; - } = { - method: 'GET', - url: 'https://catfact.ninja/fact', - headers: { ...mergedHeaders }, - } + { + method = "GET", + url = "https://catfact.ninja/fact", + headers = { ...mergedHeaders }, + data = undefined, + }: { + method?: Method; + url?: string; + headers?: Dictionary<string>; + data?: any; + } = { + method: "GET", + url: "https://catfact.ninja/fact", + headers: { ...mergedHeaders }, + }, ): AxiosRequestConfig { - return { - method, - url, - headers, - data, - }; + return { + method, + url, + headers, + data, + }; } const responseData: JSONObject = { - fact: 'Cats have 3 eyelids.', - length: 20, + fact: "Cats have 3 eyelids.", + length: 20, }; const requestData: any = { - breed: 'Siamese', + breed: "Siamese", }; const requestHeaders: Headers = { - 'Content-Type': 'application/json', - 'X-PoweredBy': 'coffee', + "Content-Type": "application/json", + "X-PoweredBy": "coffee", }; afterAll(() => { - jest.restoreAllMocks(); + jest.restoreAllMocks(); }); -describe('API', () => { - test('should create an instance', () => { - const protocol: string = 'https://'; - const hostname: string = 'catfact.ninja'; - const api: API = new API(Protocol.HTTPS, new Hostname(hostname)); +describe("API", () => { + test("should create an instance", () => { + const protocol: string = "https://"; + const hostname: string = "catfact.ninja"; + const api: API = new API(Protocol.HTTPS, new Hostname(hostname)); - expect(api).toBeInstanceOf(API); - expect(api.baseRoute.toString()).toBe('/'); - expect(api.protocol).toBe(protocol); - expect(api.hostname.toString()).toBe(hostname); - }); + expect(api).toBeInstanceOf(API); + expect(api.baseRoute.toString()).toBe("/"); + expect(api.protocol).toBe(protocol); + expect(api.hostname.toString()).toBe(hostname); + }); - test('should create an instance with base route', () => { - const protocol: string = 'https://'; - const hostname: string = 'catfact.ninja'; - const route: string = 'fact'; + test("should create an instance with base route", () => { + const protocol: string = "https://"; + const hostname: string = "catfact.ninja"; + const route: string = "fact"; - const api: API = new API( - Protocol.HTTPS, - new Hostname(hostname), - new Route(route) - ); + const api: API = new API( + Protocol.HTTPS, + new Hostname(hostname), + new Route(route), + ); - expect(api).toBeInstanceOf(API); - expect(api.baseRoute.toString()).toBe('fact'); - expect(api.protocol).toBe(protocol); - expect(api.hostname.toString()).toBe(hostname); - }); + expect(api).toBeInstanceOf(API); + expect(api.baseRoute.toString()).toBe("fact"); + expect(api.protocol).toBe(protocol); + expect(api.hostname.toString()).toBe(hostname); + }); }); -describe('getErrorResponse', () => { - test('should create an HTTPErrorResponse instance', () => { - const data: any = { message: 'Something went wrong' }; - const status: number = 500; - const headers: Headers = { 'X-PoweredBy': 'coffee' }; +describe("getErrorResponse", () => { + test("should create an HTTPErrorResponse instance", () => { + const data: any = { message: "Something went wrong" }; + const status: number = 500; + const headers: Headers = { "X-PoweredBy": "coffee" }; - const response: AxiosResponse<typeof data, GenericObject> = - createAxiosResponse({ - data, - headers, - status, - }); - const axiosError: AxiosError<typeof data, GenericObject> = - createAxiosError({ - response, - }); + const response: AxiosResponse<typeof data, GenericObject> = + createAxiosResponse({ + data, + headers, + status, + }); + const axiosError: AxiosError<typeof data, GenericObject> = createAxiosError( + { + response, + }, + ); - // Use bracket notation property access to access private method - const errorResponse: HTTPErrorResponse = - API['getErrorResponse'](axiosError); + // Use bracket notation property access to access private method + const errorResponse: HTTPErrorResponse = + API["getErrorResponse"](axiosError); - expect(errorResponse).toBeInstanceOf(HTTPErrorResponse); - expect(HTTPErrorResponseMock).toHaveBeenCalledTimes(1); - expect(HTTPErrorResponseMock).toHaveBeenCalledWith( - status, - data, - headers - ); - }); + expect(errorResponse).toBeInstanceOf(HTTPErrorResponse); + expect(HTTPErrorResponseMock).toHaveBeenCalledTimes(1); + expect(HTTPErrorResponseMock).toHaveBeenCalledWith(status, data, headers); + }); - test('should throw if response error has no response', () => { - // NOTE: Passing undefined will initialize the default parameter - const axiosError: AxiosError<null, GenericObject> = createAxiosError({ - response: null!, - }) as AxiosError<null, GenericObject>; + test("should throw if response error has no response", () => { + // NOTE: Passing undefined will initialize the default parameter + const axiosError: AxiosError<null, GenericObject> = createAxiosError({ + response: null!, + }) as AxiosError<null, GenericObject>; - // Use bracket notation property access to access private method - expect(() => { - API['getErrorResponse'](axiosError); - }).toThrowError(APIException); - }); + // Use bracket notation property access to access private method + expect(() => { + API["getErrorResponse"](axiosError); + }).toThrowError(APIException); + }); }); -describe('fetch', () => { - test('should return an HTTPResponse if request is successful', async () => { - const status: number = 200; - const params: Dictionary<string> = { - count: '1', - }; +describe("fetch", () => { + test("should return an HTTPResponse if request is successful", async () => { + const status: number = 200; + const params: Dictionary<string> = { + count: "1", + }; - const mockedParsedResponse: HTTPResponse<typeof responseData> = - new HTTPResponse(status, responseData, DEFAULT_HEADERS); + const mockedParsedResponse: HTTPResponse<typeof responseData> = + new HTTPResponse(status, responseData, DEFAULT_HEADERS); - const mockedAxiosResponse: AxiosResponse< - typeof responseData, - GenericObject - > = createAxiosResponse({ - status, - data: responseData, - }); - - mockedAxios.mockResolvedValueOnce(mockedAxiosResponse); - - const response: HTTPResponse<typeof responseData> = await API.fetch( - HTTPMethod.POST, - new URL(Protocol.HTTPS, 'catfact.ninja', new Route('fact')), - requestData, - requestHeaders, - params - ); - - // Check method, url (protocol, hostname, parameters), headers, request data - expect(axios).toBeCalledWith({ - method: 'POST', - url: 'https://catfact.ninja/fact?count=1', - headers: mergedHeaders, - data: requestData, - }); - - expect(response).toEqual(mockedParsedResponse); + const mockedAxiosResponse: AxiosResponse< + typeof responseData, + GenericObject + > = createAxiosResponse({ + status, + data: responseData, }); - test('should return an HTTPErrorResponse if request fails', async () => { - const status: number = 404; - const statusText: string = 'Not Found'; - const data: JSONObject = { - message: 'Not Found', - }; + mockedAxios.mockResolvedValueOnce(mockedAxiosResponse); - const mockedAxiosError: AxiosError<undefined, GenericObject> = - createAxiosError({ - response: createAxiosResponse({ - status, - statusText, - data, - }), - message: 'An error occurred', - }) as AxiosError<undefined, GenericObject>; + const response: HTTPResponse<typeof responseData> = await API.fetch( + HTTPMethod.POST, + new URL(Protocol.HTTPS, "catfact.ninja", new Route("fact")), + requestData, + requestHeaders, + params, + ); - mockedAxios.mockRejectedValueOnce(mockedAxiosError); - - const httpErrorResponse: HTTPResponse<JSONObject> = await API.fetch( - HTTPMethod.GET, - new URL(Protocol.HTTPS, 'catfact.ninja', new Route('fact')) - ); - - expect(axios).toBeCalledWith({ - method: 'GET', - url: 'https://catfact.ninja/fact', - headers: DEFAULT_HEADERS, - data: undefined, - }); - - expect(httpErrorResponse).toBeInstanceOf(HTTPErrorResponse); + // Check method, url (protocol, hostname, parameters), headers, request data + expect(axios).toBeCalledWith({ + method: "POST", + url: "https://catfact.ninja/fact?count=1", + headers: mergedHeaders, + data: requestData, }); - test('should throw an APIException if initializing request fails', async () => { - mockedAxios.mockImplementationOnce(() => { - throw new Error('Something went wrong'); - }); + expect(response).toEqual(mockedParsedResponse); + }); - await expect(async () => { - await API.fetch( - HTTPMethod.GET, - new URL(Protocol.HTTPS, 'catfact.ninja', new Route('fact')) - ); - }).rejects.toThrowError(APIException); + test("should return an HTTPErrorResponse if request fails", async () => { + const status: number = 404; + const statusText: string = "Not Found"; + const data: JSONObject = { + message: "Not Found", + }; + + const mockedAxiosError: AxiosError<undefined, GenericObject> = + createAxiosError({ + response: createAxiosResponse({ + status, + statusText, + data, + }), + message: "An error occurred", + }) as AxiosError<undefined, GenericObject>; + + mockedAxios.mockRejectedValueOnce(mockedAxiosError); + + const httpErrorResponse: HTTPResponse<JSONObject> = await API.fetch( + HTTPMethod.GET, + new URL(Protocol.HTTPS, "catfact.ninja", new Route("fact")), + ); + + expect(axios).toBeCalledWith({ + method: "GET", + url: "https://catfact.ninja/fact", + headers: DEFAULT_HEADERS, + data: undefined, }); + + expect(httpErrorResponse).toBeInstanceOf(HTTPErrorResponse); + }); + + test("should throw an APIException if initializing request fails", async () => { + mockedAxios.mockImplementationOnce(() => { + throw new Error("Something went wrong"); + }); + + await expect(async () => { + await API.fetch( + HTTPMethod.GET, + new URL(Protocol.HTTPS, "catfact.ninja", new Route("fact")), + ); + }).rejects.toThrowError(APIException); + }); }); -describe('getDefaultHeaders', () => { - test('should return default headers', () => { - expect(API.getDefaultHeaders()).toEqual(DEFAULT_HEADERS); - }); +describe("getDefaultHeaders", () => { + test("should return default headers", () => { + expect(API.getDefaultHeaders()).toEqual(DEFAULT_HEADERS); + }); }); -describe('getHeaders', () => { - test('should merge headers', () => { - // Use bracket notation to access protected member - expect(API['getHeaders'](requestHeaders)).toEqual(mergedHeaders); - }); +describe("getHeaders", () => { + test("should merge headers", () => { + // Use bracket notation to access protected member + expect(API["getHeaders"](requestHeaders)).toEqual(mergedHeaders); + }); }); interface HTTPMethodType { - name: Lowercase<HTTPMethod>; // 'get' | 'post' | 'delete' | 'put' - method: HTTPMethod; + name: Lowercase<HTTPMethod>; // 'get' | 'post' | 'delete' | 'put' + method: HTTPMethod; } // Set up table-driven tests for // .get(), .post(), .put(), .delete(), get(), post(), put(), delete() const httpMethodTests: Array<HTTPMethodType> = [ - { - name: 'get', - method: HTTPMethod.GET, - }, - { - name: 'post', - method: HTTPMethod.POST, - }, - { - name: 'put', - method: HTTPMethod.PUT, - }, - { - name: 'delete', - method: HTTPMethod.DELETE, - }, - { - name: 'head', - method: HTTPMethod.HEAD, - }, + { + name: "get", + method: HTTPMethod.GET, + }, + { + name: "post", + method: HTTPMethod.POST, + }, + { + name: "put", + method: HTTPMethod.PUT, + }, + { + name: "delete", + method: HTTPMethod.DELETE, + }, + { + name: "head", + method: HTTPMethod.HEAD, + }, ]; -describe.each(httpMethodTests)('$name', ({ name, method }: HTTPMethodType) => { - test(`should make a ${method} request`, async () => { - mockedAxios.mockResolvedValueOnce(createAxiosResponse()); +describe.each(httpMethodTests)("$name", ({ name, method }: HTTPMethodType) => { + test(`should make a ${method} request`, async () => { + mockedAxios.mockResolvedValueOnce(createAxiosResponse()); - const url: URL = new URL( - Protocol.HTTPS, - 'catfact.ninja', - new Route('fact') - ); - const got: HTTPResponse<JSONObject> = await API[name]( - url, - requestData, - requestHeaders - ); + const url: URL = new URL( + Protocol.HTTPS, + "catfact.ninja", + new Route("fact"), + ); + const got: HTTPResponse<JSONObject> = await API[name]( + url, + requestData, + requestHeaders, + ); - // Check method, url, headers, request data - expect(axios).toBeCalledWith( - createAxiosParameters({ - method, - url: 'https://catfact.ninja/fact', - data: requestData, - headers: mergedHeaders, - }) - ); - expect(got).toBeInstanceOf(HTTPResponse); - }); + // Check method, url, headers, request data + expect(axios).toBeCalledWith( + createAxiosParameters({ + method, + url: "https://catfact.ninja/fact", + data: requestData, + headers: mergedHeaders, + }), + ); + expect(got).toBeInstanceOf(HTTPResponse); + }); }); -describe.each(httpMethodTests)('.$name', ({ name, method }: HTTPMethodType) => { - test(`should make a ${method} request`, async () => { - const route: string = 'fact'; - const hostname: string = 'catfact.ninja'; - const api: API = new API(Protocol.HTTPS, new Hostname(hostname)); +describe.each(httpMethodTests)(".$name", ({ name, method }: HTTPMethodType) => { + test(`should make a ${method} request`, async () => { + const route: string = "fact"; + const hostname: string = "catfact.ninja"; + const api: API = new API(Protocol.HTTPS, new Hostname(hostname)); - mockedAxios.mockResolvedValueOnce(createAxiosResponse()); + mockedAxios.mockResolvedValueOnce(createAxiosResponse()); - const got: HTTPResponse<JSONObject> = await api[name]( - new Route(route), - requestData, - requestHeaders - ); + const got: HTTPResponse<JSONObject> = await api[name]( + new Route(route), + requestData, + requestHeaders, + ); - // Check method, url (protocol, hostname, route), headers, request data - expect(axios).toBeCalledWith( - createAxiosParameters({ - url: 'https://catfact.ninja/fact', - method, - data: requestData, - }) - ); - expect(got).toBeInstanceOf(HTTPResponse); - }); + // Check method, url (protocol, hostname, route), headers, request data + expect(axios).toBeCalledWith( + createAxiosParameters({ + url: "https://catfact.ninja/fact", + method, + data: requestData, + }), + ); + expect(got).toBeInstanceOf(HTTPResponse); + }); }); diff --git a/Common/Tests/Utils/Analytics.test.ts b/Common/Tests/Utils/Analytics.test.ts index 988fe22092..e1cbb16bc5 100644 --- a/Common/Tests/Utils/Analytics.test.ts +++ b/Common/Tests/Utils/Analytics.test.ts @@ -1,86 +1,86 @@ -import Email from '../../Types/Email'; -import { JSONObject } from '../../Types/JSON'; -import Analytics from '../../Utils/Analytics'; -import { describe, expect, it } from '@jest/globals'; -import posthog from 'posthog-js'; +import Email from "../../Types/Email"; +import { JSONObject } from "../../Types/JSON"; +import Analytics from "../../Utils/Analytics"; +import { describe, expect, it } from "@jest/globals"; +import posthog from "posthog-js"; -jest.mock('posthog-js', () => { - return { - init: jest.fn(), - identify: jest.fn(), - reset: jest.fn(), - capture: jest.fn(), - }; +jest.mock("posthog-js", () => { + return { + init: jest.fn(), + identify: jest.fn(), + reset: jest.fn(), + capture: jest.fn(), + }; }); -const apiHost: string = 'https://example.com'; -const apiKey: string = 'your-api-key'; +const apiHost: string = "https://example.com"; +const apiKey: string = "your-api-key"; -describe('Analytics Class', () => { - afterEach(() => { - jest.clearAllMocks(); +describe("Analytics Class", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should initialize the Analytics class", () => { + const analytics: Analytics = new Analytics(apiHost, apiKey); + + expect(posthog.init).toHaveBeenCalledWith(apiKey, { + api_host: apiHost, + autocapture: false, }); + expect(analytics.isInitialized).toBe(true); + }); - it('should initialize the Analytics class', () => { - const analytics: Analytics = new Analytics(apiHost, apiKey); + it("should not initialize if apiHost and apiKey are not provided", () => { + const analytics: Analytics = new Analytics("", ""); - expect(posthog.init).toHaveBeenCalledWith(apiKey, { - api_host: apiHost, - autocapture: false, - }); - expect(analytics.isInitialized).toBe(true); - }); + expect(posthog.init).not.toHaveBeenCalled(); + expect(analytics.isInitialized).toBe(false); + }); - it('should not initialize if apiHost and apiKey are not provided', () => { - const analytics: Analytics = new Analytics('', ''); + it("should authenticate a user", () => { + const analytics: Analytics = new Analytics(apiHost, apiKey); + const email: Email = new Email("test@example.com"); - expect(posthog.init).not.toHaveBeenCalled(); - expect(analytics.isInitialized).toBe(false); - }); + analytics.userAuth(email); + expect(posthog.identify).toHaveBeenCalledWith(email.toString()); + }); - it('should authenticate a user', () => { - const analytics: Analytics = new Analytics(apiHost, apiKey); - const email: Email = new Email('test@example.com'); + it("should not authenticate a user if not initialized", () => { + const analytics: Analytics = new Analytics("", ""); + const email: Email = new Email("test@example.com"); - analytics.userAuth(email); - expect(posthog.identify).toHaveBeenCalledWith(email.toString()); - }); + analytics.userAuth(email); + expect(posthog.identify).not.toHaveBeenCalled(); + }); - it('should not authenticate a user if not initialized', () => { - const analytics: Analytics = new Analytics('', ''); - const email: Email = new Email('test@example.com'); + it("should reset the user session on logout", () => { + const analytics: Analytics = new Analytics(apiHost, apiKey); - analytics.userAuth(email); - expect(posthog.identify).not.toHaveBeenCalled(); - }); + analytics.logout(); + expect(posthog.reset).toHaveBeenCalled(); + }); - it('should reset the user session on logout', () => { - const analytics: Analytics = new Analytics(apiHost, apiKey); + it("should not reset the user session if not initialized", () => { + const analytics: Analytics = new Analytics("", ""); - analytics.logout(); - expect(posthog.reset).toHaveBeenCalled(); - }); + analytics.logout(); + expect(posthog.reset).not.toHaveBeenCalled(); + }); - it('should not reset the user session if not initialized', () => { - const analytics: Analytics = new Analytics('', ''); + it("should capture an event with optional data", () => { + const analytics: Analytics = new Analytics(apiHost, apiKey); + const eventName: string = "testEvent"; + const data: JSONObject = { key: "value" }; - analytics.logout(); - expect(posthog.reset).not.toHaveBeenCalled(); - }); + analytics.capture(eventName, data); + expect(posthog.capture).toHaveBeenCalledWith(eventName, data); + }); - it('should capture an event with optional data', () => { - const analytics: Analytics = new Analytics(apiHost, apiKey); - const eventName: string = 'testEvent'; - const data: JSONObject = { key: 'value' }; + it("should not capture an event if not initialized", () => { + const analytics: Analytics = new Analytics("", ""); - analytics.capture(eventName, data); - expect(posthog.capture).toHaveBeenCalledWith(eventName, data); - }); - - it('should not capture an event if not initialized', () => { - const analytics: Analytics = new Analytics('', ''); - - analytics.capture('testEvent'); - expect(posthog.capture).not.toHaveBeenCalled(); - }); + analytics.capture("testEvent"); + expect(posthog.capture).not.toHaveBeenCalled(); + }); }); diff --git a/Common/Tests/Utils/CronTime.test.ts b/Common/Tests/Utils/CronTime.test.ts index d19b2d09ed..3b320fad33 100644 --- a/Common/Tests/Utils/CronTime.test.ts +++ b/Common/Tests/Utils/CronTime.test.ts @@ -1,34 +1,34 @@ import { - EVERY_DAY, - EVERY_FIVE_MINUTE, - EVERY_FIVE_SECONDS, - EVERY_HOUR, - EVERY_MINUTE, - EVERY_WEEK, -} from '../../Utils/CronTime'; + EVERY_DAY, + EVERY_FIVE_MINUTE, + EVERY_FIVE_SECONDS, + EVERY_HOUR, + EVERY_MINUTE, + EVERY_WEEK, +} from "../../Utils/CronTime"; -describe('CronTime', () => { - test('should return every minute', () => { - expect(EVERY_MINUTE).toEqual('* * * * *'); - }); +describe("CronTime", () => { + test("should return every minute", () => { + expect(EVERY_MINUTE).toEqual("* * * * *"); + }); - test('should return every day', () => { - expect(EVERY_DAY).toEqual('0 8 * * *'); - }); + test("should return every day", () => { + expect(EVERY_DAY).toEqual("0 8 * * *"); + }); - test('should return every hour', () => { - expect(EVERY_HOUR).toEqual('1 * * * *'); - }); + test("should return every hour", () => { + expect(EVERY_HOUR).toEqual("1 * * * *"); + }); - test('should return every five minute', () => { - expect(EVERY_FIVE_MINUTE).toEqual('*/5 * * * *'); - }); + test("should return every five minute", () => { + expect(EVERY_FIVE_MINUTE).toEqual("*/5 * * * *"); + }); - test('should return every five seconds', () => { - expect(EVERY_FIVE_SECONDS).toEqual('*/5 * * * * *'); - }); + test("should return every five seconds", () => { + expect(EVERY_FIVE_SECONDS).toEqual("*/5 * * * * *"); + }); - test('should return every week', () => { - expect(EVERY_WEEK).toEqual('0 0 * * 0'); - }); + test("should return every week", () => { + expect(EVERY_WEEK).toEqual("0 0 * * 0"); + }); }); diff --git a/Common/Tests/Utils/Faker.test.ts b/Common/Tests/Utils/Faker.test.ts index 85bd065321..72625d678d 100644 --- a/Common/Tests/Utils/Faker.test.ts +++ b/Common/Tests/Utils/Faker.test.ts @@ -1,39 +1,37 @@ -import Email from '../../Types/Email'; -import Name from '../../Types/Name'; -import Phone from '../../Types/Phone'; -import Faker from '../../Utils/Faker'; -import { describe, expect, it } from '@jest/globals'; +import Email from "../../Types/Email"; +import Name from "../../Types/Name"; +import Phone from "../../Types/Phone"; +import Faker from "../../Utils/Faker"; +import { describe, expect, it } from "@jest/globals"; -describe('Faker Class', () => { - it('should generate a random name with alphanumeric characters', () => { - expect(Faker.generateName()).toMatch(/^[a-zA-Z0-9]{10}$/); - }); +describe("Faker Class", () => { + it("should generate a random name with alphanumeric characters", () => { + expect(Faker.generateName()).toMatch(/^[a-zA-Z0-9]{10}$/); + }); - it('should generate a random company name', () => { - expect(Faker.generateCompanyName()).toBeTruthy(); - }); + it("should generate a random company name", () => { + expect(Faker.generateCompanyName()).toBeTruthy(); + }); - it('should generate a string of random numbers of specified length', () => { - expect(Faker.randomNumbers(8)).toMatch(/^\d{8}$/); - }); + it("should generate a string of random numbers of specified length", () => { + expect(Faker.randomNumbers(8)).toMatch(/^\d{8}$/); + }); - it('should generate a user full name', () => { - const userFullName: Name = Faker.generateUserFullName(); - expect(userFullName).toHaveProperty('name'); - expect(userFullName.name).toBeTruthy(); - }); + it("should generate a user full name", () => { + const userFullName: Name = Faker.generateUserFullName(); + expect(userFullName).toHaveProperty("name"); + expect(userFullName.name).toBeTruthy(); + }); - it('should generate a valid email address', () => { - const email: Email = Faker.generateEmail(); - expect(email.email).toMatch( - /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,9}$/i - ); - }); + it("should generate a valid email address", () => { + const email: Email = Faker.generateEmail(); + expect(email.email).toMatch(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,9}$/i); + }); - it('should generate a valid phone number', () => { - const phone: Phone = Faker.generatePhone(); - expect(phone.phone).toMatch( - /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,7}$/ - ); - }); + it("should generate a valid phone number", () => { + const phone: Phone = Faker.generatePhone(); + expect(phone.phone).toMatch( + /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,7}$/, + ); + }); }); diff --git a/Common/Tests/Utils/Slug.test.ts b/Common/Tests/Utils/Slug.test.ts index 56f2f09679..60d9a370a6 100644 --- a/Common/Tests/Utils/Slug.test.ts +++ b/Common/Tests/Utils/Slug.test.ts @@ -1,23 +1,23 @@ -import Slug from '../../Utils/Slug'; +import Slug from "../../Utils/Slug"; -describe('Slug.getSlug()', () => { - test('should return empty string, if name is empty ', () => { - expect(Slug.getSlug('')).toEqual(''); - expect(Slug.getSlug(' ')).toEqual(''); - }); - test('should generate a slug from a valid name when name is null', () => { - expect(Slug.getSlug(null)).toMatch(/^[a-z0-9-]+$/); - }); - test('should replaces spaces in nonEmpty with hyphen -', () => { - expect(Slug.getSlug('this is slug')).toMatch(/this-is-slug/g); - }); +describe("Slug.getSlug()", () => { + test("should return empty string, if name is empty ", () => { + expect(Slug.getSlug("")).toEqual(""); + expect(Slug.getSlug(" ")).toEqual(""); + }); + test("should generate a slug from a valid name when name is null", () => { + expect(Slug.getSlug(null)).toMatch(/^[a-z0-9-]+$/); + }); + test("should replaces spaces in nonEmpty with hyphen -", () => { + expect(Slug.getSlug("this is slug")).toMatch(/this-is-slug/g); + }); - test('should append 10 numbers if non-empty string name is given', () => { - expect(Slug.getSlug('slug')).toMatch(/^slug-+[\d]{10}$/); - }); - test('should remove character in [&*+~.,\\/()|\'"!:@]', () => { - expect(Slug.getSlug(' *+~.,\\/()\'"!:@slug is awesome')).toMatch( - /^slug-is-awesome-+[\d]{10}$/ - ); - }); + test("should append 10 numbers if non-empty string name is given", () => { + expect(Slug.getSlug("slug")).toMatch(/^slug-+[\d]{10}$/); + }); + test("should remove character in [&*+~.,\\/()|'\"!:@]", () => { + expect(Slug.getSlug(" *+~.,\\/()'\"!:@slug is awesome")).toMatch( + /^slug-is-awesome-+[\d]{10}$/, + ); + }); }); diff --git a/Common/Tests/Utils/UUID.test.ts b/Common/Tests/Utils/UUID.test.ts index 289bc3acf8..c2b8c1a0ac 100644 --- a/Common/Tests/Utils/UUID.test.ts +++ b/Common/Tests/Utils/UUID.test.ts @@ -1,12 +1,12 @@ -import UUID from '../../Utils/UUID'; +import UUID from "../../Utils/UUID"; -describe('UUID', () => { - test('UUID.generate() should generate a valid UUID', () => { - const uuid: string = UUID.generate(); - expect(uuid).toBeDefined(); - expect(uuid).toMatch( - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ - ); - expect(uuid.length).toBe(36); - }); +describe("UUID", () => { + test("UUID.generate() should generate a valid UUID", () => { + const uuid: string = UUID.generate(); + expect(uuid).toBeDefined(); + expect(uuid).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, + ); + expect(uuid.length).toBe(36); + }); }); diff --git a/Common/Types/API/EmptyResponse.ts b/Common/Types/API/EmptyResponse.ts index 55d4560bb3..935ebd796d 100644 --- a/Common/Types/API/EmptyResponse.ts +++ b/Common/Types/API/EmptyResponse.ts @@ -1,3 +1,3 @@ -import { JSONObject } from '../JSON'; +import { JSONObject } from "../JSON"; export default interface EmptyResponseData extends JSONObject {} diff --git a/Common/Types/API/HTTPErrorResponse.ts b/Common/Types/API/HTTPErrorResponse.ts index f0e6901ea8..3037696d81 100644 --- a/Common/Types/API/HTTPErrorResponse.ts +++ b/Common/Types/API/HTTPErrorResponse.ts @@ -1,28 +1,25 @@ -import { JSONObject } from '../JSON'; -import Typeof from '../Typeof'; -import HTTPResponse from './HTTPResponse'; +import { JSONObject } from "../JSON"; +import Typeof from "../Typeof"; +import HTTPResponse from "./HTTPResponse"; export default class HTTPErrorResponse extends HTTPResponse<JSONObject> { - public get message(): string { - if (!this.data) { - return ''; - } - - if (this.data['data'] && Typeof.String === typeof this.data['data']) { - return this.data['data'] as string; - } - - if ( - this.data['message'] && - Typeof.String === typeof this.data['message'] - ) { - return this.data['message'] as string; - } - - if (!this.data['error']) { - return ''; - } - - return this.data['error'] as string; + public get message(): string { + if (!this.data) { + return ""; } + + if (this.data["data"] && Typeof.String === typeof this.data["data"]) { + return this.data["data"] as string; + } + + if (this.data["message"] && Typeof.String === typeof this.data["message"]) { + return this.data["message"] as string; + } + + if (!this.data["error"]) { + return ""; + } + + return this.data["error"] as string; + } } diff --git a/Common/Types/API/HTTPMethod.ts b/Common/Types/API/HTTPMethod.ts index 4318878ea6..b238f38fc3 100644 --- a/Common/Types/API/HTTPMethod.ts +++ b/Common/Types/API/HTTPMethod.ts @@ -1,9 +1,9 @@ enum HTTPMethod { - GET = 'GET', - POST = 'POST', - DELETE = 'DELETE', - PUT = 'PUT', - HEAD = 'HEAD', + GET = "GET", + POST = "POST", + DELETE = "DELETE", + PUT = "PUT", + HEAD = "HEAD", } export default HTTPMethod; diff --git a/Common/Types/API/HTTPResponse.ts b/Common/Types/API/HTTPResponse.ts index 994a84147c..6e3d032725 100644 --- a/Common/Types/API/HTTPResponse.ts +++ b/Common/Types/API/HTTPResponse.ts @@ -1,128 +1,126 @@ -import AnalyticsBaseModel from '../../AnalyticsModels/BaseModel'; -import BaseModel from '../../Models/BaseModel'; -import { JSONArray, JSONObject, JSONObjectOrArray } from '../JSON'; -import JSONFunctions from '../JSONFunctions'; -import Typeof from '../Typeof'; -import Headers from './Headers'; +import AnalyticsBaseModel from "../../AnalyticsModels/BaseModel"; +import BaseModel from "../../Models/BaseModel"; +import { JSONArray, JSONObject, JSONObjectOrArray } from "../JSON"; +import JSONFunctions from "../JSONFunctions"; +import Typeof from "../Typeof"; +import Headers from "./Headers"; export default class HTTPResponse< - T extends - | JSONObjectOrArray - | BaseModel - | Array<BaseModel> - | AnalyticsBaseModel - | Array<AnalyticsBaseModel> + T extends + | JSONObjectOrArray + | BaseModel + | Array<BaseModel> + | AnalyticsBaseModel + | Array<AnalyticsBaseModel>, > { - private _statusCode: number = -1; - public get statusCode(): number { - return this._statusCode; - } - public set statusCode(v: number) { - this._statusCode = v; - } + private _statusCode: number = -1; + public get statusCode(): number { + return this._statusCode; + } + public set statusCode(v: number) { + this._statusCode = v; + } - private _jsonData!: JSONObjectOrArray; - public get jsonData(): JSONObjectOrArray { - return this._jsonData; - } - public set jsonData(v: JSONObjectOrArray) { - this._jsonData = v; - } + private _jsonData!: JSONObjectOrArray; + public get jsonData(): JSONObjectOrArray { + return this._jsonData; + } + public set jsonData(v: JSONObjectOrArray) { + this._jsonData = v; + } - private _data!: T; - public get data(): T { - return this._data; - } - public set data(v: T) { - this._data = v; - } + private _data!: T; + public get data(): T { + return this._data; + } + public set data(v: T) { + this._data = v; + } - private _headers: Headers = {}; - public get headers(): Headers { - return this._headers; - } - public set headers(v: Headers) { - this._headers = v; - } + private _headers: Headers = {}; + public get headers(): Headers { + return this._headers; + } + public set headers(v: Headers) { + this._headers = v; + } - private _count: number = 0; - public get count(): number { - return this._count; - } - public set count(v: number) { - this._count = v; - } + private _count: number = 0; + public get count(): number { + return this._count; + } + public set count(v: number) { + this._count = v; + } - private _limit: number = 0; - public get limit(): number { - return this._limit; - } - public set limit(v: number) { - this._limit = v; - } + private _limit: number = 0; + public get limit(): number { + return this._limit; + } + public set limit(v: number) { + this._limit = v; + } - private _skip: number = 0; - public get skip(): number { - return this._skip; - } - public set skip(v: number) { - this._skip = v; - } + private _skip: number = 0; + public get skip(): number { + return this._skip; + } + public set skip(v: number) { + this._skip = v; + } - public constructor( - statusCode: number, - data: JSONObject | Array<JSONObject>, - headers: Headers + public constructor( + statusCode: number, + data: JSONObject | Array<JSONObject>, + headers: Headers, + ) { + this.statusCode = statusCode; + + if ( + !Array.isArray(data) && + Object.keys(data).includes("count") && + Object.keys(data).includes("skip") && + Object.keys(data).includes("limit") ) { - this.statusCode = statusCode; - - if ( - !Array.isArray(data) && - Object.keys(data).includes('count') && - Object.keys(data).includes('skip') && - Object.keys(data).includes('limit') - ) { - // likely a list returned. - this.count = data['count'] as number; - this.skip = data['skip'] as number; - this.limit = data['limit'] as number; - this.jsonData = JSONFunctions.deserializeArray( - data['data'] as JSONArray - ); - } else if (Array.isArray(data)) { - this.jsonData = JSONFunctions.deserializeArray(data as JSONArray); - } else if (Typeof.String === typeof data) { - this.jsonData = { - data, - }; - } else { - this.jsonData = JSONFunctions.deserialize(data as JSONObject); - } - this.headers = headers; - this.data = this.jsonData as T; + // likely a list returned. + this.count = data["count"] as number; + this.skip = data["skip"] as number; + this.limit = data["limit"] as number; + this.jsonData = JSONFunctions.deserializeArray(data["data"] as JSONArray); + } else if (Array.isArray(data)) { + this.jsonData = JSONFunctions.deserializeArray(data as JSONArray); + } else if (Typeof.String === typeof data) { + this.jsonData = { + data, + }; + } else { + this.jsonData = JSONFunctions.deserialize(data as JSONObject); } + this.headers = headers; + this.data = this.jsonData as T; + } - public isSuccess(): boolean { - return this.statusCode === 200; - } + public isSuccess(): boolean { + return this.statusCode === 200; + } - public isFailure(): boolean { - return this.statusCode !== 200; - } + public isFailure(): boolean { + return this.statusCode !== 200; + } - public isNotAuthorized(): boolean { - return this.statusCode === 401; - } + public isNotAuthorized(): boolean { + return this.statusCode === 401; + } - public isTooManyRequests(): boolean { - return this.statusCode === 429; - } + public isTooManyRequests(): boolean { + return this.statusCode === 429; + } - public isPaymentDeclined(): boolean { - return this.statusCode === 402; - } + public isPaymentDeclined(): boolean { + return this.statusCode === 402; + } - public isServerError(): boolean { - return this.statusCode === 500; - } + public isServerError(): boolean { + return this.statusCode === 500; + } } diff --git a/Common/Types/API/Headers.ts b/Common/Types/API/Headers.ts index ab274d5b48..e79e6031bb 100644 --- a/Common/Types/API/Headers.ts +++ b/Common/Types/API/Headers.ts @@ -1,4 +1,4 @@ -import Dictionary from '../Dictionary'; +import Dictionary from "../Dictionary"; type Headers = Dictionary<string>; export default Headers; diff --git a/Common/Types/API/Hostname.ts b/Common/Types/API/Hostname.ts index 1383eee43b..6cce9f48af 100644 --- a/Common/Types/API/Hostname.ts +++ b/Common/Types/API/Hostname.ts @@ -1,118 +1,118 @@ -import DatabaseProperty from '../Database/DatabaseProperty'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import Port from '../Port'; -import Typeof from '../Typeof'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "../Database/DatabaseProperty"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import Port from "../Port"; +import Typeof from "../Typeof"; +import { FindOperator } from "typeorm"; export default class Hostname extends DatabaseProperty { - private _route: string = ''; - public get hostname(): string { - return this._route; + private _route: string = ""; + public get hostname(): string { + return this._route; + } + + private _port!: Port; + public get port(): Port { + return this._port; + } + public set port(v: Port) { + this._port = v; + } + + public set hostname(value: string) { + value = value.trim(); + + if (Hostname.isValid(value)) { + this._route = value; + } else { + throw new BadDataException( + "Hostname " + value + " is not in valid format.", + ); + } + } + + public static isValid(value: string): boolean { + const re: RegExp = /^[a-zA-Z-\d!#$&'*+,/:;=?@[\].]*$/; + const isValid: boolean = re.test(value); + if (!isValid) { + return false; + } + return true; + } + + public constructor(hostname: string, port?: Port | string | number) { + super(); + if (hostname) { + this.hostname = hostname; } - private _port!: Port; - public get port(): Port { - return this._port; + if (port instanceof Port) { + this.port = port; + } else if (typeof port === Typeof.String) { + this.port = new Port(port as string); + } else if (typeof port === Typeof.Number) { + this.port = new Port(port as number); } - public set port(v: Port) { - this._port = v; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Hostname, + value: (this as Hostname).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Hostname { + if (json["_type"] === ObjectType.Hostname) { + return new Hostname((json["value"] as string) || ""); } - public set hostname(value: string) { - value = value.trim(); + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } - if (Hostname.isValid(value)) { - this._route = value; - } else { - throw new BadDataException( - 'Hostname ' + value + ' is not in valid format.' - ); - } + public override toString(): string { + let hostname: string = this.hostname; + + if (this.port) { + hostname += ":" + this.port.toString(); } - public static isValid(value: string): boolean { - const re: RegExp = /^[a-zA-Z-\d!#$&'*+,/:;=?@[\].]*$/; - const isValid: boolean = re.test(value); - if (!isValid) { - return false; - } - return true; + return hostname; + } + + public static fromString(hostname: string | Hostname): Hostname { + if (hostname instanceof Hostname) { + hostname = hostname.toString(); } - public constructor(hostname: string, port?: Port | string | number) { - super(); - if (hostname) { - this.hostname = hostname; - } + if (hostname.includes(":")) { + return new Hostname( + hostname.split(":")[0] as string, + hostname.split(":")[1], + ); + } + return new Hostname(hostname); + } - if (port instanceof Port) { - this.port = port; - } else if (typeof port === Typeof.String) { - this.port = new Port(port as string); - } else if (typeof port === Typeof.Number) { - this.port = new Port(port as number); - } + public static override toDatabase( + value: Hostname | FindOperator<Hostname>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new Hostname(value); + } + + return value.toString(); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.Hostname, - value: (this as Hostname).toString(), - }; + return value; + } + + public static override fromDatabase(_value: string): Hostname | null { + if (_value) { + return new Hostname(_value); } - public static override fromJSON(json: JSONObject): Hostname { - if (json['_type'] === ObjectType.Hostname) { - return new Hostname((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - public override toString(): string { - let hostname: string = this.hostname; - - if (this.port) { - hostname += ':' + this.port.toString(); - } - - return hostname; - } - - public static fromString(hostname: string | Hostname): Hostname { - if (hostname instanceof Hostname) { - hostname = hostname.toString(); - } - - if (hostname.includes(':')) { - return new Hostname( - hostname.split(':')[0] as string, - hostname.split(':')[1] - ); - } - return new Hostname(hostname); - } - - public static override toDatabase( - value: Hostname | FindOperator<Hostname> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new Hostname(value); - } - - return value.toString(); - } - - return value; - } - - public static override fromDatabase(_value: string): Hostname | null { - if (_value) { - return new Hostname(_value); - } - - return null; - } + return null; + } } diff --git a/Common/Types/API/Protocol.ts b/Common/Types/API/Protocol.ts index d7fa58acd7..7f3150030d 100644 --- a/Common/Types/API/Protocol.ts +++ b/Common/Types/API/Protocol.ts @@ -1,10 +1,10 @@ enum Protocol { - HTTP = 'http://', - HTTPS = 'https://', - MONGO_DB = 'mongodb://', - WS = 'ws://', - WSS = 'wss://', - MAIL = 'mailto:', + HTTP = "http://", + HTTPS = "https://", + MONGO_DB = "mongodb://", + WS = "ws://", + WSS = "wss://", + MAIL = "mailto:", } export default Protocol; diff --git a/Common/Types/API/ResponseType.ts b/Common/Types/API/ResponseType.ts index 4c5baa9610..0d81b234a0 100644 --- a/Common/Types/API/ResponseType.ts +++ b/Common/Types/API/ResponseType.ts @@ -1,7 +1,7 @@ enum ResponseType { - HTML = 'html', - JSON = 'json', - CSV = 'csv', + HTML = "html", + JSON = "json", + CSV = "csv", } export default ResponseType; diff --git a/Common/Types/API/Route.ts b/Common/Types/API/Route.ts index 97aea11720..178da074a3 100644 --- a/Common/Types/API/Route.ts +++ b/Common/Types/API/Route.ts @@ -1,95 +1,95 @@ -import DatabaseProperty from '../Database/DatabaseProperty'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "../Database/DatabaseProperty"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import { FindOperator } from "typeorm"; export default class Route extends DatabaseProperty { - private _route: string = ''; - public get route(): string { - return this._route; + private _route: string = ""; + public get route(): string { + return this._route; + } + public set route(v: string) { + const matchRouteCharacters: RegExp = + /^[a-zA-Z_\d\-!#$%&'()*+,./:;=?@[\]]*$/; + if (v && !matchRouteCharacters.test(v)) { + throw new BadDataException(`Invalid route: ${v}`); } - public set route(v: string) { - const matchRouteCharacters: RegExp = - /^[a-zA-Z_\d\-!#$%&'()*+,./:;=?@[\]]*$/; - if (v && !matchRouteCharacters.test(v)) { - throw new BadDataException(`Invalid route: ${v}`); - } - this._route = v; + this._route = v; + } + + public constructor(route?: string | Route) { + super(); + if (route && route instanceof Route) { + route = route.toString(); } - public constructor(route?: string | Route) { - super(); - if (route && route instanceof Route) { - route = route.toString(); - } + route = route?.replace(/\/+/g, "/"); // remove multiple slashes from route and replace with single slash - route = route?.replace(/\/+/g, '/'); // remove multiple slashes from route and replace with single slash + if (route) { + this.route = route; + } + } - if (route) { - this.route = route; - } + public override toJSON(): JSONObject { + return { + _type: ObjectType.Route, + value: (this as Route).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Route { + if (json["_type"] === ObjectType.Route) { + return new Route((json["value"] as string) || ""); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.Route, - value: (this as Route).toString(), - }; + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + public addRoute(route: Route | string): Route { + if (typeof route === "string") { + route = new Route(route); } - public static override fromJSON(json: JSONObject): Route { - if (json['_type'] === ObjectType.Route) { - return new Route((json['value'] as string) || ''); - } + let routeToBeAdded: string = route.toString(); + if (this.route.endsWith("/") && routeToBeAdded.trim().startsWith("/")) { + routeToBeAdded = routeToBeAdded.trim().substring(1); // remove leading "/" from route + } + this.route = new Route(this.route + routeToBeAdded).route; + return this; + } - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); + public override toString(): string { + return this.route; + } + + public static fromString(route: string): Route { + return new Route(route); + } + + public addRouteParam(paramName: string, value: string): Route { + this.route = this.route.replace(paramName, value); + return this; + } + + public static override toDatabase( + value: Route | FindOperator<Route>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new Route(value); + } + + return value.toString(); } - public addRoute(route: Route | string): Route { - if (typeof route === 'string') { - route = new Route(route); - } + return value; + } - let routeToBeAdded: string = route.toString(); - if (this.route.endsWith('/') && routeToBeAdded.trim().startsWith('/')) { - routeToBeAdded = routeToBeAdded.trim().substring(1); // remove leading "/" from route - } - this.route = new Route(this.route + routeToBeAdded).route; - return this; + public static override fromDatabase(_value: string): Route | null { + if (_value) { + return new Route(_value); } - public override toString(): string { - return this.route; - } - - public static fromString(route: string): Route { - return new Route(route); - } - - public addRouteParam(paramName: string, value: string): Route { - this.route = this.route.replace(paramName, value); - return this; - } - - public static override toDatabase( - value: Route | FindOperator<Route> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new Route(value); - } - - return value.toString(); - } - - return value; - } - - public static override fromDatabase(_value: string): Route | null { - if (_value) { - return new Route(_value); - } - - return null; - } + return null; + } } diff --git a/Common/Types/API/StatusCode.ts b/Common/Types/API/StatusCode.ts index a81e5bac8d..02f771f49e 100644 --- a/Common/Types/API/StatusCode.ts +++ b/Common/Types/API/StatusCode.ts @@ -1,45 +1,42 @@ -import PositiveNumber from '../PositiveNumber'; -import Typeof from '../Typeof'; +import PositiveNumber from "../PositiveNumber"; +import Typeof from "../Typeof"; export default class StatusCode { - private _statusCode: PositiveNumber = new PositiveNumber(200); - public get statusCode(): PositiveNumber { - return this._statusCode; - } - public set statusCode(v: PositiveNumber) { - this._statusCode = v; - } + private _statusCode: PositiveNumber = new PositiveNumber(200); + public get statusCode(): PositiveNumber { + return this._statusCode; + } + public set statusCode(v: PositiveNumber) { + this._statusCode = v; + } - public constructor(statusCode: number | string) { - if (statusCode) { - this.statusCode = new PositiveNumber(statusCode); - } + public constructor(statusCode: number | string) { + if (statusCode) { + this.statusCode = new PositiveNumber(statusCode); } + } - public toString(): string { - return this.statusCode.toString(); - } - - public toNumber(): number { - return this.statusCode.toNumber(); - } - - public static isValidStatusCode(statusCode: number | string): boolean { - try { - if (typeof statusCode === Typeof.String) { - statusCode = parseInt(statusCode as string); - } - - if ( - (statusCode as number) >= 100 && - (statusCode as number) <= 599 - ) { - return true; - } - - return false; - } catch (err) { - return false; - } + public toString(): string { + return this.statusCode.toString(); + } + + public toNumber(): number { + return this.statusCode.toNumber(); + } + + public static isValidStatusCode(statusCode: number | string): boolean { + try { + if (typeof statusCode === Typeof.String) { + statusCode = parseInt(statusCode as string); + } + + if ((statusCode as number) >= 100 && (statusCode as number) <= 599) { + return true; + } + + return false; + } catch (err) { + return false; } + } } diff --git a/Common/Types/API/URL.ts b/Common/Types/API/URL.ts index 2ed7c27611..6071e5a9fd 100644 --- a/Common/Types/API/URL.ts +++ b/Common/Types/API/URL.ts @@ -1,273 +1,269 @@ -import DatabaseProperty from '../Database/DatabaseProperty'; -import Dictionary from '../Dictionary'; -import Email from '../Email'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import Typeof from '../Typeof'; -import Hostname from './Hostname'; -import Protocol from './Protocol'; -import Route from './Route'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "../Database/DatabaseProperty"; +import Dictionary from "../Dictionary"; +import Email from "../Email"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import Typeof from "../Typeof"; +import Hostname from "./Hostname"; +import Protocol from "./Protocol"; +import Route from "./Route"; +import { FindOperator } from "typeorm"; export default class URL extends DatabaseProperty { - private _route: Route = new Route(); - public get route(): Route { - return this._route; - } - public set route(v: Route) { - this._route = v; - } + private _route: Route = new Route(); + public get route(): Route { + return this._route; + } + public set route(v: Route) { + this._route = v; + } - private _params: Dictionary<string> = {}; - public get params(): Dictionary<string> { - return this._params; - } - public set params(v: Dictionary<string>) { - this._params = v; - } + private _params: Dictionary<string> = {}; + public get params(): Dictionary<string> { + return this._params; + } + public set params(v: Dictionary<string>) { + this._params = v; + } - private _email!: Email; - public get email(): Email { - return this._email; - } - public set email(v: Email) { - this._email = v; - } + private _email!: Email; + public get email(): Email { + return this._email; + } + public set email(v: Email) { + this._email = v; + } - private _hostname!: Hostname; - public get hostname(): Hostname { - return this._hostname; - } - public set hostname(v: Hostname) { - this._hostname = v; - } + private _hostname!: Hostname; + public get hostname(): Hostname { + return this._hostname; + } + public set hostname(v: Hostname) { + this._hostname = v; + } - private _protocol: Protocol = Protocol.HTTPS; - public get protocol(): Protocol { - return this._protocol; - } - public set protocol(v: Protocol) { - this._protocol = v; - } + private _protocol: Protocol = Protocol.HTTPS; + public get protocol(): Protocol { + return this._protocol; + } + public set protocol(v: Protocol) { + this._protocol = v; + } - public constructor( - protocol: Protocol, - hostname: Hostname | string | Email, - route?: Route, - queryString?: string + public constructor( + protocol: Protocol, + hostname: Hostname | string | Email, + route?: Route, + queryString?: string, + ) { + super(); + + if ( + typeof hostname === Typeof.String && + Email.isValid(hostname as string) ) { - super(); - - if ( - typeof hostname === Typeof.String && - Email.isValid(hostname as string) - ) { - this.email = new Email(hostname as string); - } else if (hostname instanceof Email) { - this.email = hostname; - } else if (hostname instanceof Hostname) { - this.hostname = hostname; - } else if (typeof hostname === Typeof.String) { - this.hostname = Hostname.fromString(hostname); - } - - this.protocol = protocol; - - if (route) { - this.route = route; - } - - if (queryString) { - const keyValues: Array<string> = queryString.split('&'); - for (const keyValue of keyValues) { - if (keyValue.split('=')[0] && keyValue.split('=')[1]) { - const key: string | undefined = keyValue.split('=')[0]; - const value: string | undefined = keyValue.split('=')[1]; - if (key && value) { - this._params[key] = value; - } - } - } - } + this.email = new Email(hostname as string); + } else if (hostname instanceof Email) { + this.email = hostname; + } else if (hostname instanceof Hostname) { + this.hostname = hostname; + } else if (typeof hostname === Typeof.String) { + this.hostname = Hostname.fromString(hostname); } - public isHttps(): boolean { - return this.protocol === Protocol.HTTPS; + this.protocol = protocol; + + if (route) { + this.route = route; } - public override toString(): string { - let urlString: string = `${this.protocol}${ - this.hostname || this.email - }`; - if (!this.email && !urlString.startsWith('mailto:')) { - if (this.route && this.route.toString().startsWith('/')) { - if (urlString.endsWith('/')) { - urlString = urlString.substring(0, urlString.length - 1); - } - urlString += this.route.toString(); - } else { - if (urlString.endsWith('/')) { - urlString = urlString.substring(0, urlString.length - 1); - } - urlString += '/' + this.route.toString(); - } + if (queryString) { + const keyValues: Array<string> = queryString.split("&"); + for (const keyValue of keyValues) { + if (keyValue.split("=")[0] && keyValue.split("=")[1]) { + const key: string | undefined = keyValue.split("=")[0]; + const value: string | undefined = keyValue.split("=")[1]; + if (key && value) { + this._params[key] = value; + } + } + } + } + } - if (Object.keys(this.params).length > 0) { - urlString += '?'; + public isHttps(): boolean { + return this.protocol === Protocol.HTTPS; + } - for (const key of Object.keys(this.params)) { - urlString += key + '=' + this.params[key] + '&'; - } + public override toString(): string { + let urlString: string = `${this.protocol}${this.hostname || this.email}`; + if (!this.email && !urlString.startsWith("mailto:")) { + if (this.route && this.route.toString().startsWith("/")) { + if (urlString.endsWith("/")) { + urlString = urlString.substring(0, urlString.length - 1); + } + urlString += this.route.toString(); + } else { + if (urlString.endsWith("/")) { + urlString = urlString.substring(0, urlString.length - 1); + } + urlString += "/" + this.route.toString(); + } - urlString = urlString.substring(0, urlString.length - 1); // remove last & - } + if (Object.keys(this.params).length > 0) { + urlString += "?"; + + for (const key of Object.keys(this.params)) { + urlString += key + "=" + this.params[key] + "&"; } - return urlString; + urlString = urlString.substring(0, urlString.length - 1); // remove last & + } } - public static fromURL(url: URL): URL { - return URL.fromString(url.toString()); + return urlString; + } + + public static fromURL(url: URL): URL { + return URL.fromString(url.toString()); + } + + public static fromString(url: string): URL { + let protocol: Protocol = Protocol.HTTPS; + + if (url.startsWith("https://")) { + protocol = Protocol.HTTPS; + url = url.replace("https://", ""); } - public static fromString(url: string): URL { - let protocol: Protocol = Protocol.HTTPS; - - if (url.startsWith('https://')) { - protocol = Protocol.HTTPS; - url = url.replace('https://', ''); - } - - if (url.startsWith('http://')) { - protocol = Protocol.HTTP; - url = url.replace('http://', ''); - } - - if (url.startsWith('wss://')) { - protocol = Protocol.WSS; - url = url.replace('wss://', ''); - } - - if (url.startsWith('ws://')) { - protocol = Protocol.WS; - url = url.replace('ws://', ''); - } - - if (url.startsWith('mongodb://')) { - protocol = Protocol.MONGO_DB; - url = url.replace('mongodb://', ''); - } - - if (url.startsWith('mailto:')) { - protocol = Protocol.MAIL; - url = url.replace('mailto:', ''); - } - - const hostname: Hostname = new Hostname(url.split('/')[0] || ''); - - let route: Route | undefined; - - if (url.split('/').length > 1) { - const paths: Array<string> = url.split('/'); - paths.shift(); - route = new Route(paths.join('/').split('?')[0]); - } - - const queryString: string | undefined = url.split('?')[1] || ''; - - return new URL(protocol, hostname, route, queryString); + if (url.startsWith("http://")) { + protocol = Protocol.HTTP; + url = url.replace("http://", ""); } - public removeQueryString(): URL { - return URL.fromString(this.toString().split('?')[0] || ''); + if (url.startsWith("wss://")) { + protocol = Protocol.WSS; + url = url.replace("wss://", ""); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.URL, - value: (this as URL).toString(), - }; + if (url.startsWith("ws://")) { + protocol = Protocol.WS; + url = url.replace("ws://", ""); } - public static override fromJSON(json: JSONObject): URL { - if (json['_type'] === ObjectType.URL) { - return URL.fromString((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); + if (url.startsWith("mongodb://")) { + protocol = Protocol.MONGO_DB; + url = url.replace("mongodb://", ""); } - public addRoute(route: Route | string): URL { - if (typeof route === Typeof.String) { - this.route.addRoute(new Route(route.toString())); - } - - if (route instanceof Route) { - this.route.addRoute(route); - } - - return this; + if (url.startsWith("mailto:")) { + protocol = Protocol.MAIL; + url = url.replace("mailto:", ""); } - public addQueryParam( - paramName: string, - value: string, - encode?: boolean | undefined - ): URL { - if (encode) { - value = encodeURIComponent(value); - } + const hostname: Hostname = new Hostname(url.split("/")[0] || ""); - this.params[paramName] = value; - return this; + let route: Route | undefined; + + if (url.split("/").length > 1) { + const paths: Array<string> = url.split("/"); + paths.shift(); + route = new Route(paths.join("/").split("?")[0]); } - public getQueryParam(paramName: string): string | null { - return this.params[paramName] || null; + const queryString: string | undefined = url.split("?")[1] || ""; + + return new URL(protocol, hostname, route, queryString); + } + + public removeQueryString(): URL { + return URL.fromString(this.toString().split("?")[0] || ""); + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.URL, + value: (this as URL).toString(), + }; + } + + public static override fromJSON(json: JSONObject): URL { + if (json["_type"] === ObjectType.URL) { + return URL.fromString((json["value"] as string) || ""); } - public addQueryParams(params: Dictionary<string>): URL { - this.params = { - ...this.params, - ...params, - }; - return this; + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + public addRoute(route: Route | string): URL { + if (typeof route === Typeof.String) { + this.route.addRoute(new Route(route.toString())); } - public getLastRoute(getFromLastRoute?: number): Route | null { - const paths: Array<string> = this.route.toString().split('/'); - - if (paths.length > 0) { - if (!getFromLastRoute) { - return new Route('/' + paths[paths.length - 1]); - } - return new Route( - '/' + paths[paths.length - (1 + getFromLastRoute)] - ); - } - - return null; + if (route instanceof Route) { + this.route.addRoute(route); } - protected static override toDatabase( - value: URL | FindOperator<URL> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = URL.fromString(value); - } + return this; + } - return value.toString(); - } - - return null; + public addQueryParam( + paramName: string, + value: string, + encode?: boolean | undefined, + ): URL { + if (encode) { + value = encodeURIComponent(value); } - protected static override fromDatabase(_value: string): URL | null { - if (_value) { - return URL.fromString(_value); - } + this.params[paramName] = value; + return this; + } - return null; + public getQueryParam(paramName: string): string | null { + return this.params[paramName] || null; + } + + public addQueryParams(params: Dictionary<string>): URL { + this.params = { + ...this.params, + ...params, + }; + return this; + } + + public getLastRoute(getFromLastRoute?: number): Route | null { + const paths: Array<string> = this.route.toString().split("/"); + + if (paths.length > 0) { + if (!getFromLastRoute) { + return new Route("/" + paths[paths.length - 1]); + } + return new Route("/" + paths[paths.length - (1 + getFromLastRoute)]); } + + return null; + } + + protected static override toDatabase( + value: URL | FindOperator<URL>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = URL.fromString(value); + } + + return value.toString(); + } + + return null; + } + + protected static override fromDatabase(_value: string): URL | null { + if (_value) { + return URL.fromString(_value); + } + + return null; + } } diff --git a/Common/Types/Alerts/AlertEventType.ts b/Common/Types/Alerts/AlertEventType.ts index d4763fd71a..f62670f51b 100644 --- a/Common/Types/Alerts/AlertEventType.ts +++ b/Common/Types/Alerts/AlertEventType.ts @@ -1,14 +1,14 @@ enum AlertEventType { - Identified = 'Identified', - Acknowledged = 'Acknowledged', - Resolved = 'Resolved', - InvestigationNoteCreated = 'Investigation note created', - InvestigationNoteUpdated = 'Investigation note updated', - ScheduledMaintenanceCreated = 'Scheduled maintenance created', - ScheduledMaintenanceNoteCreated = 'Scheduled maintenance note created', - ScheduledMaintenanceResolved = 'Scheduled maintenance resolved', - ScheduledMaintenanceCancelled = 'Scheduled maintenance cancelled', - AnnouncementNotificationCreated = 'Announcement notification created', + Identified = "Identified", + Acknowledged = "Acknowledged", + Resolved = "Resolved", + InvestigationNoteCreated = "Investigation note created", + InvestigationNoteUpdated = "Investigation note updated", + ScheduledMaintenanceCreated = "Scheduled maintenance created", + ScheduledMaintenanceNoteCreated = "Scheduled maintenance note created", + ScheduledMaintenanceResolved = "Scheduled maintenance resolved", + ScheduledMaintenanceCancelled = "Scheduled maintenance cancelled", + AnnouncementNotificationCreated = "Announcement notification created", } export default AlertEventType; diff --git a/Common/Types/Alerts/AlertType.ts b/Common/Types/Alerts/AlertType.ts index 5baa256696..f6159e213e 100644 --- a/Common/Types/Alerts/AlertType.ts +++ b/Common/Types/Alerts/AlertType.ts @@ -1,9 +1,9 @@ enum AlertType { - Webhook = 'Webhook', - Email = 'Email', - SMS = 'SMS', - Call = 'Call', - PushNotification = 'PushNotification', + Webhook = "Webhook", + Email = "Email", + SMS = "SMS", + Call = "Call", + PushNotification = "PushNotification", } export default AlertType; diff --git a/Common/Types/AnalyticsDatabase/AnalyticsTableEngine.ts b/Common/Types/AnalyticsDatabase/AnalyticsTableEngine.ts index 55c909e0c8..28f91c6855 100644 --- a/Common/Types/AnalyticsDatabase/AnalyticsTableEngine.ts +++ b/Common/Types/AnalyticsDatabase/AnalyticsTableEngine.ts @@ -1,5 +1,5 @@ enum AnalyticsTableEngine { - MergeTree = 'MergeTree', + MergeTree = "MergeTree", } export default AnalyticsTableEngine; diff --git a/Common/Types/AnalyticsDatabase/TableColumn.ts b/Common/Types/AnalyticsDatabase/TableColumn.ts index 72a62480af..a18f1885ec 100644 --- a/Common/Types/AnalyticsDatabase/TableColumn.ts +++ b/Common/Types/AnalyticsDatabase/TableColumn.ts @@ -1,163 +1,160 @@ -import NestedModel from '../../AnalyticsModels/NestedModel'; -import TableColumnType from '../AnalyticsDatabase/TableColumnType'; -import { ColumnAccessControl } from '../BaseDatabase/AccessControl'; -import ColumnBillingAccessControl from '../BaseDatabase/ColumnBillingAccessControl'; -import { JSONValue } from '../JSON'; +import NestedModel from "../../AnalyticsModels/NestedModel"; +import TableColumnType from "../AnalyticsDatabase/TableColumnType"; +import { ColumnAccessControl } from "../BaseDatabase/AccessControl"; +import ColumnBillingAccessControl from "../BaseDatabase/ColumnBillingAccessControl"; +import { JSONValue } from "../JSON"; export default class AnalyticsTableColumn { - private _key: string = 'id'; + private _key: string = "id"; - public get key(): string { - return this._key; - } - public set key(v: string) { - this._key = v; + public get key(): string { + return this._key; + } + public set key(v: string) { + this._key = v; + } + + private _title: string = ""; + public get title(): string { + return this._title; + } + public set title(v: string) { + this._title = v; + } + + private _description: string = ""; + public get description(): string { + return this._description; + } + public set description(v: string) { + this._description = v; + } + + private _required: boolean = false; + public get required(): boolean { + return this._required; + } + public set required(v: boolean) { + this._required = v; + } + + private _isTenantId: boolean = false; + public get isTenantId(): boolean { + return this._isTenantId; + } + public set isTenantId(v: boolean) { + this._isTenantId = v; + } + + private _type: TableColumnType = TableColumnType.Text; + public get type(): TableColumnType { + return this._type; + } + public set type(v: TableColumnType) { + this._type = v; + } + + private _forceGetDefaultValueOnCreate?: + | (() => Date | string | number | boolean) + | undefined; + public get forceGetDefaultValueOnCreate(): + | (() => Date | string | number | boolean) + | undefined { + return this._forceGetDefaultValueOnCreate; + } + public set forceGetDefaultValueOnCreate( + v: (() => Date | string | number | boolean) | undefined, + ) { + this._forceGetDefaultValueOnCreate = v; + } + + private _defaultValue: JSONValue | undefined; + public get defaultValue(): JSONValue { + return this._defaultValue; + } + public set defaultValue(v: JSONValue) { + this._defaultValue = v; + } + + public get isDefaultValueColumn(): boolean { + return Boolean(this.defaultValue !== undefined); + } + + private _billingAccessControl?: ColumnBillingAccessControl | undefined; + public get billingAccessControl(): ColumnBillingAccessControl | undefined { + return this._billingAccessControl; + } + public set billingAccessControl(v: ColumnBillingAccessControl | undefined) { + this._billingAccessControl = v; + } + + private _allowAccessIfSubscriptionIsUnpaid: boolean = false; + public get allowAccessIfSubscriptionIsUnpaid(): boolean { + return this._allowAccessIfSubscriptionIsUnpaid; + } + public set allowAccessIfSubscriptionIsUnpaid(v: boolean) { + this._allowAccessIfSubscriptionIsUnpaid = v; + } + + private _accessControl: ColumnAccessControl | undefined; + public get accessControl(): ColumnAccessControl | undefined { + return this._accessControl; + } + public set accessControl(v: ColumnAccessControl | undefined) { + this._accessControl = v; + } + + private _nestedModel?: NestedModel | undefined; + public get nestedModel(): NestedModel | undefined { + return this._nestedModel; + } + public set nestedModel(v: NestedModel | undefined) { + this._nestedModel = v; + } + + private _nestedModelType?: { new (): NestedModel } | undefined; + public get nestedModelType(): { new (): NestedModel } | undefined { + return this._nestedModelType; + } + public set nestedModelType(v: { new (): NestedModel } | undefined) { + this._nestedModelType = v; + } + + public constructor(data: { + key: string; + nestedModelType?: { new (): NestedModel } | undefined; + title: string; + description: string; + required: boolean; + defaultValue?: JSONValue | undefined; + type: TableColumnType; + billingAccessControl?: ColumnBillingAccessControl | undefined; + isTenantId?: boolean | undefined; + accessControl?: ColumnAccessControl | undefined; + allowAccessIfSubscriptionIsUnpaid?: boolean | undefined; + forceGetDefaultValueOnCreate?: + | (() => Date | string | number | boolean) + | undefined; + }) { + if (data.type === TableColumnType.NestedModel && !data.nestedModelType) { + throw new Error("NestedModel is required when type is NestedModel"); } - private _title: string = ''; - public get title(): string { - return this._title; - } - public set title(v: string) { - this._title = v; - } - - private _description: string = ''; - public get description(): string { - return this._description; - } - public set description(v: string) { - this._description = v; - } - - private _required: boolean = false; - public get required(): boolean { - return this._required; - } - public set required(v: boolean) { - this._required = v; - } - - private _isTenantId: boolean = false; - public get isTenantId(): boolean { - return this._isTenantId; - } - public set isTenantId(v: boolean) { - this._isTenantId = v; - } - - private _type: TableColumnType = TableColumnType.Text; - public get type(): TableColumnType { - return this._type; - } - public set type(v: TableColumnType) { - this._type = v; - } - - private _forceGetDefaultValueOnCreate?: - | (() => Date | string | number | boolean) - | undefined; - public get forceGetDefaultValueOnCreate(): - | (() => Date | string | number | boolean) - | undefined { - return this._forceGetDefaultValueOnCreate; - } - public set forceGetDefaultValueOnCreate( - v: (() => Date | string | number | boolean) | undefined - ) { - this._forceGetDefaultValueOnCreate = v; - } - - private _defaultValue: JSONValue | undefined; - public get defaultValue(): JSONValue { - return this._defaultValue; - } - public set defaultValue(v: JSONValue) { - this._defaultValue = v; - } - - public get isDefaultValueColumn(): boolean { - return Boolean(this.defaultValue !== undefined); - } - - private _billingAccessControl?: ColumnBillingAccessControl | undefined; - public get billingAccessControl(): ColumnBillingAccessControl | undefined { - return this._billingAccessControl; - } - public set billingAccessControl(v: ColumnBillingAccessControl | undefined) { - this._billingAccessControl = v; - } - - private _allowAccessIfSubscriptionIsUnpaid: boolean = false; - public get allowAccessIfSubscriptionIsUnpaid(): boolean { - return this._allowAccessIfSubscriptionIsUnpaid; - } - public set allowAccessIfSubscriptionIsUnpaid(v: boolean) { - this._allowAccessIfSubscriptionIsUnpaid = v; - } - - private _accessControl: ColumnAccessControl | undefined; - public get accessControl(): ColumnAccessControl | undefined { - return this._accessControl; - } - public set accessControl(v: ColumnAccessControl | undefined) { - this._accessControl = v; - } - - private _nestedModel?: NestedModel | undefined; - public get nestedModel(): NestedModel | undefined { - return this._nestedModel; - } - public set nestedModel(v: NestedModel | undefined) { - this._nestedModel = v; - } - - private _nestedModelType?: { new (): NestedModel } | undefined; - public get nestedModelType(): { new (): NestedModel } | undefined { - return this._nestedModelType; - } - public set nestedModelType(v: { new (): NestedModel } | undefined) { - this._nestedModelType = v; - } - - public constructor(data: { - key: string; - nestedModelType?: { new (): NestedModel } | undefined; - title: string; - description: string; - required: boolean; - defaultValue?: JSONValue | undefined; - type: TableColumnType; - billingAccessControl?: ColumnBillingAccessControl | undefined; - isTenantId?: boolean | undefined; - accessControl?: ColumnAccessControl | undefined; - allowAccessIfSubscriptionIsUnpaid?: boolean | undefined; - forceGetDefaultValueOnCreate?: - | (() => Date | string | number | boolean) - | undefined; - }) { - if ( - data.type === TableColumnType.NestedModel && - !data.nestedModelType - ) { - throw new Error('NestedModel is required when type is NestedModel'); - } - - this.accessControl = data.accessControl; - this.key = data.key; - this.title = data.title; - this.description = data.description; - this.required = data.required; - this.type = data.type; - this.isTenantId = data.isTenantId || false; - this.forceGetDefaultValueOnCreate = data.forceGetDefaultValueOnCreate; - this.defaultValue = data.defaultValue; - this.billingAccessControl = data.billingAccessControl; - this.allowAccessIfSubscriptionIsUnpaid = - data.allowAccessIfSubscriptionIsUnpaid || false; - if (data.nestedModelType) { - this.nestedModel = new data.nestedModelType(); - this.nestedModelType = data.nestedModelType; - } + this.accessControl = data.accessControl; + this.key = data.key; + this.title = data.title; + this.description = data.description; + this.required = data.required; + this.type = data.type; + this.isTenantId = data.isTenantId || false; + this.forceGetDefaultValueOnCreate = data.forceGetDefaultValueOnCreate; + this.defaultValue = data.defaultValue; + this.billingAccessControl = data.billingAccessControl; + this.allowAccessIfSubscriptionIsUnpaid = + data.allowAccessIfSubscriptionIsUnpaid || false; + if (data.nestedModelType) { + this.nestedModel = new data.nestedModelType(); + this.nestedModelType = data.nestedModelType; } + } } diff --git a/Common/Types/AnalyticsDatabase/TableColumnType.ts b/Common/Types/AnalyticsDatabase/TableColumnType.ts index aedb8c1f2d..7ba46cd8cf 100644 --- a/Common/Types/AnalyticsDatabase/TableColumnType.ts +++ b/Common/Types/AnalyticsDatabase/TableColumnType.ts @@ -1,16 +1,16 @@ enum ColumnType { - ObjectID = 'Object ID', - Date = 'Date', - Boolean = 'Boolean', - Number = 'Number', - Text = 'Text', - NestedModel = 'Nested Model', - JSON = 'JSON', - JSONArray = 'JSON Array', - Decimal = 'Decimal', - ArrayNumber = 'Array of Numbers', - ArrayText = 'Array of Text', - LongNumber = 'Long Number', + ObjectID = "Object ID", + Date = "Date", + Boolean = "Boolean", + Number = "Number", + Text = "Text", + NestedModel = "Nested Model", + JSON = "JSON", + JSONArray = "JSON Array", + Decimal = "Decimal", + ArrayNumber = "Array of Numbers", + ArrayText = "Array of Text", + LongNumber = "Long Number", } export default ColumnType; diff --git a/Common/Types/AppEnvironment.ts b/Common/Types/AppEnvironment.ts index 4568889829..a843411d4f 100644 --- a/Common/Types/AppEnvironment.ts +++ b/Common/Types/AppEnvironment.ts @@ -1,7 +1,7 @@ enum AppEnvironment { - Production = 'production', - Development = 'development', - Test = 'test', + Production = "production", + Development = "development", + Test = "test", } export default AppEnvironment; diff --git a/Common/Types/ApplicationLog/ApplicationLogType.ts b/Common/Types/ApplicationLog/ApplicationLogType.ts index 82ce1ac6c5..a2faeb27b1 100644 --- a/Common/Types/ApplicationLog/ApplicationLogType.ts +++ b/Common/Types/ApplicationLog/ApplicationLogType.ts @@ -1,7 +1,7 @@ enum ApplicationLogType { - Info = 'Info', - Error = 'Error', - Warning = 'Warning', + Info = "Info", + Error = "Error", + Warning = "Warning", } export default ApplicationLogType; diff --git a/Common/Types/ArrayUtil.ts b/Common/Types/ArrayUtil.ts index 9a292e3665..f6a5a1c6ac 100644 --- a/Common/Types/ArrayUtil.ts +++ b/Common/Types/ArrayUtil.ts @@ -1,86 +1,84 @@ -import ObjectID from './ObjectID'; +import ObjectID from "./ObjectID"; export default class ArrayUtil { - public static removeDuplicates(array: Array<any>): Array<any> { - return array.filter((value: any, index: number, self: Array<any>) => { - return self.indexOf(value) === index; - }); + public static removeDuplicates(array: Array<any>): Array<any> { + return array.filter((value: any, index: number, self: Array<any>) => { + return self.indexOf(value) === index; + }); + } + + public static removeDuplicatesFromObjectIDArray( + array: Array<ObjectID>, + ): Array<ObjectID> { + const distinctIds: Array<ObjectID> = []; + + for (const objectId of array) { + if ( + distinctIds.filter((item: ObjectID) => { + return item.toString() === objectId.toString(); + }).length > 0 + ) { + continue; + } + + distinctIds.push(objectId); } - public static removeDuplicatesFromObjectIDArray( - array: Array<ObjectID> - ): Array<ObjectID> { - const distinctIds: Array<ObjectID> = []; + return distinctIds; + } - for (const objectId of array) { - if ( - distinctIds.filter((item: ObjectID) => { - return item.toString() === objectId.toString(); - }).length > 0 - ) { - continue; - } - - distinctIds.push(objectId); - } - - return distinctIds; + public static isEqual(a: Array<any>, b: Array<any>): boolean { + // Check if the arrays have the same length + if (a.length !== b.length) { + return false; } - public static isEqual(a: Array<any>, b: Array<any>): boolean { - // Check if the arrays have the same length - if (a.length !== b.length) { - return false; - } + // Sort both arrays by their JSON representation + const sortedArr1: string = JSON.stringify([...a].sort()); + const sortedArr2: string = JSON.stringify([...b].sort()); - // Sort both arrays by their JSON representation - const sortedArr1: string = JSON.stringify([...a].sort()); - const sortedArr2: string = JSON.stringify([...b].sort()); + // Compare the sorted arrays + return sortedArr1 === sortedArr2; + } - // Compare the sorted arrays - return sortedArr1 === sortedArr2; + public static sortByFieldName(fieldName: string): (a: any, b: any) => number { + return (a: any, b: any): number => { + if (a[fieldName] < b[fieldName]) { + return -1; + } + if (a[fieldName] > b[fieldName]) { + return 1; + } + return 0; + }; + } + + public static selectItemByRandom<T>(array: Array<T>): T { + return array[Math.floor(Math.random() * array.length)]!; + } + + public static distinctByFieldName( + array: Array<any>, + fieldName: string, + ): Array<any> { + // Get the distinct values by field name of the array + const distinctValues: Array<any> = array + .map((item: any) => { + return item[fieldName]; + }) + .filter((value: any, index: number, self: Array<any>) => { + return self.indexOf(value) === index; + }); + + // Create a new array with the distinct values + const distinctArray: Array<any> = []; + for (const value of distinctValues) { + const item: any = array.find((item: any) => { + return item[fieldName] === value; + }); + distinctArray.push(item); } - public static sortByFieldName( - fieldName: string - ): (a: any, b: any) => number { - return (a: any, b: any): number => { - if (a[fieldName] < b[fieldName]) { - return -1; - } - if (a[fieldName] > b[fieldName]) { - return 1; - } - return 0; - }; - } - - public static selectItemByRandom<T>(array: Array<T>): T { - return array[Math.floor(Math.random() * array.length)]!; - } - - public static distinctByFieldName( - array: Array<any>, - fieldName: string - ): Array<any> { - // Get the distinct values by field name of the array - const distinctValues: Array<any> = array - .map((item: any) => { - return item[fieldName]; - }) - .filter((value: any, index: number, self: Array<any>) => { - return self.indexOf(value) === index; - }); - - // Create a new array with the distinct values - const distinctArray: Array<any> = []; - for (const value of distinctValues) { - const item: any = array.find((item: any) => { - return item[fieldName] === value; - }); - distinctArray.push(item); - } - - return distinctArray; - } + return distinctArray; + } } diff --git a/Common/Types/BaseDatabase/AccessControl.ts b/Common/Types/BaseDatabase/AccessControl.ts index 079a5aaec2..b3fd2c8497 100644 --- a/Common/Types/BaseDatabase/AccessControl.ts +++ b/Common/Types/BaseDatabase/AccessControl.ts @@ -1,11 +1,11 @@ -import Permission from '../Permission'; +import Permission from "../Permission"; export interface ColumnAccessControl { - read: Array<Permission>; - create: Array<Permission>; - update: Array<Permission>; + read: Array<Permission>; + create: Array<Permission>; + update: Array<Permission>; } export interface TableAccessControl extends ColumnAccessControl { - delete: Array<Permission>; + delete: Array<Permission>; } diff --git a/Common/Types/BaseDatabase/ColumnBillingAccessControl.ts b/Common/Types/BaseDatabase/ColumnBillingAccessControl.ts index 3dde988c21..500b45cfca 100644 --- a/Common/Types/BaseDatabase/ColumnBillingAccessControl.ts +++ b/Common/Types/BaseDatabase/ColumnBillingAccessControl.ts @@ -1,7 +1,7 @@ -import { PlanSelect } from '../Billing/SubscriptionPlan'; +import { PlanSelect } from "../Billing/SubscriptionPlan"; export default interface ColumnBillingAccessControl { - create: PlanSelect; - read: PlanSelect; - update: PlanSelect; + create: PlanSelect; + read: PlanSelect; + update: PlanSelect; } diff --git a/Common/Types/BaseDatabase/DatabaseCommonInteractionProps.ts b/Common/Types/BaseDatabase/DatabaseCommonInteractionProps.ts index be2dbcfda4..c7be9c7c1a 100644 --- a/Common/Types/BaseDatabase/DatabaseCommonInteractionProps.ts +++ b/Common/Types/BaseDatabase/DatabaseCommonInteractionProps.ts @@ -1,24 +1,24 @@ -import { PlanSelect } from '../Billing/SubscriptionPlan'; -import Dictionary from '../Dictionary'; -import ObjectID from '../ObjectID'; +import { PlanSelect } from "../Billing/SubscriptionPlan"; +import Dictionary from "../Dictionary"; +import ObjectID from "../ObjectID"; import { - UserGlobalAccessPermission, - UserTenantAccessPermission, -} from '../Permission'; -import UserType from '../UserType'; + UserGlobalAccessPermission, + UserTenantAccessPermission, +} from "../Permission"; +import UserType from "../UserType"; export default interface DatabaseCommonInteractionProps { - userId?: ObjectID | undefined; - userGlobalAccessPermission?: UserGlobalAccessPermission | undefined; - userTenantAccessPermission?: - | Dictionary<UserTenantAccessPermission> // tenantId <-> UserTenantAccessPermission - | undefined; - userType?: UserType | undefined; - tenantId?: ObjectID | undefined; - isRoot?: boolean | undefined; - isMultiTenantRequest?: boolean | undefined; - ignoreHooks?: boolean | undefined; - currentPlan?: PlanSelect | undefined; - isSubscriptionUnpaid?: boolean | undefined; - isMasterAdmin?: boolean | undefined; + userId?: ObjectID | undefined; + userGlobalAccessPermission?: UserGlobalAccessPermission | undefined; + userTenantAccessPermission?: + | Dictionary<UserTenantAccessPermission> // tenantId <-> UserTenantAccessPermission + | undefined; + userType?: UserType | undefined; + tenantId?: ObjectID | undefined; + isRoot?: boolean | undefined; + isMultiTenantRequest?: boolean | undefined; + ignoreHooks?: boolean | undefined; + currentPlan?: PlanSelect | undefined; + isSubscriptionUnpaid?: boolean | undefined; + isMasterAdmin?: boolean | undefined; } diff --git a/Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts b/Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts index 4cc6a066df..f16388abdd 100644 --- a/Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts +++ b/Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts @@ -1,89 +1,88 @@ -import Permission, { UserPermission } from '../Permission'; -import DatabaseCommonInteractionProps from './DatabaseCommonInteractionProps'; +import Permission, { UserPermission } from "../Permission"; +import DatabaseCommonInteractionProps from "./DatabaseCommonInteractionProps"; export enum PermissionType { - Allow = 'Allow', - Block = 'Block', + Allow = "Allow", + Block = "Block", } export default class DatabaseCommonInteractionPropsUtil { - public static getUserPermissions( - props: DatabaseCommonInteractionProps, - permissionType: PermissionType - ): Array<UserPermission> { - // Check first if the user has Global Permissions. - // Global permissions includes all the tenantId user has access to. - // and it includes all the global permissions that applies to all the tenant, like PUBLIC. - if (!props.userGlobalAccessPermission) { - props.userGlobalAccessPermission = { - globalPermissions: [Permission.Public], - projectIds: [], - _type: 'UserGlobalAccessPermission', - }; - } - - // If the PUBLIC Permission is not found in global permissions, include it. - if ( - props.userGlobalAccessPermission && - !props.userGlobalAccessPermission.globalPermissions.includes( - Permission.Public - ) - ) { - props.userGlobalAccessPermission.globalPermissions.push( - Permission.Public - ); // add public permission if not already. - } - - // If the CurrentUser Permission is not found in global permissions, include it. - if ( - props.userId && - props.userGlobalAccessPermission && - !props.userGlobalAccessPermission.globalPermissions.includes( - Permission.CurrentUser - ) - ) { - props.userGlobalAccessPermission.globalPermissions.push( - Permission.CurrentUser - ); - } - - let userPermissions: Array<UserPermission> = []; - - // Include global permission in userPermissions. - - if ( - props.userGlobalAccessPermission && - permissionType === PermissionType.Allow - ) { - /// take global permissions. - userPermissions = - props.userGlobalAccessPermission.globalPermissions.map( - (permission: Permission) => { - return { - permission: permission, - labelIds: [], - isBlockPermission: false, - _type: 'UserPermission', - }; - } - ); - } - - if (props.tenantId && props.userTenantAccessPermission) { - // Include Tenant Permission in userPermissions. - userPermissions = [ - ...userPermissions, - ...(props.userTenantAccessPermission[ - props.tenantId.toString() - ]?.permissions.filter((userPermission: UserPermission) => { - return ( - userPermission.isBlockPermission === - (permissionType === PermissionType.Block) - ); - }) || []), - ]; - } - - return userPermissions; + public static getUserPermissions( + props: DatabaseCommonInteractionProps, + permissionType: PermissionType, + ): Array<UserPermission> { + // Check first if the user has Global Permissions. + // Global permissions includes all the tenantId user has access to. + // and it includes all the global permissions that applies to all the tenant, like PUBLIC. + if (!props.userGlobalAccessPermission) { + props.userGlobalAccessPermission = { + globalPermissions: [Permission.Public], + projectIds: [], + _type: "UserGlobalAccessPermission", + }; } + + // If the PUBLIC Permission is not found in global permissions, include it. + if ( + props.userGlobalAccessPermission && + !props.userGlobalAccessPermission.globalPermissions.includes( + Permission.Public, + ) + ) { + props.userGlobalAccessPermission.globalPermissions.push( + Permission.Public, + ); // add public permission if not already. + } + + // If the CurrentUser Permission is not found in global permissions, include it. + if ( + props.userId && + props.userGlobalAccessPermission && + !props.userGlobalAccessPermission.globalPermissions.includes( + Permission.CurrentUser, + ) + ) { + props.userGlobalAccessPermission.globalPermissions.push( + Permission.CurrentUser, + ); + } + + let userPermissions: Array<UserPermission> = []; + + // Include global permission in userPermissions. + + if ( + props.userGlobalAccessPermission && + permissionType === PermissionType.Allow + ) { + /// take global permissions. + userPermissions = props.userGlobalAccessPermission.globalPermissions.map( + (permission: Permission) => { + return { + permission: permission, + labelIds: [], + isBlockPermission: false, + _type: "UserPermission", + }; + }, + ); + } + + if (props.tenantId && props.userTenantAccessPermission) { + // Include Tenant Permission in userPermissions. + userPermissions = [ + ...userPermissions, + ...(props.userTenantAccessPermission[ + props.tenantId.toString() + ]?.permissions.filter((userPermission: UserPermission) => { + return ( + userPermission.isBlockPermission === + (permissionType === PermissionType.Block) + ); + }) || []), + ]; + } + + return userPermissions; + } } diff --git a/Common/Types/BaseDatabase/DatabaseType.ts b/Common/Types/BaseDatabase/DatabaseType.ts index 69421e347c..a88f19636c 100644 --- a/Common/Types/BaseDatabase/DatabaseType.ts +++ b/Common/Types/BaseDatabase/DatabaseType.ts @@ -1,6 +1,6 @@ enum DatabaseType { - Database = 'Database', - AnalyticsDatabase = 'AnalyticsDatabase', + Database = "Database", + AnalyticsDatabase = "AnalyticsDatabase", } export default DatabaseType; diff --git a/Common/Types/BaseDatabase/EnableWorkflowOn.ts b/Common/Types/BaseDatabase/EnableWorkflowOn.ts index 2c20a53705..b54c9eb01e 100644 --- a/Common/Types/BaseDatabase/EnableWorkflowOn.ts +++ b/Common/Types/BaseDatabase/EnableWorkflowOn.ts @@ -1,6 +1,6 @@ export default interface EnableWorkflowOn { - create?: boolean | undefined; - update?: boolean | undefined; - delete?: boolean | undefined; - read?: boolean | undefined; + create?: boolean | undefined; + update?: boolean | undefined; + delete?: boolean | undefined; + read?: boolean | undefined; } diff --git a/Common/Types/BaseDatabase/EqualToOrNull.ts b/Common/Types/BaseDatabase/EqualToOrNull.ts index 6a90d5b6e3..2ac88927cc 100644 --- a/Common/Types/BaseDatabase/EqualToOrNull.ts +++ b/Common/Types/BaseDatabase/EqualToOrNull.ts @@ -1,37 +1,37 @@ -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import SerializableObject from '../SerializableObject'; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import SerializableObject from "../SerializableObject"; export default class EqualToOrNull extends SerializableObject { - private _value!: string; - public get value(): string { - return this._value; - } - public set value(v: string) { - this._value = v; + private _value!: string; + public get value(): string { + return this._value; + } + public set value(v: string) { + this._value = v; + } + + public constructor(value: string) { + super(); + this.value = value; + } + + public override toString(): string { + return this.value; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.EqualToOrNull, + value: (this as EqualToOrNull).toString(), + }; + } + + public static override fromJSON(json: JSONObject): EqualToOrNull { + if (json["_type"] === ObjectType.EqualToOrNull) { + return new EqualToOrNull(json["value"] as string); } - public constructor(value: string) { - super(); - this.value = value; - } - - public override toString(): string { - return this.value; - } - - public override toJSON(): JSONObject { - return { - _type: ObjectType.EqualToOrNull, - value: (this as EqualToOrNull).toString(), - }; - } - - public static override fromJSON(json: JSONObject): EqualToOrNull { - if (json['_type'] === ObjectType.EqualToOrNull) { - return new EqualToOrNull(json['value'] as string); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } } diff --git a/Common/Types/BaseDatabase/GreaterThan.ts b/Common/Types/BaseDatabase/GreaterThan.ts index e7c76faca9..9d082bb26a 100644 --- a/Common/Types/BaseDatabase/GreaterThan.ts +++ b/Common/Types/BaseDatabase/GreaterThan.ts @@ -1,24 +1,24 @@ -import CompareBase from '../Database/CompareBase'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; +import CompareBase from "../Database/CompareBase"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; export default class GreaterThan extends CompareBase { - public constructor(value: number | Date) { - super(value); + public constructor(value: number | Date) { + super(value); + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.GreaterThan, + value: (this as GreaterThan).toString(), + }; + } + + public static override fromJSON(json: JSONObject): GreaterThan { + if (json["_type"] === ObjectType.GreaterThan) { + return new GreaterThan(json["value"] as number | Date); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.GreaterThan, - value: (this as GreaterThan).toString(), - }; - } - - public static override fromJSON(json: JSONObject): GreaterThan { - if (json['_type'] === ObjectType.GreaterThan) { - return new GreaterThan(json['value'] as number | Date); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } } diff --git a/Common/Types/BaseDatabase/GreaterThanOrEqual.ts b/Common/Types/BaseDatabase/GreaterThanOrEqual.ts index 2cbdeb4a18..02e4bb2694 100644 --- a/Common/Types/BaseDatabase/GreaterThanOrEqual.ts +++ b/Common/Types/BaseDatabase/GreaterThanOrEqual.ts @@ -1,24 +1,24 @@ -import CompareBase from '../Database/CompareBase'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; +import CompareBase from "../Database/CompareBase"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; export default class GreaterThanOrEqual extends CompareBase { - public constructor(value: number | Date) { - super(value); + public constructor(value: number | Date) { + super(value); + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.GreaterThanOrEqual, + value: (this as GreaterThanOrEqual).toString(), + }; + } + + public static override fromJSON(json: JSONObject): GreaterThanOrEqual { + if (json["_type"] === ObjectType.GreaterThanOrEqual) { + return new GreaterThanOrEqual(json["value"] as number | Date); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.GreaterThanOrEqual, - value: (this as GreaterThanOrEqual).toString(), - }; - } - - public static override fromJSON(json: JSONObject): GreaterThanOrEqual { - if (json['_type'] === ObjectType.GreaterThanOrEqual) { - return new GreaterThanOrEqual(json['value'] as number | Date); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } } diff --git a/Common/Types/BaseDatabase/InBetween.ts b/Common/Types/BaseDatabase/InBetween.ts index 164fe8a3eb..dc714e2684 100644 --- a/Common/Types/BaseDatabase/InBetween.ts +++ b/Common/Types/BaseDatabase/InBetween.ts @@ -1,77 +1,77 @@ -import OneUptimeDate from '../Date'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import SerializableObject from '../SerializableObject'; +import OneUptimeDate from "../Date"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import SerializableObject from "../SerializableObject"; export default class InBetween extends SerializableObject { - private _startValue!: number | Date | string; - public get startValue(): number | Date | string { - return this._startValue; - } - public set startValue(v: number | Date | string) { - this._startValue = v; + private _startValue!: number | Date | string; + public get startValue(): number | Date | string { + return this._startValue; + } + public set startValue(v: number | Date | string) { + this._startValue = v; + } + + private _endValue!: number | Date | string; + public get endValue(): number | Date | string { + return this._endValue; + } + public set endValue(v: number | Date | string) { + this._endValue = v; + } + + public constructor( + startValue: number | Date | string, + endValue: number | Date | string, + ) { + super(); + this.endValue = endValue; + this.startValue = startValue; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.InBetween, + startValue: (this as InBetween).startValue, + endValue: (this as InBetween).endValue, + }; + } + + public static override fromJSON(json: JSONObject): InBetween { + if (json["_type"] === ObjectType.InBetween) { + return new InBetween( + json["startValue"] as number | Date | string, + json["endValue"] as number | Date | string, + ); } - private _endValue!: number | Date | string; - public get endValue(): number | Date | string { - return this._endValue; - } - public set endValue(v: number | Date | string) { - this._endValue = v; + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + public override toString(): string { + let startValue: number | Date | string = this.startValue; + let endValue: number | Date | string = this.endValue; + + if (startValue instanceof Date) { + startValue = OneUptimeDate.asDateForDatabaseQuery(startValue); } - public constructor( - startValue: number | Date | string, - endValue: number | Date | string - ) { - super(); - this.endValue = endValue; - this.startValue = startValue; + if (endValue instanceof Date) { + endValue = OneUptimeDate.asDateForDatabaseQuery(endValue); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.InBetween, - startValue: (this as InBetween).startValue, - endValue: (this as InBetween).endValue, - }; + if (startValue.toString() === endValue.toString()) { + return this.startValue.toString(); } - public static override fromJSON(json: JSONObject): InBetween { - if (json['_type'] === ObjectType.InBetween) { - return new InBetween( - json['startValue'] as number | Date | string, - json['endValue'] as number | Date | string - ); - } + return this.startValue.toString() + " - " + this.endValue.toString(); + } - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } + public toStartValueString(): string { + return this.startValue.toString(); + } - public override toString(): string { - let startValue: number | Date | string = this.startValue; - let endValue: number | Date | string = this.endValue; - - if (startValue instanceof Date) { - startValue = OneUptimeDate.asDateForDatabaseQuery(startValue); - } - - if (endValue instanceof Date) { - endValue = OneUptimeDate.asDateForDatabaseQuery(endValue); - } - - if (startValue.toString() === endValue.toString()) { - return this.startValue.toString(); - } - - return this.startValue.toString() + ' - ' + this.endValue.toString(); - } - - public toStartValueString(): string { - return this.startValue.toString(); - } - - public toEndValueString(): string { - return this.endValue.toString(); - } + public toEndValueString(): string { + return this.endValue.toString(); + } } diff --git a/Common/Types/BaseDatabase/Includes.ts b/Common/Types/BaseDatabase/Includes.ts index bfd0c3d6c3..efeaf1401d 100644 --- a/Common/Types/BaseDatabase/Includes.ts +++ b/Common/Types/BaseDatabase/Includes.ts @@ -1,47 +1,45 @@ -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import JSONFunctions from '../JSONFunctions'; -import ObjectID from '../ObjectID'; -import SerializableObject from '../SerializableObject'; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import JSONFunctions from "../JSONFunctions"; +import ObjectID from "../ObjectID"; +import SerializableObject from "../SerializableObject"; export type IncludesType = Array<string> | Array<ObjectID>; export default class Includes extends SerializableObject { - private _values: IncludesType = []; + private _values: IncludesType = []; - public get values(): IncludesType { - return this._values; + public get values(): IncludesType { + return this._values; + } + + public set values(v: IncludesType) { + this._values = v; + } + + public constructor(values: IncludesType) { + super(); + this.values = values; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Includes, + value: (this as Includes)._values, + }; + } + + public static override fromJSON(json: JSONObject): Includes { + if (json["_type"] === ObjectType.Includes) { + const valuesArray: Array<string> = []; + + for (const value of (json["value"] as Array<string>) || []) { + valuesArray.push(JSONFunctions.deserializeValue(value) as string); + } + + return new Includes(valuesArray); } - public set values(v: IncludesType) { - this._values = v; - } - - public constructor(values: IncludesType) { - super(); - this.values = values; - } - - public override toJSON(): JSONObject { - return { - _type: ObjectType.Includes, - value: (this as Includes)._values, - }; - } - - public static override fromJSON(json: JSONObject): Includes { - if (json['_type'] === ObjectType.Includes) { - const valuesArray: Array<string> = []; - - for (const value of (json['value'] as Array<string>) || []) { - valuesArray.push( - JSONFunctions.deserializeValue(value) as string - ); - } - - return new Includes(valuesArray); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } } diff --git a/Common/Types/BaseDatabase/IsNull.ts b/Common/Types/BaseDatabase/IsNull.ts index 361d9e5f5d..d5608d44fe 100644 --- a/Common/Types/BaseDatabase/IsNull.ts +++ b/Common/Types/BaseDatabase/IsNull.ts @@ -1,28 +1,28 @@ -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import SerializableObject from '../SerializableObject'; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import SerializableObject from "../SerializableObject"; export default class IsNull extends SerializableObject { - public constructor() { - super(); + public constructor() { + super(); + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.IsNull, + value: null, + }; + } + + public static override fromJSON(json: JSONObject): IsNull { + if (json["_type"] === ObjectType.IsNull) { + return new IsNull(); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.IsNull, - value: null, - }; - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } - public static override fromJSON(json: JSONObject): IsNull { - if (json['_type'] === ObjectType.IsNull) { - return new IsNull(); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - public override toString(): string { - return ''; - } + public override toString(): string { + return ""; + } } diff --git a/Common/Types/BaseDatabase/LessThan.ts b/Common/Types/BaseDatabase/LessThan.ts index 47753e6e0b..c013ecd8a9 100644 --- a/Common/Types/BaseDatabase/LessThan.ts +++ b/Common/Types/BaseDatabase/LessThan.ts @@ -1,24 +1,24 @@ -import CompareBase from '../Database/CompareBase'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; +import CompareBase from "../Database/CompareBase"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; export default class LessThan extends CompareBase { - public constructor(value: number | Date) { - super(value); + public constructor(value: number | Date) { + super(value); + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.LessThan, + value: (this as LessThan).toString(), + }; + } + + public static override fromJSON(json: JSONObject): LessThan { + if (json["_type"] === ObjectType.LessThan) { + return new LessThan(json["value"] as number | Date); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.LessThan, - value: (this as LessThan).toString(), - }; - } - - public static override fromJSON(json: JSONObject): LessThan { - if (json['_type'] === ObjectType.LessThan) { - return new LessThan(json['value'] as number | Date); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } } diff --git a/Common/Types/BaseDatabase/LessThanOrEqual.ts b/Common/Types/BaseDatabase/LessThanOrEqual.ts index 455950edae..cb7f7cbe33 100644 --- a/Common/Types/BaseDatabase/LessThanOrEqual.ts +++ b/Common/Types/BaseDatabase/LessThanOrEqual.ts @@ -1,24 +1,24 @@ -import CompareBase from '../Database/CompareBase'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; +import CompareBase from "../Database/CompareBase"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; export default class LessThanOrEqual extends CompareBase { - public constructor(value: number | Date) { - super(value); + public constructor(value: number | Date) { + super(value); + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.LessThanOrEqual, + value: (this as LessThanOrEqual).toString(), + }; + } + + public static override fromJSON(json: JSONObject): LessThanOrEqual { + if (json["_type"] === ObjectType.LessThanOrEqual) { + return new LessThanOrEqual(json["value"] as number | Date); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.LessThanOrEqual, - value: (this as LessThanOrEqual).toString(), - }; - } - - public static override fromJSON(json: JSONObject): LessThanOrEqual { - if (json['_type'] === ObjectType.LessThanOrEqual) { - return new LessThanOrEqual(json['value'] as number | Date); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } } diff --git a/Common/Types/BaseDatabase/ModelPermission.ts b/Common/Types/BaseDatabase/ModelPermission.ts index 83c19b874e..ae2032019c 100644 --- a/Common/Types/BaseDatabase/ModelPermission.ts +++ b/Common/Types/BaseDatabase/ModelPermission.ts @@ -1,37 +1,37 @@ import Permission, { - PermissionHelper, - UserPermission, - UserTenantAccessPermission, - instanceOfUserTenantAccessPermission, -} from '../Permission'; + PermissionHelper, + UserPermission, + UserTenantAccessPermission, + instanceOfUserTenantAccessPermission, +} from "../Permission"; export default class ModelPermission { - public static hasPermissions( - userProjectPermissions: UserTenantAccessPermission | Array<Permission>, - modelPermissions: Array<Permission> - ): boolean { - let userPermissions: Array<Permission> = []; + public static hasPermissions( + userProjectPermissions: UserTenantAccessPermission | Array<Permission>, + modelPermissions: Array<Permission>, + ): boolean { + let userPermissions: Array<Permission> = []; - if ( - instanceOfUserTenantAccessPermission(userProjectPermissions) && - userProjectPermissions.permissions && - Array.isArray(userProjectPermissions.permissions) - ) { - userPermissions = userProjectPermissions.permissions.map( - (item: UserPermission) => { - return item.permission; - } - ); - } else { - userPermissions = userProjectPermissions as Array<Permission>; - } - - return Boolean( - userPermissions && - PermissionHelper.doesPermissionsIntersect( - modelPermissions, - userPermissions - ) - ); + if ( + instanceOfUserTenantAccessPermission(userProjectPermissions) && + userProjectPermissions.permissions && + Array.isArray(userProjectPermissions.permissions) + ) { + userPermissions = userProjectPermissions.permissions.map( + (item: UserPermission) => { + return item.permission; + }, + ); + } else { + userPermissions = userProjectPermissions as Array<Permission>; } + + return Boolean( + userPermissions && + PermissionHelper.doesPermissionsIntersect( + modelPermissions, + userPermissions, + ), + ); + } } diff --git a/Common/Types/BaseDatabase/NotEqual.ts b/Common/Types/BaseDatabase/NotEqual.ts index 1b6e170325..47f2d957c1 100644 --- a/Common/Types/BaseDatabase/NotEqual.ts +++ b/Common/Types/BaseDatabase/NotEqual.ts @@ -1,37 +1,37 @@ -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import SerializableObject from '../SerializableObject'; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import SerializableObject from "../SerializableObject"; export default class NotEqual extends SerializableObject { - private _value!: string; - public get value(): string { - return this._value; - } - public set value(v: string) { - this._value = v; + private _value!: string; + public get value(): string { + return this._value; + } + public set value(v: string) { + this._value = v; + } + + public constructor(value: string) { + super(); + this.value = value; + } + + public override toString(): string { + return this.value; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.NotEqual, + value: (this as NotEqual).toString(), + }; + } + + public static override fromJSON(json: JSONObject): NotEqual { + if (json["_type"] === ObjectType.NotEqual) { + return new NotEqual(json["value"] as string); } - public constructor(value: string) { - super(); - this.value = value; - } - - public override toString(): string { - return this.value; - } - - public override toJSON(): JSONObject { - return { - _type: ObjectType.NotEqual, - value: (this as NotEqual).toString(), - }; - } - - public static override fromJSON(json: JSONObject): NotEqual { - if (json['_type'] === ObjectType.NotEqual) { - return new NotEqual(json['value'] as string); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } } diff --git a/Common/Types/BaseDatabase/NotNull.ts b/Common/Types/BaseDatabase/NotNull.ts index 1897798db2..a5978ff7c9 100644 --- a/Common/Types/BaseDatabase/NotNull.ts +++ b/Common/Types/BaseDatabase/NotNull.ts @@ -1,28 +1,28 @@ -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import SerializableObject from '../SerializableObject'; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import SerializableObject from "../SerializableObject"; export default class NotNull extends SerializableObject { - public constructor() { - super(); + public constructor() { + super(); + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.NotNull, + value: null, + }; + } + + public static override fromJSON(json: JSONObject): NotNull { + if (json["_type"] === ObjectType.NotNull) { + return new NotNull(); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.NotNull, - value: null, - }; - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } - public static override fromJSON(json: JSONObject): NotNull { - if (json['_type'] === ObjectType.NotNull) { - return new NotNull(); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - public override toString(): string { - return ''; - } + public override toString(): string { + return ""; + } } diff --git a/Common/Types/BaseDatabase/Search.ts b/Common/Types/BaseDatabase/Search.ts index 77b98167da..a581dd0de5 100644 --- a/Common/Types/BaseDatabase/Search.ts +++ b/Common/Types/BaseDatabase/Search.ts @@ -1,39 +1,39 @@ -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import SerializableObject from '../SerializableObject'; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import SerializableObject from "../SerializableObject"; export default class Search extends SerializableObject { - private _searchValue!: string; + private _searchValue!: string; - public get value(): string { - return this._searchValue; + public get value(): string { + return this._searchValue; + } + + public set value(v: string) { + this._searchValue = v; + } + + public constructor(value: string) { + super(); + this.value = value; + } + + public override toString(): string { + return this.value; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Search, + value: (this as Search).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Search { + if (json["_type"] === ObjectType.Search) { + return new Search((json["value"] as string) || ""); } - public set value(v: string) { - this._searchValue = v; - } - - public constructor(value: string) { - super(); - this.value = value; - } - - public override toString(): string { - return this.value; - } - - public override toJSON(): JSONObject { - return { - _type: ObjectType.Search, - value: (this as Search).toString(), - }; - } - - public static override fromJSON(json: JSONObject): Search { - if (json['_type'] === ObjectType.Search) { - return new Search((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } } diff --git a/Common/Types/BaseDatabase/SortOrder.ts b/Common/Types/BaseDatabase/SortOrder.ts index bb26d5fa76..abe798165d 100644 --- a/Common/Types/BaseDatabase/SortOrder.ts +++ b/Common/Types/BaseDatabase/SortOrder.ts @@ -1,6 +1,6 @@ enum SortOrder { - Ascending = 'ASC', - Descending = 'DESC', + Ascending = "ASC", + Descending = "DESC", } export default SortOrder; diff --git a/Common/Types/BaseDatabase/TableBillingAccessControl.ts b/Common/Types/BaseDatabase/TableBillingAccessControl.ts index 0cbcc1970c..4e487208d3 100644 --- a/Common/Types/BaseDatabase/TableBillingAccessControl.ts +++ b/Common/Types/BaseDatabase/TableBillingAccessControl.ts @@ -1,8 +1,8 @@ -import { PlanSelect } from '../Billing/SubscriptionPlan'; +import { PlanSelect } from "../Billing/SubscriptionPlan"; export default interface TableBillingAccessControl { - create: PlanSelect; - read: PlanSelect; - update: PlanSelect; - delete: PlanSelect; + create: PlanSelect; + read: PlanSelect; + update: PlanSelect; + delete: PlanSelect; } diff --git a/Common/Types/Billing/MeteredPlan.ts b/Common/Types/Billing/MeteredPlan.ts index 4b8aeb3d63..d37d7d82bd 100644 --- a/Common/Types/Billing/MeteredPlan.ts +++ b/Common/Types/Billing/MeteredPlan.ts @@ -1,27 +1,27 @@ export default class MeteredPlan { - private priceId: string; - private unitName: string; - private pricePerUnitInUSD: number; + private priceId: string; + private unitName: string; + private pricePerUnitInUSD: number; - public constructor(data: { - priceId: string; - pricePerUnitInUSD: number; - unitName: string; - }) { - this.priceId = data.priceId; - this.pricePerUnitInUSD = data.pricePerUnitInUSD; - this.unitName = data.unitName; - } + public constructor(data: { + priceId: string; + pricePerUnitInUSD: number; + unitName: string; + }) { + this.priceId = data.priceId; + this.pricePerUnitInUSD = data.pricePerUnitInUSD; + this.unitName = data.unitName; + } - public getPriceId(): string { - return this.priceId; - } + public getPriceId(): string { + return this.priceId; + } - public getPricePerUnit(): number { - return this.pricePerUnitInUSD; - } + public getPricePerUnit(): number { + return this.pricePerUnitInUSD; + } - public getUnitName(): string { - return this.unitName; - } + public getUnitName(): string { + return this.unitName; + } } diff --git a/Common/Types/Billing/SubscriptionPlan.ts b/Common/Types/Billing/SubscriptionPlan.ts index c925f145a8..e73f81ba8d 100644 --- a/Common/Types/Billing/SubscriptionPlan.ts +++ b/Common/Types/Billing/SubscriptionPlan.ts @@ -1,241 +1,236 @@ -import BadDataException from '../Exception/BadDataException'; -import { JSONObject } from '../JSON'; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject } from "../JSON"; export enum PlanSelect { - Free = 'Free', - Growth = 'Growth', - Enterprise = 'Enterprise', - Scale = 'Scale', + Free = "Free", + Growth = "Growth", + Enterprise = "Enterprise", + Scale = "Scale", } export default class SubscriptionPlan { - private monthlyPlanId: string = ''; - private yearlyPlanId: string = ''; - private name: string = ''; - private monthlySubscriptionAmountInUSD: number = 0; - private yearlySubscriptionAmountInUSD: number = 0; - private order: number = -1; - private trialPeriodInDays: number = 0; + private monthlyPlanId: string = ""; + private yearlyPlanId: string = ""; + private name: string = ""; + private monthlySubscriptionAmountInUSD: number = 0; + private yearlySubscriptionAmountInUSD: number = 0; + private order: number = -1; + private trialPeriodInDays: number = 0; - public constructor( - monthlyPlanId: string, - yearlyPlanId: string, - name: string, - monthlySubscriptionAmountInUSD: number, - yearlySubscriptionAmountInUSD: number, - order: number, - trialPeriodInDays: number - ) { - this.monthlyPlanId = monthlyPlanId; - this.yearlyPlanId = yearlyPlanId; - this.name = name; - this.monthlySubscriptionAmountInUSD = monthlySubscriptionAmountInUSD; - this.yearlySubscriptionAmountInUSD = yearlySubscriptionAmountInUSD; - this.order = order; - this.trialPeriodInDays = trialPeriodInDays; - } + public constructor( + monthlyPlanId: string, + yearlyPlanId: string, + name: string, + monthlySubscriptionAmountInUSD: number, + yearlySubscriptionAmountInUSD: number, + order: number, + trialPeriodInDays: number, + ) { + this.monthlyPlanId = monthlyPlanId; + this.yearlyPlanId = yearlyPlanId; + this.name = name; + this.monthlySubscriptionAmountInUSD = monthlySubscriptionAmountInUSD; + this.yearlySubscriptionAmountInUSD = yearlySubscriptionAmountInUSD; + this.order = order; + this.trialPeriodInDays = trialPeriodInDays; + } - public getMonthlyPlanId(): string { - return this.monthlyPlanId; - } + public getMonthlyPlanId(): string { + return this.monthlyPlanId; + } - public getYearlyPlanId(): string { - return this.yearlyPlanId; - } + public getYearlyPlanId(): string { + return this.yearlyPlanId; + } - public getPlanOrder(): number { - return this.order; - } + public getPlanOrder(): number { + return this.order; + } - public getTrialPeriod(): number { - return this.trialPeriodInDays; - } + public getTrialPeriod(): number { + return this.trialPeriodInDays; + } - public getName(): string { - return this.name; - } - - public static isFreePlan(planId: string, env: JSONObject): boolean { - const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById( - planId, - env - ); - if (plan) { - if ( - plan.getMonthlyPlanId() === planId && - plan.getMonthlySubscriptionAmountInUSD() === 0 - ) { - return true; - } - - if ( - plan.getYearlyPlanId() === planId && - plan.getYearlySubscriptionAmountInUSD() === 0 - ) { - return true; - } - } - - return false; - } - - public static isCustomPricingPlan( - planId: string, - env: JSONObject - ): boolean { - const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById( - planId, - env - ); - if (plan) { - if (plan.getMonthlyPlanId() === planId && plan.isCustomPricing()) { - return true; - } - } - - return false; - } - - public getYearlySubscriptionAmountInUSD(): number { - return this.yearlySubscriptionAmountInUSD; - } - - public getMonthlySubscriptionAmountInUSD(): number { - return this.monthlySubscriptionAmountInUSD; - } - - public isCustomPricing(): boolean { - return this.monthlySubscriptionAmountInUSD === -1; - } - - public static getSubscriptionPlans( - env?: JSONObject | undefined - ): Array<SubscriptionPlan> { - const plans: Array<SubscriptionPlan> = []; - - for (const key in env || process.env) { - if (key.startsWith('SUBSCRIPTION_PLAN_')) { - const content: string = - ((env || process.env)[key] as string) || ''; - const values: Array<string> = content.split(','); - - if (values.length > 0) { - plans.push( - new SubscriptionPlan( - values[1] as string, - values[2] as string, - values[0] as string, - parseInt(values[3] as string), - parseInt(values[4] as string), - parseInt(values[5] as string), - parseInt(values[6] as string) - ) - ); - } - } - } - - plans.sort((a: SubscriptionPlan, b: SubscriptionPlan) => { - return a.order - b.order; - }); - - return plans; - } - - public static getSubscriptionPlanById( - planId: string, - env?: JSONObject | undefined - ): SubscriptionPlan | undefined { - const plans: Array<SubscriptionPlan> = this.getSubscriptionPlans(env); - return plans.find((plan: SubscriptionPlan) => { - return ( - plan.getMonthlyPlanId() === planId || - plan.getYearlyPlanId() === planId - ); - }); - } - - public static isValidPlanId( - planId: string, - env?: JSONObject | undefined - ): boolean { - return Boolean(this.getSubscriptionPlanById(planId, env)); - } - - public static getPlanSelect( - planId: string, - env?: JSONObject | undefined - ): PlanSelect { - const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById( - planId, - env - ); - if (!plan) { - throw new BadDataException('Plan ID is invalid'); - } - - return plan.getName() as PlanSelect; - } - - public static getSubscriptionPlanFromPlanSelect( - planSelect: PlanSelect, - env?: JSONObject | undefined - ): SubscriptionPlan { - const plan: SubscriptionPlan | undefined = this.getSubscriptionPlans( - env - ).find((plan: SubscriptionPlan) => { - return plan.getName() === planSelect; - }); - - if (!plan) { - throw new BadDataException('Invalid Plan'); - } - - return plan; - } - - public static isFeatureAccessibleOnCurrentPlan( - featurePlan: PlanSelect, - currentPlan: PlanSelect, - env?: JSONObject | undefined - ): boolean { - const featureSubscriptionPlan: SubscriptionPlan | undefined = - this.getSubscriptionPlanFromPlanSelect(featurePlan, env); - const currentSubscriptionPlan: SubscriptionPlan | undefined = - this.getSubscriptionPlanFromPlanSelect(currentPlan, env); - - if ( - featureSubscriptionPlan.getPlanOrder() > - currentSubscriptionPlan.getPlanOrder() - ) { - return false; - } + public getName(): string { + return this.name; + } + public static isFreePlan(planId: string, env: JSONObject): boolean { + const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById( + planId, + env, + ); + if (plan) { + if ( + plan.getMonthlyPlanId() === planId && + plan.getMonthlySubscriptionAmountInUSD() === 0 + ) { return true; + } + + if ( + plan.getYearlyPlanId() === planId && + plan.getYearlySubscriptionAmountInUSD() === 0 + ) { + return true; + } } - public static isYearlyPlan( - planId: string, - env?: JSONObject | undefined - ): boolean { - const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById( - planId, - env - ); - return plan?.getYearlyPlanId() === planId; + return false; + } + + public static isCustomPricingPlan(planId: string, env: JSONObject): boolean { + const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById( + planId, + env, + ); + if (plan) { + if (plan.getMonthlyPlanId() === planId && plan.isCustomPricing()) { + return true; + } } - public static isUnpaid(subscriptionStatus: string): boolean { - if ( - subscriptionStatus === 'incomplete' || - subscriptionStatus === 'incomplete_expired' || - subscriptionStatus === 'past_due' || - subscriptionStatus === 'canceled' || - subscriptionStatus === 'unpaid' - ) { - return true; + return false; + } + + public getYearlySubscriptionAmountInUSD(): number { + return this.yearlySubscriptionAmountInUSD; + } + + public getMonthlySubscriptionAmountInUSD(): number { + return this.monthlySubscriptionAmountInUSD; + } + + public isCustomPricing(): boolean { + return this.monthlySubscriptionAmountInUSD === -1; + } + + public static getSubscriptionPlans( + env?: JSONObject | undefined, + ): Array<SubscriptionPlan> { + const plans: Array<SubscriptionPlan> = []; + + for (const key in env || process.env) { + if (key.startsWith("SUBSCRIPTION_PLAN_")) { + const content: string = ((env || process.env)[key] as string) || ""; + const values: Array<string> = content.split(","); + + if (values.length > 0) { + plans.push( + new SubscriptionPlan( + values[1] as string, + values[2] as string, + values[0] as string, + parseInt(values[3] as string), + parseInt(values[4] as string), + parseInt(values[5] as string), + parseInt(values[6] as string), + ), + ); } - - return false; + } } + + plans.sort((a: SubscriptionPlan, b: SubscriptionPlan) => { + return a.order - b.order; + }); + + return plans; + } + + public static getSubscriptionPlanById( + planId: string, + env?: JSONObject | undefined, + ): SubscriptionPlan | undefined { + const plans: Array<SubscriptionPlan> = this.getSubscriptionPlans(env); + return plans.find((plan: SubscriptionPlan) => { + return ( + plan.getMonthlyPlanId() === planId || plan.getYearlyPlanId() === planId + ); + }); + } + + public static isValidPlanId( + planId: string, + env?: JSONObject | undefined, + ): boolean { + return Boolean(this.getSubscriptionPlanById(planId, env)); + } + + public static getPlanSelect( + planId: string, + env?: JSONObject | undefined, + ): PlanSelect { + const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById( + planId, + env, + ); + if (!plan) { + throw new BadDataException("Plan ID is invalid"); + } + + return plan.getName() as PlanSelect; + } + + public static getSubscriptionPlanFromPlanSelect( + planSelect: PlanSelect, + env?: JSONObject | undefined, + ): SubscriptionPlan { + const plan: SubscriptionPlan | undefined = this.getSubscriptionPlans( + env, + ).find((plan: SubscriptionPlan) => { + return plan.getName() === planSelect; + }); + + if (!plan) { + throw new BadDataException("Invalid Plan"); + } + + return plan; + } + + public static isFeatureAccessibleOnCurrentPlan( + featurePlan: PlanSelect, + currentPlan: PlanSelect, + env?: JSONObject | undefined, + ): boolean { + const featureSubscriptionPlan: SubscriptionPlan | undefined = + this.getSubscriptionPlanFromPlanSelect(featurePlan, env); + const currentSubscriptionPlan: SubscriptionPlan | undefined = + this.getSubscriptionPlanFromPlanSelect(currentPlan, env); + + if ( + featureSubscriptionPlan.getPlanOrder() > + currentSubscriptionPlan.getPlanOrder() + ) { + return false; + } + + return true; + } + + public static isYearlyPlan( + planId: string, + env?: JSONObject | undefined, + ): boolean { + const plan: SubscriptionPlan | undefined = this.getSubscriptionPlanById( + planId, + env, + ); + return plan?.getYearlyPlanId() === planId; + } + + public static isUnpaid(subscriptionStatus: string): boolean { + if ( + subscriptionStatus === "incomplete" || + subscriptionStatus === "incomplete_expired" || + subscriptionStatus === "past_due" || + subscriptionStatus === "canceled" || + subscriptionStatus === "unpaid" + ) { + return true; + } + + return false; + } } diff --git a/Common/Types/Billing/SubscriptionStatus.ts b/Common/Types/Billing/SubscriptionStatus.ts index ea0f63d217..9888aa17c1 100644 --- a/Common/Types/Billing/SubscriptionStatus.ts +++ b/Common/Types/Billing/SubscriptionStatus.ts @@ -1,32 +1,32 @@ enum SubscriptionStatus { - Incomplete = 'incomplete', - IncompleteExpired = 'incomplete_expired', - Trialing = 'trialing', - Active = 'active', - PastDue = 'past_due', - Canceled = 'canceled', - Unpaid = 'unpaid', + Incomplete = "incomplete", + IncompleteExpired = "incomplete_expired", + Trialing = "trialing", + Active = "active", + PastDue = "past_due", + Canceled = "canceled", + Unpaid = "unpaid", } export class SubscriptionStatusUtil { - public static isSubscriptionActive( - status?: SubscriptionStatus | undefined - ): boolean { - if (!status) { - return true; - } - - return ( - status === SubscriptionStatus.Active || - status === SubscriptionStatus.Trialing - ); + public static isSubscriptionActive( + status?: SubscriptionStatus | undefined, + ): boolean { + if (!status) { + return true; } - public static isSubscriptionInactive( - status?: SubscriptionStatus | undefined - ): boolean { - return !SubscriptionStatusUtil.isSubscriptionActive(status); - } + return ( + status === SubscriptionStatus.Active || + status === SubscriptionStatus.Trialing + ); + } + + public static isSubscriptionInactive( + status?: SubscriptionStatus | undefined, + ): boolean { + return !SubscriptionStatusUtil.isSubscriptionActive(status); + } } export default SubscriptionStatus; diff --git a/Common/Types/BrandColors.ts b/Common/Types/BrandColors.ts index f8966191b0..b38e736944 100644 --- a/Common/Types/BrandColors.ts +++ b/Common/Types/BrandColors.ts @@ -1,67 +1,67 @@ -import Color from './Color'; +import Color from "./Color"; // Standard Colors -export const Black: Color = Color.fromString('#000000'); -export const White: Color = Color.fromString('#ffffff'); -export const slate: Color = Color.fromString('#564ab1'); -export const Purple: Color = Color.fromString('#6f42c1'); -export const Pink: Color = Color.fromString('#e83e8c'); -export const Red: Color = Color.fromString('#fd625e'); -export const Orange: Color = Color.fromString('#f1734f'); -export const Yellow: Color = Color.fromString('#ffbf53'); -export const Green: Color = Color.fromString('#2ab57d'); -export const Teal: Color = Color.fromString('#050505'); -export const Cyan: Color = Color.fromString('#4ba6ef'); -export const VeryLightGray: Color = Color.fromString('#c2c2c2'); -export const Grey: Color = Color.fromString('#575757'); -export const LightGray: Color = Color.fromString('#908B8B'); -export const Moroon: Color = Color.fromString('#b70400'); -export const Blue: Color = Color.fromString('#3686be'); +export const Black: Color = Color.fromString("#000000"); +export const White: Color = Color.fromString("#ffffff"); +export const slate: Color = Color.fromString("#564ab1"); +export const Purple: Color = Color.fromString("#6f42c1"); +export const Pink: Color = Color.fromString("#e83e8c"); +export const Red: Color = Color.fromString("#fd625e"); +export const Orange: Color = Color.fromString("#f1734f"); +export const Yellow: Color = Color.fromString("#ffbf53"); +export const Green: Color = Color.fromString("#2ab57d"); +export const Teal: Color = Color.fromString("#050505"); +export const Cyan: Color = Color.fromString("#4ba6ef"); +export const VeryLightGray: Color = Color.fromString("#c2c2c2"); +export const Grey: Color = Color.fromString("#575757"); +export const LightGray: Color = Color.fromString("#908B8B"); +export const Moroon: Color = Color.fromString("#b70400"); +export const Blue: Color = Color.fromString("#3686be"); // these are *-500 colors from tailwindcss -export const Zinc500: Color = Color.fromString('#71717a'); -export const Neutra500l: Color = Color.fromString('#737373'); -export const Stone500: Color = Color.fromString('#78716c'); -export const Slate500: Color = Color.fromString('#64748b'); -export const Purple500: Color = Color.fromString('#a855f7'); -export const Pink500: Color = Color.fromString('#ec4899'); -export const Red500: Color = Color.fromString('#ef4444'); -export const Orange500: Color = Color.fromString('#f97316'); -export const Yellow500: Color = Color.fromString('#ffbf53'); -export const Green500: Color = Color.fromString('#22c55e'); -export const Teal500: Color = Color.fromString('#14b8a6'); -export const Cyan500: Color = Color.fromString('#06b6d4'); -export const Gray500: Color = Color.fromString('#6b7280'); -export const Moroon500: Color = Color.fromString('#b70400'); -export const Blue500: Color = Color.fromString('#3b82f6'); -export const Indigo500: Color = Color.fromString('#6366f1'); -export const Amber500: Color = Color.fromString('#f59e0b'); -export const Lime500: Color = Color.fromString('#84cc16'); -export const Emerald500: Color = Color.fromString('#10b981'); -export const Sky500: Color = Color.fromString('#0ea5e9'); -export const Violet500: Color = Color.fromString('#8b5cf6'); -export const Fuchsia500: Color = Color.fromString('#d946ef'); -export const Rose500: Color = Color.fromString('#f43f5e'); +export const Zinc500: Color = Color.fromString("#71717a"); +export const Neutra500l: Color = Color.fromString("#737373"); +export const Stone500: Color = Color.fromString("#78716c"); +export const Slate500: Color = Color.fromString("#64748b"); +export const Purple500: Color = Color.fromString("#a855f7"); +export const Pink500: Color = Color.fromString("#ec4899"); +export const Red500: Color = Color.fromString("#ef4444"); +export const Orange500: Color = Color.fromString("#f97316"); +export const Yellow500: Color = Color.fromString("#ffbf53"); +export const Green500: Color = Color.fromString("#22c55e"); +export const Teal500: Color = Color.fromString("#14b8a6"); +export const Cyan500: Color = Color.fromString("#06b6d4"); +export const Gray500: Color = Color.fromString("#6b7280"); +export const Moroon500: Color = Color.fromString("#b70400"); +export const Blue500: Color = Color.fromString("#3b82f6"); +export const Indigo500: Color = Color.fromString("#6366f1"); +export const Amber500: Color = Color.fromString("#f59e0b"); +export const Lime500: Color = Color.fromString("#84cc16"); +export const Emerald500: Color = Color.fromString("#10b981"); +export const Sky500: Color = Color.fromString("#0ea5e9"); +export const Violet500: Color = Color.fromString("#8b5cf6"); +export const Fuchsia500: Color = Color.fromString("#d946ef"); +export const Rose500: Color = Color.fromString("#f43f5e"); export const BrightColors: Color[] = [ - Black, - Indigo500, - Green500, - Red500, - Cyan500, - Pink500, - Orange500, - Purple500, - Yellow500, - Teal500, - Gray500, - Moroon500, - Blue500, - Rose500, - Fuchsia500, - Violet500, - Sky500, - Emerald500, - Lime500, - Amber500, + Black, + Indigo500, + Green500, + Red500, + Cyan500, + Pink500, + Orange500, + Purple500, + Yellow500, + Teal500, + Gray500, + Moroon500, + Blue500, + Rose500, + Fuchsia500, + Violet500, + Sky500, + Emerald500, + Lime500, + Amber500, ]; diff --git a/Common/Types/BrowserType.ts b/Common/Types/BrowserType.ts index df6e9cb31a..834a6e04d8 100644 --- a/Common/Types/BrowserType.ts +++ b/Common/Types/BrowserType.ts @@ -1,7 +1,7 @@ export enum BrowserType { - Chromium = 'Chromium', - Firefox = 'Firefox', - // Webkit = 'Webkit', + Chromium = "Chromium", + Firefox = "Firefox", + // Webkit = 'Webkit', } export default BrowserType; diff --git a/Common/Types/Calendar/CalendarEvent.ts b/Common/Types/Calendar/CalendarEvent.ts index db26f97045..82e62471bb 100644 --- a/Common/Types/Calendar/CalendarEvent.ts +++ b/Common/Types/Calendar/CalendarEvent.ts @@ -1,12 +1,12 @@ -import { JSONObject } from '../JSON'; +import { JSONObject } from "../JSON"; export default interface CalendarEvent extends JSONObject { - id: number; - title: string; - start: Date; - end: Date; - allDay?: boolean | undefined; - desc?: string | undefined; - color?: string | undefined; - textColor?: string | undefined; + id: number; + title: string; + start: Date; + end: Date; + allDay?: boolean | undefined; + desc?: string | undefined; + color?: string | undefined; + textColor?: string | undefined; } diff --git a/Common/Types/Call/CallRequest.ts b/Common/Types/Call/CallRequest.ts index d7197558f2..7d6588726b 100644 --- a/Common/Types/Call/CallRequest.ts +++ b/Common/Types/Call/CallRequest.ts @@ -1,43 +1,43 @@ -import URL from '../API/URL'; -import Phone from '../Phone'; +import URL from "../API/URL"; +import Phone from "../Phone"; export interface Say { - sayMessage: string; + sayMessage: string; } export interface OnCallInputRequest { - [x: string]: Say; // input. - default: Say; // what if there is no input or invalid input. + [x: string]: Say; // input. + default: Say; // what if there is no input or invalid input. } export interface GatherInput { - introMessage: string; - numDigits: number; - timeoutInSeconds: number; - noInputMessage: string; - onInputCallRequest: OnCallInputRequest; - responseUrl: URL; + introMessage: string; + numDigits: number; + timeoutInSeconds: number; + noInputMessage: string; + onInputCallRequest: OnCallInputRequest; + responseUrl: URL; } export enum CallAction {} export interface CallRequestMessage { - data: Array<Say | CallAction | GatherInput>; + data: Array<Say | CallAction | GatherInput>; } export default interface CallRequest extends CallRequestMessage { - to: Phone; + to: Phone; } type IsHighRiskPhoneNumberFunction = (phoneNumber: Phone) => boolean; export const isHighRiskPhoneNumber: IsHighRiskPhoneNumberFunction = ( - phoneNumber: Phone + phoneNumber: Phone, ): boolean => { - // Pakistan - if (phoneNumber.toString().startsWith('+92')) { - return true; - } + // Pakistan + if (phoneNumber.toString().startsWith("+92")) { + return true; + } - return false; + return false; }; diff --git a/Common/Types/Call/CallStatus.ts b/Common/Types/Call/CallStatus.ts index c54eb01061..d379bf31d2 100644 --- a/Common/Types/Call/CallStatus.ts +++ b/Common/Types/Call/CallStatus.ts @@ -1,9 +1,9 @@ enum CallStatus { - Success = 'Success', - Error = 'Error', - LowBalance = 'Low Balance', - MissedCall = 'Missed Call', - Busy = 'Busy', + Success = "Success", + Error = "Error", + LowBalance = "Low Balance", + MissedCall = "Missed Call", + Busy = "Busy", } export default CallStatus; diff --git a/Common/Types/CallAndSMS/TwilioConfig.ts b/Common/Types/CallAndSMS/TwilioConfig.ts index 2f94ce9eda..fdcfc3534f 100644 --- a/Common/Types/CallAndSMS/TwilioConfig.ts +++ b/Common/Types/CallAndSMS/TwilioConfig.ts @@ -1,7 +1,7 @@ -import Phone from '../Phone'; +import Phone from "../Phone"; export default interface TwilioConfig { - accountSid: string; - authToken: string; - phoneNumber: Phone; + accountSid: string; + authToken: string; + phoneNumber: Phone; } diff --git a/Common/Types/Char.ts b/Common/Types/Char.ts index 3d695c9331..43bfd45da3 100644 --- a/Common/Types/Char.ts +++ b/Common/Types/Char.ts @@ -1,65 +1,65 @@ type Char = - | 'a' - | 'b' - | 'c' - | 'd' - | 'e' - | 'f' - | 'g' - | 'h' - | 'i' - | 'j' - | 'k' - | 'l' - | 'm' - | 'n' - | 'o' - | 'p' - | 'q' - | 'r' - | 's' - | 't' - | 'u' - | 'v' - | 'w' - | 'x' - | 'y' - | 'z' - | 'A' - | 'B' - | 'C' - | 'D' - | 'E' - | 'F' - | 'G' - | 'H' - | 'I' - | 'J' - | 'K' - | 'L' - | 'M' - | 'N' - | 'O' - | 'P' - | 'Q' - | 'R' - | 'S' - | 'T' - | 'U' - | 'V' - | 'W' - | 'X' - | 'Y' - | 'Z' - | '0' - | '1' - | '2' - | '3' - | '4' - | '5' - | '6' - | '7' - | '8' - | '9'; + | "a" + | "b" + | "c" + | "d" + | "e" + | "f" + | "g" + | "h" + | "i" + | "j" + | "k" + | "l" + | "m" + | "n" + | "o" + | "p" + | "q" + | "r" + | "s" + | "t" + | "u" + | "v" + | "w" + | "x" + | "y" + | "z" + | "A" + | "B" + | "C" + | "D" + | "E" + | "F" + | "G" + | "H" + | "I" + | "J" + | "K" + | "L" + | "M" + | "N" + | "O" + | "P" + | "Q" + | "R" + | "S" + | "T" + | "U" + | "V" + | "W" + | "X" + | "Y" + | "Z" + | "0" + | "1" + | "2" + | "3" + | "4" + | "5" + | "6" + | "7" + | "8" + | "9"; export default Char; diff --git a/Common/Types/Code/CodeType.ts b/Common/Types/Code/CodeType.ts index 621ed93c41..678bfb9dde 100644 --- a/Common/Types/Code/CodeType.ts +++ b/Common/Types/Code/CodeType.ts @@ -1,11 +1,11 @@ enum CodeType { - JavaScript = 'javascript', - CSS = 'css', - HTML = 'html', - JSON = 'json', - Markdown = 'markdown', - SQL = 'sql', - // TODO add more mime types. + JavaScript = "javascript", + CSS = "css", + HTML = "html", + JSON = "json", + Markdown = "markdown", + SQL = "sql", + // TODO add more mime types. } export default CodeType; diff --git a/Common/Types/CodeRepository/CodeRepositoryType.ts b/Common/Types/CodeRepository/CodeRepositoryType.ts index 25bd1f9591..075bcb1ef6 100644 --- a/Common/Types/CodeRepository/CodeRepositoryType.ts +++ b/Common/Types/CodeRepository/CodeRepositoryType.ts @@ -1,6 +1,6 @@ enum CodeRepositoryType { - GitHub = 'GitHub', - // GitLab = 'GitLab', + GitHub = "GitHub", + // GitLab = 'GitLab', } export default CodeRepositoryType; diff --git a/Common/Types/CodeRepository/PullRequest.ts b/Common/Types/CodeRepository/PullRequest.ts index 2530a3d2f6..1f22a6fb71 100644 --- a/Common/Types/CodeRepository/PullRequest.ts +++ b/Common/Types/CodeRepository/PullRequest.ts @@ -1,16 +1,16 @@ -import URL from '../API/URL'; -import PullRequestState from './PullRequestState'; +import URL from "../API/URL"; +import PullRequestState from "./PullRequestState"; export default interface PullRequest { - url: URL; - pullRequestId: number; - pullRequestNumber: number; - state: PullRequestState; - title: string; - body: string; - createdAt: Date; - updatedAt: Date; - headRefName: string; // this is the branch name of the pull request - repoOrganizationName: string; - repoName: string; + url: URL; + pullRequestId: number; + pullRequestNumber: number; + state: PullRequestState; + title: string; + body: string; + createdAt: Date; + updatedAt: Date; + headRefName: string; // this is the branch name of the pull request + repoOrganizationName: string; + repoName: string; } diff --git a/Common/Types/CodeRepository/PullRequestState.ts b/Common/Types/CodeRepository/PullRequestState.ts index c4be84cfe8..8d15d36f37 100644 --- a/Common/Types/CodeRepository/PullRequestState.ts +++ b/Common/Types/CodeRepository/PullRequestState.ts @@ -1,7 +1,7 @@ enum PullRequestState { - Open = 'open', - Closed = 'closed', - All = 'all', + Open = "open", + Closed = "closed", + All = "all", } export default PullRequestState; diff --git a/Common/Types/Color.ts b/Common/Types/Color.ts index 4686b6d92b..06132247df 100644 --- a/Common/Types/Color.ts +++ b/Common/Types/Color.ts @@ -1,108 +1,108 @@ -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import { FindOperator } from "typeorm"; export interface RGB { - red: number; - green: number; - blue: number; + red: number; + green: number; + blue: number; } export default class Color extends DatabaseProperty { - private _color!: string; - public get color(): string { - return this._color; - } - public set color(v: string) { - this._color = v; + private _color!: string; + public get color(): string { + return this._color; + } + public set color(v: string) { + this._color = v; + } + + public constructor(color: string) { + super(); + this.color = color; + } + + public override toString(): string { + return this.color; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Color, + value: (this as Color).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Color { + if (json["_type"] === ObjectType.Color) { + return new Color((json["value"] as string) || ""); } - public constructor(color: string) { - super(); - this.color = color; + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + public static override toDatabase( + value: Color | FindOperator<Color>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new Color(value); + } + + return value.toString(); } - public override toString(): string { - return this.color; + return null; + } + + public static override fromDatabase(_value: string): Color | null { + if (_value) { + return new Color(_value); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.Color, - value: (this as Color).toString(), - }; + return null; + } + + public static colorToRgb(color: Color): RGB { + const re: RegExp = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; + const result: RegExpExecArray | null = re.exec(color.toString()); + + if (!result) { + throw new BadDataException("Invalid color: " + color.toString()); } - public static override fromJSON(json: JSONObject): Color { - if (json['_type'] === ObjectType.Color) { - return new Color((json['value'] as string) || ''); - } + return { + red: parseInt(result[1] ? result[1] : "0", 16), + green: parseInt(result[2] ? result[2] : "0", 16), + blue: parseInt(result[3] ? result[3] : "0", 16), + }; + } - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - public static override toDatabase( - value: Color | FindOperator<Color> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new Color(value); - } - - return value.toString(); - } - - return null; - } - - public static override fromDatabase(_value: string): Color | null { - if (_value) { - return new Color(_value); - } - - return null; - } - - public static colorToRgb(color: Color): RGB { - const re: RegExp = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; - const result: RegExpExecArray | null = re.exec(color.toString()); - - if (!result) { - throw new BadDataException('Invalid color: ' + color.toString()); - } - - return { - red: parseInt(result[1] ? result[1] : '0', 16), - green: parseInt(result[2] ? result[2] : '0', 16), - blue: parseInt(result[3] ? result[3] : '0', 16), - }; - } - - private static _componentToHex(c: number): string { - const hex: string = c.toString(16); - return hex.length === 1 ? '0' + hex : hex; - } - - public static rgbToColor(rgb: RGB): Color { - return new Color( - '#' + - this._componentToHex(rgb.red) + - this._componentToHex(rgb.green) + - this._componentToHex(rgb.blue) - ); - } - - public static fromString(color: string): Color { - return new Color(color); - } - - public static shouldUseDarkText(color: Color): boolean { - const rgb: RGB = Color.colorToRgb(color); - - if (rgb.red * 0.299 + rgb.green * 0.587 + rgb.blue * 0.114 > 186) { - return true; - } - return false; + private static _componentToHex(c: number): string { + const hex: string = c.toString(16); + return hex.length === 1 ? "0" + hex : hex; + } + + public static rgbToColor(rgb: RGB): Color { + return new Color( + "#" + + this._componentToHex(rgb.red) + + this._componentToHex(rgb.green) + + this._componentToHex(rgb.blue), + ); + } + + public static fromString(color: string): Color { + return new Color(color); + } + + public static shouldUseDarkText(color: Color): boolean { + const rgb: RGB = Color.colorToRgb(color); + + if (rgb.red * 0.299 + rgb.green * 0.587 + rgb.blue * 0.114 > 186) { + return true; } + return false; + } } diff --git a/Common/Types/Company/CompanySize.ts b/Common/Types/Company/CompanySize.ts index a449cd61d8..895bcbaad6 100644 --- a/Common/Types/Company/CompanySize.ts +++ b/Common/Types/Company/CompanySize.ts @@ -1,9 +1,9 @@ enum CompanySize { - OneToTen = '1 to 10', - ElevenToFifty = '11 to 50', - FiftyToTwoHundred = '51 to 200', - TwoHundredToFiveHundred = '201 to 500', - FiveHundredAndMore = '500+', + OneToTen = "1 to 10", + ElevenToFifty = "11 to 50", + FiftyToTwoHundred = "51 to 200", + TwoHundredToFiveHundred = "201 to 500", + FiveHundredAndMore = "500+", } export default CompanySize; diff --git a/Common/Types/Company/JobRole.ts b/Common/Types/Company/JobRole.ts index 826f7262d4..916757b45c 100644 --- a/Common/Types/Company/JobRole.ts +++ b/Common/Types/Company/JobRole.ts @@ -1,10 +1,10 @@ enum JobRole { - CEO = 'CEO', - CTO = 'CTO', - CIO = 'CIO', - Executive = 'Executive', - Developer = 'Developer', - EngineeringManager = 'Engineering Manager', + CEO = "CEO", + CTO = "CTO", + CIO = "CIO", + Executive = "Executive", + Developer = "Developer", + EngineeringManager = "Engineering Manager", } export default JobRole; diff --git a/Common/Types/Copilot/CopilotEventStatus.ts b/Common/Types/Copilot/CopilotEventStatus.ts index 5053491a3b..0abf6e7905 100644 --- a/Common/Types/Copilot/CopilotEventStatus.ts +++ b/Common/Types/Copilot/CopilotEventStatus.ts @@ -1,6 +1,6 @@ enum CopilotEventStatus { - PR_CREATED = 'Pull Request Created', // PR created and waiting for review - NO_ACTION_REQUIRED = 'No Action Required', // No PR needed. All is good. + PR_CREATED = "Pull Request Created", // PR created and waiting for review + NO_ACTION_REQUIRED = "No Action Required", // No PR needed. All is good. } export default CopilotEventStatus; diff --git a/Common/Types/Copilot/CopilotEventType.ts b/Common/Types/Copilot/CopilotEventType.ts index 7ab55a6832..4e4d124402 100644 --- a/Common/Types/Copilot/CopilotEventType.ts +++ b/Common/Types/Copilot/CopilotEventType.ts @@ -1,6 +1,6 @@ enum CopilotEventType { - IMPROVE_COMMENTS = 'IMPROVE_COMMENTS', - FIX_GRAMMAR_AND_SPELLING = 'FIX_GRAMMAR_AND_SPELLING', + IMPROVE_COMMENTS = "IMPROVE_COMMENTS", + FIX_GRAMMAR_AND_SPELLING = "FIX_GRAMMAR_AND_SPELLING", } export default CopilotEventType; diff --git a/Common/Types/Countries.ts b/Common/Types/Countries.ts index 4f8b8b1e42..8486dd6f3b 100644 --- a/Common/Types/Countries.ts +++ b/Common/Types/Countries.ts @@ -1,247 +1,247 @@ enum Countries { - 'Afghanistan' = 'Afghanistan', - 'Åland Islands' = 'Åland Islands', - 'Albania' = 'Albania', - 'Algeria' = 'Algeria', - 'American Samoa' = 'American Samoa', - 'Andorra' = 'Andorra', - 'Angola' = 'Angola', - 'Anguilla' = 'Anguilla', - 'Antarctica' = 'Antarctica', - 'Antigua and Barbuda' = 'Antigua and Barbuda', - 'Argentina' = 'Argentina', - 'Armenia' = 'Armenia', - 'Aruba' = 'Aruba', - 'Australia' = 'Australia', - 'Austria' = 'Austria', - 'Azerbaijan' = 'Azerbaijan', - 'Bahamas' = 'Bahamas', - 'Bahrain' = 'Bahrain', - 'Bangladesh' = 'Bangladesh', - 'Barbados' = 'Barbados', - 'Belarus' = 'Belarus', - 'Belgium' = 'Belgium', - 'Belize' = 'Belize', - 'Benin' = 'Benin', - 'Bermuda' = 'Bermuda', - 'Bhutan' = 'Bhutan', - 'Bolivia' = 'Bolivia', - 'Bosnia and Herzegovina' = 'Bosnia and Herzegovina', - 'Botswana' = 'Botswana', - 'Bouvet Island' = 'Bouvet Island', - 'Brazil' = 'Brazil', - 'British Indian Ocean Territory' = 'British Indian Ocean Territory', - 'Brunei Darussalam' = 'Brunei Darussalam', - 'Bulgaria' = 'Bulgaria', - 'Burkina Faso' = 'Burkina Faso', - 'Burundi' = 'Burundi', - 'Cambodia' = 'Cambodia', - 'Cameroon' = 'Cameroon', - 'Canada' = 'Canada', - 'Cape Verde' = 'Cape Verde', - 'Cayman Islands' = 'Cayman Islands', - 'Central African Republic' = 'Central African Republic', - 'Chad' = 'Chad', - 'Chile' = 'Chile', - 'China' = 'China', - 'Christmas Island' = 'Christmas Island', - 'Cocos (Keeling) Islands' = 'Cocos (Keeling) Islands', - 'Colombia' = 'Colombia', - 'Comoros' = 'Comoros', - 'Congo' = 'Congo', - 'Congo, The Democratic Republic of the' = 'Congo, The Democratic Republic of the', - 'Cook Islands' = 'Cook Islands', - 'Costa Rica' = 'Costa Rica', - "Cote d'Ivoire" = "Cote d'Ivoire", - 'Croatia' = 'Croatia', - 'Cuba' = 'Cuba', - 'Cyprus' = 'Cyprus', - 'Czech Republic' = 'Czech Republic', - 'Denmark' = 'Denmark', - 'Djibouti' = 'Djibouti', - 'Dominica' = 'Dominica', - 'Dominican Republic' = 'Dominican Republic', - 'Ecuador' = 'Ecuador', - 'Egypt' = 'Egypt', - 'El Salvador' = 'El Salvador', - 'Equatorial Guinea' = 'Equatorial Guinea', - 'Eritrea' = 'Eritrea', - 'Estonia' = 'Estonia', - 'Ethiopia' = 'Ethiopia', - 'Falkland Islands (Malvinas)' = 'Falkland Islands (Malvinas)', - 'Faroe Islands' = 'Faroe Islands', - 'Fiji' = 'Fiji', - 'Finland' = 'Finland', - 'France' = 'France', - 'French Guiana' = 'French Guiana', - 'French Polynesia' = 'French Polynesia', - 'French Southern Territories' = 'French Southern Territories', - 'Gabon' = 'Gabon', - 'Gambia' = 'Gambia', - 'Georgia' = 'Georgia', - 'Germany' = 'Germany', - 'Ghana' = 'Ghana', - 'Gibraltar' = 'Gibraltar', - 'Greece' = 'Greece', - 'Greenland' = 'Greenland', - 'Grenada' = 'Grenada', - 'Guadeloupe' = 'Guadeloupe', - 'Guam' = 'Guam', - 'Guatemala' = 'Guatemala', - 'Guernsey' = 'Guernsey', - 'Guinea' = 'Guinea', - 'Guinea-Bissau' = 'Guinea-Bissau', - 'Guyana' = 'Guyana', - 'Haiti' = 'Haiti', - 'Heard Island and Mcdonald Islands' = 'Heard Island and Mcdonald Islands', - 'Holy See (Vatican City State)' = 'Holy See (Vatican City State)', - 'Honduras' = 'Honduras', - 'Hong Kong' = 'Hong Kong', - 'Hungary' = 'Hungary', - 'Iceland' = 'Iceland', - 'India' = 'India', - 'Indonesia' = 'Indonesia', - 'Iran' = 'Iran', - 'Iraq' = 'Iraq', - 'Ireland' = 'Ireland', - 'Isle of Man' = 'Isle of Man', - 'Israel' = 'Israel', - 'Italy' = 'Italy', - 'Jamaica' = 'Jamaica', - 'Japan' = 'Japan', - 'Jersey' = 'Jersey', - 'Jordan' = 'Jordan', - 'Kazakhstan' = 'Kazakhstan', - 'Kenya' = 'Kenya', - 'Kiribati' = 'Kiribati', - 'North Korea' = 'North Korea', - 'South Korea' = 'South Korea', - 'Kuwait' = 'Kuwait', - 'Kyrgyzstan' = 'Kyrgyzstan', - "Lao People's Democratic Republic" = "Lao People's Democratic Republic", - 'Latvia' = 'Latvia', - 'Lebanon' = 'Lebanon', - 'Lesotho' = 'Lesotho', - 'Liberia' = 'Liberia', - 'Libyan Arab Jamahiriya' = 'Libyan Arab Jamahiriya', - 'Liechtenstein' = 'Liechtenstein', - 'Lithuania' = 'Lithuania', - 'Luxembourg' = 'Luxembourg', - 'Macao' = 'Macao', - 'Macedonia' = 'Macedonia', - 'Madagascar' = 'Madagascar', - 'Malawi' = 'Malawi', - 'Malaysia' = 'Malaysia', - 'Maldives' = 'Maldives', - 'Mali' = 'Mali', - 'Malta' = 'Malta', - 'Marshall Islands' = 'Marshall Islands', - 'Martinique' = 'Martinique', - 'Mauritania' = 'Mauritania', - 'Mauritius' = 'Mauritius', - 'Mayotte' = 'Mayotte', - 'Mexico' = 'Mexico', - 'Micronesia' = 'Micronesia', - 'Moldova' = 'Moldova', - 'Monaco' = 'Monaco', - 'Mongolia' = 'Mongolia', - 'Montserrat' = 'Montserrat', - 'Morocco' = 'Morocco', - 'Mozambique' = 'Mozambique', - 'Myanmar' = 'Myanmar', - 'Namibia' = 'Namibia', - 'Nauru' = 'Nauru', - 'Nepal' = 'Nepal', - 'Netherlands' = 'Netherlands', - 'Netherlands Antilles' = 'Netherlands Antilles', - 'New Caledonia' = 'New Caledonia', - 'New Zealand' = 'New Zealand', - 'Nicaragua' = 'Nicaragua', - 'Niger' = 'Niger', - 'Nigeria' = 'Nigeria', - 'Niue' = 'Niue', - 'Norfolk Island' = 'Norfolk Island', - 'Northern Mariana Islands' = 'Northern Mariana Islands', - 'Norway' = 'Norway', - 'Oman' = 'Oman', - 'Pakistan' = 'Pakistan', - 'Palau' = 'Palau', - 'Palestine State' = 'Palestine State', - 'Panama' = 'Panama', - 'Papua New Guinea' = 'Papua New Guinea', - 'Paraguay' = 'Paraguay', - 'Peru' = 'Peru', - 'Philippines' = 'Philippines', - 'Pitcairn' = 'Pitcairn', - 'Poland' = 'Poland', - 'Portugal' = 'Portugal', - 'Puerto Rico' = 'Puerto Rico', - 'Qatar' = 'Qatar', - 'Reunion' = 'Reunion', - 'Romania' = 'Romania', - 'Russian Federation' = 'Russian Federation', - 'Rwanda' = 'Rwanda', - 'Saint Helena' = 'Saint Helena', - 'Saint Kitts and Nevis' = 'Saint Kitts and Nevis', - 'Saint Lucia' = 'Saint Lucia', - 'Saint Pierre and Miquelon' = 'Saint Pierre and Miquelon', - 'Saint Vincent and the Grenadines' = 'Saint Vincent and the Grenadines', - 'Samoa' = 'Samoa', - 'San Marino' = 'San Marino', - 'Sao Tome and Principe' = 'Sao Tome and Principe', - 'Saudi Arabia' = 'Saudi Arabia', - 'Senegal' = 'Senegal', - 'Serbia and Montenegro' = 'Serbia and Montenegro', - 'Seychelles' = 'Seychelles', - 'Sierra Leone' = 'Sierra Leone', - 'Singapore' = 'Singapore', - 'Slovakia' = 'Slovakia', - 'Slovenia' = 'Slovenia', - 'Solomon Islands' = 'Solomon Islands', - 'Somalia' = 'Somalia', - 'South Africa' = 'South Africa', - 'South Georgia and the South Sandwich Islands' = 'South Georgia and the South Sandwich Islands', - 'Spain' = 'Spain', - 'Sri Lanka' = 'Sri Lanka', - 'Sudan' = 'Sudan', - 'Suriname' = 'Suriname', - 'Svalbard and Jan Mayen' = 'Svalbard and Jan Mayen', - 'Swaziland' = 'Swaziland', - 'Sweden' = 'Sweden', - 'Switzerland' = 'Switzerland', - 'Syrian Arab Republic' = 'Syrian Arab Republic', - 'Taiwan, Province of China' = 'Taiwan, Province of China', - 'Tajikistan' = 'Tajikistan', - 'Tanzania' = 'Tanzania', - 'Thailand' = 'Thailand', - 'Timor-Leste' = 'Timor-Leste', - 'Togo' = 'Togo', - 'Tokelau' = 'Tokelau', - 'Tonga' = 'Tonga', - 'Trinidad and Tobago' = 'Trinidad and Tobago', - 'Tunisia' = 'Tunisia', - 'Turkey' = 'Turkey', - 'Turkmenistan' = 'Turkmenistan', - 'Turks and Caicos Islands' = 'Turks and Caicos Islands', - 'Tuvalu' = 'Tuvalu', - 'Uganda' = 'Uganda', - 'Ukraine' = 'Ukraine', - 'United Arab Emirates' = 'United Arab Emirates', - 'United Kingdom' = 'United Kingdom', - 'United States' = 'United States', - 'United States Minor Outlying Islands' = 'United States Minor Outlying Islands', - 'Uruguay' = 'Uruguay', - 'Uzbekistan' = 'Uzbekistan', - 'Vanuatu' = 'Vanuatu', - 'Venezuela' = 'Venezuela', - 'Viet Nam' = 'Viet Nam', - 'Virgin Islands, British' = 'Virgin Islands, British', - 'Virgin Islands, U.S.' = 'Virgin Islands, U.S.', - 'Wallis and Futuna' = 'Wallis and Futuna', - 'Western Sahara' = 'Western Sahara', - 'Yemen' = 'Yemen', - 'Zambia' = 'Zambia', - 'Zimbabwe' = 'Zimbabwe', + "Afghanistan" = "Afghanistan", + "Åland Islands" = "Åland Islands", + "Albania" = "Albania", + "Algeria" = "Algeria", + "American Samoa" = "American Samoa", + "Andorra" = "Andorra", + "Angola" = "Angola", + "Anguilla" = "Anguilla", + "Antarctica" = "Antarctica", + "Antigua and Barbuda" = "Antigua and Barbuda", + "Argentina" = "Argentina", + "Armenia" = "Armenia", + "Aruba" = "Aruba", + "Australia" = "Australia", + "Austria" = "Austria", + "Azerbaijan" = "Azerbaijan", + "Bahamas" = "Bahamas", + "Bahrain" = "Bahrain", + "Bangladesh" = "Bangladesh", + "Barbados" = "Barbados", + "Belarus" = "Belarus", + "Belgium" = "Belgium", + "Belize" = "Belize", + "Benin" = "Benin", + "Bermuda" = "Bermuda", + "Bhutan" = "Bhutan", + "Bolivia" = "Bolivia", + "Bosnia and Herzegovina" = "Bosnia and Herzegovina", + "Botswana" = "Botswana", + "Bouvet Island" = "Bouvet Island", + "Brazil" = "Brazil", + "British Indian Ocean Territory" = "British Indian Ocean Territory", + "Brunei Darussalam" = "Brunei Darussalam", + "Bulgaria" = "Bulgaria", + "Burkina Faso" = "Burkina Faso", + "Burundi" = "Burundi", + "Cambodia" = "Cambodia", + "Cameroon" = "Cameroon", + "Canada" = "Canada", + "Cape Verde" = "Cape Verde", + "Cayman Islands" = "Cayman Islands", + "Central African Republic" = "Central African Republic", + "Chad" = "Chad", + "Chile" = "Chile", + "China" = "China", + "Christmas Island" = "Christmas Island", + "Cocos (Keeling) Islands" = "Cocos (Keeling) Islands", + "Colombia" = "Colombia", + "Comoros" = "Comoros", + "Congo" = "Congo", + "Congo, The Democratic Republic of the" = "Congo, The Democratic Republic of the", + "Cook Islands" = "Cook Islands", + "Costa Rica" = "Costa Rica", + "Cote d'Ivoire" = "Cote d'Ivoire", + "Croatia" = "Croatia", + "Cuba" = "Cuba", + "Cyprus" = "Cyprus", + "Czech Republic" = "Czech Republic", + "Denmark" = "Denmark", + "Djibouti" = "Djibouti", + "Dominica" = "Dominica", + "Dominican Republic" = "Dominican Republic", + "Ecuador" = "Ecuador", + "Egypt" = "Egypt", + "El Salvador" = "El Salvador", + "Equatorial Guinea" = "Equatorial Guinea", + "Eritrea" = "Eritrea", + "Estonia" = "Estonia", + "Ethiopia" = "Ethiopia", + "Falkland Islands (Malvinas)" = "Falkland Islands (Malvinas)", + "Faroe Islands" = "Faroe Islands", + "Fiji" = "Fiji", + "Finland" = "Finland", + "France" = "France", + "French Guiana" = "French Guiana", + "French Polynesia" = "French Polynesia", + "French Southern Territories" = "French Southern Territories", + "Gabon" = "Gabon", + "Gambia" = "Gambia", + "Georgia" = "Georgia", + "Germany" = "Germany", + "Ghana" = "Ghana", + "Gibraltar" = "Gibraltar", + "Greece" = "Greece", + "Greenland" = "Greenland", + "Grenada" = "Grenada", + "Guadeloupe" = "Guadeloupe", + "Guam" = "Guam", + "Guatemala" = "Guatemala", + "Guernsey" = "Guernsey", + "Guinea" = "Guinea", + "Guinea-Bissau" = "Guinea-Bissau", + "Guyana" = "Guyana", + "Haiti" = "Haiti", + "Heard Island and Mcdonald Islands" = "Heard Island and Mcdonald Islands", + "Holy See (Vatican City State)" = "Holy See (Vatican City State)", + "Honduras" = "Honduras", + "Hong Kong" = "Hong Kong", + "Hungary" = "Hungary", + "Iceland" = "Iceland", + "India" = "India", + "Indonesia" = "Indonesia", + "Iran" = "Iran", + "Iraq" = "Iraq", + "Ireland" = "Ireland", + "Isle of Man" = "Isle of Man", + "Israel" = "Israel", + "Italy" = "Italy", + "Jamaica" = "Jamaica", + "Japan" = "Japan", + "Jersey" = "Jersey", + "Jordan" = "Jordan", + "Kazakhstan" = "Kazakhstan", + "Kenya" = "Kenya", + "Kiribati" = "Kiribati", + "North Korea" = "North Korea", + "South Korea" = "South Korea", + "Kuwait" = "Kuwait", + "Kyrgyzstan" = "Kyrgyzstan", + "Lao People's Democratic Republic" = "Lao People's Democratic Republic", + "Latvia" = "Latvia", + "Lebanon" = "Lebanon", + "Lesotho" = "Lesotho", + "Liberia" = "Liberia", + "Libyan Arab Jamahiriya" = "Libyan Arab Jamahiriya", + "Liechtenstein" = "Liechtenstein", + "Lithuania" = "Lithuania", + "Luxembourg" = "Luxembourg", + "Macao" = "Macao", + "Macedonia" = "Macedonia", + "Madagascar" = "Madagascar", + "Malawi" = "Malawi", + "Malaysia" = "Malaysia", + "Maldives" = "Maldives", + "Mali" = "Mali", + "Malta" = "Malta", + "Marshall Islands" = "Marshall Islands", + "Martinique" = "Martinique", + "Mauritania" = "Mauritania", + "Mauritius" = "Mauritius", + "Mayotte" = "Mayotte", + "Mexico" = "Mexico", + "Micronesia" = "Micronesia", + "Moldova" = "Moldova", + "Monaco" = "Monaco", + "Mongolia" = "Mongolia", + "Montserrat" = "Montserrat", + "Morocco" = "Morocco", + "Mozambique" = "Mozambique", + "Myanmar" = "Myanmar", + "Namibia" = "Namibia", + "Nauru" = "Nauru", + "Nepal" = "Nepal", + "Netherlands" = "Netherlands", + "Netherlands Antilles" = "Netherlands Antilles", + "New Caledonia" = "New Caledonia", + "New Zealand" = "New Zealand", + "Nicaragua" = "Nicaragua", + "Niger" = "Niger", + "Nigeria" = "Nigeria", + "Niue" = "Niue", + "Norfolk Island" = "Norfolk Island", + "Northern Mariana Islands" = "Northern Mariana Islands", + "Norway" = "Norway", + "Oman" = "Oman", + "Pakistan" = "Pakistan", + "Palau" = "Palau", + "Palestine State" = "Palestine State", + "Panama" = "Panama", + "Papua New Guinea" = "Papua New Guinea", + "Paraguay" = "Paraguay", + "Peru" = "Peru", + "Philippines" = "Philippines", + "Pitcairn" = "Pitcairn", + "Poland" = "Poland", + "Portugal" = "Portugal", + "Puerto Rico" = "Puerto Rico", + "Qatar" = "Qatar", + "Reunion" = "Reunion", + "Romania" = "Romania", + "Russian Federation" = "Russian Federation", + "Rwanda" = "Rwanda", + "Saint Helena" = "Saint Helena", + "Saint Kitts and Nevis" = "Saint Kitts and Nevis", + "Saint Lucia" = "Saint Lucia", + "Saint Pierre and Miquelon" = "Saint Pierre and Miquelon", + "Saint Vincent and the Grenadines" = "Saint Vincent and the Grenadines", + "Samoa" = "Samoa", + "San Marino" = "San Marino", + "Sao Tome and Principe" = "Sao Tome and Principe", + "Saudi Arabia" = "Saudi Arabia", + "Senegal" = "Senegal", + "Serbia and Montenegro" = "Serbia and Montenegro", + "Seychelles" = "Seychelles", + "Sierra Leone" = "Sierra Leone", + "Singapore" = "Singapore", + "Slovakia" = "Slovakia", + "Slovenia" = "Slovenia", + "Solomon Islands" = "Solomon Islands", + "Somalia" = "Somalia", + "South Africa" = "South Africa", + "South Georgia and the South Sandwich Islands" = "South Georgia and the South Sandwich Islands", + "Spain" = "Spain", + "Sri Lanka" = "Sri Lanka", + "Sudan" = "Sudan", + "Suriname" = "Suriname", + "Svalbard and Jan Mayen" = "Svalbard and Jan Mayen", + "Swaziland" = "Swaziland", + "Sweden" = "Sweden", + "Switzerland" = "Switzerland", + "Syrian Arab Republic" = "Syrian Arab Republic", + "Taiwan, Province of China" = "Taiwan, Province of China", + "Tajikistan" = "Tajikistan", + "Tanzania" = "Tanzania", + "Thailand" = "Thailand", + "Timor-Leste" = "Timor-Leste", + "Togo" = "Togo", + "Tokelau" = "Tokelau", + "Tonga" = "Tonga", + "Trinidad and Tobago" = "Trinidad and Tobago", + "Tunisia" = "Tunisia", + "Turkey" = "Turkey", + "Turkmenistan" = "Turkmenistan", + "Turks and Caicos Islands" = "Turks and Caicos Islands", + "Tuvalu" = "Tuvalu", + "Uganda" = "Uganda", + "Ukraine" = "Ukraine", + "United Arab Emirates" = "United Arab Emirates", + "United Kingdom" = "United Kingdom", + "United States" = "United States", + "United States Minor Outlying Islands" = "United States Minor Outlying Islands", + "Uruguay" = "Uruguay", + "Uzbekistan" = "Uzbekistan", + "Vanuatu" = "Vanuatu", + "Venezuela" = "Venezuela", + "Viet Nam" = "Viet Nam", + "Virgin Islands, British" = "Virgin Islands, British", + "Virgin Islands, U.S." = "Virgin Islands, U.S.", + "Wallis and Futuna" = "Wallis and Futuna", + "Western Sahara" = "Western Sahara", + "Yemen" = "Yemen", + "Zambia" = "Zambia", + "Zimbabwe" = "Zimbabwe", } export default Countries; diff --git a/Common/Types/Currency.ts b/Common/Types/Currency.ts index 9dd87c996f..ee488bff0d 100644 --- a/Common/Types/Currency.ts +++ b/Common/Types/Currency.ts @@ -1,34 +1,34 @@ -import BadDataException from './Exception/BadDataException'; +import BadDataException from "./Exception/BadDataException"; export default class Currency { - public static convertToDecimalPlaces( - value: number, - decimalPlaces: number = 2 - ): number { - if (decimalPlaces < 0) { - throw new BadDataException( - 'decimalPlaces must be greater than or equal to 0.' - ); - } - - if (typeof value === 'string') { - value = parseFloat(value); - } - - if (decimalPlaces === 0) { - return Math.ceil(value); - } - - value = value * Math.pow(10, decimalPlaces); - - // convert to int. - - value = Math.round(value); - - // convert back to float. - - value = value / Math.pow(10, decimalPlaces); - - return value; + public static convertToDecimalPlaces( + value: number, + decimalPlaces: number = 2, + ): number { + if (decimalPlaces < 0) { + throw new BadDataException( + "decimalPlaces must be greater than or equal to 0.", + ); } + + if (typeof value === "string") { + value = parseFloat(value); + } + + if (decimalPlaces === 0) { + return Math.ceil(value); + } + + value = value * Math.pow(10, decimalPlaces); + + // convert to int. + + value = Math.round(value); + + // convert back to float. + + value = value / Math.pow(10, decimalPlaces); + + return value; + } } diff --git a/Common/Types/CustomField/CustomFieldType.ts b/Common/Types/CustomField/CustomFieldType.ts index b6655b8bca..a3a909d790 100644 --- a/Common/Types/CustomField/CustomFieldType.ts +++ b/Common/Types/CustomField/CustomFieldType.ts @@ -1,7 +1,7 @@ enum CustomFieldType { - Text = 'Text', - Number = 'Number', - Boolean = 'Boolean', + Text = "Text", + Number = "Number", + Boolean = "Boolean", } export default CustomFieldType; diff --git a/Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid.ts b/Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid.ts index db258bb6a6..0637d1da07 100644 --- a/Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid.ts +++ b/Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid.ts @@ -1,7 +1,7 @@ -import GenericFunction from '../../GenericFunction'; +import GenericFunction from "../../GenericFunction"; export default () => { - return (ctr: GenericFunction) => { - ctr.prototype.allowAccessIfSubscriptionIsUnpaid = true; - }; + return (ctr: GenericFunction) => { + ctr.prototype.allowAccessIfSubscriptionIsUnpaid = true; + }; }; diff --git a/Common/Types/Database/AccessControl/ColumnAccessControl.ts b/Common/Types/Database/AccessControl/ColumnAccessControl.ts index 6dcf6b9d4c..f57271f27e 100644 --- a/Common/Types/Database/AccessControl/ColumnAccessControl.ts +++ b/Common/Types/Database/AccessControl/ColumnAccessControl.ts @@ -1,49 +1,49 @@ -import BaseModel from '../../../Models/BaseModel'; -import { ColumnAccessControl } from '../../BaseDatabase/AccessControl'; -import Dictionary from '../../Dictionary'; -import { ReflectionMetadataType } from '../../Reflection'; -import 'reflect-metadata'; +import BaseModel from "../../../Models/BaseModel"; +import { ColumnAccessControl } from "../../BaseDatabase/AccessControl"; +import Dictionary from "../../Dictionary"; +import { ReflectionMetadataType } from "../../Reflection"; +import "reflect-metadata"; -const accessControlSymbol: symbol = Symbol('ColumnAccessControl'); +const accessControlSymbol: symbol = Symbol("ColumnAccessControl"); export default (accessControl: ColumnAccessControl): ReflectionMetadataType => { - return Reflect.metadata(accessControlSymbol, accessControl); + return Reflect.metadata(accessControlSymbol, accessControl); }; type GetColumnAccessControlFunction = ( - target: BaseModel, - propertyKey: string + target: BaseModel, + propertyKey: string, ) => ColumnAccessControl; export const getColumnAccessControl: GetColumnAccessControlFunction = ( - target: BaseModel, - propertyKey: string + target: BaseModel, + propertyKey: string, ): ColumnAccessControl => { - return Reflect.getMetadata( - accessControlSymbol, - target, - propertyKey - ) as ColumnAccessControl; + return Reflect.getMetadata( + accessControlSymbol, + target, + propertyKey, + ) as ColumnAccessControl; }; type GetColumnAccessControlForAllColumnsFunction = <T extends BaseModel>( - target: T + target: T, ) => Dictionary<ColumnAccessControl>; export const getColumnAccessControlForAllColumns: GetColumnAccessControlForAllColumnsFunction = - <T extends BaseModel>(target: T): Dictionary<ColumnAccessControl> => { - const dictonary: Dictionary<ColumnAccessControl> = {}; - const keys: Array<string> = Object.keys(target); + <T extends BaseModel>(target: T): Dictionary<ColumnAccessControl> => { + const dictonary: Dictionary<ColumnAccessControl> = {}; + const keys: Array<string> = Object.keys(target); - for (const key of keys) { - if (Reflect.getMetadata(accessControlSymbol, target, key)) { - dictonary[key] = Reflect.getMetadata( - accessControlSymbol, - target, - key - ) as ColumnAccessControl; - } - } + for (const key of keys) { + if (Reflect.getMetadata(accessControlSymbol, target, key)) { + dictonary[key] = Reflect.getMetadata( + accessControlSymbol, + target, + key, + ) as ColumnAccessControl; + } + } - return dictonary; - }; + return dictonary; + }; diff --git a/Common/Types/Database/AccessControl/ColumnBillingAccessControl.ts b/Common/Types/Database/AccessControl/ColumnBillingAccessControl.ts index 2801c94f68..90b2ffbe7c 100644 --- a/Common/Types/Database/AccessControl/ColumnBillingAccessControl.ts +++ b/Common/Types/Database/AccessControl/ColumnBillingAccessControl.ts @@ -1,51 +1,49 @@ -import BaseModel from '../../../Models/BaseModel'; -import ColumnBillingAccessControl from '../../BaseDatabase/ColumnBillingAccessControl'; -import Dictionary from '../../Dictionary'; -import { ReflectionMetadataType } from '../../Reflection'; -import 'reflect-metadata'; +import BaseModel from "../../../Models/BaseModel"; +import ColumnBillingAccessControl from "../../BaseDatabase/ColumnBillingAccessControl"; +import Dictionary from "../../Dictionary"; +import { ReflectionMetadataType } from "../../Reflection"; +import "reflect-metadata"; -const accessControlSymbol: symbol = Symbol('ColumnBillingAccessControl'); +const accessControlSymbol: symbol = Symbol("ColumnBillingAccessControl"); export default ( - accessControl: ColumnBillingAccessControl + accessControl: ColumnBillingAccessControl, ): ReflectionMetadataType => { - return Reflect.metadata(accessControlSymbol, accessControl); + return Reflect.metadata(accessControlSymbol, accessControl); }; type GetColumnBillingAccessControlFunction = ( - target: BaseModel, - propertyKey: string + target: BaseModel, + propertyKey: string, ) => ColumnBillingAccessControl; export const getColumnBillingAccessControl: GetColumnBillingAccessControlFunction = - (target: BaseModel, propertyKey: string): ColumnBillingAccessControl => { - return Reflect.getMetadata( - accessControlSymbol, - target, - propertyKey - ) as ColumnBillingAccessControl; - }; + (target: BaseModel, propertyKey: string): ColumnBillingAccessControl => { + return Reflect.getMetadata( + accessControlSymbol, + target, + propertyKey, + ) as ColumnBillingAccessControl; + }; type GetColumnBillingAccessControlForAllColumnsFunction = <T extends BaseModel>( - target: T + target: T, ) => Dictionary<ColumnBillingAccessControl>; export const getColumnBillingAccessControlForAllColumns: GetColumnBillingAccessControlForAllColumnsFunction = - <T extends BaseModel>( - target: T - ): Dictionary<ColumnBillingAccessControl> => { - const dictonary: Dictionary<ColumnBillingAccessControl> = {}; - const keys: Array<string> = Object.keys(target); + <T extends BaseModel>(target: T): Dictionary<ColumnBillingAccessControl> => { + const dictonary: Dictionary<ColumnBillingAccessControl> = {}; + const keys: Array<string> = Object.keys(target); - for (const key of keys) { - if (Reflect.getMetadata(accessControlSymbol, target, key)) { - dictonary[key] = Reflect.getMetadata( - accessControlSymbol, - target, - key - ) as ColumnBillingAccessControl; - } - } + for (const key of keys) { + if (Reflect.getMetadata(accessControlSymbol, target, key)) { + dictonary[key] = Reflect.getMetadata( + accessControlSymbol, + target, + key, + ) as ColumnBillingAccessControl; + } + } - return dictonary; - }; + return dictonary; + }; diff --git a/Common/Types/Database/AccessControl/TableAccessControl.ts b/Common/Types/Database/AccessControl/TableAccessControl.ts index d16e9120d0..b3ae913eb6 100644 --- a/Common/Types/Database/AccessControl/TableAccessControl.ts +++ b/Common/Types/Database/AccessControl/TableAccessControl.ts @@ -1,22 +1,22 @@ -import { TableAccessControl } from '../../BaseDatabase/AccessControl'; -import GenericFunction from '../../GenericFunction'; +import { TableAccessControl } from "../../BaseDatabase/AccessControl"; +import GenericFunction from "../../GenericFunction"; export default (accessControl: TableAccessControl) => { - return (ctr: GenericFunction) => { - if (accessControl.create) { - ctr.prototype.createRecordPermissions = accessControl.create; - } + return (ctr: GenericFunction) => { + if (accessControl.create) { + ctr.prototype.createRecordPermissions = accessControl.create; + } - if (accessControl.read) { - ctr.prototype.readRecordPermissions = accessControl.read; - } + if (accessControl.read) { + ctr.prototype.readRecordPermissions = accessControl.read; + } - if (accessControl.update) { - ctr.prototype.updateRecordPermissions = accessControl.update; - } + if (accessControl.update) { + ctr.prototype.updateRecordPermissions = accessControl.update; + } - if (accessControl.delete) { - ctr.prototype.deleteRecordPermissions = accessControl.delete; - } - }; + if (accessControl.delete) { + ctr.prototype.deleteRecordPermissions = accessControl.delete; + } + }; }; diff --git a/Common/Types/Database/AccessControl/TableBillingAccessControl.ts b/Common/Types/Database/AccessControl/TableBillingAccessControl.ts index 24e312eb34..fe35b347ad 100644 --- a/Common/Types/Database/AccessControl/TableBillingAccessControl.ts +++ b/Common/Types/Database/AccessControl/TableBillingAccessControl.ts @@ -1,22 +1,22 @@ -import TableBillingAccessControl from '../../BaseDatabase/TableBillingAccessControl'; -import GenericFunction from '../../GenericFunction'; +import TableBillingAccessControl from "../../BaseDatabase/TableBillingAccessControl"; +import GenericFunction from "../../GenericFunction"; export default (accessControl: TableBillingAccessControl) => { - return (ctr: GenericFunction) => { - if (accessControl.create) { - ctr.prototype.createBillingPlan = accessControl.create; - } + return (ctr: GenericFunction) => { + if (accessControl.create) { + ctr.prototype.createBillingPlan = accessControl.create; + } - if (accessControl.read) { - ctr.prototype.readBillingPlan = accessControl.read; - } + if (accessControl.read) { + ctr.prototype.readBillingPlan = accessControl.read; + } - if (accessControl.update) { - ctr.prototype.updateBillingPlan = accessControl.update; - } + if (accessControl.update) { + ctr.prototype.updateBillingPlan = accessControl.update; + } - if (accessControl.delete) { - ctr.prototype.deleteBillingPlan = accessControl.delete; - } - }; + if (accessControl.delete) { + ctr.prototype.deleteBillingPlan = accessControl.delete; + } + }; }; diff --git a/Common/Types/Database/AccessControlColumn.ts b/Common/Types/Database/AccessControlColumn.ts index 324affb693..6f55896e36 100644 --- a/Common/Types/Database/AccessControlColumn.ts +++ b/Common/Types/Database/AccessControlColumn.ts @@ -1,7 +1,7 @@ -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export default (columnName: string) => { - return (ctr: GenericFunction) => { - ctr.prototype.accessControlColumn = columnName; - }; + return (ctr: GenericFunction) => { + ctr.prototype.accessControlColumn = columnName; + }; }; diff --git a/Common/Types/Database/AllowUserQueryWithoutTenant.ts b/Common/Types/Database/AllowUserQueryWithoutTenant.ts index 3e5939ddb8..80d948cdb0 100644 --- a/Common/Types/Database/AllowUserQueryWithoutTenant.ts +++ b/Common/Types/Database/AllowUserQueryWithoutTenant.ts @@ -1,7 +1,7 @@ -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export default (allowed: boolean) => { - return (ctr: GenericFunction) => { - ctr.prototype.allowUserQueryWithoutTenant = allowed; - }; + return (ctr: GenericFunction) => { + ctr.prototype.allowUserQueryWithoutTenant = allowed; + }; }; diff --git a/Common/Types/Database/CanAccessIfCanReadOn.ts b/Common/Types/Database/CanAccessIfCanReadOn.ts index 58b4aad9ab..45787b07fc 100644 --- a/Common/Types/Database/CanAccessIfCanReadOn.ts +++ b/Common/Types/Database/CanAccessIfCanReadOn.ts @@ -1,10 +1,10 @@ // This Annotation only allows the record to be CRUD if the user has READ access on related record. -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export default (columnName: string) => { - return (ctr: GenericFunction) => { - if (columnName) { - ctr.prototype.canAccessIfCanReadOn = columnName; - } - }; + return (ctr: GenericFunction) => { + if (columnName) { + ctr.prototype.canAccessIfCanReadOn = columnName; + } + }; }; diff --git a/Common/Types/Database/ColumnLength.ts b/Common/Types/Database/ColumnLength.ts index 98a4f76ef8..b67e1f41f5 100644 --- a/Common/Types/Database/ColumnLength.ts +++ b/Common/Types/Database/ColumnLength.ts @@ -1,72 +1,72 @@ -import TableColumnType from './TableColumnType'; +import TableColumnType from "./TableColumnType"; enum ColumnLength { - Version = 30, - Slug = 100, - Email = 100, - Domain = 100, - Color = 7, - Name = 50, - Description = 500, - LongText = 500, - Password = 500, - ShortURL = 100, - ShortText = 100, - HashedString = 64, - Phone = 30, - OTP = 8, + Version = 30, + Slug = 100, + Email = 100, + Domain = 100, + Color = 7, + Name = 50, + Description = 500, + LongText = 500, + Password = 500, + ShortURL = 100, + ShortText = 100, + HashedString = 64, + Phone = 30, + OTP = 8, } type GetMaxLengthFromTableColumnTypeFunction = ( - type: TableColumnType + type: TableColumnType, ) => number | undefined; export const getMaxLengthFromTableColumnType: GetMaxLengthFromTableColumnTypeFunction = - (type: TableColumnType): number | undefined => { - if (type === TableColumnType.Version) { - return ColumnLength.Version; - } - if (type === TableColumnType.Slug) { - return ColumnLength.Slug; - } - if (type === TableColumnType.Email) { - return ColumnLength.Email; - } - if (type === TableColumnType.Domain) { - return ColumnLength.Domain; - } - if (type === TableColumnType.Color) { - return ColumnLength.Color; - } - if (type === TableColumnType.Name) { - return ColumnLength.Name; - } - if (type === TableColumnType.Description) { - return ColumnLength.Description; - } - if (type === TableColumnType.LongText) { - return ColumnLength.LongText; - } - if (type === TableColumnType.Password) { - return ColumnLength.Password; - } - if (type === TableColumnType.ShortURL) { - return ColumnLength.ShortURL; - } - if (type === TableColumnType.ShortText) { - return ColumnLength.ShortText; - } - if (type === TableColumnType.HashedString) { - return ColumnLength.HashedString; - } - if (type === TableColumnType.Phone) { - return ColumnLength.Phone; - } - if (type === TableColumnType.OTP) { - return ColumnLength.OTP; - } + (type: TableColumnType): number | undefined => { + if (type === TableColumnType.Version) { + return ColumnLength.Version; + } + if (type === TableColumnType.Slug) { + return ColumnLength.Slug; + } + if (type === TableColumnType.Email) { + return ColumnLength.Email; + } + if (type === TableColumnType.Domain) { + return ColumnLength.Domain; + } + if (type === TableColumnType.Color) { + return ColumnLength.Color; + } + if (type === TableColumnType.Name) { + return ColumnLength.Name; + } + if (type === TableColumnType.Description) { + return ColumnLength.Description; + } + if (type === TableColumnType.LongText) { + return ColumnLength.LongText; + } + if (type === TableColumnType.Password) { + return ColumnLength.Password; + } + if (type === TableColumnType.ShortURL) { + return ColumnLength.ShortURL; + } + if (type === TableColumnType.ShortText) { + return ColumnLength.ShortText; + } + if (type === TableColumnType.HashedString) { + return ColumnLength.HashedString; + } + if (type === TableColumnType.Phone) { + return ColumnLength.Phone; + } + if (type === TableColumnType.OTP) { + return ColumnLength.OTP; + } - return undefined; - }; + return undefined; + }; export default ColumnLength; diff --git a/Common/Types/Database/ColumnType.ts b/Common/Types/Database/ColumnType.ts index fbb596c74f..5e4c3fd074 100644 --- a/Common/Types/Database/ColumnType.ts +++ b/Common/Types/Database/ColumnType.ts @@ -1,37 +1,37 @@ enum ColumnType { - Color = 'varchar', - Version = 'varchar', - Phone = 'varchar', - HashedString = 'varchar', - Password = 'varchar', - Email = 'varchar', - Slug = 'varchar', - Name = 'varchar', - Description = 'varchar', - ObjectID = 'uuid', - ShortURL = 'varchar', - LongURL = 'text', - ShortText = 'varchar', - OTP = 'varchar', - Domain = 'varchar', - LongText = 'varchar', - VeryLongText = 'text', - HTML = 'text', - JavaScript = 'text', - CSS = 'text', - Date = 'timestamptz', - Boolean = 'boolean', - Array = 'simple-array', - SmallPositiveNumber = 'smallint', - PositiveNumber = 'integer', - BigPositiveNumber = 'bigint', - SmallNumber = 'smallint', - Decimal = 'decimal', - Number = 'integer', - BigNumber = 'bigint', - Markdown = 'text', - File = 'bytea', - JSON = 'jsonb', + Color = "varchar", + Version = "varchar", + Phone = "varchar", + HashedString = "varchar", + Password = "varchar", + Email = "varchar", + Slug = "varchar", + Name = "varchar", + Description = "varchar", + ObjectID = "uuid", + ShortURL = "varchar", + LongURL = "text", + ShortText = "varchar", + OTP = "varchar", + Domain = "varchar", + LongText = "varchar", + VeryLongText = "text", + HTML = "text", + JavaScript = "text", + CSS = "text", + Date = "timestamptz", + Boolean = "boolean", + Array = "simple-array", + SmallPositiveNumber = "smallint", + PositiveNumber = "integer", + BigPositiveNumber = "bigint", + SmallNumber = "smallint", + Decimal = "decimal", + Number = "integer", + BigNumber = "bigint", + Markdown = "text", + File = "bytea", + JSON = "jsonb", } export default ColumnType; diff --git a/Common/Types/Database/Columns.ts b/Common/Types/Database/Columns.ts index 2799a3c65a..d872f434eb 100644 --- a/Common/Types/Database/Columns.ts +++ b/Common/Types/Database/Columns.ts @@ -1,21 +1,21 @@ export default class Columns { - private _columns: Array<string> = []; - public get columns(): Array<string> { - return this._columns; - } - public set columns(v: Array<string>) { - this._columns = v; - } + private _columns: Array<string> = []; + public get columns(): Array<string> { + return this._columns; + } + public set columns(v: Array<string>) { + this._columns = v; + } - public constructor(columns: Array<string>) { - this.columns = columns; - } + public constructor(columns: Array<string>) { + this.columns = columns; + } - public addColumn(columnName: string): void { - this.columns.push(columnName); - } + public addColumn(columnName: string): void { + this.columns.push(columnName); + } - public hasColumn(columnName: string): boolean { - return this.columns.includes(columnName); - } + public hasColumn(columnName: string): boolean { + return this.columns.includes(columnName); + } } diff --git a/Common/Types/Database/CompareBase.ts b/Common/Types/Database/CompareBase.ts index 26e4e6f082..cfcff12186 100644 --- a/Common/Types/Database/CompareBase.ts +++ b/Common/Types/Database/CompareBase.ts @@ -1,38 +1,38 @@ -import BadDataException from '../Exception/BadDataException'; -import SerializableObject from '../SerializableObject'; -import Typeof from '../Typeof'; +import BadDataException from "../Exception/BadDataException"; +import SerializableObject from "../SerializableObject"; +import Typeof from "../Typeof"; export default class CompareBase extends SerializableObject { - private _value!: number | Date; - public get value(): number | Date { - return this._value; - } - public set value(v: number | Date) { - this._value = v; + private _value!: number | Date; + public get value(): number | Date { + return this._value; + } + public set value(v: number | Date) { + this._value = v; + } + + public constructor(value: number | Date) { + super(); + this.value = value; + } + + public override toString(): string { + return this.value.toString(); + } + + public toNumber(): number { + if (Typeof.Number === typeof this.value) { + return this.value as number; } - public constructor(value: number | Date) { - super(); - this.value = value; + throw new BadDataException("Value is not a number"); + } + + public toDate(): Date { + if (this.value instanceof Date) { + return this.value as Date; } - public override toString(): string { - return this.value.toString(); - } - - public toNumber(): number { - if (Typeof.Number === typeof this.value) { - return this.value as number; - } - - throw new BadDataException('Value is not a number'); - } - - public toDate(): Date { - if (this.value instanceof Date) { - return this.value as Date; - } - - throw new BadDataException('Value is not a date object'); - } + throw new BadDataException("Value is not a date object"); + } } diff --git a/Common/Types/Database/CrudApiEndpoint.ts b/Common/Types/Database/CrudApiEndpoint.ts index 516f3436e6..c8af34b5ff 100644 --- a/Common/Types/Database/CrudApiEndpoint.ts +++ b/Common/Types/Database/CrudApiEndpoint.ts @@ -1,8 +1,8 @@ -import Route from '../API/Route'; -import GenericFunction from '../GenericFunction'; +import Route from "../API/Route"; +import GenericFunction from "../GenericFunction"; export default (apiPath: Route) => { - return (ctr: GenericFunction) => { - ctr.prototype.crudApiPath = apiPath; - }; + return (ctr: GenericFunction) => { + ctr.prototype.crudApiPath = apiPath; + }; }; diff --git a/Common/Types/Database/CurrentUserCanAccessRecordBy.ts b/Common/Types/Database/CurrentUserCanAccessRecordBy.ts index aef2124e4b..d01f40fbe3 100644 --- a/Common/Types/Database/CurrentUserCanAccessRecordBy.ts +++ b/Common/Types/Database/CurrentUserCanAccessRecordBy.ts @@ -1,7 +1,7 @@ -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export default (columnName: string) => { - return (ctr: GenericFunction) => { - ctr.prototype.currentUserCanAccessColumnBy = columnName; - }; + return (ctr: GenericFunction) => { + ctr.prototype.currentUserCanAccessColumnBy = columnName; + }; }; diff --git a/Common/Types/Database/DatabaseProperty.ts b/Common/Types/Database/DatabaseProperty.ts index bc61d89e1b..d704da2d6d 100644 --- a/Common/Types/Database/DatabaseProperty.ts +++ b/Common/Types/Database/DatabaseProperty.ts @@ -1,51 +1,51 @@ -import NotImplementedException from '../Exception/NotImplementedException'; -import { JSONArray, JSONObject } from '../JSON'; -import SerializableObject from '../SerializableObject'; -import { FindOperator } from 'typeorm'; -import { ValueTransformer } from 'typeorm/decorator/options/ValueTransformer'; +import NotImplementedException from "../Exception/NotImplementedException"; +import { JSONArray, JSONObject } from "../JSON"; +import SerializableObject from "../SerializableObject"; +import { FindOperator } from "typeorm"; +import { ValueTransformer } from "typeorm/decorator/options/ValueTransformer"; export default class DatabaseProperty extends SerializableObject { - public constructor() { - super(); + public constructor() { + super(); + } + + protected static fromDatabase( + _value: string | number | JSONObject | JSONArray, + ): DatabaseProperty | null { + throw new NotImplementedException(); + } + + protected static toDatabase( + _value: DatabaseProperty | FindOperator<DatabaseProperty>, + ): string | number | JSONObject | JSONArray | null { + throw new NotImplementedException(); + } + + protected static _fromDatabase( + value: string | number | JSONObject | JSONArray, + ): DatabaseProperty | null { + return this.fromDatabase(value); + } + + protected static _toDatabase( + value: DatabaseProperty | FindOperator<DatabaseProperty>, + ): string | number | JSONObject | JSONArray | null { + // if its a RAW query. Return a raw query. + if (value && (value as any)._type === "raw") { + return value as any; } - protected static fromDatabase( - _value: string | number | JSONObject | JSONArray - ): DatabaseProperty | null { - throw new NotImplementedException(); - } + return this.toDatabase(value); + } - protected static toDatabase( - _value: DatabaseProperty | FindOperator<DatabaseProperty> - ): string | number | JSONObject | JSONArray | null { - throw new NotImplementedException(); - } - - protected static _fromDatabase( - value: string | number | JSONObject | JSONArray - ): DatabaseProperty | null { - return this.fromDatabase(value); - } - - protected static _toDatabase( - value: DatabaseProperty | FindOperator<DatabaseProperty> - ): string | number | JSONObject | JSONArray | null { - // if its a RAW query. Return a raw query. - if (value && (value as any)._type === 'raw') { - return value as any; - } - - return this.toDatabase(value); - } - - public static getDatabaseTransformer(): ValueTransformer { - return { - to: (value: any) => { - return this._toDatabase(value); - }, - from: (value: any) => { - return this._fromDatabase(value); - }, - }; - } + public static getDatabaseTransformer(): ValueTransformer { + return { + to: (value: any) => { + return this._toDatabase(value); + }, + from: (value: any) => { + return this._fromDatabase(value); + }, + }; + } } diff --git a/Common/Types/Database/Date.ts b/Common/Types/Database/Date.ts index e53ce97b49..5f146701fb 100644 --- a/Common/Types/Database/Date.ts +++ b/Common/Types/Database/Date.ts @@ -1,28 +1,28 @@ -import InBetween from '../BaseDatabase/InBetween'; -import OneUptimeDate from '../Date'; +import InBetween from "../BaseDatabase/InBetween"; +import OneUptimeDate from "../Date"; export default class DatabaseDate { - public static asDateStartOfTheDayEndOfTheDayForDatabaseQuery( - date: string | Date - ): InBetween { - let startValue: string | Date = date; + public static asDateStartOfTheDayEndOfTheDayForDatabaseQuery( + date: string | Date, + ): InBetween { + let startValue: string | Date = date; - if (!(startValue instanceof Date)) { - startValue = OneUptimeDate.fromString(startValue); - } - - let endValue: string | Date = date; - - if (!(endValue instanceof Date)) { - endValue = OneUptimeDate.fromString(endValue); - } - - startValue = OneUptimeDate.getStartOfDay(startValue); - endValue = OneUptimeDate.getEndOfDay(endValue); - - return new InBetween( - OneUptimeDate.toDatabaseDate(startValue), - OneUptimeDate.toDatabaseDate(endValue) - ); + if (!(startValue instanceof Date)) { + startValue = OneUptimeDate.fromString(startValue); } + + let endValue: string | Date = date; + + if (!(endValue instanceof Date)) { + endValue = OneUptimeDate.fromString(endValue); + } + + startValue = OneUptimeDate.getStartOfDay(startValue); + endValue = OneUptimeDate.getEndOfDay(endValue); + + return new InBetween( + OneUptimeDate.toDatabaseDate(startValue), + OneUptimeDate.toDatabaseDate(endValue), + ); + } } diff --git a/Common/Types/Database/EnableDocumentation.ts b/Common/Types/Database/EnableDocumentation.ts index 66dd0cdb36..1f82823e27 100644 --- a/Common/Types/Database/EnableDocumentation.ts +++ b/Common/Types/Database/EnableDocumentation.ts @@ -1,13 +1,12 @@ -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export interface EnableDocumentationProps { - isMasterAdminApiDocs?: boolean | undefined; + isMasterAdminApiDocs?: boolean | undefined; } export default (props?: EnableDocumentationProps | undefined) => { - return (ctr: GenericFunction) => { - ctr.prototype.enableDocumentation = true; - ctr.prototype.isMasterAdminApiDocs = - props?.isMasterAdminApiDocs || false; - }; + return (ctr: GenericFunction) => { + ctr.prototype.enableDocumentation = true; + ctr.prototype.isMasterAdminApiDocs = props?.isMasterAdminApiDocs || false; + }; }; diff --git a/Common/Types/Database/EnableWorkflow.ts b/Common/Types/Database/EnableWorkflow.ts index 593bcbb59f..9a926755c1 100644 --- a/Common/Types/Database/EnableWorkflow.ts +++ b/Common/Types/Database/EnableWorkflow.ts @@ -1,8 +1,8 @@ -import EnableWorkflowOn from '../BaseDatabase/EnableWorkflowOn'; -import GenericFunction from '../GenericFunction'; +import EnableWorkflowOn from "../BaseDatabase/EnableWorkflowOn"; +import GenericFunction from "../GenericFunction"; export default (enableWorkflowOn: EnableWorkflowOn) => { - return (ctr: GenericFunction) => { - ctr.prototype.enableWorkflowOn = enableWorkflowOn; - }; + return (ctr: GenericFunction) => { + ctr.prototype.enableWorkflowOn = enableWorkflowOn; + }; }; diff --git a/Common/Types/Database/IsPermissionsIf.ts b/Common/Types/Database/IsPermissionsIf.ts index e078eb7118..a3472ab734 100644 --- a/Common/Types/Database/IsPermissionsIf.ts +++ b/Common/Types/Database/IsPermissionsIf.ts @@ -1,18 +1,18 @@ -import GenericFunction from '../GenericFunction'; -import Permission from '../Permission'; +import GenericFunction from "../GenericFunction"; +import Permission from "../Permission"; export default ( - permission: Permission, - columnName: string, - value: string | boolean | null + permission: Permission, + columnName: string, + value: string | boolean | null, ) => { - return (ctr: GenericFunction) => { - if (!ctr.prototype.isPermissionIf) { - ctr.prototype.isPermissionIf = {}; - } + return (ctr: GenericFunction) => { + if (!ctr.prototype.isPermissionIf) { + ctr.prototype.isPermissionIf = {}; + } - ctr.prototype.isPermissionIf[permission] = { - [columnName]: value, - }; + ctr.prototype.isPermissionIf[permission] = { + [columnName]: value, }; + }; }; diff --git a/Common/Types/Database/LabelsColumn.ts b/Common/Types/Database/LabelsColumn.ts index 72883fb47a..dfa937a40b 100644 --- a/Common/Types/Database/LabelsColumn.ts +++ b/Common/Types/Database/LabelsColumn.ts @@ -1,7 +1,7 @@ -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export default (columnName: string) => { - return (ctr: GenericFunction) => { - ctr.prototype.labelsColumn = columnName; - }; + return (ctr: GenericFunction) => { + ctr.prototype.labelsColumn = columnName; + }; }; diff --git a/Common/Types/Database/MultiTenentQueryAllowed.ts b/Common/Types/Database/MultiTenentQueryAllowed.ts index 0e430b2f20..4ee26fb35d 100644 --- a/Common/Types/Database/MultiTenentQueryAllowed.ts +++ b/Common/Types/Database/MultiTenentQueryAllowed.ts @@ -1,7 +1,7 @@ -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export default (allowed: boolean) => { - return (ctr: GenericFunction) => { - ctr.prototype.isMultiTenantRequestAllowed = allowed; - }; + return (ctr: GenericFunction) => { + ctr.prototype.isMultiTenantRequestAllowed = allowed; + }; }; diff --git a/Common/Types/Database/PartialEntity.ts b/Common/Types/Database/PartialEntity.ts index 7634e51865..fe9145b814 100644 --- a/Common/Types/Database/PartialEntity.ts +++ b/Common/Types/Database/PartialEntity.ts @@ -1,3 +1,3 @@ -import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"; export default QueryDeepPartialEntity; diff --git a/Common/Types/Database/SlugifyColumn.ts b/Common/Types/Database/SlugifyColumn.ts index 254f047706..d1f0256748 100644 --- a/Common/Types/Database/SlugifyColumn.ts +++ b/Common/Types/Database/SlugifyColumn.ts @@ -1,8 +1,8 @@ -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export default (columnName: string, saveSlugToColumnName: string) => { - return (ctr: GenericFunction) => { - ctr.prototype.slugifyColumn = columnName; - ctr.prototype.saveSlugToColumn = saveSlugToColumnName; - }; + return (ctr: GenericFunction) => { + ctr.prototype.slugifyColumn = columnName; + ctr.prototype.saveSlugToColumn = saveSlugToColumnName; + }; }; diff --git a/Common/Types/Database/TableColumn.ts b/Common/Types/Database/TableColumn.ts index 8d78197ef4..8eac23a386 100644 --- a/Common/Types/Database/TableColumn.ts +++ b/Common/Types/Database/TableColumn.ts @@ -1,66 +1,66 @@ -import BaseModel, { BaseModelType } from '../../Models/BaseModel'; -import Dictionary from '../Dictionary'; -import { ReflectionMetadataType } from '../Reflection'; -import TableColumnType from './TableColumnType'; -import 'reflect-metadata'; +import BaseModel, { BaseModelType } from "../../Models/BaseModel"; +import Dictionary from "../Dictionary"; +import { ReflectionMetadataType } from "../Reflection"; +import TableColumnType from "./TableColumnType"; +import "reflect-metadata"; -const tableColumn: symbol = Symbol('TableColumn'); +const tableColumn: symbol = Symbol("TableColumn"); export interface TableColumnMetadata { - title?: string; - description?: string; - placeholder?: string; - isDefaultValueColumn?: boolean; - required?: boolean; - unique?: boolean; - hashed?: boolean; - encrypted?: boolean; - manyToOneRelationColumn?: string; - type: TableColumnType; - canReadOnRelationQuery?: boolean; - modelType?: BaseModelType; - forceGetDefaultValueOnCreate?: () => string | number | boolean; // overwrites any value that is being passed and generates a new one. Useful for generating OTPs, etc. + title?: string; + description?: string; + placeholder?: string; + isDefaultValueColumn?: boolean; + required?: boolean; + unique?: boolean; + hashed?: boolean; + encrypted?: boolean; + manyToOneRelationColumn?: string; + type: TableColumnType; + canReadOnRelationQuery?: boolean; + modelType?: BaseModelType; + forceGetDefaultValueOnCreate?: () => string | number | boolean; // overwrites any value that is being passed and generates a new one. Useful for generating OTPs, etc. } export default (props: TableColumnMetadata): ReflectionMetadataType => { - return Reflect.metadata(tableColumn, props); + return Reflect.metadata(tableColumn, props); }; type GetTableColumnFunction = <T extends BaseModel>( - target: T, - propertyKey: string + target: T, + propertyKey: string, ) => TableColumnMetadata; export const getTableColumn: GetTableColumnFunction = <T extends BaseModel>( - target: T, - propertyKey: string + target: T, + propertyKey: string, ): TableColumnMetadata => { - return Reflect.getMetadata( - tableColumn, - target, - propertyKey - ) as TableColumnMetadata; + return Reflect.getMetadata( + tableColumn, + target, + propertyKey, + ) as TableColumnMetadata; }; type GetTableColumnsFunction = <T extends BaseModel>( - target: T + target: T, ) => Dictionary<TableColumnMetadata>; export const getTableColumns: GetTableColumnsFunction = <T extends BaseModel>( - target: T + target: T, ): Dictionary<TableColumnMetadata> => { - const dictonary: Dictionary<TableColumnMetadata> = {}; - const keys: Array<string> = Object.keys(target); + const dictonary: Dictionary<TableColumnMetadata> = {}; + const keys: Array<string> = Object.keys(target); - for (const key of keys) { - if (Reflect.getMetadata(tableColumn, target, key)) { - dictonary[key] = Reflect.getMetadata( - tableColumn, - target, - key - ) as TableColumnMetadata; - } + for (const key of keys) { + if (Reflect.getMetadata(tableColumn, target, key)) { + dictonary[key] = Reflect.getMetadata( + tableColumn, + target, + key, + ) as TableColumnMetadata; } + } - return dictonary; + return dictonary; }; diff --git a/Common/Types/Database/TableColumnType.ts b/Common/Types/Database/TableColumnType.ts index cb2677b647..5ada723db3 100644 --- a/Common/Types/Database/TableColumnType.ts +++ b/Common/Types/Database/TableColumnType.ts @@ -1,43 +1,43 @@ enum ColumnType { - Version = 'Version', - Phone = 'Phone', - HashedString = 'Hashed String', - Password = 'Password', - Email = 'Email', - Color = 'Color', - Slug = 'Slug', - Name = 'Name', - Description = 'Description', - ObjectID = 'Object ID', - File = 'File', - Buffer = 'Buffer', - ShortURL = 'Short URL', - Markdown = 'Markdown', - Domain = 'Domain', - LongURL = 'URL', - ShortText = 'Text', - OTP = 'One Time Password', - LongText = 'Long Text', - VeryLongText = 'Very Long Text', - HTML = 'HTML', - JavaScript = 'JavaSCript', - CSS = 'CSS', - Date = 'Date', - Boolean = 'Boolean', - Array = 'Array', - SmallPositiveNumber = 'Small Positive Number', - PositiveNumber = 'Positive Number', - BigPositiveNumber = 'Big Positive Number', - SmallNumber = 'Small Number', - Number = 'Number', - BigNumber = 'Big Number', - Entity = 'Entity', - EntityArray = 'Entity Array', - JSON = 'JSON', - Permission = 'Permission', - CustomFieldType = 'Custom Field Type', - MonitorType = 'Monitor Type', - WorkflowStatus = 'Workflow Status', + Version = "Version", + Phone = "Phone", + HashedString = "Hashed String", + Password = "Password", + Email = "Email", + Color = "Color", + Slug = "Slug", + Name = "Name", + Description = "Description", + ObjectID = "Object ID", + File = "File", + Buffer = "Buffer", + ShortURL = "Short URL", + Markdown = "Markdown", + Domain = "Domain", + LongURL = "URL", + ShortText = "Text", + OTP = "One Time Password", + LongText = "Long Text", + VeryLongText = "Very Long Text", + HTML = "HTML", + JavaScript = "JavaSCript", + CSS = "CSS", + Date = "Date", + Boolean = "Boolean", + Array = "Array", + SmallPositiveNumber = "Small Positive Number", + PositiveNumber = "Positive Number", + BigPositiveNumber = "Big Positive Number", + SmallNumber = "Small Number", + Number = "Number", + BigNumber = "Big Number", + Entity = "Entity", + EntityArray = "Entity Array", + JSON = "JSON", + Permission = "Permission", + CustomFieldType = "Custom Field Type", + MonitorType = "Monitor Type", + WorkflowStatus = "Workflow Status", } export default ColumnType; diff --git a/Common/Types/Database/TableMetadata.ts b/Common/Types/Database/TableMetadata.ts index d175d35edb..f9802bca95 100644 --- a/Common/Types/Database/TableMetadata.ts +++ b/Common/Types/Database/TableMetadata.ts @@ -1,18 +1,18 @@ -import GenericFunction from '../GenericFunction'; -import IconProp from '../Icon/IconProp'; +import GenericFunction from "../GenericFunction"; +import IconProp from "../Icon/IconProp"; export default (props: { - tableName: string; - singularName: string; - pluralName: string; - icon: IconProp; - tableDescription: string; + tableName: string; + singularName: string; + pluralName: string; + icon: IconProp; + tableDescription: string; }) => { - return (ctr: GenericFunction) => { - ctr.prototype.singularName = props.singularName; - ctr.prototype.tableName = props.tableName; - ctr.prototype.icon = props.icon; - ctr.prototype.tableDescription = props.tableDescription; - ctr.prototype.pluralName = props.pluralName; - }; + return (ctr: GenericFunction) => { + ctr.prototype.singularName = props.singularName; + ctr.prototype.tableName = props.tableName; + ctr.prototype.icon = props.icon; + ctr.prototype.tableDescription = props.tableDescription; + ctr.prototype.pluralName = props.pluralName; + }; }; diff --git a/Common/Types/Database/TenantColumn.ts b/Common/Types/Database/TenantColumn.ts index 91401ad759..99f3f146d7 100644 --- a/Common/Types/Database/TenantColumn.ts +++ b/Common/Types/Database/TenantColumn.ts @@ -1,7 +1,7 @@ -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export default (columnName: string) => { - return (ctr: GenericFunction) => { - ctr.prototype.tenantColumn = columnName; - }; + return (ctr: GenericFunction) => { + ctr.prototype.tenantColumn = columnName; + }; }; diff --git a/Common/Types/Database/TotalItemsBy.ts b/Common/Types/Database/TotalItemsBy.ts index f2c5026824..b39055c044 100644 --- a/Common/Types/Database/TotalItemsBy.ts +++ b/Common/Types/Database/TotalItemsBy.ts @@ -1,13 +1,13 @@ -import GenericFunction from '../GenericFunction'; +import GenericFunction from "../GenericFunction"; export default ( - columnName: string, - totalItems: number, - errorMessage: string + columnName: string, + totalItems: number, + errorMessage: string, ) => { - return (ctr: GenericFunction) => { - ctr.prototype.totalItemsByColumnName = columnName; - ctr.prototype.totalItemsNumber = totalItems; - ctr.prototype.totalItemsErrorMessage = errorMessage; - }; + return (ctr: GenericFunction) => { + ctr.prototype.totalItemsByColumnName = columnName; + ctr.prototype.totalItemsNumber = totalItems; + ctr.prototype.totalItemsErrorMessage = errorMessage; + }; }; diff --git a/Common/Types/Database/UniqueColumnBy.ts b/Common/Types/Database/UniqueColumnBy.ts index d944171ccd..8c44b41758 100644 --- a/Common/Types/Database/UniqueColumnBy.ts +++ b/Common/Types/Database/UniqueColumnBy.ts @@ -1,49 +1,49 @@ -import BaseModel from '../../Models/BaseModel'; -import Dictionary from '../Dictionary'; -import { ReflectionMetadataType } from '../Reflection'; -import 'reflect-metadata'; +import BaseModel from "../../Models/BaseModel"; +import Dictionary from "../Dictionary"; +import { ReflectionMetadataType } from "../Reflection"; +import "reflect-metadata"; -const uniqueColumnBy: symbol = Symbol('UniqueColumnBy'); +const uniqueColumnBy: symbol = Symbol("UniqueColumnBy"); export default (columnName: string | Array<string>): ReflectionMetadataType => { - return Reflect.metadata(uniqueColumnBy, columnName); + return Reflect.metadata(uniqueColumnBy, columnName); }; type GetUniqueColumnByFunction = <T extends BaseModel>( - target: T, - propertyKey: string + target: T, + propertyKey: string, ) => string; export const getUniqueColumnBy: GetUniqueColumnByFunction = < - T extends BaseModel + T extends BaseModel, >( - target: T, - propertyKey: string + target: T, + propertyKey: string, ): string => { - return Reflect.getMetadata(uniqueColumnBy, target, propertyKey) as string; + return Reflect.getMetadata(uniqueColumnBy, target, propertyKey) as string; }; type GetUniqueColumnsByFunction = <T extends BaseModel>( - target: T + target: T, ) => Dictionary<string>; export const getUniqueColumnsBy: GetUniqueColumnsByFunction = < - T extends BaseModel + T extends BaseModel, >( - target: T + target: T, ): Dictionary<string> => { - const dictonary: Dictionary<string> = {}; - const keys: Array<string> = Object.keys(target); + const dictonary: Dictionary<string> = {}; + const keys: Array<string> = Object.keys(target); - for (const key of keys) { - if (Reflect.getMetadata(uniqueColumnBy, target, key)) { - dictonary[key] = Reflect.getMetadata( - uniqueColumnBy, - target, - key - ) as string; - } + for (const key of keys) { + if (Reflect.getMetadata(uniqueColumnBy, target, key)) { + dictonary[key] = Reflect.getMetadata( + uniqueColumnBy, + target, + key, + ) as string; } + } - return dictonary; + return dictonary; }; diff --git a/Common/Types/DatabaseType.ts b/Common/Types/DatabaseType.ts index 4a0630447b..f7d5c530da 100644 --- a/Common/Types/DatabaseType.ts +++ b/Common/Types/DatabaseType.ts @@ -1,6 +1,6 @@ enum DatabaseType { - Postgres = 'postgres', - Clickhouse = 'clickhouse', + Postgres = "postgres", + Clickhouse = "clickhouse", } export default DatabaseType; diff --git a/Common/Types/Date.ts b/Common/Types/Date.ts index 90ad120949..6e71a5a95c 100644 --- a/Common/Types/Date.ts +++ b/Common/Types/Date.ts @@ -1,1069 +1,1056 @@ -import InBetween from './BaseDatabase/InBetween'; -import DayOfWeek, { DayOfWeekUtil } from './Day/DayOfWeek'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import PositiveNumber from './PositiveNumber'; -import moment from 'moment-timezone'; +import InBetween from "./BaseDatabase/InBetween"; +import DayOfWeek, { DayOfWeekUtil } from "./Day/DayOfWeek"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import PositiveNumber from "./PositiveNumber"; +import moment from "moment-timezone"; export const Moment: typeof moment = moment; export default class OneUptimeDate { - public getNanoSecondsFromSeconds(seconds: number): number { - return seconds * 1000 * 1000 * 1000; - } - - public static getDateFromYYYYMMDD( - year: string, - month: string, - day: string - ): Date { - return moment(`${year}-${month}-${day}`).toDate(); - } - - public getMicroSecondsFromSeconds(seconds: number): number { - return seconds * 1000 * 1000; - } - - public getMilliSecondsFromSeconds(seconds: number): number { - return seconds * 1000; - } - - public static getCurrentDateAsUnixNano(): number { - return this.toUnixNano(this.getCurrentDate()); - } - - public static toUnixNano(date: Date): number { - return date.getTime() * 1000000; - } - - public static getLocalHourAndMinuteFromDate(date: Date | string): string { - date = this.fromString(date); - return moment(date).format('HH:mm'); - } - - public static getMillisecondsBetweenTwoUnixNanoDates( - startDate: number, - endDate: number - ): number { - return endDate - startDate; - } - - public static moveDateToTheDayOfWeek( - date: Date, - moveToWeek: Date, - dayOfWeek: DayOfWeek - ): Date { - // date will be moved to the week of "moveToWeek" and then to the day of week "dayOfWeek" - - date = this.fromString(date); - date = this.keepTimeButMoveDay(date, moveToWeek); - - // now move the date to the day of week - - const dateDayOfWeek: DayOfWeek = this.getDayOfWeek(date); - - if (dateDayOfWeek === dayOfWeek) { - return date; - } - - const numberOfDayOfWeek: number = - DayOfWeekUtil.getNumberOfDayOfWeek(dayOfWeek); - - const dateDayOfWeekNumber: number = - DayOfWeekUtil.getNumberOfDayOfWeek(dateDayOfWeek); - - const difference: number = numberOfDayOfWeek - dateDayOfWeekNumber; - - if (difference === 0) { - return date; - } - - return this.addRemoveDays(date, difference); - } - - public static isOverlapping( - start: Date, - end: Date, - start1: Date, - end1: Date - ): unknown { - start = this.fromString(start); - end = this.fromString(end); - start1 = this.fromString(start1); - end1 = this.fromString(end1); - - let isOverlapping: boolean = - moment(start).isBetween(start1, end1) || - moment(end).isBetween(start1, end1) || - moment(start).isSame(start1) || - moment(end).isSame(end1); - - if (!isOverlapping) { - // check if the start1 and end1 are in between start and end - - isOverlapping = - moment(start1).isBetween(start, end) || - moment(end1).isBetween(start, end) || - moment(start1).isSame(start) || - moment(end1).isSame(end); - } - - return isOverlapping; - } - - public static getCurrentDate(): Date { - return moment().toDate(); - } - - public static fromNow(date: Date): string { - return moment(date).fromNow(); - } - - public static differenceBetweenTwoDatesAsFromattedString( - date1: Date, - date2: Date - ): string { - const seconds: number = this.getSecondsBetweenTwoDates(date1, date2); - return this.secondsToFormattedFriendlyTimeString(seconds); - } - - public static toTimeString(date: Date | string): string { - if (typeof date === 'string') { - date = this.fromString(date); - } - - return moment(date).format('HH:mm'); - } - - public static isSame(date1: Date, date2: Date): boolean { - date1 = this.fromString(date1); - date2 = this.fromString(date2); - return moment(date1).isSame(date2); - } - - public static getDaysBetweenTwoDates( - startDate: Date, - endDate: Date - ): number { - startDate = this.fromString(startDate); - endDate = this.fromString(endDate); - return moment(endDate).diff(moment(startDate), 'days'); - } - - public static getDaysBetweenTwoDatesInclusive( - startDate: Date, - endDate: Date - ): number { - return this.getDaysBetweenTwoDates(startDate, endDate) + 1; - } - - public static getHoursBetweenTwoDates( - startDate: Date, - endDate: Date - ): number { - startDate = this.fromString(startDate); - endDate = this.fromString(endDate); - return moment(endDate).diff(moment(startDate), 'hours'); - } - - public static getHoursBetweenTwoDatesInclusive( - startDate: Date, - endDate: Date - ): number { - return this.getHoursBetweenTwoDates(startDate, endDate) + 1; - } - - public static getMinutesBetweenTwoDates( - startDate: Date, - endDate: Date - ): number { - startDate = this.fromString(startDate); - endDate = this.fromString(endDate); - return moment(endDate).diff(moment(startDate), 'minutes'); - } - - public static getMinutesBetweenTwoDatesInclusive( - startDate: Date, - endDate: Date - ): number { - return this.getMinutesBetweenTwoDates(startDate, endDate) + 1; - } - - public static getSecondsBetweenTwoDates( - startDate: Date, - endDate: Date - ): number { - startDate = this.fromString(startDate); - endDate = this.fromString(endDate); - return moment(endDate).diff(moment(startDate), 'seconds'); - } - - public static getSecondsBetweenTwoDatesInclusive( - startDate: Date, - endDate: Date - ): number { - return this.getSecondsBetweenTwoDates(startDate, endDate) + 1; - } - - public static getWeeksBetweenTwoDates( - startDate: Date, - endDate: Date - ): number { - startDate = this.fromString(startDate); - endDate = this.fromString(endDate); - return moment(endDate).diff(moment(startDate), 'weeks'); - } - - public static getWeeksBetweenTwoDatesInclusive( - startDate: Date, - endDate: Date - ): number { - return this.getWeeksBetweenTwoDates(startDate, endDate) + 1; - } - - public static getMonthsBetweenTwoDates( - startDate: Date, - endDate: Date - ): number { - startDate = this.fromString(startDate); - endDate = this.fromString(endDate); - return moment(endDate).diff(moment(startDate), 'months'); - } - - public static getMonthsBetweenTwoDatesInclusive( - startDate: Date, - endDate: Date - ): number { - return this.getMonthsBetweenTwoDates(startDate, endDate) + 1; - } - - public static getYearsBetweenTwoDates( - startDate: Date, - endDate: Date - ): number { - startDate = this.fromString(startDate); - endDate = this.fromString(endDate); - return moment(endDate).diff(moment(startDate), 'years'); - } - - public static getYearsBetweenTwoDatesInclusive( - startDate: Date, - endDate: Date - ): number { - return this.getYearsBetweenTwoDates(startDate, endDate) + 1; - } - - public static toString(date: Date | undefined): string { - if (!date) { - return ''; - } - - date = this.fromString(date); - - return date.toISOString(); - } - - public static getCurrentMomentDate(): moment.Moment { - return moment(); - } - - public static keepTimeButMoveDay(keepTimeFor: Date, moveDayTo: Date): Date { - keepTimeFor = this.fromString(keepTimeFor); - moveDayTo = this.fromString(moveDayTo); - return moment(moveDayTo) - .set({ - hour: keepTimeFor.getHours(), - minute: keepTimeFor.getMinutes(), - second: keepTimeFor.getSeconds(), - millisecond: keepTimeFor.getMilliseconds(), - }) - .toDate(); - } - - public static getOneMinAgo(): Date { - return this.getSomeMinutesAgo(new PositiveNumber(1)); - } - - public static getOneDayAgo(): Date { - return this.getSomeDaysAgo(new PositiveNumber(1)); - } - - public static fromUnixNano(timestamp: number): Date { - return moment(timestamp / 1000000).toDate(); - } - - public static getSecondsTo(date: Date): number { - date = this.fromString(date); - const dif: number = date.getTime() - this.getCurrentDate().getTime(); - const Seconds_from_T1_to_T2: number = dif / 1000; - return Math.abs(Seconds_from_T1_to_T2); - } - - public static getSomeMinutesAgo(minutes: PositiveNumber | number): Date { - if (!(minutes instanceof PositiveNumber)) { - minutes = new PositiveNumber(minutes); - } - - return this.getCurrentMomentDate() - .add(-1 * minutes.toNumber(), 'minutes') - .toDate(); - } - - public static timezoneOffsetDate(date: Date): Date { - date = this.fromString(date); - return this.addRemoveMinutes(date, date.getTimezoneOffset()); - } - - public static toDateTimeLocalString(date: Date): string { - date = this.fromString(date); - - type TenFunction = (i: number) => string; - - const ten: TenFunction = (i: number): string => { - return (i < 10 ? '0' : '') + i; - }, - YYYY: number = date.getFullYear(), - MM: string = ten(date.getMonth() + 1), - DD: string = ten(date.getDate()), - HH: string = ten(date.getHours()), - II: string = ten(date.getMinutes()), - SS: string = ten(date.getSeconds()); - - return YYYY + '-' + MM + '-' + DD + 'T' + HH + ':' + II + ':' + SS; - } - - public static fromJSON(json: JSONObject): Date { - if (json['_type'] === ObjectType.DateTime) { - return OneUptimeDate.fromString(json['value'] as string); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - public static toJSON(date: Date): JSONObject { - return { - _type: ObjectType.DateTime, - value: OneUptimeDate.toString(date), - }; - } - - public static areOnTheSameDay(date1: Date, date2: Date): boolean { - date1 = this.fromString(date1); - date2 = this.fromString(date2); - return moment(date1).isSame(date2, 'day'); - } - - public static areOnTheSameMonth(date1: Date, date2: Date): boolean { - date1 = this.fromString(date1); - date2 = this.fromString(date2); - return moment(date1).isSame(date2, 'month'); - } - - public static areOnTheSameYear(date1: Date, date2: Date): boolean { - date1 = this.fromString(date1); - date2 = this.fromString(date2); - return moment(date1).isSame(date2, 'year'); - } + public getNanoSecondsFromSeconds(seconds: number): number { + return seconds * 1000 * 1000 * 1000; + } - public static areOnTheSameHour(date1: Date, date2: Date): boolean { - date1 = this.fromString(date1); - date2 = this.fromString(date2); - return moment(date1).isSame(date2, 'hour'); - } + public static getDateFromYYYYMMDD( + year: string, + month: string, + day: string, + ): Date { + return moment(`${year}-${month}-${day}`).toDate(); + } - public static areOnTheSameMinute(date1: Date, date2: Date): boolean { - date1 = this.fromString(date1); - date2 = this.fromString(date2); - return moment(date1).isSame(date2, 'minute'); - } + public getMicroSecondsFromSeconds(seconds: number): number { + return seconds * 1000 * 1000; + } + + public getMilliSecondsFromSeconds(seconds: number): number { + return seconds * 1000; + } + + public static getCurrentDateAsUnixNano(): number { + return this.toUnixNano(this.getCurrentDate()); + } - public static areOnTheSameSecond(date1: Date, date2: Date): boolean { - date1 = this.fromString(date1); - date2 = this.fromString(date2); - return moment(date1).isSame(date2, 'second'); - } + public static toUnixNano(date: Date): number { + return date.getTime() * 1000000; + } - public static areOnTheSameWeek(date1: Date, date2: Date): boolean { - date1 = this.fromString(date1); - date2 = this.fromString(date2); - return moment(date1).isSame(date2, 'week'); - } + public static getLocalHourAndMinuteFromDate(date: Date | string): string { + date = this.fromString(date); + return moment(date).format("HH:mm"); + } - public static addRemoveMinutes(date: Date, minutes: number): Date { - date = this.fromString(date); - return moment(date).add(minutes, 'minutes').toDate(); - } + public static getMillisecondsBetweenTwoUnixNanoDates( + startDate: number, + endDate: number, + ): number { + return endDate - startDate; + } + + public static moveDateToTheDayOfWeek( + date: Date, + moveToWeek: Date, + dayOfWeek: DayOfWeek, + ): Date { + // date will be moved to the week of "moveToWeek" and then to the day of week "dayOfWeek" + + date = this.fromString(date); + date = this.keepTimeButMoveDay(date, moveToWeek); + + // now move the date to the day of week + + const dateDayOfWeek: DayOfWeek = this.getDayOfWeek(date); + + if (dateDayOfWeek === dayOfWeek) { + return date; + } + + const numberOfDayOfWeek: number = + DayOfWeekUtil.getNumberOfDayOfWeek(dayOfWeek); + + const dateDayOfWeekNumber: number = + DayOfWeekUtil.getNumberOfDayOfWeek(dateDayOfWeek); + + const difference: number = numberOfDayOfWeek - dateDayOfWeekNumber; + + if (difference === 0) { + return date; + } + + return this.addRemoveDays(date, difference); + } + + public static isOverlapping( + start: Date, + end: Date, + start1: Date, + end1: Date, + ): unknown { + start = this.fromString(start); + end = this.fromString(end); + start1 = this.fromString(start1); + end1 = this.fromString(end1); + + let isOverlapping: boolean = + moment(start).isBetween(start1, end1) || + moment(end).isBetween(start1, end1) || + moment(start).isSame(start1) || + moment(end).isSame(end1); + + if (!isOverlapping) { + // check if the start1 and end1 are in between start and end + + isOverlapping = + moment(start1).isBetween(start, end) || + moment(end1).isBetween(start, end) || + moment(start1).isSame(start) || + moment(end1).isSame(end); + } + + return isOverlapping; + } + + public static getCurrentDate(): Date { + return moment().toDate(); + } + + public static fromNow(date: Date): string { + return moment(date).fromNow(); + } + + public static differenceBetweenTwoDatesAsFromattedString( + date1: Date, + date2: Date, + ): string { + const seconds: number = this.getSecondsBetweenTwoDates(date1, date2); + return this.secondsToFormattedFriendlyTimeString(seconds); + } + + public static toTimeString(date: Date | string): string { + if (typeof date === "string") { + date = this.fromString(date); + } + + return moment(date).format("HH:mm"); + } + + public static isSame(date1: Date, date2: Date): boolean { + date1 = this.fromString(date1); + date2 = this.fromString(date2); + return moment(date1).isSame(date2); + } + + public static getDaysBetweenTwoDates(startDate: Date, endDate: Date): number { + startDate = this.fromString(startDate); + endDate = this.fromString(endDate); + return moment(endDate).diff(moment(startDate), "days"); + } + + public static getDaysBetweenTwoDatesInclusive( + startDate: Date, + endDate: Date, + ): number { + return this.getDaysBetweenTwoDates(startDate, endDate) + 1; + } + + public static getHoursBetweenTwoDates( + startDate: Date, + endDate: Date, + ): number { + startDate = this.fromString(startDate); + endDate = this.fromString(endDate); + return moment(endDate).diff(moment(startDate), "hours"); + } + + public static getHoursBetweenTwoDatesInclusive( + startDate: Date, + endDate: Date, + ): number { + return this.getHoursBetweenTwoDates(startDate, endDate) + 1; + } + + public static getMinutesBetweenTwoDates( + startDate: Date, + endDate: Date, + ): number { + startDate = this.fromString(startDate); + endDate = this.fromString(endDate); + return moment(endDate).diff(moment(startDate), "minutes"); + } + + public static getMinutesBetweenTwoDatesInclusive( + startDate: Date, + endDate: Date, + ): number { + return this.getMinutesBetweenTwoDates(startDate, endDate) + 1; + } + + public static getSecondsBetweenTwoDates( + startDate: Date, + endDate: Date, + ): number { + startDate = this.fromString(startDate); + endDate = this.fromString(endDate); + return moment(endDate).diff(moment(startDate), "seconds"); + } + + public static getSecondsBetweenTwoDatesInclusive( + startDate: Date, + endDate: Date, + ): number { + return this.getSecondsBetweenTwoDates(startDate, endDate) + 1; + } + + public static getWeeksBetweenTwoDates( + startDate: Date, + endDate: Date, + ): number { + startDate = this.fromString(startDate); + endDate = this.fromString(endDate); + return moment(endDate).diff(moment(startDate), "weeks"); + } + + public static getWeeksBetweenTwoDatesInclusive( + startDate: Date, + endDate: Date, + ): number { + return this.getWeeksBetweenTwoDates(startDate, endDate) + 1; + } + + public static getMonthsBetweenTwoDates( + startDate: Date, + endDate: Date, + ): number { + startDate = this.fromString(startDate); + endDate = this.fromString(endDate); + return moment(endDate).diff(moment(startDate), "months"); + } + + public static getMonthsBetweenTwoDatesInclusive( + startDate: Date, + endDate: Date, + ): number { + return this.getMonthsBetweenTwoDates(startDate, endDate) + 1; + } + + public static getYearsBetweenTwoDates( + startDate: Date, + endDate: Date, + ): number { + startDate = this.fromString(startDate); + endDate = this.fromString(endDate); + return moment(endDate).diff(moment(startDate), "years"); + } + + public static getYearsBetweenTwoDatesInclusive( + startDate: Date, + endDate: Date, + ): number { + return this.getYearsBetweenTwoDates(startDate, endDate) + 1; + } + + public static toString(date: Date | undefined): string { + if (!date) { + return ""; + } + + date = this.fromString(date); + + return date.toISOString(); + } + + public static getCurrentMomentDate(): moment.Moment { + return moment(); + } + + public static keepTimeButMoveDay(keepTimeFor: Date, moveDayTo: Date): Date { + keepTimeFor = this.fromString(keepTimeFor); + moveDayTo = this.fromString(moveDayTo); + return moment(moveDayTo) + .set({ + hour: keepTimeFor.getHours(), + minute: keepTimeFor.getMinutes(), + second: keepTimeFor.getSeconds(), + millisecond: keepTimeFor.getMilliseconds(), + }) + .toDate(); + } + + public static getOneMinAgo(): Date { + return this.getSomeMinutesAgo(new PositiveNumber(1)); + } + + public static getOneDayAgo(): Date { + return this.getSomeDaysAgo(new PositiveNumber(1)); + } + + public static fromUnixNano(timestamp: number): Date { + return moment(timestamp / 1000000).toDate(); + } + + public static getSecondsTo(date: Date): number { + date = this.fromString(date); + const dif: number = date.getTime() - this.getCurrentDate().getTime(); + const Seconds_from_T1_to_T2: number = dif / 1000; + return Math.abs(Seconds_from_T1_to_T2); + } + + public static getSomeMinutesAgo(minutes: PositiveNumber | number): Date { + if (!(minutes instanceof PositiveNumber)) { + minutes = new PositiveNumber(minutes); + } + + return this.getCurrentMomentDate() + .add(-1 * minutes.toNumber(), "minutes") + .toDate(); + } + + public static timezoneOffsetDate(date: Date): Date { + date = this.fromString(date); + return this.addRemoveMinutes(date, date.getTimezoneOffset()); + } + + public static toDateTimeLocalString(date: Date): string { + date = this.fromString(date); + + type TenFunction = (i: number) => string; + + const ten: TenFunction = (i: number): string => { + return (i < 10 ? "0" : "") + i; + }, + YYYY: number = date.getFullYear(), + MM: string = ten(date.getMonth() + 1), + DD: string = ten(date.getDate()), + HH: string = ten(date.getHours()), + II: string = ten(date.getMinutes()), + SS: string = ten(date.getSeconds()); + + return YYYY + "-" + MM + "-" + DD + "T" + HH + ":" + II + ":" + SS; + } + + public static fromJSON(json: JSONObject): Date { + if (json["_type"] === ObjectType.DateTime) { + return OneUptimeDate.fromString(json["value"] as string); + } + + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + public static toJSON(date: Date): JSONObject { + return { + _type: ObjectType.DateTime, + value: OneUptimeDate.toString(date), + }; + } + + public static areOnTheSameDay(date1: Date, date2: Date): boolean { + date1 = this.fromString(date1); + date2 = this.fromString(date2); + return moment(date1).isSame(date2, "day"); + } + + public static areOnTheSameMonth(date1: Date, date2: Date): boolean { + date1 = this.fromString(date1); + date2 = this.fromString(date2); + return moment(date1).isSame(date2, "month"); + } + + public static areOnTheSameYear(date1: Date, date2: Date): boolean { + date1 = this.fromString(date1); + date2 = this.fromString(date2); + return moment(date1).isSame(date2, "year"); + } + + public static areOnTheSameHour(date1: Date, date2: Date): boolean { + date1 = this.fromString(date1); + date2 = this.fromString(date2); + return moment(date1).isSame(date2, "hour"); + } + + public static areOnTheSameMinute(date1: Date, date2: Date): boolean { + date1 = this.fromString(date1); + date2 = this.fromString(date2); + return moment(date1).isSame(date2, "minute"); + } + + public static areOnTheSameSecond(date1: Date, date2: Date): boolean { + date1 = this.fromString(date1); + date2 = this.fromString(date2); + return moment(date1).isSame(date2, "second"); + } + + public static areOnTheSameWeek(date1: Date, date2: Date): boolean { + date1 = this.fromString(date1); + date2 = this.fromString(date2); + return moment(date1).isSame(date2, "week"); + } + + public static addRemoveMinutes(date: Date, minutes: number): Date { + date = this.fromString(date); + return moment(date).add(minutes, "minutes").toDate(); + } + + public static addRemoveDays(date: Date, days: number): Date { + date = this.fromString(date); + return moment(date).add(days, "days").toDate(); + } + + public static addRemoveHours(date: Date, hours: number): Date { + date = this.fromString(date); + return moment(date).add(hours, "hours").toDate(); + } + + public static addRemoveYears(date: Date, years: number): Date { + date = this.fromString(date); + return moment(date).add(years, "years").toDate(); + } + + public static addRemoveMonths(date: Date, months: number): Date { + date = this.fromString(date); + return moment(date).add(months, "months").toDate(); + } + + public static addRemoveWeeks(date: Date, weeks: number): Date { + date = this.fromString(date); + return moment(date).add(weeks, "weeks").toDate(); + } + + public static addRemoveSeconds(date: Date, seconds: number): Date { + date = this.fromString(date); + return moment(date).add(seconds, "seconds").toDate(); + } + + public static getSecondsInDays(days: PositiveNumber | number): number { + if (!(days instanceof PositiveNumber)) { + days = new PositiveNumber(days); + } + return days.positiveNumber * 24 * 60 * 60; + } + + public static getMillisecondsInDays(days: PositiveNumber | number): number { + return this.getSecondsInDays(days) * 1000; + } + + public static getSomeHoursAgo(hours: PositiveNumber | number): Date { + if (!(hours instanceof PositiveNumber)) { + hours = new PositiveNumber(hours); + } + return this.getCurrentMomentDate() + .add(-1 * hours.toNumber(), "hours") + .toDate(); + } + + public static getSomeDaysAgo(days: PositiveNumber | number): Date { + if (!(days instanceof PositiveNumber)) { + days = new PositiveNumber(days); + } + return this.getCurrentMomentDate() + .add(-1 * days.toNumber(), "days") + .toDate(); + } + + public static getSomeDaysAgoFromDate( + date: Date, + days: PositiveNumber | number, + ): Date { + date = this.fromString(date); + if (!(days instanceof PositiveNumber)) { + days = new PositiveNumber(days); + } + return moment(date) + .add(-1 * days.toNumber(), "days") + .toDate(); + } + + public static getSomeDaysAfterFromDate( + date: Date, + days: PositiveNumber | number, + ): Date { + date = this.fromString(date); + if (!(days instanceof PositiveNumber)) { + days = new PositiveNumber(days); + } + return moment(date).add(Number(days.toNumber()), "days").toDate(); + } - public static addRemoveDays(date: Date, days: number): Date { - date = this.fromString(date); - return moment(date).add(days, 'days').toDate(); + public static getSomeDaysBeforeFromDate( + date: Date, + days: PositiveNumber | number, + ): Date { + date = this.fromString(date); + if (!(days instanceof PositiveNumber)) { + days = new PositiveNumber(days); } + return moment(date) + .add(-1 * Number(days.toNumber()), "days") + .toDate(); + } - public static addRemoveHours(date: Date, hours: number): Date { - date = this.fromString(date); - return moment(date).add(hours, 'hours').toDate(); + public static getSomeSecondsAgo(seconds: PositiveNumber | number): Date { + if (!(seconds instanceof PositiveNumber)) { + seconds = new PositiveNumber(seconds); } - public static addRemoveYears(date: Date, years: number): Date { - date = this.fromString(date); - return moment(date).add(years, 'years').toDate(); - } + return this.getCurrentMomentDate() + .add(-1 * seconds.toNumber(), "seconds") + .toDate(); + } - public static addRemoveMonths(date: Date, months: number): Date { - date = this.fromString(date); - return moment(date).add(months, 'months').toDate(); - } + public static getOneMinAfter(): Date { + return this.getSomeMinutesAfter(new PositiveNumber(1)); + } - public static addRemoveWeeks(date: Date, weeks: number): Date { - date = this.fromString(date); - return moment(date).add(weeks, 'weeks').toDate(); - } + public static getOneDayAfter(): Date { + return this.getSomeDaysAfter(new PositiveNumber(1)); + } - public static addRemoveSeconds(date: Date, seconds: number): Date { - date = this.fromString(date); - return moment(date).add(seconds, 'seconds').toDate(); + public static getSomeMinutesAfter(minutes: PositiveNumber | number): Date { + if (!(minutes instanceof PositiveNumber)) { + minutes = new PositiveNumber(minutes); } - public static getSecondsInDays(days: PositiveNumber | number): number { - if (!(days instanceof PositiveNumber)) { - days = new PositiveNumber(days); - } - return days.positiveNumber * 24 * 60 * 60; - } + return this.getCurrentMomentDate() + .add(minutes.toNumber(), "minutes") + .toDate(); + } - public static getMillisecondsInDays(days: PositiveNumber | number): number { - return this.getSecondsInDays(days) * 1000; + public static getSomeHoursAfter(hours: PositiveNumber | number): Date { + if (!(hours instanceof PositiveNumber)) { + hours = new PositiveNumber(hours); } + return this.getCurrentMomentDate().add(hours.toNumber(), "hours").toDate(); + } - public static getSomeHoursAgo(hours: PositiveNumber | number): Date { - if (!(hours instanceof PositiveNumber)) { - hours = new PositiveNumber(hours); - } - return this.getCurrentMomentDate() - .add(-1 * hours.toNumber(), 'hours') - .toDate(); + public static getSomeDaysAfter(days: PositiveNumber | number): Date { + if (!(days instanceof PositiveNumber)) { + days = new PositiveNumber(days); } - public static getSomeDaysAgo(days: PositiveNumber | number): Date { - if (!(days instanceof PositiveNumber)) { - days = new PositiveNumber(days); - } - return this.getCurrentMomentDate() - .add(-1 * days.toNumber(), 'days') - .toDate(); - } + return this.getCurrentMomentDate().add(days.toNumber(), "days").toDate(); + } - public static getSomeDaysAgoFromDate( - date: Date, - days: PositiveNumber | number - ): Date { - date = this.fromString(date); - if (!(days instanceof PositiveNumber)) { - days = new PositiveNumber(days); - } - return moment(date) - .add(-1 * days.toNumber(), 'days') - .toDate(); - } + public static secondsToFormattedTimeString(seconds: number): string { + return moment.utc(seconds * 1000).format("HH:mm:ss"); + } - public static getSomeDaysAfterFromDate( - date: Date, - days: PositiveNumber | number - ): Date { - date = this.fromString(date); - if (!(days instanceof PositiveNumber)) { - days = new PositiveNumber(days); - } - return moment(date).add(Number(days.toNumber()), 'days').toDate(); - } + public static toUnixTimestamp(date: Date): number { + return Math.floor(date.getTime() / 1000); + } - public static getSomeDaysBeforeFromDate( - date: Date, - days: PositiveNumber | number - ): Date { - date = this.fromString(date); - if (!(days instanceof PositiveNumber)) { - days = new PositiveNumber(days); - } - return moment(date) - .add(-1 * Number(days.toNumber()), 'days') - .toDate(); - } + public static secondsToFormattedFriendlyTimeString(seconds: number): string { + const startDate: moment.Moment = moment.utc(0); + const date: moment.Moment = moment.utc(seconds * 1000); - public static getSomeSecondsAgo(seconds: PositiveNumber | number): Date { - if (!(seconds instanceof PositiveNumber)) { - seconds = new PositiveNumber(seconds); - } + // get the difference between the two dates as friendly formatted string - return this.getCurrentMomentDate() - .add(-1 * seconds.toNumber(), 'seconds') - .toDate(); - } + let formattedString: string = ""; - public static getOneMinAfter(): Date { - return this.getSomeMinutesAfter(new PositiveNumber(1)); - } + // years between two dates + const years: number = date.diff(startDate, "years"); - public static getOneDayAfter(): Date { - return this.getSomeDaysAfter(new PositiveNumber(1)); - } + if (years > 0) { + let text: string = "years "; + if (years === 1) { + text = "year "; + } - public static getSomeMinutesAfter(minutes: PositiveNumber | number): Date { - if (!(minutes instanceof PositiveNumber)) { - minutes = new PositiveNumber(minutes); - } + // add years to start date + startDate.add(years, "years"); - return this.getCurrentMomentDate() - .add(minutes.toNumber(), 'minutes') - .toDate(); + formattedString += years + " " + text; } - public static getSomeHoursAfter(hours: PositiveNumber | number): Date { - if (!(hours instanceof PositiveNumber)) { - hours = new PositiveNumber(hours); - } - return this.getCurrentMomentDate() - .add(hours.toNumber(), 'hours') - .toDate(); - } + const months: number = date.diff(startDate, "months"); - public static getSomeDaysAfter(days: PositiveNumber | number): Date { - if (!(days instanceof PositiveNumber)) { - days = new PositiveNumber(days); - } + if (months > 0) { + let text: string = "months "; - return this.getCurrentMomentDate() - .add(days.toNumber(), 'days') - .toDate(); - } + if (months === 1) { + text = "month "; + } - public static secondsToFormattedTimeString(seconds: number): string { - return moment.utc(seconds * 1000).format('HH:mm:ss'); - } + // add months to start date + startDate.add(months, "months"); - public static toUnixTimestamp(date: Date): number { - return Math.floor(date.getTime() / 1000); + formattedString += months + " " + text; } - public static secondsToFormattedFriendlyTimeString( - seconds: number - ): string { - const startDate: moment.Moment = moment.utc(0); - const date: moment.Moment = moment.utc(seconds * 1000); - - // get the difference between the two dates as friendly formatted string - - let formattedString: string = ''; - - // years between two dates - const years: number = date.diff(startDate, 'years'); - - if (years > 0) { - let text: string = 'years '; - if (years === 1) { - text = 'year '; - } - - // add years to start date - startDate.add(years, 'years'); - - formattedString += years + ' ' + text; - } + const days: number = date.diff(startDate, "days"); - const months: number = date.diff(startDate, 'months'); + if (days > 0) { + let text: string = "days "; - if (months > 0) { - let text: string = 'months '; + if (days === 1) { + text = "day "; + } - if (months === 1) { - text = 'month '; - } + // add days to start date + startDate.add(days, "days"); - // add months to start date - startDate.add(months, 'months'); - - formattedString += months + ' ' + text; - } - - const days: number = date.diff(startDate, 'days'); - - if (days > 0) { - let text: string = 'days '; - - if (days === 1) { - text = 'day '; - } - - // add days to start date - startDate.add(days, 'days'); - - formattedString += days + ' ' + text; - } - - const hours: number = date.diff(startDate, 'hours'); - - if (hours > 0) { - let text: string = 'hours '; - - if (hours === 1) { - text = 'hour '; - } - - // add hours to start date - startDate.add(hours, 'hours'); - - formattedString += hours + ' ' + text; - } - - const minutes: number = date.diff(startDate, 'minutes'); - - if (minutes > 0) { - let text: string = 'mins '; - - if (minutes === 1) { - text = 'min '; - } - - // add minutes to start date - startDate.add(minutes, 'minutes'); - - formattedString += minutes + ' ' + text; - } - - const secondsLeft: number = date.diff(startDate, 'seconds'); - - if (secondsLeft > 0) { - let text: string = 'secs '; - - if (secondsLeft === 1) { - text = 'sec '; - } - - // add seconds to start date - startDate.add(secondsLeft, 'seconds'); - - formattedString += secondsLeft + ' ' + text; - } - - return formattedString.trim(); + formattedString += days + " " + text; } - public static getGreaterDate(a: Date, b: Date): Date { - a = this.fromString(a); - b = this.fromString(b); - if (this.isAfter(a, b)) { - return a; - } + const hours: number = date.diff(startDate, "hours"); - return b; - } + if (hours > 0) { + let text: string = "hours "; - public static getLesserDate(a: Date, b: Date): Date { - a = this.fromString(a); - b = this.fromString(b); - if (this.isBefore(a, b)) { - return a; - } + if (hours === 1) { + text = "hour "; + } - return b; - } + // add hours to start date + startDate.add(hours, "hours"); - public static getSecondsBetweenDates(start: Date, end: Date): number { - start = this.fromString(start); - end = this.fromString(end); - const duration: moment.Duration = moment.duration( - moment(end).diff(moment(start)) - ); - return duration.asSeconds(); + formattedString += hours + " " + text; } - public static getSomeDaysAfterDate( - date: Date, - days: PositiveNumber | number - ): Date { - if (!(days instanceof PositiveNumber)) { - days = new PositiveNumber(days); - } + const minutes: number = date.diff(startDate, "minutes"); - return moment(date).add(days.toNumber(), 'days').toDate(); - } + if (minutes > 0) { + let text: string = "mins "; - public static getSomeSecondsAfter(seconds: PositiveNumber | number): Date { - if (!(seconds instanceof PositiveNumber)) { - seconds = new PositiveNumber(seconds); - } + if (minutes === 1) { + text = "min "; + } - return this.getCurrentMomentDate() - .add(seconds.toNumber(), 'seconds') - .toDate(); - } + // add minutes to start date + startDate.add(minutes, "minutes"); - public static getNumberOfDaysBetweenDates( - startDate: Date, - endDate: Date - ): number { - const a: moment.Moment = moment(startDate); - const b: moment.Moment = moment(endDate); - return b.diff(a, 'days'); + formattedString += minutes + " " + text; } - public static getNumberOfMinutesBetweenDates( - startDate: Date, - endDate: Date - ): number { - const a: moment.Moment = moment(startDate); - const b: moment.Moment = moment(endDate); - return b.diff(a, 'minutes'); - } + const secondsLeft: number = date.diff(startDate, "seconds"); - public static getNumberOfDaysBetweenDatesInclusive( - startDate: Date, - endDate: Date - ): number { - return this.getNumberOfDaysBetweenDates(startDate, endDate) + 1; - } + if (secondsLeft > 0) { + let text: string = "secs "; - public static momentToDate(moment: moment.Moment): Date { - return moment.toDate(); - } + if (secondsLeft === 1) { + text = "sec "; + } - public static getCurrentYear(): number { - return moment().year(); - } + // add seconds to start date + startDate.add(secondsLeft, "seconds"); - public static getStartOfDay(date: Date): Date { - date = this.fromString(date); - return moment(date).startOf('day').toDate(); + formattedString += secondsLeft + " " + text; } - public static getEndOfDay(date: Date): Date { - date = this.fromString(date); - return moment(date).endOf('day').toDate(); - } + return formattedString.trim(); + } - public static isBetween( - date: Date, - startDate: Date, - endDate: Date - ): boolean { - date = this.fromString(date); - startDate = this.fromString(startDate); - endDate = this.fromString(endDate); - return moment(date).isBetween(startDate, endDate); + public static getGreaterDate(a: Date, b: Date): Date { + a = this.fromString(a); + b = this.fromString(b); + if (this.isAfter(a, b)) { + return a; } - public static isAfter(date: Date, startDate: Date): boolean { - date = this.fromString(date); - startDate = this.fromString(startDate); - return moment(date).isAfter(startDate); - } + return b; + } - public static isOnOrAfter(date: Date, startDate: Date): boolean { - date = this.fromString(date); - startDate = this.fromString(startDate); - return moment(date).isSameOrAfter(startDate); + public static getLesserDate(a: Date, b: Date): Date { + a = this.fromString(a); + b = this.fromString(b); + if (this.isBefore(a, b)) { + return a; } - public static getDayOfWeek(date: Date): DayOfWeek { - const dayOfWeek: number = this.geyDayOfWeekAsNumber(date); - - if (dayOfWeek === 1) { - return DayOfWeek.Monday; - } else if (dayOfWeek === 2) { - return DayOfWeek.Tuesday; - } else if (dayOfWeek === 3) { - return DayOfWeek.Wednesday; - } else if (dayOfWeek === 4) { - return DayOfWeek.Thursday; - } else if (dayOfWeek === 5) { - return DayOfWeek.Friday; - } else if (dayOfWeek === 6) { - return DayOfWeek.Saturday; - } else if (dayOfWeek === 7) { - return DayOfWeek.Sunday; - } - - throw new BadDataException('Invalid day of week'); - } + return b; + } - public static geyDayOfWeekAsNumber(date: Date): number { - date = this.fromString(date); - return moment(date).isoWeekday(); - } - - public static isOnOrBefore(date: Date, endDate: Date): boolean { - date = this.fromString(date); - endDate = this.fromString(endDate); - return moment(date).isSameOrBefore(endDate); - } + public static getSecondsBetweenDates(start: Date, end: Date): number { + start = this.fromString(start); + end = this.fromString(end); + const duration: moment.Duration = moment.duration( + moment(end).diff(moment(start)), + ); + return duration.asSeconds(); + } - public static isEqualBySeconds(date: Date, startDate: Date): boolean { - date = this.fromString(date); - startDate = this.fromString(startDate); - return moment(date).isSame(startDate, 'seconds'); + public static getSomeDaysAfterDate( + date: Date, + days: PositiveNumber | number, + ): Date { + if (!(days instanceof PositiveNumber)) { + days = new PositiveNumber(days); } - public static hasExpired(expirationDate: Date): boolean { - expirationDate = this.fromString(expirationDate); - return !moment(this.getCurrentDate()).isBefore(expirationDate); - } - - public static isBefore(date: Date, endDate: Date): boolean { - date = this.fromString(date); - endDate = this.fromString(endDate); - return moment(date).isBefore(endDate); - } + return moment(date).add(days.toNumber(), "days").toDate(); + } - public static getCurrentDateAsFormattedString(): string { - return this.getDateAsFormattedString(new Date()); + public static getSomeSecondsAfter(seconds: PositiveNumber | number): Date { + if (!(seconds instanceof PositiveNumber)) { + seconds = new PositiveNumber(seconds); } - public static getDateAsFormattedString( - date: string | Date, - onlyShowDate?: boolean - ): string { - date = this.fromString(date); + return this.getCurrentMomentDate() + .add(seconds.toNumber(), "seconds") + .toDate(); + } - let formatstring: string = 'MMM DD YYYY, HH:mm'; + public static getNumberOfDaysBetweenDates( + startDate: Date, + endDate: Date, + ): number { + const a: moment.Moment = moment(startDate); + const b: moment.Moment = moment(endDate); + return b.diff(a, "days"); + } + + public static getNumberOfMinutesBetweenDates( + startDate: Date, + endDate: Date, + ): number { + const a: moment.Moment = moment(startDate); + const b: moment.Moment = moment(endDate); + return b.diff(a, "minutes"); + } + + public static getNumberOfDaysBetweenDatesInclusive( + startDate: Date, + endDate: Date, + ): number { + return this.getNumberOfDaysBetweenDates(startDate, endDate) + 1; + } + + public static momentToDate(moment: moment.Moment): Date { + return moment.toDate(); + } + + public static getCurrentYear(): number { + return moment().year(); + } + + public static getStartOfDay(date: Date): Date { + date = this.fromString(date); + return moment(date).startOf("day").toDate(); + } + + public static getEndOfDay(date: Date): Date { + date = this.fromString(date); + return moment(date).endOf("day").toDate(); + } + + public static isBetween(date: Date, startDate: Date, endDate: Date): boolean { + date = this.fromString(date); + startDate = this.fromString(startDate); + endDate = this.fromString(endDate); + return moment(date).isBetween(startDate, endDate); + } + + public static isAfter(date: Date, startDate: Date): boolean { + date = this.fromString(date); + startDate = this.fromString(startDate); + return moment(date).isAfter(startDate); + } + + public static isOnOrAfter(date: Date, startDate: Date): boolean { + date = this.fromString(date); + startDate = this.fromString(startDate); + return moment(date).isSameOrAfter(startDate); + } + + public static getDayOfWeek(date: Date): DayOfWeek { + const dayOfWeek: number = this.geyDayOfWeekAsNumber(date); + + if (dayOfWeek === 1) { + return DayOfWeek.Monday; + } else if (dayOfWeek === 2) { + return DayOfWeek.Tuesday; + } else if (dayOfWeek === 3) { + return DayOfWeek.Wednesday; + } else if (dayOfWeek === 4) { + return DayOfWeek.Thursday; + } else if (dayOfWeek === 5) { + return DayOfWeek.Friday; + } else if (dayOfWeek === 6) { + return DayOfWeek.Saturday; + } else if (dayOfWeek === 7) { + return DayOfWeek.Sunday; + } + + throw new BadDataException("Invalid day of week"); + } + + public static geyDayOfWeekAsNumber(date: Date): number { + date = this.fromString(date); + return moment(date).isoWeekday(); + } + + public static isOnOrBefore(date: Date, endDate: Date): boolean { + date = this.fromString(date); + endDate = this.fromString(endDate); + return moment(date).isSameOrBefore(endDate); + } + + public static isEqualBySeconds(date: Date, startDate: Date): boolean { + date = this.fromString(date); + startDate = this.fromString(startDate); + return moment(date).isSame(startDate, "seconds"); + } + + public static hasExpired(expirationDate: Date): boolean { + expirationDate = this.fromString(expirationDate); + return !moment(this.getCurrentDate()).isBefore(expirationDate); + } + + public static isBefore(date: Date, endDate: Date): boolean { + date = this.fromString(date); + endDate = this.fromString(endDate); + return moment(date).isBefore(endDate); + } + + public static getCurrentDateAsFormattedString(): string { + return this.getDateAsFormattedString(new Date()); + } + + public static getDateAsFormattedString( + date: string | Date, + onlyShowDate?: boolean, + ): string { + date = this.fromString(date); + + let formatstring: string = "MMM DD YYYY, HH:mm"; + + if (onlyShowDate) { + formatstring = "MMM DD, YYYY"; + } + + return ( + moment(date).format(formatstring) + + " " + + (onlyShowDate ? "" : this.getCurrentTimezoneString()) + ); + } + + public static getDifferenceInMinutes(date: Date, date2: Date): number { + date = this.fromString(date); + date2 = this.fromString(date2); + const minutes: number = moment(date).diff(moment(date2), "minutes"); + + if (minutes < 0) { + return minutes * -1; + } + + return minutes; + } + + public static convertMinutesToDaysHoursAndMinutes(minutes: number): string { + // should output 2 days, 3 hours and 4 minutes. If the days are 0, it should not show the days. If the hours are 0, it should not show the hours. If the minutes are 0, it should not show the minutes. + + const days: number = Math.floor(minutes / (24 * 60)); + const hours: number = Math.floor((minutes % (24 * 60)) / 60); + const mins: number = minutes % 60; + + let formattedString: string = ""; + + if (days > 0) { + formattedString += days + " days"; + } + + if (hours > 0) { + if (formattedString.length > 0) { + formattedString += ", "; + } + + formattedString += hours + " hours"; + } + + if (mins >= 0) { + if (formattedString.length > 0) { + formattedString += ", "; + } + + formattedString += mins + " minutes"; + } + + return formattedString; + } + + public static getDateAsFormattedArrayInMultipleTimezones( + date: string | Date, + onlyShowDate?: boolean, + ): Array<string> { + date = this.fromString(date); + + let formatstring: string = "MMM DD YYYY, HH:mm"; + + if (onlyShowDate) { + formatstring = "MMM DD, YYYY"; + } + + // convert this date into GMT, EST, PST, IST, ACT with moment + const timezoneDates: Array<string> = []; + + timezoneDates.push( + moment(date).tz("UTC").format(formatstring) + + " " + + (onlyShowDate ? "" : "GMT"), + ); + timezoneDates.push( + moment(date).tz("America/New_York").format(formatstring) + + " " + + (onlyShowDate ? "" : "EST"), + ); + timezoneDates.push( + moment(date).tz("America/Los_Angeles").format(formatstring) + + " " + + (onlyShowDate ? "" : "PST"), + ); + timezoneDates.push( + moment(date).tz("Asia/Kolkata").format(formatstring) + + " " + + (onlyShowDate ? "" : "IST"), + ); + timezoneDates.push( + moment(date).tz("Australia/Sydney").format(formatstring) + + " " + + (onlyShowDate ? "" : "AEST"), + ); - if (onlyShowDate) { - formatstring = 'MMM DD, YYYY'; - } + return timezoneDates; + } + + public static getDateAsFormattedHTMLInMultipleTimezones( + date: string | Date, + onlyShowDate?: boolean, + ): string { + return this.getDateAsFormattedArrayInMultipleTimezones( + date, + onlyShowDate, + ).join("<br/>"); + } + + public static getDateAsFormattedStringInMultipleTimezones( + date: string | Date, + onlyShowDate?: boolean, + ): string { + return this.getDateAsFormattedArrayInMultipleTimezones( + date, + onlyShowDate, + ).join("\n"); + } + + public static getDateAsLocalFormattedString( + date: string | Date, + onlyShowDate?: boolean, + ): string { + date = this.fromString(date); + + let formatstring: string = "MMM DD YYYY, HH:mm"; + + if (onlyShowDate) { + formatstring = "MMM DD, YYYY"; + } + + const momentDate: moment.Moment = moment(date).local(); + + return ( + momentDate.format(formatstring) + + " " + + (onlyShowDate ? "" : this.getCurrentTimezoneString()) + ).trim(); + } + + public static getDayInSeconds(days?: number | undefined): number { + if (!days) { + days = 1; + } + return 24 * 60 * 60 * days; + } + + public static getCurrentTimezoneString(): string { + return moment.tz(moment.tz.guess()).zoneAbbr(); + } + + public static getDateString(date: Date): string { + date = this.fromString(date); + return this.getDateAsLocalFormattedString(date, true); + } + + public static isInThePast(date: string | Date): boolean { + date = this.fromString(date); + return moment(date).isBefore(new Date()); + } + + public static isInTheFuture(date: string | Date): boolean { + date = this.fromString(date); + return moment(date).isAfter(new Date()); + } + + public static fromString(date: string | JSONObject | Date): Date { + if (date instanceof Date) { + return date; + } + + if (typeof date === "string") { + return moment(date).toDate(); + } + + if ( + date && + date["value"] && + typeof date["value"] === "string" && + date["_type"] && + (date["_type"] === "Date" || date["_type"] === "DateTime") + ) { + return moment(date["value"]).toDate(); + } + + throw new BadDataException("Invalid date"); + } + + public static asDateForDatabaseQuery(date: string | Date): string { + date = this.fromString(date); + const formatstring: string = "YYYY-MM-DD"; + return moment(date).local().format(formatstring); + } + + public static asFilterDateForDatabaseQuery(date: string | Date): InBetween { + date = this.fromString(date); + const formattedDate: Date = moment(date).toDate(); + return new InBetween( + OneUptimeDate.getStartOfDay(formattedDate), + OneUptimeDate.getEndOfDay(formattedDate), + ); + } + + public static getDateWithCustomTime(data: { + hours: number; + minutes: number; + seconds: number; + }): Date { + const hour: number = data.hours; + const minutes: number = data.minutes; + const seconds: number = data.seconds; - return ( - moment(date).format(formatstring) + - ' ' + - (onlyShowDate ? '' : this.getCurrentTimezoneString()) - ); + //validate the hour + if (hour < 0 || hour > 23) { + throw new BadDataException("Invalid hour"); } - public static getDifferenceInMinutes(date: Date, date2: Date): number { - date = this.fromString(date); - date2 = this.fromString(date2); - const minutes: number = moment(date).diff(moment(date2), 'minutes'); - - if (minutes < 0) { - return minutes * -1; - } - - return minutes; + //validate the minutes + if (minutes < 0 || minutes > 59) { + throw new BadDataException("Invalid minutes"); } - public static convertMinutesToDaysHoursAndMinutes(minutes: number): string { - // should output 2 days, 3 hours and 4 minutes. If the days are 0, it should not show the days. If the hours are 0, it should not show the hours. If the minutes are 0, it should not show the minutes. - - const days: number = Math.floor(minutes / (24 * 60)); - const hours: number = Math.floor((minutes % (24 * 60)) / 60); - const mins: number = minutes % 60; - - let formattedString: string = ''; - - if (days > 0) { - formattedString += days + ' days'; - } - - if (hours > 0) { - if (formattedString.length > 0) { - formattedString += ', '; - } - - formattedString += hours + ' hours'; - } - - if (mins >= 0) { - if (formattedString.length > 0) { - formattedString += ', '; - } - - formattedString += mins + ' minutes'; - } - - return formattedString; + //validate the seconds + if (seconds < 0 || seconds > 59) { + throw new BadDataException("Invalid seconds"); } - public static getDateAsFormattedArrayInMultipleTimezones( - date: string | Date, - onlyShowDate?: boolean - ): Array<string> { - date = this.fromString(date); - - let formatstring: string = 'MMM DD YYYY, HH:mm'; - - if (onlyShowDate) { - formatstring = 'MMM DD, YYYY'; - } - - // convert this date into GMT, EST, PST, IST, ACT with moment - const timezoneDates: Array<string> = []; - - timezoneDates.push( - moment(date).tz('UTC').format(formatstring) + - ' ' + - (onlyShowDate ? '' : 'GMT') - ); - timezoneDates.push( - moment(date).tz('America/New_York').format(formatstring) + - ' ' + - (onlyShowDate ? '' : 'EST') - ); - timezoneDates.push( - moment(date).tz('America/Los_Angeles').format(formatstring) + - ' ' + - (onlyShowDate ? '' : 'PST') - ); - timezoneDates.push( - moment(date).tz('Asia/Kolkata').format(formatstring) + - ' ' + - (onlyShowDate ? '' : 'IST') - ); - timezoneDates.push( - moment(date).tz('Australia/Sydney').format(formatstring) + - ' ' + - (onlyShowDate ? '' : 'AEST') - ); - - return timezoneDates; - } + const date: Date = OneUptimeDate.getCurrentDate(); - public static getDateAsFormattedHTMLInMultipleTimezones( - date: string | Date, - onlyShowDate?: boolean - ): string { - return this.getDateAsFormattedArrayInMultipleTimezones( - date, - onlyShowDate - ).join('<br/>'); - } + date.setHours(hour); + date.setMinutes(minutes); + date.setSeconds(seconds); - public static getDateAsFormattedStringInMultipleTimezones( - date: string | Date, - onlyShowDate?: boolean - ): string { - return this.getDateAsFormattedArrayInMultipleTimezones( - date, - onlyShowDate - ).join('\n'); - } + return date; + } - public static getDateAsLocalFormattedString( - date: string | Date, - onlyShowDate?: boolean - ): string { - date = this.fromString(date); - - let formatstring: string = 'MMM DD YYYY, HH:mm'; - - if (onlyShowDate) { - formatstring = 'MMM DD, YYYY'; - } - - const momentDate: moment.Moment = moment(date).local(); - - return ( - momentDate.format(formatstring) + - ' ' + - (onlyShowDate ? '' : this.getCurrentTimezoneString()) - ).trim(); - } - - public static getDayInSeconds(days?: number | undefined): number { - if (!days) { - days = 1; - } - return 24 * 60 * 60 * days; - } - - public static getCurrentTimezoneString(): string { - return moment.tz(moment.tz.guess()).zoneAbbr(); - } - - public static getDateString(date: Date): string { - date = this.fromString(date); - return this.getDateAsLocalFormattedString(date, true); - } - - public static isInThePast(date: string | Date): boolean { - date = this.fromString(date); - return moment(date).isBefore(new Date()); - } - - public static isInTheFuture(date: string | Date): boolean { - date = this.fromString(date); - return moment(date).isAfter(new Date()); - } - - public static fromString(date: string | JSONObject | Date): Date { - if (date instanceof Date) { - return date; - } - - if (typeof date === 'string') { - return moment(date).toDate(); - } - - if ( - date && - date['value'] && - typeof date['value'] === 'string' && - date['_type'] && - (date['_type'] === 'Date' || date['_type'] === 'DateTime') - ) { - return moment(date['value']).toDate(); - } - - throw new BadDataException('Invalid date'); - } - - public static asDateForDatabaseQuery(date: string | Date): string { - date = this.fromString(date); - const formatstring: string = 'YYYY-MM-DD'; - return moment(date).local().format(formatstring); - } - - public static asFilterDateForDatabaseQuery(date: string | Date): InBetween { - date = this.fromString(date); - const formattedDate: Date = moment(date).toDate(); - return new InBetween( - OneUptimeDate.getStartOfDay(formattedDate), - OneUptimeDate.getEndOfDay(formattedDate) - ); - } - - public static getDateWithCustomTime(data: { - hours: number; - minutes: number; - seconds: number; - }): Date { - const hour: number = data.hours; - const minutes: number = data.minutes; - const seconds: number = data.seconds; - - //validate the hour - if (hour < 0 || hour > 23) { - throw new BadDataException('Invalid hour'); - } - - //validate the minutes - if (minutes < 0 || minutes > 59) { - throw new BadDataException('Invalid minutes'); - } - - //validate the seconds - if (seconds < 0 || seconds > 59) { - throw new BadDataException('Invalid seconds'); - } - - const date: Date = OneUptimeDate.getCurrentDate(); - - date.setHours(hour); - date.setMinutes(minutes); - date.setSeconds(seconds); - - return date; - } - - public static toDatabaseDate(date: Date): string { - date = this.fromString(date); - return moment(date).format('YYYY-MM-DD HH:mm:ss'); - } + public static toDatabaseDate(date: Date): string { + date = this.fromString(date); + return moment(date).format("YYYY-MM-DD HH:mm:ss"); + } } diff --git a/Common/Types/Day/DayOfWeek.ts b/Common/Types/Day/DayOfWeek.ts index c673cddd27..79264fe0ba 100644 --- a/Common/Types/Day/DayOfWeek.ts +++ b/Common/Types/Day/DayOfWeek.ts @@ -1,32 +1,32 @@ enum DayOfWeek { - Sunday = 'Sunday', - Monday = 'Monday', - Tuesday = 'Tuesday', - Wednesday = 'Wednesday', - Thursday = 'Thursday', - Friday = 'Friday', - Saturday = 'Saturday', + Sunday = "Sunday", + Monday = "Monday", + Tuesday = "Tuesday", + Wednesday = "Wednesday", + Thursday = "Thursday", + Friday = "Friday", + Saturday = "Saturday", } export class DayOfWeekUtil { - public static getNumberOfDayOfWeek(dayOfWeek: DayOfWeek): number { - switch (dayOfWeek) { - case DayOfWeek.Sunday: - return 0; - case DayOfWeek.Monday: - return 1; - case DayOfWeek.Tuesday: - return 2; - case DayOfWeek.Wednesday: - return 3; - case DayOfWeek.Thursday: - return 4; - case DayOfWeek.Friday: - return 5; - case DayOfWeek.Saturday: - return 6; - } + public static getNumberOfDayOfWeek(dayOfWeek: DayOfWeek): number { + switch (dayOfWeek) { + case DayOfWeek.Sunday: + return 0; + case DayOfWeek.Monday: + return 1; + case DayOfWeek.Tuesday: + return 2; + case DayOfWeek.Wednesday: + return 3; + case DayOfWeek.Thursday: + return 4; + case DayOfWeek.Friday: + return 5; + case DayOfWeek.Saturday: + return 6; } + } } export default DayOfWeek; diff --git a/Common/Types/Decimal.ts b/Common/Types/Decimal.ts index 5110db3604..d61f7a19a4 100644 --- a/Common/Types/Decimal.ts +++ b/Common/Types/Decimal.ts @@ -1,78 +1,78 @@ // This is for Object ID for all the things in our database. -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import { FindOperator } from "typeorm"; export default class Decimal extends DatabaseProperty { - private _value: number = 0; - public get value(): number { - return this._value; - } - public set value(v: number) { - this._value = v; + private _value: number = 0; + public get value(): number { + return this._value; + } + public set value(v: number) { + this._value = v; + } + + public constructor(value: number | Decimal | string) { + super(); + + if (typeof value === "string") { + value = parseFloat(value); } - public constructor(value: number | Decimal | string) { - super(); - - if (typeof value === 'string') { - value = parseFloat(value); - } - - if (value instanceof Decimal) { - value = value.value; - } - - this.value = value; + if (value instanceof Decimal) { + value = value.value; } - public equals(other: Decimal): boolean { - return this.value.toString() === other.value.toString(); + this.value = value; + } + + public equals(other: Decimal): boolean { + return this.value.toString() === other.value.toString(); + } + + public override toString(): string { + return this.value.toString(); + } + + protected static override toDatabase( + value: Decimal | FindOperator<Decimal>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new Decimal(value); + } + + return value.toString(); } - public override toString(): string { - return this.value.toString(); + return null; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Decimal, + value: (this as Decimal).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Decimal { + if (json["_type"] === ObjectType.Decimal) { + return new Decimal((json["value"] as number) || 0); } - protected static override toDatabase( - value: Decimal | FindOperator<Decimal> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new Decimal(value); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } - return value.toString(); - } - - return null; + protected static override fromDatabase(_value: number): Decimal | null { + if (_value) { + return new Decimal(_value); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.Decimal, - value: (this as Decimal).toString(), - }; - } + return null; + } - public static override fromJSON(json: JSONObject): Decimal { - if (json['_type'] === ObjectType.Decimal) { - return new Decimal((json['value'] as number) || 0); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - protected static override fromDatabase(_value: number): Decimal | null { - if (_value) { - return new Decimal(_value); - } - - return null; - } - - public static fromString(value: string): Decimal { - return new Decimal(value); - } + public static fromString(value: string): Decimal { + return new Decimal(value); + } } diff --git a/Common/Types/Dictionary.ts b/Common/Types/Dictionary.ts index 3c35267335..0f730bb7a5 100644 --- a/Common/Types/Dictionary.ts +++ b/Common/Types/Dictionary.ts @@ -1,3 +1,3 @@ export default interface Dictionary<T> { - [x: string]: T; + [x: string]: T; } diff --git a/Common/Types/DiskSize.ts b/Common/Types/DiskSize.ts index 56d3e2ae3d..b6f242b1d2 100644 --- a/Common/Types/DiskSize.ts +++ b/Common/Types/DiskSize.ts @@ -1,46 +1,46 @@ -import BadDataException from './Exception/BadDataException'; +import BadDataException from "./Exception/BadDataException"; export default class DiskSize { - public static convertToDecimalPlaces( - value: number, - decimalPlaces: number = 2 - ): number { - if (decimalPlaces < 0) { - throw new BadDataException( - 'decimalPlaces must be greater than or equal to 0.' - ); - } - - if (typeof value === 'string') { - value = parseFloat(value); - } - - if (decimalPlaces === 0) { - return Math.ceil(value); - } - - value = value * Math.pow(10, decimalPlaces); - - // convert to int. - - value = Math.round(value); - - // convert back to float. - - value = value / Math.pow(10, decimalPlaces); - - return value; + public static convertToDecimalPlaces( + value: number, + decimalPlaces: number = 2, + ): number { + if (decimalPlaces < 0) { + throw new BadDataException( + "decimalPlaces must be greater than or equal to 0.", + ); } - public static byteSizeToGB(byteSize: number): number { - return byteSize / 1024 / 1024 / 1024; + if (typeof value === "string") { + value = parseFloat(value); } - public static byteSizeToMB(byteSize: number): number { - return byteSize / 1024 / 1024; + if (decimalPlaces === 0) { + return Math.ceil(value); } - public static byteSizeToKB(byteSize: number): number { - return byteSize / 1024; - } + value = value * Math.pow(10, decimalPlaces); + + // convert to int. + + value = Math.round(value); + + // convert back to float. + + value = value / Math.pow(10, decimalPlaces); + + return value; + } + + public static byteSizeToGB(byteSize: number): number { + return byteSize / 1024 / 1024 / 1024; + } + + public static byteSizeToMB(byteSize: number): number { + return byteSize / 1024 / 1024; + } + + public static byteSizeToKB(byteSize: number): number { + return byteSize / 1024; + } } diff --git a/Common/Types/Domain.ts b/Common/Types/Domain.ts index c16a3333f7..500c7d55de 100644 --- a/Common/Types/Domain.ts +++ b/Common/Types/Domain.ts @@ -1,94 +1,94 @@ -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import { FindOperator } from 'typeorm/find-options/FindOperator'; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import { FindOperator } from "typeorm/find-options/FindOperator"; export default class Domain extends DatabaseProperty { - private _domain: string = ''; - public get domain(): string { - return this._domain; + private _domain: string = ""; + public get domain(): string { + return this._domain; + } + public set domain(v: string) { + const isValid: boolean = Domain.isValidDomain(v); + if (!isValid) { + throw new BadDataException("Domain is not in valid format."); } - public set domain(v: string) { - const isValid: boolean = Domain.isValidDomain(v); - if (!isValid) { - throw new BadDataException('Domain is not in valid format.'); - } - this._domain = v; + this._domain = v; + } + + public static isValidDomain(domain: string): boolean { + if (!domain.includes(".")) { + return false; } - public static isValidDomain(domain: string): boolean { - if (!domain.includes('.')) { - return false; - } + const firstTLDs: Array<string> = + "ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|be|bf|bg|bh|bi|bj|bm|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|cl|cm|cn|co|cr|cu|cv|cw|cx|cz|de|dj|dk|dm|do|dz|ec|ee|eg|es|et|eu|fi|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|im|in|io|iq|ir|is|it|je|jo|jp|kg|ki|km|kn|kp|kr|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|na|nc|ne|nf|ng|nl|no|nr|nu|nz|om|pa|pe|pf|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|yt".split( + "|", + ); + const secondTLDs: Array<string> = + "ac|academy|accountant|accountants|actor|adult|ag|agency|ai|airforce|am|amsterdam|apartments|app|archi|army|art|asia|associates|at|attorney|au|auction|auto|autos|baby|band|bar|barcelona|bargains|basketball|bayern|be|beauty|beer|berlin|best|bet|bid|bike|bingo|bio|biz|biz.pl|black|blog|blue|boats|boston|boutique|broker|build|builders|business|buzz|bz|ca|cab|cafe|camera|camp|capital|car|cards|care|careers|cars|casa|cash|casino|catering|cc|center|ceo|ch|charity|chat|cheap|church|city|cl|claims|cleaning|clinic|clothing|cloud|club|cn|co|co.in|co.jp|co.kr|co.nz|co.uk|co.za|coach|codes|coffee|college|com|com.ag|com.au|com.br|com.bz|com.cn|com.co|com.es|com.ky|com.mx|com.pe|com.ph|com.pl|com.ru|com.tw|community|company|computer|condos|construction|consulting|contact|contractors|cooking|cool|country|coupons|courses|credit|creditcard|cricket|cruises|cymru|cz|dance|date|dating|de|deals|degree|delivery|democrat|dental|dentist|design|dev|diamonds|digital|direct|directory|discount|dk|doctor|dog|domains|download|earth|education|email|energy|engineer|engineering|enterprises|equipment|es|estate|eu|events|exchange|expert|exposed|express|fail|faith|family|fan|fans|farm|fashion|film|finance|financial|firm.in|fish|fishing|fit|fitness|flights|florist|fm|football|forsale|foundation|fr|fun|fund|furniture|futbol|fyi|gallery|games|garden|gay|gen.in|gg|gifts|gives|giving|glass|global|gmbh|gold|golf|graphics|gratis|green|gripe|group|gs|guide|guru|hair|haus|health|healthcare|hockey|holdings|holiday|homes|horse|hospital|host|house|idv.tw|immo|immobilien|in|inc|ind.in|industries|info|info.pl|ink|institute|insure|international|investments|io|irish|ist|istanbul|it|jetzt|jewelry|jobs|jp|kaufen|kids|kim|kitchen|kiwi|kr|ky|la|land|lat|law|lawyer|lease|legal|lgbt|life|lighting|limited|limo|live|llc|llp|loan|loans|london|love|ltd|ltda|luxury|maison|makeup|management|market|marketing|mba|me|me.uk|media|melbourne|memorial|men|menu|miami|mobi|moda|moe|money|monster|mortgage|motorcycles|movie|ms|music|mx|nagoya|name|navy|ne.kr|net|net.ag|net.au|net.br|net.bz|net.cn|net.co|net.in|net.ky|net.nz|net.pe|net.ph|net.pl|net.ru|network|news|ninja|nl|no|nom.co|nom.es|nom.pe|nrw|nyc|okinawa|one|onl|online|org|org.ag|org.au|org.cn|org.es|org.in|org.ky|org.nz|org.pe|org.ph|org.pl|org.ru|org.uk|organic|page|paris|partners|parts|party|pe|pet|ph|photography|photos|pictures|pink|pizza|pl|place|plumbing|plus|poker|porn|press|pro|productions|promo|properties|protection|pub|pw|quebec|quest|racing|re.kr|realestate|recipes|red|rehab|reise|reisen|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|rip|rocks|rodeo|rugby|run|ryukyu|sale|salon|sarl|school|schule|science|se|security|services|sex|sg|sh|shiksha|shoes|shop|shopping|show|singles|site|ski|skin|soccer|social|software|solar|solutions|space|storage|store|stream|studio|study|style|supplies|supply|support|surf|surgery|sydney|systems|tax|taxi|team|tech|technology|tel|tennis|theater|theatre|tickets|tienda|tips|tires|today|tokyo|tools|tours|town|toys|trade|trading|training|travel|tube|tv|tw|uk|university|uno|us|vacations|vc|vegas|ventures|vet|viajes|video|villas|vin|vip|vision|vodka|vote|voto|voyage|wales|watch|web|webcam|website|wedding|wiki|win|wine|work|works|world|ws|wtf|xxx|xyz|yachts|yoga|yokohama|zone|移动|dev|com|edu|gov|net|mil|org|nom|sch|sbs|caa|res|off|gob|int|tur|ip6|uri|urn|asn|act|nsw|qld|tas|vic|pro|biz|adm|adv|agr|arq|art|ato|bio|bmd|cim|cng|cnt|ecn|eco|emp|eng|esp|etc|eti|far|fnd|fot|fst|g12|ggf|imb|ind|inf|jor|jus|leg|lel|mat|med|mus|not|ntr|odo|ppg|psc|psi|qsl|rec|slg|srv|teo|tmp|trd|vet|zlg|web|ltd|sld|pol|fin|k12|lib|pri|aip|fie|eun|sci|prd|cci|pvt|mod|idv|rel|sex|gen|nic|abr|bas|cal|cam|emr|fvg|laz|lig|lom|mar|mol|pmn|pug|sar|sic|taa|tos|umb|vao|vda|ven|mie|北海道|和歌山|神奈川|鹿児島|ass|rep|tra|per|ngo|soc|grp|plc|its|air|and|bus|can|ddr|jfk|mad|nrw|nyc|ski|spy|tcm|ulm|usa|war|fhs|vgs|dep|eid|fet|fla|flå|gol|hof|hol|sel|vik|cri|iwi|ing|abo|fam|gok|gon|gop|gos|aid|atm|gsm|sos|elk|waw|est|aca|bar|cpa|jur|law|sec|plo|www|bir|cbg|jar|khv|msk|nov|nsk|ptz|rnd|spb|stv|tom|tsk|udm|vrn|cmw|kms|nkz|snz|pub|fhv|red|ens|nat|rns|rnu|bbs|tel|bel|kep|nhs|dni|fed|isa|nsn|gub|e12|tec|орг|обр|упр|alt|nis|jpn|mex|ath|iki|nid|gda|inc|za|ovh".split( + "|", + ); - const firstTLDs: Array<string> = - 'ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|be|bf|bg|bh|bi|bj|bm|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|cl|cm|cn|co|cr|cu|cv|cw|cx|cz|de|dj|dk|dm|do|dz|ec|ee|eg|es|et|eu|fi|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|im|in|io|iq|ir|is|it|je|jo|jp|kg|ki|km|kn|kp|kr|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|na|nc|ne|nf|ng|nl|no|nr|nu|nz|om|pa|pe|pf|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|yt'.split( - '|' - ); - const secondTLDs: Array<string> = - 'ac|academy|accountant|accountants|actor|adult|ag|agency|ai|airforce|am|amsterdam|apartments|app|archi|army|art|asia|associates|at|attorney|au|auction|auto|autos|baby|band|bar|barcelona|bargains|basketball|bayern|be|beauty|beer|berlin|best|bet|bid|bike|bingo|bio|biz|biz.pl|black|blog|blue|boats|boston|boutique|broker|build|builders|business|buzz|bz|ca|cab|cafe|camera|camp|capital|car|cards|care|careers|cars|casa|cash|casino|catering|cc|center|ceo|ch|charity|chat|cheap|church|city|cl|claims|cleaning|clinic|clothing|cloud|club|cn|co|co.in|co.jp|co.kr|co.nz|co.uk|co.za|coach|codes|coffee|college|com|com.ag|com.au|com.br|com.bz|com.cn|com.co|com.es|com.ky|com.mx|com.pe|com.ph|com.pl|com.ru|com.tw|community|company|computer|condos|construction|consulting|contact|contractors|cooking|cool|country|coupons|courses|credit|creditcard|cricket|cruises|cymru|cz|dance|date|dating|de|deals|degree|delivery|democrat|dental|dentist|design|dev|diamonds|digital|direct|directory|discount|dk|doctor|dog|domains|download|earth|education|email|energy|engineer|engineering|enterprises|equipment|es|estate|eu|events|exchange|expert|exposed|express|fail|faith|family|fan|fans|farm|fashion|film|finance|financial|firm.in|fish|fishing|fit|fitness|flights|florist|fm|football|forsale|foundation|fr|fun|fund|furniture|futbol|fyi|gallery|games|garden|gay|gen.in|gg|gifts|gives|giving|glass|global|gmbh|gold|golf|graphics|gratis|green|gripe|group|gs|guide|guru|hair|haus|health|healthcare|hockey|holdings|holiday|homes|horse|hospital|host|house|idv.tw|immo|immobilien|in|inc|ind.in|industries|info|info.pl|ink|institute|insure|international|investments|io|irish|ist|istanbul|it|jetzt|jewelry|jobs|jp|kaufen|kids|kim|kitchen|kiwi|kr|ky|la|land|lat|law|lawyer|lease|legal|lgbt|life|lighting|limited|limo|live|llc|llp|loan|loans|london|love|ltd|ltda|luxury|maison|makeup|management|market|marketing|mba|me|me.uk|media|melbourne|memorial|men|menu|miami|mobi|moda|moe|money|monster|mortgage|motorcycles|movie|ms|music|mx|nagoya|name|navy|ne.kr|net|net.ag|net.au|net.br|net.bz|net.cn|net.co|net.in|net.ky|net.nz|net.pe|net.ph|net.pl|net.ru|network|news|ninja|nl|no|nom.co|nom.es|nom.pe|nrw|nyc|okinawa|one|onl|online|org|org.ag|org.au|org.cn|org.es|org.in|org.ky|org.nz|org.pe|org.ph|org.pl|org.ru|org.uk|organic|page|paris|partners|parts|party|pe|pet|ph|photography|photos|pictures|pink|pizza|pl|place|plumbing|plus|poker|porn|press|pro|productions|promo|properties|protection|pub|pw|quebec|quest|racing|re.kr|realestate|recipes|red|rehab|reise|reisen|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|rip|rocks|rodeo|rugby|run|ryukyu|sale|salon|sarl|school|schule|science|se|security|services|sex|sg|sh|shiksha|shoes|shop|shopping|show|singles|site|ski|skin|soccer|social|software|solar|solutions|space|storage|store|stream|studio|study|style|supplies|supply|support|surf|surgery|sydney|systems|tax|taxi|team|tech|technology|tel|tennis|theater|theatre|tickets|tienda|tips|tires|today|tokyo|tools|tours|town|toys|trade|trading|training|travel|tube|tv|tw|uk|university|uno|us|vacations|vc|vegas|ventures|vet|viajes|video|villas|vin|vip|vision|vodka|vote|voto|voyage|wales|watch|web|webcam|website|wedding|wiki|win|wine|work|works|world|ws|wtf|xxx|xyz|yachts|yoga|yokohama|zone|移动|dev|com|edu|gov|net|mil|org|nom|sch|sbs|caa|res|off|gob|int|tur|ip6|uri|urn|asn|act|nsw|qld|tas|vic|pro|biz|adm|adv|agr|arq|art|ato|bio|bmd|cim|cng|cnt|ecn|eco|emp|eng|esp|etc|eti|far|fnd|fot|fst|g12|ggf|imb|ind|inf|jor|jus|leg|lel|mat|med|mus|not|ntr|odo|ppg|psc|psi|qsl|rec|slg|srv|teo|tmp|trd|vet|zlg|web|ltd|sld|pol|fin|k12|lib|pri|aip|fie|eun|sci|prd|cci|pvt|mod|idv|rel|sex|gen|nic|abr|bas|cal|cam|emr|fvg|laz|lig|lom|mar|mol|pmn|pug|sar|sic|taa|tos|umb|vao|vda|ven|mie|北海道|和歌山|神奈川|鹿児島|ass|rep|tra|per|ngo|soc|grp|plc|its|air|and|bus|can|ddr|jfk|mad|nrw|nyc|ski|spy|tcm|ulm|usa|war|fhs|vgs|dep|eid|fet|fla|flå|gol|hof|hol|sel|vik|cri|iwi|ing|abo|fam|gok|gon|gop|gos|aid|atm|gsm|sos|elk|waw|est|aca|bar|cpa|jur|law|sec|plo|www|bir|cbg|jar|khv|msk|nov|nsk|ptz|rnd|spb|stv|tom|tsk|udm|vrn|cmw|kms|nkz|snz|pub|fhv|red|ens|nat|rns|rnu|bbs|tel|bel|kep|nhs|dni|fed|isa|nsn|gub|e12|tec|орг|обр|упр|alt|nis|jpn|mex|ath|iki|nid|gda|inc|za|ovh'.split( - '|' - ); + const parts: Array<string> = domain.split("."); + const lastItem: string = parts[parts.length - 1] as string; + const beforeLastItem: string = parts[parts.length - 2] as string; - const parts: Array<string> = domain.split('.'); - const lastItem: string = parts[parts.length - 1] as string; - const beforeLastItem: string = parts[parts.length - 2] as string; - - if (firstTLDs.includes(lastItem)) { - if (secondTLDs.includes(beforeLastItem)) { - return true; - } - return true; - } else if (secondTLDs.includes(lastItem)) { - return true; - } - - return false; + if (firstTLDs.includes(lastItem)) { + if (secondTLDs.includes(beforeLastItem)) { + return true; + } + return true; + } else if (secondTLDs.includes(lastItem)) { + return true; } - public constructor(domain: string) { - super(); - this.domain = domain; + return false; + } + + public constructor(domain: string) { + super(); + this.domain = domain; + } + + public override toString(): string { + return this.domain; + } + + protected static override toDatabase( + value: Domain | FindOperator<Domain>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new Domain(value); + } + + return value.toString(); } - public override toString(): string { - return this.domain; + return null; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Domain, + value: (this as Domain).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Domain { + if (json["_type"] === ObjectType.Domain) { + return new Domain((json["value"] as string) || ""); } - protected static override toDatabase( - value: Domain | FindOperator<Domain> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new Domain(value); - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } - return value.toString(); - } - - return null; + protected static override fromDatabase(_value: string): Domain | null { + if (_value) { + return new Domain(_value); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.Domain, - value: (this as Domain).toString(), - }; - } - - public static override fromJSON(json: JSONObject): Domain { - if (json['_type'] === ObjectType.Domain) { - return new Domain((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - protected static override fromDatabase(_value: string): Domain | null { - if (_value) { - return new Domain(_value); - } - - return null; - } + return null; + } } diff --git a/Common/Types/Email.ts b/Common/Types/Email.ts index a81fcf7161..0f5ac10ad1 100644 --- a/Common/Types/Email.ts +++ b/Common/Types/Email.ts @@ -1,128 +1,126 @@ -import Hostname from './API/Hostname'; -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import { FindOperator } from 'typeorm'; +import Hostname from "./API/Hostname"; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import { FindOperator } from "typeorm"; const nonBusinessEmailDomains: Array<string> = [ - 'gmail', - 'yahoo', - 'yahoomail', - 'googlemail', - 'ymail', - 'icloud', - 'aol', - 'hotmail', - 'outlook', - 'msn', - 'wanadoo', - 'orange', - 'comcast', - 'facebook', - 'hey.com', - 'protonmail', - 'inbox.com', - 'mail.com', - 'zoho', - 'yandex', + "gmail", + "yahoo", + "yahoomail", + "googlemail", + "ymail", + "icloud", + "aol", + "hotmail", + "outlook", + "msn", + "wanadoo", + "orange", + "comcast", + "facebook", + "hey.com", + "protonmail", + "inbox.com", + "mail.com", + "zoho", + "yandex", ]; export default class Email extends DatabaseProperty { - private _email: string = ''; - public get email(): string { - return this._email; + private _email: string = ""; + public get email(): string { + return this._email; + } + public set email(value: string) { + if (value && typeof value === "string") { + value = value.trim(); + value = value.toLowerCase(); } - public set email(value: string) { - if (value && typeof value === 'string') { - value = value.trim(); - value = value.toLowerCase(); - } - - if (Email.isValid(value)) { - this._email = value; - } else { - throw new BadDataException( - `Email ${value} is not in valid format.` - ); + + if (Email.isValid(value)) { + this._email = value; + } else { + throw new BadDataException(`Email ${value} is not in valid format.`); + } + } + + public constructor(email: string) { + super(); + this.email = email; + } + + public static isValid(value: string): boolean { + // from https://datatracker.ietf.org/doc/html/rfc5322 + + const re: RegExp = + /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/i; + const isValid: boolean = re.test(value); + if (!isValid) { + return false; + } + return true; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Email, + value: (this as Email).toString(), + }; + } + + public static fromString(value: string): Email { + return new Email(value); + } + + public static override fromJSON(json: JSONObject): Email { + if (json["_type"] === ObjectType.Email) { + return new Email((json["value"] as string) || ""); + } + + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + public override toString(): string { + return this.email; + } + + public getEmailDomain(): Hostname { + return new Hostname(this.email!.split("@")[1]!); + } + + public isBusinessEmail(): boolean { + const domain: string = this.getEmailDomain().hostname || ""; + if (domain) { + for (let i: number = 0; i < nonBusinessEmailDomains.length; i++) { + if (domain.includes(nonBusinessEmailDomains[i]!)) { + return false; } + } } - public constructor(email: string) { - super(); - this.email = email; + return true; + } + + public static override toDatabase( + value: Email | FindOperator<Email>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new Email(value); + } + + return value.toString(); } - public static isValid(value: string): boolean { - // from https://datatracker.ietf.org/doc/html/rfc5322 + return null; + } - const re: RegExp = - /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/i; - const isValid: boolean = re.test(value); - if (!isValid) { - return false; - } - return true; + public static override fromDatabase(_value: string): Email | null { + if (_value) { + return new Email(_value); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.Email, - value: (this as Email).toString(), - }; - } - - public static fromString(value: string): Email { - return new Email(value); - } - - public static override fromJSON(json: JSONObject): Email { - if (json['_type'] === ObjectType.Email) { - return new Email((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - public override toString(): string { - return this.email; - } - - public getEmailDomain(): Hostname { - return new Hostname(this.email!.split('@')[1]!); - } - - public isBusinessEmail(): boolean { - const domain: string = this.getEmailDomain().hostname || ''; - if (domain) { - for (let i: number = 0; i < nonBusinessEmailDomains.length; i++) { - if (domain.includes(nonBusinessEmailDomains[i]!)) { - return false; - } - } - } - - return true; - } - - public static override toDatabase( - value: Email | FindOperator<Email> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new Email(value); - } - - return value.toString(); - } - - return null; - } - - public static override fromDatabase(_value: string): Email | null { - if (_value) { - return new Email(_value); - } - - return null; - } + return null; + } } diff --git a/Common/Types/Email/EmailBody.ts b/Common/Types/Email/EmailBody.ts index 54be37583e..05be0eb701 100644 --- a/Common/Types/Email/EmailBody.ts +++ b/Common/Types/Email/EmailBody.ts @@ -1,4 +1,4 @@ export default interface EmailBody { - subject: string; - body: string; + subject: string; + body: string; } diff --git a/Common/Types/Email/EmailMessage.ts b/Common/Types/Email/EmailMessage.ts index 3f313a96a2..c7dc89776b 100644 --- a/Common/Types/Email/EmailMessage.ts +++ b/Common/Types/Email/EmailMessage.ts @@ -1,14 +1,14 @@ -import Dictionary from '../Dictionary'; -import Email from '../Email'; -import EmailTemplateType from './EmailTemplateType'; +import Dictionary from "../Dictionary"; +import Email from "../Email"; +import EmailTemplateType from "./EmailTemplateType"; export interface EmailEnvelope { - subject: string; - templateType?: EmailTemplateType; - vars: Dictionary<string>; - body?: string; + subject: string; + templateType?: EmailTemplateType; + vars: Dictionary<string>; + body?: string; } export default interface EmailMessage extends EmailEnvelope { - toEmail: Email; + toEmail: Email; } diff --git a/Common/Types/Email/EmailServer.ts b/Common/Types/Email/EmailServer.ts index 84d100fe94..8bd580409d 100644 --- a/Common/Types/Email/EmailServer.ts +++ b/Common/Types/Email/EmailServer.ts @@ -1,15 +1,15 @@ -import Hostname from '../API/Hostname'; -import Email from '../Email'; -import ObjectID from '../ObjectID'; -import Port from '../Port'; +import Hostname from "../API/Hostname"; +import Email from "../Email"; +import ObjectID from "../ObjectID"; +import Port from "../Port"; export default interface EmailServer { - id?: ObjectID | undefined; // If this is custom SMTP, this is the ID of the SMTP config. Otherwise, it's undefined - host: Hostname; - port: Port; - username: string | undefined; - password: string | undefined; - secure: boolean; - fromEmail: Email; - fromName: string; + id?: ObjectID | undefined; // If this is custom SMTP, this is the ID of the SMTP config. Otherwise, it's undefined + host: Hostname; + port: Port; + username: string | undefined; + password: string | undefined; + secure: boolean; + fromEmail: Email; + fromName: string; } diff --git a/Common/Types/Email/EmailTemplate.ts b/Common/Types/Email/EmailTemplate.ts index e8cc504409..3815b1f211 100644 --- a/Common/Types/Email/EmailTemplate.ts +++ b/Common/Types/Email/EmailTemplate.ts @@ -1,8 +1,8 @@ -import EmailBody from './EmailBody'; -import EmailTemplateType from './EmailTemplateType'; +import EmailBody from "./EmailBody"; +import EmailTemplateType from "./EmailTemplateType"; export default interface EmailTemplate { - allowedVariables: Array<string>; - emailType: EmailTemplateType; - emailBody: EmailBody; + allowedVariables: Array<string>; + emailType: EmailTemplateType; + emailBody: EmailBody; } diff --git a/Common/Types/Email/EmailTemplateType.ts b/Common/Types/Email/EmailTemplateType.ts index a904397845..2b88fcfe72 100644 --- a/Common/Types/Email/EmailTemplateType.ts +++ b/Common/Types/Email/EmailTemplateType.ts @@ -1,40 +1,40 @@ enum EmailTemplateType { - ForgotPassword = 'ForgotPassword.hbs', - ProbeOffline = 'ProbeOffline.hbs', - SignupWelcomeEmail = 'SignupWelcomeEmail.hbs', - EmailVerified = 'EmailVerified.hbs', - PasswordChanged = 'PasswordChanged.hbs', - InviteMember = 'InviteMember.hbs', - EmailChanged = 'EmailChanged.hbs', - SubscribedToStatusPage = 'SubscribedToStatusPage.hbs', - SubscriberAnnouncementCreated = 'SubscriberAnnouncementCreated.hbs', - SubscriberIncidentCreated = 'SubscriberIncidentCreated.hbs', - SubscriberIncidentNoteCreated = 'SubscriberIncidentNoteCreated.hbs', - SubscriberIncidentStateChanged = 'SubscriberIncidentStateChanged.hbs', - SubscriberScheduledMaintenanceEventCreated = 'SubscriberScheduledMaintenanceEventCreated.hbs', - SubscriberScheduledMaintenanceEventStateChanged = 'SubscriberScheduledMaintenanceEventStateChanged.hbs', - StatusPageForgotPassword = 'StatusPageForgotPassword.hbs', - StatusPagePasswordChanged = 'StatusPagePasswordChanged.hbs', - StatusPageWelcomeEmail = 'StatusPageWelcomeEmail.hbs', - SubscriberScheduledMaintenanceEventNoteCreated = 'SubscriberScheduledMaintenanceEventNoteCreated.hbs', - SMTPTest = 'SMTPTest.hbs', - MonitorOwnerAdded = 'MonitorOwnerAdded.hbs', - MonitorOwnerResourceCreated = 'MonitorOwnerResourceCreated.hbs', - MonitorOwnerStatusChanged = 'MonitorOwnerStatusChanged.hbs', - IncidentOwnerAdded = 'IncidentOwnerAdded.hbs', - IncidentOwnerStateChanged = 'IncidentOwnerStateChanged.hbs', - IncidentOwnerNotePosted = 'IncidentOwnerNotePosted.hbs', - IncidentOwnerResourceCreated = 'IncidentOwnerResourceCreated.hbs', - ScheduledMaintenanceOwnerNotePosted = 'ScheduledMaintenanceOwnerNotePosted.hbs', - ScheduledMaintenanceOwnerAdded = 'ScheduledMaintenanceOwnerAdded.hbs', - ScheduledMaintenanceOwnerStateChanged = 'ScheduledMaintenanceOwnerStateChanged.hbs', - ScheduledMaintenanceOwnerResourceCreated = 'ScheduledMaintenanceOwnerResourceCreated.hbs', - StatusPageOwnerResourceCreated = 'StatusPageOwnerResourceCreated.hbs', - StatusPageOwnerAdded = 'StatusPageOwnerAdded.hbs', - StatusPageOwnerAnnouncementPosted = 'StatusPageOwnerAnnouncementPosted.hbs', - SimpleMessage = 'SimpleMessage.hbs', - VerificationCode = 'VerificationCode.hbs', - AcknowledgeIncident = 'AcknowledgeIncident.hbs', + ForgotPassword = "ForgotPassword.hbs", + ProbeOffline = "ProbeOffline.hbs", + SignupWelcomeEmail = "SignupWelcomeEmail.hbs", + EmailVerified = "EmailVerified.hbs", + PasswordChanged = "PasswordChanged.hbs", + InviteMember = "InviteMember.hbs", + EmailChanged = "EmailChanged.hbs", + SubscribedToStatusPage = "SubscribedToStatusPage.hbs", + SubscriberAnnouncementCreated = "SubscriberAnnouncementCreated.hbs", + SubscriberIncidentCreated = "SubscriberIncidentCreated.hbs", + SubscriberIncidentNoteCreated = "SubscriberIncidentNoteCreated.hbs", + SubscriberIncidentStateChanged = "SubscriberIncidentStateChanged.hbs", + SubscriberScheduledMaintenanceEventCreated = "SubscriberScheduledMaintenanceEventCreated.hbs", + SubscriberScheduledMaintenanceEventStateChanged = "SubscriberScheduledMaintenanceEventStateChanged.hbs", + StatusPageForgotPassword = "StatusPageForgotPassword.hbs", + StatusPagePasswordChanged = "StatusPagePasswordChanged.hbs", + StatusPageWelcomeEmail = "StatusPageWelcomeEmail.hbs", + SubscriberScheduledMaintenanceEventNoteCreated = "SubscriberScheduledMaintenanceEventNoteCreated.hbs", + SMTPTest = "SMTPTest.hbs", + MonitorOwnerAdded = "MonitorOwnerAdded.hbs", + MonitorOwnerResourceCreated = "MonitorOwnerResourceCreated.hbs", + MonitorOwnerStatusChanged = "MonitorOwnerStatusChanged.hbs", + IncidentOwnerAdded = "IncidentOwnerAdded.hbs", + IncidentOwnerStateChanged = "IncidentOwnerStateChanged.hbs", + IncidentOwnerNotePosted = "IncidentOwnerNotePosted.hbs", + IncidentOwnerResourceCreated = "IncidentOwnerResourceCreated.hbs", + ScheduledMaintenanceOwnerNotePosted = "ScheduledMaintenanceOwnerNotePosted.hbs", + ScheduledMaintenanceOwnerAdded = "ScheduledMaintenanceOwnerAdded.hbs", + ScheduledMaintenanceOwnerStateChanged = "ScheduledMaintenanceOwnerStateChanged.hbs", + ScheduledMaintenanceOwnerResourceCreated = "ScheduledMaintenanceOwnerResourceCreated.hbs", + StatusPageOwnerResourceCreated = "StatusPageOwnerResourceCreated.hbs", + StatusPageOwnerAdded = "StatusPageOwnerAdded.hbs", + StatusPageOwnerAnnouncementPosted = "StatusPageOwnerAnnouncementPosted.hbs", + SimpleMessage = "SimpleMessage.hbs", + VerificationCode = "VerificationCode.hbs", + AcknowledgeIncident = "AcknowledgeIncident.hbs", } export default EmailTemplateType; diff --git a/Common/Types/EmailWithName.ts b/Common/Types/EmailWithName.ts index 205162c639..30d494f21f 100644 --- a/Common/Types/EmailWithName.ts +++ b/Common/Types/EmailWithName.ts @@ -1,35 +1,35 @@ -import Email from './Email'; -import Typeof from './Typeof'; +import Email from "./Email"; +import Typeof from "./Typeof"; export default class EmailWithName { - private _email: Email = new Email('noreply@oneuptime.com'); - public get email(): Email { - return this._email; - } - public set email(v: Email | string) { - if (typeof v === Typeof.String) { - this._email = new Email(v.toString()); - } - - if (v instanceof Email) { - this._email = v; - } + private _email: Email = new Email("noreply@oneuptime.com"); + public get email(): Email { + return this._email; + } + public set email(v: Email | string) { + if (typeof v === Typeof.String) { + this._email = new Email(v.toString()); } - private _name: string = ''; - public get name(): string { - return this._name; - } - public set name(v: string) { - this._name = v; + if (v instanceof Email) { + this._email = v; } + } - public constructor(name: string, email: string | Email) { - this.email = email; - this.name = name; - } + private _name: string = ""; + public get name(): string { + return this._name; + } + public set name(v: string) { + this._name = v; + } - public toString(): string { - return `"${this.name}" <${this.email}>`; - } + public constructor(name: string, email: string | Email) { + this.email = email; + this.name = name; + } + + public toString(): string { + return `"${this.name}" <${this.email}>`; + } } diff --git a/Common/Types/EncryptionAlgorithm.ts b/Common/Types/EncryptionAlgorithm.ts index 5fe2e0796e..be2352c7a2 100644 --- a/Common/Types/EncryptionAlgorithm.ts +++ b/Common/Types/EncryptionAlgorithm.ts @@ -1,5 +1,5 @@ enum EncryptionAlgorithm { - SHA256 = 'SHA-256', + SHA256 = "SHA-256", } export default EncryptionAlgorithm; diff --git a/Common/Types/Events/EventInterval.ts b/Common/Types/Events/EventInterval.ts index 75cf363718..61b876cd12 100644 --- a/Common/Types/Events/EventInterval.ts +++ b/Common/Types/Events/EventInterval.ts @@ -1,9 +1,9 @@ enum EventInterval { - Hour = 'Hour', - Day = 'Day', - Week = 'Week', - Month = 'Month', - Year = 'Year', + Hour = "Hour", + Day = "Day", + Week = "Week", + Month = "Month", + Year = "Year", } export default EventInterval; diff --git a/Common/Types/Events/Recurring.ts b/Common/Types/Events/Recurring.ts index beac175d6b..2f44c79f5d 100644 --- a/Common/Types/Events/Recurring.ts +++ b/Common/Types/Events/Recurring.ts @@ -1,119 +1,117 @@ -import DatabaseProperty from '../Database/DatabaseProperty'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import JSONFunctions from '../JSONFunctions'; -import PositiveNumber from '../PositiveNumber'; -import EventInterval from './EventInterval'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "../Database/DatabaseProperty"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import JSONFunctions from "../JSONFunctions"; +import PositiveNumber from "../PositiveNumber"; +import EventInterval from "./EventInterval"; +import { FindOperator } from "typeorm"; export interface RecurringData extends JSONObject { - intervalType: EventInterval; - intervalCount: PositiveNumber; + intervalType: EventInterval; + intervalCount: PositiveNumber; } export default class Recurring extends DatabaseProperty { - public static getDefaultRotationData(): RecurringData { - return { - intervalType: EventInterval.Day, - intervalCount: new PositiveNumber(1), - }; + public static getDefaultRotationData(): RecurringData { + return { + intervalType: EventInterval.Day, + intervalCount: new PositiveNumber(1), + }; + } + + private data: RecurringData = Recurring.getDefaultRotationData(); + + public get intervalType(): EventInterval { + return this.data.intervalType; + } + public set intervalType(v: EventInterval) { + this.data.intervalType = v; + } + + // intervalCount + + public get intervalCount(): PositiveNumber { + return this.data.intervalCount; + } + + public set intervalCount(v: PositiveNumber) { + this.data.intervalCount = v; + } + + public constructor() { + super(); + + this.data = Recurring.getDefaultRotationData(); + } + + public static getDefault(): Recurring { + return new Recurring(); + } + + public override toJSON(): JSONObject { + return JSONFunctions.serialize({ + _type: ObjectType.Recurring, + value: { + intervalType: this.intervalType, + intervalCount: this.intervalCount.toJSON(), + }, + }); + } + + public static override fromJSON(json: JSONObject | Recurring): Recurring { + if (json instanceof Recurring) { + return json; } - private data: RecurringData = Recurring.getDefaultRotationData(); - - public get intervalType(): EventInterval { - return this.data.intervalType; - } - public set intervalType(v: EventInterval) { - this.data.intervalType = v; + if (!json || json["_type"] !== ObjectType.Recurring) { + throw new BadDataException("Invalid Rotation"); } - // intervalCount - - public get intervalCount(): PositiveNumber { - return this.data.intervalCount; + if (!json["value"]) { + throw new BadDataException("Invalid Rotation"); } - public set intervalCount(v: PositiveNumber) { - this.data.intervalCount = v; + json = json["value"] as JSONObject; + + let intervalType: EventInterval = EventInterval.Day; + + if (json && json["intervalType"]) { + intervalType = json["intervalType"] as EventInterval; } - public constructor() { - super(); + let intervalCount: PositiveNumber = new PositiveNumber(1); - this.data = Recurring.getDefaultRotationData(); + if (json && json["intervalCount"]) { + intervalCount = PositiveNumber.fromJSON(json["intervalCount"]); } - public static getDefault(): Recurring { - return new Recurring(); + const rotation: Recurring = new Recurring(); + + rotation.data = { + intervalType, + intervalCount, + }; + + return rotation; + } + + protected static override toDatabase( + value: Recurring | FindOperator<Recurring>, + ): JSONObject | null { + if (value && value instanceof Recurring) { + return (value as Recurring).toJSON(); + } else if (value) { + return JSONFunctions.serialize(value as any); } - public override toJSON(): JSONObject { - return JSONFunctions.serialize({ - _type: ObjectType.Recurring, - value: { - intervalType: this.intervalType, - intervalCount: this.intervalCount.toJSON(), - }, - }); + return null; + } + + protected static override fromDatabase(value: JSONObject): Recurring | null { + if (value) { + return Recurring.fromJSON(value); } - public static override fromJSON(json: JSONObject | Recurring): Recurring { - if (json instanceof Recurring) { - return json; - } - - if (!json || json['_type'] !== ObjectType.Recurring) { - throw new BadDataException('Invalid Rotation'); - } - - if (!json['value']) { - throw new BadDataException('Invalid Rotation'); - } - - json = json['value'] as JSONObject; - - let intervalType: EventInterval = EventInterval.Day; - - if (json && json['intervalType']) { - intervalType = json['intervalType'] as EventInterval; - } - - let intervalCount: PositiveNumber = new PositiveNumber(1); - - if (json && json['intervalCount']) { - intervalCount = PositiveNumber.fromJSON(json['intervalCount']); - } - - const rotation: Recurring = new Recurring(); - - rotation.data = { - intervalType, - intervalCount, - }; - - return rotation; - } - - protected static override toDatabase( - value: Recurring | FindOperator<Recurring> - ): JSONObject | null { - if (value && value instanceof Recurring) { - return (value as Recurring).toJSON(); - } else if (value) { - return JSONFunctions.serialize(value as any); - } - - return null; - } - - protected static override fromDatabase( - value: JSONObject - ): Recurring | null { - if (value) { - return Recurring.fromJSON(value); - } - - return null; - } + return null; + } } diff --git a/Common/Types/Exception/ApiException.ts b/Common/Types/Exception/ApiException.ts index 013da03758..8052933627 100644 --- a/Common/Types/Exception/ApiException.ts +++ b/Common/Types/Exception/ApiException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class APIException extends Exception { - public constructor(message: string) { - super(ExceptionCode.APIException, message); - } + public constructor(message: string) { + super(ExceptionCode.APIException, message); + } } diff --git a/Common/Types/Exception/BadDataException.ts b/Common/Types/Exception/BadDataException.ts index cea906b872..eca2d07e4e 100644 --- a/Common/Types/Exception/BadDataException.ts +++ b/Common/Types/Exception/BadDataException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class BadDataException extends Exception { - public constructor(message: string) { - super(ExceptionCode.BadDataException, message); - } + public constructor(message: string) { + super(ExceptionCode.BadDataException, message); + } } diff --git a/Common/Types/Exception/BadOperationException.ts b/Common/Types/Exception/BadOperationException.ts index 29614de92f..43682f7da2 100644 --- a/Common/Types/Exception/BadOperationException.ts +++ b/Common/Types/Exception/BadOperationException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class BadOperationException extends Exception { - public constructor(message: string) { - super(ExceptionCode.BadOperationException, message); - } + public constructor(message: string) { + super(ExceptionCode.BadOperationException, message); + } } diff --git a/Common/Types/Exception/BadRequestException.ts b/Common/Types/Exception/BadRequestException.ts index 8463628dde..82ee22ebcd 100644 --- a/Common/Types/Exception/BadRequestException.ts +++ b/Common/Types/Exception/BadRequestException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class BadRequestException extends Exception { - public constructor(message: string) { - super(ExceptionCode.BadDataException, message); - } + public constructor(message: string) { + super(ExceptionCode.BadDataException, message); + } } diff --git a/Common/Types/Exception/DatabaseNotConnectedException.ts b/Common/Types/Exception/DatabaseNotConnectedException.ts index 02191cb91c..6464137b92 100644 --- a/Common/Types/Exception/DatabaseNotConnectedException.ts +++ b/Common/Types/Exception/DatabaseNotConnectedException.ts @@ -1,11 +1,11 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class DatabaseNotConnectedException extends Exception { - public constructor(message?: string) { - super( - ExceptionCode.DatabaseNotConnectedException, - message || 'Database not connected' - ); - } + public constructor(message?: string) { + super( + ExceptionCode.DatabaseNotConnectedException, + message || "Database not connected", + ); + } } diff --git a/Common/Types/Exception/Exception.ts b/Common/Types/Exception/Exception.ts index 80657c7103..baffe728f8 100644 --- a/Common/Types/Exception/Exception.ts +++ b/Common/Types/Exception/Exception.ts @@ -1,22 +1,22 @@ -import ExceptionCode from './ExceptionCode'; +import ExceptionCode from "./ExceptionCode"; export default class Exception extends Error { - private _code: ExceptionCode = ExceptionCode.GeneralException; + private _code: ExceptionCode = ExceptionCode.GeneralException; - public get code(): ExceptionCode { - return this._code; - } + public get code(): ExceptionCode { + return this._code; + } - public set code(value: ExceptionCode) { - this._code = value; - } + public set code(value: ExceptionCode) { + this._code = value; + } - public constructor(code: ExceptionCode, message: string) { - super(message); - this.code = code; - } + public constructor(code: ExceptionCode, message: string) { + super(message); + this.code = code; + } - public getMessage(): string { - return this.message; - } + public getMessage(): string { + return this.message; + } } diff --git a/Common/Types/Exception/ExceptionCode.ts b/Common/Types/Exception/ExceptionCode.ts index 69e1b5d902..40e8c82b5e 100644 --- a/Common/Types/Exception/ExceptionCode.ts +++ b/Common/Types/Exception/ExceptionCode.ts @@ -1,21 +1,21 @@ enum ExceptionCode { - NotImplementedException = 0, - GeneralException = 1, - APIException = 2, - DatabaseNotConnectedException = 500, - BadOperationException = 5, - WebRequestException = 6, - BadDataException = 400, - BadRequestException = 400, - UnabletoReachServerException = 415, - ServerException = 500, - NotAuthorizedException = 403, - NotAuthenticatedException = 401, - PaymentRequiredException = 402, - NotFoundException = 404, - TimeoutException = 408, - TenantNotFoundException = 405, - SsoAuthorizationException = 406, + NotImplementedException = 0, + GeneralException = 1, + APIException = 2, + DatabaseNotConnectedException = 500, + BadOperationException = 5, + WebRequestException = 6, + BadDataException = 400, + BadRequestException = 400, + UnabletoReachServerException = 415, + ServerException = 500, + NotAuthorizedException = 403, + NotAuthenticatedException = 401, + PaymentRequiredException = 402, + NotFoundException = 404, + TimeoutException = 408, + TenantNotFoundException = 405, + SsoAuthorizationException = 406, } export default ExceptionCode; diff --git a/Common/Types/Exception/NotAuthenticatedException.ts b/Common/Types/Exception/NotAuthenticatedException.ts index 95c6b38b70..96a52564d0 100644 --- a/Common/Types/Exception/NotAuthenticatedException.ts +++ b/Common/Types/Exception/NotAuthenticatedException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class NotAuthenticatedException extends Exception { - public constructor(message: string) { - super(ExceptionCode.NotAuthenticatedException, message); - } + public constructor(message: string) { + super(ExceptionCode.NotAuthenticatedException, message); + } } diff --git a/Common/Types/Exception/NotAuthorizedException.ts b/Common/Types/Exception/NotAuthorizedException.ts index 13078f33db..d05422f1e5 100644 --- a/Common/Types/Exception/NotAuthorizedException.ts +++ b/Common/Types/Exception/NotAuthorizedException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class NotAuthorizedException extends Exception { - public constructor(message: string) { - super(ExceptionCode.NotAuthorizedException, message); - } + public constructor(message: string) { + super(ExceptionCode.NotAuthorizedException, message); + } } diff --git a/Common/Types/Exception/NotFoundException.ts b/Common/Types/Exception/NotFoundException.ts index 1359628030..566eccbcc9 100644 --- a/Common/Types/Exception/NotFoundException.ts +++ b/Common/Types/Exception/NotFoundException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class NotFoundException extends Exception { - public constructor(message: string) { - super(ExceptionCode.NotFoundException, message); - } + public constructor(message: string) { + super(ExceptionCode.NotFoundException, message); + } } diff --git a/Common/Types/Exception/NotImplementedException.ts b/Common/Types/Exception/NotImplementedException.ts index 0bb13269ad..c7d0d80790 100644 --- a/Common/Types/Exception/NotImplementedException.ts +++ b/Common/Types/Exception/NotImplementedException.ts @@ -1,11 +1,11 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class NotImplementedException extends Exception { - public constructor() { - super( - ExceptionCode.NotImplementedException, - 'This code is not implemented' - ); - } + public constructor() { + super( + ExceptionCode.NotImplementedException, + "This code is not implemented", + ); + } } diff --git a/Common/Types/Exception/PaymentRequiredException.ts b/Common/Types/Exception/PaymentRequiredException.ts index d57572a5e4..fcdbfc5f0b 100644 --- a/Common/Types/Exception/PaymentRequiredException.ts +++ b/Common/Types/Exception/PaymentRequiredException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class PaymentRequiredException extends Exception { - public constructor(message: string) { - super(ExceptionCode.PaymentRequiredException, message); - } + public constructor(message: string) { + super(ExceptionCode.PaymentRequiredException, message); + } } diff --git a/Common/Types/Exception/ServerException.ts b/Common/Types/Exception/ServerException.ts index 72a75390bc..45d97283ab 100644 --- a/Common/Types/Exception/ServerException.ts +++ b/Common/Types/Exception/ServerException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class ServerException extends Exception { - public constructor(message?: string) { - super(ExceptionCode.ServerException, message || 'Server Error'); - } + public constructor(message?: string) { + super(ExceptionCode.ServerException, message || "Server Error"); + } } diff --git a/Common/Types/Exception/SsoAuthorizationException.ts b/Common/Types/Exception/SsoAuthorizationException.ts index f1fa1abe14..4e40c7847b 100644 --- a/Common/Types/Exception/SsoAuthorizationException.ts +++ b/Common/Types/Exception/SsoAuthorizationException.ts @@ -1,17 +1,17 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class SSOAuthorizationException extends Exception { - private static message: string = 'SSO Authorization Required'; + private static message: string = "SSO Authorization Required"; - public constructor() { - super( - ExceptionCode.SsoAuthorizationException, - SSOAuthorizationException.message - ); - } + public constructor() { + super( + ExceptionCode.SsoAuthorizationException, + SSOAuthorizationException.message, + ); + } - public static isException(errorMessage: string): boolean { - return errorMessage === SSOAuthorizationException.message; - } + public static isException(errorMessage: string): boolean { + return errorMessage === SSOAuthorizationException.message; + } } diff --git a/Common/Types/Exception/TenantNotFoundException.ts b/Common/Types/Exception/TenantNotFoundException.ts index 5fb06b2450..a275309fc1 100644 --- a/Common/Types/Exception/TenantNotFoundException.ts +++ b/Common/Types/Exception/TenantNotFoundException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class TenantNotFoundException extends Exception { - public constructor(message: string) { - super(ExceptionCode.TenantNotFoundException, message); - } + public constructor(message: string) { + super(ExceptionCode.TenantNotFoundException, message); + } } diff --git a/Common/Types/Exception/TimeoutException.ts b/Common/Types/Exception/TimeoutException.ts index 7a3a65c01b..073c390673 100644 --- a/Common/Types/Exception/TimeoutException.ts +++ b/Common/Types/Exception/TimeoutException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class TimeoutException extends Exception { - public constructor(message: string) { - super(ExceptionCode.TimeoutException, message); - } + public constructor(message: string) { + super(ExceptionCode.TimeoutException, message); + } } diff --git a/Common/Types/Exception/UnableToReachServer.ts b/Common/Types/Exception/UnableToReachServer.ts index 08e2cb353f..180306c0f0 100644 --- a/Common/Types/Exception/UnableToReachServer.ts +++ b/Common/Types/Exception/UnableToReachServer.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class UnableToReachServer extends Exception { - public constructor(message: string) { - super(ExceptionCode.UnabletoReachServerException, message); - } + public constructor(message: string) { + super(ExceptionCode.UnabletoReachServerException, message); + } } diff --git a/Common/Types/Exception/WebsiteRequestException.ts b/Common/Types/Exception/WebsiteRequestException.ts index f1b7d57ae8..a244355180 100644 --- a/Common/Types/Exception/WebsiteRequestException.ts +++ b/Common/Types/Exception/WebsiteRequestException.ts @@ -1,8 +1,8 @@ -import Exception from './Exception'; -import ExceptionCode from './ExceptionCode'; +import Exception from "./Exception"; +import ExceptionCode from "./ExceptionCode"; export default class WebsiteRequestException extends Exception { - public constructor(message: string) { - super(ExceptionCode.WebRequestException, message); - } + public constructor(message: string) { + super(ExceptionCode.WebRequestException, message); + } } diff --git a/Common/Types/File.ts b/Common/Types/File.ts index 013e124c76..320efda478 100644 --- a/Common/Types/File.ts +++ b/Common/Types/File.ts @@ -1,4 +1,4 @@ export interface File { - name: string; - contentType: string; + name: string; + contentType: string; } diff --git a/Common/Types/File/MimeType.ts b/Common/Types/File/MimeType.ts index 63b3e75038..fc84f9fbed 100644 --- a/Common/Types/File/MimeType.ts +++ b/Common/Types/File/MimeType.ts @@ -1,12 +1,12 @@ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types enum MimeType { - aac = 'audio/aac', - png = 'image/png', - jpg = 'image/jpeg', - jpeg = 'image/jpeg', + aac = "audio/aac", + png = "image/png", + jpg = "image/jpeg", + jpeg = "image/jpeg", - // TODO add more mime types. + // TODO add more mime types. } export default MimeType; diff --git a/Common/Types/Filter/FilterCondition.ts b/Common/Types/Filter/FilterCondition.ts index 18324995e6..3287c07be4 100644 --- a/Common/Types/Filter/FilterCondition.ts +++ b/Common/Types/Filter/FilterCondition.ts @@ -1,6 +1,6 @@ enum FilterCondition { - And = 'And', - Or = 'Or', + And = "And", + Or = "Or", } export default FilterCondition; diff --git a/Common/Types/Filter/FilterType.ts b/Common/Types/Filter/FilterType.ts index 73c7647218..b134a39618 100644 --- a/Common/Types/Filter/FilterType.ts +++ b/Common/Types/Filter/FilterType.ts @@ -1,10 +1,10 @@ enum FilterType { - EqualTo = 'EqualTo', - NotEqualTo = 'NotEqualTo', - LessThan = 'LessThan', - GreaterThan = 'GreaterThan', - GreaterThanOrEqualTo = 'GreaterThanOrEqualTo', - LessThanOrEqualTo = 'LessThanOrEqualTo', + EqualTo = "EqualTo", + NotEqualTo = "NotEqualTo", + LessThan = "LessThan", + GreaterThan = "GreaterThan", + GreaterThanOrEqualTo = "GreaterThanOrEqualTo", + LessThanOrEqualTo = "LessThanOrEqualTo", } export default FilterType; diff --git a/Common/Types/HashCode.ts b/Common/Types/HashCode.ts index 9d06586aab..f8558f9813 100644 --- a/Common/Types/HashCode.ts +++ b/Common/Types/HashCode.ts @@ -1,11 +1,11 @@ export default class HashCode { - public static fromString(text: string): number { - let hash: number = 0; - for (let i: number = 0; i < text.length; i++) { - const code: number = text.charCodeAt(i); - hash = (hash << 5) - hash + code; - hash = hash & hash; // Convert to 32bit integer - } - return hash; + public static fromString(text: string): number { + let hash: number = 0; + for (let i: number = 0; i < text.length; i++) { + const code: number = text.charCodeAt(i); + hash = (hash << 5) - hash + code; + hash = hash & hash; // Convert to 32bit integer } + return hash; + } } diff --git a/Common/Types/HashedString.ts b/Common/Types/HashedString.ts index 4db27c536f..c11b3b2584 100644 --- a/Common/Types/HashedString.ts +++ b/Common/Types/HashedString.ts @@ -1,105 +1,103 @@ -import UUID from '../Utils/UUID'; -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import BadOperationException from './Exception/BadOperationException'; -import { JSONObject, ObjectType } from './JSON'; -import ObjectID from './ObjectID'; -import CryptoJS from 'crypto-js'; -import { FindOperator } from 'typeorm'; +import UUID from "../Utils/UUID"; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import BadOperationException from "./Exception/BadOperationException"; +import { JSONObject, ObjectType } from "./JSON"; +import ObjectID from "./ObjectID"; +import CryptoJS from "crypto-js"; +import { FindOperator } from "typeorm"; export default class HashedString extends DatabaseProperty { - private isHashed: boolean = false; + private isHashed: boolean = false; - private _value: string = ''; - public get value(): string { - return this._value; - } - public set value(v: string) { - this._value = v; + private _value: string = ""; + public get value(): string { + return this._value; + } + public set value(v: string) { + this._value = v; + } + + public constructor(value: string, isValueHashed: boolean = false) { + super(); + this.value = value; + this.isHashed = isValueHashed; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.HashedString, + value: (this as HashedString).toString(), + }; + } + + public static override fromJSON(json: JSONObject): HashedString { + if (json["_type"] === ObjectType.HashedString) { + return new HashedString((json["value"] as string) || ""); } - public constructor(value: string, isValueHashed: boolean = false) { - super(); - this.value = value; - this.isHashed = isValueHashed; + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + public override toString(): string { + return this.value; + } + + public static generate(): HashedString { + return new this(UUID.generate()); + } + + protected static override toDatabase( + value: HashedString | FindOperator<HashedString>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new HashedString(value); + } + + return value.toString(); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.HashedString, - value: (this as HashedString).toString(), - }; + return null; + } + + public isValueHashed(): boolean { + return this.isHashed; + } + + public async hashValue(encryptionSecret: ObjectID | null): Promise<string> { + if (!this.value) { + return ""; } - public static override fromJSON(json: JSONObject): HashedString { - if (json['_type'] === ObjectType.HashedString) { - return new HashedString((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); + if (this.isHashed) { + throw new BadOperationException("Value is already hashed"); } - public override toString(): string { - return this.value; + const valueToHash: string = (encryptionSecret || "") + this.value; + this.isHashed = true; + + this.value = CryptoJS.SHA256(valueToHash).toString(); + return this.value; + } + + public static async hashValue( + value: string, + encryptionSecret: ObjectID | null, + ): Promise<string> { + const hashstring: HashedString = new HashedString(value, false); + return await hashstring.hashValue(encryptionSecret); + } + + protected static override fromDatabase(_value: string): HashedString | null { + if (_value) { + return new HashedString(_value, true); } - public static generate(): HashedString { - return new this(UUID.generate()); - } + return null; + } - protected static override toDatabase( - value: HashedString | FindOperator<HashedString> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new HashedString(value); - } - - return value.toString(); - } - - return null; - } - - public isValueHashed(): boolean { - return this.isHashed; - } - - public async hashValue(encryptionSecret: ObjectID | null): Promise<string> { - if (!this.value) { - return ''; - } - - if (this.isHashed) { - throw new BadOperationException('Value is already hashed'); - } - - const valueToHash: string = (encryptionSecret || '') + this.value; - this.isHashed = true; - - this.value = CryptoJS.SHA256(valueToHash).toString(); - return this.value; - } - - public static async hashValue( - value: string, - encryptionSecret: ObjectID | null - ): Promise<string> { - const hashstring: HashedString = new HashedString(value, false); - return await hashstring.hashValue(encryptionSecret); - } - - protected static override fromDatabase( - _value: string - ): HashedString | null { - if (_value) { - return new HashedString(_value, true); - } - - return null; - } - - public static fromString(value: string): HashedString { - return new HashedString(value, false); - } + public static fromString(value: string): HashedString { + return new HashedString(value, false); + } } diff --git a/Common/Types/Html.ts b/Common/Types/Html.ts index 473e74c532..ab41fcfb50 100644 --- a/Common/Types/Html.ts +++ b/Common/Types/Html.ts @@ -1,17 +1,17 @@ export default class HTML { - private _html: string = ''; - public get html(): string { - return this._html; - } - public set html(v: string) { - this._html = v; - } + private _html: string = ""; + public get html(): string { + return this._html; + } + public set html(v: string) { + this._html = v; + } - public constructor(html: string) { - this.html = html; - } + public constructor(html: string) { + this.html = html; + } - public toString(): string { - return this.html; - } + public toString(): string { + return this.html; + } } diff --git a/Common/Types/IP/IP.ts b/Common/Types/IP/IP.ts index 0016692cb0..6f806a6182 100644 --- a/Common/Types/IP/IP.ts +++ b/Common/Types/IP/IP.ts @@ -1,114 +1,114 @@ -import DatabaseProperty from '../Database/DatabaseProperty'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import Typeof from '../Typeof'; -import IPType from './IPType'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "../Database/DatabaseProperty"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import Typeof from "../Typeof"; +import IPType from "./IPType"; +import { FindOperator } from "typeorm"; export default class IP extends DatabaseProperty { - private _ip: string = ''; - protected type: IPType = IPType.IPv4; - public get ip(): string { - return this._ip; + private _ip: string = ""; + protected type: IPType = IPType.IPv4; + public get ip(): string { + return this._ip; + } + public set ip(value: string) { + if (IP.isIPv4(value)) { + this._ip = value; + this.type = IPType.IPv4; + } else if (IP.isIPv6(value)) { + this._ip = value; + this.type = IPType.IPv6; + } else { + throw new BadDataException("IP is not a valid address"); } - public set ip(value: string) { - if (IP.isIPv4(value)) { - this._ip = value; - this.type = IPType.IPv4; - } else if (IP.isIPv6(value)) { - this._ip = value; - this.type = IPType.IPv6; - } else { - throw new BadDataException('IP is not a valid address'); - } + } + + public constructor(ip: string) { + super(); + + this.ip = ip; + } + + public static fromString(ip: string): IP { + return new IP(ip); + } + + public static isIP(ip: string): boolean { + if (IP.isIPv4(ip) || IP.isIPv6(ip)) { + return true; + } + return false; + } + + public override toString(): string { + return this.ip; + } + + private static isIPv4(str: string): boolean { + const regexExp: RegExp = + /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gi; + const result: boolean = regexExp.test(str); + + return result; + } + + private static isIPv6(str: string): boolean { + const regexExp: RegExp = + /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/gi; + return regexExp.test(str); + } + + public isIPv4(): boolean { + if (IP.isIPv4(this.ip)) { + return true; + } + return false; + } + + public isIPv6(): boolean { + if (IP.isIPv6(this.ip)) { + return true; + } + return false; + } + + public static override fromJSON(json: JSONObject): IP { + if (json && json["_type"] !== ObjectType.IP) { + throw new BadDataException("Invalid JSON for IP"); } - public constructor(ip: string) { - super(); - - this.ip = ip; + if (json && json["value"] && typeof json["value"] !== Typeof.String) { + throw new BadDataException("Invalid JSON for IP"); } - public static fromString(ip: string): IP { - return new IP(ip); + return new IP(json["value"] as string); + } + + public override toJSON(): JSONObject { + return { + value: this.toString(), + _type: ObjectType.IP, + }; + } + + public static override toDatabase( + value: IP | FindOperator<IP>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new IP(value); + } + + return value.toString(); } - public static isIP(ip: string): boolean { - if (IP.isIPv4(ip) || IP.isIPv6(ip)) { - return true; - } - return false; - } - - public override toString(): string { - return this.ip; - } - - private static isIPv4(str: string): boolean { - const regexExp: RegExp = - /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gi; - const result: boolean = regexExp.test(str); - - return result; - } - - private static isIPv6(str: string): boolean { - const regexExp: RegExp = - /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/gi; - return regexExp.test(str); - } - - public isIPv4(): boolean { - if (IP.isIPv4(this.ip)) { - return true; - } - return false; - } - - public isIPv6(): boolean { - if (IP.isIPv6(this.ip)) { - return true; - } - return false; - } - - public static override fromJSON(json: JSONObject): IP { - if (json && json['_type'] !== ObjectType.IP) { - throw new BadDataException('Invalid JSON for IP'); - } - - if (json && json['value'] && typeof json['value'] !== Typeof.String) { - throw new BadDataException('Invalid JSON for IP'); - } - - return new IP(json['value'] as string); - } - - public override toJSON(): JSONObject { - return { - value: this.toString(), - _type: ObjectType.IP, - }; - } - - public static override toDatabase( - value: IP | FindOperator<IP> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new IP(value); - } - - return value.toString(); - } - - return null; - } - - public static override fromDatabase(value: string): IP | null { - if (value) { - return new IP(value); - } - return null; + return null; + } + + public static override fromDatabase(value: string): IP | null { + if (value) { + return new IP(value); } + return null; + } } diff --git a/Common/Types/IP/IPType.ts b/Common/Types/IP/IPType.ts index 20b979d2d5..39db4cee01 100644 --- a/Common/Types/IP/IPType.ts +++ b/Common/Types/IP/IPType.ts @@ -1,6 +1,6 @@ enum IPType { - IPv4 = 'IPv4', - IPv6 = 'IPv6', + IPv4 = "IPv4", + IPv6 = "IPv6", } export default IPType; diff --git a/Common/Types/IP/IPv4.ts b/Common/Types/IP/IPv4.ts index ef3ccb0b06..2715df50ef 100644 --- a/Common/Types/IP/IPv4.ts +++ b/Common/Types/IP/IPv4.ts @@ -1,12 +1,12 @@ -import BadDataException from '../Exception/BadDataException'; -import IP from './IP'; +import BadDataException from "../Exception/BadDataException"; +import IP from "./IP"; export default class IPv4 extends IP { - public constructor(ip: string) { - super(ip); + public constructor(ip: string) { + super(ip); - if (!this.isIPv4()) { - throw new BadDataException('IP is not a valid IPv4 address'); - } + if (!this.isIPv4()) { + throw new BadDataException("IP is not a valid IPv4 address"); } + } } diff --git a/Common/Types/IP/IPv6.ts b/Common/Types/IP/IPv6.ts index f05acf29ce..239a7cf7b5 100644 --- a/Common/Types/IP/IPv6.ts +++ b/Common/Types/IP/IPv6.ts @@ -1,12 +1,12 @@ -import BadDataException from '../Exception/BadDataException'; -import IP from './IP'; +import BadDataException from "../Exception/BadDataException"; +import IP from "./IP"; export default class IPv4 extends IP { - public constructor(ip: string) { - super(ip); + public constructor(ip: string) { + super(ip); - if (!this.isIPv6()) { - throw new BadDataException('IP is not a valid IPv6 address'); - } + if (!this.isIPv6()) { + throw new BadDataException("IP is not a valid IPv6 address"); } + } } diff --git a/Common/Types/Icon/IconProp.ts b/Common/Types/Icon/IconProp.ts index 40eac3951a..46f0f73601 100644 --- a/Common/Types/Icon/IconProp.ts +++ b/Common/Types/Icon/IconProp.ts @@ -1,126 +1,126 @@ enum IconProp { - Equals = 'Equals', - File = 'File', - Automation = 'Automation', - Workflow = 'Workflow', - TableCells = 'TableCells', - Layout = 'Layout', - Compass = 'Compass', - User = 'User', - Disc = 'Disc', - Settings = 'Settings', - Criteria = 'Criteria', - Notification = 'Notification', - CursorArrowRays = 'CursorArrowRays', - ArrowUpDown = 'ArrowUpDown', - Cube = 'Cube', - Swatch = 'Swatch', - Squares = 'Squares', - RectangleStack = 'RectangleStack', - ChartBar = 'ChartBar', - CircleClose = 'CircleClose', - SquareStack = 'SquareStack', - Calendar = 'Calendar', - Help = 'Help', - Reload = 'Reload', - Book = 'Book', - JSON = 'JSON', - Signal = 'Signal', - Stop = 'Stop', - Database = 'Database', - ChevronDown = 'ChevronDown', - Pencil = 'Pencil', - Flag = 'Flag', - Copy = 'Copy', - ChevronRight = 'ChevronRight', - ChevronUp = 'ChevronUp', - Play = 'Play', - ChevronLeft = 'ChevronLeft', - UpDownArrow = 'UpDownArrow', - Public = 'Public', - Home = 'Home', - Graph = 'Graph', - Variable = 'Variable', - ListBullet = 'ListBullet', - Image = 'Image', - Grid = 'Grid', - More = 'More', - Condition = 'Condition', - Activity = 'Activity', - Alert = 'Alert', - Call = 'Call', - List = 'List', - CheckCircle = 'CheckCircle', - Search = 'Search', - TextFile = 'TextFile', - Globe = 'Globe', - Logout = 'Logout', - Billing = 'Billing', - Email = 'Email', - Slack = 'Slack', - Time = 'Time', - Terminal = 'Terminal', - Drag = 'Drag', - Error = 'Error', - Code = 'Code', - Report = 'Report', - Team = 'Team', - Lock = 'Lock', - Key = 'Key', - Folder = 'Folder', - Integrations = 'Integrations', - SMS = 'SMS', - Info = 'Info', - Success = 'Success', - Trash = 'Trash', - Close = 'Close', - RSS = 'RSS', - Add = 'Add', - Label = 'Label', - Refresh = 'Refresh', - Filter = 'Filter', - Edit = 'Edit', - Hide = 'Hide', - Check = 'Check', - True = 'True', - False = 'False', - Text = 'Text', - Circle = 'Circle', - Webhook = 'Webhook', - SendMessage = 'SendMessage', - ExternalLink = 'ExternalLink', - Link = 'Link', - Layers = 'Layers', - Clock = 'Clock', - Invoice = 'Invoice', - Upgrade = 'Upgrade', - Star = 'Star', - Download = 'Download', - ErrorSolid = 'ErrorSolid', - Spinner = 'Spinner', - AltGlobe = 'AltGlobe', - ArrowCircleRight = 'ArrowCircleRight', - ArrowCircleUp = 'ArrowCircleUp', - ArrowCircleDown = 'ArrowCircleDown', - ArrowCircleLeft = 'ArrowCircleLeft', - Announcement = 'Announcement', - Window = 'Window', - AddFolder = 'AddFolder', - AddImage = 'AddImage', - Chat = 'Chat', - Wrench = 'Wrench', - TransparentCube = 'TransparentCube', - Logs = 'Logs', - Bolt = 'Bolt', - BarsArrowUp = 'BarsArrowUp', - BarsArrowDown = 'BarsArrowDown', - Bell = 'Bell', - BellRinging = 'BellRinging', - AdjustmentVertical = 'AdjustmentVertical', - AdjustmentHorizontal = 'AdjustmentHorizontal', - Minus = 'Minus', - MinusSmall = 'MinusSmall', - Template = 'Template', + Equals = "Equals", + File = "File", + Automation = "Automation", + Workflow = "Workflow", + TableCells = "TableCells", + Layout = "Layout", + Compass = "Compass", + User = "User", + Disc = "Disc", + Settings = "Settings", + Criteria = "Criteria", + Notification = "Notification", + CursorArrowRays = "CursorArrowRays", + ArrowUpDown = "ArrowUpDown", + Cube = "Cube", + Swatch = "Swatch", + Squares = "Squares", + RectangleStack = "RectangleStack", + ChartBar = "ChartBar", + CircleClose = "CircleClose", + SquareStack = "SquareStack", + Calendar = "Calendar", + Help = "Help", + Reload = "Reload", + Book = "Book", + JSON = "JSON", + Signal = "Signal", + Stop = "Stop", + Database = "Database", + ChevronDown = "ChevronDown", + Pencil = "Pencil", + Flag = "Flag", + Copy = "Copy", + ChevronRight = "ChevronRight", + ChevronUp = "ChevronUp", + Play = "Play", + ChevronLeft = "ChevronLeft", + UpDownArrow = "UpDownArrow", + Public = "Public", + Home = "Home", + Graph = "Graph", + Variable = "Variable", + ListBullet = "ListBullet", + Image = "Image", + Grid = "Grid", + More = "More", + Condition = "Condition", + Activity = "Activity", + Alert = "Alert", + Call = "Call", + List = "List", + CheckCircle = "CheckCircle", + Search = "Search", + TextFile = "TextFile", + Globe = "Globe", + Logout = "Logout", + Billing = "Billing", + Email = "Email", + Slack = "Slack", + Time = "Time", + Terminal = "Terminal", + Drag = "Drag", + Error = "Error", + Code = "Code", + Report = "Report", + Team = "Team", + Lock = "Lock", + Key = "Key", + Folder = "Folder", + Integrations = "Integrations", + SMS = "SMS", + Info = "Info", + Success = "Success", + Trash = "Trash", + Close = "Close", + RSS = "RSS", + Add = "Add", + Label = "Label", + Refresh = "Refresh", + Filter = "Filter", + Edit = "Edit", + Hide = "Hide", + Check = "Check", + True = "True", + False = "False", + Text = "Text", + Circle = "Circle", + Webhook = "Webhook", + SendMessage = "SendMessage", + ExternalLink = "ExternalLink", + Link = "Link", + Layers = "Layers", + Clock = "Clock", + Invoice = "Invoice", + Upgrade = "Upgrade", + Star = "Star", + Download = "Download", + ErrorSolid = "ErrorSolid", + Spinner = "Spinner", + AltGlobe = "AltGlobe", + ArrowCircleRight = "ArrowCircleRight", + ArrowCircleUp = "ArrowCircleUp", + ArrowCircleDown = "ArrowCircleDown", + ArrowCircleLeft = "ArrowCircleLeft", + Announcement = "Announcement", + Window = "Window", + AddFolder = "AddFolder", + AddImage = "AddImage", + Chat = "Chat", + Wrench = "Wrench", + TransparentCube = "TransparentCube", + Logs = "Logs", + Bolt = "Bolt", + BarsArrowUp = "BarsArrowUp", + BarsArrowDown = "BarsArrowDown", + Bell = "Bell", + BellRinging = "BellRinging", + AdjustmentVertical = "AdjustmentVertical", + AdjustmentHorizontal = "AdjustmentHorizontal", + Minus = "Minus", + MinusSmall = "MinusSmall", + Template = "Template", } export default IconProp; diff --git a/Common/Types/Infrastructure/BasicMetrics.ts b/Common/Types/Infrastructure/BasicMetrics.ts index b3f62b04ae..ee6b1e89a9 100644 --- a/Common/Types/Infrastructure/BasicMetrics.ts +++ b/Common/Types/Infrastructure/BasicMetrics.ts @@ -1,26 +1,26 @@ export interface MemoryMetrics { - total: number; - free: number; - used: number; - percentUsed: number; - percentFree: number; + total: number; + free: number; + used: number; + percentUsed: number; + percentFree: number; } export interface CPUMetrics { - percentUsed: number; + percentUsed: number; } export interface BasicDiskMetrics { - total: number; - free: number; - used: number; - diskPath: string; - percentUsed: number; - percentFree: number; + total: number; + free: number; + used: number; + diskPath: string; + percentUsed: number; + percentFree: number; } export default interface BasicInfrastructureMetrics { - cpuMetrics: CPUMetrics; - memoryMetrics: MemoryMetrics; - diskMetrics: Array<BasicDiskMetrics>; + cpuMetrics: CPUMetrics; + memoryMetrics: MemoryMetrics; + diskMetrics: Array<BasicDiskMetrics>; } diff --git a/Common/Types/Infrastructure/OSType.ts b/Common/Types/Infrastructure/OSType.ts index 3140ed369a..1759ada8bb 100644 --- a/Common/Types/Infrastructure/OSType.ts +++ b/Common/Types/Infrastructure/OSType.ts @@ -1,8 +1,8 @@ enum OSType { - Windows = 'Windows', - Linux = 'Linux', - MacOS = 'MacOS', - Unknown = 'Unknown', + Windows = "Windows", + Linux = "Linux", + MacOS = "MacOS", + Unknown = "Unknown", } export default OSType; diff --git a/Common/Types/IsolatedVM/ReturnResult.ts b/Common/Types/IsolatedVM/ReturnResult.ts index ae03890ceb..ac811bf263 100644 --- a/Common/Types/IsolatedVM/ReturnResult.ts +++ b/Common/Types/IsolatedVM/ReturnResult.ts @@ -1,4 +1,4 @@ export default interface ReturnResult { - returnValue: any; - logMessages: string[]; + returnValue: any; + logMessages: string[]; } diff --git a/Common/Types/JSON.ts b/Common/Types/JSON.ts index 8fd0131973..d0ae66d54b 100644 --- a/Common/Types/JSON.ts +++ b/Common/Types/JSON.ts @@ -1,149 +1,149 @@ -import Hostname from './API/Hostname'; -import Route from './API/Route'; -import URL from './API/URL'; -import EqualToOrNull from './BaseDatabase/EqualToOrNull'; -import GreaterThan from './BaseDatabase/GreaterThan'; -import GreaterThanOrEqual from './BaseDatabase/GreaterThanOrEqual'; -import InBetween from './BaseDatabase/InBetween'; -import Includes from './BaseDatabase/Includes'; -import LessThan from './BaseDatabase/LessThan'; -import LessThanOrEqual from './BaseDatabase/LessThanOrEqual'; -import NotEqual from './BaseDatabase/NotEqual'; -import NotNull from './BaseDatabase/NotNull'; -import Search from './BaseDatabase/Search'; -import CallRequest from './Call/CallRequest'; -import Color from './Color'; -import Domain from './Domain'; -import Email from './Email'; -import HashedString from './HashedString'; -import { CheckOn, FilterType } from './Monitor/CriteriaFilter'; -import Name from './Name'; -import ObjectID from './ObjectID'; -import Permission from './Permission'; -import Phone from './Phone'; -import Port from './Port'; -import PositiveNumber from './PositiveNumber'; -import StartAndEndTime from './Time/StartAndEndTime'; -import Version from './Version'; -import { BaseEntity } from 'typeorm'; +import Hostname from "./API/Hostname"; +import Route from "./API/Route"; +import URL from "./API/URL"; +import EqualToOrNull from "./BaseDatabase/EqualToOrNull"; +import GreaterThan from "./BaseDatabase/GreaterThan"; +import GreaterThanOrEqual from "./BaseDatabase/GreaterThanOrEqual"; +import InBetween from "./BaseDatabase/InBetween"; +import Includes from "./BaseDatabase/Includes"; +import LessThan from "./BaseDatabase/LessThan"; +import LessThanOrEqual from "./BaseDatabase/LessThanOrEqual"; +import NotEqual from "./BaseDatabase/NotEqual"; +import NotNull from "./BaseDatabase/NotNull"; +import Search from "./BaseDatabase/Search"; +import CallRequest from "./Call/CallRequest"; +import Color from "./Color"; +import Domain from "./Domain"; +import Email from "./Email"; +import HashedString from "./HashedString"; +import { CheckOn, FilterType } from "./Monitor/CriteriaFilter"; +import Name from "./Name"; +import ObjectID from "./ObjectID"; +import Permission from "./Permission"; +import Phone from "./Phone"; +import Port from "./Port"; +import PositiveNumber from "./PositiveNumber"; +import StartAndEndTime from "./Time/StartAndEndTime"; +import Version from "./Version"; +import { BaseEntity } from "typeorm"; export enum ObjectType { - ObjectID = 'ObjectID', - Decimal = 'Decimal', - Name = 'Name', - EqualToOrNull = 'EqualToOrNull', - MonitorSteps = 'MonitorSteps', - MonitorStep = 'MonitorStep', - Recurring = 'Recurring', - RestrictionTimes = 'RestrictionTimes', - MonitorCriteria = 'MonitorCriteria', - PositiveNumber = 'PositiveNumber', - MonitorCriteriaInstance = 'MonitorCriteriaInstance', - NotEqual = 'NotEqual', - Email = 'Email', - Phone = 'Phone', - Color = 'Color', - Domain = 'Domain', - Version = 'Version', - IP = 'IP', - Route = 'Route', - URL = 'URL', - Permission = 'Permission', - Search = 'Search', - GreaterThan = 'GreaterThan', - GreaterThanOrEqual = 'GreaterThanOrEqual', - LessThan = 'LessThan', - LessThanOrEqual = 'LessThanOrEqual', - Port = 'Port', - Hostname = 'Hostname', - HashedString = 'HashedString', - DateTime = 'DateTime', - Buffer = 'Buffer', - InBetween = 'InBetween', - NotNull = 'NotNull', - IsNull = 'IsNull', - Includes = 'Includes', + ObjectID = "ObjectID", + Decimal = "Decimal", + Name = "Name", + EqualToOrNull = "EqualToOrNull", + MonitorSteps = "MonitorSteps", + MonitorStep = "MonitorStep", + Recurring = "Recurring", + RestrictionTimes = "RestrictionTimes", + MonitorCriteria = "MonitorCriteria", + PositiveNumber = "PositiveNumber", + MonitorCriteriaInstance = "MonitorCriteriaInstance", + NotEqual = "NotEqual", + Email = "Email", + Phone = "Phone", + Color = "Color", + Domain = "Domain", + Version = "Version", + IP = "IP", + Route = "Route", + URL = "URL", + Permission = "Permission", + Search = "Search", + GreaterThan = "GreaterThan", + GreaterThanOrEqual = "GreaterThanOrEqual", + LessThan = "LessThan", + LessThanOrEqual = "LessThanOrEqual", + Port = "Port", + Hostname = "Hostname", + HashedString = "HashedString", + DateTime = "DateTime", + Buffer = "Buffer", + InBetween = "InBetween", + NotNull = "NotNull", + IsNull = "IsNull", + Includes = "Includes", } export type JSONValue = - | Array<string> - | string - | Array<number> - | number - | Array<boolean> - | boolean - | JSONObject - | Uint8Array - | JSONArray - | Date - | Array<Date> - | ObjectID - | Array<ObjectID> - | BaseEntity - | Array<BaseEntity> - | Name - | Array<Name> - | Email - | Array<Email> - | Color - | Array<Color> - | Phone - | Array<Phone> - | Route - | Array<Route> - | URL - | Array<URL> - | Array<Version> - | Version - | Buffer - | Permission - | Array<Permission> - | CheckOn - | Array<CheckOn> - | FilterType - | Array<FilterType> - | Search - | Domain - | Array<Domain> - | Array<Search> - | EqualToOrNull - | Array<EqualToOrNull> - | NotEqual - | Array<NotEqual> - | GreaterThan - | Array<GreaterThan> - | GreaterThanOrEqual - | Array<GreaterThanOrEqual> - | PositiveNumber - | Array<PositiveNumber> - | LessThan - | Array<LessThan> - | InBetween - | Array<InBetween> - | NotNull - | Array<NotNull> - | LessThanOrEqual - | Array<LessThanOrEqual> - | Port - | Array<Port> - | HashedString - | Array<HashedString> - | Hostname - | Array<Hostname> - | Array<JSONValue> - | Array<Permission> - | Array<JSONValue> - | Array<ObjectID> - | CallRequest - | undefined - | null - | StartAndEndTime - | Array<StartAndEndTime> - | Includes - | Array<Includes>; + | Array<string> + | string + | Array<number> + | number + | Array<boolean> + | boolean + | JSONObject + | Uint8Array + | JSONArray + | Date + | Array<Date> + | ObjectID + | Array<ObjectID> + | BaseEntity + | Array<BaseEntity> + | Name + | Array<Name> + | Email + | Array<Email> + | Color + | Array<Color> + | Phone + | Array<Phone> + | Route + | Array<Route> + | URL + | Array<URL> + | Array<Version> + | Version + | Buffer + | Permission + | Array<Permission> + | CheckOn + | Array<CheckOn> + | FilterType + | Array<FilterType> + | Search + | Domain + | Array<Domain> + | Array<Search> + | EqualToOrNull + | Array<EqualToOrNull> + | NotEqual + | Array<NotEqual> + | GreaterThan + | Array<GreaterThan> + | GreaterThanOrEqual + | Array<GreaterThanOrEqual> + | PositiveNumber + | Array<PositiveNumber> + | LessThan + | Array<LessThan> + | InBetween + | Array<InBetween> + | NotNull + | Array<NotNull> + | LessThanOrEqual + | Array<LessThanOrEqual> + | Port + | Array<Port> + | HashedString + | Array<HashedString> + | Hostname + | Array<Hostname> + | Array<JSONValue> + | Array<Permission> + | Array<JSONValue> + | Array<ObjectID> + | CallRequest + | undefined + | null + | StartAndEndTime + | Array<StartAndEndTime> + | Includes + | Array<Includes>; export interface JSONObject { - [x: string]: JSONValue; + [x: string]: JSONValue; } export type JSONArray = Array<JSONObject>; diff --git a/Common/Types/JSONFunctions.ts b/Common/Types/JSONFunctions.ts index 49e1347129..38fc039746 100644 --- a/Common/Types/JSONFunctions.ts +++ b/Common/Types/JSONFunctions.ts @@ -1,399 +1,389 @@ -import BaseModel from '../Models/BaseModel'; -import DatabaseProperty from './Database/DatabaseProperty'; -import OneUptimeDate from './Date'; -import { JSONArray, JSONObject, JSONValue, ObjectType } from './JSON'; -import SerializableObject from './SerializableObject'; -import SerializableObjectDictionary from './SerializableObjectDictionary'; -import Typeof from './Typeof'; -import JSON5 from 'json5'; +import BaseModel from "../Models/BaseModel"; +import DatabaseProperty from "./Database/DatabaseProperty"; +import OneUptimeDate from "./Date"; +import { JSONArray, JSONObject, JSONValue, ObjectType } from "./JSON"; +import SerializableObject from "./SerializableObject"; +import SerializableObjectDictionary from "./SerializableObjectDictionary"; +import Typeof from "./Typeof"; +import JSON5 from "json5"; export default class JSONFunctions { - public static nestJson(obj: JSONObject): JSONObject { - // obj could be in this format: + public static nestJson(obj: JSONObject): JSONObject { + // obj could be in this format: - /** + /** * { "http.url.protocol": "http", "http.url.hostname": "localhost", "http.host": "localhost", */ - // we want to convert it to this format: + // we want to convert it to this format: - /** - * { - * - * "http": { - * "url": { - * "protocol": "http", - * "hostname": "localhost" - * }, - * "host": "localhost", - * "method": "POST", - * "scheme": "http", - * "client_ip": " - * ... - * - * }, - */ + /** + * { + * + * "http": { + * "url": { + * "protocol": "http", + * "hostname": "localhost" + * }, + * "host": "localhost", + * "method": "POST", + * "scheme": "http", + * "client_ip": " + * ... + * + * }, + */ - const result: JSONObject = {}; + const result: JSONObject = {}; - for (const key in obj) { - const keys: Array<string> = key.split('.'); + for (const key in obj) { + const keys: Array<string> = key.split("."); - let currentObj: JSONObject = result; + let currentObj: JSONObject = result; - for (let i: number = 0; i < keys.length; i++) { - const k: string | undefined = keys[i]; + for (let i: number = 0; i < keys.length; i++) { + const k: string | undefined = keys[i]; - if (!k) { - continue; - } - - if (i === keys.length - 1) { - currentObj[k] = obj[key]; - } else { - if (!currentObj[k]) { - currentObj[k] = {}; - } - - currentObj = currentObj[k] as JSONObject; - } - } + if (!k) { + continue; } - return result; + if (i === keys.length - 1) { + currentObj[k] = obj[key]; + } else { + if (!currentObj[k]) { + currentObj[k] = {}; + } + + currentObj = currentObj[k] as JSONObject; + } + } } - public static isEmptyObject( - obj: JSONObject | BaseModel | null | undefined - ): boolean { - if (!obj) { - return true; + return result; + } + + public static isEmptyObject( + obj: JSONObject | BaseModel | null | undefined, + ): boolean { + if (!obj) { + return true; + } + + return Object.keys(obj).length === 0; + } + + public static removeCircularReferences(obj: JSONObject): JSONObject { + const cache: any[] = []; + const returnValue: string = JSON.stringify( + obj, + (_key: string, value: any) => { + if (typeof value === "object" && value !== null) { + if (cache.includes(value)) { + return; + } + + cache.push(value); } - return Object.keys(obj).length === 0; + return value; + }, + ); + + return JSON.parse(returnValue); + } + + public static isEqualObject( + obj1: JSONObject | undefined, + obj2: JSONObject | undefined, + ): boolean { + // check if all the keys are the same + + if (!obj1 && !obj2) { + return true; } - public static removeCircularReferences(obj: JSONObject): JSONObject { - const cache: any[] = []; - const returnValue: string = JSON.stringify( - obj, - (_key: string, value: any) => { - if (typeof value === 'object' && value !== null) { - if (cache.includes(value)) { - return; - } - - cache.push(value); - } - - return value; - } - ); - - return JSON.parse(returnValue); + if (!obj1 || !obj2) { + return false; } - public static isEqualObject( - obj1: JSONObject | undefined, - obj2: JSONObject | undefined - ): boolean { - // check if all the keys are the same + const keys1: Array<string> = Object.keys(obj1); + const keys2: Array<string> = Object.keys(obj2); - if (!obj1 && !obj2) { - return true; + if (keys1.length !== keys2.length) { + return false; + } + + for (const key of keys1) { + if (!keys2.includes(key)) { + return false; + } + } + + // check if all the values are the same + + for (const key of keys1) { + if (obj1[key] !== obj2[key]) { + return false; + } + } + + return true; + } + + public static toCompressedString(val: JSONValue): string { + return JSON.stringify(val, null, 2); + } + + public static toString(val: JSONValue): string { + if (typeof val === Typeof.String) { + return val as string; + } + + return JSON.stringify(val); + } + + public static getJSONValueInPath( + obj: JSONObject, + path: string, + ): JSONValue | null { + const paths: Array<string> = path.split("."); + let returnValue: JSONObject = obj as JSONObject; + for (const p of paths) { + if (!p) { + continue; + } + + if (returnValue && returnValue[p as string]!) { + returnValue = returnValue[p] as JSONObject; + } else { + return null; + } + } + + return returnValue as JSONValue; + } + + // this function serializes JSON with Common Objects to JSON that can be stringified. + public static serialize(val: JSONObject): JSONObject { + const newVal: JSONObject = {}; + + for (const key in val) { + if (val[key] === undefined) { + continue; + } + + if (val[key] === null) { + newVal[key] = val[key]; + } + + if (Array.isArray(val[key])) { + const arraySerialize: Array<JSONValue> = []; + for (const arrVal of val[key] as Array<JSONValue>) { + arraySerialize.push(this.serializeValue(arrVal)); } - if (!obj1 || !obj2) { - return false; + newVal[key] = arraySerialize; + } else { + newVal[key] = this.serializeValue(val[key] as JSONValue); + } + } + + return newVal; + } + + public static serializeValue(val: JSONValue): JSONValue { + if (val === null || val === undefined) { + return val; + } else if (typeof val === Typeof.String && val.toString().trim() === "") { + return val; + } else if (val instanceof BaseModel) { + return BaseModel.toJSON(val, BaseModel); + } else if (typeof val === Typeof.Number) { + return val; + } else if (ArrayBuffer.isView(val)) { + return { + _type: ObjectType.Buffer, + value: val as Uint8Array, + }; + } else if (val && val instanceof SerializableObject) { + return val.toJSON(); + } else if (val && val instanceof Date) { + return { + _type: ObjectType.DateTime, + value: OneUptimeDate.toString(val as Date).toString(), + }; + } else if ( + typeof val === Typeof.Object && + (val as JSONObject)["_type"] && + Object.keys(ObjectType).includes((val as JSONObject)["_type"] as string) + ) { + return val; + } else if (typeof val === Typeof.Object) { + return this.serialize(val as JSONObject); + } + + return val; + } + + public static deserializeValue(val: JSONValue): JSONValue { + if (val === null || val === undefined) { + return val; + } else if (typeof val === Typeof.String && val.toString().trim() === "") { + return val; + } else if ( + val && + typeof val === Typeof.Object && + (val as JSONObject)["_type"] && + (val as JSONObject)["value"] && + ((val as JSONObject)["value"] as JSONObject)["data"] && + ((val as JSONObject)["value"] as JSONObject)["type"] && + ((val as JSONObject)["value"] as JSONObject)["type"] === + ObjectType.Buffer && + ((val as JSONObject)["_type"] as string) === ObjectType.Buffer + ) { + return Buffer.from( + ((val as JSONObject)["value"] as JSONObject)["data"] as Uint8Array, + ); + } else if (val && ArrayBuffer.isView(val)) { + return Buffer.from(val as Uint8Array); + } else if (typeof val === Typeof.Number) { + return val; + } else if (val instanceof DatabaseProperty) { + return val; + } else if ( + val && + typeof val === Typeof.Object && + (val as JSONObject)["_type"] && + SerializableObjectDictionary[(val as JSONObject)["_type"] as string] + ) { + return SerializableObjectDictionary[ + (val as JSONObject)["_type"] as string + ].fromJSON(val); + } else if (val instanceof Date) { + return val; + } else if (typeof val === Typeof.Object) { + return this.deserialize(val as JSONObject); + } else if (Array.isArray(val)) { + const arr: Array<JSONValue> = []; + + for (const v of val) { + arr.push(this.deserializeValue(v)); + } + + return arr; + } + + return val; + } + + public static deserializeArray(array: JSONArray): JSONArray { + const returnArr: JSONArray = []; + + for (const obj of array) { + returnArr.push(this.deserialize(obj)); + } + + return returnArr; + } + + public static serializeArray(array: JSONArray): JSONArray { + const returnArr: JSONArray = []; + + for (const obj of array) { + returnArr.push(this.serialize(obj)); + } + + return returnArr; + } + + public static parse(val: string): JSONObject | JSONArray { + return JSON5.parse(val); + } + + public static parseJSONObject(val: string): JSONObject { + const result: JSONObject | JSONArray = this.parse(val); + + if (Array.isArray(result)) { + throw new Error("Expected JSONObject, but got JSONArray"); + } + + return result; + } + + public static parseJSONArray(val: string): JSONArray { + const result: JSONObject | JSONArray = this.parse(val); + + if (!Array.isArray(result)) { + throw new Error("Expected JSONArray, but got JSONObject"); + } + + return result; + } + + public static deserialize(val: JSONObject): JSONObject { + const newVal: JSONObject = {}; + for (const key in val) { + if (val[key] === null || val[key] === undefined) { + newVal[key] = val[key]; + } + + if (Array.isArray(val[key])) { + const arraySerialize: Array<JSONValue> = []; + for (const arrVal of val[key] as Array<JSONValue>) { + arraySerialize.push(this.deserializeValue(arrVal)); } - const keys1: Array<string> = Object.keys(obj1); - const keys2: Array<string> = Object.keys(obj2); + newVal[key] = arraySerialize; + } else { + newVal[key] = this.deserializeValue(val[key] as JSONValue); + } + } - if (keys1.length !== keys2.length) { - return false; + return newVal; + } + + public static toFormattedString(val: JSONValue): string { + return JSON.stringify(val, null, 4); + } + + public static anyObjectToJSONObject(val: any): JSONObject { + return JSON.parse(JSON.stringify(val)); + } + + public static flattenObject(val: JSONObject): JSONObject { + const returnObj: JSONObject = {}; + + type FlattenFunction = (obj: JSONObject, prefix: string) => void; + + const flatten: FlattenFunction = ( + obj: JSONObject, + prefix: string, + ): void => { + for (const key in obj) { + if (typeof obj[key] === Typeof.Object) { + flatten(obj[key] as JSONObject, `${prefix}${key}.`); + } else { + returnObj[`${prefix}${key}`] = obj[key]; } + } + }; - for (const key of keys1) { - if (!keys2.includes(key)) { - return false; - } - } + flatten(val, ""); - // check if all the values are the same + return returnObj; + } - for (const key of keys1) { - if (obj1[key] !== obj2[key]) { - return false; - } - } + public static flattenArray(val: JSONArray): JSONArray { + const returnArr: JSONArray = []; - return true; + for (const obj of val) { + returnArr.push(this.flattenObject(obj as JSONObject)); } - public static toCompressedString(val: JSONValue): string { - return JSON.stringify(val, null, 2); - } - - public static toString(val: JSONValue): string { - if (typeof val === Typeof.String) { - return val as string; - } - - return JSON.stringify(val); - } - - public static getJSONValueInPath( - obj: JSONObject, - path: string - ): JSONValue | null { - const paths: Array<string> = path.split('.'); - let returnValue: JSONObject = obj as JSONObject; - for (const p of paths) { - if (!p) { - continue; - } - - if (returnValue && returnValue[p as string]!) { - returnValue = returnValue[p] as JSONObject; - } else { - return null; - } - } - - return returnValue as JSONValue; - } - - // this function serializes JSON with Common Objects to JSON that can be stringified. - public static serialize(val: JSONObject): JSONObject { - const newVal: JSONObject = {}; - - for (const key in val) { - if (val[key] === undefined) { - continue; - } - - if (val[key] === null) { - newVal[key] = val[key]; - } - - if (Array.isArray(val[key])) { - const arraySerialize: Array<JSONValue> = []; - for (const arrVal of val[key] as Array<JSONValue>) { - arraySerialize.push(this.serializeValue(arrVal)); - } - - newVal[key] = arraySerialize; - } else { - newVal[key] = this.serializeValue(val[key] as JSONValue); - } - } - - return newVal; - } - - public static serializeValue(val: JSONValue): JSONValue { - if (val === null || val === undefined) { - return val; - } else if ( - typeof val === Typeof.String && - val.toString().trim() === '' - ) { - return val; - } else if (val instanceof BaseModel) { - return BaseModel.toJSON(val, BaseModel); - } else if (typeof val === Typeof.Number) { - return val; - } else if (ArrayBuffer.isView(val)) { - return { - _type: ObjectType.Buffer, - value: val as Uint8Array, - }; - } else if (val && val instanceof SerializableObject) { - return val.toJSON(); - } else if (val && val instanceof Date) { - return { - _type: ObjectType.DateTime, - value: OneUptimeDate.toString(val as Date).toString(), - }; - } else if ( - typeof val === Typeof.Object && - (val as JSONObject)['_type'] && - Object.keys(ObjectType).includes( - (val as JSONObject)['_type'] as string - ) - ) { - return val; - } else if (typeof val === Typeof.Object) { - return this.serialize(val as JSONObject); - } - - return val; - } - - public static deserializeValue(val: JSONValue): JSONValue { - if (val === null || val === undefined) { - return val; - } else if ( - typeof val === Typeof.String && - val.toString().trim() === '' - ) { - return val; - } else if ( - val && - typeof val === Typeof.Object && - (val as JSONObject)['_type'] && - (val as JSONObject)['value'] && - ((val as JSONObject)['value'] as JSONObject)['data'] && - ((val as JSONObject)['value'] as JSONObject)['type'] && - ((val as JSONObject)['value'] as JSONObject)['type'] === - ObjectType.Buffer && - ((val as JSONObject)['_type'] as string) === ObjectType.Buffer - ) { - return Buffer.from( - ((val as JSONObject)['value'] as JSONObject)[ - 'data' - ] as Uint8Array - ); - } else if (val && ArrayBuffer.isView(val)) { - return Buffer.from(val as Uint8Array); - } else if (typeof val === Typeof.Number) { - return val; - } else if (val instanceof DatabaseProperty) { - return val; - } else if ( - val && - typeof val === Typeof.Object && - (val as JSONObject)['_type'] && - SerializableObjectDictionary[(val as JSONObject)['_type'] as string] - ) { - return SerializableObjectDictionary[ - (val as JSONObject)['_type'] as string - ].fromJSON(val); - } else if (val instanceof Date) { - return val; - } else if (typeof val === Typeof.Object) { - return this.deserialize(val as JSONObject); - } else if (Array.isArray(val)) { - const arr: Array<JSONValue> = []; - - for (const v of val) { - arr.push(this.deserializeValue(v)); - } - - return arr; - } - - return val; - } - - public static deserializeArray(array: JSONArray): JSONArray { - const returnArr: JSONArray = []; - - for (const obj of array) { - returnArr.push(this.deserialize(obj)); - } - - return returnArr; - } - - public static serializeArray(array: JSONArray): JSONArray { - const returnArr: JSONArray = []; - - for (const obj of array) { - returnArr.push(this.serialize(obj)); - } - - return returnArr; - } - - public static parse(val: string): JSONObject | JSONArray { - return JSON5.parse(val); - } - - public static parseJSONObject(val: string): JSONObject { - const result: JSONObject | JSONArray = this.parse(val); - - if (Array.isArray(result)) { - throw new Error('Expected JSONObject, but got JSONArray'); - } - - return result; - } - - public static parseJSONArray(val: string): JSONArray { - const result: JSONObject | JSONArray = this.parse(val); - - if (!Array.isArray(result)) { - throw new Error('Expected JSONArray, but got JSONObject'); - } - - return result; - } - - public static deserialize(val: JSONObject): JSONObject { - const newVal: JSONObject = {}; - for (const key in val) { - if (val[key] === null || val[key] === undefined) { - newVal[key] = val[key]; - } - - if (Array.isArray(val[key])) { - const arraySerialize: Array<JSONValue> = []; - for (const arrVal of val[key] as Array<JSONValue>) { - arraySerialize.push(this.deserializeValue(arrVal)); - } - - newVal[key] = arraySerialize; - } else { - newVal[key] = this.deserializeValue(val[key] as JSONValue); - } - } - - return newVal; - } - - public static toFormattedString(val: JSONValue): string { - return JSON.stringify(val, null, 4); - } - - public static anyObjectToJSONObject(val: any): JSONObject { - return JSON.parse(JSON.stringify(val)); - } - - public static flattenObject(val: JSONObject): JSONObject { - const returnObj: JSONObject = {}; - - type FlattenFunction = (obj: JSONObject, prefix: string) => void; - - const flatten: FlattenFunction = ( - obj: JSONObject, - prefix: string - ): void => { - for (const key in obj) { - if (typeof obj[key] === Typeof.Object) { - flatten(obj[key] as JSONObject, `${prefix}${key}.`); - } else { - returnObj[`${prefix}${key}`] = obj[key]; - } - } - }; - - flatten(val, ''); - - return returnObj; - } - - public static flattenArray(val: JSONArray): JSONArray { - const returnArr: JSONArray = []; - - for (const obj of val) { - returnArr.push(this.flattenObject(obj as JSONObject)); - } - - return returnArr; - } + return returnArr; + } } diff --git a/Common/Types/JsonWebTokenData.ts b/Common/Types/JsonWebTokenData.ts index d8ca596ca5..f5f9029047 100644 --- a/Common/Types/JsonWebTokenData.ts +++ b/Common/Types/JsonWebTokenData.ts @@ -1,14 +1,14 @@ -import Email from './Email'; -import { JSONObject } from './JSON'; -import Name from './Name'; -import ObjectID from './ObjectID'; +import Email from "./Email"; +import { JSONObject } from "./JSON"; +import Name from "./Name"; +import ObjectID from "./ObjectID"; export default interface JSONWebTokenData extends JSONObject { - userId: ObjectID; - email: Email; - name?: Name | undefined; - isMasterAdmin: boolean; - statusPageId?: ObjectID | undefined; // for status page logins. - projectId?: ObjectID | undefined; // for SSO logins. - isGlobalLogin: boolean; // If this is OneUptime username and password login. This is true, if this is SSO login. Then, this is false. + userId: ObjectID; + email: Email; + name?: Name | undefined; + isMasterAdmin: boolean; + statusPageId?: ObjectID | undefined; // for status page logins. + projectId?: ObjectID | undefined; // for SSO logins. + isGlobalLogin: boolean; // If this is OneUptime username and password login. This is true, if this is SSO login. Then, this is false. } diff --git a/Common/Types/Link.ts b/Common/Types/Link.ts index 9f984fd427..e978de8c05 100644 --- a/Common/Types/Link.ts +++ b/Common/Types/Link.ts @@ -1,8 +1,8 @@ -import Route from './API/Route'; -import URL from './API/URL'; +import Route from "./API/Route"; +import URL from "./API/URL"; export default interface Link { - title: string; - to: Route | URL; - openInNewTab?: boolean | undefined; + title: string; + to: Route | URL; + openInNewTab?: boolean | undefined; } diff --git a/Common/Types/ListData.ts b/Common/Types/ListData.ts index e029f0ce39..319a7c3e46 100644 --- a/Common/Types/ListData.ts +++ b/Common/Types/ListData.ts @@ -1,32 +1,32 @@ -import { JSONArray, JSONObject } from './JSON'; -import PositiveNumber from './PositiveNumber'; +import { JSONArray, JSONObject } from "./JSON"; +import PositiveNumber from "./PositiveNumber"; export default class ListData { - public constructor(obj: { - data: JSONArray; - count: PositiveNumber; - skip: PositiveNumber; - limit: PositiveNumber; - }) { - this.data = obj.data; - this.count = obj.count; - this.skip = obj.skip; - this.limit = obj.limit; - } + public constructor(obj: { + data: JSONArray; + count: PositiveNumber; + skip: PositiveNumber; + limit: PositiveNumber; + }) { + this.data = obj.data; + this.count = obj.count; + this.skip = obj.skip; + this.limit = obj.limit; + } - public data: JSONArray; - public count: PositiveNumber; - public skip: PositiveNumber; - public limit: PositiveNumber; + public data: JSONArray; + public count: PositiveNumber; + public skip: PositiveNumber; + public limit: PositiveNumber; - public toJSON(): JSONObject { - const json: JSONObject = { - data: this.data, - count: this.count.toNumber(), - skip: this.skip.toNumber(), - limit: this.limit.toNumber(), - }; + public toJSON(): JSONObject { + const json: JSONObject = { + data: this.data, + count: this.count.toNumber(), + skip: this.skip.toNumber(), + limit: this.limit.toNumber(), + }; - return json; - } + return json; + } } diff --git a/Common/Types/Mail/MailStatus.ts b/Common/Types/Mail/MailStatus.ts index d325ed0d27..491c316b4a 100644 --- a/Common/Types/Mail/MailStatus.ts +++ b/Common/Types/Mail/MailStatus.ts @@ -1,6 +1,6 @@ enum MailStatus { - Success = 'Success', - Error = 'Error', + Success = "Success", + Error = "Error", } export default MailStatus; diff --git a/Common/Types/MeteredPlan/ProductType.ts b/Common/Types/MeteredPlan/ProductType.ts index 66cd610869..a09b0674a7 100644 --- a/Common/Types/MeteredPlan/ProductType.ts +++ b/Common/Types/MeteredPlan/ProductType.ts @@ -1,8 +1,8 @@ enum ProductType { - Logs = 'Logs', - Traces = 'Traces', - Metrics = 'Metrics', - ActiveMonitoring = 'Active Monitoring', + Logs = "Logs", + Traces = "Traces", + Metrics = "Metrics", + ActiveMonitoring = "Active Monitoring", } export default ProductType; diff --git a/Common/Types/Mixins.ts b/Common/Types/Mixins.ts index e73c363dca..c2c7f77b33 100644 --- a/Common/Types/Mixins.ts +++ b/Common/Types/Mixins.ts @@ -1,4 +1,4 @@ // https://www.typescriptlang.org/docs/handbook/mixins.html -import GenericObject from './GenericObject'; +import GenericObject from "./GenericObject"; export type GConstructor<T = GenericObject> = new (...args: any[]) => T; diff --git a/Common/Types/Monitor/CriteriaFilter.ts b/Common/Types/Monitor/CriteriaFilter.ts index 8f662e7bb5..c92eadf5d4 100644 --- a/Common/Types/Monitor/CriteriaFilter.ts +++ b/Common/Types/Monitor/CriteriaFilter.ts @@ -1,139 +1,139 @@ -import MonitorType from './MonitorType'; +import MonitorType from "./MonitorType"; export enum CheckOn { - ResponseTime = 'Response Time (in ms)', - ResponseStatusCode = 'Response Status Code', - ResponseHeader = 'Response Header', - ResponseHeaderValue = 'Response Header Value', - ResponseBody = 'Response Body', - IsOnline = 'Is Online', - IncomingRequest = 'Incoming Request', - ServerProcessName = 'Server Process Name', - ServerProcessCommand = 'Server Process Command', - ServerProcessPID = 'Server Process PID', - RequestBody = 'Request Body', - RequestHeader = 'Request Header', - RequestHeaderValue = 'Request Header Value', - JavaScriptExpression = 'JavaScript Expression', - DiskUsagePercent = 'Disk Usage (in %)', - CPUUsagePercent = 'CPU Usage (in %)', - MemoryUsagePercent = 'Memory Usage (in %)', - ExpiresInHours = 'Expires In Hours', - ExpiresInDays = 'Expires In Days', - IsSelfSignedCertificate = 'Is Self Signed Certificate', - IsExpiredCertificate = 'Is Expired Certificate', - IsValidCertificate = 'Is Valid Certificate', - IsNotAValidCertificate = 'Is Not A Valid Certificate', + ResponseTime = "Response Time (in ms)", + ResponseStatusCode = "Response Status Code", + ResponseHeader = "Response Header", + ResponseHeaderValue = "Response Header Value", + ResponseBody = "Response Body", + IsOnline = "Is Online", + IncomingRequest = "Incoming Request", + ServerProcessName = "Server Process Name", + ServerProcessCommand = "Server Process Command", + ServerProcessPID = "Server Process PID", + RequestBody = "Request Body", + RequestHeader = "Request Header", + RequestHeaderValue = "Request Header Value", + JavaScriptExpression = "JavaScript Expression", + DiskUsagePercent = "Disk Usage (in %)", + CPUUsagePercent = "CPU Usage (in %)", + MemoryUsagePercent = "Memory Usage (in %)", + ExpiresInHours = "Expires In Hours", + ExpiresInDays = "Expires In Days", + IsSelfSignedCertificate = "Is Self Signed Certificate", + IsExpiredCertificate = "Is Expired Certificate", + IsValidCertificate = "Is Valid Certificate", + IsNotAValidCertificate = "Is Not A Valid Certificate", - // Custom code or synthetic monitor. - ResultValue = 'Result Value', - Error = 'Error', - ExecutionTime = 'Execution Time (in ms)', + // Custom code or synthetic monitor. + ResultValue = "Result Value", + Error = "Error", + ExecutionTime = "Execution Time (in ms)", - // synthetic monitors + // synthetic monitors - ScreenSizeType = 'Screen Size', - BrowserType = 'Browser Type', + ScreenSizeType = "Screen Size", + BrowserType = "Browser Type", } export interface ServerMonitorOptions { - diskPath?: string | undefined; + diskPath?: string | undefined; } export enum EvaluateOverTimeType { - Average = 'Average', - Sum = 'Sum', - MaximumValue = 'Maximum Value', - MunimumValue = 'Minimum Value', - AllValues = 'All Values', - AnyValue = 'Any Value', + Average = "Average", + Sum = "Sum", + MaximumValue = "Maximum Value", + MunimumValue = "Minimum Value", + AllValues = "All Values", + AnyValue = "Any Value", } export enum EvaluateOverTimeMinutes { - TwoMinutes = '2', - ThreeMinutes = '3', - FiveMinutes = '5', - TenMinutes = '10', - FifteenMinutes = '15', - TwentyMinutes = '20', - ThirtyMinutes = '30', - FortyFiveMinutes = '45', - SixtyMinutes = '60', + TwoMinutes = "2", + ThreeMinutes = "3", + FiveMinutes = "5", + TenMinutes = "10", + FifteenMinutes = "15", + TwentyMinutes = "20", + ThirtyMinutes = "30", + FortyFiveMinutes = "45", + SixtyMinutes = "60", } export interface EvaluateOverTimeOptions { - timeValueInMinutes: number | undefined; - evaluateOverTimeType: EvaluateOverTimeType | undefined; + timeValueInMinutes: number | undefined; + evaluateOverTimeType: EvaluateOverTimeType | undefined; } export interface CriteriaFilter { - checkOn: CheckOn; - serverMonitorOptions?: ServerMonitorOptions | undefined; - filterType: FilterType | undefined; - value: string | number | undefined; - eveluateOverTime?: boolean | undefined; - evaluateOverTimeOptions?: EvaluateOverTimeOptions | undefined; + checkOn: CheckOn; + serverMonitorOptions?: ServerMonitorOptions | undefined; + filterType: FilterType | undefined; + value: string | number | undefined; + eveluateOverTime?: boolean | undefined; + evaluateOverTimeOptions?: EvaluateOverTimeOptions | undefined; } export enum FilterType { - EqualTo = 'Equal To', - NotEqualTo = 'Not Equal To', - GreaterThan = 'Greater Than', - LessThan = 'Less Than', - GreaterThanOrEqualTo = 'Greater Than Or Equal To', - LessThanOrEqualTo = 'Less Than Or Equal To', - Contains = 'Contains', - NotContains = 'Not Contains', - StartsWith = 'Starts With', - EndsWith = 'Ends With', - IsEmpty = 'Is Empty', - IsNotEmpty = 'Is Not Empty', - True = 'True', - False = 'False', - NotRecievedInMinutes = 'Not Recieved In Minutes', - RecievedInMinutes = 'Recieved In Minutes', - EvaluatesToTrue = 'Evaluates To True', - IsExecuting = 'Is Executing', - IsNotExecuting = 'Is Not Executing', + EqualTo = "Equal To", + NotEqualTo = "Not Equal To", + GreaterThan = "Greater Than", + LessThan = "Less Than", + GreaterThanOrEqualTo = "Greater Than Or Equal To", + LessThanOrEqualTo = "Less Than Or Equal To", + Contains = "Contains", + NotContains = "Not Contains", + StartsWith = "Starts With", + EndsWith = "Ends With", + IsEmpty = "Is Empty", + IsNotEmpty = "Is Not Empty", + True = "True", + False = "False", + NotRecievedInMinutes = "Not Recieved In Minutes", + RecievedInMinutes = "Recieved In Minutes", + EvaluatesToTrue = "Evaluates To True", + IsExecuting = "Is Executing", + IsNotExecuting = "Is Not Executing", } export enum FilterCondition { - All = 'All', - Any = 'Any', + All = "All", + Any = "Any", } export class CriteriaFilterUtil { - public static isEvaluateOverTimeFilter(checkOn: CheckOn): boolean { - return ( - checkOn === CheckOn.ResponseStatusCode || - checkOn === CheckOn.ResponseTime || - checkOn === CheckOn.DiskUsagePercent || - checkOn === CheckOn.CPUUsagePercent || - checkOn === CheckOn.MemoryUsagePercent - ); - } + public static isEvaluateOverTimeFilter(checkOn: CheckOn): boolean { + return ( + checkOn === CheckOn.ResponseStatusCode || + checkOn === CheckOn.ResponseTime || + checkOn === CheckOn.DiskUsagePercent || + checkOn === CheckOn.CPUUsagePercent || + checkOn === CheckOn.MemoryUsagePercent + ); + } - public static getTimeFiltersByMonitorType( - monitorType: MonitorType - ): Array<CheckOn> { - if ( - monitorType === MonitorType.API || - monitorType === MonitorType.Website - ) { - return [CheckOn.ResponseStatusCode, CheckOn.ResponseTime]; - } else if ( - monitorType === MonitorType.Ping || - monitorType === MonitorType.IP || - monitorType === MonitorType.Port - ) { - return [CheckOn.ResponseTime]; - } else if (monitorType === MonitorType.Server) { - return [ - CheckOn.DiskUsagePercent, - CheckOn.CPUUsagePercent, - CheckOn.MemoryUsagePercent, - ]; - } - return []; + public static getTimeFiltersByMonitorType( + monitorType: MonitorType, + ): Array<CheckOn> { + if ( + monitorType === MonitorType.API || + monitorType === MonitorType.Website + ) { + return [CheckOn.ResponseStatusCode, CheckOn.ResponseTime]; + } else if ( + monitorType === MonitorType.Ping || + monitorType === MonitorType.IP || + monitorType === MonitorType.Port + ) { + return [CheckOn.ResponseTime]; + } else if (monitorType === MonitorType.Server) { + return [ + CheckOn.DiskUsagePercent, + CheckOn.CPUUsagePercent, + CheckOn.MemoryUsagePercent, + ]; } + return []; + } } diff --git a/Common/Types/Monitor/CriteriaIncident.ts b/Common/Types/Monitor/CriteriaIncident.ts index e64bf6022c..a641e564fd 100644 --- a/Common/Types/Monitor/CriteriaIncident.ts +++ b/Common/Types/Monitor/CriteriaIncident.ts @@ -1,10 +1,10 @@ -import ObjectID from '../ObjectID'; +import ObjectID from "../ObjectID"; export interface CriteriaIncident { - title: string; - description: string; - incidentSeverityId?: ObjectID | undefined; - autoResolveIncident?: boolean | undefined; - id: string; - onCallPolicyIds?: Array<ObjectID> | undefined; + title: string; + description: string; + incidentSeverityId?: ObjectID | undefined; + autoResolveIncident?: boolean | undefined; + id: string; + onCallPolicyIds?: Array<ObjectID> | undefined; } diff --git a/Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts b/Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts index 8f43041611..628a30ced4 100644 --- a/Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts +++ b/Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts @@ -1,8 +1,8 @@ -import { JSONObject } from '../../JSON'; +import { JSONObject } from "../../JSON"; export default interface CustomCodeMonitorResponse { - result: string | number | boolean | JSONObject | undefined; - scriptError?: string | undefined; - logMessages: string[]; - executionTimeInMS: number; + result: string | number | boolean | JSONObject | undefined; + scriptError?: string | undefined; + logMessages: string[]; + executionTimeInMS: number; } diff --git a/Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest.ts b/Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest.ts index 83f3394905..9e62b66392 100644 --- a/Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest.ts +++ b/Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest.ts @@ -1,13 +1,13 @@ -import HTTPMethod from '../../API/HTTPMethod'; -import Dictionary from '../../Dictionary'; -import { JSONObject } from '../../JSON'; -import ObjectID from '../../ObjectID'; +import HTTPMethod from "../../API/HTTPMethod"; +import Dictionary from "../../Dictionary"; +import { JSONObject } from "../../JSON"; +import ObjectID from "../../ObjectID"; export default interface IncomingMonitorRequest { - monitorId: ObjectID; - requestHeaders?: Dictionary<string> | undefined; - requestBody?: string | JSONObject | undefined; - requestMethod?: HTTPMethod | undefined; - incomingRequestReceivedAt: Date; - onlyCheckForIncomingRequestReceivedAt?: boolean | undefined; + monitorId: ObjectID; + requestHeaders?: Dictionary<string> | undefined; + requestBody?: string | JSONObject | undefined; + requestMethod?: HTTPMethod | undefined; + incomingRequestReceivedAt: Date; + onlyCheckForIncomingRequestReceivedAt?: boolean | undefined; } diff --git a/Common/Types/Monitor/MonitorCriteria.ts b/Common/Types/Monitor/MonitorCriteria.ts index 5b80f2f295..14cffbe454 100644 --- a/Common/Types/Monitor/MonitorCriteria.ts +++ b/Common/Types/Monitor/MonitorCriteria.ts @@ -1,191 +1,180 @@ -import DatabaseProperty from '../Database/DatabaseProperty'; -import BadDataException from '../Exception/BadDataException'; -import { JSONArray, JSONObject, ObjectType } from '../JSON'; -import JSONFunctions from '../JSONFunctions'; -import ObjectID from '../ObjectID'; -import MonitorCriteriaInstance from './MonitorCriteriaInstance'; -import MonitorType from './MonitorType'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "../Database/DatabaseProperty"; +import BadDataException from "../Exception/BadDataException"; +import { JSONArray, JSONObject, ObjectType } from "../JSON"; +import JSONFunctions from "../JSONFunctions"; +import ObjectID from "../ObjectID"; +import MonitorCriteriaInstance from "./MonitorCriteriaInstance"; +import MonitorType from "./MonitorType"; +import { FindOperator } from "typeorm"; export interface MonitorCriteriaType { - monitorCriteriaInstanceArray: Array<MonitorCriteriaInstance>; + monitorCriteriaInstanceArray: Array<MonitorCriteriaInstance>; } export default class MonitorCriteria extends DatabaseProperty { - public data: MonitorCriteriaType | undefined = undefined; + public data: MonitorCriteriaType | undefined = undefined; - public constructor() { - super(); - this.data = { - monitorCriteriaInstanceArray: [new MonitorCriteriaInstance()], - }; + public constructor() { + super(); + this.data = { + monitorCriteriaInstanceArray: [new MonitorCriteriaInstance()], + }; + } + + public static getDefaultMonitorCriteria(arg: { + monitorType: MonitorType; + monitorName: string; + onlineMonitorStatusId: ObjectID; + offlineMonitorStatusId: ObjectID; + defaultIncidentSeverityId: ObjectID; + }): MonitorCriteria { + const monitorCriteria: MonitorCriteria = new MonitorCriteria(); + const offlineCriteria: MonitorCriteriaInstance = + MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({ + monitorType: arg.monitorType, + monitorStatusId: arg.offlineMonitorStatusId, + incidentSeverityId: arg.defaultIncidentSeverityId, + monitorName: arg.monitorName, + }); + + const onlineCriteria: MonitorCriteriaInstance | null = + MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({ + monitorType: arg.monitorType, + monitorStatusId: arg.onlineMonitorStatusId, + monitorName: arg.monitorName, + }); + + monitorCriteria.data = { + monitorCriteriaInstanceArray: [], + }; + + if (offlineCriteria) { + monitorCriteria.data.monitorCriteriaInstanceArray.push(offlineCriteria); } - public static getDefaultMonitorCriteria(arg: { - monitorType: MonitorType; - monitorName: string; - onlineMonitorStatusId: ObjectID; - offlineMonitorStatusId: ObjectID; - defaultIncidentSeverityId: ObjectID; - }): MonitorCriteria { - const monitorCriteria: MonitorCriteria = new MonitorCriteria(); - const offlineCriteria: MonitorCriteriaInstance = - MonitorCriteriaInstance.getDefaultOfflineMonitorCriteriaInstance({ - monitorType: arg.monitorType, - monitorStatusId: arg.offlineMonitorStatusId, - incidentSeverityId: arg.defaultIncidentSeverityId, - monitorName: arg.monitorName, - }); - - const onlineCriteria: MonitorCriteriaInstance | null = - MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({ - monitorType: arg.monitorType, - monitorStatusId: arg.onlineMonitorStatusId, - monitorName: arg.monitorName, - }); - - monitorCriteria.data = { - monitorCriteriaInstanceArray: [], - }; - - if (offlineCriteria) { - monitorCriteria.data.monitorCriteriaInstanceArray.push( - offlineCriteria - ); - } - - if (onlineCriteria) { - monitorCriteria.data.monitorCriteriaInstanceArray.push( - onlineCriteria - ); - } - - return monitorCriteria; + if (onlineCriteria) { + monitorCriteria.data.monitorCriteriaInstanceArray.push(onlineCriteria); } - public static getValidationError( - value: MonitorCriteria, - monitorType: MonitorType - ): string | null { - if (!value.data) { - return 'Monitor Criteria is required'; - } + return monitorCriteria; + } - if (value.data.monitorCriteriaInstanceArray.length === 0) { - return 'Monitor Criteria is required'; - } - - for (const criteria of value.data.monitorCriteriaInstanceArray) { - if ( - MonitorCriteriaInstance.getValidationError( - criteria, - monitorType - ) - ) { - return MonitorCriteriaInstance.getValidationError( - criteria, - monitorType - ); - } - } - - return null; + public static getValidationError( + value: MonitorCriteria, + monitorType: MonitorType, + ): string | null { + if (!value.data) { + return "Monitor Criteria is required"; } - public static getNewMonitorCriteriaAsJSON(): JSONObject { - return { - _type: 'MonitorCriteria', - value: { - monitorCriteriaInstanceArray: [ - new MonitorCriteriaInstance().toJSON(), - ], + if (value.data.monitorCriteriaInstanceArray.length === 0) { + return "Monitor Criteria is required"; + } + + for (const criteria of value.data.monitorCriteriaInstanceArray) { + if (MonitorCriteriaInstance.getValidationError(criteria, monitorType)) { + return MonitorCriteriaInstance.getValidationError( + criteria, + monitorType, + ); + } + } + + return null; + } + + public static getNewMonitorCriteriaAsJSON(): JSONObject { + return { + _type: "MonitorCriteria", + value: { + monitorCriteriaInstanceArray: [new MonitorCriteriaInstance().toJSON()], + }, + }; + } + + public override toJSON(): JSONObject { + if (!this.data) { + return MonitorCriteria.getNewMonitorCriteriaAsJSON(); + } + + return JSONFunctions.serialize({ + _type: ObjectType.MonitorCriteria, + value: { + monitorCriteriaInstanceArray: + this.data.monitorCriteriaInstanceArray.map( + (criteria: MonitorCriteriaInstance) => { + return criteria.toJSON(); }, - }; + ), + }, + }); + } + + public static override fromJSON(json: JSONObject): MonitorCriteria { + if (json instanceof MonitorCriteria) { + return json; } - public override toJSON(): JSONObject { - if (!this.data) { - return MonitorCriteria.getNewMonitorCriteriaAsJSON(); - } - - return JSONFunctions.serialize({ - _type: ObjectType.MonitorCriteria, - value: { - monitorCriteriaInstanceArray: - this.data.monitorCriteriaInstanceArray.map( - (criteria: MonitorCriteriaInstance) => { - return criteria.toJSON(); - } - ), - }, - }); + if (!json || json["_type"] !== ObjectType.MonitorCriteria) { + throw new BadDataException("Invalid monitor criteria"); } - public static override fromJSON(json: JSONObject): MonitorCriteria { - if (json instanceof MonitorCriteria) { - return json; - } - - if (!json || json['_type'] !== ObjectType.MonitorCriteria) { - throw new BadDataException('Invalid monitor criteria'); - } - - if (!json) { - throw new BadDataException('Invalid monitor criteria'); - } - - if (!json['value']) { - throw new BadDataException('Invalid monitor criteria'); - } - - if (!(json['value'] as JSONObject)['monitorCriteriaInstanceArray']) { - throw new BadDataException('Invalid monitor criteria'); - } - - const monitorCriteriaInstanceArray: JSONArray = ( - json['value'] as JSONObject - )['monitorCriteriaInstanceArray'] as JSONArray; - - const monitorCriteria: MonitorCriteria = new MonitorCriteria(); - - monitorCriteria.data = { - monitorCriteriaInstanceArray: monitorCriteriaInstanceArray.map( - (json: JSONObject) => { - return MonitorCriteriaInstance.fromJSON(json); - } - ), - }; - - return monitorCriteria; + if (!json) { + throw new BadDataException("Invalid monitor criteria"); } - public static isValid(_json: JSONObject): boolean { - return true; + if (!json["value"]) { + throw new BadDataException("Invalid monitor criteria"); } - protected static override toDatabase( - value: MonitorCriteria | FindOperator<MonitorCriteria> - ): JSONObject | null { - if (value && value instanceof MonitorCriteria) { - return (value as MonitorCriteria).toJSON(); - } else if (value) { - return JSONFunctions.serialize(value as any); - } - - return null; + if (!(json["value"] as JSONObject)["monitorCriteriaInstanceArray"]) { + throw new BadDataException("Invalid monitor criteria"); } - protected static override fromDatabase( - value: JSONObject - ): MonitorCriteria | null { - if (value) { - return MonitorCriteria.fromJSON(value); - } + const monitorCriteriaInstanceArray: JSONArray = ( + json["value"] as JSONObject + )["monitorCriteriaInstanceArray"] as JSONArray; - return null; + const monitorCriteria: MonitorCriteria = new MonitorCriteria(); + + monitorCriteria.data = { + monitorCriteriaInstanceArray: monitorCriteriaInstanceArray.map( + (json: JSONObject) => { + return MonitorCriteriaInstance.fromJSON(json); + }, + ), + }; + + return monitorCriteria; + } + + public static isValid(_json: JSONObject): boolean { + return true; + } + + protected static override toDatabase( + value: MonitorCriteria | FindOperator<MonitorCriteria>, + ): JSONObject | null { + if (value && value instanceof MonitorCriteria) { + return (value as MonitorCriteria).toJSON(); + } else if (value) { + return JSONFunctions.serialize(value as any); } - public override toString(): string { - return JSON.stringify(this.toJSON()); + return null; + } + + protected static override fromDatabase( + value: JSONObject, + ): MonitorCriteria | null { + if (value) { + return MonitorCriteria.fromJSON(value); } + + return null; + } + + public override toString(): string { + return JSON.stringify(this.toJSON()); + } } diff --git a/Common/Types/Monitor/MonitorCriteriaInstance.ts b/Common/Types/Monitor/MonitorCriteriaInstance.ts index 3c41426728..2aad09e65b 100644 --- a/Common/Types/Monitor/MonitorCriteriaInstance.ts +++ b/Common/Types/Monitor/MonitorCriteriaInstance.ts @@ -1,692 +1,686 @@ -import DatabaseProperty from '../Database/DatabaseProperty'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import JSONFunctions from '../JSONFunctions'; -import ObjectID from '../ObjectID'; -import Typeof from '../Typeof'; +import DatabaseProperty from "../Database/DatabaseProperty"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import JSONFunctions from "../JSONFunctions"; +import ObjectID from "../ObjectID"; +import Typeof from "../Typeof"; import { - CheckOn, - CriteriaFilter, - FilterCondition, - FilterType, -} from './CriteriaFilter'; -import { CriteriaIncident } from './CriteriaIncident'; -import MonitorType from './MonitorType'; -import { FindOperator } from 'typeorm'; + CheckOn, + CriteriaFilter, + FilterCondition, + FilterType, +} from "./CriteriaFilter"; +import { CriteriaIncident } from "./CriteriaIncident"; +import MonitorType from "./MonitorType"; +import { FindOperator } from "typeorm"; export interface MonitorCriteriaInstanceType { - monitorStatusId: ObjectID | undefined; - filterCondition: FilterCondition; - filters: Array<CriteriaFilter>; - incidents: Array<CriteriaIncident>; - name: string; - description: string; - changeMonitorStatus?: boolean | undefined; - createIncidents?: boolean | undefined; - id: string; + monitorStatusId: ObjectID | undefined; + filterCondition: FilterCondition; + filters: Array<CriteriaFilter>; + incidents: Array<CriteriaIncident>; + name: string; + description: string; + changeMonitorStatus?: boolean | undefined; + createIncidents?: boolean | undefined; + id: string; } export default class MonitorCriteriaInstance extends DatabaseProperty { - public data: MonitorCriteriaInstanceType | undefined = undefined; + public data: MonitorCriteriaInstanceType | undefined = undefined; - public constructor() { - super(); - this.data = { - id: ObjectID.generate().toString(), - monitorStatusId: undefined, - filterCondition: FilterCondition.All, - filters: [ - { - checkOn: CheckOn.IsOnline, - filterType: undefined, - value: undefined, - }, - ], - createIncidents: false, - changeMonitorStatus: false, - incidents: [], - name: '', - description: '', - }; + public constructor() { + super(); + this.data = { + id: ObjectID.generate().toString(), + monitorStatusId: undefined, + filterCondition: FilterCondition.All, + filters: [ + { + checkOn: CheckOn.IsOnline, + filterType: undefined, + value: undefined, + }, + ], + createIncidents: false, + changeMonitorStatus: false, + incidents: [], + name: "", + description: "", + }; + } + + public static getDefaultOnlineMonitorCriteriaInstance(arg: { + monitorType: MonitorType; + monitorStatusId: ObjectID; + monitorName: string; + }): MonitorCriteriaInstance | null { + if (arg.monitorType === MonitorType.IncomingRequest) { + const monitorCriteriaInstance: MonitorCriteriaInstance = + new MonitorCriteriaInstance(); + + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.All, + filters: [ + { + checkOn: CheckOn.IncomingRequest, + filterType: FilterType.RecievedInMinutes, + value: 30, + }, + ], + incidents: [], + changeMonitorStatus: true, + createIncidents: false, + name: `Check if ${arg.monitorName} is online`, + description: `This criteria checks if the ${arg.monitorName} is online`, + }; + + return monitorCriteriaInstance; } - public static getDefaultOnlineMonitorCriteriaInstance(arg: { - monitorType: MonitorType; - monitorStatusId: ObjectID; - monitorName: string; - }): MonitorCriteriaInstance | null { - if (arg.monitorType === MonitorType.IncomingRequest) { - const monitorCriteriaInstance: MonitorCriteriaInstance = - new MonitorCriteriaInstance(); + if (arg.monitorType === MonitorType.SSLCertificate) { + const monitorCriteriaInstance: MonitorCriteriaInstance = + new MonitorCriteriaInstance(); - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.All, - filters: [ - { - checkOn: CheckOn.IncomingRequest, - filterType: FilterType.RecievedInMinutes, - value: 30, - }, - ], - incidents: [], - changeMonitorStatus: true, - createIncidents: false, - name: `Check if ${arg.monitorName} is online`, - description: `This criteria checks if the ${arg.monitorName} is online`, - }; + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.All, + filters: [ + { + checkOn: CheckOn.IsValidCertificate, + filterType: FilterType.True, + value: undefined, + }, + ], + incidents: [], + changeMonitorStatus: true, + createIncidents: false, + name: `Check if ${arg.monitorName} is online`, + description: `This criteria checks if the ${arg.monitorName} is online`, + }; - return monitorCriteriaInstance; - } - - if (arg.monitorType === MonitorType.SSLCertificate) { - const monitorCriteriaInstance: MonitorCriteriaInstance = - new MonitorCriteriaInstance(); - - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.All, - filters: [ - { - checkOn: CheckOn.IsValidCertificate, - filterType: FilterType.True, - value: undefined, - }, - ], - incidents: [], - changeMonitorStatus: true, - createIncidents: false, - name: `Check if ${arg.monitorName} is online`, - description: `This criteria checks if the ${arg.monitorName} is online`, - }; - - return monitorCriteriaInstance; - } - - if ( - arg.monitorType === MonitorType.CustomJavaScriptCode || - arg.monitorType === MonitorType.SyntheticMonitor - ) { - const monitorCriteriaInstance: MonitorCriteriaInstance = - new MonitorCriteriaInstance(); - - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.All, - filters: [ - { - checkOn: CheckOn.Error, - filterType: FilterType.IsEmpty, - value: undefined, - }, - ], - incidents: [], - changeMonitorStatus: true, - createIncidents: false, - name: `Check if ${arg.monitorName} is online`, - description: `This criteria checks if the ${arg.monitorName} is online`, - }; - - return monitorCriteriaInstance; - } - - if (arg.monitorType === MonitorType.Server) { - const monitorCriteriaInstance: MonitorCriteriaInstance = - new MonitorCriteriaInstance(); - - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.All, - filters: [ - { - checkOn: CheckOn.IsOnline, - filterType: FilterType.True, - value: undefined, - }, - ], - incidents: [], - changeMonitorStatus: true, - createIncidents: false, - name: `Check if ${arg.monitorName} is online`, - description: `This criteria checks if the ${arg.monitorName} is online`, - }; - - return monitorCriteriaInstance; - } - - if ( - arg.monitorType === MonitorType.Website || - arg.monitorType === MonitorType.API || - arg.monitorType === MonitorType.Ping || - arg.monitorType === MonitorType.IP || - arg.monitorType === MonitorType.Port - ) { - const monitorCriteriaInstance: MonitorCriteriaInstance = - new MonitorCriteriaInstance(); - - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.All, - filters: [ - { - checkOn: CheckOn.IsOnline, - filterType: FilterType.True, - value: undefined, - }, - ], - incidents: [], - changeMonitorStatus: true, - createIncidents: false, - name: `Check if ${arg.monitorName} is online`, - description: `This criteria checks if the ${arg.monitorName} is online`, - }; - - if ( - arg.monitorType === MonitorType.Website || - arg.monitorType === MonitorType.API - ) { - monitorCriteriaInstance.data.filters.push({ - checkOn: CheckOn.ResponseStatusCode, - filterType: FilterType.EqualTo, - value: 200, - }); - } - - return monitorCriteriaInstance; - } - - return null; + return monitorCriteriaInstance; } - public static getDefaultOfflineMonitorCriteriaInstance(arg: { - monitorType: MonitorType; - monitorStatusId: ObjectID; - incidentSeverityId: ObjectID; - monitorName: string; - }): MonitorCriteriaInstance { - const monitorCriteriaInstance: MonitorCriteriaInstance = - new MonitorCriteriaInstance(); + if ( + arg.monitorType === MonitorType.CustomJavaScriptCode || + arg.monitorType === MonitorType.SyntheticMonitor + ) { + const monitorCriteriaInstance: MonitorCriteriaInstance = + new MonitorCriteriaInstance(); - if ( - arg.monitorType === MonitorType.Ping || - arg.monitorType === MonitorType.IP || - arg.monitorType === MonitorType.Port || - arg.monitorType === MonitorType.Server - ) { - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.Any, - filters: [ - { - checkOn: CheckOn.IsOnline, - filterType: FilterType.False, - value: undefined, - }, - ], - incidents: [ - { - title: `${arg.monitorName} is offline`, - description: `${arg.monitorName} is currently offline.`, - incidentSeverityId: arg.incidentSeverityId, - autoResolveIncident: true, - id: ObjectID.generate().toString(), - onCallPolicyIds: [], - }, - ], - changeMonitorStatus: true, - createIncidents: true, - name: `Check if ${arg.monitorName} is offline`, - description: `This criteria checks if the ${arg.monitorName} is offline`, - }; - } + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.All, + filters: [ + { + checkOn: CheckOn.Error, + filterType: FilterType.IsEmpty, + value: undefined, + }, + ], + incidents: [], + changeMonitorStatus: true, + createIncidents: false, + name: `Check if ${arg.monitorName} is online`, + description: `This criteria checks if the ${arg.monitorName} is online`, + }; - if ( - arg.monitorType === MonitorType.API || - arg.monitorType === MonitorType.Website - ) { - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.Any, - filters: [ - { - checkOn: CheckOn.IsOnline, - filterType: FilterType.False, - value: undefined, - }, - { - checkOn: CheckOn.ResponseStatusCode, - filterType: FilterType.NotEqualTo, - value: 200, - }, - ], - incidents: [ - { - title: `${arg.monitorName} is offline`, - description: `${arg.monitorName} is currently offline.`, - incidentSeverityId: arg.incidentSeverityId, - autoResolveIncident: true, - id: ObjectID.generate().toString(), - onCallPolicyIds: [], - }, - ], - changeMonitorStatus: true, - createIncidents: true, - name: `Check if ${arg.monitorName} is offline`, - description: `This criteria checks if the ${arg.monitorName} is offline`, - }; - } - - if (arg.monitorType === MonitorType.IncomingRequest) { - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.Any, - filters: [ - { - checkOn: CheckOn.IncomingRequest, - filterType: FilterType.NotRecievedInMinutes, - value: 30, // if the request is not recieved in 30 minutes, then the monitor is offline - }, - ], - incidents: [ - { - title: `${arg.monitorName} is offline`, - description: `${arg.monitorName} is currently offline.`, - incidentSeverityId: arg.incidentSeverityId, - autoResolveIncident: true, - id: ObjectID.generate().toString(), - onCallPolicyIds: [], - }, - ], - changeMonitorStatus: true, - createIncidents: true, - name: `Check if ${arg.monitorName} is offline`, - description: `This criteria checks if the ${arg.monitorName} is offline`, - }; - } - - if ( - arg.monitorType === MonitorType.CustomJavaScriptCode || - arg.monitorType === MonitorType.SyntheticMonitor - ) { - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.Any, - filters: [ - { - checkOn: CheckOn.Error, - filterType: FilterType.IsNotEmpty, - value: undefined, - }, - ], - incidents: [ - { - title: `${arg.monitorName} is offline`, - description: `${arg.monitorName} is currently offline.`, - incidentSeverityId: arg.incidentSeverityId, - autoResolveIncident: true, - id: ObjectID.generate().toString(), - onCallPolicyIds: [], - }, - ], - changeMonitorStatus: true, - createIncidents: true, - name: `Check if ${arg.monitorName} is offline`, - description: `This criteria checks if the ${arg.monitorName} is offline`, - }; - } - - if (arg.monitorType === MonitorType.SSLCertificate) { - monitorCriteriaInstance.data = { - id: ObjectID.generate().toString(), - monitorStatusId: arg.monitorStatusId, - filterCondition: FilterCondition.Any, - filters: [ - { - checkOn: CheckOn.IsNotAValidCertificate, - filterType: FilterType.True, - value: undefined, - }, - ], - incidents: [ - { - title: `${arg.monitorName} is offline`, - description: `${arg.monitorName} is currently offline.`, - incidentSeverityId: arg.incidentSeverityId, - autoResolveIncident: true, - id: ObjectID.generate().toString(), - onCallPolicyIds: [], - }, - ], - changeMonitorStatus: true, - createIncidents: true, - name: `Check if ${arg.monitorName} is offline`, - description: `This criteria checks if the ${arg.monitorName} is offline`, - }; - } - - return monitorCriteriaInstance; + return monitorCriteriaInstance; } - public static getNewMonitorCriteriaInstanceAsJSON(): JSONObject { - return { - id: ObjectID.generate().toString(), - monitorStatusId: undefined, - filterCondition: FilterCondition.All, - filters: [ - { - checkOn: CheckOn.IsOnline, - filterType: FilterType.True, - value: undefined, - }, - ], - incidents: [], - name: '', - description: '', - createIncidents: false, - changeMonitorStatus: false, - }; + if (arg.monitorType === MonitorType.Server) { + const monitorCriteriaInstance: MonitorCriteriaInstance = + new MonitorCriteriaInstance(); + + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.All, + filters: [ + { + checkOn: CheckOn.IsOnline, + filterType: FilterType.True, + value: undefined, + }, + ], + incidents: [], + changeMonitorStatus: true, + createIncidents: false, + name: `Check if ${arg.monitorName} is online`, + description: `This criteria checks if the ${arg.monitorName} is online`, + }; + + return monitorCriteriaInstance; } - public static getValidationError( - value: MonitorCriteriaInstance, - monitorType: MonitorType - ): string | null { - if (!value.data) { - return 'Monitor Step is required'; - } + if ( + arg.monitorType === MonitorType.Website || + arg.monitorType === MonitorType.API || + arg.monitorType === MonitorType.Ping || + arg.monitorType === MonitorType.IP || + arg.monitorType === MonitorType.Port + ) { + const monitorCriteriaInstance: MonitorCriteriaInstance = + new MonitorCriteriaInstance(); - if (value.data.filters.length === 0) { - return 'Monitor Criteria filter is required'; - } + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.All, + filters: [ + { + checkOn: CheckOn.IsOnline, + filterType: FilterType.True, + value: undefined, + }, + ], + incidents: [], + changeMonitorStatus: true, + createIncidents: false, + name: `Check if ${arg.monitorName} is online`, + description: `This criteria checks if the ${arg.monitorName} is online`, + }; - if (!value.data.name) { - return 'Monitor Criteria name is required'; - } - - if (!value.data.description) { - return 'Monitor Criteria description is required'; - } - - for (const incident of value.data.incidents) { - if (!incident) { - continue; - } - - if (!incident.title) { - return 'Monitor Criteria incident title is required'; - } - - if (!incident.description) { - return 'Monitor Criteria incident description is required'; - } - - if (!incident.incidentSeverityId) { - return 'Monitor Criteria incident severity is required'; - } - } - - for (const filter of value.data.filters) { - if (!filter.checkOn) { - return 'Monitor Criteria filter check on is required'; - } - - if ( - monitorType === MonitorType.Ping && - filter.checkOn !== CheckOn.IsOnline && - filter.checkOn !== CheckOn.ResponseTime - ) { - return ( - 'Ping Monitor cannot have filter criteria: ' + - filter.checkOn - ); - } - - if ( - filter.checkOn === CheckOn.DiskUsagePercent && - !filter.serverMonitorOptions?.diskPath - ) { - return 'Disk Path is required for Disk Usage Percent'; - } - } - - return null; - } - - public setName(name: string): MonitorCriteriaInstance { - if (this.data) { - this.data.name = name; - } - - return this; - } - - public setDescription(description: string): MonitorCriteriaInstance { - if (this.data) { - this.data.description = description; - } - - return this; - } - - public static clone( - monitorCriteriaInstance: MonitorCriteriaInstance - ): MonitorCriteriaInstance { - return MonitorCriteriaInstance.fromJSON( - monitorCriteriaInstance.toJSON() - ); - } - - public setMonitorStatusId( - monitorStatusId: ObjectID | undefined - ): MonitorCriteriaInstance { - if (this.data) { - this.data.monitorStatusId = monitorStatusId; - } - - return this; - } - - public setFilterCondition( - filterCondition: FilterCondition - ): MonitorCriteriaInstance { - if (this.data) { - this.data.filterCondition = filterCondition; - } - - return this; - } - - public setFilters(filters: Array<CriteriaFilter>): MonitorCriteriaInstance { - if (this.data) { - this.data.filters = filters; - } - - return this; - } - - public setIncidents( - incidents: Array<CriteriaIncident> - ): MonitorCriteriaInstance { - if (this.data) { - this.data.incidents = [...incidents]; - } - - return this; - } - - public setChangeMonitorStatus( - changeMonitorStatus: boolean | undefined - ): MonitorCriteriaInstance { - if (this.data) { - this.data.changeMonitorStatus = changeMonitorStatus; - } - - return this; - } - - public setCreateIncidents( - createIncidents: boolean | undefined - ): MonitorCriteriaInstance { - if (this.data) { - this.data.createIncidents = createIncidents; - } - - return this; - } - - public override toJSON(): JSONObject { - if (!this.data) { - return MonitorCriteriaInstance.getNewMonitorCriteriaInstanceAsJSON(); - } - - return JSONFunctions.serialize({ - _type: ObjectType.MonitorCriteriaInstance, - value: { - id: this.data.id, - monitorStatusId: this.data.monitorStatusId?.toString(), - filterCondition: this.data.filterCondition, - filters: this.data.filters, - incidents: this.data.incidents, - changeMonitorStatus: this.data.changeMonitorStatus, - createIncidents: this.data.createIncidents, - name: this.data.name, - description: this.data.description, - } as any, + if ( + arg.monitorType === MonitorType.Website || + arg.monitorType === MonitorType.API + ) { + monitorCriteriaInstance.data.filters.push({ + checkOn: CheckOn.ResponseStatusCode, + filterType: FilterType.EqualTo, + value: 200, }); + } + + return monitorCriteriaInstance; } - public static override fromJSON(json: JSONObject): MonitorCriteriaInstance { - if (json instanceof MonitorCriteriaInstance) { - return json; - } + return null; + } - if (!json) { - throw new BadDataException('json is null'); - } + public static getDefaultOfflineMonitorCriteriaInstance(arg: { + monitorType: MonitorType; + monitorStatusId: ObjectID; + incidentSeverityId: ObjectID; + monitorName: string; + }): MonitorCriteriaInstance { + const monitorCriteriaInstance: MonitorCriteriaInstance = + new MonitorCriteriaInstance(); - if (!json['_type']) { - throw new BadDataException('json._type is null'); - } - - if (json['_type'] !== ObjectType.MonitorCriteriaInstance) { - throw new BadDataException( - 'json._type should be MonitorCriteriaInstance' - ); - } - - if (!json['value']) { - throw new BadDataException('json.value is null'); - } - - json = json['value'] as JSONObject; - - if (!json['filterCondition']) { - throw new BadDataException('json.filterCondition is null'); - } - - if (!json['filters']) { - throw new BadDataException('json.filters is null'); - } - - if (!Array.isArray(json['filters'])) { - throw new BadDataException('json.filters should be an array'); - } - - if (!json['incidents']) { - throw new BadDataException('json.incidents is null'); - } - - if (!Array.isArray(json['incidents'])) { - throw new BadDataException('json.incidents should be an array'); - } - - let monitorStatusId: ObjectID | undefined = undefined; - - if ( - json['monitorStatusId'] && - typeof json['monitorStatusId'] === Typeof.String - ) { - monitorStatusId = new ObjectID(json['monitorStatusId'] as string); - } else if ( - json['monitorStatusId'] && - (json['monitorStatusId'] as JSONObject)['value'] !== null - ) { - monitorStatusId = new ObjectID( - (json['monitorStatusId'] as JSONObject)['value'] as string - ); - } - - const filterCondition: FilterCondition = json[ - 'filterCondition' - ] as FilterCondition; - - const filters: Array<CriteriaFilter> = []; - - const incidents: Array<CriteriaIncident> = []; - - for (const filter of json['filters']) { - filters.push({ ...(filter as any) }); - } - - for (const incident of json['incidents']) { - incidents.push({ ...(incident as any) }); - } - - const monitorCriteriaInstance: MonitorCriteriaInstance = - new MonitorCriteriaInstance(); - - monitorCriteriaInstance.data = JSONFunctions.deserialize({ - id: (json['id'] as string) || ObjectID.generate().toString(), - monitorStatusId, - filterCondition, - changeMonitorStatus: - (json['changeMonitorStatus'] as boolean) || false, - createIncidents: (json['createIncidents'] as boolean) || false, - filters: filters as any, - incidents: incidents as any, - name: (json['name'] as string) || '', - description: (json['description'] as string) || '', - }) as any; - - return monitorCriteriaInstance; + if ( + arg.monitorType === MonitorType.Ping || + arg.monitorType === MonitorType.IP || + arg.monitorType === MonitorType.Port || + arg.monitorType === MonitorType.Server + ) { + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.Any, + filters: [ + { + checkOn: CheckOn.IsOnline, + filterType: FilterType.False, + value: undefined, + }, + ], + incidents: [ + { + title: `${arg.monitorName} is offline`, + description: `${arg.monitorName} is currently offline.`, + incidentSeverityId: arg.incidentSeverityId, + autoResolveIncident: true, + id: ObjectID.generate().toString(), + onCallPolicyIds: [], + }, + ], + changeMonitorStatus: true, + createIncidents: true, + name: `Check if ${arg.monitorName} is offline`, + description: `This criteria checks if the ${arg.monitorName} is offline`, + }; } - public static isValid(_json: JSONObject): boolean { - return true; + if ( + arg.monitorType === MonitorType.API || + arg.monitorType === MonitorType.Website + ) { + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.Any, + filters: [ + { + checkOn: CheckOn.IsOnline, + filterType: FilterType.False, + value: undefined, + }, + { + checkOn: CheckOn.ResponseStatusCode, + filterType: FilterType.NotEqualTo, + value: 200, + }, + ], + incidents: [ + { + title: `${arg.monitorName} is offline`, + description: `${arg.monitorName} is currently offline.`, + incidentSeverityId: arg.incidentSeverityId, + autoResolveIncident: true, + id: ObjectID.generate().toString(), + onCallPolicyIds: [], + }, + ], + changeMonitorStatus: true, + createIncidents: true, + name: `Check if ${arg.monitorName} is offline`, + description: `This criteria checks if the ${arg.monitorName} is offline`, + }; } - protected static override toDatabase( - value: MonitorCriteriaInstance | FindOperator<MonitorCriteriaInstance> - ): JSONObject | null { - if (value && value instanceof MonitorCriteriaInstance) { - return (value as MonitorCriteriaInstance).toJSON(); - } else if (value) { - return JSONFunctions.serialize(value as any); - } - - return null; + if (arg.monitorType === MonitorType.IncomingRequest) { + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.Any, + filters: [ + { + checkOn: CheckOn.IncomingRequest, + filterType: FilterType.NotRecievedInMinutes, + value: 30, // if the request is not recieved in 30 minutes, then the monitor is offline + }, + ], + incidents: [ + { + title: `${arg.monitorName} is offline`, + description: `${arg.monitorName} is currently offline.`, + incidentSeverityId: arg.incidentSeverityId, + autoResolveIncident: true, + id: ObjectID.generate().toString(), + onCallPolicyIds: [], + }, + ], + changeMonitorStatus: true, + createIncidents: true, + name: `Check if ${arg.monitorName} is offline`, + description: `This criteria checks if the ${arg.monitorName} is offline`, + }; } - protected static override fromDatabase( - value: JSONObject - ): MonitorCriteriaInstance | null { - if (value) { - return MonitorCriteriaInstance.fromJSON(value); - } - - return null; + if ( + arg.monitorType === MonitorType.CustomJavaScriptCode || + arg.monitorType === MonitorType.SyntheticMonitor + ) { + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.Any, + filters: [ + { + checkOn: CheckOn.Error, + filterType: FilterType.IsNotEmpty, + value: undefined, + }, + ], + incidents: [ + { + title: `${arg.monitorName} is offline`, + description: `${arg.monitorName} is currently offline.`, + incidentSeverityId: arg.incidentSeverityId, + autoResolveIncident: true, + id: ObjectID.generate().toString(), + onCallPolicyIds: [], + }, + ], + changeMonitorStatus: true, + createIncidents: true, + name: `Check if ${arg.monitorName} is offline`, + description: `This criteria checks if the ${arg.monitorName} is offline`, + }; } - public override toString(): string { - return JSON.stringify(this.toJSON()); + if (arg.monitorType === MonitorType.SSLCertificate) { + monitorCriteriaInstance.data = { + id: ObjectID.generate().toString(), + monitorStatusId: arg.monitorStatusId, + filterCondition: FilterCondition.Any, + filters: [ + { + checkOn: CheckOn.IsNotAValidCertificate, + filterType: FilterType.True, + value: undefined, + }, + ], + incidents: [ + { + title: `${arg.monitorName} is offline`, + description: `${arg.monitorName} is currently offline.`, + incidentSeverityId: arg.incidentSeverityId, + autoResolveIncident: true, + id: ObjectID.generate().toString(), + onCallPolicyIds: [], + }, + ], + changeMonitorStatus: true, + createIncidents: true, + name: `Check if ${arg.monitorName} is offline`, + description: `This criteria checks if the ${arg.monitorName} is offline`, + }; } + + return monitorCriteriaInstance; + } + + public static getNewMonitorCriteriaInstanceAsJSON(): JSONObject { + return { + id: ObjectID.generate().toString(), + monitorStatusId: undefined, + filterCondition: FilterCondition.All, + filters: [ + { + checkOn: CheckOn.IsOnline, + filterType: FilterType.True, + value: undefined, + }, + ], + incidents: [], + name: "", + description: "", + createIncidents: false, + changeMonitorStatus: false, + }; + } + + public static getValidationError( + value: MonitorCriteriaInstance, + monitorType: MonitorType, + ): string | null { + if (!value.data) { + return "Monitor Step is required"; + } + + if (value.data.filters.length === 0) { + return "Monitor Criteria filter is required"; + } + + if (!value.data.name) { + return "Monitor Criteria name is required"; + } + + if (!value.data.description) { + return "Monitor Criteria description is required"; + } + + for (const incident of value.data.incidents) { + if (!incident) { + continue; + } + + if (!incident.title) { + return "Monitor Criteria incident title is required"; + } + + if (!incident.description) { + return "Monitor Criteria incident description is required"; + } + + if (!incident.incidentSeverityId) { + return "Monitor Criteria incident severity is required"; + } + } + + for (const filter of value.data.filters) { + if (!filter.checkOn) { + return "Monitor Criteria filter check on is required"; + } + + if ( + monitorType === MonitorType.Ping && + filter.checkOn !== CheckOn.IsOnline && + filter.checkOn !== CheckOn.ResponseTime + ) { + return "Ping Monitor cannot have filter criteria: " + filter.checkOn; + } + + if ( + filter.checkOn === CheckOn.DiskUsagePercent && + !filter.serverMonitorOptions?.diskPath + ) { + return "Disk Path is required for Disk Usage Percent"; + } + } + + return null; + } + + public setName(name: string): MonitorCriteriaInstance { + if (this.data) { + this.data.name = name; + } + + return this; + } + + public setDescription(description: string): MonitorCriteriaInstance { + if (this.data) { + this.data.description = description; + } + + return this; + } + + public static clone( + monitorCriteriaInstance: MonitorCriteriaInstance, + ): MonitorCriteriaInstance { + return MonitorCriteriaInstance.fromJSON(monitorCriteriaInstance.toJSON()); + } + + public setMonitorStatusId( + monitorStatusId: ObjectID | undefined, + ): MonitorCriteriaInstance { + if (this.data) { + this.data.monitorStatusId = monitorStatusId; + } + + return this; + } + + public setFilterCondition( + filterCondition: FilterCondition, + ): MonitorCriteriaInstance { + if (this.data) { + this.data.filterCondition = filterCondition; + } + + return this; + } + + public setFilters(filters: Array<CriteriaFilter>): MonitorCriteriaInstance { + if (this.data) { + this.data.filters = filters; + } + + return this; + } + + public setIncidents( + incidents: Array<CriteriaIncident>, + ): MonitorCriteriaInstance { + if (this.data) { + this.data.incidents = [...incidents]; + } + + return this; + } + + public setChangeMonitorStatus( + changeMonitorStatus: boolean | undefined, + ): MonitorCriteriaInstance { + if (this.data) { + this.data.changeMonitorStatus = changeMonitorStatus; + } + + return this; + } + + public setCreateIncidents( + createIncidents: boolean | undefined, + ): MonitorCriteriaInstance { + if (this.data) { + this.data.createIncidents = createIncidents; + } + + return this; + } + + public override toJSON(): JSONObject { + if (!this.data) { + return MonitorCriteriaInstance.getNewMonitorCriteriaInstanceAsJSON(); + } + + return JSONFunctions.serialize({ + _type: ObjectType.MonitorCriteriaInstance, + value: { + id: this.data.id, + monitorStatusId: this.data.monitorStatusId?.toString(), + filterCondition: this.data.filterCondition, + filters: this.data.filters, + incidents: this.data.incidents, + changeMonitorStatus: this.data.changeMonitorStatus, + createIncidents: this.data.createIncidents, + name: this.data.name, + description: this.data.description, + } as any, + }); + } + + public static override fromJSON(json: JSONObject): MonitorCriteriaInstance { + if (json instanceof MonitorCriteriaInstance) { + return json; + } + + if (!json) { + throw new BadDataException("json is null"); + } + + if (!json["_type"]) { + throw new BadDataException("json._type is null"); + } + + if (json["_type"] !== ObjectType.MonitorCriteriaInstance) { + throw new BadDataException( + "json._type should be MonitorCriteriaInstance", + ); + } + + if (!json["value"]) { + throw new BadDataException("json.value is null"); + } + + json = json["value"] as JSONObject; + + if (!json["filterCondition"]) { + throw new BadDataException("json.filterCondition is null"); + } + + if (!json["filters"]) { + throw new BadDataException("json.filters is null"); + } + + if (!Array.isArray(json["filters"])) { + throw new BadDataException("json.filters should be an array"); + } + + if (!json["incidents"]) { + throw new BadDataException("json.incidents is null"); + } + + if (!Array.isArray(json["incidents"])) { + throw new BadDataException("json.incidents should be an array"); + } + + let monitorStatusId: ObjectID | undefined = undefined; + + if ( + json["monitorStatusId"] && + typeof json["monitorStatusId"] === Typeof.String + ) { + monitorStatusId = new ObjectID(json["monitorStatusId"] as string); + } else if ( + json["monitorStatusId"] && + (json["monitorStatusId"] as JSONObject)["value"] !== null + ) { + monitorStatusId = new ObjectID( + (json["monitorStatusId"] as JSONObject)["value"] as string, + ); + } + + const filterCondition: FilterCondition = json[ + "filterCondition" + ] as FilterCondition; + + const filters: Array<CriteriaFilter> = []; + + const incidents: Array<CriteriaIncident> = []; + + for (const filter of json["filters"]) { + filters.push({ ...(filter as any) }); + } + + for (const incident of json["incidents"]) { + incidents.push({ ...(incident as any) }); + } + + const monitorCriteriaInstance: MonitorCriteriaInstance = + new MonitorCriteriaInstance(); + + monitorCriteriaInstance.data = JSONFunctions.deserialize({ + id: (json["id"] as string) || ObjectID.generate().toString(), + monitorStatusId, + filterCondition, + changeMonitorStatus: (json["changeMonitorStatus"] as boolean) || false, + createIncidents: (json["createIncidents"] as boolean) || false, + filters: filters as any, + incidents: incidents as any, + name: (json["name"] as string) || "", + description: (json["description"] as string) || "", + }) as any; + + return monitorCriteriaInstance; + } + + public static isValid(_json: JSONObject): boolean { + return true; + } + + protected static override toDatabase( + value: MonitorCriteriaInstance | FindOperator<MonitorCriteriaInstance>, + ): JSONObject | null { + if (value && value instanceof MonitorCriteriaInstance) { + return (value as MonitorCriteriaInstance).toJSON(); + } else if (value) { + return JSONFunctions.serialize(value as any); + } + + return null; + } + + protected static override fromDatabase( + value: JSONObject, + ): MonitorCriteriaInstance | null { + if (value) { + return MonitorCriteriaInstance.fromJSON(value); + } + + return null; + } + + public override toString(): string { + return JSON.stringify(this.toJSON()); + } } diff --git a/Common/Types/Monitor/MonitorStep.ts b/Common/Types/Monitor/MonitorStep.ts index 4a7eb13588..aa235b2f01 100644 --- a/Common/Types/Monitor/MonitorStep.ts +++ b/Common/Types/Monitor/MonitorStep.ts @@ -1,370 +1,365 @@ -import HTTPMethod from '../API/HTTPMethod'; -import Hostname from '../API/Hostname'; -import URL from '../API/URL'; -import DatabaseProperty from '../Database/DatabaseProperty'; -import Dictionary from '../Dictionary'; -import BadDataException from '../Exception/BadDataException'; -import IP from '../IP/IP'; -import { JSONObject, ObjectType } from '../JSON'; -import JSONFunctions from '../JSONFunctions'; -import ObjectID from '../ObjectID'; -import Port from '../Port'; -import MonitorCriteria from './MonitorCriteria'; -import MonitorType from './MonitorType'; -import BrowserType from './SyntheticMonitors//BrowserType'; -import ScreenSizeType from './SyntheticMonitors/ScreenSizeType'; -import { FindOperator } from 'typeorm'; +import HTTPMethod from "../API/HTTPMethod"; +import Hostname from "../API/Hostname"; +import URL from "../API/URL"; +import DatabaseProperty from "../Database/DatabaseProperty"; +import Dictionary from "../Dictionary"; +import BadDataException from "../Exception/BadDataException"; +import IP from "../IP/IP"; +import { JSONObject, ObjectType } from "../JSON"; +import JSONFunctions from "../JSONFunctions"; +import ObjectID from "../ObjectID"; +import Port from "../Port"; +import MonitorCriteria from "./MonitorCriteria"; +import MonitorType from "./MonitorType"; +import BrowserType from "./SyntheticMonitors//BrowserType"; +import ScreenSizeType from "./SyntheticMonitors/ScreenSizeType"; +import { FindOperator } from "typeorm"; export interface MonitorStepType { - id: string; - monitorDestination?: URL | IP | Hostname | undefined; + id: string; + monitorDestination?: URL | IP | Hostname | undefined; - monitorCriteria: MonitorCriteria; + monitorCriteria: MonitorCriteria; - // this is for API monitor. - requestType: HTTPMethod; - requestHeaders?: Dictionary<string> | undefined; - requestBody?: string | undefined; + // this is for API monitor. + requestType: HTTPMethod; + requestHeaders?: Dictionary<string> | undefined; + requestBody?: string | undefined; - // this is for port monitors. - monitorDestinationPort?: Port | undefined; + // this is for port monitors. + monitorDestinationPort?: Port | undefined; - // this is for custom code monitors or synthetic monitors. - customCode?: string | undefined; + // this is for custom code monitors or synthetic monitors. + customCode?: string | undefined; - // this is for synthetic monitors. - screenSizeTypes?: Array<ScreenSizeType> | undefined; - browserTypes?: Array<BrowserType> | undefined; + // this is for synthetic monitors. + screenSizeTypes?: Array<ScreenSizeType> | undefined; + browserTypes?: Array<BrowserType> | undefined; } export default class MonitorStep extends DatabaseProperty { - public data: MonitorStepType | undefined = undefined; + public data: MonitorStepType | undefined = undefined; - public constructor() { - super(); + public constructor() { + super(); - this.data = { - id: ObjectID.generate().toString(), - monitorDestination: undefined, - monitorDestinationPort: undefined, - monitorCriteria: new MonitorCriteria(), - requestType: HTTPMethod.GET, - requestHeaders: undefined, - requestBody: undefined, - customCode: undefined, - screenSizeTypes: undefined, - browserTypes: undefined, - }; + this.data = { + id: ObjectID.generate().toString(), + monitorDestination: undefined, + monitorDestinationPort: undefined, + monitorCriteria: new MonitorCriteria(), + requestType: HTTPMethod.GET, + requestHeaders: undefined, + requestBody: undefined, + customCode: undefined, + screenSizeTypes: undefined, + browserTypes: undefined, + }; + } + + public static getDefaultMonitorStep(arg: { + monitorName: string; + monitorType: MonitorType; + onlineMonitorStatusId: ObjectID; + offlineMonitorStatusId: ObjectID; + defaultIncidentSeverityId: ObjectID; + }): MonitorStep { + const monitorStep: MonitorStep = new MonitorStep(); + + monitorStep.data = { + id: ObjectID.generate().toString(), + monitorDestination: undefined, + monitorDestinationPort: undefined, + monitorCriteria: MonitorCriteria.getDefaultMonitorCriteria(arg), + requestType: HTTPMethod.GET, + requestHeaders: undefined, + requestBody: undefined, + customCode: undefined, + screenSizeTypes: undefined, + browserTypes: undefined, + }; + + return monitorStep; + } + + public get id(): ObjectID { + return new ObjectID(this.data?.id as string); + } + + public set id(v: ObjectID) { + this.data!.id = v.toString(); + } + + public setRequestType(requestType: HTTPMethod): MonitorStep { + this.data!.requestType = requestType; + return this; + } + + public setRequestHeaders(requestHeaders: Dictionary<string>): MonitorStep { + this.data!.requestHeaders = requestHeaders; + return this; + } + + public static clone(monitorStep: MonitorStep): MonitorStep { + return MonitorStep.fromJSON(monitorStep.toJSON()); + } + + public setRequestBody(requestBody: string): MonitorStep { + this.data!.requestBody = requestBody; + return this; + } + + public setMonitorDestination( + monitorDestination: URL | IP | Hostname, + ): MonitorStep { + this.data!.monitorDestination = monitorDestination; + return this; + } + + public setPort(monitorDestinationPort: Port): MonitorStep { + this.data!.monitorDestinationPort = monitorDestinationPort; + return this; + } + + public setScreenSizeTypes( + screenSizeTypes: Array<ScreenSizeType>, + ): MonitorStep { + this.data!.screenSizeTypes = screenSizeTypes; + return this; + } + + public setBrowserTypes(browserTypes: Array<BrowserType>): MonitorStep { + this.data!.browserTypes = browserTypes; + return this; + } + + public setCustomCode(customCode: string): MonitorStep { + this.data!.customCode = customCode; + return this; + } + + public setMonitorCriteria(monitorCriteria: MonitorCriteria): MonitorStep { + this.data!.monitorCriteria = monitorCriteria; + return this; + } + + public static getNewMonitorStepAsJSON(): JSONObject { + return { + _type: ObjectType.MonitorStep, + value: { + id: ObjectID.generate().toString(), + monitorDestination: undefined, + monitorDestinationPort: undefined, + monitorCriteria: MonitorCriteria.getNewMonitorCriteriaAsJSON(), + requestType: HTTPMethod.GET, + requestHeaders: undefined, + requestBody: undefined, + customCode: undefined, + screenSizeTypes: undefined, + browserTypes: undefined, + }, + }; + } + + public static getValidationError( + value: MonitorStep, + monitorType: MonitorType, + ): string | null { + if (!value.data) { + return "Monitor Step is required."; } - public static getDefaultMonitorStep(arg: { - monitorName: string; - monitorType: MonitorType; - onlineMonitorStatusId: ObjectID; - offlineMonitorStatusId: ObjectID; - defaultIncidentSeverityId: ObjectID; - }): MonitorStep { - const monitorStep: MonitorStep = new MonitorStep(); - - monitorStep.data = { - id: ObjectID.generate().toString(), - monitorDestination: undefined, - monitorDestinationPort: undefined, - monitorCriteria: MonitorCriteria.getDefaultMonitorCriteria(arg), - requestType: HTTPMethod.GET, - requestHeaders: undefined, - requestBody: undefined, - customCode: undefined, - screenSizeTypes: undefined, - browserTypes: undefined, - }; - - return monitorStep; + // If the monitor type is incoming request, then the monitor destination is not required + if ( + !value.data.monitorDestination && + (monitorType === MonitorType.Port || + monitorType === MonitorType.API || + monitorType === MonitorType.Ping || + monitorType === MonitorType.Website || + monitorType === MonitorType.IP || + monitorType === MonitorType.SSLCertificate) + ) { + return "Monitor Destination is required."; } - public get id(): ObjectID { - return new ObjectID(this.data?.id as string); + if ( + !value.data.customCode && + (monitorType === MonitorType.CustomJavaScriptCode || + monitorType === MonitorType.SyntheticMonitor) + ) { + if (monitorType === MonitorType.CustomJavaScriptCode) { + return "Custom Code is required"; + } + return "Playwright code is required."; } - public set id(v: ObjectID) { - this.data!.id = v.toString(); + if (!value.data.monitorCriteria) { + return "Monitor Criteria is required"; } - public setRequestType(requestType: HTTPMethod): MonitorStep { - this.data!.requestType = requestType; - return this; + if ( + !MonitorCriteria.getValidationError( + value.data.monitorCriteria, + monitorType, + ) + ) { + return MonitorCriteria.getValidationError( + value.data.monitorCriteria, + monitorType, + ); } - public setRequestHeaders(requestHeaders: Dictionary<string>): MonitorStep { - this.data!.requestHeaders = requestHeaders; - return this; + if (!value.data.requestType && monitorType === MonitorType.API) { + return "Request Type is required"; } - public static clone(monitorStep: MonitorStep): MonitorStep { - return MonitorStep.fromJSON(monitorStep.toJSON()); + if ( + monitorType === MonitorType.Port && + !value.data.monitorDestinationPort + ) { + return "Port is required"; } - public setRequestBody(requestBody: string): MonitorStep { - this.data!.requestBody = requestBody; - return this; + return null; + } + + public override toJSON(): JSONObject { + if (this.data) { + return JSONFunctions.serialize({ + _type: ObjectType.MonitorStep, + value: { + id: this.data.id, + monitorDestination: + this.data?.monitorDestination?.toJSON() || undefined, + monitorDestinationPort: + this.data?.monitorDestinationPort?.toJSON() || undefined, + monitorCriteria: this.data.monitorCriteria.toJSON(), + requestType: this.data.requestType, + requestHeaders: this.data.requestHeaders || undefined, + requestBody: this.data.requestBody || undefined, + customCode: this.data.customCode || undefined, + screenSizeTypes: this.data.screenSizeTypes || undefined, + browserTypes: this.data.browserTypes || undefined, + }, + }); } - public setMonitorDestination( - monitorDestination: URL | IP | Hostname - ): MonitorStep { - this.data!.monitorDestination = monitorDestination; - return this; + return MonitorStep.getNewMonitorStepAsJSON(); + } + + public static override fromJSON(json: JSONObject): MonitorStep { + if (json instanceof MonitorStep) { + return json; } - public setPort(monitorDestinationPort: Port): MonitorStep { - this.data!.monitorDestinationPort = monitorDestinationPort; - return this; + if (!json || json["_type"] !== "MonitorStep") { + throw new BadDataException("Invalid monitor step"); } - public setScreenSizeTypes( - screenSizeTypes: Array<ScreenSizeType> - ): MonitorStep { - this.data!.screenSizeTypes = screenSizeTypes; - return this; + if (!json["value"]) { + throw new BadDataException("Invalid monitor step"); } - public setBrowserTypes(browserTypes: Array<BrowserType>): MonitorStep { - this.data!.browserTypes = browserTypes; - return this; + json = json["value"] as JSONObject; + + let monitorDestination: URL | IP | Hostname | undefined = undefined; + + if ( + json && + json["monitorDestination"] && + (json["monitorDestination"] as JSONObject)["_type"] === ObjectType.URL + ) { + monitorDestination = URL.fromJSON( + json["monitorDestination"] as JSONObject, + ); } - public setCustomCode(customCode: string): MonitorStep { - this.data!.customCode = customCode; - return this; + if ( + json && + json["monitorDestination"] && + (json["monitorDestination"] as JSONObject)["_type"] === + ObjectType.Hostname + ) { + monitorDestination = Hostname.fromJSON( + json["monitorDestination"] as JSONObject, + ); } - public setMonitorCriteria(monitorCriteria: MonitorCriteria): MonitorStep { - this.data!.monitorCriteria = monitorCriteria; - return this; + if ( + json && + json["monitorDestination"] && + (json["monitorDestination"] as JSONObject)["_type"] === ObjectType.IP + ) { + monitorDestination = IP.fromJSON( + json["monitorDestination"] as JSONObject, + ); } - public static getNewMonitorStepAsJSON(): JSONObject { - return { - _type: ObjectType.MonitorStep, - value: { - id: ObjectID.generate().toString(), - monitorDestination: undefined, - monitorDestinationPort: undefined, - monitorCriteria: MonitorCriteria.getNewMonitorCriteriaAsJSON(), - requestType: HTTPMethod.GET, - requestHeaders: undefined, - requestBody: undefined, - customCode: undefined, - screenSizeTypes: undefined, - browserTypes: undefined, - }, - }; + const monitorDestinationPort: Port | undefined = json[ + "monitorDestinationPort" + ] + ? Port.fromJSON(json["monitorDestinationPort"] as JSONObject) + : undefined; + + if (!json["monitorCriteria"]) { + throw new BadDataException("Invalid monitor criteria"); } - public static getValidationError( - value: MonitorStep, - monitorType: MonitorType - ): string | null { - if (!value.data) { - return 'Monitor Step is required.'; - } - - // If the monitor type is incoming request, then the monitor destination is not required - if ( - !value.data.monitorDestination && - (monitorType === MonitorType.Port || - monitorType === MonitorType.API || - monitorType === MonitorType.Ping || - monitorType === MonitorType.Website || - monitorType === MonitorType.IP || - monitorType === MonitorType.SSLCertificate) - ) { - return 'Monitor Destination is required.'; - } - - if ( - !value.data.customCode && - (monitorType === MonitorType.CustomJavaScriptCode || - monitorType === MonitorType.SyntheticMonitor) - ) { - if (monitorType === MonitorType.CustomJavaScriptCode) { - return 'Custom Code is required'; - } - return 'Playwright code is required.'; - } - - if (!value.data.monitorCriteria) { - return 'Monitor Criteria is required'; - } - - if ( - !MonitorCriteria.getValidationError( - value.data.monitorCriteria, - monitorType - ) - ) { - return MonitorCriteria.getValidationError( - value.data.monitorCriteria, - monitorType - ); - } - - if (!value.data.requestType && monitorType === MonitorType.API) { - return 'Request Type is required'; - } - - if ( - monitorType === MonitorType.Port && - !value.data.monitorDestinationPort - ) { - return 'Port is required'; - } - - return null; + if ( + MonitorCriteria.isValid(json["monitorCriteria"] as JSONObject) === false + ) { + throw new BadDataException("Invalid monitor criteria"); } - public override toJSON(): JSONObject { - if (this.data) { - return JSONFunctions.serialize({ - _type: ObjectType.MonitorStep, - value: { - id: this.data.id, - monitorDestination: - this.data?.monitorDestination?.toJSON() || undefined, - monitorDestinationPort: - this.data?.monitorDestinationPort?.toJSON() || - undefined, - monitorCriteria: this.data.monitorCriteria.toJSON(), - requestType: this.data.requestType, - requestHeaders: this.data.requestHeaders || undefined, - requestBody: this.data.requestBody || undefined, - customCode: this.data.customCode || undefined, - screenSizeTypes: this.data.screenSizeTypes || undefined, - browserTypes: this.data.browserTypes || undefined, - }, - }); - } + const monitorStep: MonitorStep = new MonitorStep(); - return MonitorStep.getNewMonitorStepAsJSON(); + monitorStep.data = JSONFunctions.deserialize({ + id: json["id"] as string, + monitorDestination: monitorDestination || undefined, + monitorDestinationPort: monitorDestinationPort || undefined, + monitorCriteria: MonitorCriteria.fromJSON( + json["monitorCriteria"] as JSONObject, + ), + requestType: (json["requestType"] as HTTPMethod) || HTTPMethod.GET, + requestHeaders: + (json["requestHeaders"] as Dictionary<string>) || undefined, + requestBody: (json["requestBody"] as string) || undefined, + customCode: (json["customCode"] as string) || undefined, + screenSizeTypes: + (json["screenSizeTypes"] as Array<ScreenSizeType>) || undefined, + browserTypes: (json["browserTypes"] as Array<BrowserType>) || undefined, + }) as any; + + return monitorStep; + } + + public isValid(): boolean { + return true; + } + + protected static override toDatabase( + value: MonitorStep | FindOperator<MonitorStep>, + ): JSONObject | null { + if (value && value instanceof MonitorStep) { + return (value as MonitorStep).toJSON(); + } else if (value) { + return JSONFunctions.serialize(value as any); } - public static override fromJSON(json: JSONObject): MonitorStep { - if (json instanceof MonitorStep) { - return json; - } + return null; + } - if (!json || json['_type'] !== 'MonitorStep') { - throw new BadDataException('Invalid monitor step'); - } - - if (!json['value']) { - throw new BadDataException('Invalid monitor step'); - } - - json = json['value'] as JSONObject; - - let monitorDestination: URL | IP | Hostname | undefined = undefined; - - if ( - json && - json['monitorDestination'] && - (json['monitorDestination'] as JSONObject)['_type'] === - ObjectType.URL - ) { - monitorDestination = URL.fromJSON( - json['monitorDestination'] as JSONObject - ); - } - - if ( - json && - json['monitorDestination'] && - (json['monitorDestination'] as JSONObject)['_type'] === - ObjectType.Hostname - ) { - monitorDestination = Hostname.fromJSON( - json['monitorDestination'] as JSONObject - ); - } - - if ( - json && - json['monitorDestination'] && - (json['monitorDestination'] as JSONObject)['_type'] === - ObjectType.IP - ) { - monitorDestination = IP.fromJSON( - json['monitorDestination'] as JSONObject - ); - } - - const monitorDestinationPort: Port | undefined = json[ - 'monitorDestinationPort' - ] - ? Port.fromJSON(json['monitorDestinationPort'] as JSONObject) - : undefined; - - if (!json['monitorCriteria']) { - throw new BadDataException('Invalid monitor criteria'); - } - - if ( - MonitorCriteria.isValid(json['monitorCriteria'] as JSONObject) === - false - ) { - throw new BadDataException('Invalid monitor criteria'); - } - - const monitorStep: MonitorStep = new MonitorStep(); - - monitorStep.data = JSONFunctions.deserialize({ - id: json['id'] as string, - monitorDestination: monitorDestination || undefined, - monitorDestinationPort: monitorDestinationPort || undefined, - monitorCriteria: MonitorCriteria.fromJSON( - json['monitorCriteria'] as JSONObject - ), - requestType: (json['requestType'] as HTTPMethod) || HTTPMethod.GET, - requestHeaders: - (json['requestHeaders'] as Dictionary<string>) || undefined, - requestBody: (json['requestBody'] as string) || undefined, - customCode: (json['customCode'] as string) || undefined, - screenSizeTypes: - (json['screenSizeTypes'] as Array<ScreenSizeType>) || undefined, - browserTypes: - (json['browserTypes'] as Array<BrowserType>) || undefined, - }) as any; - - return monitorStep; + protected static override fromDatabase( + value: JSONObject, + ): MonitorStep | null { + if (value) { + return MonitorStep.fromJSON(value); } - public isValid(): boolean { - return true; - } + return null; + } - protected static override toDatabase( - value: MonitorStep | FindOperator<MonitorStep> - ): JSONObject | null { - if (value && value instanceof MonitorStep) { - return (value as MonitorStep).toJSON(); - } else if (value) { - return JSONFunctions.serialize(value as any); - } - - return null; - } - - protected static override fromDatabase( - value: JSONObject - ): MonitorStep | null { - if (value) { - return MonitorStep.fromJSON(value); - } - - return null; - } - - public override toString(): string { - return JSON.stringify(this.toJSON()); - } + public override toString(): string { + return JSON.stringify(this.toJSON()); + } } diff --git a/Common/Types/Monitor/MonitorSteps.ts b/Common/Types/Monitor/MonitorSteps.ts index 36276d37e0..f90945154e 100644 --- a/Common/Types/Monitor/MonitorSteps.ts +++ b/Common/Types/Monitor/MonitorSteps.ts @@ -1,193 +1,188 @@ -import DatabaseProperty from '../Database/DatabaseProperty'; -import BadDataException from '../Exception/BadDataException'; -import { JSONArray, JSONObject, ObjectType } from '../JSON'; -import JSONFunctions from '../JSONFunctions'; -import ObjectID from '../ObjectID'; -import MonitorStep from './MonitorStep'; -import MonitorType from './MonitorType'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "../Database/DatabaseProperty"; +import BadDataException from "../Exception/BadDataException"; +import { JSONArray, JSONObject, ObjectType } from "../JSON"; +import JSONFunctions from "../JSONFunctions"; +import ObjectID from "../ObjectID"; +import MonitorStep from "./MonitorStep"; +import MonitorType from "./MonitorType"; +import { FindOperator } from "typeorm"; export interface MonitorStepsType { - monitorStepsInstanceArray: Array<MonitorStep>; - defaultMonitorStatusId?: ObjectID | undefined; + monitorStepsInstanceArray: Array<MonitorStep>; + defaultMonitorStatusId?: ObjectID | undefined; } export default class MonitorSteps extends DatabaseProperty { - public data: MonitorStepsType | undefined = undefined; + public data: MonitorStepsType | undefined = undefined; - public constructor() { - super(); - this.data = { - monitorStepsInstanceArray: [new MonitorStep()], - defaultMonitorStatusId: undefined, - }; + public constructor() { + super(); + this.data = { + monitorStepsInstanceArray: [new MonitorStep()], + defaultMonitorStatusId: undefined, + }; + } + + public static getNewMonitorStepsAsJSON(): JSONObject { + return { + _type: ObjectType.MonitorSteps, + value: { + monitorStepsInstanceArray: [new MonitorStep().toJSON()], + defaultMonitorStatusId: undefined, + }, + }; + } + + public static getDefaultMonitorSteps(arg: { + defaultMonitorStatusId: ObjectID; + monitorType: MonitorType; + monitorName: string; + onlineMonitorStatusId: ObjectID; + offlineMonitorStatusId: ObjectID; + defaultIncidentSeverityId: ObjectID; + }): MonitorSteps { + const monitorSteps: MonitorSteps = new MonitorSteps(); + + monitorSteps.data = { + monitorStepsInstanceArray: [MonitorStep.getDefaultMonitorStep(arg)], + defaultMonitorStatusId: arg.defaultMonitorStatusId, + }; + + return monitorSteps; + } + + public setMonitorStepsInstanceArray(monitorSteps: Array<MonitorStep>): void { + if (this.data) { + this.data.monitorStepsInstanceArray = monitorSteps; + } + } + + public static clone(monitorSteps: MonitorSteps): MonitorSteps { + return MonitorSteps.fromJSON(monitorSteps.toJSON()); + } + + public setDefaultMonitorStatusId( + monitorStatusId: ObjectID | undefined, + ): MonitorSteps { + if (this.data) { + this.data.defaultMonitorStatusId = monitorStatusId; } - public static getNewMonitorStepsAsJSON(): JSONObject { - return { - _type: ObjectType.MonitorSteps, - value: { - monitorStepsInstanceArray: [new MonitorStep().toJSON()], - defaultMonitorStatusId: undefined, - }, - }; + return this; + } + + public override toJSON(): JSONObject { + if (!this.data) { + return MonitorSteps.getNewMonitorStepsAsJSON(); } - public static getDefaultMonitorSteps(arg: { - defaultMonitorStatusId: ObjectID; - monitorType: MonitorType; - monitorName: string; - onlineMonitorStatusId: ObjectID; - offlineMonitorStatusId: ObjectID; - defaultIncidentSeverityId: ObjectID; - }): MonitorSteps { - const monitorSteps: MonitorSteps = new MonitorSteps(); + return JSONFunctions.serialize({ + _type: ObjectType.MonitorSteps, + value: { + monitorStepsInstanceArray: this.data.monitorStepsInstanceArray.map( + (step: MonitorStep) => { + return step.toJSON(); + }, + ), + defaultMonitorStatusId: + this.data.defaultMonitorStatusId?.toString() || undefined, + }, + }); + } - monitorSteps.data = { - monitorStepsInstanceArray: [MonitorStep.getDefaultMonitorStep(arg)], - defaultMonitorStatusId: arg.defaultMonitorStatusId, - }; - - return monitorSteps; + public static override fromJSON(json: JSONObject): MonitorSteps { + if (json instanceof MonitorSteps) { + return json; } - public setMonitorStepsInstanceArray( - monitorSteps: Array<MonitorStep> - ): void { - if (this.data) { - this.data.monitorStepsInstanceArray = monitorSteps; - } + if (!json) { + throw new BadDataException("Invalid monitor steps"); } - public static clone(monitorSteps: MonitorSteps): MonitorSteps { - return MonitorSteps.fromJSON(monitorSteps.toJSON()); + if (json["_type"] !== "MonitorSteps") { + throw new BadDataException("Invalid monitor steps"); } - public setDefaultMonitorStatusId( - monitorStatusId: ObjectID | undefined - ): MonitorSteps { - if (this.data) { - this.data.defaultMonitorStatusId = monitorStatusId; - } - - return this; + if (!json["value"]) { + throw new BadDataException("Invalid monitor steps"); } - public override toJSON(): JSONObject { - if (!this.data) { - return MonitorSteps.getNewMonitorStepsAsJSON(); - } - - return JSONFunctions.serialize({ - _type: ObjectType.MonitorSteps, - value: { - monitorStepsInstanceArray: - this.data.monitorStepsInstanceArray.map( - (step: MonitorStep) => { - return step.toJSON(); - } - ), - defaultMonitorStatusId: - this.data.defaultMonitorStatusId?.toString() || undefined, - }, - }); + if (!(json["value"] as JSONObject)["monitorStepsInstanceArray"]) { + throw new BadDataException("Invalid monitor steps"); } - public static override fromJSON(json: JSONObject): MonitorSteps { - if (json instanceof MonitorSteps) { - return json; - } + const monitorStepsInstanceArray: JSONArray = (json["value"] as JSONObject)[ + "monitorStepsInstanceArray" + ] as JSONArray; - if (!json) { - throw new BadDataException('Invalid monitor steps'); - } + const monitorSteps: MonitorSteps = new MonitorSteps(); - if (json['_type'] !== 'MonitorSteps') { - throw new BadDataException('Invalid monitor steps'); - } + monitorSteps.data = { + monitorStepsInstanceArray: monitorStepsInstanceArray.map( + (json: JSONObject) => { + return MonitorStep.fromJSON(json); + }, + ), + defaultMonitorStatusId: (json["value"] as JSONObject)[ + "defaultMonitorStatusId" + ] + ? new ObjectID( + (json["value"] as JSONObject)["defaultMonitorStatusId"] as string, + ) + : undefined, + }; - if (!json['value']) { - throw new BadDataException('Invalid monitor steps'); - } + return monitorSteps; + } - if (!(json['value'] as JSONObject)['monitorStepsInstanceArray']) { - throw new BadDataException('Invalid monitor steps'); - } - - const monitorStepsInstanceArray: JSONArray = ( - json['value'] as JSONObject - )['monitorStepsInstanceArray'] as JSONArray; - - const monitorSteps: MonitorSteps = new MonitorSteps(); - - monitorSteps.data = { - monitorStepsInstanceArray: monitorStepsInstanceArray.map( - (json: JSONObject) => { - return MonitorStep.fromJSON(json); - } - ), - defaultMonitorStatusId: (json['value'] as JSONObject)[ - 'defaultMonitorStatusId' - ] - ? new ObjectID( - (json['value'] as JSONObject)[ - 'defaultMonitorStatusId' - ] as string - ) - : undefined, - }; - - return monitorSteps; + public static getValidationError( + value: MonitorSteps, + monitorType: MonitorType, + ): string | null { + if (!value.data) { + return "Monitor Steps is required"; } - public static getValidationError( - value: MonitorSteps, - monitorType: MonitorType - ): string | null { - if (!value.data) { - return 'Monitor Steps is required'; - } - - if (value.data.monitorStepsInstanceArray.length === 0) { - return 'Monitor Steps is required'; - } - - if (!value.data.defaultMonitorStatusId) { - return 'Default Monitor Status is required'; - } - - for (const step of value.data.monitorStepsInstanceArray) { - if (MonitorStep.getValidationError(step, monitorType)) { - return MonitorStep.getValidationError(step, monitorType); - } - } - - return null; + if (value.data.monitorStepsInstanceArray.length === 0) { + return "Monitor Steps is required"; } - protected static override toDatabase( - value: MonitorSteps | FindOperator<MonitorSteps> - ): JSONObject | null { - if (value && value instanceof MonitorSteps) { - return (value as MonitorSteps).toJSON(); - } else if (value) { - return JSONFunctions.serialize(value as any); - } - - return null; + if (!value.data.defaultMonitorStatusId) { + return "Default Monitor Status is required"; } - protected static override fromDatabase( - value: JSONObject - ): MonitorSteps | null { - if (value) { - return MonitorSteps.fromJSON(value); - } - - return null; + for (const step of value.data.monitorStepsInstanceArray) { + if (MonitorStep.getValidationError(step, monitorType)) { + return MonitorStep.getValidationError(step, monitorType); + } } - public override toString(): string { - return JSON.stringify(this.toJSON()); + return null; + } + + protected static override toDatabase( + value: MonitorSteps | FindOperator<MonitorSteps>, + ): JSONObject | null { + if (value && value instanceof MonitorSteps) { + return (value as MonitorSteps).toJSON(); + } else if (value) { + return JSONFunctions.serialize(value as any); } + + return null; + } + + protected static override fromDatabase( + value: JSONObject, + ): MonitorSteps | null { + if (value) { + return MonitorSteps.fromJSON(value); + } + + return null; + } + + public override toString(): string { + return JSON.stringify(this.toJSON()); + } } diff --git a/Common/Types/Monitor/MonitorType.ts b/Common/Types/Monitor/MonitorType.ts index 969a8e0277..05a19d09d3 100644 --- a/Common/Types/Monitor/MonitorType.ts +++ b/Common/Types/Monitor/MonitorType.ts @@ -1,186 +1,181 @@ -import BadDataException from '../Exception/BadDataException'; +import BadDataException from "../Exception/BadDataException"; enum MonitorType { - Manual = 'Manual', - Website = 'Website', - API = 'API', - Ping = 'Ping', - Kubernetes = 'Kubernetes', - IP = 'IP', - IncomingRequest = 'Incoming Request', - Port = 'Port', - Server = 'Server', - SSLCertificate = 'SSL Certificate', + Manual = "Manual", + Website = "Website", + API = "API", + Ping = "Ping", + Kubernetes = "Kubernetes", + IP = "IP", + IncomingRequest = "Incoming Request", + Port = "Port", + Server = "Server", + SSLCertificate = "SSL Certificate", - // These two monitor types are same but we are keeping them separate for now - this is for marketing purposes - SyntheticMonitor = 'Synthetic Monitor', - CustomJavaScriptCode = 'Custom JavaScript Code', + // These two monitor types are same but we are keeping them separate for now - this is for marketing purposes + SyntheticMonitor = "Synthetic Monitor", + CustomJavaScriptCode = "Custom JavaScript Code", } export default MonitorType; export interface MonitorTypeProps { - monitorType: MonitorType; - description: string; - title: string; + monitorType: MonitorType; + description: string; + title: string; } export class MonitorTypeHelper { - public static getAllMonitorTypeProps(): Array<MonitorTypeProps> { - const monitorTypeProps: Array<MonitorTypeProps> = [ - { - monitorType: MonitorType.API, - title: 'API', - description: - 'This monitor type lets you monitor any API - GET, POST, PUT, DELETE or more.', - }, - { - monitorType: MonitorType.Manual, - title: 'Manual', - description: - 'This monitor is a static monitor and will not actually monitor anything. It will however help you to integrate OneUptime with external monitoring tools and utilities.', - }, - { - monitorType: MonitorType.Website, - title: 'Website', - description: - 'This monitor type lets you monitor landing pages like home page of your company / blog or more.', - }, - { - monitorType: MonitorType.Ping, - title: 'Ping', - description: - 'This monitor type does the basic ping test of an endpoint.', - }, - // { - // monitorType: MonitorType.Kubernetes, - // title: 'Kubernetes', - // description: - // 'This monitor types lets you monitor Kubernetes clusters.', - // }, - { - monitorType: MonitorType.IP, - title: 'IP', - description: - 'This monitor type lets you monitor any IPv4 or IPv6 addresses.', - }, - { - monitorType: MonitorType.IncomingRequest, - title: 'Incoming Request', - description: - 'This monitor type lets you ping OneUptime from any external device or service with a custom payload.', - }, - { - monitorType: MonitorType.Port, - title: 'Port', - description: - 'This monitor type lets you monitor any TCP or UDP port.', - }, - { - monitorType: MonitorType.Server, - title: 'Server / VM', - description: - 'This monitor type lets you monitor any server, VM, or any machine.', - }, - { - monitorType: MonitorType.SSLCertificate, - title: 'SSL Certificate', - description: - 'This monitor type lets you monitor SSL certificates of any domain.', - }, - { - monitorType: MonitorType.SyntheticMonitor, - title: 'Synthetic Monitor', - description: - 'This monitor type lets you monitor your web application UI.', - }, - { - monitorType: MonitorType.CustomJavaScriptCode, - title: 'Custom JavaScript Code', - description: - 'This monitor type lets you run custom JavaScript code on a schedule.', - }, - ]; + public static getAllMonitorTypeProps(): Array<MonitorTypeProps> { + const monitorTypeProps: Array<MonitorTypeProps> = [ + { + monitorType: MonitorType.API, + title: "API", + description: + "This monitor type lets you monitor any API - GET, POST, PUT, DELETE or more.", + }, + { + monitorType: MonitorType.Manual, + title: "Manual", + description: + "This monitor is a static monitor and will not actually monitor anything. It will however help you to integrate OneUptime with external monitoring tools and utilities.", + }, + { + monitorType: MonitorType.Website, + title: "Website", + description: + "This monitor type lets you monitor landing pages like home page of your company / blog or more.", + }, + { + monitorType: MonitorType.Ping, + title: "Ping", + description: + "This monitor type does the basic ping test of an endpoint.", + }, + // { + // monitorType: MonitorType.Kubernetes, + // title: 'Kubernetes', + // description: + // 'This monitor types lets you monitor Kubernetes clusters.', + // }, + { + monitorType: MonitorType.IP, + title: "IP", + description: + "This monitor type lets you monitor any IPv4 or IPv6 addresses.", + }, + { + monitorType: MonitorType.IncomingRequest, + title: "Incoming Request", + description: + "This monitor type lets you ping OneUptime from any external device or service with a custom payload.", + }, + { + monitorType: MonitorType.Port, + title: "Port", + description: "This monitor type lets you monitor any TCP or UDP port.", + }, + { + monitorType: MonitorType.Server, + title: "Server / VM", + description: + "This monitor type lets you monitor any server, VM, or any machine.", + }, + { + monitorType: MonitorType.SSLCertificate, + title: "SSL Certificate", + description: + "This monitor type lets you monitor SSL certificates of any domain.", + }, + { + monitorType: MonitorType.SyntheticMonitor, + title: "Synthetic Monitor", + description: + "This monitor type lets you monitor your web application UI.", + }, + { + monitorType: MonitorType.CustomJavaScriptCode, + title: "Custom JavaScript Code", + description: + "This monitor type lets you run custom JavaScript code on a schedule.", + }, + ]; - return monitorTypeProps; + return monitorTypeProps; + } + + public static getDescription(monitorType: MonitorType): string { + const monitorTypeProps: Array<MonitorTypeProps> = + this.getAllMonitorTypeProps().filter((item: MonitorTypeProps) => { + return item.monitorType === monitorType; + }); + + if (!monitorTypeProps[0]) { + throw new BadDataException( + `${monitorType} does not have monitorType props`, + ); } - public static getDescription(monitorType: MonitorType): string { - const monitorTypeProps: Array<MonitorTypeProps> = - this.getAllMonitorTypeProps().filter((item: MonitorTypeProps) => { - return item.monitorType === monitorType; - }); + return monitorTypeProps[0].description; + } - if (!monitorTypeProps[0]) { - throw new BadDataException( - `${monitorType} does not have monitorType props` - ); - } + public static getTitle(monitorType: MonitorType): string { + const monitorTypeProps: Array<MonitorTypeProps> = + this.getAllMonitorTypeProps().filter((item: MonitorTypeProps) => { + return item.monitorType === monitorType; + }); - return monitorTypeProps[0].description; + if (!monitorTypeProps[0]) { + throw new BadDataException( + `${monitorType} does not have monitorType props`, + ); } - public static getTitle(monitorType: MonitorType): string { - const monitorTypeProps: Array<MonitorTypeProps> = - this.getAllMonitorTypeProps().filter((item: MonitorTypeProps) => { - return item.monitorType === monitorType; - }); + return monitorTypeProps[0].title; + } - if (!monitorTypeProps[0]) { - throw new BadDataException( - `${monitorType} does not have monitorType props` - ); - } + public static isProbableMonitors(monitorType: MonitorType): boolean { + const isProbeableMonitor: boolean = + monitorType === MonitorType.API || + monitorType === MonitorType.Website || + monitorType === MonitorType.IP || + monitorType === MonitorType.Ping || + monitorType === MonitorType.Port || + monitorType === MonitorType.SSLCertificate || + monitorType === MonitorType.SyntheticMonitor || + monitorType === MonitorType.CustomJavaScriptCode; + return isProbeableMonitor; + } - return monitorTypeProps[0].title; - } + public static getActiveMonitorTypes(): Array<MonitorType> { + return [ + MonitorType.API, + MonitorType.Website, + MonitorType.IP, + MonitorType.Ping, + MonitorType.Port, + MonitorType.SSLCertificate, + MonitorType.SyntheticMonitor, + MonitorType.CustomJavaScriptCode, + MonitorType.IncomingRequest, + MonitorType.Server, + ]; + } - public static isProbableMonitors(monitorType: MonitorType): boolean { - const isProbeableMonitor: boolean = - monitorType === MonitorType.API || - monitorType === MonitorType.Website || - monitorType === MonitorType.IP || - monitorType === MonitorType.Ping || - monitorType === MonitorType.Port || - monitorType === MonitorType.SSLCertificate || - monitorType === MonitorType.SyntheticMonitor || - monitorType === MonitorType.CustomJavaScriptCode; - return isProbeableMonitor; - } + public static doesMonitorTypeHaveDocumentation( + monitorType: MonitorType, + ): boolean { + return ( + monitorType === MonitorType.IncomingRequest || + monitorType === MonitorType.Server + ); + } - public static getActiveMonitorTypes(): Array<MonitorType> { - return [ - MonitorType.API, - MonitorType.Website, - MonitorType.IP, - MonitorType.Ping, - MonitorType.Port, - MonitorType.SSLCertificate, - MonitorType.SyntheticMonitor, - MonitorType.CustomJavaScriptCode, - MonitorType.IncomingRequest, - MonitorType.Server, - ]; - } + public static doesMonitorTypeHaveInterval(monitorType: MonitorType): boolean { + return this.isProbableMonitors(monitorType); + } - public static doesMonitorTypeHaveDocumentation( - monitorType: MonitorType - ): boolean { - return ( - monitorType === MonitorType.IncomingRequest || - monitorType === MonitorType.Server - ); - } - - public static doesMonitorTypeHaveInterval( - monitorType: MonitorType - ): boolean { - return this.isProbableMonitors(monitorType); - } - - public static doesMonitorTypeHaveCriteria( - monitorType: MonitorType - ): boolean { - return monitorType !== MonitorType.Manual; - } + public static doesMonitorTypeHaveCriteria(monitorType: MonitorType): boolean { + return monitorType !== MonitorType.Manual; + } } diff --git a/Common/Types/Monitor/SSLMonitor/SslMonitorResponse.ts b/Common/Types/Monitor/SSLMonitor/SslMonitorResponse.ts index 8a1c86edea..1174cf06db 100644 --- a/Common/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +++ b/Common/Types/Monitor/SSLMonitor/SslMonitorResponse.ts @@ -1,14 +1,14 @@ export default interface SslMonitorResponse { - isSelfSigned?: boolean; - createdAt?: Date; - expiresAt?: Date; - commonName?: string; - organizationalUnit?: string; - organization?: string; - locality?: string; - state?: string; - country?: string; - serialNumber?: string; - fingerprint?: string; - fingerprint256?: string; + isSelfSigned?: boolean; + createdAt?: Date; + expiresAt?: Date; + commonName?: string; + organizationalUnit?: string; + organization?: string; + locality?: string; + state?: string; + country?: string; + serialNumber?: string; + fingerprint?: string; + fingerprint256?: string; } diff --git a/Common/Types/Monitor/ServerMonitor/ServerMonitorResponse.ts b/Common/Types/Monitor/ServerMonitor/ServerMonitorResponse.ts index 42f047f12f..f69bda7a92 100644 --- a/Common/Types/Monitor/ServerMonitor/ServerMonitorResponse.ts +++ b/Common/Types/Monitor/ServerMonitor/ServerMonitorResponse.ts @@ -1,16 +1,16 @@ -import BasicInfrastructureMetrics from '../../Infrastructure/BasicMetrics'; -import ObjectID from '../../ObjectID'; +import BasicInfrastructureMetrics from "../../Infrastructure/BasicMetrics"; +import ObjectID from "../../ObjectID"; export interface ServerProcess { - pid: number; - name: string; - command: string; + pid: number; + name: string; + command: string; } export default interface ServerMonitorResponse { - monitorId: ObjectID; - basicInfrastructureMetrics?: BasicInfrastructureMetrics | undefined; - requestReceivedAt: Date; - onlyCheckRequestReceivedAt: boolean; - processes?: ServerProcess[] | undefined; + monitorId: ObjectID; + basicInfrastructureMetrics?: BasicInfrastructureMetrics | undefined; + requestReceivedAt: Date; + onlyCheckRequestReceivedAt: boolean; + processes?: ServerProcess[] | undefined; } diff --git a/Common/Types/Monitor/SyntheticMonitors/BrowserType.ts b/Common/Types/Monitor/SyntheticMonitors/BrowserType.ts index 25b76d63fc..0e34adca6c 100644 --- a/Common/Types/Monitor/SyntheticMonitors/BrowserType.ts +++ b/Common/Types/Monitor/SyntheticMonitors/BrowserType.ts @@ -1,3 +1,3 @@ -import BrowserType from '../../BrowserType'; +import BrowserType from "../../BrowserType"; export default BrowserType; diff --git a/Common/Types/Monitor/SyntheticMonitors/ScreenSizeType.ts b/Common/Types/Monitor/SyntheticMonitors/ScreenSizeType.ts index 101c613aab..4a9d058d63 100644 --- a/Common/Types/Monitor/SyntheticMonitors/ScreenSizeType.ts +++ b/Common/Types/Monitor/SyntheticMonitors/ScreenSizeType.ts @@ -1,3 +1,3 @@ -import ScreenSizeType from '../../ScreenSizeType'; +import ScreenSizeType from "../../ScreenSizeType"; export default ScreenSizeType; diff --git a/Common/Types/Monitor/SyntheticMonitors/Screenshot.ts b/Common/Types/Monitor/SyntheticMonitors/Screenshot.ts index d3a1f15902..b6cbf94e96 100644 --- a/Common/Types/Monitor/SyntheticMonitors/Screenshot.ts +++ b/Common/Types/Monitor/SyntheticMonitors/Screenshot.ts @@ -1,4 +1,4 @@ -import Dictionary from '../../Dictionary'; +import Dictionary from "../../Dictionary"; export type Screenshot = string; // base 64 encoded screenshots diff --git a/Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse.ts b/Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse.ts index 9ff9fe5409..0765a1a95f 100644 --- a/Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse.ts +++ b/Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse.ts @@ -1,11 +1,11 @@ -import CustomCodeMonitorResponse from '../CustomCodeMonitor/CustomCodeMonitorResponse'; -import BrowserType from './BrowserType'; -import ScreenSizeType from './ScreenSizeType'; -import Screenshots from './Screenshot'; +import CustomCodeMonitorResponse from "../CustomCodeMonitor/CustomCodeMonitorResponse"; +import BrowserType from "./BrowserType"; +import ScreenSizeType from "./ScreenSizeType"; +import Screenshots from "./Screenshot"; export default interface SyntheticMonitorResponse - extends CustomCodeMonitorResponse { - screenshots?: Screenshots | undefined; // base 64 encoded screenshots - browserType: BrowserType; - screenSizeType: ScreenSizeType; + extends CustomCodeMonitorResponse { + screenshots?: Screenshots | undefined; // base 64 encoded screenshots + browserType: BrowserType; + screenSizeType: ScreenSizeType; } diff --git a/Common/Types/Name.ts b/Common/Types/Name.ts index 2121953076..2d485550a2 100644 --- a/Common/Types/Name.ts +++ b/Common/Types/Name.ts @@ -1,86 +1,86 @@ -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import { FindOperator } from "typeorm"; export default class Name extends DatabaseProperty { - private _title: string = ''; - public get title(): string { - return this._title; + private _title: string = ""; + public get title(): string { + return this._title; + } + public set title(v: string) { + this._title = v; + } + + private _name: string = ""; + public get name(): string { + return this._name; + } + public set name(v: string) { + this._name = v; + } + + public constructor(name: string) { + super(); + this.name = name; + } + + public getFirstName(): string { + return this.name.split(" ")[0] || ""; + } + + public getLastName(): string { + if (this.name.split(" ").length > 1) { + return this.name.split(" ")[this.name.split(" ").length - 1] || ""; } - public set title(v: string) { - this._title = v; + return ""; + } + + public getMiddleName(): string { + if (this.name.split(" ").length > 2) { + return this.name.split(" ")[1] || ""; + } + return ""; + } + + public override toString(): string { + return this.name; + } + + public static override toDatabase( + value: Name | FindOperator<Name>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new Name(value); + } + + return value.toString(); } - private _name: string = ''; - public get name(): string { - return this._name; - } - public set name(v: string) { - this._name = v; + return null; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Name, + value: (this as Name).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Name { + if (json["_type"] === ObjectType.Name) { + return new Name((json["value"] as string) || ""); } - public constructor(name: string) { - super(); - this.name = name; + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + public static override fromDatabase(_value: string): Name | null { + if (_value) { + return new Name(_value); } - public getFirstName(): string { - return this.name.split(' ')[0] || ''; - } - - public getLastName(): string { - if (this.name.split(' ').length > 1) { - return this.name.split(' ')[this.name.split(' ').length - 1] || ''; - } - return ''; - } - - public getMiddleName(): string { - if (this.name.split(' ').length > 2) { - return this.name.split(' ')[1] || ''; - } - return ''; - } - - public override toString(): string { - return this.name; - } - - public static override toDatabase( - value: Name | FindOperator<Name> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new Name(value); - } - - return value.toString(); - } - - return null; - } - - public override toJSON(): JSONObject { - return { - _type: ObjectType.Name, - value: (this as Name).toString(), - }; - } - - public static override fromJSON(json: JSONObject): Name { - if (json['_type'] === ObjectType.Name) { - return new Name((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - public static override fromDatabase(_value: string): Name | null { - if (_value) { - return new Name(_value); - } - - return null; - } + return null; + } } diff --git a/Common/Types/NotificationRule/NotificationRuleType.ts b/Common/Types/NotificationRule/NotificationRuleType.ts index cd21109927..4395aa597f 100644 --- a/Common/Types/NotificationRule/NotificationRuleType.ts +++ b/Common/Types/NotificationRule/NotificationRuleType.ts @@ -1,7 +1,7 @@ enum NotificationRuleType { - ON_CALL_INCIDENT_CREATED = 'When incident is created during on call', - WHEN_USER_GOES_ON_CALL = 'When user goes on call', - WHEN_USER_GOES_OFF_CALL = 'When user goes off call', + ON_CALL_INCIDENT_CREATED = "When incident is created during on call", + WHEN_USER_GOES_ON_CALL = "When user goes on call", + WHEN_USER_GOES_OFF_CALL = "When user goes off call", } export default NotificationRuleType; diff --git a/Common/Types/NotificationSetting/NotificationSettingEventType.ts b/Common/Types/NotificationSetting/NotificationSettingEventType.ts index 774c866816..de54ba42fc 100644 --- a/Common/Types/NotificationSetting/NotificationSettingEventType.ts +++ b/Common/Types/NotificationSetting/NotificationSettingEventType.ts @@ -1,25 +1,25 @@ enum NotificationSettingEventType { - // Incident - SEND_INCIDENT_CREATED_OWNER_NOTIFICATION = 'Send incident created notification when I am the owner of the incident', - SEND_INCIDENT_NOTE_POSTED_OWNER_NOTIFICATION = 'Send incident note posted notification when I am the owner of the incident', - SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION = 'Send incident state changed notification when I am the owner of the incident', - SEND_INCIDENT_OWNER_ADDED_NOTIFICATION = 'Send notification when I am added as a owner to the incident', + // Incident + SEND_INCIDENT_CREATED_OWNER_NOTIFICATION = "Send incident created notification when I am the owner of the incident", + SEND_INCIDENT_NOTE_POSTED_OWNER_NOTIFICATION = "Send incident note posted notification when I am the owner of the incident", + SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION = "Send incident state changed notification when I am the owner of the incident", + SEND_INCIDENT_OWNER_ADDED_NOTIFICATION = "Send notification when I am added as a owner to the incident", - // Monitors - SEND_MONITOR_OWNER_ADDED_NOTIFICATION = 'Send notification when I am added as a owner to the monitor', - SEND_MONITOR_CREATED_OWNER_NOTIFICATION = 'Send monitor created notification when I am the owner of the monitor', - SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION = 'Send monitor status changed notification when I am the owner of the monitor', + // Monitors + SEND_MONITOR_OWNER_ADDED_NOTIFICATION = "Send notification when I am added as a owner to the monitor", + SEND_MONITOR_CREATED_OWNER_NOTIFICATION = "Send monitor created notification when I am the owner of the monitor", + SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION = "Send monitor status changed notification when I am the owner of the monitor", - // Scheduled Maintenance - SEND_SCHEDULED_MAINTENANCE_CREATED_OWNER_NOTIFICATION = 'Send event created notification when I am the owner of the event', - SEND_SCHEDULED_MAINTENANCE_NOTE_POSTED_OWNER_NOTIFICATION = 'Send event note posted notification when I am the owner of the event', - SEND_SCHEDULED_MAINTENANCE_OWNER_ADDED_NOTIFICATION = 'Send notification when I am added as a owner to the event', - SEND_SCHEDULED_MAINTENANCE_STATE_CHANGED_OWNER_NOTIFICATION = 'Send event state changed notification when I am the owner of the event', + // Scheduled Maintenance + SEND_SCHEDULED_MAINTENANCE_CREATED_OWNER_NOTIFICATION = "Send event created notification when I am the owner of the event", + SEND_SCHEDULED_MAINTENANCE_NOTE_POSTED_OWNER_NOTIFICATION = "Send event note posted notification when I am the owner of the event", + SEND_SCHEDULED_MAINTENANCE_OWNER_ADDED_NOTIFICATION = "Send notification when I am added as a owner to the event", + SEND_SCHEDULED_MAINTENANCE_STATE_CHANGED_OWNER_NOTIFICATION = "Send event state changed notification when I am the owner of the event", - // Status Page - SEND_STATUS_PAGE_ANNOUNCEMENT_CREATED_OWNER_NOTIFICATION = 'Send status page announcement created notification when I am the owner of the status page', - SEND_STATUS_PAGE_CREATED_OWNER_NOTIFICATION = 'Send status page created notification when I am the owner of the status page', - SEND_STATUS_PAGE_OWNER_ADDED_NOTIFICATION = 'Send notification when I am added as a owner to the status page', + // Status Page + SEND_STATUS_PAGE_ANNOUNCEMENT_CREATED_OWNER_NOTIFICATION = "Send status page announcement created notification when I am the owner of the status page", + SEND_STATUS_PAGE_CREATED_OWNER_NOTIFICATION = "Send status page created notification when I am the owner of the status page", + SEND_STATUS_PAGE_OWNER_ADDED_NOTIFICATION = "Send notification when I am added as a owner to the status page", } export default NotificationSettingEventType; diff --git a/Common/Types/ObjectID.ts b/Common/Types/ObjectID.ts index 377866344a..0a8bca20a7 100644 --- a/Common/Types/ObjectID.ts +++ b/Common/Types/ObjectID.ts @@ -1,94 +1,94 @@ // This is for Object ID for all the things in our database. -import UUID from '../Utils/UUID'; -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import { FindOperator } from 'typeorm'; +import UUID from "../Utils/UUID"; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import { FindOperator } from "typeorm"; export default class ObjectID extends DatabaseProperty { - private _id: string = ''; - public get id(): string { - return this._id; + private _id: string = ""; + public get id(): string { + return this._id; + } + public set id(v: string) { + this._id = v; + } + + public constructor(id: string | ObjectID | JSONObject) { + super(); + + if (id instanceof ObjectID) { + this.id = id.toString(); + } else if (typeof id === "string") { + this.id = id; + } else if ( + typeof id === "object" && + id && + id["_type"] === ObjectType.ObjectID && + id["value"] + ) { + this.id = id["value"]?.toString() || ""; } - public set id(v: string) { - this._id = v; + } + + public get value(): string { + return this._id.toString(); + } + + public equals(other: ObjectID): boolean { + return this.id.toString() === other.id.toString(); + } + + public override toString(): string { + return this.id; + } + + public static generate(): ObjectID { + return new this(UUID.generate()); + } + + protected static override toDatabase( + value: ObjectID | FindOperator<ObjectID>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new ObjectID(value); + } + + return value.toString(); } - public constructor(id: string | ObjectID | JSONObject) { - super(); + return null; + } - if (id instanceof ObjectID) { - this.id = id.toString(); - } else if (typeof id === 'string') { - this.id = id; - } else if ( - typeof id === 'object' && - id && - id['_type'] === ObjectType.ObjectID && - id['value'] - ) { - this.id = id['value']?.toString() || ''; - } + public override toJSON(): JSONObject { + return { + _type: ObjectType.ObjectID, + value: (this as ObjectID).toString(), + }; + } + + public static override fromJSON(json: JSONObject): ObjectID { + if (json["_type"] === ObjectType.ObjectID) { + return new ObjectID((json["value"] as string) || ""); } - public get value(): string { - return this._id.toString(); + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + protected static override fromDatabase(_value: string): ObjectID | null { + if (_value) { + return new ObjectID(_value); } - public equals(other: ObjectID): boolean { - return this.id.toString() === other.id.toString(); - } + return null; + } - public override toString(): string { - return this.id; - } + public static getZeroObjectID(): ObjectID { + return new ObjectID("00000000-0000-0000-0000-000000000000"); + } - public static generate(): ObjectID { - return new this(UUID.generate()); - } - - protected static override toDatabase( - value: ObjectID | FindOperator<ObjectID> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new ObjectID(value); - } - - return value.toString(); - } - - return null; - } - - public override toJSON(): JSONObject { - return { - _type: ObjectType.ObjectID, - value: (this as ObjectID).toString(), - }; - } - - public static override fromJSON(json: JSONObject): ObjectID { - if (json['_type'] === ObjectType.ObjectID) { - return new ObjectID((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - protected static override fromDatabase(_value: string): ObjectID | null { - if (_value) { - return new ObjectID(_value); - } - - return null; - } - - public static getZeroObjectID(): ObjectID { - return new ObjectID('00000000-0000-0000-0000-000000000000'); - } - - public static fromString(id: string): ObjectID { - return new ObjectID(id); - } + public static fromString(id: string): ObjectID { + return new ObjectID(id); + } } diff --git a/Common/Types/OnCallDutyPolicy/Layer.ts b/Common/Types/OnCallDutyPolicy/Layer.ts index 17c6fd36ee..a7e295e569 100644 --- a/Common/Types/OnCallDutyPolicy/Layer.ts +++ b/Common/Types/OnCallDutyPolicy/Layer.ts @@ -1,1018 +1,976 @@ -import UserModel from '../../Models/UserModel'; -import CalendarEvent from '../Calendar/CalendarEvent'; -import OneUptimeDate from '../Date'; -import DayOfWeek from '../Day/DayOfWeek'; -import EventInterval from '../Events/EventInterval'; -import Recurring from '../Events/Recurring'; -import StartAndEndTime from '../Time/StartAndEndTime'; -import Typeof from '../Typeof'; +import UserModel from "../../Models/UserModel"; +import CalendarEvent from "../Calendar/CalendarEvent"; +import OneUptimeDate from "../Date"; +import DayOfWeek from "../Day/DayOfWeek"; +import EventInterval from "../Events/EventInterval"; +import Recurring from "../Events/Recurring"; +import StartAndEndTime from "../Time/StartAndEndTime"; +import Typeof from "../Typeof"; import RestrictionTimes, { - RestrictionType, - WeeklyResctriction, -} from './RestrictionTimes'; + RestrictionType, + WeeklyResctriction, +} from "./RestrictionTimes"; export interface LayerProps { - users: Array<UserModel>; - startDateTimeOfLayer: Date; - restrictionTimes: RestrictionTimes; - handOffTime: Date; - rotation: Recurring; + users: Array<UserModel>; + startDateTimeOfLayer: Date; + restrictionTimes: RestrictionTimes; + handOffTime: Date; + rotation: Recurring; } export interface EventProps extends LayerProps { - calendarStartDate: Date; - calendarEndDate: Date; + calendarStartDate: Date; + calendarEndDate: Date; } export interface MultiLayerProps { - layers: Array<LayerProps>; - calendarStartDate: Date; - calendarEndDate: Date; + layers: Array<LayerProps>; + calendarStartDate: Date; + calendarEndDate: Date; } export interface PriorityCalendarEvents extends CalendarEvent { - priority: number; + priority: number; } export default class LayerUtil { - public static getEvents(data: EventProps): Array<CalendarEvent> { - let events: Array<CalendarEvent> = []; + public static getEvents(data: EventProps): Array<CalendarEvent> { + let events: Array<CalendarEvent> = []; - if (!LayerUtil.isDataValid(data)) { - return []; - } + if (!LayerUtil.isDataValid(data)) { + return []; + } - data = LayerUtil.sanitizeData(data); + data = LayerUtil.sanitizeData(data); - let start: Date = data.calendarStartDate; - const end: Date = data.calendarEndDate; + let start: Date = data.calendarStartDate; + const end: Date = data.calendarEndDate; - // start time of the layer is after the start time of the calendar, so we need to update the start time of the calendar - if (OneUptimeDate.isAfter(data.startDateTimeOfLayer, start)) { - start = data.startDateTimeOfLayer; - } + // start time of the layer is after the start time of the calendar, so we need to update the start time of the calendar + if (OneUptimeDate.isAfter(data.startDateTimeOfLayer, start)) { + start = data.startDateTimeOfLayer; + } - // split events by rotation. + // split events by rotation. - const rotation: Recurring = data.rotation; + const rotation: Recurring = data.rotation; - let hasReachedTheEndOfTheCalendar: boolean = false; + let hasReachedTheEndOfTheCalendar: boolean = false; - let handOffTime: Date = data.handOffTime; + let handOffTime: Date = data.handOffTime; - if (!handOffTime) { - return []; - } + if (!handOffTime) { + return []; + } - // Looop vars - let currentUserIndex: number = 0; - let currentEventStartTime: Date = start; + // Looop vars + let currentUserIndex: number = 0; + let currentEventStartTime: Date = start; - // bring handoff time to the same day as the currentStartTime. + // bring handoff time to the same day as the currentStartTime. - // before we do this, we need to update the user index. + // before we do this, we need to update the user index. - currentUserIndex = LayerUtil.getCurrentUserIndexBasedOnHandoffTime({ - rotation, - handOffTime, - currentUserIndex, - startDateTimeOfLayer: data.startDateTimeOfLayer, - users: data.users, - currentEventStartTime, + currentUserIndex = LayerUtil.getCurrentUserIndexBasedOnHandoffTime({ + rotation, + handOffTime, + currentUserIndex, + startDateTimeOfLayer: data.startDateTimeOfLayer, + users: data.users, + currentEventStartTime, + }); + + // update handoff time to the same day as current start time + + handOffTime = LayerUtil.moveHandsOffTimeAfterCurrentEventStartTime({ + handOffTime, + currentEventStartTime, + rotation: data.rotation, + }); + + let currentEventEndTime: Date = OneUptimeDate.getCurrentDate(); // temporary set to current time to avoid typescript error + + // check if calendar end is before the handoff time. if it is, then we need to return the event with the current user index as no handoff is needed. + + if (OneUptimeDate.isBefore(end, handOffTime)) { + const trimmedStartAndEndTimes: Array<StartAndEndTime> = + LayerUtil.trimStartAndEndTimesBasedOnRestrictionTimes({ + eventStartTime: currentEventStartTime, + eventEndTime: end, + restrictionTimes: data.restrictionTimes, }); - // update handoff time to the same day as current start time + events = [ + ...events, + ...LayerUtil.getCalendarEventsFromStartAndEndDates( + trimmedStartAndEndTimes, + data.users, + currentUserIndex, + ), + ]; + return events; + } + + while (!hasReachedTheEndOfTheCalendar) { + currentEventEndTime = handOffTime; + + // if current event start time and end time is the same then increase current event start time by 1 second. + + if (OneUptimeDate.isSame(currentEventStartTime, currentEventEndTime)) { + currentEventStartTime = OneUptimeDate.addRemoveSeconds( + currentEventEndTime, + 1, + ); handOffTime = LayerUtil.moveHandsOffTimeAfterCurrentEventStartTime({ - handOffTime, - currentEventStartTime, - rotation: data.rotation, + handOffTime, + currentEventStartTime, + rotation: data.rotation, }); - let currentEventEndTime: Date = OneUptimeDate.getCurrentDate(); // temporary set to current time to avoid typescript error + continue; + } - // check if calendar end is before the handoff time. if it is, then we need to return the event with the current user index as no handoff is needed. + // check calendar end time. if the end time of the event is after the end time of the calendar, we need to update the end time of the event + if (OneUptimeDate.isAfter(currentEventEndTime, end)) { + currentEventEndTime = end; + hasReachedTheEndOfTheCalendar = true; + } - if (OneUptimeDate.isBefore(end, handOffTime)) { - const trimmedStartAndEndTimes: Array<StartAndEndTime> = - LayerUtil.trimStartAndEndTimesBasedOnRestrictionTimes({ - eventStartTime: currentEventStartTime, - eventEndTime: end, - restrictionTimes: data.restrictionTimes, - }); + // check restriction times. if the end time of the event is after the end time of the restriction times, we need to update the end time of the event. - events = [ - ...events, - ...LayerUtil.getCalendarEventsFromStartAndEndDates( - trimmedStartAndEndTimes, - data.users, - currentUserIndex - ), - ]; + const trimmedStartAndEndTimes: Array<StartAndEndTime> = + LayerUtil.trimStartAndEndTimesBasedOnRestrictionTimes({ + eventStartTime: currentEventStartTime, + eventEndTime: currentEventEndTime, + restrictionTimes: data.restrictionTimes, + }); - return events; - } + events = [ + ...events, + ...LayerUtil.getCalendarEventsFromStartAndEndDates( + trimmedStartAndEndTimes, + data.users, + currentUserIndex, + ), + ]; - while (!hasReachedTheEndOfTheCalendar) { - currentEventEndTime = handOffTime; + // update the current event start time - // if current event start time and end time is the same then increase current event start time by 1 second. + currentEventStartTime = OneUptimeDate.addRemoveSeconds( + currentEventEndTime, + 1, + ); - if ( - OneUptimeDate.isSame(currentEventStartTime, currentEventEndTime) - ) { - currentEventStartTime = OneUptimeDate.addRemoveSeconds( - currentEventEndTime, - 1 - ); - handOffTime = - LayerUtil.moveHandsOffTimeAfterCurrentEventStartTime({ - handOffTime, - currentEventStartTime, - rotation: data.rotation, - }); + // update the handoff time - continue; - } + handOffTime = LayerUtil.moveHandsOffTimeAfterCurrentEventStartTime({ + handOffTime, + currentEventStartTime, + rotation: data.rotation, + }); - // check calendar end time. if the end time of the event is after the end time of the calendar, we need to update the end time of the event - if (OneUptimeDate.isAfter(currentEventEndTime, end)) { - currentEventEndTime = end; - hasReachedTheEndOfTheCalendar = true; - } - - // check restriction times. if the end time of the event is after the end time of the restriction times, we need to update the end time of the event. - - const trimmedStartAndEndTimes: Array<StartAndEndTime> = - LayerUtil.trimStartAndEndTimesBasedOnRestrictionTimes({ - eventStartTime: currentEventStartTime, - eventEndTime: currentEventEndTime, - restrictionTimes: data.restrictionTimes, - }); - - events = [ - ...events, - ...LayerUtil.getCalendarEventsFromStartAndEndDates( - trimmedStartAndEndTimes, - data.users, - currentUserIndex - ), - ]; - - // update the current event start time - - currentEventStartTime = OneUptimeDate.addRemoveSeconds( - currentEventEndTime, - 1 - ); - - // update the handoff time - - handOffTime = LayerUtil.moveHandsOffTimeAfterCurrentEventStartTime({ - handOffTime, - currentEventStartTime, - rotation: data.rotation, - }); - - // update the current user index - currentUserIndex = LayerUtil.incrementUserIndex( - currentUserIndex, - data.users.length - ); - } - - // increment ids of all the events and return them, to make sure they are unique - - let id: number = 1; - - for (const event of events) { - event.id = id; - id++; - } - - return events; + // update the current user index + currentUserIndex = LayerUtil.incrementUserIndex( + currentUserIndex, + data.users.length, + ); } - private static sanitizeData(data: EventProps): EventProps { - if (!(data.restrictionTimes instanceof RestrictionTimes)) { - data.restrictionTimes = RestrictionTimes.fromJSON( - data.restrictionTimes - ); - } + // increment ids of all the events and return them, to make sure they are unique - if (!(data.rotation instanceof Recurring)) { - data.rotation = Recurring.fromJSON(data.rotation); - } + let id: number = 1; - if (typeof data.startDateTimeOfLayer === Typeof.String) { - data.startDateTimeOfLayer = OneUptimeDate.fromString( - data.startDateTimeOfLayer - ); - } - - if (typeof data.calendarStartDate === Typeof.String) { - data.calendarStartDate = OneUptimeDate.fromString( - data.calendarStartDate - ); - } - - if (typeof data.calendarEndDate === Typeof.String) { - data.calendarEndDate = OneUptimeDate.fromString( - data.calendarEndDate - ); - } - - if (typeof data.handOffTime === Typeof.String) { - data.handOffTime = OneUptimeDate.fromString(data.handOffTime); - } - - return data; + for (const event of events) { + event.id = id; + id++; } - private static isDataValid(data: EventProps): boolean { - // if calendar end time is before the start time then return an empty array. - if ( - OneUptimeDate.isBefore(data.calendarEndDate, data.calendarStartDate) - ) { - return false; - } + return events; + } - // end time of the layer is before the end time of the calendar, so, we dont have any events and we can return empty array - if ( - OneUptimeDate.isAfter( - data.startDateTimeOfLayer, - data.calendarEndDate - ) - ) { - return false; - } - - // if users are empty, we dont have any events and we can return empty array - if (data.users.length === 0) { - return false; - } - - return true; + private static sanitizeData(data: EventProps): EventProps { + if (!(data.restrictionTimes instanceof RestrictionTimes)) { + data.restrictionTimes = RestrictionTimes.fromJSON(data.restrictionTimes); } - private static moveHandsOffTimeAfterCurrentEventStartTime(data: { - handOffTime: Date; - currentEventStartTime: Date; - rotation: Recurring; - }): Date { - // if handoff time is ahead of the current event start time, then we dont need to move and we can return it as is. - - if ( - OneUptimeDate.isAfter(data.handOffTime, data.currentEventStartTime) - ) { - return data.handOffTime; - } - - let handOffTime: Date = data.handOffTime; - - let intervalBetweenStartTimeAndHandoffTime: number = 0; - const rotationInterval: number = data.rotation.intervalCount.toNumber(); - - if (data.rotation.intervalType === EventInterval.Day) { - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getDaysBetweenTwoDatesInclusive( - handOffTime, - data.currentEventStartTime - ); - - if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { - intervalBetweenStartTimeAndHandoffTime = rotationInterval; - } else if ( - intervalBetweenStartTimeAndHandoffTime % rotationInterval !== - 0 - ) { - intervalBetweenStartTimeAndHandoffTime += rotationInterval; - } - - // add intervalBetweenStartTimeAndHandoffTime to handoff time - - handOffTime = OneUptimeDate.addRemoveDays( - handOffTime, - intervalBetweenStartTimeAndHandoffTime - ); - - return handOffTime; - } - - if (data.rotation.intervalType === EventInterval.Hour) { - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getHoursBetweenTwoDatesInclusive( - handOffTime, - data.currentEventStartTime - ); - - if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { - intervalBetweenStartTimeAndHandoffTime = rotationInterval; - } else if ( - intervalBetweenStartTimeAndHandoffTime % rotationInterval !== - 0 - ) { - intervalBetweenStartTimeAndHandoffTime += rotationInterval; - } - - // add intervalBetweenStartTimeAndHandoffTime to handoff time - - handOffTime = OneUptimeDate.addRemoveHours( - handOffTime, - intervalBetweenStartTimeAndHandoffTime - ); - - return handOffTime; - } - - if (data.rotation.intervalType === EventInterval.Week) { - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getWeeksBetweenTwoDatesInclusive( - handOffTime, - data.currentEventStartTime - ); - - if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { - intervalBetweenStartTimeAndHandoffTime = rotationInterval; - } else if ( - intervalBetweenStartTimeAndHandoffTime % rotationInterval !== - 0 - ) { - intervalBetweenStartTimeAndHandoffTime += rotationInterval; - } - - // add intervalBetweenStartTimeAndHandoffTime to handoff time - - handOffTime = OneUptimeDate.addRemoveWeeks( - handOffTime, - intervalBetweenStartTimeAndHandoffTime - ); - - return handOffTime; - } - - if (data.rotation.intervalType === EventInterval.Month) { - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getMonthsBetweenTwoDatesInclusive( - handOffTime, - data.currentEventStartTime - ); - - if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { - intervalBetweenStartTimeAndHandoffTime = rotationInterval; - } else if ( - intervalBetweenStartTimeAndHandoffTime % rotationInterval !== - 0 - ) { - intervalBetweenStartTimeAndHandoffTime += rotationInterval; - } - - // add intervalBetweenStartTimeAndHandoffTime to handoff time - - handOffTime = OneUptimeDate.addRemoveMonths( - handOffTime, - intervalBetweenStartTimeAndHandoffTime - ); - - return handOffTime; - } - - if (data.rotation.intervalType === EventInterval.Year) { - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getYearsBetweenTwoDatesInclusive( - handOffTime, - data.currentEventStartTime - ); - - if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { - intervalBetweenStartTimeAndHandoffTime = rotationInterval; - } else if ( - intervalBetweenStartTimeAndHandoffTime % rotationInterval !== - 0 - ) { - intervalBetweenStartTimeAndHandoffTime += rotationInterval; - } - - // add intervalBetweenStartTimeAndHandoffTime to handoff time - - handOffTime = OneUptimeDate.addRemoveYears( - handOffTime, - intervalBetweenStartTimeAndHandoffTime - ); - - return handOffTime; - } - - return handOffTime; + if (!(data.rotation instanceof Recurring)) { + data.rotation = Recurring.fromJSON(data.rotation); } - private static getCurrentUserIndexBasedOnHandoffTime(data: { - rotation: Recurring; - handOffTime: Date; - currentUserIndex: number; - startDateTimeOfLayer: Date; - users: Array<UserModel>; - currentEventStartTime: Date; - }): number { - let intervalBetweenStartTimeAndHandoffTime: number = 0; - const rotation: Recurring = data.rotation; - const handOffTime: Date = data.handOffTime; - let currentUserIndex: number = data.currentUserIndex; + if (typeof data.startDateTimeOfLayer === Typeof.String) { + data.startDateTimeOfLayer = OneUptimeDate.fromString( + data.startDateTimeOfLayer, + ); + } - // if current event start time if before the start time of the layer then return current Index + if (typeof data.calendarStartDate === Typeof.String) { + data.calendarStartDate = OneUptimeDate.fromString(data.calendarStartDate); + } - if ( - OneUptimeDate.isBefore( - data.currentEventStartTime, - data.startDateTimeOfLayer - ) - ) { - return currentUserIndex; - } + if (typeof data.calendarEndDate === Typeof.String) { + data.calendarEndDate = OneUptimeDate.fromString(data.calendarEndDate); + } - // if handoff time is ahead of current event stat time then return current index + if (typeof data.handOffTime === Typeof.String) { + data.handOffTime = OneUptimeDate.fromString(data.handOffTime); + } - if (OneUptimeDate.isAfter(handOffTime, data.currentEventStartTime)) { - return currentUserIndex; - } + return data; + } - if (rotation.intervalType === EventInterval.Day) { - // calculate the number of days between the start time of the layer and the handoff time. - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getDaysBetweenTwoDatesInclusive( - handOffTime, - data.currentEventStartTime - ); - } + private static isDataValid(data: EventProps): boolean { + // if calendar end time is before the start time then return an empty array. + if (OneUptimeDate.isBefore(data.calendarEndDate, data.calendarStartDate)) { + return false; + } - if (rotation.intervalType === EventInterval.Hour) { - // calculate the number of hours between the start time of the layer and the handoff time. - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getHoursBetweenTwoDatesInclusive( - handOffTime, - data.currentEventStartTime - ); - } + // end time of the layer is before the end time of the calendar, so, we dont have any events and we can return empty array + if ( + OneUptimeDate.isAfter(data.startDateTimeOfLayer, data.calendarEndDate) + ) { + return false; + } - if (rotation.intervalType === EventInterval.Week) { - // calculate the number of weeks between the start time of the layer and the handoff time. - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getWeeksBetweenTwoDatesInclusive( - handOffTime, - data.currentEventStartTime - ); - } + // if users are empty, we dont have any events and we can return empty array + if (data.users.length === 0) { + return false; + } - if (rotation.intervalType === EventInterval.Month) { - // calculate the number of months between the start time of the layer and the handoff time. - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getMonthsBetweenTwoDatesInclusive( - handOffTime, - data.currentEventStartTime - ); - } + return true; + } - if (rotation.intervalType === EventInterval.Year) { - // calculate the number of years between the start time of the layer and the handoff time. - intervalBetweenStartTimeAndHandoffTime = - OneUptimeDate.getYearsBetweenTwoDatesInclusive( - data.startDateTimeOfLayer, - handOffTime - ); - } + private static moveHandsOffTimeAfterCurrentEventStartTime(data: { + handOffTime: Date; + currentEventStartTime: Date; + rotation: Recurring; + }): Date { + // if handoff time is ahead of the current event start time, then we dont need to move and we can return it as is. - // now divide the interval between start time and handoff time by the interval count. + if (OneUptimeDate.isAfter(data.handOffTime, data.currentEventStartTime)) { + return data.handOffTime; + } - let numberOfIntervalsBetweenStartAndHandoffTime: number = Math.ceil( - intervalBetweenStartTimeAndHandoffTime / - rotation.intervalCount.toNumber() + let handOffTime: Date = data.handOffTime; + + let intervalBetweenStartTimeAndHandoffTime: number = 0; + const rotationInterval: number = data.rotation.intervalCount.toNumber(); + + if (data.rotation.intervalType === EventInterval.Day) { + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getDaysBetweenTwoDatesInclusive( + handOffTime, + data.currentEventStartTime, ); - if (numberOfIntervalsBetweenStartAndHandoffTime < 0) { - numberOfIntervalsBetweenStartAndHandoffTime = - numberOfIntervalsBetweenStartAndHandoffTime * -1; - } + if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { + intervalBetweenStartTimeAndHandoffTime = rotationInterval; + } else if ( + intervalBetweenStartTimeAndHandoffTime % rotationInterval !== + 0 + ) { + intervalBetweenStartTimeAndHandoffTime += rotationInterval; + } - currentUserIndex = LayerUtil.incrementUserIndex( - currentUserIndex, - data.users.length, - numberOfIntervalsBetweenStartAndHandoffTime + // add intervalBetweenStartTimeAndHandoffTime to handoff time + + handOffTime = OneUptimeDate.addRemoveDays( + handOffTime, + intervalBetweenStartTimeAndHandoffTime, + ); + + return handOffTime; + } + + if (data.rotation.intervalType === EventInterval.Hour) { + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getHoursBetweenTwoDatesInclusive( + handOffTime, + data.currentEventStartTime, ); - return currentUserIndex; + if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { + intervalBetweenStartTimeAndHandoffTime = rotationInterval; + } else if ( + intervalBetweenStartTimeAndHandoffTime % rotationInterval !== + 0 + ) { + intervalBetweenStartTimeAndHandoffTime += rotationInterval; + } + + // add intervalBetweenStartTimeAndHandoffTime to handoff time + + handOffTime = OneUptimeDate.addRemoveHours( + handOffTime, + intervalBetweenStartTimeAndHandoffTime, + ); + + return handOffTime; } - public static trimStartAndEndTimesBasedOnRestrictionTimes(data: { - eventStartTime: Date; - eventEndTime: Date; - restrictionTimes: RestrictionTimes; - }): Array<StartAndEndTime> { - const restrictionTimes: RestrictionTimes = data.restrictionTimes; + if (data.rotation.intervalType === EventInterval.Week) { + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getWeeksBetweenTwoDatesInclusive( + handOffTime, + data.currentEventStartTime, + ); - if (restrictionTimes.restictionType === RestrictionType.None) { - return [ - { - startTime: data.eventStartTime, - endTime: data.eventEndTime, - }, - ]; - } + if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { + intervalBetweenStartTimeAndHandoffTime = rotationInterval; + } else if ( + intervalBetweenStartTimeAndHandoffTime % rotationInterval !== + 0 + ) { + intervalBetweenStartTimeAndHandoffTime += rotationInterval; + } - if ( - restrictionTimes.restictionType === RestrictionType.Daily && - restrictionTimes.dayRestrictionTimes - ) { - // before this we need to make sure restrciton times are moved to the day of the event. - restrictionTimes.dayRestrictionTimes.startTime = - OneUptimeDate.keepTimeButMoveDay( - restrictionTimes.dayRestrictionTimes.startTime, - data.eventStartTime - ); + // add intervalBetweenStartTimeAndHandoffTime to handoff time - restrictionTimes.dayRestrictionTimes.endTime = - OneUptimeDate.keepTimeButMoveDay( - restrictionTimes.dayRestrictionTimes.endTime, - data.eventStartTime - ); + handOffTime = OneUptimeDate.addRemoveWeeks( + handOffTime, + intervalBetweenStartTimeAndHandoffTime, + ); - return LayerUtil.getEventsByDailyRestriction({ - eventStartTime: data.eventStartTime, - eventEndTime: data.eventEndTime, - restrictionStartAndEndTime: - restrictionTimes.dayRestrictionTimes, - props: { - intervalType: EventInterval.Day, - }, - }); - } - - if (restrictionTimes.restictionType === RestrictionType.Weekly) { - return LayerUtil.getEventsByWeeklyRestriction(data); - } - - return []; + return handOffTime; } - public static getEventsByWeeklyRestriction(data: { - eventStartTime: Date; - eventEndTime: Date; - restrictionTimes: RestrictionTimes; - }): Array<StartAndEndTime> { - const weeklyRestrictionTimes: Array<WeeklyResctriction> = - data.restrictionTimes.weeklyRestrictionTimes; + if (data.rotation.intervalType === EventInterval.Month) { + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getMonthsBetweenTwoDatesInclusive( + handOffTime, + data.currentEventStartTime, + ); - // if there are no weekly restriction times, we dont have any restrictions and we can return the event start and end times + if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { + intervalBetweenStartTimeAndHandoffTime = rotationInterval; + } else if ( + intervalBetweenStartTimeAndHandoffTime % rotationInterval !== + 0 + ) { + intervalBetweenStartTimeAndHandoffTime += rotationInterval; + } - let trimmedStartAndEndTimes: Array<StartAndEndTime> = []; + // add intervalBetweenStartTimeAndHandoffTime to handoff time - if (!weeklyRestrictionTimes || weeklyRestrictionTimes.length === 0) { - return [ - { - startTime: data.eventStartTime, - endTime: data.eventEndTime, - }, - ]; - } + handOffTime = OneUptimeDate.addRemoveMonths( + handOffTime, + intervalBetweenStartTimeAndHandoffTime, + ); - const restrictionStartAndEndTimes: Array<StartAndEndTime> = - LayerUtil.getWeeklyRestrictionTimesForWeek(data); - - for (const restrictionStartAndEndTime of restrictionStartAndEndTimes) { - const trimmedStartAndEndTimesForRestriction: Array<StartAndEndTime> = - LayerUtil.getEventsByDailyRestriction({ - eventStartTime: data.eventStartTime, - eventEndTime: data.eventEndTime, - restrictionStartAndEndTime: restrictionStartAndEndTime, - props: { - intervalType: EventInterval.Week, - }, - }); - - trimmedStartAndEndTimes = [ - ...trimmedStartAndEndTimes, - ...trimmedStartAndEndTimesForRestriction, - ]; - } - - return trimmedStartAndEndTimes; + return handOffTime; } - public static getWeeklyRestrictionTimesForWeek(data: { - eventStartTime: Date; - eventEndTime: Date; - restrictionTimes: RestrictionTimes; - }): Array<StartAndEndTime> { - const weeklyRestrictionTimes: Array<WeeklyResctriction> = - data.restrictionTimes.weeklyRestrictionTimes; + if (data.rotation.intervalType === EventInterval.Year) { + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getYearsBetweenTwoDatesInclusive( + handOffTime, + data.currentEventStartTime, + ); - const eventStartTime: Date = data.eventStartTime; + if (intervalBetweenStartTimeAndHandoffTime < rotationInterval) { + intervalBetweenStartTimeAndHandoffTime = rotationInterval; + } else if ( + intervalBetweenStartTimeAndHandoffTime % rotationInterval !== + 0 + ) { + intervalBetweenStartTimeAndHandoffTime += rotationInterval; + } - const startAndEndTimesOfWeeklyRestrictions: Array<StartAndEndTime> = []; + // add intervalBetweenStartTimeAndHandoffTime to handoff time - for (const weeklyRestriction of weeklyRestrictionTimes) { - // move all of these to the week of the event start time + handOffTime = OneUptimeDate.addRemoveYears( + handOffTime, + intervalBetweenStartTimeAndHandoffTime, + ); - const startDayOfWeek: DayOfWeek = weeklyRestriction.startDay; - const endDayOfWeek: DayOfWeek = weeklyRestriction.endDay; - - let startTime: Date = weeklyRestriction.startTime; - let endTime: Date = weeklyRestriction.endTime; - - // move start and end times to the week of the event start time - - startTime = OneUptimeDate.moveDateToTheDayOfWeek( - startTime, - eventStartTime, - startDayOfWeek - ); - endTime = OneUptimeDate.moveDateToTheDayOfWeek( - endTime, - eventStartTime, - endDayOfWeek - ); - - // now we have true start and end times of the weekly restriction - - // if start time is after end time, we need to add one week to the end time - - if (OneUptimeDate.isAfter(startTime, endTime)) { - endTime = OneUptimeDate.addRemoveWeeks(endTime, 1); - } - - startAndEndTimesOfWeeklyRestrictions.push({ - startTime, - endTime, - }); - } - - return startAndEndTimesOfWeeklyRestrictions; + return handOffTime; } - public static getEventsByDailyRestriction(data: { - eventStartTime: Date; - eventEndTime: Date; - restrictionStartAndEndTime: StartAndEndTime; + return handOffTime; + } + + private static getCurrentUserIndexBasedOnHandoffTime(data: { + rotation: Recurring; + handOffTime: Date; + currentUserIndex: number; + startDateTimeOfLayer: Date; + users: Array<UserModel>; + currentEventStartTime: Date; + }): number { + let intervalBetweenStartTimeAndHandoffTime: number = 0; + const rotation: Recurring = data.rotation; + const handOffTime: Date = data.handOffTime; + let currentUserIndex: number = data.currentUserIndex; + + // if current event start time if before the start time of the layer then return current Index + + if ( + OneUptimeDate.isBefore( + data.currentEventStartTime, + data.startDateTimeOfLayer, + ) + ) { + return currentUserIndex; + } + + // if handoff time is ahead of current event stat time then return current index + + if (OneUptimeDate.isAfter(handOffTime, data.currentEventStartTime)) { + return currentUserIndex; + } + + if (rotation.intervalType === EventInterval.Day) { + // calculate the number of days between the start time of the layer and the handoff time. + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getDaysBetweenTwoDatesInclusive( + handOffTime, + data.currentEventStartTime, + ); + } + + if (rotation.intervalType === EventInterval.Hour) { + // calculate the number of hours between the start time of the layer and the handoff time. + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getHoursBetweenTwoDatesInclusive( + handOffTime, + data.currentEventStartTime, + ); + } + + if (rotation.intervalType === EventInterval.Week) { + // calculate the number of weeks between the start time of the layer and the handoff time. + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getWeeksBetweenTwoDatesInclusive( + handOffTime, + data.currentEventStartTime, + ); + } + + if (rotation.intervalType === EventInterval.Month) { + // calculate the number of months between the start time of the layer and the handoff time. + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getMonthsBetweenTwoDatesInclusive( + handOffTime, + data.currentEventStartTime, + ); + } + + if (rotation.intervalType === EventInterval.Year) { + // calculate the number of years between the start time of the layer and the handoff time. + intervalBetweenStartTimeAndHandoffTime = + OneUptimeDate.getYearsBetweenTwoDatesInclusive( + data.startDateTimeOfLayer, + handOffTime, + ); + } + + // now divide the interval between start time and handoff time by the interval count. + + let numberOfIntervalsBetweenStartAndHandoffTime: number = Math.ceil( + intervalBetweenStartTimeAndHandoffTime / + rotation.intervalCount.toNumber(), + ); + + if (numberOfIntervalsBetweenStartAndHandoffTime < 0) { + numberOfIntervalsBetweenStartAndHandoffTime = + numberOfIntervalsBetweenStartAndHandoffTime * -1; + } + + currentUserIndex = LayerUtil.incrementUserIndex( + currentUserIndex, + data.users.length, + numberOfIntervalsBetweenStartAndHandoffTime, + ); + + return currentUserIndex; + } + + public static trimStartAndEndTimesBasedOnRestrictionTimes(data: { + eventStartTime: Date; + eventEndTime: Date; + restrictionTimes: RestrictionTimes; + }): Array<StartAndEndTime> { + const restrictionTimes: RestrictionTimes = data.restrictionTimes; + + if (restrictionTimes.restictionType === RestrictionType.None) { + return [ + { + startTime: data.eventStartTime, + endTime: data.eventEndTime, + }, + ]; + } + + if ( + restrictionTimes.restictionType === RestrictionType.Daily && + restrictionTimes.dayRestrictionTimes + ) { + // before this we need to make sure restrciton times are moved to the day of the event. + restrictionTimes.dayRestrictionTimes.startTime = + OneUptimeDate.keepTimeButMoveDay( + restrictionTimes.dayRestrictionTimes.startTime, + data.eventStartTime, + ); + + restrictionTimes.dayRestrictionTimes.endTime = + OneUptimeDate.keepTimeButMoveDay( + restrictionTimes.dayRestrictionTimes.endTime, + data.eventStartTime, + ); + + return LayerUtil.getEventsByDailyRestriction({ + eventStartTime: data.eventStartTime, + eventEndTime: data.eventEndTime, + restrictionStartAndEndTime: restrictionTimes.dayRestrictionTimes, props: { - intervalType: EventInterval; - }; - }): Array<StartAndEndTime> { - const dayRestrictionTimes: StartAndEndTime | null = - data.restrictionStartAndEndTime; - - // if there are no day restriction times, we dont have any restrictions and we can return the event start and end times - - if (!dayRestrictionTimes) { - return [ - { - startTime: data.eventStartTime, - endTime: data.eventEndTime, - }, - ]; - } - - // - - let restrictionStartTime: Date = dayRestrictionTimes.startTime; - let restrictionEndTime: Date = dayRestrictionTimes.endTime; - - let currentStartTime: Date = data.eventStartTime; - const currentEndTime: Date = data.eventEndTime; - - const trimmedStartAndEndTimes: Array<StartAndEndTime> = []; - - let reachedTheEndOfTheCurrentEvent: boolean = false; - - while (!reachedTheEndOfTheCurrentEvent) { - // if current end time is equalto or before than the current start time, we need to return the current event and exit the loop - - if (OneUptimeDate.isBefore(currentEndTime, currentStartTime)) { - reachedTheEndOfTheCurrentEvent = true; - } - - // if the event is ourside the restriction times, we need to return the trimmed array - - if (OneUptimeDate.isAfter(restrictionStartTime, currentEndTime)) { - return trimmedStartAndEndTimes; - } - - // if current event start time is after the restriction end time then we need to return empty array as there is no event. - - if (OneUptimeDate.isAfter(currentStartTime, restrictionEndTime)) { - return trimmedStartAndEndTimes; - } - - // if the restriction end time is before the restriction start time, we need to add one day to the restriction end time - if ( - OneUptimeDate.isAfter(restrictionStartTime, restrictionEndTime) - ) { - restrictionEndTime = OneUptimeDate.addRemoveDays( - restrictionEndTime, - data.props.intervalType === EventInterval.Day ? 1 : 7 // daily or weekly - ); - } - - // 1 - if the current event falls within the restriction times, we need to return the current event. - - if ( - OneUptimeDate.isOnOrAfter( - currentStartTime, - restrictionStartTime - ) && - OneUptimeDate.isOnOrAfter(restrictionEndTime, currentEndTime) - ) { - trimmedStartAndEndTimes.push({ - startTime: currentStartTime, - endTime: currentEndTime, - }); - reachedTheEndOfTheCurrentEvent = true; - } - - // 2 - Start Restriction: If the current event starts after the restriction start time and ends after the restriction end time, we need to return the current event with the start time of the current event and end time of the restriction - - if ( - OneUptimeDate.isOnOrAfter( - currentStartTime, - restrictionStartTime - ) && - OneUptimeDate.isOnOrAfter(currentEndTime, restrictionEndTime) - ) { - trimmedStartAndEndTimes.push({ - startTime: currentStartTime, - endTime: restrictionEndTime, - }); - reachedTheEndOfTheCurrentEvent = true; - } - - // 3 - End Restriction - If the current event starts before the restriction start time and ends before the restriction end time, we need to return the current event with the start time of the restriction and end time of the current event. - - if ( - OneUptimeDate.isBefore( - currentStartTime, - restrictionStartTime - ) && - OneUptimeDate.isBefore(currentEndTime, restrictionEndTime) && - OneUptimeDate.isAfter(currentEndTime, restrictionStartTime) - ) { - trimmedStartAndEndTimes.push({ - startTime: restrictionStartTime, - endTime: currentEndTime, - }); - reachedTheEndOfTheCurrentEvent = true; - } - - // 4 - If the current event starts before the restriction start time and ends after the restriction end time, we need to return the current event with the start time of the restriction and end time of the restriction. - - if ( - OneUptimeDate.isBefore( - currentStartTime, - restrictionStartTime - ) && - OneUptimeDate.isOnOrAfter(currentEndTime, restrictionEndTime) - ) { - trimmedStartAndEndTimes.push({ - startTime: restrictionStartTime, - endTime: restrictionEndTime, - }); - - currentStartTime = OneUptimeDate.addRemoveSeconds( - restrictionEndTime, - 1 - ); - - // add day to restriction start and end times. - - restrictionStartTime = OneUptimeDate.addRemoveDays( - restrictionStartTime, - data.props.intervalType === EventInterval.Day ? 1 : 7 // daily or weekly - ); - restrictionEndTime = OneUptimeDate.addRemoveDays( - restrictionEndTime, - data.props.intervalType === EventInterval.Day ? 1 : 7 // daily or weekly - ); - } - } - - return trimmedStartAndEndTimes; + intervalType: EventInterval.Day, + }, + }); } - // helper functions. - - private static incrementUserIndex( - currentIndex: number, - userArrayLength: number, - incrementBy?: number - ): number { - // update the current user index - - if (incrementBy === undefined) { - incrementBy = 1; - } - - currentIndex = currentIndex + incrementBy; - - // if the current user index is greater than the length of the users array, we need to reset the current user index to 0 - if (currentIndex >= userArrayLength) { - // then modulo the current user index by the length of the users array - currentIndex = currentIndex % userArrayLength; // so this rotates the users. - } - - return currentIndex; + if (restrictionTimes.restictionType === RestrictionType.Weekly) { + return LayerUtil.getEventsByWeeklyRestriction(data); } - private static getCalendarEventsFromStartAndEndDates( - trimmedStartAndEndTimes: Array<StartAndEndTime>, - users: Array<UserModel>, - currentUserIndex: number - ): Array<CalendarEvent> { - const events: Array<CalendarEvent> = []; + return []; + } - const userId: string = users[currentUserIndex]?.id?.toString() || ''; + public static getEventsByWeeklyRestriction(data: { + eventStartTime: Date; + eventEndTime: Date; + restrictionTimes: RestrictionTimes; + }): Array<StartAndEndTime> { + const weeklyRestrictionTimes: Array<WeeklyResctriction> = + data.restrictionTimes.weeklyRestrictionTimes; - for (const trimmedStartAndEndTime of trimmedStartAndEndTimes) { - const event: CalendarEvent = { - id: 0, - title: userId, // This will be changed to username in the UI or will bve kept the same if used on the server. - allDay: false, - start: trimmedStartAndEndTime.startTime, - end: trimmedStartAndEndTime.endTime, - }; + // if there are no weekly restriction times, we dont have any restrictions and we can return the event start and end times - events.push(event); - } + let trimmedStartAndEndTimes: Array<StartAndEndTime> = []; - return events; + if (!weeklyRestrictionTimes || weeklyRestrictionTimes.length === 0) { + return [ + { + startTime: data.eventStartTime, + endTime: data.eventEndTime, + }, + ]; } - public static getMultiLayerEvents( - data: MultiLayerProps - ): Array<CalendarEvent> { - const events: Array<PriorityCalendarEvents> = []; - let layerPriority: number = 1; + const restrictionStartAndEndTimes: Array<StartAndEndTime> = + LayerUtil.getWeeklyRestrictionTimesForWeek(data); - for (const layer of data.layers) { - const layerEvents: Array<CalendarEvent> = LayerUtil.getEvents({ - users: layer.users, - startDateTimeOfLayer: layer.startDateTimeOfLayer, - restrictionTimes: layer.restrictionTimes, - handOffTime: layer.handOffTime, - rotation: layer.rotation, - calendarStartDate: data.calendarStartDate, - calendarEndDate: data.calendarEndDate, - }); - - // add priority to each event - - for (const layerEvent of layerEvents) { - const priorityEvent: PriorityCalendarEvents = { - ...layerEvent, - priority: layerPriority, - }; - - events.push(priorityEvent); - } - - // increment layer priority - layerPriority++; - } - - // now remove the overlapping events - - const nonOverlappingEvents: Array<CalendarEvent> = - LayerUtil.removeOverlappingEvents(events); - - return nonOverlappingEvents; - } - - public static removeOverlappingEvents( - events: PriorityCalendarEvents[] - ): CalendarEvent[] { - // now remove overlapping events by priority and trim them by priority. Lower priority number will be kept and higher priority number will be trimmed. - // so if there are two events with the same start and end time, we will keep the one with the lower priority number and remove the one with the higher priority number. - // if there are overlapping events, we will trim the one with the higher priority number. - - // sort the events by priority - - // now remove the overlapping events - - const finalEvents: PriorityCalendarEvents[] = []; - - // sort events by start time - - events.sort((a: CalendarEvent, b: CalendarEvent) => { - if (OneUptimeDate.isBefore(a.start, b.start)) { - return -1; - } - - if (OneUptimeDate.isAfter(a.start, b.start)) { - return 1; - } - - return 0; + for (const restrictionStartAndEndTime of restrictionStartAndEndTimes) { + const trimmedStartAndEndTimesForRestriction: Array<StartAndEndTime> = + LayerUtil.getEventsByDailyRestriction({ + eventStartTime: data.eventStartTime, + eventEndTime: data.eventEndTime, + restrictionStartAndEndTime: restrictionStartAndEndTime, + props: { + intervalType: EventInterval.Week, + }, }); - for (const event of events) { - // trim the trimmed events by the current event based on priority - - for (const finalEvent of finalEvents) { - // check if this final event overlaps with the current event - - if ( - OneUptimeDate.isOverlapping( - finalEvent.start, - finalEvent.end, - event.start, - event.end - ) - ) { - // if the current event has a higher priority than the final event, we need to trim the final event - - if (event.priority < finalEvent.priority) { - // trim the final event based on the current event - // end time of the final event will be the start time of the current event - 1 second - - const tempFinalEventEnd: Date = finalEvent.end; - - finalEvent.end = OneUptimeDate.addRemoveSeconds( - event.start, - -1 - ); - - // final event was originally ending after the current event, so we need to add the trimmed event to the final events array - - if ( - OneUptimeDate.isAfter(tempFinalEventEnd, event.end) - ) { - // add the trimmed event to the final events array - - const trimmedEvent: PriorityCalendarEvents = { - ...finalEvent, - priority: finalEvent.priority, - start: OneUptimeDate.addRemoveSeconds( - event.end, - 1 - ), - end: tempFinalEventEnd, - }; - - finalEvents.push(trimmedEvent); - } - } else { - // trim the current event based on the final event - // start time of the current event will be the end time of the final event + 1 second - event.start = OneUptimeDate.addRemoveSeconds( - finalEvent.end, - 1 - ); - } - } - } - - finalEvents.push(event); - - // sort by start times - - finalEvents.sort((a: CalendarEvent, b: CalendarEvent) => { - if (OneUptimeDate.isBefore(a.start, b.start)) { - return -1; - } - - if (OneUptimeDate.isAfter(a.start, b.start)) { - return 1; - } - - return 0; - }); - - // if an event starts and end at the same time, we need to remove it - - finalEvents.forEach((finalEvent: CalendarEvent, index: number) => { - if (OneUptimeDate.isSame(finalEvent.start, finalEvent.end)) { - finalEvents.splice(index, 1); - } - - // if any event ends before it starts, we need to remove it - - if (OneUptimeDate.isBefore(finalEvent.end, finalEvent.start)) { - finalEvents.splice(index, 1); - } - }); - } - - // convert PriorityCalendarEvents to CalendarEvents - - const calendarEvents: CalendarEvent[] = []; - let id: number = 1; - - for (const event of finalEvents) { - const calendarEvent: CalendarEvent = { - ...event, - id: id, - }; - - calendarEvents.push(calendarEvent); - id++; - } - - return calendarEvents; + trimmedStartAndEndTimes = [ + ...trimmedStartAndEndTimes, + ...trimmedStartAndEndTimesForRestriction, + ]; } + + return trimmedStartAndEndTimes; + } + + public static getWeeklyRestrictionTimesForWeek(data: { + eventStartTime: Date; + eventEndTime: Date; + restrictionTimes: RestrictionTimes; + }): Array<StartAndEndTime> { + const weeklyRestrictionTimes: Array<WeeklyResctriction> = + data.restrictionTimes.weeklyRestrictionTimes; + + const eventStartTime: Date = data.eventStartTime; + + const startAndEndTimesOfWeeklyRestrictions: Array<StartAndEndTime> = []; + + for (const weeklyRestriction of weeklyRestrictionTimes) { + // move all of these to the week of the event start time + + const startDayOfWeek: DayOfWeek = weeklyRestriction.startDay; + const endDayOfWeek: DayOfWeek = weeklyRestriction.endDay; + + let startTime: Date = weeklyRestriction.startTime; + let endTime: Date = weeklyRestriction.endTime; + + // move start and end times to the week of the event start time + + startTime = OneUptimeDate.moveDateToTheDayOfWeek( + startTime, + eventStartTime, + startDayOfWeek, + ); + endTime = OneUptimeDate.moveDateToTheDayOfWeek( + endTime, + eventStartTime, + endDayOfWeek, + ); + + // now we have true start and end times of the weekly restriction + + // if start time is after end time, we need to add one week to the end time + + if (OneUptimeDate.isAfter(startTime, endTime)) { + endTime = OneUptimeDate.addRemoveWeeks(endTime, 1); + } + + startAndEndTimesOfWeeklyRestrictions.push({ + startTime, + endTime, + }); + } + + return startAndEndTimesOfWeeklyRestrictions; + } + + public static getEventsByDailyRestriction(data: { + eventStartTime: Date; + eventEndTime: Date; + restrictionStartAndEndTime: StartAndEndTime; + props: { + intervalType: EventInterval; + }; + }): Array<StartAndEndTime> { + const dayRestrictionTimes: StartAndEndTime | null = + data.restrictionStartAndEndTime; + + // if there are no day restriction times, we dont have any restrictions and we can return the event start and end times + + if (!dayRestrictionTimes) { + return [ + { + startTime: data.eventStartTime, + endTime: data.eventEndTime, + }, + ]; + } + + // + + let restrictionStartTime: Date = dayRestrictionTimes.startTime; + let restrictionEndTime: Date = dayRestrictionTimes.endTime; + + let currentStartTime: Date = data.eventStartTime; + const currentEndTime: Date = data.eventEndTime; + + const trimmedStartAndEndTimes: Array<StartAndEndTime> = []; + + let reachedTheEndOfTheCurrentEvent: boolean = false; + + while (!reachedTheEndOfTheCurrentEvent) { + // if current end time is equalto or before than the current start time, we need to return the current event and exit the loop + + if (OneUptimeDate.isBefore(currentEndTime, currentStartTime)) { + reachedTheEndOfTheCurrentEvent = true; + } + + // if the event is ourside the restriction times, we need to return the trimmed array + + if (OneUptimeDate.isAfter(restrictionStartTime, currentEndTime)) { + return trimmedStartAndEndTimes; + } + + // if current event start time is after the restriction end time then we need to return empty array as there is no event. + + if (OneUptimeDate.isAfter(currentStartTime, restrictionEndTime)) { + return trimmedStartAndEndTimes; + } + + // if the restriction end time is before the restriction start time, we need to add one day to the restriction end time + if (OneUptimeDate.isAfter(restrictionStartTime, restrictionEndTime)) { + restrictionEndTime = OneUptimeDate.addRemoveDays( + restrictionEndTime, + data.props.intervalType === EventInterval.Day ? 1 : 7, // daily or weekly + ); + } + + // 1 - if the current event falls within the restriction times, we need to return the current event. + + if ( + OneUptimeDate.isOnOrAfter(currentStartTime, restrictionStartTime) && + OneUptimeDate.isOnOrAfter(restrictionEndTime, currentEndTime) + ) { + trimmedStartAndEndTimes.push({ + startTime: currentStartTime, + endTime: currentEndTime, + }); + reachedTheEndOfTheCurrentEvent = true; + } + + // 2 - Start Restriction: If the current event starts after the restriction start time and ends after the restriction end time, we need to return the current event with the start time of the current event and end time of the restriction + + if ( + OneUptimeDate.isOnOrAfter(currentStartTime, restrictionStartTime) && + OneUptimeDate.isOnOrAfter(currentEndTime, restrictionEndTime) + ) { + trimmedStartAndEndTimes.push({ + startTime: currentStartTime, + endTime: restrictionEndTime, + }); + reachedTheEndOfTheCurrentEvent = true; + } + + // 3 - End Restriction - If the current event starts before the restriction start time and ends before the restriction end time, we need to return the current event with the start time of the restriction and end time of the current event. + + if ( + OneUptimeDate.isBefore(currentStartTime, restrictionStartTime) && + OneUptimeDate.isBefore(currentEndTime, restrictionEndTime) && + OneUptimeDate.isAfter(currentEndTime, restrictionStartTime) + ) { + trimmedStartAndEndTimes.push({ + startTime: restrictionStartTime, + endTime: currentEndTime, + }); + reachedTheEndOfTheCurrentEvent = true; + } + + // 4 - If the current event starts before the restriction start time and ends after the restriction end time, we need to return the current event with the start time of the restriction and end time of the restriction. + + if ( + OneUptimeDate.isBefore(currentStartTime, restrictionStartTime) && + OneUptimeDate.isOnOrAfter(currentEndTime, restrictionEndTime) + ) { + trimmedStartAndEndTimes.push({ + startTime: restrictionStartTime, + endTime: restrictionEndTime, + }); + + currentStartTime = OneUptimeDate.addRemoveSeconds( + restrictionEndTime, + 1, + ); + + // add day to restriction start and end times. + + restrictionStartTime = OneUptimeDate.addRemoveDays( + restrictionStartTime, + data.props.intervalType === EventInterval.Day ? 1 : 7, // daily or weekly + ); + restrictionEndTime = OneUptimeDate.addRemoveDays( + restrictionEndTime, + data.props.intervalType === EventInterval.Day ? 1 : 7, // daily or weekly + ); + } + } + + return trimmedStartAndEndTimes; + } + + // helper functions. + + private static incrementUserIndex( + currentIndex: number, + userArrayLength: number, + incrementBy?: number, + ): number { + // update the current user index + + if (incrementBy === undefined) { + incrementBy = 1; + } + + currentIndex = currentIndex + incrementBy; + + // if the current user index is greater than the length of the users array, we need to reset the current user index to 0 + if (currentIndex >= userArrayLength) { + // then modulo the current user index by the length of the users array + currentIndex = currentIndex % userArrayLength; // so this rotates the users. + } + + return currentIndex; + } + + private static getCalendarEventsFromStartAndEndDates( + trimmedStartAndEndTimes: Array<StartAndEndTime>, + users: Array<UserModel>, + currentUserIndex: number, + ): Array<CalendarEvent> { + const events: Array<CalendarEvent> = []; + + const userId: string = users[currentUserIndex]?.id?.toString() || ""; + + for (const trimmedStartAndEndTime of trimmedStartAndEndTimes) { + const event: CalendarEvent = { + id: 0, + title: userId, // This will be changed to username in the UI or will bve kept the same if used on the server. + allDay: false, + start: trimmedStartAndEndTime.startTime, + end: trimmedStartAndEndTime.endTime, + }; + + events.push(event); + } + + return events; + } + + public static getMultiLayerEvents( + data: MultiLayerProps, + ): Array<CalendarEvent> { + const events: Array<PriorityCalendarEvents> = []; + let layerPriority: number = 1; + + for (const layer of data.layers) { + const layerEvents: Array<CalendarEvent> = LayerUtil.getEvents({ + users: layer.users, + startDateTimeOfLayer: layer.startDateTimeOfLayer, + restrictionTimes: layer.restrictionTimes, + handOffTime: layer.handOffTime, + rotation: layer.rotation, + calendarStartDate: data.calendarStartDate, + calendarEndDate: data.calendarEndDate, + }); + + // add priority to each event + + for (const layerEvent of layerEvents) { + const priorityEvent: PriorityCalendarEvents = { + ...layerEvent, + priority: layerPriority, + }; + + events.push(priorityEvent); + } + + // increment layer priority + layerPriority++; + } + + // now remove the overlapping events + + const nonOverlappingEvents: Array<CalendarEvent> = + LayerUtil.removeOverlappingEvents(events); + + return nonOverlappingEvents; + } + + public static removeOverlappingEvents( + events: PriorityCalendarEvents[], + ): CalendarEvent[] { + // now remove overlapping events by priority and trim them by priority. Lower priority number will be kept and higher priority number will be trimmed. + // so if there are two events with the same start and end time, we will keep the one with the lower priority number and remove the one with the higher priority number. + // if there are overlapping events, we will trim the one with the higher priority number. + + // sort the events by priority + + // now remove the overlapping events + + const finalEvents: PriorityCalendarEvents[] = []; + + // sort events by start time + + events.sort((a: CalendarEvent, b: CalendarEvent) => { + if (OneUptimeDate.isBefore(a.start, b.start)) { + return -1; + } + + if (OneUptimeDate.isAfter(a.start, b.start)) { + return 1; + } + + return 0; + }); + + for (const event of events) { + // trim the trimmed events by the current event based on priority + + for (const finalEvent of finalEvents) { + // check if this final event overlaps with the current event + + if ( + OneUptimeDate.isOverlapping( + finalEvent.start, + finalEvent.end, + event.start, + event.end, + ) + ) { + // if the current event has a higher priority than the final event, we need to trim the final event + + if (event.priority < finalEvent.priority) { + // trim the final event based on the current event + // end time of the final event will be the start time of the current event - 1 second + + const tempFinalEventEnd: Date = finalEvent.end; + + finalEvent.end = OneUptimeDate.addRemoveSeconds(event.start, -1); + + // final event was originally ending after the current event, so we need to add the trimmed event to the final events array + + if (OneUptimeDate.isAfter(tempFinalEventEnd, event.end)) { + // add the trimmed event to the final events array + + const trimmedEvent: PriorityCalendarEvents = { + ...finalEvent, + priority: finalEvent.priority, + start: OneUptimeDate.addRemoveSeconds(event.end, 1), + end: tempFinalEventEnd, + }; + + finalEvents.push(trimmedEvent); + } + } else { + // trim the current event based on the final event + // start time of the current event will be the end time of the final event + 1 second + event.start = OneUptimeDate.addRemoveSeconds(finalEvent.end, 1); + } + } + } + + finalEvents.push(event); + + // sort by start times + + finalEvents.sort((a: CalendarEvent, b: CalendarEvent) => { + if (OneUptimeDate.isBefore(a.start, b.start)) { + return -1; + } + + if (OneUptimeDate.isAfter(a.start, b.start)) { + return 1; + } + + return 0; + }); + + // if an event starts and end at the same time, we need to remove it + + finalEvents.forEach((finalEvent: CalendarEvent, index: number) => { + if (OneUptimeDate.isSame(finalEvent.start, finalEvent.end)) { + finalEvents.splice(index, 1); + } + + // if any event ends before it starts, we need to remove it + + if (OneUptimeDate.isBefore(finalEvent.end, finalEvent.start)) { + finalEvents.splice(index, 1); + } + }); + } + + // convert PriorityCalendarEvents to CalendarEvents + + const calendarEvents: CalendarEvent[] = []; + let id: number = 1; + + for (const event of finalEvents) { + const calendarEvent: CalendarEvent = { + ...event, + id: id, + }; + + calendarEvents.push(calendarEvent); + id++; + } + + return calendarEvents; + } } diff --git a/Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus.ts b/Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus.ts index 3d82e9624c..68a93af0a2 100644 --- a/Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus.ts +++ b/Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus.ts @@ -1,10 +1,10 @@ enum OnCallDutyExecutionLogTimelineStatus { - Skipped = 'Skipped', - Started = 'Started', - Executing = 'Executing', - SuccessfullyAcknowledged = 'Successfully Acknowledged', - NotificationSent = 'Notification Sent', - Error = 'Error', + Skipped = "Skipped", + Started = "Started", + Executing = "Executing", + SuccessfullyAcknowledged = "Successfully Acknowledged", + NotificationSent = "Notification Sent", + Error = "Error", } export default OnCallDutyExecutionLogTimelineStatus; diff --git a/Common/Types/OnCallDutyPolicy/OnCallDutyPolicyAlertStatus.ts b/Common/Types/OnCallDutyPolicy/OnCallDutyPolicyAlertStatus.ts index d3965182be..e6c9292fd3 100644 --- a/Common/Types/OnCallDutyPolicy/OnCallDutyPolicyAlertStatus.ts +++ b/Common/Types/OnCallDutyPolicy/OnCallDutyPolicyAlertStatus.ts @@ -1,7 +1,7 @@ enum OnCallDutyPolicyStatus { - SuccessfullyAcknowledged = 'Successfully Acknowledged', - FailedToAcknowledge = 'Failed to Acknowledge', - Error = 'Error', + SuccessfullyAcknowledged = "Successfully Acknowledged", + FailedToAcknowledge = "Failed to Acknowledge", + Error = "Error", } export default OnCallDutyPolicyStatus; diff --git a/Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts b/Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts index 7b37ad52e7..518b24610f 100644 --- a/Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts +++ b/Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts @@ -1,9 +1,9 @@ enum OnCallDutyPolicyStatus { - Scheduled = 'Scheduled', - Started = 'Started', - Executing = 'Executing', - Completed = 'Execution Completed', - Error = 'Error', + Scheduled = "Scheduled", + Started = "Started", + Executing = "Executing", + Completed = "Execution Completed", + Error = "Error", } export default OnCallDutyPolicyStatus; diff --git a/Common/Types/OnCallDutyPolicy/RestrictionTimes.ts b/Common/Types/OnCallDutyPolicy/RestrictionTimes.ts index 14ecade6a4..5fcb21ec7a 100644 --- a/Common/Types/OnCallDutyPolicy/RestrictionTimes.ts +++ b/Common/Types/OnCallDutyPolicy/RestrictionTimes.ts @@ -1,193 +1,191 @@ -import DatabaseProperty from '../Database/DatabaseProperty'; -import OneUptimeDate from '../Date'; -import DayOfWeek from '../Day/DayOfWeek'; -import BadDataException from '../Exception/BadDataException'; -import { JSONObject, ObjectType } from '../JSON'; -import JSONFunctions from '../JSONFunctions'; -import StartAndEndTime from '../Time/StartAndEndTime'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "../Database/DatabaseProperty"; +import OneUptimeDate from "../Date"; +import DayOfWeek from "../Day/DayOfWeek"; +import BadDataException from "../Exception/BadDataException"; +import { JSONObject, ObjectType } from "../JSON"; +import JSONFunctions from "../JSONFunctions"; +import StartAndEndTime from "../Time/StartAndEndTime"; +import { FindOperator } from "typeorm"; export enum RestrictionType { - Daily = 'Daily', - Weekly = 'Weekly', - None = 'None', + Daily = "Daily", + Weekly = "Weekly", + None = "None", } export interface WeeklyResctriction { - startDay: DayOfWeek; - endDay: DayOfWeek; - startTime: Date; - endTime: Date; + startDay: DayOfWeek; + endDay: DayOfWeek; + startTime: Date; + endTime: Date; } export interface RestrictionTimesData extends JSONObject { - restictionType: RestrictionType; - dayRestrictionTimes: StartAndEndTime | null; - weeklyRestrictionTimes: Array<WeeklyResctriction>; + restictionType: RestrictionType; + dayRestrictionTimes: StartAndEndTime | null; + weeklyRestrictionTimes: Array<WeeklyResctriction>; } export default class RestrictionTimes extends DatabaseProperty { - private data: RestrictionTimesData = - RestrictionTimes.getDefaultRestrictonTimeData(); + private data: RestrictionTimesData = + RestrictionTimes.getDefaultRestrictonTimeData(); - public get restictionType(): RestrictionType { - return this.data.restictionType; - } - public set restictionType(v: RestrictionType) { - this.data.restictionType = v; + public get restictionType(): RestrictionType { + return this.data.restictionType; + } + public set restictionType(v: RestrictionType) { + this.data.restictionType = v; + } + + // dayRestrictionTimes + + public get dayRestrictionTimes(): StartAndEndTime | null { + return this.data.dayRestrictionTimes; + } + + public set dayRestrictionTimes(v: StartAndEndTime | null) { + this.data.dayRestrictionTimes = v; + } + + // weeklyRestrictionTimes + + public get weeklyRestrictionTimes(): Array<WeeklyResctriction> { + return this.data.weeklyRestrictionTimes; + } + + public set weeklyRestrictionTimes(v: Array<WeeklyResctriction>) { + this.data.weeklyRestrictionTimes = v; + } + + public constructor() { + super(); + + this.data = RestrictionTimes.getDefaultRestrictonTimeData(); + } + + public static getDefaultRestrictonTimeData(): RestrictionTimesData { + return { + restictionType: RestrictionType.None, + dayRestrictionTimes: null, + weeklyRestrictionTimes: [], + }; + } + + public static getDefault(): RestrictionTimes { + return new RestrictionTimes(); + } + + public override toJSON(): JSONObject { + return JSONFunctions.serialize({ + _type: ObjectType.RestrictionTimes, + value: { + restictionType: this.restictionType, + dayRestrictionTimes: this.dayRestrictionTimes, + weeklyRestrictionTimes: this.weeklyRestrictionTimes, + }, + }); + } + + public static override fromJSON( + json: JSONObject | RestrictionTimes, + ): RestrictionTimes { + if (json instanceof RestrictionTimes) { + return json; } - // dayRestrictionTimes - - public get dayRestrictionTimes(): StartAndEndTime | null { - return this.data.dayRestrictionTimes; + if (!json || json["_type"] !== ObjectType.RestrictionTimes) { + throw new BadDataException("Invalid Restriction Times"); } - public set dayRestrictionTimes(v: StartAndEndTime | null) { - this.data.dayRestrictionTimes = v; + if (!json["value"]) { + throw new BadDataException("Invalid Restriction Times"); } - // weeklyRestrictionTimes + const data: JSONObject = json["value"] as JSONObject; - public get weeklyRestrictionTimes(): Array<WeeklyResctriction> { - return this.data.weeklyRestrictionTimes; + const restrictionTimes: RestrictionTimes = new RestrictionTimes(); + + restrictionTimes.restictionType = data["restictionType"] as RestrictionType; + + restrictionTimes.dayRestrictionTimes = data[ + "dayRestrictionTimes" + ] as StartAndEndTime | null; + + const weeklyRestrictionTimes: Array<WeeklyResctriction> = + (data["weeklyRestrictionTimes"] as Array<WeeklyResctriction>) || {}; + + restrictionTimes.weeklyRestrictionTimes = weeklyRestrictionTimes; + + return restrictionTimes; + } + + public removeAllRestrictions(): void { + this.restictionType = RestrictionType.None; + this.dayRestrictionTimes = null; + this.weeklyRestrictionTimes = []; + } + + public addDefaultDailyRestriction(): void { + this.restictionType = RestrictionType.Daily; + this.dayRestrictionTimes = { + startTime: OneUptimeDate.getDateWithCustomTime({ + hours: 0, + minutes: 0, + seconds: 0, + }), + endTime: OneUptimeDate.getDateWithCustomTime({ + hours: 1, + minutes: 0, + seconds: 0, + }), + }; + this.weeklyRestrictionTimes = []; + } + + public addDefaultWeeklyRestriction(): void { + this.restictionType = RestrictionType.Weekly; + this.dayRestrictionTimes = null; + this.weeklyRestrictionTimes = [ + RestrictionTimes.getDefaultWeeklyRestrictionTIme(), + ]; + } + + public static getDefaultWeeklyRestrictionTIme(): WeeklyResctriction { + return { + startDay: DayOfWeek.Sunday, + endDay: DayOfWeek.Monday, + startTime: OneUptimeDate.getDateWithCustomTime({ + hours: 0, + minutes: 0, + seconds: 0, + }), + endTime: OneUptimeDate.getDateWithCustomTime({ + hours: 1, + minutes: 0, + seconds: 0, + }), + }; + } + + protected static override toDatabase( + value: RestrictionTimes | FindOperator<RestrictionTimes>, + ): JSONObject | null { + if (value && value instanceof RestrictionTimes) { + return (value as RestrictionTimes).toJSON(); + } else if (value) { + return JSONFunctions.serialize(value as any); } - public set weeklyRestrictionTimes(v: Array<WeeklyResctriction>) { - this.data.weeklyRestrictionTimes = v; + return null; + } + + protected static override fromDatabase( + value: JSONObject, + ): RestrictionTimes | null { + if (value) { + return RestrictionTimes.fromJSON(value); } - public constructor() { - super(); - - this.data = RestrictionTimes.getDefaultRestrictonTimeData(); - } - - public static getDefaultRestrictonTimeData(): RestrictionTimesData { - return { - restictionType: RestrictionType.None, - dayRestrictionTimes: null, - weeklyRestrictionTimes: [], - }; - } - - public static getDefault(): RestrictionTimes { - return new RestrictionTimes(); - } - - public override toJSON(): JSONObject { - return JSONFunctions.serialize({ - _type: ObjectType.RestrictionTimes, - value: { - restictionType: this.restictionType, - dayRestrictionTimes: this.dayRestrictionTimes, - weeklyRestrictionTimes: this.weeklyRestrictionTimes, - }, - }); - } - - public static override fromJSON( - json: JSONObject | RestrictionTimes - ): RestrictionTimes { - if (json instanceof RestrictionTimes) { - return json; - } - - if (!json || json['_type'] !== ObjectType.RestrictionTimes) { - throw new BadDataException('Invalid Restriction Times'); - } - - if (!json['value']) { - throw new BadDataException('Invalid Restriction Times'); - } - - const data: JSONObject = json['value'] as JSONObject; - - const restrictionTimes: RestrictionTimes = new RestrictionTimes(); - - restrictionTimes.restictionType = data[ - 'restictionType' - ] as RestrictionType; - - restrictionTimes.dayRestrictionTimes = data[ - 'dayRestrictionTimes' - ] as StartAndEndTime | null; - - const weeklyRestrictionTimes: Array<WeeklyResctriction> = - (data['weeklyRestrictionTimes'] as Array<WeeklyResctriction>) || {}; - - restrictionTimes.weeklyRestrictionTimes = weeklyRestrictionTimes; - - return restrictionTimes; - } - - public removeAllRestrictions(): void { - this.restictionType = RestrictionType.None; - this.dayRestrictionTimes = null; - this.weeklyRestrictionTimes = []; - } - - public addDefaultDailyRestriction(): void { - this.restictionType = RestrictionType.Daily; - this.dayRestrictionTimes = { - startTime: OneUptimeDate.getDateWithCustomTime({ - hours: 0, - minutes: 0, - seconds: 0, - }), - endTime: OneUptimeDate.getDateWithCustomTime({ - hours: 1, - minutes: 0, - seconds: 0, - }), - }; - this.weeklyRestrictionTimes = []; - } - - public addDefaultWeeklyRestriction(): void { - this.restictionType = RestrictionType.Weekly; - this.dayRestrictionTimes = null; - this.weeklyRestrictionTimes = [ - RestrictionTimes.getDefaultWeeklyRestrictionTIme(), - ]; - } - - public static getDefaultWeeklyRestrictionTIme(): WeeklyResctriction { - return { - startDay: DayOfWeek.Sunday, - endDay: DayOfWeek.Monday, - startTime: OneUptimeDate.getDateWithCustomTime({ - hours: 0, - minutes: 0, - seconds: 0, - }), - endTime: OneUptimeDate.getDateWithCustomTime({ - hours: 1, - minutes: 0, - seconds: 0, - }), - }; - } - - protected static override toDatabase( - value: RestrictionTimes | FindOperator<RestrictionTimes> - ): JSONObject | null { - if (value && value instanceof RestrictionTimes) { - return (value as RestrictionTimes).toJSON(); - } else if (value) { - return JSONFunctions.serialize(value as any); - } - - return null; - } - - protected static override fromDatabase( - value: JSONObject - ): RestrictionTimes | null { - if (value) { - return RestrictionTimes.fromJSON(value); - } - - return null; - } + return null; + } } diff --git a/Common/Types/Operation/OperationResult.ts b/Common/Types/Operation/OperationResult.ts index 4fcca0ad32..241d788d8d 100644 --- a/Common/Types/Operation/OperationResult.ts +++ b/Common/Types/Operation/OperationResult.ts @@ -1,6 +1,6 @@ enum OperationResult { - Success = 'Success', - Error = 'Error', + Success = "Success", + Error = "Error", } export default OperationResult; diff --git a/Common/Types/Operation/OperationStatus.ts b/Common/Types/Operation/OperationStatus.ts index e1c86f4b23..f12c5b8cc1 100644 --- a/Common/Types/Operation/OperationStatus.ts +++ b/Common/Types/Operation/OperationStatus.ts @@ -1,9 +1,9 @@ enum OperationStatus { - Pending = 'Pending', - Executing = 'Executing', - Success = 'Success', - Error = 'Error', - Cancelled = 'Cancelled', + Pending = "Pending", + Executing = "Executing", + Success = "Success", + Error = "Error", + Cancelled = "Cancelled", } export default OperationStatus; diff --git a/Common/Types/Permission.ts b/Common/Types/Permission.ts index 753cb83a5d..5795f88fdf 100644 --- a/Common/Types/Permission.ts +++ b/Common/Types/Permission.ts @@ -1,3517 +1,3429 @@ // Have "Project" string in the permission to make sure this permission is by Project. -import Dictionary from './Dictionary'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject } from './JSON'; -import ObjectID from './ObjectID'; +import Dictionary from "./Dictionary"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject } from "./JSON"; +import ObjectID from "./ObjectID"; export interface PermissionProps { - permission: Permission; - description: string; - isAssignableToTenant: boolean; - title: string; - isAccessControlPermission: boolean; + permission: Permission; + description: string; + isAssignableToTenant: boolean; + title: string; + isAccessControlPermission: boolean; } enum Permission { - // All users in the project will have this permission. - ProjectUser = 'ProjectUser', + // All users in the project will have this permission. + ProjectUser = "ProjectUser", - // Users who are in the project but do not have SSO authorization. - UnAuthorizedSsoUser = 'UnAuthorizedSsoUser', + // Users who are in the project but do not have SSO authorization. + UnAuthorizedSsoUser = "UnAuthorizedSsoUser", - // Owner of a Project - ProjectOwner = 'ProjectOwner', + // Owner of a Project + ProjectOwner = "ProjectOwner", - // Project Admin - ProjectAdmin = 'ProjectAdmin', - - ProjectMember = 'ProjectMember', // member of a project + // Project Admin + ProjectAdmin = "ProjectAdmin", + + ProjectMember = "ProjectMember", // member of a project - User = 'User', //registered user. Can or cannot belong to a project. - - CurrentUser = 'CurrentUser', // Current logged in user. + User = "User", //registered user. Can or cannot belong to a project. + + CurrentUser = "CurrentUser", // Current logged in user. - CustomerSupport = 'CustomerSupport', // Customer Support for OneUptime. - - Public = 'Public', // non-registered user. Everyone has this permission. - - // Billing Permissions (Owner Permission) - CreateProjectApiKey = 'CreateProjectApiKey', - DeleteProjectApiKey = 'DeleteProjectApiKey', - ReadProjectApiKey = 'ReadProjectApiKey', - EditProjectApiKey = 'EditProjectApiKey', - EditProjectApiKeyPermissions = 'EditProjectApiKeyPermissions', - - // Logs - - CreateTelemetryServiceLog = 'CreateTelemetryServiceLog', - DeleteTelemetryServiceLog = 'DeleteTelemetryServiceLog', - EditTelemetryServiceLog = 'EditTelemetryServiceLog', - ReadTelemetryServiceLog = 'ReadTelemetryServiceLog', - - // Spans - - CreateTelemetryServiceTraces = 'CreateTelemetryServiceTraces', - DeleteTelemetryServiceTraces = 'DeleteTelemetryServiceTraces', - EditTelemetryServiceTraces = 'EditTelemetryServiceTraces', - ReadTelemetryServiceTraces = 'ReadTelemetryServiceTraces', - - // Billing Permissions (Owner Permission) - ManageProjectBilling = 'ManageProjectBilling', - - // Billing Permissions (Owner Permission) - CreateProjectTeam = 'CreateProjectTeam', - DeleteProjectTeam = 'DeleteProjectTeam', - ReadProjectTeam = 'ReadProjectTeam', - EditProjectTeam = 'EditProjectTeam', - InviteProjectTeamMembers = 'InviteProjectTeamMembers', - EditProjectTeamPermissions = 'EditProjectTeamPermissions', - - CreateProjectProbe = 'CreateProjectProbe', - DeleteProjectProbe = 'DeleteProjectProbe', - EditProjectProbe = 'EditProjectProbe', - ReadProjectProbe = 'ReadProjectProbe', - - CreateTelemetryService = 'CreateTelemetryService', - DeleteTelemetryService = 'DeleteTelemetryService', - EditTelemetryService = 'EditTelemetryService', - ReadTelemetryService = 'ReadTelemetryService', - - CreateMonitorGroupResource = 'CreateMonitorGroupResource', - DeleteMonitorGroupResource = 'DeleteMonitorGroupResource', - EditMonitorGroupResource = 'EditMonitorGroupResource', - ReadMonitorGroupResource = 'ReadMonitorGroupResource', - - CreateMonitorCustomField = 'CreateMonitorCustomField', - DeleteMonitorCustomField = 'DeleteMonitorCustomField', - EditMonitorCustomField = 'EditMonitorCustomField', - ReadMonitorCustomField = 'ReadMonitorCustomField', - - CreateOnCallDutyPolicyCustomField = 'CreateOnCallDutyPolicyCustomField', - DeleteOnCallDutyPolicyCustomField = 'DeleteOnCallDutyPolicyCustomField', - EditOnCallDutyPolicyCustomField = 'EditOnCallDutyPolicyCustomField', - ReadOnCallDutyPolicyCustomField = 'ReadOnCallDutyPolicyCustomField', - - CreateOnCallDutyPolicyScheduleLayer = 'CreateOnCallDutyPolicyScheduleLayer', - DeleteOnCallDutyPolicyScheduleLayer = 'DeleteOnCallDutyPolicyScheduleLayer', - EditOnCallDutyPolicyScheduleLayer = 'EditOnCallDutyPolicyScheduleLayer', - ReadOnCallDutyPolicyScheduleLayer = 'ReadOnCallDutyPolicyScheduleLayer', - - CreateOnCallDutyPolicyScheduleLayerUser = 'CreateOnCallDutyPolicyScheduleLayerUser', - DeleteOnCallDutyPolicyScheduleLayerUser = 'DeleteOnCallDutyPolicyScheduleLayerUser', - EditOnCallDutyPolicyScheduleLayerUser = 'EditOnCallDutyPolicyScheduleLayerUser', - ReadOnCallDutyPolicyScheduleLayerUser = 'ReadOnCallDutyPolicyScheduleLayerUser', - - CreateScheduledMaintenanceCustomField = 'CreateScheduledMaintenanceCustomField', - DeleteScheduledMaintenanceCustomField = 'DeleteScheduledMaintenanceCustomField', - EditScheduledMaintenanceCustomField = 'EditScheduledMaintenanceCustomField', - ReadScheduledMaintenanceCustomField = 'ReadScheduledMaintenanceCustomField', - - CreateMonitorProbe = 'CreateMonitorProbe', - DeleteMonitorProbe = 'DeleteMonitorProbe', - EditMonitorProbe = 'EditMonitorProbe', - ReadMonitorProbe = 'ReadMonitorProbe', - - ReadSmsLog = 'ReadSmsLog', - ReadEmailLog = 'ReadEmailLog', - ReadCallLog = 'ReadCallLog', - - CreateIncidentOwnerTeam = 'CreateIncidentOwnerTeam', - DeleteIncidentOwnerTeam = 'DeleteIncidentOwnerTeam', - EditIncidentOwnerTeam = 'EditIncidentOwnerTeam', - ReadIncidentOwnerTeam = 'ReadIncidentOwnerTeam', - - CreateIncidentOwnerUser = 'CreateIncidentOwner', - DeleteIncidentOwnerUser = 'DeleteIncidentOwnerUser', - EditIncidentOwnerUser = 'EditIncidentOwnerUser', - ReadIncidentOwnerUser = 'ReadIncidentOwnerUser', - - CreateIncidentTemplate = 'CreateIncidentTemplate', - DeleteIncidentTemplate = 'DeleteIncidentTemplate', - EditIncidentTemplate = 'EditIncidentTemplate', - ReadIncidentTemplate = 'ReadIncidentTemplate', - - CreateIncidentNoteTemplate = 'CreateIncidentNoteTemplate', - DeleteIncidentNoteTemplate = 'DeleteIncidentNoteTemplate', - EditIncidentNoteTemplate = 'EditIncidentNoteTemplate', - ReadIncidentNoteTemplate = 'ReadIncidentNoteTemplate', - - CreateScheduledMaintenanceNoteTemplate = 'CreateScheduledMaintenanceNoteTemplate', - DeleteScheduledMaintenanceNoteTemplate = 'DeleteScheduledMaintenanceNoteTemplate', - EditScheduledMaintenanceNoteTemplate = 'EditScheduledMaintenanceNoteTemplate', - ReadScheduledMaintenanceNoteTemplate = 'ReadScheduledMaintenanceNoteTemplate', - - CreateIncidentTemplateOwnerTeam = 'CreateIncidentTemplateOwnerTeam', - DeleteIncidentTemplateOwnerTeam = 'DeleteIncidentTemplateOwnerTeam', - EditIncidentTemplateOwnerTeam = 'EditIncidentTemplateOwnerTeam', - ReadIncidentTemplateOwnerTeam = 'ReadIncidentTemplateOwnerTeam', - - CreateIncidentTemplateOwnerUser = 'CreateIncidentTemplateOwner', - DeleteIncidentTemplateOwnerUser = 'DeleteIncidentTemplateOwnerUser', - EditIncidentTemplateOwnerUser = 'EditIncidentTemplateOwnerUser', - ReadIncidentTemplateOwnerUser = 'ReadIncidentTemplateOwnerUser', - - CreateScheduledMaintenanceOwnerTeam = 'CreateScheduledMaintenanceOwnerTeam', - DeleteScheduledMaintenanceOwnerTeam = 'DeleteScheduledMaintenanceOwnerTeam', - EditScheduledMaintenanceOwnerTeam = 'EditScheduledMaintenanceOwnerTeam', - ReadScheduledMaintenanceOwnerTeam = 'ReadScheduledMaintenanceOwnerTeam', - - CreateScheduledMaintenanceOwnerUser = 'CreateScheduledMaintenanceOwner', - DeleteScheduledMaintenanceOwnerUser = 'DeleteScheduledMaintenanceOwnerUser', - EditScheduledMaintenanceOwnerUser = 'EditScheduledMaintenanceOwnerUser', - ReadScheduledMaintenanceOwnerUser = 'ReadScheduledMaintenanceOwnerUser', - - CreateStatusPageOwnerTeam = 'CreateStatusPageOwnerTeam', - DeleteStatusPageOwnerTeam = 'DeleteStatusPageOwnerTeam', - EditStatusPageOwnerTeam = 'EditStatusPageOwnerTeam', - ReadStatusPageOwnerTeam = 'ReadStatusPageOwnerTeam', - - CreateStatusPageOwnerUser = 'CreateStatusPageOwner', - DeleteStatusPageOwnerUser = 'DeleteStatusPageOwnerUser', - EditStatusPageOwnerUser = 'EditStatusPageOwnerUser', - ReadStatusPageOwnerUser = 'ReadStatusPageOwnerUser', - - CreateServiceCatalogOwnerTeam = 'CreateServiceCatalogOwnerTeam', - DeleteServiceCatalogOwnerTeam = 'DeleteServiceCatalogOwnerTeam', - EditServiceCatalogOwnerTeam = 'EditServiceCatalogOwnerTeam', - ReadServiceCatalogOwnerTeam = 'ReadServiceCatalogOwnerTeam', - - CreateServiceCatalogOwnerUser = 'CreateServiceCatalogOwner', - DeleteServiceCatalogOwnerUser = 'DeleteServiceCatalogOwnerUser', - EditServiceCatalogOwnerUser = 'EditServiceCatalogOwnerUser', - ReadServiceCatalogOwnerUser = 'ReadServiceCatalogOwnerUser', - - CreateMonitorOwnerTeam = 'CreateMonitorOwnerTeam', - DeleteMonitorOwnerTeam = 'DeleteMonitorOwnerTeam', - EditMonitorOwnerTeam = 'EditMonitorOwnerTeam', - ReadMonitorOwnerTeam = 'ReadMonitorOwnerTeam', - - CreateMonitorOwnerUser = 'CreateMonitorOwner', - DeleteMonitorOwnerUser = 'DeleteMonitorOwnerUser', - EditMonitorOwnerUser = 'EditMonitorOwnerUser', - ReadMonitorOwnerUser = 'ReadMonitorOwnerUser', - - CreateMonitorGroupOwnerTeam = 'CreateMonitorGroupOwnerTeam', - DeleteMonitorGroupOwnerTeam = 'DeleteMonitorGroupOwnerTeam', - EditMonitorGroupOwnerTeam = 'EditMonitorGroupOwnerTeam', - ReadMonitorGroupOwnerTeam = 'ReadMonitorGroupOwnerTeam', - - CreateMonitorGroupOwnerUser = 'CreateMonitorGroupOwner', - DeleteMonitorGroupOwnerUser = 'DeleteMonitorGroupOwnerUser', - EditMonitorGroupOwnerUser = 'EditMonitorGroupOwnerUser', - ReadMonitorGroupOwnerUser = 'ReadMonitorGroupOwnerUser', - - CreateStatusPageCustomField = 'CreateStatusPageCustomField', - DeleteStatusPageCustomField = 'DeleteStatusPageCustomField', - EditStatusPageCustomField = 'EditStatusPageCustomField', - ReadStatusPageCustomField = 'ReadStatusPageCustomField', - - CreateIncidentCustomField = 'CreateIncidentCustomField', - DeleteIncidentCustomField = 'DeleteIncidentCustomField', - EditIncidentCustomField = 'EditIncidentCustomField', - ReadIncidentCustomField = 'ReadIncidentCustomField', - - CreateProjectIncident = 'CreateProjectIncident', - DeleteProjectIncident = 'DeleteProjectIncident', - EditProjectIncident = 'EditProjectIncident', - ReadProjectIncident = 'ReadProjectIncident', - - CreateStatusPageSubscriber = 'CreateStatusPageSubscriber', - DeleteStatusPageSubscriber = 'DeleteStatusPageSubscriber', - EditStatusPageSubscriber = 'EditStatusPageSubscriber', - ReadStatusPageSubscriber = 'ReadStatusPageSubscriber', - - CreateStatusPagePrivateUser = 'CreateStatusPagePrivateUser', - DeleteStatusPagePrivateUser = 'DeleteStatusPagePrivateUser', - EditStatusPagePrivateUser = 'EditStatusPagePrivateUser', - ReadStatusPagePrivateUser = 'ReadStatusPagePrivateUser', - - CreateProjectDomain = 'CreateProjectDomain', - DeleteProjectDomain = 'DeleteProjectDomain', - EditProjectDomain = 'EditProjectDomain', - ReadProjectDomain = 'ReadProjectDomain', - - CreateStatusPageHeaderLink = 'CreateStatusPageHeaderLink', - DeleteStatusPageHeaderLink = 'DeleteStatusPageHeaderLink', - EditStatusPageHeaderLink = 'EditStatusPageHeaderLink', - ReadStatusPageHeaderLink = 'ReadStatusPageHeaderLink', - - CreateStatusPageFooterLink = 'CreateStatusPageFooterLink', - DeleteStatusPageFooterLink = 'DeleteStatusPageFooterLink', - EditStatusPageFooterLink = 'EditStatusPageFooterLink', - ReadStatusPageFooterLink = 'ReadStatusPageFooterLink', - - CreateStatusPageResource = 'CreateStatusPageResource', - DeleteStatusPageResource = 'DeleteStatusPageResource', - EditStatusPageResource = 'EditStatusPageResource', - ReadStatusPageResource = 'ReadStatusPageResource', - - CreateStatusPageHistoryChartBarColorRule = 'CreateStatusPageHistoryChartBarColorRule', - DeleteStatusPageHistoryChartBarColorRule = 'DeleteStatusPageHistoryChartBarColorRule', - EditStatusPageHistoryChartBarColorRule = 'EditStatusPageHistoryChartBarColorRule', - ReadStatusPageHistoryChartBarColorRule = 'ReadStatusPageHistoryChartBarColorRule', - - CreateWorkflow = 'CreateWorkflow', - DeleteWorkflow = 'DeleteWorkflow', - EditWorkflow = 'EditWorkflow', - ReadWorkflow = 'ReadWorkflow', - - DeleteProject = 'DeleteProject', - EditProject = 'EditProject', - ReadProject = 'ReadProject', - - CreateWorkflowLog = 'CreateWorkflowLog', - DeleteWorkflowLog = 'DeleteWorkflowLog', - EditWorkflowLog = 'EditWorkflowLog', - ReadWorkflowLog = 'ReadWorkflowLog', - - CreateWorkflowVariable = 'CreateWorkflowVariable', - DeleteWorkflowVariable = 'DeleteWorkflowVariable', - EditWorkflowVariable = 'EditWorkflowVariable', - ReadWorkflowVariable = 'ReadWorkflowVariable', - - CreateStatusPageGroup = 'CreateStatusPageGroup', - DeleteStatusPageGroup = 'DeleteStatusPageGroup', - EditStatusPageGroup = 'EditStatusPageGroup', - ReadStatusPageGroup = 'ReadStatusPageGroup', - - CreateStatusPageDomain = 'CreateStatusPageDomain', - DeleteStatusPageDomain = 'DeleteStatusPageDomain', - EditStatusPageDomain = 'EditStatusPageDomain', - ReadStatusPageDomain = 'ReadStatusPageDomain', - - CreateMonitorGroup = 'CreateMonitorGroup', - DeleteMonitorGroup = 'DeleteMonitorGroup', - EditMonitorGroup = 'EditMonitorGroup', - ReadMonitorGroup = 'ReadMonitorGroup', - - CreateProjectSSO = 'CreateProjectSSO', - DeleteProjectSSO = 'DeleteProjectSSO', - EditProjectSSO = 'EditProjectSSO', - ReadProjectSSO = 'ReadProjectSSO', - - CreateStatusPageSSO = 'CreateStatusPageSSO', - DeleteStatusPageSSO = 'DeleteStatusPageSSO', - EditStatusPageSSO = 'EditStatusPageSSO', - ReadStatusPageSSO = 'ReadStatusPageSSO', - - // Label Permissions (Owner + Admin Permission by default) - CreateProjectLabel = 'CreateProjectLabel', - EditProjectLabel = 'EditProjectLabel', - ReadProjectLabel = 'ReadProjectLabel', - DeleteProjectLabel = 'DeleteProjectLabel', - AddLabelsToProjectResources = 'AddLabelsToProjectResources', - - // Scheduled Maintenance - - // Scheduled Maintenance Status Permissions (Owner + Admin Permission by default) - CreateScheduledMaintenanceState = 'CreateScheduledMaintenanceState', - EditScheduledMaintenanceState = 'EditScheduledMaintenanceState', - ReadScheduledMaintenanceState = 'ReadScheduledMaintenanceState', - DeleteScheduledMaintenanceState = 'DeleteScheduledMaintenanceState', - - // Scheduled Maintenance Status Permissions (Owner + Admin Permission by default) - CreateScheduledMaintenanceStateTimeline = 'CreateScheduledMaintenanceStateTimeline', - EditScheduledMaintenanceStateTimeline = 'EditScheduledMaintenanceStateTimeline', - ReadScheduledMaintenanceStateTimeline = 'ReadScheduledMaintenanceStateTimeline', - DeleteScheduledMaintenanceStateTimeline = 'DeleteScheduledMaintenanceStateTimeline', - - // Resource Permissions (Team Permission) - CreateScheduledMaintenanceInternalNote = 'CreateScheduledMaintenanceInternalNote', - EditScheduledMaintenanceInternalNote = 'EditScheduledMaintenanceInternalNote', - DeleteScheduledMaintenanceInternalNote = 'DeleteScheduledMaintenanceInternalNote', - ReadScheduledMaintenanceInternalNote = 'ReadScheduledMaintenanceInternalNote', - - CreateScheduledMaintenancePublicNote = 'CreateScheduledMaintenancePublicNote', - EditScheduledMaintenancePublicNote = 'EditScheduledMaintenancePublicNote', - DeleteScheduledMaintenancePublicNote = 'DeleteScheduledMaintenancePublicNote', - ReadScheduledMaintenancePublicNote = 'ReadScheduledMaintenancePublicNote', - - CreateProjectScheduledMaintenance = 'CreateProjectScheduledMaintenance', - DeleteProjectScheduledMaintenance = 'DeleteProjectScheduledMaintenance', - EditProjectScheduledMaintenance = 'EditProjectScheduledMaintenance', - ReadProjectScheduledMaintenance = 'ReadProjectScheduledMaintenance', - - // Incident Status Permissions (Owner + Admin Permission by default) - CreateIncidentState = 'CreateIncidentState', - EditIncidentState = 'EditIncidentState', - ReadIncidentState = 'ReadIncidentState', - DeleteIncidentState = 'DeleteIncidentState', - - // Incident Status Permissions (Owner + Admin Permission by default) - CreateIncidentStateTimeline = 'CreateIncidentStateTimeline', - EditIncidentStateTimeline = 'EditIncidentStateTimeline', - ReadIncidentStateTimeline = 'ReadIncidentStateTimeline', - DeleteIncidentStateTimeline = 'DeleteIncidentStateTimeline', - - // Incident Status Permissions (Owner + Admin Permission by default) - CreateMonitorStatusTimeline = 'CreateMonitorStatusTimeline', - EditMonitorStatusTimeline = 'EditMonitorStatusTimeline', - ReadMonitorStatusTimeline = 'ReadMonitorStatusTimeline', - DeleteMonitorStatusTimeline = 'DeleteMonitorStatusTimeline', - - // MonitorStatus Permissions (Owner + Admin Permission by default) - CreateProjectMonitorStatus = 'CreateProjectMonitorStatus', - EditProjectMonitorStatus = 'EditProjectMonitorStatus', - ReadProjectMonitorStatus = 'ReadProjectMonitorStatus', - DeleteProjectMonitorStatus = 'DeleteProjectMonitorStatus', - - // MonitorStatus Permissions (Owner + Admin Permission by default) - CreateStatusPageAnnouncement = 'CreateStatusPageAnnouncement', - EditStatusPageAnnouncement = 'EditStatusPageAnnouncement', - ReadStatusPageAnnouncement = 'ReadStatusPageAnnouncement', - DeleteStatusPageAnnouncement = 'DeleteStatusPageAnnouncement', - - // Resource Permissions (Team Permission) - CreateIncidentInternalNote = 'CreateIncidentInternalNote', - EditIncidentInternalNote = 'EditIncidentInternalNote', - DeleteIncidentInternalNote = 'DeleteIncidentInternalNote', - ReadIncidentInternalNote = 'ReadIncidentInternalNote', - - CreateIncidentPublicNote = 'CreateIncidentPublicNote', - EditIncidentPublicNote = 'EditIncidentPublicNote', - DeleteIncidentPublicNote = 'DeleteIncidentPublicNote', - ReadIncidentPublicNote = 'ReadIncidentPublicNote', - - CreateInvoices = 'CreateInvoices', - EditInvoices = 'EditInvoices', - DeleteInvoices = 'DeleteInvoices', - ReadInvoices = 'ReadInvoices', - - CreateBillingPaymentMethod = 'CreateBillingPaymentMethod', - EditBillingPaymentMethod = 'EditBillingPaymentMethod', - DeleteBillingPaymentMethod = 'DeleteBillingPaymentMethod', - ReadBillingPaymentMethod = 'ReadBillingPaymentMethod', - - CreateProjectMonitor = 'CreateProjectMonitor', - EditProjectMonitor = 'EditProjectMonitor', - DeleteProjectMonitor = 'DeleteProjectMonitor', - ReadProjectMonitor = 'ReadProjectMonitor', - - // Resource Permissions (Team Permission) - CreateProjectStatusPage = 'CreateProjectStatusPage', - EditProjectStatusPage = 'EditProjectStatusPage', - DeleteProjectStatusPage = 'DeleteProjectStatusPage', - ReadProjectStatusPage = 'ReadProjectStatusPage', - - // Resource Permissions (Team Permission) - CreateProjectOnCallDutyPolicy = 'CreateProjectOnCallDutyPolicy', - EditProjectOnCallDutyPolicy = 'EditProjectOnCallDutyPolicy', - DeleteProjectOnCallDutyPolicy = 'DeleteProjectOnCallDutyPolicy', - ReadProjectOnCallDutyPolicy = 'ReadProjectOnCallDutyPolicy', - - // Resource Permissions (Team Permission) - CreateProjectOnCallDutyPolicySchedule = 'CreateProjectOnCallDutyPolicySchedule', - EditProjectOnCallDutyPolicySchedule = 'EditProjectOnCallDutyPolicySchedule', - DeleteProjectOnCallDutyPolicySchedule = 'DeleteProjectOnCallDutyPolicySchedule', - ReadProjectOnCallDutyPolicySchedule = 'ReadProjectOnCallDutyPolicySchedule', - - ReadProjectOnCallDutyPolicyExecutionLogTimeline = 'ReadProjectOnCallDutyPolicyExecutionLogTimeline', - ReadProjectOnCallDutyPolicyExecutionLog = 'ReadProjectOnCallDutyPolicyExecutionLog', - - // Resource Permissions (Team Permission) - CreateProjectOnCallDutyPolicyEscalationRule = 'CreateProjectOnCallDutyPolicyEscalationRule', - EditProjectOnCallDutyPolicyEscalationRule = 'EditProjectOnCallDutyPolicyEscalationRule', - DeleteProjectOnCallDutyPolicyEscalationRule = 'DeleteProjectOnCallDutyPolicyEscalationRule', - ReadProjectOnCallDutyPolicyEscalationRule = 'ReadProjectOnCallDutyPolicyEscalationRule', - - // Resource Permissions (Team Permission) - CreateProjectOnCallDutyPolicyEscalationRuleUser = 'CreateProjectOnCallDutyPolicyEscalationRuleUser', - EditProjectOnCallDutyPolicyEscalationRuleUser = 'EditProjectOnCallDutyPolicyEscalationRuleUser', - DeleteProjectOnCallDutyPolicyEscalationRuleUser = 'DeleteProjectOnCallDutyPolicyEscalationRuleUser', - ReadProjectOnCallDutyPolicyEscalationRuleUser = 'ReadProjectOnCallDutyPolicyEscalationRuleUser', - - // Resource Permissions (Team Permission) - CreateProjectOnCallDutyPolicyEscalationRuleSchedule = 'CreateProjectOnCallDutyPolicyEscalationRuleSchedule', - EditProjectOnCallDutyPolicyEscalationRuleSchedule = 'EditProjectOnCallDutyPolicyEscalationRuleSchedule', - DeleteProjectOnCallDutyPolicyEscalationRuleSchedule = 'DeleteProjectOnCallDutyPolicyEscalationRuleSchedule', - ReadProjectOnCallDutyPolicyEscalationRuleSchedule = 'ReadProjectOnCallDutyPolicyEscalationRuleSchedule', - - // Monitor Secret Permissions - CreateMonitorSecret = 'CreateMonitorSecret', - EditMonitorSecret = 'EditMonitorSecret', - DeleteMonitorSecret = 'DeleteMonitorSecret', - ReadMonitorSecret = 'ReadMonitorSecret', - - CreateProjectOnCallDutyPolicyEscalationRuleTeam = 'CreateProjectOnCallDutyPolicyEscalationRuleTeam', - EditProjectOnCallDutyPolicyEscalationRuleTeam = 'EditProjectOnCallDutyPolicyEscalationRuleTeam', - DeleteProjectOnCallDutyPolicyEscalationRuleTeam = 'DeleteProjectOnCallDutyPolicyEscalationRuleTeam', - ReadProjectOnCallDutyPolicyEscalationRuleTeam = 'ReadProjectOnCallDutyPolicyEscalationRuleTeam', - - // Project SMTP Config (Team Permission) - CreateProjectSMTPConfig = 'CreateProjectSMTPConfig', - EditProjectSMTPConfig = 'EditProjectSMTPConfig', - DeleteProjectSMTPConfig = 'DeleteProjectSMTPConfig', - ReadProjectSMTPConfig = 'ReadProjectSMTPConfig', - - CreateProjectCallSMSConfig = 'CreateProjectCallSMSConfig', - EditProjectCallSMSConfig = 'EditProjectCallSMSConfig', - DeleteProjectCallSMSConfig = 'DeleteProjectCallSMSConfig', - ReadProjectCallSMSConfig = 'ReadProjectCallSMSConfig', - - // Project SMTP Config (Team Permission) - CreateIncidentSeverity = 'CreateIncidentSeverity', - EditIncidentSeverity = 'EditIncidentSeverity', - DeleteIncidentSeverity = 'DeleteIncidentSeverity', - ReadIncidentSeverity = 'ReadIncidentSeverity', - - CreateServiceCatalog = 'CreateServiceCatalog', - DeleteServiceCatalog = 'DeleteServiceCatalog', - EditServiceCatalog = 'EditServiceCatalog', - ReadServiceCatalog = 'ReadServiceCatalog', - - CreateCodeRepository = 'CreateCodeRepository', - DeleteCodeRepository = 'DeleteCodeRepository', - EditCodeRepository = 'EditCodeRepository', - ReadCodeRepository = 'ReadCodeRepository', - - ReadCopilotEvent = 'ReadCopilotEvent', - - CreateServiceRepository = 'CreateServiceRepository', - DeleteServiceRepository = 'DeleteServiceRepository', - EditServiceRepository = 'EditServiceRepository', - ReadServiceRepository = 'ReadServiceRepository', + CustomerSupport = "CustomerSupport", // Customer Support for OneUptime. + + Public = "Public", // non-registered user. Everyone has this permission. + + // Billing Permissions (Owner Permission) + CreateProjectApiKey = "CreateProjectApiKey", + DeleteProjectApiKey = "DeleteProjectApiKey", + ReadProjectApiKey = "ReadProjectApiKey", + EditProjectApiKey = "EditProjectApiKey", + EditProjectApiKeyPermissions = "EditProjectApiKeyPermissions", + + // Logs + + CreateTelemetryServiceLog = "CreateTelemetryServiceLog", + DeleteTelemetryServiceLog = "DeleteTelemetryServiceLog", + EditTelemetryServiceLog = "EditTelemetryServiceLog", + ReadTelemetryServiceLog = "ReadTelemetryServiceLog", + + // Spans + + CreateTelemetryServiceTraces = "CreateTelemetryServiceTraces", + DeleteTelemetryServiceTraces = "DeleteTelemetryServiceTraces", + EditTelemetryServiceTraces = "EditTelemetryServiceTraces", + ReadTelemetryServiceTraces = "ReadTelemetryServiceTraces", + + // Billing Permissions (Owner Permission) + ManageProjectBilling = "ManageProjectBilling", + + // Billing Permissions (Owner Permission) + CreateProjectTeam = "CreateProjectTeam", + DeleteProjectTeam = "DeleteProjectTeam", + ReadProjectTeam = "ReadProjectTeam", + EditProjectTeam = "EditProjectTeam", + InviteProjectTeamMembers = "InviteProjectTeamMembers", + EditProjectTeamPermissions = "EditProjectTeamPermissions", + + CreateProjectProbe = "CreateProjectProbe", + DeleteProjectProbe = "DeleteProjectProbe", + EditProjectProbe = "EditProjectProbe", + ReadProjectProbe = "ReadProjectProbe", + + CreateTelemetryService = "CreateTelemetryService", + DeleteTelemetryService = "DeleteTelemetryService", + EditTelemetryService = "EditTelemetryService", + ReadTelemetryService = "ReadTelemetryService", + + CreateMonitorGroupResource = "CreateMonitorGroupResource", + DeleteMonitorGroupResource = "DeleteMonitorGroupResource", + EditMonitorGroupResource = "EditMonitorGroupResource", + ReadMonitorGroupResource = "ReadMonitorGroupResource", + + CreateMonitorCustomField = "CreateMonitorCustomField", + DeleteMonitorCustomField = "DeleteMonitorCustomField", + EditMonitorCustomField = "EditMonitorCustomField", + ReadMonitorCustomField = "ReadMonitorCustomField", + + CreateOnCallDutyPolicyCustomField = "CreateOnCallDutyPolicyCustomField", + DeleteOnCallDutyPolicyCustomField = "DeleteOnCallDutyPolicyCustomField", + EditOnCallDutyPolicyCustomField = "EditOnCallDutyPolicyCustomField", + ReadOnCallDutyPolicyCustomField = "ReadOnCallDutyPolicyCustomField", + + CreateOnCallDutyPolicyScheduleLayer = "CreateOnCallDutyPolicyScheduleLayer", + DeleteOnCallDutyPolicyScheduleLayer = "DeleteOnCallDutyPolicyScheduleLayer", + EditOnCallDutyPolicyScheduleLayer = "EditOnCallDutyPolicyScheduleLayer", + ReadOnCallDutyPolicyScheduleLayer = "ReadOnCallDutyPolicyScheduleLayer", + + CreateOnCallDutyPolicyScheduleLayerUser = "CreateOnCallDutyPolicyScheduleLayerUser", + DeleteOnCallDutyPolicyScheduleLayerUser = "DeleteOnCallDutyPolicyScheduleLayerUser", + EditOnCallDutyPolicyScheduleLayerUser = "EditOnCallDutyPolicyScheduleLayerUser", + ReadOnCallDutyPolicyScheduleLayerUser = "ReadOnCallDutyPolicyScheduleLayerUser", + + CreateScheduledMaintenanceCustomField = "CreateScheduledMaintenanceCustomField", + DeleteScheduledMaintenanceCustomField = "DeleteScheduledMaintenanceCustomField", + EditScheduledMaintenanceCustomField = "EditScheduledMaintenanceCustomField", + ReadScheduledMaintenanceCustomField = "ReadScheduledMaintenanceCustomField", + + CreateMonitorProbe = "CreateMonitorProbe", + DeleteMonitorProbe = "DeleteMonitorProbe", + EditMonitorProbe = "EditMonitorProbe", + ReadMonitorProbe = "ReadMonitorProbe", + + ReadSmsLog = "ReadSmsLog", + ReadEmailLog = "ReadEmailLog", + ReadCallLog = "ReadCallLog", + + CreateIncidentOwnerTeam = "CreateIncidentOwnerTeam", + DeleteIncidentOwnerTeam = "DeleteIncidentOwnerTeam", + EditIncidentOwnerTeam = "EditIncidentOwnerTeam", + ReadIncidentOwnerTeam = "ReadIncidentOwnerTeam", + + CreateIncidentOwnerUser = "CreateIncidentOwner", + DeleteIncidentOwnerUser = "DeleteIncidentOwnerUser", + EditIncidentOwnerUser = "EditIncidentOwnerUser", + ReadIncidentOwnerUser = "ReadIncidentOwnerUser", + + CreateIncidentTemplate = "CreateIncidentTemplate", + DeleteIncidentTemplate = "DeleteIncidentTemplate", + EditIncidentTemplate = "EditIncidentTemplate", + ReadIncidentTemplate = "ReadIncidentTemplate", + + CreateIncidentNoteTemplate = "CreateIncidentNoteTemplate", + DeleteIncidentNoteTemplate = "DeleteIncidentNoteTemplate", + EditIncidentNoteTemplate = "EditIncidentNoteTemplate", + ReadIncidentNoteTemplate = "ReadIncidentNoteTemplate", + + CreateScheduledMaintenanceNoteTemplate = "CreateScheduledMaintenanceNoteTemplate", + DeleteScheduledMaintenanceNoteTemplate = "DeleteScheduledMaintenanceNoteTemplate", + EditScheduledMaintenanceNoteTemplate = "EditScheduledMaintenanceNoteTemplate", + ReadScheduledMaintenanceNoteTemplate = "ReadScheduledMaintenanceNoteTemplate", + + CreateIncidentTemplateOwnerTeam = "CreateIncidentTemplateOwnerTeam", + DeleteIncidentTemplateOwnerTeam = "DeleteIncidentTemplateOwnerTeam", + EditIncidentTemplateOwnerTeam = "EditIncidentTemplateOwnerTeam", + ReadIncidentTemplateOwnerTeam = "ReadIncidentTemplateOwnerTeam", + + CreateIncidentTemplateOwnerUser = "CreateIncidentTemplateOwner", + DeleteIncidentTemplateOwnerUser = "DeleteIncidentTemplateOwnerUser", + EditIncidentTemplateOwnerUser = "EditIncidentTemplateOwnerUser", + ReadIncidentTemplateOwnerUser = "ReadIncidentTemplateOwnerUser", + + CreateScheduledMaintenanceOwnerTeam = "CreateScheduledMaintenanceOwnerTeam", + DeleteScheduledMaintenanceOwnerTeam = "DeleteScheduledMaintenanceOwnerTeam", + EditScheduledMaintenanceOwnerTeam = "EditScheduledMaintenanceOwnerTeam", + ReadScheduledMaintenanceOwnerTeam = "ReadScheduledMaintenanceOwnerTeam", + + CreateScheduledMaintenanceOwnerUser = "CreateScheduledMaintenanceOwner", + DeleteScheduledMaintenanceOwnerUser = "DeleteScheduledMaintenanceOwnerUser", + EditScheduledMaintenanceOwnerUser = "EditScheduledMaintenanceOwnerUser", + ReadScheduledMaintenanceOwnerUser = "ReadScheduledMaintenanceOwnerUser", + + CreateStatusPageOwnerTeam = "CreateStatusPageOwnerTeam", + DeleteStatusPageOwnerTeam = "DeleteStatusPageOwnerTeam", + EditStatusPageOwnerTeam = "EditStatusPageOwnerTeam", + ReadStatusPageOwnerTeam = "ReadStatusPageOwnerTeam", + + CreateStatusPageOwnerUser = "CreateStatusPageOwner", + DeleteStatusPageOwnerUser = "DeleteStatusPageOwnerUser", + EditStatusPageOwnerUser = "EditStatusPageOwnerUser", + ReadStatusPageOwnerUser = "ReadStatusPageOwnerUser", + + CreateServiceCatalogOwnerTeam = "CreateServiceCatalogOwnerTeam", + DeleteServiceCatalogOwnerTeam = "DeleteServiceCatalogOwnerTeam", + EditServiceCatalogOwnerTeam = "EditServiceCatalogOwnerTeam", + ReadServiceCatalogOwnerTeam = "ReadServiceCatalogOwnerTeam", + + CreateServiceCatalogOwnerUser = "CreateServiceCatalogOwner", + DeleteServiceCatalogOwnerUser = "DeleteServiceCatalogOwnerUser", + EditServiceCatalogOwnerUser = "EditServiceCatalogOwnerUser", + ReadServiceCatalogOwnerUser = "ReadServiceCatalogOwnerUser", + + CreateMonitorOwnerTeam = "CreateMonitorOwnerTeam", + DeleteMonitorOwnerTeam = "DeleteMonitorOwnerTeam", + EditMonitorOwnerTeam = "EditMonitorOwnerTeam", + ReadMonitorOwnerTeam = "ReadMonitorOwnerTeam", + + CreateMonitorOwnerUser = "CreateMonitorOwner", + DeleteMonitorOwnerUser = "DeleteMonitorOwnerUser", + EditMonitorOwnerUser = "EditMonitorOwnerUser", + ReadMonitorOwnerUser = "ReadMonitorOwnerUser", + + CreateMonitorGroupOwnerTeam = "CreateMonitorGroupOwnerTeam", + DeleteMonitorGroupOwnerTeam = "DeleteMonitorGroupOwnerTeam", + EditMonitorGroupOwnerTeam = "EditMonitorGroupOwnerTeam", + ReadMonitorGroupOwnerTeam = "ReadMonitorGroupOwnerTeam", + + CreateMonitorGroupOwnerUser = "CreateMonitorGroupOwner", + DeleteMonitorGroupOwnerUser = "DeleteMonitorGroupOwnerUser", + EditMonitorGroupOwnerUser = "EditMonitorGroupOwnerUser", + ReadMonitorGroupOwnerUser = "ReadMonitorGroupOwnerUser", + + CreateStatusPageCustomField = "CreateStatusPageCustomField", + DeleteStatusPageCustomField = "DeleteStatusPageCustomField", + EditStatusPageCustomField = "EditStatusPageCustomField", + ReadStatusPageCustomField = "ReadStatusPageCustomField", + + CreateIncidentCustomField = "CreateIncidentCustomField", + DeleteIncidentCustomField = "DeleteIncidentCustomField", + EditIncidentCustomField = "EditIncidentCustomField", + ReadIncidentCustomField = "ReadIncidentCustomField", + + CreateProjectIncident = "CreateProjectIncident", + DeleteProjectIncident = "DeleteProjectIncident", + EditProjectIncident = "EditProjectIncident", + ReadProjectIncident = "ReadProjectIncident", + + CreateStatusPageSubscriber = "CreateStatusPageSubscriber", + DeleteStatusPageSubscriber = "DeleteStatusPageSubscriber", + EditStatusPageSubscriber = "EditStatusPageSubscriber", + ReadStatusPageSubscriber = "ReadStatusPageSubscriber", + + CreateStatusPagePrivateUser = "CreateStatusPagePrivateUser", + DeleteStatusPagePrivateUser = "DeleteStatusPagePrivateUser", + EditStatusPagePrivateUser = "EditStatusPagePrivateUser", + ReadStatusPagePrivateUser = "ReadStatusPagePrivateUser", + + CreateProjectDomain = "CreateProjectDomain", + DeleteProjectDomain = "DeleteProjectDomain", + EditProjectDomain = "EditProjectDomain", + ReadProjectDomain = "ReadProjectDomain", + + CreateStatusPageHeaderLink = "CreateStatusPageHeaderLink", + DeleteStatusPageHeaderLink = "DeleteStatusPageHeaderLink", + EditStatusPageHeaderLink = "EditStatusPageHeaderLink", + ReadStatusPageHeaderLink = "ReadStatusPageHeaderLink", + + CreateStatusPageFooterLink = "CreateStatusPageFooterLink", + DeleteStatusPageFooterLink = "DeleteStatusPageFooterLink", + EditStatusPageFooterLink = "EditStatusPageFooterLink", + ReadStatusPageFooterLink = "ReadStatusPageFooterLink", + + CreateStatusPageResource = "CreateStatusPageResource", + DeleteStatusPageResource = "DeleteStatusPageResource", + EditStatusPageResource = "EditStatusPageResource", + ReadStatusPageResource = "ReadStatusPageResource", + + CreateStatusPageHistoryChartBarColorRule = "CreateStatusPageHistoryChartBarColorRule", + DeleteStatusPageHistoryChartBarColorRule = "DeleteStatusPageHistoryChartBarColorRule", + EditStatusPageHistoryChartBarColorRule = "EditStatusPageHistoryChartBarColorRule", + ReadStatusPageHistoryChartBarColorRule = "ReadStatusPageHistoryChartBarColorRule", + + CreateWorkflow = "CreateWorkflow", + DeleteWorkflow = "DeleteWorkflow", + EditWorkflow = "EditWorkflow", + ReadWorkflow = "ReadWorkflow", + + DeleteProject = "DeleteProject", + EditProject = "EditProject", + ReadProject = "ReadProject", + + CreateWorkflowLog = "CreateWorkflowLog", + DeleteWorkflowLog = "DeleteWorkflowLog", + EditWorkflowLog = "EditWorkflowLog", + ReadWorkflowLog = "ReadWorkflowLog", + + CreateWorkflowVariable = "CreateWorkflowVariable", + DeleteWorkflowVariable = "DeleteWorkflowVariable", + EditWorkflowVariable = "EditWorkflowVariable", + ReadWorkflowVariable = "ReadWorkflowVariable", + + CreateStatusPageGroup = "CreateStatusPageGroup", + DeleteStatusPageGroup = "DeleteStatusPageGroup", + EditStatusPageGroup = "EditStatusPageGroup", + ReadStatusPageGroup = "ReadStatusPageGroup", + + CreateStatusPageDomain = "CreateStatusPageDomain", + DeleteStatusPageDomain = "DeleteStatusPageDomain", + EditStatusPageDomain = "EditStatusPageDomain", + ReadStatusPageDomain = "ReadStatusPageDomain", + + CreateMonitorGroup = "CreateMonitorGroup", + DeleteMonitorGroup = "DeleteMonitorGroup", + EditMonitorGroup = "EditMonitorGroup", + ReadMonitorGroup = "ReadMonitorGroup", + + CreateProjectSSO = "CreateProjectSSO", + DeleteProjectSSO = "DeleteProjectSSO", + EditProjectSSO = "EditProjectSSO", + ReadProjectSSO = "ReadProjectSSO", + + CreateStatusPageSSO = "CreateStatusPageSSO", + DeleteStatusPageSSO = "DeleteStatusPageSSO", + EditStatusPageSSO = "EditStatusPageSSO", + ReadStatusPageSSO = "ReadStatusPageSSO", + + // Label Permissions (Owner + Admin Permission by default) + CreateProjectLabel = "CreateProjectLabel", + EditProjectLabel = "EditProjectLabel", + ReadProjectLabel = "ReadProjectLabel", + DeleteProjectLabel = "DeleteProjectLabel", + AddLabelsToProjectResources = "AddLabelsToProjectResources", + + // Scheduled Maintenance + + // Scheduled Maintenance Status Permissions (Owner + Admin Permission by default) + CreateScheduledMaintenanceState = "CreateScheduledMaintenanceState", + EditScheduledMaintenanceState = "EditScheduledMaintenanceState", + ReadScheduledMaintenanceState = "ReadScheduledMaintenanceState", + DeleteScheduledMaintenanceState = "DeleteScheduledMaintenanceState", + + // Scheduled Maintenance Status Permissions (Owner + Admin Permission by default) + CreateScheduledMaintenanceStateTimeline = "CreateScheduledMaintenanceStateTimeline", + EditScheduledMaintenanceStateTimeline = "EditScheduledMaintenanceStateTimeline", + ReadScheduledMaintenanceStateTimeline = "ReadScheduledMaintenanceStateTimeline", + DeleteScheduledMaintenanceStateTimeline = "DeleteScheduledMaintenanceStateTimeline", + + // Resource Permissions (Team Permission) + CreateScheduledMaintenanceInternalNote = "CreateScheduledMaintenanceInternalNote", + EditScheduledMaintenanceInternalNote = "EditScheduledMaintenanceInternalNote", + DeleteScheduledMaintenanceInternalNote = "DeleteScheduledMaintenanceInternalNote", + ReadScheduledMaintenanceInternalNote = "ReadScheduledMaintenanceInternalNote", + + CreateScheduledMaintenancePublicNote = "CreateScheduledMaintenancePublicNote", + EditScheduledMaintenancePublicNote = "EditScheduledMaintenancePublicNote", + DeleteScheduledMaintenancePublicNote = "DeleteScheduledMaintenancePublicNote", + ReadScheduledMaintenancePublicNote = "ReadScheduledMaintenancePublicNote", + + CreateProjectScheduledMaintenance = "CreateProjectScheduledMaintenance", + DeleteProjectScheduledMaintenance = "DeleteProjectScheduledMaintenance", + EditProjectScheduledMaintenance = "EditProjectScheduledMaintenance", + ReadProjectScheduledMaintenance = "ReadProjectScheduledMaintenance", + + // Incident Status Permissions (Owner + Admin Permission by default) + CreateIncidentState = "CreateIncidentState", + EditIncidentState = "EditIncidentState", + ReadIncidentState = "ReadIncidentState", + DeleteIncidentState = "DeleteIncidentState", + + // Incident Status Permissions (Owner + Admin Permission by default) + CreateIncidentStateTimeline = "CreateIncidentStateTimeline", + EditIncidentStateTimeline = "EditIncidentStateTimeline", + ReadIncidentStateTimeline = "ReadIncidentStateTimeline", + DeleteIncidentStateTimeline = "DeleteIncidentStateTimeline", + + // Incident Status Permissions (Owner + Admin Permission by default) + CreateMonitorStatusTimeline = "CreateMonitorStatusTimeline", + EditMonitorStatusTimeline = "EditMonitorStatusTimeline", + ReadMonitorStatusTimeline = "ReadMonitorStatusTimeline", + DeleteMonitorStatusTimeline = "DeleteMonitorStatusTimeline", + + // MonitorStatus Permissions (Owner + Admin Permission by default) + CreateProjectMonitorStatus = "CreateProjectMonitorStatus", + EditProjectMonitorStatus = "EditProjectMonitorStatus", + ReadProjectMonitorStatus = "ReadProjectMonitorStatus", + DeleteProjectMonitorStatus = "DeleteProjectMonitorStatus", + + // MonitorStatus Permissions (Owner + Admin Permission by default) + CreateStatusPageAnnouncement = "CreateStatusPageAnnouncement", + EditStatusPageAnnouncement = "EditStatusPageAnnouncement", + ReadStatusPageAnnouncement = "ReadStatusPageAnnouncement", + DeleteStatusPageAnnouncement = "DeleteStatusPageAnnouncement", + + // Resource Permissions (Team Permission) + CreateIncidentInternalNote = "CreateIncidentInternalNote", + EditIncidentInternalNote = "EditIncidentInternalNote", + DeleteIncidentInternalNote = "DeleteIncidentInternalNote", + ReadIncidentInternalNote = "ReadIncidentInternalNote", + + CreateIncidentPublicNote = "CreateIncidentPublicNote", + EditIncidentPublicNote = "EditIncidentPublicNote", + DeleteIncidentPublicNote = "DeleteIncidentPublicNote", + ReadIncidentPublicNote = "ReadIncidentPublicNote", + + CreateInvoices = "CreateInvoices", + EditInvoices = "EditInvoices", + DeleteInvoices = "DeleteInvoices", + ReadInvoices = "ReadInvoices", + + CreateBillingPaymentMethod = "CreateBillingPaymentMethod", + EditBillingPaymentMethod = "EditBillingPaymentMethod", + DeleteBillingPaymentMethod = "DeleteBillingPaymentMethod", + ReadBillingPaymentMethod = "ReadBillingPaymentMethod", + + CreateProjectMonitor = "CreateProjectMonitor", + EditProjectMonitor = "EditProjectMonitor", + DeleteProjectMonitor = "DeleteProjectMonitor", + ReadProjectMonitor = "ReadProjectMonitor", + + // Resource Permissions (Team Permission) + CreateProjectStatusPage = "CreateProjectStatusPage", + EditProjectStatusPage = "EditProjectStatusPage", + DeleteProjectStatusPage = "DeleteProjectStatusPage", + ReadProjectStatusPage = "ReadProjectStatusPage", + + // Resource Permissions (Team Permission) + CreateProjectOnCallDutyPolicy = "CreateProjectOnCallDutyPolicy", + EditProjectOnCallDutyPolicy = "EditProjectOnCallDutyPolicy", + DeleteProjectOnCallDutyPolicy = "DeleteProjectOnCallDutyPolicy", + ReadProjectOnCallDutyPolicy = "ReadProjectOnCallDutyPolicy", + + // Resource Permissions (Team Permission) + CreateProjectOnCallDutyPolicySchedule = "CreateProjectOnCallDutyPolicySchedule", + EditProjectOnCallDutyPolicySchedule = "EditProjectOnCallDutyPolicySchedule", + DeleteProjectOnCallDutyPolicySchedule = "DeleteProjectOnCallDutyPolicySchedule", + ReadProjectOnCallDutyPolicySchedule = "ReadProjectOnCallDutyPolicySchedule", + + ReadProjectOnCallDutyPolicyExecutionLogTimeline = "ReadProjectOnCallDutyPolicyExecutionLogTimeline", + ReadProjectOnCallDutyPolicyExecutionLog = "ReadProjectOnCallDutyPolicyExecutionLog", + + // Resource Permissions (Team Permission) + CreateProjectOnCallDutyPolicyEscalationRule = "CreateProjectOnCallDutyPolicyEscalationRule", + EditProjectOnCallDutyPolicyEscalationRule = "EditProjectOnCallDutyPolicyEscalationRule", + DeleteProjectOnCallDutyPolicyEscalationRule = "DeleteProjectOnCallDutyPolicyEscalationRule", + ReadProjectOnCallDutyPolicyEscalationRule = "ReadProjectOnCallDutyPolicyEscalationRule", + + // Resource Permissions (Team Permission) + CreateProjectOnCallDutyPolicyEscalationRuleUser = "CreateProjectOnCallDutyPolicyEscalationRuleUser", + EditProjectOnCallDutyPolicyEscalationRuleUser = "EditProjectOnCallDutyPolicyEscalationRuleUser", + DeleteProjectOnCallDutyPolicyEscalationRuleUser = "DeleteProjectOnCallDutyPolicyEscalationRuleUser", + ReadProjectOnCallDutyPolicyEscalationRuleUser = "ReadProjectOnCallDutyPolicyEscalationRuleUser", + + // Resource Permissions (Team Permission) + CreateProjectOnCallDutyPolicyEscalationRuleSchedule = "CreateProjectOnCallDutyPolicyEscalationRuleSchedule", + EditProjectOnCallDutyPolicyEscalationRuleSchedule = "EditProjectOnCallDutyPolicyEscalationRuleSchedule", + DeleteProjectOnCallDutyPolicyEscalationRuleSchedule = "DeleteProjectOnCallDutyPolicyEscalationRuleSchedule", + ReadProjectOnCallDutyPolicyEscalationRuleSchedule = "ReadProjectOnCallDutyPolicyEscalationRuleSchedule", + + // Monitor Secret Permissions + CreateMonitorSecret = "CreateMonitorSecret", + EditMonitorSecret = "EditMonitorSecret", + DeleteMonitorSecret = "DeleteMonitorSecret", + ReadMonitorSecret = "ReadMonitorSecret", + + CreateProjectOnCallDutyPolicyEscalationRuleTeam = "CreateProjectOnCallDutyPolicyEscalationRuleTeam", + EditProjectOnCallDutyPolicyEscalationRuleTeam = "EditProjectOnCallDutyPolicyEscalationRuleTeam", + DeleteProjectOnCallDutyPolicyEscalationRuleTeam = "DeleteProjectOnCallDutyPolicyEscalationRuleTeam", + ReadProjectOnCallDutyPolicyEscalationRuleTeam = "ReadProjectOnCallDutyPolicyEscalationRuleTeam", + + // Project SMTP Config (Team Permission) + CreateProjectSMTPConfig = "CreateProjectSMTPConfig", + EditProjectSMTPConfig = "EditProjectSMTPConfig", + DeleteProjectSMTPConfig = "DeleteProjectSMTPConfig", + ReadProjectSMTPConfig = "ReadProjectSMTPConfig", + + CreateProjectCallSMSConfig = "CreateProjectCallSMSConfig", + EditProjectCallSMSConfig = "EditProjectCallSMSConfig", + DeleteProjectCallSMSConfig = "DeleteProjectCallSMSConfig", + ReadProjectCallSMSConfig = "ReadProjectCallSMSConfig", + + // Project SMTP Config (Team Permission) + CreateIncidentSeverity = "CreateIncidentSeverity", + EditIncidentSeverity = "EditIncidentSeverity", + DeleteIncidentSeverity = "DeleteIncidentSeverity", + ReadIncidentSeverity = "ReadIncidentSeverity", + + CreateServiceCatalog = "CreateServiceCatalog", + DeleteServiceCatalog = "DeleteServiceCatalog", + EditServiceCatalog = "EditServiceCatalog", + ReadServiceCatalog = "ReadServiceCatalog", + + CreateCodeRepository = "CreateCodeRepository", + DeleteCodeRepository = "DeleteCodeRepository", + EditCodeRepository = "EditCodeRepository", + ReadCodeRepository = "ReadCodeRepository", + + ReadCopilotEvent = "ReadCopilotEvent", + + CreateServiceRepository = "CreateServiceRepository", + DeleteServiceRepository = "DeleteServiceRepository", + EditServiceRepository = "EditServiceRepository", + ReadServiceRepository = "ReadServiceRepository", } export class PermissionHelper { - public static doesPermissionsIntersect( - permissions1: Array<Permission>, - permissions2: Array<Permission> - ): boolean { - if (!permissions1) { - permissions1 = []; - } + public static doesPermissionsIntersect( + permissions1: Array<Permission>, + permissions2: Array<Permission>, + ): boolean { + if (!permissions1) { + permissions1 = []; + } - if (!permissions2) { - permissions2 = []; - } + if (!permissions2) { + permissions2 = []; + } + return ( + permissions1.filter((value: Permission) => { + return permissions2.includes(value); + }).length > 0 + ); + } + + public static getIntersectingPermissions( + permissions1: Array<Permission>, + permissions2: Array<Permission>, + ): Array<Permission> { + return permissions1.filter((value: Permission) => { + return permissions2.includes(value); + }); + } + + public static getTenantPermissionProps(): Array<PermissionProps> { + return this.getAllPermissionProps().filter((item: PermissionProps) => { + return item.isAssignableToTenant; + }); + } + + public static getAccessControlPermissionProps(): Array<PermissionProps> { + return this.getAllPermissionProps().filter((item: PermissionProps) => { + return item.isAccessControlPermission; + }); + } + + public static isAccessControlPermission(permission: Permission): boolean { + return ( + this.getAllPermissionProps() + .filter((item: PermissionProps) => { + return item.permission === permission; + }) + .filter((prop: PermissionProps) => { + return prop.isAccessControlPermission; + }).length > 0 + ); + } + + public static getNonAccessControlPermissions( + userPermissions: Array<UserPermission>, + ): Array<Permission> { + return userPermissions + .filter((i: UserPermission) => { return ( - permissions1.filter((value: Permission) => { - return permissions2.includes(value); - }).length > 0 + i.labelIds.length === 0 || + !PermissionHelper.isAccessControlPermission(i.permission) ); + }) + .map((i: UserPermission) => { + return i.permission; + }); + } + + public static getAccessControlPermissions( + userPermissions: Array<UserPermission>, + ): Array<UserPermission> { + return userPermissions.filter((i: UserPermission) => { + return ( + i.labelIds.length > 0 && + PermissionHelper.isAccessControlPermission(i.permission) + ); + }); + } + + public static getDescription(permission: Permission): string { + const permissionProps: Array<PermissionProps> = + this.getAllPermissionProps().filter((item: PermissionProps) => { + return item.permission === permission; + }); + + if (!permissionProps[0]) { + throw new BadDataException( + `${permission} does not have permission props`, + ); } - public static getIntersectingPermissions( - permissions1: Array<Permission>, - permissions2: Array<Permission> - ): Array<Permission> { - return permissions1.filter((value: Permission) => { - return permissions2.includes(value); - }); + return permissionProps[0].description; + } + + public static getTitle(permission: Permission): string { + const permissionProps: Array<PermissionProps> = + this.getAllPermissionProps().filter((item: PermissionProps) => { + return item.permission === permission; + }); + + if (!permissionProps[0]) { + throw new BadDataException( + `${permission} does not have permission props`, + ); } - public static getTenantPermissionProps(): Array<PermissionProps> { - return this.getAllPermissionProps().filter((item: PermissionProps) => { - return item.isAssignableToTenant; - }); + return permissionProps[0].title; + } + + public static getPermissionTitles( + permissions: Array<Permission>, + ): Array<string> { + const props: Array<PermissionProps> = this.getAllPermissionProps(); + const titles: Array<string> = []; + + for (const permission of permissions) { + const permissionProp: PermissionProps | undefined = props.find( + (item: PermissionProps) => { + return item.permission === permission; + }, + ); + + if (permissionProp) { + titles.push(permissionProp.title); + } } - public static getAccessControlPermissionProps(): Array<PermissionProps> { - return this.getAllPermissionProps().filter((item: PermissionProps) => { - return item.isAccessControlPermission; - }); + return titles; + } + + public static getAllPermissionProps(): Array<PermissionProps> { + const permissions: Array<PermissionProps> = [ + { + permission: Permission.ProjectOwner, + title: "Project Owner", + description: + "Owner of this project. Manages billing, inviting other admins to this project, and can delete this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ProjectMember, + title: "Project Member", + description: + "Member of this project. Can view most resources unless restricted.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ProjectAdmin, + title: "Project Admin", + description: + "Admin of this project. Manages team members in this project, however cannot manage billing or delete this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ProjectUser, + title: "Project User", + description: "User of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.CurrentUser, + title: "Logged in User", + description: "This permission is assigned to any registered user.", + isAssignableToTenant: false, + isAccessControlPermission: false, + }, + { + permission: Permission.CustomerSupport, + title: "Customer Support", + description: "Customer Support Resource of OneUptime.", + isAssignableToTenant: false, + isAccessControlPermission: false, + }, + { + permission: Permission.User, + title: "User", + description: + "Owner of this project, manages billing, inviting other admins to this project, and can delete this project.", + isAssignableToTenant: false, + isAccessControlPermission: false, + }, + { + permission: Permission.Public, + title: "Public", + description: + "Non registered user. Typically used for sign up or log in.", + isAssignableToTenant: false, + isAccessControlPermission: false, + }, + + { + permission: Permission.ManageProjectBilling, + title: "Manage Billing", + description: "This permission can update project billing.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.CreateProjectApiKey, + title: "Create API Key", + description: "This permission can create api keys of this project", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectApiKey, + title: "Delete API Key", + description: "This permission can delete api keys of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectApiKeyPermissions, + title: "Edit API Key Permissions", + description: + "This permission can edit api key permissions of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectApiKey, + title: "Edit API Key", + description: "This permission can edit api keys of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectApiKey, + title: "Read API Key", + description: "This permission can read api keys of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectLabel, + title: "Create Label", + description: "This permission can create labels this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectLabel, + title: "Delete Label", + description: "This permission can delete labels of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.AddLabelsToProjectResources, + title: "Add Label to Resources", + description: + "This permission can add project labels to resources of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectLabel, + title: "Edit Label", + description: "This permission can edit labels of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectLabel, + title: "Read Label", + description: "This permission can read labels of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentState, + title: "Create Incident State", + description: "This permission can create incident states this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentState, + title: "Delete Incident State", + description: + "This permission can delete incident states of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentState, + title: "Edit Incident State", + description: + "This permission can edit incident states of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentState, + title: "Read Incident State", + description: + "This permission can read incident states of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentStateTimeline, + title: "Create Incident State Timeline", + description: + "This permission can create incident state history of an incident in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentStateTimeline, + title: "Delete Incident State Timeline", + description: + "This permission can delete incident state history of an incident in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentStateTimeline, + title: "Edit Incident State Timeline", + description: + "This permission can edit incident state history of an incident in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentStateTimeline, + title: "Read Incident State Timeline", + description: + "This permission can read incident state history of an incident in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateMonitorStatusTimeline, + title: "Create Monitor Status Timeline", + description: + "This permission can create Monitor Status history of an incident in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorStatusTimeline, + title: "Delete Monitor Status Timeline", + description: + "This permission can delete Monitor Status history of an incident in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditMonitorStatusTimeline, + title: "Edit Monitor Status Timeline", + description: + "This permission can edit Monitor Status history of an incident in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadMonitorStatusTimeline, + title: "Read Monitor Status Timeline", + description: + "This permission can read Monitor Status history of an incident in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.ReadEmailLog, + title: "Read Email Log", + description: "This permission can read email logs of the project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectMonitorStatus, + title: "Create Monitor Status", + description: + "This permission can create monitor statuses this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectMonitorStatus, + title: "Delete Monitor Status", + description: + "This permission can delete monitor statuses of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectMonitorStatus, + title: "Edit Monitor Status", + description: + "This permission can edit monitor statuses of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectMonitorStatus, + title: "Read Monitor Status", + description: + "This permission can read monitor statuses of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageAnnouncement, + title: "Create Status Page Announcement", + description: + "This permission can create Status Page Announcement this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageAnnouncement, + title: "Delete Status Page Announcement", + description: + "This permission can delete Status Page Announcement of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageAnnouncement, + title: "Edit Status Page Announcement", + description: + "This permission can edit Status Page Announcement of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageAnnouncement, + title: "Read Status Page Announcement", + description: + "This permission can read Status Page Announcement of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageAnnouncement, + title: "Create Monitor Status", + description: + "This permission can create monitor statuses this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageAnnouncement, + title: "Delete Monitor Status", + description: + "This permission can delete monitor statuses of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageAnnouncement, + title: "Edit Monitor Status", + description: + "This permission can edit monitor statuses of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageAnnouncement, + title: "Read Monitor Status", + description: + "This permission can read monitor statuses of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectDomain, + title: "Create Domain", + description: "This permission can create Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectDomain, + title: "Delete Domain", + description: "This permission can delete Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectDomain, + title: "Edit Domain", + description: "This permission can edit Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectDomain, + title: "Read Domain", + description: "This permission can read Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageHeaderLink, + title: "Create Header Link", + description: "This permission can create Header Link in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageHeaderLink, + title: "Delete Header Link", + description: "This permission can delete Header Link in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageHeaderLink, + title: "Edit Header Link", + description: "This permission can edit Header Link in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageHeaderLink, + title: "Read Header Link", + description: "This permission can read Header Link in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageFooterLink, + title: "Create Footer Link", + description: "This permission can create Footer Link in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageFooterLink, + title: "Delete Footer Link", + description: "This permission can delete Footer Link in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageFooterLink, + title: "Edit Footer Link", + description: "This permission can edit Footer Link in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageFooterLink, + title: "Read Footer Link", + description: "This permission can read Footer Link in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageResource, + title: "Create Status Page Resource", + description: + "This permission can create Status Page Resource in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageResource, + title: "Delete Status Page Resource", + description: + "This permission can delete Status Page Resource in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageResource, + title: "Edit Status Page Resource", + description: + "This permission can edit Status Page Resource in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageResource, + title: "Read Status Page Resource", + description: + "This permission can read Status Page Resource in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageHistoryChartBarColorRule, + title: "Create Status Page History Chart Bar Color Rule", + description: + "This permission can create Status Page History Chart Bar Color Rule in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageHistoryChartBarColorRule, + title: "Delete Status Page History Chart Bar Color Rule", + description: + "This permission can delete Status Page History Chart Bar Color Rule in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageHistoryChartBarColorRule, + title: "Edit Status Page History Chart Bar Color Rule", + description: + "This permission can edit Status Page History Chart Bar Color Rule in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageHistoryChartBarColorRule, + title: "Read Status Page History Chart Bar Color Rule", + description: + "This permission can read Status Page History Chart Bar Color Rule in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateWorkflow, + title: "Create Workflow", + description: "This permission can create Workflow in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteWorkflow, + title: "Delete Workflow", + description: "This permission can delete Workflow in this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditWorkflow, + title: "Edit Workflow", + description: "This permission can edit Workflow in this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadWorkflow, + title: "Read Workflow", + description: "This permission can read Workflow in this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.DeleteProject, + title: "Delete Project", + description: "This permission can delete Project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProject, + title: "Edit Project", + description: "This permission can edit Project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProject, + title: "Read Project", + description: "This permission can read this Project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateWorkflowVariable, + title: "Create Workflow Variables", + description: + "This permission can create Workflow Variables in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteWorkflowVariable, + title: "Delete Workflow Variables", + description: + "This permission can delete Workflow Variables in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditWorkflowVariable, + title: "Edit Workflow Variables", + description: + "This permission can edit Workflow Variables in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadWorkflowVariable, + title: "Read Workflow Variables", + description: + "This permission can read Workflow Variables in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateWorkflowLog, + title: "Create Workflow Log", + description: "This permission can create Workflow Log in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteWorkflowLog, + title: "Delete Workflow Log", + description: "This permission can delete Workflow Log in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditWorkflowLog, + title: "Edit Workflow Log", + description: "This permission can edit Workflow Log in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadWorkflowLog, + title: "Read Workflow Log", + description: "This permission can read Workflow Log in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageGroup, + title: "Create Status Page Group", + description: + "This permission can create Status Page Group in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageGroup, + title: "Delete Status Page Group", + description: + "This permission can delete Status Page Group in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageGroup, + title: "Edit Status Page Group", + description: + "This permission can edit Status Page Group in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageGroup, + title: "Read Status Page Group", + description: + "This permission can read Status Page Group in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageDomain, + title: "Create Status Page Domain", + description: + "This permission can create Status Page Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageDomain, + title: "Delete Status Page Domain", + description: + "This permission can delete Status Page Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageDomain, + title: "Edit Status Page Domain", + description: + "This permission can edit Status Page Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageDomain, + title: "Read Status Page Domain", + description: + "This permission can read Status Page Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateMonitorGroup, + title: "Create Monitor Group", + description: + "This permission can create Monitor Group in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorGroup, + title: "Delete Monitor Group", + description: + "This permission can delete Monitor Group in this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditMonitorGroup, + title: "Edit Monitor Group", + description: "This permission can edit Monitor Group in this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadMonitorGroup, + title: "Read Monitor Group", + description: "This permission can read Monitor Group in this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateProjectSSO, + title: "Create Project SSO", + description: "This permission can create Project SSO in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectSSO, + title: "Delete Project SSO", + description: "This permission can delete Project SSO in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectSSO, + title: "Edit Project SSO", + description: "This permission can edit Project SSO in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectSSO, + title: "Read Project SSO", + description: "This permission can read Project SSO in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageSSO, + title: "Create Status Page SSO", + description: + "This permission can create Status Page SSO in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageSSO, + title: "Delete Status Page SSO", + description: + "This permission can delete Status Page SSO in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageSSO, + title: "Edit Status Page SSO", + description: + "This permission can edit Status Page SSO in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageSSO, + title: "Read Status Page SSO", + description: + "This permission can read Status Page SSO in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectSMTPConfig, + title: "Create SMTP Config", + description: "This permission can create SMTP configs this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectSMTPConfig, + title: "Delete SMTP Config", + description: "This permission can delete SMTP configs of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectSMTPConfig, + title: "Edit SMTP Config", + description: "This permission can edit SMTP configs of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectSMTPConfig, + title: "Read SMTP Config", + description: "This permission can read SMTP configs of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectCallSMSConfig, + title: "Create Call and SMS", + description: "This permission can create Call and SMS this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectCallSMSConfig, + title: "Delete Call and SMS", + description: "This permission can delete Call and SMS of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectCallSMSConfig, + title: "Edit Call and SMS", + description: "This permission can edit Call and SMS of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectCallSMSConfig, + title: "Read Call and SMS", + description: "This permission can read Call and SMS of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageDomain, + title: "Create Status Page Domain", + description: + "This permission can create Status Page Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageDomain, + title: "Delete Status Page Domain", + description: + "This permission can delete Status Page Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageDomain, + title: "Edit Status Page Domain", + description: + "This permission can edit Status Page Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageDomain, + title: "Read Status Page Domain", + description: + "This permission can read Status Page Domain in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentSeverity, + title: "Create Incident Severity", + description: + "This permission can create Incident Severity this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentSeverity, + title: "Delete Incident Severity", + description: + "This permission can delete Incident Severity of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentSeverity, + title: "Edit Incident Severity", + description: + "This permission can edit Incident Severity of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentSeverity, + title: "Read Incident Severity", + description: + "This permission can read Incident Severity of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectTeam, + title: "Create Team", + description: "This permission can create teams this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectTeam, + title: "Delete Team", + description: "This permission can delete teams of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.InviteProjectTeamMembers, + title: "Invite New Members", + description: "This permission can invite users to the team.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectTeamPermissions, + title: "Edit Team Permissions", + description: + "This permission can edit team permissions of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectTeam, + title: "Edit Team", + description: "This permission can edit teams of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectTeam, + title: "Read Teams", + description: "This permission can read teams of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectMonitor, + title: "Create Monitor", + description: "This permission can create monitor this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectMonitor, + title: "Delete Monitor", + description: "This permission can delete monitor of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditProjectMonitor, + title: "Edit Monitor", + description: "This permission can edit monitor of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadProjectMonitor, + title: "Read Monitor", + description: "This permission can read monitor of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateIncidentInternalNote, + title: "Create Incident Internal Note", + description: + "This permission can create Incident Internal Note this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentInternalNote, + title: "Delete Incident Internal Note", + description: + "This permission can delete Incident Internal Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentInternalNote, + title: "Edit Incident Internal Note", + description: + "This permission can edit Incident Internal Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentInternalNote, + title: "Read Incident Internal Note", + description: + "This permission can read Incident Internal Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentPublicNote, + title: "Create Incident Status Page Note", + description: + "This permission can create Incident Status Page Note this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentPublicNote, + title: "Delete Incident Status Page Note", + description: + "This permission can delete Incident Status Page Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentPublicNote, + title: "Edit Incident Status Page Note", + description: + "This permission can edit Incident Status Page Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentPublicNote, + title: "Read Incident Status Page Note", + description: + "This permission can read Incident Status Page Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateInvoices, + title: "Create Invoices", + description: "This permission can create Invoices this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteInvoices, + title: "Delete Invoices", + description: "This permission can delete Invoices of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditInvoices, + title: "Edit Invoices", + description: "This permission can edit Invoices of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadInvoices, + title: "Read Invoices", + description: "This permission can read Invoices of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateBillingPaymentMethod, + title: "Create Payment Method", + description: "This permission can create Payment Method this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteBillingPaymentMethod, + title: "Delete Payment Method", + description: + "This permission can delete Payment Method of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditBillingPaymentMethod, + title: "Edit Payment Method", + description: "This permission can edit Payment Method of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadBillingPaymentMethod, + title: "Read Payment Method", + description: "This permission can read Payment Method of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + title: "Read On-Call Duty Policy Execution Log Timeline", + description: + "This permission can read teams in on-call duty execution log timeline.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.ReadProjectOnCallDutyPolicyExecutionLog, + title: "Read On-Call Duty Policy Execution Log", + description: + "This permission can read teams in on-call duty execution log.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + title: "Create On-Call Duty Policy Escalation Rule", + description: + "This permission can create teams in on-call duty escalation rule this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectOnCallDutyPolicyEscalationRuleTeam, + title: "Delete On-Call Duty Policy Escalation Rule Team", + description: + "This permission can delete teams in on-call duty escalation rule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectOnCallDutyPolicyEscalationRuleTeam, + title: "Edit On-Call Duty Policy Escalation Rule Team", + description: + "This permission can edit teams in on-call duty escalation rule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + title: "Read On-Call Duty Policy Escalation Rule Team", + description: + "This permission can read teams in on-call duty escalation rule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + title: "Create On-Call Duty Policy Escalation Rule Schedule", + description: + "This permission can create teams in on-call duty escalation rule schedule this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: + Permission.DeleteProjectOnCallDutyPolicyEscalationRuleSchedule, + title: "Delete On-Call Duty Policy Escalation Rule Schedule", + description: + "This permission can delete teams in on-call duty escalation rule schedule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: + Permission.EditProjectOnCallDutyPolicyEscalationRuleSchedule, + title: "Edit On-Call Duty Policy Escalation Rule Schedule", + description: + "This permission can edit teams in on-call duty escalation rule schedule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + title: "Read On-Call Duty Policy Escalation Rule Schedule", + description: + "This permission can read teams in on-call duty escalation rule schedule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateMonitorSecret, + title: "Create Monitor Secret", + description: "This permission can create monitor secret.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorSecret, + title: "Delete Monitor Secret", + description: "This permission can delete monitor secret", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditMonitorSecret, + title: "Edit Monitor Secret", + description: "This permission can edit monitor secret.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadMonitorSecret, + title: "Read Monitor Secret", + description: "This permission can read monitor secret.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + title: "Create On-Call Duty Policy Escalation Rule User", + description: + "This permission can create on-call duty escalation rule this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectOnCallDutyPolicyEscalationRuleUser, + title: "Delete On-Call Duty Policy Escalation Rule User", + description: + "This permission can delete on-call duty escalation rule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectOnCallDutyPolicyEscalationRuleUser, + title: "Edit On-Call Duty Policy Escalation Rule User", + description: + "This permission can edit on-call duty escalation rule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + title: "Read On-Call Duty Policy Escalation Rule User", + description: + "This permission can read on-call duty escalation rule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectOnCallDutyPolicyEscalationRule, + title: "Create On-Call Duty Policy Escalation Rule", + description: + "This permission can create on-call duty escalation rule this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectOnCallDutyPolicyEscalationRule, + title: "Delete On-Call Duty Policy Escalation Rule", + description: + "This permission can delete on-call duty escalation rule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectOnCallDutyPolicyEscalationRule, + title: "Edit On-Call Duty Policy Escalation Rule", + description: + "This permission can edit on-call duty escalation rule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectOnCallDutyPolicyEscalationRule, + title: "Read On-Call Duty Policy Escalation Rule", + description: + "This permission can read on-call duty escalation rule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectOnCallDutyPolicy, + title: "Create On-Call Duty Policy", + description: "This permission can create on-call duty this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectOnCallDutyPolicy, + title: "Delete On-Call Duty Policy", + description: "This permission can delete on-call duty of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditProjectOnCallDutyPolicy, + title: "Edit On-Call Duty Policy", + description: "This permission can edit on-call duty of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadProjectOnCallDutyPolicy, + title: "Read On-Call Duty Policy", + description: "This permission can read on-call duty of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateProjectOnCallDutyPolicySchedule, + title: "Create On-Call Duty Policy Schedule", + description: + "This permission can create on-call duty schedule this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectOnCallDutyPolicySchedule, + title: "Delete On-Call Duty Policy Schedule", + description: + "This permission can delete on-call duty schedule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditProjectOnCallDutyPolicySchedule, + title: "Edit On-Call Duty Policy Schedule", + description: + "This permission can edit on-call duty schedule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadProjectOnCallDutyPolicySchedule, + title: "Read On-Call Duty Policy Schedule", + description: + "This permission can read on-call duty schedule of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateProjectStatusPage, + title: "Create Status Page", + description: "This permission can create status pages this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectStatusPage, + title: "Delete Status Page", + description: "This permission can delete status pages of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditProjectStatusPage, + title: "Edit Status Page", + description: "This permission can edit status pages of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadProjectStatusPage, + title: "Read Status Page", + description: "This permission can read status pages of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateProjectProbe, + title: "Create Probe", + description: "This permission can create probe this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectProbe, + title: "Delete Probe", + description: "This permission can delete probe of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditProjectProbe, + title: "Edit Probe", + description: "This permission can edit probe of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadProjectProbe, + title: "Read Probe", + description: "This permission can read probe of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateTelemetryService, + title: "Create Telemetry Service", + description: + "This permission can create Telemetry Service this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteTelemetryService, + title: "Delete Telemetry Service", + description: + "This permission can delete Telemetry Service of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditTelemetryService, + title: "Edit Telemetry Service", + description: + "This permission can edit Telemetry Service of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadTelemetryService, + title: "Read Telemetry Service", + description: "This permission can read Service of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateMonitorGroupResource, + title: "Create Monitor Group Resource", + description: "This permission can create monitor group resource.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorGroupResource, + title: "Delete Monitor Group Resource", + description: "This permission can delete monitor group resource.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditMonitorGroupResource, + title: "Edit Monitor Group Resource", + description: "This permission can edit monitor group resource.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadMonitorGroupResource, + title: "Read Monitor Group Resource", + description: "This permission can read monitor group resource.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateOnCallDutyPolicyCustomField, + title: "Create On-Call Policy Custom Field", + description: + "This permission can create On-Call Policy Custom Field this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteOnCallDutyPolicyCustomField, + title: "Delete On-Call Policy Custom Field", + description: + "This permission can delete On-Call Policy Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditOnCallDutyPolicyCustomField, + title: "Edit On-Call Policy Custom Field", + description: + "This permission can edit On-Call Policy Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadOnCallDutyPolicyCustomField, + title: "Read On-Call Policy Custom Field", + description: + "This permission can read On-Call Policy Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateOnCallDutyPolicyScheduleLayer, + title: "Create On-Call Schedule Layer", + description: + "This permission can create On-Call Schedule Layer this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteOnCallDutyPolicyScheduleLayer, + title: "Delete On-Call Schedule Layer", + description: + "This permission can delete On-Call Schedule Layer of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditOnCallDutyPolicyScheduleLayer, + title: "Edit On-Call Schedule Layer", + description: + "This permission can edit On-Call Schedule Layer of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadOnCallDutyPolicyScheduleLayer, + title: "Read On-Call Schedule Layer", + description: + "This permission can read On-Call Schedule Layer of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateOnCallDutyPolicyScheduleLayerUser, + title: "Create On-Call Schedule Layer User", + description: + "This permission can create On-Call Schedule Layer User this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteOnCallDutyPolicyScheduleLayerUser, + title: "Delete On-Call Schedule Layer User", + description: + "This permission can delete On-Call Schedule Layer User of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditOnCallDutyPolicyScheduleLayerUser, + title: "Edit On-Call Schedule Layer User", + description: + "This permission can edit On-Call Schedule Layer User of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadOnCallDutyPolicyScheduleLayerUser, + title: "Read On-Call Schedule Layer User", + description: + "This permission can read On-Call Schedule Layer User of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateMonitorCustomField, + title: "Create Monitor Custom Field", + description: + "This permission can create Monitor Custom Field this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorCustomField, + title: "Delete Monitor Custom Field", + description: + "This permission can delete Monitor Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditMonitorCustomField, + title: "Edit Monitor Custom Field", + description: + "This permission can edit Monitor Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadMonitorCustomField, + title: "Read Monitor Custom Field", + description: + "This permission can read Monitor Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentCustomField, + title: "Create Incident Custom Field", + description: + "This permission can create Incident Custom Field this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentCustomField, + title: "Delete Incident Custom Field", + description: + "This permission can delete Incident Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentCustomField, + title: "Edit Incident Custom Field", + description: + "This permission can edit Incident Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentCustomField, + title: "Read Incident Custom Field", + description: + "This permission can read Incident Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageCustomField, + title: "Create Status Page Custom Field", + description: + "This permission can create Status Page Custom Field this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageCustomField, + title: "Delete Status Page Custom Field", + description: + "This permission can delete Status Page Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageCustomField, + title: "Edit Status Page Custom Field", + description: + "This permission can edit Status Page Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageCustomField, + title: "Read Status Page Custom Field", + description: + "This permission can read Status Page Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateScheduledMaintenanceCustomField, + title: "Create Scheduled Maintenance Custom Field", + description: + "This permission can create Scheduled Maintenance Custom Field this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteScheduledMaintenanceCustomField, + title: "Delete Scheduled Maintenance Custom Field", + description: + "This permission can delete Scheduled Maintenance Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditScheduledMaintenanceCustomField, + title: "Edit Scheduled Maintenance Custom Field", + description: + "This permission can edit Scheduled Maintenance Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadScheduledMaintenanceCustomField, + title: "Read Scheduled Maintenance Custom Field", + description: + "This permission can read Scheduled Maintenance Custom Field of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.ReadSmsLog, + title: "Read SMS Log", + description: "This permission can read SMS Log of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.ReadCallLog, + title: "Read Call Log", + description: "This permission can read Call Logs of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateMonitorProbe, + title: "Create Monitor Probe", + description: "This permission can create Monitor Probe this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorProbe, + title: "Delete Monitor Probe", + description: + "This permission can delete Monitor Probe of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditMonitorProbe, + title: "Edit Monitor Probe", + description: "This permission can edit Monitor Probe of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadMonitorProbe, + title: "Read Monitor Probe", + description: "This permission can read Monitor Probe of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateTelemetryServiceLog, + title: "Create Telemetry Service Log", + description: + "This permission can create Telemetry Service Log this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteTelemetryServiceLog, + title: "Delete Telemetry Service Log", + description: + "This permission can delete Telemetry Service Log of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditTelemetryServiceLog, + title: "Edit Telemetry Service Log", + description: + "This permission can edit Telemetry Service Log of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadTelemetryServiceLog, + title: "Read Telemetry Service Log", + description: + "This permission can read Telemetry Service Log of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateCodeRepository, + title: "Create Code Repository", + description: "This permission can create Code Repository this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.DeleteCodeRepository, + title: "Delete Code Repository", + description: + "This permission can delete Code Repository of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditCodeRepository, + title: "Edit Code Repository", + description: + "This permission can edit Code Repository of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadCodeRepository, + title: "Read Code Repository", + description: + "This permission can read Code Repository of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateServiceRepository, + title: "Create Service Repository", + description: + "This permission can create Service Repository this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.DeleteServiceRepository, + title: "Delete Service Repository", + description: + "This permission can delete Service Repository of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditServiceRepository, + title: "Edit Service Repository", + description: + "This permission can edit Service Repository of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadServiceRepository, + title: "Read Service Repository", + description: + "This permission can read Service Repository of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.ReadCopilotEvent, + title: "Read Copilot Event", + description: "This permission can read Copilot Event of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateServiceCatalog, + title: "Create Service Catalog", + description: "This permission can create Service Catalog this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.DeleteServiceCatalog, + title: "Delete Service Catalog", + description: + "This permission can delete Service Catalog of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditServiceCatalog, + title: "Edit Service Catalog", + description: + "This permission can edit Service Catalog of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadServiceCatalog, + title: "Read Service Catalog", + description: + "This permission can read Service Catalog of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateTelemetryServiceTraces, + title: "Create Telemetry Service Traces", + description: + "This permission can create Telemetry Service Traces this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteTelemetryServiceTraces, + title: "Delete Telemetry Service Traces", + description: + "This permission can delete Telemetry Service Traces of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditTelemetryServiceTraces, + title: "Edit Telemetry Service Traces", + description: + "This permission can edit Telemetry Service Traces of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadTelemetryServiceTraces, + title: "Read Telemetry Service Traces", + description: + "This permission can read Telemetry Service Traces of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateScheduledMaintenanceOwnerTeam, + title: "Create Scheduled Maintenance Team Owner", + description: + "This permission can create Scheduled Maintenance Team Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteScheduledMaintenanceOwnerTeam, + title: "Delete Scheduled Maintenance Team Owner", + description: + "This permission can delete Scheduled Maintenance Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditScheduledMaintenanceOwnerTeam, + title: "Edit Scheduled Maintenance Team Owner", + description: + "This permission can edit Scheduled Maintenance Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadScheduledMaintenanceOwnerTeam, + title: "Read Scheduled Maintenance Team Owner", + description: + "This permission can read Scheduled Maintenance Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateScheduledMaintenanceOwnerUser, + title: "Create Scheduled Maintenance User Owner", + description: + "This permission can create Scheduled Maintenance User Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteScheduledMaintenanceOwnerUser, + title: "Delete Scheduled Maintenance User Owner", + description: + "This permission can delete Scheduled Maintenance User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditScheduledMaintenanceOwnerUser, + title: "Edit Scheduled Maintenance User Owner", + description: + "This permission can edit Scheduled Maintenance User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadScheduledMaintenanceOwnerUser, + title: "Read Scheduled Maintenance User Owner", + description: + "This permission can read Scheduled Maintenance User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentOwnerTeam, + title: "Create Incident Team Owner", + description: + "This permission can create Incident Team Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentOwnerTeam, + title: "Delete Incident Team Owner", + description: + "This permission can delete Incident Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentOwnerTeam, + title: "Edit Incident Team Owner", + description: + "This permission can edit Incident Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentOwnerTeam, + title: "Read Incident Team Owner", + description: + "This permission can read Incident Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentNoteTemplate, + title: "Create Incident Note Template", + description: + "This permission can create Incident Note Template this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentNoteTemplate, + title: "Delete Incident Note Template", + description: + "This permission can delete Incident Note Template of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentNoteTemplate, + title: "Edit Incident Note Template", + description: + "This permission can edit Incident Note Template of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentNoteTemplate, + title: "Read Incident Note Template", + description: + "This permission can read Incident Note Template of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateScheduledMaintenanceNoteTemplate, + title: "Create Scheduled Maintenance Note Template", + description: + "This permission can create Scheduled Maintenance Note Template this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteScheduledMaintenanceNoteTemplate, + title: "Delete Scheduled Maintenance Note Template", + description: + "This permission can delete Scheduled Maintenance Note Template of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditScheduledMaintenanceNoteTemplate, + title: "Edit Scheduled Maintenance Note Template", + description: + "This permission can edit Scheduled Maintenance Note Template of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadScheduledMaintenanceNoteTemplate, + title: "Read Scheduled Maintenance Note Template", + description: + "This permission can read Scheduled Maintenance Note Template of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentTemplate, + title: "Create Incident Template", + description: + "This permission can create Incident Template this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentTemplate, + title: "Delete Incident Template", + description: + "This permission can delete Incident Template of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentTemplate, + title: "Edit Incident Template", + description: + "This permission can edit Incident Template of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentTemplate, + title: "Read Incident Template", + description: + "This permission can read Incident Template of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentOwnerUser, + title: "Create Incident User Owner", + description: + "This permission can create Incident User Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentOwnerUser, + title: "Delete Incident User Owner", + description: + "This permission can delete Incident User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentOwnerUser, + title: "Edit Incident User Owner", + description: + "This permission can edit Incident User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentOwnerUser, + title: "Read Incident User Owner", + description: + "This permission can read Incident User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageOwnerTeam, + title: "Create Status Page Team Owner", + description: + "This permission can create Status Page Team Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageOwnerTeam, + title: "Delete Status Page Team Owner", + description: + "This permission can delete Status Page Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageOwnerTeam, + title: "Edit Status Page Team Owner", + description: + "This permission can edit Status Page Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageOwnerTeam, + title: "Read Status Page Team Owner", + description: + "This permission can read Status Page Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentTemplateOwnerTeam, + title: "Create IncidentTemplate Team Owner", + description: + "This permission can create IncidentTemplate Team Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentTemplateOwnerTeam, + title: "Delete IncidentTemplate Team Owner", + description: + "This permission can delete IncidentTemplate Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentTemplateOwnerTeam, + title: "Edit IncidentTemplate Team Owner", + description: + "This permission can edit IncidentTemplate Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentTemplateOwnerTeam, + title: "Read IncidentTemplate Team Owner", + description: + "This permission can read IncidentTemplate Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateServiceCatalogOwnerTeam, + title: "Create Service Catalog Team Owner", + description: + "This permission can create Service Catalog Team Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteServiceCatalogOwnerTeam, + title: "Delete Service Catalog Team Owner", + description: + "This permission can delete Service Catalog Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditServiceCatalogOwnerTeam, + title: "Edit Service Catalog Team Owner", + description: + "This permission can edit Service Catalog Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadServiceCatalogOwnerTeam, + title: "Read Service Catalog Team Owner", + description: + "This permission can read Service Catalog Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateServiceCatalogOwnerUser, + title: "Create Service Catalog User Owner", + description: + "This permission can create Service Catalog User Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteServiceCatalogOwnerUser, + title: "Delete Service Catalog User Owner", + description: + "This permission can delete Service Catalog User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditServiceCatalogOwnerUser, + title: "Edit Service Catalog User Owner", + description: + "This permission can edit Service Catalog User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadServiceCatalogOwnerUser, + title: "Read Service Catalog User Owner", + description: + "This permission can read Service Catalog User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateIncidentTemplateOwnerUser, + title: "Create IncidentTemplate User Owner", + description: + "This permission can create IncidentTemplate User Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteIncidentTemplateOwnerUser, + title: "Delete IncidentTemplate User Owner", + description: + "This permission can delete IncidentTemplate User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditIncidentTemplateOwnerUser, + title: "Edit IncidentTemplate User Owner", + description: + "This permission can edit IncidentTemplate User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadIncidentTemplateOwnerUser, + title: "Read IncidentTemplate User Owner", + description: + "This permission can read IncidentTemplate User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageOwnerTeam, + title: "Create Status Page Team Owner", + description: + "This permission can create Status Page Team Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageOwnerTeam, + title: "Delete Status Page Team Owner", + description: + "This permission can delete Status Page Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageOwnerTeam, + title: "Edit Status Page Team Owner", + description: + "This permission can edit Status Page Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageOwnerTeam, + title: "Read Status Page Team Owner", + description: + "This permission can read Status Page Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPageOwnerUser, + title: "Create Status Page User Owner", + description: + "This permission can create Status Page User Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageOwnerUser, + title: "Delete Status Page User Owner", + description: + "This permission can delete Status Page User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageOwnerUser, + title: "Edit Status Page User Owner", + description: + "This permission can edit Status Page User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageOwnerUser, + title: "Read Status Page User Owner", + description: + "This permission can read Status Page User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateMonitorOwnerTeam, + title: "Create Monitor Team Owner", + description: + "This permission can create Monitor Team Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorOwnerTeam, + title: "Delete Monitor Team Owner", + description: + "This permission can delete Monitor Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditMonitorOwnerTeam, + title: "Edit Monitor Team Owner", + description: + "This permission can edit Monitor Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadMonitorOwnerTeam, + title: "Read Monitor Team Owner", + description: + "This permission can read Monitor Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateMonitorOwnerUser, + title: "Create Monitor User Owner", + description: + "This permission can create Monitor User Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorOwnerUser, + title: "Delete Monitor User Owner", + description: + "This permission can delete Monitor User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditMonitorOwnerUser, + title: "Edit Monitor User Owner", + description: + "This permission can edit Monitor User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadMonitorOwnerUser, + title: "Read Monitor User Owner", + description: + "This permission can read Monitor User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateMonitorGroupOwnerTeam, + title: "Create Monitor Group Team Owner", + description: + "This permission can create Monitor Group Team Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorGroupOwnerTeam, + title: "Delete Monitor Group Team Owner", + description: + "This permission can delete Monitor Group Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditMonitorGroupOwnerTeam, + title: "Edit Monitor Group Team Owner", + description: + "This permission can edit Monitor Group Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadMonitorGroupOwnerTeam, + title: "Read Monitor Group Team Owner", + description: + "This permission can read Monitor Group Team Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateMonitorGroupOwnerUser, + title: "Create Monitor Group User Owner", + description: + "This permission can create Monitor Group User Owner this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteMonitorGroupOwnerUser, + title: "Delete Monitor Group User Owner", + description: + "This permission can delete Monitor Group User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditMonitorGroupOwnerUser, + title: "Edit Monitor Group User Owner", + description: + "This permission can edit Monitor Group User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadMonitorGroupOwnerUser, + title: "Read Monitor Group User Owner", + description: + "This permission can read Monitor Group User Owner of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectIncident, + title: "Create Incident", + description: "This permission can create incident this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectIncident, + title: "Delete Incident", + description: "This permission can delete incident of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditProjectIncident, + title: "Edit Incident", + description: "This permission can edit incident of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadProjectIncident, + title: "Read Incident", + description: "This permission can read incident of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateStatusPageSubscriber, + title: "Create Status Page Subscriber", + description: + "This permission can create subscriber on status page this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPageSubscriber, + title: "Delete Status Page Subscriber", + description: + "This permission can delete subscriber on status page of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPageSubscriber, + title: "Edit Status Page Subscriber", + description: + "This permission can edit subscriber on status page of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPageSubscriber, + title: "Read Status Page Subscriber", + description: + "This permission can read subscriber on status page of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateStatusPagePrivateUser, + title: "Create Status Page Private User", + description: + "This permission can create private user on status page this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteStatusPagePrivateUser, + title: "Delete Status Page PrivateUser", + description: + "This permission can delete private user on status page of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditStatusPagePrivateUser, + title: "Edit Status Page PrivateUser", + description: + "This permission can edit private user on status page of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadStatusPagePrivateUser, + title: "Read Status Page Private User", + description: + "This permission can read private user on status page of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + // Scheduled Maintenance Permissions. + + { + permission: Permission.CreateScheduledMaintenanceState, + title: "Create Scheduled Maintenance State", + description: + "This permission can create Scheduled Maintenance states this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteScheduledMaintenanceState, + title: "Delete Scheduled Maintenance State", + description: + "This permission can delete Scheduled Maintenance states of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditScheduledMaintenanceState, + title: "Edit Scheduled Maintenance State", + description: + "This permission can edit Scheduled Maintenance states of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadScheduledMaintenanceState, + title: "Read Scheduled Maintenance State", + description: + "This permission can read Scheduled Maintenance states of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateProjectScheduledMaintenance, + title: "Create Scheduled Maintenance", + description: + "This permission can create Scheduled Maintenance this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteProjectScheduledMaintenance, + title: "Delete Scheduled Maintenance", + description: + "This permission can delete Scheduled Maintenance of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.EditProjectScheduledMaintenance, + title: "Edit Scheduled Maintenance", + description: + "This permission can edit Scheduled Maintenance of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + { + permission: Permission.ReadProjectScheduledMaintenance, + title: "Read Scheduled Maintenance", + description: + "This permission can read Scheduled Maintenance of this project.", + isAssignableToTenant: true, + isAccessControlPermission: true, + }, + + { + permission: Permission.CreateScheduledMaintenanceStateTimeline, + title: "Create Scheduled Maintenance State Timeline", + description: + "This permission can create Scheduled Maintenance state history of an Scheduled Maintenance in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteScheduledMaintenanceStateTimeline, + title: "Delete Scheduled Maintenance State Timeline", + description: + "This permission can delete Scheduled Maintenance state history of an Scheduled Maintenance in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditScheduledMaintenanceStateTimeline, + title: "Edit Scheduled Maintenance State Timeline", + description: + "This permission can edit Scheduled Maintenance state history of an Scheduled Maintenance in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadScheduledMaintenanceStateTimeline, + title: "Read Scheduled Maintenance State Timeline", + description: + "This permission can read Scheduled Maintenance state history of an Scheduled Maintenance in this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateScheduledMaintenanceInternalNote, + title: "Create Scheduled Maintenance Internal Note", + description: + "This permission can create Scheduled Maintenance Internal Note this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteScheduledMaintenanceInternalNote, + title: "Delete Scheduled Maintenance Internal Note", + description: + "This permission can delete Scheduled Maintenance Internal Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditScheduledMaintenanceInternalNote, + title: "Edit Scheduled Maintenance Internal Note", + description: + "This permission can edit Scheduled Maintenance Internal Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadScheduledMaintenanceInternalNote, + title: "Read Scheduled Maintenance Internal Note", + description: + "This permission can read Scheduled Maintenance Internal Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + + { + permission: Permission.CreateScheduledMaintenancePublicNote, + title: "Create Scheduled Maintenance Status Page Note", + description: + "This permission can create Scheduled Maintenance Status Page Note this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.DeleteScheduledMaintenancePublicNote, + title: "Delete Scheduled Maintenance Status Page Note", + description: + "This permission can delete Scheduled Maintenance Status Page Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.EditScheduledMaintenancePublicNote, + title: "Edit Scheduled Maintenance Status Page Note", + description: + "This permission can edit Scheduled Maintenance Status Page Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + { + permission: Permission.ReadScheduledMaintenancePublicNote, + title: "Read Scheduled Maintenance Status Page Note", + description: + "This permission can read Scheduled Maintenance Status Page Note of this project.", + isAssignableToTenant: true, + isAccessControlPermission: false, + }, + ]; + + return permissions; + } + + public static getAllPermissionPropsAsDictionary(): Dictionary<PermissionProps> { + const permissions: Array<PermissionProps> = + PermissionHelper.getAllPermissionProps(); + + const dict: Dictionary<PermissionProps> = {}; + + for (const permission of permissions) { + dict[permission.permission] = permission; } - public static isAccessControlPermission(permission: Permission): boolean { - return ( - this.getAllPermissionProps() - .filter((item: PermissionProps) => { - return item.permission === permission; - }) - .filter((prop: PermissionProps) => { - return prop.isAccessControlPermission; - }).length > 0 - ); - } - - public static getNonAccessControlPermissions( - userPermissions: Array<UserPermission> - ): Array<Permission> { - return userPermissions - .filter((i: UserPermission) => { - return ( - i.labelIds.length === 0 || - !PermissionHelper.isAccessControlPermission(i.permission) - ); - }) - .map((i: UserPermission) => { - return i.permission; - }); - } - - public static getAccessControlPermissions( - userPermissions: Array<UserPermission> - ): Array<UserPermission> { - return userPermissions.filter((i: UserPermission) => { - return ( - i.labelIds.length > 0 && - PermissionHelper.isAccessControlPermission(i.permission) - ); - }); - } - - public static getDescription(permission: Permission): string { - const permissionProps: Array<PermissionProps> = - this.getAllPermissionProps().filter((item: PermissionProps) => { - return item.permission === permission; - }); - - if (!permissionProps[0]) { - throw new BadDataException( - `${permission} does not have permission props` - ); - } - - return permissionProps[0].description; - } - - public static getTitle(permission: Permission): string { - const permissionProps: Array<PermissionProps> = - this.getAllPermissionProps().filter((item: PermissionProps) => { - return item.permission === permission; - }); - - if (!permissionProps[0]) { - throw new BadDataException( - `${permission} does not have permission props` - ); - } - - return permissionProps[0].title; - } - - public static getPermissionTitles( - permissions: Array<Permission> - ): Array<string> { - const props: Array<PermissionProps> = this.getAllPermissionProps(); - const titles: Array<string> = []; - - for (const permission of permissions) { - const permissionProp: PermissionProps | undefined = props.find( - (item: PermissionProps) => { - return item.permission === permission; - } - ); - - if (permissionProp) { - titles.push(permissionProp.title); - } - } - - return titles; - } - - public static getAllPermissionProps(): Array<PermissionProps> { - const permissions: Array<PermissionProps> = [ - { - permission: Permission.ProjectOwner, - title: 'Project Owner', - description: - 'Owner of this project. Manages billing, inviting other admins to this project, and can delete this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ProjectMember, - title: 'Project Member', - description: - 'Member of this project. Can view most resources unless restricted.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ProjectAdmin, - title: 'Project Admin', - description: - 'Admin of this project. Manages team members in this project, however cannot manage billing or delete this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ProjectUser, - title: 'Project User', - description: 'User of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.CurrentUser, - title: 'Logged in User', - description: - 'This permission is assigned to any registered user.', - isAssignableToTenant: false, - isAccessControlPermission: false, - }, - { - permission: Permission.CustomerSupport, - title: 'Customer Support', - description: 'Customer Support Resource of OneUptime.', - isAssignableToTenant: false, - isAccessControlPermission: false, - }, - { - permission: Permission.User, - title: 'User', - description: - 'Owner of this project, manages billing, inviting other admins to this project, and can delete this project.', - isAssignableToTenant: false, - isAccessControlPermission: false, - }, - { - permission: Permission.Public, - title: 'Public', - description: - 'Non registered user. Typically used for sign up or log in.', - isAssignableToTenant: false, - isAccessControlPermission: false, - }, - - { - permission: Permission.ManageProjectBilling, - title: 'Manage Billing', - description: 'This permission can update project billing.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.CreateProjectApiKey, - title: 'Create API Key', - description: - 'This permission can create api keys of this project', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectApiKey, - title: 'Delete API Key', - description: - 'This permission can delete api keys of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectApiKeyPermissions, - title: 'Edit API Key Permissions', - description: - 'This permission can edit api key permissions of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectApiKey, - title: 'Edit API Key', - description: - 'This permission can edit api keys of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProjectApiKey, - title: 'Read API Key', - description: - 'This permission can read api keys of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectLabel, - title: 'Create Label', - description: 'This permission can create labels this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectLabel, - title: 'Delete Label', - description: - 'This permission can delete labels of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.AddLabelsToProjectResources, - title: 'Add Label to Resources', - description: - 'This permission can add project labels to resources of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectLabel, - title: 'Edit Label', - description: 'This permission can edit labels of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProjectLabel, - title: 'Read Label', - description: 'This permission can read labels of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentState, - title: 'Create Incident State', - description: - 'This permission can create incident states this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentState, - title: 'Delete Incident State', - description: - 'This permission can delete incident states of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentState, - title: 'Edit Incident State', - description: - 'This permission can edit incident states of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentState, - title: 'Read Incident State', - description: - 'This permission can read incident states of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentStateTimeline, - title: 'Create Incident State Timeline', - description: - 'This permission can create incident state history of an incident in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentStateTimeline, - title: 'Delete Incident State Timeline', - description: - 'This permission can delete incident state history of an incident in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentStateTimeline, - title: 'Edit Incident State Timeline', - description: - 'This permission can edit incident state history of an incident in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentStateTimeline, - title: 'Read Incident State Timeline', - description: - 'This permission can read incident state history of an incident in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateMonitorStatusTimeline, - title: 'Create Monitor Status Timeline', - description: - 'This permission can create Monitor Status history of an incident in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorStatusTimeline, - title: 'Delete Monitor Status Timeline', - description: - 'This permission can delete Monitor Status history of an incident in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditMonitorStatusTimeline, - title: 'Edit Monitor Status Timeline', - description: - 'This permission can edit Monitor Status history of an incident in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadMonitorStatusTimeline, - title: 'Read Monitor Status Timeline', - description: - 'This permission can read Monitor Status history of an incident in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.ReadEmailLog, - title: 'Read Email Log', - description: - 'This permission can read email logs of the project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectMonitorStatus, - title: 'Create Monitor Status', - description: - 'This permission can create monitor statuses this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectMonitorStatus, - title: 'Delete Monitor Status', - description: - 'This permission can delete monitor statuses of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectMonitorStatus, - title: 'Edit Monitor Status', - description: - 'This permission can edit monitor statuses of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProjectMonitorStatus, - title: 'Read Monitor Status', - description: - 'This permission can read monitor statuses of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageAnnouncement, - title: 'Create Status Page Announcement', - description: - 'This permission can create Status Page Announcement this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageAnnouncement, - title: 'Delete Status Page Announcement', - description: - 'This permission can delete Status Page Announcement of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageAnnouncement, - title: 'Edit Status Page Announcement', - description: - 'This permission can edit Status Page Announcement of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageAnnouncement, - title: 'Read Status Page Announcement', - description: - 'This permission can read Status Page Announcement of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageAnnouncement, - title: 'Create Monitor Status', - description: - 'This permission can create monitor statuses this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageAnnouncement, - title: 'Delete Monitor Status', - description: - 'This permission can delete monitor statuses of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageAnnouncement, - title: 'Edit Monitor Status', - description: - 'This permission can edit monitor statuses of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageAnnouncement, - title: 'Read Monitor Status', - description: - 'This permission can read monitor statuses of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectDomain, - title: 'Create Domain', - description: - 'This permission can create Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectDomain, - title: 'Delete Domain', - description: - 'This permission can delete Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectDomain, - title: 'Edit Domain', - description: 'This permission can edit Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProjectDomain, - title: 'Read Domain', - description: 'This permission can read Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageHeaderLink, - title: 'Create Header Link', - description: - 'This permission can create Header Link in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageHeaderLink, - title: 'Delete Header Link', - description: - 'This permission can delete Header Link in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageHeaderLink, - title: 'Edit Header Link', - description: - 'This permission can edit Header Link in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageHeaderLink, - title: 'Read Header Link', - description: - 'This permission can read Header Link in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageFooterLink, - title: 'Create Footer Link', - description: - 'This permission can create Footer Link in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageFooterLink, - title: 'Delete Footer Link', - description: - 'This permission can delete Footer Link in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageFooterLink, - title: 'Edit Footer Link', - description: - 'This permission can edit Footer Link in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageFooterLink, - title: 'Read Footer Link', - description: - 'This permission can read Footer Link in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageResource, - title: 'Create Status Page Resource', - description: - 'This permission can create Status Page Resource in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageResource, - title: 'Delete Status Page Resource', - description: - 'This permission can delete Status Page Resource in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageResource, - title: 'Edit Status Page Resource', - description: - 'This permission can edit Status Page Resource in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageResource, - title: 'Read Status Page Resource', - description: - 'This permission can read Status Page Resource in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageHistoryChartBarColorRule, - title: 'Create Status Page History Chart Bar Color Rule', - description: - 'This permission can create Status Page History Chart Bar Color Rule in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageHistoryChartBarColorRule, - title: 'Delete Status Page History Chart Bar Color Rule', - description: - 'This permission can delete Status Page History Chart Bar Color Rule in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageHistoryChartBarColorRule, - title: 'Edit Status Page History Chart Bar Color Rule', - description: - 'This permission can edit Status Page History Chart Bar Color Rule in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageHistoryChartBarColorRule, - title: 'Read Status Page History Chart Bar Color Rule', - description: - 'This permission can read Status Page History Chart Bar Color Rule in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateWorkflow, - title: 'Create Workflow', - description: - 'This permission can create Workflow in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteWorkflow, - title: 'Delete Workflow', - description: - 'This permission can delete Workflow in this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditWorkflow, - title: 'Edit Workflow', - description: - 'This permission can edit Workflow in this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadWorkflow, - title: 'Read Workflow', - description: - 'This permission can read Workflow in this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.DeleteProject, - title: 'Delete Project', - description: 'This permission can delete Project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProject, - title: 'Edit Project', - description: 'This permission can edit Project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProject, - title: 'Read Project', - description: 'This permission can read this Project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateWorkflowVariable, - title: 'Create Workflow Variables', - description: - 'This permission can create Workflow Variables in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteWorkflowVariable, - title: 'Delete Workflow Variables', - description: - 'This permission can delete Workflow Variables in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditWorkflowVariable, - title: 'Edit Workflow Variables', - description: - 'This permission can edit Workflow Variables in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadWorkflowVariable, - title: 'Read Workflow Variables', - description: - 'This permission can read Workflow Variables in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateWorkflowLog, - title: 'Create Workflow Log', - description: - 'This permission can create Workflow Log in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteWorkflowLog, - title: 'Delete Workflow Log', - description: - 'This permission can delete Workflow Log in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditWorkflowLog, - title: 'Edit Workflow Log', - description: - 'This permission can edit Workflow Log in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadWorkflowLog, - title: 'Read Workflow Log', - description: - 'This permission can read Workflow Log in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageGroup, - title: 'Create Status Page Group', - description: - 'This permission can create Status Page Group in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageGroup, - title: 'Delete Status Page Group', - description: - 'This permission can delete Status Page Group in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageGroup, - title: 'Edit Status Page Group', - description: - 'This permission can edit Status Page Group in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageGroup, - title: 'Read Status Page Group', - description: - 'This permission can read Status Page Group in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageDomain, - title: 'Create Status Page Domain', - description: - 'This permission can create Status Page Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageDomain, - title: 'Delete Status Page Domain', - description: - 'This permission can delete Status Page Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageDomain, - title: 'Edit Status Page Domain', - description: - 'This permission can edit Status Page Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageDomain, - title: 'Read Status Page Domain', - description: - 'This permission can read Status Page Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateMonitorGroup, - title: 'Create Monitor Group', - description: - 'This permission can create Monitor Group in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorGroup, - title: 'Delete Monitor Group', - description: - 'This permission can delete Monitor Group in this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditMonitorGroup, - title: 'Edit Monitor Group', - description: - 'This permission can edit Monitor Group in this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadMonitorGroup, - title: 'Read Monitor Group', - description: - 'This permission can read Monitor Group in this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateProjectSSO, - title: 'Create Project SSO', - description: - 'This permission can create Project SSO in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectSSO, - title: 'Delete Project SSO', - description: - 'This permission can delete Project SSO in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectSSO, - title: 'Edit Project SSO', - description: - 'This permission can edit Project SSO in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProjectSSO, - title: 'Read Project SSO', - description: - 'This permission can read Project SSO in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageSSO, - title: 'Create Status Page SSO', - description: - 'This permission can create Status Page SSO in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageSSO, - title: 'Delete Status Page SSO', - description: - 'This permission can delete Status Page SSO in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageSSO, - title: 'Edit Status Page SSO', - description: - 'This permission can edit Status Page SSO in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageSSO, - title: 'Read Status Page SSO', - description: - 'This permission can read Status Page SSO in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectSMTPConfig, - title: 'Create SMTP Config', - description: - 'This permission can create SMTP configs this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectSMTPConfig, - title: 'Delete SMTP Config', - description: - 'This permission can delete SMTP configs of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectSMTPConfig, - title: 'Edit SMTP Config', - description: - 'This permission can edit SMTP configs of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProjectSMTPConfig, - title: 'Read SMTP Config', - description: - 'This permission can read SMTP configs of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectCallSMSConfig, - title: 'Create Call and SMS', - description: - 'This permission can create Call and SMS this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectCallSMSConfig, - title: 'Delete Call and SMS', - description: - 'This permission can delete Call and SMS of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectCallSMSConfig, - title: 'Edit Call and SMS', - description: - 'This permission can edit Call and SMS of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProjectCallSMSConfig, - title: 'Read Call and SMS', - description: - 'This permission can read Call and SMS of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageDomain, - title: 'Create Status Page Domain', - description: - 'This permission can create Status Page Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageDomain, - title: 'Delete Status Page Domain', - description: - 'This permission can delete Status Page Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageDomain, - title: 'Edit Status Page Domain', - description: - 'This permission can edit Status Page Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageDomain, - title: 'Read Status Page Domain', - description: - 'This permission can read Status Page Domain in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentSeverity, - title: 'Create Incident Severity', - description: - 'This permission can create Incident Severity this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentSeverity, - title: 'Delete Incident Severity', - description: - 'This permission can delete Incident Severity of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentSeverity, - title: 'Edit Incident Severity', - description: - 'This permission can edit Incident Severity of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentSeverity, - title: 'Read Incident Severity', - description: - 'This permission can read Incident Severity of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectTeam, - title: 'Create Team', - description: 'This permission can create teams this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectTeam, - title: 'Delete Team', - description: - 'This permission can delete teams of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.InviteProjectTeamMembers, - title: 'Invite New Members', - description: 'This permission can invite users to the team.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectTeamPermissions, - title: 'Edit Team Permissions', - description: - 'This permission can edit team permissions of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectTeam, - title: 'Edit Team', - description: 'This permission can edit teams of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProjectTeam, - title: 'Read Teams', - description: 'This permission can read teams of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectMonitor, - title: 'Create Monitor', - description: 'This permission can create monitor this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectMonitor, - title: 'Delete Monitor', - description: - 'This permission can delete monitor of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditProjectMonitor, - title: 'Edit Monitor', - description: - 'This permission can edit monitor of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadProjectMonitor, - title: 'Read Monitor', - description: - 'This permission can read monitor of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateIncidentInternalNote, - title: 'Create Incident Internal Note', - description: - 'This permission can create Incident Internal Note this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentInternalNote, - title: 'Delete Incident Internal Note', - description: - 'This permission can delete Incident Internal Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentInternalNote, - title: 'Edit Incident Internal Note', - description: - 'This permission can edit Incident Internal Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentInternalNote, - title: 'Read Incident Internal Note', - description: - 'This permission can read Incident Internal Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentPublicNote, - title: 'Create Incident Status Page Note', - description: - 'This permission can create Incident Status Page Note this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentPublicNote, - title: 'Delete Incident Status Page Note', - description: - 'This permission can delete Incident Status Page Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentPublicNote, - title: 'Edit Incident Status Page Note', - description: - 'This permission can edit Incident Status Page Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentPublicNote, - title: 'Read Incident Status Page Note', - description: - 'This permission can read Incident Status Page Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateInvoices, - title: 'Create Invoices', - description: - 'This permission can create Invoices this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteInvoices, - title: 'Delete Invoices', - description: - 'This permission can delete Invoices of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditInvoices, - title: 'Edit Invoices', - description: - 'This permission can edit Invoices of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadInvoices, - title: 'Read Invoices', - description: - 'This permission can read Invoices of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateBillingPaymentMethod, - title: 'Create Payment Method', - description: - 'This permission can create Payment Method this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteBillingPaymentMethod, - title: 'Delete Payment Method', - description: - 'This permission can delete Payment Method of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditBillingPaymentMethod, - title: 'Edit Payment Method', - description: - 'This permission can edit Payment Method of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadBillingPaymentMethod, - title: 'Read Payment Method', - description: - 'This permission can read Payment Method of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - title: 'Read On-Call Duty Policy Execution Log Timeline', - description: - 'This permission can read teams in on-call duty execution log timeline.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.ReadProjectOnCallDutyPolicyExecutionLog, - title: 'Read On-Call Duty Policy Execution Log', - description: - 'This permission can read teams in on-call duty execution log.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - title: 'Create On-Call Duty Policy Escalation Rule', - description: - 'This permission can create teams in on-call duty escalation rule this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.DeleteProjectOnCallDutyPolicyEscalationRuleTeam, - title: 'Delete On-Call Duty Policy Escalation Rule Team', - description: - 'This permission can delete teams in on-call duty escalation rule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.EditProjectOnCallDutyPolicyEscalationRuleTeam, - title: 'Edit On-Call Duty Policy Escalation Rule Team', - description: - 'This permission can edit teams in on-call duty escalation rule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - title: 'Read On-Call Duty Policy Escalation Rule Team', - description: - 'This permission can read teams in on-call duty escalation rule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - title: 'Create On-Call Duty Policy Escalation Rule Schedule', - description: - 'This permission can create teams in on-call duty escalation rule schedule this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.DeleteProjectOnCallDutyPolicyEscalationRuleSchedule, - title: 'Delete On-Call Duty Policy Escalation Rule Schedule', - description: - 'This permission can delete teams in on-call duty escalation rule schedule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.EditProjectOnCallDutyPolicyEscalationRuleSchedule, - title: 'Edit On-Call Duty Policy Escalation Rule Schedule', - description: - 'This permission can edit teams in on-call duty escalation rule schedule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - title: 'Read On-Call Duty Policy Escalation Rule Schedule', - description: - 'This permission can read teams in on-call duty escalation rule schedule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateMonitorSecret, - title: 'Create Monitor Secret', - description: 'This permission can create monitor secret.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorSecret, - title: 'Delete Monitor Secret', - description: 'This permission can delete monitor secret', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditMonitorSecret, - title: 'Edit Monitor Secret', - description: 'This permission can edit monitor secret.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadMonitorSecret, - title: 'Read Monitor Secret', - description: 'This permission can read monitor secret.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - title: 'Create On-Call Duty Policy Escalation Rule User', - description: - 'This permission can create on-call duty escalation rule this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.DeleteProjectOnCallDutyPolicyEscalationRuleUser, - title: 'Delete On-Call Duty Policy Escalation Rule User', - description: - 'This permission can delete on-call duty escalation rule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.EditProjectOnCallDutyPolicyEscalationRuleUser, - title: 'Edit On-Call Duty Policy Escalation Rule User', - description: - 'This permission can edit on-call duty escalation rule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - title: 'Read On-Call Duty Policy Escalation Rule User', - description: - 'This permission can read on-call duty escalation rule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - title: 'Create On-Call Duty Policy Escalation Rule', - description: - 'This permission can create on-call duty escalation rule this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.DeleteProjectOnCallDutyPolicyEscalationRule, - title: 'Delete On-Call Duty Policy Escalation Rule', - description: - 'This permission can delete on-call duty escalation rule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.EditProjectOnCallDutyPolicyEscalationRule, - title: 'Edit On-Call Duty Policy Escalation Rule', - description: - 'This permission can edit on-call duty escalation rule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - title: 'Read On-Call Duty Policy Escalation Rule', - description: - 'This permission can read on-call duty escalation rule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectOnCallDutyPolicy, - title: 'Create On-Call Duty Policy', - description: - 'This permission can create on-call duty this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectOnCallDutyPolicy, - title: 'Delete On-Call Duty Policy', - description: - 'This permission can delete on-call duty of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditProjectOnCallDutyPolicy, - title: 'Edit On-Call Duty Policy', - description: - 'This permission can edit on-call duty of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadProjectOnCallDutyPolicy, - title: 'Read On-Call Duty Policy', - description: - 'This permission can read on-call duty of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateProjectOnCallDutyPolicySchedule, - title: 'Create On-Call Duty Policy Schedule', - description: - 'This permission can create on-call duty schedule this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectOnCallDutyPolicySchedule, - title: 'Delete On-Call Duty Policy Schedule', - description: - 'This permission can delete on-call duty schedule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditProjectOnCallDutyPolicySchedule, - title: 'Edit On-Call Duty Policy Schedule', - description: - 'This permission can edit on-call duty schedule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadProjectOnCallDutyPolicySchedule, - title: 'Read On-Call Duty Policy Schedule', - description: - 'This permission can read on-call duty schedule of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateProjectStatusPage, - title: 'Create Status Page', - description: - 'This permission can create status pages this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectStatusPage, - title: 'Delete Status Page', - description: - 'This permission can delete status pages of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditProjectStatusPage, - title: 'Edit Status Page', - description: - 'This permission can edit status pages of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadProjectStatusPage, - title: 'Read Status Page', - description: - 'This permission can read status pages of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateProjectProbe, - title: 'Create Probe', - description: 'This permission can create probe this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectProbe, - title: 'Delete Probe', - description: - 'This permission can delete probe of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditProjectProbe, - title: 'Edit Probe', - description: 'This permission can edit probe of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadProjectProbe, - title: 'Read Probe', - description: 'This permission can read probe of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateTelemetryService, - title: 'Create Telemetry Service', - description: - 'This permission can create Telemetry Service this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteTelemetryService, - title: 'Delete Telemetry Service', - description: - 'This permission can delete Telemetry Service of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditTelemetryService, - title: 'Edit Telemetry Service', - description: - 'This permission can edit Telemetry Service of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadTelemetryService, - title: 'Read Telemetry Service', - description: - 'This permission can read Service of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateMonitorGroupResource, - title: 'Create Monitor Group Resource', - description: - 'This permission can create monitor group resource.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorGroupResource, - title: 'Delete Monitor Group Resource', - description: - 'This permission can delete monitor group resource.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditMonitorGroupResource, - title: 'Edit Monitor Group Resource', - description: 'This permission can edit monitor group resource.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadMonitorGroupResource, - title: 'Read Monitor Group Resource', - description: 'This permission can read monitor group resource.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateOnCallDutyPolicyCustomField, - title: 'Create On-Call Policy Custom Field', - description: - 'This permission can create On-Call Policy Custom Field this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteOnCallDutyPolicyCustomField, - title: 'Delete On-Call Policy Custom Field', - description: - 'This permission can delete On-Call Policy Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditOnCallDutyPolicyCustomField, - title: 'Edit On-Call Policy Custom Field', - description: - 'This permission can edit On-Call Policy Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadOnCallDutyPolicyCustomField, - title: 'Read On-Call Policy Custom Field', - description: - 'This permission can read On-Call Policy Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateOnCallDutyPolicyScheduleLayer, - title: 'Create On-Call Schedule Layer', - description: - 'This permission can create On-Call Schedule Layer this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteOnCallDutyPolicyScheduleLayer, - title: 'Delete On-Call Schedule Layer', - description: - 'This permission can delete On-Call Schedule Layer of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditOnCallDutyPolicyScheduleLayer, - title: 'Edit On-Call Schedule Layer', - description: - 'This permission can edit On-Call Schedule Layer of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadOnCallDutyPolicyScheduleLayer, - title: 'Read On-Call Schedule Layer', - description: - 'This permission can read On-Call Schedule Layer of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateOnCallDutyPolicyScheduleLayerUser, - title: 'Create On-Call Schedule Layer User', - description: - 'This permission can create On-Call Schedule Layer User this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteOnCallDutyPolicyScheduleLayerUser, - title: 'Delete On-Call Schedule Layer User', - description: - 'This permission can delete On-Call Schedule Layer User of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditOnCallDutyPolicyScheduleLayerUser, - title: 'Edit On-Call Schedule Layer User', - description: - 'This permission can edit On-Call Schedule Layer User of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadOnCallDutyPolicyScheduleLayerUser, - title: 'Read On-Call Schedule Layer User', - description: - 'This permission can read On-Call Schedule Layer User of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateMonitorCustomField, - title: 'Create Monitor Custom Field', - description: - 'This permission can create Monitor Custom Field this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorCustomField, - title: 'Delete Monitor Custom Field', - description: - 'This permission can delete Monitor Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditMonitorCustomField, - title: 'Edit Monitor Custom Field', - description: - 'This permission can edit Monitor Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadMonitorCustomField, - title: 'Read Monitor Custom Field', - description: - 'This permission can read Monitor Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentCustomField, - title: 'Create Incident Custom Field', - description: - 'This permission can create Incident Custom Field this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentCustomField, - title: 'Delete Incident Custom Field', - description: - 'This permission can delete Incident Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentCustomField, - title: 'Edit Incident Custom Field', - description: - 'This permission can edit Incident Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentCustomField, - title: 'Read Incident Custom Field', - description: - 'This permission can read Incident Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageCustomField, - title: 'Create Status Page Custom Field', - description: - 'This permission can create Status Page Custom Field this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageCustomField, - title: 'Delete Status Page Custom Field', - description: - 'This permission can delete Status Page Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageCustomField, - title: 'Edit Status Page Custom Field', - description: - 'This permission can edit Status Page Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageCustomField, - title: 'Read Status Page Custom Field', - description: - 'This permission can read Status Page Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateScheduledMaintenanceCustomField, - title: 'Create Scheduled Maintenance Custom Field', - description: - 'This permission can create Scheduled Maintenance Custom Field this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteScheduledMaintenanceCustomField, - title: 'Delete Scheduled Maintenance Custom Field', - description: - 'This permission can delete Scheduled Maintenance Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditScheduledMaintenanceCustomField, - title: 'Edit Scheduled Maintenance Custom Field', - description: - 'This permission can edit Scheduled Maintenance Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadScheduledMaintenanceCustomField, - title: 'Read Scheduled Maintenance Custom Field', - description: - 'This permission can read Scheduled Maintenance Custom Field of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.ReadSmsLog, - title: 'Read SMS Log', - description: - 'This permission can read SMS Log of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.ReadCallLog, - title: 'Read Call Log', - description: - 'This permission can read Call Logs of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateMonitorProbe, - title: 'Create Monitor Probe', - description: - 'This permission can create Monitor Probe this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorProbe, - title: 'Delete Monitor Probe', - description: - 'This permission can delete Monitor Probe of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditMonitorProbe, - title: 'Edit Monitor Probe', - description: - 'This permission can edit Monitor Probe of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadMonitorProbe, - title: 'Read Monitor Probe', - description: - 'This permission can read Monitor Probe of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateTelemetryServiceLog, - title: 'Create Telemetry Service Log', - description: - 'This permission can create Telemetry Service Log this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteTelemetryServiceLog, - title: 'Delete Telemetry Service Log', - description: - 'This permission can delete Telemetry Service Log of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditTelemetryServiceLog, - title: 'Edit Telemetry Service Log', - description: - 'This permission can edit Telemetry Service Log of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadTelemetryServiceLog, - title: 'Read Telemetry Service Log', - description: - 'This permission can read Telemetry Service Log of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateCodeRepository, - title: 'Create Code Repository', - description: - 'This permission can create Code Repository this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.DeleteCodeRepository, - title: 'Delete Code Repository', - description: - 'This permission can delete Code Repository of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditCodeRepository, - title: 'Edit Code Repository', - description: - 'This permission can edit Code Repository of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadCodeRepository, - title: 'Read Code Repository', - description: - 'This permission can read Code Repository of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateServiceRepository, - title: 'Create Service Repository', - description: - 'This permission can create Service Repository this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.DeleteServiceRepository, - title: 'Delete Service Repository', - description: - 'This permission can delete Service Repository of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditServiceRepository, - title: 'Edit Service Repository', - description: - 'This permission can edit Service Repository of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadServiceRepository, - title: 'Read Service Repository', - description: - 'This permission can read Service Repository of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.ReadCopilotEvent, - title: 'Read Copilot Event', - description: - 'This permission can read Copilot Event of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateServiceCatalog, - title: 'Create Service Catalog', - description: - 'This permission can create Service Catalog this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.DeleteServiceCatalog, - title: 'Delete Service Catalog', - description: - 'This permission can delete Service Catalog of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditServiceCatalog, - title: 'Edit Service Catalog', - description: - 'This permission can edit Service Catalog of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadServiceCatalog, - title: 'Read Service Catalog', - description: - 'This permission can read Service Catalog of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateTelemetryServiceTraces, - title: 'Create Telemetry Service Traces', - description: - 'This permission can create Telemetry Service Traces this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteTelemetryServiceTraces, - title: 'Delete Telemetry Service Traces', - description: - 'This permission can delete Telemetry Service Traces of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditTelemetryServiceTraces, - title: 'Edit Telemetry Service Traces', - description: - 'This permission can edit Telemetry Service Traces of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadTelemetryServiceTraces, - title: 'Read Telemetry Service Traces', - description: - 'This permission can read Telemetry Service Traces of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateScheduledMaintenanceOwnerTeam, - title: 'Create Scheduled Maintenance Team Owner', - description: - 'This permission can create Scheduled Maintenance Team Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteScheduledMaintenanceOwnerTeam, - title: 'Delete Scheduled Maintenance Team Owner', - description: - 'This permission can delete Scheduled Maintenance Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditScheduledMaintenanceOwnerTeam, - title: 'Edit Scheduled Maintenance Team Owner', - description: - 'This permission can edit Scheduled Maintenance Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadScheduledMaintenanceOwnerTeam, - title: 'Read Scheduled Maintenance Team Owner', - description: - 'This permission can read Scheduled Maintenance Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateScheduledMaintenanceOwnerUser, - title: 'Create Scheduled Maintenance User Owner', - description: - 'This permission can create Scheduled Maintenance User Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteScheduledMaintenanceOwnerUser, - title: 'Delete Scheduled Maintenance User Owner', - description: - 'This permission can delete Scheduled Maintenance User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditScheduledMaintenanceOwnerUser, - title: 'Edit Scheduled Maintenance User Owner', - description: - 'This permission can edit Scheduled Maintenance User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadScheduledMaintenanceOwnerUser, - title: 'Read Scheduled Maintenance User Owner', - description: - 'This permission can read Scheduled Maintenance User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentOwnerTeam, - title: 'Create Incident Team Owner', - description: - 'This permission can create Incident Team Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentOwnerTeam, - title: 'Delete Incident Team Owner', - description: - 'This permission can delete Incident Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentOwnerTeam, - title: 'Edit Incident Team Owner', - description: - 'This permission can edit Incident Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentOwnerTeam, - title: 'Read Incident Team Owner', - description: - 'This permission can read Incident Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentNoteTemplate, - title: 'Create Incident Note Template', - description: - 'This permission can create Incident Note Template this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentNoteTemplate, - title: 'Delete Incident Note Template', - description: - 'This permission can delete Incident Note Template of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentNoteTemplate, - title: 'Edit Incident Note Template', - description: - 'This permission can edit Incident Note Template of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentNoteTemplate, - title: 'Read Incident Note Template', - description: - 'This permission can read Incident Note Template of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateScheduledMaintenanceNoteTemplate, - title: 'Create Scheduled Maintenance Note Template', - description: - 'This permission can create Scheduled Maintenance Note Template this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteScheduledMaintenanceNoteTemplate, - title: 'Delete Scheduled Maintenance Note Template', - description: - 'This permission can delete Scheduled Maintenance Note Template of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditScheduledMaintenanceNoteTemplate, - title: 'Edit Scheduled Maintenance Note Template', - description: - 'This permission can edit Scheduled Maintenance Note Template of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadScheduledMaintenanceNoteTemplate, - title: 'Read Scheduled Maintenance Note Template', - description: - 'This permission can read Scheduled Maintenance Note Template of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentTemplate, - title: 'Create Incident Template', - description: - 'This permission can create Incident Template this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentTemplate, - title: 'Delete Incident Template', - description: - 'This permission can delete Incident Template of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentTemplate, - title: 'Edit Incident Template', - description: - 'This permission can edit Incident Template of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentTemplate, - title: 'Read Incident Template', - description: - 'This permission can read Incident Template of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentOwnerUser, - title: 'Create Incident User Owner', - description: - 'This permission can create Incident User Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentOwnerUser, - title: 'Delete Incident User Owner', - description: - 'This permission can delete Incident User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentOwnerUser, - title: 'Edit Incident User Owner', - description: - 'This permission can edit Incident User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentOwnerUser, - title: 'Read Incident User Owner', - description: - 'This permission can read Incident User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageOwnerTeam, - title: 'Create Status Page Team Owner', - description: - 'This permission can create Status Page Team Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageOwnerTeam, - title: 'Delete Status Page Team Owner', - description: - 'This permission can delete Status Page Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageOwnerTeam, - title: 'Edit Status Page Team Owner', - description: - 'This permission can edit Status Page Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageOwnerTeam, - title: 'Read Status Page Team Owner', - description: - 'This permission can read Status Page Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentTemplateOwnerTeam, - title: 'Create IncidentTemplate Team Owner', - description: - 'This permission can create IncidentTemplate Team Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentTemplateOwnerTeam, - title: 'Delete IncidentTemplate Team Owner', - description: - 'This permission can delete IncidentTemplate Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentTemplateOwnerTeam, - title: 'Edit IncidentTemplate Team Owner', - description: - 'This permission can edit IncidentTemplate Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentTemplateOwnerTeam, - title: 'Read IncidentTemplate Team Owner', - description: - 'This permission can read IncidentTemplate Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateServiceCatalogOwnerTeam, - title: 'Create Service Catalog Team Owner', - description: - 'This permission can create Service Catalog Team Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteServiceCatalogOwnerTeam, - title: 'Delete Service Catalog Team Owner', - description: - 'This permission can delete Service Catalog Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditServiceCatalogOwnerTeam, - title: 'Edit Service Catalog Team Owner', - description: - 'This permission can edit Service Catalog Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadServiceCatalogOwnerTeam, - title: 'Read Service Catalog Team Owner', - description: - 'This permission can read Service Catalog Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateServiceCatalogOwnerUser, - title: 'Create Service Catalog User Owner', - description: - 'This permission can create Service Catalog User Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteServiceCatalogOwnerUser, - title: 'Delete Service Catalog User Owner', - description: - 'This permission can delete Service Catalog User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditServiceCatalogOwnerUser, - title: 'Edit Service Catalog User Owner', - description: - 'This permission can edit Service Catalog User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadServiceCatalogOwnerUser, - title: 'Read Service Catalog User Owner', - description: - 'This permission can read Service Catalog User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateIncidentTemplateOwnerUser, - title: 'Create IncidentTemplate User Owner', - description: - 'This permission can create IncidentTemplate User Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteIncidentTemplateOwnerUser, - title: 'Delete IncidentTemplate User Owner', - description: - 'This permission can delete IncidentTemplate User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditIncidentTemplateOwnerUser, - title: 'Edit IncidentTemplate User Owner', - description: - 'This permission can edit IncidentTemplate User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadIncidentTemplateOwnerUser, - title: 'Read IncidentTemplate User Owner', - description: - 'This permission can read IncidentTemplate User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageOwnerTeam, - title: 'Create Status Page Team Owner', - description: - 'This permission can create Status Page Team Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageOwnerTeam, - title: 'Delete Status Page Team Owner', - description: - 'This permission can delete Status Page Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageOwnerTeam, - title: 'Edit Status Page Team Owner', - description: - 'This permission can edit Status Page Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageOwnerTeam, - title: 'Read Status Page Team Owner', - description: - 'This permission can read Status Page Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPageOwnerUser, - title: 'Create Status Page User Owner', - description: - 'This permission can create Status Page User Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageOwnerUser, - title: 'Delete Status Page User Owner', - description: - 'This permission can delete Status Page User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageOwnerUser, - title: 'Edit Status Page User Owner', - description: - 'This permission can edit Status Page User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageOwnerUser, - title: 'Read Status Page User Owner', - description: - 'This permission can read Status Page User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateMonitorOwnerTeam, - title: 'Create Monitor Team Owner', - description: - 'This permission can create Monitor Team Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorOwnerTeam, - title: 'Delete Monitor Team Owner', - description: - 'This permission can delete Monitor Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditMonitorOwnerTeam, - title: 'Edit Monitor Team Owner', - description: - 'This permission can edit Monitor Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadMonitorOwnerTeam, - title: 'Read Monitor Team Owner', - description: - 'This permission can read Monitor Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateMonitorOwnerUser, - title: 'Create Monitor User Owner', - description: - 'This permission can create Monitor User Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorOwnerUser, - title: 'Delete Monitor User Owner', - description: - 'This permission can delete Monitor User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditMonitorOwnerUser, - title: 'Edit Monitor User Owner', - description: - 'This permission can edit Monitor User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadMonitorOwnerUser, - title: 'Read Monitor User Owner', - description: - 'This permission can read Monitor User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateMonitorGroupOwnerTeam, - title: 'Create Monitor Group Team Owner', - description: - 'This permission can create Monitor Group Team Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorGroupOwnerTeam, - title: 'Delete Monitor Group Team Owner', - description: - 'This permission can delete Monitor Group Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditMonitorGroupOwnerTeam, - title: 'Edit Monitor Group Team Owner', - description: - 'This permission can edit Monitor Group Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadMonitorGroupOwnerTeam, - title: 'Read Monitor Group Team Owner', - description: - 'This permission can read Monitor Group Team Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateMonitorGroupOwnerUser, - title: 'Create Monitor Group User Owner', - description: - 'This permission can create Monitor Group User Owner this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteMonitorGroupOwnerUser, - title: 'Delete Monitor Group User Owner', - description: - 'This permission can delete Monitor Group User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditMonitorGroupOwnerUser, - title: 'Edit Monitor Group User Owner', - description: - 'This permission can edit Monitor Group User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadMonitorGroupOwnerUser, - title: 'Read Monitor Group User Owner', - description: - 'This permission can read Monitor Group User Owner of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectIncident, - title: 'Create Incident', - description: - 'This permission can create incident this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectIncident, - title: 'Delete Incident', - description: - 'This permission can delete incident of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditProjectIncident, - title: 'Edit Incident', - description: - 'This permission can edit incident of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadProjectIncident, - title: 'Read Incident', - description: - 'This permission can read incident of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateStatusPageSubscriber, - title: 'Create Status Page Subscriber', - description: - 'This permission can create subscriber on status page this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPageSubscriber, - title: 'Delete Status Page Subscriber', - description: - 'This permission can delete subscriber on status page of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPageSubscriber, - title: 'Edit Status Page Subscriber', - description: - 'This permission can edit subscriber on status page of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPageSubscriber, - title: 'Read Status Page Subscriber', - description: - 'This permission can read subscriber on status page of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateStatusPagePrivateUser, - title: 'Create Status Page Private User', - description: - 'This permission can create private user on status page this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteStatusPagePrivateUser, - title: 'Delete Status Page PrivateUser', - description: - 'This permission can delete private user on status page of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditStatusPagePrivateUser, - title: 'Edit Status Page PrivateUser', - description: - 'This permission can edit private user on status page of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadStatusPagePrivateUser, - title: 'Read Status Page Private User', - description: - 'This permission can read private user on status page of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - // Scheduled Maintenance Permissions. - - { - permission: Permission.CreateScheduledMaintenanceState, - title: 'Create Scheduled Maintenance State', - description: - 'This permission can create Scheduled Maintenance states this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteScheduledMaintenanceState, - title: 'Delete Scheduled Maintenance State', - description: - 'This permission can delete Scheduled Maintenance states of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditScheduledMaintenanceState, - title: 'Edit Scheduled Maintenance State', - description: - 'This permission can edit Scheduled Maintenance states of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadScheduledMaintenanceState, - title: 'Read Scheduled Maintenance State', - description: - 'This permission can read Scheduled Maintenance states of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateProjectScheduledMaintenance, - title: 'Create Scheduled Maintenance', - description: - 'This permission can create Scheduled Maintenance this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteProjectScheduledMaintenance, - title: 'Delete Scheduled Maintenance', - description: - 'This permission can delete Scheduled Maintenance of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.EditProjectScheduledMaintenance, - title: 'Edit Scheduled Maintenance', - description: - 'This permission can edit Scheduled Maintenance of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - { - permission: Permission.ReadProjectScheduledMaintenance, - title: 'Read Scheduled Maintenance', - description: - 'This permission can read Scheduled Maintenance of this project.', - isAssignableToTenant: true, - isAccessControlPermission: true, - }, - - { - permission: Permission.CreateScheduledMaintenanceStateTimeline, - title: 'Create Scheduled Maintenance State Timeline', - description: - 'This permission can create Scheduled Maintenance state history of an Scheduled Maintenance in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteScheduledMaintenanceStateTimeline, - title: 'Delete Scheduled Maintenance State Timeline', - description: - 'This permission can delete Scheduled Maintenance state history of an Scheduled Maintenance in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditScheduledMaintenanceStateTimeline, - title: 'Edit Scheduled Maintenance State Timeline', - description: - 'This permission can edit Scheduled Maintenance state history of an Scheduled Maintenance in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadScheduledMaintenanceStateTimeline, - title: 'Read Scheduled Maintenance State Timeline', - description: - 'This permission can read Scheduled Maintenance state history of an Scheduled Maintenance in this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateScheduledMaintenanceInternalNote, - title: 'Create Scheduled Maintenance Internal Note', - description: - 'This permission can create Scheduled Maintenance Internal Note this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteScheduledMaintenanceInternalNote, - title: 'Delete Scheduled Maintenance Internal Note', - description: - 'This permission can delete Scheduled Maintenance Internal Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditScheduledMaintenanceInternalNote, - title: 'Edit Scheduled Maintenance Internal Note', - description: - 'This permission can edit Scheduled Maintenance Internal Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadScheduledMaintenanceInternalNote, - title: 'Read Scheduled Maintenance Internal Note', - description: - 'This permission can read Scheduled Maintenance Internal Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - - { - permission: Permission.CreateScheduledMaintenancePublicNote, - title: 'Create Scheduled Maintenance Status Page Note', - description: - 'This permission can create Scheduled Maintenance Status Page Note this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.DeleteScheduledMaintenancePublicNote, - title: 'Delete Scheduled Maintenance Status Page Note', - description: - 'This permission can delete Scheduled Maintenance Status Page Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.EditScheduledMaintenancePublicNote, - title: 'Edit Scheduled Maintenance Status Page Note', - description: - 'This permission can edit Scheduled Maintenance Status Page Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - { - permission: Permission.ReadScheduledMaintenancePublicNote, - title: 'Read Scheduled Maintenance Status Page Note', - description: - 'This permission can read Scheduled Maintenance Status Page Note of this project.', - isAssignableToTenant: true, - isAccessControlPermission: false, - }, - ]; - - return permissions; - } - - public static getAllPermissionPropsAsDictionary(): Dictionary<PermissionProps> { - const permissions: Array<PermissionProps> = - PermissionHelper.getAllPermissionProps(); - - const dict: Dictionary<PermissionProps> = {}; - - for (const permission of permissions) { - dict[permission.permission] = permission; - } - - return dict; - } + return dict; + } } export interface UserGlobalAccessPermission extends JSONObject { - projectIds: Array<ObjectID>; - globalPermissions: Array<Permission>; - _type: 'UserGlobalAccessPermission'; + projectIds: Array<ObjectID>; + globalPermissions: Array<Permission>; + _type: "UserGlobalAccessPermission"; } export interface UserPermission extends JSONObject { - _type: 'UserPermission'; - permission: Permission; - labelIds: Array<ObjectID>; - isBlockPermission?: boolean | undefined; + _type: "UserPermission"; + permission: Permission; + labelIds: Array<ObjectID>; + isBlockPermission?: boolean | undefined; } export interface UserTenantAccessPermission extends JSONObject { - _type: 'UserTenantAccessPermission'; - projectId: ObjectID; - permissions: Array<UserPermission>; + _type: "UserTenantAccessPermission"; + projectId: ObjectID; + permissions: Array<UserPermission>; } export const PermissionsArray: Array<string> = [ - ...new Set(Object.keys(Permission)), + ...new Set(Object.keys(Permission)), ]; // Returns ["Owner", "Administrator"...] export function instanceOfUserTenantAccessPermission( - object: any + object: any, ): object is UserTenantAccessPermission { - return object._type === 'UserTenantAccessPermission'; + return object._type === "UserTenantAccessPermission"; } export function instanceOfUserPermission( - object: any + object: any, ): object is UserPermission { - return object._type === 'UserPermission'; + return object._type === "UserPermission"; } export function instanceOfUserGlobalAccessPermission( - object: any + object: any, ): object is UserGlobalAccessPermission { - return object._type === 'UserGlobalAccessPermission'; + return object._type === "UserGlobalAccessPermission"; } export default Permission; diff --git a/Common/Types/Phone.ts b/Common/Types/Phone.ts index b52397c570..657948c1a8 100644 --- a/Common/Types/Phone.ts +++ b/Common/Types/Phone.ts @@ -1,75 +1,74 @@ -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import { FindOperator } from "typeorm"; export default class Phone extends DatabaseProperty { - private _phone: string = ''; - public get phone(): string { - return this._phone; + private _phone: string = ""; + public get phone(): string { + return this._phone; + } + public set phone(v: string) { + /* + * TODO: Have a valid regex for phone. + * const re: RegExp = + * /^(([^<>()[\].,;:\s@"]+(.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+.)+[^<>()[\].,;:\s@"]{2,})$/i; + * const isValid: boolean = re.test(v); + * if (!isValid) { + * throw new BadDataException('Phone is not in valid format.'); + * } + */ + const re: RegExp = /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,7}$/; // regex for international phone numbers format based on (ITU-T E.123) + const isValid: boolean = re.test(v); + if (!isValid) { + throw new BadDataException(`Phone is not in valid format: ${v}`); } - public set phone(v: string) { - /* - * TODO: Have a valid regex for phone. - * const re: RegExp = - * /^(([^<>()[\].,;:\s@"]+(.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+.)+[^<>()[\].,;:\s@"]{2,})$/i; - * const isValid: boolean = re.test(v); - * if (!isValid) { - * throw new BadDataException('Phone is not in valid format.'); - * } - */ - const re: RegExp = - /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,7}$/; // regex for international phone numbers format based on (ITU-T E.123) - const isValid: boolean = re.test(v); - if (!isValid) { - throw new BadDataException(`Phone is not in valid format: ${v}`); - } - this._phone = v; + this._phone = v; + } + + public constructor(phone: string) { + super(); + this.phone = phone; + } + + public override toString(): string { + return this.phone; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Phone, + value: (this as Phone).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Phone { + if (json["_type"] === ObjectType.Phone) { + return new Phone((json["value"] as string) || ""); } - public constructor(phone: string) { - super(); - this.phone = phone; + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + protected static override toDatabase( + value: Phone | FindOperator<Phone>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new Phone(value); + } + + return value.toString(); } - public override toString(): string { - return this.phone; + return null; + } + + protected static override fromDatabase(_value: string): Phone | null { + if (_value) { + return new Phone(_value); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.Phone, - value: (this as Phone).toString(), - }; - } - - public static override fromJSON(json: JSONObject): Phone { - if (json['_type'] === ObjectType.Phone) { - return new Phone((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - protected static override toDatabase( - value: Phone | FindOperator<Phone> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new Phone(value); - } - - return value.toString(); - } - - return null; - } - - protected static override fromDatabase(_value: string): Phone | null { - if (_value) { - return new Phone(_value); - } - - return null; - } + return null; + } } diff --git a/Common/Types/Port.ts b/Common/Types/Port.ts index 65d246d55f..9c3bfe2303 100644 --- a/Common/Types/Port.ts +++ b/Common/Types/Port.ts @@ -1,92 +1,92 @@ -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import PositiveNumber from './PositiveNumber'; -import Typeof from './Typeof'; -import { FindOperator } from 'typeorm/find-options/FindOperator'; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import PositiveNumber from "./PositiveNumber"; +import Typeof from "./Typeof"; +import { FindOperator } from "typeorm/find-options/FindOperator"; export default class Port extends DatabaseProperty { - private _port: PositiveNumber = new PositiveNumber(0); + private _port: PositiveNumber = new PositiveNumber(0); - public get port(): PositiveNumber { - return this._port; + public get port(): PositiveNumber { + return this._port; + } + public set port(value: PositiveNumber) { + if (Port.isValid(value)) { + this._port = value; + } else { + throw new BadDataException("Port is not in valid format."); } - public set port(value: PositiveNumber) { - if (Port.isValid(value)) { - this._port = value; - } else { - throw new BadDataException('Port is not in valid format.'); - } + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Port, + value: (this as Port).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Port { + if (json["_type"] === ObjectType.Port) { + return new Port((json["value"] as string) || ""); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.Port, - value: (this as Port).toString(), - }; - } + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } - public static override fromJSON(json: JSONObject): Port { - if (json['_type'] === ObjectType.Port) { - return new Port((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - public static isValid(port: number | string | PositiveNumber): boolean { - if (typeof port === Typeof.String) { - try { - port = Number.parseInt(port.toString().trim(), 10); - } catch (error) { - return false; - } - } - - if (port instanceof PositiveNumber) { - port = port.toNumber(); - } - - if ((port as number) >= 0 && (port as number) <= 65535) { - return true; - } + public static isValid(port: number | string | PositiveNumber): boolean { + if (typeof port === Typeof.String) { + try { + port = Number.parseInt(port.toString().trim(), 10); + } catch (error) { return false; + } } - public constructor(port: number | string) { - super(); - this.port = new PositiveNumber(port); + if (port instanceof PositiveNumber) { + port = port.toNumber(); } - public static override toDatabase( - value: Port | FindOperator<Port> - ): number | null { - if (typeof value === 'string') { - value = new Port(value); - } + if ((port as number) >= 0 && (port as number) <= 65535) { + return true; + } + return false; + } - if (value instanceof Port) { - return value.toNumber(); - } else if (typeof value === 'string') { - return parseInt(value); - } + public constructor(port: number | string) { + super(); + this.port = new PositiveNumber(port); + } - return null; + public static override toDatabase( + value: Port | FindOperator<Port>, + ): number | null { + if (typeof value === "string") { + value = new Port(value); } - public static override fromDatabase(_value: string | number): Port | null { - if (_value) { - return new Port(_value); - } - - return null; + if (value instanceof Port) { + return value.toNumber(); + } else if (typeof value === "string") { + return parseInt(value); } - public override toString(): string { - return this.port.toString(); + return null; + } + + public static override fromDatabase(_value: string | number): Port | null { + if (_value) { + return new Port(_value); } - public toNumber(): number { - return this.port.toNumber(); - } + return null; + } + + public override toString(): string { + return this.port.toString(); + } + + public toNumber(): number { + return this.port.toNumber(); + } } diff --git a/Common/Types/PositiveNumber.ts b/Common/Types/PositiveNumber.ts index fd5ba475af..58a7a9ab96 100644 --- a/Common/Types/PositiveNumber.ts +++ b/Common/Types/PositiveNumber.ts @@ -1,79 +1,71 @@ -import BadDataException from './Exception/BadDataException'; -import { JSONObject, JSONValue, ObjectType } from './JSON'; -import Typeof from './Typeof'; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, JSONValue, ObjectType } from "./JSON"; +import Typeof from "./Typeof"; export default class PositiveNumber { - private _positiveNumber: number = 0; - public get positiveNumber(): number { - return this._positiveNumber; - } - public set positiveNumber(v: number) { - this._positiveNumber = v; + private _positiveNumber: number = 0; + public get positiveNumber(): number { + return this._positiveNumber; + } + public set positiveNumber(v: number) { + this._positiveNumber = v; + } + + public constructor(positiveNumber: number | string) { + if (typeof positiveNumber === Typeof.String) { + positiveNumber = Number.parseInt(positiveNumber.toString().trim(), 10); + if (isNaN(positiveNumber)) { + throw new BadDataException(`Invalid number: ${positiveNumber}`); + } } - public constructor(positiveNumber: number | string) { - if (typeof positiveNumber === Typeof.String) { - positiveNumber = Number.parseInt( - positiveNumber.toString().trim(), - 10 - ); - if (isNaN(positiveNumber)) { - throw new BadDataException(`Invalid number: ${positiveNumber}`); - } - } - - if ((positiveNumber as number) < 0) { - throw new BadDataException('positiveNumber cannot be less than 0'); - } - - this.positiveNumber = positiveNumber as number; + if ((positiveNumber as number) < 0) { + throw new BadDataException("positiveNumber cannot be less than 0"); } - public toString(): string { - return this.positiveNumber.toString(); + this.positiveNumber = positiveNumber as number; + } + + public toString(): string { + return this.positiveNumber.toString(); + } + + public isZero(): boolean { + return this.positiveNumber === 0; + } + + public isOne(): boolean { + return this.positiveNumber === 1; + } + + public toNumber(): number { + return this.positiveNumber; + } + + public toJSON(): JSONObject { + return { + _type: ObjectType.PositiveNumber, + value: (this as PositiveNumber).toNumber(), + }; + } + + public static fromJSON(json: JSONValue): PositiveNumber { + if (json instanceof PositiveNumber) { + return json; } - public isZero(): boolean { - return this.positiveNumber === 0; + if (typeof json === Typeof.Number) { + return new PositiveNumber(json as number); } - public isOne(): boolean { - return this.positiveNumber === 1; + if (typeof json === Typeof.String) { + return new PositiveNumber(json as string); } - public toNumber(): number { - return this.positiveNumber; + if (!json || (json as JSONObject)["_type"] !== ObjectType.PositiveNumber) { + throw new BadDataException("Invalid PositiveNumber"); } - public toJSON(): JSONObject { - return { - _type: ObjectType.PositiveNumber, - value: (this as PositiveNumber).toNumber(), - }; - } - - public static fromJSON(json: JSONValue): PositiveNumber { - if (json instanceof PositiveNumber) { - return json; - } - - if (typeof json === Typeof.Number) { - return new PositiveNumber(json as number); - } - - if (typeof json === Typeof.String) { - return new PositiveNumber(json as string); - } - - if ( - !json || - (json as JSONObject)['_type'] !== ObjectType.PositiveNumber - ) { - throw new BadDataException('Invalid PositiveNumber'); - } - - return new PositiveNumber( - (json as JSONObject)['value'] as number | string - ); - } + return new PositiveNumber((json as JSONObject)["value"] as number | string); + } } diff --git a/Common/Types/PricingPlan.ts b/Common/Types/PricingPlan.ts index a7c2415dce..2b96029c87 100644 --- a/Common/Types/PricingPlan.ts +++ b/Common/Types/PricingPlan.ts @@ -1,7 +1,7 @@ export default interface PricingPlan { - category: string; - planId: string; - type: string; - amount: number; - details: string; + category: string; + planId: string; + type: string; + amount: number; + details: string; } diff --git a/Common/Types/Probe/ProbeApiIngestResponse.ts b/Common/Types/Probe/ProbeApiIngestResponse.ts index 1a473a7aef..46eb72f35c 100644 --- a/Common/Types/Probe/ProbeApiIngestResponse.ts +++ b/Common/Types/Probe/ProbeApiIngestResponse.ts @@ -1,9 +1,9 @@ -import ObjectID from '../ObjectID'; +import ObjectID from "../ObjectID"; export default interface ProbeApiIngestResponse { - monitorId: ObjectID; - ingestedMonitorStepId?: ObjectID | undefined; - nextMonitorStepId?: ObjectID | undefined; - criteriaMetId?: string | undefined; - rootCause: string | null; // this is in markdown format + monitorId: ObjectID; + ingestedMonitorStepId?: ObjectID | undefined; + nextMonitorStepId?: ObjectID | undefined; + criteriaMetId?: string | undefined; + rootCause: string | null; // this is in markdown format } diff --git a/Common/Types/Probe/ProbeMonitorResponse.ts b/Common/Types/Probe/ProbeMonitorResponse.ts index d5132328c5..6315e46680 100644 --- a/Common/Types/Probe/ProbeMonitorResponse.ts +++ b/Common/Types/Probe/ProbeMonitorResponse.ts @@ -1,28 +1,28 @@ -import Hostname from '../API/Hostname'; -import URL from '../API/URL'; -import Dictionary from '../Dictionary'; -import IP from '../IP/IP'; -import { JSONObject } from '../JSON'; -import CustomCodeMonitorResponse from '../Monitor/CustomCodeMonitor/CustomCodeMonitorResponse'; -import SslMonitorResponse from '../Monitor/SSLMonitor/SslMonitorResponse'; -import SyntheticMonitorResponse from '../Monitor/SyntheticMonitors/SyntheticMonitorResponse'; -import ObjectID from '../ObjectID'; -import Port from '../Port'; +import Hostname from "../API/Hostname"; +import URL from "../API/URL"; +import Dictionary from "../Dictionary"; +import IP from "../IP/IP"; +import { JSONObject } from "../JSON"; +import CustomCodeMonitorResponse from "../Monitor/CustomCodeMonitor/CustomCodeMonitorResponse"; +import SslMonitorResponse from "../Monitor/SSLMonitor/SslMonitorResponse"; +import SyntheticMonitorResponse from "../Monitor/SyntheticMonitors/SyntheticMonitorResponse"; +import ObjectID from "../ObjectID"; +import Port from "../Port"; export default interface ProbeMonitorResponse { - isOnline?: boolean | undefined; - monitorDestination?: URL | IP | Hostname | undefined; - monitorDestinationPort?: Port | undefined; - responseTimeInMs?: number | undefined; - responseCode?: number | undefined; - responseHeaders?: Dictionary<string> | undefined; - responseBody?: string | JSONObject | undefined; - monitorStepId: ObjectID; - monitorId: ObjectID; - probeId: ObjectID; - failureCause: string; - sslResponse?: SslMonitorResponse | undefined; - syntheticMonitorResponse?: Array<SyntheticMonitorResponse> | undefined; - customCodeMonitorResponse?: CustomCodeMonitorResponse | undefined; - monitoredAt: Date; + isOnline?: boolean | undefined; + monitorDestination?: URL | IP | Hostname | undefined; + monitorDestinationPort?: Port | undefined; + responseTimeInMs?: number | undefined; + responseCode?: number | undefined; + responseHeaders?: Dictionary<string> | undefined; + responseBody?: string | JSONObject | undefined; + monitorStepId: ObjectID; + monitorId: ObjectID; + probeId: ObjectID; + failureCause: string; + sslResponse?: SslMonitorResponse | undefined; + syntheticMonitorResponse?: Array<SyntheticMonitorResponse> | undefined; + customCodeMonitorResponse?: CustomCodeMonitorResponse | undefined; + monitoredAt: Date; } diff --git a/Common/Types/Probe/ProbeStatusReport.ts b/Common/Types/Probe/ProbeStatusReport.ts index f60592b984..48534bdcbd 100644 --- a/Common/Types/Probe/ProbeStatusReport.ts +++ b/Common/Types/Probe/ProbeStatusReport.ts @@ -1,8 +1,8 @@ interface ProbeStatusReport { - isPingCheckOffline: boolean; - isWebsiteCheckOffline: boolean; - isPortCheckOffline: boolean; - hostname: string; + isPingCheckOffline: boolean; + isWebsiteCheckOffline: boolean; + isPortCheckOffline: boolean; + hostname: string; } export default ProbeStatusReport; diff --git a/Common/Types/Reflection.ts b/Common/Types/Reflection.ts index 1bf9321a5c..b2818cd0e2 100644 --- a/Common/Types/Reflection.ts +++ b/Common/Types/Reflection.ts @@ -1,7 +1,7 @@ -import GenericFunction from './GenericFunction'; -import GenericObject from './GenericObject'; +import GenericFunction from "./GenericFunction"; +import GenericObject from "./GenericObject"; export type ReflectionMetadataType = { - (target: GenericFunction): void; - (target: GenericObject, propertyKey: string | symbol): void; + (target: GenericFunction): void; + (target: GenericObject, propertyKey: string | symbol): void; }; diff --git a/Common/Types/SMS/SMS.ts b/Common/Types/SMS/SMS.ts index 8412766cf5..b993637d04 100644 --- a/Common/Types/SMS/SMS.ts +++ b/Common/Types/SMS/SMS.ts @@ -1,9 +1,9 @@ -import Phone from '../Phone'; +import Phone from "../Phone"; export interface SMSMessage { - message: string; + message: string; } export default interface SMS extends SMSMessage { - to: Phone; + to: Phone; } diff --git a/Common/Types/SMS/SmsTemplateType.ts b/Common/Types/SMS/SmsTemplateType.ts index d1a352c974..8a49c7d117 100644 --- a/Common/Types/SMS/SmsTemplateType.ts +++ b/Common/Types/SMS/SmsTemplateType.ts @@ -1,13 +1,13 @@ enum SmsTemplateType { - SUBSCRIBER_INCIDENT_CREATED = 'Subscriber Incident Created', - SUBSCRIBER_INCIDENT_ACKNOWLEDGED = 'Subscriber Incident Acknowledged', - SUBSCRIBER_INCIDENT_RESOLVED = 'Subscriber Incident Resolved', - INVESTIGATION_NOTE_CREATED = 'Investigation Note Created', - SUBSCRIBER_SCHEDULED_MAINTENANCE_CREATED = 'Subscriber Scheduled Maintenance Created', - SUBSCRIBER_SCHEDULED_MAINTENANCE_NOTE_CREATED = 'Subscriber Scheduled Maintenance Note Created', - SUBSCRIBER_SCHEDULED_MAINTENANCE_RESOLVED = 'Subscriber Scheduled Maintenance Resolved', - SUBSCRIBER_SCHEDULED_MAINTENANCE_CANCELLED = 'Subscriber Scheduled Maintenance Cancelled', - SUBSCRIBER_ANNOUNCEMENT_NOTIFICATION_CREATED = 'Subscriber Announcement Notification Created', + SUBSCRIBER_INCIDENT_CREATED = "Subscriber Incident Created", + SUBSCRIBER_INCIDENT_ACKNOWLEDGED = "Subscriber Incident Acknowledged", + SUBSCRIBER_INCIDENT_RESOLVED = "Subscriber Incident Resolved", + INVESTIGATION_NOTE_CREATED = "Investigation Note Created", + SUBSCRIBER_SCHEDULED_MAINTENANCE_CREATED = "Subscriber Scheduled Maintenance Created", + SUBSCRIBER_SCHEDULED_MAINTENANCE_NOTE_CREATED = "Subscriber Scheduled Maintenance Note Created", + SUBSCRIBER_SCHEDULED_MAINTENANCE_RESOLVED = "Subscriber Scheduled Maintenance Resolved", + SUBSCRIBER_SCHEDULED_MAINTENANCE_CANCELLED = "Subscriber Scheduled Maintenance Cancelled", + SUBSCRIBER_ANNOUNCEMENT_NOTIFICATION_CREATED = "Subscriber Announcement Notification Created", } export default SmsTemplateType; diff --git a/Common/Types/SSO/DigestMethod.ts b/Common/Types/SSO/DigestMethod.ts index c85a1151dd..3dc7ada6db 100644 --- a/Common/Types/SSO/DigestMethod.ts +++ b/Common/Types/SSO/DigestMethod.ts @@ -1,8 +1,8 @@ enum DigestMethod { - SHA1 = 'SHA1', - SHA256 = 'SHA256', - SHA384 = 'SHA384', - SHA512 = 'SHA512', + SHA1 = "SHA1", + SHA256 = "SHA256", + SHA384 = "SHA384", + SHA512 = "SHA512", } export default DigestMethod; diff --git a/Common/Types/SSO/SignatureMethod.ts b/Common/Types/SSO/SignatureMethod.ts index 3a589d86af..de7eba9b76 100644 --- a/Common/Types/SSO/SignatureMethod.ts +++ b/Common/Types/SSO/SignatureMethod.ts @@ -1,8 +1,8 @@ enum SignatureMethod { - SHA1 = 'RSA-SHA1', - SHA256 = 'RSA-SHA256', - SHA384 = 'RSA-SHA384', - SHA512 = 'RSA-SHA512', + SHA1 = "RSA-SHA1", + SHA256 = "RSA-SHA256", + SHA384 = "RSA-SHA384", + SHA512 = "RSA-SHA512", } export default SignatureMethod; diff --git a/Common/Types/ScheduledEvent/ScheduledEventState.ts b/Common/Types/ScheduledEvent/ScheduledEventState.ts index 5b8ec8e9f4..616d52429e 100644 --- a/Common/Types/ScheduledEvent/ScheduledEventState.ts +++ b/Common/Types/ScheduledEvent/ScheduledEventState.ts @@ -1,8 +1,8 @@ enum ScheduledEventState { - Scheduled = 'Scheduled', - Ongoing = 'Ongoing', - Resolved = 'Resolved', - Cancelled = 'Cancelled', + Scheduled = "Scheduled", + Ongoing = "Ongoing", + Resolved = "Resolved", + Cancelled = "Cancelled", } export default ScheduledEventState; diff --git a/Common/Types/ScreenSizeType.ts b/Common/Types/ScreenSizeType.ts index 2bf6efc9b1..6603bac778 100644 --- a/Common/Types/ScreenSizeType.ts +++ b/Common/Types/ScreenSizeType.ts @@ -1,7 +1,7 @@ enum ScreenSizeType { - Mobile = 'Mobile', - Tablet = 'Tablet', - Desktop = 'Desktop', + Mobile = "Mobile", + Tablet = "Tablet", + Desktop = "Desktop", } export default ScreenSizeType; diff --git a/Common/Types/SecuritySeverity.ts b/Common/Types/SecuritySeverity.ts index d9268f6def..5641cbeb88 100644 --- a/Common/Types/SecuritySeverity.ts +++ b/Common/Types/SecuritySeverity.ts @@ -1,8 +1,8 @@ enum SecuritySeverity { - Critical = 'Critical', - High = 'High', - Medium = 'Medium', - Low = 'Low', + Critical = "Critical", + High = "High", + Medium = "Medium", + Low = "Low", } export default SecuritySeverity; diff --git a/Common/Types/SerializableObject.ts b/Common/Types/SerializableObject.ts index 622494d4ff..64550ed2bb 100644 --- a/Common/Types/SerializableObject.ts +++ b/Common/Types/SerializableObject.ts @@ -1,18 +1,18 @@ -import NotImplementedException from './Exception/NotImplementedException'; -import { JSONObject } from './JSON'; +import NotImplementedException from "./Exception/NotImplementedException"; +import { JSONObject } from "./JSON"; export default class SerializableObject { - public constructor() {} + public constructor() {} - public toJSON(): JSONObject { - throw new NotImplementedException(); - } + public toJSON(): JSONObject { + throw new NotImplementedException(); + } - public static fromJSON(_json: JSONObject): SerializableObject { - throw new NotImplementedException(); - } + public static fromJSON(_json: JSONObject): SerializableObject { + throw new NotImplementedException(); + } - public fromJSON(json: JSONObject): SerializableObject { - return SerializableObject.fromJSON(json); - } + public fromJSON(json: JSONObject): SerializableObject { + return SerializableObject.fromJSON(json); + } } diff --git a/Common/Types/SerializableObjectDictionary.ts b/Common/Types/SerializableObjectDictionary.ts index 84f6bb227c..cfd6b2000d 100644 --- a/Common/Types/SerializableObjectDictionary.ts +++ b/Common/Types/SerializableObjectDictionary.ts @@ -1,63 +1,63 @@ -import Hostname from './API/Hostname'; -import Route from './API/Route'; -import URL from './API/URL'; -import EqualToOrNull from './BaseDatabase/EqualToOrNull'; -import GreaterThan from './BaseDatabase/GreaterThan'; -import GreaterThanOrEqual from './BaseDatabase/GreaterThanOrEqual'; -import InBetween from './BaseDatabase/InBetween'; -import Includes from './BaseDatabase/Includes'; -import IsNull from './BaseDatabase/IsNull'; -import LessThan from './BaseDatabase/LessThan'; -import LessThanOrEqual from './BaseDatabase/LessThanOrEqual'; -import NotEqual from './BaseDatabase/NotEqual'; -import NotNull from './BaseDatabase/NotNull'; -import Search from './BaseDatabase/Search'; -import Color from './Color'; -import OneUptimeDate from './Date'; -import Dictionary from './Dictionary'; -import Domain from './Domain'; -import Email from './Email'; -import HashedString from './HashedString'; -import { ObjectType } from './JSON'; -import MonitorCriteria from './Monitor/MonitorCriteria'; -import MonitorCriteriaInstance from './Monitor/MonitorCriteriaInstance'; -import MonitorStep from './Monitor/MonitorStep'; -import MonitorSteps from './Monitor/MonitorSteps'; -import Name from './Name'; -import ObjectID from './ObjectID'; -import Phone from './Phone'; -import Port from './Port'; -import Version from './Version'; +import Hostname from "./API/Hostname"; +import Route from "./API/Route"; +import URL from "./API/URL"; +import EqualToOrNull from "./BaseDatabase/EqualToOrNull"; +import GreaterThan from "./BaseDatabase/GreaterThan"; +import GreaterThanOrEqual from "./BaseDatabase/GreaterThanOrEqual"; +import InBetween from "./BaseDatabase/InBetween"; +import Includes from "./BaseDatabase/Includes"; +import IsNull from "./BaseDatabase/IsNull"; +import LessThan from "./BaseDatabase/LessThan"; +import LessThanOrEqual from "./BaseDatabase/LessThanOrEqual"; +import NotEqual from "./BaseDatabase/NotEqual"; +import NotNull from "./BaseDatabase/NotNull"; +import Search from "./BaseDatabase/Search"; +import Color from "./Color"; +import OneUptimeDate from "./Date"; +import Dictionary from "./Dictionary"; +import Domain from "./Domain"; +import Email from "./Email"; +import HashedString from "./HashedString"; +import { ObjectType } from "./JSON"; +import MonitorCriteria from "./Monitor/MonitorCriteria"; +import MonitorCriteriaInstance from "./Monitor/MonitorCriteriaInstance"; +import MonitorStep from "./Monitor/MonitorStep"; +import MonitorSteps from "./Monitor/MonitorSteps"; +import Name from "./Name"; +import ObjectID from "./ObjectID"; +import Phone from "./Phone"; +import Port from "./Port"; +import Version from "./Version"; const SerializableObjectDictionary: Dictionary<any> = { - [ObjectType.Phone]: Phone, - [ObjectType.DateTime]: OneUptimeDate, - [ObjectType.ObjectID]: ObjectID, - [ObjectType.Name]: Name, - [ObjectType.EqualToOrNull]: EqualToOrNull, - [ObjectType.MonitorSteps]: MonitorSteps, - [ObjectType.MonitorStep]: MonitorStep, - [ObjectType.MonitorCriteria]: MonitorCriteria, - [ObjectType.MonitorCriteriaInstance]: MonitorCriteriaInstance, - [ObjectType.NotEqual]: NotEqual, - [ObjectType.Email]: Email, - [ObjectType.Color]: Color, - [ObjectType.Domain]: Domain, - [ObjectType.Version]: Version, - [ObjectType.Route]: Route, - [ObjectType.URL]: URL, - [ObjectType.Search]: Search, - [ObjectType.GreaterThan]: GreaterThan, - [ObjectType.GreaterThanOrEqual]: GreaterThanOrEqual, - [ObjectType.LessThan]: LessThan, - [ObjectType.LessThanOrEqual]: LessThanOrEqual, - [ObjectType.Port]: Port, - [ObjectType.Hostname]: Hostname, - [ObjectType.HashedString]: HashedString, - [ObjectType.InBetween]: InBetween, - [ObjectType.Includes]: Includes, - [ObjectType.NotNull]: NotNull, - [ObjectType.IsNull]: IsNull, + [ObjectType.Phone]: Phone, + [ObjectType.DateTime]: OneUptimeDate, + [ObjectType.ObjectID]: ObjectID, + [ObjectType.Name]: Name, + [ObjectType.EqualToOrNull]: EqualToOrNull, + [ObjectType.MonitorSteps]: MonitorSteps, + [ObjectType.MonitorStep]: MonitorStep, + [ObjectType.MonitorCriteria]: MonitorCriteria, + [ObjectType.MonitorCriteriaInstance]: MonitorCriteriaInstance, + [ObjectType.NotEqual]: NotEqual, + [ObjectType.Email]: Email, + [ObjectType.Color]: Color, + [ObjectType.Domain]: Domain, + [ObjectType.Version]: Version, + [ObjectType.Route]: Route, + [ObjectType.URL]: URL, + [ObjectType.Search]: Search, + [ObjectType.GreaterThan]: GreaterThan, + [ObjectType.GreaterThanOrEqual]: GreaterThanOrEqual, + [ObjectType.LessThan]: LessThan, + [ObjectType.LessThanOrEqual]: LessThanOrEqual, + [ObjectType.Port]: Port, + [ObjectType.Hostname]: Hostname, + [ObjectType.HashedString]: HashedString, + [ObjectType.InBetween]: InBetween, + [ObjectType.Includes]: Includes, + [ObjectType.NotNull]: NotNull, + [ObjectType.IsNull]: IsNull, }; export default SerializableObjectDictionary; diff --git a/Common/Types/ServiceCatalog/ServiceLanguage.ts b/Common/Types/ServiceCatalog/ServiceLanguage.ts index e8850b082d..fb1dca5ff3 100644 --- a/Common/Types/ServiceCatalog/ServiceLanguage.ts +++ b/Common/Types/ServiceCatalog/ServiceLanguage.ts @@ -1,20 +1,20 @@ export enum ServiceLanguage { - NodeJS = 'NodeJS', - React = 'React', - Python = 'Python', - Ruby = 'Ruby', - Go = 'Go', - Java = 'Java', - PHP = 'PHP', - CSharp = 'C#', - CPlusPlus = 'C++', - Rust = 'Rust', - Swift = 'Swift', - Kotlin = 'Kotlin', - TypeScript = 'TypeScript', - JavaScript = 'JavaScript', - Shell = 'Shell', - Other = 'Other', + NodeJS = "NodeJS", + React = "React", + Python = "Python", + Ruby = "Ruby", + Go = "Go", + Java = "Java", + PHP = "PHP", + CSharp = "C#", + CPlusPlus = "C++", + Rust = "Rust", + Swift = "Swift", + Kotlin = "Kotlin", + TypeScript = "TypeScript", + JavaScript = "JavaScript", + Shell = "Shell", + Other = "Other", } export default ServiceLanguage; diff --git a/Common/Types/Sleep.ts b/Common/Types/Sleep.ts index e3f7879f0c..b76496634d 100644 --- a/Common/Types/Sleep.ts +++ b/Common/Types/Sleep.ts @@ -1,9 +1,9 @@ -import { VoidFunction } from './FunctionTypes'; +import { VoidFunction } from "./FunctionTypes"; export default class Sleep { - public static async sleep(ms: number): Promise<void> { - return new Promise((resolve: VoidFunction) => { - setTimeout(resolve, ms); - }); - } + public static async sleep(ms: number): Promise<void> { + return new Promise((resolve: VoidFunction) => { + setTimeout(resolve, ms); + }); + } } diff --git a/Common/Types/SmsStatus.ts b/Common/Types/SmsStatus.ts index e4f63cb5fb..613b07c200 100644 --- a/Common/Types/SmsStatus.ts +++ b/Common/Types/SmsStatus.ts @@ -1,7 +1,7 @@ enum SmsStatus { - Success = 'Success', - Error = 'Error', - LowBalance = 'Low Balance', + Success = "Success", + Error = "Error", + LowBalance = "Low Balance", } export default SmsStatus; diff --git a/Common/Types/StatusPage/StatusPageChartType.ts b/Common/Types/StatusPage/StatusPageChartType.ts index 6d7fb1c92b..e57130c4b6 100644 --- a/Common/Types/StatusPage/StatusPageChartType.ts +++ b/Common/Types/StatusPage/StatusPageChartType.ts @@ -1,10 +1,10 @@ enum StatusPageChartType { - Uptime = 'Uptime', - Memory = 'Memory', - CPU = 'CPU', - Storage = 'Storage', - ResponseTime = 'ResponseTime', - Temperature = 'Temperature', + Uptime = "Uptime", + Memory = "Memory", + CPU = "CPU", + Storage = "Storage", + ResponseTime = "ResponseTime", + Temperature = "Temperature", } export default StatusPageChartType; diff --git a/Common/Types/Text.ts b/Common/Types/Text.ts index 4e1fed0877..a01e9682fc 100644 --- a/Common/Types/Text.ts +++ b/Common/Types/Text.ts @@ -1,152 +1,148 @@ export default class Text { - public static convertBase64ToHex(textInBase64: string): string { - if (!textInBase64) { - return textInBase64; - } - - if (!this.isBase64(textInBase64)) { - return textInBase64; - } - - const hex: string = Buffer.from(textInBase64, 'base64').toString('hex'); - return hex; + public static convertBase64ToHex(textInBase64: string): string { + if (!textInBase64) { + return textInBase64; } - public static isBase64(text: string): boolean { - const regex: RegExp = /^[a-zA-Z0-9+/]*={0,2}$/; - return regex.test(text); + if (!this.isBase64(textInBase64)) { + return textInBase64; } - public static generateRandomText(length?: number): string { - if (!length) { - length = 10; - } + const hex: string = Buffer.from(textInBase64, "base64").toString("hex"); + return hex; + } - let result: string = ''; - const characters: string = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - const charactersLength: number = characters.length; - for (let i: number = 0; i < length; i++) { - result += characters.charAt( - Math.floor(Math.random() * charactersLength) - ); - } - return result; + public static isBase64(text: string): boolean { + const regex: RegExp = /^[a-zA-Z0-9+/]*={0,2}$/; + return regex.test(text); + } + + public static generateRandomText(length?: number): string { + if (!length) { + length = 10; } - public static trimLines(text: string): string { - return text - .split('\n') - .map((line: string) => { - return line.trim(); - }) - .join('\n'); + let result: string = ""; + const characters: string = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + const charactersLength: number = characters.length; + for (let i: number = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + } + + public static trimLines(text: string): string { + return text + .split("\n") + .map((line: string) => { + return line.trim(); + }) + .join("\n"); + } + + public static generateRandomNumber(length?: number): string { + if (!length) { + length = 10; } - public static generateRandomNumber(length?: number): string { - if (!length) { - length = 10; - } + let result: string = ""; + const characters: string = "12134567890"; + const charactersLength: number = characters.length; + for (let i: number = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + } - let result: string = ''; - const characters: string = '12134567890'; - const charactersLength: number = characters.length; - for (let i: number = 0; i < length; i++) { - result += characters.charAt( - Math.floor(Math.random() * charactersLength) - ); - } - return result; + public static convertNumberToWords(num: number): string { + const words: Array<string> = [ + "first", + "second", + "third", + "fourth", + "fifth", + "sixth", + "seventh", + "eighth", + "ninth", + "tenth", + "eleventh", + "twelfth", + "thirteenth", + "fourteenth", + "fifteenth", + "sixteenth", + "seventeenth", + "eighteenth", + "nineteenth", + "twentieth", + ]; + + if (num <= 20) { + return words[num - 1]!; } - public static convertNumberToWords(num: number): string { - const words: Array<string> = [ - 'first', - 'second', - 'third', - 'fourth', - 'fifth', - 'sixth', - 'seventh', - 'eighth', - 'ninth', - 'tenth', - 'eleventh', - 'twelfth', - 'thirteenth', - 'fourteenth', - 'fifteenth', - 'sixteenth', - 'seventeenth', - 'eighteenth', - 'nineteenth', - 'twentieth', - ]; - - if (num <= 20) { - return words[num - 1]!; - } - - if (num % 10 === 0) { - return `${words[19]} ${words[num / 10 - 2]}`; - } - - return `${words[19]} ${words[Math.floor(num / 10) - 2]}-${ - words[(num % 10) - 1] - }`; + if (num % 10 === 0) { + return `${words[19]} ${words[num / 10 - 2]}`; } - public static uppercaseFirstLetter(word: string): string { - if (word.length > 0) { - return word.charAt(0).toUpperCase() + word.slice(1); - } - return word; + return `${words[19]} ${words[Math.floor(num / 10) - 2]}-${ + words[(num % 10) - 1] + }`; + } + + public static uppercaseFirstLetter(word: string): string { + if (word.length > 0) { + return word.charAt(0).toUpperCase() + word.slice(1); + } + return word; + } + + public static fromDashesToPascalCase(word: string): string { + let tempWord: string = word.replace(/-/g, " "); + tempWord = tempWord.replace(/\b\w/g, (m: string): string => { + return m.toUpperCase(); + }); + return tempWord; + } + + public static pascalCaseToDashes(word: string): string { + let tempWord: string = word.replace(/[A-Z]/g, (m: string): string => { + return "-" + m.toLowerCase(); + }); + while (tempWord.includes(" ")) { + tempWord = tempWord.replace(" ", "-"); } - public static fromDashesToPascalCase(word: string): string { - let tempWord: string = word.replace(/-/g, ' '); - tempWord = tempWord.replace(/\b\w/g, (m: string): string => { - return m.toUpperCase(); - }); - return tempWord; + if (tempWord.startsWith("-")) { + tempWord = this.replaceAt(0, tempWord, " "); } - public static pascalCaseToDashes(word: string): string { - let tempWord: string = word.replace(/[A-Z]/g, (m: string): string => { - return '-' + m.toLowerCase(); - }); - while (tempWord.includes(' ')) { - tempWord = tempWord.replace(' ', '-'); - } - - if (tempWord.startsWith('-')) { - tempWord = this.replaceAt(0, tempWord, ' '); - } - - if (tempWord.endsWith('-')) { - tempWord = this.replaceAt(tempWord.length - 1, tempWord, ' '); - } - - return tempWord.toLowerCase().trim(); + if (tempWord.endsWith("-")) { + tempWord = this.replaceAt(tempWord.length - 1, tempWord, " "); } - public static replaceAt( - index: number, - word: string, - replacement: string - ): string { - return ( - word.substring(0, index) + - replacement + - word.substring(index + replacement.length) - ); - } + return tempWord.toLowerCase().trim(); + } - public static replaceAll( - sentence: string, - search: string, - replaceBy: string - ): string { - return sentence.split(search).join(replaceBy); - } + public static replaceAt( + index: number, + word: string, + replacement: string, + ): string { + return ( + word.substring(0, index) + + replacement + + word.substring(index + replacement.length) + ); + } + + public static replaceAll( + sentence: string, + search: string, + replaceBy: string, + ): string { + return sentence.split(search).join(replaceBy); + } } diff --git a/Common/Types/Time/StartAndEndTime.ts b/Common/Types/Time/StartAndEndTime.ts index 1b6534b89f..31274c43e2 100644 --- a/Common/Types/Time/StartAndEndTime.ts +++ b/Common/Types/Time/StartAndEndTime.ts @@ -1,4 +1,4 @@ export default interface StartAndEndTime { - startTime: Date; - endTime: Date; + startTime: Date; + endTime: Date; } diff --git a/Common/Types/Timezone.ts b/Common/Types/Timezone.ts index 7fc5a8c1d0..71dbddbda3 100644 --- a/Common/Types/Timezone.ts +++ b/Common/Types/Timezone.ts @@ -1,595 +1,595 @@ enum Timezone { - AfricaAbidjan = 'Africa/Abidjan', - AfricaAccra = 'Africa/Accra', - AfricaAddis_Ababa = 'Africa/Addis_Ababa', - AfricaAlgiers = 'Africa/Algiers', - AfricaAsmara = 'Africa/Asmara', - AfricaAsmera = 'Africa/Asmera', - AfricaBamako = 'Africa/Bamako', - AfricaBangui = 'Africa/Bangui', - AfricaBanjul = 'Africa/Banjul', - AfricaBissau = 'Africa/Bissau', - AfricaBlantyre = 'Africa/Blantyre', - AfricaBrazzaville = 'Africa/Brazzaville', - AfricaBujumbura = 'Africa/Bujumbura', - AfricaCairo = 'Africa/Cairo', - AfricaCasablanca = 'Africa/Casablanca', - AfricaCeuta = 'Africa/Ceuta', - AfricaConakry = 'Africa/Conakry', - AfricaDakar = 'Africa/Dakar', - AfricaDar_es_Salaam = 'Africa/Dar_es_Salaam', - AfricaDjibouti = 'Africa/Djibouti', - AfricaDouala = 'Africa/Douala', - AfricaEl_Aaiun = 'Africa/El_Aaiun', - AfricaFreetown = 'Africa/Freetown', - AfricaGaborone = 'Africa/Gaborone', - AfricaHarare = 'Africa/Harare', - AfricaJohannesburg = 'Africa/Johannesburg', - AfricaJuba = 'Africa/Juba', - AfricaKampala = 'Africa/Kampala', - AfricaKhartoum = 'Africa/Khartoum', - AfricaKigali = 'Africa/Kigali', - AfricaKinshasa = 'Africa/Kinshasa', - AfricaLagos = 'Africa/Lagos', - AfricaLibreville = 'Africa/Libreville', - AfricaLome = 'Africa/Lome', - AfricaLuanda = 'Africa/Luanda', - AfricaLubumbashi = 'Africa/Lubumbashi', - AfricaLusaka = 'Africa/Lusaka', - AfricaMalabo = 'Africa/Malabo', - AfricaMaputo = 'Africa/Maputo', - AfricaMaseru = 'Africa/Maseru', - AfricaMbabane = 'Africa/Mbabane', - AfricaMogadishu = 'Africa/Mogadishu', - AfricaMonrovia = 'Africa/Monrovia', - AfricaNairobi = 'Africa/Nairobi', - AfricaNdjamena = 'Africa/Ndjamena', - AfricaNiamey = 'Africa/Niamey', - AfricaNouakchott = 'Africa/Nouakchott', - AfricaOuagadougou = 'Africa/Ouagadougou', - AfricaPortoNovo = 'Africa/Porto-Novo', - AfricaSao_Tome = 'Africa/Sao_Tome', - AfricaTimbuktu = 'Africa/Timbuktu', - AfricaTripoli = 'Africa/Tripoli', - AfricaTunis = 'Africa/Tunis', - AfricaWindhoek = 'Africa/Windhoek', - AmericaAdak = 'America/Adak', - AmericaAnchorage = 'America/Anchorage', - AmericaAnguilla = 'America/Anguilla', - AmericaAntigua = 'America/Antigua', - AmericaAraguaina = 'America/Araguaina', - AmericaArgentinaBuenos_Aires = 'America/Argentina/Buenos_Aires', - AmericaArgentinaCatamarca = 'America/Argentina/Catamarca', - AmericaArgentinaComodRivadavia = 'America/Argentina/ComodRivadavia', - AmericaArgentinaCordoba = 'America/Argentina/Cordoba', - AmericaArgentinaJujuy = 'America/Argentina/Jujuy', - AmericaArgentinaLa_Rioja = 'America/Argentina/La_Rioja', - AmericaArgentinaMendoza = 'America/Argentina/Mendoza', - AmericaArgentinaRio_Gallegos = 'America/Argentina/Rio_Gallegos', - AmericaArgentinaSalta = 'America/Argentina/Salta', - AmericaArgentinaSan_Juan = 'America/Argentina/San_Juan', - AmericaArgentinaSan_Luis = 'America/Argentina/San_Luis', - AmericaArgentinaTucuman = 'America/Argentina/Tucuman', - AmericaArgentinaUshuaia = 'America/Argentina/Ushuaia', - AmericaAruba = 'America/Aruba', - AmericaAsuncion = 'America/Asuncion', - AmericaAtikokan = 'America/Atikokan', - AmericaAtka = 'America/Atka', - AmericaBahia = 'America/Bahia', - AmericaBahia_Banderas = 'America/Bahia_Banderas', - AmericaBarbados = 'America/Barbados', - AmericaBelem = 'America/Belem', - AmericaBelize = 'America/Belize', - AmericaBlancSablon = 'America/Blanc-Sablon', - AmericaBoa_Vista = 'America/Boa_Vista', - AmericaBogota = 'America/Bogota', - AmericaBoise = 'America/Boise', - AmericaBuenos_Aires = 'America/Buenos_Aires', - AmericaCambridge_Bay = 'America/Cambridge_Bay', - AmericaCampo_Grande = 'America/Campo_Grande', - AmericaCancun = 'America/Cancun', - AmericaCaracas = 'America/Caracas', - AmericaCatamarca = 'America/Catamarca', - AmericaCayenne = 'America/Cayenne', - AmericaCayman = 'America/Cayman', - AmericaChicago = 'America/Chicago', - AmericaChihuahua = 'America/Chihuahua', - AmericaCoral_Harbour = 'America/Coral_Harbour', - AmericaCordoba = 'America/Cordoba', - AmericaCosta_Rica = 'America/Costa_Rica', - AmericaCreston = 'America/Creston', - AmericaCuiaba = 'America/Cuiaba', - AmericaCuracao = 'America/Curacao', - AmericaDanmarkshavn = 'America/Danmarkshavn', - AmericaDawson = 'America/Dawson', - AmericaDawson_Creek = 'America/Dawson_Creek', - AmericaDenver = 'America/Denver', - AmericaDetroit = 'America/Detroit', - AmericaDominica = 'America/Dominica', - AmericaEdmonton = 'America/Edmonton', - AmericaEirunepe = 'America/Eirunepe', - AmericaEl_Salvador = 'America/El_Salvador', - AmericaEnsenada = 'America/Ensenada', - AmericaFort_Nelson = 'America/Fort_Nelson', - AmericaFort_Wayne = 'America/Fort_Wayne', - AmericaFortaleza = 'America/Fortaleza', - AmericaGlace_Bay = 'America/Glace_Bay', - AmericaGodthab = 'America/Godthab', - AmericaGoose_Bay = 'America/Goose_Bay', - AmericaGrand_Turk = 'America/Grand_Turk', - AmericaGrenada = 'America/Grenada', - AmericaGuadeloupe = 'America/Guadeloupe', - AmericaGuatemala = 'America/Guatemala', - AmericaGuayaquil = 'America/Guayaquil', - AmericaGuyana = 'America/Guyana', - AmericaHalifax = 'America/Halifax', - AmericaHavana = 'America/Havana', - AmericaHermosillo = 'America/Hermosillo', - AmericaIndianaIndianapolis = 'America/Indiana/Indianapolis', - AmericaIndianaKnox = 'America/Indiana/Knox', - AmericaIndianaMarengo = 'America/Indiana/Marengo', - AmericaIndianaPetersburg = 'America/Indiana/Petersburg', - AmericaIndianaTell_City = 'America/Indiana/Tell_City', - AmericaIndianaVevay = 'America/Indiana/Vevay', - AmericaIndianaVincennes = 'America/Indiana/Vincennes', - AmericaIndianaWinamac = 'America/Indiana/Winamac', - AmericaIndianapolis = 'America/Indianapolis', - AmericaInuvik = 'America/Inuvik', - AmericaIqaluit = 'America/Iqaluit', - AmericaJamaica = 'America/Jamaica', - AmericaJujuy = 'America/Jujuy', - AmericaJuneau = 'America/Juneau', - AmericaKentuckyLouisville = 'America/Kentucky/Louisville', - AmericaKentuckyMonticello = 'America/Kentucky/Monticello', - AmericaKnox_IN = 'America/Knox_IN', - AmericaKralendijk = 'America/Kralendijk', - AmericaLa_Paz = 'America/La_Paz', - AmericaLima = 'America/Lima', - AmericaLos_Angeles = 'America/Los_Angeles', - AmericaLouisville = 'America/Louisville', - AmericaLower_Princes = 'America/Lower_Princes', - AmericaMaceio = 'America/Maceio', - AmericaManagua = 'America/Managua', - AmericaManaus = 'America/Manaus', - AmericaMarigot = 'America/Marigot', - AmericaMartinique = 'America/Martinique', - AmericaMatamoros = 'America/Matamoros', - AmericaMazatlan = 'America/Mazatlan', - AmericaMendoza = 'America/Mendoza', - AmericaMenominee = 'America/Menominee', - AmericaMerida = 'America/Merida', - AmericaMetlakatla = 'America/Metlakatla', - AmericaMexico_City = 'America/Mexico_City', - AmericaMiquelon = 'America/Miquelon', - AmericaMoncton = 'America/Moncton', - AmericaMonterrey = 'America/Monterrey', - AmericaMontevideo = 'America/Montevideo', - AmericaMontreal = 'America/Montreal', - AmericaMontserrat = 'America/Montserrat', - AmericaNassau = 'America/Nassau', - AmericaNew_York = 'America/New_York', - AmericaNipigon = 'America/Nipigon', - AmericaNome = 'America/Nome', - AmericaNoronha = 'America/Noronha', - AmericaNorth_DakotaBeulah = 'America/North_Dakota/Beulah', - AmericaNorth_DakotaCenter = 'America/North_Dakota/Center', - AmericaNorth_DakotaNew_Salem = 'America/North_Dakota/New_Salem', - AmericaOjinaga = 'America/Ojinaga', - AmericaPanama = 'America/Panama', - AmericaPangnirtung = 'America/Pangnirtung', - AmericaParamaribo = 'America/Paramaribo', - AmericaPhoenix = 'America/Phoenix', - AmericaPortAuPrince = 'America/Port-au-Prince', - AmericaPort_of_Spain = 'America/Port_of_Spain', - AmericaPorto_Acre = 'America/Porto_Acre', - AmericaPorto_Velho = 'America/Porto_Velho', - AmericaPuerto_Rico = 'America/Puerto_Rico', - AmericaPunta_Arenas = 'America/Punta_Arenas', - AmericaRainy_River = 'America/Rainy_River', - AmericaRankin_Inlet = 'America/Rankin_Inlet', - AmericaRecife = 'America/Recife', - AmericaRegina = 'America/Regina', - AmericaResolute = 'America/Resolute', - AmericaRio_Branco = 'America/Rio_Branco', - AmericaRosario = 'America/Rosario', - AmericaSanta_Isabel = 'America/Santa_Isabel', - AmericaSantarem = 'America/Santarem', - AmericaSantiago = 'America/Santiago', - AmericaSanto_Domingo = 'America/Santo_Domingo', - AmericaSao_Paulo = 'America/Sao_Paulo', - AmericaScoresbysund = 'America/Scoresbysund', - AmericaShiprock = 'America/Shiprock', - AmericaSitka = 'America/Sitka', - AmericaSt_Barthelemy = 'America/St_Barthelemy', - AmericaSt_Johns = 'America/St_Johns', - AmericaSt_Kitts = 'America/St_Kitts', - AmericaSt_Lucia = 'America/St_Lucia', - AmericaSt_Thomas = 'America/St_Thomas', - AmericaSt_Vincent = 'America/St_Vincent', - AmericaSwift_Current = 'America/Swift_Current', - AmericaTegucigalpa = 'America/Tegucigalpa', - AmericaThule = 'America/Thule', - AmericaThunder_Bay = 'America/Thunder_Bay', - AmericaTijuana = 'America/Tijuana', - AmericaToronto = 'America/Toronto', - AmericaTortola = 'America/Tortola', - AmericaVancouver = 'America/Vancouver', - AmericaVirgin = 'America/Virgin', - AmericaWhitehorse = 'America/Whitehorse', - AmericaWinnipeg = 'America/Winnipeg', - AmericaYakutat = 'America/Yakutat', - AmericaYellowknife = 'America/Yellowknife', - AntarcticaCasey = 'Antarctica/Casey', - AntarcticaDavis = 'Antarctica/Davis', - AntarcticaDumontDUrville = 'Antarctica/DumontDUrville', - AntarcticaMacquarie = 'Antarctica/Macquarie', - AntarcticaMawson = 'Antarctica/Mawson', - AntarcticaMcMurdo = 'Antarctica/McMurdo', - AntarcticaPalmer = 'Antarctica/Palmer', - AntarcticaRothera = 'Antarctica/Rothera', - AntarcticaSouth_Pole = 'Antarctica/South_Pole', - AntarcticaSyowa = 'Antarctica/Syowa', - AntarcticaTroll = 'Antarctica/Troll', - AntarcticaVostok = 'Antarctica/Vostok', - ArcticLongyearbyen = 'Arctic/Longyearbyen', - AsiaAden = 'Asia/Aden', - AsiaAlmaty = 'Asia/Almaty', - AsiaAmman = 'Asia/Amman', - AsiaAnadyr = 'Asia/Anadyr', - AsiaAqtau = 'Asia/Aqtau', - AsiaAqtobe = 'Asia/Aqtobe', - AsiaAshgabat = 'Asia/Ashgabat', - AsiaAshkhabad = 'Asia/Ashkhabad', - AsiaAtyrau = 'Asia/Atyrau', - AsiaBaghdad = 'Asia/Baghdad', - AsiaBahrain = 'Asia/Bahrain', - AsiaBaku = 'Asia/Baku', - AsiaBangkok = 'Asia/Bangkok', - AsiaBarnaul = 'Asia/Barnaul', - AsiaBeirut = 'Asia/Beirut', - AsiaBishkek = 'Asia/Bishkek', - AsiaBrunei = 'Asia/Brunei', - AsiaCalcutta = 'Asia/Calcutta', - AsiaChita = 'Asia/Chita', - AsiaChoibalsan = 'Asia/Choibalsan', - AsiaChongqing = 'Asia/Chongqing', - AsiaChungking = 'Asia/Chungking', - AsiaColombo = 'Asia/Colombo', - AsiaDacca = 'Asia/Dacca', - AsiaDamascus = 'Asia/Damascus', - AsiaDhaka = 'Asia/Dhaka', - AsiaDili = 'Asia/Dili', - AsiaDubai = 'Asia/Dubai', - AsiaDushanbe = 'Asia/Dushanbe', - AsiaFamagusta = 'Asia/Famagusta', - AsiaGaza = 'Asia/Gaza', - AsiaHarbin = 'Asia/Harbin', - AsiaHebron = 'Asia/Hebron', - AsiaHo_Chi_Minh = 'Asia/Ho_Chi_Minh', - AsiaHong_Kong = 'Asia/Hong_Kong', - AsiaHovd = 'Asia/Hovd', - AsiaIrkutsk = 'Asia/Irkutsk', - AsiaIstanbul = 'Asia/Istanbul', - AsiaJakarta = 'Asia/Jakarta', - AsiaJayapura = 'Asia/Jayapura', - AsiaJerusalem = 'Asia/Jerusalem', - AsiaKabul = 'Asia/Kabul', - AsiaKamchatka = 'Asia/Kamchatka', - AsiaKarachi = 'Asia/Karachi', - AsiaKashgar = 'Asia/Kashgar', - AsiaKathmandu = 'Asia/Kathmandu', - AsiaKatmandu = 'Asia/Katmandu', - AsiaKhandyga = 'Asia/Khandyga', - AsiaKolkata = 'Asia/Kolkata', - AsiaKrasnoyarsk = 'Asia/Krasnoyarsk', - AsiaKuala_Lumpur = 'Asia/Kuala_Lumpur', - AsiaKuching = 'Asia/Kuching', - AsiaKuwait = 'Asia/Kuwait', - AsiaMacao = 'Asia/Macao', - AsiaMacau = 'Asia/Macau', - AsiaMagadan = 'Asia/Magadan', - AsiaMakassar = 'Asia/Makassar', - AsiaManila = 'Asia/Manila', - AsiaMuscat = 'Asia/Muscat', - AsiaNicosia = 'Asia/Nicosia', - AsiaNovokuznetsk = 'Asia/Novokuznetsk', - AsiaNovosibirsk = 'Asia/Novosibirsk', - AsiaOmsk = 'Asia/Omsk', - AsiaOral = 'Asia/Oral', - AsiaPhnom_Penh = 'Asia/Phnom_Penh', - AsiaPontianak = 'Asia/Pontianak', - AsiaPyongyang = 'Asia/Pyongyang', - AsiaQatar = 'Asia/Qatar', - AsiaQyzylorda = 'Asia/Qyzylorda', - AsiaRangoon = 'Asia/Rangoon', - AsiaRiyadh = 'Asia/Riyadh', - AsiaSaigon = 'Asia/Saigon', - AsiaSakhalin = 'Asia/Sakhalin', - AsiaSamarkand = 'Asia/Samarkand', - AsiaSeoul = 'Asia/Seoul', - AsiaShanghai = 'Asia/Shanghai', - AsiaSingapore = 'Asia/Singapore', - AsiaSrednekolymsk = 'Asia/Srednekolymsk', - AsiaTaipei = 'Asia/Taipei', - AsiaTashkent = 'Asia/Tashkent', - AsiaTbilisi = 'Asia/Tbilisi', - AsiaTehran = 'Asia/Tehran', - AsiaTel_Aviv = 'Asia/Tel_Aviv', - AsiaThimbu = 'Asia/Thimbu', - AsiaThimphu = 'Asia/Thimphu', - AsiaTokyo = 'Asia/Tokyo', - AsiaTomsk = 'Asia/Tomsk', - AsiaUjung_Pandang = 'Asia/Ujung_Pandang', - AsiaUlaanbaatar = 'Asia/Ulaanbaatar', - AsiaUlan_Bator = 'Asia/Ulan_Bator', - AsiaUrumqi = 'Asia/Urumqi', - AsiaUstNera = 'Asia/Ust-Nera', - AsiaVientiane = 'Asia/Vientiane', - AsiaVladivostok = 'Asia/Vladivostok', - AsiaYakutsk = 'Asia/Yakutsk', - AsiaYangon = 'Asia/Yangon', - AsiaYekaterinburg = 'Asia/Yekaterinburg', - AsiaYerevan = 'Asia/Yerevan', - AtlanticAzores = 'Atlantic/Azores', - AtlanticBermuda = 'Atlantic/Bermuda', - AtlanticCanary = 'Atlantic/Canary', - AtlanticCape_Verde = 'Atlantic/Cape_Verde', - AtlanticFaeroe = 'Atlantic/Faeroe', - AtlanticFaroe = 'Atlantic/Faroe', - AtlanticJan_Mayen = 'Atlantic/Jan_Mayen', - AtlanticMadeira = 'Atlantic/Madeira', - AtlanticReykjavik = 'Atlantic/Reykjavik', - AtlanticSouth_Georgia = 'Atlantic/South_Georgia', - AtlanticSt_Helena = 'Atlantic/St_Helena', - AtlanticStanley = 'Atlantic/Stanley', - AustraliaACT = 'Australia/ACT', - AustraliaAdelaide = 'Australia/Adelaide', - AustraliaBrisbane = 'Australia/Brisbane', - AustraliaBroken_Hill = 'Australia/Broken_Hill', - AustraliaCanberra = 'Australia/Canberra', - AustraliaCurrie = 'Australia/Currie', - AustraliaDarwin = 'Australia/Darwin', - AustraliaEucla = 'Australia/Eucla', - AustraliaHobart = 'Australia/Hobart', - AustraliaLHI = 'Australia/LHI', - AustraliaLindeman = 'Australia/Lindeman', - AustraliaLord_Howe = 'Australia/Lord_Howe', - AustraliaMelbourne = 'Australia/Melbourne', - AustraliaNSW = 'Australia/NSW', - AustraliaNorth = 'Australia/North', - AustraliaPerth = 'Australia/Perth', - AustraliaQueensland = 'Australia/Queensland', - AustraliaSouth = 'Australia/South', - AustraliaSydney = 'Australia/Sydney', - AustraliaTasmania = 'Australia/Tasmania', - AustraliaVictoria = 'Australia/Victoria', - AustraliaWest = 'Australia/West', - AustraliaYancowinna = 'Australia/Yancowinna', - BrazilAcre = 'Brazil/Acre', - BrazilDeNoronha = 'Brazil/DeNoronha', - BrazilEast = 'Brazil/East', - BrazilWest = 'Brazil/West', - CET = 'CET', - CST6CDT = 'CST6CDT', - CanadaAtlantic = 'Canada/Atlantic', - CanadaCentral = 'Canada/Central', - CanadaEastern = 'Canada/Eastern', - CanadaMountain = 'Canada/Mountain', - CanadaNewfoundland = 'Canada/Newfoundland', - CanadaPacific = 'Canada/Pacific', - CanadaSaskatchewan = 'Canada/Saskatchewan', - CanadaYukon = 'Canada/Yukon', - ChileContinental = 'Chile/Continental', - ChileEasterIsland = 'Chile/EasterIsland', - Cuba = 'Cuba', - EET = 'EET', - EST = 'EST', - EST5EDT = 'EST5EDT', - Egypt = 'Egypt', - Eire = 'Eire', - EtcGMT = 'Etc/GMT', - EtcGMTPositive0 = 'Etc/GMT+0', - EtcGMTPositive1 = 'Etc/GMT+1', - EtcGMTPositive10 = 'Etc/GMT+10', - EtcGMTPositive11 = 'Etc/GMT+11', - EtcGMTPositive12 = 'Etc/GMT+12', - EtcGMTPositive2 = 'Etc/GMT+2', - EtcGMTPositive3 = 'Etc/GMT+3', - EtcGMTPositive4 = 'Etc/GMT+4', - EtcGMTPositive5 = 'Etc/GMT+5', - EtcGMTPositive6 = 'Etc/GMT+6', - EtcGMTPositive7 = 'Etc/GMT+7', - EtcGMTPositive8 = 'Etc/GMT+8', - EtcGMTPositive9 = 'Etc/GMT+9', - EtcGMTNegative0 = 'Etc/GMT-0', - EtcGMTNegative1 = 'Etc/GMT-1', - EtcGMTNegative10 = 'Etc/GMT-10', - EtcGMTNegative11 = 'Etc/GMT-11', - EtcGMTNegative12 = 'Etc/GMT-12', - EtcGMTNegative13 = 'Etc/GMT-13', - EtcGMTNegative14 = 'Etc/GMT-14', - EtcGMTNegative2 = 'Etc/GMT-2', - EtcGMTNegative3 = 'Etc/GMT-3', - EtcGMTNegative4 = 'Etc/GMT-4', - EtcGMTNegative5 = 'Etc/GMT-5', - EtcGMTNegative6 = 'Etc/GMT-6', - EtcGMTNegative7 = 'Etc/GMT-7', - EtcGMTNegative8 = 'Etc/GMT-8', - EtcGMTNegative9 = 'Etc/GMT-9', - EtcGMT0 = 'Etc/GMT0', - EtcGreenwich = 'Etc/Greenwich', - EtcUCT = 'Etc/UCT', - EtcUTC = 'Etc/UTC', - EtcUniversal = 'Etc/Universal', - EtcZulu = 'Etc/Zulu', - EuropeAmsterdam = 'Europe/Amsterdam', - EuropeAndorra = 'Europe/Andorra', - EuropeAstrakhan = 'Europe/Astrakhan', - EuropeAthens = 'Europe/Athens', - EuropeBelfast = 'Europe/Belfast', - EuropeBelgrade = 'Europe/Belgrade', - EuropeBerlin = 'Europe/Berlin', - EuropeBratislava = 'Europe/Bratislava', - EuropeBrussels = 'Europe/Brussels', - EuropeBucharest = 'Europe/Bucharest', - EuropeBudapest = 'Europe/Budapest', - EuropeBusingen = 'Europe/Busingen', - EuropeChisinau = 'Europe/Chisinau', - EuropeCopenhagen = 'Europe/Copenhagen', - EuropeDublin = 'Europe/Dublin', - EuropeGibraltar = 'Europe/Gibraltar', - EuropeGuernsey = 'Europe/Guernsey', - EuropeHelsinki = 'Europe/Helsinki', - EuropeIsle_of_Man = 'Europe/Isle_of_Man', - EuropeIstanbul = 'Europe/Istanbul', - EuropeJersey = 'Europe/Jersey', - EuropeKaliningrad = 'Europe/Kaliningrad', - EuropeKiev = 'Europe/Kiev', - EuropeKirov = 'Europe/Kirov', - EuropeLisbon = 'Europe/Lisbon', - EuropeLjubljana = 'Europe/Ljubljana', - EuropeLondon = 'Europe/London', - EuropeLuxembourg = 'Europe/Luxembourg', - EuropeMadrid = 'Europe/Madrid', - EuropeMalta = 'Europe/Malta', - EuropeMariehamn = 'Europe/Mariehamn', - EuropeMinsk = 'Europe/Minsk', - EuropeMonaco = 'Europe/Monaco', - EuropeMoscow = 'Europe/Moscow', - EuropeNicosia = 'Europe/Nicosia', - EuropeOslo = 'Europe/Oslo', - EuropeParis = 'Europe/Paris', - EuropePodgorica = 'Europe/Podgorica', - EuropePrague = 'Europe/Prague', - EuropeRiga = 'Europe/Riga', - EuropeRome = 'Europe/Rome', - EuropeSamara = 'Europe/Samara', - EuropeSan_Marino = 'Europe/San_Marino', - EuropeSarajevo = 'Europe/Sarajevo', - EuropeSaratov = 'Europe/Saratov', - EuropeSimferopol = 'Europe/Simferopol', - EuropeSkopje = 'Europe/Skopje', - EuropeSofia = 'Europe/Sofia', - EuropeStockholm = 'Europe/Stockholm', - EuropeTallinn = 'Europe/Tallinn', - EuropeTirane = 'Europe/Tirane', - EuropeTiraspol = 'Europe/Tiraspol', - EuropeUlyanovsk = 'Europe/Ulyanovsk', - EuropeUzhgorod = 'Europe/Uzhgorod', - EuropeVaduz = 'Europe/Vaduz', - EuropeVatican = 'Europe/Vatican', - EuropeVienna = 'Europe/Vienna', - EuropeVilnius = 'Europe/Vilnius', - EuropeVolgograd = 'Europe/Volgograd', - EuropeWarsaw = 'Europe/Warsaw', - EuropeZagreb = 'Europe/Zagreb', - EuropeZaporozhye = 'Europe/Zaporozhye', - EuropeZurich = 'Europe/Zurich', - GB = 'GB', - GBEire = 'GB-Eire', - GMT = 'GMT', - GMTPositive0 = 'GMT+0', - GMTNegative0 = 'GMT-0', - GMT0 = 'GMT0', - Greenwich = 'Greenwich', - HST = 'HST', - Hongkong = 'Hongkong', - Iceland = 'Iceland', - IndianAntananarivo = 'Indian/Antananarivo', - IndianChagos = 'Indian/Chagos', - IndianChristmas = 'Indian/Christmas', - IndianCocos = 'Indian/Cocos', - IndianComoro = 'Indian/Comoro', - IndianKerguelen = 'Indian/Kerguelen', - IndianMahe = 'Indian/Mahe', - IndianMaldives = 'Indian/Maldives', - IndianMauritius = 'Indian/Mauritius', - IndianMayotte = 'Indian/Mayotte', - IndianReunion = 'Indian/Reunion', - Iran = 'Iran', - Israel = 'Israel', - Jamaica = 'Jamaica', - Japan = 'Japan', - Kwajalein = 'Kwajalein', - Libya = 'Libya', - MET = 'MET', - MST = 'MST', - MST7MDT = 'MST7MDT', - MexicoBajaNorte = 'Mexico/BajaNorte', - MexicoBajaSur = 'Mexico/BajaSur', - MexicoGeneral = 'Mexico/General', - NZ = 'NZ', - NZCHAT = 'NZ-CHAT', - Navajo = 'Navajo', - PRC = 'PRC', - PST8PDT = 'PST8PDT', - PacificApia = 'Pacific/Apia', - PacificAuckland = 'Pacific/Auckland', - PacificBougainville = 'Pacific/Bougainville', - PacificChatham = 'Pacific/Chatham', - PacificChuuk = 'Pacific/Chuuk', - PacificEaster = 'Pacific/Easter', - PacificEfate = 'Pacific/Efate', - PacificEnderbury = 'Pacific/Enderbury', - PacificFakaofo = 'Pacific/Fakaofo', - PacificFiji = 'Pacific/Fiji', - PacificFunafuti = 'Pacific/Funafuti', - PacificGalapagos = 'Pacific/Galapagos', - PacificGambier = 'Pacific/Gambier', - PacificGuadalcanal = 'Pacific/Guadalcanal', - PacificGuam = 'Pacific/Guam', - PacificHonolulu = 'Pacific/Honolulu', - PacificJohnston = 'Pacific/Johnston', - PacificKiritimati = 'Pacific/Kiritimati', - PacificKosrae = 'Pacific/Kosrae', - PacificKwajalein = 'Pacific/Kwajalein', - PacificMajuro = 'Pacific/Majuro', - PacificMarquesas = 'Pacific/Marquesas', - PacificMidway = 'Pacific/Midway', - PacificNauru = 'Pacific/Nauru', - PacificNiue = 'Pacific/Niue', - PacificNorfolk = 'Pacific/Norfolk', - PacificNoumea = 'Pacific/Noumea', - PacificPago_Pago = 'Pacific/Pago_Pago', - PacificPalau = 'Pacific/Palau', - PacificPitcairn = 'Pacific/Pitcairn', - PacificPohnpei = 'Pacific/Pohnpei', - PacificPonape = 'Pacific/Ponape', - PacificPort_Moresby = 'Pacific/Port_Moresby', - PacificRarotonga = 'Pacific/Rarotonga', - PacificSaipan = 'Pacific/Saipan', - PacificSamoa = 'Pacific/Samoa', - PacificTahiti = 'Pacific/Tahiti', - PacificTarawa = 'Pacific/Tarawa', - PacificTongatapu = 'Pacific/Tongatapu', - PacificTruk = 'Pacific/Truk', - PacificWake = 'Pacific/Wake', - PacificWallis = 'Pacific/Wallis', - PacificYap = 'Pacific/Yap', - Poland = 'Poland', - Portugal = 'Portugal', - ROC = 'ROC', - ROK = 'ROK', - Singapore = 'Singapore', - Turkey = 'Turkey', - UCT = 'UCT', - USAlaska = 'US/Alaska', - USAleutian = 'US/Aleutian', - USArizona = 'US/Arizona', - USCentral = 'US/Central', - USEastIndiana = 'US/East-Indiana', - USEastern = 'US/Eastern', - USHawaii = 'US/Hawaii', - USIndianaStarke = 'US/Indiana-Starke', - USMichigan = 'US/Michigan', - USMountain = 'US/Mountain', - USPacific = 'US/Pacific', - USPacificNew = 'US/Pacific-New', - USSamoa = 'US/Samoa', - UTC = 'UTC', - Universal = 'Universal', - WSU = 'W-SU', - WET = 'WET', - Zulu = 'Zulu', + AfricaAbidjan = "Africa/Abidjan", + AfricaAccra = "Africa/Accra", + AfricaAddis_Ababa = "Africa/Addis_Ababa", + AfricaAlgiers = "Africa/Algiers", + AfricaAsmara = "Africa/Asmara", + AfricaAsmera = "Africa/Asmera", + AfricaBamako = "Africa/Bamako", + AfricaBangui = "Africa/Bangui", + AfricaBanjul = "Africa/Banjul", + AfricaBissau = "Africa/Bissau", + AfricaBlantyre = "Africa/Blantyre", + AfricaBrazzaville = "Africa/Brazzaville", + AfricaBujumbura = "Africa/Bujumbura", + AfricaCairo = "Africa/Cairo", + AfricaCasablanca = "Africa/Casablanca", + AfricaCeuta = "Africa/Ceuta", + AfricaConakry = "Africa/Conakry", + AfricaDakar = "Africa/Dakar", + AfricaDar_es_Salaam = "Africa/Dar_es_Salaam", + AfricaDjibouti = "Africa/Djibouti", + AfricaDouala = "Africa/Douala", + AfricaEl_Aaiun = "Africa/El_Aaiun", + AfricaFreetown = "Africa/Freetown", + AfricaGaborone = "Africa/Gaborone", + AfricaHarare = "Africa/Harare", + AfricaJohannesburg = "Africa/Johannesburg", + AfricaJuba = "Africa/Juba", + AfricaKampala = "Africa/Kampala", + AfricaKhartoum = "Africa/Khartoum", + AfricaKigali = "Africa/Kigali", + AfricaKinshasa = "Africa/Kinshasa", + AfricaLagos = "Africa/Lagos", + AfricaLibreville = "Africa/Libreville", + AfricaLome = "Africa/Lome", + AfricaLuanda = "Africa/Luanda", + AfricaLubumbashi = "Africa/Lubumbashi", + AfricaLusaka = "Africa/Lusaka", + AfricaMalabo = "Africa/Malabo", + AfricaMaputo = "Africa/Maputo", + AfricaMaseru = "Africa/Maseru", + AfricaMbabane = "Africa/Mbabane", + AfricaMogadishu = "Africa/Mogadishu", + AfricaMonrovia = "Africa/Monrovia", + AfricaNairobi = "Africa/Nairobi", + AfricaNdjamena = "Africa/Ndjamena", + AfricaNiamey = "Africa/Niamey", + AfricaNouakchott = "Africa/Nouakchott", + AfricaOuagadougou = "Africa/Ouagadougou", + AfricaPortoNovo = "Africa/Porto-Novo", + AfricaSao_Tome = "Africa/Sao_Tome", + AfricaTimbuktu = "Africa/Timbuktu", + AfricaTripoli = "Africa/Tripoli", + AfricaTunis = "Africa/Tunis", + AfricaWindhoek = "Africa/Windhoek", + AmericaAdak = "America/Adak", + AmericaAnchorage = "America/Anchorage", + AmericaAnguilla = "America/Anguilla", + AmericaAntigua = "America/Antigua", + AmericaAraguaina = "America/Araguaina", + AmericaArgentinaBuenos_Aires = "America/Argentina/Buenos_Aires", + AmericaArgentinaCatamarca = "America/Argentina/Catamarca", + AmericaArgentinaComodRivadavia = "America/Argentina/ComodRivadavia", + AmericaArgentinaCordoba = "America/Argentina/Cordoba", + AmericaArgentinaJujuy = "America/Argentina/Jujuy", + AmericaArgentinaLa_Rioja = "America/Argentina/La_Rioja", + AmericaArgentinaMendoza = "America/Argentina/Mendoza", + AmericaArgentinaRio_Gallegos = "America/Argentina/Rio_Gallegos", + AmericaArgentinaSalta = "America/Argentina/Salta", + AmericaArgentinaSan_Juan = "America/Argentina/San_Juan", + AmericaArgentinaSan_Luis = "America/Argentina/San_Luis", + AmericaArgentinaTucuman = "America/Argentina/Tucuman", + AmericaArgentinaUshuaia = "America/Argentina/Ushuaia", + AmericaAruba = "America/Aruba", + AmericaAsuncion = "America/Asuncion", + AmericaAtikokan = "America/Atikokan", + AmericaAtka = "America/Atka", + AmericaBahia = "America/Bahia", + AmericaBahia_Banderas = "America/Bahia_Banderas", + AmericaBarbados = "America/Barbados", + AmericaBelem = "America/Belem", + AmericaBelize = "America/Belize", + AmericaBlancSablon = "America/Blanc-Sablon", + AmericaBoa_Vista = "America/Boa_Vista", + AmericaBogota = "America/Bogota", + AmericaBoise = "America/Boise", + AmericaBuenos_Aires = "America/Buenos_Aires", + AmericaCambridge_Bay = "America/Cambridge_Bay", + AmericaCampo_Grande = "America/Campo_Grande", + AmericaCancun = "America/Cancun", + AmericaCaracas = "America/Caracas", + AmericaCatamarca = "America/Catamarca", + AmericaCayenne = "America/Cayenne", + AmericaCayman = "America/Cayman", + AmericaChicago = "America/Chicago", + AmericaChihuahua = "America/Chihuahua", + AmericaCoral_Harbour = "America/Coral_Harbour", + AmericaCordoba = "America/Cordoba", + AmericaCosta_Rica = "America/Costa_Rica", + AmericaCreston = "America/Creston", + AmericaCuiaba = "America/Cuiaba", + AmericaCuracao = "America/Curacao", + AmericaDanmarkshavn = "America/Danmarkshavn", + AmericaDawson = "America/Dawson", + AmericaDawson_Creek = "America/Dawson_Creek", + AmericaDenver = "America/Denver", + AmericaDetroit = "America/Detroit", + AmericaDominica = "America/Dominica", + AmericaEdmonton = "America/Edmonton", + AmericaEirunepe = "America/Eirunepe", + AmericaEl_Salvador = "America/El_Salvador", + AmericaEnsenada = "America/Ensenada", + AmericaFort_Nelson = "America/Fort_Nelson", + AmericaFort_Wayne = "America/Fort_Wayne", + AmericaFortaleza = "America/Fortaleza", + AmericaGlace_Bay = "America/Glace_Bay", + AmericaGodthab = "America/Godthab", + AmericaGoose_Bay = "America/Goose_Bay", + AmericaGrand_Turk = "America/Grand_Turk", + AmericaGrenada = "America/Grenada", + AmericaGuadeloupe = "America/Guadeloupe", + AmericaGuatemala = "America/Guatemala", + AmericaGuayaquil = "America/Guayaquil", + AmericaGuyana = "America/Guyana", + AmericaHalifax = "America/Halifax", + AmericaHavana = "America/Havana", + AmericaHermosillo = "America/Hermosillo", + AmericaIndianaIndianapolis = "America/Indiana/Indianapolis", + AmericaIndianaKnox = "America/Indiana/Knox", + AmericaIndianaMarengo = "America/Indiana/Marengo", + AmericaIndianaPetersburg = "America/Indiana/Petersburg", + AmericaIndianaTell_City = "America/Indiana/Tell_City", + AmericaIndianaVevay = "America/Indiana/Vevay", + AmericaIndianaVincennes = "America/Indiana/Vincennes", + AmericaIndianaWinamac = "America/Indiana/Winamac", + AmericaIndianapolis = "America/Indianapolis", + AmericaInuvik = "America/Inuvik", + AmericaIqaluit = "America/Iqaluit", + AmericaJamaica = "America/Jamaica", + AmericaJujuy = "America/Jujuy", + AmericaJuneau = "America/Juneau", + AmericaKentuckyLouisville = "America/Kentucky/Louisville", + AmericaKentuckyMonticello = "America/Kentucky/Monticello", + AmericaKnox_IN = "America/Knox_IN", + AmericaKralendijk = "America/Kralendijk", + AmericaLa_Paz = "America/La_Paz", + AmericaLima = "America/Lima", + AmericaLos_Angeles = "America/Los_Angeles", + AmericaLouisville = "America/Louisville", + AmericaLower_Princes = "America/Lower_Princes", + AmericaMaceio = "America/Maceio", + AmericaManagua = "America/Managua", + AmericaManaus = "America/Manaus", + AmericaMarigot = "America/Marigot", + AmericaMartinique = "America/Martinique", + AmericaMatamoros = "America/Matamoros", + AmericaMazatlan = "America/Mazatlan", + AmericaMendoza = "America/Mendoza", + AmericaMenominee = "America/Menominee", + AmericaMerida = "America/Merida", + AmericaMetlakatla = "America/Metlakatla", + AmericaMexico_City = "America/Mexico_City", + AmericaMiquelon = "America/Miquelon", + AmericaMoncton = "America/Moncton", + AmericaMonterrey = "America/Monterrey", + AmericaMontevideo = "America/Montevideo", + AmericaMontreal = "America/Montreal", + AmericaMontserrat = "America/Montserrat", + AmericaNassau = "America/Nassau", + AmericaNew_York = "America/New_York", + AmericaNipigon = "America/Nipigon", + AmericaNome = "America/Nome", + AmericaNoronha = "America/Noronha", + AmericaNorth_DakotaBeulah = "America/North_Dakota/Beulah", + AmericaNorth_DakotaCenter = "America/North_Dakota/Center", + AmericaNorth_DakotaNew_Salem = "America/North_Dakota/New_Salem", + AmericaOjinaga = "America/Ojinaga", + AmericaPanama = "America/Panama", + AmericaPangnirtung = "America/Pangnirtung", + AmericaParamaribo = "America/Paramaribo", + AmericaPhoenix = "America/Phoenix", + AmericaPortAuPrince = "America/Port-au-Prince", + AmericaPort_of_Spain = "America/Port_of_Spain", + AmericaPorto_Acre = "America/Porto_Acre", + AmericaPorto_Velho = "America/Porto_Velho", + AmericaPuerto_Rico = "America/Puerto_Rico", + AmericaPunta_Arenas = "America/Punta_Arenas", + AmericaRainy_River = "America/Rainy_River", + AmericaRankin_Inlet = "America/Rankin_Inlet", + AmericaRecife = "America/Recife", + AmericaRegina = "America/Regina", + AmericaResolute = "America/Resolute", + AmericaRio_Branco = "America/Rio_Branco", + AmericaRosario = "America/Rosario", + AmericaSanta_Isabel = "America/Santa_Isabel", + AmericaSantarem = "America/Santarem", + AmericaSantiago = "America/Santiago", + AmericaSanto_Domingo = "America/Santo_Domingo", + AmericaSao_Paulo = "America/Sao_Paulo", + AmericaScoresbysund = "America/Scoresbysund", + AmericaShiprock = "America/Shiprock", + AmericaSitka = "America/Sitka", + AmericaSt_Barthelemy = "America/St_Barthelemy", + AmericaSt_Johns = "America/St_Johns", + AmericaSt_Kitts = "America/St_Kitts", + AmericaSt_Lucia = "America/St_Lucia", + AmericaSt_Thomas = "America/St_Thomas", + AmericaSt_Vincent = "America/St_Vincent", + AmericaSwift_Current = "America/Swift_Current", + AmericaTegucigalpa = "America/Tegucigalpa", + AmericaThule = "America/Thule", + AmericaThunder_Bay = "America/Thunder_Bay", + AmericaTijuana = "America/Tijuana", + AmericaToronto = "America/Toronto", + AmericaTortola = "America/Tortola", + AmericaVancouver = "America/Vancouver", + AmericaVirgin = "America/Virgin", + AmericaWhitehorse = "America/Whitehorse", + AmericaWinnipeg = "America/Winnipeg", + AmericaYakutat = "America/Yakutat", + AmericaYellowknife = "America/Yellowknife", + AntarcticaCasey = "Antarctica/Casey", + AntarcticaDavis = "Antarctica/Davis", + AntarcticaDumontDUrville = "Antarctica/DumontDUrville", + AntarcticaMacquarie = "Antarctica/Macquarie", + AntarcticaMawson = "Antarctica/Mawson", + AntarcticaMcMurdo = "Antarctica/McMurdo", + AntarcticaPalmer = "Antarctica/Palmer", + AntarcticaRothera = "Antarctica/Rothera", + AntarcticaSouth_Pole = "Antarctica/South_Pole", + AntarcticaSyowa = "Antarctica/Syowa", + AntarcticaTroll = "Antarctica/Troll", + AntarcticaVostok = "Antarctica/Vostok", + ArcticLongyearbyen = "Arctic/Longyearbyen", + AsiaAden = "Asia/Aden", + AsiaAlmaty = "Asia/Almaty", + AsiaAmman = "Asia/Amman", + AsiaAnadyr = "Asia/Anadyr", + AsiaAqtau = "Asia/Aqtau", + AsiaAqtobe = "Asia/Aqtobe", + AsiaAshgabat = "Asia/Ashgabat", + AsiaAshkhabad = "Asia/Ashkhabad", + AsiaAtyrau = "Asia/Atyrau", + AsiaBaghdad = "Asia/Baghdad", + AsiaBahrain = "Asia/Bahrain", + AsiaBaku = "Asia/Baku", + AsiaBangkok = "Asia/Bangkok", + AsiaBarnaul = "Asia/Barnaul", + AsiaBeirut = "Asia/Beirut", + AsiaBishkek = "Asia/Bishkek", + AsiaBrunei = "Asia/Brunei", + AsiaCalcutta = "Asia/Calcutta", + AsiaChita = "Asia/Chita", + AsiaChoibalsan = "Asia/Choibalsan", + AsiaChongqing = "Asia/Chongqing", + AsiaChungking = "Asia/Chungking", + AsiaColombo = "Asia/Colombo", + AsiaDacca = "Asia/Dacca", + AsiaDamascus = "Asia/Damascus", + AsiaDhaka = "Asia/Dhaka", + AsiaDili = "Asia/Dili", + AsiaDubai = "Asia/Dubai", + AsiaDushanbe = "Asia/Dushanbe", + AsiaFamagusta = "Asia/Famagusta", + AsiaGaza = "Asia/Gaza", + AsiaHarbin = "Asia/Harbin", + AsiaHebron = "Asia/Hebron", + AsiaHo_Chi_Minh = "Asia/Ho_Chi_Minh", + AsiaHong_Kong = "Asia/Hong_Kong", + AsiaHovd = "Asia/Hovd", + AsiaIrkutsk = "Asia/Irkutsk", + AsiaIstanbul = "Asia/Istanbul", + AsiaJakarta = "Asia/Jakarta", + AsiaJayapura = "Asia/Jayapura", + AsiaJerusalem = "Asia/Jerusalem", + AsiaKabul = "Asia/Kabul", + AsiaKamchatka = "Asia/Kamchatka", + AsiaKarachi = "Asia/Karachi", + AsiaKashgar = "Asia/Kashgar", + AsiaKathmandu = "Asia/Kathmandu", + AsiaKatmandu = "Asia/Katmandu", + AsiaKhandyga = "Asia/Khandyga", + AsiaKolkata = "Asia/Kolkata", + AsiaKrasnoyarsk = "Asia/Krasnoyarsk", + AsiaKuala_Lumpur = "Asia/Kuala_Lumpur", + AsiaKuching = "Asia/Kuching", + AsiaKuwait = "Asia/Kuwait", + AsiaMacao = "Asia/Macao", + AsiaMacau = "Asia/Macau", + AsiaMagadan = "Asia/Magadan", + AsiaMakassar = "Asia/Makassar", + AsiaManila = "Asia/Manila", + AsiaMuscat = "Asia/Muscat", + AsiaNicosia = "Asia/Nicosia", + AsiaNovokuznetsk = "Asia/Novokuznetsk", + AsiaNovosibirsk = "Asia/Novosibirsk", + AsiaOmsk = "Asia/Omsk", + AsiaOral = "Asia/Oral", + AsiaPhnom_Penh = "Asia/Phnom_Penh", + AsiaPontianak = "Asia/Pontianak", + AsiaPyongyang = "Asia/Pyongyang", + AsiaQatar = "Asia/Qatar", + AsiaQyzylorda = "Asia/Qyzylorda", + AsiaRangoon = "Asia/Rangoon", + AsiaRiyadh = "Asia/Riyadh", + AsiaSaigon = "Asia/Saigon", + AsiaSakhalin = "Asia/Sakhalin", + AsiaSamarkand = "Asia/Samarkand", + AsiaSeoul = "Asia/Seoul", + AsiaShanghai = "Asia/Shanghai", + AsiaSingapore = "Asia/Singapore", + AsiaSrednekolymsk = "Asia/Srednekolymsk", + AsiaTaipei = "Asia/Taipei", + AsiaTashkent = "Asia/Tashkent", + AsiaTbilisi = "Asia/Tbilisi", + AsiaTehran = "Asia/Tehran", + AsiaTel_Aviv = "Asia/Tel_Aviv", + AsiaThimbu = "Asia/Thimbu", + AsiaThimphu = "Asia/Thimphu", + AsiaTokyo = "Asia/Tokyo", + AsiaTomsk = "Asia/Tomsk", + AsiaUjung_Pandang = "Asia/Ujung_Pandang", + AsiaUlaanbaatar = "Asia/Ulaanbaatar", + AsiaUlan_Bator = "Asia/Ulan_Bator", + AsiaUrumqi = "Asia/Urumqi", + AsiaUstNera = "Asia/Ust-Nera", + AsiaVientiane = "Asia/Vientiane", + AsiaVladivostok = "Asia/Vladivostok", + AsiaYakutsk = "Asia/Yakutsk", + AsiaYangon = "Asia/Yangon", + AsiaYekaterinburg = "Asia/Yekaterinburg", + AsiaYerevan = "Asia/Yerevan", + AtlanticAzores = "Atlantic/Azores", + AtlanticBermuda = "Atlantic/Bermuda", + AtlanticCanary = "Atlantic/Canary", + AtlanticCape_Verde = "Atlantic/Cape_Verde", + AtlanticFaeroe = "Atlantic/Faeroe", + AtlanticFaroe = "Atlantic/Faroe", + AtlanticJan_Mayen = "Atlantic/Jan_Mayen", + AtlanticMadeira = "Atlantic/Madeira", + AtlanticReykjavik = "Atlantic/Reykjavik", + AtlanticSouth_Georgia = "Atlantic/South_Georgia", + AtlanticSt_Helena = "Atlantic/St_Helena", + AtlanticStanley = "Atlantic/Stanley", + AustraliaACT = "Australia/ACT", + AustraliaAdelaide = "Australia/Adelaide", + AustraliaBrisbane = "Australia/Brisbane", + AustraliaBroken_Hill = "Australia/Broken_Hill", + AustraliaCanberra = "Australia/Canberra", + AustraliaCurrie = "Australia/Currie", + AustraliaDarwin = "Australia/Darwin", + AustraliaEucla = "Australia/Eucla", + AustraliaHobart = "Australia/Hobart", + AustraliaLHI = "Australia/LHI", + AustraliaLindeman = "Australia/Lindeman", + AustraliaLord_Howe = "Australia/Lord_Howe", + AustraliaMelbourne = "Australia/Melbourne", + AustraliaNSW = "Australia/NSW", + AustraliaNorth = "Australia/North", + AustraliaPerth = "Australia/Perth", + AustraliaQueensland = "Australia/Queensland", + AustraliaSouth = "Australia/South", + AustraliaSydney = "Australia/Sydney", + AustraliaTasmania = "Australia/Tasmania", + AustraliaVictoria = "Australia/Victoria", + AustraliaWest = "Australia/West", + AustraliaYancowinna = "Australia/Yancowinna", + BrazilAcre = "Brazil/Acre", + BrazilDeNoronha = "Brazil/DeNoronha", + BrazilEast = "Brazil/East", + BrazilWest = "Brazil/West", + CET = "CET", + CST6CDT = "CST6CDT", + CanadaAtlantic = "Canada/Atlantic", + CanadaCentral = "Canada/Central", + CanadaEastern = "Canada/Eastern", + CanadaMountain = "Canada/Mountain", + CanadaNewfoundland = "Canada/Newfoundland", + CanadaPacific = "Canada/Pacific", + CanadaSaskatchewan = "Canada/Saskatchewan", + CanadaYukon = "Canada/Yukon", + ChileContinental = "Chile/Continental", + ChileEasterIsland = "Chile/EasterIsland", + Cuba = "Cuba", + EET = "EET", + EST = "EST", + EST5EDT = "EST5EDT", + Egypt = "Egypt", + Eire = "Eire", + EtcGMT = "Etc/GMT", + EtcGMTPositive0 = "Etc/GMT+0", + EtcGMTPositive1 = "Etc/GMT+1", + EtcGMTPositive10 = "Etc/GMT+10", + EtcGMTPositive11 = "Etc/GMT+11", + EtcGMTPositive12 = "Etc/GMT+12", + EtcGMTPositive2 = "Etc/GMT+2", + EtcGMTPositive3 = "Etc/GMT+3", + EtcGMTPositive4 = "Etc/GMT+4", + EtcGMTPositive5 = "Etc/GMT+5", + EtcGMTPositive6 = "Etc/GMT+6", + EtcGMTPositive7 = "Etc/GMT+7", + EtcGMTPositive8 = "Etc/GMT+8", + EtcGMTPositive9 = "Etc/GMT+9", + EtcGMTNegative0 = "Etc/GMT-0", + EtcGMTNegative1 = "Etc/GMT-1", + EtcGMTNegative10 = "Etc/GMT-10", + EtcGMTNegative11 = "Etc/GMT-11", + EtcGMTNegative12 = "Etc/GMT-12", + EtcGMTNegative13 = "Etc/GMT-13", + EtcGMTNegative14 = "Etc/GMT-14", + EtcGMTNegative2 = "Etc/GMT-2", + EtcGMTNegative3 = "Etc/GMT-3", + EtcGMTNegative4 = "Etc/GMT-4", + EtcGMTNegative5 = "Etc/GMT-5", + EtcGMTNegative6 = "Etc/GMT-6", + EtcGMTNegative7 = "Etc/GMT-7", + EtcGMTNegative8 = "Etc/GMT-8", + EtcGMTNegative9 = "Etc/GMT-9", + EtcGMT0 = "Etc/GMT0", + EtcGreenwich = "Etc/Greenwich", + EtcUCT = "Etc/UCT", + EtcUTC = "Etc/UTC", + EtcUniversal = "Etc/Universal", + EtcZulu = "Etc/Zulu", + EuropeAmsterdam = "Europe/Amsterdam", + EuropeAndorra = "Europe/Andorra", + EuropeAstrakhan = "Europe/Astrakhan", + EuropeAthens = "Europe/Athens", + EuropeBelfast = "Europe/Belfast", + EuropeBelgrade = "Europe/Belgrade", + EuropeBerlin = "Europe/Berlin", + EuropeBratislava = "Europe/Bratislava", + EuropeBrussels = "Europe/Brussels", + EuropeBucharest = "Europe/Bucharest", + EuropeBudapest = "Europe/Budapest", + EuropeBusingen = "Europe/Busingen", + EuropeChisinau = "Europe/Chisinau", + EuropeCopenhagen = "Europe/Copenhagen", + EuropeDublin = "Europe/Dublin", + EuropeGibraltar = "Europe/Gibraltar", + EuropeGuernsey = "Europe/Guernsey", + EuropeHelsinki = "Europe/Helsinki", + EuropeIsle_of_Man = "Europe/Isle_of_Man", + EuropeIstanbul = "Europe/Istanbul", + EuropeJersey = "Europe/Jersey", + EuropeKaliningrad = "Europe/Kaliningrad", + EuropeKiev = "Europe/Kiev", + EuropeKirov = "Europe/Kirov", + EuropeLisbon = "Europe/Lisbon", + EuropeLjubljana = "Europe/Ljubljana", + EuropeLondon = "Europe/London", + EuropeLuxembourg = "Europe/Luxembourg", + EuropeMadrid = "Europe/Madrid", + EuropeMalta = "Europe/Malta", + EuropeMariehamn = "Europe/Mariehamn", + EuropeMinsk = "Europe/Minsk", + EuropeMonaco = "Europe/Monaco", + EuropeMoscow = "Europe/Moscow", + EuropeNicosia = "Europe/Nicosia", + EuropeOslo = "Europe/Oslo", + EuropeParis = "Europe/Paris", + EuropePodgorica = "Europe/Podgorica", + EuropePrague = "Europe/Prague", + EuropeRiga = "Europe/Riga", + EuropeRome = "Europe/Rome", + EuropeSamara = "Europe/Samara", + EuropeSan_Marino = "Europe/San_Marino", + EuropeSarajevo = "Europe/Sarajevo", + EuropeSaratov = "Europe/Saratov", + EuropeSimferopol = "Europe/Simferopol", + EuropeSkopje = "Europe/Skopje", + EuropeSofia = "Europe/Sofia", + EuropeStockholm = "Europe/Stockholm", + EuropeTallinn = "Europe/Tallinn", + EuropeTirane = "Europe/Tirane", + EuropeTiraspol = "Europe/Tiraspol", + EuropeUlyanovsk = "Europe/Ulyanovsk", + EuropeUzhgorod = "Europe/Uzhgorod", + EuropeVaduz = "Europe/Vaduz", + EuropeVatican = "Europe/Vatican", + EuropeVienna = "Europe/Vienna", + EuropeVilnius = "Europe/Vilnius", + EuropeVolgograd = "Europe/Volgograd", + EuropeWarsaw = "Europe/Warsaw", + EuropeZagreb = "Europe/Zagreb", + EuropeZaporozhye = "Europe/Zaporozhye", + EuropeZurich = "Europe/Zurich", + GB = "GB", + GBEire = "GB-Eire", + GMT = "GMT", + GMTPositive0 = "GMT+0", + GMTNegative0 = "GMT-0", + GMT0 = "GMT0", + Greenwich = "Greenwich", + HST = "HST", + Hongkong = "Hongkong", + Iceland = "Iceland", + IndianAntananarivo = "Indian/Antananarivo", + IndianChagos = "Indian/Chagos", + IndianChristmas = "Indian/Christmas", + IndianCocos = "Indian/Cocos", + IndianComoro = "Indian/Comoro", + IndianKerguelen = "Indian/Kerguelen", + IndianMahe = "Indian/Mahe", + IndianMaldives = "Indian/Maldives", + IndianMauritius = "Indian/Mauritius", + IndianMayotte = "Indian/Mayotte", + IndianReunion = "Indian/Reunion", + Iran = "Iran", + Israel = "Israel", + Jamaica = "Jamaica", + Japan = "Japan", + Kwajalein = "Kwajalein", + Libya = "Libya", + MET = "MET", + MST = "MST", + MST7MDT = "MST7MDT", + MexicoBajaNorte = "Mexico/BajaNorte", + MexicoBajaSur = "Mexico/BajaSur", + MexicoGeneral = "Mexico/General", + NZ = "NZ", + NZCHAT = "NZ-CHAT", + Navajo = "Navajo", + PRC = "PRC", + PST8PDT = "PST8PDT", + PacificApia = "Pacific/Apia", + PacificAuckland = "Pacific/Auckland", + PacificBougainville = "Pacific/Bougainville", + PacificChatham = "Pacific/Chatham", + PacificChuuk = "Pacific/Chuuk", + PacificEaster = "Pacific/Easter", + PacificEfate = "Pacific/Efate", + PacificEnderbury = "Pacific/Enderbury", + PacificFakaofo = "Pacific/Fakaofo", + PacificFiji = "Pacific/Fiji", + PacificFunafuti = "Pacific/Funafuti", + PacificGalapagos = "Pacific/Galapagos", + PacificGambier = "Pacific/Gambier", + PacificGuadalcanal = "Pacific/Guadalcanal", + PacificGuam = "Pacific/Guam", + PacificHonolulu = "Pacific/Honolulu", + PacificJohnston = "Pacific/Johnston", + PacificKiritimati = "Pacific/Kiritimati", + PacificKosrae = "Pacific/Kosrae", + PacificKwajalein = "Pacific/Kwajalein", + PacificMajuro = "Pacific/Majuro", + PacificMarquesas = "Pacific/Marquesas", + PacificMidway = "Pacific/Midway", + PacificNauru = "Pacific/Nauru", + PacificNiue = "Pacific/Niue", + PacificNorfolk = "Pacific/Norfolk", + PacificNoumea = "Pacific/Noumea", + PacificPago_Pago = "Pacific/Pago_Pago", + PacificPalau = "Pacific/Palau", + PacificPitcairn = "Pacific/Pitcairn", + PacificPohnpei = "Pacific/Pohnpei", + PacificPonape = "Pacific/Ponape", + PacificPort_Moresby = "Pacific/Port_Moresby", + PacificRarotonga = "Pacific/Rarotonga", + PacificSaipan = "Pacific/Saipan", + PacificSamoa = "Pacific/Samoa", + PacificTahiti = "Pacific/Tahiti", + PacificTarawa = "Pacific/Tarawa", + PacificTongatapu = "Pacific/Tongatapu", + PacificTruk = "Pacific/Truk", + PacificWake = "Pacific/Wake", + PacificWallis = "Pacific/Wallis", + PacificYap = "Pacific/Yap", + Poland = "Poland", + Portugal = "Portugal", + ROC = "ROC", + ROK = "ROK", + Singapore = "Singapore", + Turkey = "Turkey", + UCT = "UCT", + USAlaska = "US/Alaska", + USAleutian = "US/Aleutian", + USArizona = "US/Arizona", + USCentral = "US/Central", + USEastIndiana = "US/East-Indiana", + USEastern = "US/Eastern", + USHawaii = "US/Hawaii", + USIndianaStarke = "US/Indiana-Starke", + USMichigan = "US/Michigan", + USMountain = "US/Mountain", + USPacific = "US/Pacific", + USPacificNew = "US/Pacific-New", + USSamoa = "US/Samoa", + UTC = "UTC", + Universal = "Universal", + WSU = "W-SU", + WET = "WET", + Zulu = "Zulu", } export default Timezone; diff --git a/Common/Types/TimezoneCode.ts b/Common/Types/TimezoneCode.ts index f5a76368c1..14bc75000c 100644 --- a/Common/Types/TimezoneCode.ts +++ b/Common/Types/TimezoneCode.ts @@ -1,63 +1,63 @@ enum TimezoneCode { - UTC = 'UTC', - GMT = 'GMT', - EST = 'EST', - EDT = 'EDT', - CST = 'CST', - CDT = 'CDT', - MST = 'MST', - MDT = 'MDT', - PST = 'PST', - PDT = 'PDT', - AKST = 'AKST', - AKDT = 'AKDT', - HST = 'HST', - HDT = 'HDT', - AEST = 'AEST', - AEDT = 'AEDT', - ACST = 'ACST', - ACDT = 'ACDT', - AWST = 'AWST', - AWDT = 'AWDT', - NZST = 'NZST', - NZDT = 'NZDT', - NFT = 'NFT', - IDLW = 'IDLW', - NT = 'NT', - CAT = 'CAT', - EAT = 'EAT', - IST = 'IST', - MSK = 'MSK', - EET = 'EET', - CET = 'CET', - WAT = 'WAT', - GMT1 = 'GMT+1', - GMT2 = 'GMT+2', - GMT3 = 'GMT+3', - GMT4 = 'GMT+4', - GMT5 = 'GMT+5', - GMT6 = 'GMT+6', - GMT7 = 'GMT+7', - GMT8 = 'GMT+8', - GMT9 = 'GMT+9', - GMT10 = 'GMT+10', - GMT11 = 'GMT+11', - GMT12 = 'GMT+12', - GMT13 = 'GMT+13', - GMT14 = 'GMT+14', - GMT_1 = 'GMT-1', - GMT_2 = 'GMT-2', - GMT_3 = 'GMT-3', - GMT_4 = 'GMT-4', - GMT_5 = 'GMT-5', - GMT_6 = 'GMT-6', - GMT_7 = 'GMT-7', - GMT_8 = 'GMT-8', - GMT_9 = 'GMT-9', - GMT_10 = 'GMT-10', - GMT_11 = 'GMT-11', - GMT_12 = 'GMT-12', - GMT_13 = 'GMT-13', + UTC = "UTC", + GMT = "GMT", + EST = "EST", + EDT = "EDT", + CST = "CST", + CDT = "CDT", + MST = "MST", + MDT = "MDT", + PST = "PST", + PDT = "PDT", + AKST = "AKST", + AKDT = "AKDT", + HST = "HST", + HDT = "HDT", + AEST = "AEST", + AEDT = "AEDT", + ACST = "ACST", + ACDT = "ACDT", + AWST = "AWST", + AWDT = "AWDT", + NZST = "NZST", + NZDT = "NZDT", + NFT = "NFT", + IDLW = "IDLW", + NT = "NT", + CAT = "CAT", + EAT = "EAT", + IST = "IST", + MSK = "MSK", + EET = "EET", + CET = "CET", + WAT = "WAT", + GMT1 = "GMT+1", + GMT2 = "GMT+2", + GMT3 = "GMT+3", + GMT4 = "GMT+4", + GMT5 = "GMT+5", + GMT6 = "GMT+6", + GMT7 = "GMT+7", + GMT8 = "GMT+8", + GMT9 = "GMT+9", + GMT10 = "GMT+10", + GMT11 = "GMT+11", + GMT12 = "GMT+12", + GMT13 = "GMT+13", + GMT14 = "GMT+14", + GMT_1 = "GMT-1", + GMT_2 = "GMT-2", + GMT_3 = "GMT-3", + GMT_4 = "GMT-4", + GMT_5 = "GMT-5", + GMT_6 = "GMT-6", + GMT_7 = "GMT-7", + GMT_8 = "GMT-8", + GMT_9 = "GMT-9", + GMT_10 = "GMT-10", + GMT_11 = "GMT-11", + GMT_12 = "GMT-12", + GMT_13 = "GMT-13", } export default TimezoneCode; diff --git a/Common/Types/Typeof.ts b/Common/Types/Typeof.ts index 77bd97c4e7..615e7cbae4 100644 --- a/Common/Types/Typeof.ts +++ b/Common/Types/Typeof.ts @@ -1,8 +1,8 @@ enum Typeof { - String = 'string', - Boolean = 'boolean', - Number = 'number', - Object = 'object', + String = "string", + Boolean = "boolean", + Number = "number", + Object = "object", } export default Typeof; diff --git a/Common/Types/UserNotification/UserNotificationEventType.ts b/Common/Types/UserNotification/UserNotificationEventType.ts index e2741608bd..3bb5271ade 100644 --- a/Common/Types/UserNotification/UserNotificationEventType.ts +++ b/Common/Types/UserNotification/UserNotificationEventType.ts @@ -1,5 +1,5 @@ enum UserNotificationEventType { - IncidentCreated = 'Incident Created', + IncidentCreated = "Incident Created", } export default UserNotificationEventType; diff --git a/Common/Types/UserNotification/UserNotificationExecutionStatus.ts b/Common/Types/UserNotification/UserNotificationExecutionStatus.ts index 4314b02a76..8856e973a3 100644 --- a/Common/Types/UserNotification/UserNotificationExecutionStatus.ts +++ b/Common/Types/UserNotification/UserNotificationExecutionStatus.ts @@ -1,9 +1,9 @@ enum UserNotificationExecutionStatus { - Scheduled = 'Scheduled', - Started = 'Started', - Executing = 'Executing', - Completed = 'Completed', - Error = 'Error', + Scheduled = "Scheduled", + Started = "Started", + Executing = "Executing", + Completed = "Completed", + Error = "Error", } export default UserNotificationExecutionStatus; diff --git a/Common/Types/UserNotification/UserNotificationStatus.ts b/Common/Types/UserNotification/UserNotificationStatus.ts index bb0ef9efd6..a609f64449 100644 --- a/Common/Types/UserNotification/UserNotificationStatus.ts +++ b/Common/Types/UserNotification/UserNotificationStatus.ts @@ -1,9 +1,9 @@ enum UserNotificationStatus { - Sent = 'Sent', - Acknowledged = 'Acknowledged', - Error = 'Error', - Sending = 'Sending', - Skipped = 'Skipped', + Sent = "Sent", + Acknowledged = "Acknowledged", + Error = "Error", + Sending = "Sending", + Skipped = "Skipped", } export default UserNotificationStatus; diff --git a/Common/Types/UserType.ts b/Common/Types/UserType.ts index c645d8f155..2fc29becf5 100644 --- a/Common/Types/UserType.ts +++ b/Common/Types/UserType.ts @@ -1,8 +1,8 @@ enum UserType { - API = 'API', - User = 'User', - MasterAdmin = 'MasterAdmin', - Public = 'Public', + API = "API", + User = "User", + MasterAdmin = "MasterAdmin", + Public = "Public", } export default UserType; diff --git a/Common/Types/Version.ts b/Common/Types/Version.ts index 3de4c2e404..92a38ccba6 100644 --- a/Common/Types/Version.ts +++ b/Common/Types/Version.ts @@ -1,66 +1,66 @@ -import DatabaseProperty from './Database/DatabaseProperty'; -import BadDataException from './Exception/BadDataException'; -import { JSONObject, ObjectType } from './JSON'; -import { FindOperator } from 'typeorm'; +import DatabaseProperty from "./Database/DatabaseProperty"; +import BadDataException from "./Exception/BadDataException"; +import { JSONObject, ObjectType } from "./JSON"; +import { FindOperator } from "typeorm"; export default class Version extends DatabaseProperty { - private _version: string = ''; - public get version(): string { - return this._version; + private _version: string = ""; + public get version(): string { + return this._version; + } + public set version(v: string) { + const re: RegExp = + /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[a-zA-Z\d][-a-zA-Z.\d]*)?(\+[a-zA-Z\d][-a-zA-Z.\d]*)?$/i; + const isValid: boolean = re.test(v); + if (!isValid) { + throw new BadDataException("Version is not in valid format."); } - public set version(v: string) { - const re: RegExp = - /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[a-zA-Z\d][-a-zA-Z.\d]*)?(\+[a-zA-Z\d][-a-zA-Z.\d]*)?$/i; - const isValid: boolean = re.test(v); - if (!isValid) { - throw new BadDataException('Version is not in valid format.'); - } - this._version = v; + this._version = v; + } + + public constructor(version: string) { + super(); + this.version = version; + } + + public override toString(): string { + return this.version; + } + + public override toJSON(): JSONObject { + return { + _type: ObjectType.Version, + value: (this as Version).toString(), + }; + } + + public static override fromJSON(json: JSONObject): Version { + if (json["_type"] === ObjectType.Version) { + return new Version((json["value"] as string) || ""); } - public constructor(version: string) { - super(); - this.version = version; + throw new BadDataException("Invalid JSON: " + JSON.stringify(json)); + } + + protected static override toDatabase( + value: Version | FindOperator<Version>, + ): string | null { + if (value) { + if (typeof value === "string") { + value = new Version(value); + } + + return value.toString(); } - public override toString(): string { - return this.version; + return null; + } + + protected static override fromDatabase(_value: string): Version | null { + if (_value) { + return new Version(_value); } - public override toJSON(): JSONObject { - return { - _type: ObjectType.Version, - value: (this as Version).toString(), - }; - } - - public static override fromJSON(json: JSONObject): Version { - if (json['_type'] === ObjectType.Version) { - return new Version((json['value'] as string) || ''); - } - - throw new BadDataException('Invalid JSON: ' + JSON.stringify(json)); - } - - protected static override toDatabase( - value: Version | FindOperator<Version> - ): string | null { - if (value) { - if (typeof value === 'string') { - value = new Version(value); - } - - return value.toString(); - } - - return null; - } - - protected static override fromDatabase(_value: string): Version | null { - if (_value) { - return new Version(_value); - } - - return null; - } + return null; + } } diff --git a/Common/Types/WebsiteRequest.ts b/Common/Types/WebsiteRequest.ts index e8a854599b..3fc88df491 100644 --- a/Common/Types/WebsiteRequest.ts +++ b/Common/Types/WebsiteRequest.ts @@ -1,66 +1,66 @@ -import HTTPMethod from './API/HTTPMethod'; -import Headers from './API/Headers'; -import URL from './API/URL'; -import Dictionary from './Dictionary'; -import HTML from './Html'; -import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; +import HTTPMethod from "./API/HTTPMethod"; +import Headers from "./API/Headers"; +import URL from "./API/URL"; +import Dictionary from "./Dictionary"; +import HTML from "./Html"; +import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; export interface WebsiteResponse { - url: URL; - requestHeaders: Headers; - responseHeaders: Headers; - responseStatusCode: number; - responseBody: HTML; - isOnline: boolean; + url: URL; + requestHeaders: Headers; + responseHeaders: Headers; + responseStatusCode: number; + responseBody: HTML; + isOnline: boolean; } export default class WebsiteRequest { - public static async fetch( - url: URL, - options: { - headers?: Headers | undefined; - timeout?: number | undefined; - isHeadRequest?: boolean | undefined; - } - ): Promise<WebsiteResponse> { - const axiosOptions: AxiosRequestConfig = { - timeout: options.timeout || 5000, - method: HTTPMethod.GET, - }; + public static async fetch( + url: URL, + options: { + headers?: Headers | undefined; + timeout?: number | undefined; + isHeadRequest?: boolean | undefined; + }, + ): Promise<WebsiteResponse> { + const axiosOptions: AxiosRequestConfig = { + timeout: options.timeout || 5000, + method: HTTPMethod.GET, + }; - if (options.headers) { - axiosOptions.headers = options.headers; - } - - if (options.isHeadRequest) { - axiosOptions.method = HTTPMethod.HEAD; - } - - // use axios to fetch an HTML page - let response: AxiosResponse | null = null; - - try { - response = await axios(url.toString(), axiosOptions); - } catch (err: unknown) { - if (err && options.isHeadRequest) { - // 404 because of HEAD request. Retry with GET request. - response = await axios(url.toString(), { - ...axiosOptions, - method: HTTPMethod.GET, - }); - } else { - throw err; - } - } - - // return the response - return { - url: url, - requestHeaders: options.headers || {}, - responseHeaders: response!.headers as Dictionary<string>, - responseStatusCode: response!.status, - responseBody: new HTML(response!.data), - isOnline: true, - }; + if (options.headers) { + axiosOptions.headers = options.headers; } + + if (options.isHeadRequest) { + axiosOptions.method = HTTPMethod.HEAD; + } + + // use axios to fetch an HTML page + let response: AxiosResponse | null = null; + + try { + response = await axios(url.toString(), axiosOptions); + } catch (err: unknown) { + if (err && options.isHeadRequest) { + // 404 because of HEAD request. Retry with GET request. + response = await axios(url.toString(), { + ...axiosOptions, + method: HTTPMethod.GET, + }); + } else { + throw err; + } + } + + // return the response + return { + url: url, + requestHeaders: options.headers || {}, + responseHeaders: response!.headers as Dictionary<string>, + responseStatusCode: response!.status, + responseBody: new HTML(response!.data), + isOnline: true, + }; + } } diff --git a/Common/Types/Workflow/Component.ts b/Common/Types/Workflow/Component.ts index 48a50a27b2..ca347e6e7b 100644 --- a/Common/Types/Workflow/Component.ts +++ b/Common/Types/Workflow/Component.ts @@ -1,99 +1,99 @@ -import Route from '../API/Route'; -import IconProp from '../Icon/IconProp'; -import { JSONObject } from '../JSON'; +import Route from "../API/Route"; +import IconProp from "../Icon/IconProp"; +import { JSONObject } from "../JSON"; export enum ComponentInputType { - Text = 'Text', - Password = 'Password', - Date = 'Date', - DateTime = 'Date Time', - Boolean = 'True or False', - Number = 'Number', - Decimal = 'Decimal', - JavaScript = 'JavaScript', - AnyValue = 'Any Type', - JSON = 'JSON', - StringDictionary = 'Dictionary of String', - URL = 'URL', - Email = 'Email', - CronTab = 'CronTab', - Query = 'Database Query', - Select = 'Database Select', - BaseModel = 'Database Record', - BaseModelArray = 'Database Records', - JSONArray = 'List of JSON', - LongText = 'Long Text', - HTML = 'HTML', - Operator = 'Operator', - Markdown = 'Markdown', + Text = "Text", + Password = "Password", + Date = "Date", + DateTime = "Date Time", + Boolean = "True or False", + Number = "Number", + Decimal = "Decimal", + JavaScript = "JavaScript", + AnyValue = "Any Type", + JSON = "JSON", + StringDictionary = "Dictionary of String", + URL = "URL", + Email = "Email", + CronTab = "CronTab", + Query = "Database Query", + Select = "Database Select", + BaseModel = "Database Record", + BaseModelArray = "Database Records", + JSONArray = "List of JSON", + LongText = "Long Text", + HTML = "HTML", + Operator = "Operator", + Markdown = "Markdown", } export enum ComponentType { - Trigger = 'Trigger', - Component = 'Component', + Trigger = "Trigger", + Component = "Component", } export enum NodeType { - Node = 'Node', - PlaceholderNode = 'PlaceholderNode', + Node = "Node", + PlaceholderNode = "PlaceholderNode", } export interface NodeDataProp { - error: string; - id: string; - nodeType: NodeType; - onClick?: (node: NodeDataProp) => void | undefined; - isPreview?: boolean | undefined; // is this used to show in the components modal? - metadata: ComponentMetadata; - metadataId: string; - internalId: string; - arguments: JSONObject; - returnValues: JSONObject; - componentType: ComponentType; + error: string; + id: string; + nodeType: NodeType; + onClick?: (node: NodeDataProp) => void | undefined; + isPreview?: boolean | undefined; // is this used to show in the components modal? + metadata: ComponentMetadata; + metadataId: string; + internalId: string; + arguments: JSONObject; + returnValues: JSONObject; + componentType: ComponentType; } export interface Port { - title: string; - description: string; - id: string; + title: string; + description: string; + id: string; } export interface Argument { - name: string; - description: string; - required: boolean; - type: ComponentInputType; - id: string; - isAdvanced?: boolean | undefined; - placeholder?: string | undefined; + name: string; + description: string; + required: boolean; + type: ComponentInputType; + id: string; + isAdvanced?: boolean | undefined; + placeholder?: string | undefined; } export interface ReturnValue { - id: string; - name: string; - description: string; - type: ComponentInputType; - required: boolean; - placeholder?: string | undefined; + id: string; + name: string; + description: string; + type: ComponentInputType; + required: boolean; + placeholder?: string | undefined; } export default interface ComponentMetadata { - id: string; - title: string; - category: string; - description: string; - iconProp: IconProp; - componentType: ComponentType; - arguments: Array<Argument>; - returnValues: Array<ReturnValue>; - inPorts: Array<Port>; - outPorts: Array<Port>; - tableName?: string | undefined; - documentationLink?: Route; + id: string; + title: string; + category: string; + description: string; + iconProp: IconProp; + componentType: ComponentType; + arguments: Array<Argument>; + returnValues: Array<ReturnValue>; + inPorts: Array<Port>; + outPorts: Array<Port>; + tableName?: string | undefined; + documentationLink?: Route; } export interface ComponentCategory { - name: string; - description: string; - icon: IconProp; + name: string; + description: string; + icon: IconProp; } diff --git a/Common/Types/Workflow/ComponentID.ts b/Common/Types/Workflow/ComponentID.ts index cdac4680cb..ec5c4d22d9 100644 --- a/Common/Types/Workflow/ComponentID.ts +++ b/Common/Types/Workflow/ComponentID.ts @@ -1,20 +1,20 @@ enum ComponentID { - Webhook = 'webhook', - Log = 'log', - SlackSendMessageToChannel = 'slack-send-message-to-channel', - MicrosoftTeamsSendMessageToChannel = 'microsoft-teams-send-message-to-channel', - Schedule = 'schedule', - JavaScriptCode = 'javascript', - Manual = 'manual', - JsonToText = 'json-to-text', - TextToJson = 'text-to-json', - MergeJson = 'merge-json', - ApiGet = 'api-get', - ApiPut = 'api-put', - ApiPost = 'api-post', - ApiDelete = 'api-delete', - SendEmail = 'send-email', - IfElse = 'if-else', + Webhook = "webhook", + Log = "log", + SlackSendMessageToChannel = "slack-send-message-to-channel", + MicrosoftTeamsSendMessageToChannel = "microsoft-teams-send-message-to-channel", + Schedule = "schedule", + JavaScriptCode = "javascript", + Manual = "manual", + JsonToText = "json-to-text", + TextToJson = "text-to-json", + MergeJson = "merge-json", + ApiGet = "api-get", + ApiPut = "api-put", + ApiPost = "api-post", + ApiDelete = "api-delete", + SendEmail = "send-email", + IfElse = "if-else", } export default ComponentID; diff --git a/Common/Types/Workflow/Components.ts b/Common/Types/Workflow/Components.ts index 59c2125f89..ada54fb086 100644 --- a/Common/Types/Workflow/Components.ts +++ b/Common/Types/Workflow/Components.ts @@ -1,84 +1,84 @@ -import IconProp from '../Icon/IconProp'; -import ComponentMetadata, { ComponentCategory } from './Component'; -import APIComponents from './Components/API'; -import ConditionComponents from './Components/Condition'; -import EmailComponents from './Components/Email'; -import JsonComponents from './Components/JSON'; -import JavaScriptComponents from './Components/JavaScript'; -import LogComponents from './Components/Log'; -import ManualComponents from './Components/Manual'; -import MicrosoftTeamsComponents from './Components/MicrosoftTeams'; -import ScheduleComponents from './Components/Schedule'; -import SlackComponents from './Components/Slack'; -import WebhookComponents from './Components/Webhook'; -import WorkflowComponents from './Components/Workflow'; +import IconProp from "../Icon/IconProp"; +import ComponentMetadata, { ComponentCategory } from "./Component"; +import APIComponents from "./Components/API"; +import ConditionComponents from "./Components/Condition"; +import EmailComponents from "./Components/Email"; +import JsonComponents from "./Components/JSON"; +import JavaScriptComponents from "./Components/JavaScript"; +import LogComponents from "./Components/Log"; +import ManualComponents from "./Components/Manual"; +import MicrosoftTeamsComponents from "./Components/MicrosoftTeams"; +import ScheduleComponents from "./Components/Schedule"; +import SlackComponents from "./Components/Slack"; +import WebhookComponents from "./Components/Webhook"; +import WorkflowComponents from "./Components/Workflow"; const components: Array<ComponentMetadata> = [ - ...LogComponents, - ...APIComponents, - ...ScheduleComponents, - ...SlackComponents, - ...ConditionComponents, - ...JsonComponents, - ...JavaScriptComponents, - ...EmailComponents, - ...WebhookComponents, - ...WorkflowComponents, - ...ManualComponents, - ...MicrosoftTeamsComponents, + ...LogComponents, + ...APIComponents, + ...ScheduleComponents, + ...SlackComponents, + ...ConditionComponents, + ...JsonComponents, + ...JavaScriptComponents, + ...EmailComponents, + ...WebhookComponents, + ...WorkflowComponents, + ...ManualComponents, + ...MicrosoftTeamsComponents, ]; export default components; export const Categories: Array<ComponentCategory> = [ - { - name: 'Webhook', - description: 'Integrate any apps into the workflow with webhooks.', - icon: IconProp.AltGlobe, - }, - { - name: 'API', - description: 'Integrate with any API out on the web.', - icon: IconProp.Globe, - }, - { - name: 'Slack', - description: 'Integrate OneUptime with your Slack team.', - icon: IconProp.SendMessage, - }, - { - name: 'Microsoft Teams', - description: 'Integrate OneUptime with your Microsoft Teams.', - icon: IconProp.SendMessage, - }, - { - name: 'Conditions', - description: 'Add logic to your workflows.', - icon: IconProp.Condition, - }, - { - name: 'Custom Code', - description: 'Add JaavScript to your workflows.', - icon: IconProp.Code, - }, - { - name: 'JSON', - description: 'Work with JSON Object in your workflows.', - icon: IconProp.JSON, - }, - { - name: 'Schedule', - description: 'Make your workflows run at regular intervals.', - icon: IconProp.Clock, - }, - { - name: 'Email', - description: 'Send email to anyone in your workflows.', - icon: IconProp.Clock, - }, - { - name: 'Utils', - description: 'Utils that make workflow design simpler.', - icon: IconProp.Window, - }, + { + name: "Webhook", + description: "Integrate any apps into the workflow with webhooks.", + icon: IconProp.AltGlobe, + }, + { + name: "API", + description: "Integrate with any API out on the web.", + icon: IconProp.Globe, + }, + { + name: "Slack", + description: "Integrate OneUptime with your Slack team.", + icon: IconProp.SendMessage, + }, + { + name: "Microsoft Teams", + description: "Integrate OneUptime with your Microsoft Teams.", + icon: IconProp.SendMessage, + }, + { + name: "Conditions", + description: "Add logic to your workflows.", + icon: IconProp.Condition, + }, + { + name: "Custom Code", + description: "Add JaavScript to your workflows.", + icon: IconProp.Code, + }, + { + name: "JSON", + description: "Work with JSON Object in your workflows.", + icon: IconProp.JSON, + }, + { + name: "Schedule", + description: "Make your workflows run at regular intervals.", + icon: IconProp.Clock, + }, + { + name: "Email", + description: "Send email to anyone in your workflows.", + icon: IconProp.Clock, + }, + { + name: "Utils", + description: "Utils that make workflow design simpler.", + icon: IconProp.Window, + }, ]; diff --git a/Common/Types/Workflow/Components/API.ts b/Common/Types/Workflow/Components/API.ts index 9dd52cc4cf..c6cac213f1 100644 --- a/Common/Types/Workflow/Components/API.ts +++ b/Common/Types/Workflow/Components/API.ts @@ -1,363 +1,355 @@ -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.ApiGet, - title: 'API Get (JSON)', - category: 'API', - description: 'Send Get API Request and get JSON Response', - iconProp: IconProp.Globe, - componentType: ComponentType.Component, - arguments: [ - { - id: 'url', - name: 'URL', - description: 'URL to send request to.', - type: ComponentInputType.URL, - required: true, - placeholder: 'https://api.yourcompany.com', - }, - { - id: 'request-body', - name: 'Request Body', - description: 'Request Body in JSON', - type: ComponentInputType.JSON, - required: false, - placeholder: - 'Example: {"key1": "value1", "key2": "value2", ....}', - }, - { - id: 'request-headers', - name: 'Request Headers', - description: 'Request headers to send.', - type: ComponentInputType.StringDictionary, - required: false, - isAdvanced: true, - placeholder: - 'Example: {"header1": "value1", "header2": "value2", ....}', - }, - ], - returnValues: [ - { - id: 'error', - name: 'Error', - description: 'Error, if there is any.', - type: ComponentInputType.Text, - required: false, - }, - { - id: 'response-status', - name: 'Response Status', - description: 'Response Status (200, for example)', - type: ComponentInputType.Number, - required: false, - }, - { - id: 'response-headers', - name: 'Response Headers', - description: 'Response Headers for this request', - type: ComponentInputType.StringDictionary, - required: false, - }, - { - id: 'response-body', - name: 'Response Body', - description: 'Response Body', - type: ComponentInputType.JSON, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the message is successfully posted', - id: 'success', - }, - { - title: 'Error', - description: 'This is executed when there is an error', - id: 'error', - }, - ], - }, - { - id: ComponentID.ApiPost, - title: 'API Post (JSON)', - category: 'API', - description: 'Send a POST Request and get JSON Response', - iconProp: IconProp.Globe, - componentType: ComponentType.Component, - arguments: [ - { - id: 'url', - name: 'URL', - description: 'URL to send request to.', - type: ComponentInputType.URL, - required: true, - placeholder: 'https://api.yourcompany.com', - }, - { - id: 'request-body', - name: 'Request Body', - description: 'Request Body in JSON', - type: ComponentInputType.JSON, - required: false, - placeholder: - 'Example: {"key1": "value1", "key2": "value2", ....}', - }, - { - id: 'request-headers', - name: 'Request Headers', - description: 'Request headers to send.', - type: ComponentInputType.StringDictionary, - required: false, - isAdvanced: true, - placeholder: - 'Example: {"header1": "value1", "header2": "value2", ....}', - }, - ], - returnValues: [ - { - id: 'error', - name: 'Error', - description: 'Error, if there is any.', - type: ComponentInputType.Text, - required: false, - }, - { - id: 'response-status', - name: 'Response Status', - description: 'Response Status (200, for example)', - type: ComponentInputType.Number, - required: false, - }, - { - id: 'response-headers', - name: 'Response Headers', - description: 'Response Headers for this request', - type: ComponentInputType.StringDictionary, - required: false, - }, - { - id: 'response-body', - name: 'Response Body', - description: 'Response Body', - type: ComponentInputType.JSON, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the message is successfully posted', - id: 'success', - }, - { - title: 'Error', - description: 'This is executed when there is an error', - id: 'error', - }, - ], - }, - { - id: ComponentID.ApiPut, - title: 'API Put (JSON)', - category: 'API', - description: 'Send a PATCH Request and get JSON Response', - iconProp: IconProp.Globe, - componentType: ComponentType.Component, - arguments: [ - { - id: 'url', - name: 'URL', - description: 'URL to send request to.', - type: ComponentInputType.URL, - required: true, - placeholder: 'https://api.yourcompany.com', - }, - { - id: 'request-body', - name: 'Request Body', - description: 'Request Body in JSON', - type: ComponentInputType.JSON, - required: false, - placeholder: - 'Example: {"key1": "value1", "key2": "value2", ....}', - }, - { - id: 'request-headers', - name: 'Request Headers', - description: 'Request headers to send.', - type: ComponentInputType.StringDictionary, - required: false, - isAdvanced: true, - placeholder: - 'Example: {"header1": "value1", "header2": "value2", ....}', - }, - ], - returnValues: [ - { - id: 'error', - name: 'Error', - description: 'Error, if there is any.', - type: ComponentInputType.Text, - required: false, - }, - { - id: 'response-status', - name: 'Response Status', - description: 'Response Status (200, for example)', - type: ComponentInputType.Number, - required: false, - }, - { - id: 'response-headers', - name: 'Response Headers', - description: 'Response Headers for this request', - type: ComponentInputType.StringDictionary, - required: false, - }, - { - id: 'response-body', - name: 'Response Body', - description: 'Response Body', - type: ComponentInputType.JSON, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the message is successfully posted', - id: 'success', - }, - { - title: 'Error', - description: 'This is executed when there is an error', - id: 'error', - }, - ], - }, - { - id: ComponentID.ApiDelete, - title: 'API Delete (JSON)', - category: 'API', - description: 'Send a PATCH Request and get JSON Response', - iconProp: IconProp.Globe, - componentType: ComponentType.Component, - arguments: [ - { - id: 'url', - name: 'URL', - description: 'URL to send request to.', - type: ComponentInputType.URL, - required: true, - placeholder: 'https://api.yourcompany.com', - }, - { - id: 'request-body', - name: 'Request Body', - description: 'Request Body in JSON', - type: ComponentInputType.JSON, - required: false, - placeholder: - 'Example: {"key1": "value1", "key2": "value2", ....}', - }, - { - id: 'request-headers', - name: 'Request Headers', - description: 'Request headers to send.', - type: ComponentInputType.StringDictionary, - required: false, - isAdvanced: true, - placeholder: - 'Example: {"header1": "value1", "header2": "value2", ....}', - }, - ], - returnValues: [ - { - id: 'error', - name: 'Error', - description: 'Error, if there is any.', - type: ComponentInputType.Text, - required: false, - }, - { - id: 'response-status', - name: 'Response Status', - description: 'Response Status (200, for example)', - type: ComponentInputType.Number, - required: false, - }, - { - id: 'response-headers', - name: 'Response Headers', - description: 'Response Headers for this request', - type: ComponentInputType.StringDictionary, - required: false, - }, - { - id: 'response-body', - name: 'Response Body', - description: 'Response Body', - type: ComponentInputType.JSON, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the message is successfully posted', - id: 'success', - }, - { - title: 'Error', - description: 'This is executed when there is an error', - id: 'error', - }, - ], - }, + { + id: ComponentID.ApiGet, + title: "API Get (JSON)", + category: "API", + description: "Send Get API Request and get JSON Response", + iconProp: IconProp.Globe, + componentType: ComponentType.Component, + arguments: [ + { + id: "url", + name: "URL", + description: "URL to send request to.", + type: ComponentInputType.URL, + required: true, + placeholder: "https://api.yourcompany.com", + }, + { + id: "request-body", + name: "Request Body", + description: "Request Body in JSON", + type: ComponentInputType.JSON, + required: false, + placeholder: 'Example: {"key1": "value1", "key2": "value2", ....}', + }, + { + id: "request-headers", + name: "Request Headers", + description: "Request headers to send.", + type: ComponentInputType.StringDictionary, + required: false, + isAdvanced: true, + placeholder: + 'Example: {"header1": "value1", "header2": "value2", ....}', + }, + ], + returnValues: [ + { + id: "error", + name: "Error", + description: "Error, if there is any.", + type: ComponentInputType.Text, + required: false, + }, + { + id: "response-status", + name: "Response Status", + description: "Response Status (200, for example)", + type: ComponentInputType.Number, + required: false, + }, + { + id: "response-headers", + name: "Response Headers", + description: "Response Headers for this request", + type: ComponentInputType.StringDictionary, + required: false, + }, + { + id: "response-body", + name: "Response Body", + description: "Response Body", + type: ComponentInputType.JSON, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the message is successfully posted", + id: "success", + }, + { + title: "Error", + description: "This is executed when there is an error", + id: "error", + }, + ], + }, + { + id: ComponentID.ApiPost, + title: "API Post (JSON)", + category: "API", + description: "Send a POST Request and get JSON Response", + iconProp: IconProp.Globe, + componentType: ComponentType.Component, + arguments: [ + { + id: "url", + name: "URL", + description: "URL to send request to.", + type: ComponentInputType.URL, + required: true, + placeholder: "https://api.yourcompany.com", + }, + { + id: "request-body", + name: "Request Body", + description: "Request Body in JSON", + type: ComponentInputType.JSON, + required: false, + placeholder: 'Example: {"key1": "value1", "key2": "value2", ....}', + }, + { + id: "request-headers", + name: "Request Headers", + description: "Request headers to send.", + type: ComponentInputType.StringDictionary, + required: false, + isAdvanced: true, + placeholder: + 'Example: {"header1": "value1", "header2": "value2", ....}', + }, + ], + returnValues: [ + { + id: "error", + name: "Error", + description: "Error, if there is any.", + type: ComponentInputType.Text, + required: false, + }, + { + id: "response-status", + name: "Response Status", + description: "Response Status (200, for example)", + type: ComponentInputType.Number, + required: false, + }, + { + id: "response-headers", + name: "Response Headers", + description: "Response Headers for this request", + type: ComponentInputType.StringDictionary, + required: false, + }, + { + id: "response-body", + name: "Response Body", + description: "Response Body", + type: ComponentInputType.JSON, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the message is successfully posted", + id: "success", + }, + { + title: "Error", + description: "This is executed when there is an error", + id: "error", + }, + ], + }, + { + id: ComponentID.ApiPut, + title: "API Put (JSON)", + category: "API", + description: "Send a PATCH Request and get JSON Response", + iconProp: IconProp.Globe, + componentType: ComponentType.Component, + arguments: [ + { + id: "url", + name: "URL", + description: "URL to send request to.", + type: ComponentInputType.URL, + required: true, + placeholder: "https://api.yourcompany.com", + }, + { + id: "request-body", + name: "Request Body", + description: "Request Body in JSON", + type: ComponentInputType.JSON, + required: false, + placeholder: 'Example: {"key1": "value1", "key2": "value2", ....}', + }, + { + id: "request-headers", + name: "Request Headers", + description: "Request headers to send.", + type: ComponentInputType.StringDictionary, + required: false, + isAdvanced: true, + placeholder: + 'Example: {"header1": "value1", "header2": "value2", ....}', + }, + ], + returnValues: [ + { + id: "error", + name: "Error", + description: "Error, if there is any.", + type: ComponentInputType.Text, + required: false, + }, + { + id: "response-status", + name: "Response Status", + description: "Response Status (200, for example)", + type: ComponentInputType.Number, + required: false, + }, + { + id: "response-headers", + name: "Response Headers", + description: "Response Headers for this request", + type: ComponentInputType.StringDictionary, + required: false, + }, + { + id: "response-body", + name: "Response Body", + description: "Response Body", + type: ComponentInputType.JSON, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the message is successfully posted", + id: "success", + }, + { + title: "Error", + description: "This is executed when there is an error", + id: "error", + }, + ], + }, + { + id: ComponentID.ApiDelete, + title: "API Delete (JSON)", + category: "API", + description: "Send a PATCH Request and get JSON Response", + iconProp: IconProp.Globe, + componentType: ComponentType.Component, + arguments: [ + { + id: "url", + name: "URL", + description: "URL to send request to.", + type: ComponentInputType.URL, + required: true, + placeholder: "https://api.yourcompany.com", + }, + { + id: "request-body", + name: "Request Body", + description: "Request Body in JSON", + type: ComponentInputType.JSON, + required: false, + placeholder: 'Example: {"key1": "value1", "key2": "value2", ....}', + }, + { + id: "request-headers", + name: "Request Headers", + description: "Request headers to send.", + type: ComponentInputType.StringDictionary, + required: false, + isAdvanced: true, + placeholder: + 'Example: {"header1": "value1", "header2": "value2", ....}', + }, + ], + returnValues: [ + { + id: "error", + name: "Error", + description: "Error, if there is any.", + type: ComponentInputType.Text, + required: false, + }, + { + id: "response-status", + name: "Response Status", + description: "Response Status (200, for example)", + type: ComponentInputType.Number, + required: false, + }, + { + id: "response-headers", + name: "Response Headers", + description: "Response Headers for this request", + type: ComponentInputType.StringDictionary, + required: false, + }, + { + id: "response-body", + name: "Response Body", + description: "Response Body", + type: ComponentInputType.JSON, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the message is successfully posted", + id: "success", + }, + { + title: "Error", + description: "This is executed when there is an error", + id: "error", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/BaseModel.ts b/Common/Types/Workflow/Components/BaseModel.ts index 1faf7141b0..75a99e599c 100644 --- a/Common/Types/Workflow/Components/BaseModel.ts +++ b/Common/Types/Workflow/Components/BaseModel.ts @@ -1,590 +1,589 @@ -import BaseModel from '../../../Models/BaseModel'; -import IconProp from '../../Icon/IconProp'; -import Text from '../../Text'; +import BaseModel from "../../../Models/BaseModel"; +import IconProp from "../../Icon/IconProp"; +import Text from "../../Text"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; export default class BaseModelComponent { - public static getComponents(model: BaseModel): Array<ComponentMetadata> { - const components: Array<ComponentMetadata> = []; + public static getComponents(model: BaseModel): Array<ComponentMetadata> { + const components: Array<ComponentMetadata> = []; - if (!model.enableWorkflowOn) { - return []; - } - - if (model.enableWorkflowOn.read) { - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-find-one`, - title: `Find One ${model.singularName}`, - category: `${model.singularName}`, - description: `Database query to find one ${model.singularName}`, - iconProp: IconProp.ArrowCircleDown, - tableName: model.tableName!, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Query, - name: 'Query', - description: `Query on ${model.singularName}`, - required: true, - id: 'query', - placeholder: 'Example: {"columnName": "value", ...}', - }, - { - type: ComponentInputType.Select, - name: 'Select Fields', - description: `Select on ${model.singularName}`, - required: true, - id: 'select', - placeholder: 'Example: {"columnName": true, ...}', - }, - ], - returnValues: [ - { - id: 'model', - name: `${model.singularName}`, - description: `${model.singularName} fetched from the database`, - type: ComponentInputType.BaseModel, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the query executes successfully', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error fetching data from the database', - id: 'error', - }, - ], - }); - - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-find-many`, - title: `Find Many ${model.pluralName}`, - category: `${model.singularName}`, - description: `Database query to find many ${model.pluralName}`, - iconProp: IconProp.ArrowCircleDown, - tableName: model.tableName!, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Query, - name: 'Query', - description: 'Please fill out this query', - required: true, - id: 'query', - placeholder: 'Example: {"columnName": "value", ...}', - }, - { - type: ComponentInputType.Select, - name: 'Select Fields', - description: `Select on ${model.singularName}`, - required: true, - id: 'select', - placeholder: 'Example: {"columnName": true, ...}', - }, - { - type: ComponentInputType.Number, - name: 'Skip', - description: `Skip the first X number of items. This can be helpful to implement pagination. Defaults to 0.`, - required: false, - id: 'skip', - }, - { - type: ComponentInputType.Number, - name: 'Limit', - description: `Limit to first X items. This can be helpful to implement pagination. Defaults to 10.`, - required: false, - id: 'limit', - }, - ], - returnValues: [ - { - id: 'models', - name: `${model.singularName}`, - description: `${model.singularName} array fetched from the database`, - type: ComponentInputType.BaseModelArray, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the query executes successfully', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error fetching data from the database', - id: 'error', - }, - ], - }); - } - - if (model.enableWorkflowOn.delete) { - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-on-delete`, - title: `On Delete ${model.singularName}`, - category: `${model.singularName}`, - description: `When the ${model.singularName} is deleted...`, - iconProp: IconProp.Bolt, - tableName: model.tableName!, - componentType: ComponentType.Trigger, - arguments: [], - returnValues: [ - { - id: 'model', - name: `${model.singularName}`, - description: `${model.singularName} deleted in the database`, - type: ComponentInputType.BaseModel, - required: false, - }, - ], - inPorts: [], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the query executes successfully', - id: 'success', - }, - ], - }); - - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-delete-one`, - title: `Delete One ${model.singularName}`, - category: `${model.singularName}`, - description: `Database query to delete one ${model.singularName}`, - iconProp: IconProp.Trash, - tableName: model.tableName!, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Query, - name: 'Delete by', - description: 'Please fill out this query', - required: true, - id: 'query', - placeholder: 'Example: {"columnName": "value", ...}', - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the query executes successfully', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error deleting data from the database', - id: 'error', - }, - ], - }); - - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-delete-many`, - title: `Delete Many ${model.pluralName}`, - category: `${model.singularName}`, - description: `Database query to find many ${model.pluralName}`, - iconProp: IconProp.Trash, - tableName: model.tableName!, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Query, - name: 'Delete by', - description: 'Please fill out this query', - required: true, - id: 'query', - placeholder: 'Example: {"columnName": "value", ...}', - }, - { - type: ComponentInputType.Number, - name: 'Skip', - description: `Skip the first X number of items. This can be helpful to implement pagination. Defaults to 0.`, - required: false, - id: 'skip', - }, - { - type: ComponentInputType.Number, - name: 'Limit', - description: `Limit to first X items. This can be helpful to implement pagination. Defaults to 10.`, - required: false, - id: 'limit', - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the query executes successfully', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error deleting data from the database', - id: 'error', - }, - ], - }); - } - - if (model.enableWorkflowOn.create) { - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-on-create`, - title: `On Create ${model.singularName}`, - category: `${model.singularName}`, - description: `When the ${model.singularName} is created...`, - iconProp: IconProp.Bolt, - tableName: model.tableName!, - componentType: ComponentType.Trigger, - arguments: [ - { - type: ComponentInputType.Select, - name: 'Select Fields', - description: `Select on ${model.singularName}`, - required: true, - id: 'select', - placeholder: 'Example: {"columnName": true, ...}', - }, - ], - returnValues: [ - { - id: 'model', - name: `${model.singularName}`, - description: `${model.singularName} created in the database`, - type: ComponentInputType.BaseModel, - required: false, - }, - ], - inPorts: [], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the model is created successfully.', - id: 'success', - }, - ], - }); - - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-create-one`, - title: `Create One ${model.singularName}`, - category: `${model.singularName}`, - description: `Database query to create one ${model.singularName}`, - iconProp: IconProp.Database, - tableName: model.tableName!, - componentType: ComponentType.Component, - arguments: [ - { - id: 'json', - placeholder: 'Example: {"columnName": "value", ...}', - name: 'JSON Object', - description: `${model.singularName} represented as JSON`, - type: ComponentInputType.JSON, - required: true, - }, - ], - returnValues: [ - { - id: 'model', - name: `${model.singularName}`, - description: `${model.singularName} created in the database`, - type: ComponentInputType.JSON, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the query executes successfully', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error creating data from the database', - id: 'error', - }, - ], - }); - - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-create-many`, - title: `Create Many ${model.pluralName}`, - category: `${model.singularName}`, - description: `Database query to create many ${model.pluralName}`, - iconProp: IconProp.Database, - tableName: model.tableName!, - componentType: ComponentType.Component, - arguments: [ - { - id: 'json-array', - name: 'JSON Array', - placeholder: - 'Example: [{"columnName": "value", ...}, {...}]', - description: 'List of models represented as JSON array', - type: ComponentInputType.JSONArray, - required: true, - }, - ], - returnValues: [ - { - id: 'models', - name: `${model.pluralName}`, - description: 'Models created in the database', - type: ComponentInputType.BaseModel, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the query executes successfully', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error creating data from the database', - id: 'error', - }, - ], - }); - } - - if (model.enableWorkflowOn.update) { - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-on-update`, - title: `On Update ${model.singularName}`, - category: `${model.singularName}`, - description: `When the ${model.singularName} is updated...`, - iconProp: IconProp.Bolt, - tableName: model.tableName!, - componentType: ComponentType.Trigger, - arguments: [ - { - type: ComponentInputType.Select, - name: 'Listen on', - description: `Workflow only executes when an upate happens to these fields on ${model.singularName}. If you leave this blank then the workflow will fire on any field that's updated on ${model.singularName}.`, - required: false, - id: 'listen-on', - placeholder: 'Example: {"columnName": true, ...}', - }, - { - type: ComponentInputType.Select, - name: 'Select Fields', - description: `Select on ${model.singularName}`, - required: true, - id: 'select', - placeholder: 'Example: {"columnName": true, ...}', - }, - ], - returnValues: [ - { - id: 'model', - name: `${model.singularName}`, - description: `Updated ${model.singularName}`, - type: ComponentInputType.BaseModel, - required: true, - }, - ], - inPorts: [], - outPorts: [ - { - title: 'Success', - description: `This is executed when the ${model.singularName} is updated successfully.`, - id: 'success', - }, - ], - }); - - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-update-one`, - title: `Update One ${model.singularName}`, - category: `${model.singularName}`, - description: `Database query to update one ${model.singularName}`, - iconProp: IconProp.ArrowCircleUp, - tableName: model.tableName!, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Query, - name: 'Query', - description: 'Please fill out this query', - required: true, - id: 'query', - placeholder: 'Example: {"columnName": "value", ...}', - }, - { - id: 'data', - placeholder: 'Example: {"columnName": "value", ...}', - name: 'Data (JSON Object)', - description: `${model.singularName} represented as JSON`, - type: ComponentInputType.JSON, - required: true, - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the query executes successfully', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error updating data from the database', - id: 'error', - }, - ], - }); - - components.push({ - id: `${Text.pascalCaseToDashes(model.tableName!)}-update-many`, - title: `Update Many ${model.pluralName}`, - category: `${model.singularName}`, - description: `Database query to update many ${model.pluralName}`, - iconProp: IconProp.ArrowCircleUp, - tableName: model.tableName!, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Query, - name: 'Query', - description: 'Please fill out this query', - required: true, - id: 'query', - placeholder: 'Example: {"columnName": "value", ...}', - }, - { - id: 'data', - name: 'Data (JSON Object)', - placeholder: 'Example: {"columnName": "value", ...}', - description: `${model.singularName} represented as JSON`, - type: ComponentInputType.JSON, - required: true, - }, - { - type: ComponentInputType.Number, - name: 'Skip', - description: `Skip the first X number of items. This can be helpful to implement pagination. Defaults to 0.`, - required: false, - id: 'skip', - }, - { - type: ComponentInputType.Number, - name: 'Limit', - description: `Limit to first X items. This can be helpful to implement pagination. Defaults to 10.`, - required: false, - id: 'limit', - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the query executes successfully', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error updating data from the database', - id: 'error', - }, - ], - }); - } - - return components; + if (!model.enableWorkflowOn) { + return []; } + + if (model.enableWorkflowOn.read) { + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-find-one`, + title: `Find One ${model.singularName}`, + category: `${model.singularName}`, + description: `Database query to find one ${model.singularName}`, + iconProp: IconProp.ArrowCircleDown, + tableName: model.tableName!, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.Query, + name: "Query", + description: `Query on ${model.singularName}`, + required: true, + id: "query", + placeholder: 'Example: {"columnName": "value", ...}', + }, + { + type: ComponentInputType.Select, + name: "Select Fields", + description: `Select on ${model.singularName}`, + required: true, + id: "select", + placeholder: 'Example: {"columnName": true, ...}', + }, + ], + returnValues: [ + { + id: "model", + name: `${model.singularName}`, + description: `${model.singularName} fetched from the database`, + type: ComponentInputType.BaseModel, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: + "This is executed when the query executes successfully", + id: "success", + }, + { + title: "Error", + description: + "This is executed when there is an error fetching data from the database", + id: "error", + }, + ], + }); + + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-find-many`, + title: `Find Many ${model.pluralName}`, + category: `${model.singularName}`, + description: `Database query to find many ${model.pluralName}`, + iconProp: IconProp.ArrowCircleDown, + tableName: model.tableName!, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.Query, + name: "Query", + description: "Please fill out this query", + required: true, + id: "query", + placeholder: 'Example: {"columnName": "value", ...}', + }, + { + type: ComponentInputType.Select, + name: "Select Fields", + description: `Select on ${model.singularName}`, + required: true, + id: "select", + placeholder: 'Example: {"columnName": true, ...}', + }, + { + type: ComponentInputType.Number, + name: "Skip", + description: `Skip the first X number of items. This can be helpful to implement pagination. Defaults to 0.`, + required: false, + id: "skip", + }, + { + type: ComponentInputType.Number, + name: "Limit", + description: `Limit to first X items. This can be helpful to implement pagination. Defaults to 10.`, + required: false, + id: "limit", + }, + ], + returnValues: [ + { + id: "models", + name: `${model.singularName}`, + description: `${model.singularName} array fetched from the database`, + type: ComponentInputType.BaseModelArray, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: + "This is executed when the query executes successfully", + id: "success", + }, + { + title: "Error", + description: + "This is executed when there is an error fetching data from the database", + id: "error", + }, + ], + }); + } + + if (model.enableWorkflowOn.delete) { + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-on-delete`, + title: `On Delete ${model.singularName}`, + category: `${model.singularName}`, + description: `When the ${model.singularName} is deleted...`, + iconProp: IconProp.Bolt, + tableName: model.tableName!, + componentType: ComponentType.Trigger, + arguments: [], + returnValues: [ + { + id: "model", + name: `${model.singularName}`, + description: `${model.singularName} deleted in the database`, + type: ComponentInputType.BaseModel, + required: false, + }, + ], + inPorts: [], + outPorts: [ + { + title: "Success", + description: + "This is executed when the query executes successfully", + id: "success", + }, + ], + }); + + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-delete-one`, + title: `Delete One ${model.singularName}`, + category: `${model.singularName}`, + description: `Database query to delete one ${model.singularName}`, + iconProp: IconProp.Trash, + tableName: model.tableName!, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.Query, + name: "Delete by", + description: "Please fill out this query", + required: true, + id: "query", + placeholder: 'Example: {"columnName": "value", ...}', + }, + ], + returnValues: [], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: + "This is executed when the query executes successfully", + id: "success", + }, + { + title: "Error", + description: + "This is executed when there is an error deleting data from the database", + id: "error", + }, + ], + }); + + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-delete-many`, + title: `Delete Many ${model.pluralName}`, + category: `${model.singularName}`, + description: `Database query to find many ${model.pluralName}`, + iconProp: IconProp.Trash, + tableName: model.tableName!, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.Query, + name: "Delete by", + description: "Please fill out this query", + required: true, + id: "query", + placeholder: 'Example: {"columnName": "value", ...}', + }, + { + type: ComponentInputType.Number, + name: "Skip", + description: `Skip the first X number of items. This can be helpful to implement pagination. Defaults to 0.`, + required: false, + id: "skip", + }, + { + type: ComponentInputType.Number, + name: "Limit", + description: `Limit to first X items. This can be helpful to implement pagination. Defaults to 10.`, + required: false, + id: "limit", + }, + ], + returnValues: [], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: + "This is executed when the query executes successfully", + id: "success", + }, + { + title: "Error", + description: + "This is executed when there is an error deleting data from the database", + id: "error", + }, + ], + }); + } + + if (model.enableWorkflowOn.create) { + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-on-create`, + title: `On Create ${model.singularName}`, + category: `${model.singularName}`, + description: `When the ${model.singularName} is created...`, + iconProp: IconProp.Bolt, + tableName: model.tableName!, + componentType: ComponentType.Trigger, + arguments: [ + { + type: ComponentInputType.Select, + name: "Select Fields", + description: `Select on ${model.singularName}`, + required: true, + id: "select", + placeholder: 'Example: {"columnName": true, ...}', + }, + ], + returnValues: [ + { + id: "model", + name: `${model.singularName}`, + description: `${model.singularName} created in the database`, + type: ComponentInputType.BaseModel, + required: false, + }, + ], + inPorts: [], + outPorts: [ + { + title: "Success", + description: + "This is executed when the model is created successfully.", + id: "success", + }, + ], + }); + + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-create-one`, + title: `Create One ${model.singularName}`, + category: `${model.singularName}`, + description: `Database query to create one ${model.singularName}`, + iconProp: IconProp.Database, + tableName: model.tableName!, + componentType: ComponentType.Component, + arguments: [ + { + id: "json", + placeholder: 'Example: {"columnName": "value", ...}', + name: "JSON Object", + description: `${model.singularName} represented as JSON`, + type: ComponentInputType.JSON, + required: true, + }, + ], + returnValues: [ + { + id: "model", + name: `${model.singularName}`, + description: `${model.singularName} created in the database`, + type: ComponentInputType.JSON, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: + "This is executed when the query executes successfully", + id: "success", + }, + { + title: "Error", + description: + "This is executed when there is an error creating data from the database", + id: "error", + }, + ], + }); + + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-create-many`, + title: `Create Many ${model.pluralName}`, + category: `${model.singularName}`, + description: `Database query to create many ${model.pluralName}`, + iconProp: IconProp.Database, + tableName: model.tableName!, + componentType: ComponentType.Component, + arguments: [ + { + id: "json-array", + name: "JSON Array", + placeholder: 'Example: [{"columnName": "value", ...}, {...}]', + description: "List of models represented as JSON array", + type: ComponentInputType.JSONArray, + required: true, + }, + ], + returnValues: [ + { + id: "models", + name: `${model.pluralName}`, + description: "Models created in the database", + type: ComponentInputType.BaseModel, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: + "This is executed when the query executes successfully", + id: "success", + }, + { + title: "Error", + description: + "This is executed when there is an error creating data from the database", + id: "error", + }, + ], + }); + } + + if (model.enableWorkflowOn.update) { + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-on-update`, + title: `On Update ${model.singularName}`, + category: `${model.singularName}`, + description: `When the ${model.singularName} is updated...`, + iconProp: IconProp.Bolt, + tableName: model.tableName!, + componentType: ComponentType.Trigger, + arguments: [ + { + type: ComponentInputType.Select, + name: "Listen on", + description: `Workflow only executes when an upate happens to these fields on ${model.singularName}. If you leave this blank then the workflow will fire on any field that's updated on ${model.singularName}.`, + required: false, + id: "listen-on", + placeholder: 'Example: {"columnName": true, ...}', + }, + { + type: ComponentInputType.Select, + name: "Select Fields", + description: `Select on ${model.singularName}`, + required: true, + id: "select", + placeholder: 'Example: {"columnName": true, ...}', + }, + ], + returnValues: [ + { + id: "model", + name: `${model.singularName}`, + description: `Updated ${model.singularName}`, + type: ComponentInputType.BaseModel, + required: true, + }, + ], + inPorts: [], + outPorts: [ + { + title: "Success", + description: `This is executed when the ${model.singularName} is updated successfully.`, + id: "success", + }, + ], + }); + + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-update-one`, + title: `Update One ${model.singularName}`, + category: `${model.singularName}`, + description: `Database query to update one ${model.singularName}`, + iconProp: IconProp.ArrowCircleUp, + tableName: model.tableName!, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.Query, + name: "Query", + description: "Please fill out this query", + required: true, + id: "query", + placeholder: 'Example: {"columnName": "value", ...}', + }, + { + id: "data", + placeholder: 'Example: {"columnName": "value", ...}', + name: "Data (JSON Object)", + description: `${model.singularName} represented as JSON`, + type: ComponentInputType.JSON, + required: true, + }, + ], + returnValues: [], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: + "This is executed when the query executes successfully", + id: "success", + }, + { + title: "Error", + description: + "This is executed when there is an error updating data from the database", + id: "error", + }, + ], + }); + + components.push({ + id: `${Text.pascalCaseToDashes(model.tableName!)}-update-many`, + title: `Update Many ${model.pluralName}`, + category: `${model.singularName}`, + description: `Database query to update many ${model.pluralName}`, + iconProp: IconProp.ArrowCircleUp, + tableName: model.tableName!, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.Query, + name: "Query", + description: "Please fill out this query", + required: true, + id: "query", + placeholder: 'Example: {"columnName": "value", ...}', + }, + { + id: "data", + name: "Data (JSON Object)", + placeholder: 'Example: {"columnName": "value", ...}', + description: `${model.singularName} represented as JSON`, + type: ComponentInputType.JSON, + required: true, + }, + { + type: ComponentInputType.Number, + name: "Skip", + description: `Skip the first X number of items. This can be helpful to implement pagination. Defaults to 0.`, + required: false, + id: "skip", + }, + { + type: ComponentInputType.Number, + name: "Limit", + description: `Limit to first X items. This can be helpful to implement pagination. Defaults to 10.`, + required: false, + id: "limit", + }, + ], + returnValues: [], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: + "This is executed when the query executes successfully", + id: "success", + }, + { + title: "Error", + description: + "This is executed when there is an error updating data from the database", + id: "error", + }, + ], + }); + } + + return components; + } } diff --git a/Common/Types/Workflow/Components/Condition.ts b/Common/Types/Workflow/Components/Condition.ts index 0cdf4fc8b8..617ea34cd3 100644 --- a/Common/Types/Workflow/Components/Condition.ts +++ b/Common/Types/Workflow/Components/Condition.ts @@ -1,66 +1,66 @@ -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.IfElse, - title: 'If / Else', - category: 'Conditions', - description: 'Branch based on Inputs', - iconProp: IconProp.Condition, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Text, - name: 'Input 1', - description: 'Input 1', - placeholder: 'x', - required: true, - id: 'input-1', - }, - { - type: ComponentInputType.Operator, - name: 'Operator', - description: 'Operator', - placeholder: 'Equal To', - required: true, - id: 'operator', - }, - { - type: ComponentInputType.Text, - name: 'Input 2', - description: 'Input 2', - placeholder: 'x', - required: true, - id: 'input-2', - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Yes', - description: 'If, yes then this port will be executed', - id: 'yes', - }, - { - title: 'No', - description: 'If, no then this port will be executed', - id: 'no', - }, - ], - }, + { + id: ComponentID.IfElse, + title: "If / Else", + category: "Conditions", + description: "Branch based on Inputs", + iconProp: IconProp.Condition, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.Text, + name: "Input 1", + description: "Input 1", + placeholder: "x", + required: true, + id: "input-1", + }, + { + type: ComponentInputType.Operator, + name: "Operator", + description: "Operator", + placeholder: "Equal To", + required: true, + id: "operator", + }, + { + type: ComponentInputType.Text, + name: "Input 2", + description: "Input 2", + placeholder: "x", + required: true, + id: "input-2", + }, + ], + returnValues: [], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Yes", + description: "If, yes then this port will be executed", + id: "yes", + }, + { + title: "No", + description: "If, no then this port will be executed", + id: "no", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/Email.ts b/Common/Types/Workflow/Components/Email.ts index daf22d3cde..65008c20de 100644 --- a/Common/Types/Workflow/Components/Email.ts +++ b/Common/Types/Workflow/Components/Email.ts @@ -1,109 +1,108 @@ -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.SendEmail, - title: 'Send Email', - category: 'Email', - description: 'Send email from your workflows', - iconProp: IconProp.Email, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Text, - name: 'From Email', - description: 'Email to send from', - placeholder: 'Name <email@company.com>', - required: true, - id: 'from', - }, - { - type: ComponentInputType.Text, - name: 'To Email', - description: 'Email to send to', - placeholder: 'email@company.com; email2@company.com; ...', - required: true, - id: 'to', - }, - { - type: ComponentInputType.Text, - name: 'Subject', - description: 'Email to send to', - required: false, - id: 'subject', - }, - { - type: ComponentInputType.HTML, - name: 'Email Body', - description: 'Email to send to', - required: false, - id: 'email-body', - }, - { - type: ComponentInputType.Text, - name: 'SMTP HOST', - description: 'SMTP Host to send emails from', - required: true, - id: 'smtp-host', - }, - { - type: ComponentInputType.Text, - name: 'SMTP Username', - description: 'SMTP Username to send emails from', - required: true, - id: 'smtp-username', - }, - { - type: ComponentInputType.Password, - name: 'SMTP Password', - description: 'SMTP Password to send emails from', - required: true, - id: 'smtp-password', - }, - { - type: ComponentInputType.Number, - name: 'SMTP Port', - description: 'SMTP Port to send emails from', - required: true, - id: 'smtp-port', - }, - { - type: ComponentInputType.Boolean, - name: 'Use TLS/SSL', - description: - 'Check this box if you would like to use TLS/SSL to send emails', - required: false, - id: 'secure', - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the message is successfully posted', - id: 'success', - }, - { - title: 'Error', - description: 'This is executed when there is an error', - id: 'error', - }, - ], - }, + { + id: ComponentID.SendEmail, + title: "Send Email", + category: "Email", + description: "Send email from your workflows", + iconProp: IconProp.Email, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.Text, + name: "From Email", + description: "Email to send from", + placeholder: "Name <email@company.com>", + required: true, + id: "from", + }, + { + type: ComponentInputType.Text, + name: "To Email", + description: "Email to send to", + placeholder: "email@company.com; email2@company.com; ...", + required: true, + id: "to", + }, + { + type: ComponentInputType.Text, + name: "Subject", + description: "Email to send to", + required: false, + id: "subject", + }, + { + type: ComponentInputType.HTML, + name: "Email Body", + description: "Email to send to", + required: false, + id: "email-body", + }, + { + type: ComponentInputType.Text, + name: "SMTP HOST", + description: "SMTP Host to send emails from", + required: true, + id: "smtp-host", + }, + { + type: ComponentInputType.Text, + name: "SMTP Username", + description: "SMTP Username to send emails from", + required: true, + id: "smtp-username", + }, + { + type: ComponentInputType.Password, + name: "SMTP Password", + description: "SMTP Password to send emails from", + required: true, + id: "smtp-password", + }, + { + type: ComponentInputType.Number, + name: "SMTP Port", + description: "SMTP Port to send emails from", + required: true, + id: "smtp-port", + }, + { + type: ComponentInputType.Boolean, + name: "Use TLS/SSL", + description: + "Check this box if you would like to use TLS/SSL to send emails", + required: false, + id: "secure", + }, + ], + returnValues: [], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the message is successfully posted", + id: "success", + }, + { + title: "Error", + description: "This is executed when there is an error", + id: "error", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/JSON.ts b/Common/Types/Workflow/Components/JSON.ts index fa49c08266..a8031d8914 100644 --- a/Common/Types/Workflow/Components/JSON.ts +++ b/Common/Types/Workflow/Components/JSON.ts @@ -1,162 +1,157 @@ -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.JsonToText, - title: 'JSON to Text', - category: 'JSON', - description: 'Converts JSON Object to Text', - iconProp: IconProp.JSON, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.JSON, - name: 'JSON', - description: 'JSON Object as Input', - required: true, - id: 'json', - }, - ], - returnValues: [ - { - type: ComponentInputType.Text, - name: 'Text', - description: 'Text as Output', - required: true, - id: 'text', - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the JSON is successfully converted', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error in conversion', - id: 'error', - }, - ], - }, - { - id: ComponentID.TextToJson, - title: 'Text to JSON', - category: 'JSON', - description: 'Converts Text to JSON Object', - iconProp: IconProp.JSON, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Text, - name: 'Text', - description: 'Text as Input', - required: true, - id: 'text', - }, - ], - returnValues: [ - { - type: ComponentInputType.JSON, - name: 'JSON', - description: 'JSON Object as Output', - required: true, - id: 'json', - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the JSON is successfully converted', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when there is an error in conversion', - id: 'error', - }, - ], - }, - { - id: ComponentID.MergeJson, - title: 'Merge JSON', - category: 'JSON', - description: 'Merge two JSON Objects into one', - iconProp: IconProp.JSON, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.JSON, - name: 'JSON 1', - description: 'JSON Object 1 as Input', - required: true, - id: 'json1', - }, - { - type: ComponentInputType.JSON, - name: 'JSON 2', - description: 'JSON Object 2 as Input', - required: true, - id: 'json2', - }, - ], - returnValues: [ - { - type: ComponentInputType.JSON, - name: 'JSON', - description: 'JSON Object as Output', - required: true, - id: 'json', - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the JSON is successfully merged', - id: 'success', - }, - { - title: 'Error', - description: - 'This is executed when the JSON is not successfully merged', - id: 'error', - }, - ], - }, + { + id: ComponentID.JsonToText, + title: "JSON to Text", + category: "JSON", + description: "Converts JSON Object to Text", + iconProp: IconProp.JSON, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.JSON, + name: "JSON", + description: "JSON Object as Input", + required: true, + id: "json", + }, + ], + returnValues: [ + { + type: ComponentInputType.Text, + name: "Text", + description: "Text as Output", + required: true, + id: "text", + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the JSON is successfully converted", + id: "success", + }, + { + title: "Error", + description: "This is executed when there is an error in conversion", + id: "error", + }, + ], + }, + { + id: ComponentID.TextToJson, + title: "Text to JSON", + category: "JSON", + description: "Converts Text to JSON Object", + iconProp: IconProp.JSON, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.Text, + name: "Text", + description: "Text as Input", + required: true, + id: "text", + }, + ], + returnValues: [ + { + type: ComponentInputType.JSON, + name: "JSON", + description: "JSON Object as Output", + required: true, + id: "json", + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the JSON is successfully converted", + id: "success", + }, + { + title: "Error", + description: "This is executed when there is an error in conversion", + id: "error", + }, + ], + }, + { + id: ComponentID.MergeJson, + title: "Merge JSON", + category: "JSON", + description: "Merge two JSON Objects into one", + iconProp: IconProp.JSON, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.JSON, + name: "JSON 1", + description: "JSON Object 1 as Input", + required: true, + id: "json1", + }, + { + type: ComponentInputType.JSON, + name: "JSON 2", + description: "JSON Object 2 as Input", + required: true, + id: "json2", + }, + ], + returnValues: [ + { + type: ComponentInputType.JSON, + name: "JSON", + description: "JSON Object as Output", + required: true, + id: "json", + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the JSON is successfully merged", + id: "success", + }, + { + title: "Error", + description: + "This is executed when the JSON is not successfully merged", + id: "error", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/JavaScript.ts b/Common/Types/Workflow/Components/JavaScript.ts index 72bb595c33..01d069205c 100644 --- a/Common/Types/Workflow/Components/JavaScript.ts +++ b/Common/Types/Workflow/Components/JavaScript.ts @@ -1,67 +1,67 @@ -import Route from '../../API/Route'; -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import Route from "../../API/Route"; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.JavaScriptCode, - title: 'Run Custom JavaScript', - category: 'Custom Code', - description: 'Run custom JavaScript in your workflow', - iconProp: IconProp.Code, - componentType: ComponentType.Component, - documentationLink: Route.fromString('/workflow/docs/JavaScript.md'), - arguments: [ - { - type: ComponentInputType.JavaScript, - name: 'JavaScript Code', - description: 'JavaScript Code', - required: true, - id: 'code', - }, - { - type: ComponentInputType.JSON, - name: 'Arguments', - description: - 'Pass in arguments to your JavaScript Code from this workflow', - required: false, - id: 'arguments', - }, - ], - returnValues: [ - { - type: ComponentInputType.AnyValue, - name: 'Value', - description: 'Value as Output', - required: false, - id: 'returnValue', - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: 'This is executed when the code runs successfully', - id: 'success', - }, - { - title: 'Error', - description: 'This is executed when code fails to run', - id: 'error', - }, - ], - }, + { + id: ComponentID.JavaScriptCode, + title: "Run Custom JavaScript", + category: "Custom Code", + description: "Run custom JavaScript in your workflow", + iconProp: IconProp.Code, + componentType: ComponentType.Component, + documentationLink: Route.fromString("/workflow/docs/JavaScript.md"), + arguments: [ + { + type: ComponentInputType.JavaScript, + name: "JavaScript Code", + description: "JavaScript Code", + required: true, + id: "code", + }, + { + type: ComponentInputType.JSON, + name: "Arguments", + description: + "Pass in arguments to your JavaScript Code from this workflow", + required: false, + id: "arguments", + }, + ], + returnValues: [ + { + type: ComponentInputType.AnyValue, + name: "Value", + description: "Value as Output", + required: false, + id: "returnValue", + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the code runs successfully", + id: "success", + }, + { + title: "Error", + description: "This is executed when code fails to run", + id: "error", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/Log.ts b/Common/Types/Workflow/Components/Log.ts index d9dfc88ab1..5808c8095a 100644 --- a/Common/Types/Workflow/Components/Log.ts +++ b/Common/Types/Workflow/Components/Log.ts @@ -1,45 +1,45 @@ -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.Log, - title: 'Log', - category: 'Utils', - description: 'Log to console what ever is passed to this component', - iconProp: IconProp.Logs, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.AnyValue, - name: 'Value', - description: 'Value to log', - required: true, - id: 'value', - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Out', - description: - 'Connect to this port if you want other components to execute after the value has been logged.', - id: 'out', - }, - ], - }, + { + id: ComponentID.Log, + title: "Log", + category: "Utils", + description: "Log to console what ever is passed to this component", + iconProp: IconProp.Logs, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.AnyValue, + name: "Value", + description: "Value to log", + required: true, + id: "value", + }, + ], + returnValues: [], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Out", + description: + "Connect to this port if you want other components to execute after the value has been logged.", + id: "out", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/Manual.ts b/Common/Types/Workflow/Components/Manual.ts index 7866896149..d28a817c1c 100644 --- a/Common/Types/Workflow/Components/Manual.ts +++ b/Common/Types/Workflow/Components/Manual.ts @@ -1,40 +1,39 @@ -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.Manual, - title: 'Manual', - category: 'Utils', - description: 'Run this workflow manually', - iconProp: IconProp.Play, - componentType: ComponentType.Trigger, - arguments: [], - returnValues: [ - { - type: ComponentInputType.JSON, - name: 'JSON', - description: - 'Enter JSON value that you need to run this workflow', - required: false, - id: 'value', - placeholder: '{"key1": "value1", "key2": "value2", ....}', - }, - ], - inPorts: [], - outPorts: [ - { - title: 'Execute', - description: - 'Connect other components to this port if you want them to be executed.', - id: 'success', - }, - ], - }, + { + id: ComponentID.Manual, + title: "Manual", + category: "Utils", + description: "Run this workflow manually", + iconProp: IconProp.Play, + componentType: ComponentType.Trigger, + arguments: [], + returnValues: [ + { + type: ComponentInputType.JSON, + name: "JSON", + description: "Enter JSON value that you need to run this workflow", + required: false, + id: "value", + placeholder: '{"key1": "value1", "key2": "value2", ....}', + }, + ], + inPorts: [], + outPorts: [ + { + title: "Execute", + description: + "Connect other components to this port if you want them to be executed.", + id: "success", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/MicrosoftTeams.ts b/Common/Types/Workflow/Components/MicrosoftTeams.ts index f61bc04b08..d0cee81999 100644 --- a/Common/Types/Workflow/Components/MicrosoftTeams.ts +++ b/Common/Types/Workflow/Components/MicrosoftTeams.ts @@ -1,68 +1,67 @@ -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.MicrosoftTeamsSendMessageToChannel, - title: 'Send Message to Teams', - category: 'Microsoft Teams', - description: 'Send message to teams channel', - iconProp: IconProp.SendMessage, - componentType: ComponentType.Component, - arguments: [ - { - id: 'webhook-url', - name: 'Teams Incoming Webhook URL', - description: - 'Need help creating a webhook? Check docs here: https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook', - type: ComponentInputType.URL, - required: true, - placeholder: 'https://xxxxx.webhook.office.com/xxxxxxxxx', - }, - { - id: 'text', - name: 'Message Text', - description: 'Message to send to Teams.', - type: ComponentInputType.LongText, - required: true, - placeholder: 'Test teams message from OneUptime', - }, - ], - returnValues: [ - { - id: 'error', - name: 'Error', - description: 'Error, if there is any.', - type: ComponentInputType.Text, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the message is successfully posted', - id: 'success', - }, - { - title: 'Error', - description: 'This is executed when there is an error', - id: 'error', - }, - ], - }, + { + id: ComponentID.MicrosoftTeamsSendMessageToChannel, + title: "Send Message to Teams", + category: "Microsoft Teams", + description: "Send message to teams channel", + iconProp: IconProp.SendMessage, + componentType: ComponentType.Component, + arguments: [ + { + id: "webhook-url", + name: "Teams Incoming Webhook URL", + description: + "Need help creating a webhook? Check docs here: https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook", + type: ComponentInputType.URL, + required: true, + placeholder: "https://xxxxx.webhook.office.com/xxxxxxxxx", + }, + { + id: "text", + name: "Message Text", + description: "Message to send to Teams.", + type: ComponentInputType.LongText, + required: true, + placeholder: "Test teams message from OneUptime", + }, + ], + returnValues: [ + { + id: "error", + name: "Error", + description: "Error, if there is any.", + type: ComponentInputType.Text, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the message is successfully posted", + id: "success", + }, + { + title: "Error", + description: "This is executed when there is an error", + id: "error", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/Schedule.ts b/Common/Types/Workflow/Components/Schedule.ts index 17f430b6a5..fe92ae0896 100644 --- a/Common/Types/Workflow/Components/Schedule.ts +++ b/Common/Types/Workflow/Components/Schedule.ts @@ -1,38 +1,38 @@ -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.Schedule, - title: 'Schedule', - category: 'Schedule', - description: 'Run this workflow on particular schedule', - iconProp: IconProp.Clock, - componentType: ComponentType.Trigger, - arguments: [ - { - type: ComponentInputType.CronTab, - name: 'Schedule at', - description: 'Trigger this workflow at', - required: true, - id: 'schedule', - }, - ], - returnValues: [], - inPorts: [], - outPorts: [ - { - title: 'Execute', - description: - 'Connect other components to this port if you want them to be executed.', - id: 'execute', - }, - ], - }, + { + id: ComponentID.Schedule, + title: "Schedule", + category: "Schedule", + description: "Run this workflow on particular schedule", + iconProp: IconProp.Clock, + componentType: ComponentType.Trigger, + arguments: [ + { + type: ComponentInputType.CronTab, + name: "Schedule at", + description: "Trigger this workflow at", + required: true, + id: "schedule", + }, + ], + returnValues: [], + inPorts: [], + outPorts: [ + { + title: "Execute", + description: + "Connect other components to this port if you want them to be executed.", + id: "execute", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/Slack.ts b/Common/Types/Workflow/Components/Slack.ts index fbbc532122..07be2c2d11 100644 --- a/Common/Types/Workflow/Components/Slack.ts +++ b/Common/Types/Workflow/Components/Slack.ts @@ -1,70 +1,69 @@ -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.SlackSendMessageToChannel, - title: 'Send Message to Slack', - category: 'Slack', - description: 'Send message to slack channel', - iconProp: IconProp.SendMessage, - componentType: ComponentType.Component, - arguments: [ - { - id: 'webhook-url', - name: 'Slack Incoming Webhook URL', - description: - 'Need help creating a webhook? Check docs here: https://api.slack.com/messaging/webhooks', - type: ComponentInputType.URL, - required: true, - placeholder: - 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX', - }, - { - id: 'text', - name: 'Message Text', - description: 'Message to send to Slack. ', - type: ComponentInputType.Markdown, - required: true, - placeholder: - 'Guide for message formatting: https://api.slack.com/reference/surfaces/formatting', - }, - ], - returnValues: [ - { - id: 'error', - name: 'Error', - description: 'Error, if there is any.', - type: ComponentInputType.Text, - required: false, - }, - ], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Success', - description: - 'This is executed when the message is successfully posted', - id: 'success', - }, - { - title: 'Error', - description: 'This is executed when there is an error', - id: 'error', - }, - ], - }, + { + id: ComponentID.SlackSendMessageToChannel, + title: "Send Message to Slack", + category: "Slack", + description: "Send message to slack channel", + iconProp: IconProp.SendMessage, + componentType: ComponentType.Component, + arguments: [ + { + id: "webhook-url", + name: "Slack Incoming Webhook URL", + description: + "Need help creating a webhook? Check docs here: https://api.slack.com/messaging/webhooks", + type: ComponentInputType.URL, + required: true, + placeholder: + "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX", + }, + { + id: "text", + name: "Message Text", + description: "Message to send to Slack. ", + type: ComponentInputType.Markdown, + required: true, + placeholder: + "Guide for message formatting: https://api.slack.com/reference/surfaces/formatting", + }, + ], + returnValues: [ + { + id: "error", + name: "Error", + description: "Error, if there is any.", + type: ComponentInputType.Text, + required: false, + }, + ], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Success", + description: "This is executed when the message is successfully posted", + id: "success", + }, + { + title: "Error", + description: "This is executed when there is an error", + id: "error", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/Webhook.ts b/Common/Types/Workflow/Components/Webhook.ts index c087e62120..3625a707f7 100644 --- a/Common/Types/Workflow/Components/Webhook.ts +++ b/Common/Types/Workflow/Components/Webhook.ts @@ -1,58 +1,58 @@ -import Route from '../../API/Route'; -import IconProp from '../../Icon/IconProp'; -import ComponentID from '../ComponentID'; +import Route from "../../API/Route"; +import IconProp from "../../Icon/IconProp"; +import ComponentID from "../ComponentID"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: ComponentID.Webhook, - title: 'Webhook', - category: 'Webhook', + { + id: ComponentID.Webhook, + title: "Webhook", + category: "Webhook", + description: + "Hook any of your external apps and services with this workflow.", + iconProp: IconProp.AltGlobe, + componentType: ComponentType.Trigger, + documentationLink: Route.fromString("/workflow/docs/Webhook.md"), + arguments: [], + returnValues: [ + { + id: "request-headers", + name: "Request Headers", + description: "Request Headers for this request", + type: ComponentInputType.StringDictionary, + required: false, + placeholder: '{"header1": "value1", "header2": "value2", ....}', + }, + { + id: "request-params", + name: "Request Query Params", + description: "Request Query Params for this request", + type: ComponentInputType.StringDictionary, + required: false, + placeholder: '{"query1": "value1", "query2": "value2", ....}', + }, + { + id: "request-body", + name: "Request Body", + description: "Request Body", + type: ComponentInputType.JSON, + required: false, + placeholder: '{"key1": "value1", "key2": "value2", ....}', + }, + ], + inPorts: [], + outPorts: [ + { + title: "Out", description: - 'Hook any of your external apps and services with this workflow.', - iconProp: IconProp.AltGlobe, - componentType: ComponentType.Trigger, - documentationLink: Route.fromString('/workflow/docs/Webhook.md'), - arguments: [], - returnValues: [ - { - id: 'request-headers', - name: 'Request Headers', - description: 'Request Headers for this request', - type: ComponentInputType.StringDictionary, - required: false, - placeholder: '{"header1": "value1", "header2": "value2", ....}', - }, - { - id: 'request-params', - name: 'Request Query Params', - description: 'Request Query Params for this request', - type: ComponentInputType.StringDictionary, - required: false, - placeholder: '{"query1": "value1", "query2": "value2", ....}', - }, - { - id: 'request-body', - name: 'Request Body', - description: 'Request Body', - type: ComponentInputType.JSON, - required: false, - placeholder: '{"key1": "value1", "key2": "value2", ....}', - }, - ], - inPorts: [], - outPorts: [ - { - title: 'Out', - description: - 'Connect to this port if you want other components to execute after the value has been logged.', - id: 'out', - }, - ], - }, + "Connect to this port if you want other components to execute after the value has been logged.", + id: "out", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/Components/Workflow.ts b/Common/Types/Workflow/Components/Workflow.ts index b310fe7cca..256f6b4d55 100644 --- a/Common/Types/Workflow/Components/Workflow.ts +++ b/Common/Types/Workflow/Components/Workflow.ts @@ -1,44 +1,44 @@ -import IconProp from '../../Icon/IconProp'; +import IconProp from "../../Icon/IconProp"; import ComponentMetadata, { - ComponentInputType, - ComponentType, -} from './../Component'; + ComponentInputType, + ComponentType, +} from "./../Component"; const components: Array<ComponentMetadata> = [ - { - id: 'workflow-run', - title: 'Execute Workflow', - category: 'Utils', - description: 'Execute another workflow', - iconProp: IconProp.Workflow, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.AnyValue, - name: 'Value', - description: 'Value to pass to another workflow', - required: false, - id: 'value', - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Out', - description: - 'Connect to this port if you want other components to execute after the workflow is triggered', - id: 'out', - }, - ], - }, + { + id: "workflow-run", + title: "Execute Workflow", + category: "Utils", + description: "Execute another workflow", + iconProp: IconProp.Workflow, + componentType: ComponentType.Component, + arguments: [ + { + type: ComponentInputType.AnyValue, + name: "Value", + description: "Value to pass to another workflow", + required: false, + id: "value", + }, + ], + returnValues: [], + inPorts: [ + { + title: "In", + description: + "Please connect components to this port for this component to work.", + id: "in", + }, + ], + outPorts: [ + { + title: "Out", + description: + "Connect to this port if you want other components to execute after the workflow is triggered", + id: "out", + }, + ], + }, ]; export default components; diff --git a/Common/Types/Workflow/WorkflowPlan.ts b/Common/Types/Workflow/WorkflowPlan.ts index 3d20cf1fbd..b44473e670 100644 --- a/Common/Types/Workflow/WorkflowPlan.ts +++ b/Common/Types/Workflow/WorkflowPlan.ts @@ -1,8 +1,8 @@ enum WorkflowPlan { - 'Free' = 0, - 'Enterprise' = 9999999, - 'Growth' = 500, - 'Scale' = 2000, + "Free" = 0, + "Enterprise" = 9999999, + "Growth" = 500, + "Scale" = 2000, } export default WorkflowPlan; diff --git a/Common/Types/Workflow/WorkflowStatus.ts b/Common/Types/Workflow/WorkflowStatus.ts index b4168f0ba8..8a8d105895 100644 --- a/Common/Types/Workflow/WorkflowStatus.ts +++ b/Common/Types/Workflow/WorkflowStatus.ts @@ -1,10 +1,10 @@ enum WorkflowStatus { - Scheduled = 'Scheduled', - Running = 'Running', - Success = 'Success', - Error = 'Error', - Timeout = 'Timeout', - WorkflowCountExceeded = 'Workflow Count Exceeded', + Scheduled = "Scheduled", + Running = "Running", + Success = "Success", + Error = "Error", + Timeout = "Timeout", + WorkflowCountExceeded = "Workflow Count Exceeded", } export default WorkflowStatus; diff --git a/Common/Types/XML.ts b/Common/Types/XML.ts index 4555faea9b..a9db8c8d14 100644 --- a/Common/Types/XML.ts +++ b/Common/Types/XML.ts @@ -1,22 +1,22 @@ -import BadDataException from './Exception/BadDataException'; +import BadDataException from "./Exception/BadDataException"; export default class XML { - private _xml: string = ''; - public get xml(): string { - return this._xml; - } - public set xml(v: string) { - if (!v) { - throw new BadDataException('XML is not in valid format.'); - } - this._xml = v; + private _xml: string = ""; + public get xml(): string { + return this._xml; + } + public set xml(v: string) { + if (!v) { + throw new BadDataException("XML is not in valid format."); } + this._xml = v; + } - public constructor(xml: string) { - this.xml = xml; - } + public constructor(xml: string) { + this.xml = xml; + } - public toString(): string { - return this.xml; - } + public toString(): string { + return this.xml; + } } diff --git a/Common/Typings/Index.d.ts b/Common/Typings/Index.d.ts index 0f2bb2bd62..2a40049343 100644 --- a/Common/Typings/Index.d.ts +++ b/Common/Typings/Index.d.ts @@ -1,3 +1,3 @@ -declare module '*.png'; -declare module '*.jpg'; -declare module '*.gif'; +declare module "*.png"; +declare module "*.jpg"; +declare module "*.gif"; diff --git a/Common/Utils/API.ts b/Common/Utils/API.ts index 19f56ad842..644b90bb3b 100644 --- a/Common/Utils/API.ts +++ b/Common/Utils/API.ts @@ -1,425 +1,397 @@ -import AnalyticsBaseModel from '../AnalyticsModels/BaseModel'; -import BaseModel from '../Models/BaseModel'; -import HTTPErrorResponse from '../Types/API/HTTPErrorResponse'; -import HTTPMethod from '../Types/API/HTTPMethod'; -import HTTPResponse from '../Types/API/HTTPResponse'; -import Headers from '../Types/API/Headers'; -import Hostname from '../Types/API/Hostname'; -import Protocol from '../Types/API/Protocol'; -import Route from '../Types/API/Route'; -import URL from '../Types/API/URL'; -import Dictionary from '../Types/Dictionary'; -import APIException from '../Types/Exception/ApiException'; -import { JSONArray, JSONObject } from '../Types/JSON'; -import axios, { AxiosError, AxiosResponse } from 'axios'; +import AnalyticsBaseModel from "../AnalyticsModels/BaseModel"; +import BaseModel from "../Models/BaseModel"; +import HTTPErrorResponse from "../Types/API/HTTPErrorResponse"; +import HTTPMethod from "../Types/API/HTTPMethod"; +import HTTPResponse from "../Types/API/HTTPResponse"; +import Headers from "../Types/API/Headers"; +import Hostname from "../Types/API/Hostname"; +import Protocol from "../Types/API/Protocol"; +import Route from "../Types/API/Route"; +import URL from "../Types/API/URL"; +import Dictionary from "../Types/Dictionary"; +import APIException from "../Types/Exception/ApiException"; +import { JSONArray, JSONObject } from "../Types/JSON"; +import axios, { AxiosError, AxiosResponse } from "axios"; export default class API { - private _protocol: Protocol = Protocol.HTTPS; - public get protocol(): Protocol { - return this._protocol; + private _protocol: Protocol = Protocol.HTTPS; + public get protocol(): Protocol { + return this._protocol; + } + public set protocol(v: Protocol) { + this._protocol = v; + } + + private _hostname!: Hostname; + public get hostname(): Hostname { + return this._hostname; + } + public set hostname(v: Hostname) { + this._hostname = v; + } + + private _baseRoute!: Route; + public get baseRoute(): Route { + return this._baseRoute; + } + public set baseRoute(v: Route) { + this._baseRoute = v; + } + + public constructor( + protocol: Protocol, + hostname: Hostname, + baseRoute?: Route, + ) { + this.protocol = protocol; + this.hostname = hostname; + + if (baseRoute) { + this.baseRoute = baseRoute; + } else { + this.baseRoute = new Route("/"); } - public set protocol(v: Protocol) { - this._protocol = v; + } + + public async get< + T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>, + >( + path: Route, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await API.get<T>( + new URL(this.protocol, this.hostname, this.baseRoute.addRoute(path)), + data, + headers, + ); + } + + public async delete< + T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>, + >( + path: Route, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await API.delete<T>( + new URL(this.protocol, this.hostname, this.baseRoute.addRoute(path)), + data, + headers, + ); + } + + public async head< + T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>, + >( + path: Route, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await API.head<T>( + new URL(this.protocol, this.hostname, this.baseRoute.addRoute(path)), + data, + headers, + ); + } + + public async put< + T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>, + >( + path: Route, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await API.put<T>( + new URL(this.protocol, this.hostname, this.baseRoute.addRoute(path)), + data, + headers, + ); + } + + public async post< + T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>, + >( + path: Route, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await API.post<T>( + new URL(this.protocol, this.hostname, this.baseRoute.addRoute(path)), + data, + headers, + ); + } + + protected static handleError( + error: HTTPErrorResponse | APIException, + ): HTTPErrorResponse | APIException { + return error; + } + + protected static async onResponseSuccessHeaders( + headers: Dictionary<string>, + ): Promise<Dictionary<string>> { + return Promise.resolve(headers); + } + + public static getDefaultHeaders(_props?: any): Headers { + const defaultHeaders: Headers = { + "Access-Control-Allow-Origin": "*", + Accept: "application/json", + "Content-Type": "application/json;charset=UTF-8", + }; + + return defaultHeaders; + } + + protected static getHeaders(headers?: Headers): Headers { + let defaultHeaders: Headers = this.getDefaultHeaders(); + + if (headers) { + defaultHeaders = { + ...defaultHeaders, + ...headers, + }; } - private _hostname!: Hostname; - public get hostname(): Hostname { - return this._hostname; - } - public set hostname(v: Hostname) { - this._hostname = v; + return defaultHeaders; + } + + public static async get< + T extends + | JSONObject + | JSONArray + | BaseModel + | Array<BaseModel> + | AnalyticsBaseModel + | Array<AnalyticsBaseModel>, + >( + url: URL, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await this.fetch<T>(HTTPMethod.GET, url, data, headers); + } + + public static async delete< + T extends + | JSONObject + | JSONArray + | BaseModel + | Array<BaseModel> + | AnalyticsBaseModel + | Array<AnalyticsBaseModel>, + >( + url: URL, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await this.fetch(HTTPMethod.DELETE, url, data, headers); + } + + public static async head< + T extends + | JSONObject + | JSONArray + | BaseModel + | Array<BaseModel> + | AnalyticsBaseModel + | Array<AnalyticsBaseModel>, + >( + url: URL, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await this.fetch(HTTPMethod.HEAD, url, data, headers); + } + + public static async put< + T extends + | JSONObject + | JSONArray + | BaseModel + | Array<BaseModel> + | AnalyticsBaseModel + | Array<AnalyticsBaseModel>, + >( + url: URL, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await this.fetch(HTTPMethod.PUT, url, data, headers); + } + + public static async post< + T extends + | JSONObject + | JSONArray + | BaseModel + | Array<BaseModel> + | AnalyticsBaseModel + | Array<AnalyticsBaseModel>, + >( + url: URL, + data?: JSONObject | JSONArray, + headers?: Headers, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + return await this.fetch(HTTPMethod.POST, url, data, headers); + } + + public static async fetch< + T extends + | JSONObject + | JSONArray + | BaseModel + | Array<BaseModel> + | AnalyticsBaseModel + | Array<AnalyticsBaseModel>, + >( + method: HTTPMethod, + url: URL, + data?: JSONObject | JSONArray, + headers?: Headers, + params?: Dictionary<string>, + ): Promise<HTTPResponse<T> | HTTPErrorResponse> { + const apiHeaders: Headers = this.getHeaders(headers); + + if (params) { + url.addQueryParams(params); } - private _baseRoute!: Route; - public get baseRoute(): Route { - return this._baseRoute; + try { + const finalHeaders: Dictionary<string> = { + ...apiHeaders, + ...headers, + }; + + let finalBody: JSONObject | JSONArray | URLSearchParams | undefined = + data; + + // if content-type is form-url-encoded, then stringify the data + + if ( + finalHeaders["Content-Type"] === "application/x-www-form-urlencoded" && + data + ) { + finalBody = new URLSearchParams(data as Dictionary<string>); + } + + const result: AxiosResponse = await axios({ + method: method, + url: url.toString(), + headers: finalHeaders, + data: finalBody, + }); + + result.headers = await this.onResponseSuccessHeaders( + result.headers as Dictionary<string>, + ); + + const response: HTTPResponse<T> = new HTTPResponse<T>( + result.status, + result.data, + result.headers as Dictionary<string>, + ); + + return response; + } catch (e) { + const error: Error | AxiosError = e as Error | AxiosError; + let errorResponse: HTTPErrorResponse; + if (axios.isAxiosError(error)) { + // Do whatever you want with native error + errorResponse = this.getErrorResponse(error); + } else { + throw new APIException(error.message); + } + + this.handleError(errorResponse); + return errorResponse; } - public set baseRoute(v: Route) { - this._baseRoute = v; + } + + private static getErrorResponse(error: AxiosError): HTTPErrorResponse { + if (error.response) { + return new HTTPErrorResponse( + error.response.status, + error.response.data as JSONObject | JSONArray, + error.response.headers as Dictionary<string>, + ); } - public constructor( - protocol: Protocol, - hostname: Hostname, - baseRoute?: Route + // get url from error + const url: string = error?.config?.url || ""; + + throw new APIException(`URL ${url ? url + " " : ""}is not available.`); + } + + public static getFriendlyErrorMessage(error: AxiosError | Error): string { + const errorString: string = error.message || error.toString(); + + if (errorString.toLocaleLowerCase().includes("network error")) { + return "Network Error."; + } + + if (errorString.toLocaleLowerCase().includes("timeout")) { + return "Timeout Error."; + } + + if (errorString.toLocaleLowerCase().includes("request aborted")) { + return "Request Aborted."; + } + + if (errorString.toLocaleLowerCase().includes("canceled")) { + return "Request Canceled."; + } + + if (errorString.toLocaleLowerCase().includes("connection refused")) { + return "Connection Refused."; + } + + if (errorString.toLocaleLowerCase().includes("connection reset")) { + return "Connection Reset."; + } + + if (errorString.toLocaleLowerCase().includes("connection closed")) { + return "Connection Closed."; + } + + if (errorString.toLocaleLowerCase().includes("connection failed")) { + return "Connection Failed."; + } + + if (errorString.toLocaleLowerCase().includes("enotfound")) { + return "Cannot Find Host."; + } + + if (errorString.toLocaleLowerCase().includes("econnreset")) { + return "Connection Reset."; + } + + if (errorString.toLocaleLowerCase().includes("econnrefused")) { + return "Connection Refused."; + } + + if (errorString.toLocaleLowerCase().includes("econnaborted")) { + return "Connection Aborted."; + } + + if (errorString.toLocaleLowerCase().includes("certificate has expired")) { + return "SSL Certificate Expired."; + } + + if ( + errorString + .toLocaleLowerCase() + .includes("certificate signed by unknown authority") ) { - this.protocol = protocol; - this.hostname = hostname; - - if (baseRoute) { - this.baseRoute = baseRoute; - } else { - this.baseRoute = new Route('/'); - } + return "SSL Certificate Signed By Unknown Authority."; } - public async get< - T extends JSONObject | JSONArray | BaseModel | Array<BaseModel> - >( - path: Route, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await API.get<T>( - new URL( - this.protocol, - this.hostname, - this.baseRoute.addRoute(path) - ), - data, - headers - ); + if (errorString.toLocaleLowerCase().includes("self-signed certificate")) { + return "Self Signed Certificate."; } - public async delete< - T extends JSONObject | JSONArray | BaseModel | Array<BaseModel> - >( - path: Route, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await API.delete<T>( - new URL( - this.protocol, - this.hostname, - this.baseRoute.addRoute(path) - ), - data, - headers - ); - } - - public async head< - T extends JSONObject | JSONArray | BaseModel | Array<BaseModel> - >( - path: Route, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await API.head<T>( - new URL( - this.protocol, - this.hostname, - this.baseRoute.addRoute(path) - ), - data, - headers - ); - } - - public async put< - T extends JSONObject | JSONArray | BaseModel | Array<BaseModel> - >( - path: Route, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await API.put<T>( - new URL( - this.protocol, - this.hostname, - this.baseRoute.addRoute(path) - ), - data, - headers - ); - } - - public async post< - T extends JSONObject | JSONArray | BaseModel | Array<BaseModel> - >( - path: Route, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await API.post<T>( - new URL( - this.protocol, - this.hostname, - this.baseRoute.addRoute(path) - ), - data, - headers - ); - } - - protected static handleError( - error: HTTPErrorResponse | APIException - ): HTTPErrorResponse | APIException { - return error; - } - - protected static async onResponseSuccessHeaders( - headers: Dictionary<string> - ): Promise<Dictionary<string>> { - return Promise.resolve(headers); - } - - public static getDefaultHeaders(_props?: any): Headers { - const defaultHeaders: Headers = { - 'Access-Control-Allow-Origin': '*', - Accept: 'application/json', - 'Content-Type': 'application/json;charset=UTF-8', - }; - - return defaultHeaders; - } - - protected static getHeaders(headers?: Headers): Headers { - let defaultHeaders: Headers = this.getDefaultHeaders(); - - if (headers) { - defaultHeaders = { - ...defaultHeaders, - ...headers, - }; - } - - return defaultHeaders; - } - - public static async get< - T extends - | JSONObject - | JSONArray - | BaseModel - | Array<BaseModel> - | AnalyticsBaseModel - | Array<AnalyticsBaseModel> - >( - url: URL, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await this.fetch<T>(HTTPMethod.GET, url, data, headers); - } - - public static async delete< - T extends - | JSONObject - | JSONArray - | BaseModel - | Array<BaseModel> - | AnalyticsBaseModel - | Array<AnalyticsBaseModel> - >( - url: URL, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await this.fetch(HTTPMethod.DELETE, url, data, headers); - } - - public static async head< - T extends - | JSONObject - | JSONArray - | BaseModel - | Array<BaseModel> - | AnalyticsBaseModel - | Array<AnalyticsBaseModel> - >( - url: URL, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await this.fetch(HTTPMethod.HEAD, url, data, headers); - } - - public static async put< - T extends - | JSONObject - | JSONArray - | BaseModel - | Array<BaseModel> - | AnalyticsBaseModel - | Array<AnalyticsBaseModel> - >( - url: URL, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await this.fetch(HTTPMethod.PUT, url, data, headers); - } - - public static async post< - T extends - | JSONObject - | JSONArray - | BaseModel - | Array<BaseModel> - | AnalyticsBaseModel - | Array<AnalyticsBaseModel> - >( - url: URL, - data?: JSONObject | JSONArray, - headers?: Headers - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - return await this.fetch(HTTPMethod.POST, url, data, headers); - } - - public static async fetch< - T extends - | JSONObject - | JSONArray - | BaseModel - | Array<BaseModel> - | AnalyticsBaseModel - | Array<AnalyticsBaseModel> - >( - method: HTTPMethod, - url: URL, - data?: JSONObject | JSONArray, - headers?: Headers, - params?: Dictionary<string> - ): Promise<HTTPResponse<T> | HTTPErrorResponse> { - const apiHeaders: Headers = this.getHeaders(headers); - - if (params) { - url.addQueryParams(params); - } - - try { - const finalHeaders: Dictionary<string> = { - ...apiHeaders, - ...headers, - }; - - let finalBody: - | JSONObject - | JSONArray - | URLSearchParams - | undefined = data; - - // if content-type is form-url-encoded, then stringify the data - - if ( - finalHeaders['Content-Type'] === - 'application/x-www-form-urlencoded' && - data - ) { - finalBody = new URLSearchParams(data as Dictionary<string>); - } - - const result: AxiosResponse = await axios({ - method: method, - url: url.toString(), - headers: finalHeaders, - data: finalBody, - }); - - result.headers = await this.onResponseSuccessHeaders( - result.headers as Dictionary<string> - ); - - const response: HTTPResponse<T> = new HTTPResponse<T>( - result.status, - result.data, - result.headers as Dictionary<string> - ); - - return response; - } catch (e) { - const error: Error | AxiosError = e as Error | AxiosError; - let errorResponse: HTTPErrorResponse; - if (axios.isAxiosError(error)) { - // Do whatever you want with native error - errorResponse = this.getErrorResponse(error); - } else { - throw new APIException(error.message); - } - - this.handleError(errorResponse); - return errorResponse; - } - } - - private static getErrorResponse(error: AxiosError): HTTPErrorResponse { - if (error.response) { - return new HTTPErrorResponse( - error.response.status, - error.response.data as JSONObject | JSONArray, - error.response.headers as Dictionary<string> - ); - } - - // get url from error - const url: string = error?.config?.url || ''; - - throw new APIException(`URL ${url ? url + ' ' : ''}is not available.`); - } - - public static getFriendlyErrorMessage(error: AxiosError | Error): string { - const errorString: string = error.message || error.toString(); - - if (errorString.toLocaleLowerCase().includes('network error')) { - return 'Network Error.'; - } - - if (errorString.toLocaleLowerCase().includes('timeout')) { - return 'Timeout Error.'; - } - - if (errorString.toLocaleLowerCase().includes('request aborted')) { - return 'Request Aborted.'; - } - - if (errorString.toLocaleLowerCase().includes('canceled')) { - return 'Request Canceled.'; - } - - if (errorString.toLocaleLowerCase().includes('connection refused')) { - return 'Connection Refused.'; - } - - if (errorString.toLocaleLowerCase().includes('connection reset')) { - return 'Connection Reset.'; - } - - if (errorString.toLocaleLowerCase().includes('connection closed')) { - return 'Connection Closed.'; - } - - if (errorString.toLocaleLowerCase().includes('connection failed')) { - return 'Connection Failed.'; - } - - if (errorString.toLocaleLowerCase().includes('enotfound')) { - return 'Cannot Find Host.'; - } - - if (errorString.toLocaleLowerCase().includes('econnreset')) { - return 'Connection Reset.'; - } - - if (errorString.toLocaleLowerCase().includes('econnrefused')) { - return 'Connection Refused.'; - } - - if (errorString.toLocaleLowerCase().includes('econnaborted')) { - return 'Connection Aborted.'; - } - - if ( - errorString.toLocaleLowerCase().includes('certificate has expired') - ) { - return 'SSL Certificate Expired.'; - } - - if ( - errorString - .toLocaleLowerCase() - .includes('certificate signed by unknown authority') - ) { - return 'SSL Certificate Signed By Unknown Authority.'; - } - - if ( - errorString.toLocaleLowerCase().includes('self-signed certificate') - ) { - return 'Self Signed Certificate.'; - } - - return errorString; - } + return errorString; + } } diff --git a/Common/Utils/Analytics.ts b/Common/Utils/Analytics.ts index 8713412f78..abb3767826 100644 --- a/Common/Utils/Analytics.ts +++ b/Common/Utils/Analytics.ts @@ -1,42 +1,42 @@ -import Email from '../Types/Email'; -import { JSONObject } from '../Types/JSON'; -import posthog from 'posthog-js'; +import Email from "../Types/Email"; +import { JSONObject } from "../Types/JSON"; +import posthog from "posthog-js"; export default class Analytics { - private _isInitialized: boolean = false; - public get isInitialized(): boolean { - return this._isInitialized; + private _isInitialized: boolean = false; + public get isInitialized(): boolean { + return this._isInitialized; + } + public set isInitialized(v: boolean) { + this._isInitialized = v; + } + + public constructor(apiHost: string, apiKey: string) { + if (apiHost && apiKey) { + posthog.init(apiKey, { api_host: apiHost, autocapture: false }); + this.isInitialized = true; } - public set isInitialized(v: boolean) { - this._isInitialized = v; + } + + public userAuth(email: Email): void { + if (!this.isInitialized) { + return; + } + posthog.identify(email.toString()); + } + + public logout(): void { + if (!this.isInitialized) { + return; + } + posthog.reset(); + } + + public capture(eventName: string, data?: JSONObject): void { + if (!this.isInitialized) { + return; } - public constructor(apiHost: string, apiKey: string) { - if (apiHost && apiKey) { - posthog.init(apiKey, { api_host: apiHost, autocapture: false }); - this.isInitialized = true; - } - } - - public userAuth(email: Email): void { - if (!this.isInitialized) { - return; - } - posthog.identify(email.toString()); - } - - public logout(): void { - if (!this.isInitialized) { - return; - } - posthog.reset(); - } - - public capture(eventName: string, data?: JSONObject): void { - if (!this.isInitialized) { - return; - } - - posthog.capture(eventName, data); - } + posthog.capture(eventName, data); + } } diff --git a/Common/Utils/CronTime.ts b/Common/Utils/CronTime.ts index d648d591bf..4274b78b8f 100644 --- a/Common/Utils/CronTime.ts +++ b/Common/Utils/CronTime.ts @@ -1,7 +1,7 @@ -export const EVERY_MINUTE: string = '* * * * *'; -export const EVERY_DAY: string = '0 8 * * *'; -export const EVERY_HOUR: string = '1 * * * *'; -export const EVERY_FIVE_MINUTE: string = '*/5 * * * *'; -export const EVERY_FIVE_SECONDS: string = '*/5 * * * * *'; -export const EVERY_WEEK: string = '0 0 * * 0'; -export const EVERY_FIFTEEN_MINUTE: string = '*/15 * * * *'; +export const EVERY_MINUTE: string = "* * * * *"; +export const EVERY_DAY: string = "0 8 * * *"; +export const EVERY_HOUR: string = "1 * * * *"; +export const EVERY_FIVE_MINUTE: string = "*/5 * * * *"; +export const EVERY_FIVE_SECONDS: string = "*/5 * * * * *"; +export const EVERY_WEEK: string = "0 0 * * 0"; +export const EVERY_FIFTEEN_MINUTE: string = "*/15 * * * *"; diff --git a/Common/Utils/Enum.ts b/Common/Utils/Enum.ts index 8c0541fd9f..fea2773685 100644 --- a/Common/Utils/Enum.ts +++ b/Common/Utils/Enum.ts @@ -1,16 +1,14 @@ -import GenericObject from '../Types/GenericObject'; +import GenericObject from "../Types/GenericObject"; export default class EnumUtil { - public static isValidEnumValue<T extends GenericObject>( - enumType: T, - value: any - ): boolean { - return this.getValues(enumType).includes(value); - } + public static isValidEnumValue<T extends GenericObject>( + enumType: T, + value: any, + ): boolean { + return this.getValues(enumType).includes(value); + } - public static getValues<T extends GenericObject>( - enumType: T - ): Array<string> { - return Object.values(enumType); - } + public static getValues<T extends GenericObject>(enumType: T): Array<string> { + return Object.values(enumType); + } } diff --git a/Common/Utils/Faker.ts b/Common/Utils/Faker.ts index 63662bc8fd..2a0fe48a1f 100644 --- a/Common/Utils/Faker.ts +++ b/Common/Utils/Faker.ts @@ -1,34 +1,34 @@ -import Email from '../Types/Email'; -import Name from '../Types/Name'; -import Phone from '../Types/Phone'; -import { faker } from '@faker-js/faker'; +import Email from "../Types/Email"; +import Name from "../Types/Name"; +import Phone from "../Types/Phone"; +import { faker } from "@faker-js/faker"; export default class Faker { - public static generateName(): string { - return faker.string.alphanumeric(10); - } + public static generateName(): string { + return faker.string.alphanumeric(10); + } - public static generateCompanyName(): string { - return faker.company.name(); - } + public static generateCompanyName(): string { + return faker.company.name(); + } - public static randomNumbers(count: number): string { - const randomNumbers: Array<number> = []; - for (let i: number = 0; i < count; i++) { - randomNumbers.push(Math.floor(Math.random() * 10)); // You can adjust the range as needed - } - return randomNumbers.join('').substring(0, count); + public static randomNumbers(count: number): string { + const randomNumbers: Array<number> = []; + for (let i: number = 0; i < count; i++) { + randomNumbers.push(Math.floor(Math.random() * 10)); // You can adjust the range as needed } + return randomNumbers.join("").substring(0, count); + } - public static generateUserFullName(): Name { - return new Name(faker.person.fullName()); - } + public static generateUserFullName(): Name { + return new Name(faker.person.fullName()); + } - public static generateEmail(): Email { - return new Email(faker.internet.email()); - } + public static generateEmail(): Email { + return new Email(faker.internet.email()); + } - public static generatePhone(): Phone { - return new Phone(this.randomNumbers(10)); - } + public static generatePhone(): Phone { + return new Phone(this.randomNumbers(10)); + } } diff --git a/Common/Utils/ObjectUtil.ts b/Common/Utils/ObjectUtil.ts index a501d04416..5dd632c668 100644 --- a/Common/Utils/ObjectUtil.ts +++ b/Common/Utils/ObjectUtil.ts @@ -1,8 +1,8 @@ -import { GlobalObject } from '../Types/Object'; +import { GlobalObject } from "../Types/Object"; export default class ObjectUtil { - public static isEmpty(object: GlobalObject): boolean { - // check if object is empty - return Object.keys(object).length === 0; - } + public static isEmpty(object: GlobalObject): boolean { + // check if object is empty + return Object.keys(object).length === 0; + } } diff --git a/Common/Utils/Realtime.ts b/Common/Utils/Realtime.ts index a058a925b4..f5213ee233 100644 --- a/Common/Utils/Realtime.ts +++ b/Common/Utils/Realtime.ts @@ -1,39 +1,39 @@ -import DatabaseType from '../Types/BaseDatabase/DatabaseType'; -import { JSONObject } from '../Types/JSON'; -import ObjectID from '../Types/ObjectID'; +import DatabaseType from "../Types/BaseDatabase/DatabaseType"; +import { JSONObject } from "../Types/JSON"; +import ObjectID from "../Types/ObjectID"; export enum EventName { - ListenToModalEvent = 'ListenToModelEvent', + ListenToModalEvent = "ListenToModelEvent", } export enum ModelEventType { - Create = 'Create', - Update = 'Update', - Delete = 'Delete', + Create = "Create", + Update = "Update", + Delete = "Delete", } export interface ListenToModelEventJSON { - modelName: string; - modelType: DatabaseType; - query: JSONObject; - eventType: ModelEventType; - tenantId: string; - select: JSONObject; + modelName: string; + modelType: DatabaseType; + query: JSONObject; + eventType: ModelEventType; + tenantId: string; + select: JSONObject; } export interface EnableRealtimeEventsOn { - create?: boolean | undefined; - update?: boolean | undefined; - delete?: boolean | undefined; - read?: boolean | undefined; + create?: boolean | undefined; + update?: boolean | undefined; + delete?: boolean | undefined; + read?: boolean | undefined; } export default class RealtimeUtil { - public static getRoomId( - tenantId: string | ObjectID, - modelName: string, - eventType: ModelEventType - ): string { - return tenantId.toString() + '-' + modelName + '-' + eventType; - } + public static getRoomId( + tenantId: string | ObjectID, + modelName: string, + eventType: ModelEventType, + ): string { + return tenantId.toString() + "-" + modelName + "-" + eventType; + } } diff --git a/Common/Utils/Slug.ts b/Common/Utils/Slug.ts index de3e3c22b5..caf2126a36 100644 --- a/Common/Utils/Slug.ts +++ b/Common/Utils/Slug.ts @@ -1,21 +1,21 @@ -import Faker from './Faker'; -import slugify from 'slugify'; +import Faker from "./Faker"; +import slugify from "slugify"; export default class Slug { - public static getSlug(name: string | null): string { - if (name === null) { - name = Faker.generateName(); - } - - name = String(name); - if (!name || !name.trim()) { - return ''; - } - - let slug: string = slugify(name, { remove: /[&*+~.,\\/()|'"!:@]+/g }); - slug = `${slug}-${Faker.randomNumbers(10)}`; - slug = slug.toLowerCase(); - - return slug; + public static getSlug(name: string | null): string { + if (name === null) { + name = Faker.generateName(); } + + name = String(name); + if (!name || !name.trim()) { + return ""; + } + + let slug: string = slugify(name, { remove: /[&*+~.,\\/()|'"!:@]+/g }); + slug = `${slug}-${Faker.randomNumbers(10)}`; + slug = slug.toLowerCase(); + + return slug; + } } diff --git a/Common/Utils/UUID.ts b/Common/Utils/UUID.ts index fdc24ff6d7..3f734419b4 100644 --- a/Common/Utils/UUID.ts +++ b/Common/Utils/UUID.ts @@ -1,7 +1,7 @@ -import { v1 as uuidv1 } from 'uuid'; +import { v1 as uuidv1 } from "uuid"; export default class UUID { - public static generate(): string { - return uuidv1(); - } + public static generate(): string { + return uuidv1(); + } } diff --git a/CommonServer/API/BaseAPI.ts b/CommonServer/API/BaseAPI.ts index 27c9cc8419..a6a412cee7 100644 --- a/CommonServer/API/BaseAPI.ts +++ b/CommonServer/API/BaseAPI.ts @@ -1,470 +1,419 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; -import DatabaseService from '../Services/DatabaseService'; -import CreateBy from '../Types/Database/CreateBy'; -import GroupBy from '../Types/Database/GroupBy'; -import Query from '../Types/Database/Query'; -import Select from '../Types/Database/Select'; -import Sort from '../Types/Database/Sort'; +import UserMiddleware from "../Middleware/UserAuthorization"; +import DatabaseService from "../Services/DatabaseService"; +import CreateBy from "../Types/Database/CreateBy"; +import GroupBy from "../Types/Database/GroupBy"; +import Query from "../Types/Database/Query"; +import Select from "../Types/Database/Select"; +import Sort from "../Types/Database/Sort"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, - OneUptimeRequest, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import CommonAPI from './CommonAPI'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, + OneUptimeRequest, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import CommonAPI from "./CommonAPI"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; import { - DEFAULT_LIMIT, - LIMIT_PER_PROJECT, -} from 'Common/Types/Database/LimitMax'; -import PartialEntity from 'Common/Types/Database/PartialEntity'; -import BadRequestException from 'Common/Types/Exception/BadRequestException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import { UserPermission } from 'Common/Types/Permission'; -import PositiveNumber from 'Common/Types/PositiveNumber'; + DEFAULT_LIMIT, + LIMIT_PER_PROJECT, +} from "Common/Types/Database/LimitMax"; +import PartialEntity from "Common/Types/Database/PartialEntity"; +import BadRequestException from "Common/Types/Exception/BadRequestException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import { UserPermission } from "Common/Types/Permission"; +import PositiveNumber from "Common/Types/PositiveNumber"; export default class BaseAPI< - TBaseModel extends BaseModel, - TBaseService extends DatabaseService<BaseModel> + TBaseModel extends BaseModel, + TBaseService extends DatabaseService<BaseModel>, > { - public entityType: { new (): TBaseModel }; + public entityType: { new (): TBaseModel }; - public router: ExpressRouter; - public service: TBaseService; + public router: ExpressRouter; + public service: TBaseService; - public constructor(type: { new (): TBaseModel }, service: TBaseService) { - this.entityType = type; - const router: ExpressRouter = Express.getRouter(); - // Create - router.post( - `${new this.entityType().getCrudApiPath()?.toString()}`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.createItem(req, res); - } catch (err) { - next(err); - } - } - ); - - // List - router.post( - `${new this.entityType().getCrudApiPath()?.toString()}/get-list`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.getList(req, res); - } catch (err) { - next(err); - } - } - ); - - // List - router.get( - `${new this.entityType().getCrudApiPath()?.toString()}/get-list`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.getList(req, res); - } catch (err) { - next(err); - } - } - ); - - // count - router.post( - `${new this.entityType().getCrudApiPath()?.toString()}/count`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.count(req, res); - } catch (err) { - next(err); - } - } - ); - - // Get Item - router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/:id/get-item`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.getItem(req, res); - } catch (err) { - next(err); - } - } - ); - - // Get Item - router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/:id/get-item`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.getItem(req, res); - } catch (err) { - next(err); - } - } - ); - - // Update - router.put( - `${new this.entityType().getCrudApiPath()?.toString()}/:id`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.updateItem(req, res); - } catch (err) { - next(err); - } - } - ); - - // Delete - router.delete( - `${new this.entityType().getCrudApiPath()?.toString()}/:id`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.deleteItem(req, res); - } catch (err) { - next(err); - } - } - ); - - this.router = router; - this.service = service; - } - - public async getPermissionsForTenant( - req: ExpressRequest - ): Promise<Array<UserPermission>> { - const permissions: Array<UserPermission> = []; - - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(req); - - if ( - props && - props.userTenantAccessPermission && - props.userTenantAccessPermission[props.tenantId?.toString() || ''] - ) { - return ( - props.userTenantAccessPermission[ - props.tenantId?.toString() || '' - ]?.permissions || [] - ); + public constructor(type: { new (): TBaseModel }, service: TBaseService) { + this.entityType = type; + const router: ExpressRouter = Express.getRouter(); + // Create + router.post( + `${new this.entityType().getCrudApiPath()?.toString()}`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.createItem(req, res); + } catch (err) { + next(err); } + }, + ); - return permissions; - } - - public getTenantId(req: ExpressRequest): ObjectID | null { - if ((req as OneUptimeRequest).tenantId) { - return (req as OneUptimeRequest).tenantId as ObjectID; + // List + router.post( + `${new this.entityType().getCrudApiPath()?.toString()}/get-list`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.getList(req, res); + } catch (err) { + next(err); } + }, + ); - return null; - } - - public async getList( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - await this.onBeforeList(req, res); - - const skip: PositiveNumber = req.query['skip'] - ? new PositiveNumber(req.query['skip'] as string) - : new PositiveNumber(0); - - const limit: PositiveNumber = req.query['limit'] - ? new PositiveNumber(req.query['limit'] as string) - : new PositiveNumber(DEFAULT_LIMIT); - - if (limit.toNumber() > LIMIT_PER_PROJECT) { - throw new BadRequestException( - 'Limit should be less than ' + LIMIT_PER_PROJECT - ); + // List + router.get( + `${new this.entityType().getCrudApiPath()?.toString()}/get-list`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.getList(req, res); + } catch (err) { + next(err); } + }, + ); - if (skip.toNumber() < 0) { - throw new BadRequestException( - 'Skip should be greater than or equal to 0' - ); + // count + router.post( + `${new this.entityType().getCrudApiPath()?.toString()}/count`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.count(req, res); + } catch (err) { + next(err); } + }, + ); - if (limit.toNumber() <= 0) { - throw new BadRequestException('Limit should be greater than 0'); + // Get Item + router.post( + `${new this.entityType().getCrudApiPath()?.toString()}/:id/get-item`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.getItem(req, res); + } catch (err) { + next(err); } + }, + ); - let query: Query<BaseModel> = {}; - let select: Select<BaseModel> = {}; - let sort: Sort<BaseModel> = {}; - let groupBy: GroupBy<BaseModel> | undefined; - - if (req.body) { - query = JSONFunctions.deserialize( - req.body['query'] - ) as Query<BaseModel>; - - select = JSONFunctions.deserialize( - req.body['select'] - ) as Select<BaseModel>; - - sort = JSONFunctions.deserialize( - req.body['sort'] - ) as Sort<BaseModel>; - - groupBy = JSONFunctions.deserialize( - req.body['groupBy'] - ) as GroupBy<BaseModel>; + // Get Item + router.get( + `${new this.entityType().getCrudApiPath()?.toString()}/:id/get-item`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.getItem(req, res); + } catch (err) { + next(err); } + }, + ); - const databaseProps: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(req); - - const list: Array<BaseModel> = await this.service.findBy({ - query, - select, - skip: skip, - limit: limit, - groupBy: groupBy, - sort: sort, - props: databaseProps, - }); - - const count: PositiveNumber = await this.service.countBy({ - query, - props: databaseProps, - }); - - return Response.sendEntityArrayResponse( - req, - res, - list, - count, - this.entityType - ); - } - - public async count( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - let query: Query<BaseModel> = {}; - - await this.onBeforeCount(req, res); - - if (req.body) { - query = JSONFunctions.deserialize( - req.body['query'] - ) as Query<BaseModel>; + // Update + router.put( + `${new this.entityType().getCrudApiPath()?.toString()}/:id`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.updateItem(req, res); + } catch (err) { + next(err); } + }, + ); - const databaseProps: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(req); - - const count: PositiveNumber = await this.service.countBy({ - query, - props: databaseProps, - }); - - return Response.sendJsonObjectResponse(req, res, { - count: count.toNumber(), - }); - } - - public async getItem( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - const objectId: ObjectID = new ObjectID(req.params['id'] as string); - await this.onBeforeGet(req, res); - let select: Select<BaseModel> = {}; - - if (req.body) { - select = JSONFunctions.deserialize( - req.body['select'] - ) as Select<BaseModel>; + // Delete + router.delete( + `${new this.entityType().getCrudApiPath()?.toString()}/:id`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.deleteItem(req, res); + } catch (err) { + next(err); } + }, + ); - const item: BaseModel | null = await this.service.findOneById({ - id: objectId, - select, - props: await CommonAPI.getDatabaseCommonInteractionProps(req), - }); + this.router = router; + this.service = service; + } - return Response.sendEntityResponse(req, res, item, this.entityType); + public async getPermissionsForTenant( + req: ExpressRequest, + ): Promise<Array<UserPermission>> { + const permissions: Array<UserPermission> = []; + + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); + + if ( + props && + props.userTenantAccessPermission && + props.userTenantAccessPermission[props.tenantId?.toString() || ""] + ) { + return ( + props.userTenantAccessPermission[props.tenantId?.toString() || ""] + ?.permissions || [] + ); } - public async deleteItem( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - await this.onBeforeDelete(req, res); - const objectId: ObjectID = new ObjectID(req.params['id'] as string); + return permissions; + } - await this.service.deleteOneById({ - id: objectId, - props: await CommonAPI.getDatabaseCommonInteractionProps(req), - }); - - return Response.sendEmptySuccessResponse(req, res); + public getTenantId(req: ExpressRequest): ObjectID | null { + if ((req as OneUptimeRequest).tenantId) { + return (req as OneUptimeRequest).tenantId as ObjectID; } - public async updateItem( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - await this.onBeforeUpdate(req, res); - const objectId: ObjectID = new ObjectID(req.params['id'] as string); - const objectIdString: string = objectId.toString(); - const body: JSONObject = req.body; + return null; + } - const item: PartialEntity<TBaseModel> = JSONFunctions.deserialize( - body['data'] as JSONObject - ) as PartialEntity<TBaseModel>; + public async getList( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + await this.onBeforeList(req, res); - delete (item as any)['_id']; - delete (item as any)['createdAt']; - delete (item as any)['updatedAt']; + const skip: PositiveNumber = req.query["skip"] + ? new PositiveNumber(req.query["skip"] as string) + : new PositiveNumber(0); - await this.service.updateOneById({ - id: new ObjectID(objectIdString), - data: item, - props: await CommonAPI.getDatabaseCommonInteractionProps(req), - }); + const limit: PositiveNumber = req.query["limit"] + ? new PositiveNumber(req.query["limit"] as string) + : new PositiveNumber(DEFAULT_LIMIT); - return Response.sendEmptySuccessResponse(req, res); + if (limit.toNumber() > LIMIT_PER_PROJECT) { + throw new BadRequestException( + "Limit should be less than " + LIMIT_PER_PROJECT, + ); } - public async createItem( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - await this.onBeforeCreate(req, res); - const body: JSONObject = req.body; - - const item: TBaseModel = BaseModel.fromJSON<TBaseModel>( - body['data'] as JSONObject, - this.entityType - ) as TBaseModel; - - const miscDataProps: JSONObject = JSONFunctions.deserialize( - body['miscDataProps'] as JSONObject - ); - - const createBy: CreateBy<TBaseModel> = { - data: item, - miscDataProps: miscDataProps, - props: await CommonAPI.getDatabaseCommonInteractionProps(req), - }; - - const savedItem: BaseModel = await this.service.create(createBy); - - return Response.sendEntityResponse( - req, - res, - savedItem, - this.entityType - ); + if (skip.toNumber() < 0) { + throw new BadRequestException( + "Skip should be greater than or equal to 0", + ); } - public getRouter(): ExpressRouter { - return this.router; + if (limit.toNumber() <= 0) { + throw new BadRequestException("Limit should be greater than 0"); } - public getEntityName(): string { - return this.entityType.name; + let query: Query<BaseModel> = {}; + let select: Select<BaseModel> = {}; + let sort: Sort<BaseModel> = {}; + let groupBy: GroupBy<BaseModel> | undefined; + + if (req.body) { + query = JSONFunctions.deserialize(req.body["query"]) as Query<BaseModel>; + + select = JSONFunctions.deserialize( + req.body["select"], + ) as Select<BaseModel>; + + sort = JSONFunctions.deserialize(req.body["sort"]) as Sort<BaseModel>; + + groupBy = JSONFunctions.deserialize( + req.body["groupBy"], + ) as GroupBy<BaseModel>; } - protected async onBeforeList( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); + const databaseProps: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); + + const list: Array<BaseModel> = await this.service.findBy({ + query, + select, + skip: skip, + limit: limit, + groupBy: groupBy, + sort: sort, + props: databaseProps, + }); + + const count: PositiveNumber = await this.service.countBy({ + query, + props: databaseProps, + }); + + return Response.sendEntityArrayResponse( + req, + res, + list, + count, + this.entityType, + ); + } + + public async count(req: ExpressRequest, res: ExpressResponse): Promise<void> { + let query: Query<BaseModel> = {}; + + await this.onBeforeCount(req, res); + + if (req.body) { + query = JSONFunctions.deserialize(req.body["query"]) as Query<BaseModel>; } - protected async onBeforeCreate( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); + const databaseProps: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); + + const count: PositiveNumber = await this.service.countBy({ + query, + props: databaseProps, + }); + + return Response.sendJsonObjectResponse(req, res, { + count: count.toNumber(), + }); + } + + public async getItem( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + const objectId: ObjectID = new ObjectID(req.params["id"] as string); + await this.onBeforeGet(req, res); + let select: Select<BaseModel> = {}; + + if (req.body) { + select = JSONFunctions.deserialize( + req.body["select"], + ) as Select<BaseModel>; } - protected async onBeforeGet( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + const item: BaseModel | null = await this.service.findOneById({ + id: objectId, + select, + props: await CommonAPI.getDatabaseCommonInteractionProps(req), + }); - protected async onBeforeUpdate( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + return Response.sendEntityResponse(req, res, item, this.entityType); + } - protected async onBeforeDelete( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + public async deleteItem( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + await this.onBeforeDelete(req, res); + const objectId: ObjectID = new ObjectID(req.params["id"] as string); - protected async onBeforeCount( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + await this.service.deleteOneById({ + id: objectId, + props: await CommonAPI.getDatabaseCommonInteractionProps(req), + }); + + return Response.sendEmptySuccessResponse(req, res); + } + + public async updateItem( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + await this.onBeforeUpdate(req, res); + const objectId: ObjectID = new ObjectID(req.params["id"] as string); + const objectIdString: string = objectId.toString(); + const body: JSONObject = req.body; + + const item: PartialEntity<TBaseModel> = JSONFunctions.deserialize( + body["data"] as JSONObject, + ) as PartialEntity<TBaseModel>; + + delete (item as any)["_id"]; + delete (item as any)["createdAt"]; + delete (item as any)["updatedAt"]; + + await this.service.updateOneById({ + id: new ObjectID(objectIdString), + data: item, + props: await CommonAPI.getDatabaseCommonInteractionProps(req), + }); + + return Response.sendEmptySuccessResponse(req, res); + } + + public async createItem( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + await this.onBeforeCreate(req, res); + const body: JSONObject = req.body; + + const item: TBaseModel = BaseModel.fromJSON<TBaseModel>( + body["data"] as JSONObject, + this.entityType, + ) as TBaseModel; + + const miscDataProps: JSONObject = JSONFunctions.deserialize( + body["miscDataProps"] as JSONObject, + ); + + const createBy: CreateBy<TBaseModel> = { + data: item, + miscDataProps: miscDataProps, + props: await CommonAPI.getDatabaseCommonInteractionProps(req), + }; + + const savedItem: BaseModel = await this.service.create(createBy); + + return Response.sendEntityResponse(req, res, savedItem, this.entityType); + } + + public getRouter(): ExpressRouter { + return this.router; + } + + public getEntityName(): string { + return this.entityType.name; + } + + protected async onBeforeList( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } + + protected async onBeforeCreate( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } + + protected async onBeforeGet( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } + + protected async onBeforeUpdate( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } + + protected async onBeforeDelete( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } + + protected async onBeforeCount( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } } diff --git a/CommonServer/API/BaseAnalyticsAPI.ts b/CommonServer/API/BaseAnalyticsAPI.ts index caaeb7e21a..e1c9f5dd55 100644 --- a/CommonServer/API/BaseAnalyticsAPI.ts +++ b/CommonServer/API/BaseAnalyticsAPI.ts @@ -1,463 +1,420 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; -import AnalyticsDatabaseService from '../Services/AnalyticsDatabaseService'; -import CreateBy from '../Types/AnalyticsDatabase/CreateBy'; -import GroupBy from '../Types/AnalyticsDatabase/GroupBy'; -import Query from '../Types/AnalyticsDatabase/Query'; -import Select from '../Types/AnalyticsDatabase/Select'; -import Sort from '../Types/AnalyticsDatabase/Sort'; +import UserMiddleware from "../Middleware/UserAuthorization"; +import AnalyticsDatabaseService from "../Services/AnalyticsDatabaseService"; +import CreateBy from "../Types/AnalyticsDatabase/CreateBy"; +import GroupBy from "../Types/AnalyticsDatabase/GroupBy"; +import Query from "../Types/AnalyticsDatabase/Query"; +import Select from "../Types/AnalyticsDatabase/Select"; +import Sort from "../Types/AnalyticsDatabase/Sort"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, - OneUptimeRequest, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import CommonAPI from './CommonAPI'; -import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, + OneUptimeRequest, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import CommonAPI from "./CommonAPI"; +import AnalyticsDataModel from "Common/AnalyticsModels/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; import { - DEFAULT_LIMIT, - LIMIT_PER_PROJECT, -} from 'Common/Types/Database/LimitMax'; -import BadRequestException from 'Common/Types/Exception/BadRequestException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import { UserPermission } from 'Common/Types/Permission'; -import PositiveNumber from 'Common/Types/PositiveNumber'; + DEFAULT_LIMIT, + LIMIT_PER_PROJECT, +} from "Common/Types/Database/LimitMax"; +import BadRequestException from "Common/Types/Exception/BadRequestException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import { UserPermission } from "Common/Types/Permission"; +import PositiveNumber from "Common/Types/PositiveNumber"; export default class BaseAnalyticsAPI< - TAnalyticsDataModel extends AnalyticsDataModel, - TBaseService extends AnalyticsDatabaseService<AnalyticsDataModel> + TAnalyticsDataModel extends AnalyticsDataModel, + TBaseService extends AnalyticsDatabaseService<AnalyticsDataModel>, > { - public entityType: { new (): TAnalyticsDataModel }; + public entityType: { new (): TAnalyticsDataModel }; - public router: ExpressRouter; - public service: TBaseService; + public router: ExpressRouter; + public service: TBaseService; - public constructor( - type: { new (): TAnalyticsDataModel }, - service: TBaseService + public constructor( + type: { new (): TAnalyticsDataModel }, + service: TBaseService, + ) { + this.entityType = type; + const router: ExpressRouter = Express.getRouter(); + // Create + router.post( + `${new this.entityType().crudApiPath.toString()}`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.createItem(req, res); + } catch (err) { + next(err); + } + }, + ); + + // List + router.post( + `${new this.entityType().crudApiPath?.toString()}/get-list`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.getList(req, res); + } catch (err) { + next(err); + } + }, + ); + + // List + router.get( + `${new this.entityType().crudApiPath?.toString()}/get-list`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.getList(req, res); + } catch (err) { + next(err); + } + }, + ); + + // count + router.post( + `${new this.entityType().crudApiPath?.toString()}/count`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.count(req, res); + } catch (err) { + next(err); + } + }, + ); + + // Get Item + router.post( + `${new this.entityType().crudApiPath?.toString()}/:id/get-item`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.getItem(req, res); + } catch (err) { + next(err); + } + }, + ); + + // Get Item + router.get( + `${new this.entityType().crudApiPath?.toString()}/:id/get-item`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.getItem(req, res); + } catch (err) { + next(err); + } + }, + ); + + // Update + router.put( + `${new this.entityType().crudApiPath?.toString()}/:id`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.updateItem(req, res); + } catch (err) { + next(err); + } + }, + ); + + // Delete + router.delete( + `${new this.entityType().crudApiPath?.toString()}/:id`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.deleteItem(req, res); + } catch (err) { + next(err); + } + }, + ); + + this.router = router; + this.service = service; + } + + public async getPermissionsForTenant( + req: ExpressRequest, + ): Promise<Array<UserPermission>> { + const permissions: Array<UserPermission> = []; + + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); + + if ( + props && + props.userTenantAccessPermission && + props.userTenantAccessPermission[props.tenantId?.toString() || ""] ) { - this.entityType = type; - const router: ExpressRouter = Express.getRouter(); - // Create - router.post( - `${new this.entityType().crudApiPath.toString()}`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.createItem(req, res); - } catch (err) { - next(err); - } - } - ); - - // List - router.post( - `${new this.entityType().crudApiPath?.toString()}/get-list`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.getList(req, res); - } catch (err) { - next(err); - } - } - ); - - // List - router.get( - `${new this.entityType().crudApiPath?.toString()}/get-list`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.getList(req, res); - } catch (err) { - next(err); - } - } - ); - - // count - router.post( - `${new this.entityType().crudApiPath?.toString()}/count`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.count(req, res); - } catch (err) { - next(err); - } - } - ); - - // Get Item - router.post( - `${new this.entityType().crudApiPath?.toString()}/:id/get-item`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.getItem(req, res); - } catch (err) { - next(err); - } - } - ); - - // Get Item - router.get( - `${new this.entityType().crudApiPath?.toString()}/:id/get-item`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.getItem(req, res); - } catch (err) { - next(err); - } - } - ); - - // Update - router.put( - `${new this.entityType().crudApiPath?.toString()}/:id`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.updateItem(req, res); - } catch (err) { - next(err); - } - } - ); - - // Delete - router.delete( - `${new this.entityType().crudApiPath?.toString()}/:id`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.deleteItem(req, res); - } catch (err) { - next(err); - } - } - ); - - this.router = router; - this.service = service; + return ( + props.userTenantAccessPermission[props.tenantId?.toString() || ""] + ?.permissions || [] + ); } - public async getPermissionsForTenant( - req: ExpressRequest - ): Promise<Array<UserPermission>> { - const permissions: Array<UserPermission> = []; + return permissions; + } - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(req); - - if ( - props && - props.userTenantAccessPermission && - props.userTenantAccessPermission[props.tenantId?.toString() || ''] - ) { - return ( - props.userTenantAccessPermission[ - props.tenantId?.toString() || '' - ]?.permissions || [] - ); - } - - return permissions; + public getTenantId(req: ExpressRequest): ObjectID | null { + if ((req as OneUptimeRequest).tenantId) { + return (req as OneUptimeRequest).tenantId as ObjectID; } - public getTenantId(req: ExpressRequest): ObjectID | null { - if ((req as OneUptimeRequest).tenantId) { - return (req as OneUptimeRequest).tenantId as ObjectID; - } + return null; + } - return null; + public async getList( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + await this.onBeforeList(req, res); + + const skip: PositiveNumber = req.query["skip"] + ? new PositiveNumber(req.query["skip"] as string) + : new PositiveNumber(0); + + const limit: PositiveNumber = req.query["limit"] + ? new PositiveNumber(req.query["limit"] as string) + : new PositiveNumber(DEFAULT_LIMIT); + + if (limit.toNumber() > LIMIT_PER_PROJECT) { + throw new BadRequestException( + "Limit should be less than " + LIMIT_PER_PROJECT, + ); } - public async getList( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - await this.onBeforeList(req, res); + let query: Query<AnalyticsDataModel> = {}; + let select: Select<AnalyticsDataModel> = {}; + let sort: Sort<AnalyticsDataModel> = {}; + let groupBy: GroupBy<AnalyticsDataModel> = {}; - const skip: PositiveNumber = req.query['skip'] - ? new PositiveNumber(req.query['skip'] as string) - : new PositiveNumber(0); + if (req.body) { + query = JSONFunctions.deserialize( + req.body["query"], + ) as Query<AnalyticsDataModel>; - const limit: PositiveNumber = req.query['limit'] - ? new PositiveNumber(req.query['limit'] as string) - : new PositiveNumber(DEFAULT_LIMIT); + select = JSONFunctions.deserialize( + req.body["select"], + ) as Select<AnalyticsDataModel>; - if (limit.toNumber() > LIMIT_PER_PROJECT) { - throw new BadRequestException( - 'Limit should be less than ' + LIMIT_PER_PROJECT - ); - } + sort = JSONFunctions.deserialize( + req.body["sort"], + ) as Sort<AnalyticsDataModel>; - let query: Query<AnalyticsDataModel> = {}; - let select: Select<AnalyticsDataModel> = {}; - let sort: Sort<AnalyticsDataModel> = {}; - let groupBy: GroupBy<AnalyticsDataModel> = {}; - - if (req.body) { - query = JSONFunctions.deserialize( - req.body['query'] - ) as Query<AnalyticsDataModel>; - - select = JSONFunctions.deserialize( - req.body['select'] - ) as Select<AnalyticsDataModel>; - - sort = JSONFunctions.deserialize( - req.body['sort'] - ) as Sort<AnalyticsDataModel>; - - groupBy = JSONFunctions.deserialize( - req.body['groupBy'] - ) as GroupBy<AnalyticsDataModel>; - } - - const databaseProps: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(req); - - const list: Array<AnalyticsDataModel> = await this.service.findBy({ - query, - select, - skip: skip, - limit: limit, - sort: sort, - groupBy: groupBy, - props: databaseProps, - }); - - const count: PositiveNumber = await this.service.countBy({ - query, - groupBy: groupBy, - props: databaseProps, - }); - - return Response.sendEntityArrayResponse( - req, - res, - list, - count, - this.entityType - ); + groupBy = JSONFunctions.deserialize( + req.body["groupBy"], + ) as GroupBy<AnalyticsDataModel>; } - public async count( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - let query: Query<AnalyticsDataModel> = {}; + const databaseProps: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); - await this.onBeforeCount(req, res); + const list: Array<AnalyticsDataModel> = await this.service.findBy({ + query, + select, + skip: skip, + limit: limit, + sort: sort, + groupBy: groupBy, + props: databaseProps, + }); - if (req.body) { - query = JSONFunctions.deserialize( - req.body['query'] - ) as Query<AnalyticsDataModel>; - } + const count: PositiveNumber = await this.service.countBy({ + query, + groupBy: groupBy, + props: databaseProps, + }); - const databaseProps: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(req); + return Response.sendEntityArrayResponse( + req, + res, + list, + count, + this.entityType, + ); + } - const count: PositiveNumber = await this.service.countBy({ - query, - props: databaseProps, - }); + public async count(req: ExpressRequest, res: ExpressResponse): Promise<void> { + let query: Query<AnalyticsDataModel> = {}; - return Response.sendJsonObjectResponse(req, res, { - count: count.toNumber(), - }); + await this.onBeforeCount(req, res); + + if (req.body) { + query = JSONFunctions.deserialize( + req.body["query"], + ) as Query<AnalyticsDataModel>; } - public async getItem( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - const objectId: ObjectID = new ObjectID(req.params['id'] as string); - await this.onBeforeGet(req, res); - let select: Select<AnalyticsDataModel> = {}; + const databaseProps: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); - if (req.body) { - select = JSONFunctions.deserialize( - req.body['select'] - ) as Select<AnalyticsDataModel>; - } + const count: PositiveNumber = await this.service.countBy({ + query, + props: databaseProps, + }); - const item: AnalyticsDataModel | null = await this.service.findOneById({ - id: objectId, - select, - props: await CommonAPI.getDatabaseCommonInteractionProps(req), - }); + return Response.sendJsonObjectResponse(req, res, { + count: count.toNumber(), + }); + } - return Response.sendEntityResponse(req, res, item, this.entityType); + public async getItem( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + const objectId: ObjectID = new ObjectID(req.params["id"] as string); + await this.onBeforeGet(req, res); + let select: Select<AnalyticsDataModel> = {}; + + if (req.body) { + select = JSONFunctions.deserialize( + req.body["select"], + ) as Select<AnalyticsDataModel>; } - public async deleteItem( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - await this.onBeforeDelete(req, res); - const objectId: ObjectID = new ObjectID(req.params['id'] as string); + const item: AnalyticsDataModel | null = await this.service.findOneById({ + id: objectId, + select, + props: await CommonAPI.getDatabaseCommonInteractionProps(req), + }); - await this.service.deleteBy({ - query: { - _id: objectId.toString(), - }, - props: await CommonAPI.getDatabaseCommonInteractionProps(req), - }); + return Response.sendEntityResponse(req, res, item, this.entityType); + } - return Response.sendEmptySuccessResponse(req, res); - } + public async deleteItem( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + await this.onBeforeDelete(req, res); + const objectId: ObjectID = new ObjectID(req.params["id"] as string); - public async updateItem( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - await this.onBeforeUpdate(req, res); - const objectId: ObjectID = new ObjectID(req.params['id'] as string); - const objectIdString: string = objectId.toString(); - const body: JSONObject = req.body; + await this.service.deleteBy({ + query: { + _id: objectId.toString(), + }, + props: await CommonAPI.getDatabaseCommonInteractionProps(req), + }); - const item: TAnalyticsDataModel = - AnalyticsDataModel.fromJSON<TAnalyticsDataModel>( - body['data'] as JSONObject, - this.entityType - ) as TAnalyticsDataModel; + return Response.sendEmptySuccessResponse(req, res); + } - delete (item as any)['_id']; - delete (item as any)['createdAt']; - delete (item as any)['updatedAt']; + public async updateItem( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + await this.onBeforeUpdate(req, res); + const objectId: ObjectID = new ObjectID(req.params["id"] as string); + const objectIdString: string = objectId.toString(); + const body: JSONObject = req.body; - await this.service.updateBy({ - query: { - _id: objectIdString, - }, - data: item, - props: await CommonAPI.getDatabaseCommonInteractionProps(req), - }); + const item: TAnalyticsDataModel = + AnalyticsDataModel.fromJSON<TAnalyticsDataModel>( + body["data"] as JSONObject, + this.entityType, + ) as TAnalyticsDataModel; - return Response.sendEmptySuccessResponse(req, res); - } + delete (item as any)["_id"]; + delete (item as any)["createdAt"]; + delete (item as any)["updatedAt"]; - public async createItem( - req: ExpressRequest, - res: ExpressResponse - ): Promise<void> { - await this.onBeforeCreate(req, res); - const body: JSONObject = req.body; + await this.service.updateBy({ + query: { + _id: objectIdString, + }, + data: item, + props: await CommonAPI.getDatabaseCommonInteractionProps(req), + }); - const item: TAnalyticsDataModel = - AnalyticsDataModel.fromJSON<TAnalyticsDataModel>( - body['data'] as JSONObject, - this.entityType - ) as TAnalyticsDataModel; + return Response.sendEmptySuccessResponse(req, res); + } - const createBy: CreateBy<TAnalyticsDataModel> = { - data: item, - props: await CommonAPI.getDatabaseCommonInteractionProps(req), - }; + public async createItem( + req: ExpressRequest, + res: ExpressResponse, + ): Promise<void> { + await this.onBeforeCreate(req, res); + const body: JSONObject = req.body; - const savedItem: AnalyticsDataModel = await this.service.create( - createBy - ); + const item: TAnalyticsDataModel = + AnalyticsDataModel.fromJSON<TAnalyticsDataModel>( + body["data"] as JSONObject, + this.entityType, + ) as TAnalyticsDataModel; - return Response.sendEntityResponse( - req, - res, - savedItem, - this.entityType - ); - } + const createBy: CreateBy<TAnalyticsDataModel> = { + data: item, + props: await CommonAPI.getDatabaseCommonInteractionProps(req), + }; - public getRouter(): ExpressRouter { - return this.router; - } + const savedItem: AnalyticsDataModel = await this.service.create(createBy); - public getEntityName(): string { - return this.entityType.name; - } + return Response.sendEntityResponse(req, res, savedItem, this.entityType); + } - protected async onBeforeList( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + public getRouter(): ExpressRouter { + return this.router; + } - protected async onBeforeCreate( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + public getEntityName(): string { + return this.entityType.name; + } - protected async onBeforeGet( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + protected async onBeforeList( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } - protected async onBeforeUpdate( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + protected async onBeforeCreate( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } - protected async onBeforeDelete( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + protected async onBeforeGet( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } - protected async onBeforeCount( - _req: ExpressRequest, - _res: ExpressResponse - ): Promise<any> { - return Promise.resolve(true); - } + protected async onBeforeUpdate( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } + + protected async onBeforeDelete( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } + + protected async onBeforeCount( + _req: ExpressRequest, + _res: ExpressResponse, + ): Promise<any> { + return Promise.resolve(true); + } } diff --git a/CommonServer/API/BillingInvoiceAPI.ts b/CommonServer/API/BillingInvoiceAPI.ts index 46911a3bdf..36a1accb73 100644 --- a/CommonServer/API/BillingInvoiceAPI.ts +++ b/CommonServer/API/BillingInvoiceAPI.ts @@ -1,176 +1,164 @@ -import { IsBillingEnabled } from '../EnvironmentConfig'; -import UserMiddleware from '../Middleware/UserAuthorization'; +import { IsBillingEnabled } from "../EnvironmentConfig"; +import UserMiddleware from "../Middleware/UserAuthorization"; import BillingInvoiceService, { - Service as BillingInvoiceServiceType, -} from '../Services/BillingInvoiceService'; -import BillingService, { Invoice } from '../Services/BillingService'; -import ProjectService from '../Services/ProjectService'; + Service as BillingInvoiceServiceType, +} from "../Services/BillingInvoiceService"; +import BillingService, { Invoice } from "../Services/BillingService"; +import ProjectService from "../Services/ProjectService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import BaseModel from 'Common/Models/BaseModel'; -import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import Permission, { UserPermission } from 'Common/Types/Permission'; -import BillingInvoice, { InvoiceStatus } from 'Model/Models/BillingInvoice'; -import Project from 'Model/Models/Project'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import BaseModel from "Common/Models/BaseModel"; +import SubscriptionStatus from "Common/Types/Billing/SubscriptionStatus"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import Permission, { UserPermission } from "Common/Types/Permission"; +import BillingInvoice, { InvoiceStatus } from "Model/Models/BillingInvoice"; +import Project from "Model/Models/Project"; export default class UserAPI extends BaseAPI< - BillingInvoice, - BillingInvoiceServiceType + BillingInvoice, + BillingInvoiceServiceType > { - public constructor() { - super(BillingInvoice, BillingInvoiceService); + public constructor() { + super(BillingInvoice, BillingInvoiceService); - this.router.post( - `${new this.entityType().getCrudApiPath()?.toString()}/pay`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - if (!IsBillingEnabled) { - throw new BadDataException( - 'Billing is not enabled for this server' - ); - } + this.router.post( + `${new this.entityType().getCrudApiPath()?.toString()}/pay`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + if (!IsBillingEnabled) { + throw new BadDataException( + "Billing is not enabled for this server", + ); + } - if (req.body['projectId']) { - throw new BadDataException( - 'projectId is required in request body' - ); - } + if (req.body["projectId"]) { + throw new BadDataException("projectId is required in request body"); + } - const userPermissions: Array<UserPermission> = ( - await this.getPermissionsForTenant(req) - ).filter((permission: UserPermission) => { - return ( - permission.permission.toString() === - Permission.ProjectOwner.toString() || - permission.permission.toString() === - Permission.EditInvoices.toString() - ); - }); + const userPermissions: Array<UserPermission> = ( + await this.getPermissionsForTenant(req) + ).filter((permission: UserPermission) => { + return ( + permission.permission.toString() === + Permission.ProjectOwner.toString() || + permission.permission.toString() === + Permission.EditInvoices.toString() + ); + }); - if (userPermissions.length === 0) { - throw new BadDataException( - `You need ${Permission.ProjectOwner} or ${Permission.EditInvoices} permission to pay invoices.` - ); - } + if (userPermissions.length === 0) { + throw new BadDataException( + `You need ${Permission.ProjectOwner} or ${Permission.EditInvoices} permission to pay invoices.`, + ); + } - const project: Project | null = - await ProjectService.findOneById({ - id: this.getTenantId(req)!, - props: { - isRoot: true, - }, - select: { - _id: true, - paymentProviderCustomerId: true, - paymentProviderSubscriptionId: true, - paymentProviderMeteredSubscriptionId: true, - }, - }); + const project: Project | null = await ProjectService.findOneById({ + id: this.getTenantId(req)!, + props: { + isRoot: true, + }, + select: { + _id: true, + paymentProviderCustomerId: true, + paymentProviderSubscriptionId: true, + paymentProviderMeteredSubscriptionId: true, + }, + }); - if (!project) { - throw new BadDataException('Project not found'); - } + if (!project) { + throw new BadDataException("Project not found"); + } - if (!project.paymentProviderCustomerId) { - throw new BadDataException( - 'Payment Provider customer not found' - ); - } + if (!project.paymentProviderCustomerId) { + throw new BadDataException("Payment Provider customer not found"); + } - if (!project.paymentProviderSubscriptionId) { - throw new BadDataException( - 'Payment Provider subscription not found' - ); - } + if (!project.paymentProviderSubscriptionId) { + throw new BadDataException( + "Payment Provider subscription not found", + ); + } - const body: JSONObject = req.body; + const body: JSONObject = req.body; - const item: BillingInvoice = - BaseModel.fromJSON<BillingInvoice>( - body['data'] as JSONObject, - this.entityType - ) as BillingInvoice; + const item: BillingInvoice = BaseModel.fromJSON<BillingInvoice>( + body["data"] as JSONObject, + this.entityType, + ) as BillingInvoice; - if (!item.paymentProviderInvoiceId) { - throw new BadDataException('Invoice ID not found'); - } + if (!item.paymentProviderInvoiceId) { + throw new BadDataException("Invoice ID not found"); + } - if (!item.paymentProviderCustomerId) { - throw new BadDataException('Customer ID not found'); - } + if (!item.paymentProviderCustomerId) { + throw new BadDataException("Customer ID not found"); + } - const invoice: Invoice = await BillingService.payInvoice( - item.paymentProviderCustomerId!, - item.paymentProviderInvoiceId! - ); + const invoice: Invoice = await BillingService.payInvoice( + item.paymentProviderCustomerId!, + item.paymentProviderInvoiceId!, + ); - // save updated status. + // save updated status. - await this.service.updateOneBy({ - query: { - paymentProviderInvoiceId: invoice.id!, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - data: { - status: invoice.status as InvoiceStatus, - }, - }); + await this.service.updateOneBy({ + query: { + paymentProviderInvoiceId: invoice.id!, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + data: { + status: invoice.status as InvoiceStatus, + }, + }); - // refresh subscription status. - const subscriptionState: SubscriptionStatus = - await BillingService.getSubscriptionStatus( - project.paymentProviderSubscriptionId as string - ); + // refresh subscription status. + const subscriptionState: SubscriptionStatus = + await BillingService.getSubscriptionStatus( + project.paymentProviderSubscriptionId as string, + ); - const meteredSubscriptionState: SubscriptionStatus = - await BillingService.getSubscriptionStatus( - project.paymentProviderMeteredSubscriptionId as string - ); + const meteredSubscriptionState: SubscriptionStatus = + await BillingService.getSubscriptionStatus( + project.paymentProviderMeteredSubscriptionId as string, + ); - // if subscription is cancelled, create a new subscription and update project. + // if subscription is cancelled, create a new subscription and update project. - if ( - meteredSubscriptionState === - SubscriptionStatus.Canceled || - subscriptionState === SubscriptionStatus.Canceled - ) { - await ProjectService.reactiveSubscription(project.id!); - } + if ( + meteredSubscriptionState === SubscriptionStatus.Canceled || + subscriptionState === SubscriptionStatus.Canceled + ) { + await ProjectService.reactiveSubscription(project.id!); + } - await ProjectService.updateOneById({ - id: project.id!, - data: { - paymentProviderSubscriptionStatus: - subscriptionState, - paymentProviderMeteredSubscriptionStatus: - meteredSubscriptionState, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + await ProjectService.updateOneById({ + id: project.id!, + data: { + paymentProviderSubscriptionStatus: subscriptionState, + paymentProviderMeteredSubscriptionStatus: + meteredSubscriptionState, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - next(err); - } - } - ); - } + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + next(err); + } + }, + ); + } } diff --git a/CommonServer/API/BillingPaymentMethodAPI.ts b/CommonServer/API/BillingPaymentMethodAPI.ts index 61dca71bb2..de66baf40a 100644 --- a/CommonServer/API/BillingPaymentMethodAPI.ts +++ b/CommonServer/API/BillingPaymentMethodAPI.ts @@ -1,105 +1,95 @@ -import { IsBillingEnabled } from '../EnvironmentConfig'; -import UserMiddleware from '../Middleware/UserAuthorization'; +import { IsBillingEnabled } from "../EnvironmentConfig"; +import UserMiddleware from "../Middleware/UserAuthorization"; import BillingPaymentMethodService, { - Service as BillingPaymentMethodServiceType, -} from '../Services/BillingPaymentMethodService'; -import BillingService from '../Services/BillingService'; -import ProjectService from '../Services/ProjectService'; + Service as BillingPaymentMethodServiceType, +} from "../Services/BillingPaymentMethodService"; +import BillingService from "../Services/BillingService"; +import ProjectService from "../Services/ProjectService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Permission, { UserPermission } from 'Common/Types/Permission'; -import BillingPaymentMethod from 'Model/Models/BillingPaymentMethod'; -import Project from 'Model/Models/Project'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Permission, { UserPermission } from "Common/Types/Permission"; +import BillingPaymentMethod from "Model/Models/BillingPaymentMethod"; +import Project from "Model/Models/Project"; export default class UserAPI extends BaseAPI< - BillingPaymentMethod, - BillingPaymentMethodServiceType + BillingPaymentMethod, + BillingPaymentMethodServiceType > { - public constructor() { - super(BillingPaymentMethod, BillingPaymentMethodService); + public constructor() { + super(BillingPaymentMethod, BillingPaymentMethodService); - this.router.post( - `${new this.entityType().getCrudApiPath()?.toString()}/setup`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - if (!IsBillingEnabled) { - throw new BadDataException( - 'Billing is not enabled for this server' - ); - } + this.router.post( + `${new this.entityType().getCrudApiPath()?.toString()}/setup`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + if (!IsBillingEnabled) { + throw new BadDataException( + "Billing is not enabled for this server", + ); + } - if (req.body['projectId']) { - throw new BadDataException( - 'projectId is required in request body' - ); - } + if (req.body["projectId"]) { + throw new BadDataException("projectId is required in request body"); + } - const userPermissions: Array<UserPermission> = ( - await this.getPermissionsForTenant(req) - ).filter((permission: UserPermission) => { - return ( - permission.permission.toString() === - Permission.ProjectOwner.toString() || - permission.permission.toString() === - Permission.CreateBillingPaymentMethod.toString() - ); - }); + const userPermissions: Array<UserPermission> = ( + await this.getPermissionsForTenant(req) + ).filter((permission: UserPermission) => { + return ( + permission.permission.toString() === + Permission.ProjectOwner.toString() || + permission.permission.toString() === + Permission.CreateBillingPaymentMethod.toString() + ); + }); - if (userPermissions.length === 0) { - throw new BadDataException( - 'Only Project owner can add payment methods.' - ); - } + if (userPermissions.length === 0) { + throw new BadDataException( + "Only Project owner can add payment methods.", + ); + } - const project: Project | null = - await ProjectService.findOneById({ - id: this.getTenantId(req)!, - props: { - isRoot: true, - }, - select: { - _id: true, - paymentProviderCustomerId: true, - }, - }); + const project: Project | null = await ProjectService.findOneById({ + id: this.getTenantId(req)!, + props: { + isRoot: true, + }, + select: { + _id: true, + paymentProviderCustomerId: true, + }, + }); - if (!project) { - throw new BadDataException('Project not found'); - } + if (!project) { + throw new BadDataException("Project not found"); + } - if (!project) { - throw new BadDataException('Project not found'); - } + if (!project) { + throw new BadDataException("Project not found"); + } - if (!project.paymentProviderCustomerId) { - throw new BadDataException( - 'Payment Provider customer not found' - ); - } + if (!project.paymentProviderCustomerId) { + throw new BadDataException("Payment Provider customer not found"); + } - const setupIntent: string = - await BillingService.getSetupIntentSecret( - project.paymentProviderCustomerId - ); + const setupIntent: string = await BillingService.getSetupIntentSecret( + project.paymentProviderCustomerId, + ); - return Response.sendJsonObjectResponse(req, res, { - setupIntent: setupIntent, - }); - } catch (err) { - next(err); - } - } - ); - } + return Response.sendJsonObjectResponse(req, res, { + setupIntent: setupIntent, + }); + } catch (err) { + next(err); + } + }, + ); + } } diff --git a/CommonServer/API/CodeRepositoryAPI.ts b/CommonServer/API/CodeRepositoryAPI.ts index 6102ae4b9a..76e7a8a463 100644 --- a/CommonServer/API/CodeRepositoryAPI.ts +++ b/CommonServer/API/CodeRepositoryAPI.ts @@ -1,194 +1,181 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; +import UserMiddleware from "../Middleware/UserAuthorization"; import CodeRepositoryService, { - Service as CodeRepositoryServiceType, -} from '../Services/CodeRepositoryService'; -import CopilotEventService from '../Services/CopilotEventService'; -import ServiceRepositoryService from '../Services/ServiceRepositoryService'; + Service as CodeRepositoryServiceType, +} from "../Services/CodeRepositoryService"; +import CopilotEventService from "../Services/CopilotEventService"; +import ServiceRepositoryService from "../Services/ServiceRepositoryService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import CodeRepository from 'Model/Models/CodeRepository'; -import CopilotEvent from 'Model/Models/CopilotEvent'; -import ServiceRepository from 'Model/Models/ServiceRepository'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import CodeRepository from "Model/Models/CodeRepository"; +import CopilotEvent from "Model/Models/CopilotEvent"; +import ServiceRepository from "Model/Models/ServiceRepository"; export default class CodeRepositoryAPI extends BaseAPI< - CodeRepository, - CodeRepositoryServiceType + CodeRepository, + CodeRepositoryServiceType > { - public constructor() { - super(CodeRepository, CodeRepositoryService); + public constructor() { + super(CodeRepository, CodeRepositoryService); - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/get-code-repository/:secretkey`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const secretkey: string = req.params['secretkey']!; + this.router.get( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/get-code-repository/:secretkey`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const secretkey: string = req.params["secretkey"]!; - if (!secretkey) { - throw new BadDataException('Secret key is required'); - } + if (!secretkey) { + throw new BadDataException("Secret key is required"); + } - const codeRepository: CodeRepository | null = - await CodeRepositoryService.findOneBy({ - query: { - secretToken: new ObjectID(secretkey), - }, - select: { - name: true, - mainBranchName: true, - organizationName: true, - repositoryHostedAt: true, - repositoryName: true, - }, - props: { - isRoot: true, - }, - }); + const codeRepository: CodeRepository | null = + await CodeRepositoryService.findOneBy({ + query: { + secretToken: new ObjectID(secretkey), + }, + select: { + name: true, + mainBranchName: true, + organizationName: true, + repositoryHostedAt: true, + repositoryName: true, + }, + props: { + isRoot: true, + }, + }); - if (!codeRepository) { - throw new BadDataException( - 'Code repository not found. Secret key is invalid.' - ); - } + if (!codeRepository) { + throw new BadDataException( + "Code repository not found. Secret key is invalid.", + ); + } - const servicesRepository: Array<ServiceRepository> = - await ServiceRepositoryService.findBy({ - query: { - codeRepositoryId: codeRepository.id!, - enablePullRequests: true, - }, - select: { - serviceCatalog: { - name: true, - _id: true, - serviceLanguage: true, - }, - servicePathInRepository: true, - limitNumberOfOpenPullRequestsCount: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); + const servicesRepository: Array<ServiceRepository> = + await ServiceRepositoryService.findBy({ + query: { + codeRepositoryId: codeRepository.id!, + enablePullRequests: true, + }, + select: { + serviceCatalog: { + name: true, + _id: true, + serviceLanguage: true, + }, + servicePathInRepository: true, + limitNumberOfOpenPullRequestsCount: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); - return Response.sendJsonObjectResponse(req, res, { - codeRepository: CodeRepository.toJSON( - codeRepository, - CodeRepository - ), - servicesRepository: ServiceRepository.toJSONArray( - servicesRepository, - ServiceRepository - ), - }); - } catch (err) { - next(err); - } - } - ); + return Response.sendJsonObjectResponse(req, res, { + codeRepository: CodeRepository.toJSON( + codeRepository, + CodeRepository, + ), + servicesRepository: ServiceRepository.toJSONArray( + servicesRepository, + ServiceRepository, + ), + }); + } catch (err) { + next(err); + } + }, + ); - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/get-copilot-events-by-file/:secretkey`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const secretkey: string = req.params['secretkey']!; + this.router.get( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/get-copilot-events-by-file/:secretkey`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const secretkey: string = req.params["secretkey"]!; - if (!secretkey) { - throw new BadDataException('Secret key is required'); - } + if (!secretkey) { + throw new BadDataException("Secret key is required"); + } - const filePath: string = req.body['filePath']!; + const filePath: string = req.body["filePath"]!; - if (!filePath) { - throw new BadDataException('File path is required'); - } + if (!filePath) { + throw new BadDataException("File path is required"); + } - const serviceCatalogId: string = - req.body['serviceCatalogId']!; + const serviceCatalogId: string = req.body["serviceCatalogId"]!; - if (!serviceCatalogId) { - throw new BadDataException( - 'Service catalog id is required' - ); - } + if (!serviceCatalogId) { + throw new BadDataException("Service catalog id is required"); + } - const codeRepository: CodeRepository | null = - await CodeRepositoryService.findOneBy({ - query: { - secretToken: new ObjectID(secretkey), - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); + const codeRepository: CodeRepository | null = + await CodeRepositoryService.findOneBy({ + query: { + secretToken: new ObjectID(secretkey), + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (!codeRepository) { - throw new BadDataException( - 'Code repository not found. Secret key is invalid.' - ); - } + if (!codeRepository) { + throw new BadDataException( + "Code repository not found. Secret key is invalid.", + ); + } - const copilotEvents: Array<CopilotEvent> = - await CopilotEventService.findBy({ - query: { - codeRepositoryId: codeRepository.id!, - filePath: filePath, - serviceCatalogId: new ObjectID( - serviceCatalogId - ), - }, - select: { - _id: true, - codeRepositoryId: true, - serviceCatalogId: true, - filePath: true, - copilotEventStatus: true, - copilotEventType: true, - createdAt: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); + const copilotEvents: Array<CopilotEvent> = + await CopilotEventService.findBy({ + query: { + codeRepositoryId: codeRepository.id!, + filePath: filePath, + serviceCatalogId: new ObjectID(serviceCatalogId), + }, + select: { + _id: true, + codeRepositoryId: true, + serviceCatalogId: true, + filePath: true, + copilotEventStatus: true, + copilotEventType: true, + createdAt: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); - return Response.sendJsonObjectResponse(req, res, { - copilotEvents: CopilotEvent.toJSONArray( - copilotEvents, - CopilotEvent - ), - }); - } catch (err) { - next(err); - } - } - ); - } + return Response.sendJsonObjectResponse(req, res, { + copilotEvents: CopilotEvent.toJSONArray( + copilotEvents, + CopilotEvent, + ), + }); + } catch (err) { + next(err); + } + }, + ); + } } diff --git a/CommonServer/API/CommonAPI.ts b/CommonServer/API/CommonAPI.ts index fb8ac56a70..6bdae03eed 100644 --- a/CommonServer/API/CommonAPI.ts +++ b/CommonServer/API/CommonAPI.ts @@ -1,65 +1,65 @@ -import { IsBillingEnabled } from '../EnvironmentConfig'; -import ProjectService from '../Services/ProjectService'; -import { ExpressRequest, OneUptimeRequest } from '../Utils/Express'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import UserType from 'Common/Types/UserType'; +import { IsBillingEnabled } from "../EnvironmentConfig"; +import ProjectService from "../Services/ProjectService"; +import { ExpressRequest, OneUptimeRequest } from "../Utils/Express"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import UserType from "Common/Types/UserType"; export default class CommonAPI { - public static async getDatabaseCommonInteractionProps( - req: ExpressRequest - ): Promise<DatabaseCommonInteractionProps> { - const props: DatabaseCommonInteractionProps = { - tenantId: undefined, - userGlobalAccessPermission: undefined, - userTenantAccessPermission: undefined, - userId: undefined, - userType: (req as OneUptimeRequest).userType, - isMultiTenantRequest: undefined, - }; + public static async getDatabaseCommonInteractionProps( + req: ExpressRequest, + ): Promise<DatabaseCommonInteractionProps> { + const props: DatabaseCommonInteractionProps = { + tenantId: undefined, + userGlobalAccessPermission: undefined, + userTenantAccessPermission: undefined, + userId: undefined, + userType: (req as OneUptimeRequest).userType, + isMultiTenantRequest: undefined, + }; - if ( - (req as OneUptimeRequest).userAuthorization && - (req as OneUptimeRequest).userAuthorization?.userId - ) { - props.userId = (req as OneUptimeRequest).userAuthorization!.userId; - } - - if ((req as OneUptimeRequest).userGlobalAccessPermission) { - props.userGlobalAccessPermission = ( - req as OneUptimeRequest - ).userGlobalAccessPermission; - } - - if ((req as OneUptimeRequest).userTenantAccessPermission) { - props.userTenantAccessPermission = ( - req as OneUptimeRequest - ).userTenantAccessPermission; - } - - if ((req as OneUptimeRequest).tenantId) { - props.tenantId = (req as OneUptimeRequest).tenantId || undefined; - } - - if (req.headers['is-multi-tenant-query']) { - props.isMultiTenantRequest = true; - } - - if (IsBillingEnabled && props.tenantId) { - const plan: { - plan: PlanSelect | null; - isSubscriptionUnpaid: boolean; - } = await ProjectService.getCurrentPlan(props.tenantId!); - props.currentPlan = plan.plan || undefined; - props.isSubscriptionUnpaid = plan.isSubscriptionUnpaid; - } - - // check for root permissions. - - if (props.userType === UserType.MasterAdmin) { - props.isMasterAdmin = true; - } - - return props; + if ( + (req as OneUptimeRequest).userAuthorization && + (req as OneUptimeRequest).userAuthorization?.userId + ) { + props.userId = (req as OneUptimeRequest).userAuthorization!.userId; } + + if ((req as OneUptimeRequest).userGlobalAccessPermission) { + props.userGlobalAccessPermission = ( + req as OneUptimeRequest + ).userGlobalAccessPermission; + } + + if ((req as OneUptimeRequest).userTenantAccessPermission) { + props.userTenantAccessPermission = ( + req as OneUptimeRequest + ).userTenantAccessPermission; + } + + if ((req as OneUptimeRequest).tenantId) { + props.tenantId = (req as OneUptimeRequest).tenantId || undefined; + } + + if (req.headers["is-multi-tenant-query"]) { + props.isMultiTenantRequest = true; + } + + if (IsBillingEnabled && props.tenantId) { + const plan: { + plan: PlanSelect | null; + isSubscriptionUnpaid: boolean; + } = await ProjectService.getCurrentPlan(props.tenantId!); + props.currentPlan = plan.plan || undefined; + props.isSubscriptionUnpaid = plan.isSubscriptionUnpaid; + } + + // check for root permissions. + + if (props.userType === UserType.MasterAdmin) { + props.isMasterAdmin = true; + } + + return props; + } } diff --git a/CommonServer/API/FileAPI.ts b/CommonServer/API/FileAPI.ts index 16db78d9b8..194c09c109 100644 --- a/CommonServer/API/FileAPI.ts +++ b/CommonServer/API/FileAPI.ts @@ -1,52 +1,50 @@ import FileService, { - Service as FileServiceType, -} from '../Services/FileService'; + Service as FileServiceType, +} from "../Services/FileService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import NotFoundException from 'Common/Types/Exception/NotFoundException'; -import ObjectID from 'Common/Types/ObjectID'; -import File from 'Model/Models/File'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import NotFoundException from "Common/Types/Exception/NotFoundException"; +import ObjectID from "Common/Types/ObjectID"; +import File from "Model/Models/File"; export default class FileAPI extends BaseAPI<File, FileServiceType> { - public constructor() { - super(File, FileService); + public constructor() { + super(File, FileService); - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/image/:imageId`, - async ( - req: ExpressRequest, - res: ExpressResponse, - _next: NextFunction - ) => { - const file: File | null = await FileService.findOneById({ - id: new ObjectID(req.params['imageId']!), - props: { - isRoot: true, - ignoreHooks: true, - }, - select: { - file: true, - type: true, - }, - }); + this.router.get( + `${new this.entityType().getCrudApiPath()?.toString()}/image/:imageId`, + async ( + req: ExpressRequest, + res: ExpressResponse, + _next: NextFunction, + ) => { + const file: File | null = await FileService.findOneById({ + id: new ObjectID(req.params["imageId"]!), + props: { + isRoot: true, + ignoreHooks: true, + }, + select: { + file: true, + type: true, + }, + }); - if (!file || !file.file || !file.type) { - return Response.sendErrorResponse( - req, - res, - new NotFoundException('File not found') - ); - } + if (!file || !file.file || !file.type) { + return Response.sendErrorResponse( + req, + res, + new NotFoundException("File not found"), + ); + } - return Response.sendFileResponse(req, res, file); - } - ); - } + return Response.sendFileResponse(req, res, file); + }, + ); + } } diff --git a/CommonServer/API/GlobalConfigAPI.ts b/CommonServer/API/GlobalConfigAPI.ts index af0b1950f8..57e0282c37 100644 --- a/CommonServer/API/GlobalConfigAPI.ts +++ b/CommonServer/API/GlobalConfigAPI.ts @@ -1,51 +1,47 @@ import GlobalConfigService, { - Service as GlobalConfigServiceType, -} from '../Services/GlobalConfigService'; + Service as GlobalConfigServiceType, +} from "../Services/GlobalConfigService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import GlobalConfig from 'Model/Models/GlobalConfig'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import GlobalConfig from "Model/Models/GlobalConfig"; // import ObjectID from 'Common/Types/ObjectID'; export default class GlobalConfigAPI extends BaseAPI< - GlobalConfig, - GlobalConfigServiceType + GlobalConfig, + GlobalConfigServiceType > { - public constructor() { - super(GlobalConfig, GlobalConfigService); + public constructor() { + super(GlobalConfig, GlobalConfigService); - this.router.get( - `${new this.entityType().getCrudApiPath()?.toString()}/vars`, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - // const globalConfig: GlobalConfig | null = - // await GlobalConfigService.findOneById({ - // id: ObjectID.getZeroObjectID(), - // select: { - // useHttps: true, - // }, - // props: { - // isRoot: true, - // }, - // }); + this.router.get( + `${new this.entityType().getCrudApiPath()?.toString()}/vars`, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + // const globalConfig: GlobalConfig | null = + // await GlobalConfigService.findOneById({ + // id: ObjectID.getZeroObjectID(), + // select: { + // useHttps: true, + // }, + // props: { + // isRoot: true, + // }, + // }); - return Response.sendJsonObjectResponse(req, res, { - // USE_HTTPS: - // globalConfig?.useHttps?.toString() || 'false', - }); - } catch (err) { - next(err); - } - } - ); - } + return Response.sendJsonObjectResponse(req, res, { + // USE_HTTPS: + // globalConfig?.useHttps?.toString() || 'false', + }); + } catch (err) { + next(err); + } + }, + ); + } } diff --git a/CommonServer/API/Index.ts b/CommonServer/API/Index.ts index 1d9a77172d..a4aba7cf9c 100644 --- a/CommonServer/API/Index.ts +++ b/CommonServer/API/Index.ts @@ -1,19 +1,19 @@ -import Express, { ExpressApplication } from '../Utils/Express'; -import StatusAPI, { StatusAPIOptions } from './StatusAPI'; -import version from './VersionAPI'; +import Express, { ExpressApplication } from "../Utils/Express"; +import StatusAPI, { StatusAPIOptions } from "./StatusAPI"; +import version from "./VersionAPI"; const app: ExpressApplication = Express.getExpressApp(); export interface InitOptions { - appName: string; - statusOptions: StatusAPIOptions; + appName: string; + statusOptions: StatusAPIOptions; } type InitFunction = (data: InitOptions) => void; const init: InitFunction = (data: InitOptions): void => { - app.use([`/${data.appName}`, '/'], version); - app.use([`/${data.appName}`, '/'], StatusAPI.init(data.statusOptions)); + app.use([`/${data.appName}`, "/"], version); + app.use([`/${data.appName}`, "/"], StatusAPI.init(data.statusOptions)); }; export default init; diff --git a/CommonServer/API/MonitorGroupAPI.ts b/CommonServer/API/MonitorGroupAPI.ts index dfb60b3f1a..38c4b2b159 100644 --- a/CommonServer/API/MonitorGroupAPI.ts +++ b/CommonServer/API/MonitorGroupAPI.ts @@ -1,115 +1,95 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; +import UserMiddleware from "../Middleware/UserAuthorization"; import MonitorGroupService, { - Service as MonitorGroupServiceType, -} from '../Services/MonitorGroupService'; + Service as MonitorGroupServiceType, +} from "../Services/MonitorGroupService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import CommonAPI from './CommonAPI'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import MonitorGroup from 'Model/Models/MonitorGroup'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import CommonAPI from "./CommonAPI"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import MonitorGroup from "Model/Models/MonitorGroup"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; export default class MonitorGroupAPI extends BaseAPI< - MonitorGroup, - MonitorGroupServiceType + MonitorGroup, + MonitorGroupServiceType > { - public constructor() { - super(MonitorGroup, MonitorGroupService); + public constructor() { + super(MonitorGroup, MonitorGroupService); - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/current-status/:monitorGroupId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - // get group id. + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/current-status/:monitorGroupId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + // get group id. - if (!req.params['monitorGroupId']) { - throw new BadDataException( - 'Monitor group id is required.' - ); - } + if (!req.params["monitorGroupId"]) { + throw new BadDataException("Monitor group id is required."); + } - const currentStatus: MonitorStatus = - await this.service.getCurrentStatus( - new ObjectID( - req.params['monitorGroupId'].toString() - ), - await CommonAPI.getDatabaseCommonInteractionProps( - req - ) - ); + const currentStatus: MonitorStatus = + await this.service.getCurrentStatus( + new ObjectID(req.params["monitorGroupId"].toString()), + await CommonAPI.getDatabaseCommonInteractionProps(req), + ); - return Response.sendEntityResponse( - req, - res, - currentStatus, - MonitorStatus - ); - } catch (err) { - next(err); - } - } - ); + return Response.sendEntityResponse( + req, + res, + currentStatus, + MonitorStatus, + ); + } catch (err) { + next(err); + } + }, + ); - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/timeline/:monitorGroupId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - // get group id. + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/timeline/:monitorGroupId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + // get group id. - if (!req.params['monitorGroupId']) { - throw new BadDataException( - 'Monitor group id is required.' - ); - } + if (!req.params["monitorGroupId"]) { + throw new BadDataException("Monitor group id is required."); + } - const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); - const endDate: Date = OneUptimeDate.getCurrentDate(); + const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); + const endDate: Date = OneUptimeDate.getCurrentDate(); - const timeline: Array<MonitorStatusTimeline> = - await this.service.getStatusTimeline( - new ObjectID( - req.params['monitorGroupId'].toString() - ), - startDate, - endDate, - await CommonAPI.getDatabaseCommonInteractionProps( - req - ) - ); + const timeline: Array<MonitorStatusTimeline> = + await this.service.getStatusTimeline( + new ObjectID(req.params["monitorGroupId"].toString()), + startDate, + endDate, + await CommonAPI.getDatabaseCommonInteractionProps(req), + ); - return Response.sendEntityArrayResponse( - req, - res, - timeline, - timeline.length, - MonitorStatusTimeline - ); - } catch (err) { - next(err); - } - } - ); - } + return Response.sendEntityArrayResponse( + req, + res, + timeline, + timeline.length, + MonitorStatusTimeline, + ); + } catch (err) { + next(err); + } + }, + ); + } } diff --git a/CommonServer/API/NotificationAPI.ts b/CommonServer/API/NotificationAPI.ts index dbbb95df5b..6408fad44b 100644 --- a/CommonServer/API/NotificationAPI.ts +++ b/CommonServer/API/NotificationAPI.ts @@ -1,118 +1,117 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; -import NotificationService from '../Services/NotificationService'; +import UserMiddleware from "../Middleware/UserAuthorization"; +import NotificationService from "../Services/NotificationService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - OneUptimeRequest, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Exception from 'Common/Types/Exception/Exception'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission, { UserPermission } from 'Common/Types/Permission'; -import PositiveNumber from 'Common/Types/PositiveNumber'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + OneUptimeRequest, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Exception from "Common/Types/Exception/Exception"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import Permission, { UserPermission } from "Common/Types/Permission"; +import PositiveNumber from "Common/Types/PositiveNumber"; const router: ExpressRouter = Express.getRouter(); router.post( - '/notification/recharge', - UserMiddleware.getUserMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - try { - let amount: number | PositiveNumber = - JSONFunctions.deserializeValue(req.body.amount) as - | number - | PositiveNumber; + "/notification/recharge", + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + try { + let amount: number | PositiveNumber = JSONFunctions.deserializeValue( + req.body.amount, + ) as number | PositiveNumber; - if (amount instanceof PositiveNumber) { - amount = amount.toNumber(); - } + if (amount instanceof PositiveNumber) { + amount = amount.toNumber(); + } - if (typeof amount === 'string') { - amount = parseInt(amount); - } + if (typeof amount === "string") { + amount = parseInt(amount); + } - const projectId: ObjectID = JSONFunctions.deserializeValue( - req.body.projectId - ) as ObjectID; + const projectId: ObjectID = JSONFunctions.deserializeValue( + req.body.projectId, + ) as ObjectID; - if (!amount || typeof amount !== 'number') { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid amount') - ); - } + if (!amount || typeof amount !== "number") { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid amount"), + ); + } - if (amount > 1000) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Amount cannot be greater than 1000') - ); - } + if (amount > 1000) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Amount cannot be greater than 1000"), + ); + } - if (amount < 20) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Amount cannot be less than 20') - ); - } + if (amount < 20) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Amount cannot be less than 20"), + ); + } - if (!projectId || !projectId.toString()) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid projectId') - ); - } + if (!projectId || !projectId.toString()) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid projectId"), + ); + } - // get permissions. if user has permission to recharge, then recharge + // get permissions. if user has permission to recharge, then recharge - if ( - !(req as OneUptimeRequest).userTenantAccessPermission || - !(req as OneUptimeRequest).userTenantAccessPermission![ - projectId.toString() - ] - ) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Permission for this user not found') - ); - } + if ( + !(req as OneUptimeRequest).userTenantAccessPermission || + !(req as OneUptimeRequest).userTenantAccessPermission![ + projectId.toString() + ] + ) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Permission for this user not found"), + ); + } - const permissions: Array<Permission> = ( - req as OneUptimeRequest - ).userTenantAccessPermission![ - projectId.toString() - ]!.permissions.map((permission: UserPermission) => { - return permission.permission; - }); + const permissions: Array<Permission> = ( + req as OneUptimeRequest + ).userTenantAccessPermission![projectId.toString()]!.permissions.map( + (permission: UserPermission) => { + return permission.permission; + }, + ); - if ( - permissions.includes(Permission.ProjectOwner) || - permissions.includes(Permission.ManageProjectBilling) - ) { - await NotificationService.rechargeBalance(projectId, amount); - } else { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'User does not have permission to recharge. You need any one of these permissions - ProjectOwner, CanManageProjectBilling' - ) - ); - } - } catch (err: any) { - return Response.sendErrorResponse(req, res, err as Exception); - } - - return Response.sendEmptySuccessResponse(req, res); + if ( + permissions.includes(Permission.ProjectOwner) || + permissions.includes(Permission.ManageProjectBilling) + ) { + await NotificationService.rechargeBalance(projectId, amount); + } else { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "User does not have permission to recharge. You need any one of these permissions - ProjectOwner, CanManageProjectBilling", + ), + ); + } + } catch (err: any) { + return Response.sendErrorResponse(req, res, err as Exception); } + + return Response.sendEmptySuccessResponse(req, res); + }, ); export default router; diff --git a/CommonServer/API/ProbeAPI.ts b/CommonServer/API/ProbeAPI.ts index 60b367a5c1..c4ab4558e0 100644 --- a/CommonServer/API/ProbeAPI.ts +++ b/CommonServer/API/ProbeAPI.ts @@ -1,61 +1,55 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; +import UserMiddleware from "../Middleware/UserAuthorization"; import ProbeService, { - Service as ProbeServiceType, -} from '../Services/ProbeService'; + Service as ProbeServiceType, +} from "../Services/ProbeService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Probe from 'Model/Models/Probe'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Probe from "Model/Models/Probe"; export default class Ingestor extends BaseAPI<Probe, ProbeServiceType> { - public constructor() { - super(Probe, ProbeService); + public constructor() { + super(Probe, ProbeService); - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/global-probes`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const probes: Array<Probe> = await ProbeService.findBy({ - query: { - isGlobalProbe: true, - }, - select: { - name: true, - description: true, - lastAlive: true, - iconFileId: true, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_MAX, - }); + this.router.post( + `${new this.entityType().getCrudApiPath()?.toString()}/global-probes`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const probes: Array<Probe> = await ProbeService.findBy({ + query: { + isGlobalProbe: true, + }, + select: { + name: true, + description: true, + lastAlive: true, + iconFileId: true, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_MAX, + }); - return Response.sendEntityArrayResponse( - req, - res, - probes, - new PositiveNumber(probes.length), - Probe - ); - } catch (err) { - next(err); - } - } - ); - } + return Response.sendEntityArrayResponse( + req, + res, + probes, + new PositiveNumber(probes.length), + Probe, + ); + } catch (err) { + next(err); + } + }, + ); + } } diff --git a/CommonServer/API/ProjectAPI.ts b/CommonServer/API/ProjectAPI.ts index 0bd186584a..32eb2dc7d8 100644 --- a/CommonServer/API/ProjectAPI.ts +++ b/CommonServer/API/ProjectAPI.ts @@ -1,126 +1,121 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; +import UserMiddleware from "../Middleware/UserAuthorization"; import ProjectService, { - Service as ProjectServiceType, -} from '../Services/ProjectService'; -import ResellerService from '../Services/ResellerService'; -import TeamMemberService from '../Services/TeamMemberService'; + Service as ProjectServiceType, +} from "../Services/ProjectService"; +import ResellerService from "../Services/ResellerService"; +import TeamMemberService from "../Services/TeamMemberService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, - OneUptimeRequest, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Project from 'Model/Models/Project'; -import Reseller from 'Model/Models/Reseller'; -import TeamMember from 'Model/Models/TeamMember'; + ExpressRequest, + ExpressResponse, + NextFunction, + OneUptimeRequest, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Project from "Model/Models/Project"; +import Reseller from "Model/Models/Reseller"; +import TeamMember from "Model/Models/TeamMember"; export default class ProjectAPI extends BaseAPI<Project, ProjectServiceType> { - public constructor() { - super(Project, ProjectService); + public constructor() { + super(Project, ProjectService); - /// This API lists all the projects where user is its team member. - /// This API is usually used to show project selector dropdown in the UI - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/list-user-projects`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - if (!(req as OneUptimeRequest).userAuthorization?.userId) { - throw new NotAuthenticatedException( - 'User should be logged in to access this API' - ); - } + /// This API lists all the projects where user is its team member. + /// This API is usually used to show project selector dropdown in the UI + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/list-user-projects`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + if (!(req as OneUptimeRequest).userAuthorization?.userId) { + throw new NotAuthenticatedException( + "User should be logged in to access this API", + ); + } - const teamMembers: Array<TeamMember> = - await TeamMemberService.findBy({ - query: { - userId: (req as OneUptimeRequest) - .userAuthorization!.userId!, - hasAcceptedInvitation: true, - }, - select: { - project: { - _id: true, - name: true, - trialEndsAt: true, - paymentProviderPlanId: true, - resellerId: true, - isFeatureFlagMonitorGroupsEnabled: true, - paymentProviderMeteredSubscriptionStatus: - true, - paymentProviderSubscriptionStatus: true, - }, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); + const teamMembers: Array<TeamMember> = await TeamMemberService.findBy( + { + query: { + userId: (req as OneUptimeRequest).userAuthorization!.userId!, + hasAcceptedInvitation: true, + }, + select: { + project: { + _id: true, + name: true, + trialEndsAt: true, + paymentProviderPlanId: true, + resellerId: true, + isFeatureFlagMonitorGroupsEnabled: true, + paymentProviderMeteredSubscriptionStatus: true, + paymentProviderSubscriptionStatus: true, + }, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }, + ); - const projects: Array<Project> = []; + const projects: Array<Project> = []; - for (const teamMember of teamMembers) { - if (!teamMember.project) { - continue; - } - - if ( - projects.findIndex((project: Project) => { - return ( - project._id?.toString() === - teamMember.project!._id?.toString() - ); - }) === -1 - ) { - projects.push(teamMember.project!); - } - } - - // get reseller for each project. - for (const project of projects) { - if (project.resellerId) { - const reseller: Reseller | null = - await ResellerService.findOneById({ - id: project.resellerId, - select: { - enableTelemetryFeatures: true, - }, - props: { - isRoot: true, - }, - }); - - if (!reseller) { - continue; - } - - project.reseller = reseller; - } - } - - return Response.sendEntityArrayResponse( - req, - res, - projects, - new PositiveNumber(projects.length), - Project - ); - } catch (err) { - next(err); - } + for (const teamMember of teamMembers) { + if (!teamMember.project) { + continue; } - ); - } + + if ( + projects.findIndex((project: Project) => { + return ( + project._id?.toString() === + teamMember.project!._id?.toString() + ); + }) === -1 + ) { + projects.push(teamMember.project!); + } + } + + // get reseller for each project. + for (const project of projects) { + if (project.resellerId) { + const reseller: Reseller | null = + await ResellerService.findOneById({ + id: project.resellerId, + select: { + enableTelemetryFeatures: true, + }, + props: { + isRoot: true, + }, + }); + + if (!reseller) { + continue; + } + + project.reseller = reseller; + } + } + + return Response.sendEntityArrayResponse( + req, + res, + projects, + new PositiveNumber(projects.length), + Project, + ); + } catch (err) { + next(err); + } + }, + ); + } } diff --git a/CommonServer/API/ProjectSSO.ts b/CommonServer/API/ProjectSSO.ts index e3e754db71..98bdfdfa09 100644 --- a/CommonServer/API/ProjectSSO.ts +++ b/CommonServer/API/ProjectSSO.ts @@ -1,65 +1,65 @@ import ProjectSsoService, { - Service as ProjectSsoServiceType, -} from '../Services/ProjectSsoService'; -import { ExpressRequest, ExpressResponse } from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import ProjectSSO from 'Model/Models/ProjectSso'; + Service as ProjectSsoServiceType, +} from "../Services/ProjectSsoService"; +import { ExpressRequest, ExpressResponse } from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import ProjectSSO from "Model/Models/ProjectSso"; export default class ProjectSsoAPI extends BaseAPI< - ProjectSSO, - ProjectSsoServiceType + ProjectSSO, + ProjectSsoServiceType > { - public constructor() { - super(ProjectSSO, ProjectSsoService); + public constructor() { + super(ProjectSSO, ProjectSsoService); - // SSO Fetch API - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/:projectId/sso-list`, - async (req: ExpressRequest, res: ExpressResponse) => { - const projectId: ObjectID = new ObjectID( - req.params['projectId'] as string - ); - - if (!projectId) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid project id.') - ); - } - - const sso: Array<ProjectSSO> = await this.service.findBy({ - query: { - projectId: projectId, - isEnabled: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - description: true, - _id: true, - }, - props: { - isRoot: true, - }, - }); - - return Response.sendEntityArrayResponse( - req, - res, - sso, - new PositiveNumber(sso.length), - ProjectSSO - ); - } + // SSO Fetch API + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/:projectId/sso-list`, + async (req: ExpressRequest, res: ExpressResponse) => { + const projectId: ObjectID = new ObjectID( + req.params["projectId"] as string, ); - } + + if (!projectId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid project id."), + ); + } + + const sso: Array<ProjectSSO> = await this.service.findBy({ + query: { + projectId: projectId, + isEnabled: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + description: true, + _id: true, + }, + props: { + isRoot: true, + }, + }); + + return Response.sendEntityArrayResponse( + req, + res, + sso, + new PositiveNumber(sso.length), + ProjectSSO, + ); + }, + ); + } } diff --git a/CommonServer/API/ResellerPlanAPI.ts b/CommonServer/API/ResellerPlanAPI.ts index 2714560c65..6e2ab9fe5c 100644 --- a/CommonServer/API/ResellerPlanAPI.ts +++ b/CommonServer/API/ResellerPlanAPI.ts @@ -1,270 +1,246 @@ -import DatabaseConfig from '../DatabaseConfig'; -import BearerTokenAuthorization from '../Middleware/BearerTokenAuthorization'; -import BillingService from '../Services/BillingService'; -import ProjectService from '../Services/ProjectService'; -import PromoCodeService from '../Services/PromoCodeService'; +import DatabaseConfig from "../DatabaseConfig"; +import BearerTokenAuthorization from "../Middleware/BearerTokenAuthorization"; +import BillingService from "../Services/BillingService"; +import ProjectService from "../Services/ProjectService"; +import PromoCodeService from "../Services/PromoCodeService"; import ResellerPlanService, { - Service as ResellerPlanServiceType, -} from '../Services/ResellerPlanService'; + Service as ResellerPlanServiceType, +} from "../Services/ResellerPlanService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import StatusCode from 'Common/Types/API/StatusCode'; -import URL from 'Common/Types/API/URL'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import Email from 'Common/Types/Email'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Project from 'Model/Models/Project'; -import PromoCode from 'Model/Models/PromoCode'; -import ResellerPlan from 'Model/Models/ResellerPlan'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import StatusCode from "Common/Types/API/StatusCode"; +import URL from "Common/Types/API/URL"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import Email from "Common/Types/Email"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Project from "Model/Models/Project"; +import PromoCode from "Model/Models/PromoCode"; +import ResellerPlan from "Model/Models/ResellerPlan"; export default class ResellerPlanAPI extends BaseAPI< - ResellerPlan, - ResellerPlanServiceType + ResellerPlan, + ResellerPlanServiceType > { - public constructor() { - super(ResellerPlan, ResellerPlanService); + public constructor() { + super(ResellerPlan, ResellerPlanService); - // Reseller Plan Action API - // TODO: Reafactor this APi and make it partner specific. - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/action/:resellerId`, - BearerTokenAuthorization.isAuthorizedBearerToken, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const resellerId: string | undefined = - req.params['resellerId']; + // Reseller Plan Action API + // TODO: Reafactor this APi and make it partner specific. + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/action/:resellerId`, + BearerTokenAuthorization.isAuthorizedBearerToken, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const resellerId: string | undefined = req.params["resellerId"]; - if (!resellerId) { - throw new Error('Invalid reseller id.'); - } + if (!resellerId) { + throw new Error("Invalid reseller id."); + } - if ( - resellerId.trim().toLowerCase() !== - (req as any).bearerTokenData?.resellerId - ?.trim() - .toLowerCase() - ) { - throw new Error( - 'Invalid reseller id found in access token' - ); - } + if ( + resellerId.trim().toLowerCase() !== + (req as any).bearerTokenData?.resellerId?.trim().toLowerCase() + ) { + throw new Error("Invalid reseller id found in access token"); + } - const action: string = req.body.action; + const action: string = req.body.action; - if (!action) { - throw new BadDataException('Invalid action.'); - } + if (!action) { + throw new BadDataException("Invalid action."); + } - const resellerPlanId: string | undefined = req.body.plan_id; + const resellerPlanId: string | undefined = req.body.plan_id; - if (!resellerPlanId) { - throw new BadDataException('Invalid reseller plan id.'); - } + if (!resellerPlanId) { + throw new BadDataException("Invalid reseller plan id."); + } - // check reseller Plan. + // check reseller Plan. - const resellerPlan: ResellerPlan | null = - await ResellerPlanService.findOneBy({ - query: { - planId: resellerPlanId, - }, - select: { - _id: true, - planId: true, - reseller: { - resellerId: true, - }, - teamMemberLimit: true, - monitorLimit: true, - planType: true, - }, - props: { - isRoot: true, - }, - }); + const resellerPlan: ResellerPlan | null = + await ResellerPlanService.findOneBy({ + query: { + planId: resellerPlanId, + }, + select: { + _id: true, + planId: true, + reseller: { + resellerId: true, + }, + teamMemberLimit: true, + monitorLimit: true, + planType: true, + }, + props: { + isRoot: true, + }, + }); - if (!resellerPlan) { - throw new BadDataException('Invalid reseller plan id.'); - } + if (!resellerPlan) { + throw new BadDataException("Invalid reseller plan id."); + } - if (resellerPlan.reseller?.resellerId !== resellerId) { - throw new BadDataException( - 'This plan does not belong to reseller: ' + - resellerId - ); - } + if (resellerPlan.reseller?.resellerId !== resellerId) { + throw new BadDataException( + "This plan does not belong to reseller: " + resellerId, + ); + } - const licenseKey: string | undefined = req.body.uuid; + const licenseKey: string | undefined = req.body.uuid; - if (!licenseKey) { - throw new BadDataException('Invalid license key.'); - } + if (!licenseKey) { + throw new BadDataException("Invalid license key."); + } - const userEmail: Email = new Email( - req.body.activation_email - ); + const userEmail: Email = new Email(req.body.activation_email); - if (action === 'activate') { - // generate a coupon code. Billing is handled by the reseller so OneUptime will have 100% discount on its plans. + if (action === "activate") { + // generate a coupon code. Billing is handled by the reseller so OneUptime will have 100% discount on its plans. - const couponcode: string = - await BillingService.generateCouponCode({ - name: resellerId, - percentOff: 100, - maxRedemptions: 2, - durationInMonths: 12 * 20, // 20 years. - metadata: { - licenseKey: licenseKey || '', - resellerPlanId: - resellerPlan?.id?.toString() || '', - }, - }); + const couponcode: string = await BillingService.generateCouponCode({ + name: resellerId, + percentOff: 100, + maxRedemptions: 2, + durationInMonths: 12 * 20, // 20 years. + metadata: { + licenseKey: licenseKey || "", + resellerPlanId: resellerPlan?.id?.toString() || "", + }, + }); - // save this in promocode table. + // save this in promocode table. - const promoCode: PromoCode = new PromoCode(); + const promoCode: PromoCode = new PromoCode(); - promoCode.promoCodeId = couponcode; - promoCode.resellerId = resellerPlan?.reseller - .id as ObjectID; - promoCode.resellerPlanId = resellerPlan?.id as ObjectID; - promoCode.userEmail = userEmail; - promoCode.planType = - resellerPlan?.planType as PlanSelect; - promoCode.resellerLicenseId = licenseKey || ''; + promoCode.promoCodeId = couponcode; + promoCode.resellerId = resellerPlan?.reseller.id as ObjectID; + promoCode.resellerPlanId = resellerPlan?.id as ObjectID; + promoCode.userEmail = userEmail; + promoCode.planType = resellerPlan?.planType as PlanSelect; + promoCode.resellerLicenseId = licenseKey || ""; - await PromoCodeService.create({ - data: promoCode, - props: { - isRoot: true, - }, - }); + await PromoCodeService.create({ + data: promoCode, + props: { + isRoot: true, + }, + }); - // now redirect to accounts sign up page with this promocode. + // now redirect to accounts sign up page with this promocode. - const accountUrl: URL = - await DatabaseConfig.getAccountsUrl(); + const accountUrl: URL = await DatabaseConfig.getAccountsUrl(); - return Response.sendJsonObjectResponse( - req, - res, - { - message: 'product activated', - redirect_url: URL.fromString( - accountUrl.toString() - ) - .addRoute('/register') - .addQueryParams({ - email: userEmail.toString(), - promoCode: couponcode, - partnerId: resellerId, - }) - .toString(), - }, - { - statusCode: new StatusCode(201), - } - ); - } else if ( - action === 'enhance_tier' || - action === 'reduce_tier' - ) { - // update monitor and team seat limits. + return Response.sendJsonObjectResponse( + req, + res, + { + message: "product activated", + redirect_url: URL.fromString(accountUrl.toString()) + .addRoute("/register") + .addQueryParams({ + email: userEmail.toString(), + promoCode: couponcode, + partnerId: resellerId, + }) + .toString(), + }, + { + statusCode: new StatusCode(201), + }, + ); + } else if (action === "enhance_tier" || action === "reduce_tier") { + // update monitor and team seat limits. - const project: Project | null = - await ProjectService.findOneBy({ - query: { - resellerLicenseId: licenseKey, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); + const project: Project | null = await ProjectService.findOneBy({ + query: { + resellerLicenseId: licenseKey, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (!project) { - throw new BadDataException( - 'Project not found with this license key' - ); - } - - // update limits. - - await ProjectService.updateOneById({ - id: project.id!, - data: { - activeMonitorsLimit: resellerPlan.monitorLimit!, - seatLimit: resellerPlan.teamMemberLimit!, - resellerPlanId: resellerPlan.id!, - }, - props: { - isRoot: true, - }, - }); - - return Response.sendJsonObjectResponse(req, res, { - message: - action === 'enhance_tier' - ? 'product enhanced' - : 'product reduced', - }); - } else if (action === 'refund') { - // delete this project. - - const project: Project | null = - await ProjectService.findOneBy({ - query: { - resellerLicenseId: licenseKey, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); - - if (!project) { - // maybe already deleted. so, we just issue a refund and return. - return Response.sendJsonObjectResponse(req, res, { - message: 'product refunded', - }); - } - - await ProjectService.deleteOneBy({ - query: { - resellerLicenseId: licenseKey, - _id: project.id?.toString() as string, - }, - props: { - isRoot: true, - }, - }); - - return Response.sendJsonObjectResponse(req, res, { - message: 'product refunded', - }); - } - - throw new BadDataException('Invalid action.'); - } catch (err) { - next(err); - } + if (!project) { + throw new BadDataException( + "Project not found with this license key", + ); } - ); - } + + // update limits. + + await ProjectService.updateOneById({ + id: project.id!, + data: { + activeMonitorsLimit: resellerPlan.monitorLimit!, + seatLimit: resellerPlan.teamMemberLimit!, + resellerPlanId: resellerPlan.id!, + }, + props: { + isRoot: true, + }, + }); + + return Response.sendJsonObjectResponse(req, res, { + message: + action === "enhance_tier" + ? "product enhanced" + : "product reduced", + }); + } else if (action === "refund") { + // delete this project. + + const project: Project | null = await ProjectService.findOneBy({ + query: { + resellerLicenseId: licenseKey, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); + + if (!project) { + // maybe already deleted. so, we just issue a refund and return. + return Response.sendJsonObjectResponse(req, res, { + message: "product refunded", + }); + } + + await ProjectService.deleteOneBy({ + query: { + resellerLicenseId: licenseKey, + _id: project.id?.toString() as string, + }, + props: { + isRoot: true, + }, + }); + + return Response.sendJsonObjectResponse(req, res, { + message: "product refunded", + }); + } + + throw new BadDataException("Invalid action."); + } catch (err) { + next(err); + } + }, + ); + } } diff --git a/CommonServer/API/ShortLinkAPI.ts b/CommonServer/API/ShortLinkAPI.ts index 7a7aff76a4..9818494c5e 100644 --- a/CommonServer/API/ShortLinkAPI.ts +++ b/CommonServer/API/ShortLinkAPI.ts @@ -1,51 +1,52 @@ import ShortLinkService, { - Service as ShortLinkServiceType, -} from '../Services/ShortLinkService'; -import { ExpressRequest, ExpressResponse } from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ShortLink from 'Model/Models/ShortLink'; + Service as ShortLinkServiceType, +} from "../Services/ShortLinkService"; +import { ExpressRequest, ExpressResponse } from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ShortLink from "Model/Models/ShortLink"; export default class ShortLinkAPI extends BaseAPI< - ShortLink, - ShortLinkServiceType + ShortLink, + ShortLinkServiceType > { - public constructor() { - super(ShortLink, ShortLinkService); + public constructor() { + super(ShortLink, ShortLinkService); - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/redirect-to-shortlink/:id`, - async (req: ExpressRequest, res: ExpressResponse) => { - if (!req.params['id']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('id is required') - ); - } + this.router.get( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/redirect-to-shortlink/:id`, + async (req: ExpressRequest, res: ExpressResponse) => { + if (!req.params["id"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("id is required"), + ); + } - if (req.params['id'] === 'status') { - return Response.sendJsonObjectResponse(req, res, { - status: 'ok', - }); - } + if (req.params["id"] === "status") { + return Response.sendJsonObjectResponse(req, res, { + status: "ok", + }); + } - const link: ShortLink | null = - await ShortLinkService.getShortLinkFor(req.params['id']); - - if (!link || !link.link) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('This URL is invalid or expired') - ); - } - - return Response.redirect(req, res, link.link); - } + const link: ShortLink | null = await ShortLinkService.getShortLinkFor( + req.params["id"], ); - } + + if (!link || !link.link) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("This URL is invalid or expired"), + ); + } + + return Response.redirect(req, res, link.link); + }, + ); + } } diff --git a/CommonServer/API/StatusAPI.ts b/CommonServer/API/StatusAPI.ts index 51efee7441..0d13f88da2 100644 --- a/CommonServer/API/StatusAPI.ts +++ b/CommonServer/API/StatusAPI.ts @@ -1,121 +1,118 @@ -import LocalCache from '../Infrastructure/LocalCache'; +import LocalCache from "../Infrastructure/LocalCache"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, -} from '../Utils/Express'; -import logger from '../Utils/Logger'; -import Response from '../Utils/Response'; -import Telemetry from '../Utils/Telemetry'; -import Exception from 'Common/Types/Exception/Exception'; -import ServerException from 'Common/Types/Exception/ServerException'; + ExpressRequest, + ExpressResponse, + ExpressRouter, +} from "../Utils/Express"; +import logger from "../Utils/Logger"; +import Response from "../Utils/Response"; +import Telemetry from "../Utils/Telemetry"; +import Exception from "Common/Types/Exception/Exception"; +import ServerException from "Common/Types/Exception/ServerException"; export interface StatusAPIOptions { - readyCheck: () => Promise<void>; - liveCheck: () => Promise<void>; + readyCheck: () => Promise<void>; + liveCheck: () => Promise<void>; } export default class StatusAPI { - public static statusCheckSuccessCounter = Telemetry.getCounter({ - name: 'status.check.success', - description: 'Status check counter', + public static statusCheckSuccessCounter = Telemetry.getCounter({ + name: "status.check.success", + description: "Status check counter", + }); + + // ready counter + public static stausReadySuccess = Telemetry.getCounter({ + name: "status.ready.success", + description: "Ready check counter", + }); + // live counter + + public static stausLiveSuccess = Telemetry.getCounter({ + name: "status.live.success", + description: "Live check counter", + }); + + // ready failed counter + public static stausReadyFailed = Telemetry.getCounter({ + name: "status.ready.failed", + description: "Ready check counter", + }); + + // live failed counter + public static stausLiveFailed = Telemetry.getCounter({ + name: "status.live.failed", + description: "Live check counter", + }); + + public static init(options: StatusAPIOptions): ExpressRouter { + const router: ExpressRouter = Express.getRouter(); + + router.get("/app-name", (_req: ExpressRequest, res: ExpressResponse) => { + res.send({ app: LocalCache.getString("app", "name") }); }); - // ready counter - public static stausReadySuccess = Telemetry.getCounter({ - name: 'status.ready.success', - description: 'Ready check counter', - }); - // live counter + // General status + router.get("/status", (req: ExpressRequest, res: ExpressResponse) => { + this.statusCheckSuccessCounter.add(1); - public static stausLiveSuccess = Telemetry.getCounter({ - name: 'status.live.success', - description: 'Live check counter', + logger.info("Status check: ok"); + + Response.sendJsonObjectResponse(req, res, { + status: "ok", + }); }); - // ready failed counter - public static stausReadyFailed = Telemetry.getCounter({ - name: 'status.ready.failed', - description: 'Ready check counter', - }); + //Healthy probe + router.get( + "/status/ready", + async (req: ExpressRequest, res: ExpressResponse) => { + try { + logger.debug("Ready check"); + await options.readyCheck(); + logger.info("Ready check: ok"); + this.stausReadySuccess.add(1); + Response.sendJsonObjectResponse(req, res, { + status: "ok", + }); + } catch (e) { + this.stausReadyFailed.add(1); + Response.sendErrorResponse( + req, + res, + e instanceof Exception + ? e + : new ServerException("Server is not ready"), + ); + } + }, + ); - // live failed counter - public static stausLiveFailed = Telemetry.getCounter({ - name: 'status.live.failed', - description: 'Live check counter', - }); + //Liveness probe + router.get( + "/status/live", + async (req: ExpressRequest, res: ExpressResponse) => { + try { + logger.debug("Live check"); + await options.readyCheck(); + logger.info("Live check: ok"); + this.stausLiveSuccess.add(1); + Response.sendJsonObjectResponse(req, res, { + status: "ok", + }); + } catch (e) { + this.stausLiveFailed.add(1); + Response.sendErrorResponse( + req, + res, + e instanceof Exception + ? e + : new ServerException("Server is not ready"), + ); + } + }, + ); - public static init(options: StatusAPIOptions): ExpressRouter { - const router: ExpressRouter = Express.getRouter(); - - router.get( - '/app-name', - (_req: ExpressRequest, res: ExpressResponse) => { - res.send({ app: LocalCache.getString('app', 'name') }); - } - ); - - // General status - router.get('/status', (req: ExpressRequest, res: ExpressResponse) => { - this.statusCheckSuccessCounter.add(1); - - logger.info('Status check: ok'); - - Response.sendJsonObjectResponse(req, res, { - status: 'ok', - }); - }); - - //Healthy probe - router.get( - '/status/ready', - async (req: ExpressRequest, res: ExpressResponse) => { - try { - logger.debug('Ready check'); - await options.readyCheck(); - logger.info('Ready check: ok'); - this.stausReadySuccess.add(1); - Response.sendJsonObjectResponse(req, res, { - status: 'ok', - }); - } catch (e) { - this.stausReadyFailed.add(1); - Response.sendErrorResponse( - req, - res, - e instanceof Exception - ? e - : new ServerException('Server is not ready') - ); - } - } - ); - - //Liveness probe - router.get( - '/status/live', - async (req: ExpressRequest, res: ExpressResponse) => { - try { - logger.debug('Live check'); - await options.readyCheck(); - logger.info('Live check: ok'); - this.stausLiveSuccess.add(1); - Response.sendJsonObjectResponse(req, res, { - status: 'ok', - }); - } catch (e) { - this.stausLiveFailed.add(1); - Response.sendErrorResponse( - req, - res, - e instanceof Exception - ? e - : new ServerException('Server is not ready') - ); - } - } - ); - - return router; - } + return router; + } } diff --git a/CommonServer/API/StatusPageAPI.ts b/CommonServer/API/StatusPageAPI.ts index 7a42001842..6aa7580769 100644 --- a/CommonServer/API/StatusPageAPI.ts +++ b/CommonServer/API/StatusPageAPI.ts @@ -1,1564 +1,909 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; -import AcmeChallengeService from '../Services/AcmeChallengeService'; -import IncidentPublicNoteService from '../Services/IncidentPublicNoteService'; -import IncidentService from '../Services/IncidentService'; -import IncidentStateService from '../Services/IncidentStateService'; -import IncidentStateTimelineService from '../Services/IncidentStateTimelineService'; -import MonitorGroupResourceService from '../Services/MonitorGroupResourceService'; -import MonitorGroupService from '../Services/MonitorGroupService'; -import MonitorStatusService from '../Services/MonitorStatusService'; -import MonitorStatusTimelineService from '../Services/MonitorStatusTimelineService'; -import ScheduledMaintenancePublicNoteService from '../Services/ScheduledMaintenancePublicNoteService'; -import ScheduledMaintenanceService from '../Services/ScheduledMaintenanceService'; -import ScheduledMaintenanceStateService from '../Services/ScheduledMaintenanceStateService'; -import ScheduledMaintenanceStateTimelineService from '../Services/ScheduledMaintenanceStateTimelineService'; -import StatusPageAnnouncementService from '../Services/StatusPageAnnouncementService'; -import StatusPageDomainService from '../Services/StatusPageDomainService'; -import StatusPageFooterLinkService from '../Services/StatusPageFooterLinkService'; -import StatusPageGroupService from '../Services/StatusPageGroupService'; -import StatusPageHeaderLinkService from '../Services/StatusPageHeaderLinkService'; -import StatusPageHistoryChartBarColorRuleService from '../Services/StatusPageHistoryChartBarColorRuleService'; -import StatusPageResourceService from '../Services/StatusPageResourceService'; +import UserMiddleware from "../Middleware/UserAuthorization"; +import AcmeChallengeService from "../Services/AcmeChallengeService"; +import IncidentPublicNoteService from "../Services/IncidentPublicNoteService"; +import IncidentService from "../Services/IncidentService"; +import IncidentStateService from "../Services/IncidentStateService"; +import IncidentStateTimelineService from "../Services/IncidentStateTimelineService"; +import MonitorGroupResourceService from "../Services/MonitorGroupResourceService"; +import MonitorGroupService from "../Services/MonitorGroupService"; +import MonitorStatusService from "../Services/MonitorStatusService"; +import MonitorStatusTimelineService from "../Services/MonitorStatusTimelineService"; +import ScheduledMaintenancePublicNoteService from "../Services/ScheduledMaintenancePublicNoteService"; +import ScheduledMaintenanceService from "../Services/ScheduledMaintenanceService"; +import ScheduledMaintenanceStateService from "../Services/ScheduledMaintenanceStateService"; +import ScheduledMaintenanceStateTimelineService from "../Services/ScheduledMaintenanceStateTimelineService"; +import StatusPageAnnouncementService from "../Services/StatusPageAnnouncementService"; +import StatusPageDomainService from "../Services/StatusPageDomainService"; +import StatusPageFooterLinkService from "../Services/StatusPageFooterLinkService"; +import StatusPageGroupService from "../Services/StatusPageGroupService"; +import StatusPageHeaderLinkService from "../Services/StatusPageHeaderLinkService"; +import StatusPageHistoryChartBarColorRuleService from "../Services/StatusPageHistoryChartBarColorRuleService"; +import StatusPageResourceService from "../Services/StatusPageResourceService"; import StatusPageService, { - Service as StatusPageServiceType, -} from '../Services/StatusPageService'; -import StatusPageSsoService from '../Services/StatusPageSsoService'; -import StatusPageSubscriberService from '../Services/StatusPageSubscriberService'; -import Query from '../Types/Database/Query'; -import QueryHelper from '../Types/Database/QueryHelper'; -import Select from '../Types/Database/Select'; + Service as StatusPageServiceType, +} from "../Services/StatusPageService"; +import StatusPageSsoService from "../Services/StatusPageSsoService"; +import StatusPageSubscriberService from "../Services/StatusPageSubscriberService"; +import Query from "../Types/Database/Query"; +import QueryHelper from "../Types/Database/QueryHelper"; +import Select from "../Types/Database/Select"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import logger from '../Utils/Logger'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import CommonAPI from './CommonAPI'; -import BaseModel from 'Common/Models/BaseModel'; -import ArrayUtil from 'Common/Types/ArrayUtil'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import Email from 'Common/Types/Email'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException'; -import NotFoundException from 'Common/Types/Exception/NotFoundException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import Phone from 'Common/Types/Phone'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import AcmeChallenge from 'Model/Models/AcmeChallenge'; -import Incident from 'Model/Models/Incident'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import MonitorGroupResource from 'Model/Models/MonitorGroupResource'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageAnnouncement from 'Model/Models/StatusPageAnnouncement'; -import StatusPageDomain from 'Model/Models/StatusPageDomain'; -import StatusPageFooterLink from 'Model/Models/StatusPageFooterLink'; -import StatusPageGroup from 'Model/Models/StatusPageGroup'; -import StatusPageHeaderLink from 'Model/Models/StatusPageHeaderLink'; -import StatusPageHistoryChartBarColorRule from 'Model/Models/StatusPageHistoryChartBarColorRule'; -import StatusPageResource from 'Model/Models/StatusPageResource'; -import StatusPageSSO from 'Model/Models/StatusPageSso'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import logger from "../Utils/Logger"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import CommonAPI from "./CommonAPI"; +import BaseModel from "Common/Models/BaseModel"; +import ArrayUtil from "Common/Types/ArrayUtil"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import Email from "Common/Types/Email"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException"; +import NotFoundException from "Common/Types/Exception/NotFoundException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import Phone from "Common/Types/Phone"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import AcmeChallenge from "Model/Models/AcmeChallenge"; +import Incident from "Model/Models/Incident"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import MonitorGroupResource from "Model/Models/MonitorGroupResource"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageAnnouncement from "Model/Models/StatusPageAnnouncement"; +import StatusPageDomain from "Model/Models/StatusPageDomain"; +import StatusPageFooterLink from "Model/Models/StatusPageFooterLink"; +import StatusPageGroup from "Model/Models/StatusPageGroup"; +import StatusPageHeaderLink from "Model/Models/StatusPageHeaderLink"; +import StatusPageHistoryChartBarColorRule from "Model/Models/StatusPageHistoryChartBarColorRule"; +import StatusPageResource from "Model/Models/StatusPageResource"; +import StatusPageSSO from "Model/Models/StatusPageSso"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; export default class StatusPageAPI extends BaseAPI< - StatusPage, - StatusPageServiceType + StatusPage, + StatusPageServiceType > { - public constructor() { - super(StatusPage, StatusPageService); - - // CNAME verification api - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/cname-verification/:token`, - async (req: ExpressRequest, res: ExpressResponse) => { - const host: string | undefined = req.get('host'); - - if (!host) { - throw new BadDataException('Host not found'); - } - - const token: string = req.params['token'] as string; - - logger.debug( - `CNAME Verification: Host:${host} - Token:${token}` - ); - - const domain: StatusPageDomain | null = - await StatusPageDomainService.findOneBy({ - query: { - cnameVerificationToken: token, - fullDomain: host, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); - - if (!domain) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid token.') - ); - } - - return Response.sendEmptySuccessResponse(req, res); - } - ); - - // ACME Challenge Validation. - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/.well-known/acme-challenge/:token`, - async (req: ExpressRequest, res: ExpressResponse) => { - const challenge: AcmeChallenge | null = - await AcmeChallengeService.findOneBy({ - query: { - token: req.params['token'] as string, - }, - select: { - challenge: true, - }, - props: { - isRoot: true, - }, - }); - - if (!challenge) { - return Response.sendErrorResponse( - req, - res, - new NotFoundException('Challenge not found') - ); - } - - return Response.sendTextResponse( - req, - res, - challenge.challenge as string - ); - } - ); - - this.router.post( - `${new this.entityType().getCrudApiPath()?.toString()}/domain`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - if (!req.body['domain']) { - throw new BadDataException( - 'domain is required in request body' - ); - } - - const domain: string = req.body['domain'] as string; - - const statusPageDomain: StatusPageDomain | null = - await StatusPageDomainService.findOneBy({ - query: { - fullDomain: domain, - domain: { - isVerified: true, - } as any, - }, - select: { - statusPageId: true, - }, - props: { - isRoot: true, - }, - }); - - if (!statusPageDomain) { - throw new BadDataException( - 'No status page found with this domain' - ); - } - - const objectId: ObjectID = statusPageDomain.statusPageId!; - - return Response.sendJsonObjectResponse(req, res, { - statusPageId: objectId.toString(), - }); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/master-page/:statusPageId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - const select: Select<StatusPage> = { - _id: true, - slug: true, - coverImageFileId: true, - logoFileId: true, - pageTitle: true, - pageDescription: true, - copyrightText: true, - customCSS: true, - customJavaScript: true, - hidePoweredByOneUptimeBranding: true, - headerHTML: true, - footerHTML: true, - enableEmailSubscribers: true, - enableSmsSubscribers: true, - isPublicStatusPage: true, - allowSubscribersToChooseResources: true, - requireSsoForLogin: true, - coverImageFile: { - file: true, - _id: true, - type: true, - name: true, - }, - faviconFile: { - file: true, - _id: true, - type: true, - name: true, - }, - logoFile: { - file: true, - _id: true, - type: true, - name: true, - }, - }; - - const hasEnabledSSO: PositiveNumber = - await StatusPageSsoService.countBy({ - query: { - isEnabled: true, - statusPageId: objectId, - }, - props: { - isRoot: true, - }, - }); - - const item: StatusPage | null = - await this.service.findOneById({ - id: objectId, - select, - props: { - isRoot: true, - }, - }); - - if (!item) { - throw new BadDataException('Status Page not found'); - } - - const footerLinks: Array<StatusPageFooterLink> = - await StatusPageFooterLinkService.findBy({ - query: { - statusPageId: objectId, - }, - select: { - _id: true, - link: true, - title: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - const headerLinks: Array<StatusPageHeaderLink> = - await StatusPageHeaderLinkService.findBy({ - query: { - statusPageId: objectId, - }, - select: { - _id: true, - link: true, - title: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - const response: JSONObject = { - statusPage: BaseModel.toJSON(item, StatusPage), - footerLinks: BaseModel.toJSONArray( - footerLinks, - StatusPageFooterLink - ), - headerLinks: BaseModel.toJSONArray( - headerLinks, - StatusPageHeaderLink - ), - hasEnabledSSO: hasEnabledSSO.toNumber(), - }; - - return Response.sendJsonObjectResponse(req, res, response); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/sso/:statusPageId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - const sso: Array<StatusPageSSO> = - await StatusPageSsoService.findBy({ - query: { - statusPageId: objectId, - isEnabled: true, - }, - select: { - signOnURL: true, - name: true, - description: true, - _id: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - return Response.sendEntityArrayResponse( - req, - res, - sso, - new PositiveNumber(sso.length), - StatusPageSSO - ); - } catch (err) { - next(err); - } - } - ); - - // Get all status page resources for subscriber to subscribe to. - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/resources/:statusPageId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - if ( - !(await this.service.hasReadAccess( - objectId, - await CommonAPI.getDatabaseCommonInteractionProps( - req - ), - req - )) - ) { - throw new NotAuthenticatedException( - 'You are not authenticated to access this status page' - ); - } - - const resources: Array<StatusPageResource> = - await StatusPageResourceService.findBy({ - query: { - statusPageId: objectId, - }, - select: { - _id: true, - displayName: true, - order: true, - statusPageGroup: { - _id: true, - name: true, - order: true, - }, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - return Response.sendEntityArrayResponse( - req, - res, - resources, - new PositiveNumber(resources.length), - StatusPageResource - ); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/overview/:statusPageId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); - const endDate: Date = OneUptimeDate.getCurrentDate(); - - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - if ( - !(await this.service.hasReadAccess( - objectId, - await CommonAPI.getDatabaseCommonInteractionProps( - req - ), - req - )) - ) { - throw new NotAuthenticatedException( - 'You are not authenticated to access this status page' - ); - } - - const statusPage: StatusPage | null = - await StatusPageService.findOneBy({ - query: { - _id: objectId.toString(), - }, - select: { - _id: true, - projectId: true, - isPublicStatusPage: true, - overviewPageDescription: true, - showIncidentLabelsOnStatusPage: true, - showScheduledEventLabelsOnStatusPage: true, - downtimeMonitorStatuses: { - _id: true, - }, - defaultBarColor: true, - }, - props: { - isRoot: true, - }, - }); - - if (!statusPage) { - throw new BadDataException('Status Page not found'); - } - - //get monitor statuses - - const monitorStatuses: Array<MonitorStatus> = - await MonitorStatusService.findBy({ - query: { - projectId: statusPage.projectId!, - }, - select: { - name: true, - color: true, - priority: true, - isOperationalState: true, - }, - sort: { - priority: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - // get resource groups. - - const groups: Array<StatusPageGroup> = - await StatusPageGroupService.findBy({ - query: { - statusPageId: objectId, - }, - select: { - name: true, - order: true, - description: true, - isExpandedByDefault: true, - }, - sort: { - order: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - // get monitors on status page. - const statusPageResources: Array<StatusPageResource> = - await StatusPageResourceService.findBy({ - query: { - statusPageId: objectId, - }, - select: { - statusPageGroupId: true, - monitorId: true, - displayTooltip: true, - displayDescription: true, - displayName: true, - showStatusHistoryChart: true, - showCurrentStatus: true, - order: true, - monitor: { - _id: true, - currentMonitorStatusId: true, - }, - monitorGroupId: true, - showUptimePercent: true, - uptimePercentPrecision: true, - }, - sort: { - order: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - const monitorGroupIds: Array<ObjectID> = statusPageResources - .map((resource: StatusPageResource) => { - return resource.monitorGroupId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - // get monitors in the group. - const monitorGroupCurrentStatuses: Dictionary<ObjectID> = - {}; - const monitorsInGroup: Dictionary<Array<ObjectID>> = {}; - - // get monitor status charts. - const monitorsOnStatusPage: Array<ObjectID> = - statusPageResources - .map((monitor: StatusPageResource) => { - return monitor.monitorId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - const monitorsOnStatusPageForTimeline: Array<ObjectID> = - statusPageResources - .filter((monitor: StatusPageResource) => { - return ( - monitor.showStatusHistoryChart || - monitor.showUptimePercent - ); - }) - .map((monitor: StatusPageResource) => { - return monitor.monitorId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - for (const monitorGroupId of monitorGroupIds) { - // get current status of monitors in the group. - - const currentStatus: MonitorStatus = - await MonitorGroupService.getCurrentStatus( - monitorGroupId, - { - isRoot: true, - } - ); - - monitorGroupCurrentStatuses[monitorGroupId.toString()] = - currentStatus.id!; - - // get monitors in the group. - - const groupResources: Array<MonitorGroupResource> = - await MonitorGroupResourceService.findBy({ - query: { - monitorGroupId: monitorGroupId, - }, - select: { - monitorId: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - }); - - const monitorsInGroupIds: Array<ObjectID> = - groupResources - .map((resource: MonitorGroupResource) => { - return resource.monitorId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - const shouldShowTimelineForThisGroup: boolean = Boolean( - statusPageResources.find( - (resource: StatusPageResource) => { - return ( - resource.monitorGroupId?.toString() === - monitorGroupId.toString() && - (resource.showStatusHistoryChart || - resource.showUptimePercent) - ); - } - ) - ); - - for (const monitorId of monitorsInGroupIds) { - if (!monitorId) { - continue; - } - - if ( - !monitorsOnStatusPage.find((item: ObjectID) => { - return ( - item.toString() === monitorId.toString() - ); - }) - ) { - monitorsOnStatusPage.push(monitorId); - } - - // add this to the timeline event for this group. - - if ( - shouldShowTimelineForThisGroup && - !monitorsOnStatusPageForTimeline.find( - (item: ObjectID) => { - return ( - item.toString() === - monitorId.toString() - ); - } - ) - ) { - monitorsOnStatusPageForTimeline.push(monitorId); - } - } - - monitorsInGroup[monitorGroupId.toString()] = - monitorsInGroupIds; - } - - let monitorStatusTimelines: Array<MonitorStatusTimeline> = - []; - - if (monitorsOnStatusPageForTimeline.length > 0) { - monitorStatusTimelines = - await MonitorStatusTimelineService.findBy({ - query: { - monitorId: QueryHelper.any( - monitorsOnStatusPageForTimeline - ), - endsAt: QueryHelper.inBetween( - startDate, - endDate - ), - }, - select: { - monitorId: true, - createdAt: true, - endsAt: true, - startsAt: true, - monitorStatus: { - name: true, - color: true, - priority: true, - } as any, - }, - sort: { - createdAt: SortOrder.Descending, - }, - skip: 0, - limit: LIMIT_MAX, // This can be optimized. - props: { - isRoot: true, - }, - }); - - monitorStatusTimelines = monitorStatusTimelines.concat( - await MonitorStatusTimelineService.findBy({ - query: { - monitorId: QueryHelper.any( - monitorsOnStatusPageForTimeline - ), - endsAt: QueryHelper.isNull(), - }, - select: { - monitorId: true, - createdAt: true, - endsAt: true, - startsAt: true, - monitorStatus: { - name: true, - color: true, - priority: true, - } as any, - }, - sort: { - createdAt: SortOrder.Descending, - }, - skip: 0, - limit: LIMIT_MAX, // This can be optimized. - props: { - isRoot: true, - }, - }) - ); - - // sort monitorStatusTimelines by createdAt. - monitorStatusTimelines = monitorStatusTimelines.sort( - ( - a: MonitorStatusTimeline, - b: MonitorStatusTimeline - ) => { - if (!a.createdAt || !b.createdAt) { - return 0; - } - - return ( - b.createdAt!.getTime() - - a.createdAt!.getTime() - ); - } - ); - } - - // check if status page has active incident. - let activeIncidents: Array<Incident> = []; - if (monitorsOnStatusPage.length > 0) { - let select: Select<Incident> = { - createdAt: true, - title: true, - description: true, - _id: true, - incidentSeverity: { - name: true, - color: true, - }, - currentIncidentState: { - _id: true, - name: true, - color: true, - order: true, - }, - monitors: { - _id: true, - }, - }; - - if (statusPage.showIncidentLabelsOnStatusPage) { - select = { - ...select, - labels: { - name: true, - color: true, - }, - }; - } - - const unresolvedIncidentStates: Array<IncidentState> = - await IncidentStateService.getUnresolvedIncidentStates( - statusPage.projectId!, - { - isRoot: true, - } - ); - - const unresolvedIncidentStateIds: Array<ObjectID> = - unresolvedIncidentStates.map( - (state: IncidentState) => { - return state.id!; - } - ); - - activeIncidents = await IncidentService.findBy({ - query: { - monitors: monitorsOnStatusPage as any, - currentIncidentStateId: QueryHelper.any( - unresolvedIncidentStateIds - ), - projectId: statusPage.projectId!, - }, - select: select, - sort: { - createdAt: SortOrder.Ascending, - }, - - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - } - - const incidentsOnStatusPage: Array<ObjectID> = - activeIncidents.map((incident: Incident) => { - return incident.id!; - }); - - let incidentPublicNotes: Array<IncidentPublicNote> = []; - - if (incidentsOnStatusPage.length > 0) { - incidentPublicNotes = - await IncidentPublicNoteService.findBy({ - query: { - incidentId: QueryHelper.any( - incidentsOnStatusPage - ), - projectId: statusPage.projectId!, - }, - select: { - note: true, - incidentId: true, - postedAt: true, - }, - sort: { - postedAt: SortOrder.Descending, // new note first - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - } - - let incidentStateTimelines: Array<IncidentStateTimeline> = - []; - - if (incidentsOnStatusPage.length > 0) { - incidentStateTimelines = - await IncidentStateTimelineService.findBy({ - query: { - incidentId: QueryHelper.any( - incidentsOnStatusPage - ), - projectId: statusPage.projectId!, - }, - select: { - _id: true, - createdAt: true, - incidentId: true, - incidentState: { - _id: true, - name: true, - color: true, - isCreatedState: true, - isResolvedState: true, - isAcknowledgedState: true, - }, - }, - - sort: { - createdAt: SortOrder.Descending, // new note first - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - } - - // check if status page has active announcement. - - const today: Date = OneUptimeDate.getCurrentDate(); - - const activeAnnouncements: Array<StatusPageAnnouncement> = - await StatusPageAnnouncementService.findBy({ - query: { - statusPages: objectId as any, - showAnnouncementAt: QueryHelper.lessThan(today), - endAnnouncementAt: - QueryHelper.greaterThan(today), - projectId: statusPage.projectId!, - }, - select: { - createdAt: true, - title: true, - description: true, - _id: true, - showAnnouncementAt: true, - endAnnouncementAt: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - // check if status page has active scheduled events. - - let scheduledEventsSelect: Select<ScheduledMaintenance> = { - createdAt: true, - title: true, - description: true, - _id: true, - endsAt: true, - startsAt: true, - currentScheduledMaintenanceState: { - name: true, - color: true, - isScheduledState: true, - isResolvedState: true, - isOngoingState: true, - }, - monitors: { - _id: true, - }, - }; - - if (statusPage.showScheduledEventLabelsOnStatusPage) { - scheduledEventsSelect = { - ...scheduledEventsSelect, - labels: { - name: true, - color: true, - }, - }; - } - - const scheduledMaintenanceEvents: Array<ScheduledMaintenance> = - await ScheduledMaintenanceService.findBy({ - query: { - currentScheduledMaintenanceState: { - isOngoingState: true, - } as any, - statusPages: objectId as any, - projectId: statusPage.projectId!, - }, - select: scheduledEventsSelect, - sort: { - startsAt: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - const futureScheduledMaintenanceEvents: Array<ScheduledMaintenance> = - await ScheduledMaintenanceService.findBy({ - query: { - currentScheduledMaintenanceState: { - isScheduledState: true, - } as any, - statusPages: objectId as any, - projectId: statusPage.projectId!, - }, - select: scheduledEventsSelect, - sort: { - startsAt: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - futureScheduledMaintenanceEvents.forEach( - (event: ScheduledMaintenance) => { - scheduledMaintenanceEvents.push(event); - } - ); - - const scheduledMaintenanceEventsOnStatusPage: Array<ObjectID> = - scheduledMaintenanceEvents.map( - (event: ScheduledMaintenance) => { - return event.id!; - } - ); - - let scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = - []; - - if (scheduledMaintenanceEventsOnStatusPage.length > 0) { - scheduledMaintenanceEventsPublicNotes = - await ScheduledMaintenancePublicNoteService.findBy({ - query: { - scheduledMaintenanceId: QueryHelper.any( - scheduledMaintenanceEventsOnStatusPage - ), - projectId: statusPage.projectId!, - }, - select: { - postedAt: true, - note: true, - scheduledMaintenanceId: true, - }, - sort: { - postedAt: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - } - - let scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = - []; - - if (scheduledMaintenanceEventsOnStatusPage.length > 0) { - scheduledMaintenanceStateTimelines = - await ScheduledMaintenanceStateTimelineService.findBy( - { - query: { - scheduledMaintenanceId: QueryHelper.any( - scheduledMaintenanceEventsOnStatusPage - ), - projectId: statusPage.projectId!, - }, - select: { - _id: true, - createdAt: true, - scheduledMaintenanceId: true, - scheduledMaintenanceState: { - _id: true, - color: true, - name: true, - isScheduledState: true, - isResolvedState: true, - isOngoingState: true, - }, - }, - - sort: { - createdAt: SortOrder.Descending, // new note first - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - } - ); - } - - // get all status page bar chart rules - const statusPageHistoryChartBarColorRules: Array<StatusPageHistoryChartBarColorRule> = - await StatusPageHistoryChartBarColorRuleService.findBy({ - query: { - statusPageId: objectId, - }, - select: { - _id: true, - barColor: true, - order: true, - statusPageId: true, - uptimePercentGreaterThanOrEqualTo: true, - }, - sort: { - order: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - const response: JSONObject = { - scheduledMaintenanceEventsPublicNotes: - BaseModel.toJSONArray( - scheduledMaintenanceEventsPublicNotes, - ScheduledMaintenancePublicNote - ), - statusPageHistoryChartBarColorRules: - BaseModel.toJSONArray( - statusPageHistoryChartBarColorRules, - StatusPageHistoryChartBarColorRule - ), - scheduledMaintenanceEvents: BaseModel.toJSONArray( - scheduledMaintenanceEvents, - ScheduledMaintenance - ), - activeAnnouncements: BaseModel.toJSONArray( - activeAnnouncements, - StatusPageAnnouncement - ), - incidentPublicNotes: BaseModel.toJSONArray( - incidentPublicNotes, - IncidentPublicNote - ), - activeIncidents: BaseModel.toJSONArray( - activeIncidents, - Incident - ), - monitorStatusTimelines: BaseModel.toJSONArray( - monitorStatusTimelines, - MonitorStatusTimeline - ), - resourceGroups: BaseModel.toJSONArray( - groups, - StatusPageGroup - ), - monitorStatuses: BaseModel.toJSONArray( - monitorStatuses, - MonitorStatus - ), - statusPageResources: BaseModel.toJSONArray( - statusPageResources, - StatusPageResource - ), - incidentStateTimelines: BaseModel.toJSONArray( - incidentStateTimelines, - IncidentStateTimeline - ), - statusPage: BaseModel.toJSONObject( - statusPage, - StatusPage - ), - scheduledMaintenanceStateTimelines: - BaseModel.toJSONArray( - scheduledMaintenanceStateTimelines, - ScheduledMaintenanceStateTimeline - ), - - monitorGroupCurrentStatuses: JSONFunctions.serialize( - monitorGroupCurrentStatuses - ), - monitorsInGroup: - JSONFunctions.serialize(monitorsInGroup), - }; - - return Response.sendJsonObjectResponse(req, res, response); - } catch (err) { - next(err); - } - } - ); - - this.router.put( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/update-subscription/:statusPageId/:subscriberId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.subscribeToStatusPage(req); - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/get-subscription/:statusPageId/:subscriberId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const subscriber: StatusPageSubscriber = - await this.getSubscriber(req); - - return Response.sendEntityResponse( - req, - res, - subscriber, - StatusPageSubscriber - ); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/subscribe/:statusPageId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.subscribeToStatusPage(req); - - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/incidents/:statusPageId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - const response: JSONObject = await this.getIncidents( - objectId, - null, - await CommonAPI.getDatabaseCommonInteractionProps(req), - req - ); - - return Response.sendJsonObjectResponse(req, res, response); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/scheduled-maintenance-events/:statusPageId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - const response: JSONObject = - await this.getScheduledMaintenanceEvents( - objectId, - null, - await CommonAPI.getDatabaseCommonInteractionProps( - req - ), - req - ); - - return Response.sendJsonObjectResponse(req, res, response); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/announcements/:statusPageId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - const response: JSONObject = await this.getAnnouncements( - objectId, - null, - await CommonAPI.getDatabaseCommonInteractionProps(req), - req - ); - - return Response.sendJsonObjectResponse(req, res, response); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/incidents/:statusPageId/:incidentId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - const incidentId: ObjectID = new ObjectID( - req.params['incidentId'] as string - ); - - const response: JSONObject = await this.getIncidents( - objectId, - incidentId, - await CommonAPI.getDatabaseCommonInteractionProps(req), - req - ); - - return Response.sendJsonObjectResponse(req, res, response); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/scheduled-maintenance-events/:statusPageId/:scheduledMaintenanceId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - const scheduledMaintenanceId: ObjectID = new ObjectID( - req.params['scheduledMaintenanceId'] as string - ); - - const response: JSONObject = - await this.getScheduledMaintenanceEvents( - objectId, - scheduledMaintenanceId, - await CommonAPI.getDatabaseCommonInteractionProps( - req - ), - req - ); - - return Response.sendJsonObjectResponse(req, res, response); - } catch (err) { - next(err); - } - } - ); - - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/announcements/:statusPageId/:announcementId`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - const announcementId: ObjectID = new ObjectID( - req.params['announcementId'] as string - ); - - const response: JSONObject = await this.getAnnouncements( - objectId, - announcementId, - await CommonAPI.getDatabaseCommonInteractionProps(req), - req - ); - - return Response.sendJsonObjectResponse(req, res, response); - } catch (err) { - next(err); - } - } - ); - } - - public async getScheduledMaintenanceEvents( - statusPageId: ObjectID, - scheduledMaintenanceId: ObjectID | null, - props: DatabaseCommonInteractionProps, - req: ExpressRequest - ): Promise<JSONObject> { - if (!(await this.service.hasReadAccess(statusPageId, props, req))) { - throw new NotAuthenticatedException( - 'You are not authenticated to access this status page' - ); + public constructor() { + super(StatusPage, StatusPageService); + + // CNAME verification api + this.router.get( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/cname-verification/:token`, + async (req: ExpressRequest, res: ExpressResponse) => { + const host: string | undefined = req.get("host"); + + if (!host) { + throw new BadDataException("Host not found"); } - const statusPage: StatusPage | null = await StatusPageService.findOneBy( - { - query: { - _id: statusPageId.toString(), - }, - select: { - _id: true, - projectId: true, - showScheduledEventHistoryInDays: true, - showScheduledEventLabelsOnStatusPage: true, - }, - props: { - isRoot: true, - }, - } - ); + const token: string = req.params["token"] as string; - if (!statusPage) { - throw new BadDataException('Status Page not found'); + logger.debug(`CNAME Verification: Host:${host} - Token:${token}`); + + const domain: StatusPageDomain | null = + await StatusPageDomainService.findOneBy({ + query: { + cnameVerificationToken: token, + fullDomain: host, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); + + if (!domain) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid token."), + ); } - // get monitors on status page. - const statusPageResources: Array<StatusPageResource> = - await StatusPageResourceService.findBy({ - query: { - statusPageId: statusPageId, - }, - select: { - statusPageGroupId: true, - monitorId: true, - displayTooltip: true, - displayDescription: true, - displayName: true, - monitor: { - _id: true, - currentMonitorStatusId: true, - }, - monitorGroupId: true, - }, + return Response.sendEmptySuccessResponse(req, res); + }, + ); - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, + // ACME Challenge Validation. + this.router.get( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/.well-known/acme-challenge/:token`, + async (req: ExpressRequest, res: ExpressResponse) => { + const challenge: AcmeChallenge | null = + await AcmeChallengeService.findOneBy({ + query: { + token: req.params["token"] as string, + }, + select: { + challenge: true, + }, + props: { + isRoot: true, + }, + }); + + if (!challenge) { + return Response.sendErrorResponse( + req, + res, + new NotFoundException("Challenge not found"), + ); + } + + return Response.sendTextResponse( + req, + res, + challenge.challenge as string, + ); + }, + ); + + this.router.post( + `${new this.entityType().getCrudApiPath()?.toString()}/domain`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + if (!req.body["domain"]) { + throw new BadDataException("domain is required in request body"); + } + + const domain: string = req.body["domain"] as string; + + const statusPageDomain: StatusPageDomain | null = + await StatusPageDomainService.findOneBy({ + query: { + fullDomain: domain, + domain: { + isVerified: true, + } as any, + }, + select: { + statusPageId: true, + }, + props: { + isRoot: true, + }, }); - // check if status page has active scheduled events. - const today: Date = OneUptimeDate.getCurrentDate(); - const historyDays: Date = OneUptimeDate.getSomeDaysAgo( - statusPage.showScheduledEventHistoryInDays || 14 - ); + if (!statusPageDomain) { + throw new BadDataException("No status page found with this domain"); + } - let query: Query<ScheduledMaintenance> = { - startsAt: QueryHelper.inBetween(historyDays, today), - statusPages: [statusPageId] as any, - projectId: statusPage.projectId!, - }; + const objectId: ObjectID = statusPageDomain.statusPageId!; - if (scheduledMaintenanceId) { - query = { - _id: scheduledMaintenanceId.toString(), - statusPages: [statusPageId] as any, - projectId: statusPage.projectId!, - }; + return Response.sendJsonObjectResponse(req, res, { + statusPageId: objectId.toString(), + }); + } catch (err) { + next(err); } + }, + ); - let scheduledEventsSelect: Select<ScheduledMaintenance> = { + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/master-page/:statusPageId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + const select: Select<StatusPage> = { + _id: true, + slug: true, + coverImageFileId: true, + logoFileId: true, + pageTitle: true, + pageDescription: true, + copyrightText: true, + customCSS: true, + customJavaScript: true, + hidePoweredByOneUptimeBranding: true, + headerHTML: true, + footerHTML: true, + enableEmailSubscribers: true, + enableSmsSubscribers: true, + isPublicStatusPage: true, + allowSubscribersToChooseResources: true, + requireSsoForLogin: true, + coverImageFile: { + file: true, + _id: true, + type: true, + name: true, + }, + faviconFile: { + file: true, + _id: true, + type: true, + name: true, + }, + logoFile: { + file: true, + _id: true, + type: true, + name: true, + }, + }; + + const hasEnabledSSO: PositiveNumber = + await StatusPageSsoService.countBy({ + query: { + isEnabled: true, + statusPageId: objectId, + }, + props: { + isRoot: true, + }, + }); + + const item: StatusPage | null = await this.service.findOneById({ + id: objectId, + select, + props: { + isRoot: true, + }, + }); + + if (!item) { + throw new BadDataException("Status Page not found"); + } + + const footerLinks: Array<StatusPageFooterLink> = + await StatusPageFooterLinkService.findBy({ + query: { + statusPageId: objectId, + }, + select: { + _id: true, + link: true, + title: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + const headerLinks: Array<StatusPageHeaderLink> = + await StatusPageHeaderLinkService.findBy({ + query: { + statusPageId: objectId, + }, + select: { + _id: true, + link: true, + title: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + const response: JSONObject = { + statusPage: BaseModel.toJSON(item, StatusPage), + footerLinks: BaseModel.toJSONArray( + footerLinks, + StatusPageFooterLink, + ), + headerLinks: BaseModel.toJSONArray( + headerLinks, + StatusPageHeaderLink, + ), + hasEnabledSSO: hasEnabledSSO.toNumber(), + }; + + return Response.sendJsonObjectResponse(req, res, response); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType().getCrudApiPath()?.toString()}/sso/:statusPageId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + const sso: Array<StatusPageSSO> = await StatusPageSsoService.findBy({ + query: { + statusPageId: objectId, + isEnabled: true, + }, + select: { + signOnURL: true, + name: true, + description: true, + _id: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + return Response.sendEntityArrayResponse( + req, + res, + sso, + new PositiveNumber(sso.length), + StatusPageSSO, + ); + } catch (err) { + next(err); + } + }, + ); + + // Get all status page resources for subscriber to subscribe to. + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/resources/:statusPageId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + if ( + !(await this.service.hasReadAccess( + objectId, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + )) + ) { + throw new NotAuthenticatedException( + "You are not authenticated to access this status page", + ); + } + + const resources: Array<StatusPageResource> = + await StatusPageResourceService.findBy({ + query: { + statusPageId: objectId, + }, + select: { + _id: true, + displayName: true, + order: true, + statusPageGroup: { + _id: true, + name: true, + order: true, + }, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + return Response.sendEntityArrayResponse( + req, + res, + resources, + new PositiveNumber(resources.length), + StatusPageResource, + ); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/overview/:statusPageId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); + const endDate: Date = OneUptimeDate.getCurrentDate(); + + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + if ( + !(await this.service.hasReadAccess( + objectId, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + )) + ) { + throw new NotAuthenticatedException( + "You are not authenticated to access this status page", + ); + } + + const statusPage: StatusPage | null = + await StatusPageService.findOneBy({ + query: { + _id: objectId.toString(), + }, + select: { + _id: true, + projectId: true, + isPublicStatusPage: true, + overviewPageDescription: true, + showIncidentLabelsOnStatusPage: true, + showScheduledEventLabelsOnStatusPage: true, + downtimeMonitorStatuses: { + _id: true, + }, + defaultBarColor: true, + }, + props: { + isRoot: true, + }, + }); + + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } + + //get monitor statuses + + const monitorStatuses: Array<MonitorStatus> = + await MonitorStatusService.findBy({ + query: { + projectId: statusPage.projectId!, + }, + select: { + name: true, + color: true, + priority: true, + isOperationalState: true, + }, + sort: { + priority: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + // get resource groups. + + const groups: Array<StatusPageGroup> = + await StatusPageGroupService.findBy({ + query: { + statusPageId: objectId, + }, + select: { + name: true, + order: true, + description: true, + isExpandedByDefault: true, + }, + sort: { + order: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + // get monitors on status page. + const statusPageResources: Array<StatusPageResource> = + await StatusPageResourceService.findBy({ + query: { + statusPageId: objectId, + }, + select: { + statusPageGroupId: true, + monitorId: true, + displayTooltip: true, + displayDescription: true, + displayName: true, + showStatusHistoryChart: true, + showCurrentStatus: true, + order: true, + monitor: { + _id: true, + currentMonitorStatusId: true, + }, + monitorGroupId: true, + showUptimePercent: true, + uptimePercentPrecision: true, + }, + sort: { + order: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const monitorGroupIds: Array<ObjectID> = statusPageResources + .map((resource: StatusPageResource) => { + return resource.monitorGroupId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + // get monitors in the group. + const monitorGroupCurrentStatuses: Dictionary<ObjectID> = {}; + const monitorsInGroup: Dictionary<Array<ObjectID>> = {}; + + // get monitor status charts. + const monitorsOnStatusPage: Array<ObjectID> = statusPageResources + .map((monitor: StatusPageResource) => { + return monitor.monitorId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + const monitorsOnStatusPageForTimeline: Array<ObjectID> = + statusPageResources + .filter((monitor: StatusPageResource) => { + return ( + monitor.showStatusHistoryChart || monitor.showUptimePercent + ); + }) + .map((monitor: StatusPageResource) => { + return monitor.monitorId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + for (const monitorGroupId of monitorGroupIds) { + // get current status of monitors in the group. + + const currentStatus: MonitorStatus = + await MonitorGroupService.getCurrentStatus(monitorGroupId, { + isRoot: true, + }); + + monitorGroupCurrentStatuses[monitorGroupId.toString()] = + currentStatus.id!; + + // get monitors in the group. + + const groupResources: Array<MonitorGroupResource> = + await MonitorGroupResourceService.findBy({ + query: { + monitorGroupId: monitorGroupId, + }, + select: { + monitorId: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + }); + + const monitorsInGroupIds: Array<ObjectID> = groupResources + .map((resource: MonitorGroupResource) => { + return resource.monitorId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + const shouldShowTimelineForThisGroup: boolean = Boolean( + statusPageResources.find((resource: StatusPageResource) => { + return ( + resource.monitorGroupId?.toString() === + monitorGroupId.toString() && + (resource.showStatusHistoryChart || + resource.showUptimePercent) + ); + }), + ); + + for (const monitorId of monitorsInGroupIds) { + if (!monitorId) { + continue; + } + + if ( + !monitorsOnStatusPage.find((item: ObjectID) => { + return item.toString() === monitorId.toString(); + }) + ) { + monitorsOnStatusPage.push(monitorId); + } + + // add this to the timeline event for this group. + + if ( + shouldShowTimelineForThisGroup && + !monitorsOnStatusPageForTimeline.find((item: ObjectID) => { + return item.toString() === monitorId.toString(); + }) + ) { + monitorsOnStatusPageForTimeline.push(monitorId); + } + } + + monitorsInGroup[monitorGroupId.toString()] = monitorsInGroupIds; + } + + let monitorStatusTimelines: Array<MonitorStatusTimeline> = []; + + if (monitorsOnStatusPageForTimeline.length > 0) { + monitorStatusTimelines = await MonitorStatusTimelineService.findBy({ + query: { + monitorId: QueryHelper.any(monitorsOnStatusPageForTimeline), + endsAt: QueryHelper.inBetween(startDate, endDate), + }, + select: { + monitorId: true, + createdAt: true, + endsAt: true, + startsAt: true, + monitorStatus: { + name: true, + color: true, + priority: true, + } as any, + }, + sort: { + createdAt: SortOrder.Descending, + }, + skip: 0, + limit: LIMIT_MAX, // This can be optimized. + props: { + isRoot: true, + }, + }); + + monitorStatusTimelines = monitorStatusTimelines.concat( + await MonitorStatusTimelineService.findBy({ + query: { + monitorId: QueryHelper.any(monitorsOnStatusPageForTimeline), + endsAt: QueryHelper.isNull(), + }, + select: { + monitorId: true, + createdAt: true, + endsAt: true, + startsAt: true, + monitorStatus: { + name: true, + color: true, + priority: true, + } as any, + }, + sort: { + createdAt: SortOrder.Descending, + }, + skip: 0, + limit: LIMIT_MAX, // This can be optimized. + props: { + isRoot: true, + }, + }), + ); + + // sort monitorStatusTimelines by createdAt. + monitorStatusTimelines = monitorStatusTimelines.sort( + (a: MonitorStatusTimeline, b: MonitorStatusTimeline) => { + if (!a.createdAt || !b.createdAt) { + return 0; + } + + return b.createdAt!.getTime() - a.createdAt!.getTime(); + }, + ); + } + + // check if status page has active incident. + let activeIncidents: Array<Incident> = []; + if (monitorsOnStatusPage.length > 0) { + let select: Select<Incident> = { + createdAt: true, + title: true, + description: true, + _id: true, + incidentSeverity: { + name: true, + color: true, + }, + currentIncidentState: { + _id: true, + name: true, + color: true, + order: true, + }, + monitors: { + _id: true, + }, + }; + + if (statusPage.showIncidentLabelsOnStatusPage) { + select = { + ...select, + labels: { + name: true, + color: true, + }, + }; + } + + const unresolvedIncidentStates: Array<IncidentState> = + await IncidentStateService.getUnresolvedIncidentStates( + statusPage.projectId!, + { + isRoot: true, + }, + ); + + const unresolvedIncidentStateIds: Array<ObjectID> = + unresolvedIncidentStates.map((state: IncidentState) => { + return state.id!; + }); + + activeIncidents = await IncidentService.findBy({ + query: { + monitors: monitorsOnStatusPage as any, + currentIncidentStateId: QueryHelper.any( + unresolvedIncidentStateIds, + ), + projectId: statusPage.projectId!, + }, + select: select, + sort: { + createdAt: SortOrder.Ascending, + }, + + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + const incidentsOnStatusPage: Array<ObjectID> = activeIncidents.map( + (incident: Incident) => { + return incident.id!; + }, + ); + + let incidentPublicNotes: Array<IncidentPublicNote> = []; + + if (incidentsOnStatusPage.length > 0) { + incidentPublicNotes = await IncidentPublicNoteService.findBy({ + query: { + incidentId: QueryHelper.any(incidentsOnStatusPage), + projectId: statusPage.projectId!, + }, + select: { + note: true, + incidentId: true, + postedAt: true, + }, + sort: { + postedAt: SortOrder.Descending, // new note first + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + let incidentStateTimelines: Array<IncidentStateTimeline> = []; + + if (incidentsOnStatusPage.length > 0) { + incidentStateTimelines = await IncidentStateTimelineService.findBy({ + query: { + incidentId: QueryHelper.any(incidentsOnStatusPage), + projectId: statusPage.projectId!, + }, + select: { + _id: true, + createdAt: true, + incidentId: true, + incidentState: { + _id: true, + name: true, + color: true, + isCreatedState: true, + isResolvedState: true, + isAcknowledgedState: true, + }, + }, + + sort: { + createdAt: SortOrder.Descending, // new note first + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + // check if status page has active announcement. + + const today: Date = OneUptimeDate.getCurrentDate(); + + const activeAnnouncements: Array<StatusPageAnnouncement> = + await StatusPageAnnouncementService.findBy({ + query: { + statusPages: objectId as any, + showAnnouncementAt: QueryHelper.lessThan(today), + endAnnouncementAt: QueryHelper.greaterThan(today), + projectId: statusPage.projectId!, + }, + select: { + createdAt: true, + title: true, + description: true, + _id: true, + showAnnouncementAt: true, + endAnnouncementAt: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + // check if status page has active scheduled events. + + let scheduledEventsSelect: Select<ScheduledMaintenance> = { createdAt: true, title: true, description: true, @@ -1566,920 +911,1424 @@ export default class StatusPageAPI extends BaseAPI< endsAt: true, startsAt: true, currentScheduledMaintenanceState: { - name: true, - color: true, - isScheduledState: true, - isResolvedState: true, - isOngoingState: true, - order: true, + name: true, + color: true, + isScheduledState: true, + isResolvedState: true, + isOngoingState: true, }, monitors: { - _id: true, + _id: true, }, - }; + }; - if (statusPage.showScheduledEventLabelsOnStatusPage) { + if (statusPage.showScheduledEventLabelsOnStatusPage) { scheduledEventsSelect = { - ...scheduledEventsSelect, - labels: { - name: true, - color: true, - }, + ...scheduledEventsSelect, + labels: { + name: true, + color: true, + }, }; - } + } - const scheduledMaintenanceEvents: Array<ScheduledMaintenance> = + const scheduledMaintenanceEvents: Array<ScheduledMaintenance> = await ScheduledMaintenanceService.findBy({ - query: query, - select: scheduledEventsSelect, - sort: { - startsAt: SortOrder.Descending, - }, - - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, + query: { + currentScheduledMaintenanceState: { + isOngoingState: true, + } as any, + statusPages: objectId as any, + projectId: statusPage.projectId!, + }, + select: scheduledEventsSelect, + sort: { + startsAt: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, }); - let futureScheduledMaintenanceEvents: Array<ScheduledMaintenance> = []; + const futureScheduledMaintenanceEvents: Array<ScheduledMaintenance> = + await ScheduledMaintenanceService.findBy({ + query: { + currentScheduledMaintenanceState: { + isScheduledState: true, + } as any, + statusPages: objectId as any, + projectId: statusPage.projectId!, + }, + select: scheduledEventsSelect, + sort: { + startsAt: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); - // If there is no scheduledMaintenanceId, then fetch all future scheduled events. - if (!scheduledMaintenanceId) { - futureScheduledMaintenanceEvents = - await ScheduledMaintenanceService.findBy({ - query: { - currentScheduledMaintenanceState: { - isScheduledState: true, - } as any, - statusPages: [statusPageId] as any, - projectId: statusPage.projectId!, - }, - select: scheduledEventsSelect, - sort: { - createdAt: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); + futureScheduledMaintenanceEvents.forEach( + (event: ScheduledMaintenance) => { + scheduledMaintenanceEvents.push(event); + }, + ); - futureScheduledMaintenanceEvents.forEach( - (event: ScheduledMaintenance) => { - scheduledMaintenanceEvents.push(event); - } - ); - } - - const scheduledMaintenanceEventsOnStatusPage: Array<ObjectID> = + const scheduledMaintenanceEventsOnStatusPage: Array<ObjectID> = scheduledMaintenanceEvents.map((event: ScheduledMaintenance) => { - return event.id!; + return event.id!; }); - let scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = + let scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = []; - if (scheduledMaintenanceEventsOnStatusPage.length > 0) { + if (scheduledMaintenanceEventsOnStatusPage.length > 0) { scheduledMaintenanceEventsPublicNotes = - await ScheduledMaintenancePublicNoteService.findBy({ - query: { - scheduledMaintenanceId: QueryHelper.any( - scheduledMaintenanceEventsOnStatusPage - ), - projectId: statusPage.projectId!, - }, - select: { - postedAt: true, - note: true, - scheduledMaintenanceId: true, - }, - sort: { - postedAt: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - } - - let scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = - []; - - if (scheduledMaintenanceEventsOnStatusPage.length > 0) { - scheduledMaintenanceStateTimelines = - await ScheduledMaintenanceStateTimelineService.findBy({ - query: { - scheduledMaintenanceId: QueryHelper.any( - scheduledMaintenanceEventsOnStatusPage - ), - projectId: statusPage.projectId!, - }, - select: { - _id: true, - createdAt: true, - scheduledMaintenanceId: true, - scheduledMaintenanceState: { - name: true, - color: true, - isScheduledState: true, - isResolvedState: true, - isOngoingState: true, - }, - }, - - sort: { - createdAt: SortOrder.Descending, // new note first - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - } - - const monitorGroupIds: Array<ObjectID> = statusPageResources - .map((resource: StatusPageResource) => { - return resource.monitorGroupId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - // get monitors in the group. - const monitorsInGroup: Dictionary<Array<ObjectID>> = {}; - - // get monitor status charts. - const monitorsOnStatusPage: Array<ObjectID> = statusPageResources - .map((monitor: StatusPageResource) => { - return monitor.monitorId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - for (const monitorGroupId of monitorGroupIds) { - // get monitors in the group. - - const groupResources: Array<MonitorGroupResource> = - await MonitorGroupResourceService.findBy({ - query: { - monitorGroupId: monitorGroupId, - }, - select: { - monitorId: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - }); - - const monitorsInGroupIds: Array<ObjectID> = groupResources - .map((resource: MonitorGroupResource) => { - return resource.monitorId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - for (const monitorId of monitorsInGroupIds) { - if ( - !monitorsOnStatusPage.find((item: ObjectID) => { - return item.toString() === monitorId.toString(); - }) - ) { - monitorsOnStatusPage.push(monitorId); - } - } - - monitorsInGroup[monitorGroupId.toString()] = monitorsInGroupIds; - } - - // get scheduled event states. - const scheduledEventStates: Array<ScheduledMaintenanceState> = - await ScheduledMaintenanceStateService.findBy({ + await ScheduledMaintenancePublicNoteService.findBy({ query: { - projectId: statusPage.projectId!, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, + scheduledMaintenanceId: QueryHelper.any( + scheduledMaintenanceEventsOnStatusPage, + ), + projectId: statusPage.projectId!, }, select: { - _id: true, - order: true, - isEndedState: true, - isOngoingState: true, - isScheduledState: true, + postedAt: true, + note: true, + scheduledMaintenanceId: true, }, + sort: { + postedAt: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + let scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = + []; + + if (scheduledMaintenanceEventsOnStatusPage.length > 0) { + scheduledMaintenanceStateTimelines = + await ScheduledMaintenanceStateTimelineService.findBy({ + query: { + scheduledMaintenanceId: QueryHelper.any( + scheduledMaintenanceEventsOnStatusPage, + ), + projectId: statusPage.projectId!, + }, + select: { + _id: true, + createdAt: true, + scheduledMaintenanceId: true, + scheduledMaintenanceState: { + _id: true, + color: true, + name: true, + isScheduledState: true, + isResolvedState: true, + isOngoingState: true, + }, + }, + + sort: { + createdAt: SortOrder.Descending, // new note first + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + // get all status page bar chart rules + const statusPageHistoryChartBarColorRules: Array<StatusPageHistoryChartBarColorRule> = + await StatusPageHistoryChartBarColorRuleService.findBy({ + query: { + statusPageId: objectId, + }, + select: { + _id: true, + barColor: true, + order: true, + statusPageId: true, + uptimePercentGreaterThanOrEqualTo: true, + }, + sort: { + order: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, }); - const response: JSONObject = { + const response: JSONObject = { scheduledMaintenanceEventsPublicNotes: BaseModel.toJSONArray( - scheduledMaintenanceEventsPublicNotes, - ScheduledMaintenancePublicNote + scheduledMaintenanceEventsPublicNotes, + ScheduledMaintenancePublicNote, ), - scheduledMaintenanceStates: BaseModel.toJSONArray( - scheduledEventStates, - ScheduledMaintenanceState + statusPageHistoryChartBarColorRules: BaseModel.toJSONArray( + statusPageHistoryChartBarColorRules, + StatusPageHistoryChartBarColorRule, ), scheduledMaintenanceEvents: BaseModel.toJSONArray( - scheduledMaintenanceEvents, - ScheduledMaintenance + scheduledMaintenanceEvents, + ScheduledMaintenance, ), - statusPageResources: BaseModel.toJSONArray( - statusPageResources, - StatusPageResource + activeAnnouncements: BaseModel.toJSONArray( + activeAnnouncements, + StatusPageAnnouncement, ), - scheduledMaintenanceStateTimelines: BaseModel.toJSONArray( - scheduledMaintenanceStateTimelines, - ScheduledMaintenanceStateTimeline - ), - monitorsInGroup: JSONFunctions.serialize(monitorsInGroup), - }; - - return response; - } - - public async getAnnouncements( - statusPageId: ObjectID, - announcementId: ObjectID | null, - props: DatabaseCommonInteractionProps, - req: ExpressRequest - ): Promise<JSONObject> { - if (!(await this.service.hasReadAccess(statusPageId, props, req))) { - throw new NotAuthenticatedException( - 'You are not authenticated to access this status page' - ); - } - - const statusPage: StatusPage | null = await StatusPageService.findOneBy( - { - query: { - _id: statusPageId.toString(), - }, - select: { - _id: true, - projectId: true, - showAnnouncementHistoryInDays: true, - }, - props: { - isRoot: true, - }, - } - ); - - if (!statusPage) { - throw new BadDataException('Status Page not found'); - } - - // check if status page has active announcement. - - const today: Date = OneUptimeDate.getCurrentDate(); - const historyDays: Date = OneUptimeDate.getSomeDaysAgo( - statusPage.showAnnouncementHistoryInDays || 14 - ); - - let query: Query<StatusPageAnnouncement> = { - statusPages: [statusPageId] as any, - showAnnouncementAt: QueryHelper.inBetween(historyDays, today), - projectId: statusPage.projectId!, - }; - - if (announcementId) { - query = { - statusPages: [statusPageId] as any, - _id: announcementId.toString(), - projectId: statusPage.projectId!, - }; - } - - const announcements: Array<StatusPageAnnouncement> = - await StatusPageAnnouncementService.findBy({ - query: query, - select: { - createdAt: true, - title: true, - description: true, - _id: true, - showAnnouncementAt: true, - endAnnouncementAt: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - // get monitors on status page. - const statusPageResources: Array<StatusPageResource> = - await StatusPageResourceService.findBy({ - query: { - statusPageId: statusPageId, - }, - select: { - statusPageGroupId: true, - monitorId: true, - displayTooltip: true, - displayDescription: true, - displayName: true, - monitor: { - _id: true, - currentMonitorStatusId: true, - }, - }, - - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - const response: JSONObject = { - announcements: BaseModel.toJSONArray( - announcements, - StatusPageAnnouncement - ), - statusPageResources: BaseModel.toJSONArray( - statusPageResources, - StatusPageResource - ), - }; - - return response; - } - - public async subscribeToStatusPage(req: ExpressRequest): Promise<void> { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - if ( - !(await this.service.hasReadAccess( - objectId, - await CommonAPI.getDatabaseCommonInteractionProps(req), - req - )) - ) { - throw new NotAuthenticatedException( - 'You are not authenticated to access this status page' - ); - } - - const statusPage: StatusPage | null = await StatusPageService.findOneBy( - { - query: { - _id: objectId.toString(), - }, - select: { - _id: true, - projectId: true, - enableEmailSubscribers: true, - enableSmsSubscribers: true, - allowSubscribersToChooseResources: true, - }, - props: { - isRoot: true, - }, - } - ); - - if (!statusPage) { - throw new BadDataException('Status Page not found'); - } - - if ( - req.body.data['subscriberEmail'] && - !statusPage.enableEmailSubscribers - ) { - throw new BadDataException( - 'Email subscribers not enabled for this status page.' - ); - } - - if ( - req.body.data['subscriberPhone'] && - !statusPage.enableSmsSubscribers - ) { - throw new BadDataException( - 'SMS subscribers not enabled for this status page.' - ); - } - - // if no email or phone, throw error. - - if ( - !req.body.data['subscriberEmail'] && - !req.body.data['subscriberPhone'] - ) { - throw new BadDataException( - 'Email or phone is required to subscribe to this status page.' - ); - } - - const email: Email | undefined = req.body.data['subscriberEmail'] - ? new Email(req.body.data['subscriberEmail'] as string) - : undefined; - - const phone: Phone | undefined = req.body.data['subscriberPhone'] - ? new Phone(req.body.data['subscriberPhone'] as string) - : undefined; - - let statusPageSubscriber: StatusPageSubscriber | null = null; - - let isUpdate: boolean = false; - - if (!req.params['subscriberId']) { - statusPageSubscriber = new StatusPageSubscriber(); - } else { - const subscriberId: ObjectID = new ObjectID( - req.params['subscriberId'] as string - ); - - statusPageSubscriber = await StatusPageSubscriberService.findOneBy({ - query: { - _id: subscriberId.toString(), - }, - props: { - isRoot: true, - }, - }); - - if (!statusPageSubscriber) { - throw new BadDataException('Subscriber not found'); - } - - isUpdate = true; - } - - if (email) { - statusPageSubscriber.subscriberEmail = email; - } - - if (phone) { - statusPageSubscriber.subscriberPhone = phone; - } - - if ( - req.body.data['statusPageResources'] && - !statusPage.allowSubscribersToChooseResources - ) { - throw new BadDataException( - 'Subscribers are not allowed to choose resources for this status page.' - ); - } - - statusPageSubscriber.statusPageId = objectId; - statusPageSubscriber.sendYouHaveSubscribedMessage = true; - statusPageSubscriber.projectId = statusPage.projectId!; - statusPageSubscriber.isSubscribedToAllResources = Boolean( - req.body.data['isSubscribedToAllResources'] - ); - - if ( - req.body.data['statusPageResources'] && - req.body.data['statusPageResources'].length > 0 - ) { - statusPageSubscriber.statusPageResources = req.body.data[ - 'statusPageResources' - ] as Array<StatusPageResource>; - } - - if (isUpdate) { - // check isUnsubscribed is set to false. - - statusPageSubscriber.isUnsubscribed = Boolean( - req.body.data['isUnsubscribed'] - ); - - await StatusPageSubscriberService.updateOneById({ - id: statusPageSubscriber.id!, - data: { - statusPageResources: - statusPageSubscriber.statusPageResources!, - isSubscribedToAllResources: - statusPageSubscriber.isSubscribedToAllResources!, - isUnsubscribed: statusPageSubscriber.isUnsubscribed, - } as any, - props: { - isRoot: true, - }, - }); - } else { - await StatusPageSubscriberService.create({ - data: statusPageSubscriber, - props: { - isRoot: true, - }, - }); - } - } - - public async getSubscriber( - req: ExpressRequest - ): Promise<StatusPageSubscriber> { - const objectId: ObjectID = new ObjectID( - req.params['statusPageId'] as string - ); - - if ( - !(await this.service.hasReadAccess( - objectId, - await CommonAPI.getDatabaseCommonInteractionProps(req), - req - )) - ) { - throw new NotAuthenticatedException( - 'You are not authenticated to access this status page' - ); - } - - const statusPage: StatusPage | null = await StatusPageService.findOneBy( - { - query: { - _id: objectId.toString(), - }, - select: { - _id: true, - projectId: true, - }, - props: { - isRoot: true, - }, - } - ); - - if (!statusPage) { - throw new BadDataException('Status Page not found'); - } - - const subscriberId: ObjectID = new ObjectID( - req.params['subscriberId'] as string - ); - - const statusPageSubscriber: StatusPageSubscriber | null = - await StatusPageSubscriberService.findOneBy({ - query: { - _id: subscriberId.toString(), - statusPageId: statusPage.id!, - }, - select: { - isUnsubscribed: true, - subscriberEmail: true, - subscriberPhone: true, - statusPageId: true, - statusPageResources: true, - isSubscribedToAllResources: true, - }, - props: { - isRoot: true, - }, - }); - - if (!statusPageSubscriber) { - throw new BadDataException('Subscriber not found'); - } - - return statusPageSubscriber; - } - - public async getIncidents( - statusPageId: ObjectID, - incidentId: ObjectID | null, - props: DatabaseCommonInteractionProps, - req: ExpressRequest - ): Promise<JSONObject> { - if (!(await this.service.hasReadAccess(statusPageId, props, req))) { - throw new NotAuthenticatedException( - 'You are not authenticated to access this status page' - ); - } - - const statusPage: StatusPage | null = await StatusPageService.findOneBy( - { - query: { - _id: statusPageId.toString(), - }, - select: { - _id: true, - projectId: true, - showIncidentHistoryInDays: true, - showIncidentLabelsOnStatusPage: true, - }, - props: { - isRoot: true, - }, - } - ); - - if (!statusPage) { - throw new BadDataException('Status Page not found'); - } - - // get monitors on status page. - const statusPageResources: Array<StatusPageResource> = - await StatusPageResourceService.findBy({ - query: { - statusPageId: statusPageId, - }, - select: { - statusPageGroupId: true, - monitorId: true, - displayTooltip: true, - displayDescription: true, - displayName: true, - monitor: { - _id: true, - currentMonitorStatusId: true, - }, - monitorGroupId: true, - }, - - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - const monitorGroupIds: Array<ObjectID> = statusPageResources - .map((resource: StatusPageResource) => { - return resource.monitorGroupId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - const monitorsInGroup: Dictionary<Array<ObjectID>> = {}; - - // get monitor status charts. - const monitorsOnStatusPage: Array<ObjectID> = statusPageResources - .map((monitor: StatusPageResource) => { - return monitor.monitorId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - for (const monitorGroupId of monitorGroupIds) { - // get current status of monitors in the group. - - // get monitors in the group. - - const groupResources: Array<MonitorGroupResource> = - await MonitorGroupResourceService.findBy({ - query: { - monitorGroupId: monitorGroupId, - }, - select: { - monitorId: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - }); - - const monitorsInGroupIds: Array<ObjectID> = groupResources - .map((resource: MonitorGroupResource) => { - return resource.monitorId!; - }) - .filter((id: ObjectID) => { - return Boolean(id); // remove nulls - }); - - for (const monitorId of monitorsInGroupIds) { - if ( - !monitorsOnStatusPage.find((item: ObjectID) => { - return item.toString() === monitorId.toString(); - }) - ) { - monitorsOnStatusPage.push(monitorId); - } - } - - monitorsInGroup[monitorGroupId.toString()] = monitorsInGroupIds; - } - - const today: Date = OneUptimeDate.getCurrentDate(); - const historyDays: Date = OneUptimeDate.getSomeDaysAgo( - statusPage.showIncidentHistoryInDays || 14 - ); - - let incidentQuery: Query<Incident> = { - monitors: monitorsOnStatusPage as any, - projectId: statusPage.projectId!, - createdAt: QueryHelper.inBetween(historyDays, today), - }; - - if (incidentId) { - incidentQuery = { - monitors: monitorsOnStatusPage as any, - projectId: statusPage.projectId!, - _id: incidentId.toString(), - }; - } - - // check if status page has active incident. - let incidents: Array<Incident> = []; - - let selectIncidents: Select<Incident> = { - createdAt: true, - title: true, - description: true, - _id: true, - incidentSeverity: { - name: true, - color: true, - }, - currentIncidentState: { - name: true, - color: true, - _id: true, - order: true, - }, - monitors: { - _id: true, - }, - }; - - if (statusPage.showIncidentLabelsOnStatusPage) { - selectIncidents = { - ...selectIncidents, - labels: { - name: true, - color: true, - }, - }; - } - - if (monitorsOnStatusPage.length > 0) { - incidents = await IncidentService.findBy({ - query: incidentQuery, - select: selectIncidents, - sort: { - createdAt: SortOrder.Descending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - let activeIncidents: Array<Incident> = []; - - const unresolvedIncidentStates: Array<IncidentState> = - await IncidentStateService.getUnresolvedIncidentStates( - statusPage.projectId!, - { - isRoot: true, - } - ); - - const unresolvbedIncidentStateIds: Array<ObjectID> = - unresolvedIncidentStates.map((state: IncidentState) => { - return state.id!; - }); - - // If there is no particular incident id to fetch then fetch active incidents. - if (!incidentId) { - activeIncidents = await IncidentService.findBy({ - query: { - monitors: monitorsOnStatusPage as any, - currentIncidentStateId: QueryHelper.any( - unresolvbedIncidentStateIds - ), - projectId: statusPage.projectId!, - }, - select: selectIncidents, - sort: { - createdAt: SortOrder.Descending, - }, - - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - } - - incidents = [...activeIncidents, ...incidents]; - - // get distinct by id. - - incidents = ArrayUtil.distinctByFieldName(incidents, '_id'); - } - - const incidentsOnStatusPage: Array<ObjectID> = incidents.map( - (incident: Incident) => { - return incident.id!; - } - ); - - let incidentPublicNotes: Array<IncidentPublicNote> = []; - - if (incidentsOnStatusPage.length > 0) { - incidentPublicNotes = await IncidentPublicNoteService.findBy({ - query: { - incidentId: QueryHelper.any(incidentsOnStatusPage), - projectId: statusPage.projectId!, - }, - select: { - postedAt: true, - note: true, - incidentId: true, - }, - sort: { - postedAt: SortOrder.Descending, // new note first - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - } - - let incidentStateTimelines: Array<IncidentStateTimeline> = []; - - if (incidentsOnStatusPage.length > 0) { - incidentStateTimelines = await IncidentStateTimelineService.findBy({ - query: { - incidentId: QueryHelper.any(incidentsOnStatusPage), - projectId: statusPage.projectId!, - }, - select: { - _id: true, - createdAt: true, - incidentId: true, - incidentState: { - name: true, - color: true, - }, - }, - sort: { - createdAt: SortOrder.Descending, // new note first - }, - - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - } - - // get all the incident states for this project. - const incidentStates: Array<IncidentState> = - await IncidentStateService.findBy({ - query: { - projectId: statusPage.projectId!, - }, - select: { - isResolvedState: true, - order: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - const response: JSONObject = { incidentPublicNotes: BaseModel.toJSONArray( - incidentPublicNotes, - IncidentPublicNote + incidentPublicNotes, + IncidentPublicNote, ), - incidentStates: BaseModel.toJSONArray( - incidentStates, - IncidentState + activeIncidents: BaseModel.toJSONArray(activeIncidents, Incident), + monitorStatusTimelines: BaseModel.toJSONArray( + monitorStatusTimelines, + MonitorStatusTimeline, + ), + resourceGroups: BaseModel.toJSONArray(groups, StatusPageGroup), + monitorStatuses: BaseModel.toJSONArray( + monitorStatuses, + MonitorStatus, ), - incidents: BaseModel.toJSONArray(incidents, Incident), statusPageResources: BaseModel.toJSONArray( - statusPageResources, - StatusPageResource + statusPageResources, + StatusPageResource, ), incidentStateTimelines: BaseModel.toJSONArray( - incidentStateTimelines, - IncidentStateTimeline + incidentStateTimelines, + IncidentStateTimeline, + ), + statusPage: BaseModel.toJSONObject(statusPage, StatusPage), + scheduledMaintenanceStateTimelines: BaseModel.toJSONArray( + scheduledMaintenanceStateTimelines, + ScheduledMaintenanceStateTimeline, + ), + + monitorGroupCurrentStatuses: JSONFunctions.serialize( + monitorGroupCurrentStatuses, ), monitorsInGroup: JSONFunctions.serialize(monitorsInGroup), - }; + }; - return response; + return Response.sendJsonObjectResponse(req, res, response); + } catch (err) { + next(err); + } + }, + ); + + this.router.put( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/update-subscription/:statusPageId/:subscriberId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.subscribeToStatusPage(req); + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/get-subscription/:statusPageId/:subscriberId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const subscriber: StatusPageSubscriber = + await this.getSubscriber(req); + + return Response.sendEntityResponse( + req, + res, + subscriber, + StatusPageSubscriber, + ); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/subscribe/:statusPageId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.subscribeToStatusPage(req); + + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/incidents/:statusPageId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + const response: JSONObject = await this.getIncidents( + objectId, + null, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + ); + + return Response.sendJsonObjectResponse(req, res, response); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/scheduled-maintenance-events/:statusPageId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + const response: JSONObject = await this.getScheduledMaintenanceEvents( + objectId, + null, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + ); + + return Response.sendJsonObjectResponse(req, res, response); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/announcements/:statusPageId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + const response: JSONObject = await this.getAnnouncements( + objectId, + null, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + ); + + return Response.sendJsonObjectResponse(req, res, response); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/incidents/:statusPageId/:incidentId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + const incidentId: ObjectID = new ObjectID( + req.params["incidentId"] as string, + ); + + const response: JSONObject = await this.getIncidents( + objectId, + incidentId, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + ); + + return Response.sendJsonObjectResponse(req, res, response); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/scheduled-maintenance-events/:statusPageId/:scheduledMaintenanceId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + const scheduledMaintenanceId: ObjectID = new ObjectID( + req.params["scheduledMaintenanceId"] as string, + ); + + const response: JSONObject = await this.getScheduledMaintenanceEvents( + objectId, + scheduledMaintenanceId, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + ); + + return Response.sendJsonObjectResponse(req, res, response); + } catch (err) { + next(err); + } + }, + ); + + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/announcements/:statusPageId/:announcementId`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + const announcementId: ObjectID = new ObjectID( + req.params["announcementId"] as string, + ); + + const response: JSONObject = await this.getAnnouncements( + objectId, + announcementId, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + ); + + return Response.sendJsonObjectResponse(req, res, response); + } catch (err) { + next(err); + } + }, + ); + } + + public async getScheduledMaintenanceEvents( + statusPageId: ObjectID, + scheduledMaintenanceId: ObjectID | null, + props: DatabaseCommonInteractionProps, + req: ExpressRequest, + ): Promise<JSONObject> { + if (!(await this.service.hasReadAccess(statusPageId, props, req))) { + throw new NotAuthenticatedException( + "You are not authenticated to access this status page", + ); } + + const statusPage: StatusPage | null = await StatusPageService.findOneBy({ + query: { + _id: statusPageId.toString(), + }, + select: { + _id: true, + projectId: true, + showScheduledEventHistoryInDays: true, + showScheduledEventLabelsOnStatusPage: true, + }, + props: { + isRoot: true, + }, + }); + + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } + + // get monitors on status page. + const statusPageResources: Array<StatusPageResource> = + await StatusPageResourceService.findBy({ + query: { + statusPageId: statusPageId, + }, + select: { + statusPageGroupId: true, + monitorId: true, + displayTooltip: true, + displayDescription: true, + displayName: true, + monitor: { + _id: true, + currentMonitorStatusId: true, + }, + monitorGroupId: true, + }, + + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + // check if status page has active scheduled events. + const today: Date = OneUptimeDate.getCurrentDate(); + const historyDays: Date = OneUptimeDate.getSomeDaysAgo( + statusPage.showScheduledEventHistoryInDays || 14, + ); + + let query: Query<ScheduledMaintenance> = { + startsAt: QueryHelper.inBetween(historyDays, today), + statusPages: [statusPageId] as any, + projectId: statusPage.projectId!, + }; + + if (scheduledMaintenanceId) { + query = { + _id: scheduledMaintenanceId.toString(), + statusPages: [statusPageId] as any, + projectId: statusPage.projectId!, + }; + } + + let scheduledEventsSelect: Select<ScheduledMaintenance> = { + createdAt: true, + title: true, + description: true, + _id: true, + endsAt: true, + startsAt: true, + currentScheduledMaintenanceState: { + name: true, + color: true, + isScheduledState: true, + isResolvedState: true, + isOngoingState: true, + order: true, + }, + monitors: { + _id: true, + }, + }; + + if (statusPage.showScheduledEventLabelsOnStatusPage) { + scheduledEventsSelect = { + ...scheduledEventsSelect, + labels: { + name: true, + color: true, + }, + }; + } + + const scheduledMaintenanceEvents: Array<ScheduledMaintenance> = + await ScheduledMaintenanceService.findBy({ + query: query, + select: scheduledEventsSelect, + sort: { + startsAt: SortOrder.Descending, + }, + + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + let futureScheduledMaintenanceEvents: Array<ScheduledMaintenance> = []; + + // If there is no scheduledMaintenanceId, then fetch all future scheduled events. + if (!scheduledMaintenanceId) { + futureScheduledMaintenanceEvents = + await ScheduledMaintenanceService.findBy({ + query: { + currentScheduledMaintenanceState: { + isScheduledState: true, + } as any, + statusPages: [statusPageId] as any, + projectId: statusPage.projectId!, + }, + select: scheduledEventsSelect, + sort: { + createdAt: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + futureScheduledMaintenanceEvents.forEach( + (event: ScheduledMaintenance) => { + scheduledMaintenanceEvents.push(event); + }, + ); + } + + const scheduledMaintenanceEventsOnStatusPage: Array<ObjectID> = + scheduledMaintenanceEvents.map((event: ScheduledMaintenance) => { + return event.id!; + }); + + let scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = + []; + + if (scheduledMaintenanceEventsOnStatusPage.length > 0) { + scheduledMaintenanceEventsPublicNotes = + await ScheduledMaintenancePublicNoteService.findBy({ + query: { + scheduledMaintenanceId: QueryHelper.any( + scheduledMaintenanceEventsOnStatusPage, + ), + projectId: statusPage.projectId!, + }, + select: { + postedAt: true, + note: true, + scheduledMaintenanceId: true, + }, + sort: { + postedAt: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + let scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = + []; + + if (scheduledMaintenanceEventsOnStatusPage.length > 0) { + scheduledMaintenanceStateTimelines = + await ScheduledMaintenanceStateTimelineService.findBy({ + query: { + scheduledMaintenanceId: QueryHelper.any( + scheduledMaintenanceEventsOnStatusPage, + ), + projectId: statusPage.projectId!, + }, + select: { + _id: true, + createdAt: true, + scheduledMaintenanceId: true, + scheduledMaintenanceState: { + name: true, + color: true, + isScheduledState: true, + isResolvedState: true, + isOngoingState: true, + }, + }, + + sort: { + createdAt: SortOrder.Descending, // new note first + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + const monitorGroupIds: Array<ObjectID> = statusPageResources + .map((resource: StatusPageResource) => { + return resource.monitorGroupId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + // get monitors in the group. + const monitorsInGroup: Dictionary<Array<ObjectID>> = {}; + + // get monitor status charts. + const monitorsOnStatusPage: Array<ObjectID> = statusPageResources + .map((monitor: StatusPageResource) => { + return monitor.monitorId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + for (const monitorGroupId of monitorGroupIds) { + // get monitors in the group. + + const groupResources: Array<MonitorGroupResource> = + await MonitorGroupResourceService.findBy({ + query: { + monitorGroupId: monitorGroupId, + }, + select: { + monitorId: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + }); + + const monitorsInGroupIds: Array<ObjectID> = groupResources + .map((resource: MonitorGroupResource) => { + return resource.monitorId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + for (const monitorId of monitorsInGroupIds) { + if ( + !monitorsOnStatusPage.find((item: ObjectID) => { + return item.toString() === monitorId.toString(); + }) + ) { + monitorsOnStatusPage.push(monitorId); + } + } + + monitorsInGroup[monitorGroupId.toString()] = monitorsInGroupIds; + } + + // get scheduled event states. + const scheduledEventStates: Array<ScheduledMaintenanceState> = + await ScheduledMaintenanceStateService.findBy({ + query: { + projectId: statusPage.projectId!, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + isEndedState: true, + isOngoingState: true, + isScheduledState: true, + }, + }); + + const response: JSONObject = { + scheduledMaintenanceEventsPublicNotes: BaseModel.toJSONArray( + scheduledMaintenanceEventsPublicNotes, + ScheduledMaintenancePublicNote, + ), + scheduledMaintenanceStates: BaseModel.toJSONArray( + scheduledEventStates, + ScheduledMaintenanceState, + ), + scheduledMaintenanceEvents: BaseModel.toJSONArray( + scheduledMaintenanceEvents, + ScheduledMaintenance, + ), + statusPageResources: BaseModel.toJSONArray( + statusPageResources, + StatusPageResource, + ), + scheduledMaintenanceStateTimelines: BaseModel.toJSONArray( + scheduledMaintenanceStateTimelines, + ScheduledMaintenanceStateTimeline, + ), + monitorsInGroup: JSONFunctions.serialize(monitorsInGroup), + }; + + return response; + } + + public async getAnnouncements( + statusPageId: ObjectID, + announcementId: ObjectID | null, + props: DatabaseCommonInteractionProps, + req: ExpressRequest, + ): Promise<JSONObject> { + if (!(await this.service.hasReadAccess(statusPageId, props, req))) { + throw new NotAuthenticatedException( + "You are not authenticated to access this status page", + ); + } + + const statusPage: StatusPage | null = await StatusPageService.findOneBy({ + query: { + _id: statusPageId.toString(), + }, + select: { + _id: true, + projectId: true, + showAnnouncementHistoryInDays: true, + }, + props: { + isRoot: true, + }, + }); + + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } + + // check if status page has active announcement. + + const today: Date = OneUptimeDate.getCurrentDate(); + const historyDays: Date = OneUptimeDate.getSomeDaysAgo( + statusPage.showAnnouncementHistoryInDays || 14, + ); + + let query: Query<StatusPageAnnouncement> = { + statusPages: [statusPageId] as any, + showAnnouncementAt: QueryHelper.inBetween(historyDays, today), + projectId: statusPage.projectId!, + }; + + if (announcementId) { + query = { + statusPages: [statusPageId] as any, + _id: announcementId.toString(), + projectId: statusPage.projectId!, + }; + } + + const announcements: Array<StatusPageAnnouncement> = + await StatusPageAnnouncementService.findBy({ + query: query, + select: { + createdAt: true, + title: true, + description: true, + _id: true, + showAnnouncementAt: true, + endAnnouncementAt: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + // get monitors on status page. + const statusPageResources: Array<StatusPageResource> = + await StatusPageResourceService.findBy({ + query: { + statusPageId: statusPageId, + }, + select: { + statusPageGroupId: true, + monitorId: true, + displayTooltip: true, + displayDescription: true, + displayName: true, + monitor: { + _id: true, + currentMonitorStatusId: true, + }, + }, + + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const response: JSONObject = { + announcements: BaseModel.toJSONArray( + announcements, + StatusPageAnnouncement, + ), + statusPageResources: BaseModel.toJSONArray( + statusPageResources, + StatusPageResource, + ), + }; + + return response; + } + + public async subscribeToStatusPage(req: ExpressRequest): Promise<void> { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + if ( + !(await this.service.hasReadAccess( + objectId, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + )) + ) { + throw new NotAuthenticatedException( + "You are not authenticated to access this status page", + ); + } + + const statusPage: StatusPage | null = await StatusPageService.findOneBy({ + query: { + _id: objectId.toString(), + }, + select: { + _id: true, + projectId: true, + enableEmailSubscribers: true, + enableSmsSubscribers: true, + allowSubscribersToChooseResources: true, + }, + props: { + isRoot: true, + }, + }); + + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } + + if ( + req.body.data["subscriberEmail"] && + !statusPage.enableEmailSubscribers + ) { + throw new BadDataException( + "Email subscribers not enabled for this status page.", + ); + } + + if (req.body.data["subscriberPhone"] && !statusPage.enableSmsSubscribers) { + throw new BadDataException( + "SMS subscribers not enabled for this status page.", + ); + } + + // if no email or phone, throw error. + + if ( + !req.body.data["subscriberEmail"] && + !req.body.data["subscriberPhone"] + ) { + throw new BadDataException( + "Email or phone is required to subscribe to this status page.", + ); + } + + const email: Email | undefined = req.body.data["subscriberEmail"] + ? new Email(req.body.data["subscriberEmail"] as string) + : undefined; + + const phone: Phone | undefined = req.body.data["subscriberPhone"] + ? new Phone(req.body.data["subscriberPhone"] as string) + : undefined; + + let statusPageSubscriber: StatusPageSubscriber | null = null; + + let isUpdate: boolean = false; + + if (!req.params["subscriberId"]) { + statusPageSubscriber = new StatusPageSubscriber(); + } else { + const subscriberId: ObjectID = new ObjectID( + req.params["subscriberId"] as string, + ); + + statusPageSubscriber = await StatusPageSubscriberService.findOneBy({ + query: { + _id: subscriberId.toString(), + }, + props: { + isRoot: true, + }, + }); + + if (!statusPageSubscriber) { + throw new BadDataException("Subscriber not found"); + } + + isUpdate = true; + } + + if (email) { + statusPageSubscriber.subscriberEmail = email; + } + + if (phone) { + statusPageSubscriber.subscriberPhone = phone; + } + + if ( + req.body.data["statusPageResources"] && + !statusPage.allowSubscribersToChooseResources + ) { + throw new BadDataException( + "Subscribers are not allowed to choose resources for this status page.", + ); + } + + statusPageSubscriber.statusPageId = objectId; + statusPageSubscriber.sendYouHaveSubscribedMessage = true; + statusPageSubscriber.projectId = statusPage.projectId!; + statusPageSubscriber.isSubscribedToAllResources = Boolean( + req.body.data["isSubscribedToAllResources"], + ); + + if ( + req.body.data["statusPageResources"] && + req.body.data["statusPageResources"].length > 0 + ) { + statusPageSubscriber.statusPageResources = req.body.data[ + "statusPageResources" + ] as Array<StatusPageResource>; + } + + if (isUpdate) { + // check isUnsubscribed is set to false. + + statusPageSubscriber.isUnsubscribed = Boolean( + req.body.data["isUnsubscribed"], + ); + + await StatusPageSubscriberService.updateOneById({ + id: statusPageSubscriber.id!, + data: { + statusPageResources: statusPageSubscriber.statusPageResources!, + isSubscribedToAllResources: + statusPageSubscriber.isSubscribedToAllResources!, + isUnsubscribed: statusPageSubscriber.isUnsubscribed, + } as any, + props: { + isRoot: true, + }, + }); + } else { + await StatusPageSubscriberService.create({ + data: statusPageSubscriber, + props: { + isRoot: true, + }, + }); + } + } + + public async getSubscriber( + req: ExpressRequest, + ): Promise<StatusPageSubscriber> { + const objectId: ObjectID = new ObjectID( + req.params["statusPageId"] as string, + ); + + if ( + !(await this.service.hasReadAccess( + objectId, + await CommonAPI.getDatabaseCommonInteractionProps(req), + req, + )) + ) { + throw new NotAuthenticatedException( + "You are not authenticated to access this status page", + ); + } + + const statusPage: StatusPage | null = await StatusPageService.findOneBy({ + query: { + _id: objectId.toString(), + }, + select: { + _id: true, + projectId: true, + }, + props: { + isRoot: true, + }, + }); + + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } + + const subscriberId: ObjectID = new ObjectID( + req.params["subscriberId"] as string, + ); + + const statusPageSubscriber: StatusPageSubscriber | null = + await StatusPageSubscriberService.findOneBy({ + query: { + _id: subscriberId.toString(), + statusPageId: statusPage.id!, + }, + select: { + isUnsubscribed: true, + subscriberEmail: true, + subscriberPhone: true, + statusPageId: true, + statusPageResources: true, + isSubscribedToAllResources: true, + }, + props: { + isRoot: true, + }, + }); + + if (!statusPageSubscriber) { + throw new BadDataException("Subscriber not found"); + } + + return statusPageSubscriber; + } + + public async getIncidents( + statusPageId: ObjectID, + incidentId: ObjectID | null, + props: DatabaseCommonInteractionProps, + req: ExpressRequest, + ): Promise<JSONObject> { + if (!(await this.service.hasReadAccess(statusPageId, props, req))) { + throw new NotAuthenticatedException( + "You are not authenticated to access this status page", + ); + } + + const statusPage: StatusPage | null = await StatusPageService.findOneBy({ + query: { + _id: statusPageId.toString(), + }, + select: { + _id: true, + projectId: true, + showIncidentHistoryInDays: true, + showIncidentLabelsOnStatusPage: true, + }, + props: { + isRoot: true, + }, + }); + + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } + + // get monitors on status page. + const statusPageResources: Array<StatusPageResource> = + await StatusPageResourceService.findBy({ + query: { + statusPageId: statusPageId, + }, + select: { + statusPageGroupId: true, + monitorId: true, + displayTooltip: true, + displayDescription: true, + displayName: true, + monitor: { + _id: true, + currentMonitorStatusId: true, + }, + monitorGroupId: true, + }, + + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const monitorGroupIds: Array<ObjectID> = statusPageResources + .map((resource: StatusPageResource) => { + return resource.monitorGroupId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + const monitorsInGroup: Dictionary<Array<ObjectID>> = {}; + + // get monitor status charts. + const monitorsOnStatusPage: Array<ObjectID> = statusPageResources + .map((monitor: StatusPageResource) => { + return monitor.monitorId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + for (const monitorGroupId of monitorGroupIds) { + // get current status of monitors in the group. + + // get monitors in the group. + + const groupResources: Array<MonitorGroupResource> = + await MonitorGroupResourceService.findBy({ + query: { + monitorGroupId: monitorGroupId, + }, + select: { + monitorId: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + }); + + const monitorsInGroupIds: Array<ObjectID> = groupResources + .map((resource: MonitorGroupResource) => { + return resource.monitorId!; + }) + .filter((id: ObjectID) => { + return Boolean(id); // remove nulls + }); + + for (const monitorId of monitorsInGroupIds) { + if ( + !monitorsOnStatusPage.find((item: ObjectID) => { + return item.toString() === monitorId.toString(); + }) + ) { + monitorsOnStatusPage.push(monitorId); + } + } + + monitorsInGroup[monitorGroupId.toString()] = monitorsInGroupIds; + } + + const today: Date = OneUptimeDate.getCurrentDate(); + const historyDays: Date = OneUptimeDate.getSomeDaysAgo( + statusPage.showIncidentHistoryInDays || 14, + ); + + let incidentQuery: Query<Incident> = { + monitors: monitorsOnStatusPage as any, + projectId: statusPage.projectId!, + createdAt: QueryHelper.inBetween(historyDays, today), + }; + + if (incidentId) { + incidentQuery = { + monitors: monitorsOnStatusPage as any, + projectId: statusPage.projectId!, + _id: incidentId.toString(), + }; + } + + // check if status page has active incident. + let incidents: Array<Incident> = []; + + let selectIncidents: Select<Incident> = { + createdAt: true, + title: true, + description: true, + _id: true, + incidentSeverity: { + name: true, + color: true, + }, + currentIncidentState: { + name: true, + color: true, + _id: true, + order: true, + }, + monitors: { + _id: true, + }, + }; + + if (statusPage.showIncidentLabelsOnStatusPage) { + selectIncidents = { + ...selectIncidents, + labels: { + name: true, + color: true, + }, + }; + } + + if (monitorsOnStatusPage.length > 0) { + incidents = await IncidentService.findBy({ + query: incidentQuery, + select: selectIncidents, + sort: { + createdAt: SortOrder.Descending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + let activeIncidents: Array<Incident> = []; + + const unresolvedIncidentStates: Array<IncidentState> = + await IncidentStateService.getUnresolvedIncidentStates( + statusPage.projectId!, + { + isRoot: true, + }, + ); + + const unresolvbedIncidentStateIds: Array<ObjectID> = + unresolvedIncidentStates.map((state: IncidentState) => { + return state.id!; + }); + + // If there is no particular incident id to fetch then fetch active incidents. + if (!incidentId) { + activeIncidents = await IncidentService.findBy({ + query: { + monitors: monitorsOnStatusPage as any, + currentIncidentStateId: QueryHelper.any( + unresolvbedIncidentStateIds, + ), + projectId: statusPage.projectId!, + }, + select: selectIncidents, + sort: { + createdAt: SortOrder.Descending, + }, + + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + incidents = [...activeIncidents, ...incidents]; + + // get distinct by id. + + incidents = ArrayUtil.distinctByFieldName(incidents, "_id"); + } + + const incidentsOnStatusPage: Array<ObjectID> = incidents.map( + (incident: Incident) => { + return incident.id!; + }, + ); + + let incidentPublicNotes: Array<IncidentPublicNote> = []; + + if (incidentsOnStatusPage.length > 0) { + incidentPublicNotes = await IncidentPublicNoteService.findBy({ + query: { + incidentId: QueryHelper.any(incidentsOnStatusPage), + projectId: statusPage.projectId!, + }, + select: { + postedAt: true, + note: true, + incidentId: true, + }, + sort: { + postedAt: SortOrder.Descending, // new note first + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + let incidentStateTimelines: Array<IncidentStateTimeline> = []; + + if (incidentsOnStatusPage.length > 0) { + incidentStateTimelines = await IncidentStateTimelineService.findBy({ + query: { + incidentId: QueryHelper.any(incidentsOnStatusPage), + projectId: statusPage.projectId!, + }, + select: { + _id: true, + createdAt: true, + incidentId: true, + incidentState: { + name: true, + color: true, + }, + }, + sort: { + createdAt: SortOrder.Descending, // new note first + }, + + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + } + + // get all the incident states for this project. + const incidentStates: Array<IncidentState> = + await IncidentStateService.findBy({ + query: { + projectId: statusPage.projectId!, + }, + select: { + isResolvedState: true, + order: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + const response: JSONObject = { + incidentPublicNotes: BaseModel.toJSONArray( + incidentPublicNotes, + IncidentPublicNote, + ), + incidentStates: BaseModel.toJSONArray(incidentStates, IncidentState), + incidents: BaseModel.toJSONArray(incidents, Incident), + statusPageResources: BaseModel.toJSONArray( + statusPageResources, + StatusPageResource, + ), + incidentStateTimelines: BaseModel.toJSONArray( + incidentStateTimelines, + IncidentStateTimeline, + ), + monitorsInGroup: JSONFunctions.serialize(monitorsInGroup), + }; + + return response; + } } diff --git a/CommonServer/API/StatusPageDomainAPI.ts b/CommonServer/API/StatusPageDomainAPI.ts index 3fe9241ad4..c948c32090 100644 --- a/CommonServer/API/StatusPageDomainAPI.ts +++ b/CommonServer/API/StatusPageDomainAPI.ts @@ -1,259 +1,240 @@ -import { StatusPageCNameRecord } from '../EnvironmentConfig'; -import UserMiddleware from '../Middleware/UserAuthorization'; +import { StatusPageCNameRecord } from "../EnvironmentConfig"; +import UserMiddleware from "../Middleware/UserAuthorization"; import StatusPageDomainService, { - Service as StatusPageDomainServiceType, -} from '../Services/StatusPageDomainService'; + Service as StatusPageDomainServiceType, +} from "../Services/StatusPageDomainService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import logger from '../Utils/Logger'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import CommonAPI from './CommonAPI'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import StatusPageDomain from 'Model/Models/StatusPageDomain'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import logger from "../Utils/Logger"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import CommonAPI from "./CommonAPI"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import StatusPageDomain from "Model/Models/StatusPageDomain"; export default class StatusPageDomainAPI extends BaseAPI< - StatusPageDomain, - StatusPageDomainServiceType + StatusPageDomain, + StatusPageDomainServiceType > { - public constructor() { - super(StatusPageDomain, StatusPageDomainService); + public constructor() { + super(StatusPageDomain, StatusPageDomainService); - // CNAME verification api. THis API will be used from the dashboard to validate the CNAME MANUALLY. - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/verify-cname/:id`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - if (!StatusPageCNameRecord) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - `Custom Domains not enabled for this + // CNAME verification api. THis API will be used from the dashboard to validate the CNAME MANUALLY. + this.router.get( + `${new this.entityType().getCrudApiPath()?.toString()}/verify-cname/:id`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + if (!StatusPageCNameRecord) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + `Custom Domains not enabled for this OneUptime installation. Please contact your server admin to enable this - feature.` - ) - ); - } + feature.`, + ), + ); + } - const databaseProps: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(req); + const databaseProps: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); - const id: ObjectID = new ObjectID( - req.params['id'] as string - ); + const id: ObjectID = new ObjectID(req.params["id"] as string); - // check if the user can read the domain. + // check if the user can read the domain. - const domainCount: PositiveNumber = - await StatusPageDomainService.countBy({ - query: { - _id: id.toString(), - }, - props: databaseProps, - }); + const domainCount: PositiveNumber = + await StatusPageDomainService.countBy({ + query: { + _id: id.toString(), + }, + props: databaseProps, + }); - if (domainCount.toNumber() === 0) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'The domain does not exist or user does not have access to it.' - ) - ); - } + if (domainCount.toNumber() === 0) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "The domain does not exist or user does not have access to it.", + ), + ); + } - const domain: StatusPageDomain | null = - await StatusPageDomainService.findOneBy({ - query: { - _id: id.toString(), - }, - select: { - _id: true, - fullDomain: true, - }, - props: { - isRoot: true, - }, - }); + const domain: StatusPageDomain | null = + await StatusPageDomainService.findOneBy({ + query: { + _id: id.toString(), + }, + select: { + _id: true, + fullDomain: true, + }, + props: { + isRoot: true, + }, + }); - if (!domain) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid token.') - ); - } + if (!domain) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid token."), + ); + } - if (!domain.fullDomain) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid domain.') - ); - } + if (!domain.fullDomain) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid domain."), + ); + } - const isValid: boolean = - await StatusPageDomainService.isCnameValid( - domain.fullDomain! - ); + const isValid: boolean = await StatusPageDomainService.isCnameValid( + domain.fullDomain!, + ); - if (!isValid) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'CNAME is not verified. Please make sure you have the correct record and please verify CNAME again. If you are sure that the record is correct, please wait for some time for the DNS to propagate.' - ) - ); - } + if (!isValid) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "CNAME is not verified. Please make sure you have the correct record and please verify CNAME again. If you are sure that the record is correct, please wait for some time for the DNS to propagate.", + ), + ); + } - return Response.sendEmptySuccessResponse(req, res); - } catch (e) { - next(e); - } - } - ); + return Response.sendEmptySuccessResponse(req, res); + } catch (e) { + next(e); + } + }, + ); - // Provision SSL API. THis API will be used from the dashboard to validate the CNAME MANUALLY. - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/order-ssl/:id`, - UserMiddleware.getUserMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - if (!StatusPageCNameRecord) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - `Custom Domains not enabled for this + // Provision SSL API. THis API will be used from the dashboard to validate the CNAME MANUALLY. + this.router.get( + `${new this.entityType().getCrudApiPath()?.toString()}/order-ssl/:id`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + if (!StatusPageCNameRecord) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + `Custom Domains not enabled for this OneUptime installation. Please contact your server admin to enable this - feature.` - ) - ); - } + feature.`, + ), + ); + } - const databaseProps: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(req); + const databaseProps: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(req); - const id: ObjectID = new ObjectID( - req.params['id'] as string - ); + const id: ObjectID = new ObjectID(req.params["id"] as string); - // check if the user can read the domain. + // check if the user can read the domain. - const domainCount: PositiveNumber = - await StatusPageDomainService.countBy({ - query: { - _id: id.toString(), - }, - props: databaseProps, - }); + const domainCount: PositiveNumber = + await StatusPageDomainService.countBy({ + query: { + _id: id.toString(), + }, + props: databaseProps, + }); - if (domainCount.toNumber() === 0) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'The domain does not exist or user does not have access to it.' - ) - ); - } + if (domainCount.toNumber() === 0) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "The domain does not exist or user does not have access to it.", + ), + ); + } - const domain: StatusPageDomain | null = - await StatusPageDomainService.findOneBy({ - query: { - _id: id.toString(), - }, - select: { - _id: true, - fullDomain: true, - cnameVerificationToken: true, - isCnameVerified: true, - isSslProvisioned: true, - }, - props: { - isRoot: true, - }, - }); + const domain: StatusPageDomain | null = + await StatusPageDomainService.findOneBy({ + query: { + _id: id.toString(), + }, + select: { + _id: true, + fullDomain: true, + cnameVerificationToken: true, + isCnameVerified: true, + isSslProvisioned: true, + }, + props: { + isRoot: true, + }, + }); - if (!domain) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid token.') - ); - } + if (!domain) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid token."), + ); + } - if (!domain.cnameVerificationToken) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid token.') - ); - } + if (!domain.cnameVerificationToken) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid token."), + ); + } - if (!domain.isCnameVerified) { - return Response.sendErrorResponse( - req, - res, - new BadDataException( - 'CNAME is not verified. Please verify CNAME first before you provision SSL.' - ) - ); - } + if (!domain.isCnameVerified) { + return Response.sendErrorResponse( + req, + res, + new BadDataException( + "CNAME is not verified. Please verify CNAME first before you provision SSL.", + ), + ); + } - if (domain.isSslProvisioned) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('SSL is already provisioned.') - ); - } + if (domain.isSslProvisioned) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("SSL is already provisioned."), + ); + } - if (!domain.fullDomain) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid domain.') - ); - } + if (!domain.fullDomain) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid domain."), + ); + } - logger.debug('Ordering SSL'); + logger.debug("Ordering SSL"); - // provision SSL - await StatusPageDomainService.orderCert(domain); + // provision SSL + await StatusPageDomainService.orderCert(domain); - logger.debug( - 'SSL Provisioned for domain - ' + domain.fullDomain - ); + logger.debug("SSL Provisioned for domain - " + domain.fullDomain); - return Response.sendEmptySuccessResponse(req, res); - } catch (e) { - next(e); - } - } - ); - } + return Response.sendEmptySuccessResponse(req, res); + } catch (e) { + next(e); + } + }, + ); + } } diff --git a/CommonServer/API/StatusPageSubscriberAPI.ts b/CommonServer/API/StatusPageSubscriberAPI.ts index df1230e2b5..9c0fe9c0e8 100644 --- a/CommonServer/API/StatusPageSubscriberAPI.ts +++ b/CommonServer/API/StatusPageSubscriberAPI.ts @@ -1,54 +1,48 @@ import StatusPageSubscriberService, { - Service as StatusPageSubscriberServiceType, -} from '../Services/StatusPageSubscriberService'; + Service as StatusPageSubscriberServiceType, +} from "../Services/StatusPageSubscriberService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; export default class StatusPageSubscriberAPI extends BaseAPI< - StatusPageSubscriber, - StatusPageSubscriberServiceType + StatusPageSubscriber, + StatusPageSubscriberServiceType > { - public constructor() { - super(StatusPageSubscriber, StatusPageSubscriberService); + public constructor() { + super(StatusPageSubscriber, StatusPageSubscriberService); - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/unsubscribe/:id`, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - try { - await this.service.updateOneBy({ - query: { - _id: req.params['id'] as string, - }, - data: { - isUnsubscribed: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + this.router.get( + `${new this.entityType().getCrudApiPath()?.toString()}/unsubscribe/:id`, + async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { + try { + await this.service.updateOneBy({ + query: { + _id: req.params["id"] as string, + }, + data: { + isUnsubscribed: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); - return Response.sendHtmlResponse( - req, - res, - '<html><body><p> You have been unsubscribed.</p><body><html>' - ); - } catch (err) { - next(err); - } - } - ); - } + return Response.sendHtmlResponse( + req, + res, + "<html><body><p> You have been unsubscribed.</p><body><html>", + ); + } catch (err) { + next(err); + } + }, + ); + } } diff --git a/CommonServer/API/UserAPI.ts b/CommonServer/API/UserAPI.ts index aeee65878c..ffef8f1377 100644 --- a/CommonServer/API/UserAPI.ts +++ b/CommonServer/API/UserAPI.ts @@ -1,11 +1,11 @@ import UserService, { - Service as UserServiceType, -} from '../Services/UserService'; -import BaseAPI from './BaseAPI'; -import User from 'Model/Models/User'; + Service as UserServiceType, +} from "../Services/UserService"; +import BaseAPI from "./BaseAPI"; +import User from "Model/Models/User"; export default class UserAPI extends BaseAPI<User, UserServiceType> { - public constructor() { - super(User, UserService); - } + public constructor() { + super(User, UserService); + } } diff --git a/CommonServer/API/UserCallAPI.ts b/CommonServer/API/UserCallAPI.ts index 6292d4ff83..70bdd26fa4 100644 --- a/CommonServer/API/UserCallAPI.ts +++ b/CommonServer/API/UserCallAPI.ts @@ -1,122 +1,120 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; +import UserMiddleware from "../Middleware/UserAuthorization"; import UserCallService, { - Service as UserCallServiceType, -} from '../Services/UserCallService'; + Service as UserCallServiceType, +} from "../Services/UserCallService"; import { - ExpressRequest, - ExpressResponse, - OneUptimeRequest, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import UserCall from 'Model/Models/UserCall'; -import UserSMS from 'Model/Models/UserSMS'; + ExpressRequest, + ExpressResponse, + OneUptimeRequest, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import UserCall from "Model/Models/UserCall"; +import UserSMS from "Model/Models/UserSMS"; export default class UserCallAPI extends BaseAPI< - UserCall, - UserCallServiceType + UserCall, + UserCallServiceType > { - public constructor() { - super(UserCall, UserCallService); + public constructor() { + super(UserCall, UserCallService); - this.router.post( - `/user-call/verify`, - UserMiddleware.getUserMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - req = req as OneUptimeRequest; + this.router.post( + `/user-call/verify`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + req = req as OneUptimeRequest; - if (!req.body.itemId) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid item ID') - ); - } + if (!req.body.itemId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid item ID"), + ); + } - if (!req.body.code) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid code') - ); - } + if (!req.body.code) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid code"), + ); + } - // Check if the code matches and verify the phone number. - const item: UserSMS | null = await this.service.findOneById({ - id: req.body['itemId'], - props: { - isRoot: true, - }, - select: { - userId: true, - verificationCode: true, - }, - }); + // Check if the code matches and verify the phone number. + const item: UserSMS | null = await this.service.findOneById({ + id: req.body["itemId"], + props: { + isRoot: true, + }, + select: { + userId: true, + verificationCode: true, + }, + }); - if (!item) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Item not found') - ); - } + if (!item) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Item not found"), + ); + } - //check user id + //check user id - if ( - item.userId?.toString() !== - ( - req as OneUptimeRequest - )?.userAuthorization?.userId?.toString() - ) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid user ID') - ); - } + if ( + item.userId?.toString() !== + (req as OneUptimeRequest)?.userAuthorization?.userId?.toString() + ) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid user ID"), + ); + } - if (item.verificationCode !== req.body['code']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid code') - ); - } + if (item.verificationCode !== req.body["code"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid code"), + ); + } - await this.service.updateOneById({ - id: item.id!, - props: { - isRoot: true, - }, - data: { - isVerified: true, - }, - }); + await this.service.updateOneById({ + id: item.id!, + props: { + isRoot: true, + }, + data: { + isVerified: true, + }, + }); - return Response.sendEmptySuccessResponse(req, res); - } - ); + return Response.sendEmptySuccessResponse(req, res); + }, + ); - this.router.post( - `/user-call/resend-verification-code`, - UserMiddleware.getUserMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - req = req as OneUptimeRequest; + this.router.post( + `/user-call/resend-verification-code`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + req = req as OneUptimeRequest; - if (!req.body.itemId) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid item ID') - ); - } + if (!req.body.itemId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid item ID"), + ); + } - await this.service.resendVerificationCode(req.body.itemId); + await this.service.resendVerificationCode(req.body.itemId); - return Response.sendEmptySuccessResponse(req, res); - } - ); - } + return Response.sendEmptySuccessResponse(req, res); + }, + ); + } } diff --git a/CommonServer/API/UserEmailAPI.ts b/CommonServer/API/UserEmailAPI.ts index 7338d8f67a..31efda8dae 100644 --- a/CommonServer/API/UserEmailAPI.ts +++ b/CommonServer/API/UserEmailAPI.ts @@ -1,123 +1,121 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; +import UserMiddleware from "../Middleware/UserAuthorization"; import UserEmailService, { - Service as UserEmailServiceType, -} from '../Services/UserEmailService'; + Service as UserEmailServiceType, +} from "../Services/UserEmailService"; import { - ExpressRequest, - ExpressResponse, - OneUptimeRequest, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import UserEmail from 'Model/Models/UserEmail'; + ExpressRequest, + ExpressResponse, + OneUptimeRequest, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import UserEmail from "Model/Models/UserEmail"; export default class UserEmailAPI extends BaseAPI< - UserEmail, - UserEmailServiceType + UserEmail, + UserEmailServiceType > { - public constructor() { - super(UserEmail, UserEmailService); + public constructor() { + super(UserEmail, UserEmailService); - this.router.post( - `${new this.entityType().getCrudApiPath()?.toString()}/verify`, - UserMiddleware.getUserMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - req = req as OneUptimeRequest; + this.router.post( + `${new this.entityType().getCrudApiPath()?.toString()}/verify`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + req = req as OneUptimeRequest; - if (!req.body.itemId) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid item ID') - ); - } + if (!req.body.itemId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid item ID"), + ); + } - if (!req.body.code) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid code') - ); - } + if (!req.body.code) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid code"), + ); + } - // Check if the code matches and verify the email. - const item: UserEmail | null = await this.service.findOneById({ - id: req.body['itemId'], - props: { - isRoot: true, - }, - select: { - userId: true, - verificationCode: true, - }, - }); + // Check if the code matches and verify the email. + const item: UserEmail | null = await this.service.findOneById({ + id: req.body["itemId"], + props: { + isRoot: true, + }, + select: { + userId: true, + verificationCode: true, + }, + }); - if (!item) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Item not found') - ); - } + if (!item) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Item not found"), + ); + } - //check user id + //check user id - if ( - item.userId?.toString() !== - ( - req as OneUptimeRequest - )?.userAuthorization?.userId?.toString() - ) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid user ID') - ); - } + if ( + item.userId?.toString() !== + (req as OneUptimeRequest)?.userAuthorization?.userId?.toString() + ) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid user ID"), + ); + } - if (item.verificationCode !== req.body['code']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid code') - ); - } + if (item.verificationCode !== req.body["code"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid code"), + ); + } - await this.service.updateOneById({ - id: item.id!, - props: { - isRoot: true, - }, - data: { - isVerified: true, - }, - }); + await this.service.updateOneById({ + id: item.id!, + props: { + isRoot: true, + }, + data: { + isVerified: true, + }, + }); - return Response.sendEmptySuccessResponse(req, res); - } - ); + return Response.sendEmptySuccessResponse(req, res); + }, + ); - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/resend-verification-code`, - UserMiddleware.getUserMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - req = req as OneUptimeRequest; + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/resend-verification-code`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + req = req as OneUptimeRequest; - if (!req.body.itemId) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid item ID') - ); - } + if (!req.body.itemId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid item ID"), + ); + } - await this.service.resendVerificationCode(req.body.itemId); + await this.service.resendVerificationCode(req.body.itemId); - return Response.sendEmptySuccessResponse(req, res); - } - ); - } + return Response.sendEmptySuccessResponse(req, res); + }, + ); + } } diff --git a/CommonServer/API/UserOnCallLogTimelineAPI.ts b/CommonServer/API/UserOnCallLogTimelineAPI.ts index 26168c6f04..cc2e4e4c10 100644 --- a/CommonServer/API/UserOnCallLogTimelineAPI.ts +++ b/CommonServer/API/UserOnCallLogTimelineAPI.ts @@ -1,169 +1,164 @@ -import DatabaseConfig from '../DatabaseConfig'; -import NotificationMiddleware from '../Middleware/NotificationMiddleware'; +import DatabaseConfig from "../DatabaseConfig"; +import NotificationMiddleware from "../Middleware/NotificationMiddleware"; import UserOnCallLogTimelineService, { - Service as UserNotificationLogTimelineServiceType, -} from '../Services/UserOnCallLogTimelineService'; + Service as UserNotificationLogTimelineServiceType, +} from "../Services/UserOnCallLogTimelineService"; import { - ExpressRequest, - ExpressResponse, - OneUptimeRequest, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import { DashboardRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import UserNotificationStatus from 'Common/Types/UserNotification/UserNotificationStatus'; -import UserOnCallLogTimeline from 'Model/Models/UserOnCallLogTimeline'; + ExpressRequest, + ExpressResponse, + OneUptimeRequest, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import { DashboardRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import UserNotificationStatus from "Common/Types/UserNotification/UserNotificationStatus"; +import UserOnCallLogTimeline from "Model/Models/UserOnCallLogTimeline"; export default class UserNotificationLogTimelineAPI extends BaseAPI< - UserOnCallLogTimeline, - UserNotificationLogTimelineServiceType + UserOnCallLogTimeline, + UserNotificationLogTimelineServiceType > { - public constructor() { - super(UserOnCallLogTimeline, UserOnCallLogTimelineService); + public constructor() { + super(UserOnCallLogTimeline, UserOnCallLogTimelineService); - this.router.post( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/call/gather-input/:itemId`, - NotificationMiddleware.isValidCallNotificationRequest, - async (req: ExpressRequest, res: ExpressResponse) => { - req = req as OneUptimeRequest; + this.router.post( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/call/gather-input/:itemId`, + NotificationMiddleware.isValidCallNotificationRequest, + async (req: ExpressRequest, res: ExpressResponse) => { + req = req as OneUptimeRequest; - if (!req.params['itemId']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid item ID') - ); - } + if (!req.params["itemId"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid item ID"), + ); + } - const token: JSONObject = (req as any).callTokenData; + const token: JSONObject = (req as any).callTokenData; - const itemId: ObjectID = new ObjectID(req.params['itemId']); + const itemId: ObjectID = new ObjectID(req.params["itemId"]); - const timelineItem: UserOnCallLogTimeline | null = - await this.service.findOneById({ - id: itemId, - select: { - _id: true, - projectId: true, - triggeredByIncidentId: true, - }, - props: { - isRoot: true, - }, - }); + const timelineItem: UserOnCallLogTimeline | null = + await this.service.findOneById({ + id: itemId, + select: { + _id: true, + projectId: true, + triggeredByIncidentId: true, + }, + props: { + isRoot: true, + }, + }); - if (!timelineItem) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid item Id') - ); - } + if (!timelineItem) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid item Id"), + ); + } - // check digits. + // check digits. - if (req.body['Digits'] === '1') { - // then ack incident - await this.service.updateOneById({ - id: itemId, - data: { - acknowledgedAt: OneUptimeDate.getCurrentDate(), - isAcknowledged: true, - status: UserNotificationStatus.Acknowledged, - statusMessage: 'Notification Acknowledged', - }, - props: { - isRoot: true, - }, - }); - } + if (req.body["Digits"] === "1") { + // then ack incident + await this.service.updateOneById({ + id: itemId, + data: { + acknowledgedAt: OneUptimeDate.getCurrentDate(), + isAcknowledged: true, + status: UserNotificationStatus.Acknowledged, + statusMessage: "Notification Acknowledged", + }, + props: { + isRoot: true, + }, + }); + } - return NotificationMiddleware.sendResponse( - req, - res, - token as any - ); - } + return NotificationMiddleware.sendResponse(req, res, token as any); + }, + ); + + this.router.get( + `${new this.entityType() + .getCrudApiPath() + ?.toString()}/acknowledge/:itemId`, + async (req: ExpressRequest, res: ExpressResponse) => { + req = req as OneUptimeRequest; + + if (!req.params["itemId"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Item ID is required"), + ); + } + + const itemId: ObjectID = new ObjectID(req.params["itemId"]); + + const timelineItem: UserOnCallLogTimeline | null = + await this.service.findOneById({ + id: itemId, + select: { + _id: true, + projectId: true, + triggeredByIncidentId: true, + }, + props: { + isRoot: true, + }, + }); + + if (!timelineItem) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid item Id"), + ); + } + + await this.service.updateOneById({ + id: itemId, + data: { + acknowledgedAt: OneUptimeDate.getCurrentDate(), + isAcknowledged: true, + status: UserNotificationStatus.Acknowledged, + statusMessage: "Notification Acknowledged", + }, + props: { + isRoot: true, + }, + }); + + // redirect to dashboard to incidents page. + + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + return Response.redirect( + req, + res, + new URL( + httpProtocol, + host, + DashboardRoute.addRoute( + `/${timelineItem.projectId?.toString()}/incidents/${timelineItem.triggeredByIncidentId!.toString()}`, + ), + ), ); - - this.router.get( - `${new this.entityType() - .getCrudApiPath() - ?.toString()}/acknowledge/:itemId`, - async (req: ExpressRequest, res: ExpressResponse) => { - req = req as OneUptimeRequest; - - if (!req.params['itemId']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Item ID is required') - ); - } - - const itemId: ObjectID = new ObjectID(req.params['itemId']); - - const timelineItem: UserOnCallLogTimeline | null = - await this.service.findOneById({ - id: itemId, - select: { - _id: true, - projectId: true, - triggeredByIncidentId: true, - }, - props: { - isRoot: true, - }, - }); - - if (!timelineItem) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid item Id') - ); - } - - await this.service.updateOneById({ - id: itemId, - data: { - acknowledgedAt: OneUptimeDate.getCurrentDate(), - isAcknowledged: true, - status: UserNotificationStatus.Acknowledged, - statusMessage: 'Notification Acknowledged', - }, - props: { - isRoot: true, - }, - }); - - // redirect to dashboard to incidents page. - - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); - - return Response.redirect( - req, - res, - new URL( - httpProtocol, - host, - DashboardRoute.addRoute( - `/${timelineItem.projectId?.toString()}/incidents/${timelineItem.triggeredByIncidentId!.toString()}` - ) - ) - ); - } - ); - } + }, + ); + } } diff --git a/CommonServer/API/UserSmsAPI.ts b/CommonServer/API/UserSmsAPI.ts index b25e9b82e5..4c22207961 100644 --- a/CommonServer/API/UserSmsAPI.ts +++ b/CommonServer/API/UserSmsAPI.ts @@ -1,118 +1,116 @@ -import UserMiddleware from '../Middleware/UserAuthorization'; +import UserMiddleware from "../Middleware/UserAuthorization"; import UserSMSService, { - Service as UserSMSServiceType, -} from '../Services/UserSmsService'; + Service as UserSMSServiceType, +} from "../Services/UserSmsService"; import { - ExpressRequest, - ExpressResponse, - OneUptimeRequest, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import BaseAPI from './BaseAPI'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import UserSMS from 'Model/Models/UserSMS'; + ExpressRequest, + ExpressResponse, + OneUptimeRequest, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import BaseAPI from "./BaseAPI"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import UserSMS from "Model/Models/UserSMS"; export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> { - public constructor() { - super(UserSMS, UserSMSService); + public constructor() { + super(UserSMS, UserSMSService); - this.router.post( - `/user-sms/verify`, - UserMiddleware.getUserMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - req = req as OneUptimeRequest; + this.router.post( + `/user-sms/verify`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + req = req as OneUptimeRequest; - if (!req.body.itemId) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid item ID') - ); - } + if (!req.body.itemId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid item ID"), + ); + } - if (!req.body.code) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid code') - ); - } + if (!req.body.code) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid code"), + ); + } - // Check if the code matches and verify the phone number. - const item: UserSMS | null = await this.service.findOneById({ - id: req.body['itemId'], - props: { - isRoot: true, - }, - select: { - userId: true, - verificationCode: true, - }, - }); + // Check if the code matches and verify the phone number. + const item: UserSMS | null = await this.service.findOneById({ + id: req.body["itemId"], + props: { + isRoot: true, + }, + select: { + userId: true, + verificationCode: true, + }, + }); - if (!item) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Item not found') - ); - } + if (!item) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Item not found"), + ); + } - //check user id + //check user id - if ( - item.userId?.toString() !== - ( - req as OneUptimeRequest - )?.userAuthorization?.userId?.toString() - ) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid user ID') - ); - } + if ( + item.userId?.toString() !== + (req as OneUptimeRequest)?.userAuthorization?.userId?.toString() + ) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid user ID"), + ); + } - if (item.verificationCode !== req.body['code']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid code') - ); - } + if (item.verificationCode !== req.body["code"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid code"), + ); + } - await this.service.updateOneById({ - id: item.id!, - props: { - isRoot: true, - }, - data: { - isVerified: true, - }, - }); + await this.service.updateOneById({ + id: item.id!, + props: { + isRoot: true, + }, + data: { + isVerified: true, + }, + }); - return Response.sendEmptySuccessResponse(req, res); - } - ); + return Response.sendEmptySuccessResponse(req, res); + }, + ); - this.router.post( - `/user-sms/resend-verification-code`, - UserMiddleware.getUserMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - req = req as OneUptimeRequest; + this.router.post( + `/user-sms/resend-verification-code`, + UserMiddleware.getUserMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + req = req as OneUptimeRequest; - if (!req.body.itemId) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid item ID') - ); - } + if (!req.body.itemId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid item ID"), + ); + } - await this.service.resendVerificationCode(req.body.itemId); + await this.service.resendVerificationCode(req.body.itemId); - return Response.sendEmptySuccessResponse(req, res); - } - ); - } + return Response.sendEmptySuccessResponse(req, res); + }, + ); + } } diff --git a/CommonServer/API/VersionAPI.ts b/CommonServer/API/VersionAPI.ts index dd6223a012..79e561a8cd 100644 --- a/CommonServer/API/VersionAPI.ts +++ b/CommonServer/API/VersionAPI.ts @@ -1,14 +1,14 @@ -import { AppVersion, GitSha } from '../EnvironmentConfig'; +import { AppVersion, GitSha } from "../EnvironmentConfig"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, -} from '../Utils/Express'; + ExpressRequest, + ExpressResponse, + ExpressRouter, +} from "../Utils/Express"; const router: ExpressRouter = Express.getRouter(); -router.get('/version', (_req: ExpressRequest, res: ExpressResponse) => { - res.send({ version: AppVersion, commit: GitSha }); +router.get("/version", (_req: ExpressRequest, res: ExpressResponse) => { + res.send({ version: AppVersion, commit: GitSha }); }); export default router; diff --git a/CommonServer/BillingConfig.ts b/CommonServer/BillingConfig.ts index e1862647ef..77e51fe61d 100644 --- a/CommonServer/BillingConfig.ts +++ b/CommonServer/BillingConfig.ts @@ -1,9 +1,9 @@ -const IsBillingEnabled: boolean = process.env['BILLING_ENABLED'] === 'true'; -const BillingPublicKey: string = process.env['BILLING_PUBLIC_KEY'] || ''; -const BillingPrivateKey: string = process.env['BILLING_PRIVATE_KEY'] || ''; +const IsBillingEnabled: boolean = process.env["BILLING_ENABLED"] === "true"; +const BillingPublicKey: string = process.env["BILLING_PUBLIC_KEY"] || ""; +const BillingPrivateKey: string = process.env["BILLING_PRIVATE_KEY"] || ""; export default { - IsBillingEnabled, - BillingPublicKey, - BillingPrivateKey, + IsBillingEnabled, + BillingPublicKey, + BillingPrivateKey, }; diff --git a/CommonServer/DatabaseConfig.ts b/CommonServer/DatabaseConfig.ts index 099f3803d1..73ce743ee5 100644 --- a/CommonServer/DatabaseConfig.ts +++ b/CommonServer/DatabaseConfig.ts @@ -1,70 +1,62 @@ -import GlobalConfigService from './Services/GlobalConfigService'; -import { AccountsRoute, DashboardRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONValue } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import GlobalConfig from 'Model/Models/GlobalConfig'; +import GlobalConfigService from "./Services/GlobalConfigService"; +import { AccountsRoute, DashboardRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONValue } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import GlobalConfig from "Model/Models/GlobalConfig"; export default class DatabaseConfig { - public static async getFromGlobalConfig(key: string): Promise<JSONValue> { - const globalConfig: GlobalConfig | null = - await GlobalConfigService.findOneBy({ - query: { - _id: ObjectID.getZeroObjectID().toString(), - }, - props: { - isRoot: true, - }, - select: { - [key]: true, - }, - }); + public static async getFromGlobalConfig(key: string): Promise<JSONValue> { + const globalConfig: GlobalConfig | null = + await GlobalConfigService.findOneBy({ + query: { + _id: ObjectID.getZeroObjectID().toString(), + }, + props: { + isRoot: true, + }, + select: { + [key]: true, + }, + }); - if (!globalConfig) { - throw new BadDataException('Global Config not found'); - } - - return globalConfig.getColumnValue(key); + if (!globalConfig) { + throw new BadDataException("Global Config not found"); } - public static async getHost(): Promise<Hostname> { - return Promise.resolve( - new Hostname(process.env['HOST'] || 'localhost') - ); - } + return globalConfig.getColumnValue(key); + } - public static async getHttpProtocol(): Promise<Protocol> { - return Promise.resolve( - process.env['HTTP_PROTOCOL'] === 'https' - ? Protocol.HTTPS - : Protocol.HTTP - ); - } + public static async getHost(): Promise<Hostname> { + return Promise.resolve(new Hostname(process.env["HOST"] || "localhost")); + } - public static async getAccountsUrl(): Promise<URL> { - const host: Hostname = await DatabaseConfig.getHost(); - return new URL( - await DatabaseConfig.getHttpProtocol(), - host, - AccountsRoute - ); - } + public static async getHttpProtocol(): Promise<Protocol> { + return Promise.resolve( + process.env["HTTP_PROTOCOL"] === "https" ? Protocol.HTTPS : Protocol.HTTP, + ); + } - public static async getDashboardUrl(): Promise<URL> { - const host: Hostname = await DatabaseConfig.getHost(); - return new URL( - await DatabaseConfig.getHttpProtocol(), - host, - DashboardRoute - ); - } + public static async getAccountsUrl(): Promise<URL> { + const host: Hostname = await DatabaseConfig.getHost(); + return new URL(await DatabaseConfig.getHttpProtocol(), host, AccountsRoute); + } - public static async shouldDisableSignup(): Promise<boolean> { - return (await DatabaseConfig.getFromGlobalConfig( - 'disableSignup' - )) as boolean; - } + public static async getDashboardUrl(): Promise<URL> { + const host: Hostname = await DatabaseConfig.getHost(); + return new URL( + await DatabaseConfig.getHttpProtocol(), + host, + DashboardRoute, + ); + } + + public static async shouldDisableSignup(): Promise<boolean> { + return (await DatabaseConfig.getFromGlobalConfig( + "disableSignup", + )) as boolean; + } } diff --git a/CommonServer/EnvironmentConfig.ts b/CommonServer/EnvironmentConfig.ts index cca82cbd86..514655d040 100644 --- a/CommonServer/EnvironmentConfig.ts +++ b/CommonServer/EnvironmentConfig.ts @@ -1,21 +1,21 @@ -import BillingConfig from './BillingConfig'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import Email from 'Common/Types/Email'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Port from 'Common/Types/Port'; +import BillingConfig from "./BillingConfig"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import Email from "Common/Types/Email"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Port from "Common/Types/Port"; export enum ConfigLogLevel { - INFO = 'INFO', - WARN = 'WARN', - ERROR = 'ERROR', - DEBUG = 'DEBUG', + INFO = "INFO", + WARN = "WARN", + ERROR = "ERROR", + DEBUG = "DEBUG", } export const getAllEnvVars: () => JSONObject = (): JSONObject => { - return process.env; + return process.env; }; export const IsBillingEnabled: boolean = BillingConfig.IsBillingEnabled; @@ -23,191 +23,190 @@ export const BillingPublicKey: string = BillingConfig.BillingPublicKey; export const BillingPrivateKey: string = BillingConfig.BillingPrivateKey; export const DatabaseHost: Hostname = Hostname.fromString( - process.env['DATABASE_HOST'] || 'postgres' + process.env["DATABASE_HOST"] || "postgres", ); export const LetsEncryptNotificationEmail: Email = Email.fromString( - process.env['LETS_ENCRYPT_NOTIFICATION_EMAIL'] || - 'notifications@example.com' + process.env["LETS_ENCRYPT_NOTIFICATION_EMAIL"] || "notifications@example.com", ); export const LetsEncryptAccountKey: string = - process.env['LETS_ENCRYPT_ACCOUNT_KEY'] || ''; + process.env["LETS_ENCRYPT_ACCOUNT_KEY"] || ""; export const DatabasePort: Port = new Port( - process.env['DATABASE_PORT'] || '5432' + process.env["DATABASE_PORT"] || "5432", ); export const DatabaseUsername: string = - process.env['DATABASE_USERNAME'] || 'postgres'; + process.env["DATABASE_USERNAME"] || "postgres"; export const DatabasePassword: string = - process.env['DATABASE_PASSWORD'] || 'password'; + process.env["DATABASE_PASSWORD"] || "password"; export const DatabaseName: string = - process.env['DATABASE_NAME'] || 'oneuptimedb'; + process.env["DATABASE_NAME"] || "oneuptimedb"; export const DatabaseSslCa: string | undefined = - process.env['DATABASE_SSL_CA'] || undefined; + process.env["DATABASE_SSL_CA"] || undefined; export const DatabaseSslKey: string | undefined = - process.env['DATABASE_SSL_KEY'] || undefined; + process.env["DATABASE_SSL_KEY"] || undefined; export const DatabaseSslCert: string | undefined = - process.env['DATABASE_SSL_CERT'] || undefined; + process.env["DATABASE_SSL_CERT"] || undefined; export const DatabaseRejectUnauthorized: boolean = - process.env['DATABASE_SSL_REJECT_UNAUTHORIZED'] === 'true'; + process.env["DATABASE_SSL_REJECT_UNAUTHORIZED"] === "true"; export const ShouldDatabaseSslEnable: boolean = Boolean( - DatabaseSslCa || (DatabaseSslCert && DatabaseSslKey) + DatabaseSslCa || (DatabaseSslCert && DatabaseSslKey), ); export const EncryptionSecret: ObjectID = new ObjectID( - process.env['ENCRYPTION_SECRET'] || 'secret' + process.env["ENCRYPTION_SECRET"] || "secret", ); -export const AirtableApiKey: string = process.env['AIRTABLE_API_KEY'] || ''; +export const AirtableApiKey: string = process.env["AIRTABLE_API_KEY"] || ""; -export const AirtableBaseId: string = process.env['AIRTABLE_BASE_ID'] || ''; +export const AirtableBaseId: string = process.env["AIRTABLE_BASE_ID"] || ""; export const ClusterKey: ObjectID = new ObjectID( - process.env['ONEUPTIME_SECRET'] || 'secret' + process.env["ONEUPTIME_SECRET"] || "secret", ); -export const HasClusterKey: boolean = Boolean(process.env['ONEUPTIME_SECRET']); +export const HasClusterKey: boolean = Boolean(process.env["ONEUPTIME_SECRET"]); export const AppApiHostname: Hostname = Hostname.fromString( - `${process.env['SERVER_APP_HOSTNAME'] || 'localhost'}:${ - process.env['APP_PORT'] || 80 - }` + `${process.env["SERVER_APP_HOSTNAME"] || "localhost"}:${ + process.env["APP_PORT"] || 80 + }`, ); export const IngestorHostname: Hostname = Hostname.fromString( - `${process.env['SERVER_INGESTOR_HOSTNAME'] || 'localhost'}:${ - process.env['INGESTOR_PORT'] || 80 - }` + `${process.env["SERVER_INGESTOR_HOSTNAME"] || "localhost"}:${ + process.env["INGESTOR_PORT"] || 80 + }`, ); export const IsolatedVMHostname: Hostname = Hostname.fromString( - `${process.env['SERVER_ISOLATED_VM_HOSTNAME'] || 'localhost'}:${ - process.env['ISOLATED_VM_PORT'] || 80 - }` + `${process.env["SERVER_ISOLATED_VM_HOSTNAME"] || "localhost"}:${ + process.env["ISOLATED_VM_PORT"] || 80 + }`, ); export const AccountsHostname: Hostname = Hostname.fromString( - `${process.env['SERVER_ACCOUNTS_HOSTNAME'] || 'localhost'}:${ - process.env['ACCOUNTS_PORT'] || 80 - }` + `${process.env["SERVER_ACCOUNTS_HOSTNAME"] || "localhost"}:${ + process.env["ACCOUNTS_PORT"] || 80 + }`, ); export const DashboardHostname: Hostname = Hostname.fromString( - `${process.env['SERVER_DASHBOARD_HOSTNAME'] || 'localhost'}:${ - process.env['DASHBOARD_PORT'] || 80 - }` + `${process.env["SERVER_DASHBOARD_HOSTNAME"] || "localhost"}:${ + process.env["DASHBOARD_PORT"] || 80 + }`, ); -export const Env: string = process.env['NODE_ENV'] || 'production'; +export const Env: string = process.env["NODE_ENV"] || "production"; // Redis does not require password. -export const RedisHostname: string = process.env['REDIS_HOST'] || 'redis'; -export const RedisPort: Port = new Port(process.env['REDIS_PORT'] || '6379'); -export const RedisDb: number = Number(process.env['REDIS_DB']) || 0; -export const RedisUsername: string = process.env['REDIS_USERNAME'] || 'default'; +export const RedisHostname: string = process.env["REDIS_HOST"] || "redis"; +export const RedisPort: Port = new Port(process.env["REDIS_PORT"] || "6379"); +export const RedisDb: number = Number(process.env["REDIS_DB"]) || 0; +export const RedisUsername: string = process.env["REDIS_USERNAME"] || "default"; export const RedisPassword: string = - process.env['REDIS_PASSWORD'] || 'password'; + process.env["REDIS_PASSWORD"] || "password"; export const RedisTlsCa: string | undefined = - process.env['REDIS_TLS_CA'] || undefined; + process.env["REDIS_TLS_CA"] || undefined; export const RedisTlsCert: string | undefined = - process.env['REDIS_TLS_CERT'] || undefined; + process.env["REDIS_TLS_CERT"] || undefined; export const RedisTlsKey: string | undefined = - process.env['REDIS_TLS_KEY'] || undefined; + process.env["REDIS_TLS_KEY"] || undefined; export const RedisTlsSentinelMode: boolean = - process.env['REDIS_TLS_SENTINEL_MODE'] === 'true'; + process.env["REDIS_TLS_SENTINEL_MODE"] === "true"; export const ShouldRedisTlsEnable: boolean = Boolean( - RedisTlsCa || (RedisTlsCert && RedisTlsKey) + RedisTlsCa || (RedisTlsCert && RedisTlsKey), ); export const IsProduction: boolean = - process.env['ENVIRONMENT'] === 'production'; + process.env["ENVIRONMENT"] === "production"; export const IsDevelopment: boolean = - process.env['ENVIRONMENT'] === 'development'; + process.env["ENVIRONMENT"] === "development"; -export const IsTest: boolean = process.env['ENVIRONMENT'] === 'test'; +export const IsTest: boolean = process.env["ENVIRONMENT"] === "test"; export const SubscriptionPlans: Array<SubscriptionPlan> = - SubscriptionPlan.getSubscriptionPlans(getAllEnvVars()); + SubscriptionPlan.getSubscriptionPlans(getAllEnvVars()); -export const AnalyticsKey: string = process.env['ANALYTICS_KEY'] || ''; -export const AnalyticsHost: string = process.env['ANALYTICS_HOST'] || ''; +export const AnalyticsKey: string = process.env["ANALYTICS_KEY"] || ""; +export const AnalyticsHost: string = process.env["ANALYTICS_HOST"] || ""; export const DisableAutomaticIncidentCreation: boolean = - process.env['DISABLE_AUTOMATIC_INCIDENT_CREATION'] === 'true'; + process.env["DISABLE_AUTOMATIC_INCIDENT_CREATION"] === "true"; export const ClickhouseHost: Hostname = Hostname.fromString( - process.env['CLICKHOUSE_HOST'] || 'clickhouse' + process.env["CLICKHOUSE_HOST"] || "clickhouse", ); export const StatusPageCNameRecord: string = - process.env['STATUS_PAGE_CNAME_RECORD'] || ''; + process.env["STATUS_PAGE_CNAME_RECORD"] || ""; export const ClickhousePort: Port = new Port( - process.env['CLICKHOUSE_PORT'] || '8123' + process.env["CLICKHOUSE_PORT"] || "8123", ); export const ClickhouseUsername: string = - process.env['CLICKHOUSE_USER'] || 'default'; + process.env["CLICKHOUSE_USER"] || "default"; export const ClickhousePassword: string = - process.env['CLICKHOUSE_PASSWORD'] || 'password'; + process.env["CLICKHOUSE_PASSWORD"] || "password"; export const ClickhouseDatabase: string = - process.env['CLICKHOUSE_DATABASE'] || 'oneuptime'; + process.env["CLICKHOUSE_DATABASE"] || "oneuptime"; export const ClickhouseTlsCa: string | undefined = - process.env['CLICKHOUSE_TLS_CA'] || undefined; + process.env["CLICKHOUSE_TLS_CA"] || undefined; export const ClickhouseTlsCert: string | undefined = - process.env['CLICKHOUSE_TLS_CERT'] || undefined; + process.env["CLICKHOUSE_TLS_CERT"] || undefined; export const ClickhouseTlsKey: string | undefined = - process.env['CLICKHOUSE_TLS_KEY'] || undefined; + process.env["CLICKHOUSE_TLS_KEY"] || undefined; export const ClickHouseIsHostHttps: boolean = - process.env['CLICKHOUSE_IS_HOST_HTTPS'] === 'true'; + process.env["CLICKHOUSE_IS_HOST_HTTPS"] === "true"; export const ShouldClickhouseSslEnable: boolean = Boolean( - ClickhouseTlsCa || (ClickhouseTlsCert && ClickhouseTlsKey) + ClickhouseTlsCa || (ClickhouseTlsCert && ClickhouseTlsKey), ); -export const GitSha: string = process.env['GIT_SHA'] || 'unknown'; +export const GitSha: string = process.env["GIT_SHA"] || "unknown"; -export const AppVersion: string = process.env['APP_VERSION'] || 'unknown'; +export const AppVersion: string = process.env["APP_VERSION"] || "unknown"; export const LogLevel: ConfigLogLevel = - (process.env['LOG_LEVEL'] as ConfigLogLevel) || ConfigLogLevel.INFO; + (process.env["LOG_LEVEL"] as ConfigLogLevel) || ConfigLogLevel.INFO; export const HttpProtocol: Protocol = - process.env['HTTP_PROTOCOL'] === 'https' ? Protocol.HTTPS : Protocol.HTTP; + process.env["HTTP_PROTOCOL"] === "https" ? Protocol.HTTPS : Protocol.HTTP; -export const Host: string = process.env['HOST'] || ''; +export const Host: string = process.env["HOST"] || ""; export const WorkflowScriptTimeoutInMS: number = process.env[ - 'WORKFLOW_SCRIPT_TIMEOUT_IN_MS' + "WORKFLOW_SCRIPT_TIMEOUT_IN_MS" ] - ? parseInt(process.env['WORKFLOW_SCRIPT_TIMEOUT_IN_MS'].toString()) - : 5000; + ? parseInt(process.env["WORKFLOW_SCRIPT_TIMEOUT_IN_MS"].toString()) + : 5000; export const AllowedActiveMonitorCountInFreePlan: number = process.env[ - 'ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN' + "ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN" ] - ? parseInt( - process.env['ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN'].toString() - ) - : 10; + ? parseInt( + process.env["ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN"].toString(), + ) + : 10; diff --git a/CommonServer/Infrastructure/ClickhouseConfig.ts b/CommonServer/Infrastructure/ClickhouseConfig.ts index d98598b1d1..6101fa476c 100644 --- a/CommonServer/Infrastructure/ClickhouseConfig.ts +++ b/CommonServer/Infrastructure/ClickhouseConfig.ts @@ -1,49 +1,49 @@ import { - ClickHouseIsHostHttps, - ClickhouseDatabase, - ClickhouseHost, - ClickhousePassword, - ClickhousePort, - ClickhouseTlsCa, - ClickhouseTlsCert, - ClickhouseTlsKey, - ClickhouseUsername, - ShouldClickhouseSslEnable, -} from '../EnvironmentConfig'; -import { NodeClickHouseClientConfigOptions } from '@clickhouse/client/dist/client'; + ClickHouseIsHostHttps, + ClickhouseDatabase, + ClickhouseHost, + ClickhousePassword, + ClickhousePort, + ClickhouseTlsCa, + ClickhouseTlsCert, + ClickhouseTlsKey, + ClickhouseUsername, + ShouldClickhouseSslEnable, +} from "../EnvironmentConfig"; +import { NodeClickHouseClientConfigOptions } from "@clickhouse/client/dist/client"; export type ClickHouseClientConfigOptions = NodeClickHouseClientConfigOptions; -const hostProtocol: string = ClickHouseIsHostHttps ? 'https' : 'http'; +const hostProtocol: string = ClickHouseIsHostHttps ? "https" : "http"; const options: ClickHouseClientConfigOptions = { - host: `${hostProtocol}://${ClickhouseHost.toString()}:${ClickhousePort.toNumber()}`, - username: ClickhouseUsername, - password: ClickhousePassword, - database: ClickhouseDatabase, - application: 'oneuptime', + host: `${hostProtocol}://${ClickhouseHost.toString()}:${ClickhousePort.toNumber()}`, + username: ClickhouseUsername, + password: ClickhousePassword, + database: ClickhouseDatabase, + application: "oneuptime", }; if (ShouldClickhouseSslEnable && ClickhouseTlsCa) { - options.tls = { - ca_cert: Buffer.from(ClickhouseTlsCa), - }; + options.tls = { + ca_cert: Buffer.from(ClickhouseTlsCa), + }; } if ( - ShouldClickhouseSslEnable && - ClickhouseTlsCa && - ClickhouseTlsCert && - ClickhouseTlsKey + ShouldClickhouseSslEnable && + ClickhouseTlsCa && + ClickhouseTlsCert && + ClickhouseTlsKey ) { - options.tls = { - ca_cert: Buffer.from(ClickhouseTlsCa), - cert: Buffer.from(ClickhouseTlsCert), - key: Buffer.from(ClickhouseTlsKey), - }; + options.tls = { + ca_cert: Buffer.from(ClickhouseTlsCa), + cert: Buffer.from(ClickhouseTlsCert), + key: Buffer.from(ClickhouseTlsKey), + }; } export const dataSourceOptions: ClickHouseClientConfigOptions = options; export const testDataSourceOptions: ClickHouseClientConfigOptions = - dataSourceOptions; + dataSourceOptions; diff --git a/CommonServer/Infrastructure/ClickhouseDatabase.ts b/CommonServer/Infrastructure/ClickhouseDatabase.ts index a3ed917c05..d6cc8afe65 100644 --- a/CommonServer/Infrastructure/ClickhouseDatabase.ts +++ b/CommonServer/Infrastructure/ClickhouseDatabase.ts @@ -1,130 +1,128 @@ -import logger from '../Utils/Logger'; +import logger from "../Utils/Logger"; import { - ClickHouseClientConfigOptions, - dataSourceOptions, - testDataSourceOptions, -} from './ClickhouseConfig'; -import { ClickHouseClient, PingResult, createClient } from '@clickhouse/client'; -import DatabaseNotConnectedException from 'Common/Types/Exception/DatabaseNotConnectedException'; -import Sleep from 'Common/Types/Sleep'; -import Stream from 'stream'; + ClickHouseClientConfigOptions, + dataSourceOptions, + testDataSourceOptions, +} from "./ClickhouseConfig"; +import { ClickHouseClient, PingResult, createClient } from "@clickhouse/client"; +import DatabaseNotConnectedException from "Common/Types/Exception/DatabaseNotConnectedException"; +import Sleep from "Common/Types/Sleep"; +import Stream from "stream"; export type ClickhouseClient = ClickHouseClient<Stream.Readable>; export default class ClickhouseDatabase { - private dataSource!: ClickhouseClient | null; + private dataSource!: ClickhouseClient | null; - public getDatasourceOptions(): ClickHouseClientConfigOptions { - return dataSourceOptions; - } + public getDatasourceOptions(): ClickHouseClientConfigOptions { + return dataSourceOptions; + } - public getTestDatasourceOptions(): ClickHouseClientConfigOptions { - return testDataSourceOptions; - } + public getTestDatasourceOptions(): ClickHouseClientConfigOptions { + return testDataSourceOptions; + } - public getDataSource(): ClickhouseClient | null { - return this.dataSource; - } + public getDataSource(): ClickhouseClient | null { + return this.dataSource; + } - public isConnected(): boolean { - return Boolean(this.dataSource); - } + public isConnected(): boolean { + return Boolean(this.dataSource); + } - public async connect( - dataSourceOptions: ClickHouseClientConfigOptions - ): Promise<ClickhouseClient> { - let retry: number = 0; + public async connect( + dataSourceOptions: ClickHouseClientConfigOptions, + ): Promise<ClickhouseClient> { + let retry: number = 0; - try { - type ConnectToDatabaseFunction = () => Promise<ClickhouseClient>; - const connectToDatabase: ConnectToDatabaseFunction = - async (): Promise<ClickhouseClient> => { - try { - const defaultDbClient: ClickhouseClient = createClient({ - ...dataSourceOptions, - database: 'default', - }); - await defaultDbClient.exec({ - query: `CREATE DATABASE IF NOT EXISTS ${dataSourceOptions.database}`, - }); + try { + type ConnectToDatabaseFunction = () => Promise<ClickhouseClient>; + const connectToDatabase: ConnectToDatabaseFunction = + async (): Promise<ClickhouseClient> => { + try { + const defaultDbClient: ClickhouseClient = createClient({ + ...dataSourceOptions, + database: "default", + }); + await defaultDbClient.exec({ + query: `CREATE DATABASE IF NOT EXISTS ${dataSourceOptions.database}`, + }); - await defaultDbClient.close(); + await defaultDbClient.close(); - const clickhouseClient: ClickhouseClient = - createClient(dataSourceOptions); - this.dataSource = clickhouseClient; + const clickhouseClient: ClickhouseClient = + createClient(dataSourceOptions); + this.dataSource = clickhouseClient; - const result: PingResult = - await clickhouseClient.ping(); + const result: PingResult = await clickhouseClient.ping(); - if (result.success === false) { - throw new DatabaseNotConnectedException( - 'Clickhouse Database is not connected' - ); - } + if (result.success === false) { + throw new DatabaseNotConnectedException( + "Clickhouse Database is not connected", + ); + } - logger.debug( - `Clickhouse Database Connected: ${dataSourceOptions.host?.toString()}` - ); + logger.debug( + `Clickhouse Database Connected: ${dataSourceOptions.host?.toString()}`, + ); - return clickhouseClient; - } catch (err) { - if (retry < 3) { - logger.debug( - 'Cannot connect to Clickhouse. Retrying again in 5 seconds' - ); - // sleep for 5 seconds. + return clickhouseClient; + } catch (err) { + if (retry < 3) { + logger.debug( + "Cannot connect to Clickhouse. Retrying again in 5 seconds", + ); + // sleep for 5 seconds. - await Sleep.sleep(5000); + await Sleep.sleep(5000); - retry++; - return await connectToDatabase(); - } - throw err; - } - }; - - return await connectToDatabase(); - } catch (err) { - logger.error('Clickhouse Database Connection Failed'); - logger.error(err); + retry++; + return await connectToDatabase(); + } throw err; - } + } + }; + + return await connectToDatabase(); + } catch (err) { + logger.error("Clickhouse Database Connection Failed"); + logger.error(err); + throw err; } + } - public async disconnect(): Promise<void> { - if (this.dataSource) { - await this.dataSource.close(); - this.dataSource = null; - } + public async disconnect(): Promise<void> { + if (this.dataSource) { + await this.dataSource.close(); + this.dataSource = null; } + } - public async checkConnnectionStatus(): Promise<boolean> { - // Ping clickhouse to check if the connection is still alive - try { - const result: PingResult | undefined = - await this.getDataSource()?.ping(); + public async checkConnnectionStatus(): Promise<boolean> { + // Ping clickhouse to check if the connection is still alive + try { + const result: PingResult | undefined = await this.getDataSource()?.ping(); - if (!result) { - throw new DatabaseNotConnectedException( - 'Clickhouse Database is not connected' - ); - } + if (!result) { + throw new DatabaseNotConnectedException( + "Clickhouse Database is not connected", + ); + } - if (result?.success === false) { - throw new DatabaseNotConnectedException( - 'Clickhouse Database is not connected' - ); - } + if (result?.success === false) { + throw new DatabaseNotConnectedException( + "Clickhouse Database is not connected", + ); + } - return true; - } catch (err) { - logger.error('Clickhouse Connection Lost'); - logger.error(err); - return false; - } + return true; + } catch (err) { + logger.error("Clickhouse Connection Lost"); + logger.error(err); + return false; } + } } export const ClickhouseAppInstance: ClickhouseDatabase = - new ClickhouseDatabase(); + new ClickhouseDatabase(); diff --git a/CommonServer/Infrastructure/GlobalCache.ts b/CommonServer/Infrastructure/GlobalCache.ts index d327f942a7..bbcd40c559 100644 --- a/CommonServer/Infrastructure/GlobalCache.ts +++ b/CommonServer/Infrastructure/GlobalCache.ts @@ -1,128 +1,128 @@ -import logger from '../Utils/Logger'; -import Redis, { ClientType } from './Redis'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import DatabaseNotConnectedException from 'Common/Types/Exception/DatabaseNotConnectedException'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; +import logger from "../Utils/Logger"; +import Redis, { ClientType } from "./Redis"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import DatabaseNotConnectedException from "Common/Types/Exception/DatabaseNotConnectedException"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; export default abstract class GlobalCache { - public static async getJSONObject( - namespace: string, - key: string - ): Promise<JSONObject | null> { - const json: JSONArray | JSONObject | null = - await this.getJSONArrayOrObject(namespace, key); + public static async getJSONObject( + namespace: string, + key: string, + ): Promise<JSONObject | null> { + const json: JSONArray | JSONObject | null = await this.getJSONArrayOrObject( + namespace, + key, + ); - if (!json) { - return null; - } - - if (Array.isArray(json)) { - throw new BadDataException( - 'Expected JSONObject, but got JSONArray' - ); - } - - return json; + if (!json) { + return null; } - public static async getJSONArray( - namespace: string, - key: string - ): Promise<JSONArray | null> { - const json: JSONArray | JSONObject | null = - await this.getJSONArrayOrObject(namespace, key); - - if (!json) { - return null; - } - - if (!Array.isArray(json)) { - throw new BadDataException( - 'Expected JSONArray, but got JSONObject' - ); - } - - return json; + if (Array.isArray(json)) { + throw new BadDataException("Expected JSONObject, but got JSONArray"); } - private static async getJSONArrayOrObject( - namespace: string, - key: string - ): Promise<JSONObject | JSONArray | null> { - const value: string | null = await this.getString(namespace, key); + return json; + } - if (!value) { - return null; - } + public static async getJSONArray( + namespace: string, + key: string, + ): Promise<JSONArray | null> { + const json: JSONArray | JSONObject | null = await this.getJSONArrayOrObject( + namespace, + key, + ); - try { - let jsonObject: JSONObject | JSONArray = JSONFunctions.parse(value); - - if (Array.isArray(jsonObject)) { - jsonObject = JSONFunctions.deserializeArray(jsonObject); - } else { - jsonObject = JSONFunctions.deserialize(jsonObject); - } - - if (!jsonObject) { - return null; - } - - return jsonObject; - } catch (err) { - logger.error(err); - return null; - } + if (!json) { + return null; } - public static async getString( - namespace: string, - key: string - ): Promise<string | null> { - const client: ClientType | null = Redis.getClient(); - - if (!client || !Redis.isConnected()) { - throw new DatabaseNotConnectedException('Cache is not connected'); - } - - const value: string | null = await client?.get(`${namespace}-${key}`); - - if (!value) { - return null; - } - - return value; + if (!Array.isArray(json)) { + throw new BadDataException("Expected JSONArray, but got JSONObject"); } - public static async setJSON( - namespace: string, - key: string, - value: JSONObject - ): Promise<void> { - await this.setString( - namespace, - key, - JSON.stringify(JSONFunctions.serialize(value)) - ); + return json; + } + + private static async getJSONArrayOrObject( + namespace: string, + key: string, + ): Promise<JSONObject | JSONArray | null> { + const value: string | null = await this.getString(namespace, key); + + if (!value) { + return null; } - public static async setString( - namespace: string, - key: string, - value: string - ): Promise<void> { - const client: ClientType | null = Redis.getClient(); + try { + let jsonObject: JSONObject | JSONArray = JSONFunctions.parse(value); - if (!client || !Redis.isConnected()) { - throw new DatabaseNotConnectedException('Cache is not connected'); - } + if (Array.isArray(jsonObject)) { + jsonObject = JSONFunctions.deserializeArray(jsonObject); + } else { + jsonObject = JSONFunctions.deserialize(jsonObject); + } - await client.set(`${namespace}-${key}`, value); - await client.expire( - `${namespace}-${key}`, - OneUptimeDate.getSecondsInDays(30) - ); + if (!jsonObject) { + return null; + } + + return jsonObject; + } catch (err) { + logger.error(err); + return null; } + } + + public static async getString( + namespace: string, + key: string, + ): Promise<string | null> { + const client: ClientType | null = Redis.getClient(); + + if (!client || !Redis.isConnected()) { + throw new DatabaseNotConnectedException("Cache is not connected"); + } + + const value: string | null = await client?.get(`${namespace}-${key}`); + + if (!value) { + return null; + } + + return value; + } + + public static async setJSON( + namespace: string, + key: string, + value: JSONObject, + ): Promise<void> { + await this.setString( + namespace, + key, + JSON.stringify(JSONFunctions.serialize(value)), + ); + } + + public static async setString( + namespace: string, + key: string, + value: string, + ): Promise<void> { + const client: ClientType | null = Redis.getClient(); + + if (!client || !Redis.isConnected()) { + throw new DatabaseNotConnectedException("Cache is not connected"); + } + + await client.set(`${namespace}-${key}`, value); + await client.expire( + `${namespace}-${key}`, + OneUptimeDate.getSecondsInDays(30), + ); + } } diff --git a/CommonServer/Infrastructure/LocalCache.ts b/CommonServer/Infrastructure/LocalCache.ts index 90212f6467..459ed16eaa 100644 --- a/CommonServer/Infrastructure/LocalCache.ts +++ b/CommonServer/Infrastructure/LocalCache.ts @@ -1,74 +1,66 @@ -import BaseModel from 'Common/Models/BaseModel'; -import Dictionary from 'Common/Types/Dictionary'; -import { JSONValue } from 'Common/Types/JSON'; +import BaseModel from "Common/Models/BaseModel"; +import Dictionary from "Common/Types/Dictionary"; +import { JSONValue } from "Common/Types/JSON"; export default abstract class LocalCache { - private static cache: Dictionary<JSONValue | BaseModel> = {}; + private static cache: Dictionary<JSONValue | BaseModel> = {}; - public static setJSON( - namespace: string, - key: string, - value: JSONValue - ): void { - this.cache[namespace + '.' + key] = value; + public static setJSON( + namespace: string, + key: string, + value: JSONValue, + ): void { + this.cache[namespace + "." + key] = value; + } + + public static setString(namespace: string, key: string, value: string): void { + this.cache[namespace + "." + key] = value; + } + + public static setNumber(namespace: string, key: string, value: number): void { + this.cache[namespace + "." + key] = value; + } + + public static setModel( + namespace: string, + key: string, + value: BaseModel, + ): void { + this.cache[namespace + "." + key] = value; + } + + public static getModel<TBaseModel extends BaseModel>( + namespace: string, + key: string, + ): TBaseModel { + return this.cache[namespace + "." + key] as TBaseModel; + } + + public static getJSON(namespace: string, key: string): JSONValue { + return this.cache[namespace + "." + key] as JSONValue; + } + + public static getString(namespace: string, key: string): string { + return this.cache[namespace + "." + key] as string; + } + + public static getNumber(namespace: string, key: string): number { + return this.cache[namespace + "." + key] as number; + } + + public static async getOrSetString( + namespace: string, + key: string, + getStringFunction: () => Promise<string>, + ): Promise<string> { + if (!LocalCache.getString(namespace, key)) { + LocalCache.setString(namespace, key, await getStringFunction()); } - public static setString( - namespace: string, - key: string, - value: string - ): void { - this.cache[namespace + '.' + key] = value; - } + return LocalCache.getString(namespace, key); + } - public static setNumber( - namespace: string, - key: string, - value: number - ): void { - this.cache[namespace + '.' + key] = value; - } - - public static setModel( - namespace: string, - key: string, - value: BaseModel - ): void { - this.cache[namespace + '.' + key] = value; - } - - public static getModel<TBaseModel extends BaseModel>( - namespace: string, - key: string - ): TBaseModel { - return this.cache[namespace + '.' + key] as TBaseModel; - } - - public static getJSON(namespace: string, key: string): JSONValue { - return this.cache[namespace + '.' + key] as JSONValue; - } - - public static getString(namespace: string, key: string): string { - return this.cache[namespace + '.' + key] as string; - } - - public static getNumber(namespace: string, key: string): number { - return this.cache[namespace + '.' + key] as number; - } - - public static async getOrSetString( - namespace: string, - key: string, - getStringFunction: () => Promise<string> - ): Promise<string> { - if (!LocalCache.getString(namespace, key)) { - LocalCache.setString(namespace, key, await getStringFunction()); - } - - return LocalCache.getString(namespace, key); - } - - public static hasValue(namespace: string, key: string): boolean { - return Boolean(this.cache[namespace + '.' + key]); - } + public static hasValue(namespace: string, key: string): boolean { + return Boolean(this.cache[namespace + "." + key]); + } } diff --git a/CommonServer/Infrastructure/Postgres/DataSourceOptions.ts b/CommonServer/Infrastructure/Postgres/DataSourceOptions.ts index f05019952b..2c052e8399 100644 --- a/CommonServer/Infrastructure/Postgres/DataSourceOptions.ts +++ b/CommonServer/Infrastructure/Postgres/DataSourceOptions.ts @@ -1,42 +1,42 @@ import { - DatabaseHost, - DatabaseName, - DatabasePassword, - DatabasePort, - DatabaseRejectUnauthorized, - DatabaseSslCa, - DatabaseSslCert, - DatabaseSslKey, - DatabaseUsername, - ShouldDatabaseSslEnable, -} from '../../EnvironmentConfig'; -import Migrations from './SchemaMigrations/Index'; -import DatabaseType from 'Common/Types/DatabaseType'; -import Entities from 'Model/Models/Index'; -import { DataSourceOptions } from 'typeorm'; + DatabaseHost, + DatabaseName, + DatabasePassword, + DatabasePort, + DatabaseRejectUnauthorized, + DatabaseSslCa, + DatabaseSslCert, + DatabaseSslKey, + DatabaseUsername, + ShouldDatabaseSslEnable, +} from "../../EnvironmentConfig"; +import Migrations from "./SchemaMigrations/Index"; +import DatabaseType from "Common/Types/DatabaseType"; +import Entities from "Model/Models/Index"; +import { DataSourceOptions } from "typeorm"; const dataSourceOptions: DataSourceOptions = { - type: DatabaseType.Postgres, - host: DatabaseHost.toString(), - port: DatabasePort.toNumber(), - username: DatabaseUsername, - password: DatabasePassword, - database: DatabaseName, - migrationsTableName: 'migrations', - migrations: Migrations, - migrationsRun: true, - entities: Entities, - applicationName: 'oneuptime', - ssl: ShouldDatabaseSslEnable - ? { - rejectUnauthorized: DatabaseRejectUnauthorized, - ca: DatabaseSslCa, - key: DatabaseSslKey, - cert: DatabaseSslCert, - } - : false, - // logging: 'all', - synchronize: false, + type: DatabaseType.Postgres, + host: DatabaseHost.toString(), + port: DatabasePort.toNumber(), + username: DatabaseUsername, + password: DatabasePassword, + database: DatabaseName, + migrationsTableName: "migrations", + migrations: Migrations, + migrationsRun: true, + entities: Entities, + applicationName: "oneuptime", + ssl: ShouldDatabaseSslEnable + ? { + rejectUnauthorized: DatabaseRejectUnauthorized, + ca: DatabaseSslCa, + key: DatabaseSslKey, + cert: DatabaseSslCert, + } + : false, + // logging: 'all', + synchronize: false, }; export default dataSourceOptions; diff --git a/CommonServer/Infrastructure/Postgres/LocalMigrationGenerationDataSource.ts b/CommonServer/Infrastructure/Postgres/LocalMigrationGenerationDataSource.ts index 391c26a09e..d112bc89e2 100644 --- a/CommonServer/Infrastructure/Postgres/LocalMigrationGenerationDataSource.ts +++ b/CommonServer/Infrastructure/Postgres/LocalMigrationGenerationDataSource.ts @@ -1,14 +1,14 @@ -import dataSourceOptions from './DataSourceOptions'; -import { DataSource } from 'typeorm'; +import dataSourceOptions from "./DataSourceOptions"; +import { DataSource } from "typeorm"; const dataSourceOptionToMigrate: any = { - ...dataSourceOptions, - host: 'localhost', - port: 5400, + ...dataSourceOptions, + host: "localhost", + port: 5400, }; const PostgresDataSource: DataSource = new DataSource( - dataSourceOptionToMigrate + dataSourceOptionToMigrate, ); export default PostgresDataSource; diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717605043663-InitialMigration.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717605043663-InitialMigration.ts index e8ce7112dc..40be89368f 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717605043663-InitialMigration.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717605043663-InitialMigration.ts @@ -1,5318 +1,5302 @@ -import { MigrationInterface, QueryRunner, Table } from 'typeorm'; +import { MigrationInterface, QueryRunner, Table } from "typeorm"; export default class InitialMigration implements MigrationInterface { - public name: string = 'InitialMigration1717605043663'; + public name: string = "InitialMigration1717605043663"; - public async up(queryRunner: QueryRunner): Promise<void> { - // check if File table already exists, then dont run the migration - const fileTable: Table | undefined = await queryRunner.getTable('File'); + public async up(queryRunner: QueryRunner): Promise<void> { + // check if File table already exists, then dont run the migration + const fileTable: Table | undefined = await queryRunner.getTable("File"); - if (fileTable) { - return; - } - - await queryRunner.query( - `CREATE TABLE "File" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "file" bytea NOT NULL, "name" character varying(100) NOT NULL, "type" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "isPublic" character varying(100) NOT NULL DEFAULT true, CONSTRAINT "PK_a5e28609454dda7dc62b97d235c" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE TABLE "User" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "name" character varying(50), "email" character varying(100) NOT NULL, "newUnverifiedTemporaryEmail" character varying(100), "slug" character varying(100) NOT NULL, "password" character varying(64), "isEmailVerified" boolean NOT NULL DEFAULT false, "companyName" character varying(100), "jobRole" character varying(100), "companySize" character varying(100), "referral" character varying(100), "companyPhoneNumber" character varying(30), "profilePictureId" uuid, "twoFactorAuthEnabled" boolean NOT NULL DEFAULT false, "twoFactorSecretCode" character varying(100), "twoFactorAuthUrl" character varying(100), "backupCodes" text, "jwtRefreshToken" character varying(100), "paymentProviderCustomerId" character varying(100), "resetPasswordToken" character varying(100), "resetPasswordExpires" TIMESTAMP WITH TIME ZONE, "timezone" character varying(100), "lastActive" TIMESTAMP WITH TIME ZONE, "promotionName" character varying(100), "isDisabled" boolean NOT NULL DEFAULT false, "paymentFailedDate" TIMESTAMP WITH TIME ZONE, "isMasterAdmin" boolean NOT NULL DEFAULT false, "isBlocked" boolean NOT NULL DEFAULT false, "alertPhoneNumber" character varying(30), "alertPhoneVerificationCode" character varying(8), "utmSource" character varying(500), "utmMedium" character varying(500), "utmCampaign" character varying(500), "utmTerm" character varying(500), "utmContent" character varying(500), "utmUrl" character varying(500), "alertPhoneVerificationCodeRequestTime" TIMESTAMP WITH TIME ZONE, "tempAlertPhoneNumber" character varying(30), "deletedByUserId" uuid, CONSTRAINT "UQ_4a257d2c9837248d70640b3e36e" UNIQUE ("email"), CONSTRAINT "UQ_70f42c60b74c0e931f0d599f03d" UNIQUE ("slug"), CONSTRAINT "PK_decdf2abb1c7cbeb1a805aada89" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_70f42c60b74c0e931f0d599f03" ON "User" ("slug") ` - ); - await queryRunner.query( - `CREATE TABLE "AcmeCertificate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "domain" character varying(100) NOT NULL, "certificate" text NOT NULL, "certificateKey" text NOT NULL, "issuedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_185a0eac2bf16488e0b0c940735" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_8545260bc2f2b2cdb2e7184362" ON "AcmeCertificate" ("domain") ` - ); - await queryRunner.query( - `CREATE TABLE "AcmeChallenge" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "domain" character varying(500) NOT NULL, "token" character varying(500) NOT NULL, "challenge" character varying NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_72f3dfbc2c52c2d9442b5dbfec2" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fe7dd70f059b5b9bd0452d3ebf" ON "AcmeChallenge" ("domain") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_936afe487b8f9da2f6aae1d11d" ON "AcmeChallenge" ("token") ` - ); - await queryRunner.query( - `CREATE TABLE "Reseller" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "resellerId" character varying(100) NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(100) NOT NULL, "username" character varying(100) NOT NULL, "password" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "changePlanLink" character varying(100), "hidePhoneNumberOnSignup" boolean, "enableTelemetryFeatures" boolean DEFAULT false, CONSTRAINT "PK_c6ba1f78a2b2458d85c318e0c0f" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE TABLE "ResellerPlan" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "resellerId" uuid NOT NULL, "planId" character varying(100) NOT NULL, "name" character varying(100) NOT NULL, "planType" character varying(100) NOT NULL, "description" character varying(100) NOT NULL, "monitorLimit" integer, "teamMemberLimit" integer, "createdByUserId" uuid, "deletedByUserId" uuid, "otherFeatures" character varying(100), CONSTRAINT "PK_92b9172e490226e492c5cede708" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fc269bd109ac405a458b2acc67" ON "ResellerPlan" ("resellerId") ` - ); - await queryRunner.query( - `CREATE TABLE "Project" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "paymentProviderPlanId" character varying(100), "paymentProviderSubscriptionId" character varying(100), "paymentProviderMeteredSubscriptionId" character varying(100), "paymentProviderSubscriptionSeats" integer, "trialEndsAt" TIMESTAMP WITH TIME ZONE, "paymentProviderCustomerId" character varying(100), "paymentProviderSubscriptionStatus" character varying(100), "paymentProviderMeteredSubscriptionStatus" character varying(100), "paymentProviderPromoCode" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, "isBlocked" boolean NOT NULL DEFAULT false, "isFeatureFlagMonitorGroupsEnabled" boolean DEFAULT false, "unpaidSubscriptionNotificationCount" smallint, "paymentFailedDate" TIMESTAMP WITH TIME ZONE, "paymentSuccessDate" TIMESTAMP WITH TIME ZONE, "workflowRunsInLast30Days" integer, "requireSsoForLogin" boolean NOT NULL DEFAULT false, "activeMonitorsLimit" integer, "seatLimit" integer, "currentActiveMonitorsCount" integer, "smsOrCallCurrentBalanceInUSDCents" integer NOT NULL DEFAULT '0', "autoRechargeSmsOrCallByBalanceInUSD" integer NOT NULL DEFAULT '20', "autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD" integer NOT NULL DEFAULT '10', "enableSmsNotifications" boolean NOT NULL DEFAULT false, "enableCallNotifications" boolean NOT NULL DEFAULT false, "enableAutoRechargeSmsOrCallBalance" boolean NOT NULL DEFAULT false, "lowCallAndSMSBalanceNotificationSentToOwners" boolean NOT NULL DEFAULT false, "failedCallAndSMSBalanceChargeNotificationSentToOwners" boolean NOT NULL DEFAULT false, "notEnabledSmsOrCallNotificationSentToOwners" boolean NOT NULL DEFAULT false, "planName" character varying(100), "lastActive" TIMESTAMP WITH TIME ZONE, "createdOwnerPhone" character varying(30), "createdOwnerEmail" character varying(100), "createdOwnerName" character varying(50), "utmSource" character varying(500), "utmMedium" character varying(500), "utmCampaign" character varying(500), "utmTerm" character varying(500), "utmContent" character varying(500), "utmUrl" character varying(500), "createdOwnerCompanyName" character varying(100), "resellerId" uuid, "resellerPlanId" uuid, "resellerLicenseId" character varying(100), CONSTRAINT "UQ_38f5c1d2bf0743a868288fc8e64" UNIQUE ("slug"), CONSTRAINT "PK_08138e668356f0fe5d9fb43ba6a" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_38f5c1d2bf0743a868288fc8e6" ON "Project" ("slug") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4ee6a519d48b26fe2a78fdc1c9" ON "Project" ("resellerId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b5ee87614c184778810283c299" ON "Project" ("resellerPlanId") ` - ); - await queryRunner.query( - `CREATE TABLE "ApiKey" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "apiKey" uuid NOT NULL, CONSTRAINT "PK_e71aa2e686b8cc09ac083b362a1" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_bb1019f0078a21b4854f5cb3ed" ON "ApiKey" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0e26b8c243b0ed1395bd52aaaf" ON "ApiKey" ("name") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_17bec9c02846cdf64b5d6bb71f" ON "ApiKey" ("apiKey") ` - ); - await queryRunner.query( - `CREATE TABLE "Label" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, CONSTRAINT "PK_fa0b1a36a5943fe60fc72b7332d" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f10d59c2ba66e085722e0053cb" ON "Label" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "ApiKeyPermission" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "apiKeyId" uuid NOT NULL, "projectId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "permission" character varying(100) NOT NULL, "isBlockPermission" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_557178680bd9b61c3fb437bbfb3" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0cf347c575f15d3836615f5325" ON "ApiKeyPermission" ("apiKeyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fb09dd7fefa9d5d44b1907be5f" ON "ApiKeyPermission" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "BillingInvoice" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "amount" numeric NOT NULL, "currencyCode" character varying(100) NOT NULL, "downloadableLink" text NOT NULL, "status" character varying(100) NOT NULL, "paymentProviderCustomerId" character varying(100) NOT NULL, "paymentProviderSubscriptionId" character varying(100), "paymentProviderInvoiceId" character varying(100) NOT NULL, CONSTRAINT "PK_44308aa9517968a8b794fae6f5f" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0ab13e9a92ce4801c37c2a0a77" ON "BillingInvoice" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "BillingPaymentMethod" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "type" character varying(100) NOT NULL, "paymentProviderPaymentMethodId" character varying(100) NOT NULL, "paymentProviderCustomerId" character varying(100) NOT NULL, "last4Digits" character varying(100) NOT NULL, "isDefault" boolean, CONSTRAINT "PK_9752e07a1ef457e672017373d13" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_db4bb9add01b7d8286869fd9a0" ON "BillingPaymentMethod" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "CallLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "toNumber" character varying(30) NOT NULL, "fromNumber" character varying(30) NOT NULL, "callData" jsonb NOT NULL, "statusMessage" character varying(500), "status" character varying(100) NOT NULL, "callCostInUSDCents" integer NOT NULL DEFAULT '0', "deletedByUserId" uuid, CONSTRAINT "PK_86bec18cba9886025ef8e24a88b" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5648767682195afaeb09098a21" ON "CallLog" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_14a8701025e30ab54de66990dc" ON "CallLog" ("toNumber") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_af110ffcec14a2770dada25c92" ON "CallLog" ("fromNumber") ` - ); - await queryRunner.query( - `CREATE TABLE "DataMigrations" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "name" character varying(100) NOT NULL, "executed" boolean NOT NULL DEFAULT true, "executedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_ebe23fc5dac97954a1e0295988d" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE TABLE "Domain" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "domain" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isVerified" boolean NOT NULL DEFAULT false, "domainVerificationText" character varying(100) NOT NULL, CONSTRAINT "UQ_6bf543b5b16ff1e18637a1d6e26" UNIQUE ("domainVerificationText"), CONSTRAINT "PK_4c9c7a5e777572225bde32ddd8e" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f1ce90d3f9693be29b72fabe93" ON "Domain" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "ProjectSMTPConfig" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "username" character varying(100), "password" character varying(500), "hostname" character varying(100) NOT NULL, "port" integer NOT NULL, "fromEmail" character varying(100) NOT NULL, "fromName" character varying(100) NOT NULL, "secure" boolean NOT NULL DEFAULT true, CONSTRAINT "PK_8ac366e3dfa1bd3cb1560cc8f87" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a540dab929fa6582b93f258ffe" ON "ProjectSMTPConfig" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "EmailLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "toEmail" character varying(100), "fromEmail" character varying(100), "subject" character varying(500) NOT NULL, "statusMessage" character varying(500), "status" character varying(100) NOT NULL, "projectSmtpConfigId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_ad2639709254abb94b89551e613" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7b72c5131b3dd1f3edf201a561" ON "EmailLog" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_2c92ae54071acce65fa134d855" ON "EmailLog" ("toEmail") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c4d606cbaafbdbbf5130c97058" ON "EmailLog" ("fromEmail") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_046364c162885b6ac65d5dd367" ON "EmailLog" ("projectSmtpConfigId") ` - ); - await queryRunner.query( - `CREATE TABLE "EmailVerificationToken" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "userId" uuid NOT NULL, "email" character varying(100) NOT NULL, "token" uuid NOT NULL, "expires" TIMESTAMP WITH TIME ZONE NOT NULL, "deletedByUserId" uuid, CONSTRAINT "UQ_0817e87f2e8de1019a09b1eb606" UNIQUE ("token"), CONSTRAINT "PK_527b465552328251512aea2587a" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0817e87f2e8de1019a09b1eb60" ON "EmailVerificationToken" ("token") ` - ); - await queryRunner.query( - `CREATE TABLE "GlobalConfig" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "disableSignup" boolean DEFAULT false, "isSMTPSecure" boolean, "smtpUsername" character varying(100), "smtpPassword" character varying(100), "smtpPort" integer, "smtpHost" character varying(100), "smtpFromEmail" character varying(100), "smtpFromName" character varying(100), "twilioAccountSID" character varying(100), "twilioAuthToken" character varying(100), "twilioPhoneNumber" character varying(30), "emailServerType" character varying, "sendgridApiKey" character varying, "sendgridFromEmail" character varying, "sendgridFromName" character varying, "isMasterApiKeyEnabled" boolean DEFAULT false, "masterApiKey" uuid, "adminNotificationEmail" character varying(100), CONSTRAINT "UQ_5f6e5421b62acf7ac0dc24efe1f" UNIQUE ("disableSignup"), CONSTRAINT "UQ_7f637cadeb879b83663c724ada6" UNIQUE ("isSMTPSecure"), CONSTRAINT "UQ_9cfe51eb0e99f5b3c3f4183540a" UNIQUE ("smtpUsername"), CONSTRAINT "UQ_26512a721317adaa03d37eb00d4" UNIQUE ("smtpPassword"), CONSTRAINT "UQ_ec87ad4cb593cbce6c29c63f2e5" UNIQUE ("smtpPort"), CONSTRAINT "UQ_6abdf5f1b02c3f1f4b63455b5b7" UNIQUE ("smtpHost"), CONSTRAINT "UQ_f180b8fbfd18df3fc9e09d3dc0e" UNIQUE ("smtpFromEmail"), CONSTRAINT "UQ_8cca9c2a2c1bae5509cbbbb4d4c" UNIQUE ("smtpFromName"), CONSTRAINT "UQ_ff4de79bf16e52712a4b264f906" UNIQUE ("twilioAccountSID"), CONSTRAINT "UQ_bc4b634ed3ed8854b06872115a0" UNIQUE ("twilioAuthToken"), CONSTRAINT "UQ_c223b66a0ca2fa8095cb7a6c7cc" UNIQUE ("twilioPhoneNumber"), CONSTRAINT "UQ_0be4582bb7cc1337e58f1d9b45e" UNIQUE ("emailServerType"), CONSTRAINT "UQ_1489dd2e4879912dd7caa0dee5c" UNIQUE ("sendgridApiKey"), CONSTRAINT "UQ_cc133cd137597be73a2540ece9f" UNIQUE ("sendgridFromEmail"), CONSTRAINT "UQ_6d73f5eea5f1d49c1701fb04f2d" UNIQUE ("sendgridFromName"), CONSTRAINT "UQ_7d5fa0275bdc336a22af4ab2b96" UNIQUE ("isMasterApiKeyEnabled"), CONSTRAINT "UQ_117d0b6a9f6c32be770c07f1cf6" UNIQUE ("masterApiKey"), CONSTRAINT "UQ_b99fa557d0e88c933575e7deaa9" UNIQUE ("adminNotificationEmail"), CONSTRAINT "PK_b494a3ff229268d132914e71282" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE TABLE "GreenlockCertificate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "key" character varying(500) NOT NULL, "blob" text NOT NULL, "isKeyPair" boolean NOT NULL DEFAULT false, "deletedByUserId" uuid, CONSTRAINT "PK_f7a338910a034c62b3efa5aa701" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d47fcf292f2ceae490da8404d8" ON "GreenlockCertificate" ("key") ` - ); - await queryRunner.query( - `CREATE TABLE "GreenlockChallenge" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "key" character varying(500) NOT NULL, "token" character varying(500) NOT NULL, "challenge" character varying NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_e2685e2af35bad33efc615c365c" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_36dde33ab3f3fefb63cb164c7d" ON "GreenlockChallenge" ("key") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_8358adfc13448a58e30a799f6e" ON "GreenlockChallenge" ("token") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentSeverity" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, "order" smallint NOT NULL, CONSTRAINT "PK_55c88aed1689e00cce0fdbf4d78" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_00d2f503174bf201abc6e77afd" ON "IncidentSeverity" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentState" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, "isCreatedState" boolean NOT NULL DEFAULT false, "isAcknowledgedState" boolean NOT NULL DEFAULT false, "isResolvedState" boolean NOT NULL DEFAULT false, "order" smallint NOT NULL, CONSTRAINT "PK_8d3b37bb11d8f5aa90bf6df2f1a" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3d279e530067f599f3186e3821" ON "IncidentState" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorStatus" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, "isOperationalState" boolean NOT NULL DEFAULT false, "isOfflineState" boolean NOT NULL DEFAULT false, "priority" integer NOT NULL, CONSTRAINT "PK_2d865658fb987bbe9c9b7517103" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_db1783158a23bd20dbebaae56e" ON "MonitorStatus" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "Monitor" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "monitorType" character varying(100) NOT NULL, "currentMonitorStatusId" uuid NOT NULL, "monitorSteps" jsonb, "monitoringInterval" character varying(100), "customFields" jsonb, "isOwnerNotifiedOfResourceCreation" boolean NOT NULL DEFAULT false, "disableActiveMonitoring" boolean NOT NULL DEFAULT false, "incomingRequestReceivedAt" TIMESTAMP WITH TIME ZONE, "disableActiveMonitoringBecauseOfScheduledMaintenanceEvent" boolean NOT NULL DEFAULT false, "disableActiveMonitoringBecauseOfManualIncident" boolean NOT NULL DEFAULT false, "serverMonitorRequestReceivedAt" TIMESTAMP WITH TIME ZONE, "serverMonitorSecretKey" uuid, "incomingRequestSecretKey" uuid, "incomingMonitorRequest" jsonb, "serverMonitorResponse" jsonb, CONSTRAINT "UQ_42ecc6e0ac9984cd24d5f9ddd8d" UNIQUE ("slug"), CONSTRAINT "PK_eb992d1e4e316083d3535709e43" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_996acfb590bda327843f78b7ad" ON "Monitor" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_36d301a9e41af9b6e62b0c0a02" ON "Monitor" ("name") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_42ecc6e0ac9984cd24d5f9ddd8" ON "Monitor" ("slug") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d3461ab640467c8c2100ea55c7" ON "Monitor" ("currentMonitorStatusId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_db5cc02633b36957c9be4d70c6" ON "Monitor" ("monitoringInterval") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b4c392c3163a2a32da5b401c91" ON "Monitor" ("isOwnerNotifiedOfResourceCreation") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1e0f03c5e067eeb505b2b87aa8" ON "Monitor" ("disableActiveMonitoring") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_9bfee6c29045d1c236d9395f65" ON "Monitor" ("incomingRequestReceivedAt") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ea5170227069c85269b3a6db93" ON "Monitor" ("disableActiveMonitoringBecauseOfScheduledMaintenanceEvent") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_9c8f4b9103fa6b62ff5a121f36" ON "Monitor" ("disableActiveMonitoringBecauseOfManualIncident") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_28d1e0cdf3fcf1ac30249f5d3a" ON "Monitor" ("serverMonitorRequestReceivedAt") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_899a35fe28dd2661f9c999c130" ON "Monitor" ("serverMonitorSecretKey") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_2fb8ee83943588b9c5f3358570" ON "Monitor" ("incomingRequestSecretKey") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicy" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "repeatPolicyIfNoOneAcknowledges" boolean NOT NULL DEFAULT false, "repeatPolicyIfNoOneAcknowledgesNoOfTimes" integer NOT NULL DEFAULT '0', "customFields" jsonb, CONSTRAINT "UQ_68884a658e2c47f67c9b2dd9af5" UNIQUE ("slug"), CONSTRAINT "PK_728c01bb7c570a189dcd5f8f846" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_31508550a088ba2cc843a6c90c" ON "OnCallDutyPolicy" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_570a79facd5fe1c01ccdca55ae" ON "OnCallDutyPolicy" ("name") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_68884a658e2c47f67c9b2dd9af" ON "OnCallDutyPolicy" ("slug") ` - ); - await queryRunner.query( - `CREATE TABLE "Probe" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "key" character varying NOT NULL, "name" character varying(50) NOT NULL, "description" character varying(50), "slug" character varying(100) NOT NULL, "probeVersion" character varying(30) NOT NULL, "lastAlive" TIMESTAMP WITH TIME ZONE, "iconFileId" uuid, "projectId" uuid, "deletedByUserId" uuid, "createdByUserId" uuid, "isGlobalProbe" boolean NOT NULL DEFAULT false, "shouldAutoEnableProbeOnNewMonitors" boolean NOT NULL DEFAULT false, CONSTRAINT "UQ_8f390f96121dc8772b0c29518a4" UNIQUE ("key"), CONSTRAINT "PK_41e4920c56fbb2d208bc4de73a2" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE TABLE "Incident" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "title" character varying(500) NOT NULL, "description" text, "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "currentIncidentStateId" uuid NOT NULL, "incidentSeverityId" uuid NOT NULL, "changeMonitorStatusToId" uuid, "isStatusPageSubscribersNotifiedOnIncidentCreated" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotifiedOnIncidentCreated" boolean NOT NULL DEFAULT true, "customFields" jsonb, "isOwnerNotifiedOfResourceCreation" boolean NOT NULL DEFAULT false, "rootCause" text, "createdStateLog" jsonb, "createdCriteriaId" character varying, "createdIncidentTemplateId" character varying, "createdByProbeId" uuid, "isCreatedAutomatically" boolean NOT NULL DEFAULT false, CONSTRAINT "UQ_eb410c8eb2e2eadfa5880936fe2" UNIQUE ("slug"), CONSTRAINT "PK_bc6d93c3f46bc96e209c379996b" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_eccbc31fa1f58bd051b6f7e108" ON "Incident" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5f53415da5b48954185a6c32b7" ON "Incident" ("title") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_eb410c8eb2e2eadfa5880936fe" ON "Incident" ("slug") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6592d4f7f3b260efc23fc9b4bc" ON "Incident" ("currentIncidentStateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3f28fe3b32abed354a49b26c9c" ON "Incident" ("incidentSeverityId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ed0ec4960a85240f51e6779a00" ON "Incident" ("changeMonitorStatusToId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c3ad64a7aaf39c1f7885527e24" ON "Incident" ("isOwnerNotifiedOfResourceCreation") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5218e92f700d91afe6a8db79cb" ON "Incident" ("rootCause") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ee2acd83fe08dfe3b46a533b7f" ON "Incident" ("createdCriteriaId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6ddd5e3433dcdc6832b2a93845" ON "Incident" ("createdIncidentTemplateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7e537806a80e869917ca1d7e2e" ON "Incident" ("createdByProbeId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_9cdf1c2cd13120af17445219e7a" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_59e7f6a43dbc5ee54e1a1aaaaf" ON "IncidentCustomField" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentInternalNote" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "note" text NOT NULL, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d81f41e2baba0591716e3d1f762" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ac48058b3e5f9e8361d2b8328c" ON "IncidentInternalNote" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b92e75645fd252e4c2f866047d" ON "IncidentInternalNote" ("incidentId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_582d48a4d9cce7dd74ea1dd282" ON "IncidentInternalNote" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentNoteTemplate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "note" text NOT NULL, "templateName" character varying(100) NOT NULL, "templateDescription" character varying(500) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_7c2220728bdefd8c1f0878dd3da" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_21d5bc0d24b3e5032dd391ec8d" ON "IncidentNoteTemplate" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7904e01f4867510e9cb3ba09cd" ON "IncidentNoteTemplate" ("note") ` - ); - await queryRunner.query( - `CREATE TABLE "Team" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isPermissionsEditable" boolean NOT NULL DEFAULT true, "isTeamDeleteable" boolean NOT NULL DEFAULT true, "shouldHaveAtLeastOneMember" boolean NOT NULL DEFAULT false, "isTeamEditable" boolean NOT NULL DEFAULT true, CONSTRAINT "UQ_fcdfc85e8e6b0a1f8128ce572ac" UNIQUE ("slug"), CONSTRAINT "PK_2e76d339d65d3fe4156bbcdd96b" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_baac847c494f692f03fd686e9c" ON "Team" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_319e120005dff229ac97e9e21d" ON "Team" ("name") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fcdfc85e8e6b0a1f8128ce572a" ON "Team" ("slug") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_cf830a956d75a7d10fba1d323f9" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_95f76375ccac835f815d7e926a" ON "IncidentOwnerTeam" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_389aadeb39a0806e80d4001016" ON "IncidentOwnerTeam" ("teamId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_408324d3635a826538a792422f" ON "IncidentOwnerTeam" ("incidentId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a913a00c56edac2d2e3364fb8b" ON "IncidentOwnerTeam" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_28a06b920ce7baf44968e1fa0b3" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0ee7ae6757442fba470b213015" ON "IncidentOwnerUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6311087eeb14ab51e6a1e6133f" ON "IncidentOwnerUser" ("userId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6aa9a6b46f8e044d722da8f5a7" ON "IncidentOwnerUser" ("incidentId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_8347c45d2b96d4f809bbefeb80" ON "IncidentOwnerUser" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentPublicNote" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "note" text NOT NULL, "isStatusPageSubscribersNotifiedOnNoteCreated" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotifiedOnNoteCreated" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, "postedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_d7a24846b34fe3538745bf3fcb9" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a9a77e5f286b5724f4e2280d0a" ON "IncidentPublicNote" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a6964d3aab71608daab9f20e30" ON "IncidentPublicNote" ("incidentId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_8bbc15605fce799ab2abf6532b" ON "IncidentPublicNote" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentStateTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "incidentStateId" uuid NOT NULL, "isStatusPageSubscribersNotified" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotified" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, "stateChangeLog" jsonb, "rootCause" text, "endsAt" TIMESTAMP WITH TIME ZONE, "startsAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_5f3add0368060e7eb60e540ea5b" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_764daa366a4e195768a49e0ee3" ON "IncidentStateTimeline" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fe2dff414a1f67260e3c518981" ON "IncidentStateTimeline" ("incidentId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ff0fca6570d47798771763533a" ON "IncidentStateTimeline" ("incidentStateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5a1a64bc4c38107b25a4bdcd17" ON "IncidentStateTimeline" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7db6b1a8fbbc9eb44c2e7f5047" ON "IncidentStateTimeline" ("rootCause") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_71c94e9f34772d46fd50e18b64" ON "IncidentStateTimeline" ("endsAt") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_dc5784aa146b249a22afe48b7e" ON "IncidentStateTimeline" ("startsAt") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentTemplate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "title" character varying(500) NOT NULL, "templateName" character varying(100) NOT NULL, "templateDescription" character varying(500) NOT NULL, "description" text, "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "incidentSeverityId" uuid, "changeMonitorStatusToId" uuid, "customFields" jsonb, CONSTRAINT "UQ_9fe9e55006c2a1f26727e479ab4" UNIQUE ("slug"), CONSTRAINT "PK_f00b9bff4a246bd76cfe7179435" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b82dafef226b0fae1ad6cb1857" ON "IncidentTemplate" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0683d65aa3e3483127671d120f" ON "IncidentTemplate" ("title") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_9fe9e55006c2a1f26727e479ab" ON "IncidentTemplate" ("slug") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ceff6a4dfdccecc4aa40dbfe91" ON "IncidentTemplate" ("incidentSeverityId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6d7627ab9d5172c66fc5019216" ON "IncidentTemplate" ("changeMonitorStatusToId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentTemplateOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "incidentTemplateId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_e466241948b2440f2683b38c865" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_cf172eb6797a64ee3750e3f3e2" ON "IncidentTemplateOwnerTeam" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e8090c6569c3a5dbd7ef7485c9" ON "IncidentTemplateOwnerTeam" ("teamId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a895b946fccb109dedd55b85f6" ON "IncidentTemplateOwnerTeam" ("incidentTemplateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_2cb2fc022da183b99cf06f0043" ON "IncidentTemplateOwnerTeam" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentTemplateOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "incidentTemplateId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_ef8497118e191717349bda8fe18" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_da9dd65b4401b954a0ea2b5c8d" ON "IncidentTemplateOwnerUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_36b9b0204f4b17063483cb7308" ON "IncidentTemplateOwnerUser" ("userId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_876dd05e0dfc64219ef5df241c" ON "IncidentTemplateOwnerUser" ("incidentTemplateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_38583ea60d2df4a525098065f3" ON "IncidentTemplateOwnerUser" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_333fe20ed9ea0c59c79e766d2ed" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1c6b61e904d8e3fec1ae719b9e" ON "MonitorCustomField" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorGroup" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "UQ_04092730e7f93ee58b544f484c5" UNIQUE ("slug"), CONSTRAINT "PK_61a04922aee7dd836f27b89a970" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_13905ff40843b11145f21e33ff" ON "MonitorGroup" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_136524b17f6dbf70f1e720e8f6" ON "MonitorGroup" ("name") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_04092730e7f93ee58b544f484c" ON "MonitorGroup" ("slug") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorGroupOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "monitorGroupId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_370b2e5defa4d1cce38673a384c" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0d7052620f268d0fa17f948a85" ON "MonitorGroupOwnerTeam" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_8f19947114087883cea771e1cb" ON "MonitorGroupOwnerTeam" ("teamId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_618bcd9015c257b0727df36fd1" ON "MonitorGroupOwnerTeam" ("monitorGroupId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0736d9a7117c25ee216507fa47" ON "MonitorGroupOwnerTeam" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorGroupOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "monitorGroupId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_436159266b7e87be025d1acedb9" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d5cfbebf8b07405652f5382e15" ON "MonitorGroupOwnerUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4b25f4a18bec8cb177e8d65c5f" ON "MonitorGroupOwnerUser" ("userId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6a4095ee3d04454071816a5bad" ON "MonitorGroupOwnerUser" ("monitorGroupId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7c88d50a62d9ba48e9578c9128" ON "MonitorGroupOwnerUser" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorGroupResource" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "monitorGroupId" uuid NOT NULL, "monitorId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_6af7cd205f96311471e5e9979b3" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_825791d5edb2403d7937f16ed9" ON "MonitorGroupResource" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_cf3dcaa746835ae36615a39d86" ON "MonitorGroupResource" ("monitorGroupId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_50b373c428cfd4566cc5caf98e" ON "MonitorGroupResource" ("monitorId") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "monitorId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_b9c6db3ddceeab34383b8f36fbe" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6a6213072d8637e6e625bc7892" ON "MonitorOwnerTeam" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_63e5bbac01d1f68c7b08f126cd" ON "MonitorOwnerTeam" ("teamId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1d5265d1f3ca2f8b8e461e4998" ON "MonitorOwnerTeam" ("monitorId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_acb20c616e3781a3a5506f89ef" ON "MonitorOwnerTeam" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "monitorId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_4d05a001b7a955f011c7933effb" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_12a3497e1404fcdbc4e8963a58" ON "MonitorOwnerUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_324f1d50d0427bbd0e3308c459" ON "MonitorOwnerUser" ("userId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a65ce9b11b2d7bde123aa7633f" ON "MonitorOwnerUser" ("monitorId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ab449be4e08009bfc2e68f5c78" ON "MonitorOwnerUser" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorProbe" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "probeId" uuid NOT NULL, "monitorId" uuid NOT NULL, "lastPingAt" TIMESTAMP WITH TIME ZONE, "nextPingAt" TIMESTAMP WITH TIME ZONE, "createdByUserId" uuid, "deletedByUserId" uuid, "isEnabled" boolean NOT NULL DEFAULT true, "lastMonitoringLog" jsonb, CONSTRAINT "PK_6888f9282ecc31fe8d6d089e015" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d887935f224b896ce64872c37c" ON "MonitorProbe" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_2d50a0e0e624369e7f90a62e8d" ON "MonitorProbe" ("probeId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a540272f483ef1de68f7e64748" ON "MonitorProbe" ("monitorId") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorSecret" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "secretValue" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_0ea8e83c6410e83ceb84bfbab3d" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7c3629c5ae14e97bede3bc548e" ON "MonitorSecret" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorStatusTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "monitorId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "monitorStatusId" uuid NOT NULL, "isOwnerNotified" boolean NOT NULL DEFAULT false, "statusChangeLog" jsonb, "rootCause" text, "endsAt" TIMESTAMP WITH TIME ZONE, "startsAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_266e2a64b41ff8497a2e8461ef4" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_80213eb3f228f1e3d423f5127e" ON "MonitorStatusTimeline" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c08a7c1ef8d511b335a991aac4" ON "MonitorStatusTimeline" ("monitorId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_574feb4161c5216c2c7ee0faaf" ON "MonitorStatusTimeline" ("monitorStatusId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0ea6641468483b2ace63144031" ON "MonitorStatusTimeline" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_01ac1d1ef9e72aeb6dac6575dd" ON "MonitorStatusTimeline" ("rootCause") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_26f6632b71574ff4dbe87c352d" ON "MonitorStatusTimeline" ("endsAt") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_2606f4914507b3471f40864348" ON "MonitorStatusTimeline" ("startsAt") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_42e1ccec0ecdd48a12a0f235a19" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e791474e098f276eda27704b47" ON "OnCallDutyPolicyCustomField" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyEscalationRule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "escalateAfterInMinutes" integer, "order" integer NOT NULL, CONSTRAINT "PK_560358a01ddcdec1b21fbf76409" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d45a545669dc46da25cc60d1df" ON "OnCallDutyPolicyEscalationRule" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b90c1cda36981c41e3965a9380" ON "OnCallDutyPolicyEscalationRule" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e3e66222024c1119865f3eae0f" ON "OnCallDutyPolicyEscalationRule" ("name") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4020168f8d1ee248b8d4bd6293" ON "OnCallDutyPolicyEscalationRule" ("escalateAfterInMinutes") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicySchedule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "UQ_f2f09fcba2e6eabe61d16aa2423" UNIQUE ("slug"), CONSTRAINT "PK_d2a1948140b469f311129c52db8" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0e1b7c3c3e8305a10716cdb8d6" ON "OnCallDutyPolicySchedule" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_15bea18a6b3f9730ce6fad2804" ON "OnCallDutyPolicySchedule" ("name") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f2f09fcba2e6eabe61d16aa242" ON "OnCallDutyPolicySchedule" ("slug") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyEscalationRuleSchedule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "onCallDutyPolicyScheduleId" uuid, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_86a8318c50e27e228aad5c544be" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_efa24aa8feb92d4e15a707c20e" ON "OnCallDutyPolicyEscalationRuleSchedule" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_651a4ab9e3cbb20f6b62a87a6b" ON "OnCallDutyPolicyEscalationRuleSchedule" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_089081f83ef22cdba5a0903ce5" ON "OnCallDutyPolicyEscalationRuleSchedule" ("onCallDutyPolicyEscalationRuleId") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyEscalationRuleTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "teamId" uuid, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_6aec616275791acb65c0c81fe05" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c8aff8439fbfb07e7388aa9011" ON "OnCallDutyPolicyEscalationRuleTeam" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f5121f361345d858ce740a55a2" ON "OnCallDutyPolicyEscalationRuleTeam" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0e7d4060e2fabe0957b9fedb42" ON "OnCallDutyPolicyEscalationRuleTeam" ("onCallDutyPolicyEscalationRuleId") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyEscalationRuleUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_8aded5412d5176f07878de3fc87" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b15552e664640f67346193598a" ON "OnCallDutyPolicyEscalationRuleUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_9ca3fbb66842324aa987d4c972" ON "OnCallDutyPolicyEscalationRuleUser" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_bff6f4ae726b5c5cbae10e7d74" ON "OnCallDutyPolicyEscalationRuleUser" ("onCallDutyPolicyEscalationRuleId") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyExecutionLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "triggeredByIncidentId" uuid, "status" character varying(100) NOT NULL, "statusMessage" character varying(500) NOT NULL, "userNotificationEventType" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "acknowledgedByUserId" uuid, "acknowledgedAt" TIMESTAMP WITH TIME ZONE, "acknowledgedByTeamId" uuid, "lastExecutedEscalationRuleOrder" integer, "lastEscalationRuleExecutedAt" TIMESTAMP WITH TIME ZONE, "lastExecutedEscalationRuleId" uuid, "executeNextEscalationRuleInMinutes" integer, "onCallPolicyExecutionRepeatCount" integer NOT NULL DEFAULT '1', CONSTRAINT "PK_516d5244ab7e4b73b5cc3a7d1ba" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_00e9676d39eeb807de70443051" ON "OnCallDutyPolicyExecutionLog" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e4bb332263960531a4c9e2d425" ON "OnCallDutyPolicyExecutionLog" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fbb50489ef5eb354f46d479e2a" ON "OnCallDutyPolicyExecutionLog" ("lastExecutedEscalationRuleOrder") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_296e2be818e3fba28e43b457fb" ON "OnCallDutyPolicyExecutionLog" ("lastEscalationRuleExecutedAt") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d4a0ffc5e9e698bb2612ba0e55" ON "OnCallDutyPolicyExecutionLog" ("lastExecutedEscalationRuleId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6ad07641fe00f29edc65716aca" ON "OnCallDutyPolicyExecutionLog" ("executeNextEscalationRuleInMinutes") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a217798f1dbf12d31b498a1020" ON "OnCallDutyPolicyExecutionLog" ("onCallPolicyExecutionRepeatCount") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyExecutionLogTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "triggeredByIncidentId" uuid NOT NULL, "onCallDutyPolicyExecutionLogId" uuid NOT NULL, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "userNotificationEventType" character varying(100) NOT NULL, "alertSentToUserId" uuid, "userBelongsToTeamId" uuid, "onCallDutyScheduleId" uuid, "statusMessage" character varying(500) NOT NULL, "status" character varying(100) NOT NULL, "createdByUserId" uuid, "isAcknowledged" boolean, "acknowledgedAt" TIMESTAMP WITH TIME ZONE, "deletedByUserId" uuid, CONSTRAINT "PK_720de860de392ed64dc9b2a75d1" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_591b7ed73c964e105bfecc6fd6" ON "OnCallDutyPolicyExecutionLogTimeline" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c5a798ca667fedda71d4ed5465" ON "OnCallDutyPolicyExecutionLogTimeline" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_90119ec7f77fa2efd82261e044" ON "OnCallDutyPolicyExecutionLogTimeline" ("triggeredByIncidentId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_872c2f6a9739bab1b57b6d51ea" ON "OnCallDutyPolicyExecutionLogTimeline" ("onCallDutyPolicyExecutionLogId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0ad4222a4c48b8a64e3a58b351" ON "OnCallDutyPolicyExecutionLogTimeline" ("onCallDutyPolicyEscalationRuleId") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyScheduleLayer" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyScheduleId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "order" integer NOT NULL, "startsAt" TIMESTAMP WITH TIME ZONE NOT NULL, "rotation" jsonb NOT NULL DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}', "handOffTime" TIMESTAMP WITH TIME ZONE NOT NULL, "restrictionTimes" jsonb NOT NULL DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}', CONSTRAINT "PK_d7a261a9f6c6b17e582bf0c3639" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3b892ef36671f1ea1c8457a96d" ON "OnCallDutyPolicyScheduleLayer" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6fa6574a45cf1352c5a3b96251" ON "OnCallDutyPolicyScheduleLayer" ("onCallDutyPolicyScheduleId") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyScheduleLayerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyScheduleId" uuid NOT NULL, "onCallDutyPolicyScheduleLayerId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "order" integer NOT NULL, "userId" uuid NOT NULL, CONSTRAINT "PK_1c9f5d86c8045253f4ef592db0e" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_08afccd6cbbd1a7015d4fe25e3" ON "OnCallDutyPolicyScheduleLayerUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_9b36bba6d9898331920805a29c" ON "OnCallDutyPolicyScheduleLayerUser" ("onCallDutyPolicyScheduleId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_41f4ecc29351e1a406e83b30a9" ON "OnCallDutyPolicyScheduleLayerUser" ("onCallDutyPolicyScheduleLayerId") ` - ); - await queryRunner.query( - `CREATE TABLE "ProjectCallSMSConfig" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "twilioAccountSID" character varying(100), "twilioAuthToken" character varying(100), "twilioPhoneNumber" character varying(30), CONSTRAINT "UQ_0886139eac04ad49627e446d477" UNIQUE ("twilioAccountSID"), CONSTRAINT "UQ_2eb1a240d549a7701b6e82d2f94" UNIQUE ("twilioAuthToken"), CONSTRAINT "UQ_50235223d7fd7b0c27063bfb08e" UNIQUE ("twilioPhoneNumber"), CONSTRAINT "PK_3661ce6081b280d86a68821d8d4" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_20334b9571a6cd1a871e70d8e7" ON "ProjectCallSMSConfig" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "ProjectSSO" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying NOT NULL, "signatureMethod" character varying(100) NOT NULL, "digestMethod" character varying(100) NOT NULL, "signOnURL" text NOT NULL, "issuerURL" text NOT NULL, "publicCertificate" text NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isEnabled" boolean NOT NULL DEFAULT false, "isTested" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_cba071f29cefd68a7109c987dc0" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_be9e6751765501ea1db126fcb2" ON "ProjectSSO" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "PromoCode" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "promoCodeId" character varying(100) NOT NULL, "planType" character varying(100), "userEmail" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, "resellerId" uuid, "resellerPlanId" uuid, "resellerLicenseId" character varying(100), "isPromoCodeUsed" boolean NOT NULL DEFAULT false, "promoCodeUsedAt" TIMESTAMP WITH TIME ZONE, "projectId" uuid, CONSTRAINT "PK_80093d5bf3e6bc69726124cf0ef" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6dcdf97c0834dd44b4f2c93e66" ON "PromoCode" ("resellerId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7aecf4b1ae3e45647cb911f4c1" ON "PromoCode" ("resellerPlanId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a7b405e2a9ae144be016bcf973" ON "PromoCode" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceState" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, "isScheduledState" boolean NOT NULL DEFAULT false, "isOngoingState" boolean NOT NULL DEFAULT false, "isEndedState" boolean NOT NULL DEFAULT false, "isResolvedState" boolean NOT NULL DEFAULT false, "order" smallint NOT NULL, CONSTRAINT "PK_6a6e85bc85c65a673855bd5fcc4" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_57a31fb2a5e4caa223d2506a4e" ON "ScheduledMaintenanceState" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPage" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "pageTitle" character varying(100), "pageDescription" character varying(500), "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "faviconFileId" uuid, "logoFileId" uuid, "coverImageFileId" uuid, "headerHTML" text, "footerHTML" text, "customCSS" text, "customJavaScript" text, "isPublicStatusPage" boolean NOT NULL DEFAULT true, "showIncidentLabelsOnStatusPage" boolean NOT NULL DEFAULT false, "showScheduledEventLabelsOnStatusPage" boolean NOT NULL DEFAULT false, "enableSubscribers" boolean NOT NULL DEFAULT true, "enableEmailSubscribers" boolean NOT NULL DEFAULT true, "allowSubscribersToChooseResources" boolean NOT NULL DEFAULT false, "enableSmsSubscribers" boolean NOT NULL DEFAULT false, "copyrightText" character varying, "customFields" jsonb, "requireSsoForLogin" boolean NOT NULL DEFAULT false, "smtpConfigId" uuid, "callSmsConfigId" uuid, "isOwnerNotifiedOfResourceCreation" boolean NOT NULL DEFAULT false, "showIncidentHistoryInDays" integer NOT NULL DEFAULT '14', "showAnnouncementHistoryInDays" integer NOT NULL DEFAULT '14', "showScheduledEventHistoryInDays" integer NOT NULL DEFAULT '14', "overviewPageDescription" text, "hidePoweredByOneUptimeBranding" boolean NOT NULL DEFAULT false, "defaultBarColor" character varying(7), CONSTRAINT "UQ_4cf5977515ca677e569942fb096" UNIQUE ("slug"), CONSTRAINT "PK_6a581c0dbe022a59d4d6e197332" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_34a4c35e0d7afe6f023825a68c" ON "StatusPage" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5f7df6dd7b1a85b933bd953b47" ON "StatusPage" ("name") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4cf5977515ca677e569942fb09" ON "StatusPage" ("slug") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_61944d851b4a7213d79ef28174" ON "StatusPage" ("smtpConfigId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4e347d3f99b67dacd149beaf61" ON "StatusPage" ("callSmsConfigId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b8a8b32e0d7e06ed8a9a3171ab" ON "StatusPage" ("isOwnerNotifiedOfResourceCreation") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7213cf79fce2db23927de0aac0" ON "StatusPage" ("showIncidentHistoryInDays") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_97509c6e7f41b59c5447cec669" ON "StatusPage" ("showAnnouncementHistoryInDays") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_cfcd002648dcf692a2e126ab05" ON "StatusPage" ("showScheduledEventHistoryInDays") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b5138c4c7dc1c36ef592af784f" ON "StatusPage" ("overviewPageDescription") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenance" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "title" character varying(100) NOT NULL, "description" text, "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "currentScheduledMaintenanceStateId" uuid NOT NULL, "changeMonitorStatusToId" uuid, "startsAt" TIMESTAMP WITH TIME ZONE NOT NULL, "endsAt" TIMESTAMP WITH TIME ZONE NOT NULL, "isStatusPageSubscribersNotifiedOnEventScheduled" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotifiedOnEventCreated" boolean NOT NULL DEFAULT true, "shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing" boolean NOT NULL DEFAULT true, "shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded" boolean NOT NULL DEFAULT true, "customFields" jsonb, "isOwnerNotifiedOfResourceCreation" boolean NOT NULL DEFAULT false, CONSTRAINT "UQ_e0d4bcb3e28628a47b8b55ead8c" UNIQUE ("slug"), CONSTRAINT "PK_be50d5233ebfd8db940db9e50b6" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4059dd569d6a482062352bf266" ON "ScheduledMaintenance" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_20f04dff3b9d1f3d62985dd9de" ON "ScheduledMaintenance" ("title") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e0d4bcb3e28628a47b8b55ead8" ON "ScheduledMaintenance" ("slug") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_883038abda021ce79fa838d027" ON "ScheduledMaintenance" ("currentScheduledMaintenanceStateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fab9cc7e6ffcf02872fccfab97" ON "ScheduledMaintenance" ("changeMonitorStatusToId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c8306d145e77473ee7ac859a0d" ON "ScheduledMaintenance" ("isOwnerNotifiedOfResourceCreation") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_b4c186084a3be506bf61ead8cda" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3621c488327e1c00518aa4e881" ON "ScheduledMaintenanceCustomField" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceInternalNote" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "note" text NOT NULL, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_277bd22df1a0a0292a6361ef165" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d79c49a0a613a6b432fd400e69" ON "ScheduledMaintenanceInternalNote" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a53ef45aebd4a6a6e7dde7f896" ON "ScheduledMaintenanceInternalNote" ("scheduledMaintenanceId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_39a27db645744ef9177d4ab7c9" ON "ScheduledMaintenanceInternalNote" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceNoteTemplate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "note" text NOT NULL, "templateName" character varying(100) NOT NULL, "templateDescription" character varying(500) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_2d15584043cc151b1291fb02c26" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b4cb4c1312eb72459907e1bbe9" ON "ScheduledMaintenanceNoteTemplate" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_49d4245d0066dc1e14a63a4234" ON "ScheduledMaintenanceNoteTemplate" ("note") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_260c660b0cb76b661f66d873f40" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e3053b1725658b4a120b4e3185" ON "ScheduledMaintenanceOwnerTeam" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1206beb611e0779ce2248ecbae" ON "ScheduledMaintenanceOwnerTeam" ("teamId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1251fb7d4a4bf8586f2bd1528e" ON "ScheduledMaintenanceOwnerTeam" ("scheduledMaintenanceId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_cbe4eac5f23c115ddd4a695747" ON "ScheduledMaintenanceOwnerTeam" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_cd8d017d0e0ec04d7c17a081c7d" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_518742c7037d9a38a6594dc18a" ON "ScheduledMaintenanceOwnerUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_be6a25806925f93b8949e61929" ON "ScheduledMaintenanceOwnerUser" ("userId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_967c33f7bce5de522c1d1a80e7" ON "ScheduledMaintenanceOwnerUser" ("scheduledMaintenanceId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5d71879db35e7c4104b56bef09" ON "ScheduledMaintenanceOwnerUser" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenancePublicNote" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "note" text NOT NULL, "isStatusPageSubscribersNotifiedOnNoteCreated" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotifiedOnNoteCreated" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, "postedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_62d669cf499354d7c8438f76f37" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_73c6737ab4a7718c45932bffad" ON "ScheduledMaintenancePublicNote" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_937aabd4adbfce78663406a248" ON "ScheduledMaintenancePublicNote" ("scheduledMaintenanceId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0b7ec5df94e08afda7569ea1ff" ON "ScheduledMaintenancePublicNote" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceStateTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "scheduledMaintenanceStateId" uuid NOT NULL, "isStatusPageSubscribersNotified" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotified" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, "endsAt" TIMESTAMP WITH TIME ZONE, "startsAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_ca5a043c4ab040e7ae65eaf6eb1" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4faf556988f5b6a755ef2e85ae" ON "ScheduledMaintenanceStateTimeline" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_411146017bcfe98bbe028b8d15" ON "ScheduledMaintenanceStateTimeline" ("scheduledMaintenanceId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7c0f750d3a964180d1e1efa16e" ON "ScheduledMaintenanceStateTimeline" ("scheduledMaintenanceStateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_58aa5722dde40c062793ede637" ON "ScheduledMaintenanceStateTimeline" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_978cd5638c6e44186cbd1099d9" ON "ScheduledMaintenanceStateTimeline" ("endsAt") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_590161c4f8e0b63e7ed3fd2163" ON "ScheduledMaintenanceStateTimeline" ("startsAt") ` - ); - await queryRunner.query( - `CREATE TABLE "ShortLink" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "shortId" character varying(100) NOT NULL, "link" text NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_7ddbabbe684e03ba2d9e508d2a1" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_339f8fe3bc6fb4440541cc61a4" ON "ShortLink" ("shortId") ` - ); - await queryRunner.query( - `CREATE TABLE "SmsLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "toNumber" character varying(30) NOT NULL, "fromNumber" character varying(30) NOT NULL, "smsText" text, "statusMessage" character varying(500), "status" character varying(100) NOT NULL, "smsCostInUSDCents" integer NOT NULL DEFAULT '0', "deletedByUserId" uuid, CONSTRAINT "PK_ce409036fe144b388c56688d33e" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a30bbda7f5480e498ebc609663" ON "SmsLog" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_12854d2b71004825924a476dfc" ON "SmsLog" ("toNumber") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_68d0fe13df157c0a93d1ff6fa1" ON "SmsLog" ("fromNumber") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageAnnouncement" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "title" character varying(100) NOT NULL, "showAnnouncementAt" TIMESTAMP WITH TIME ZONE NOT NULL, "endAnnouncementAt" TIMESTAMP WITH TIME ZONE NOT NULL, "description" text NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isStatusPageSubscribersNotified" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotified" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_4e9a99bb763b7be65ef01a2f40f" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5e9c5a7393ac9aa477d625de67" ON "StatusPageAnnouncement" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3c5cbb3fcaf6be56918520501c" ON "StatusPageAnnouncement" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_8b102bbed53bc8f5793a1411469" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4f5ae90bc48a0ddeb50cd009aa" ON "StatusPageCustomField" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageDomain" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "domainId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "subdomain" character varying(100) NOT NULL, "fullDomain" character varying(100) NOT NULL, "createdByUserId" uuid, "cnameVerificationToken" character varying(100) NOT NULL, "isCnameVerified" boolean NOT NULL DEFAULT false, "isSslOrdered" boolean NOT NULL DEFAULT false, "isSslProvisioned" boolean NOT NULL DEFAULT false, "deletedByUserId" uuid, CONSTRAINT "PK_e9ae7614164ee86237ed0b64a1c" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_40cca185c8cf933c04a0534676" ON "StatusPageDomain" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7fab5bc54a8f36eac8f31c8256" ON "StatusPageDomain" ("domainId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_842a66fcb103388fcedffef75f" ON "StatusPageDomain" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageFooterLink" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "title" character varying(100) NOT NULL, "link" character varying(100) NOT NULL, "createdByUserId" uuid, "order" integer NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_73ac6c05ace86d6b2f1a1f9daf6" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5f8f65447c9b881860bf742dc9" ON "StatusPageFooterLink" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1a80c698b2205074f53376d631" ON "StatusPageFooterLink" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageGroup" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" text, "createdByUserId" uuid, "order" integer NOT NULL, "isExpandedByDefault" boolean NOT NULL DEFAULT true, "deletedByUserId" uuid, CONSTRAINT "PK_dfba3695e73b92b24f3408b0040" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4a96c34f030a6e39218352a947" ON "StatusPageGroup" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5dbcfd7d38e5ea7a78a6a78a33" ON "StatusPageGroup" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageHeaderLink" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "title" character varying(100) NOT NULL, "link" character varying(100) NOT NULL, "createdByUserId" uuid, "order" integer NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_11c21c610618e5f36a5d04bb2a4" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_70d70692cbe9d9be188723df4f" ON "StatusPageHeaderLink" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_99c017b6ced8da63abdfbb506e" ON "StatusPageHeaderLink" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageHistoryChartBarColorRule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "uptimePercentGreaterThanOrEqualTo" numeric NOT NULL, "barColor" character varying(7) NOT NULL, "createdByUserId" uuid, "order" integer NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_c91ba8fce53892bef71f7876abf" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a65f8fabf888d227d64570f52b" ON "StatusPageHistoryChartBarColorRule" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_54edc7ff7a74a0310a512c5389" ON "StatusPageHistoryChartBarColorRule" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d0d67c8db3a8eb42aeb18819c52" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_9fac66064d88c514d2e5503237" ON "StatusPageOwnerTeam" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f60296efefe379693bfd55a776" ON "StatusPageOwnerTeam" ("teamId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6a98e42d8df3ba84bd0f79da55" ON "StatusPageOwnerTeam" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_14768105dd06f0e3e75ec5b051" ON "StatusPageOwnerTeam" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_449db4a6703eb1dd929d0d48cca" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c28e05c08656f8aa756734c37c" ON "StatusPageOwnerUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3c59f811e0660c5522e45e85b6" ON "StatusPageOwnerUser" ("userId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_69b1659abafe656563259784d0" ON "StatusPageOwnerUser" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_feea72d826c4bf508a95c7c757" ON "StatusPageOwnerUser" ("isOwnerNotified") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPagePrivateUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "email" character varying(100), "password" character varying(64), "resetPasswordToken" character varying(100), "resetPasswordExpires" TIMESTAMP WITH TIME ZONE, "lastActive" TIMESTAMP WITH TIME ZONE, "createdByUserId" uuid, "isSsoUser" boolean NOT NULL DEFAULT false, "deletedByUserId" uuid, CONSTRAINT "PK_6a541c0c9e0538dcc1499df13ae" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1ce45fe77324ede75166d0f57d" ON "StatusPagePrivateUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0589c51161d13b752fed41a319" ON "StatusPagePrivateUser" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageResource" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "monitorId" uuid, "monitorGroupId" uuid, "statusPageGroupId" uuid, "displayName" character varying(100) NOT NULL, "displayDescription" text, "displayTooltip" character varying(500), "showCurrentStatus" boolean NOT NULL DEFAULT true, "showUptimePercent" boolean NOT NULL DEFAULT false, "uptimePercentPrecision" character varying, "showStatusHistoryChart" boolean NOT NULL DEFAULT true, "createdByUserId" uuid, "order" integer NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_31a5bda3b231f58c41603a56180" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_8fba35fb87a0ad6037eb8fc804" ON "StatusPageResource" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ade3f7acf902dcb313d230ca1f" ON "StatusPageResource" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b1c4fe08e1d90ae4d26d934653" ON "StatusPageResource" ("monitorId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a55bb812676bff276cef1f14c8" ON "StatusPageResource" ("monitorGroupId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_9b61276c47d6091295dec9e535" ON "StatusPageResource" ("statusPageGroupId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageSSO" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying NOT NULL, "signatureMethod" character varying(100) NOT NULL, "digestMethod" character varying(100) NOT NULL, "signOnURL" text NOT NULL, "issuerURL" text NOT NULL, "publicCertificate" text NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isEnabled" boolean NOT NULL DEFAULT false, "isTested" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_55453332aa136bd5e3962a1abb1" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1d63fa142dd4175ef256f21d2a" ON "StatusPageSSO" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_dc05de7939af3ada1567fc7106" ON "StatusPageSSO" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageSubscriber" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "subscriberEmail" character varying(100), "subscriberPhone" character varying(30), "subscriberWebhook" character varying, "createdByUserId" uuid, "deletedByUserId" uuid, "isUnsubscribed" boolean NOT NULL DEFAULT false, "isSubscribedToAllResources" boolean NOT NULL DEFAULT true, CONSTRAINT "PK_9f289c2fb525ab8f6cefec03923" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6adf943966e01699e86117d2e3" ON "StatusPageSubscriber" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_217b295d5882faa6cf3418ed81" ON "StatusPageSubscriber" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "TeamMember" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "teamId" uuid, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "hasAcceptedInvitation" boolean NOT NULL DEFAULT false, "invitationAcceptedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_6f582955f553c7e3035937933ca" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3cc297d538f01065f9925cfb11" ON "TeamMember" ("teamId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fd952f76f5a5297ce69a9a588e" ON "TeamMember" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "TeamPermission" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "permission" character varying(100) NOT NULL, "isBlockPermission" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_34aa36d573354d1f1e7901c4b11" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_78293e9cc1746e5f29ccccfdfc" ON "TeamPermission" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5064c0bdc8ff238952f9a2acf4" ON "TeamPermission" ("teamId") ` - ); - await queryRunner.query( - `CREATE TABLE "TelemetryService" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "telemetryServiceToken" uuid NOT NULL, "retainTelemetryDataForDays" integer DEFAULT '15', "serviceColor" character varying, CONSTRAINT "PK_0bb1c96ecbe42ec7d65544eaecc" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3a3321fd538aa014aa5e4f3522" ON "TelemetryService" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6c89ae3af06376fe3411cf8295" ON "TelemetryService" ("telemetryServiceToken") ` - ); - await queryRunner.query( - `CREATE TABLE "TelemetryUsageBilling" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "day" character varying(100) NOT NULL, "productType" character varying(100) NOT NULL, "retainTelemetryDataForDays" integer DEFAULT '15', "dataIngestedInGB" numeric NOT NULL, "totalCostInUSD" numeric NOT NULL, "isReportedToBillingProvider" boolean NOT NULL DEFAULT false, "telemetryServiceId" uuid NOT NULL, "reportedToBillingProviderAt" TIMESTAMP WITH TIME ZONE, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_218b46ea3e545bc0c017d7a5d81" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5670be95a9496b9380b7c0d793" ON "TelemetryUsageBilling" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_91333210492e5d2f334231468a" ON "TelemetryUsageBilling" ("telemetryServiceId") ` - ); - await queryRunner.query( - `CREATE TABLE "UserCall" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "phone" character varying(30) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "isVerified" boolean NOT NULL DEFAULT false, "verificationCode" character varying(100) NOT NULL, CONSTRAINT "PK_6c486fc3456229565b1dbf1ad89" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_db4e522c086be556e5101c4e91" ON "UserCall" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4d90bd0e309ad43c4541bb428e" ON "UserCall" ("userId") ` - ); - await queryRunner.query( - `CREATE TABLE "UserEmail" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "email" character varying(100) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "isVerified" boolean NOT NULL DEFAULT false, "verificationCode" character varying(100) NOT NULL, CONSTRAINT "PK_72aad6f19ecf4ee9d19253b8b4e" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e6beed22b36201aea1d70ba0d7" ON "UserEmail" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1f713c701d85c69f706e4e82b8" ON "UserEmail" ("userId") ` - ); - await queryRunner.query( - `CREATE TABLE "UserSMS" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "phone" character varying(30) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "isVerified" boolean NOT NULL DEFAULT false, "verificationCode" character varying(100) NOT NULL, CONSTRAINT "PK_f46801e2914a3e668ccf632f29b" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6439689a29a2192708e3f3603d" ON "UserSMS" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3cb16b5c2d69dbdc812247788f" ON "UserSMS" ("userId") ` - ); - await queryRunner.query( - `CREATE TABLE "UserNotificationRule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "ruleType" character varying(100) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "userCallId" uuid, "userSmsId" uuid, "userEmailId" uuid, "notifyAfterMinutes" integer NOT NULL DEFAULT '0', "incidentSeverityId" uuid, CONSTRAINT "PK_c40ab942792cd8e5ad426d95d93" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_69d439e9f60f05ae16732c4999" ON "UserNotificationRule" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_32efb91af1b432a25ceb55bc0d" ON "UserNotificationRule" ("userId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ec8e7a39fcd38d0ea2d40b8afa" ON "UserNotificationRule" ("userCallId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_32ca4397660dafe0cab7d03e5b" ON "UserNotificationRule" ("userSmsId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b407063ee43233b0cc8e9106cb" ON "UserNotificationRule" ("userEmailId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f1a5912cdf877c89121a3090cd" ON "UserNotificationRule" ("notifyAfterMinutes") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_bf703b216d7f59424302dc5d70" ON "UserNotificationRule" ("incidentSeverityId") ` - ); - await queryRunner.query( - `CREATE TABLE "UserNotificationSetting" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "eventType" character varying(100) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "alertByEmail" boolean NOT NULL DEFAULT false, "alertBySMS" boolean NOT NULL DEFAULT false, "alertByCall" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_e79f19e013370f8cf428f8bc9e4" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6f110fc752889f922d6a3c57a5" ON "UserNotificationSetting" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e2565b0aa7d7e015fb6685afed" ON "UserNotificationSetting" ("userId") ` - ); - await queryRunner.query( - `CREATE TABLE "UserOnCallLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "userId" uuid NOT NULL, "userBelongsToTeamId" uuid, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "onCallDutyPolicyExecutionLogId" uuid NOT NULL, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "triggeredByIncidentId" uuid NOT NULL, "status" character varying(100) NOT NULL, "userNotificationEventType" character varying(100) NOT NULL, "onCallDutyPolicyExecutionLogTimelineId" uuid NOT NULL, "statusMessage" character varying(500) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "acknowledgedByUserId" uuid, "acknowledgedAt" TIMESTAMP WITH TIME ZONE, "executedNotificationRules" jsonb, "onCallDutyScheduleId" uuid, CONSTRAINT "PK_61d65627421be2a8a7e0486b054" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ea993eab5c402623b61203e325" ON "UserOnCallLog" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4cd37d481ef366d975c6a7cd9b" ON "UserOnCallLog" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f047dd6a708175bae0ee6f8c4b" ON "UserOnCallLog" ("onCallDutyPolicyExecutionLogId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_550f9f6177451d4902467991a1" ON "UserOnCallLog" ("onCallDutyPolicyEscalationRuleId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7498ad42c4f77f7fab2a6bc2e3" ON "UserOnCallLog" ("onCallDutyPolicyExecutionLogTimelineId") ` - ); - await queryRunner.query( - `CREATE TABLE "UserOnCallLogTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "userId" uuid NOT NULL, "projectId" uuid NOT NULL, "userNotificationLogId" uuid NOT NULL, "userNotificationRuleId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "triggeredByIncidentId" uuid NOT NULL, "onCallDutyPolicyExecutionLogId" uuid NOT NULL, "onCallDutyPolicyExecutionLogTimelineId" uuid NOT NULL, "userNotificationEventType" character varying(100) NOT NULL, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "userBelongsToTeamId" uuid, "statusMessage" character varying(500) NOT NULL, "status" character varying(100) NOT NULL, "createdByUserId" uuid, "isAcknowledged" boolean, "acknowledgedAt" TIMESTAMP WITH TIME ZONE, "userCallId" uuid, "userSmsId" uuid, "userEmailId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_f82bd96ee9c1e338f49c9327430" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_79580470f34858375cae5d353a" ON "UserOnCallLogTimeline" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_dc79ea74fba7b99835bd475081" ON "UserOnCallLogTimeline" ("userNotificationLogId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_06a427cdcbae1ddcb1301b860f" ON "UserOnCallLogTimeline" ("userNotificationRuleId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_ef993920a9967b61fb3fb9bf16" ON "UserOnCallLogTimeline" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_58a44736718a5ec4fe41526289" ON "UserOnCallLogTimeline" ("triggeredByIncidentId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6491952f9d8066aa5cfff92cd8" ON "UserOnCallLogTimeline" ("onCallDutyPolicyExecutionLogId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_26e16ed3e46c6a6589a88d3abb" ON "UserOnCallLogTimeline" ("onCallDutyPolicyExecutionLogTimelineId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_222212e7157f8816357a4f0253" ON "UserOnCallLogTimeline" ("onCallDutyPolicyEscalationRuleId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d5c3df01bbb2a9ce168b36b523" ON "UserOnCallLogTimeline" ("userCallId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_12ef8407b6359205df8339f849" ON "UserOnCallLogTimeline" ("userSmsId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_815c728d905c44bc440ec91308" ON "UserOnCallLogTimeline" ("userEmailId") ` - ); - await queryRunner.query( - `CREATE TABLE "Workflow" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "isEnabled" boolean NOT NULL DEFAULT false, "graph" jsonb, "triggerId" character varying(100), "triggerArguments" jsonb, "repeatableJobKey" character varying, CONSTRAINT "PK_9f6d966c72bccdb6fe2e12a5614" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a87518833c744b2df4324b61a6" ON "Workflow" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "WorkflowLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "workflowId" uuid NOT NULL, "logs" text NOT NULL, "workflowStatus" character varying NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "completedAt" TIMESTAMP WITH TIME ZONE, "deletedByUserId" uuid, CONSTRAINT "PK_dc0cc28be769df9d7e2cb670c72" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6c3dc31b09a96d81982a472e22" ON "WorkflowLog" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_55293b16d84f048f44c771595b" ON "WorkflowLog" ("workflowId") ` - ); - await queryRunner.query( - `CREATE TABLE "WorkflowVariable" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "workflowId" uuid, "name" character varying(100) NOT NULL, "description" character varying(500), "content" text NOT NULL, "isSecret" boolean NOT NULL DEFAULT false, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_9c6680c9bd6a5a7beb7127e73f1" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_bb47f6d0cabd55fde5b199ae20" ON "WorkflowVariable" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c093a47a2ecfa1d5f2d4aeb04a" ON "WorkflowVariable" ("workflowId") ` - ); - await queryRunner.query( - `CREATE TABLE "ApiKeyPermissionLabel" ("apiKeyPermissionId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_264b4d74ff24861aafb0b8fa2ea" PRIMARY KEY ("apiKeyPermissionId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_0946f6b41113cd842ee69f69fb" ON "ApiKeyPermissionLabel" ("apiKeyPermissionId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4db7f66405df73287c639ece90" ON "ApiKeyPermissionLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorLabel" ("monitorId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_7651b053ae253106356301ef280" PRIMARY KEY ("monitorId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3af64cebb5a7cc7f1ad0aa70c1" ON "MonitorLabel" ("monitorId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1866fb21ea1acd3d5c37e28ca1" ON "MonitorLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyLabel" ("onCallDutyPolicyId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_619f9dd19e4c9f2f0f695edb757" PRIMARY KEY ("onCallDutyPolicyId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d4a3f7dc4e33b896b0984e7316" ON "OnCallDutyPolicyLabel" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1c0248fe6856bbe029fc492ec7" ON "OnCallDutyPolicyLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentMonitor" ("incidentId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_0d50be2afc9e6b3182fa5d61bbe" PRIMARY KEY ("incidentId", "monitorId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_55e3162c1259b1b092f0ac63ee" ON "IncidentMonitor" ("incidentId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_9e2000a938f2e12c6653e68780" ON "IncidentMonitor" ("monitorId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentOnCallDutyPolicy" ("onCallDutyPolicyId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_185b450b39a568ea486b69df0df" PRIMARY KEY ("onCallDutyPolicyId", "monitorId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_2d127b6da0e4fab9f905b4d332" ON "IncidentOnCallDutyPolicy" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f89b23e3cafd1c6a0bfd42c297" ON "IncidentOnCallDutyPolicy" ("monitorId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentLabel" ("incidentId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_40289610e0edf423d01074471ec" PRIMARY KEY ("incidentId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1084d1ddbabbcfcab7cd9d547a" ON "IncidentLabel" ("incidentId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_17f4085273d14d4d6145cf6585" ON "IncidentLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentTemplateMonitor" ("incidentTemplateId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_9a793ac183fa115b23db033ae9f" PRIMARY KEY ("incidentTemplateId", "monitorId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_390a45e855282ae55ad56d1e1f" ON "IncidentTemplateMonitor" ("incidentTemplateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_33fb90ba0116b7db4efd4ec7a4" ON "IncidentTemplateMonitor" ("monitorId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentTemplateOnCallDutyPolicy" ("incidentTemplateId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, CONSTRAINT "PK_219e90dde24e7096fdaee3089c7" PRIMARY KEY ("incidentTemplateId", "onCallDutyPolicyId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_99245f38769689fe8a172dcb81" ON "IncidentTemplateOnCallDutyPolicy" ("incidentTemplateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_2b76b57ea6659f97a4bcd1156c" ON "IncidentTemplateOnCallDutyPolicy" ("onCallDutyPolicyId") ` - ); - await queryRunner.query( - `CREATE TABLE "IncidentTemplateLabel" ("incidentTemplateId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_8c893e072c280f3ea36c40a4543" PRIMARY KEY ("incidentTemplateId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d527ebc73a91eefcae4beaaf82" ON "IncidentTemplateLabel" ("incidentTemplateId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fcf64673a74380a67159376b85" ON "IncidentTemplateLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorGroupLabel" ("monitorGroupId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_007b146dcc3053bcae3a480fc27" PRIMARY KEY ("monitorGroupId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_2fed2a41449af9a9cf821b759d" ON "MonitorGroupLabel" ("monitorGroupId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a91be5ccf47cbd470c3f9ee560" ON "MonitorGroupLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "MonitorSecretMonitor" ("monitorSecretId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_bcabf4d30297287277c049e312f" PRIMARY KEY ("monitorSecretId", "monitorId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fed65a7701822d21a66810bfe2" ON "MonitorSecretMonitor" ("monitorSecretId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_8ad10d0cd8fd68ac64ae3967dc" ON "MonitorSecretMonitor" ("monitorId") ` - ); - await queryRunner.query( - `CREATE TABLE "OnCallDutyPolicyScheduleLabel" ("onCallDutyPolicyScheduleId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_309b0a81c1dc85eb7adbf52b074" PRIMARY KEY ("onCallDutyPolicyScheduleId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_facb426dc0647d760bba573c2d" ON "OnCallDutyPolicyScheduleLabel" ("onCallDutyPolicyScheduleId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c27ee1fc2df7788145ed9e3333" ON "OnCallDutyPolicyScheduleLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "ProjectSsoTeam" ("projectSsoId" uuid NOT NULL, "teamId" uuid NOT NULL, CONSTRAINT "PK_0d5098a73b48af5e3bc6f04caf8" PRIMARY KEY ("projectSsoId", "teamId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_43c6fb265bd3b69e26f1d98b66" ON "ProjectSsoTeam" ("projectSsoId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d6d43b15c7dca2734a768379f8" ON "ProjectSsoTeam" ("teamId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageLabel" ("statusPageId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_511c59beeae2315d01f21848d34" PRIMARY KEY ("statusPageId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d409a46e63e25fbd4fcc9d5242" ON "StatusPageLabel" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_6842f6301436d26a3115406279" ON "StatusPageLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageDownMonitorStatus" ("statusPageId" uuid NOT NULL, "monitorStatusId" uuid NOT NULL, CONSTRAINT "PK_599e5298f4663a42124ef04819b" PRIMARY KEY ("statusPageId", "monitorStatusId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_5d5aa67b52755d47e81bb19feb" ON "StatusPageDownMonitorStatus" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e9f66cc920a6dfd8b20be8497c" ON "StatusPageDownMonitorStatus" ("monitorStatusId") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceMonitor" ("scheduledMaintenanceId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_c48a2ac3e9aa04d61c689416b92" PRIMARY KEY ("scheduledMaintenanceId", "monitorId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_9f74e5e95ad88301cbc6e97da6" ON "ScheduledMaintenanceMonitor" ("scheduledMaintenanceId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d1a5797fdd98c1fa2f99670aab" ON "ScheduledMaintenanceMonitor" ("monitorId") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceStatusPage" ("scheduledMaintenanceId" uuid NOT NULL, "statusPageId" uuid NOT NULL, CONSTRAINT "PK_1a8ec8af0d0f7d83627235f08f6" PRIMARY KEY ("scheduledMaintenanceId", "statusPageId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_fcab6c2c59de7fa9e1a4ed52d3" ON "ScheduledMaintenanceStatusPage" ("scheduledMaintenanceId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_266f1f927ed89d829a2349d2e2" ON "ScheduledMaintenanceStatusPage" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "ScheduledMaintenanceLabel" ("scheduledMaintenanceId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_abb6424e4ce1a915ad291c98e7f" PRIMARY KEY ("scheduledMaintenanceId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c88790ffdbb71aa66a5795be22" ON "ScheduledMaintenanceLabel" ("scheduledMaintenanceId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4f3c993200714127eb2d0851cc" ON "ScheduledMaintenanceLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "AnnouncementStatusPage" ("announcementId" uuid NOT NULL, "statusPageId" uuid NOT NULL, CONSTRAINT "PK_d0372c7b16adafd18b8591980a1" PRIMARY KEY ("announcementId", "statusPageId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f10f946bb8a5da2ef395557805" ON "AnnouncementStatusPage" ("announcementId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_d7895bb35944a68cccf8286521" ON "AnnouncementStatusPage" ("statusPageId") ` - ); - await queryRunner.query( - `CREATE TABLE "StatusPageSubscriberStatusPageResource" ("statusPageSubscriberId" uuid NOT NULL, "statusPageResourceId" uuid NOT NULL, CONSTRAINT "PK_1a244f904475ff50c824e0e565d" PRIMARY KEY ("statusPageSubscriberId", "statusPageResourceId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_618293911d2e52dc3c6a6873b4" ON "StatusPageSubscriberStatusPageResource" ("statusPageSubscriberId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f4af9c94a4b3ba11b4739a25ef" ON "StatusPageSubscriberStatusPageResource" ("statusPageResourceId") ` - ); - await queryRunner.query( - `CREATE TABLE "TeamPermissionLabel" ("teamPermissionId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_977590cfe880ea88b607aca0ed6" PRIMARY KEY ("teamPermissionId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7281dda2d397d6d75c2f5285bb" ON "TeamPermissionLabel" ("teamPermissionId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4523aa1dd9163aaf37698d137e" ON "TeamPermissionLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "ServiceLabel" ("telemetryServiceId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_69948538ec718918cb04dc06882" PRIMARY KEY ("telemetryServiceId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_94d495f938d819dab20480c5f8" ON "ServiceLabel" ("telemetryServiceId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7711266422ebad1188c4879d66" ON "ServiceLabel" ("labelId") ` - ); - await queryRunner.query( - `CREATE TABLE "WorkflowLabel" ("workflowId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_1c9a857f4e8cbba7c8ccbb911cd" PRIMARY KEY ("workflowId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3747a5f42be5c977e574abcd71" ON "WorkflowLabel" ("workflowId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_4e72fad380eca9abfa3b989554" ON "WorkflowLabel" ("labelId") ` - ); - await queryRunner.query( - `ALTER TABLE "User" ADD CONSTRAINT "FK_1f25f5fc0032f7014482d9c195e" FOREIGN KEY ("profilePictureId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "User" ADD CONSTRAINT "FK_644c3c0393979f57f71892ff0d7" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "AcmeCertificate" ADD CONSTRAINT "FK_130a8fd12e7505eebfce670b198" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "AcmeChallenge" ADD CONSTRAINT "FK_71371b224feb48f1d60e847cf1b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Reseller" ADD CONSTRAINT "FK_fe790bb94630d701a8ad93287ce" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Reseller" ADD CONSTRAINT "FK_952b3ed48545aaf18033150dc66" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ResellerPlan" ADD CONSTRAINT "FK_fc269bd109ac405a458b2acc678" FOREIGN KEY ("resellerId") REFERENCES "Reseller"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ResellerPlan" ADD CONSTRAINT "FK_34cdc5e0500513f321f0da35a64" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ResellerPlan" ADD CONSTRAINT "FK_e756416e4b0983e158f71c47c1a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Project" ADD CONSTRAINT "FK_639312a8ef82cbd5cee77c5b1ba" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Project" ADD CONSTRAINT "FK_43989dee7f7af742f6d8ec2664a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Project" ADD CONSTRAINT "FK_4ee6a519d48b26fe2a78fdc1c9c" FOREIGN KEY ("resellerId") REFERENCES "Reseller"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Project" ADD CONSTRAINT "FK_b5ee87614c184778810283c2991" FOREIGN KEY ("resellerPlanId") REFERENCES "ResellerPlan"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ApiKey" ADD CONSTRAINT "FK_bb1019f0078a21b4854f5cb3ed4" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ApiKey" ADD CONSTRAINT "FK_891c55549057af9a0c90c925ebb" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ApiKey" ADD CONSTRAINT "FK_bcbc7d80fb0cfe2cbb5ae7db791" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Label" ADD CONSTRAINT "FK_f10d59c2ba66e085722e0053cb7" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Label" ADD CONSTRAINT "FK_84520cbda97d2a9cb9da7ccb18c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Label" ADD CONSTRAINT "FK_f46caf81c5fd7664ba8da9c99ba" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" ADD CONSTRAINT "FK_0cf347c575f15d3836615f53258" FOREIGN KEY ("apiKeyId") REFERENCES "ApiKey"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" ADD CONSTRAINT "FK_fb09dd7fefa9d5d44b1907be5fd" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" ADD CONSTRAINT "FK_dc8eb846ffbceafbc9c60bbfaa5" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" ADD CONSTRAINT "FK_ac42ef4597147c260e89a0f3d3a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "BillingInvoice" ADD CONSTRAINT "FK_0ab13e9a92ce4801c37c2a0a77f" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "BillingInvoice" ADD CONSTRAINT "FK_15b8130f5378f2079ed5b2fe7d1" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "BillingInvoice" ADD CONSTRAINT "FK_0a0a1a9865d157e46b1ecf14873" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "BillingPaymentMethod" ADD CONSTRAINT "FK_db4bb9add01b7d8286869fd9a0a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "BillingPaymentMethod" ADD CONSTRAINT "FK_55c3c9a9fc28000262b811cebc8" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "BillingPaymentMethod" ADD CONSTRAINT "FK_93a1554cb316127896f66acddd3" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CallLog" ADD CONSTRAINT "FK_5648767682195afaeb09098a213" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CallLog" ADD CONSTRAINT "FK_3e510124d923fe3b994936a7cb5" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "DataMigrations" ADD CONSTRAINT "FK_1619179d46a4411e1bb4af5d342" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "DataMigrations" ADD CONSTRAINT "FK_183a8261590c30a27a1b51f4bdb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Domain" ADD CONSTRAINT "FK_f1ce90d3f9693be29b72fabe93b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Domain" ADD CONSTRAINT "FK_12e6ebc5c806263d562045e9282" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Domain" ADD CONSTRAINT "FK_9ace4c275b42c057b7581543ce3" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSMTPConfig" ADD CONSTRAINT "FK_a540dab929fa6582b93f258ffe5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSMTPConfig" ADD CONSTRAINT "FK_3b7ed2d3bd1a2ee9638cccef5b0" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSMTPConfig" ADD CONSTRAINT "FK_d5458705e98b89c08c0d960422e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "EmailLog" ADD CONSTRAINT "FK_7b72c5131b3dd1f3edf201a561c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "EmailLog" ADD CONSTRAINT "FK_046364c162885b6ac65d5dd367c" FOREIGN KEY ("projectSmtpConfigId") REFERENCES "ProjectSMTPConfig"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "EmailLog" ADD CONSTRAINT "FK_6d0739da601917d316494fcae3b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "EmailVerificationToken" ADD CONSTRAINT "FK_0b65c7ffb685f6ed78aac195b1a" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "EmailVerificationToken" ADD CONSTRAINT "FK_9e86ebfdbef16789e9571f22074" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "GreenlockCertificate" ADD CONSTRAINT "FK_895b9b802ed002a3804136bacf1" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "GreenlockChallenge" ADD CONSTRAINT "FK_7517f5a285255f031b0f6d9663c" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentSeverity" ADD CONSTRAINT "FK_00d2f503174bf201abc6e77afde" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentSeverity" ADD CONSTRAINT "FK_2677e0a9dbf97ba0f4a7849eac6" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentSeverity" ADD CONSTRAINT "FK_d0d87151a7872a44c3d2457bfdc" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentState" ADD CONSTRAINT "FK_3d279e530067f599f3186e3821d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentState" ADD CONSTRAINT "FK_eb33bd015e0e57ee96b60f8d773" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentState" ADD CONSTRAINT "FK_88a0ecd4b1714ac0e2eef9ac27d" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatus" ADD CONSTRAINT "FK_db1783158a23bd20dbebaae56ef" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatus" ADD CONSTRAINT "FK_bdda7fecdf44ed43ef2004e7be5" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatus" ADD CONSTRAINT "FK_55a0e488581a0d02bcdd67a4348" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Monitor" ADD CONSTRAINT "FK_996acfb590bda327843f78b7ad3" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Monitor" ADD CONSTRAINT "FK_a84bbba0dbad47918136d4dfb43" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Monitor" ADD CONSTRAINT "FK_73bdf22259019b90836aac86b28" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Monitor" ADD CONSTRAINT "FK_d3461ab640467c8c2100ea55c79" FOREIGN KEY ("currentMonitorStatusId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicy" ADD CONSTRAINT "FK_31508550a088ba2cc843a6c90c4" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicy" ADD CONSTRAINT "FK_c0c63ac58f97fd254bb5c2813dc" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicy" ADD CONSTRAINT "FK_0424b49cfcd68cdd1721df53acd" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Probe" ADD CONSTRAINT "FK_6bd931aae4920e296ea08864cd0" FOREIGN KEY ("iconFileId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Probe" ADD CONSTRAINT "FK_b357696dc9462ad3f9e84c6dc52" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Probe" ADD CONSTRAINT "FK_1963e116be9832b23490cca933f" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Probe" ADD CONSTRAINT "FK_272ece82a96099041b93c9141e3" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Incident" ADD CONSTRAINT "FK_eccbc31fa1f58bd051b6f7e108b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Incident" ADD CONSTRAINT "FK_9b101f023b5db6491203d5c9951" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Incident" ADD CONSTRAINT "FK_067855888a3d71803d3a5aeaecf" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Incident" ADD CONSTRAINT "FK_6592d4f7f3b260efc23fc9b4bc9" FOREIGN KEY ("currentIncidentStateId") REFERENCES "IncidentState"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Incident" ADD CONSTRAINT "FK_3f28fe3b32abed354a49b26c9cb" FOREIGN KEY ("incidentSeverityId") REFERENCES "IncidentSeverity"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Incident" ADD CONSTRAINT "FK_ed0ec4960a85240f51e6779a00a" FOREIGN KEY ("changeMonitorStatusToId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Incident" ADD CONSTRAINT "FK_7e537806a80e869917ca1d7e2e4" FOREIGN KEY ("createdByProbeId") REFERENCES "Probe"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentCustomField" ADD CONSTRAINT "FK_59e7f6a43dbc5ee54e1a1aaaaf1" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentCustomField" ADD CONSTRAINT "FK_5c1c7369e696f580186a4ff12de" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentCustomField" ADD CONSTRAINT "FK_bc64c76e766b1b880845afbcbf7" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentInternalNote" ADD CONSTRAINT "FK_ac48058b3e5f9e8361d2b8328c2" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentInternalNote" ADD CONSTRAINT "FK_b92e75645fd252e4c2f866047de" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentInternalNote" ADD CONSTRAINT "FK_c798e09321f06d8a180916d7a5e" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentInternalNote" ADD CONSTRAINT "FK_8f23b820cbbed6d96cfedd162a2" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentNoteTemplate" ADD CONSTRAINT "FK_21d5bc0d24b3e5032dd391ec8da" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentNoteTemplate" ADD CONSTRAINT "FK_515b6970fdd528d4c9f85a5e9a4" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentNoteTemplate" ADD CONSTRAINT "FK_3c00f2b005264318a274cd38a94" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Team" ADD CONSTRAINT "FK_baac847c494f692f03fd686e9c7" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Team" ADD CONSTRAINT "FK_4be4aa023ba1c6d6443b81b3b91" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Team" ADD CONSTRAINT "FK_0d4912bf03a7a645ce95142155b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_95f76375ccac835f815d7e926a2" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_389aadeb39a0806e80d4001016e" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_408324d3635a826538a792422f3" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_278f483fc81c21b1bd1311ee289" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_60242ecfcecaa5cb1c5241bed4c" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_0ee7ae6757442fba470b213015c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_6311087eeb14ab51e6a1e6133f7" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_6aa9a6b46f8e044d722da8f5a7f" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_52591665c92658ef82944d63d26" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_c473db8745d0ebeb147a72986cb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentPublicNote" ADD CONSTRAINT "FK_a9a77e5f286b5724f4e2280d0a1" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentPublicNote" ADD CONSTRAINT "FK_a6964d3aab71608daab9f20e304" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentPublicNote" ADD CONSTRAINT "FK_691a99e582fcddcc892d8573afc" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentPublicNote" ADD CONSTRAINT "FK_cf04d778a5502be606f63e01603" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_764daa366a4e195768a49e0ee39" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_fe2dff414a1f67260e3c5189811" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_16d198b59f3416a8ddc630a90d2" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_6b6b9dbf9ca5448c9297a58ad04" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_ff0fca6570d47798771763533a9" FOREIGN KEY ("incidentStateId") REFERENCES "IncidentState"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_b82dafef226b0fae1ad6cb18570" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_b03e46665e4c075ed1398fcc409" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_0e6a4e065ffb22f95ecfc259e9a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_ceff6a4dfdccecc4aa40dbfe91a" FOREIGN KEY ("incidentSeverityId") REFERENCES "IncidentSeverity"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_6d7627ab9d5172c66fc50192163" FOREIGN KEY ("changeMonitorStatusToId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_cf172eb6797a64ee3750e3f3e21" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_e8090c6569c3a5dbd7ef7485c99" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_a895b946fccb109dedd55b85f6d" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_3e8a4bd1594da3438d8fb8a6687" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_af037dc245d77c282061fea1b1b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_da9dd65b4401b954a0ea2b5c8d5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_36b9b0204f4b17063483cb7308e" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_876dd05e0dfc64219ef5df241c3" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_a53f8aab99766a87c73c52b9037" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_026e918a31de467eeb8e30ae8d1" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorCustomField" ADD CONSTRAINT "FK_1c6b61e904d8e3fec1ae719b9ef" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorCustomField" ADD CONSTRAINT "FK_93a4da4182f93ba24ab958c1b73" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorCustomField" ADD CONSTRAINT "FK_817e69522c8d2f1e2fd3f857e91" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroup" ADD CONSTRAINT "FK_13905ff40843b11145f21e33ff5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroup" ADD CONSTRAINT "FK_abaf236c1877143fe160991cc45" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroup" ADD CONSTRAINT "FK_edd658b85b2ef7ac9b2f0687d8a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_0d7052620f268d0fa17f948a851" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_8f19947114087883cea771e1cb4" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_618bcd9015c257b0727df36fd11" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_7ce36c144e83082213587e19c23" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_fdbe93e29e60763a306358cab55" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_d5cfbebf8b07405652f5382e157" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_4b25f4a18bec8cb177e8d65c5f9" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_6a4095ee3d04454071816a5bad8" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_9267db147738caed0ccfdc3af22" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_e9bced91dce29928ebeec834905" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_825791d5edb2403d7937f16ed91" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_cf3dcaa746835ae36615a39d862" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_50b373c428cfd4566cc5caf98e6" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_cf595b683e26e560526404663fe" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_1a54eaa2d0187d10de84107a09b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_6a6213072d8637e6e625bc78929" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_63e5bbac01d1f68c7b08f126cd4" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_1d5265d1f3ca2f8b8e461e4998c" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_58610249ec4cf593e36210dcb84" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_7ebfe3ddcf597fb73ee8eac2ff4" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_12a3497e1404fcdbc4e8963a58e" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_324f1d50d0427bbd0e3308c4592" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_a65ce9b11b2d7bde123aa7633fd" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_e2cf60b88171dfe5fdd0e4fe6c1" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_e1ae2c698e6bde0a98c50162235" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_d887935f224b896ce64872c37c7" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_2d50a0e0e624369e7f90a62e8dc" FOREIGN KEY ("probeId") REFERENCES "Probe"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_a540272f483ef1de68f7e647486" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_4399ab64a5c00d55e5ce254deeb" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_a182ba062c0a216395d0dbdbdee" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecret" ADD CONSTRAINT "FK_7c3629c5ae14e97bede3bc548e5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecret" ADD CONSTRAINT "FK_a886cd3bbdfd84d01167f92cb65" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecret" ADD CONSTRAINT "FK_e4262f178662aaacdb54d4c4f4e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_80213eb3f228f1e3d423f5127e2" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_c08a7c1ef8d511b335a991aac47" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_d7f555ef162fe878e4ed62a3e23" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_d293a7e96c5bf427072514f21a9" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_574feb4161c5216c2c7ee0faaf8" FOREIGN KEY ("monitorStatusId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyCustomField" ADD CONSTRAINT "FK_e791474e098f276eda27704b479" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyCustomField" ADD CONSTRAINT "FK_456bff32fd0428134ef7396385f" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyCustomField" ADD CONSTRAINT "FK_43230e739b31e3f56284407b586" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRule" ADD CONSTRAINT "FK_d45a545669dc46da25cc60d1df5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRule" ADD CONSTRAINT "FK_b90c1cda36981c41e3965a93800" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRule" ADD CONSTRAINT "FK_ad8097a9359965d02ccbb16358b" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRule" ADD CONSTRAINT "FK_5c0911d261a941b00d41b6e5fda" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicySchedule" ADD CONSTRAINT "FK_0e1b7c3c3e8305a10716cdb8d6e" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicySchedule" ADD CONSTRAINT "FK_ecb5141b27e85674c294a2541b3" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicySchedule" ADD CONSTRAINT "FK_01e63400072d0bc6debee836cbf" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_efa24aa8feb92d4e15a707c20ec" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_651a4ab9e3cbb20f6b62a87a6b8" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_13a87c38fe2efa41940783af690" FOREIGN KEY ("onCallDutyPolicyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_089081f83ef22cdba5a0903ce59" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_90700af75cbe8129db898ac8adb" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_878e14be4e6366ec646f874347a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_c8aff8439fbfb07e7388aa9011b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_f5121f361345d858ce740a55a25" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_26ba1a6edd877647cedd1eae8ca" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_0e7d4060e2fabe0957b9fedb429" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_da2e065de293a14b69964fb3233" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_73ae2b2702aef4601c39d4d909a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_b15552e664640f67346193598a1" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_9ca3fbb66842324aa987d4c9722" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_bff6f4ae726b5c5cbae10e7d743" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_5a7b7a746409a175423a1bbd5c4" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_f9a45cea88022a9cf5b96c13e65" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_d35f668f524cc88f580a7651fe2" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_00e9676d39eeb807de704430512" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_e4bb332263960531a4c9e2d4254" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_a230899d6c2b16621954c46fb16" FOREIGN KEY ("triggeredByIncidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_16b426d34ff2c5cbd6ecfd70820" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_e9302e15399b67938e0121a0545" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_63f6618df216b74b72e62491b09" FOREIGN KEY ("acknowledgedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_3015ddfeb130417c55489da807e" FOREIGN KEY ("acknowledgedByTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_d4a0ffc5e9e698bb2612ba0e55f" FOREIGN KEY ("lastExecutedEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_591b7ed73c964e105bfecc6fd6d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_c5a798ca667fedda71d4ed54651" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_90119ec7f77fa2efd82261e0448" FOREIGN KEY ("triggeredByIncidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_872c2f6a9739bab1b57b6d51eac" FOREIGN KEY ("onCallDutyPolicyExecutionLogId") REFERENCES "OnCallDutyPolicyExecutionLog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_0ad4222a4c48b8a64e3a58b3519" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_0ae6ea2e2d38fd31543768e3609" FOREIGN KEY ("alertSentToUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_039098d4af133bd9c2b90978ef4" FOREIGN KEY ("userBelongsToTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_65998388ab4266dda712502ad65" FOREIGN KEY ("onCallDutyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_43f833a79cf4201b3fa1deed023" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_166f3696b3c70989507dd7e1f2e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ADD CONSTRAINT "FK_3b892ef36671f1ea1c8457a96d6" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ADD CONSTRAINT "FK_6fa6574a45cf1352c5a3b962512" FOREIGN KEY ("onCallDutyPolicyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ADD CONSTRAINT "FK_1db1083a896b0f77a0e87f26463" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ADD CONSTRAINT "FK_f22b52355207d2c0d5a13c168e8" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_08afccd6cbbd1a7015d4fe25e39" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_9b36bba6d9898331920805a29ca" FOREIGN KEY ("onCallDutyPolicyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_41f4ecc29351e1a406e83b30a93" FOREIGN KEY ("onCallDutyPolicyScheduleLayerId") REFERENCES "OnCallDutyPolicyScheduleLayer"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_b2ccbfcc3964caf3dfd89243f8f" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_49e5a41e1d771fe9e385295bd9a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_416e830c88f2ecfa149f4cd51c8" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ProjectCallSMSConfig" ADD CONSTRAINT "FK_20334b9571a6cd1a871e70d8e76" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ProjectCallSMSConfig" ADD CONSTRAINT "FK_e873aa20a371bd92e220332a992" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ProjectCallSMSConfig" ADD CONSTRAINT "FK_f5bc0e2b81886b21004e2a5f67b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSSO" ADD CONSTRAINT "FK_be9e6751765501ea1db126fcb23" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSSO" ADD CONSTRAINT "FK_28011315533e2d819295d261ee4" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSSO" ADD CONSTRAINT "FK_00ea9e456217ffbfff35f1e944f" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_90e44f45272c0da256951183086" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_3169f7934171e8f697bb993b010" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_6dcdf97c0834dd44b4f2c93e664" FOREIGN KEY ("resellerId") REFERENCES "Reseller"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_7aecf4b1ae3e45647cb911f4c10" FOREIGN KEY ("resellerPlanId") REFERENCES "ResellerPlan"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_a7b405e2a9ae144be016bcf973d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceState" ADD CONSTRAINT "FK_57a31fb2a5e4caa223d2506a4e1" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceState" ADD CONSTRAINT "FK_88044fd50006f1897e8c760d136" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceState" ADD CONSTRAINT "FK_4f803428e0926584d1f7c44a3d4" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_34a4c35e0d7afe6f023825a68c7" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_da54bb2c4997ee1a3b73026d7f5" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_71f429afb7678d132472b3c87b0" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_1f17f293352ebdc3bcf383158cc" FOREIGN KEY ("faviconFileId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_84df83d1f492a19a08aee465500" FOREIGN KEY ("logoFileId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_602b456a61d73a96e97f483064d" FOREIGN KEY ("coverImageFileId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_61944d851b4a7213d79ef281744" FOREIGN KEY ("smtpConfigId") REFERENCES "ProjectSMTPConfig"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_4e347d3f99b67dacd149beaf61d" FOREIGN KEY ("callSmsConfigId") REFERENCES "ProjectCallSMSConfig"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_4059dd569d6a482062352bf266a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_5442fd86c96d45e062d5ee1f093" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_50ddf8bb21e988ea5d419a66cb9" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_883038abda021ce79fa838d0273" FOREIGN KEY ("currentScheduledMaintenanceStateId") REFERENCES "ScheduledMaintenanceState"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_fab9cc7e6ffcf02872fccfab978" FOREIGN KEY ("changeMonitorStatusToId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceCustomField" ADD CONSTRAINT "FK_3621c488327e1c00518aa4e8816" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceCustomField" ADD CONSTRAINT "FK_c7cdb245d3d98be14482f092eca" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceCustomField" ADD CONSTRAINT "FK_9094eed77fb6e8f7ecf1502f5e0" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceInternalNote" ADD CONSTRAINT "FK_d79c49a0a613a6b432fd400e69b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceInternalNote" ADD CONSTRAINT "FK_a53ef45aebd4a6a6e7dde7f896a" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceInternalNote" ADD CONSTRAINT "FK_69757967d2ee696f487fb8ac37e" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceInternalNote" ADD CONSTRAINT "FK_7fb00788b6ac97988dd43e2e1b2" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceNoteTemplate" ADD CONSTRAINT "FK_b4cb4c1312eb72459907e1bbe9b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceNoteTemplate" ADD CONSTRAINT "FK_e38c1102001ae0b70c22e046424" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceNoteTemplate" ADD CONSTRAINT "FK_4c3d6b87bb1e8739cdeb8b92f74" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_e3053b1725658b4a120b4e3185d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_1206beb611e0779ce2248ecbaeb" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_1251fb7d4a4bf8586f2bd1528eb" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_cc0e8ca9e9065ca0cc24bf6275b" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_52a3a932530026bafef87e62177" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_518742c7037d9a38a6594dc18a6" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_be6a25806925f93b8949e61929b" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_967c33f7bce5de522c1d1a80e7b" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_c91d4d420e3faaf15fa928fd214" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_6e6b087ba99fe433f83f87e0a35" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenancePublicNote" ADD CONSTRAINT "FK_73c6737ab4a7718c45932bffada" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenancePublicNote" ADD CONSTRAINT "FK_937aabd4adbfce78663406a2487" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenancePublicNote" ADD CONSTRAINT "FK_1f67cfb63bd3488b7c5c5b7fac7" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenancePublicNote" ADD CONSTRAINT "FK_28e179283c409e0751aae713949" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_4faf556988f5b6a755ef2e85ae8" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_411146017bcfe98bbe028b8d15a" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_2392299477cfc4f612ecb73e839" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_aa84fcdf2fef6c2005ebab2c197" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_7c0f750d3a964180d1e1efa16ea" FOREIGN KEY ("scheduledMaintenanceStateId") REFERENCES "ScheduledMaintenanceState"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ShortLink" ADD CONSTRAINT "FK_11f179cd8e9beee22b89c316972" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "SmsLog" ADD CONSTRAINT "FK_a30bbda7f5480e498ebc609663b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "SmsLog" ADD CONSTRAINT "FK_d00778bcfaa735fbb5dc91c1945" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageAnnouncement" ADD CONSTRAINT "FK_5e9c5a7393ac9aa477d625de673" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageAnnouncement" ADD CONSTRAINT "FK_1491bd0895d515969eee2a08c80" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageAnnouncement" ADD CONSTRAINT "FK_7251cbbaa75eb9570830b0cab32" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageCustomField" ADD CONSTRAINT "FK_4f5ae90bc48a0ddeb50cd009aaf" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageCustomField" ADD CONSTRAINT "FK_e0abd7540f860de19607dc25bc0" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageCustomField" ADD CONSTRAINT "FK_26b4a892f3b31c5b0b285c4e5cb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_40cca185c8cf933c04a0534676b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_7fab5bc54a8f36eac8f31c82565" FOREIGN KEY ("domainId") REFERENCES "Domain"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_842a66fcb103388fcedffef75f7" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_106e359f945432d6583bd30ff4b" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_6c82107f63d1a3186d579a6d9cb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageFooterLink" ADD CONSTRAINT "FK_5f8f65447c9b881860bf742dc98" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageFooterLink" ADD CONSTRAINT "FK_1a80c698b2205074f53376d631b" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageFooterLink" ADD CONSTRAINT "FK_bd6f15ab951095e624ea664d9a6" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageFooterLink" ADD CONSTRAINT "FK_0328201140b59b4b813f83b06a9" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageGroup" ADD CONSTRAINT "FK_4a96c34f030a6e39218352a947a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageGroup" ADD CONSTRAINT "FK_5dbcfd7d38e5ea7a78a6a78a330" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageGroup" ADD CONSTRAINT "FK_0a63a8ee804658921edf1e870af" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageGroup" ADD CONSTRAINT "FK_61191c9c00f7279615e13af4bbd" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHeaderLink" ADD CONSTRAINT "FK_70d70692cbe9d9be188723df4f0" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHeaderLink" ADD CONSTRAINT "FK_99c017b6ced8da63abdfbb506eb" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHeaderLink" ADD CONSTRAINT "FK_88048566089097605e26fdb2893" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHeaderLink" ADD CONSTRAINT "FK_0d3a63f1c684e78297b213c348e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHistoryChartBarColorRule" ADD CONSTRAINT "FK_a65f8fabf888d227d64570f52b3" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHistoryChartBarColorRule" ADD CONSTRAINT "FK_54edc7ff7a74a0310a512c53895" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHistoryChartBarColorRule" ADD CONSTRAINT "FK_5d973aa991ba9f06b642d3fc9d7" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHistoryChartBarColorRule" ADD CONSTRAINT "FK_8041d41239c4218bf136bf20591" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_9fac66064d88c514d2e5503237a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_f60296efefe379693bfd55a7760" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_6a98e42d8df3ba84bd0f79da550" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_7c1168daf53c46678045ff39d31" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_e992fcc346afa21a89ba9f75f25" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_c28e05c08656f8aa756734c37c5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_3c59f811e0660c5522e45e85b6a" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_69b1659abafe656563259784d02" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_4ecb38fa1941bb0961641803f21" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_8d7351e844adfd5c279fd8e9f3b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPagePrivateUser" ADD CONSTRAINT "FK_1ce45fe77324ede75166d0f57dd" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPagePrivateUser" ADD CONSTRAINT "FK_0589c51161d13b752fed41a3193" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPagePrivateUser" ADD CONSTRAINT "FK_e47c85ead36095d040493775a3f" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPagePrivateUser" ADD CONSTRAINT "FK_524d2e71f90ef8f78d85d5fdfd1" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_8fba35fb87a0ad6037eb8fc8040" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_ade3f7acf902dcb313d230ca1f5" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_b1c4fe08e1d90ae4d26d934653c" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_a55bb812676bff276cef1f14c86" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_9b61276c47d6091295dec9e5350" FOREIGN KEY ("statusPageGroupId") REFERENCES "StatusPageGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_51e0fbc6d460394b1cd38959790" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_d2b2f7ffe8f976fda20f4b96c5b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSSO" ADD CONSTRAINT "FK_1d63fa142dd4175ef256f21d2a6" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSSO" ADD CONSTRAINT "FK_dc05de7939af3ada1567fc7106b" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSSO" ADD CONSTRAINT "FK_8e2cbcf07eba956fe976ca3d043" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSSO" ADD CONSTRAINT "FK_0bfc26bce8ea92b8b8a9e0400de" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriber" ADD CONSTRAINT "FK_6adf943966e01699e86117d2e34" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriber" ADD CONSTRAINT "FK_217b295d5882faa6cf3418ed811" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriber" ADD CONSTRAINT "FK_61cecfd27c2d41eb58330df1d8c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriber" ADD CONSTRAINT "FK_35ad85d2f341ebfeaca7ad67af1" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_3cc297d538f01065f9925cfb11a" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_fd952f76f5a5297ce69a9a588eb" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_4ab0af827040dbce6ef21ec7780" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_a9e764a6ad587e6e386abe3b9de" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_945ca87238e7465782215c25d8d" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermission" ADD CONSTRAINT "FK_78293e9cc1746e5f29ccccfdfc0" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermission" ADD CONSTRAINT "FK_5064c0bdc8ff238952f9a2acf43" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermission" ADD CONSTRAINT "FK_e2c33d5f98cb42f8c1f76a85095" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermission" ADD CONSTRAINT "FK_73a2d0db1de4e66582e376098de" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryService" ADD CONSTRAINT "FK_3a3321fd538aa014aa5e4f35220" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryService" ADD CONSTRAINT "FK_5d0b92dc9ab2bfd71432e9a3536" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryService" ADD CONSTRAINT "FK_46ea9e637b4454993665a436d56" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryUsageBilling" ADD CONSTRAINT "FK_5670be95a9496b9380b7c0d7935" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryUsageBilling" ADD CONSTRAINT "FK_91333210492e5d2f334231468a7" FOREIGN KEY ("telemetryServiceId") REFERENCES "TelemetryService"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryUsageBilling" ADD CONSTRAINT "FK_d71562eb0c2861797502bd99917" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryUsageBilling" ADD CONSTRAINT "FK_510252373d4e5917029308384fb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserCall" ADD CONSTRAINT "FK_db4e522c086be556e5101c4e910" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserCall" ADD CONSTRAINT "FK_4d90bd0e309ad43c4541bb428e9" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserCall" ADD CONSTRAINT "FK_1b46d8793ef542c059369481d42" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserCall" ADD CONSTRAINT "FK_996ab46825df7f3512e735c450c" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserEmail" ADD CONSTRAINT "FK_e6beed22b36201aea1d70ba0d72" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserEmail" ADD CONSTRAINT "FK_1f713c701d85c69f706e4e82b85" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserEmail" ADD CONSTRAINT "FK_06413c119aae9c3f75154c2346c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserEmail" ADD CONSTRAINT "FK_a1aa5e10dcfb571521324bbd665" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserSMS" ADD CONSTRAINT "FK_6439689a29a2192708e3f3603da" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserSMS" ADD CONSTRAINT "FK_3cb16b5c2d69dbdc812247788f8" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserSMS" ADD CONSTRAINT "FK_99fc3cdf366fd3d266fbf2d657c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserSMS" ADD CONSTRAINT "FK_0bae98162ec44540ff85f724daa" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_69d439e9f60f05ae16732c49999" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_32efb91af1b432a25ceb55bc0dc" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_85b73b64802058915df58fa013b" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_b1292f2480d0c4985898d7bf33a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_ec8e7a39fcd38d0ea2d40b8afaf" FOREIGN KEY ("userCallId") REFERENCES "UserCall"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_32ca4397660dafe0cab7d03e5b1" FOREIGN KEY ("userSmsId") REFERENCES "UserSMS"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_b407063ee43233b0cc8e9106cbb" FOREIGN KEY ("userEmailId") REFERENCES "UserEmail"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_bf703b216d7f59424302dc5d70b" FOREIGN KEY ("incidentSeverityId") REFERENCES "IncidentSeverity"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationSetting" ADD CONSTRAINT "FK_6f110fc752889f922d6a3c57a55" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationSetting" ADD CONSTRAINT "FK_e2565b0aa7d7e015fb6685afede" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationSetting" ADD CONSTRAINT "FK_aee7abeffd1c60d49f710fb3749" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationSetting" ADD CONSTRAINT "FK_b023f12dc00bcfc50d6d9ad4f71" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_7941aa92c7c740400b272d69072" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_3a4b15ce8357c6735ac1b4ae606" FOREIGN KEY ("userBelongsToTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_ea993eab5c402623b61203e3256" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_4cd37d481ef366d975c6a7cd9bf" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_f047dd6a708175bae0ee6f8c4bf" FOREIGN KEY ("onCallDutyPolicyExecutionLogId") REFERENCES "OnCallDutyPolicyExecutionLog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_550f9f6177451d4902467991a15" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_eeb0dd05d1dec542c3de5fb5074" FOREIGN KEY ("triggeredByIncidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_7498ad42c4f77f7fab2a6bc2e33" FOREIGN KEY ("onCallDutyPolicyExecutionLogTimelineId") REFERENCES "OnCallDutyPolicyExecutionLogTimeline"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_91488d7d3341bf1113902f4786c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_ac31bad932e24418ce0bb1bbb14" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_b70804c755c008c15794b6cc18d" FOREIGN KEY ("acknowledgedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_123b1825525b963fe9555d62641" FOREIGN KEY ("onCallDutyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_2a893e347fdab643867abd8dda7" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_79580470f34858375cae5d353a9" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_dc79ea74fba7b99835bd475081c" FOREIGN KEY ("userNotificationLogId") REFERENCES "UserOnCallLog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_06a427cdcbae1ddcb1301b860f2" FOREIGN KEY ("userNotificationRuleId") REFERENCES "UserNotificationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_ef993920a9967b61fb3fb9bf162" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_58a44736718a5ec4fe41526289a" FOREIGN KEY ("triggeredByIncidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_6491952f9d8066aa5cfff92cd85" FOREIGN KEY ("onCallDutyPolicyExecutionLogId") REFERENCES "OnCallDutyPolicyExecutionLog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_26e16ed3e46c6a6589a88d3abba" FOREIGN KEY ("onCallDutyPolicyExecutionLogTimelineId") REFERENCES "OnCallDutyPolicyExecutionLogTimeline"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_222212e7157f8816357a4f02536" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_6650020ce6e235164a09d1ca019" FOREIGN KEY ("userBelongsToTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_03d67a4d7fa9f087327ab0f74a7" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_a93a41d65df4cbe518393695084" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_d5c3df01bbb2a9ce168b36b5234" FOREIGN KEY ("userCallId") REFERENCES "UserCall"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_12ef8407b6359205df8339f8494" FOREIGN KEY ("userSmsId") REFERENCES "UserSMS"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_815c728d905c44bc440ec91308b" FOREIGN KEY ("userEmailId") REFERENCES "UserEmail"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Workflow" ADD CONSTRAINT "FK_a87518833c744b2df4324b61a6d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Workflow" ADD CONSTRAINT "FK_13c42a014f8c10862f23d02eb49" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "Workflow" ADD CONSTRAINT "FK_367e2e759f520b31d727d22b803" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowLog" ADD CONSTRAINT "FK_6c3dc31b09a96d81982a472e22b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowLog" ADD CONSTRAINT "FK_55293b16d84f048f44c771595bb" FOREIGN KEY ("workflowId") REFERENCES "Workflow"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowLog" ADD CONSTRAINT "FK_a4e2e2861f3ece2b7d6d5e399e2" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowVariable" ADD CONSTRAINT "FK_bb47f6d0cabd55fde5b199ae206" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowVariable" ADD CONSTRAINT "FK_c093a47a2ecfa1d5f2d4aeb04a0" FOREIGN KEY ("workflowId") REFERENCES "Workflow"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowVariable" ADD CONSTRAINT "FK_92fbc4d230accb3d12c098ca4d2" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowVariable" ADD CONSTRAINT "FK_3e414e10cb4927e233ffd32651c" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermissionLabel" ADD CONSTRAINT "FK_0946f6b41113cd842ee69f69fb1" FOREIGN KEY ("apiKeyPermissionId") REFERENCES "ApiKeyPermission"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermissionLabel" ADD CONSTRAINT "FK_4db7f66405df73287c639ece904" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "MonitorLabel" ADD CONSTRAINT "FK_3af64cebb5a7cc7f1ad0aa70c11" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "MonitorLabel" ADD CONSTRAINT "FK_1866fb21ea1acd3d5c37e28ca1b" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyLabel" ADD CONSTRAINT "FK_d4a3f7dc4e33b896b0984e73164" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyLabel" ADD CONSTRAINT "FK_1c0248fe6856bbe029fc492ec71" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentMonitor" ADD CONSTRAINT "FK_55e3162c1259b1b092f0ac63eeb" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentMonitor" ADD CONSTRAINT "FK_9e2000a938f2e12c6653e68780c" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOnCallDutyPolicy" ADD CONSTRAINT "FK_2d127b6da0e4fab9f905b4d332d" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOnCallDutyPolicy" ADD CONSTRAINT "FK_f89b23e3cafd1c6a0bfd42c297d" FOREIGN KEY ("monitorId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentLabel" ADD CONSTRAINT "FK_1084d1ddbabbcfcab7cd9d547a4" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentLabel" ADD CONSTRAINT "FK_17f4085273d14d4d6145cf65855" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateMonitor" ADD CONSTRAINT "FK_390a45e855282ae55ad56d1e1fc" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateMonitor" ADD CONSTRAINT "FK_33fb90ba0116b7db4efd4ec7a45" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOnCallDutyPolicy" ADD CONSTRAINT "FK_99245f38769689fe8a172dcb81a" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOnCallDutyPolicy" ADD CONSTRAINT "FK_2b76b57ea6659f97a4bcd1156c1" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateLabel" ADD CONSTRAINT "FK_d527ebc73a91eefcae4beaaf822" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateLabel" ADD CONSTRAINT "FK_fcf64673a74380a67159376b85f" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupLabel" ADD CONSTRAINT "FK_2fed2a41449af9a9cf821b759de" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupLabel" ADD CONSTRAINT "FK_a91be5ccf47cbd470c3f9ee5606" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecretMonitor" ADD CONSTRAINT "FK_fed65a7701822d21a66810bfe29" FOREIGN KEY ("monitorSecretId") REFERENCES "MonitorSecret"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecretMonitor" ADD CONSTRAINT "FK_8ad10d0cd8fd68ac64ae3967dc9" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLabel" ADD CONSTRAINT "FK_facb426dc0647d760bba573c2dd" FOREIGN KEY ("onCallDutyPolicyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLabel" ADD CONSTRAINT "FK_c27ee1fc2df7788145ed9e3333c" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSsoTeam" ADD CONSTRAINT "FK_43c6fb265bd3b69e26f1d98b66c" FOREIGN KEY ("projectSsoId") REFERENCES "ProjectSSO"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSsoTeam" ADD CONSTRAINT "FK_d6d43b15c7dca2734a768379f8c" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageLabel" ADD CONSTRAINT "FK_d409a46e63e25fbd4fcc9d5242f" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageLabel" ADD CONSTRAINT "FK_6842f6301436d26a31154062793" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDownMonitorStatus" ADD CONSTRAINT "FK_5d5aa67b52755d47e81bb19febf" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDownMonitorStatus" ADD CONSTRAINT "FK_e9f66cc920a6dfd8b20be8497cf" FOREIGN KEY ("monitorStatusId") REFERENCES "MonitorStatus"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceMonitor" ADD CONSTRAINT "FK_9f74e5e95ad88301cbc6e97da6d" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceMonitor" ADD CONSTRAINT "FK_d1a5797fdd98c1fa2f99670aab8" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStatusPage" ADD CONSTRAINT "FK_fcab6c2c59de7fa9e1a4ed52d37" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStatusPage" ADD CONSTRAINT "FK_266f1f927ed89d829a2349d2e20" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceLabel" ADD CONSTRAINT "FK_c88790ffdbb71aa66a5795be229" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceLabel" ADD CONSTRAINT "FK_4f3c993200714127eb2d0851cc4" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "AnnouncementStatusPage" ADD CONSTRAINT "FK_f10f946bb8a5da2ef3955578053" FOREIGN KEY ("announcementId") REFERENCES "StatusPageAnnouncement"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "AnnouncementStatusPage" ADD CONSTRAINT "FK_d7895bb35944a68cccf8286521d" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriberStatusPageResource" ADD CONSTRAINT "FK_618293911d2e52dc3c6a6873b4c" FOREIGN KEY ("statusPageSubscriberId") REFERENCES "StatusPageSubscriber"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriberStatusPageResource" ADD CONSTRAINT "FK_f4af9c94a4b3ba11b4739a25ef4" FOREIGN KEY ("statusPageResourceId") REFERENCES "StatusPageResource"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermissionLabel" ADD CONSTRAINT "FK_7281dda2d397d6d75c2f5285bb8" FOREIGN KEY ("teamPermissionId") REFERENCES "TeamPermission"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermissionLabel" ADD CONSTRAINT "FK_4523aa1dd9163aaf37698d137e1" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ServiceLabel" ADD CONSTRAINT "FK_94d495f938d819dab20480c5f80" FOREIGN KEY ("telemetryServiceId") REFERENCES "TelemetryService"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ServiceLabel" ADD CONSTRAINT "FK_7711266422ebad1188c4879d669" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowLabel" ADD CONSTRAINT "FK_3747a5f42be5c977e574abcd713" FOREIGN KEY ("workflowId") REFERENCES "Workflow"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowLabel" ADD CONSTRAINT "FK_4e72fad380eca9abfa3b9895546" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); + if (fileTable) { + return; } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "WorkflowLabel" DROP CONSTRAINT "FK_4e72fad380eca9abfa3b9895546"` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowLabel" DROP CONSTRAINT "FK_3747a5f42be5c977e574abcd713"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceLabel" DROP CONSTRAINT "FK_7711266422ebad1188c4879d669"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceLabel" DROP CONSTRAINT "FK_94d495f938d819dab20480c5f80"` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermissionLabel" DROP CONSTRAINT "FK_4523aa1dd9163aaf37698d137e1"` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermissionLabel" DROP CONSTRAINT "FK_7281dda2d397d6d75c2f5285bb8"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriberStatusPageResource" DROP CONSTRAINT "FK_f4af9c94a4b3ba11b4739a25ef4"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriberStatusPageResource" DROP CONSTRAINT "FK_618293911d2e52dc3c6a6873b4c"` - ); - await queryRunner.query( - `ALTER TABLE "AnnouncementStatusPage" DROP CONSTRAINT "FK_d7895bb35944a68cccf8286521d"` - ); - await queryRunner.query( - `ALTER TABLE "AnnouncementStatusPage" DROP CONSTRAINT "FK_f10f946bb8a5da2ef3955578053"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceLabel" DROP CONSTRAINT "FK_4f3c993200714127eb2d0851cc4"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceLabel" DROP CONSTRAINT "FK_c88790ffdbb71aa66a5795be229"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStatusPage" DROP CONSTRAINT "FK_266f1f927ed89d829a2349d2e20"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStatusPage" DROP CONSTRAINT "FK_fcab6c2c59de7fa9e1a4ed52d37"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceMonitor" DROP CONSTRAINT "FK_d1a5797fdd98c1fa2f99670aab8"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceMonitor" DROP CONSTRAINT "FK_9f74e5e95ad88301cbc6e97da6d"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDownMonitorStatus" DROP CONSTRAINT "FK_e9f66cc920a6dfd8b20be8497cf"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDownMonitorStatus" DROP CONSTRAINT "FK_5d5aa67b52755d47e81bb19febf"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageLabel" DROP CONSTRAINT "FK_6842f6301436d26a31154062793"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageLabel" DROP CONSTRAINT "FK_d409a46e63e25fbd4fcc9d5242f"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSsoTeam" DROP CONSTRAINT "FK_d6d43b15c7dca2734a768379f8c"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSsoTeam" DROP CONSTRAINT "FK_43c6fb265bd3b69e26f1d98b66c"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLabel" DROP CONSTRAINT "FK_c27ee1fc2df7788145ed9e3333c"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLabel" DROP CONSTRAINT "FK_facb426dc0647d760bba573c2dd"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecretMonitor" DROP CONSTRAINT "FK_8ad10d0cd8fd68ac64ae3967dc9"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecretMonitor" DROP CONSTRAINT "FK_fed65a7701822d21a66810bfe29"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupLabel" DROP CONSTRAINT "FK_a91be5ccf47cbd470c3f9ee5606"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupLabel" DROP CONSTRAINT "FK_2fed2a41449af9a9cf821b759de"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateLabel" DROP CONSTRAINT "FK_fcf64673a74380a67159376b85f"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateLabel" DROP CONSTRAINT "FK_d527ebc73a91eefcae4beaaf822"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOnCallDutyPolicy" DROP CONSTRAINT "FK_2b76b57ea6659f97a4bcd1156c1"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOnCallDutyPolicy" DROP CONSTRAINT "FK_99245f38769689fe8a172dcb81a"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateMonitor" DROP CONSTRAINT "FK_33fb90ba0116b7db4efd4ec7a45"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateMonitor" DROP CONSTRAINT "FK_390a45e855282ae55ad56d1e1fc"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentLabel" DROP CONSTRAINT "FK_17f4085273d14d4d6145cf65855"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentLabel" DROP CONSTRAINT "FK_1084d1ddbabbcfcab7cd9d547a4"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOnCallDutyPolicy" DROP CONSTRAINT "FK_f89b23e3cafd1c6a0bfd42c297d"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOnCallDutyPolicy" DROP CONSTRAINT "FK_2d127b6da0e4fab9f905b4d332d"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentMonitor" DROP CONSTRAINT "FK_9e2000a938f2e12c6653e68780c"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentMonitor" DROP CONSTRAINT "FK_55e3162c1259b1b092f0ac63eeb"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyLabel" DROP CONSTRAINT "FK_1c0248fe6856bbe029fc492ec71"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyLabel" DROP CONSTRAINT "FK_d4a3f7dc4e33b896b0984e73164"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorLabel" DROP CONSTRAINT "FK_1866fb21ea1acd3d5c37e28ca1b"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorLabel" DROP CONSTRAINT "FK_3af64cebb5a7cc7f1ad0aa70c11"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermissionLabel" DROP CONSTRAINT "FK_4db7f66405df73287c639ece904"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermissionLabel" DROP CONSTRAINT "FK_0946f6b41113cd842ee69f69fb1"` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowVariable" DROP CONSTRAINT "FK_3e414e10cb4927e233ffd32651c"` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowVariable" DROP CONSTRAINT "FK_92fbc4d230accb3d12c098ca4d2"` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowVariable" DROP CONSTRAINT "FK_c093a47a2ecfa1d5f2d4aeb04a0"` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowVariable" DROP CONSTRAINT "FK_bb47f6d0cabd55fde5b199ae206"` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowLog" DROP CONSTRAINT "FK_a4e2e2861f3ece2b7d6d5e399e2"` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowLog" DROP CONSTRAINT "FK_55293b16d84f048f44c771595bb"` - ); - await queryRunner.query( - `ALTER TABLE "WorkflowLog" DROP CONSTRAINT "FK_6c3dc31b09a96d81982a472e22b"` - ); - await queryRunner.query( - `ALTER TABLE "Workflow" DROP CONSTRAINT "FK_367e2e759f520b31d727d22b803"` - ); - await queryRunner.query( - `ALTER TABLE "Workflow" DROP CONSTRAINT "FK_13c42a014f8c10862f23d02eb49"` - ); - await queryRunner.query( - `ALTER TABLE "Workflow" DROP CONSTRAINT "FK_a87518833c744b2df4324b61a6d"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_815c728d905c44bc440ec91308b"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_12ef8407b6359205df8339f8494"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_d5c3df01bbb2a9ce168b36b5234"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_a93a41d65df4cbe518393695084"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_03d67a4d7fa9f087327ab0f74a7"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_6650020ce6e235164a09d1ca019"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_222212e7157f8816357a4f02536"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_26e16ed3e46c6a6589a88d3abba"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_6491952f9d8066aa5cfff92cd85"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_58a44736718a5ec4fe41526289a"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_ef993920a9967b61fb3fb9bf162"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_06a427cdcbae1ddcb1301b860f2"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_dc79ea74fba7b99835bd475081c"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_79580470f34858375cae5d353a9"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_2a893e347fdab643867abd8dda7"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_123b1825525b963fe9555d62641"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_b70804c755c008c15794b6cc18d"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_ac31bad932e24418ce0bb1bbb14"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_91488d7d3341bf1113902f4786c"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_7498ad42c4f77f7fab2a6bc2e33"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_eeb0dd05d1dec542c3de5fb5074"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_550f9f6177451d4902467991a15"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_f047dd6a708175bae0ee6f8c4bf"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_4cd37d481ef366d975c6a7cd9bf"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_ea993eab5c402623b61203e3256"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_3a4b15ce8357c6735ac1b4ae606"` - ); - await queryRunner.query( - `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_7941aa92c7c740400b272d69072"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationSetting" DROP CONSTRAINT "FK_b023f12dc00bcfc50d6d9ad4f71"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationSetting" DROP CONSTRAINT "FK_aee7abeffd1c60d49f710fb3749"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationSetting" DROP CONSTRAINT "FK_e2565b0aa7d7e015fb6685afede"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationSetting" DROP CONSTRAINT "FK_6f110fc752889f922d6a3c57a55"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_bf703b216d7f59424302dc5d70b"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_b407063ee43233b0cc8e9106cbb"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_32ca4397660dafe0cab7d03e5b1"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_ec8e7a39fcd38d0ea2d40b8afaf"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_b1292f2480d0c4985898d7bf33a"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_85b73b64802058915df58fa013b"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_32efb91af1b432a25ceb55bc0dc"` - ); - await queryRunner.query( - `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_69d439e9f60f05ae16732c49999"` - ); - await queryRunner.query( - `ALTER TABLE "UserSMS" DROP CONSTRAINT "FK_0bae98162ec44540ff85f724daa"` - ); - await queryRunner.query( - `ALTER TABLE "UserSMS" DROP CONSTRAINT "FK_99fc3cdf366fd3d266fbf2d657c"` - ); - await queryRunner.query( - `ALTER TABLE "UserSMS" DROP CONSTRAINT "FK_3cb16b5c2d69dbdc812247788f8"` - ); - await queryRunner.query( - `ALTER TABLE "UserSMS" DROP CONSTRAINT "FK_6439689a29a2192708e3f3603da"` - ); - await queryRunner.query( - `ALTER TABLE "UserEmail" DROP CONSTRAINT "FK_a1aa5e10dcfb571521324bbd665"` - ); - await queryRunner.query( - `ALTER TABLE "UserEmail" DROP CONSTRAINT "FK_06413c119aae9c3f75154c2346c"` - ); - await queryRunner.query( - `ALTER TABLE "UserEmail" DROP CONSTRAINT "FK_1f713c701d85c69f706e4e82b85"` - ); - await queryRunner.query( - `ALTER TABLE "UserEmail" DROP CONSTRAINT "FK_e6beed22b36201aea1d70ba0d72"` - ); - await queryRunner.query( - `ALTER TABLE "UserCall" DROP CONSTRAINT "FK_996ab46825df7f3512e735c450c"` - ); - await queryRunner.query( - `ALTER TABLE "UserCall" DROP CONSTRAINT "FK_1b46d8793ef542c059369481d42"` - ); - await queryRunner.query( - `ALTER TABLE "UserCall" DROP CONSTRAINT "FK_4d90bd0e309ad43c4541bb428e9"` - ); - await queryRunner.query( - `ALTER TABLE "UserCall" DROP CONSTRAINT "FK_db4e522c086be556e5101c4e910"` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryUsageBilling" DROP CONSTRAINT "FK_510252373d4e5917029308384fb"` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryUsageBilling" DROP CONSTRAINT "FK_d71562eb0c2861797502bd99917"` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryUsageBilling" DROP CONSTRAINT "FK_91333210492e5d2f334231468a7"` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryUsageBilling" DROP CONSTRAINT "FK_5670be95a9496b9380b7c0d7935"` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryService" DROP CONSTRAINT "FK_46ea9e637b4454993665a436d56"` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryService" DROP CONSTRAINT "FK_5d0b92dc9ab2bfd71432e9a3536"` - ); - await queryRunner.query( - `ALTER TABLE "TelemetryService" DROP CONSTRAINT "FK_3a3321fd538aa014aa5e4f35220"` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermission" DROP CONSTRAINT "FK_73a2d0db1de4e66582e376098de"` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermission" DROP CONSTRAINT "FK_e2c33d5f98cb42f8c1f76a85095"` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermission" DROP CONSTRAINT "FK_5064c0bdc8ff238952f9a2acf43"` - ); - await queryRunner.query( - `ALTER TABLE "TeamPermission" DROP CONSTRAINT "FK_78293e9cc1746e5f29ccccfdfc0"` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_945ca87238e7465782215c25d8d"` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_a9e764a6ad587e6e386abe3b9de"` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_4ab0af827040dbce6ef21ec7780"` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_fd952f76f5a5297ce69a9a588eb"` - ); - await queryRunner.query( - `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_3cc297d538f01065f9925cfb11a"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriber" DROP CONSTRAINT "FK_35ad85d2f341ebfeaca7ad67af1"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriber" DROP CONSTRAINT "FK_61cecfd27c2d41eb58330df1d8c"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriber" DROP CONSTRAINT "FK_217b295d5882faa6cf3418ed811"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriber" DROP CONSTRAINT "FK_6adf943966e01699e86117d2e34"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSSO" DROP CONSTRAINT "FK_0bfc26bce8ea92b8b8a9e0400de"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSSO" DROP CONSTRAINT "FK_8e2cbcf07eba956fe976ca3d043"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSSO" DROP CONSTRAINT "FK_dc05de7939af3ada1567fc7106b"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageSSO" DROP CONSTRAINT "FK_1d63fa142dd4175ef256f21d2a6"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_d2b2f7ffe8f976fda20f4b96c5b"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_51e0fbc6d460394b1cd38959790"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_9b61276c47d6091295dec9e5350"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_a55bb812676bff276cef1f14c86"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_b1c4fe08e1d90ae4d26d934653c"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_ade3f7acf902dcb313d230ca1f5"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_8fba35fb87a0ad6037eb8fc8040"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPagePrivateUser" DROP CONSTRAINT "FK_524d2e71f90ef8f78d85d5fdfd1"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPagePrivateUser" DROP CONSTRAINT "FK_e47c85ead36095d040493775a3f"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPagePrivateUser" DROP CONSTRAINT "FK_0589c51161d13b752fed41a3193"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPagePrivateUser" DROP CONSTRAINT "FK_1ce45fe77324ede75166d0f57dd"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_8d7351e844adfd5c279fd8e9f3b"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_4ecb38fa1941bb0961641803f21"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_69b1659abafe656563259784d02"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_3c59f811e0660c5522e45e85b6a"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_c28e05c08656f8aa756734c37c5"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_e992fcc346afa21a89ba9f75f25"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_7c1168daf53c46678045ff39d31"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_6a98e42d8df3ba84bd0f79da550"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_f60296efefe379693bfd55a7760"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_9fac66064d88c514d2e5503237a"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHistoryChartBarColorRule" DROP CONSTRAINT "FK_8041d41239c4218bf136bf20591"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHistoryChartBarColorRule" DROP CONSTRAINT "FK_5d973aa991ba9f06b642d3fc9d7"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHistoryChartBarColorRule" DROP CONSTRAINT "FK_54edc7ff7a74a0310a512c53895"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHistoryChartBarColorRule" DROP CONSTRAINT "FK_a65f8fabf888d227d64570f52b3"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHeaderLink" DROP CONSTRAINT "FK_0d3a63f1c684e78297b213c348e"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHeaderLink" DROP CONSTRAINT "FK_88048566089097605e26fdb2893"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHeaderLink" DROP CONSTRAINT "FK_99c017b6ced8da63abdfbb506eb"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageHeaderLink" DROP CONSTRAINT "FK_70d70692cbe9d9be188723df4f0"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageGroup" DROP CONSTRAINT "FK_61191c9c00f7279615e13af4bbd"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageGroup" DROP CONSTRAINT "FK_0a63a8ee804658921edf1e870af"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageGroup" DROP CONSTRAINT "FK_5dbcfd7d38e5ea7a78a6a78a330"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageGroup" DROP CONSTRAINT "FK_4a96c34f030a6e39218352a947a"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageFooterLink" DROP CONSTRAINT "FK_0328201140b59b4b813f83b06a9"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageFooterLink" DROP CONSTRAINT "FK_bd6f15ab951095e624ea664d9a6"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageFooterLink" DROP CONSTRAINT "FK_1a80c698b2205074f53376d631b"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageFooterLink" DROP CONSTRAINT "FK_5f8f65447c9b881860bf742dc98"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_6c82107f63d1a3186d579a6d9cb"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_106e359f945432d6583bd30ff4b"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_842a66fcb103388fcedffef75f7"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_7fab5bc54a8f36eac8f31c82565"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_40cca185c8cf933c04a0534676b"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageCustomField" DROP CONSTRAINT "FK_26b4a892f3b31c5b0b285c4e5cb"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageCustomField" DROP CONSTRAINT "FK_e0abd7540f860de19607dc25bc0"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageCustomField" DROP CONSTRAINT "FK_4f5ae90bc48a0ddeb50cd009aaf"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageAnnouncement" DROP CONSTRAINT "FK_7251cbbaa75eb9570830b0cab32"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageAnnouncement" DROP CONSTRAINT "FK_1491bd0895d515969eee2a08c80"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPageAnnouncement" DROP CONSTRAINT "FK_5e9c5a7393ac9aa477d625de673"` - ); - await queryRunner.query( - `ALTER TABLE "SmsLog" DROP CONSTRAINT "FK_d00778bcfaa735fbb5dc91c1945"` - ); - await queryRunner.query( - `ALTER TABLE "SmsLog" DROP CONSTRAINT "FK_a30bbda7f5480e498ebc609663b"` - ); - await queryRunner.query( - `ALTER TABLE "ShortLink" DROP CONSTRAINT "FK_11f179cd8e9beee22b89c316972"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_7c0f750d3a964180d1e1efa16ea"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_aa84fcdf2fef6c2005ebab2c197"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_2392299477cfc4f612ecb73e839"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_411146017bcfe98bbe028b8d15a"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_4faf556988f5b6a755ef2e85ae8"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenancePublicNote" DROP CONSTRAINT "FK_28e179283c409e0751aae713949"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenancePublicNote" DROP CONSTRAINT "FK_1f67cfb63bd3488b7c5c5b7fac7"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenancePublicNote" DROP CONSTRAINT "FK_937aabd4adbfce78663406a2487"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenancePublicNote" DROP CONSTRAINT "FK_73c6737ab4a7718c45932bffada"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_6e6b087ba99fe433f83f87e0a35"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_c91d4d420e3faaf15fa928fd214"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_967c33f7bce5de522c1d1a80e7b"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_be6a25806925f93b8949e61929b"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_518742c7037d9a38a6594dc18a6"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_52a3a932530026bafef87e62177"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_cc0e8ca9e9065ca0cc24bf6275b"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_1251fb7d4a4bf8586f2bd1528eb"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_1206beb611e0779ce2248ecbaeb"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_e3053b1725658b4a120b4e3185d"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceNoteTemplate" DROP CONSTRAINT "FK_4c3d6b87bb1e8739cdeb8b92f74"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceNoteTemplate" DROP CONSTRAINT "FK_e38c1102001ae0b70c22e046424"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceNoteTemplate" DROP CONSTRAINT "FK_b4cb4c1312eb72459907e1bbe9b"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceInternalNote" DROP CONSTRAINT "FK_7fb00788b6ac97988dd43e2e1b2"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceInternalNote" DROP CONSTRAINT "FK_69757967d2ee696f487fb8ac37e"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceInternalNote" DROP CONSTRAINT "FK_a53ef45aebd4a6a6e7dde7f896a"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceInternalNote" DROP CONSTRAINT "FK_d79c49a0a613a6b432fd400e69b"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceCustomField" DROP CONSTRAINT "FK_9094eed77fb6e8f7ecf1502f5e0"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceCustomField" DROP CONSTRAINT "FK_c7cdb245d3d98be14482f092eca"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceCustomField" DROP CONSTRAINT "FK_3621c488327e1c00518aa4e8816"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_fab9cc7e6ffcf02872fccfab978"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_883038abda021ce79fa838d0273"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_50ddf8bb21e988ea5d419a66cb9"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_5442fd86c96d45e062d5ee1f093"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_4059dd569d6a482062352bf266a"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_4e347d3f99b67dacd149beaf61d"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_61944d851b4a7213d79ef281744"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_602b456a61d73a96e97f483064d"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_84df83d1f492a19a08aee465500"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_1f17f293352ebdc3bcf383158cc"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_71f429afb7678d132472b3c87b0"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_da54bb2c4997ee1a3b73026d7f5"` - ); - await queryRunner.query( - `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_34a4c35e0d7afe6f023825a68c7"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceState" DROP CONSTRAINT "FK_4f803428e0926584d1f7c44a3d4"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceState" DROP CONSTRAINT "FK_88044fd50006f1897e8c760d136"` - ); - await queryRunner.query( - `ALTER TABLE "ScheduledMaintenanceState" DROP CONSTRAINT "FK_57a31fb2a5e4caa223d2506a4e1"` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_a7b405e2a9ae144be016bcf973d"` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_7aecf4b1ae3e45647cb911f4c10"` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_6dcdf97c0834dd44b4f2c93e664"` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_3169f7934171e8f697bb993b010"` - ); - await queryRunner.query( - `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_90e44f45272c0da256951183086"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSSO" DROP CONSTRAINT "FK_00ea9e456217ffbfff35f1e944f"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSSO" DROP CONSTRAINT "FK_28011315533e2d819295d261ee4"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSSO" DROP CONSTRAINT "FK_be9e6751765501ea1db126fcb23"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectCallSMSConfig" DROP CONSTRAINT "FK_f5bc0e2b81886b21004e2a5f67b"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectCallSMSConfig" DROP CONSTRAINT "FK_e873aa20a371bd92e220332a992"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectCallSMSConfig" DROP CONSTRAINT "FK_20334b9571a6cd1a871e70d8e76"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_416e830c88f2ecfa149f4cd51c8"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_49e5a41e1d771fe9e385295bd9a"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_b2ccbfcc3964caf3dfd89243f8f"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_41f4ecc29351e1a406e83b30a93"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_9b36bba6d9898331920805a29ca"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_08afccd6cbbd1a7015d4fe25e39"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayer" DROP CONSTRAINT "FK_f22b52355207d2c0d5a13c168e8"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayer" DROP CONSTRAINT "FK_1db1083a896b0f77a0e87f26463"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayer" DROP CONSTRAINT "FK_6fa6574a45cf1352c5a3b962512"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayer" DROP CONSTRAINT "FK_3b892ef36671f1ea1c8457a96d6"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_166f3696b3c70989507dd7e1f2e"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_43f833a79cf4201b3fa1deed023"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_65998388ab4266dda712502ad65"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_039098d4af133bd9c2b90978ef4"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_0ae6ea2e2d38fd31543768e3609"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_0ad4222a4c48b8a64e3a58b3519"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_872c2f6a9739bab1b57b6d51eac"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_90119ec7f77fa2efd82261e0448"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_c5a798ca667fedda71d4ed54651"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_591b7ed73c964e105bfecc6fd6d"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_d4a0ffc5e9e698bb2612ba0e55f"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_3015ddfeb130417c55489da807e"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_63f6618df216b74b72e62491b09"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_e9302e15399b67938e0121a0545"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_16b426d34ff2c5cbd6ecfd70820"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_a230899d6c2b16621954c46fb16"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_e4bb332263960531a4c9e2d4254"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_00e9676d39eeb807de704430512"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_d35f668f524cc88f580a7651fe2"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_f9a45cea88022a9cf5b96c13e65"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_5a7b7a746409a175423a1bbd5c4"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_bff6f4ae726b5c5cbae10e7d743"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_9ca3fbb66842324aa987d4c9722"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_b15552e664640f67346193598a1"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_73ae2b2702aef4601c39d4d909a"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_da2e065de293a14b69964fb3233"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_0e7d4060e2fabe0957b9fedb429"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_26ba1a6edd877647cedd1eae8ca"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_f5121f361345d858ce740a55a25"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_c8aff8439fbfb07e7388aa9011b"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_878e14be4e6366ec646f874347a"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_90700af75cbe8129db898ac8adb"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_089081f83ef22cdba5a0903ce59"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_13a87c38fe2efa41940783af690"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_651a4ab9e3cbb20f6b62a87a6b8"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_efa24aa8feb92d4e15a707c20ec"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicySchedule" DROP CONSTRAINT "FK_01e63400072d0bc6debee836cbf"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicySchedule" DROP CONSTRAINT "FK_ecb5141b27e85674c294a2541b3"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicySchedule" DROP CONSTRAINT "FK_0e1b7c3c3e8305a10716cdb8d6e"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRule" DROP CONSTRAINT "FK_5c0911d261a941b00d41b6e5fda"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRule" DROP CONSTRAINT "FK_ad8097a9359965d02ccbb16358b"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRule" DROP CONSTRAINT "FK_b90c1cda36981c41e3965a93800"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyEscalationRule" DROP CONSTRAINT "FK_d45a545669dc46da25cc60d1df5"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyCustomField" DROP CONSTRAINT "FK_43230e739b31e3f56284407b586"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyCustomField" DROP CONSTRAINT "FK_456bff32fd0428134ef7396385f"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyCustomField" DROP CONSTRAINT "FK_e791474e098f276eda27704b479"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_574feb4161c5216c2c7ee0faaf8"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_d293a7e96c5bf427072514f21a9"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_d7f555ef162fe878e4ed62a3e23"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_c08a7c1ef8d511b335a991aac47"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_80213eb3f228f1e3d423f5127e2"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecret" DROP CONSTRAINT "FK_e4262f178662aaacdb54d4c4f4e"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecret" DROP CONSTRAINT "FK_a886cd3bbdfd84d01167f92cb65"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorSecret" DROP CONSTRAINT "FK_7c3629c5ae14e97bede3bc548e5"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_a182ba062c0a216395d0dbdbdee"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_4399ab64a5c00d55e5ce254deeb"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_a540272f483ef1de68f7e647486"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_2d50a0e0e624369e7f90a62e8dc"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_d887935f224b896ce64872c37c7"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_e1ae2c698e6bde0a98c50162235"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_e2cf60b88171dfe5fdd0e4fe6c1"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_a65ce9b11b2d7bde123aa7633fd"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_324f1d50d0427bbd0e3308c4592"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_12a3497e1404fcdbc4e8963a58e"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_7ebfe3ddcf597fb73ee8eac2ff4"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_58610249ec4cf593e36210dcb84"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_1d5265d1f3ca2f8b8e461e4998c"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_63e5bbac01d1f68c7b08f126cd4"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_6a6213072d8637e6e625bc78929"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_1a54eaa2d0187d10de84107a09b"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_cf595b683e26e560526404663fe"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_50b373c428cfd4566cc5caf98e6"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_cf3dcaa746835ae36615a39d862"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_825791d5edb2403d7937f16ed91"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_e9bced91dce29928ebeec834905"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_9267db147738caed0ccfdc3af22"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_6a4095ee3d04454071816a5bad8"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_4b25f4a18bec8cb177e8d65c5f9"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_d5cfbebf8b07405652f5382e157"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_fdbe93e29e60763a306358cab55"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_7ce36c144e83082213587e19c23"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_618bcd9015c257b0727df36fd11"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_8f19947114087883cea771e1cb4"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_0d7052620f268d0fa17f948a851"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroup" DROP CONSTRAINT "FK_edd658b85b2ef7ac9b2f0687d8a"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroup" DROP CONSTRAINT "FK_abaf236c1877143fe160991cc45"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorGroup" DROP CONSTRAINT "FK_13905ff40843b11145f21e33ff5"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorCustomField" DROP CONSTRAINT "FK_817e69522c8d2f1e2fd3f857e91"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorCustomField" DROP CONSTRAINT "FK_93a4da4182f93ba24ab958c1b73"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorCustomField" DROP CONSTRAINT "FK_1c6b61e904d8e3fec1ae719b9ef"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_026e918a31de467eeb8e30ae8d1"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_a53f8aab99766a87c73c52b9037"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_876dd05e0dfc64219ef5df241c3"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_36b9b0204f4b17063483cb7308e"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_da9dd65b4401b954a0ea2b5c8d5"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_af037dc245d77c282061fea1b1b"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_3e8a4bd1594da3438d8fb8a6687"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_a895b946fccb109dedd55b85f6d"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_e8090c6569c3a5dbd7ef7485c99"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_cf172eb6797a64ee3750e3f3e21"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_6d7627ab9d5172c66fc50192163"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_ceff6a4dfdccecc4aa40dbfe91a"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_0e6a4e065ffb22f95ecfc259e9a"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_b03e46665e4c075ed1398fcc409"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_b82dafef226b0fae1ad6cb18570"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_ff0fca6570d47798771763533a9"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_6b6b9dbf9ca5448c9297a58ad04"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_16d198b59f3416a8ddc630a90d2"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_fe2dff414a1f67260e3c5189811"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_764daa366a4e195768a49e0ee39"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentPublicNote" DROP CONSTRAINT "FK_cf04d778a5502be606f63e01603"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentPublicNote" DROP CONSTRAINT "FK_691a99e582fcddcc892d8573afc"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentPublicNote" DROP CONSTRAINT "FK_a6964d3aab71608daab9f20e304"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentPublicNote" DROP CONSTRAINT "FK_a9a77e5f286b5724f4e2280d0a1"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_c473db8745d0ebeb147a72986cb"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_52591665c92658ef82944d63d26"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_6aa9a6b46f8e044d722da8f5a7f"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_6311087eeb14ab51e6a1e6133f7"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_0ee7ae6757442fba470b213015c"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_60242ecfcecaa5cb1c5241bed4c"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_278f483fc81c21b1bd1311ee289"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_408324d3635a826538a792422f3"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_389aadeb39a0806e80d4001016e"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_95f76375ccac835f815d7e926a2"` - ); - await queryRunner.query( - `ALTER TABLE "Team" DROP CONSTRAINT "FK_0d4912bf03a7a645ce95142155b"` - ); - await queryRunner.query( - `ALTER TABLE "Team" DROP CONSTRAINT "FK_4be4aa023ba1c6d6443b81b3b91"` - ); - await queryRunner.query( - `ALTER TABLE "Team" DROP CONSTRAINT "FK_baac847c494f692f03fd686e9c7"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentNoteTemplate" DROP CONSTRAINT "FK_3c00f2b005264318a274cd38a94"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentNoteTemplate" DROP CONSTRAINT "FK_515b6970fdd528d4c9f85a5e9a4"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentNoteTemplate" DROP CONSTRAINT "FK_21d5bc0d24b3e5032dd391ec8da"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentInternalNote" DROP CONSTRAINT "FK_8f23b820cbbed6d96cfedd162a2"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentInternalNote" DROP CONSTRAINT "FK_c798e09321f06d8a180916d7a5e"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentInternalNote" DROP CONSTRAINT "FK_b92e75645fd252e4c2f866047de"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentInternalNote" DROP CONSTRAINT "FK_ac48058b3e5f9e8361d2b8328c2"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentCustomField" DROP CONSTRAINT "FK_bc64c76e766b1b880845afbcbf7"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentCustomField" DROP CONSTRAINT "FK_5c1c7369e696f580186a4ff12de"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentCustomField" DROP CONSTRAINT "FK_59e7f6a43dbc5ee54e1a1aaaaf1"` - ); - await queryRunner.query( - `ALTER TABLE "Incident" DROP CONSTRAINT "FK_7e537806a80e869917ca1d7e2e4"` - ); - await queryRunner.query( - `ALTER TABLE "Incident" DROP CONSTRAINT "FK_ed0ec4960a85240f51e6779a00a"` - ); - await queryRunner.query( - `ALTER TABLE "Incident" DROP CONSTRAINT "FK_3f28fe3b32abed354a49b26c9cb"` - ); - await queryRunner.query( - `ALTER TABLE "Incident" DROP CONSTRAINT "FK_6592d4f7f3b260efc23fc9b4bc9"` - ); - await queryRunner.query( - `ALTER TABLE "Incident" DROP CONSTRAINT "FK_067855888a3d71803d3a5aeaecf"` - ); - await queryRunner.query( - `ALTER TABLE "Incident" DROP CONSTRAINT "FK_9b101f023b5db6491203d5c9951"` - ); - await queryRunner.query( - `ALTER TABLE "Incident" DROP CONSTRAINT "FK_eccbc31fa1f58bd051b6f7e108b"` - ); - await queryRunner.query( - `ALTER TABLE "Probe" DROP CONSTRAINT "FK_272ece82a96099041b93c9141e3"` - ); - await queryRunner.query( - `ALTER TABLE "Probe" DROP CONSTRAINT "FK_1963e116be9832b23490cca933f"` - ); - await queryRunner.query( - `ALTER TABLE "Probe" DROP CONSTRAINT "FK_b357696dc9462ad3f9e84c6dc52"` - ); - await queryRunner.query( - `ALTER TABLE "Probe" DROP CONSTRAINT "FK_6bd931aae4920e296ea08864cd0"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicy" DROP CONSTRAINT "FK_0424b49cfcd68cdd1721df53acd"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicy" DROP CONSTRAINT "FK_c0c63ac58f97fd254bb5c2813dc"` - ); - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicy" DROP CONSTRAINT "FK_31508550a088ba2cc843a6c90c4"` - ); - await queryRunner.query( - `ALTER TABLE "Monitor" DROP CONSTRAINT "FK_d3461ab640467c8c2100ea55c79"` - ); - await queryRunner.query( - `ALTER TABLE "Monitor" DROP CONSTRAINT "FK_73bdf22259019b90836aac86b28"` - ); - await queryRunner.query( - `ALTER TABLE "Monitor" DROP CONSTRAINT "FK_a84bbba0dbad47918136d4dfb43"` - ); - await queryRunner.query( - `ALTER TABLE "Monitor" DROP CONSTRAINT "FK_996acfb590bda327843f78b7ad3"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatus" DROP CONSTRAINT "FK_55a0e488581a0d02bcdd67a4348"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatus" DROP CONSTRAINT "FK_bdda7fecdf44ed43ef2004e7be5"` - ); - await queryRunner.query( - `ALTER TABLE "MonitorStatus" DROP CONSTRAINT "FK_db1783158a23bd20dbebaae56ef"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentState" DROP CONSTRAINT "FK_88a0ecd4b1714ac0e2eef9ac27d"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentState" DROP CONSTRAINT "FK_eb33bd015e0e57ee96b60f8d773"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentState" DROP CONSTRAINT "FK_3d279e530067f599f3186e3821d"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentSeverity" DROP CONSTRAINT "FK_d0d87151a7872a44c3d2457bfdc"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentSeverity" DROP CONSTRAINT "FK_2677e0a9dbf97ba0f4a7849eac6"` - ); - await queryRunner.query( - `ALTER TABLE "IncidentSeverity" DROP CONSTRAINT "FK_00d2f503174bf201abc6e77afde"` - ); - await queryRunner.query( - `ALTER TABLE "GreenlockChallenge" DROP CONSTRAINT "FK_7517f5a285255f031b0f6d9663c"` - ); - await queryRunner.query( - `ALTER TABLE "GreenlockCertificate" DROP CONSTRAINT "FK_895b9b802ed002a3804136bacf1"` - ); - await queryRunner.query( - `ALTER TABLE "EmailVerificationToken" DROP CONSTRAINT "FK_9e86ebfdbef16789e9571f22074"` - ); - await queryRunner.query( - `ALTER TABLE "EmailVerificationToken" DROP CONSTRAINT "FK_0b65c7ffb685f6ed78aac195b1a"` - ); - await queryRunner.query( - `ALTER TABLE "EmailLog" DROP CONSTRAINT "FK_6d0739da601917d316494fcae3b"` - ); - await queryRunner.query( - `ALTER TABLE "EmailLog" DROP CONSTRAINT "FK_046364c162885b6ac65d5dd367c"` - ); - await queryRunner.query( - `ALTER TABLE "EmailLog" DROP CONSTRAINT "FK_7b72c5131b3dd1f3edf201a561c"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSMTPConfig" DROP CONSTRAINT "FK_d5458705e98b89c08c0d960422e"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSMTPConfig" DROP CONSTRAINT "FK_3b7ed2d3bd1a2ee9638cccef5b0"` - ); - await queryRunner.query( - `ALTER TABLE "ProjectSMTPConfig" DROP CONSTRAINT "FK_a540dab929fa6582b93f258ffe5"` - ); - await queryRunner.query( - `ALTER TABLE "Domain" DROP CONSTRAINT "FK_9ace4c275b42c057b7581543ce3"` - ); - await queryRunner.query( - `ALTER TABLE "Domain" DROP CONSTRAINT "FK_12e6ebc5c806263d562045e9282"` - ); - await queryRunner.query( - `ALTER TABLE "Domain" DROP CONSTRAINT "FK_f1ce90d3f9693be29b72fabe93b"` - ); - await queryRunner.query( - `ALTER TABLE "DataMigrations" DROP CONSTRAINT "FK_183a8261590c30a27a1b51f4bdb"` - ); - await queryRunner.query( - `ALTER TABLE "DataMigrations" DROP CONSTRAINT "FK_1619179d46a4411e1bb4af5d342"` - ); - await queryRunner.query( - `ALTER TABLE "CallLog" DROP CONSTRAINT "FK_3e510124d923fe3b994936a7cb5"` - ); - await queryRunner.query( - `ALTER TABLE "CallLog" DROP CONSTRAINT "FK_5648767682195afaeb09098a213"` - ); - await queryRunner.query( - `ALTER TABLE "BillingPaymentMethod" DROP CONSTRAINT "FK_93a1554cb316127896f66acddd3"` - ); - await queryRunner.query( - `ALTER TABLE "BillingPaymentMethod" DROP CONSTRAINT "FK_55c3c9a9fc28000262b811cebc8"` - ); - await queryRunner.query( - `ALTER TABLE "BillingPaymentMethod" DROP CONSTRAINT "FK_db4bb9add01b7d8286869fd9a0a"` - ); - await queryRunner.query( - `ALTER TABLE "BillingInvoice" DROP CONSTRAINT "FK_0a0a1a9865d157e46b1ecf14873"` - ); - await queryRunner.query( - `ALTER TABLE "BillingInvoice" DROP CONSTRAINT "FK_15b8130f5378f2079ed5b2fe7d1"` - ); - await queryRunner.query( - `ALTER TABLE "BillingInvoice" DROP CONSTRAINT "FK_0ab13e9a92ce4801c37c2a0a77f"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" DROP CONSTRAINT "FK_ac42ef4597147c260e89a0f3d3a"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" DROP CONSTRAINT "FK_dc8eb846ffbceafbc9c60bbfaa5"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" DROP CONSTRAINT "FK_fb09dd7fefa9d5d44b1907be5fd"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" DROP CONSTRAINT "FK_0cf347c575f15d3836615f53258"` - ); - await queryRunner.query( - `ALTER TABLE "Label" DROP CONSTRAINT "FK_f46caf81c5fd7664ba8da9c99ba"` - ); - await queryRunner.query( - `ALTER TABLE "Label" DROP CONSTRAINT "FK_84520cbda97d2a9cb9da7ccb18c"` - ); - await queryRunner.query( - `ALTER TABLE "Label" DROP CONSTRAINT "FK_f10d59c2ba66e085722e0053cb7"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKey" DROP CONSTRAINT "FK_bcbc7d80fb0cfe2cbb5ae7db791"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKey" DROP CONSTRAINT "FK_891c55549057af9a0c90c925ebb"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKey" DROP CONSTRAINT "FK_bb1019f0078a21b4854f5cb3ed4"` - ); - await queryRunner.query( - `ALTER TABLE "Project" DROP CONSTRAINT "FK_b5ee87614c184778810283c2991"` - ); - await queryRunner.query( - `ALTER TABLE "Project" DROP CONSTRAINT "FK_4ee6a519d48b26fe2a78fdc1c9c"` - ); - await queryRunner.query( - `ALTER TABLE "Project" DROP CONSTRAINT "FK_43989dee7f7af742f6d8ec2664a"` - ); - await queryRunner.query( - `ALTER TABLE "Project" DROP CONSTRAINT "FK_639312a8ef82cbd5cee77c5b1ba"` - ); - await queryRunner.query( - `ALTER TABLE "ResellerPlan" DROP CONSTRAINT "FK_e756416e4b0983e158f71c47c1a"` - ); - await queryRunner.query( - `ALTER TABLE "ResellerPlan" DROP CONSTRAINT "FK_34cdc5e0500513f321f0da35a64"` - ); - await queryRunner.query( - `ALTER TABLE "ResellerPlan" DROP CONSTRAINT "FK_fc269bd109ac405a458b2acc678"` - ); - await queryRunner.query( - `ALTER TABLE "Reseller" DROP CONSTRAINT "FK_952b3ed48545aaf18033150dc66"` - ); - await queryRunner.query( - `ALTER TABLE "Reseller" DROP CONSTRAINT "FK_fe790bb94630d701a8ad93287ce"` - ); - await queryRunner.query( - `ALTER TABLE "AcmeChallenge" DROP CONSTRAINT "FK_71371b224feb48f1d60e847cf1b"` - ); - await queryRunner.query( - `ALTER TABLE "AcmeCertificate" DROP CONSTRAINT "FK_130a8fd12e7505eebfce670b198"` - ); - await queryRunner.query( - `ALTER TABLE "User" DROP CONSTRAINT "FK_644c3c0393979f57f71892ff0d7"` - ); - await queryRunner.query( - `ALTER TABLE "User" DROP CONSTRAINT "FK_1f25f5fc0032f7014482d9c195e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_4e72fad380eca9abfa3b989554"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_3747a5f42be5c977e574abcd71"` - ); - await queryRunner.query(`DROP TABLE "WorkflowLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_7711266422ebad1188c4879d66"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_94d495f938d819dab20480c5f8"` - ); - await queryRunner.query(`DROP TABLE "ServiceLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_4523aa1dd9163aaf37698d137e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_7281dda2d397d6d75c2f5285bb"` - ); - await queryRunner.query(`DROP TABLE "TeamPermissionLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_f4af9c94a4b3ba11b4739a25ef"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_618293911d2e52dc3c6a6873b4"` - ); - await queryRunner.query( - `DROP TABLE "StatusPageSubscriberStatusPageResource"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d7895bb35944a68cccf8286521"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_f10f946bb8a5da2ef395557805"` - ); - await queryRunner.query(`DROP TABLE "AnnouncementStatusPage"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_4f3c993200714127eb2d0851cc"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_c88790ffdbb71aa66a5795be22"` - ); - await queryRunner.query(`DROP TABLE "ScheduledMaintenanceLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_266f1f927ed89d829a2349d2e2"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_fcab6c2c59de7fa9e1a4ed52d3"` - ); - await queryRunner.query(`DROP TABLE "ScheduledMaintenanceStatusPage"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_d1a5797fdd98c1fa2f99670aab"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_9f74e5e95ad88301cbc6e97da6"` - ); - await queryRunner.query(`DROP TABLE "ScheduledMaintenanceMonitor"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_e9f66cc920a6dfd8b20be8497c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_5d5aa67b52755d47e81bb19feb"` - ); - await queryRunner.query(`DROP TABLE "StatusPageDownMonitorStatus"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_6842f6301436d26a3115406279"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d409a46e63e25fbd4fcc9d5242"` - ); - await queryRunner.query(`DROP TABLE "StatusPageLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_d6d43b15c7dca2734a768379f8"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_43c6fb265bd3b69e26f1d98b66"` - ); - await queryRunner.query(`DROP TABLE "ProjectSsoTeam"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_c27ee1fc2df7788145ed9e3333"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_facb426dc0647d760bba573c2d"` - ); - await queryRunner.query(`DROP TABLE "OnCallDutyPolicyScheduleLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_8ad10d0cd8fd68ac64ae3967dc"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_fed65a7701822d21a66810bfe2"` - ); - await queryRunner.query(`DROP TABLE "MonitorSecretMonitor"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_a91be5ccf47cbd470c3f9ee560"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_2fed2a41449af9a9cf821b759d"` - ); - await queryRunner.query(`DROP TABLE "MonitorGroupLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_fcf64673a74380a67159376b85"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d527ebc73a91eefcae4beaaf82"` - ); - await queryRunner.query(`DROP TABLE "IncidentTemplateLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_2b76b57ea6659f97a4bcd1156c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_99245f38769689fe8a172dcb81"` - ); - await queryRunner.query( - `DROP TABLE "IncidentTemplateOnCallDutyPolicy"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_33fb90ba0116b7db4efd4ec7a4"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_390a45e855282ae55ad56d1e1f"` - ); - await queryRunner.query(`DROP TABLE "IncidentTemplateMonitor"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_17f4085273d14d4d6145cf6585"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_1084d1ddbabbcfcab7cd9d547a"` - ); - await queryRunner.query(`DROP TABLE "IncidentLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_f89b23e3cafd1c6a0bfd42c297"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_2d127b6da0e4fab9f905b4d332"` - ); - await queryRunner.query(`DROP TABLE "IncidentOnCallDutyPolicy"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_9e2000a938f2e12c6653e68780"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_55e3162c1259b1b092f0ac63ee"` - ); - await queryRunner.query(`DROP TABLE "IncidentMonitor"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_1c0248fe6856bbe029fc492ec7"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d4a3f7dc4e33b896b0984e7316"` - ); - await queryRunner.query(`DROP TABLE "OnCallDutyPolicyLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_1866fb21ea1acd3d5c37e28ca1"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_3af64cebb5a7cc7f1ad0aa70c1"` - ); - await queryRunner.query(`DROP TABLE "MonitorLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_4db7f66405df73287c639ece90"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0946f6b41113cd842ee69f69fb"` - ); - await queryRunner.query(`DROP TABLE "ApiKeyPermissionLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_c093a47a2ecfa1d5f2d4aeb04a"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_bb47f6d0cabd55fde5b199ae20"` - ); - await queryRunner.query(`DROP TABLE "WorkflowVariable"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_55293b16d84f048f44c771595b"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6c3dc31b09a96d81982a472e22"` - ); - await queryRunner.query(`DROP TABLE "WorkflowLog"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_a87518833c744b2df4324b61a6"` - ); - await queryRunner.query(`DROP TABLE "Workflow"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_815c728d905c44bc440ec91308"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_12ef8407b6359205df8339f849"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d5c3df01bbb2a9ce168b36b523"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_222212e7157f8816357a4f0253"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_26e16ed3e46c6a6589a88d3abb"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6491952f9d8066aa5cfff92cd8"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_58a44736718a5ec4fe41526289"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ef993920a9967b61fb3fb9bf16"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_06a427cdcbae1ddcb1301b860f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_dc79ea74fba7b99835bd475081"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_79580470f34858375cae5d353a"` - ); - await queryRunner.query(`DROP TABLE "UserOnCallLogTimeline"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_7498ad42c4f77f7fab2a6bc2e3"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_550f9f6177451d4902467991a1"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_f047dd6a708175bae0ee6f8c4b"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_4cd37d481ef366d975c6a7cd9b"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ea993eab5c402623b61203e325"` - ); - await queryRunner.query(`DROP TABLE "UserOnCallLog"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_e2565b0aa7d7e015fb6685afed"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6f110fc752889f922d6a3c57a5"` - ); - await queryRunner.query(`DROP TABLE "UserNotificationSetting"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_bf703b216d7f59424302dc5d70"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_f1a5912cdf877c89121a3090cd"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b407063ee43233b0cc8e9106cb"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_32ca4397660dafe0cab7d03e5b"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ec8e7a39fcd38d0ea2d40b8afa"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_32efb91af1b432a25ceb55bc0d"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_69d439e9f60f05ae16732c4999"` - ); - await queryRunner.query(`DROP TABLE "UserNotificationRule"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_3cb16b5c2d69dbdc812247788f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6439689a29a2192708e3f3603d"` - ); - await queryRunner.query(`DROP TABLE "UserSMS"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_1f713c701d85c69f706e4e82b8"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_e6beed22b36201aea1d70ba0d7"` - ); - await queryRunner.query(`DROP TABLE "UserEmail"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_4d90bd0e309ad43c4541bb428e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_db4e522c086be556e5101c4e91"` - ); - await queryRunner.query(`DROP TABLE "UserCall"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_91333210492e5d2f334231468a"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_5670be95a9496b9380b7c0d793"` - ); - await queryRunner.query(`DROP TABLE "TelemetryUsageBilling"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_6c89ae3af06376fe3411cf8295"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_3a3321fd538aa014aa5e4f3522"` - ); - await queryRunner.query(`DROP TABLE "TelemetryService"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_5064c0bdc8ff238952f9a2acf4"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_78293e9cc1746e5f29ccccfdfc"` - ); - await queryRunner.query(`DROP TABLE "TeamPermission"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_fd952f76f5a5297ce69a9a588e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_3cc297d538f01065f9925cfb11"` - ); - await queryRunner.query(`DROP TABLE "TeamMember"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_217b295d5882faa6cf3418ed81"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6adf943966e01699e86117d2e3"` - ); - await queryRunner.query(`DROP TABLE "StatusPageSubscriber"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_dc05de7939af3ada1567fc7106"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_1d63fa142dd4175ef256f21d2a"` - ); - await queryRunner.query(`DROP TABLE "StatusPageSSO"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_9b61276c47d6091295dec9e535"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a55bb812676bff276cef1f14c8"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b1c4fe08e1d90ae4d26d934653"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ade3f7acf902dcb313d230ca1f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_8fba35fb87a0ad6037eb8fc804"` - ); - await queryRunner.query(`DROP TABLE "StatusPageResource"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_0589c51161d13b752fed41a319"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_1ce45fe77324ede75166d0f57d"` - ); - await queryRunner.query(`DROP TABLE "StatusPagePrivateUser"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_feea72d826c4bf508a95c7c757"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_69b1659abafe656563259784d0"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_3c59f811e0660c5522e45e85b6"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_c28e05c08656f8aa756734c37c"` - ); - await queryRunner.query(`DROP TABLE "StatusPageOwnerUser"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_14768105dd06f0e3e75ec5b051"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6a98e42d8df3ba84bd0f79da55"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_f60296efefe379693bfd55a776"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_9fac66064d88c514d2e5503237"` - ); - await queryRunner.query(`DROP TABLE "StatusPageOwnerTeam"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_54edc7ff7a74a0310a512c5389"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a65f8fabf888d227d64570f52b"` - ); - await queryRunner.query( - `DROP TABLE "StatusPageHistoryChartBarColorRule"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_99c017b6ced8da63abdfbb506e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_70d70692cbe9d9be188723df4f"` - ); - await queryRunner.query(`DROP TABLE "StatusPageHeaderLink"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_5dbcfd7d38e5ea7a78a6a78a33"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_4a96c34f030a6e39218352a947"` - ); - await queryRunner.query(`DROP TABLE "StatusPageGroup"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_1a80c698b2205074f53376d631"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_5f8f65447c9b881860bf742dc9"` - ); - await queryRunner.query(`DROP TABLE "StatusPageFooterLink"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_842a66fcb103388fcedffef75f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_7fab5bc54a8f36eac8f31c8256"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_40cca185c8cf933c04a0534676"` - ); - await queryRunner.query(`DROP TABLE "StatusPageDomain"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_4f5ae90bc48a0ddeb50cd009aa"` - ); - await queryRunner.query(`DROP TABLE "StatusPageCustomField"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_3c5cbb3fcaf6be56918520501c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_5e9c5a7393ac9aa477d625de67"` - ); - await queryRunner.query(`DROP TABLE "StatusPageAnnouncement"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_68d0fe13df157c0a93d1ff6fa1"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_12854d2b71004825924a476dfc"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a30bbda7f5480e498ebc609663"` - ); - await queryRunner.query(`DROP TABLE "SmsLog"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_339f8fe3bc6fb4440541cc61a4"` - ); - await queryRunner.query(`DROP TABLE "ShortLink"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_590161c4f8e0b63e7ed3fd2163"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_978cd5638c6e44186cbd1099d9"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_58aa5722dde40c062793ede637"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_7c0f750d3a964180d1e1efa16e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_411146017bcfe98bbe028b8d15"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_4faf556988f5b6a755ef2e85ae"` - ); - await queryRunner.query( - `DROP TABLE "ScheduledMaintenanceStateTimeline"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0b7ec5df94e08afda7569ea1ff"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_937aabd4adbfce78663406a248"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_73c6737ab4a7718c45932bffad"` - ); - await queryRunner.query(`DROP TABLE "ScheduledMaintenancePublicNote"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_5d71879db35e7c4104b56bef09"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_967c33f7bce5de522c1d1a80e7"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_be6a25806925f93b8949e61929"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_518742c7037d9a38a6594dc18a"` - ); - await queryRunner.query(`DROP TABLE "ScheduledMaintenanceOwnerUser"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_cbe4eac5f23c115ddd4a695747"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_1251fb7d4a4bf8586f2bd1528e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_1206beb611e0779ce2248ecbae"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_e3053b1725658b4a120b4e3185"` - ); - await queryRunner.query(`DROP TABLE "ScheduledMaintenanceOwnerTeam"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_49d4245d0066dc1e14a63a4234"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b4cb4c1312eb72459907e1bbe9"` - ); - await queryRunner.query( - `DROP TABLE "ScheduledMaintenanceNoteTemplate"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_39a27db645744ef9177d4ab7c9"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a53ef45aebd4a6a6e7dde7f896"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d79c49a0a613a6b432fd400e69"` - ); - await queryRunner.query( - `DROP TABLE "ScheduledMaintenanceInternalNote"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_3621c488327e1c00518aa4e881"` - ); - await queryRunner.query(`DROP TABLE "ScheduledMaintenanceCustomField"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_c8306d145e77473ee7ac859a0d"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_fab9cc7e6ffcf02872fccfab97"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_883038abda021ce79fa838d027"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_e0d4bcb3e28628a47b8b55ead8"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_20f04dff3b9d1f3d62985dd9de"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_4059dd569d6a482062352bf266"` - ); - await queryRunner.query(`DROP TABLE "ScheduledMaintenance"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_b5138c4c7dc1c36ef592af784f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_cfcd002648dcf692a2e126ab05"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_97509c6e7f41b59c5447cec669"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_7213cf79fce2db23927de0aac0"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b8a8b32e0d7e06ed8a9a3171ab"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_4e347d3f99b67dacd149beaf61"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_61944d851b4a7213d79ef28174"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_4cf5977515ca677e569942fb09"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_5f7df6dd7b1a85b933bd953b47"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_34a4c35e0d7afe6f023825a68c"` - ); - await queryRunner.query(`DROP TABLE "StatusPage"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_57a31fb2a5e4caa223d2506a4e"` - ); - await queryRunner.query(`DROP TABLE "ScheduledMaintenanceState"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_a7b405e2a9ae144be016bcf973"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_7aecf4b1ae3e45647cb911f4c1"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6dcdf97c0834dd44b4f2c93e66"` - ); - await queryRunner.query(`DROP TABLE "PromoCode"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_be9e6751765501ea1db126fcb2"` - ); - await queryRunner.query(`DROP TABLE "ProjectSSO"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_20334b9571a6cd1a871e70d8e7"` - ); - await queryRunner.query(`DROP TABLE "ProjectCallSMSConfig"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_41f4ecc29351e1a406e83b30a9"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_9b36bba6d9898331920805a29c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_08afccd6cbbd1a7015d4fe25e3"` - ); - await queryRunner.query( - `DROP TABLE "OnCallDutyPolicyScheduleLayerUser"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6fa6574a45cf1352c5a3b96251"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_3b892ef36671f1ea1c8457a96d"` - ); - await queryRunner.query(`DROP TABLE "OnCallDutyPolicyScheduleLayer"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_0ad4222a4c48b8a64e3a58b351"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_872c2f6a9739bab1b57b6d51ea"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_90119ec7f77fa2efd82261e044"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_c5a798ca667fedda71d4ed5465"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_591b7ed73c964e105bfecc6fd6"` - ); - await queryRunner.query( - `DROP TABLE "OnCallDutyPolicyExecutionLogTimeline"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a217798f1dbf12d31b498a1020"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6ad07641fe00f29edc65716aca"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d4a0ffc5e9e698bb2612ba0e55"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_296e2be818e3fba28e43b457fb"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_fbb50489ef5eb354f46d479e2a"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_e4bb332263960531a4c9e2d425"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_00e9676d39eeb807de70443051"` - ); - await queryRunner.query(`DROP TABLE "OnCallDutyPolicyExecutionLog"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_bff6f4ae726b5c5cbae10e7d74"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_9ca3fbb66842324aa987d4c972"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b15552e664640f67346193598a"` - ); - await queryRunner.query( - `DROP TABLE "OnCallDutyPolicyEscalationRuleUser"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0e7d4060e2fabe0957b9fedb42"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_f5121f361345d858ce740a55a2"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_c8aff8439fbfb07e7388aa9011"` - ); - await queryRunner.query( - `DROP TABLE "OnCallDutyPolicyEscalationRuleTeam"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_089081f83ef22cdba5a0903ce5"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_651a4ab9e3cbb20f6b62a87a6b"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_efa24aa8feb92d4e15a707c20e"` - ); - await queryRunner.query( - `DROP TABLE "OnCallDutyPolicyEscalationRuleSchedule"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_f2f09fcba2e6eabe61d16aa242"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_15bea18a6b3f9730ce6fad2804"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0e1b7c3c3e8305a10716cdb8d6"` - ); - await queryRunner.query(`DROP TABLE "OnCallDutyPolicySchedule"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_4020168f8d1ee248b8d4bd6293"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_e3e66222024c1119865f3eae0f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b90c1cda36981c41e3965a9380"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d45a545669dc46da25cc60d1df"` - ); - await queryRunner.query(`DROP TABLE "OnCallDutyPolicyEscalationRule"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_e791474e098f276eda27704b47"` - ); - await queryRunner.query(`DROP TABLE "OnCallDutyPolicyCustomField"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_2606f4914507b3471f40864348"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_26f6632b71574ff4dbe87c352d"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_01ac1d1ef9e72aeb6dac6575dd"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0ea6641468483b2ace63144031"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_574feb4161c5216c2c7ee0faaf"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_c08a7c1ef8d511b335a991aac4"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_80213eb3f228f1e3d423f5127e"` - ); - await queryRunner.query(`DROP TABLE "MonitorStatusTimeline"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_7c3629c5ae14e97bede3bc548e"` - ); - await queryRunner.query(`DROP TABLE "MonitorSecret"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_a540272f483ef1de68f7e64748"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_2d50a0e0e624369e7f90a62e8d"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d887935f224b896ce64872c37c"` - ); - await queryRunner.query(`DROP TABLE "MonitorProbe"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_ab449be4e08009bfc2e68f5c78"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a65ce9b11b2d7bde123aa7633f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_324f1d50d0427bbd0e3308c459"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_12a3497e1404fcdbc4e8963a58"` - ); - await queryRunner.query(`DROP TABLE "MonitorOwnerUser"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_acb20c616e3781a3a5506f89ef"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_1d5265d1f3ca2f8b8e461e4998"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_63e5bbac01d1f68c7b08f126cd"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6a6213072d8637e6e625bc7892"` - ); - await queryRunner.query(`DROP TABLE "MonitorOwnerTeam"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_50b373c428cfd4566cc5caf98e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_cf3dcaa746835ae36615a39d86"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_825791d5edb2403d7937f16ed9"` - ); - await queryRunner.query(`DROP TABLE "MonitorGroupResource"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_7c88d50a62d9ba48e9578c9128"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6a4095ee3d04454071816a5bad"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_4b25f4a18bec8cb177e8d65c5f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d5cfbebf8b07405652f5382e15"` - ); - await queryRunner.query(`DROP TABLE "MonitorGroupOwnerUser"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_0736d9a7117c25ee216507fa47"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_618bcd9015c257b0727df36fd1"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_8f19947114087883cea771e1cb"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0d7052620f268d0fa17f948a85"` - ); - await queryRunner.query(`DROP TABLE "MonitorGroupOwnerTeam"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_04092730e7f93ee58b544f484c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_136524b17f6dbf70f1e720e8f6"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_13905ff40843b11145f21e33ff"` - ); - await queryRunner.query(`DROP TABLE "MonitorGroup"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_1c6b61e904d8e3fec1ae719b9e"` - ); - await queryRunner.query(`DROP TABLE "MonitorCustomField"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_38583ea60d2df4a525098065f3"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_876dd05e0dfc64219ef5df241c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_36b9b0204f4b17063483cb7308"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_da9dd65b4401b954a0ea2b5c8d"` - ); - await queryRunner.query(`DROP TABLE "IncidentTemplateOwnerUser"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_2cb2fc022da183b99cf06f0043"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a895b946fccb109dedd55b85f6"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_e8090c6569c3a5dbd7ef7485c9"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_cf172eb6797a64ee3750e3f3e2"` - ); - await queryRunner.query(`DROP TABLE "IncidentTemplateOwnerTeam"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_6d7627ab9d5172c66fc5019216"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ceff6a4dfdccecc4aa40dbfe91"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_9fe9e55006c2a1f26727e479ab"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0683d65aa3e3483127671d120f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b82dafef226b0fae1ad6cb1857"` - ); - await queryRunner.query(`DROP TABLE "IncidentTemplate"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_dc5784aa146b249a22afe48b7e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_71c94e9f34772d46fd50e18b64"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_7db6b1a8fbbc9eb44c2e7f5047"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_5a1a64bc4c38107b25a4bdcd17"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ff0fca6570d47798771763533a"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_fe2dff414a1f67260e3c518981"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_764daa366a4e195768a49e0ee3"` - ); - await queryRunner.query(`DROP TABLE "IncidentStateTimeline"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_8bbc15605fce799ab2abf6532b"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a6964d3aab71608daab9f20e30"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a9a77e5f286b5724f4e2280d0a"` - ); - await queryRunner.query(`DROP TABLE "IncidentPublicNote"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_8347c45d2b96d4f809bbefeb80"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6aa9a6b46f8e044d722da8f5a7"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6311087eeb14ab51e6a1e6133f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0ee7ae6757442fba470b213015"` - ); - await queryRunner.query(`DROP TABLE "IncidentOwnerUser"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_a913a00c56edac2d2e3364fb8b"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_408324d3635a826538a792422f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_389aadeb39a0806e80d4001016"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_95f76375ccac835f815d7e926a"` - ); - await queryRunner.query(`DROP TABLE "IncidentOwnerTeam"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_fcdfc85e8e6b0a1f8128ce572a"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_319e120005dff229ac97e9e21d"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_baac847c494f692f03fd686e9c"` - ); - await queryRunner.query(`DROP TABLE "Team"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_7904e01f4867510e9cb3ba09cd"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_21d5bc0d24b3e5032dd391ec8d"` - ); - await queryRunner.query(`DROP TABLE "IncidentNoteTemplate"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_582d48a4d9cce7dd74ea1dd282"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b92e75645fd252e4c2f866047d"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ac48058b3e5f9e8361d2b8328c"` - ); - await queryRunner.query(`DROP TABLE "IncidentInternalNote"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_59e7f6a43dbc5ee54e1a1aaaaf"` - ); - await queryRunner.query(`DROP TABLE "IncidentCustomField"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_7e537806a80e869917ca1d7e2e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6ddd5e3433dcdc6832b2a93845"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ee2acd83fe08dfe3b46a533b7f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_5218e92f700d91afe6a8db79cb"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_c3ad64a7aaf39c1f7885527e24"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ed0ec4960a85240f51e6779a00"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_3f28fe3b32abed354a49b26c9c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_6592d4f7f3b260efc23fc9b4bc"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_eb410c8eb2e2eadfa5880936fe"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_5f53415da5b48954185a6c32b7"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_eccbc31fa1f58bd051b6f7e108"` - ); - await queryRunner.query(`DROP TABLE "Incident"`); - await queryRunner.query(`DROP TABLE "Probe"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_68884a658e2c47f67c9b2dd9af"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_570a79facd5fe1c01ccdca55ae"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_31508550a088ba2cc843a6c90c"` - ); - await queryRunner.query(`DROP TABLE "OnCallDutyPolicy"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_2fb8ee83943588b9c5f3358570"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_899a35fe28dd2661f9c999c130"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_28d1e0cdf3fcf1ac30249f5d3a"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_9c8f4b9103fa6b62ff5a121f36"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_ea5170227069c85269b3a6db93"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_9bfee6c29045d1c236d9395f65"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_1e0f03c5e067eeb505b2b87aa8"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b4c392c3163a2a32da5b401c91"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_db5cc02633b36957c9be4d70c6"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_d3461ab640467c8c2100ea55c7"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_42ecc6e0ac9984cd24d5f9ddd8"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_36d301a9e41af9b6e62b0c0a02"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_996acfb590bda327843f78b7ad"` - ); - await queryRunner.query(`DROP TABLE "Monitor"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_db1783158a23bd20dbebaae56e"` - ); - await queryRunner.query(`DROP TABLE "MonitorStatus"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_3d279e530067f599f3186e3821"` - ); - await queryRunner.query(`DROP TABLE "IncidentState"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_00d2f503174bf201abc6e77afd"` - ); - await queryRunner.query(`DROP TABLE "IncidentSeverity"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_8358adfc13448a58e30a799f6e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_36dde33ab3f3fefb63cb164c7d"` - ); - await queryRunner.query(`DROP TABLE "GreenlockChallenge"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_d47fcf292f2ceae490da8404d8"` - ); - await queryRunner.query(`DROP TABLE "GreenlockCertificate"`); - await queryRunner.query(`DROP TABLE "GlobalConfig"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_0817e87f2e8de1019a09b1eb60"` - ); - await queryRunner.query(`DROP TABLE "EmailVerificationToken"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_046364c162885b6ac65d5dd367"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_c4d606cbaafbdbbf5130c97058"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_2c92ae54071acce65fa134d855"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_7b72c5131b3dd1f3edf201a561"` - ); - await queryRunner.query(`DROP TABLE "EmailLog"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_a540dab929fa6582b93f258ffe"` - ); - await queryRunner.query(`DROP TABLE "ProjectSMTPConfig"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_f1ce90d3f9693be29b72fabe93"` - ); - await queryRunner.query(`DROP TABLE "Domain"`); - await queryRunner.query(`DROP TABLE "DataMigrations"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_af110ffcec14a2770dada25c92"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_14a8701025e30ab54de66990dc"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_5648767682195afaeb09098a21"` - ); - await queryRunner.query(`DROP TABLE "CallLog"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_db4bb9add01b7d8286869fd9a0"` - ); - await queryRunner.query(`DROP TABLE "BillingPaymentMethod"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_0ab13e9a92ce4801c37c2a0a77"` - ); - await queryRunner.query(`DROP TABLE "BillingInvoice"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_fb09dd7fefa9d5d44b1907be5f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0cf347c575f15d3836615f5325"` - ); - await queryRunner.query(`DROP TABLE "ApiKeyPermission"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_f10d59c2ba66e085722e0053cb"` - ); - await queryRunner.query(`DROP TABLE "Label"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_17bec9c02846cdf64b5d6bb71f"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_0e26b8c243b0ed1395bd52aaaf"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_bb1019f0078a21b4854f5cb3ed"` - ); - await queryRunner.query(`DROP TABLE "ApiKey"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_b5ee87614c184778810283c299"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_4ee6a519d48b26fe2a78fdc1c9"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_38f5c1d2bf0743a868288fc8e6"` - ); - await queryRunner.query(`DROP TABLE "Project"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_fc269bd109ac405a458b2acc67"` - ); - await queryRunner.query(`DROP TABLE "ResellerPlan"`); - await queryRunner.query(`DROP TABLE "Reseller"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_936afe487b8f9da2f6aae1d11d"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_fe7dd70f059b5b9bd0452d3ebf"` - ); - await queryRunner.query(`DROP TABLE "AcmeChallenge"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_8545260bc2f2b2cdb2e7184362"` - ); - await queryRunner.query(`DROP TABLE "AcmeCertificate"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_70f42c60b74c0e931f0d599f03"` - ); - await queryRunner.query(`DROP TABLE "User"`); - await queryRunner.query(`DROP TABLE "File"`); - } + await queryRunner.query( + `CREATE TABLE "File" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "file" bytea NOT NULL, "name" character varying(100) NOT NULL, "type" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "isPublic" character varying(100) NOT NULL DEFAULT true, CONSTRAINT "PK_a5e28609454dda7dc62b97d235c" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE TABLE "User" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "name" character varying(50), "email" character varying(100) NOT NULL, "newUnverifiedTemporaryEmail" character varying(100), "slug" character varying(100) NOT NULL, "password" character varying(64), "isEmailVerified" boolean NOT NULL DEFAULT false, "companyName" character varying(100), "jobRole" character varying(100), "companySize" character varying(100), "referral" character varying(100), "companyPhoneNumber" character varying(30), "profilePictureId" uuid, "twoFactorAuthEnabled" boolean NOT NULL DEFAULT false, "twoFactorSecretCode" character varying(100), "twoFactorAuthUrl" character varying(100), "backupCodes" text, "jwtRefreshToken" character varying(100), "paymentProviderCustomerId" character varying(100), "resetPasswordToken" character varying(100), "resetPasswordExpires" TIMESTAMP WITH TIME ZONE, "timezone" character varying(100), "lastActive" TIMESTAMP WITH TIME ZONE, "promotionName" character varying(100), "isDisabled" boolean NOT NULL DEFAULT false, "paymentFailedDate" TIMESTAMP WITH TIME ZONE, "isMasterAdmin" boolean NOT NULL DEFAULT false, "isBlocked" boolean NOT NULL DEFAULT false, "alertPhoneNumber" character varying(30), "alertPhoneVerificationCode" character varying(8), "utmSource" character varying(500), "utmMedium" character varying(500), "utmCampaign" character varying(500), "utmTerm" character varying(500), "utmContent" character varying(500), "utmUrl" character varying(500), "alertPhoneVerificationCodeRequestTime" TIMESTAMP WITH TIME ZONE, "tempAlertPhoneNumber" character varying(30), "deletedByUserId" uuid, CONSTRAINT "UQ_4a257d2c9837248d70640b3e36e" UNIQUE ("email"), CONSTRAINT "UQ_70f42c60b74c0e931f0d599f03d" UNIQUE ("slug"), CONSTRAINT "PK_decdf2abb1c7cbeb1a805aada89" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_70f42c60b74c0e931f0d599f03" ON "User" ("slug") `, + ); + await queryRunner.query( + `CREATE TABLE "AcmeCertificate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "domain" character varying(100) NOT NULL, "certificate" text NOT NULL, "certificateKey" text NOT NULL, "issuedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_185a0eac2bf16488e0b0c940735" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_8545260bc2f2b2cdb2e7184362" ON "AcmeCertificate" ("domain") `, + ); + await queryRunner.query( + `CREATE TABLE "AcmeChallenge" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "domain" character varying(500) NOT NULL, "token" character varying(500) NOT NULL, "challenge" character varying NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_72f3dfbc2c52c2d9442b5dbfec2" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fe7dd70f059b5b9bd0452d3ebf" ON "AcmeChallenge" ("domain") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_936afe487b8f9da2f6aae1d11d" ON "AcmeChallenge" ("token") `, + ); + await queryRunner.query( + `CREATE TABLE "Reseller" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "resellerId" character varying(100) NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(100) NOT NULL, "username" character varying(100) NOT NULL, "password" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "changePlanLink" character varying(100), "hidePhoneNumberOnSignup" boolean, "enableTelemetryFeatures" boolean DEFAULT false, CONSTRAINT "PK_c6ba1f78a2b2458d85c318e0c0f" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE TABLE "ResellerPlan" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "resellerId" uuid NOT NULL, "planId" character varying(100) NOT NULL, "name" character varying(100) NOT NULL, "planType" character varying(100) NOT NULL, "description" character varying(100) NOT NULL, "monitorLimit" integer, "teamMemberLimit" integer, "createdByUserId" uuid, "deletedByUserId" uuid, "otherFeatures" character varying(100), CONSTRAINT "PK_92b9172e490226e492c5cede708" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fc269bd109ac405a458b2acc67" ON "ResellerPlan" ("resellerId") `, + ); + await queryRunner.query( + `CREATE TABLE "Project" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "paymentProviderPlanId" character varying(100), "paymentProviderSubscriptionId" character varying(100), "paymentProviderMeteredSubscriptionId" character varying(100), "paymentProviderSubscriptionSeats" integer, "trialEndsAt" TIMESTAMP WITH TIME ZONE, "paymentProviderCustomerId" character varying(100), "paymentProviderSubscriptionStatus" character varying(100), "paymentProviderMeteredSubscriptionStatus" character varying(100), "paymentProviderPromoCode" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, "isBlocked" boolean NOT NULL DEFAULT false, "isFeatureFlagMonitorGroupsEnabled" boolean DEFAULT false, "unpaidSubscriptionNotificationCount" smallint, "paymentFailedDate" TIMESTAMP WITH TIME ZONE, "paymentSuccessDate" TIMESTAMP WITH TIME ZONE, "workflowRunsInLast30Days" integer, "requireSsoForLogin" boolean NOT NULL DEFAULT false, "activeMonitorsLimit" integer, "seatLimit" integer, "currentActiveMonitorsCount" integer, "smsOrCallCurrentBalanceInUSDCents" integer NOT NULL DEFAULT '0', "autoRechargeSmsOrCallByBalanceInUSD" integer NOT NULL DEFAULT '20', "autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD" integer NOT NULL DEFAULT '10', "enableSmsNotifications" boolean NOT NULL DEFAULT false, "enableCallNotifications" boolean NOT NULL DEFAULT false, "enableAutoRechargeSmsOrCallBalance" boolean NOT NULL DEFAULT false, "lowCallAndSMSBalanceNotificationSentToOwners" boolean NOT NULL DEFAULT false, "failedCallAndSMSBalanceChargeNotificationSentToOwners" boolean NOT NULL DEFAULT false, "notEnabledSmsOrCallNotificationSentToOwners" boolean NOT NULL DEFAULT false, "planName" character varying(100), "lastActive" TIMESTAMP WITH TIME ZONE, "createdOwnerPhone" character varying(30), "createdOwnerEmail" character varying(100), "createdOwnerName" character varying(50), "utmSource" character varying(500), "utmMedium" character varying(500), "utmCampaign" character varying(500), "utmTerm" character varying(500), "utmContent" character varying(500), "utmUrl" character varying(500), "createdOwnerCompanyName" character varying(100), "resellerId" uuid, "resellerPlanId" uuid, "resellerLicenseId" character varying(100), CONSTRAINT "UQ_38f5c1d2bf0743a868288fc8e64" UNIQUE ("slug"), CONSTRAINT "PK_08138e668356f0fe5d9fb43ba6a" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_38f5c1d2bf0743a868288fc8e6" ON "Project" ("slug") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4ee6a519d48b26fe2a78fdc1c9" ON "Project" ("resellerId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b5ee87614c184778810283c299" ON "Project" ("resellerPlanId") `, + ); + await queryRunner.query( + `CREATE TABLE "ApiKey" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, "apiKey" uuid NOT NULL, CONSTRAINT "PK_e71aa2e686b8cc09ac083b362a1" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_bb1019f0078a21b4854f5cb3ed" ON "ApiKey" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0e26b8c243b0ed1395bd52aaaf" ON "ApiKey" ("name") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_17bec9c02846cdf64b5d6bb71f" ON "ApiKey" ("apiKey") `, + ); + await queryRunner.query( + `CREATE TABLE "Label" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, CONSTRAINT "PK_fa0b1a36a5943fe60fc72b7332d" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f10d59c2ba66e085722e0053cb" ON "Label" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "ApiKeyPermission" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "apiKeyId" uuid NOT NULL, "projectId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "permission" character varying(100) NOT NULL, "isBlockPermission" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_557178680bd9b61c3fb437bbfb3" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0cf347c575f15d3836615f5325" ON "ApiKeyPermission" ("apiKeyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fb09dd7fefa9d5d44b1907be5f" ON "ApiKeyPermission" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "BillingInvoice" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "amount" numeric NOT NULL, "currencyCode" character varying(100) NOT NULL, "downloadableLink" text NOT NULL, "status" character varying(100) NOT NULL, "paymentProviderCustomerId" character varying(100) NOT NULL, "paymentProviderSubscriptionId" character varying(100), "paymentProviderInvoiceId" character varying(100) NOT NULL, CONSTRAINT "PK_44308aa9517968a8b794fae6f5f" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0ab13e9a92ce4801c37c2a0a77" ON "BillingInvoice" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "BillingPaymentMethod" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "type" character varying(100) NOT NULL, "paymentProviderPaymentMethodId" character varying(100) NOT NULL, "paymentProviderCustomerId" character varying(100) NOT NULL, "last4Digits" character varying(100) NOT NULL, "isDefault" boolean, CONSTRAINT "PK_9752e07a1ef457e672017373d13" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_db4bb9add01b7d8286869fd9a0" ON "BillingPaymentMethod" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "CallLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "toNumber" character varying(30) NOT NULL, "fromNumber" character varying(30) NOT NULL, "callData" jsonb NOT NULL, "statusMessage" character varying(500), "status" character varying(100) NOT NULL, "callCostInUSDCents" integer NOT NULL DEFAULT '0', "deletedByUserId" uuid, CONSTRAINT "PK_86bec18cba9886025ef8e24a88b" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5648767682195afaeb09098a21" ON "CallLog" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_14a8701025e30ab54de66990dc" ON "CallLog" ("toNumber") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_af110ffcec14a2770dada25c92" ON "CallLog" ("fromNumber") `, + ); + await queryRunner.query( + `CREATE TABLE "DataMigrations" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "name" character varying(100) NOT NULL, "executed" boolean NOT NULL DEFAULT true, "executedAt" TIMESTAMP WITH TIME ZONE NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_ebe23fc5dac97954a1e0295988d" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE TABLE "Domain" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "domain" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isVerified" boolean NOT NULL DEFAULT false, "domainVerificationText" character varying(100) NOT NULL, CONSTRAINT "UQ_6bf543b5b16ff1e18637a1d6e26" UNIQUE ("domainVerificationText"), CONSTRAINT "PK_4c9c7a5e777572225bde32ddd8e" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f1ce90d3f9693be29b72fabe93" ON "Domain" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "ProjectSMTPConfig" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "username" character varying(100), "password" character varying(500), "hostname" character varying(100) NOT NULL, "port" integer NOT NULL, "fromEmail" character varying(100) NOT NULL, "fromName" character varying(100) NOT NULL, "secure" boolean NOT NULL DEFAULT true, CONSTRAINT "PK_8ac366e3dfa1bd3cb1560cc8f87" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a540dab929fa6582b93f258ffe" ON "ProjectSMTPConfig" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "EmailLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "toEmail" character varying(100), "fromEmail" character varying(100), "subject" character varying(500) NOT NULL, "statusMessage" character varying(500), "status" character varying(100) NOT NULL, "projectSmtpConfigId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_ad2639709254abb94b89551e613" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7b72c5131b3dd1f3edf201a561" ON "EmailLog" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_2c92ae54071acce65fa134d855" ON "EmailLog" ("toEmail") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c4d606cbaafbdbbf5130c97058" ON "EmailLog" ("fromEmail") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_046364c162885b6ac65d5dd367" ON "EmailLog" ("projectSmtpConfigId") `, + ); + await queryRunner.query( + `CREATE TABLE "EmailVerificationToken" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "userId" uuid NOT NULL, "email" character varying(100) NOT NULL, "token" uuid NOT NULL, "expires" TIMESTAMP WITH TIME ZONE NOT NULL, "deletedByUserId" uuid, CONSTRAINT "UQ_0817e87f2e8de1019a09b1eb606" UNIQUE ("token"), CONSTRAINT "PK_527b465552328251512aea2587a" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0817e87f2e8de1019a09b1eb60" ON "EmailVerificationToken" ("token") `, + ); + await queryRunner.query( + `CREATE TABLE "GlobalConfig" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "disableSignup" boolean DEFAULT false, "isSMTPSecure" boolean, "smtpUsername" character varying(100), "smtpPassword" character varying(100), "smtpPort" integer, "smtpHost" character varying(100), "smtpFromEmail" character varying(100), "smtpFromName" character varying(100), "twilioAccountSID" character varying(100), "twilioAuthToken" character varying(100), "twilioPhoneNumber" character varying(30), "emailServerType" character varying, "sendgridApiKey" character varying, "sendgridFromEmail" character varying, "sendgridFromName" character varying, "isMasterApiKeyEnabled" boolean DEFAULT false, "masterApiKey" uuid, "adminNotificationEmail" character varying(100), CONSTRAINT "UQ_5f6e5421b62acf7ac0dc24efe1f" UNIQUE ("disableSignup"), CONSTRAINT "UQ_7f637cadeb879b83663c724ada6" UNIQUE ("isSMTPSecure"), CONSTRAINT "UQ_9cfe51eb0e99f5b3c3f4183540a" UNIQUE ("smtpUsername"), CONSTRAINT "UQ_26512a721317adaa03d37eb00d4" UNIQUE ("smtpPassword"), CONSTRAINT "UQ_ec87ad4cb593cbce6c29c63f2e5" UNIQUE ("smtpPort"), CONSTRAINT "UQ_6abdf5f1b02c3f1f4b63455b5b7" UNIQUE ("smtpHost"), CONSTRAINT "UQ_f180b8fbfd18df3fc9e09d3dc0e" UNIQUE ("smtpFromEmail"), CONSTRAINT "UQ_8cca9c2a2c1bae5509cbbbb4d4c" UNIQUE ("smtpFromName"), CONSTRAINT "UQ_ff4de79bf16e52712a4b264f906" UNIQUE ("twilioAccountSID"), CONSTRAINT "UQ_bc4b634ed3ed8854b06872115a0" UNIQUE ("twilioAuthToken"), CONSTRAINT "UQ_c223b66a0ca2fa8095cb7a6c7cc" UNIQUE ("twilioPhoneNumber"), CONSTRAINT "UQ_0be4582bb7cc1337e58f1d9b45e" UNIQUE ("emailServerType"), CONSTRAINT "UQ_1489dd2e4879912dd7caa0dee5c" UNIQUE ("sendgridApiKey"), CONSTRAINT "UQ_cc133cd137597be73a2540ece9f" UNIQUE ("sendgridFromEmail"), CONSTRAINT "UQ_6d73f5eea5f1d49c1701fb04f2d" UNIQUE ("sendgridFromName"), CONSTRAINT "UQ_7d5fa0275bdc336a22af4ab2b96" UNIQUE ("isMasterApiKeyEnabled"), CONSTRAINT "UQ_117d0b6a9f6c32be770c07f1cf6" UNIQUE ("masterApiKey"), CONSTRAINT "UQ_b99fa557d0e88c933575e7deaa9" UNIQUE ("adminNotificationEmail"), CONSTRAINT "PK_b494a3ff229268d132914e71282" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE TABLE "GreenlockCertificate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "key" character varying(500) NOT NULL, "blob" text NOT NULL, "isKeyPair" boolean NOT NULL DEFAULT false, "deletedByUserId" uuid, CONSTRAINT "PK_f7a338910a034c62b3efa5aa701" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d47fcf292f2ceae490da8404d8" ON "GreenlockCertificate" ("key") `, + ); + await queryRunner.query( + `CREATE TABLE "GreenlockChallenge" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "key" character varying(500) NOT NULL, "token" character varying(500) NOT NULL, "challenge" character varying NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_e2685e2af35bad33efc615c365c" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_36dde33ab3f3fefb63cb164c7d" ON "GreenlockChallenge" ("key") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_8358adfc13448a58e30a799f6e" ON "GreenlockChallenge" ("token") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentSeverity" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, "order" smallint NOT NULL, CONSTRAINT "PK_55c88aed1689e00cce0fdbf4d78" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_00d2f503174bf201abc6e77afd" ON "IncidentSeverity" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentState" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, "isCreatedState" boolean NOT NULL DEFAULT false, "isAcknowledgedState" boolean NOT NULL DEFAULT false, "isResolvedState" boolean NOT NULL DEFAULT false, "order" smallint NOT NULL, CONSTRAINT "PK_8d3b37bb11d8f5aa90bf6df2f1a" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3d279e530067f599f3186e3821" ON "IncidentState" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorStatus" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, "isOperationalState" boolean NOT NULL DEFAULT false, "isOfflineState" boolean NOT NULL DEFAULT false, "priority" integer NOT NULL, CONSTRAINT "PK_2d865658fb987bbe9c9b7517103" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_db1783158a23bd20dbebaae56e" ON "MonitorStatus" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "Monitor" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "monitorType" character varying(100) NOT NULL, "currentMonitorStatusId" uuid NOT NULL, "monitorSteps" jsonb, "monitoringInterval" character varying(100), "customFields" jsonb, "isOwnerNotifiedOfResourceCreation" boolean NOT NULL DEFAULT false, "disableActiveMonitoring" boolean NOT NULL DEFAULT false, "incomingRequestReceivedAt" TIMESTAMP WITH TIME ZONE, "disableActiveMonitoringBecauseOfScheduledMaintenanceEvent" boolean NOT NULL DEFAULT false, "disableActiveMonitoringBecauseOfManualIncident" boolean NOT NULL DEFAULT false, "serverMonitorRequestReceivedAt" TIMESTAMP WITH TIME ZONE, "serverMonitorSecretKey" uuid, "incomingRequestSecretKey" uuid, "incomingMonitorRequest" jsonb, "serverMonitorResponse" jsonb, CONSTRAINT "UQ_42ecc6e0ac9984cd24d5f9ddd8d" UNIQUE ("slug"), CONSTRAINT "PK_eb992d1e4e316083d3535709e43" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_996acfb590bda327843f78b7ad" ON "Monitor" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_36d301a9e41af9b6e62b0c0a02" ON "Monitor" ("name") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_42ecc6e0ac9984cd24d5f9ddd8" ON "Monitor" ("slug") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d3461ab640467c8c2100ea55c7" ON "Monitor" ("currentMonitorStatusId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_db5cc02633b36957c9be4d70c6" ON "Monitor" ("monitoringInterval") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b4c392c3163a2a32da5b401c91" ON "Monitor" ("isOwnerNotifiedOfResourceCreation") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1e0f03c5e067eeb505b2b87aa8" ON "Monitor" ("disableActiveMonitoring") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_9bfee6c29045d1c236d9395f65" ON "Monitor" ("incomingRequestReceivedAt") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ea5170227069c85269b3a6db93" ON "Monitor" ("disableActiveMonitoringBecauseOfScheduledMaintenanceEvent") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_9c8f4b9103fa6b62ff5a121f36" ON "Monitor" ("disableActiveMonitoringBecauseOfManualIncident") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_28d1e0cdf3fcf1ac30249f5d3a" ON "Monitor" ("serverMonitorRequestReceivedAt") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_899a35fe28dd2661f9c999c130" ON "Monitor" ("serverMonitorSecretKey") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_2fb8ee83943588b9c5f3358570" ON "Monitor" ("incomingRequestSecretKey") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicy" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "repeatPolicyIfNoOneAcknowledges" boolean NOT NULL DEFAULT false, "repeatPolicyIfNoOneAcknowledgesNoOfTimes" integer NOT NULL DEFAULT '0', "customFields" jsonb, CONSTRAINT "UQ_68884a658e2c47f67c9b2dd9af5" UNIQUE ("slug"), CONSTRAINT "PK_728c01bb7c570a189dcd5f8f846" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_31508550a088ba2cc843a6c90c" ON "OnCallDutyPolicy" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_570a79facd5fe1c01ccdca55ae" ON "OnCallDutyPolicy" ("name") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_68884a658e2c47f67c9b2dd9af" ON "OnCallDutyPolicy" ("slug") `, + ); + await queryRunner.query( + `CREATE TABLE "Probe" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "key" character varying NOT NULL, "name" character varying(50) NOT NULL, "description" character varying(50), "slug" character varying(100) NOT NULL, "probeVersion" character varying(30) NOT NULL, "lastAlive" TIMESTAMP WITH TIME ZONE, "iconFileId" uuid, "projectId" uuid, "deletedByUserId" uuid, "createdByUserId" uuid, "isGlobalProbe" boolean NOT NULL DEFAULT false, "shouldAutoEnableProbeOnNewMonitors" boolean NOT NULL DEFAULT false, CONSTRAINT "UQ_8f390f96121dc8772b0c29518a4" UNIQUE ("key"), CONSTRAINT "PK_41e4920c56fbb2d208bc4de73a2" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE TABLE "Incident" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "title" character varying(500) NOT NULL, "description" text, "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "currentIncidentStateId" uuid NOT NULL, "incidentSeverityId" uuid NOT NULL, "changeMonitorStatusToId" uuid, "isStatusPageSubscribersNotifiedOnIncidentCreated" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotifiedOnIncidentCreated" boolean NOT NULL DEFAULT true, "customFields" jsonb, "isOwnerNotifiedOfResourceCreation" boolean NOT NULL DEFAULT false, "rootCause" text, "createdStateLog" jsonb, "createdCriteriaId" character varying, "createdIncidentTemplateId" character varying, "createdByProbeId" uuid, "isCreatedAutomatically" boolean NOT NULL DEFAULT false, CONSTRAINT "UQ_eb410c8eb2e2eadfa5880936fe2" UNIQUE ("slug"), CONSTRAINT "PK_bc6d93c3f46bc96e209c379996b" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_eccbc31fa1f58bd051b6f7e108" ON "Incident" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5f53415da5b48954185a6c32b7" ON "Incident" ("title") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_eb410c8eb2e2eadfa5880936fe" ON "Incident" ("slug") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6592d4f7f3b260efc23fc9b4bc" ON "Incident" ("currentIncidentStateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3f28fe3b32abed354a49b26c9c" ON "Incident" ("incidentSeverityId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ed0ec4960a85240f51e6779a00" ON "Incident" ("changeMonitorStatusToId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c3ad64a7aaf39c1f7885527e24" ON "Incident" ("isOwnerNotifiedOfResourceCreation") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5218e92f700d91afe6a8db79cb" ON "Incident" ("rootCause") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ee2acd83fe08dfe3b46a533b7f" ON "Incident" ("createdCriteriaId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6ddd5e3433dcdc6832b2a93845" ON "Incident" ("createdIncidentTemplateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7e537806a80e869917ca1d7e2e" ON "Incident" ("createdByProbeId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_9cdf1c2cd13120af17445219e7a" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_59e7f6a43dbc5ee54e1a1aaaaf" ON "IncidentCustomField" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentInternalNote" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "note" text NOT NULL, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d81f41e2baba0591716e3d1f762" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ac48058b3e5f9e8361d2b8328c" ON "IncidentInternalNote" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b92e75645fd252e4c2f866047d" ON "IncidentInternalNote" ("incidentId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_582d48a4d9cce7dd74ea1dd282" ON "IncidentInternalNote" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentNoteTemplate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "note" text NOT NULL, "templateName" character varying(100) NOT NULL, "templateDescription" character varying(500) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_7c2220728bdefd8c1f0878dd3da" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_21d5bc0d24b3e5032dd391ec8d" ON "IncidentNoteTemplate" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7904e01f4867510e9cb3ba09cd" ON "IncidentNoteTemplate" ("note") `, + ); + await queryRunner.query( + `CREATE TABLE "Team" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isPermissionsEditable" boolean NOT NULL DEFAULT true, "isTeamDeleteable" boolean NOT NULL DEFAULT true, "shouldHaveAtLeastOneMember" boolean NOT NULL DEFAULT false, "isTeamEditable" boolean NOT NULL DEFAULT true, CONSTRAINT "UQ_fcdfc85e8e6b0a1f8128ce572ac" UNIQUE ("slug"), CONSTRAINT "PK_2e76d339d65d3fe4156bbcdd96b" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_baac847c494f692f03fd686e9c" ON "Team" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_319e120005dff229ac97e9e21d" ON "Team" ("name") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fcdfc85e8e6b0a1f8128ce572a" ON "Team" ("slug") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_cf830a956d75a7d10fba1d323f9" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_95f76375ccac835f815d7e926a" ON "IncidentOwnerTeam" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_389aadeb39a0806e80d4001016" ON "IncidentOwnerTeam" ("teamId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_408324d3635a826538a792422f" ON "IncidentOwnerTeam" ("incidentId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a913a00c56edac2d2e3364fb8b" ON "IncidentOwnerTeam" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_28a06b920ce7baf44968e1fa0b3" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0ee7ae6757442fba470b213015" ON "IncidentOwnerUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6311087eeb14ab51e6a1e6133f" ON "IncidentOwnerUser" ("userId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6aa9a6b46f8e044d722da8f5a7" ON "IncidentOwnerUser" ("incidentId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_8347c45d2b96d4f809bbefeb80" ON "IncidentOwnerUser" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentPublicNote" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "note" text NOT NULL, "isStatusPageSubscribersNotifiedOnNoteCreated" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotifiedOnNoteCreated" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, "postedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_d7a24846b34fe3538745bf3fcb9" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a9a77e5f286b5724f4e2280d0a" ON "IncidentPublicNote" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a6964d3aab71608daab9f20e30" ON "IncidentPublicNote" ("incidentId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_8bbc15605fce799ab2abf6532b" ON "IncidentPublicNote" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentStateTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "incidentId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "incidentStateId" uuid NOT NULL, "isStatusPageSubscribersNotified" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotified" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, "stateChangeLog" jsonb, "rootCause" text, "endsAt" TIMESTAMP WITH TIME ZONE, "startsAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_5f3add0368060e7eb60e540ea5b" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_764daa366a4e195768a49e0ee3" ON "IncidentStateTimeline" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fe2dff414a1f67260e3c518981" ON "IncidentStateTimeline" ("incidentId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ff0fca6570d47798771763533a" ON "IncidentStateTimeline" ("incidentStateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5a1a64bc4c38107b25a4bdcd17" ON "IncidentStateTimeline" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7db6b1a8fbbc9eb44c2e7f5047" ON "IncidentStateTimeline" ("rootCause") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_71c94e9f34772d46fd50e18b64" ON "IncidentStateTimeline" ("endsAt") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_dc5784aa146b249a22afe48b7e" ON "IncidentStateTimeline" ("startsAt") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentTemplate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "title" character varying(500) NOT NULL, "templateName" character varying(100) NOT NULL, "templateDescription" character varying(500) NOT NULL, "description" text, "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "incidentSeverityId" uuid, "changeMonitorStatusToId" uuid, "customFields" jsonb, CONSTRAINT "UQ_9fe9e55006c2a1f26727e479ab4" UNIQUE ("slug"), CONSTRAINT "PK_f00b9bff4a246bd76cfe7179435" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b82dafef226b0fae1ad6cb1857" ON "IncidentTemplate" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0683d65aa3e3483127671d120f" ON "IncidentTemplate" ("title") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_9fe9e55006c2a1f26727e479ab" ON "IncidentTemplate" ("slug") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ceff6a4dfdccecc4aa40dbfe91" ON "IncidentTemplate" ("incidentSeverityId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6d7627ab9d5172c66fc5019216" ON "IncidentTemplate" ("changeMonitorStatusToId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentTemplateOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "incidentTemplateId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_e466241948b2440f2683b38c865" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_cf172eb6797a64ee3750e3f3e2" ON "IncidentTemplateOwnerTeam" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e8090c6569c3a5dbd7ef7485c9" ON "IncidentTemplateOwnerTeam" ("teamId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a895b946fccb109dedd55b85f6" ON "IncidentTemplateOwnerTeam" ("incidentTemplateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_2cb2fc022da183b99cf06f0043" ON "IncidentTemplateOwnerTeam" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentTemplateOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "incidentTemplateId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_ef8497118e191717349bda8fe18" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_da9dd65b4401b954a0ea2b5c8d" ON "IncidentTemplateOwnerUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_36b9b0204f4b17063483cb7308" ON "IncidentTemplateOwnerUser" ("userId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_876dd05e0dfc64219ef5df241c" ON "IncidentTemplateOwnerUser" ("incidentTemplateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_38583ea60d2df4a525098065f3" ON "IncidentTemplateOwnerUser" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_333fe20ed9ea0c59c79e766d2ed" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1c6b61e904d8e3fec1ae719b9e" ON "MonitorCustomField" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorGroup" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "UQ_04092730e7f93ee58b544f484c5" UNIQUE ("slug"), CONSTRAINT "PK_61a04922aee7dd836f27b89a970" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_13905ff40843b11145f21e33ff" ON "MonitorGroup" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_136524b17f6dbf70f1e720e8f6" ON "MonitorGroup" ("name") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_04092730e7f93ee58b544f484c" ON "MonitorGroup" ("slug") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorGroupOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "monitorGroupId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_370b2e5defa4d1cce38673a384c" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0d7052620f268d0fa17f948a85" ON "MonitorGroupOwnerTeam" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_8f19947114087883cea771e1cb" ON "MonitorGroupOwnerTeam" ("teamId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_618bcd9015c257b0727df36fd1" ON "MonitorGroupOwnerTeam" ("monitorGroupId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0736d9a7117c25ee216507fa47" ON "MonitorGroupOwnerTeam" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorGroupOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "monitorGroupId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_436159266b7e87be025d1acedb9" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d5cfbebf8b07405652f5382e15" ON "MonitorGroupOwnerUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4b25f4a18bec8cb177e8d65c5f" ON "MonitorGroupOwnerUser" ("userId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6a4095ee3d04454071816a5bad" ON "MonitorGroupOwnerUser" ("monitorGroupId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7c88d50a62d9ba48e9578c9128" ON "MonitorGroupOwnerUser" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorGroupResource" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "monitorGroupId" uuid NOT NULL, "monitorId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_6af7cd205f96311471e5e9979b3" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_825791d5edb2403d7937f16ed9" ON "MonitorGroupResource" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_cf3dcaa746835ae36615a39d86" ON "MonitorGroupResource" ("monitorGroupId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_50b373c428cfd4566cc5caf98e" ON "MonitorGroupResource" ("monitorId") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "monitorId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_b9c6db3ddceeab34383b8f36fbe" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6a6213072d8637e6e625bc7892" ON "MonitorOwnerTeam" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_63e5bbac01d1f68c7b08f126cd" ON "MonitorOwnerTeam" ("teamId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1d5265d1f3ca2f8b8e461e4998" ON "MonitorOwnerTeam" ("monitorId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_acb20c616e3781a3a5506f89ef" ON "MonitorOwnerTeam" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "monitorId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_4d05a001b7a955f011c7933effb" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_12a3497e1404fcdbc4e8963a58" ON "MonitorOwnerUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_324f1d50d0427bbd0e3308c459" ON "MonitorOwnerUser" ("userId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a65ce9b11b2d7bde123aa7633f" ON "MonitorOwnerUser" ("monitorId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ab449be4e08009bfc2e68f5c78" ON "MonitorOwnerUser" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorProbe" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "probeId" uuid NOT NULL, "monitorId" uuid NOT NULL, "lastPingAt" TIMESTAMP WITH TIME ZONE, "nextPingAt" TIMESTAMP WITH TIME ZONE, "createdByUserId" uuid, "deletedByUserId" uuid, "isEnabled" boolean NOT NULL DEFAULT true, "lastMonitoringLog" jsonb, CONSTRAINT "PK_6888f9282ecc31fe8d6d089e015" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d887935f224b896ce64872c37c" ON "MonitorProbe" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_2d50a0e0e624369e7f90a62e8d" ON "MonitorProbe" ("probeId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a540272f483ef1de68f7e64748" ON "MonitorProbe" ("monitorId") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorSecret" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "secretValue" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_0ea8e83c6410e83ceb84bfbab3d" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7c3629c5ae14e97bede3bc548e" ON "MonitorSecret" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorStatusTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "monitorId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "monitorStatusId" uuid NOT NULL, "isOwnerNotified" boolean NOT NULL DEFAULT false, "statusChangeLog" jsonb, "rootCause" text, "endsAt" TIMESTAMP WITH TIME ZONE, "startsAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_266e2a64b41ff8497a2e8461ef4" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_80213eb3f228f1e3d423f5127e" ON "MonitorStatusTimeline" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c08a7c1ef8d511b335a991aac4" ON "MonitorStatusTimeline" ("monitorId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_574feb4161c5216c2c7ee0faaf" ON "MonitorStatusTimeline" ("monitorStatusId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0ea6641468483b2ace63144031" ON "MonitorStatusTimeline" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_01ac1d1ef9e72aeb6dac6575dd" ON "MonitorStatusTimeline" ("rootCause") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_26f6632b71574ff4dbe87c352d" ON "MonitorStatusTimeline" ("endsAt") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_2606f4914507b3471f40864348" ON "MonitorStatusTimeline" ("startsAt") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_42e1ccec0ecdd48a12a0f235a19" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e791474e098f276eda27704b47" ON "OnCallDutyPolicyCustomField" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyEscalationRule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "escalateAfterInMinutes" integer, "order" integer NOT NULL, CONSTRAINT "PK_560358a01ddcdec1b21fbf76409" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d45a545669dc46da25cc60d1df" ON "OnCallDutyPolicyEscalationRule" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b90c1cda36981c41e3965a9380" ON "OnCallDutyPolicyEscalationRule" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e3e66222024c1119865f3eae0f" ON "OnCallDutyPolicyEscalationRule" ("name") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4020168f8d1ee248b8d4bd6293" ON "OnCallDutyPolicyEscalationRule" ("escalateAfterInMinutes") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicySchedule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "UQ_f2f09fcba2e6eabe61d16aa2423" UNIQUE ("slug"), CONSTRAINT "PK_d2a1948140b469f311129c52db8" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0e1b7c3c3e8305a10716cdb8d6" ON "OnCallDutyPolicySchedule" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_15bea18a6b3f9730ce6fad2804" ON "OnCallDutyPolicySchedule" ("name") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f2f09fcba2e6eabe61d16aa242" ON "OnCallDutyPolicySchedule" ("slug") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyEscalationRuleSchedule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "onCallDutyPolicyScheduleId" uuid, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_86a8318c50e27e228aad5c544be" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_efa24aa8feb92d4e15a707c20e" ON "OnCallDutyPolicyEscalationRuleSchedule" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_651a4ab9e3cbb20f6b62a87a6b" ON "OnCallDutyPolicyEscalationRuleSchedule" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_089081f83ef22cdba5a0903ce5" ON "OnCallDutyPolicyEscalationRuleSchedule" ("onCallDutyPolicyEscalationRuleId") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyEscalationRuleTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "teamId" uuid, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_6aec616275791acb65c0c81fe05" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c8aff8439fbfb07e7388aa9011" ON "OnCallDutyPolicyEscalationRuleTeam" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f5121f361345d858ce740a55a2" ON "OnCallDutyPolicyEscalationRuleTeam" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0e7d4060e2fabe0957b9fedb42" ON "OnCallDutyPolicyEscalationRuleTeam" ("onCallDutyPolicyEscalationRuleId") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyEscalationRuleUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_8aded5412d5176f07878de3fc87" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b15552e664640f67346193598a" ON "OnCallDutyPolicyEscalationRuleUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_9ca3fbb66842324aa987d4c972" ON "OnCallDutyPolicyEscalationRuleUser" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_bff6f4ae726b5c5cbae10e7d74" ON "OnCallDutyPolicyEscalationRuleUser" ("onCallDutyPolicyEscalationRuleId") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyExecutionLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "triggeredByIncidentId" uuid, "status" character varying(100) NOT NULL, "statusMessage" character varying(500) NOT NULL, "userNotificationEventType" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "acknowledgedByUserId" uuid, "acknowledgedAt" TIMESTAMP WITH TIME ZONE, "acknowledgedByTeamId" uuid, "lastExecutedEscalationRuleOrder" integer, "lastEscalationRuleExecutedAt" TIMESTAMP WITH TIME ZONE, "lastExecutedEscalationRuleId" uuid, "executeNextEscalationRuleInMinutes" integer, "onCallPolicyExecutionRepeatCount" integer NOT NULL DEFAULT '1', CONSTRAINT "PK_516d5244ab7e4b73b5cc3a7d1ba" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_00e9676d39eeb807de70443051" ON "OnCallDutyPolicyExecutionLog" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e4bb332263960531a4c9e2d425" ON "OnCallDutyPolicyExecutionLog" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fbb50489ef5eb354f46d479e2a" ON "OnCallDutyPolicyExecutionLog" ("lastExecutedEscalationRuleOrder") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_296e2be818e3fba28e43b457fb" ON "OnCallDutyPolicyExecutionLog" ("lastEscalationRuleExecutedAt") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d4a0ffc5e9e698bb2612ba0e55" ON "OnCallDutyPolicyExecutionLog" ("lastExecutedEscalationRuleId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6ad07641fe00f29edc65716aca" ON "OnCallDutyPolicyExecutionLog" ("executeNextEscalationRuleInMinutes") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a217798f1dbf12d31b498a1020" ON "OnCallDutyPolicyExecutionLog" ("onCallPolicyExecutionRepeatCount") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyExecutionLogTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "triggeredByIncidentId" uuid NOT NULL, "onCallDutyPolicyExecutionLogId" uuid NOT NULL, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "userNotificationEventType" character varying(100) NOT NULL, "alertSentToUserId" uuid, "userBelongsToTeamId" uuid, "onCallDutyScheduleId" uuid, "statusMessage" character varying(500) NOT NULL, "status" character varying(100) NOT NULL, "createdByUserId" uuid, "isAcknowledged" boolean, "acknowledgedAt" TIMESTAMP WITH TIME ZONE, "deletedByUserId" uuid, CONSTRAINT "PK_720de860de392ed64dc9b2a75d1" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_591b7ed73c964e105bfecc6fd6" ON "OnCallDutyPolicyExecutionLogTimeline" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c5a798ca667fedda71d4ed5465" ON "OnCallDutyPolicyExecutionLogTimeline" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_90119ec7f77fa2efd82261e044" ON "OnCallDutyPolicyExecutionLogTimeline" ("triggeredByIncidentId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_872c2f6a9739bab1b57b6d51ea" ON "OnCallDutyPolicyExecutionLogTimeline" ("onCallDutyPolicyExecutionLogId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0ad4222a4c48b8a64e3a58b351" ON "OnCallDutyPolicyExecutionLogTimeline" ("onCallDutyPolicyEscalationRuleId") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyScheduleLayer" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyScheduleId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "order" integer NOT NULL, "startsAt" TIMESTAMP WITH TIME ZONE NOT NULL, "rotation" jsonb NOT NULL DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}', "handOffTime" TIMESTAMP WITH TIME ZONE NOT NULL, "restrictionTimes" jsonb NOT NULL DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}', CONSTRAINT "PK_d7a261a9f6c6b17e582bf0c3639" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3b892ef36671f1ea1c8457a96d" ON "OnCallDutyPolicyScheduleLayer" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6fa6574a45cf1352c5a3b96251" ON "OnCallDutyPolicyScheduleLayer" ("onCallDutyPolicyScheduleId") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyScheduleLayerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "onCallDutyPolicyScheduleId" uuid NOT NULL, "onCallDutyPolicyScheduleLayerId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "order" integer NOT NULL, "userId" uuid NOT NULL, CONSTRAINT "PK_1c9f5d86c8045253f4ef592db0e" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_08afccd6cbbd1a7015d4fe25e3" ON "OnCallDutyPolicyScheduleLayerUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_9b36bba6d9898331920805a29c" ON "OnCallDutyPolicyScheduleLayerUser" ("onCallDutyPolicyScheduleId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_41f4ecc29351e1a406e83b30a9" ON "OnCallDutyPolicyScheduleLayerUser" ("onCallDutyPolicyScheduleLayerId") `, + ); + await queryRunner.query( + `CREATE TABLE "ProjectCallSMSConfig" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "twilioAccountSID" character varying(100), "twilioAuthToken" character varying(100), "twilioPhoneNumber" character varying(30), CONSTRAINT "UQ_0886139eac04ad49627e446d477" UNIQUE ("twilioAccountSID"), CONSTRAINT "UQ_2eb1a240d549a7701b6e82d2f94" UNIQUE ("twilioAuthToken"), CONSTRAINT "UQ_50235223d7fd7b0c27063bfb08e" UNIQUE ("twilioPhoneNumber"), CONSTRAINT "PK_3661ce6081b280d86a68821d8d4" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_20334b9571a6cd1a871e70d8e7" ON "ProjectCallSMSConfig" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "ProjectSSO" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying NOT NULL, "signatureMethod" character varying(100) NOT NULL, "digestMethod" character varying(100) NOT NULL, "signOnURL" text NOT NULL, "issuerURL" text NOT NULL, "publicCertificate" text NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isEnabled" boolean NOT NULL DEFAULT false, "isTested" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_cba071f29cefd68a7109c987dc0" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_be9e6751765501ea1db126fcb2" ON "ProjectSSO" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "PromoCode" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "promoCodeId" character varying(100) NOT NULL, "planType" character varying(100), "userEmail" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, "resellerId" uuid, "resellerPlanId" uuid, "resellerLicenseId" character varying(100), "isPromoCodeUsed" boolean NOT NULL DEFAULT false, "promoCodeUsedAt" TIMESTAMP WITH TIME ZONE, "projectId" uuid, CONSTRAINT "PK_80093d5bf3e6bc69726124cf0ef" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6dcdf97c0834dd44b4f2c93e66" ON "PromoCode" ("resellerId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7aecf4b1ae3e45647cb911f4c1" ON "PromoCode" ("resellerPlanId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a7b405e2a9ae144be016bcf973" ON "PromoCode" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceState" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "color" character varying(7) NOT NULL, "isScheduledState" boolean NOT NULL DEFAULT false, "isOngoingState" boolean NOT NULL DEFAULT false, "isEndedState" boolean NOT NULL DEFAULT false, "isResolvedState" boolean NOT NULL DEFAULT false, "order" smallint NOT NULL, CONSTRAINT "PK_6a6e85bc85c65a673855bd5fcc4" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_57a31fb2a5e4caa223d2506a4e" ON "ScheduledMaintenanceState" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPage" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "pageTitle" character varying(100), "pageDescription" character varying(500), "description" character varying(500), "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "faviconFileId" uuid, "logoFileId" uuid, "coverImageFileId" uuid, "headerHTML" text, "footerHTML" text, "customCSS" text, "customJavaScript" text, "isPublicStatusPage" boolean NOT NULL DEFAULT true, "showIncidentLabelsOnStatusPage" boolean NOT NULL DEFAULT false, "showScheduledEventLabelsOnStatusPage" boolean NOT NULL DEFAULT false, "enableSubscribers" boolean NOT NULL DEFAULT true, "enableEmailSubscribers" boolean NOT NULL DEFAULT true, "allowSubscribersToChooseResources" boolean NOT NULL DEFAULT false, "enableSmsSubscribers" boolean NOT NULL DEFAULT false, "copyrightText" character varying, "customFields" jsonb, "requireSsoForLogin" boolean NOT NULL DEFAULT false, "smtpConfigId" uuid, "callSmsConfigId" uuid, "isOwnerNotifiedOfResourceCreation" boolean NOT NULL DEFAULT false, "showIncidentHistoryInDays" integer NOT NULL DEFAULT '14', "showAnnouncementHistoryInDays" integer NOT NULL DEFAULT '14', "showScheduledEventHistoryInDays" integer NOT NULL DEFAULT '14', "overviewPageDescription" text, "hidePoweredByOneUptimeBranding" boolean NOT NULL DEFAULT false, "defaultBarColor" character varying(7), CONSTRAINT "UQ_4cf5977515ca677e569942fb096" UNIQUE ("slug"), CONSTRAINT "PK_6a581c0dbe022a59d4d6e197332" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_34a4c35e0d7afe6f023825a68c" ON "StatusPage" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5f7df6dd7b1a85b933bd953b47" ON "StatusPage" ("name") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4cf5977515ca677e569942fb09" ON "StatusPage" ("slug") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_61944d851b4a7213d79ef28174" ON "StatusPage" ("smtpConfigId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4e347d3f99b67dacd149beaf61" ON "StatusPage" ("callSmsConfigId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b8a8b32e0d7e06ed8a9a3171ab" ON "StatusPage" ("isOwnerNotifiedOfResourceCreation") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7213cf79fce2db23927de0aac0" ON "StatusPage" ("showIncidentHistoryInDays") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_97509c6e7f41b59c5447cec669" ON "StatusPage" ("showAnnouncementHistoryInDays") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_cfcd002648dcf692a2e126ab05" ON "StatusPage" ("showScheduledEventHistoryInDays") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b5138c4c7dc1c36ef592af784f" ON "StatusPage" ("overviewPageDescription") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenance" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "title" character varying(100) NOT NULL, "description" text, "slug" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "currentScheduledMaintenanceStateId" uuid NOT NULL, "changeMonitorStatusToId" uuid, "startsAt" TIMESTAMP WITH TIME ZONE NOT NULL, "endsAt" TIMESTAMP WITH TIME ZONE NOT NULL, "isStatusPageSubscribersNotifiedOnEventScheduled" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotifiedOnEventCreated" boolean NOT NULL DEFAULT true, "shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing" boolean NOT NULL DEFAULT true, "shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded" boolean NOT NULL DEFAULT true, "customFields" jsonb, "isOwnerNotifiedOfResourceCreation" boolean NOT NULL DEFAULT false, CONSTRAINT "UQ_e0d4bcb3e28628a47b8b55ead8c" UNIQUE ("slug"), CONSTRAINT "PK_be50d5233ebfd8db940db9e50b6" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4059dd569d6a482062352bf266" ON "ScheduledMaintenance" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_20f04dff3b9d1f3d62985dd9de" ON "ScheduledMaintenance" ("title") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e0d4bcb3e28628a47b8b55ead8" ON "ScheduledMaintenance" ("slug") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_883038abda021ce79fa838d027" ON "ScheduledMaintenance" ("currentScheduledMaintenanceStateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fab9cc7e6ffcf02872fccfab97" ON "ScheduledMaintenance" ("changeMonitorStatusToId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c8306d145e77473ee7ac859a0d" ON "ScheduledMaintenance" ("isOwnerNotifiedOfResourceCreation") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_b4c186084a3be506bf61ead8cda" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3621c488327e1c00518aa4e881" ON "ScheduledMaintenanceCustomField" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceInternalNote" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "note" text NOT NULL, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_277bd22df1a0a0292a6361ef165" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d79c49a0a613a6b432fd400e69" ON "ScheduledMaintenanceInternalNote" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a53ef45aebd4a6a6e7dde7f896" ON "ScheduledMaintenanceInternalNote" ("scheduledMaintenanceId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_39a27db645744ef9177d4ab7c9" ON "ScheduledMaintenanceInternalNote" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceNoteTemplate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "note" text NOT NULL, "templateName" character varying(100) NOT NULL, "templateDescription" character varying(500) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_2d15584043cc151b1291fb02c26" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b4cb4c1312eb72459907e1bbe9" ON "ScheduledMaintenanceNoteTemplate" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_49d4245d0066dc1e14a63a4234" ON "ScheduledMaintenanceNoteTemplate" ("note") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_260c660b0cb76b661f66d873f40" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e3053b1725658b4a120b4e3185" ON "ScheduledMaintenanceOwnerTeam" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1206beb611e0779ce2248ecbae" ON "ScheduledMaintenanceOwnerTeam" ("teamId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1251fb7d4a4bf8586f2bd1528e" ON "ScheduledMaintenanceOwnerTeam" ("scheduledMaintenanceId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_cbe4eac5f23c115ddd4a695747" ON "ScheduledMaintenanceOwnerTeam" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_cd8d017d0e0ec04d7c17a081c7d" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_518742c7037d9a38a6594dc18a" ON "ScheduledMaintenanceOwnerUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_be6a25806925f93b8949e61929" ON "ScheduledMaintenanceOwnerUser" ("userId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_967c33f7bce5de522c1d1a80e7" ON "ScheduledMaintenanceOwnerUser" ("scheduledMaintenanceId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5d71879db35e7c4104b56bef09" ON "ScheduledMaintenanceOwnerUser" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenancePublicNote" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "note" text NOT NULL, "isStatusPageSubscribersNotifiedOnNoteCreated" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotifiedOnNoteCreated" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, "postedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_62d669cf499354d7c8438f76f37" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_73c6737ab4a7718c45932bffad" ON "ScheduledMaintenancePublicNote" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_937aabd4adbfce78663406a248" ON "ScheduledMaintenancePublicNote" ("scheduledMaintenanceId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0b7ec5df94e08afda7569ea1ff" ON "ScheduledMaintenancePublicNote" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceStateTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "scheduledMaintenanceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "scheduledMaintenanceStateId" uuid NOT NULL, "isStatusPageSubscribersNotified" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotified" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, "endsAt" TIMESTAMP WITH TIME ZONE, "startsAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_ca5a043c4ab040e7ae65eaf6eb1" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4faf556988f5b6a755ef2e85ae" ON "ScheduledMaintenanceStateTimeline" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_411146017bcfe98bbe028b8d15" ON "ScheduledMaintenanceStateTimeline" ("scheduledMaintenanceId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7c0f750d3a964180d1e1efa16e" ON "ScheduledMaintenanceStateTimeline" ("scheduledMaintenanceStateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_58aa5722dde40c062793ede637" ON "ScheduledMaintenanceStateTimeline" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_978cd5638c6e44186cbd1099d9" ON "ScheduledMaintenanceStateTimeline" ("endsAt") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_590161c4f8e0b63e7ed3fd2163" ON "ScheduledMaintenanceStateTimeline" ("startsAt") `, + ); + await queryRunner.query( + `CREATE TABLE "ShortLink" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "shortId" character varying(100) NOT NULL, "link" text NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_7ddbabbe684e03ba2d9e508d2a1" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_339f8fe3bc6fb4440541cc61a4" ON "ShortLink" ("shortId") `, + ); + await queryRunner.query( + `CREATE TABLE "SmsLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "toNumber" character varying(30) NOT NULL, "fromNumber" character varying(30) NOT NULL, "smsText" text, "statusMessage" character varying(500), "status" character varying(100) NOT NULL, "smsCostInUSDCents" integer NOT NULL DEFAULT '0', "deletedByUserId" uuid, CONSTRAINT "PK_ce409036fe144b388c56688d33e" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a30bbda7f5480e498ebc609663" ON "SmsLog" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_12854d2b71004825924a476dfc" ON "SmsLog" ("toNumber") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_68d0fe13df157c0a93d1ff6fa1" ON "SmsLog" ("fromNumber") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageAnnouncement" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "title" character varying(100) NOT NULL, "showAnnouncementAt" TIMESTAMP WITH TIME ZONE NOT NULL, "endAnnouncementAt" TIMESTAMP WITH TIME ZONE NOT NULL, "description" text NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isStatusPageSubscribersNotified" boolean NOT NULL DEFAULT false, "shouldStatusPageSubscribersBeNotified" boolean NOT NULL DEFAULT true, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_4e9a99bb763b7be65ef01a2f40f" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5e9c5a7393ac9aa477d625de67" ON "StatusPageAnnouncement" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3c5cbb3fcaf6be56918520501c" ON "StatusPageAnnouncement" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageCustomField" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying(500), "type" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_8b102bbed53bc8f5793a1411469" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4f5ae90bc48a0ddeb50cd009aa" ON "StatusPageCustomField" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageDomain" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "domainId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "subdomain" character varying(100) NOT NULL, "fullDomain" character varying(100) NOT NULL, "createdByUserId" uuid, "cnameVerificationToken" character varying(100) NOT NULL, "isCnameVerified" boolean NOT NULL DEFAULT false, "isSslOrdered" boolean NOT NULL DEFAULT false, "isSslProvisioned" boolean NOT NULL DEFAULT false, "deletedByUserId" uuid, CONSTRAINT "PK_e9ae7614164ee86237ed0b64a1c" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_40cca185c8cf933c04a0534676" ON "StatusPageDomain" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7fab5bc54a8f36eac8f31c8256" ON "StatusPageDomain" ("domainId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_842a66fcb103388fcedffef75f" ON "StatusPageDomain" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageFooterLink" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "title" character varying(100) NOT NULL, "link" character varying(100) NOT NULL, "createdByUserId" uuid, "order" integer NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_73ac6c05ace86d6b2f1a1f9daf6" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5f8f65447c9b881860bf742dc9" ON "StatusPageFooterLink" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1a80c698b2205074f53376d631" ON "StatusPageFooterLink" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageGroup" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" text, "createdByUserId" uuid, "order" integer NOT NULL, "isExpandedByDefault" boolean NOT NULL DEFAULT true, "deletedByUserId" uuid, CONSTRAINT "PK_dfba3695e73b92b24f3408b0040" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4a96c34f030a6e39218352a947" ON "StatusPageGroup" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5dbcfd7d38e5ea7a78a6a78a33" ON "StatusPageGroup" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageHeaderLink" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "title" character varying(100) NOT NULL, "link" character varying(100) NOT NULL, "createdByUserId" uuid, "order" integer NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_11c21c610618e5f36a5d04bb2a4" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_70d70692cbe9d9be188723df4f" ON "StatusPageHeaderLink" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_99c017b6ced8da63abdfbb506e" ON "StatusPageHeaderLink" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageHistoryChartBarColorRule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "uptimePercentGreaterThanOrEqualTo" numeric NOT NULL, "barColor" character varying(7) NOT NULL, "createdByUserId" uuid, "order" integer NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_c91ba8fce53892bef71f7876abf" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a65f8fabf888d227d64570f52b" ON "StatusPageHistoryChartBarColorRule" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_54edc7ff7a74a0310a512c5389" ON "StatusPageHistoryChartBarColorRule" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d0d67c8db3a8eb42aeb18819c52" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_9fac66064d88c514d2e5503237" ON "StatusPageOwnerTeam" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f60296efefe379693bfd55a776" ON "StatusPageOwnerTeam" ("teamId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6a98e42d8df3ba84bd0f79da55" ON "StatusPageOwnerTeam" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_14768105dd06f0e3e75ec5b051" ON "StatusPageOwnerTeam" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isOwnerNotified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_449db4a6703eb1dd929d0d48cca" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c28e05c08656f8aa756734c37c" ON "StatusPageOwnerUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3c59f811e0660c5522e45e85b6" ON "StatusPageOwnerUser" ("userId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_69b1659abafe656563259784d0" ON "StatusPageOwnerUser" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_feea72d826c4bf508a95c7c757" ON "StatusPageOwnerUser" ("isOwnerNotified") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPagePrivateUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "email" character varying(100), "password" character varying(64), "resetPasswordToken" character varying(100), "resetPasswordExpires" TIMESTAMP WITH TIME ZONE, "lastActive" TIMESTAMP WITH TIME ZONE, "createdByUserId" uuid, "isSsoUser" boolean NOT NULL DEFAULT false, "deletedByUserId" uuid, CONSTRAINT "PK_6a541c0c9e0538dcc1499df13ae" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1ce45fe77324ede75166d0f57d" ON "StatusPagePrivateUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0589c51161d13b752fed41a319" ON "StatusPagePrivateUser" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageResource" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "monitorId" uuid, "monitorGroupId" uuid, "statusPageGroupId" uuid, "displayName" character varying(100) NOT NULL, "displayDescription" text, "displayTooltip" character varying(500), "showCurrentStatus" boolean NOT NULL DEFAULT true, "showUptimePercent" boolean NOT NULL DEFAULT false, "uptimePercentPrecision" character varying, "showStatusHistoryChart" boolean NOT NULL DEFAULT true, "createdByUserId" uuid, "order" integer NOT NULL, "deletedByUserId" uuid, CONSTRAINT "PK_31a5bda3b231f58c41603a56180" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_8fba35fb87a0ad6037eb8fc804" ON "StatusPageResource" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ade3f7acf902dcb313d230ca1f" ON "StatusPageResource" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b1c4fe08e1d90ae4d26d934653" ON "StatusPageResource" ("monitorId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a55bb812676bff276cef1f14c8" ON "StatusPageResource" ("monitorGroupId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_9b61276c47d6091295dec9e535" ON "StatusPageResource" ("statusPageGroupId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageSSO" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying NOT NULL, "signatureMethod" character varying(100) NOT NULL, "digestMethod" character varying(100) NOT NULL, "signOnURL" text NOT NULL, "issuerURL" text NOT NULL, "publicCertificate" text NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isEnabled" boolean NOT NULL DEFAULT false, "isTested" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_55453332aa136bd5e3962a1abb1" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1d63fa142dd4175ef256f21d2a" ON "StatusPageSSO" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_dc05de7939af3ada1567fc7106" ON "StatusPageSSO" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageSubscriber" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "statusPageId" uuid NOT NULL, "subscriberEmail" character varying(100), "subscriberPhone" character varying(30), "subscriberWebhook" character varying, "createdByUserId" uuid, "deletedByUserId" uuid, "isUnsubscribed" boolean NOT NULL DEFAULT false, "isSubscribedToAllResources" boolean NOT NULL DEFAULT true, CONSTRAINT "PK_9f289c2fb525ab8f6cefec03923" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6adf943966e01699e86117d2e3" ON "StatusPageSubscriber" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_217b295d5882faa6cf3418ed81" ON "StatusPageSubscriber" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "TeamMember" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "teamId" uuid, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "hasAcceptedInvitation" boolean NOT NULL DEFAULT false, "invitationAcceptedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_6f582955f553c7e3035937933ca" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3cc297d538f01065f9925cfb11" ON "TeamMember" ("teamId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fd952f76f5a5297ce69a9a588e" ON "TeamMember" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "TeamPermission" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "permission" character varying(100) NOT NULL, "isBlockPermission" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_34aa36d573354d1f1e7901c4b11" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_78293e9cc1746e5f29ccccfdfc" ON "TeamPermission" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5064c0bdc8ff238952f9a2acf4" ON "TeamPermission" ("teamId") `, + ); + await queryRunner.query( + `CREATE TABLE "TelemetryService" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "telemetryServiceToken" uuid NOT NULL, "retainTelemetryDataForDays" integer DEFAULT '15', "serviceColor" character varying, CONSTRAINT "PK_0bb1c96ecbe42ec7d65544eaecc" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3a3321fd538aa014aa5e4f3522" ON "TelemetryService" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6c89ae3af06376fe3411cf8295" ON "TelemetryService" ("telemetryServiceToken") `, + ); + await queryRunner.query( + `CREATE TABLE "TelemetryUsageBilling" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "day" character varying(100) NOT NULL, "productType" character varying(100) NOT NULL, "retainTelemetryDataForDays" integer DEFAULT '15', "dataIngestedInGB" numeric NOT NULL, "totalCostInUSD" numeric NOT NULL, "isReportedToBillingProvider" boolean NOT NULL DEFAULT false, "telemetryServiceId" uuid NOT NULL, "reportedToBillingProviderAt" TIMESTAMP WITH TIME ZONE, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_218b46ea3e545bc0c017d7a5d81" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5670be95a9496b9380b7c0d793" ON "TelemetryUsageBilling" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_91333210492e5d2f334231468a" ON "TelemetryUsageBilling" ("telemetryServiceId") `, + ); + await queryRunner.query( + `CREATE TABLE "UserCall" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "phone" character varying(30) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "isVerified" boolean NOT NULL DEFAULT false, "verificationCode" character varying(100) NOT NULL, CONSTRAINT "PK_6c486fc3456229565b1dbf1ad89" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_db4e522c086be556e5101c4e91" ON "UserCall" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4d90bd0e309ad43c4541bb428e" ON "UserCall" ("userId") `, + ); + await queryRunner.query( + `CREATE TABLE "UserEmail" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "email" character varying(100) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "isVerified" boolean NOT NULL DEFAULT false, "verificationCode" character varying(100) NOT NULL, CONSTRAINT "PK_72aad6f19ecf4ee9d19253b8b4e" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e6beed22b36201aea1d70ba0d7" ON "UserEmail" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1f713c701d85c69f706e4e82b8" ON "UserEmail" ("userId") `, + ); + await queryRunner.query( + `CREATE TABLE "UserSMS" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "phone" character varying(30) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "isVerified" boolean NOT NULL DEFAULT false, "verificationCode" character varying(100) NOT NULL, CONSTRAINT "PK_f46801e2914a3e668ccf632f29b" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6439689a29a2192708e3f3603d" ON "UserSMS" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3cb16b5c2d69dbdc812247788f" ON "UserSMS" ("userId") `, + ); + await queryRunner.query( + `CREATE TABLE "UserNotificationRule" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "ruleType" character varying(100) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "userCallId" uuid, "userSmsId" uuid, "userEmailId" uuid, "notifyAfterMinutes" integer NOT NULL DEFAULT '0', "incidentSeverityId" uuid, CONSTRAINT "PK_c40ab942792cd8e5ad426d95d93" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_69d439e9f60f05ae16732c4999" ON "UserNotificationRule" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_32efb91af1b432a25ceb55bc0d" ON "UserNotificationRule" ("userId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ec8e7a39fcd38d0ea2d40b8afa" ON "UserNotificationRule" ("userCallId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_32ca4397660dafe0cab7d03e5b" ON "UserNotificationRule" ("userSmsId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b407063ee43233b0cc8e9106cb" ON "UserNotificationRule" ("userEmailId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f1a5912cdf877c89121a3090cd" ON "UserNotificationRule" ("notifyAfterMinutes") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_bf703b216d7f59424302dc5d70" ON "UserNotificationRule" ("incidentSeverityId") `, + ); + await queryRunner.query( + `CREATE TABLE "UserNotificationSetting" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "eventType" character varying(100) NOT NULL, "userId" uuid, "createdByUserId" uuid, "deletedByUserId" uuid, "alertByEmail" boolean NOT NULL DEFAULT false, "alertBySMS" boolean NOT NULL DEFAULT false, "alertByCall" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_e79f19e013370f8cf428f8bc9e4" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6f110fc752889f922d6a3c57a5" ON "UserNotificationSetting" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e2565b0aa7d7e015fb6685afed" ON "UserNotificationSetting" ("userId") `, + ); + await queryRunner.query( + `CREATE TABLE "UserOnCallLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "userId" uuid NOT NULL, "userBelongsToTeamId" uuid, "projectId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "onCallDutyPolicyExecutionLogId" uuid NOT NULL, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "triggeredByIncidentId" uuid NOT NULL, "status" character varying(100) NOT NULL, "userNotificationEventType" character varying(100) NOT NULL, "onCallDutyPolicyExecutionLogTimelineId" uuid NOT NULL, "statusMessage" character varying(500) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "acknowledgedByUserId" uuid, "acknowledgedAt" TIMESTAMP WITH TIME ZONE, "executedNotificationRules" jsonb, "onCallDutyScheduleId" uuid, CONSTRAINT "PK_61d65627421be2a8a7e0486b054" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ea993eab5c402623b61203e325" ON "UserOnCallLog" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4cd37d481ef366d975c6a7cd9b" ON "UserOnCallLog" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f047dd6a708175bae0ee6f8c4b" ON "UserOnCallLog" ("onCallDutyPolicyExecutionLogId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_550f9f6177451d4902467991a1" ON "UserOnCallLog" ("onCallDutyPolicyEscalationRuleId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7498ad42c4f77f7fab2a6bc2e3" ON "UserOnCallLog" ("onCallDutyPolicyExecutionLogTimelineId") `, + ); + await queryRunner.query( + `CREATE TABLE "UserOnCallLogTimeline" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "userId" uuid NOT NULL, "projectId" uuid NOT NULL, "userNotificationLogId" uuid NOT NULL, "userNotificationRuleId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, "triggeredByIncidentId" uuid NOT NULL, "onCallDutyPolicyExecutionLogId" uuid NOT NULL, "onCallDutyPolicyExecutionLogTimelineId" uuid NOT NULL, "userNotificationEventType" character varying(100) NOT NULL, "onCallDutyPolicyEscalationRuleId" uuid NOT NULL, "userBelongsToTeamId" uuid, "statusMessage" character varying(500) NOT NULL, "status" character varying(100) NOT NULL, "createdByUserId" uuid, "isAcknowledged" boolean, "acknowledgedAt" TIMESTAMP WITH TIME ZONE, "userCallId" uuid, "userSmsId" uuid, "userEmailId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_f82bd96ee9c1e338f49c9327430" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_79580470f34858375cae5d353a" ON "UserOnCallLogTimeline" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_dc79ea74fba7b99835bd475081" ON "UserOnCallLogTimeline" ("userNotificationLogId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_06a427cdcbae1ddcb1301b860f" ON "UserOnCallLogTimeline" ("userNotificationRuleId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_ef993920a9967b61fb3fb9bf16" ON "UserOnCallLogTimeline" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_58a44736718a5ec4fe41526289" ON "UserOnCallLogTimeline" ("triggeredByIncidentId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6491952f9d8066aa5cfff92cd8" ON "UserOnCallLogTimeline" ("onCallDutyPolicyExecutionLogId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_26e16ed3e46c6a6589a88d3abb" ON "UserOnCallLogTimeline" ("onCallDutyPolicyExecutionLogTimelineId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_222212e7157f8816357a4f0253" ON "UserOnCallLogTimeline" ("onCallDutyPolicyEscalationRuleId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d5c3df01bbb2a9ce168b36b523" ON "UserOnCallLogTimeline" ("userCallId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_12ef8407b6359205df8339f849" ON "UserOnCallLogTimeline" ("userSmsId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_815c728d905c44bc440ec91308" ON "UserOnCallLogTimeline" ("userEmailId") `, + ); + await queryRunner.query( + `CREATE TABLE "Workflow" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "isEnabled" boolean NOT NULL DEFAULT false, "graph" jsonb, "triggerId" character varying(100), "triggerArguments" jsonb, "repeatableJobKey" character varying, CONSTRAINT "PK_9f6d966c72bccdb6fe2e12a5614" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a87518833c744b2df4324b61a6" ON "Workflow" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "WorkflowLog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "workflowId" uuid NOT NULL, "logs" text NOT NULL, "workflowStatus" character varying NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "completedAt" TIMESTAMP WITH TIME ZONE, "deletedByUserId" uuid, CONSTRAINT "PK_dc0cc28be769df9d7e2cb670c72" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6c3dc31b09a96d81982a472e22" ON "WorkflowLog" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_55293b16d84f048f44c771595b" ON "WorkflowLog" ("workflowId") `, + ); + await queryRunner.query( + `CREATE TABLE "WorkflowVariable" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "workflowId" uuid, "name" character varying(100) NOT NULL, "description" character varying(500), "content" text NOT NULL, "isSecret" boolean NOT NULL DEFAULT false, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_9c6680c9bd6a5a7beb7127e73f1" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_bb47f6d0cabd55fde5b199ae20" ON "WorkflowVariable" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c093a47a2ecfa1d5f2d4aeb04a" ON "WorkflowVariable" ("workflowId") `, + ); + await queryRunner.query( + `CREATE TABLE "ApiKeyPermissionLabel" ("apiKeyPermissionId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_264b4d74ff24861aafb0b8fa2ea" PRIMARY KEY ("apiKeyPermissionId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_0946f6b41113cd842ee69f69fb" ON "ApiKeyPermissionLabel" ("apiKeyPermissionId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4db7f66405df73287c639ece90" ON "ApiKeyPermissionLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorLabel" ("monitorId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_7651b053ae253106356301ef280" PRIMARY KEY ("monitorId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3af64cebb5a7cc7f1ad0aa70c1" ON "MonitorLabel" ("monitorId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1866fb21ea1acd3d5c37e28ca1" ON "MonitorLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyLabel" ("onCallDutyPolicyId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_619f9dd19e4c9f2f0f695edb757" PRIMARY KEY ("onCallDutyPolicyId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d4a3f7dc4e33b896b0984e7316" ON "OnCallDutyPolicyLabel" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1c0248fe6856bbe029fc492ec7" ON "OnCallDutyPolicyLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentMonitor" ("incidentId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_0d50be2afc9e6b3182fa5d61bbe" PRIMARY KEY ("incidentId", "monitorId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_55e3162c1259b1b092f0ac63ee" ON "IncidentMonitor" ("incidentId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_9e2000a938f2e12c6653e68780" ON "IncidentMonitor" ("monitorId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentOnCallDutyPolicy" ("onCallDutyPolicyId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_185b450b39a568ea486b69df0df" PRIMARY KEY ("onCallDutyPolicyId", "monitorId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_2d127b6da0e4fab9f905b4d332" ON "IncidentOnCallDutyPolicy" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f89b23e3cafd1c6a0bfd42c297" ON "IncidentOnCallDutyPolicy" ("monitorId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentLabel" ("incidentId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_40289610e0edf423d01074471ec" PRIMARY KEY ("incidentId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1084d1ddbabbcfcab7cd9d547a" ON "IncidentLabel" ("incidentId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_17f4085273d14d4d6145cf6585" ON "IncidentLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentTemplateMonitor" ("incidentTemplateId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_9a793ac183fa115b23db033ae9f" PRIMARY KEY ("incidentTemplateId", "monitorId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_390a45e855282ae55ad56d1e1f" ON "IncidentTemplateMonitor" ("incidentTemplateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_33fb90ba0116b7db4efd4ec7a4" ON "IncidentTemplateMonitor" ("monitorId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentTemplateOnCallDutyPolicy" ("incidentTemplateId" uuid NOT NULL, "onCallDutyPolicyId" uuid NOT NULL, CONSTRAINT "PK_219e90dde24e7096fdaee3089c7" PRIMARY KEY ("incidentTemplateId", "onCallDutyPolicyId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_99245f38769689fe8a172dcb81" ON "IncidentTemplateOnCallDutyPolicy" ("incidentTemplateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_2b76b57ea6659f97a4bcd1156c" ON "IncidentTemplateOnCallDutyPolicy" ("onCallDutyPolicyId") `, + ); + await queryRunner.query( + `CREATE TABLE "IncidentTemplateLabel" ("incidentTemplateId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_8c893e072c280f3ea36c40a4543" PRIMARY KEY ("incidentTemplateId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d527ebc73a91eefcae4beaaf82" ON "IncidentTemplateLabel" ("incidentTemplateId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fcf64673a74380a67159376b85" ON "IncidentTemplateLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorGroupLabel" ("monitorGroupId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_007b146dcc3053bcae3a480fc27" PRIMARY KEY ("monitorGroupId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_2fed2a41449af9a9cf821b759d" ON "MonitorGroupLabel" ("monitorGroupId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a91be5ccf47cbd470c3f9ee560" ON "MonitorGroupLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "MonitorSecretMonitor" ("monitorSecretId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_bcabf4d30297287277c049e312f" PRIMARY KEY ("monitorSecretId", "monitorId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fed65a7701822d21a66810bfe2" ON "MonitorSecretMonitor" ("monitorSecretId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_8ad10d0cd8fd68ac64ae3967dc" ON "MonitorSecretMonitor" ("monitorId") `, + ); + await queryRunner.query( + `CREATE TABLE "OnCallDutyPolicyScheduleLabel" ("onCallDutyPolicyScheduleId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_309b0a81c1dc85eb7adbf52b074" PRIMARY KEY ("onCallDutyPolicyScheduleId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_facb426dc0647d760bba573c2d" ON "OnCallDutyPolicyScheduleLabel" ("onCallDutyPolicyScheduleId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c27ee1fc2df7788145ed9e3333" ON "OnCallDutyPolicyScheduleLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "ProjectSsoTeam" ("projectSsoId" uuid NOT NULL, "teamId" uuid NOT NULL, CONSTRAINT "PK_0d5098a73b48af5e3bc6f04caf8" PRIMARY KEY ("projectSsoId", "teamId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_43c6fb265bd3b69e26f1d98b66" ON "ProjectSsoTeam" ("projectSsoId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d6d43b15c7dca2734a768379f8" ON "ProjectSsoTeam" ("teamId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageLabel" ("statusPageId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_511c59beeae2315d01f21848d34" PRIMARY KEY ("statusPageId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d409a46e63e25fbd4fcc9d5242" ON "StatusPageLabel" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_6842f6301436d26a3115406279" ON "StatusPageLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageDownMonitorStatus" ("statusPageId" uuid NOT NULL, "monitorStatusId" uuid NOT NULL, CONSTRAINT "PK_599e5298f4663a42124ef04819b" PRIMARY KEY ("statusPageId", "monitorStatusId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_5d5aa67b52755d47e81bb19feb" ON "StatusPageDownMonitorStatus" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e9f66cc920a6dfd8b20be8497c" ON "StatusPageDownMonitorStatus" ("monitorStatusId") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceMonitor" ("scheduledMaintenanceId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_c48a2ac3e9aa04d61c689416b92" PRIMARY KEY ("scheduledMaintenanceId", "monitorId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_9f74e5e95ad88301cbc6e97da6" ON "ScheduledMaintenanceMonitor" ("scheduledMaintenanceId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d1a5797fdd98c1fa2f99670aab" ON "ScheduledMaintenanceMonitor" ("monitorId") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceStatusPage" ("scheduledMaintenanceId" uuid NOT NULL, "statusPageId" uuid NOT NULL, CONSTRAINT "PK_1a8ec8af0d0f7d83627235f08f6" PRIMARY KEY ("scheduledMaintenanceId", "statusPageId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_fcab6c2c59de7fa9e1a4ed52d3" ON "ScheduledMaintenanceStatusPage" ("scheduledMaintenanceId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_266f1f927ed89d829a2349d2e2" ON "ScheduledMaintenanceStatusPage" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "ScheduledMaintenanceLabel" ("scheduledMaintenanceId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_abb6424e4ce1a915ad291c98e7f" PRIMARY KEY ("scheduledMaintenanceId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c88790ffdbb71aa66a5795be22" ON "ScheduledMaintenanceLabel" ("scheduledMaintenanceId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4f3c993200714127eb2d0851cc" ON "ScheduledMaintenanceLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "AnnouncementStatusPage" ("announcementId" uuid NOT NULL, "statusPageId" uuid NOT NULL, CONSTRAINT "PK_d0372c7b16adafd18b8591980a1" PRIMARY KEY ("announcementId", "statusPageId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f10f946bb8a5da2ef395557805" ON "AnnouncementStatusPage" ("announcementId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d7895bb35944a68cccf8286521" ON "AnnouncementStatusPage" ("statusPageId") `, + ); + await queryRunner.query( + `CREATE TABLE "StatusPageSubscriberStatusPageResource" ("statusPageSubscriberId" uuid NOT NULL, "statusPageResourceId" uuid NOT NULL, CONSTRAINT "PK_1a244f904475ff50c824e0e565d" PRIMARY KEY ("statusPageSubscriberId", "statusPageResourceId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_618293911d2e52dc3c6a6873b4" ON "StatusPageSubscriberStatusPageResource" ("statusPageSubscriberId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f4af9c94a4b3ba11b4739a25ef" ON "StatusPageSubscriberStatusPageResource" ("statusPageResourceId") `, + ); + await queryRunner.query( + `CREATE TABLE "TeamPermissionLabel" ("teamPermissionId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_977590cfe880ea88b607aca0ed6" PRIMARY KEY ("teamPermissionId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7281dda2d397d6d75c2f5285bb" ON "TeamPermissionLabel" ("teamPermissionId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4523aa1dd9163aaf37698d137e" ON "TeamPermissionLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "ServiceLabel" ("telemetryServiceId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_69948538ec718918cb04dc06882" PRIMARY KEY ("telemetryServiceId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_94d495f938d819dab20480c5f8" ON "ServiceLabel" ("telemetryServiceId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7711266422ebad1188c4879d66" ON "ServiceLabel" ("labelId") `, + ); + await queryRunner.query( + `CREATE TABLE "WorkflowLabel" ("workflowId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_1c9a857f4e8cbba7c8ccbb911cd" PRIMARY KEY ("workflowId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3747a5f42be5c977e574abcd71" ON "WorkflowLabel" ("workflowId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_4e72fad380eca9abfa3b989554" ON "WorkflowLabel" ("labelId") `, + ); + await queryRunner.query( + `ALTER TABLE "User" ADD CONSTRAINT "FK_1f25f5fc0032f7014482d9c195e" FOREIGN KEY ("profilePictureId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "User" ADD CONSTRAINT "FK_644c3c0393979f57f71892ff0d7" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "AcmeCertificate" ADD CONSTRAINT "FK_130a8fd12e7505eebfce670b198" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "AcmeChallenge" ADD CONSTRAINT "FK_71371b224feb48f1d60e847cf1b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Reseller" ADD CONSTRAINT "FK_fe790bb94630d701a8ad93287ce" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Reseller" ADD CONSTRAINT "FK_952b3ed48545aaf18033150dc66" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ResellerPlan" ADD CONSTRAINT "FK_fc269bd109ac405a458b2acc678" FOREIGN KEY ("resellerId") REFERENCES "Reseller"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ResellerPlan" ADD CONSTRAINT "FK_34cdc5e0500513f321f0da35a64" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ResellerPlan" ADD CONSTRAINT "FK_e756416e4b0983e158f71c47c1a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Project" ADD CONSTRAINT "FK_639312a8ef82cbd5cee77c5b1ba" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Project" ADD CONSTRAINT "FK_43989dee7f7af742f6d8ec2664a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Project" ADD CONSTRAINT "FK_4ee6a519d48b26fe2a78fdc1c9c" FOREIGN KEY ("resellerId") REFERENCES "Reseller"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Project" ADD CONSTRAINT "FK_b5ee87614c184778810283c2991" FOREIGN KEY ("resellerPlanId") REFERENCES "ResellerPlan"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKey" ADD CONSTRAINT "FK_bb1019f0078a21b4854f5cb3ed4" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKey" ADD CONSTRAINT "FK_891c55549057af9a0c90c925ebb" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKey" ADD CONSTRAINT "FK_bcbc7d80fb0cfe2cbb5ae7db791" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Label" ADD CONSTRAINT "FK_f10d59c2ba66e085722e0053cb7" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Label" ADD CONSTRAINT "FK_84520cbda97d2a9cb9da7ccb18c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Label" ADD CONSTRAINT "FK_f46caf81c5fd7664ba8da9c99ba" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" ADD CONSTRAINT "FK_0cf347c575f15d3836615f53258" FOREIGN KEY ("apiKeyId") REFERENCES "ApiKey"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" ADD CONSTRAINT "FK_fb09dd7fefa9d5d44b1907be5fd" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" ADD CONSTRAINT "FK_dc8eb846ffbceafbc9c60bbfaa5" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" ADD CONSTRAINT "FK_ac42ef4597147c260e89a0f3d3a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "BillingInvoice" ADD CONSTRAINT "FK_0ab13e9a92ce4801c37c2a0a77f" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "BillingInvoice" ADD CONSTRAINT "FK_15b8130f5378f2079ed5b2fe7d1" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "BillingInvoice" ADD CONSTRAINT "FK_0a0a1a9865d157e46b1ecf14873" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "BillingPaymentMethod" ADD CONSTRAINT "FK_db4bb9add01b7d8286869fd9a0a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "BillingPaymentMethod" ADD CONSTRAINT "FK_55c3c9a9fc28000262b811cebc8" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "BillingPaymentMethod" ADD CONSTRAINT "FK_93a1554cb316127896f66acddd3" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CallLog" ADD CONSTRAINT "FK_5648767682195afaeb09098a213" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CallLog" ADD CONSTRAINT "FK_3e510124d923fe3b994936a7cb5" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "DataMigrations" ADD CONSTRAINT "FK_1619179d46a4411e1bb4af5d342" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "DataMigrations" ADD CONSTRAINT "FK_183a8261590c30a27a1b51f4bdb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Domain" ADD CONSTRAINT "FK_f1ce90d3f9693be29b72fabe93b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Domain" ADD CONSTRAINT "FK_12e6ebc5c806263d562045e9282" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Domain" ADD CONSTRAINT "FK_9ace4c275b42c057b7581543ce3" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSMTPConfig" ADD CONSTRAINT "FK_a540dab929fa6582b93f258ffe5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSMTPConfig" ADD CONSTRAINT "FK_3b7ed2d3bd1a2ee9638cccef5b0" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSMTPConfig" ADD CONSTRAINT "FK_d5458705e98b89c08c0d960422e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "EmailLog" ADD CONSTRAINT "FK_7b72c5131b3dd1f3edf201a561c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "EmailLog" ADD CONSTRAINT "FK_046364c162885b6ac65d5dd367c" FOREIGN KEY ("projectSmtpConfigId") REFERENCES "ProjectSMTPConfig"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "EmailLog" ADD CONSTRAINT "FK_6d0739da601917d316494fcae3b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "EmailVerificationToken" ADD CONSTRAINT "FK_0b65c7ffb685f6ed78aac195b1a" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "EmailVerificationToken" ADD CONSTRAINT "FK_9e86ebfdbef16789e9571f22074" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "GreenlockCertificate" ADD CONSTRAINT "FK_895b9b802ed002a3804136bacf1" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "GreenlockChallenge" ADD CONSTRAINT "FK_7517f5a285255f031b0f6d9663c" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentSeverity" ADD CONSTRAINT "FK_00d2f503174bf201abc6e77afde" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentSeverity" ADD CONSTRAINT "FK_2677e0a9dbf97ba0f4a7849eac6" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentSeverity" ADD CONSTRAINT "FK_d0d87151a7872a44c3d2457bfdc" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentState" ADD CONSTRAINT "FK_3d279e530067f599f3186e3821d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentState" ADD CONSTRAINT "FK_eb33bd015e0e57ee96b60f8d773" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentState" ADD CONSTRAINT "FK_88a0ecd4b1714ac0e2eef9ac27d" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatus" ADD CONSTRAINT "FK_db1783158a23bd20dbebaae56ef" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatus" ADD CONSTRAINT "FK_bdda7fecdf44ed43ef2004e7be5" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatus" ADD CONSTRAINT "FK_55a0e488581a0d02bcdd67a4348" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Monitor" ADD CONSTRAINT "FK_996acfb590bda327843f78b7ad3" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Monitor" ADD CONSTRAINT "FK_a84bbba0dbad47918136d4dfb43" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Monitor" ADD CONSTRAINT "FK_73bdf22259019b90836aac86b28" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Monitor" ADD CONSTRAINT "FK_d3461ab640467c8c2100ea55c79" FOREIGN KEY ("currentMonitorStatusId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicy" ADD CONSTRAINT "FK_31508550a088ba2cc843a6c90c4" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicy" ADD CONSTRAINT "FK_c0c63ac58f97fd254bb5c2813dc" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicy" ADD CONSTRAINT "FK_0424b49cfcd68cdd1721df53acd" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Probe" ADD CONSTRAINT "FK_6bd931aae4920e296ea08864cd0" FOREIGN KEY ("iconFileId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Probe" ADD CONSTRAINT "FK_b357696dc9462ad3f9e84c6dc52" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Probe" ADD CONSTRAINT "FK_1963e116be9832b23490cca933f" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Probe" ADD CONSTRAINT "FK_272ece82a96099041b93c9141e3" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" ADD CONSTRAINT "FK_eccbc31fa1f58bd051b6f7e108b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" ADD CONSTRAINT "FK_9b101f023b5db6491203d5c9951" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" ADD CONSTRAINT "FK_067855888a3d71803d3a5aeaecf" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" ADD CONSTRAINT "FK_6592d4f7f3b260efc23fc9b4bc9" FOREIGN KEY ("currentIncidentStateId") REFERENCES "IncidentState"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" ADD CONSTRAINT "FK_3f28fe3b32abed354a49b26c9cb" FOREIGN KEY ("incidentSeverityId") REFERENCES "IncidentSeverity"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" ADD CONSTRAINT "FK_ed0ec4960a85240f51e6779a00a" FOREIGN KEY ("changeMonitorStatusToId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" ADD CONSTRAINT "FK_7e537806a80e869917ca1d7e2e4" FOREIGN KEY ("createdByProbeId") REFERENCES "Probe"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentCustomField" ADD CONSTRAINT "FK_59e7f6a43dbc5ee54e1a1aaaaf1" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentCustomField" ADD CONSTRAINT "FK_5c1c7369e696f580186a4ff12de" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentCustomField" ADD CONSTRAINT "FK_bc64c76e766b1b880845afbcbf7" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentInternalNote" ADD CONSTRAINT "FK_ac48058b3e5f9e8361d2b8328c2" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentInternalNote" ADD CONSTRAINT "FK_b92e75645fd252e4c2f866047de" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentInternalNote" ADD CONSTRAINT "FK_c798e09321f06d8a180916d7a5e" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentInternalNote" ADD CONSTRAINT "FK_8f23b820cbbed6d96cfedd162a2" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentNoteTemplate" ADD CONSTRAINT "FK_21d5bc0d24b3e5032dd391ec8da" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentNoteTemplate" ADD CONSTRAINT "FK_515b6970fdd528d4c9f85a5e9a4" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentNoteTemplate" ADD CONSTRAINT "FK_3c00f2b005264318a274cd38a94" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Team" ADD CONSTRAINT "FK_baac847c494f692f03fd686e9c7" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Team" ADD CONSTRAINT "FK_4be4aa023ba1c6d6443b81b3b91" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Team" ADD CONSTRAINT "FK_0d4912bf03a7a645ce95142155b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_95f76375ccac835f815d7e926a2" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_389aadeb39a0806e80d4001016e" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_408324d3635a826538a792422f3" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_278f483fc81c21b1bd1311ee289" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" ADD CONSTRAINT "FK_60242ecfcecaa5cb1c5241bed4c" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_0ee7ae6757442fba470b213015c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_6311087eeb14ab51e6a1e6133f7" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_6aa9a6b46f8e044d722da8f5a7f" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_52591665c92658ef82944d63d26" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" ADD CONSTRAINT "FK_c473db8745d0ebeb147a72986cb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentPublicNote" ADD CONSTRAINT "FK_a9a77e5f286b5724f4e2280d0a1" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentPublicNote" ADD CONSTRAINT "FK_a6964d3aab71608daab9f20e304" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentPublicNote" ADD CONSTRAINT "FK_691a99e582fcddcc892d8573afc" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentPublicNote" ADD CONSTRAINT "FK_cf04d778a5502be606f63e01603" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_764daa366a4e195768a49e0ee39" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_fe2dff414a1f67260e3c5189811" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_16d198b59f3416a8ddc630a90d2" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_6b6b9dbf9ca5448c9297a58ad04" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" ADD CONSTRAINT "FK_ff0fca6570d47798771763533a9" FOREIGN KEY ("incidentStateId") REFERENCES "IncidentState"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_b82dafef226b0fae1ad6cb18570" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_b03e46665e4c075ed1398fcc409" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_0e6a4e065ffb22f95ecfc259e9a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_ceff6a4dfdccecc4aa40dbfe91a" FOREIGN KEY ("incidentSeverityId") REFERENCES "IncidentSeverity"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_6d7627ab9d5172c66fc50192163" FOREIGN KEY ("changeMonitorStatusToId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_cf172eb6797a64ee3750e3f3e21" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_e8090c6569c3a5dbd7ef7485c99" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_a895b946fccb109dedd55b85f6d" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_3e8a4bd1594da3438d8fb8a6687" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" ADD CONSTRAINT "FK_af037dc245d77c282061fea1b1b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_da9dd65b4401b954a0ea2b5c8d5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_36b9b0204f4b17063483cb7308e" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_876dd05e0dfc64219ef5df241c3" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_a53f8aab99766a87c73c52b9037" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" ADD CONSTRAINT "FK_026e918a31de467eeb8e30ae8d1" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorCustomField" ADD CONSTRAINT "FK_1c6b61e904d8e3fec1ae719b9ef" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorCustomField" ADD CONSTRAINT "FK_93a4da4182f93ba24ab958c1b73" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorCustomField" ADD CONSTRAINT "FK_817e69522c8d2f1e2fd3f857e91" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroup" ADD CONSTRAINT "FK_13905ff40843b11145f21e33ff5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroup" ADD CONSTRAINT "FK_abaf236c1877143fe160991cc45" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroup" ADD CONSTRAINT "FK_edd658b85b2ef7ac9b2f0687d8a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_0d7052620f268d0fa17f948a851" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_8f19947114087883cea771e1cb4" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_618bcd9015c257b0727df36fd11" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_7ce36c144e83082213587e19c23" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" ADD CONSTRAINT "FK_fdbe93e29e60763a306358cab55" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_d5cfbebf8b07405652f5382e157" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_4b25f4a18bec8cb177e8d65c5f9" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_6a4095ee3d04454071816a5bad8" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_9267db147738caed0ccfdc3af22" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" ADD CONSTRAINT "FK_e9bced91dce29928ebeec834905" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_825791d5edb2403d7937f16ed91" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_cf3dcaa746835ae36615a39d862" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_50b373c428cfd4566cc5caf98e6" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_cf595b683e26e560526404663fe" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" ADD CONSTRAINT "FK_1a54eaa2d0187d10de84107a09b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_6a6213072d8637e6e625bc78929" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_63e5bbac01d1f68c7b08f126cd4" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_1d5265d1f3ca2f8b8e461e4998c" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_58610249ec4cf593e36210dcb84" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" ADD CONSTRAINT "FK_7ebfe3ddcf597fb73ee8eac2ff4" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_12a3497e1404fcdbc4e8963a58e" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_324f1d50d0427bbd0e3308c4592" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_a65ce9b11b2d7bde123aa7633fd" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_e2cf60b88171dfe5fdd0e4fe6c1" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" ADD CONSTRAINT "FK_e1ae2c698e6bde0a98c50162235" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_d887935f224b896ce64872c37c7" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_2d50a0e0e624369e7f90a62e8dc" FOREIGN KEY ("probeId") REFERENCES "Probe"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_a540272f483ef1de68f7e647486" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_4399ab64a5c00d55e5ce254deeb" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" ADD CONSTRAINT "FK_a182ba062c0a216395d0dbdbdee" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecret" ADD CONSTRAINT "FK_7c3629c5ae14e97bede3bc548e5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecret" ADD CONSTRAINT "FK_a886cd3bbdfd84d01167f92cb65" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecret" ADD CONSTRAINT "FK_e4262f178662aaacdb54d4c4f4e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_80213eb3f228f1e3d423f5127e2" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_c08a7c1ef8d511b335a991aac47" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_d7f555ef162fe878e4ed62a3e23" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_d293a7e96c5bf427072514f21a9" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" ADD CONSTRAINT "FK_574feb4161c5216c2c7ee0faaf8" FOREIGN KEY ("monitorStatusId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyCustomField" ADD CONSTRAINT "FK_e791474e098f276eda27704b479" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyCustomField" ADD CONSTRAINT "FK_456bff32fd0428134ef7396385f" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyCustomField" ADD CONSTRAINT "FK_43230e739b31e3f56284407b586" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRule" ADD CONSTRAINT "FK_d45a545669dc46da25cc60d1df5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRule" ADD CONSTRAINT "FK_b90c1cda36981c41e3965a93800" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRule" ADD CONSTRAINT "FK_ad8097a9359965d02ccbb16358b" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRule" ADD CONSTRAINT "FK_5c0911d261a941b00d41b6e5fda" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicySchedule" ADD CONSTRAINT "FK_0e1b7c3c3e8305a10716cdb8d6e" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicySchedule" ADD CONSTRAINT "FK_ecb5141b27e85674c294a2541b3" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicySchedule" ADD CONSTRAINT "FK_01e63400072d0bc6debee836cbf" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_efa24aa8feb92d4e15a707c20ec" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_651a4ab9e3cbb20f6b62a87a6b8" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_13a87c38fe2efa41940783af690" FOREIGN KEY ("onCallDutyPolicyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_089081f83ef22cdba5a0903ce59" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_90700af75cbe8129db898ac8adb" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" ADD CONSTRAINT "FK_878e14be4e6366ec646f874347a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_c8aff8439fbfb07e7388aa9011b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_f5121f361345d858ce740a55a25" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_26ba1a6edd877647cedd1eae8ca" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_0e7d4060e2fabe0957b9fedb429" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_da2e065de293a14b69964fb3233" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" ADD CONSTRAINT "FK_73ae2b2702aef4601c39d4d909a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_b15552e664640f67346193598a1" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_9ca3fbb66842324aa987d4c9722" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_bff6f4ae726b5c5cbae10e7d743" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_5a7b7a746409a175423a1bbd5c4" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_f9a45cea88022a9cf5b96c13e65" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" ADD CONSTRAINT "FK_d35f668f524cc88f580a7651fe2" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_00e9676d39eeb807de704430512" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_e4bb332263960531a4c9e2d4254" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_a230899d6c2b16621954c46fb16" FOREIGN KEY ("triggeredByIncidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_16b426d34ff2c5cbd6ecfd70820" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_e9302e15399b67938e0121a0545" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_63f6618df216b74b72e62491b09" FOREIGN KEY ("acknowledgedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_3015ddfeb130417c55489da807e" FOREIGN KEY ("acknowledgedByTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" ADD CONSTRAINT "FK_d4a0ffc5e9e698bb2612ba0e55f" FOREIGN KEY ("lastExecutedEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_591b7ed73c964e105bfecc6fd6d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_c5a798ca667fedda71d4ed54651" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_90119ec7f77fa2efd82261e0448" FOREIGN KEY ("triggeredByIncidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_872c2f6a9739bab1b57b6d51eac" FOREIGN KEY ("onCallDutyPolicyExecutionLogId") REFERENCES "OnCallDutyPolicyExecutionLog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_0ad4222a4c48b8a64e3a58b3519" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_0ae6ea2e2d38fd31543768e3609" FOREIGN KEY ("alertSentToUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_039098d4af133bd9c2b90978ef4" FOREIGN KEY ("userBelongsToTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_65998388ab4266dda712502ad65" FOREIGN KEY ("onCallDutyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_43f833a79cf4201b3fa1deed023" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" ADD CONSTRAINT "FK_166f3696b3c70989507dd7e1f2e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ADD CONSTRAINT "FK_3b892ef36671f1ea1c8457a96d6" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ADD CONSTRAINT "FK_6fa6574a45cf1352c5a3b962512" FOREIGN KEY ("onCallDutyPolicyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ADD CONSTRAINT "FK_1db1083a896b0f77a0e87f26463" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ADD CONSTRAINT "FK_f22b52355207d2c0d5a13c168e8" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_08afccd6cbbd1a7015d4fe25e39" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_9b36bba6d9898331920805a29ca" FOREIGN KEY ("onCallDutyPolicyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_41f4ecc29351e1a406e83b30a93" FOREIGN KEY ("onCallDutyPolicyScheduleLayerId") REFERENCES "OnCallDutyPolicyScheduleLayer"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_b2ccbfcc3964caf3dfd89243f8f" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_49e5a41e1d771fe9e385295bd9a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" ADD CONSTRAINT "FK_416e830c88f2ecfa149f4cd51c8" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectCallSMSConfig" ADD CONSTRAINT "FK_20334b9571a6cd1a871e70d8e76" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectCallSMSConfig" ADD CONSTRAINT "FK_e873aa20a371bd92e220332a992" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectCallSMSConfig" ADD CONSTRAINT "FK_f5bc0e2b81886b21004e2a5f67b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSSO" ADD CONSTRAINT "FK_be9e6751765501ea1db126fcb23" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSSO" ADD CONSTRAINT "FK_28011315533e2d819295d261ee4" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSSO" ADD CONSTRAINT "FK_00ea9e456217ffbfff35f1e944f" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_90e44f45272c0da256951183086" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_3169f7934171e8f697bb993b010" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_6dcdf97c0834dd44b4f2c93e664" FOREIGN KEY ("resellerId") REFERENCES "Reseller"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_7aecf4b1ae3e45647cb911f4c10" FOREIGN KEY ("resellerPlanId") REFERENCES "ResellerPlan"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" ADD CONSTRAINT "FK_a7b405e2a9ae144be016bcf973d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceState" ADD CONSTRAINT "FK_57a31fb2a5e4caa223d2506a4e1" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceState" ADD CONSTRAINT "FK_88044fd50006f1897e8c760d136" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceState" ADD CONSTRAINT "FK_4f803428e0926584d1f7c44a3d4" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_34a4c35e0d7afe6f023825a68c7" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_da54bb2c4997ee1a3b73026d7f5" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_71f429afb7678d132472b3c87b0" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_1f17f293352ebdc3bcf383158cc" FOREIGN KEY ("faviconFileId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_84df83d1f492a19a08aee465500" FOREIGN KEY ("logoFileId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_602b456a61d73a96e97f483064d" FOREIGN KEY ("coverImageFileId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_61944d851b4a7213d79ef281744" FOREIGN KEY ("smtpConfigId") REFERENCES "ProjectSMTPConfig"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" ADD CONSTRAINT "FK_4e347d3f99b67dacd149beaf61d" FOREIGN KEY ("callSmsConfigId") REFERENCES "ProjectCallSMSConfig"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_4059dd569d6a482062352bf266a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_5442fd86c96d45e062d5ee1f093" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_50ddf8bb21e988ea5d419a66cb9" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_883038abda021ce79fa838d0273" FOREIGN KEY ("currentScheduledMaintenanceStateId") REFERENCES "ScheduledMaintenanceState"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" ADD CONSTRAINT "FK_fab9cc7e6ffcf02872fccfab978" FOREIGN KEY ("changeMonitorStatusToId") REFERENCES "MonitorStatus"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceCustomField" ADD CONSTRAINT "FK_3621c488327e1c00518aa4e8816" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceCustomField" ADD CONSTRAINT "FK_c7cdb245d3d98be14482f092eca" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceCustomField" ADD CONSTRAINT "FK_9094eed77fb6e8f7ecf1502f5e0" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceInternalNote" ADD CONSTRAINT "FK_d79c49a0a613a6b432fd400e69b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceInternalNote" ADD CONSTRAINT "FK_a53ef45aebd4a6a6e7dde7f896a" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceInternalNote" ADD CONSTRAINT "FK_69757967d2ee696f487fb8ac37e" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceInternalNote" ADD CONSTRAINT "FK_7fb00788b6ac97988dd43e2e1b2" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceNoteTemplate" ADD CONSTRAINT "FK_b4cb4c1312eb72459907e1bbe9b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceNoteTemplate" ADD CONSTRAINT "FK_e38c1102001ae0b70c22e046424" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceNoteTemplate" ADD CONSTRAINT "FK_4c3d6b87bb1e8739cdeb8b92f74" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_e3053b1725658b4a120b4e3185d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_1206beb611e0779ce2248ecbaeb" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_1251fb7d4a4bf8586f2bd1528eb" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_cc0e8ca9e9065ca0cc24bf6275b" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" ADD CONSTRAINT "FK_52a3a932530026bafef87e62177" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_518742c7037d9a38a6594dc18a6" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_be6a25806925f93b8949e61929b" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_967c33f7bce5de522c1d1a80e7b" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_c91d4d420e3faaf15fa928fd214" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" ADD CONSTRAINT "FK_6e6b087ba99fe433f83f87e0a35" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenancePublicNote" ADD CONSTRAINT "FK_73c6737ab4a7718c45932bffada" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenancePublicNote" ADD CONSTRAINT "FK_937aabd4adbfce78663406a2487" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenancePublicNote" ADD CONSTRAINT "FK_1f67cfb63bd3488b7c5c5b7fac7" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenancePublicNote" ADD CONSTRAINT "FK_28e179283c409e0751aae713949" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_4faf556988f5b6a755ef2e85ae8" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_411146017bcfe98bbe028b8d15a" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_2392299477cfc4f612ecb73e839" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_aa84fcdf2fef6c2005ebab2c197" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" ADD CONSTRAINT "FK_7c0f750d3a964180d1e1efa16ea" FOREIGN KEY ("scheduledMaintenanceStateId") REFERENCES "ScheduledMaintenanceState"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ShortLink" ADD CONSTRAINT "FK_11f179cd8e9beee22b89c316972" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "SmsLog" ADD CONSTRAINT "FK_a30bbda7f5480e498ebc609663b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "SmsLog" ADD CONSTRAINT "FK_d00778bcfaa735fbb5dc91c1945" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageAnnouncement" ADD CONSTRAINT "FK_5e9c5a7393ac9aa477d625de673" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageAnnouncement" ADD CONSTRAINT "FK_1491bd0895d515969eee2a08c80" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageAnnouncement" ADD CONSTRAINT "FK_7251cbbaa75eb9570830b0cab32" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageCustomField" ADD CONSTRAINT "FK_4f5ae90bc48a0ddeb50cd009aaf" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageCustomField" ADD CONSTRAINT "FK_e0abd7540f860de19607dc25bc0" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageCustomField" ADD CONSTRAINT "FK_26b4a892f3b31c5b0b285c4e5cb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_40cca185c8cf933c04a0534676b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_7fab5bc54a8f36eac8f31c82565" FOREIGN KEY ("domainId") REFERENCES "Domain"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_842a66fcb103388fcedffef75f7" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_106e359f945432d6583bd30ff4b" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" ADD CONSTRAINT "FK_6c82107f63d1a3186d579a6d9cb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageFooterLink" ADD CONSTRAINT "FK_5f8f65447c9b881860bf742dc98" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageFooterLink" ADD CONSTRAINT "FK_1a80c698b2205074f53376d631b" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageFooterLink" ADD CONSTRAINT "FK_bd6f15ab951095e624ea664d9a6" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageFooterLink" ADD CONSTRAINT "FK_0328201140b59b4b813f83b06a9" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageGroup" ADD CONSTRAINT "FK_4a96c34f030a6e39218352a947a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageGroup" ADD CONSTRAINT "FK_5dbcfd7d38e5ea7a78a6a78a330" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageGroup" ADD CONSTRAINT "FK_0a63a8ee804658921edf1e870af" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageGroup" ADD CONSTRAINT "FK_61191c9c00f7279615e13af4bbd" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHeaderLink" ADD CONSTRAINT "FK_70d70692cbe9d9be188723df4f0" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHeaderLink" ADD CONSTRAINT "FK_99c017b6ced8da63abdfbb506eb" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHeaderLink" ADD CONSTRAINT "FK_88048566089097605e26fdb2893" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHeaderLink" ADD CONSTRAINT "FK_0d3a63f1c684e78297b213c348e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHistoryChartBarColorRule" ADD CONSTRAINT "FK_a65f8fabf888d227d64570f52b3" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHistoryChartBarColorRule" ADD CONSTRAINT "FK_54edc7ff7a74a0310a512c53895" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHistoryChartBarColorRule" ADD CONSTRAINT "FK_5d973aa991ba9f06b642d3fc9d7" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHistoryChartBarColorRule" ADD CONSTRAINT "FK_8041d41239c4218bf136bf20591" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_9fac66064d88c514d2e5503237a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_f60296efefe379693bfd55a7760" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_6a98e42d8df3ba84bd0f79da550" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_7c1168daf53c46678045ff39d31" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" ADD CONSTRAINT "FK_e992fcc346afa21a89ba9f75f25" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_c28e05c08656f8aa756734c37c5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_3c59f811e0660c5522e45e85b6a" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_69b1659abafe656563259784d02" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_4ecb38fa1941bb0961641803f21" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" ADD CONSTRAINT "FK_8d7351e844adfd5c279fd8e9f3b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPagePrivateUser" ADD CONSTRAINT "FK_1ce45fe77324ede75166d0f57dd" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPagePrivateUser" ADD CONSTRAINT "FK_0589c51161d13b752fed41a3193" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPagePrivateUser" ADD CONSTRAINT "FK_e47c85ead36095d040493775a3f" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPagePrivateUser" ADD CONSTRAINT "FK_524d2e71f90ef8f78d85d5fdfd1" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_8fba35fb87a0ad6037eb8fc8040" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_ade3f7acf902dcb313d230ca1f5" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_b1c4fe08e1d90ae4d26d934653c" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_a55bb812676bff276cef1f14c86" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_9b61276c47d6091295dec9e5350" FOREIGN KEY ("statusPageGroupId") REFERENCES "StatusPageGroup"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_51e0fbc6d460394b1cd38959790" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" ADD CONSTRAINT "FK_d2b2f7ffe8f976fda20f4b96c5b" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSSO" ADD CONSTRAINT "FK_1d63fa142dd4175ef256f21d2a6" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSSO" ADD CONSTRAINT "FK_dc05de7939af3ada1567fc7106b" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSSO" ADD CONSTRAINT "FK_8e2cbcf07eba956fe976ca3d043" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSSO" ADD CONSTRAINT "FK_0bfc26bce8ea92b8b8a9e0400de" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriber" ADD CONSTRAINT "FK_6adf943966e01699e86117d2e34" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriber" ADD CONSTRAINT "FK_217b295d5882faa6cf3418ed811" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriber" ADD CONSTRAINT "FK_61cecfd27c2d41eb58330df1d8c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriber" ADD CONSTRAINT "FK_35ad85d2f341ebfeaca7ad67af1" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_3cc297d538f01065f9925cfb11a" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_fd952f76f5a5297ce69a9a588eb" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_4ab0af827040dbce6ef21ec7780" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_a9e764a6ad587e6e386abe3b9de" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" ADD CONSTRAINT "FK_945ca87238e7465782215c25d8d" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermission" ADD CONSTRAINT "FK_78293e9cc1746e5f29ccccfdfc0" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermission" ADD CONSTRAINT "FK_5064c0bdc8ff238952f9a2acf43" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermission" ADD CONSTRAINT "FK_e2c33d5f98cb42f8c1f76a85095" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermission" ADD CONSTRAINT "FK_73a2d0db1de4e66582e376098de" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryService" ADD CONSTRAINT "FK_3a3321fd538aa014aa5e4f35220" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryService" ADD CONSTRAINT "FK_5d0b92dc9ab2bfd71432e9a3536" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryService" ADD CONSTRAINT "FK_46ea9e637b4454993665a436d56" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryUsageBilling" ADD CONSTRAINT "FK_5670be95a9496b9380b7c0d7935" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryUsageBilling" ADD CONSTRAINT "FK_91333210492e5d2f334231468a7" FOREIGN KEY ("telemetryServiceId") REFERENCES "TelemetryService"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryUsageBilling" ADD CONSTRAINT "FK_d71562eb0c2861797502bd99917" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryUsageBilling" ADD CONSTRAINT "FK_510252373d4e5917029308384fb" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserCall" ADD CONSTRAINT "FK_db4e522c086be556e5101c4e910" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserCall" ADD CONSTRAINT "FK_4d90bd0e309ad43c4541bb428e9" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserCall" ADD CONSTRAINT "FK_1b46d8793ef542c059369481d42" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserCall" ADD CONSTRAINT "FK_996ab46825df7f3512e735c450c" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserEmail" ADD CONSTRAINT "FK_e6beed22b36201aea1d70ba0d72" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserEmail" ADD CONSTRAINT "FK_1f713c701d85c69f706e4e82b85" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserEmail" ADD CONSTRAINT "FK_06413c119aae9c3f75154c2346c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserEmail" ADD CONSTRAINT "FK_a1aa5e10dcfb571521324bbd665" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserSMS" ADD CONSTRAINT "FK_6439689a29a2192708e3f3603da" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserSMS" ADD CONSTRAINT "FK_3cb16b5c2d69dbdc812247788f8" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserSMS" ADD CONSTRAINT "FK_99fc3cdf366fd3d266fbf2d657c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserSMS" ADD CONSTRAINT "FK_0bae98162ec44540ff85f724daa" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_69d439e9f60f05ae16732c49999" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_32efb91af1b432a25ceb55bc0dc" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_85b73b64802058915df58fa013b" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_b1292f2480d0c4985898d7bf33a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_ec8e7a39fcd38d0ea2d40b8afaf" FOREIGN KEY ("userCallId") REFERENCES "UserCall"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_32ca4397660dafe0cab7d03e5b1" FOREIGN KEY ("userSmsId") REFERENCES "UserSMS"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_b407063ee43233b0cc8e9106cbb" FOREIGN KEY ("userEmailId") REFERENCES "UserEmail"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" ADD CONSTRAINT "FK_bf703b216d7f59424302dc5d70b" FOREIGN KEY ("incidentSeverityId") REFERENCES "IncidentSeverity"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationSetting" ADD CONSTRAINT "FK_6f110fc752889f922d6a3c57a55" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationSetting" ADD CONSTRAINT "FK_e2565b0aa7d7e015fb6685afede" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationSetting" ADD CONSTRAINT "FK_aee7abeffd1c60d49f710fb3749" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationSetting" ADD CONSTRAINT "FK_b023f12dc00bcfc50d6d9ad4f71" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_7941aa92c7c740400b272d69072" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_3a4b15ce8357c6735ac1b4ae606" FOREIGN KEY ("userBelongsToTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_ea993eab5c402623b61203e3256" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_4cd37d481ef366d975c6a7cd9bf" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_f047dd6a708175bae0ee6f8c4bf" FOREIGN KEY ("onCallDutyPolicyExecutionLogId") REFERENCES "OnCallDutyPolicyExecutionLog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_550f9f6177451d4902467991a15" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_eeb0dd05d1dec542c3de5fb5074" FOREIGN KEY ("triggeredByIncidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_7498ad42c4f77f7fab2a6bc2e33" FOREIGN KEY ("onCallDutyPolicyExecutionLogTimelineId") REFERENCES "OnCallDutyPolicyExecutionLogTimeline"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_91488d7d3341bf1113902f4786c" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_ac31bad932e24418ce0bb1bbb14" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_b70804c755c008c15794b6cc18d" FOREIGN KEY ("acknowledgedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" ADD CONSTRAINT "FK_123b1825525b963fe9555d62641" FOREIGN KEY ("onCallDutyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_2a893e347fdab643867abd8dda7" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_79580470f34858375cae5d353a9" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_dc79ea74fba7b99835bd475081c" FOREIGN KEY ("userNotificationLogId") REFERENCES "UserOnCallLog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_06a427cdcbae1ddcb1301b860f2" FOREIGN KEY ("userNotificationRuleId") REFERENCES "UserNotificationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_ef993920a9967b61fb3fb9bf162" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_58a44736718a5ec4fe41526289a" FOREIGN KEY ("triggeredByIncidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_6491952f9d8066aa5cfff92cd85" FOREIGN KEY ("onCallDutyPolicyExecutionLogId") REFERENCES "OnCallDutyPolicyExecutionLog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_26e16ed3e46c6a6589a88d3abba" FOREIGN KEY ("onCallDutyPolicyExecutionLogTimelineId") REFERENCES "OnCallDutyPolicyExecutionLogTimeline"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_222212e7157f8816357a4f02536" FOREIGN KEY ("onCallDutyPolicyEscalationRuleId") REFERENCES "OnCallDutyPolicyEscalationRule"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_6650020ce6e235164a09d1ca019" FOREIGN KEY ("userBelongsToTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_03d67a4d7fa9f087327ab0f74a7" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_a93a41d65df4cbe518393695084" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_d5c3df01bbb2a9ce168b36b5234" FOREIGN KEY ("userCallId") REFERENCES "UserCall"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_12ef8407b6359205df8339f8494" FOREIGN KEY ("userSmsId") REFERENCES "UserSMS"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" ADD CONSTRAINT "FK_815c728d905c44bc440ec91308b" FOREIGN KEY ("userEmailId") REFERENCES "UserEmail"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Workflow" ADD CONSTRAINT "FK_a87518833c744b2df4324b61a6d" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Workflow" ADD CONSTRAINT "FK_13c42a014f8c10862f23d02eb49" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "Workflow" ADD CONSTRAINT "FK_367e2e759f520b31d727d22b803" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowLog" ADD CONSTRAINT "FK_6c3dc31b09a96d81982a472e22b" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowLog" ADD CONSTRAINT "FK_55293b16d84f048f44c771595bb" FOREIGN KEY ("workflowId") REFERENCES "Workflow"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowLog" ADD CONSTRAINT "FK_a4e2e2861f3ece2b7d6d5e399e2" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowVariable" ADD CONSTRAINT "FK_bb47f6d0cabd55fde5b199ae206" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowVariable" ADD CONSTRAINT "FK_c093a47a2ecfa1d5f2d4aeb04a0" FOREIGN KEY ("workflowId") REFERENCES "Workflow"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowVariable" ADD CONSTRAINT "FK_92fbc4d230accb3d12c098ca4d2" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowVariable" ADD CONSTRAINT "FK_3e414e10cb4927e233ffd32651c" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermissionLabel" ADD CONSTRAINT "FK_0946f6b41113cd842ee69f69fb1" FOREIGN KEY ("apiKeyPermissionId") REFERENCES "ApiKeyPermission"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermissionLabel" ADD CONSTRAINT "FK_4db7f66405df73287c639ece904" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorLabel" ADD CONSTRAINT "FK_3af64cebb5a7cc7f1ad0aa70c11" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorLabel" ADD CONSTRAINT "FK_1866fb21ea1acd3d5c37e28ca1b" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyLabel" ADD CONSTRAINT "FK_d4a3f7dc4e33b896b0984e73164" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyLabel" ADD CONSTRAINT "FK_1c0248fe6856bbe029fc492ec71" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentMonitor" ADD CONSTRAINT "FK_55e3162c1259b1b092f0ac63eeb" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentMonitor" ADD CONSTRAINT "FK_9e2000a938f2e12c6653e68780c" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOnCallDutyPolicy" ADD CONSTRAINT "FK_2d127b6da0e4fab9f905b4d332d" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOnCallDutyPolicy" ADD CONSTRAINT "FK_f89b23e3cafd1c6a0bfd42c297d" FOREIGN KEY ("monitorId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentLabel" ADD CONSTRAINT "FK_1084d1ddbabbcfcab7cd9d547a4" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentLabel" ADD CONSTRAINT "FK_17f4085273d14d4d6145cf65855" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateMonitor" ADD CONSTRAINT "FK_390a45e855282ae55ad56d1e1fc" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateMonitor" ADD CONSTRAINT "FK_33fb90ba0116b7db4efd4ec7a45" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOnCallDutyPolicy" ADD CONSTRAINT "FK_99245f38769689fe8a172dcb81a" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOnCallDutyPolicy" ADD CONSTRAINT "FK_2b76b57ea6659f97a4bcd1156c1" FOREIGN KEY ("onCallDutyPolicyId") REFERENCES "OnCallDutyPolicy"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateLabel" ADD CONSTRAINT "FK_d527ebc73a91eefcae4beaaf822" FOREIGN KEY ("incidentTemplateId") REFERENCES "IncidentTemplate"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateLabel" ADD CONSTRAINT "FK_fcf64673a74380a67159376b85f" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupLabel" ADD CONSTRAINT "FK_2fed2a41449af9a9cf821b759de" FOREIGN KEY ("monitorGroupId") REFERENCES "MonitorGroup"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupLabel" ADD CONSTRAINT "FK_a91be5ccf47cbd470c3f9ee5606" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecretMonitor" ADD CONSTRAINT "FK_fed65a7701822d21a66810bfe29" FOREIGN KEY ("monitorSecretId") REFERENCES "MonitorSecret"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecretMonitor" ADD CONSTRAINT "FK_8ad10d0cd8fd68ac64ae3967dc9" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLabel" ADD CONSTRAINT "FK_facb426dc0647d760bba573c2dd" FOREIGN KEY ("onCallDutyPolicyScheduleId") REFERENCES "OnCallDutyPolicySchedule"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLabel" ADD CONSTRAINT "FK_c27ee1fc2df7788145ed9e3333c" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSsoTeam" ADD CONSTRAINT "FK_43c6fb265bd3b69e26f1d98b66c" FOREIGN KEY ("projectSsoId") REFERENCES "ProjectSSO"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSsoTeam" ADD CONSTRAINT "FK_d6d43b15c7dca2734a768379f8c" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageLabel" ADD CONSTRAINT "FK_d409a46e63e25fbd4fcc9d5242f" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageLabel" ADD CONSTRAINT "FK_6842f6301436d26a31154062793" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDownMonitorStatus" ADD CONSTRAINT "FK_5d5aa67b52755d47e81bb19febf" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDownMonitorStatus" ADD CONSTRAINT "FK_e9f66cc920a6dfd8b20be8497cf" FOREIGN KEY ("monitorStatusId") REFERENCES "MonitorStatus"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceMonitor" ADD CONSTRAINT "FK_9f74e5e95ad88301cbc6e97da6d" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceMonitor" ADD CONSTRAINT "FK_d1a5797fdd98c1fa2f99670aab8" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStatusPage" ADD CONSTRAINT "FK_fcab6c2c59de7fa9e1a4ed52d37" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStatusPage" ADD CONSTRAINT "FK_266f1f927ed89d829a2349d2e20" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceLabel" ADD CONSTRAINT "FK_c88790ffdbb71aa66a5795be229" FOREIGN KEY ("scheduledMaintenanceId") REFERENCES "ScheduledMaintenance"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceLabel" ADD CONSTRAINT "FK_4f3c993200714127eb2d0851cc4" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "AnnouncementStatusPage" ADD CONSTRAINT "FK_f10f946bb8a5da2ef3955578053" FOREIGN KEY ("announcementId") REFERENCES "StatusPageAnnouncement"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "AnnouncementStatusPage" ADD CONSTRAINT "FK_d7895bb35944a68cccf8286521d" FOREIGN KEY ("statusPageId") REFERENCES "StatusPage"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriberStatusPageResource" ADD CONSTRAINT "FK_618293911d2e52dc3c6a6873b4c" FOREIGN KEY ("statusPageSubscriberId") REFERENCES "StatusPageSubscriber"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriberStatusPageResource" ADD CONSTRAINT "FK_f4af9c94a4b3ba11b4739a25ef4" FOREIGN KEY ("statusPageResourceId") REFERENCES "StatusPageResource"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermissionLabel" ADD CONSTRAINT "FK_7281dda2d397d6d75c2f5285bb8" FOREIGN KEY ("teamPermissionId") REFERENCES "TeamPermission"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermissionLabel" ADD CONSTRAINT "FK_4523aa1dd9163aaf37698d137e1" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceLabel" ADD CONSTRAINT "FK_94d495f938d819dab20480c5f80" FOREIGN KEY ("telemetryServiceId") REFERENCES "TelemetryService"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceLabel" ADD CONSTRAINT "FK_7711266422ebad1188c4879d669" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowLabel" ADD CONSTRAINT "FK_3747a5f42be5c977e574abcd713" FOREIGN KEY ("workflowId") REFERENCES "Workflow"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowLabel" ADD CONSTRAINT "FK_4e72fad380eca9abfa3b9895546" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "WorkflowLabel" DROP CONSTRAINT "FK_4e72fad380eca9abfa3b9895546"`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowLabel" DROP CONSTRAINT "FK_3747a5f42be5c977e574abcd713"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceLabel" DROP CONSTRAINT "FK_7711266422ebad1188c4879d669"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceLabel" DROP CONSTRAINT "FK_94d495f938d819dab20480c5f80"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermissionLabel" DROP CONSTRAINT "FK_4523aa1dd9163aaf37698d137e1"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermissionLabel" DROP CONSTRAINT "FK_7281dda2d397d6d75c2f5285bb8"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriberStatusPageResource" DROP CONSTRAINT "FK_f4af9c94a4b3ba11b4739a25ef4"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriberStatusPageResource" DROP CONSTRAINT "FK_618293911d2e52dc3c6a6873b4c"`, + ); + await queryRunner.query( + `ALTER TABLE "AnnouncementStatusPage" DROP CONSTRAINT "FK_d7895bb35944a68cccf8286521d"`, + ); + await queryRunner.query( + `ALTER TABLE "AnnouncementStatusPage" DROP CONSTRAINT "FK_f10f946bb8a5da2ef3955578053"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceLabel" DROP CONSTRAINT "FK_4f3c993200714127eb2d0851cc4"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceLabel" DROP CONSTRAINT "FK_c88790ffdbb71aa66a5795be229"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStatusPage" DROP CONSTRAINT "FK_266f1f927ed89d829a2349d2e20"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStatusPage" DROP CONSTRAINT "FK_fcab6c2c59de7fa9e1a4ed52d37"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceMonitor" DROP CONSTRAINT "FK_d1a5797fdd98c1fa2f99670aab8"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceMonitor" DROP CONSTRAINT "FK_9f74e5e95ad88301cbc6e97da6d"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDownMonitorStatus" DROP CONSTRAINT "FK_e9f66cc920a6dfd8b20be8497cf"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDownMonitorStatus" DROP CONSTRAINT "FK_5d5aa67b52755d47e81bb19febf"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageLabel" DROP CONSTRAINT "FK_6842f6301436d26a31154062793"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageLabel" DROP CONSTRAINT "FK_d409a46e63e25fbd4fcc9d5242f"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSsoTeam" DROP CONSTRAINT "FK_d6d43b15c7dca2734a768379f8c"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSsoTeam" DROP CONSTRAINT "FK_43c6fb265bd3b69e26f1d98b66c"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLabel" DROP CONSTRAINT "FK_c27ee1fc2df7788145ed9e3333c"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLabel" DROP CONSTRAINT "FK_facb426dc0647d760bba573c2dd"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecretMonitor" DROP CONSTRAINT "FK_8ad10d0cd8fd68ac64ae3967dc9"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecretMonitor" DROP CONSTRAINT "FK_fed65a7701822d21a66810bfe29"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupLabel" DROP CONSTRAINT "FK_a91be5ccf47cbd470c3f9ee5606"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupLabel" DROP CONSTRAINT "FK_2fed2a41449af9a9cf821b759de"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateLabel" DROP CONSTRAINT "FK_fcf64673a74380a67159376b85f"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateLabel" DROP CONSTRAINT "FK_d527ebc73a91eefcae4beaaf822"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOnCallDutyPolicy" DROP CONSTRAINT "FK_2b76b57ea6659f97a4bcd1156c1"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOnCallDutyPolicy" DROP CONSTRAINT "FK_99245f38769689fe8a172dcb81a"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateMonitor" DROP CONSTRAINT "FK_33fb90ba0116b7db4efd4ec7a45"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateMonitor" DROP CONSTRAINT "FK_390a45e855282ae55ad56d1e1fc"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentLabel" DROP CONSTRAINT "FK_17f4085273d14d4d6145cf65855"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentLabel" DROP CONSTRAINT "FK_1084d1ddbabbcfcab7cd9d547a4"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOnCallDutyPolicy" DROP CONSTRAINT "FK_f89b23e3cafd1c6a0bfd42c297d"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOnCallDutyPolicy" DROP CONSTRAINT "FK_2d127b6da0e4fab9f905b4d332d"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentMonitor" DROP CONSTRAINT "FK_9e2000a938f2e12c6653e68780c"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentMonitor" DROP CONSTRAINT "FK_55e3162c1259b1b092f0ac63eeb"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyLabel" DROP CONSTRAINT "FK_1c0248fe6856bbe029fc492ec71"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyLabel" DROP CONSTRAINT "FK_d4a3f7dc4e33b896b0984e73164"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorLabel" DROP CONSTRAINT "FK_1866fb21ea1acd3d5c37e28ca1b"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorLabel" DROP CONSTRAINT "FK_3af64cebb5a7cc7f1ad0aa70c11"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermissionLabel" DROP CONSTRAINT "FK_4db7f66405df73287c639ece904"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermissionLabel" DROP CONSTRAINT "FK_0946f6b41113cd842ee69f69fb1"`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowVariable" DROP CONSTRAINT "FK_3e414e10cb4927e233ffd32651c"`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowVariable" DROP CONSTRAINT "FK_92fbc4d230accb3d12c098ca4d2"`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowVariable" DROP CONSTRAINT "FK_c093a47a2ecfa1d5f2d4aeb04a0"`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowVariable" DROP CONSTRAINT "FK_bb47f6d0cabd55fde5b199ae206"`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowLog" DROP CONSTRAINT "FK_a4e2e2861f3ece2b7d6d5e399e2"`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowLog" DROP CONSTRAINT "FK_55293b16d84f048f44c771595bb"`, + ); + await queryRunner.query( + `ALTER TABLE "WorkflowLog" DROP CONSTRAINT "FK_6c3dc31b09a96d81982a472e22b"`, + ); + await queryRunner.query( + `ALTER TABLE "Workflow" DROP CONSTRAINT "FK_367e2e759f520b31d727d22b803"`, + ); + await queryRunner.query( + `ALTER TABLE "Workflow" DROP CONSTRAINT "FK_13c42a014f8c10862f23d02eb49"`, + ); + await queryRunner.query( + `ALTER TABLE "Workflow" DROP CONSTRAINT "FK_a87518833c744b2df4324b61a6d"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_815c728d905c44bc440ec91308b"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_12ef8407b6359205df8339f8494"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_d5c3df01bbb2a9ce168b36b5234"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_a93a41d65df4cbe518393695084"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_03d67a4d7fa9f087327ab0f74a7"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_6650020ce6e235164a09d1ca019"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_222212e7157f8816357a4f02536"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_26e16ed3e46c6a6589a88d3abba"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_6491952f9d8066aa5cfff92cd85"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_58a44736718a5ec4fe41526289a"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_ef993920a9967b61fb3fb9bf162"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_06a427cdcbae1ddcb1301b860f2"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_dc79ea74fba7b99835bd475081c"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_79580470f34858375cae5d353a9"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLogTimeline" DROP CONSTRAINT "FK_2a893e347fdab643867abd8dda7"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_123b1825525b963fe9555d62641"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_b70804c755c008c15794b6cc18d"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_ac31bad932e24418ce0bb1bbb14"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_91488d7d3341bf1113902f4786c"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_7498ad42c4f77f7fab2a6bc2e33"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_eeb0dd05d1dec542c3de5fb5074"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_550f9f6177451d4902467991a15"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_f047dd6a708175bae0ee6f8c4bf"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_4cd37d481ef366d975c6a7cd9bf"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_ea993eab5c402623b61203e3256"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_3a4b15ce8357c6735ac1b4ae606"`, + ); + await queryRunner.query( + `ALTER TABLE "UserOnCallLog" DROP CONSTRAINT "FK_7941aa92c7c740400b272d69072"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationSetting" DROP CONSTRAINT "FK_b023f12dc00bcfc50d6d9ad4f71"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationSetting" DROP CONSTRAINT "FK_aee7abeffd1c60d49f710fb3749"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationSetting" DROP CONSTRAINT "FK_e2565b0aa7d7e015fb6685afede"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationSetting" DROP CONSTRAINT "FK_6f110fc752889f922d6a3c57a55"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_bf703b216d7f59424302dc5d70b"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_b407063ee43233b0cc8e9106cbb"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_32ca4397660dafe0cab7d03e5b1"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_ec8e7a39fcd38d0ea2d40b8afaf"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_b1292f2480d0c4985898d7bf33a"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_85b73b64802058915df58fa013b"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_32efb91af1b432a25ceb55bc0dc"`, + ); + await queryRunner.query( + `ALTER TABLE "UserNotificationRule" DROP CONSTRAINT "FK_69d439e9f60f05ae16732c49999"`, + ); + await queryRunner.query( + `ALTER TABLE "UserSMS" DROP CONSTRAINT "FK_0bae98162ec44540ff85f724daa"`, + ); + await queryRunner.query( + `ALTER TABLE "UserSMS" DROP CONSTRAINT "FK_99fc3cdf366fd3d266fbf2d657c"`, + ); + await queryRunner.query( + `ALTER TABLE "UserSMS" DROP CONSTRAINT "FK_3cb16b5c2d69dbdc812247788f8"`, + ); + await queryRunner.query( + `ALTER TABLE "UserSMS" DROP CONSTRAINT "FK_6439689a29a2192708e3f3603da"`, + ); + await queryRunner.query( + `ALTER TABLE "UserEmail" DROP CONSTRAINT "FK_a1aa5e10dcfb571521324bbd665"`, + ); + await queryRunner.query( + `ALTER TABLE "UserEmail" DROP CONSTRAINT "FK_06413c119aae9c3f75154c2346c"`, + ); + await queryRunner.query( + `ALTER TABLE "UserEmail" DROP CONSTRAINT "FK_1f713c701d85c69f706e4e82b85"`, + ); + await queryRunner.query( + `ALTER TABLE "UserEmail" DROP CONSTRAINT "FK_e6beed22b36201aea1d70ba0d72"`, + ); + await queryRunner.query( + `ALTER TABLE "UserCall" DROP CONSTRAINT "FK_996ab46825df7f3512e735c450c"`, + ); + await queryRunner.query( + `ALTER TABLE "UserCall" DROP CONSTRAINT "FK_1b46d8793ef542c059369481d42"`, + ); + await queryRunner.query( + `ALTER TABLE "UserCall" DROP CONSTRAINT "FK_4d90bd0e309ad43c4541bb428e9"`, + ); + await queryRunner.query( + `ALTER TABLE "UserCall" DROP CONSTRAINT "FK_db4e522c086be556e5101c4e910"`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryUsageBilling" DROP CONSTRAINT "FK_510252373d4e5917029308384fb"`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryUsageBilling" DROP CONSTRAINT "FK_d71562eb0c2861797502bd99917"`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryUsageBilling" DROP CONSTRAINT "FK_91333210492e5d2f334231468a7"`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryUsageBilling" DROP CONSTRAINT "FK_5670be95a9496b9380b7c0d7935"`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryService" DROP CONSTRAINT "FK_46ea9e637b4454993665a436d56"`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryService" DROP CONSTRAINT "FK_5d0b92dc9ab2bfd71432e9a3536"`, + ); + await queryRunner.query( + `ALTER TABLE "TelemetryService" DROP CONSTRAINT "FK_3a3321fd538aa014aa5e4f35220"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermission" DROP CONSTRAINT "FK_73a2d0db1de4e66582e376098de"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermission" DROP CONSTRAINT "FK_e2c33d5f98cb42f8c1f76a85095"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermission" DROP CONSTRAINT "FK_5064c0bdc8ff238952f9a2acf43"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamPermission" DROP CONSTRAINT "FK_78293e9cc1746e5f29ccccfdfc0"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_945ca87238e7465782215c25d8d"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_a9e764a6ad587e6e386abe3b9de"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_4ab0af827040dbce6ef21ec7780"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_fd952f76f5a5297ce69a9a588eb"`, + ); + await queryRunner.query( + `ALTER TABLE "TeamMember" DROP CONSTRAINT "FK_3cc297d538f01065f9925cfb11a"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriber" DROP CONSTRAINT "FK_35ad85d2f341ebfeaca7ad67af1"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriber" DROP CONSTRAINT "FK_61cecfd27c2d41eb58330df1d8c"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriber" DROP CONSTRAINT "FK_217b295d5882faa6cf3418ed811"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriber" DROP CONSTRAINT "FK_6adf943966e01699e86117d2e34"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSSO" DROP CONSTRAINT "FK_0bfc26bce8ea92b8b8a9e0400de"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSSO" DROP CONSTRAINT "FK_8e2cbcf07eba956fe976ca3d043"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSSO" DROP CONSTRAINT "FK_dc05de7939af3ada1567fc7106b"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageSSO" DROP CONSTRAINT "FK_1d63fa142dd4175ef256f21d2a6"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_d2b2f7ffe8f976fda20f4b96c5b"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_51e0fbc6d460394b1cd38959790"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_9b61276c47d6091295dec9e5350"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_a55bb812676bff276cef1f14c86"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_b1c4fe08e1d90ae4d26d934653c"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_ade3f7acf902dcb313d230ca1f5"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageResource" DROP CONSTRAINT "FK_8fba35fb87a0ad6037eb8fc8040"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPagePrivateUser" DROP CONSTRAINT "FK_524d2e71f90ef8f78d85d5fdfd1"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPagePrivateUser" DROP CONSTRAINT "FK_e47c85ead36095d040493775a3f"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPagePrivateUser" DROP CONSTRAINT "FK_0589c51161d13b752fed41a3193"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPagePrivateUser" DROP CONSTRAINT "FK_1ce45fe77324ede75166d0f57dd"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_8d7351e844adfd5c279fd8e9f3b"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_4ecb38fa1941bb0961641803f21"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_69b1659abafe656563259784d02"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_3c59f811e0660c5522e45e85b6a"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerUser" DROP CONSTRAINT "FK_c28e05c08656f8aa756734c37c5"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_e992fcc346afa21a89ba9f75f25"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_7c1168daf53c46678045ff39d31"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_6a98e42d8df3ba84bd0f79da550"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_f60296efefe379693bfd55a7760"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageOwnerTeam" DROP CONSTRAINT "FK_9fac66064d88c514d2e5503237a"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHistoryChartBarColorRule" DROP CONSTRAINT "FK_8041d41239c4218bf136bf20591"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHistoryChartBarColorRule" DROP CONSTRAINT "FK_5d973aa991ba9f06b642d3fc9d7"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHistoryChartBarColorRule" DROP CONSTRAINT "FK_54edc7ff7a74a0310a512c53895"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHistoryChartBarColorRule" DROP CONSTRAINT "FK_a65f8fabf888d227d64570f52b3"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHeaderLink" DROP CONSTRAINT "FK_0d3a63f1c684e78297b213c348e"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHeaderLink" DROP CONSTRAINT "FK_88048566089097605e26fdb2893"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHeaderLink" DROP CONSTRAINT "FK_99c017b6ced8da63abdfbb506eb"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageHeaderLink" DROP CONSTRAINT "FK_70d70692cbe9d9be188723df4f0"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageGroup" DROP CONSTRAINT "FK_61191c9c00f7279615e13af4bbd"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageGroup" DROP CONSTRAINT "FK_0a63a8ee804658921edf1e870af"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageGroup" DROP CONSTRAINT "FK_5dbcfd7d38e5ea7a78a6a78a330"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageGroup" DROP CONSTRAINT "FK_4a96c34f030a6e39218352a947a"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageFooterLink" DROP CONSTRAINT "FK_0328201140b59b4b813f83b06a9"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageFooterLink" DROP CONSTRAINT "FK_bd6f15ab951095e624ea664d9a6"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageFooterLink" DROP CONSTRAINT "FK_1a80c698b2205074f53376d631b"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageFooterLink" DROP CONSTRAINT "FK_5f8f65447c9b881860bf742dc98"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_6c82107f63d1a3186d579a6d9cb"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_106e359f945432d6583bd30ff4b"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_842a66fcb103388fcedffef75f7"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_7fab5bc54a8f36eac8f31c82565"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageDomain" DROP CONSTRAINT "FK_40cca185c8cf933c04a0534676b"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageCustomField" DROP CONSTRAINT "FK_26b4a892f3b31c5b0b285c4e5cb"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageCustomField" DROP CONSTRAINT "FK_e0abd7540f860de19607dc25bc0"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageCustomField" DROP CONSTRAINT "FK_4f5ae90bc48a0ddeb50cd009aaf"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageAnnouncement" DROP CONSTRAINT "FK_7251cbbaa75eb9570830b0cab32"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageAnnouncement" DROP CONSTRAINT "FK_1491bd0895d515969eee2a08c80"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPageAnnouncement" DROP CONSTRAINT "FK_5e9c5a7393ac9aa477d625de673"`, + ); + await queryRunner.query( + `ALTER TABLE "SmsLog" DROP CONSTRAINT "FK_d00778bcfaa735fbb5dc91c1945"`, + ); + await queryRunner.query( + `ALTER TABLE "SmsLog" DROP CONSTRAINT "FK_a30bbda7f5480e498ebc609663b"`, + ); + await queryRunner.query( + `ALTER TABLE "ShortLink" DROP CONSTRAINT "FK_11f179cd8e9beee22b89c316972"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_7c0f750d3a964180d1e1efa16ea"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_aa84fcdf2fef6c2005ebab2c197"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_2392299477cfc4f612ecb73e839"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_411146017bcfe98bbe028b8d15a"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceStateTimeline" DROP CONSTRAINT "FK_4faf556988f5b6a755ef2e85ae8"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenancePublicNote" DROP CONSTRAINT "FK_28e179283c409e0751aae713949"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenancePublicNote" DROP CONSTRAINT "FK_1f67cfb63bd3488b7c5c5b7fac7"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenancePublicNote" DROP CONSTRAINT "FK_937aabd4adbfce78663406a2487"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenancePublicNote" DROP CONSTRAINT "FK_73c6737ab4a7718c45932bffada"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_6e6b087ba99fe433f83f87e0a35"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_c91d4d420e3faaf15fa928fd214"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_967c33f7bce5de522c1d1a80e7b"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_be6a25806925f93b8949e61929b"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerUser" DROP CONSTRAINT "FK_518742c7037d9a38a6594dc18a6"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_52a3a932530026bafef87e62177"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_cc0e8ca9e9065ca0cc24bf6275b"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_1251fb7d4a4bf8586f2bd1528eb"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_1206beb611e0779ce2248ecbaeb"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceOwnerTeam" DROP CONSTRAINT "FK_e3053b1725658b4a120b4e3185d"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceNoteTemplate" DROP CONSTRAINT "FK_4c3d6b87bb1e8739cdeb8b92f74"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceNoteTemplate" DROP CONSTRAINT "FK_e38c1102001ae0b70c22e046424"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceNoteTemplate" DROP CONSTRAINT "FK_b4cb4c1312eb72459907e1bbe9b"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceInternalNote" DROP CONSTRAINT "FK_7fb00788b6ac97988dd43e2e1b2"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceInternalNote" DROP CONSTRAINT "FK_69757967d2ee696f487fb8ac37e"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceInternalNote" DROP CONSTRAINT "FK_a53ef45aebd4a6a6e7dde7f896a"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceInternalNote" DROP CONSTRAINT "FK_d79c49a0a613a6b432fd400e69b"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceCustomField" DROP CONSTRAINT "FK_9094eed77fb6e8f7ecf1502f5e0"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceCustomField" DROP CONSTRAINT "FK_c7cdb245d3d98be14482f092eca"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceCustomField" DROP CONSTRAINT "FK_3621c488327e1c00518aa4e8816"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_fab9cc7e6ffcf02872fccfab978"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_883038abda021ce79fa838d0273"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_50ddf8bb21e988ea5d419a66cb9"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_5442fd86c96d45e062d5ee1f093"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenance" DROP CONSTRAINT "FK_4059dd569d6a482062352bf266a"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_4e347d3f99b67dacd149beaf61d"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_61944d851b4a7213d79ef281744"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_602b456a61d73a96e97f483064d"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_84df83d1f492a19a08aee465500"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_1f17f293352ebdc3bcf383158cc"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_71f429afb7678d132472b3c87b0"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_da54bb2c4997ee1a3b73026d7f5"`, + ); + await queryRunner.query( + `ALTER TABLE "StatusPage" DROP CONSTRAINT "FK_34a4c35e0d7afe6f023825a68c7"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceState" DROP CONSTRAINT "FK_4f803428e0926584d1f7c44a3d4"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceState" DROP CONSTRAINT "FK_88044fd50006f1897e8c760d136"`, + ); + await queryRunner.query( + `ALTER TABLE "ScheduledMaintenanceState" DROP CONSTRAINT "FK_57a31fb2a5e4caa223d2506a4e1"`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_a7b405e2a9ae144be016bcf973d"`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_7aecf4b1ae3e45647cb911f4c10"`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_6dcdf97c0834dd44b4f2c93e664"`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_3169f7934171e8f697bb993b010"`, + ); + await queryRunner.query( + `ALTER TABLE "PromoCode" DROP CONSTRAINT "FK_90e44f45272c0da256951183086"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSSO" DROP CONSTRAINT "FK_00ea9e456217ffbfff35f1e944f"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSSO" DROP CONSTRAINT "FK_28011315533e2d819295d261ee4"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSSO" DROP CONSTRAINT "FK_be9e6751765501ea1db126fcb23"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectCallSMSConfig" DROP CONSTRAINT "FK_f5bc0e2b81886b21004e2a5f67b"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectCallSMSConfig" DROP CONSTRAINT "FK_e873aa20a371bd92e220332a992"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectCallSMSConfig" DROP CONSTRAINT "FK_20334b9571a6cd1a871e70d8e76"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_416e830c88f2ecfa149f4cd51c8"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_49e5a41e1d771fe9e385295bd9a"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_b2ccbfcc3964caf3dfd89243f8f"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_41f4ecc29351e1a406e83b30a93"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_9b36bba6d9898331920805a29ca"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayerUser" DROP CONSTRAINT "FK_08afccd6cbbd1a7015d4fe25e39"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" DROP CONSTRAINT "FK_f22b52355207d2c0d5a13c168e8"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" DROP CONSTRAINT "FK_1db1083a896b0f77a0e87f26463"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" DROP CONSTRAINT "FK_6fa6574a45cf1352c5a3b962512"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" DROP CONSTRAINT "FK_3b892ef36671f1ea1c8457a96d6"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_166f3696b3c70989507dd7e1f2e"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_43f833a79cf4201b3fa1deed023"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_65998388ab4266dda712502ad65"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_039098d4af133bd9c2b90978ef4"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_0ae6ea2e2d38fd31543768e3609"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_0ad4222a4c48b8a64e3a58b3519"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_872c2f6a9739bab1b57b6d51eac"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_90119ec7f77fa2efd82261e0448"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_c5a798ca667fedda71d4ed54651"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLogTimeline" DROP CONSTRAINT "FK_591b7ed73c964e105bfecc6fd6d"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_d4a0ffc5e9e698bb2612ba0e55f"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_3015ddfeb130417c55489da807e"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_63f6618df216b74b72e62491b09"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_e9302e15399b67938e0121a0545"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_16b426d34ff2c5cbd6ecfd70820"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_a230899d6c2b16621954c46fb16"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_e4bb332263960531a4c9e2d4254"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyExecutionLog" DROP CONSTRAINT "FK_00e9676d39eeb807de704430512"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_d35f668f524cc88f580a7651fe2"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_f9a45cea88022a9cf5b96c13e65"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_5a7b7a746409a175423a1bbd5c4"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_bff6f4ae726b5c5cbae10e7d743"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_9ca3fbb66842324aa987d4c9722"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleUser" DROP CONSTRAINT "FK_b15552e664640f67346193598a1"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_73ae2b2702aef4601c39d4d909a"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_da2e065de293a14b69964fb3233"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_0e7d4060e2fabe0957b9fedb429"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_26ba1a6edd877647cedd1eae8ca"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_f5121f361345d858ce740a55a25"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleTeam" DROP CONSTRAINT "FK_c8aff8439fbfb07e7388aa9011b"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_878e14be4e6366ec646f874347a"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_90700af75cbe8129db898ac8adb"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_089081f83ef22cdba5a0903ce59"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_13a87c38fe2efa41940783af690"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_651a4ab9e3cbb20f6b62a87a6b8"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRuleSchedule" DROP CONSTRAINT "FK_efa24aa8feb92d4e15a707c20ec"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicySchedule" DROP CONSTRAINT "FK_01e63400072d0bc6debee836cbf"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicySchedule" DROP CONSTRAINT "FK_ecb5141b27e85674c294a2541b3"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicySchedule" DROP CONSTRAINT "FK_0e1b7c3c3e8305a10716cdb8d6e"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRule" DROP CONSTRAINT "FK_5c0911d261a941b00d41b6e5fda"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRule" DROP CONSTRAINT "FK_ad8097a9359965d02ccbb16358b"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRule" DROP CONSTRAINT "FK_b90c1cda36981c41e3965a93800"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyEscalationRule" DROP CONSTRAINT "FK_d45a545669dc46da25cc60d1df5"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyCustomField" DROP CONSTRAINT "FK_43230e739b31e3f56284407b586"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyCustomField" DROP CONSTRAINT "FK_456bff32fd0428134ef7396385f"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyCustomField" DROP CONSTRAINT "FK_e791474e098f276eda27704b479"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_574feb4161c5216c2c7ee0faaf8"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_d293a7e96c5bf427072514f21a9"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_d7f555ef162fe878e4ed62a3e23"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_c08a7c1ef8d511b335a991aac47"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatusTimeline" DROP CONSTRAINT "FK_80213eb3f228f1e3d423f5127e2"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecret" DROP CONSTRAINT "FK_e4262f178662aaacdb54d4c4f4e"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecret" DROP CONSTRAINT "FK_a886cd3bbdfd84d01167f92cb65"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorSecret" DROP CONSTRAINT "FK_7c3629c5ae14e97bede3bc548e5"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_a182ba062c0a216395d0dbdbdee"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_4399ab64a5c00d55e5ce254deeb"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_a540272f483ef1de68f7e647486"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_2d50a0e0e624369e7f90a62e8dc"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorProbe" DROP CONSTRAINT "FK_d887935f224b896ce64872c37c7"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_e1ae2c698e6bde0a98c50162235"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_e2cf60b88171dfe5fdd0e4fe6c1"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_a65ce9b11b2d7bde123aa7633fd"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_324f1d50d0427bbd0e3308c4592"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerUser" DROP CONSTRAINT "FK_12a3497e1404fcdbc4e8963a58e"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_7ebfe3ddcf597fb73ee8eac2ff4"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_58610249ec4cf593e36210dcb84"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_1d5265d1f3ca2f8b8e461e4998c"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_63e5bbac01d1f68c7b08f126cd4"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorOwnerTeam" DROP CONSTRAINT "FK_6a6213072d8637e6e625bc78929"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_1a54eaa2d0187d10de84107a09b"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_cf595b683e26e560526404663fe"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_50b373c428cfd4566cc5caf98e6"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_cf3dcaa746835ae36615a39d862"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupResource" DROP CONSTRAINT "FK_825791d5edb2403d7937f16ed91"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_e9bced91dce29928ebeec834905"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_9267db147738caed0ccfdc3af22"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_6a4095ee3d04454071816a5bad8"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_4b25f4a18bec8cb177e8d65c5f9"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerUser" DROP CONSTRAINT "FK_d5cfbebf8b07405652f5382e157"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_fdbe93e29e60763a306358cab55"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_7ce36c144e83082213587e19c23"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_618bcd9015c257b0727df36fd11"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_8f19947114087883cea771e1cb4"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroupOwnerTeam" DROP CONSTRAINT "FK_0d7052620f268d0fa17f948a851"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroup" DROP CONSTRAINT "FK_edd658b85b2ef7ac9b2f0687d8a"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroup" DROP CONSTRAINT "FK_abaf236c1877143fe160991cc45"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorGroup" DROP CONSTRAINT "FK_13905ff40843b11145f21e33ff5"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorCustomField" DROP CONSTRAINT "FK_817e69522c8d2f1e2fd3f857e91"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorCustomField" DROP CONSTRAINT "FK_93a4da4182f93ba24ab958c1b73"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorCustomField" DROP CONSTRAINT "FK_1c6b61e904d8e3fec1ae719b9ef"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_026e918a31de467eeb8e30ae8d1"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_a53f8aab99766a87c73c52b9037"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_876dd05e0dfc64219ef5df241c3"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_36b9b0204f4b17063483cb7308e"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerUser" DROP CONSTRAINT "FK_da9dd65b4401b954a0ea2b5c8d5"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_af037dc245d77c282061fea1b1b"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_3e8a4bd1594da3438d8fb8a6687"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_a895b946fccb109dedd55b85f6d"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_e8090c6569c3a5dbd7ef7485c99"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplateOwnerTeam" DROP CONSTRAINT "FK_cf172eb6797a64ee3750e3f3e21"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_6d7627ab9d5172c66fc50192163"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_ceff6a4dfdccecc4aa40dbfe91a"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_0e6a4e065ffb22f95ecfc259e9a"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_b03e46665e4c075ed1398fcc409"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_b82dafef226b0fae1ad6cb18570"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_ff0fca6570d47798771763533a9"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_6b6b9dbf9ca5448c9297a58ad04"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_16d198b59f3416a8ddc630a90d2"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_fe2dff414a1f67260e3c5189811"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentStateTimeline" DROP CONSTRAINT "FK_764daa366a4e195768a49e0ee39"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentPublicNote" DROP CONSTRAINT "FK_cf04d778a5502be606f63e01603"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentPublicNote" DROP CONSTRAINT "FK_691a99e582fcddcc892d8573afc"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentPublicNote" DROP CONSTRAINT "FK_a6964d3aab71608daab9f20e304"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentPublicNote" DROP CONSTRAINT "FK_a9a77e5f286b5724f4e2280d0a1"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_c473db8745d0ebeb147a72986cb"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_52591665c92658ef82944d63d26"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_6aa9a6b46f8e044d722da8f5a7f"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_6311087eeb14ab51e6a1e6133f7"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerUser" DROP CONSTRAINT "FK_0ee7ae6757442fba470b213015c"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_60242ecfcecaa5cb1c5241bed4c"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_278f483fc81c21b1bd1311ee289"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_408324d3635a826538a792422f3"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_389aadeb39a0806e80d4001016e"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentOwnerTeam" DROP CONSTRAINT "FK_95f76375ccac835f815d7e926a2"`, + ); + await queryRunner.query( + `ALTER TABLE "Team" DROP CONSTRAINT "FK_0d4912bf03a7a645ce95142155b"`, + ); + await queryRunner.query( + `ALTER TABLE "Team" DROP CONSTRAINT "FK_4be4aa023ba1c6d6443b81b3b91"`, + ); + await queryRunner.query( + `ALTER TABLE "Team" DROP CONSTRAINT "FK_baac847c494f692f03fd686e9c7"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentNoteTemplate" DROP CONSTRAINT "FK_3c00f2b005264318a274cd38a94"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentNoteTemplate" DROP CONSTRAINT "FK_515b6970fdd528d4c9f85a5e9a4"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentNoteTemplate" DROP CONSTRAINT "FK_21d5bc0d24b3e5032dd391ec8da"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentInternalNote" DROP CONSTRAINT "FK_8f23b820cbbed6d96cfedd162a2"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentInternalNote" DROP CONSTRAINT "FK_c798e09321f06d8a180916d7a5e"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentInternalNote" DROP CONSTRAINT "FK_b92e75645fd252e4c2f866047de"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentInternalNote" DROP CONSTRAINT "FK_ac48058b3e5f9e8361d2b8328c2"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentCustomField" DROP CONSTRAINT "FK_bc64c76e766b1b880845afbcbf7"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentCustomField" DROP CONSTRAINT "FK_5c1c7369e696f580186a4ff12de"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentCustomField" DROP CONSTRAINT "FK_59e7f6a43dbc5ee54e1a1aaaaf1"`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" DROP CONSTRAINT "FK_7e537806a80e869917ca1d7e2e4"`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" DROP CONSTRAINT "FK_ed0ec4960a85240f51e6779a00a"`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" DROP CONSTRAINT "FK_3f28fe3b32abed354a49b26c9cb"`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" DROP CONSTRAINT "FK_6592d4f7f3b260efc23fc9b4bc9"`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" DROP CONSTRAINT "FK_067855888a3d71803d3a5aeaecf"`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" DROP CONSTRAINT "FK_9b101f023b5db6491203d5c9951"`, + ); + await queryRunner.query( + `ALTER TABLE "Incident" DROP CONSTRAINT "FK_eccbc31fa1f58bd051b6f7e108b"`, + ); + await queryRunner.query( + `ALTER TABLE "Probe" DROP CONSTRAINT "FK_272ece82a96099041b93c9141e3"`, + ); + await queryRunner.query( + `ALTER TABLE "Probe" DROP CONSTRAINT "FK_1963e116be9832b23490cca933f"`, + ); + await queryRunner.query( + `ALTER TABLE "Probe" DROP CONSTRAINT "FK_b357696dc9462ad3f9e84c6dc52"`, + ); + await queryRunner.query( + `ALTER TABLE "Probe" DROP CONSTRAINT "FK_6bd931aae4920e296ea08864cd0"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicy" DROP CONSTRAINT "FK_0424b49cfcd68cdd1721df53acd"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicy" DROP CONSTRAINT "FK_c0c63ac58f97fd254bb5c2813dc"`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicy" DROP CONSTRAINT "FK_31508550a088ba2cc843a6c90c4"`, + ); + await queryRunner.query( + `ALTER TABLE "Monitor" DROP CONSTRAINT "FK_d3461ab640467c8c2100ea55c79"`, + ); + await queryRunner.query( + `ALTER TABLE "Monitor" DROP CONSTRAINT "FK_73bdf22259019b90836aac86b28"`, + ); + await queryRunner.query( + `ALTER TABLE "Monitor" DROP CONSTRAINT "FK_a84bbba0dbad47918136d4dfb43"`, + ); + await queryRunner.query( + `ALTER TABLE "Monitor" DROP CONSTRAINT "FK_996acfb590bda327843f78b7ad3"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatus" DROP CONSTRAINT "FK_55a0e488581a0d02bcdd67a4348"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatus" DROP CONSTRAINT "FK_bdda7fecdf44ed43ef2004e7be5"`, + ); + await queryRunner.query( + `ALTER TABLE "MonitorStatus" DROP CONSTRAINT "FK_db1783158a23bd20dbebaae56ef"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentState" DROP CONSTRAINT "FK_88a0ecd4b1714ac0e2eef9ac27d"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentState" DROP CONSTRAINT "FK_eb33bd015e0e57ee96b60f8d773"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentState" DROP CONSTRAINT "FK_3d279e530067f599f3186e3821d"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentSeverity" DROP CONSTRAINT "FK_d0d87151a7872a44c3d2457bfdc"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentSeverity" DROP CONSTRAINT "FK_2677e0a9dbf97ba0f4a7849eac6"`, + ); + await queryRunner.query( + `ALTER TABLE "IncidentSeverity" DROP CONSTRAINT "FK_00d2f503174bf201abc6e77afde"`, + ); + await queryRunner.query( + `ALTER TABLE "GreenlockChallenge" DROP CONSTRAINT "FK_7517f5a285255f031b0f6d9663c"`, + ); + await queryRunner.query( + `ALTER TABLE "GreenlockCertificate" DROP CONSTRAINT "FK_895b9b802ed002a3804136bacf1"`, + ); + await queryRunner.query( + `ALTER TABLE "EmailVerificationToken" DROP CONSTRAINT "FK_9e86ebfdbef16789e9571f22074"`, + ); + await queryRunner.query( + `ALTER TABLE "EmailVerificationToken" DROP CONSTRAINT "FK_0b65c7ffb685f6ed78aac195b1a"`, + ); + await queryRunner.query( + `ALTER TABLE "EmailLog" DROP CONSTRAINT "FK_6d0739da601917d316494fcae3b"`, + ); + await queryRunner.query( + `ALTER TABLE "EmailLog" DROP CONSTRAINT "FK_046364c162885b6ac65d5dd367c"`, + ); + await queryRunner.query( + `ALTER TABLE "EmailLog" DROP CONSTRAINT "FK_7b72c5131b3dd1f3edf201a561c"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSMTPConfig" DROP CONSTRAINT "FK_d5458705e98b89c08c0d960422e"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSMTPConfig" DROP CONSTRAINT "FK_3b7ed2d3bd1a2ee9638cccef5b0"`, + ); + await queryRunner.query( + `ALTER TABLE "ProjectSMTPConfig" DROP CONSTRAINT "FK_a540dab929fa6582b93f258ffe5"`, + ); + await queryRunner.query( + `ALTER TABLE "Domain" DROP CONSTRAINT "FK_9ace4c275b42c057b7581543ce3"`, + ); + await queryRunner.query( + `ALTER TABLE "Domain" DROP CONSTRAINT "FK_12e6ebc5c806263d562045e9282"`, + ); + await queryRunner.query( + `ALTER TABLE "Domain" DROP CONSTRAINT "FK_f1ce90d3f9693be29b72fabe93b"`, + ); + await queryRunner.query( + `ALTER TABLE "DataMigrations" DROP CONSTRAINT "FK_183a8261590c30a27a1b51f4bdb"`, + ); + await queryRunner.query( + `ALTER TABLE "DataMigrations" DROP CONSTRAINT "FK_1619179d46a4411e1bb4af5d342"`, + ); + await queryRunner.query( + `ALTER TABLE "CallLog" DROP CONSTRAINT "FK_3e510124d923fe3b994936a7cb5"`, + ); + await queryRunner.query( + `ALTER TABLE "CallLog" DROP CONSTRAINT "FK_5648767682195afaeb09098a213"`, + ); + await queryRunner.query( + `ALTER TABLE "BillingPaymentMethod" DROP CONSTRAINT "FK_93a1554cb316127896f66acddd3"`, + ); + await queryRunner.query( + `ALTER TABLE "BillingPaymentMethod" DROP CONSTRAINT "FK_55c3c9a9fc28000262b811cebc8"`, + ); + await queryRunner.query( + `ALTER TABLE "BillingPaymentMethod" DROP CONSTRAINT "FK_db4bb9add01b7d8286869fd9a0a"`, + ); + await queryRunner.query( + `ALTER TABLE "BillingInvoice" DROP CONSTRAINT "FK_0a0a1a9865d157e46b1ecf14873"`, + ); + await queryRunner.query( + `ALTER TABLE "BillingInvoice" DROP CONSTRAINT "FK_15b8130f5378f2079ed5b2fe7d1"`, + ); + await queryRunner.query( + `ALTER TABLE "BillingInvoice" DROP CONSTRAINT "FK_0ab13e9a92ce4801c37c2a0a77f"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" DROP CONSTRAINT "FK_ac42ef4597147c260e89a0f3d3a"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" DROP CONSTRAINT "FK_dc8eb846ffbceafbc9c60bbfaa5"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" DROP CONSTRAINT "FK_fb09dd7fefa9d5d44b1907be5fd"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" DROP CONSTRAINT "FK_0cf347c575f15d3836615f53258"`, + ); + await queryRunner.query( + `ALTER TABLE "Label" DROP CONSTRAINT "FK_f46caf81c5fd7664ba8da9c99ba"`, + ); + await queryRunner.query( + `ALTER TABLE "Label" DROP CONSTRAINT "FK_84520cbda97d2a9cb9da7ccb18c"`, + ); + await queryRunner.query( + `ALTER TABLE "Label" DROP CONSTRAINT "FK_f10d59c2ba66e085722e0053cb7"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKey" DROP CONSTRAINT "FK_bcbc7d80fb0cfe2cbb5ae7db791"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKey" DROP CONSTRAINT "FK_891c55549057af9a0c90c925ebb"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKey" DROP CONSTRAINT "FK_bb1019f0078a21b4854f5cb3ed4"`, + ); + await queryRunner.query( + `ALTER TABLE "Project" DROP CONSTRAINT "FK_b5ee87614c184778810283c2991"`, + ); + await queryRunner.query( + `ALTER TABLE "Project" DROP CONSTRAINT "FK_4ee6a519d48b26fe2a78fdc1c9c"`, + ); + await queryRunner.query( + `ALTER TABLE "Project" DROP CONSTRAINT "FK_43989dee7f7af742f6d8ec2664a"`, + ); + await queryRunner.query( + `ALTER TABLE "Project" DROP CONSTRAINT "FK_639312a8ef82cbd5cee77c5b1ba"`, + ); + await queryRunner.query( + `ALTER TABLE "ResellerPlan" DROP CONSTRAINT "FK_e756416e4b0983e158f71c47c1a"`, + ); + await queryRunner.query( + `ALTER TABLE "ResellerPlan" DROP CONSTRAINT "FK_34cdc5e0500513f321f0da35a64"`, + ); + await queryRunner.query( + `ALTER TABLE "ResellerPlan" DROP CONSTRAINT "FK_fc269bd109ac405a458b2acc678"`, + ); + await queryRunner.query( + `ALTER TABLE "Reseller" DROP CONSTRAINT "FK_952b3ed48545aaf18033150dc66"`, + ); + await queryRunner.query( + `ALTER TABLE "Reseller" DROP CONSTRAINT "FK_fe790bb94630d701a8ad93287ce"`, + ); + await queryRunner.query( + `ALTER TABLE "AcmeChallenge" DROP CONSTRAINT "FK_71371b224feb48f1d60e847cf1b"`, + ); + await queryRunner.query( + `ALTER TABLE "AcmeCertificate" DROP CONSTRAINT "FK_130a8fd12e7505eebfce670b198"`, + ); + await queryRunner.query( + `ALTER TABLE "User" DROP CONSTRAINT "FK_644c3c0393979f57f71892ff0d7"`, + ); + await queryRunner.query( + `ALTER TABLE "User" DROP CONSTRAINT "FK_1f25f5fc0032f7014482d9c195e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_4e72fad380eca9abfa3b989554"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_3747a5f42be5c977e574abcd71"`, + ); + await queryRunner.query(`DROP TABLE "WorkflowLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_7711266422ebad1188c4879d66"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_94d495f938d819dab20480c5f8"`, + ); + await queryRunner.query(`DROP TABLE "ServiceLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_4523aa1dd9163aaf37698d137e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7281dda2d397d6d75c2f5285bb"`, + ); + await queryRunner.query(`DROP TABLE "TeamPermissionLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_f4af9c94a4b3ba11b4739a25ef"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_618293911d2e52dc3c6a6873b4"`, + ); + await queryRunner.query( + `DROP TABLE "StatusPageSubscriberStatusPageResource"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d7895bb35944a68cccf8286521"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_f10f946bb8a5da2ef395557805"`, + ); + await queryRunner.query(`DROP TABLE "AnnouncementStatusPage"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_4f3c993200714127eb2d0851cc"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_c88790ffdbb71aa66a5795be22"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_266f1f927ed89d829a2349d2e2"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_fcab6c2c59de7fa9e1a4ed52d3"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceStatusPage"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_d1a5797fdd98c1fa2f99670aab"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_9f74e5e95ad88301cbc6e97da6"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceMonitor"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_e9f66cc920a6dfd8b20be8497c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_5d5aa67b52755d47e81bb19feb"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageDownMonitorStatus"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_6842f6301436d26a3115406279"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d409a46e63e25fbd4fcc9d5242"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_d6d43b15c7dca2734a768379f8"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_43c6fb265bd3b69e26f1d98b66"`, + ); + await queryRunner.query(`DROP TABLE "ProjectSsoTeam"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_c27ee1fc2df7788145ed9e3333"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_facb426dc0647d760bba573c2d"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicyScheduleLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_8ad10d0cd8fd68ac64ae3967dc"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_fed65a7701822d21a66810bfe2"`, + ); + await queryRunner.query(`DROP TABLE "MonitorSecretMonitor"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_a91be5ccf47cbd470c3f9ee560"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_2fed2a41449af9a9cf821b759d"`, + ); + await queryRunner.query(`DROP TABLE "MonitorGroupLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_fcf64673a74380a67159376b85"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d527ebc73a91eefcae4beaaf82"`, + ); + await queryRunner.query(`DROP TABLE "IncidentTemplateLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_2b76b57ea6659f97a4bcd1156c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_99245f38769689fe8a172dcb81"`, + ); + await queryRunner.query(`DROP TABLE "IncidentTemplateOnCallDutyPolicy"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_33fb90ba0116b7db4efd4ec7a4"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_390a45e855282ae55ad56d1e1f"`, + ); + await queryRunner.query(`DROP TABLE "IncidentTemplateMonitor"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_17f4085273d14d4d6145cf6585"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_1084d1ddbabbcfcab7cd9d547a"`, + ); + await queryRunner.query(`DROP TABLE "IncidentLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_f89b23e3cafd1c6a0bfd42c297"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_2d127b6da0e4fab9f905b4d332"`, + ); + await queryRunner.query(`DROP TABLE "IncidentOnCallDutyPolicy"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_9e2000a938f2e12c6653e68780"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_55e3162c1259b1b092f0ac63ee"`, + ); + await queryRunner.query(`DROP TABLE "IncidentMonitor"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_1c0248fe6856bbe029fc492ec7"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d4a3f7dc4e33b896b0984e7316"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicyLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_1866fb21ea1acd3d5c37e28ca1"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_3af64cebb5a7cc7f1ad0aa70c1"`, + ); + await queryRunner.query(`DROP TABLE "MonitorLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_4db7f66405df73287c639ece90"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_0946f6b41113cd842ee69f69fb"`, + ); + await queryRunner.query(`DROP TABLE "ApiKeyPermissionLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_c093a47a2ecfa1d5f2d4aeb04a"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_bb47f6d0cabd55fde5b199ae20"`, + ); + await queryRunner.query(`DROP TABLE "WorkflowVariable"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_55293b16d84f048f44c771595b"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6c3dc31b09a96d81982a472e22"`, + ); + await queryRunner.query(`DROP TABLE "WorkflowLog"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_a87518833c744b2df4324b61a6"`, + ); + await queryRunner.query(`DROP TABLE "Workflow"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_815c728d905c44bc440ec91308"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_12ef8407b6359205df8339f849"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d5c3df01bbb2a9ce168b36b523"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_222212e7157f8816357a4f0253"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_26e16ed3e46c6a6589a88d3abb"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6491952f9d8066aa5cfff92cd8"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_58a44736718a5ec4fe41526289"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ef993920a9967b61fb3fb9bf16"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_06a427cdcbae1ddcb1301b860f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_dc79ea74fba7b99835bd475081"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_79580470f34858375cae5d353a"`, + ); + await queryRunner.query(`DROP TABLE "UserOnCallLogTimeline"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_7498ad42c4f77f7fab2a6bc2e3"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_550f9f6177451d4902467991a1"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_f047dd6a708175bae0ee6f8c4b"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_4cd37d481ef366d975c6a7cd9b"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ea993eab5c402623b61203e325"`, + ); + await queryRunner.query(`DROP TABLE "UserOnCallLog"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_e2565b0aa7d7e015fb6685afed"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6f110fc752889f922d6a3c57a5"`, + ); + await queryRunner.query(`DROP TABLE "UserNotificationSetting"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_bf703b216d7f59424302dc5d70"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_f1a5912cdf877c89121a3090cd"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b407063ee43233b0cc8e9106cb"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_32ca4397660dafe0cab7d03e5b"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ec8e7a39fcd38d0ea2d40b8afa"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_32efb91af1b432a25ceb55bc0d"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_69d439e9f60f05ae16732c4999"`, + ); + await queryRunner.query(`DROP TABLE "UserNotificationRule"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_3cb16b5c2d69dbdc812247788f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6439689a29a2192708e3f3603d"`, + ); + await queryRunner.query(`DROP TABLE "UserSMS"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_1f713c701d85c69f706e4e82b8"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_e6beed22b36201aea1d70ba0d7"`, + ); + await queryRunner.query(`DROP TABLE "UserEmail"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_4d90bd0e309ad43c4541bb428e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_db4e522c086be556e5101c4e91"`, + ); + await queryRunner.query(`DROP TABLE "UserCall"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_91333210492e5d2f334231468a"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_5670be95a9496b9380b7c0d793"`, + ); + await queryRunner.query(`DROP TABLE "TelemetryUsageBilling"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_6c89ae3af06376fe3411cf8295"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_3a3321fd538aa014aa5e4f3522"`, + ); + await queryRunner.query(`DROP TABLE "TelemetryService"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_5064c0bdc8ff238952f9a2acf4"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_78293e9cc1746e5f29ccccfdfc"`, + ); + await queryRunner.query(`DROP TABLE "TeamPermission"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_fd952f76f5a5297ce69a9a588e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_3cc297d538f01065f9925cfb11"`, + ); + await queryRunner.query(`DROP TABLE "TeamMember"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_217b295d5882faa6cf3418ed81"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6adf943966e01699e86117d2e3"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageSubscriber"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_dc05de7939af3ada1567fc7106"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_1d63fa142dd4175ef256f21d2a"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageSSO"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_9b61276c47d6091295dec9e535"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a55bb812676bff276cef1f14c8"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b1c4fe08e1d90ae4d26d934653"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ade3f7acf902dcb313d230ca1f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_8fba35fb87a0ad6037eb8fc804"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageResource"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_0589c51161d13b752fed41a319"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_1ce45fe77324ede75166d0f57d"`, + ); + await queryRunner.query(`DROP TABLE "StatusPagePrivateUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_feea72d826c4bf508a95c7c757"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_69b1659abafe656563259784d0"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_3c59f811e0660c5522e45e85b6"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_c28e05c08656f8aa756734c37c"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageOwnerUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_14768105dd06f0e3e75ec5b051"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6a98e42d8df3ba84bd0f79da55"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_f60296efefe379693bfd55a776"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_9fac66064d88c514d2e5503237"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageOwnerTeam"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_54edc7ff7a74a0310a512c5389"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a65f8fabf888d227d64570f52b"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageHistoryChartBarColorRule"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_99c017b6ced8da63abdfbb506e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_70d70692cbe9d9be188723df4f"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageHeaderLink"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_5dbcfd7d38e5ea7a78a6a78a33"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_4a96c34f030a6e39218352a947"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageGroup"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_1a80c698b2205074f53376d631"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_5f8f65447c9b881860bf742dc9"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageFooterLink"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_842a66fcb103388fcedffef75f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7fab5bc54a8f36eac8f31c8256"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_40cca185c8cf933c04a0534676"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageDomain"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_4f5ae90bc48a0ddeb50cd009aa"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageCustomField"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_3c5cbb3fcaf6be56918520501c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_5e9c5a7393ac9aa477d625de67"`, + ); + await queryRunner.query(`DROP TABLE "StatusPageAnnouncement"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_68d0fe13df157c0a93d1ff6fa1"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_12854d2b71004825924a476dfc"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a30bbda7f5480e498ebc609663"`, + ); + await queryRunner.query(`DROP TABLE "SmsLog"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_339f8fe3bc6fb4440541cc61a4"`, + ); + await queryRunner.query(`DROP TABLE "ShortLink"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_590161c4f8e0b63e7ed3fd2163"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_978cd5638c6e44186cbd1099d9"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_58aa5722dde40c062793ede637"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7c0f750d3a964180d1e1efa16e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_411146017bcfe98bbe028b8d15"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_4faf556988f5b6a755ef2e85ae"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceStateTimeline"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_0b7ec5df94e08afda7569ea1ff"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_937aabd4adbfce78663406a248"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_73c6737ab4a7718c45932bffad"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenancePublicNote"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_5d71879db35e7c4104b56bef09"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_967c33f7bce5de522c1d1a80e7"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_be6a25806925f93b8949e61929"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_518742c7037d9a38a6594dc18a"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceOwnerUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_cbe4eac5f23c115ddd4a695747"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_1251fb7d4a4bf8586f2bd1528e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_1206beb611e0779ce2248ecbae"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_e3053b1725658b4a120b4e3185"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceOwnerTeam"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_49d4245d0066dc1e14a63a4234"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b4cb4c1312eb72459907e1bbe9"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceNoteTemplate"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_39a27db645744ef9177d4ab7c9"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a53ef45aebd4a6a6e7dde7f896"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d79c49a0a613a6b432fd400e69"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceInternalNote"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_3621c488327e1c00518aa4e881"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceCustomField"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_c8306d145e77473ee7ac859a0d"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_fab9cc7e6ffcf02872fccfab97"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_883038abda021ce79fa838d027"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_e0d4bcb3e28628a47b8b55ead8"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_20f04dff3b9d1f3d62985dd9de"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_4059dd569d6a482062352bf266"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenance"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_b5138c4c7dc1c36ef592af784f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_cfcd002648dcf692a2e126ab05"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_97509c6e7f41b59c5447cec669"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7213cf79fce2db23927de0aac0"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b8a8b32e0d7e06ed8a9a3171ab"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_4e347d3f99b67dacd149beaf61"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_61944d851b4a7213d79ef28174"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_4cf5977515ca677e569942fb09"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_5f7df6dd7b1a85b933bd953b47"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_34a4c35e0d7afe6f023825a68c"`, + ); + await queryRunner.query(`DROP TABLE "StatusPage"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_57a31fb2a5e4caa223d2506a4e"`, + ); + await queryRunner.query(`DROP TABLE "ScheduledMaintenanceState"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_a7b405e2a9ae144be016bcf973"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7aecf4b1ae3e45647cb911f4c1"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6dcdf97c0834dd44b4f2c93e66"`, + ); + await queryRunner.query(`DROP TABLE "PromoCode"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_be9e6751765501ea1db126fcb2"`, + ); + await queryRunner.query(`DROP TABLE "ProjectSSO"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_20334b9571a6cd1a871e70d8e7"`, + ); + await queryRunner.query(`DROP TABLE "ProjectCallSMSConfig"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_41f4ecc29351e1a406e83b30a9"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_9b36bba6d9898331920805a29c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_08afccd6cbbd1a7015d4fe25e3"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicyScheduleLayerUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_6fa6574a45cf1352c5a3b96251"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_3b892ef36671f1ea1c8457a96d"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicyScheduleLayer"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_0ad4222a4c48b8a64e3a58b351"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_872c2f6a9739bab1b57b6d51ea"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_90119ec7f77fa2efd82261e044"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_c5a798ca667fedda71d4ed5465"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_591b7ed73c964e105bfecc6fd6"`, + ); + await queryRunner.query( + `DROP TABLE "OnCallDutyPolicyExecutionLogTimeline"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a217798f1dbf12d31b498a1020"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6ad07641fe00f29edc65716aca"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d4a0ffc5e9e698bb2612ba0e55"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_296e2be818e3fba28e43b457fb"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_fbb50489ef5eb354f46d479e2a"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_e4bb332263960531a4c9e2d425"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_00e9676d39eeb807de70443051"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicyExecutionLog"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_bff6f4ae726b5c5cbae10e7d74"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_9ca3fbb66842324aa987d4c972"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b15552e664640f67346193598a"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicyEscalationRuleUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_0e7d4060e2fabe0957b9fedb42"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_f5121f361345d858ce740a55a2"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_c8aff8439fbfb07e7388aa9011"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicyEscalationRuleTeam"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_089081f83ef22cdba5a0903ce5"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_651a4ab9e3cbb20f6b62a87a6b"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_efa24aa8feb92d4e15a707c20e"`, + ); + await queryRunner.query( + `DROP TABLE "OnCallDutyPolicyEscalationRuleSchedule"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_f2f09fcba2e6eabe61d16aa242"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_15bea18a6b3f9730ce6fad2804"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_0e1b7c3c3e8305a10716cdb8d6"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicySchedule"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_4020168f8d1ee248b8d4bd6293"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_e3e66222024c1119865f3eae0f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b90c1cda36981c41e3965a9380"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d45a545669dc46da25cc60d1df"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicyEscalationRule"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_e791474e098f276eda27704b47"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicyCustomField"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_2606f4914507b3471f40864348"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_26f6632b71574ff4dbe87c352d"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_01ac1d1ef9e72aeb6dac6575dd"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_0ea6641468483b2ace63144031"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_574feb4161c5216c2c7ee0faaf"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_c08a7c1ef8d511b335a991aac4"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_80213eb3f228f1e3d423f5127e"`, + ); + await queryRunner.query(`DROP TABLE "MonitorStatusTimeline"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_7c3629c5ae14e97bede3bc548e"`, + ); + await queryRunner.query(`DROP TABLE "MonitorSecret"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_a540272f483ef1de68f7e64748"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_2d50a0e0e624369e7f90a62e8d"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d887935f224b896ce64872c37c"`, + ); + await queryRunner.query(`DROP TABLE "MonitorProbe"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_ab449be4e08009bfc2e68f5c78"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a65ce9b11b2d7bde123aa7633f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_324f1d50d0427bbd0e3308c459"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_12a3497e1404fcdbc4e8963a58"`, + ); + await queryRunner.query(`DROP TABLE "MonitorOwnerUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_acb20c616e3781a3a5506f89ef"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_1d5265d1f3ca2f8b8e461e4998"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_63e5bbac01d1f68c7b08f126cd"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6a6213072d8637e6e625bc7892"`, + ); + await queryRunner.query(`DROP TABLE "MonitorOwnerTeam"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_50b373c428cfd4566cc5caf98e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_cf3dcaa746835ae36615a39d86"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_825791d5edb2403d7937f16ed9"`, + ); + await queryRunner.query(`DROP TABLE "MonitorGroupResource"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_7c88d50a62d9ba48e9578c9128"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6a4095ee3d04454071816a5bad"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_4b25f4a18bec8cb177e8d65c5f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d5cfbebf8b07405652f5382e15"`, + ); + await queryRunner.query(`DROP TABLE "MonitorGroupOwnerUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_0736d9a7117c25ee216507fa47"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_618bcd9015c257b0727df36fd1"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_8f19947114087883cea771e1cb"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_0d7052620f268d0fa17f948a85"`, + ); + await queryRunner.query(`DROP TABLE "MonitorGroupOwnerTeam"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_04092730e7f93ee58b544f484c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_136524b17f6dbf70f1e720e8f6"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_13905ff40843b11145f21e33ff"`, + ); + await queryRunner.query(`DROP TABLE "MonitorGroup"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_1c6b61e904d8e3fec1ae719b9e"`, + ); + await queryRunner.query(`DROP TABLE "MonitorCustomField"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_38583ea60d2df4a525098065f3"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_876dd05e0dfc64219ef5df241c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_36b9b0204f4b17063483cb7308"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_da9dd65b4401b954a0ea2b5c8d"`, + ); + await queryRunner.query(`DROP TABLE "IncidentTemplateOwnerUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_2cb2fc022da183b99cf06f0043"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a895b946fccb109dedd55b85f6"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_e8090c6569c3a5dbd7ef7485c9"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_cf172eb6797a64ee3750e3f3e2"`, + ); + await queryRunner.query(`DROP TABLE "IncidentTemplateOwnerTeam"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_6d7627ab9d5172c66fc5019216"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ceff6a4dfdccecc4aa40dbfe91"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_9fe9e55006c2a1f26727e479ab"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_0683d65aa3e3483127671d120f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b82dafef226b0fae1ad6cb1857"`, + ); + await queryRunner.query(`DROP TABLE "IncidentTemplate"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_dc5784aa146b249a22afe48b7e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_71c94e9f34772d46fd50e18b64"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7db6b1a8fbbc9eb44c2e7f5047"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_5a1a64bc4c38107b25a4bdcd17"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ff0fca6570d47798771763533a"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_fe2dff414a1f67260e3c518981"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_764daa366a4e195768a49e0ee3"`, + ); + await queryRunner.query(`DROP TABLE "IncidentStateTimeline"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_8bbc15605fce799ab2abf6532b"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a6964d3aab71608daab9f20e30"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a9a77e5f286b5724f4e2280d0a"`, + ); + await queryRunner.query(`DROP TABLE "IncidentPublicNote"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_8347c45d2b96d4f809bbefeb80"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6aa9a6b46f8e044d722da8f5a7"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6311087eeb14ab51e6a1e6133f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_0ee7ae6757442fba470b213015"`, + ); + await queryRunner.query(`DROP TABLE "IncidentOwnerUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_a913a00c56edac2d2e3364fb8b"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_408324d3635a826538a792422f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_389aadeb39a0806e80d4001016"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_95f76375ccac835f815d7e926a"`, + ); + await queryRunner.query(`DROP TABLE "IncidentOwnerTeam"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_fcdfc85e8e6b0a1f8128ce572a"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_319e120005dff229ac97e9e21d"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_baac847c494f692f03fd686e9c"`, + ); + await queryRunner.query(`DROP TABLE "Team"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_7904e01f4867510e9cb3ba09cd"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_21d5bc0d24b3e5032dd391ec8d"`, + ); + await queryRunner.query(`DROP TABLE "IncidentNoteTemplate"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_582d48a4d9cce7dd74ea1dd282"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b92e75645fd252e4c2f866047d"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ac48058b3e5f9e8361d2b8328c"`, + ); + await queryRunner.query(`DROP TABLE "IncidentInternalNote"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_59e7f6a43dbc5ee54e1a1aaaaf"`, + ); + await queryRunner.query(`DROP TABLE "IncidentCustomField"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_7e537806a80e869917ca1d7e2e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6ddd5e3433dcdc6832b2a93845"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ee2acd83fe08dfe3b46a533b7f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_5218e92f700d91afe6a8db79cb"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_c3ad64a7aaf39c1f7885527e24"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ed0ec4960a85240f51e6779a00"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_3f28fe3b32abed354a49b26c9c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_6592d4f7f3b260efc23fc9b4bc"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_eb410c8eb2e2eadfa5880936fe"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_5f53415da5b48954185a6c32b7"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_eccbc31fa1f58bd051b6f7e108"`, + ); + await queryRunner.query(`DROP TABLE "Incident"`); + await queryRunner.query(`DROP TABLE "Probe"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_68884a658e2c47f67c9b2dd9af"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_570a79facd5fe1c01ccdca55ae"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_31508550a088ba2cc843a6c90c"`, + ); + await queryRunner.query(`DROP TABLE "OnCallDutyPolicy"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_2fb8ee83943588b9c5f3358570"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_899a35fe28dd2661f9c999c130"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_28d1e0cdf3fcf1ac30249f5d3a"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_9c8f4b9103fa6b62ff5a121f36"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_ea5170227069c85269b3a6db93"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_9bfee6c29045d1c236d9395f65"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_1e0f03c5e067eeb505b2b87aa8"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b4c392c3163a2a32da5b401c91"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_db5cc02633b36957c9be4d70c6"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d3461ab640467c8c2100ea55c7"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_42ecc6e0ac9984cd24d5f9ddd8"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_36d301a9e41af9b6e62b0c0a02"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_996acfb590bda327843f78b7ad"`, + ); + await queryRunner.query(`DROP TABLE "Monitor"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_db1783158a23bd20dbebaae56e"`, + ); + await queryRunner.query(`DROP TABLE "MonitorStatus"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_3d279e530067f599f3186e3821"`, + ); + await queryRunner.query(`DROP TABLE "IncidentState"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_00d2f503174bf201abc6e77afd"`, + ); + await queryRunner.query(`DROP TABLE "IncidentSeverity"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_8358adfc13448a58e30a799f6e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_36dde33ab3f3fefb63cb164c7d"`, + ); + await queryRunner.query(`DROP TABLE "GreenlockChallenge"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_d47fcf292f2ceae490da8404d8"`, + ); + await queryRunner.query(`DROP TABLE "GreenlockCertificate"`); + await queryRunner.query(`DROP TABLE "GlobalConfig"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_0817e87f2e8de1019a09b1eb60"`, + ); + await queryRunner.query(`DROP TABLE "EmailVerificationToken"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_046364c162885b6ac65d5dd367"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_c4d606cbaafbdbbf5130c97058"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_2c92ae54071acce65fa134d855"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7b72c5131b3dd1f3edf201a561"`, + ); + await queryRunner.query(`DROP TABLE "EmailLog"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_a540dab929fa6582b93f258ffe"`, + ); + await queryRunner.query(`DROP TABLE "ProjectSMTPConfig"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_f1ce90d3f9693be29b72fabe93"`, + ); + await queryRunner.query(`DROP TABLE "Domain"`); + await queryRunner.query(`DROP TABLE "DataMigrations"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_af110ffcec14a2770dada25c92"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_14a8701025e30ab54de66990dc"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_5648767682195afaeb09098a21"`, + ); + await queryRunner.query(`DROP TABLE "CallLog"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_db4bb9add01b7d8286869fd9a0"`, + ); + await queryRunner.query(`DROP TABLE "BillingPaymentMethod"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_0ab13e9a92ce4801c37c2a0a77"`, + ); + await queryRunner.query(`DROP TABLE "BillingInvoice"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_fb09dd7fefa9d5d44b1907be5f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_0cf347c575f15d3836615f5325"`, + ); + await queryRunner.query(`DROP TABLE "ApiKeyPermission"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_f10d59c2ba66e085722e0053cb"`, + ); + await queryRunner.query(`DROP TABLE "Label"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_17bec9c02846cdf64b5d6bb71f"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_0e26b8c243b0ed1395bd52aaaf"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_bb1019f0078a21b4854f5cb3ed"`, + ); + await queryRunner.query(`DROP TABLE "ApiKey"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_b5ee87614c184778810283c299"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_4ee6a519d48b26fe2a78fdc1c9"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_38f5c1d2bf0743a868288fc8e6"`, + ); + await queryRunner.query(`DROP TABLE "Project"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_fc269bd109ac405a458b2acc67"`, + ); + await queryRunner.query(`DROP TABLE "ResellerPlan"`); + await queryRunner.query(`DROP TABLE "Reseller"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_936afe487b8f9da2f6aae1d11d"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_fe7dd70f059b5b9bd0452d3ebf"`, + ); + await queryRunner.query(`DROP TABLE "AcmeChallenge"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_8545260bc2f2b2cdb2e7184362"`, + ); + await queryRunner.query(`DROP TABLE "AcmeCertificate"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_70f42c60b74c0e931f0d599f03"`, + ); + await queryRunner.query(`DROP TABLE "User"`); + await queryRunner.query(`DROP TABLE "File"`); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717678334852-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717678334852-MigrationName.ts index 7b8d0136d5..8b72e468e7 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717678334852-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717678334852-MigrationName.ts @@ -1,52 +1,52 @@ -import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'; +import { MigrationInterface, QueryRunner, Table, TableColumn } from "typeorm"; export class MigrationName1717678334852 implements MigrationInterface { - public name: string = 'MigrationName1717678334852'; + public name: string = "MigrationName1717678334852"; - public async up(queryRunner: QueryRunner): Promise<void> { - // check if the column exists + public async up(queryRunner: QueryRunner): Promise<void> { + // check if the column exists - const apiKeyPermissionTable: Table | undefined = - await queryRunner.getTable('ApiKeyPermission'); + const apiKeyPermissionTable: Table | undefined = + await queryRunner.getTable("ApiKeyPermission"); - if (apiKeyPermissionTable) { - const isBlockPermissionColumn: TableColumn | undefined = - apiKeyPermissionTable.columns.find((column: TableColumn) => { - return column.name === 'isBlockPermission'; - }); + if (apiKeyPermissionTable) { + const isBlockPermissionColumn: TableColumn | undefined = + apiKeyPermissionTable.columns.find((column: TableColumn) => { + return column.name === "isBlockPermission"; + }); - if (!isBlockPermissionColumn) { - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" ADD "isBlockPermission" boolean NOT NULL DEFAULT false` - ); - } - } - - // check if the column exists - - const teamPermissionTable: Table | undefined = - await queryRunner.getTable('TeamPermission'); - - if (teamPermissionTable) { - const isBlockPermissionColumn: TableColumn | undefined = - teamPermissionTable.columns.find((column: TableColumn) => { - return column.name === 'isBlockPermission'; - }); - - if (!isBlockPermissionColumn) { - await queryRunner.query( - `ALTER TABLE "TeamPermission" ADD "isBlockPermission" boolean NOT NULL DEFAULT false` - ); - } - } + if (!isBlockPermissionColumn) { + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" ADD "isBlockPermission" boolean NOT NULL DEFAULT false`, + ); + } } - public async down(queryRunner: QueryRunner): Promise<void> { + // check if the column exists + + const teamPermissionTable: Table | undefined = + await queryRunner.getTable("TeamPermission"); + + if (teamPermissionTable) { + const isBlockPermissionColumn: TableColumn | undefined = + teamPermissionTable.columns.find((column: TableColumn) => { + return column.name === "isBlockPermission"; + }); + + if (!isBlockPermissionColumn) { await queryRunner.query( - `ALTER TABLE "TeamPermission" DROP COLUMN "isBlockPermission"` - ); - await queryRunner.query( - `ALTER TABLE "ApiKeyPermission" DROP COLUMN "isBlockPermission"` + `ALTER TABLE "TeamPermission" ADD "isBlockPermission" boolean NOT NULL DEFAULT false`, ); + } } + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "TeamPermission" DROP COLUMN "isBlockPermission"`, + ); + await queryRunner.query( + `ALTER TABLE "ApiKeyPermission" DROP COLUMN "isBlockPermission"`, + ); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717839110671-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717839110671-MigrationName.ts index 289e072fcb..2f091c48cb 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717839110671-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717839110671-MigrationName.ts @@ -1,67 +1,67 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1717839110671 implements MigrationInterface { - public name = 'MigrationName1717839110671'; + public name = "MigrationName1717839110671"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `CREATE TABLE "ServiceCatalog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "serviceColor" character varying, CONSTRAINT "PK_5186d54b1b97610ea80b5c55aad" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e712ff4cf5c1a865a5baa242e2" ON "ServiceCatalog" ("projectId") ` - ); - await queryRunner.query( - `CREATE TABLE "ServiceCatalogLabel" ("serviceCatalogId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_a2c59f364d3bdb0d28307ad1d46" PRIMARY KEY ("serviceCatalogId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_98e9d83b6ff61003a29590f398" ON "ServiceCatalogLabel" ("serviceCatalogId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_701f84e45404bdddcffdcaaba2" ON "ServiceCatalogLabel" ("labelId") ` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalog" ADD CONSTRAINT "FK_e712ff4cf5c1a865a5baa242e2e" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalog" ADD CONSTRAINT "FK_b8d64daaf462acd6f694ca47dad" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalog" ADD CONSTRAINT "FK_42f81942e36f5f42a5dce8e606d" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogLabel" ADD CONSTRAINT "FK_98e9d83b6ff61003a29590f3987" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogLabel" ADD CONSTRAINT "FK_701f84e45404bdddcffdcaaba20" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE TABLE "ServiceCatalog" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "serviceColor" character varying, CONSTRAINT "PK_5186d54b1b97610ea80b5c55aad" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e712ff4cf5c1a865a5baa242e2" ON "ServiceCatalog" ("projectId") `, + ); + await queryRunner.query( + `CREATE TABLE "ServiceCatalogLabel" ("serviceCatalogId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_a2c59f364d3bdb0d28307ad1d46" PRIMARY KEY ("serviceCatalogId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_98e9d83b6ff61003a29590f398" ON "ServiceCatalogLabel" ("serviceCatalogId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_701f84e45404bdddcffdcaaba2" ON "ServiceCatalogLabel" ("labelId") `, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalog" ADD CONSTRAINT "FK_e712ff4cf5c1a865a5baa242e2e" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalog" ADD CONSTRAINT "FK_b8d64daaf462acd6f694ca47dad" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalog" ADD CONSTRAINT "FK_42f81942e36f5f42a5dce8e606d" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogLabel" ADD CONSTRAINT "FK_98e9d83b6ff61003a29590f3987" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogLabel" ADD CONSTRAINT "FK_701f84e45404bdddcffdcaaba20" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "ServiceCatalogLabel" DROP CONSTRAINT "FK_701f84e45404bdddcffdcaaba20"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogLabel" DROP CONSTRAINT "FK_98e9d83b6ff61003a29590f3987"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalog" DROP CONSTRAINT "FK_42f81942e36f5f42a5dce8e606d"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalog" DROP CONSTRAINT "FK_b8d64daaf462acd6f694ca47dad"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalog" DROP CONSTRAINT "FK_e712ff4cf5c1a865a5baa242e2e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_701f84e45404bdddcffdcaaba2"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_98e9d83b6ff61003a29590f398"` - ); - await queryRunner.query(`DROP TABLE "ServiceCatalogLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_e712ff4cf5c1a865a5baa242e2"` - ); - await queryRunner.query(`DROP TABLE "ServiceCatalog"`); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "ServiceCatalogLabel" DROP CONSTRAINT "FK_701f84e45404bdddcffdcaaba20"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogLabel" DROP CONSTRAINT "FK_98e9d83b6ff61003a29590f3987"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalog" DROP CONSTRAINT "FK_42f81942e36f5f42a5dce8e606d"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalog" DROP CONSTRAINT "FK_b8d64daaf462acd6f694ca47dad"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalog" DROP CONSTRAINT "FK_e712ff4cf5c1a865a5baa242e2e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_701f84e45404bdddcffdcaaba2"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_98e9d83b6ff61003a29590f398"`, + ); + await queryRunner.query(`DROP TABLE "ServiceCatalogLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_e712ff4cf5c1a865a5baa242e2"`, + ); + await queryRunner.query(`DROP TABLE "ServiceCatalog"`); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717849921874-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717849921874-MigrationName.ts index 76f57ec25c..74de35a271 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717849921874-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717849921874-MigrationName.ts @@ -1,115 +1,115 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1717849921874 implements MigrationInterface { - public name = 'MigrationName1717849921874'; + public name = "MigrationName1717849921874"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `CREATE TABLE "ServiceCatalogOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "serviceCatalogId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_da84693caf7072d56bedfc2dc1b" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_726241162b0a853b29d85e28c4" ON "ServiceCatalogOwnerTeam" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e3090773a4106e0c4375897993" ON "ServiceCatalogOwnerTeam" ("teamId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_c015513688ebd42e5569b4d6ac" ON "ServiceCatalogOwnerTeam" ("serviceCatalogId") ` - ); - await queryRunner.query( - `CREATE TABLE "ServiceCatalogOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "serviceCatalogId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_c0fbf81bd041371f8beb69b440d" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_51c375fe9f6ffb0372d3425d99" ON "ServiceCatalogOwnerUser" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_f6abd337058906d7912164ae12" ON "ServiceCatalogOwnerUser" ("userId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_27a396dd77fb8c0d5d6cb89216" ON "ServiceCatalogOwnerUser" ("serviceCatalogId") ` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_726241162b0a853b29d85e28c4c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_e3090773a4106e0c4375897993f" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_c015513688ebd42e5569b4d6ac6" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_9afb156569266f66a2301eb09ff" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_0e93a638ddc94aaad4ad33789d7" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_51c375fe9f6ffb0372d3425d999" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_f6abd337058906d7912164ae12e" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_27a396dd77fb8c0d5d6cb892165" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_2d2c21db8da169b5b2d2bee3111" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_d61607e823057b6516f05e8f1cd" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE TABLE "ServiceCatalogOwnerTeam" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "teamId" uuid NOT NULL, "serviceCatalogId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_da84693caf7072d56bedfc2dc1b" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_726241162b0a853b29d85e28c4" ON "ServiceCatalogOwnerTeam" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e3090773a4106e0c4375897993" ON "ServiceCatalogOwnerTeam" ("teamId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_c015513688ebd42e5569b4d6ac" ON "ServiceCatalogOwnerTeam" ("serviceCatalogId") `, + ); + await queryRunner.query( + `CREATE TABLE "ServiceCatalogOwnerUser" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "userId" uuid NOT NULL, "serviceCatalogId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_c0fbf81bd041371f8beb69b440d" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_51c375fe9f6ffb0372d3425d99" ON "ServiceCatalogOwnerUser" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_f6abd337058906d7912164ae12" ON "ServiceCatalogOwnerUser" ("userId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_27a396dd77fb8c0d5d6cb89216" ON "ServiceCatalogOwnerUser" ("serviceCatalogId") `, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_726241162b0a853b29d85e28c4c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_e3090773a4106e0c4375897993f" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_c015513688ebd42e5569b4d6ac6" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_9afb156569266f66a2301eb09ff" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" ADD CONSTRAINT "FK_0e93a638ddc94aaad4ad33789d7" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_51c375fe9f6ffb0372d3425d999" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_f6abd337058906d7912164ae12e" FOREIGN KEY ("userId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_27a396dd77fb8c0d5d6cb892165" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_2d2c21db8da169b5b2d2bee3111" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" ADD CONSTRAINT "FK_d61607e823057b6516f05e8f1cd" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_d61607e823057b6516f05e8f1cd"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_2d2c21db8da169b5b2d2bee3111"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_27a396dd77fb8c0d5d6cb892165"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_f6abd337058906d7912164ae12e"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_51c375fe9f6ffb0372d3425d999"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_0e93a638ddc94aaad4ad33789d7"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_9afb156569266f66a2301eb09ff"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_c015513688ebd42e5569b4d6ac6"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_e3090773a4106e0c4375897993f"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_726241162b0a853b29d85e28c4c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_27a396dd77fb8c0d5d6cb89216"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_f6abd337058906d7912164ae12"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_51c375fe9f6ffb0372d3425d99"` - ); - await queryRunner.query(`DROP TABLE "ServiceCatalogOwnerUser"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_c015513688ebd42e5569b4d6ac"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_e3090773a4106e0c4375897993"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_726241162b0a853b29d85e28c4"` - ); - await queryRunner.query(`DROP TABLE "ServiceCatalogOwnerTeam"`); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_d61607e823057b6516f05e8f1cd"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_2d2c21db8da169b5b2d2bee3111"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_27a396dd77fb8c0d5d6cb892165"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_f6abd337058906d7912164ae12e"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerUser" DROP CONSTRAINT "FK_51c375fe9f6ffb0372d3425d999"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_0e93a638ddc94aaad4ad33789d7"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_9afb156569266f66a2301eb09ff"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_c015513688ebd42e5569b4d6ac6"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_e3090773a4106e0c4375897993f"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceCatalogOwnerTeam" DROP CONSTRAINT "FK_726241162b0a853b29d85e28c4c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_27a396dd77fb8c0d5d6cb89216"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_f6abd337058906d7912164ae12"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_51c375fe9f6ffb0372d3425d99"`, + ); + await queryRunner.query(`DROP TABLE "ServiceCatalogOwnerUser"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_c015513688ebd42e5569b4d6ac"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_e3090773a4106e0c4375897993"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_726241162b0a853b29d85e28c4"`, + ); + await queryRunner.query(`DROP TABLE "ServiceCatalogOwnerTeam"`); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717955235341-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717955235341-MigrationName.ts index de0883e49c..da83bea2b8 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717955235341-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1717955235341-MigrationName.ts @@ -1,73 +1,73 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1717955235341 implements MigrationInterface { - public name = 'MigrationName1717955235341'; + public name = "MigrationName1717955235341"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `CREATE TABLE "CodeRepository" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "secretToken" uuid NOT NULL, CONSTRAINT "PK_7b5219d06a82fbc0bc4540b74f0" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_a653bdc2fac520c9c8b9a7c7a6" ON "CodeRepository" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_789f71951901d03fe3da24dca5" ON "CodeRepository" ("secretToken") ` - ); - await queryRunner.query( - `CREATE TABLE "CodeRepositoryLabel" ("codeRepositoryId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_5adb09e0b5957488be8931f46bc" PRIMARY KEY ("codeRepositoryId", "labelId"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_7710ab8ee47601f78f3a4b76b6" ON "CodeRepositoryLabel" ("codeRepositoryId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_8f7d12100e441fc72e02151fc5" ON "CodeRepositoryLabel" ("labelId") ` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" ADD CONSTRAINT "FK_a653bdc2fac520c9c8b9a7c7a6a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" ADD CONSTRAINT "FK_a870b71b99c87ea658c11421490" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" ADD CONSTRAINT "FK_79d9249eb5f8174a6f6228311f4" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepositoryLabel" ADD CONSTRAINT "FK_7710ab8ee47601f78f3a4b76b64" FOREIGN KEY ("codeRepositoryId") REFERENCES "CodeRepository"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepositoryLabel" ADD CONSTRAINT "FK_8f7d12100e441fc72e02151fc56" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE TABLE "CodeRepository" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "slug" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "secretToken" uuid NOT NULL, CONSTRAINT "PK_7b5219d06a82fbc0bc4540b74f0" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a653bdc2fac520c9c8b9a7c7a6" ON "CodeRepository" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_789f71951901d03fe3da24dca5" ON "CodeRepository" ("secretToken") `, + ); + await queryRunner.query( + `CREATE TABLE "CodeRepositoryLabel" ("codeRepositoryId" uuid NOT NULL, "labelId" uuid NOT NULL, CONSTRAINT "PK_5adb09e0b5957488be8931f46bc" PRIMARY KEY ("codeRepositoryId", "labelId"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7710ab8ee47601f78f3a4b76b6" ON "CodeRepositoryLabel" ("codeRepositoryId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_8f7d12100e441fc72e02151fc5" ON "CodeRepositoryLabel" ("labelId") `, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" ADD CONSTRAINT "FK_a653bdc2fac520c9c8b9a7c7a6a" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" ADD CONSTRAINT "FK_a870b71b99c87ea658c11421490" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" ADD CONSTRAINT "FK_79d9249eb5f8174a6f6228311f4" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepositoryLabel" ADD CONSTRAINT "FK_7710ab8ee47601f78f3a4b76b64" FOREIGN KEY ("codeRepositoryId") REFERENCES "CodeRepository"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepositoryLabel" ADD CONSTRAINT "FK_8f7d12100e441fc72e02151fc56" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "CodeRepositoryLabel" DROP CONSTRAINT "FK_8f7d12100e441fc72e02151fc56"` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepositoryLabel" DROP CONSTRAINT "FK_7710ab8ee47601f78f3a4b76b64"` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" DROP CONSTRAINT "FK_79d9249eb5f8174a6f6228311f4"` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" DROP CONSTRAINT "FK_a870b71b99c87ea658c11421490"` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" DROP CONSTRAINT "FK_a653bdc2fac520c9c8b9a7c7a6a"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_8f7d12100e441fc72e02151fc5"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_7710ab8ee47601f78f3a4b76b6"` - ); - await queryRunner.query(`DROP TABLE "CodeRepositoryLabel"`); - await queryRunner.query( - `DROP INDEX "public"."IDX_789f71951901d03fe3da24dca5"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_a653bdc2fac520c9c8b9a7c7a6"` - ); - await queryRunner.query(`DROP TABLE "CodeRepository"`); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "CodeRepositoryLabel" DROP CONSTRAINT "FK_8f7d12100e441fc72e02151fc56"`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepositoryLabel" DROP CONSTRAINT "FK_7710ab8ee47601f78f3a4b76b64"`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" DROP CONSTRAINT "FK_79d9249eb5f8174a6f6228311f4"`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" DROP CONSTRAINT "FK_a870b71b99c87ea658c11421490"`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" DROP CONSTRAINT "FK_a653bdc2fac520c9c8b9a7c7a6a"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_8f7d12100e441fc72e02151fc5"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7710ab8ee47601f78f3a4b76b6"`, + ); + await queryRunner.query(`DROP TABLE "CodeRepositoryLabel"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_789f71951901d03fe3da24dca5"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a653bdc2fac520c9c8b9a7c7a6"`, + ); + await queryRunner.query(`DROP TABLE "CodeRepository"`); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718037833516-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718037833516-MigrationName.ts index 268fa18bf7..9ee8c11058 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718037833516-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718037833516-MigrationName.ts @@ -1,51 +1,51 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1718037833516 implements MigrationInterface { - public name = 'MigrationName1718037833516'; + public name = "MigrationName1718037833516"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `CREATE TABLE "CopilotEvent" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "codeRepositoryId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "filePath" character varying NOT NULL, "commitHash" character varying NOT NULL, "copilotEventType" character varying NOT NULL, CONSTRAINT "PK_df9ab694204304a1416a720bbfc" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_02c9884520b692949fea5c65f9" ON "CopilotEvent" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_e9db4a03a7d521b1d242ff3c9a" ON "CopilotEvent" ("codeRepositoryId") ` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_02c9884520b692949fea5c65f9c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_e9db4a03a7d521b1d242ff3c9a2" FOREIGN KEY ("codeRepositoryId") REFERENCES "CodeRepository"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_7ff1de5682d290b1686848fc5cf" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_81c5f57878dd2230d2eec3bcb44" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE TABLE "CopilotEvent" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "codeRepositoryId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "filePath" character varying NOT NULL, "commitHash" character varying NOT NULL, "copilotEventType" character varying NOT NULL, CONSTRAINT "PK_df9ab694204304a1416a720bbfc" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_02c9884520b692949fea5c65f9" ON "CopilotEvent" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_e9db4a03a7d521b1d242ff3c9a" ON "CopilotEvent" ("codeRepositoryId") `, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_02c9884520b692949fea5c65f9c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_e9db4a03a7d521b1d242ff3c9a2" FOREIGN KEY ("codeRepositoryId") REFERENCES "CodeRepository"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_7ff1de5682d290b1686848fc5cf" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_81c5f57878dd2230d2eec3bcb44" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_81c5f57878dd2230d2eec3bcb44"` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_7ff1de5682d290b1686848fc5cf"` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_e9db4a03a7d521b1d242ff3c9a2"` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_02c9884520b692949fea5c65f9c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_e9db4a03a7d521b1d242ff3c9a"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_02c9884520b692949fea5c65f9"` - ); - await queryRunner.query(`DROP TABLE "CopilotEvent"`); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_81c5f57878dd2230d2eec3bcb44"`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_7ff1de5682d290b1686848fc5cf"`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_e9db4a03a7d521b1d242ff3c9a2"`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_02c9884520b692949fea5c65f9c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_e9db4a03a7d521b1d242ff3c9a"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_02c9884520b692949fea5c65f9"`, + ); + await queryRunner.query(`DROP TABLE "CopilotEvent"`); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718100824584-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718100824584-MigrationName.ts index 24790a38f2..40fdcdc87b 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718100824584-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718100824584-MigrationName.ts @@ -1,17 +1,17 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1718100824584 implements MigrationInterface { - public name = 'MigrationName1718100824584'; + public name = "MigrationName1718100824584"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "StatusPageSubscriber" ADD "sendYouHaveSubscribedMessage" boolean NOT NULL DEFAULT true` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "StatusPageSubscriber" ADD "sendYouHaveSubscribedMessage" boolean NOT NULL DEFAULT true`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'` - ); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`, + ); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718101665865-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718101665865-MigrationName.ts index 4d43122f5e..8b9ff09f44 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718101665865-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718101665865-MigrationName.ts @@ -1,17 +1,17 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1718101665865 implements MigrationInterface { - public name = 'MigrationName1718101665865'; + public name = "MigrationName1718101665865"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "SmsLog" ALTER COLUMN "fromNumber" DROP NOT NULL` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "SmsLog" ALTER COLUMN "fromNumber" DROP NOT NULL`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "SmsLog" ALTER COLUMN "fromNumber" SET NOT NULL` - ); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "SmsLog" ALTER COLUMN "fromNumber" SET NOT NULL`, + ); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718119926223-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718119926223-MigrationName.ts index 8efd8d23ab..e2c796828c 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718119926223-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718119926223-MigrationName.ts @@ -1,17 +1,17 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1718119926223 implements MigrationInterface { - public name = 'MigrationName1718119926223'; + public name = "MigrationName1718119926223"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "ServiceCatalog" ADD "serviceLanguage" character varying(100)` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "ServiceCatalog" ADD "serviceLanguage" character varying(100)`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "ServiceCatalog" DROP COLUMN "serviceLanguage"` - ); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "ServiceCatalog" DROP COLUMN "serviceLanguage"`, + ); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718124277321-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718124277321-MigrationName.ts index 68f610d343..e9532e115d 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718124277321-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718124277321-MigrationName.ts @@ -1,99 +1,99 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1718124277321 implements MigrationInterface { - public name = 'MigrationName1718124277321'; + public name = "MigrationName1718124277321"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `CREATE TABLE "ServiceRepository" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "servicePathInRepository" character varying(500) NOT NULL DEFAULT '/', "limitNumberOfOpenPullRequestsCount" integer DEFAULT '3', "createdByUserId" uuid, "deletedByUserId" uuid, "codeRepositoryId" uuid NOT NULL, "serviceCatalogId" uuid NOT NULL, CONSTRAINT "PK_364ee8145cc35d43da0ef95d232" PRIMARY KEY ("_id"))` - ); - await queryRunner.query( - `CREATE INDEX "IDX_3fd668d4dc10d87963b19c5112" ON "ServiceRepository" ("projectId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_da3f196daf93e32b5ae51b865e" ON "ServiceRepository" ("codeRepositoryId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1f1b62e84894dbebb145361c27" ON "ServiceRepository" ("serviceCatalogId") ` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD "serviceCatalogId" uuid NOT NULL` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD "serviceRepositoryId" uuid NOT NULL` - ); - await queryRunner.query( - `CREATE INDEX "IDX_1ae20e42e24919a32772f11ccc" ON "CopilotEvent" ("serviceCatalogId") ` - ); - await queryRunner.query( - `CREATE INDEX "IDX_b567d8c08ac3810e87efc3222c" ON "CopilotEvent" ("serviceRepositoryId") ` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_3fd668d4dc10d87963b19c5112c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_ef96f4ebdf6327f7c06fad89127" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_04df693e197a04b0b7a81e3893e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_da3f196daf93e32b5ae51b865ef" FOREIGN KEY ("codeRepositoryId") REFERENCES "CodeRepository"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_1f1b62e84894dbebb145361c27b" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_1ae20e42e24919a32772f11ccc8" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_b567d8c08ac3810e87efc3222c5" FOREIGN KEY ("serviceRepositoryId") REFERENCES "ServiceRepository"("_id") ON DELETE CASCADE ON UPDATE NO ACTION` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `CREATE TABLE "ServiceRepository" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "servicePathInRepository" character varying(500) NOT NULL DEFAULT '/', "limitNumberOfOpenPullRequestsCount" integer DEFAULT '3', "createdByUserId" uuid, "deletedByUserId" uuid, "codeRepositoryId" uuid NOT NULL, "serviceCatalogId" uuid NOT NULL, CONSTRAINT "PK_364ee8145cc35d43da0ef95d232" PRIMARY KEY ("_id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_3fd668d4dc10d87963b19c5112" ON "ServiceRepository" ("projectId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_da3f196daf93e32b5ae51b865e" ON "ServiceRepository" ("codeRepositoryId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1f1b62e84894dbebb145361c27" ON "ServiceRepository" ("serviceCatalogId") `, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD "serviceCatalogId" uuid NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD "serviceRepositoryId" uuid NOT NULL`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_1ae20e42e24919a32772f11ccc" ON "CopilotEvent" ("serviceCatalogId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_b567d8c08ac3810e87efc3222c" ON "CopilotEvent" ("serviceRepositoryId") `, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_3fd668d4dc10d87963b19c5112c" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_ef96f4ebdf6327f7c06fad89127" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_04df693e197a04b0b7a81e3893e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_da3f196daf93e32b5ae51b865ef" FOREIGN KEY ("codeRepositoryId") REFERENCES "CodeRepository"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" ADD CONSTRAINT "FK_1f1b62e84894dbebb145361c27b" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_1ae20e42e24919a32772f11ccc8" FOREIGN KEY ("serviceCatalogId") REFERENCES "ServiceCatalog"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD CONSTRAINT "FK_b567d8c08ac3810e87efc3222c5" FOREIGN KEY ("serviceRepositoryId") REFERENCES "ServiceRepository"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_b567d8c08ac3810e87efc3222c5"` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_1ae20e42e24919a32772f11ccc8"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_1f1b62e84894dbebb145361c27b"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_da3f196daf93e32b5ae51b865ef"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_04df693e197a04b0b7a81e3893e"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_ef96f4ebdf6327f7c06fad89127"` - ); - await queryRunner.query( - `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_3fd668d4dc10d87963b19c5112c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_b567d8c08ac3810e87efc3222c"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_1ae20e42e24919a32772f11ccc"` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP COLUMN "serviceRepositoryId"` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP COLUMN "serviceCatalogId"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_1f1b62e84894dbebb145361c27"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_da3f196daf93e32b5ae51b865e"` - ); - await queryRunner.query( - `DROP INDEX "public"."IDX_3fd668d4dc10d87963b19c5112"` - ); - await queryRunner.query(`DROP TABLE "ServiceRepository"`); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_b567d8c08ac3810e87efc3222c5"`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP CONSTRAINT "FK_1ae20e42e24919a32772f11ccc8"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_1f1b62e84894dbebb145361c27b"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_da3f196daf93e32b5ae51b865ef"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_04df693e197a04b0b7a81e3893e"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_ef96f4ebdf6327f7c06fad89127"`, + ); + await queryRunner.query( + `ALTER TABLE "ServiceRepository" DROP CONSTRAINT "FK_3fd668d4dc10d87963b19c5112c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_b567d8c08ac3810e87efc3222c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_1ae20e42e24919a32772f11ccc"`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP COLUMN "serviceRepositoryId"`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP COLUMN "serviceCatalogId"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_1f1b62e84894dbebb145361c27"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_da3f196daf93e32b5ae51b865e"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_3fd668d4dc10d87963b19c5112"`, + ); + await queryRunner.query(`DROP TABLE "ServiceRepository"`); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718126316684-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718126316684-MigrationName.ts index 3ea98ef156..96e1d083d4 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718126316684-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718126316684-MigrationName.ts @@ -1,17 +1,17 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1718126316684 implements MigrationInterface { - public name = 'MigrationName1718126316684'; + public name = "MigrationName1718126316684"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "ServiceRepository" ADD "enablePullRequests" boolean NOT NULL DEFAULT true` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "ServiceRepository" ADD "enablePullRequests" boolean NOT NULL DEFAULT true`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "ServiceRepository" DROP COLUMN "enablePullRequests"` - ); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "ServiceRepository" DROP COLUMN "enablePullRequests"`, + ); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718188920011-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718188920011-MigrationName.ts index 8744297d7d..b508a9dfde 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718188920011-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718188920011-MigrationName.ts @@ -1,23 +1,23 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1718188920011 implements MigrationInterface { - public name = 'MigrationName1718188920011'; + public name = "MigrationName1718188920011"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "CodeRepository" ADD "mainBranchName" character varying(100) NOT NULL DEFAULT 'master'` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" ADD "repositoryHostedAt" character varying(100) NOT NULL DEFAULT 'GitHub'` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "CodeRepository" ADD "mainBranchName" character varying(100) NOT NULL DEFAULT 'master'`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" ADD "repositoryHostedAt" character varying(100) NOT NULL DEFAULT 'GitHub'`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "CodeRepository" DROP COLUMN "repositoryHostedAt"` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" DROP COLUMN "mainBranchName"` - ); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "CodeRepository" DROP COLUMN "repositoryHostedAt"`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" DROP COLUMN "mainBranchName"`, + ); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718203144945-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718203144945-MigrationName.ts index b35801ae85..f832358407 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718203144945-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718203144945-MigrationName.ts @@ -1,23 +1,23 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1718203144945 implements MigrationInterface { - public name = 'MigrationName1718203144945'; + public name = "MigrationName1718203144945"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "CodeRepository" ADD "organizationName" character varying(100) NOT NULL` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" ADD "repositoryName" character varying(100) NOT NULL` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "CodeRepository" ADD "organizationName" character varying(100) NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" ADD "repositoryName" character varying(100) NOT NULL`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "CodeRepository" DROP COLUMN "repositoryName"` - ); - await queryRunner.query( - `ALTER TABLE "CodeRepository" DROP COLUMN "organizationName"` - ); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "CodeRepository" DROP COLUMN "repositoryName"`, + ); + await queryRunner.query( + `ALTER TABLE "CodeRepository" DROP COLUMN "organizationName"`, + ); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718285877004-MigrationName.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718285877004-MigrationName.ts index a61903838a..8aca05030d 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718285877004-MigrationName.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/1718285877004-MigrationName.ts @@ -1,23 +1,23 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1718285877004 implements MigrationInterface { - public name = 'MigrationName1718285877004'; + public name = "MigrationName1718285877004"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD "pullRequestId" character varying` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" ADD "copilotEventStatus" character varying NOT NULL` - ); - } + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD "pullRequestId" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" ADD "copilotEventStatus" character varying NOT NULL`, + ); + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP COLUMN "copilotEventStatus"` - ); - await queryRunner.query( - `ALTER TABLE "CopilotEvent" DROP COLUMN "pullRequestId"` - ); - } + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP COLUMN "copilotEventStatus"`, + ); + await queryRunner.query( + `ALTER TABLE "CopilotEvent" DROP COLUMN "pullRequestId"`, + ); + } } diff --git a/CommonServer/Infrastructure/Postgres/SchemaMigrations/Index.ts b/CommonServer/Infrastructure/Postgres/SchemaMigrations/Index.ts index c377a26e96..2ff97acadf 100644 --- a/CommonServer/Infrastructure/Postgres/SchemaMigrations/Index.ts +++ b/CommonServer/Infrastructure/Postgres/SchemaMigrations/Index.ts @@ -1,31 +1,31 @@ -import InitialMigration from './1717605043663-InitialMigration'; -import { MigrationName1717678334852 } from './1717678334852-MigrationName'; -import { MigrationName1717839110671 } from './1717839110671-MigrationName'; -import { MigrationName1717849921874 } from './1717849921874-MigrationName'; -import { MigrationName1717955235341 } from './1717955235341-MigrationName'; -import { MigrationName1718037833516 } from './1718037833516-MigrationName'; -import { MigrationName1718100824584 } from './1718100824584-MigrationName'; -import { MigrationName1718101665865 } from './1718101665865-MigrationName'; -import { MigrationName1718119926223 } from './1718119926223-MigrationName'; -import { MigrationName1718124277321 } from './1718124277321-MigrationName'; -import { MigrationName1718126316684 } from './1718126316684-MigrationName'; -import { MigrationName1718188920011 } from './1718188920011-MigrationName'; -import { MigrationName1718203144945 } from './1718203144945-MigrationName'; -import { MigrationName1718285877004 } from './1718285877004-MigrationName'; +import InitialMigration from "./1717605043663-InitialMigration"; +import { MigrationName1717678334852 } from "./1717678334852-MigrationName"; +import { MigrationName1717839110671 } from "./1717839110671-MigrationName"; +import { MigrationName1717849921874 } from "./1717849921874-MigrationName"; +import { MigrationName1717955235341 } from "./1717955235341-MigrationName"; +import { MigrationName1718037833516 } from "./1718037833516-MigrationName"; +import { MigrationName1718100824584 } from "./1718100824584-MigrationName"; +import { MigrationName1718101665865 } from "./1718101665865-MigrationName"; +import { MigrationName1718119926223 } from "./1718119926223-MigrationName"; +import { MigrationName1718124277321 } from "./1718124277321-MigrationName"; +import { MigrationName1718126316684 } from "./1718126316684-MigrationName"; +import { MigrationName1718188920011 } from "./1718188920011-MigrationName"; +import { MigrationName1718203144945 } from "./1718203144945-MigrationName"; +import { MigrationName1718285877004 } from "./1718285877004-MigrationName"; export default [ - InitialMigration, - MigrationName1717678334852, - MigrationName1717839110671, - MigrationName1717849921874, - MigrationName1717955235341, - MigrationName1718037833516, - MigrationName1718100824584, - MigrationName1718101665865, - MigrationName1718119926223, - MigrationName1718124277321, - MigrationName1718126316684, - MigrationName1718188920011, - MigrationName1718203144945, - MigrationName1718285877004, + InitialMigration, + MigrationName1717678334852, + MigrationName1717839110671, + MigrationName1717849921874, + MigrationName1717955235341, + MigrationName1718037833516, + MigrationName1718100824584, + MigrationName1718101665865, + MigrationName1718119926223, + MigrationName1718124277321, + MigrationName1718126316684, + MigrationName1718188920011, + MigrationName1718203144945, + MigrationName1718285877004, ]; diff --git a/CommonServer/Infrastructure/Postgres/TestDataSourceOptions.ts b/CommonServer/Infrastructure/Postgres/TestDataSourceOptions.ts index 29f17d38bc..840a5ce4d6 100644 --- a/CommonServer/Infrastructure/Postgres/TestDataSourceOptions.ts +++ b/CommonServer/Infrastructure/Postgres/TestDataSourceOptions.ts @@ -1,21 +1,21 @@ -import { DatabaseName } from '../../EnvironmentConfig'; -import ProdDataSourceOptions from './DataSourceOptions'; -import Faker from 'Common/Utils/Faker'; -import { DataSourceOptions } from 'typeorm'; +import { DatabaseName } from "../../EnvironmentConfig"; +import ProdDataSourceOptions from "./DataSourceOptions"; +import Faker from "Common/Utils/Faker"; +import { DataSourceOptions } from "typeorm"; type GetTestDataSourceOptions = () => DataSourceOptions; const getTestDataSourceOptions: GetTestDataSourceOptions = - (): DataSourceOptions => { - // we use process.env values directly here because it can change during test runs and we need to get the latest values. - return { - ...ProdDataSourceOptions, - host: process.env['DATABASE_HOST'] || 'localhost', - port: parseInt(process.env['DATABASE_PORT']?.toString() || '5432'), - username: process.env['DATABASE_USERNAME'] || 'postgres', - password: process.env['DATABASE_PASSWORD'] || 'password', - database: DatabaseName + Faker.randomNumbers(16), - } as DataSourceOptions; - }; + (): DataSourceOptions => { + // we use process.env values directly here because it can change during test runs and we need to get the latest values. + return { + ...ProdDataSourceOptions, + host: process.env["DATABASE_HOST"] || "localhost", + port: parseInt(process.env["DATABASE_PORT"]?.toString() || "5432"), + username: process.env["DATABASE_USERNAME"] || "postgres", + password: process.env["DATABASE_PASSWORD"] || "password", + database: DatabaseName + Faker.randomNumbers(16), + } as DataSourceOptions; + }; export default getTestDataSourceOptions; diff --git a/CommonServer/Infrastructure/PostgresDatabase.ts b/CommonServer/Infrastructure/PostgresDatabase.ts index 3e03e8a7aa..c1bd4b7abf 100644 --- a/CommonServer/Infrastructure/PostgresDatabase.ts +++ b/CommonServer/Infrastructure/PostgresDatabase.ts @@ -1,97 +1,97 @@ -import logger from '../Utils/Logger'; -import dataSourceOptions from './Postgres/DataSourceOptions'; -import getTestDataSourceOptions from './Postgres/TestDataSourceOptions'; -import Sleep from 'Common/Types/Sleep'; -import { DataSource, DataSourceOptions } from 'typeorm'; +import logger from "../Utils/Logger"; +import dataSourceOptions from "./Postgres/DataSourceOptions"; +import getTestDataSourceOptions from "./Postgres/TestDataSourceOptions"; +import Sleep from "Common/Types/Sleep"; +import { DataSource, DataSourceOptions } from "typeorm"; export default class Database { - private dataSource!: DataSource | null; + private dataSource!: DataSource | null; - public getDatasourceOptions(): DataSourceOptions { - return dataSourceOptions; - } + public getDatasourceOptions(): DataSourceOptions { + return dataSourceOptions; + } - public getTestDatasourceOptions(): DataSourceOptions { - return getTestDataSourceOptions(); - } + public getTestDatasourceOptions(): DataSourceOptions { + return getTestDataSourceOptions(); + } - public getDataSource(): DataSource | null { - return this.dataSource; - } + public getDataSource(): DataSource | null { + return this.dataSource; + } - public isConnected(): boolean { - return Boolean(this.dataSource); - } + public isConnected(): boolean { + return Boolean(this.dataSource); + } - public async connect( - dataSourceOptions: DataSourceOptions - ): Promise<DataSource> { - let retry: number = 0; + public async connect( + dataSourceOptions: DataSourceOptions, + ): Promise<DataSource> { + let retry: number = 0; - try { - type ConnectToDatabaseFunction = () => Promise<DataSource>; + try { + type ConnectToDatabaseFunction = () => Promise<DataSource>; - const connectToDatabase: ConnectToDatabaseFunction = - async (): Promise<DataSource> => { - try { - const PostgresDataSource: DataSource = new DataSource( - dataSourceOptions - ); - const dataSource: DataSource = - await PostgresDataSource.initialize(); - logger.debug('Postgres Database Connected'); - this.dataSource = dataSource; - return dataSource; - } catch (err) { - if (retry < 3) { - logger.debug( - 'Cannot connect to Postgres. Retrying again in 5 seconds' - ); - // sleep for 5 seconds. + const connectToDatabase: ConnectToDatabaseFunction = + async (): Promise<DataSource> => { + try { + const PostgresDataSource: DataSource = new DataSource( + dataSourceOptions, + ); + const dataSource: DataSource = + await PostgresDataSource.initialize(); + logger.debug("Postgres Database Connected"); + this.dataSource = dataSource; + return dataSource; + } catch (err) { + if (retry < 3) { + logger.debug( + "Cannot connect to Postgres. Retrying again in 5 seconds", + ); + // sleep for 5 seconds. - await Sleep.sleep(5000); + await Sleep.sleep(5000); - retry++; - return await connectToDatabase(); - } - throw err; - } - }; - - return await connectToDatabase(); - } catch (err) { - logger.error('Postgres Database Connection Failed'); - logger.error(err); - throw err; - } - } - - public async disconnect(): Promise<void> { - if (this.dataSource) { - await this.dataSource.destroy(); - this.dataSource = null; - } - } - - public async checkConnnectionStatus(): Promise<boolean> { - // check popstgres connection to see if it is still alive - - try { - const result: any = await this.dataSource?.query( - `SELECT COUNT(domain) FROM "AcmeChallenge"` - ); // this is a dummy query to check if the connection is still alive - - if (!result) { - return false; + retry++; + return await connectToDatabase(); } + throw err; + } + }; - return true; - } catch (err) { - logger.error('Postgres Connection Lost'); - logger.error(err); - return false; - } + return await connectToDatabase(); + } catch (err) { + logger.error("Postgres Database Connection Failed"); + logger.error(err); + throw err; } + } + + public async disconnect(): Promise<void> { + if (this.dataSource) { + await this.dataSource.destroy(); + this.dataSource = null; + } + } + + public async checkConnnectionStatus(): Promise<boolean> { + // check popstgres connection to see if it is still alive + + try { + const result: any = await this.dataSource?.query( + `SELECT COUNT(domain) FROM "AcmeChallenge"`, + ); // this is a dummy query to check if the connection is still alive + + if (!result) { + return false; + } + + return true; + } catch (err) { + logger.error("Postgres Connection Lost"); + logger.error(err); + return false; + } + } } export const PostgresAppInstance: Database = new Database(); diff --git a/CommonServer/Infrastructure/Queue.ts b/CommonServer/Infrastructure/Queue.ts index dc8611cdfa..4cbc77bb90 100644 --- a/CommonServer/Infrastructure/Queue.ts +++ b/CommonServer/Infrastructure/Queue.ts @@ -1,99 +1,95 @@ -import { RedisHostname, RedisPassword, RedisPort } from '../EnvironmentConfig'; -import Dictionary from 'Common/Types/Dictionary'; -import { JSONObject } from 'Common/Types/JSON'; -import { Queue as BullQueue, Job, JobsOptions } from 'bullmq'; +import { RedisHostname, RedisPassword, RedisPort } from "../EnvironmentConfig"; +import Dictionary from "Common/Types/Dictionary"; +import { JSONObject } from "Common/Types/JSON"; +import { Queue as BullQueue, Job, JobsOptions } from "bullmq"; export enum QueueName { - Workflow = 'Workflow', - Worker = 'Worker', + Workflow = "Workflow", + Worker = "Worker", } export type QueueJob = Job; export default class Queue { - private static queueDict: Dictionary<BullQueue> = {}; + private static queueDict: Dictionary<BullQueue> = {}; - public static getQueue(queueName: QueueName): BullQueue { - // check if the queue is already created - if (this.queueDict[queueName]) { - return this.queueDict[queueName] as BullQueue; - } - - const queue: BullQueue = new BullQueue(queueName, { - connection: { - host: RedisHostname.toString(), - port: RedisPort.toNumber(), - password: RedisPassword, - }, - }); - - // save it to the dictionary - this.queueDict[queueName] = queue; - - return queue; + public static getQueue(queueName: QueueName): BullQueue { + // check if the queue is already created + if (this.queueDict[queueName]) { + return this.queueDict[queueName] as BullQueue; } - public static async removeJob( - queueName: QueueName, - jobId: string - ): Promise<void> { - if (!jobId) { - return; - } + const queue: BullQueue = new BullQueue(queueName, { + connection: { + host: RedisHostname.toString(), + port: RedisPort.toNumber(), + password: RedisPassword, + }, + }); - const job: Job | undefined = await this.getQueue(queueName).getJob( - jobId - ); + // save it to the dictionary + this.queueDict[queueName] = queue; - if (job) { - await job.remove(); - } + return queue; + } - // remove existing repeatable job - await this.getQueue(queueName).removeRepeatableByKey(jobId); + public static async removeJob( + queueName: QueueName, + jobId: string, + ): Promise<void> { + if (!jobId) { + return; } - public static async addJob( - queueName: QueueName, - jobId: string, - jobName: string, - data: JSONObject, - options?: { - scheduleAt?: string | undefined; - repeatableKey?: string | undefined; - } - ): Promise<Job> { - const optionsObject: JobsOptions = { - jobId: jobId.toString(), - }; + const job: Job | undefined = await this.getQueue(queueName).getJob(jobId); - if (options && options.scheduleAt) { - optionsObject.repeat = { - pattern: options.scheduleAt, - }; - } - - const job: Job | undefined = await this.getQueue(queueName).getJob( - jobId - ); - - if (job) { - await job.remove(); - } - - if (options?.repeatableKey) { - // remove existing repeatable job - await this.getQueue(queueName).removeRepeatableByKey( - options?.repeatableKey - ); - } - - const jobAdded: Job = await this.getQueue(queueName).add( - jobName, - data, - optionsObject - ); - - return jobAdded; + if (job) { + await job.remove(); } + + // remove existing repeatable job + await this.getQueue(queueName).removeRepeatableByKey(jobId); + } + + public static async addJob( + queueName: QueueName, + jobId: string, + jobName: string, + data: JSONObject, + options?: { + scheduleAt?: string | undefined; + repeatableKey?: string | undefined; + }, + ): Promise<Job> { + const optionsObject: JobsOptions = { + jobId: jobId.toString(), + }; + + if (options && options.scheduleAt) { + optionsObject.repeat = { + pattern: options.scheduleAt, + }; + } + + const job: Job | undefined = await this.getQueue(queueName).getJob(jobId); + + if (job) { + await job.remove(); + } + + if (options?.repeatableKey) { + // remove existing repeatable job + await this.getQueue(queueName).removeRepeatableByKey( + options?.repeatableKey, + ); + } + + const jobAdded: Job = await this.getQueue(queueName).add( + jobName, + data, + optionsObject, + ); + + return jobAdded; + } } diff --git a/CommonServer/Infrastructure/QueueWorker.ts b/CommonServer/Infrastructure/QueueWorker.ts index 23643a6356..bb16b38a77 100644 --- a/CommonServer/Infrastructure/QueueWorker.ts +++ b/CommonServer/Infrastructure/QueueWorker.ts @@ -1,54 +1,51 @@ -import { RedisHostname, RedisPassword, RedisPort } from '../EnvironmentConfig'; -import { QueueJob, QueueName } from './Queue'; -import TimeoutException from 'Common/Types/Exception/TimeoutException'; +import { RedisHostname, RedisPassword, RedisPort } from "../EnvironmentConfig"; +import { QueueJob, QueueName } from "./Queue"; +import TimeoutException from "Common/Types/Exception/TimeoutException"; import { - PromiseRejectErrorFunction, - PromiseVoidFunction, - VoidFunction, -} from 'Common/Types/FunctionTypes'; -import { Worker } from 'bullmq'; + PromiseRejectErrorFunction, + PromiseVoidFunction, + VoidFunction, +} from "Common/Types/FunctionTypes"; +import { Worker } from "bullmq"; export default class QueueWorker { - public static getWorker( - queueName: QueueName, - onJobInQueue: (job: QueueJob) => Promise<void>, - options: { concurrency: number } - ): Worker { - const worker: Worker = new Worker(queueName, onJobInQueue, { - connection: { - host: RedisHostname.toString(), - port: RedisPort.toNumber(), - password: RedisPassword, - }, - concurrency: options.concurrency, - }); + public static getWorker( + queueName: QueueName, + onJobInQueue: (job: QueueJob) => Promise<void>, + options: { concurrency: number }, + ): Worker { + const worker: Worker = new Worker(queueName, onJobInQueue, { + connection: { + host: RedisHostname.toString(), + port: RedisPort.toNumber(), + password: RedisPassword, + }, + concurrency: options.concurrency, + }); - process.on('SIGINT', async () => { - await worker.close(); - }); + process.on("SIGINT", async () => { + await worker.close(); + }); - return worker; - } + return worker; + } - public static async runJobWithTimeout( - timeout: number, - jobCallback: PromiseVoidFunction - ): Promise<void> { - type TimeoutPromise = (ms: number) => Promise<void>; + public static async runJobWithTimeout( + timeout: number, + jobCallback: PromiseVoidFunction, + ): Promise<void> { + type TimeoutPromise = (ms: number) => Promise<void>; - const timeoutPromise: TimeoutPromise = (ms: number): Promise<void> => { - return new Promise( - ( - _resolve: VoidFunction, - reject: PromiseRejectErrorFunction - ) => { - setTimeout(() => { - return reject(new TimeoutException('Job Timeout')); - }, ms); - } - ); - }; + const timeoutPromise: TimeoutPromise = (ms: number): Promise<void> => { + return new Promise( + (_resolve: VoidFunction, reject: PromiseRejectErrorFunction) => { + setTimeout(() => { + return reject(new TimeoutException("Job Timeout")); + }, ms); + }, + ); + }; - return await Promise.race([timeoutPromise(timeout), jobCallback()]); - } + return await Promise.race([timeoutPromise(timeout), jobCallback()]); + } } diff --git a/CommonServer/Infrastructure/Redis.ts b/CommonServer/Infrastructure/Redis.ts index a70610649b..3463e255b0 100644 --- a/CommonServer/Infrastructure/Redis.ts +++ b/CommonServer/Infrastructure/Redis.ts @@ -1,148 +1,144 @@ import { - RedisDb, - RedisHostname, - RedisPassword, - RedisPort, - RedisTlsCa, - RedisTlsCert, - RedisTlsKey, - RedisTlsSentinelMode, - RedisUsername, - ShouldRedisTlsEnable, -} from '../EnvironmentConfig'; -import logger from '../Utils/Logger'; -import Sleep from 'Common/Types/Sleep'; -import { Redis as RedisClient, RedisOptions } from 'ioredis'; + RedisDb, + RedisHostname, + RedisPassword, + RedisPort, + RedisTlsCa, + RedisTlsCert, + RedisTlsKey, + RedisTlsSentinelMode, + RedisUsername, + ShouldRedisTlsEnable, +} from "../EnvironmentConfig"; +import logger from "../Utils/Logger"; +import Sleep from "Common/Types/Sleep"; +import { Redis as RedisClient, RedisOptions } from "ioredis"; export type ClientType = RedisClient; export default abstract class Redis { - private static client: RedisClient | null = null; + private static client: RedisClient | null = null; - public static isConnected(): boolean { - if (!this.client) { - return false; + public static isConnected(): boolean { + if (!this.client) { + return false; + } + + return this.client.status === "ready"; + } + + public static getClient(): RedisClient | null { + return this.client; + } + + public static async connect(): Promise<RedisClient> { + let retry: number = 0; + + try { + const redisOptions: RedisOptions = { + host: RedisHostname, + port: RedisPort.toNumber(), + username: RedisUsername, + password: RedisPassword, + db: RedisDb, + enableTLSForSentinelMode: RedisTlsSentinelMode, + lazyConnect: true, + }; + + if (ShouldRedisTlsEnable) { + redisOptions.tls = { + ca: RedisTlsCa || undefined, + cert: RedisTlsCert || undefined, + key: RedisTlsKey || undefined, + }; + } + + this.client = new RedisClient(redisOptions); + + // Listen to 'error' events to the Redis connection + this.client.on("error", (error: Error) => { + if ((error as any).code === "ECONNRESET") { + logger.error("Connection to Redis Session Store timed out."); + } else if ((error as any).code === "ECONNREFUSED") { + logger.error("Connection to Redis Session Store refused!"); + } else { + logger.error(error); } + }); - return this.client.status === 'ready'; - } + // Listen to 'reconnecting' event to Redis + this.client.on("reconnecting", () => { + if (this.client?.status === "reconnecting") { + logger.error("Reconnecting to Redis Session Store..."); + } else { + logger.error("Error reconnecting to Redis Session Store."); + } + }); - public static getClient(): RedisClient | null { - return this.client; - } + // Listen to the 'connect' event to Redis + this.client.on("connect", (err: Error) => { + if (!err) { + logger.debug("Connected to Redis Session Store!"); + } + }); - public static async connect(): Promise<RedisClient> { - let retry: number = 0; + type ConnectToDatabaseFunction = (client: RedisClient) => Promise<void>; + const connectToDatabase: ConnectToDatabaseFunction = async ( + client: RedisClient, + ): Promise<void> => { try { - const redisOptions: RedisOptions = { - host: RedisHostname, - port: RedisPort.toNumber(), - username: RedisUsername, - password: RedisPassword, - db: RedisDb, - enableTLSForSentinelMode: RedisTlsSentinelMode, - lazyConnect: true, - }; - - if (ShouldRedisTlsEnable) { - redisOptions.tls = { - ca: RedisTlsCa || undefined, - cert: RedisTlsCert || undefined, - key: RedisTlsKey || undefined, - }; - } - - this.client = new RedisClient(redisOptions); - - // Listen to 'error' events to the Redis connection - this.client.on('error', (error: Error) => { - if ((error as any).code === 'ECONNRESET') { - logger.error( - 'Connection to Redis Session Store timed out.' - ); - } else if ((error as any).code === 'ECONNREFUSED') { - logger.error('Connection to Redis Session Store refused!'); - } else { - logger.error(error); - } - }); - - // Listen to 'reconnecting' event to Redis - this.client.on('reconnecting', () => { - if (this.client?.status === 'reconnecting') { - logger.error('Reconnecting to Redis Session Store...'); - } else { - logger.error('Error reconnecting to Redis Session Store.'); - } - }); - - // Listen to the 'connect' event to Redis - this.client.on('connect', (err: Error) => { - if (!err) { - logger.debug('Connected to Redis Session Store!'); - } - }); - - type ConnectToDatabaseFunction = ( - client: RedisClient - ) => Promise<void>; - - const connectToDatabase: ConnectToDatabaseFunction = async ( - client: RedisClient - ): Promise<void> => { - try { - await client.connect(); - } catch (err) { - if (retry < 3) { - logger.debug( - 'Cannot connect to Redis. Retrying again in 5 seconds' - ); - // sleep for 5 seconds. - - await Sleep.sleep(5000); - - retry++; - return await connectToDatabase(client); - } - throw err; - } - }; - - await connectToDatabase(this.client); - + await client.connect(); + } catch (err) { + if (retry < 3) { logger.debug( - `Redis connected on ${RedisHostname}:${RedisPort.toNumber()}` + "Cannot connect to Redis. Retrying again in 5 seconds", ); - return this.client; - } catch (err) { - logger.error('Redis Connection Failed'); - logger.error(err); - throw err; + // sleep for 5 seconds. + + await Sleep.sleep(5000); + + retry++; + return await connectToDatabase(client); + } + throw err; } + }; + + await connectToDatabase(this.client); + + logger.debug( + `Redis connected on ${RedisHostname}:${RedisPort.toNumber()}`, + ); + return this.client; + } catch (err) { + logger.error("Redis Connection Failed"); + logger.error(err); + throw err; } + } - public static disconnect(): void { - if (this.isConnected()) { - this.client?.disconnect(); - this.client = null; - } + public static disconnect(): void { + if (this.isConnected()) { + this.client?.disconnect(); + this.client = null; } + } - public static async checkConnnectionStatus(): Promise<boolean> { - // Ping redis to check if the connection is still alive - try { - const result: 'PONG' | undefined = await this.client?.ping(); + public static async checkConnnectionStatus(): Promise<boolean> { + // Ping redis to check if the connection is still alive + try { + const result: "PONG" | undefined = await this.client?.ping(); - if (result !== 'PONG') { - return false; - } + if (result !== "PONG") { + return false; + } - return true; - } catch (err) { - logger.error('Redis Connection Lost'); - logger.error(err); - return false; - } + return true; + } catch (err) { + logger.error("Redis Connection Lost"); + logger.error(err); + return false; } + } } diff --git a/CommonServer/Infrastructure/Semaphore.ts b/CommonServer/Infrastructure/Semaphore.ts index 85e6432810..f00e5db19f 100644 --- a/CommonServer/Infrastructure/Semaphore.ts +++ b/CommonServer/Infrastructure/Semaphore.ts @@ -1,53 +1,52 @@ -import Redis, { ClientType } from './Redis'; -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; -import { Mutex } from 'redis-semaphore'; +import Redis, { ClientType } from "./Redis"; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; +import { Mutex } from "redis-semaphore"; export default class Semaphore { - private static mutexDictionary: Dictionary<Mutex> = {}; + private static mutexDictionary: Dictionary<Mutex> = {}; - // returns the mutex id - public static async lock(data: { - key: string; - lockTimeout?: number; - }): Promise<ObjectID> { - if (!data.lockTimeout) { - data.lockTimeout = 1000; - } - - const { key } = data; - - const client: ClientType | null = Redis.getClient(); - - if (!client) { - throw new Error('Redis client is not connected'); - } - - const mutex: Mutex = new Mutex(client, key, { - lockTimeout: data.lockTimeout, - }); - - await mutex.acquire(); - - const mutexId: ObjectID = ObjectID.generate(); - - // add to the dictionary - this.mutexDictionary[mutexId.toString()] = mutex; - - return mutexId; + // returns the mutex id + public static async lock(data: { + key: string; + lockTimeout?: number; + }): Promise<ObjectID> { + if (!data.lockTimeout) { + data.lockTimeout = 1000; } - public static async release(mutexId: ObjectID): Promise<void> { - const mutex: Mutex | undefined = - this.mutexDictionary[mutexId.toString()]; + const { key } = data; - if (!mutex) { - return; // already released - } + const client: ClientType | null = Redis.getClient(); - await mutex.release(); - - // remove from the dictionary - delete this.mutexDictionary[mutexId.toString()]; + if (!client) { + throw new Error("Redis client is not connected"); } + + const mutex: Mutex = new Mutex(client, key, { + lockTimeout: data.lockTimeout, + }); + + await mutex.acquire(); + + const mutexId: ObjectID = ObjectID.generate(); + + // add to the dictionary + this.mutexDictionary[mutexId.toString()] = mutex; + + return mutexId; + } + + public static async release(mutexId: ObjectID): Promise<void> { + const mutex: Mutex | undefined = this.mutexDictionary[mutexId.toString()]; + + if (!mutex) { + return; // already released + } + + await mutex.release(); + + // remove from the dictionary + delete this.mutexDictionary[mutexId.toString()]; + } } diff --git a/CommonServer/Infrastructure/SocketIO.ts b/CommonServer/Infrastructure/SocketIO.ts index 9450ee6cc5..44a1edbe6e 100644 --- a/CommonServer/Infrastructure/SocketIO.ts +++ b/CommonServer/Infrastructure/SocketIO.ts @@ -1,40 +1,40 @@ -import Express from '../Utils/Express'; -import Redis from './Redis'; -import { RealtimeRoute } from 'Common/ServiceRoute'; -import DatabaseNotConnectedException from 'Common/Types/Exception/DatabaseNotConnectedException'; -import http from 'http'; -import SocketIO from 'socket.io'; +import Express from "../Utils/Express"; +import Redis from "./Redis"; +import { RealtimeRoute } from "Common/ServiceRoute"; +import DatabaseNotConnectedException from "Common/Types/Exception/DatabaseNotConnectedException"; +import http from "http"; +import SocketIO from "socket.io"; export type Socket = SocketIO.Socket; export type SocketServer = SocketIO.Server; export default abstract class IO { - private static socketServer: SocketIO.Server | null = null; + private static socketServer: SocketIO.Server | null = null; - public static init(): void { - const server: http.Server = Express.getHttpServer(); + public static init(): void { + const server: http.Server = Express.getHttpServer(); - this.socketServer = new SocketIO.Server(server, { - path: RealtimeRoute.toString(), - }); + this.socketServer = new SocketIO.Server(server, { + path: RealtimeRoute.toString(), + }); - if (!Redis.getClient()) { - throw new DatabaseNotConnectedException( - 'Redis is not connected. Please connect to Redis before connecting to SocketIO.' - ); - } - - // const pubClient: ClientType = Redis.getClient()!.duplicate(); - // const subClient: ClientType = Redis.getClient()!.duplicate(); - - // this.socketServer.adapter(createAdapter(pubClient, subClient)); + if (!Redis.getClient()) { + throw new DatabaseNotConnectedException( + "Redis is not connected. Please connect to Redis before connecting to SocketIO.", + ); } - public static getSocketServer(): SocketIO.Server | null { - if (!this.socketServer) { - this.init(); - } + // const pubClient: ClientType = Redis.getClient()!.duplicate(); + // const subClient: ClientType = Redis.getClient()!.duplicate(); - return this.socketServer; + // this.socketServer.adapter(createAdapter(pubClient, subClient)); + } + + public static getSocketServer(): SocketIO.Server | null { + if (!this.socketServer) { + this.init(); } + + return this.socketServer; + } } diff --git a/CommonServer/Infrastructure/Status.ts b/CommonServer/Infrastructure/Status.ts index 51b651c2bb..db02744607 100644 --- a/CommonServer/Infrastructure/Status.ts +++ b/CommonServer/Infrastructure/Status.ts @@ -1,37 +1,31 @@ // This class checks the status of all the datasources. -import { ClickhouseAppInstance } from './ClickhouseDatabase'; -import { PostgresAppInstance } from './PostgresDatabase'; -import Redis from './Redis'; -import DatabaseNotConnectedException from 'Common/Types/Exception/DatabaseNotConnectedException'; +import { ClickhouseAppInstance } from "./ClickhouseDatabase"; +import { PostgresAppInstance } from "./PostgresDatabase"; +import Redis from "./Redis"; +import DatabaseNotConnectedException from "Common/Types/Exception/DatabaseNotConnectedException"; export default class InfrastructureStatus { - public static async checkStatus(data: { - checkRedisStatus: boolean; - checkPostgresStatus: boolean; - checkClickhouseStatus: boolean; - }): Promise<void> { - if (data.checkRedisStatus) { - if (!(await Redis.checkConnnectionStatus())) { - throw new DatabaseNotConnectedException( - 'Redis is not connected' - ); - } - } - - if (data.checkPostgresStatus) { - if (!(await PostgresAppInstance.checkConnnectionStatus())) { - throw new DatabaseNotConnectedException( - 'Postgres is not connected' - ); - } - } - - if (data.checkClickhouseStatus) { - if (!(await ClickhouseAppInstance.checkConnnectionStatus())) { - throw new DatabaseNotConnectedException( - 'Clickhouse is not connected' - ); - } - } + public static async checkStatus(data: { + checkRedisStatus: boolean; + checkPostgresStatus: boolean; + checkClickhouseStatus: boolean; + }): Promise<void> { + if (data.checkRedisStatus) { + if (!(await Redis.checkConnnectionStatus())) { + throw new DatabaseNotConnectedException("Redis is not connected"); + } } + + if (data.checkPostgresStatus) { + if (!(await PostgresAppInstance.checkConnnectionStatus())) { + throw new DatabaseNotConnectedException("Postgres is not connected"); + } + } + + if (data.checkClickhouseStatus) { + if (!(await ClickhouseAppInstance.checkConnnectionStatus())) { + throw new DatabaseNotConnectedException("Clickhouse is not connected"); + } + } + } } diff --git a/CommonServer/Middleware/BearerTokenAuthorization.ts b/CommonServer/Middleware/BearerTokenAuthorization.ts index 602453cdc8..1a79d43091 100644 --- a/CommonServer/Middleware/BearerTokenAuthorization.ts +++ b/CommonServer/Middleware/BearerTokenAuthorization.ts @@ -1,45 +1,40 @@ import { - ExpressRequest, - ExpressResponse, - NextFunction, - OneUptimeRequest, -} from '../Utils/Express'; -import JSONWebToken from '../Utils/JsonWebToken'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; -import { JSONObject } from 'Common/Types/JSON'; + ExpressRequest, + ExpressResponse, + NextFunction, + OneUptimeRequest, +} from "../Utils/Express"; +import JSONWebToken from "../Utils/JsonWebToken"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; +import { JSONObject } from "Common/Types/JSON"; export default class BearerTokenAuthorization { - public static async isAuthorizedBearerToken( - req: ExpressRequest, - _res: ExpressResponse, - next: NextFunction - ): Promise<void> { - try { - req = req as OneUptimeRequest; + public static async isAuthorizedBearerToken( + req: ExpressRequest, + _res: ExpressResponse, + next: NextFunction, + ): Promise<void> { + try { + req = req as OneUptimeRequest; - if ( - req.headers?.['authorization'] || - req.headers?.['Authorization'] - ) { - let token: string | undefined | Array<string> = - req.headers['authorization'] || - req.headers['Authorization']; - token = token?.toString().replace('Bearer ', ''); - if (token) { - const tokenData: JSONObject = - JSONWebToken.decodeJsonPayload(token); + if (req.headers?.["authorization"] || req.headers?.["Authorization"]) { + let token: string | undefined | Array<string> = + req.headers["authorization"] || req.headers["Authorization"]; + token = token?.toString().replace("Bearer ", ""); + if (token) { + const tokenData: JSONObject = JSONWebToken.decodeJsonPayload(token); - (req as OneUptimeRequest).bearerTokenData = tokenData; + (req as OneUptimeRequest).bearerTokenData = tokenData; - return next(); - } - } - - throw new NotAuthorizedException( - 'Invalid bearer token, or bearer token not provided.' - ); - } catch (err) { - next(err); + return next(); } + } + + throw new NotAuthorizedException( + "Invalid bearer token, or bearer token not provided.", + ); + } catch (err) { + next(err); } + } } diff --git a/CommonServer/Middleware/ClusterKeyAuthorization.ts b/CommonServer/Middleware/ClusterKeyAuthorization.ts index a0442ac594..1796e9bafd 100644 --- a/CommonServer/Middleware/ClusterKeyAuthorization.ts +++ b/CommonServer/Middleware/ClusterKeyAuthorization.ts @@ -1,56 +1,56 @@ -import { ClusterKey as ONEUPTIME_SECRET } from '../EnvironmentConfig'; +import { ClusterKey as ONEUPTIME_SECRET } from "../EnvironmentConfig"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../Utils/Express'; -import Response from '../Utils/Response'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../Utils/Express"; +import Response from "../Utils/Response"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; export default class ClusterKeyAuthorization { - public static getClusterKeyHeaders(): Dictionary<string> { - return { - clusterkey: ONEUPTIME_SECRET.toString(), - }; + public static getClusterKeyHeaders(): Dictionary<string> { + return { + clusterkey: ONEUPTIME_SECRET.toString(), + }; + } + + public static async isAuthorizedServiceMiddleware( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> { + let clusterKey: ObjectID; + + if (req.params && req.params["clusterKey"]) { + clusterKey = new ObjectID(req.params["clusterKey"]); + } else if (req.query && req.query["clusterKey"]) { + clusterKey = new ObjectID(req.query["clusterKey"] as string); + } else if (req.headers && req.headers["clusterkey"]) { + // Header keys are automatically transformed to lowercase + clusterKey = new ObjectID(req.headers["clusterkey"] as string); + } else if (req.body && req.body.clusterKey) { + clusterKey = new ObjectID(req.body.clusterKey); + } else { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Cluster key not found."), + ); } - public static async isAuthorizedServiceMiddleware( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> { - let clusterKey: ObjectID; + const isAuthorized: boolean = + clusterKey.toString() === ONEUPTIME_SECRET.toString(); - if (req.params && req.params['clusterKey']) { - clusterKey = new ObjectID(req.params['clusterKey']); - } else if (req.query && req.query['clusterKey']) { - clusterKey = new ObjectID(req.query['clusterKey'] as string); - } else if (req.headers && req.headers['clusterkey']) { - // Header keys are automatically transformed to lowercase - clusterKey = new ObjectID(req.headers['clusterkey'] as string); - } else if (req.body && req.body.clusterKey) { - clusterKey = new ObjectID(req.body.clusterKey); - } else { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Cluster key not found.') - ); - } - - const isAuthorized: boolean = - clusterKey.toString() === ONEUPTIME_SECRET.toString(); - - if (!isAuthorized) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid cluster key provided') - ); - } - - return next(); + if (!isAuthorized) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid cluster key provided"), + ); } + + return next(); + } } diff --git a/CommonServer/Middleware/NotificationMiddleware.ts b/CommonServer/Middleware/NotificationMiddleware.ts index de1da64486..9f3aed8482 100644 --- a/CommonServer/Middleware/NotificationMiddleware.ts +++ b/CommonServer/Middleware/NotificationMiddleware.ts @@ -1,70 +1,70 @@ import { - ExpressRequest, - ExpressResponse, - NextFunction, - OneUptimeRequest, -} from '../Utils/Express'; -import JSONWebToken from '../Utils/JsonWebToken'; -import Response from '../Utils/Response'; -import { OnCallInputRequest } from 'Common/Types/Call/CallRequest'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import VoiceResponse from 'twilio/lib/twiml/VoiceResponse'; + ExpressRequest, + ExpressResponse, + NextFunction, + OneUptimeRequest, +} from "../Utils/Express"; +import JSONWebToken from "../Utils/JsonWebToken"; +import Response from "../Utils/Response"; +import { OnCallInputRequest } from "Common/Types/Call/CallRequest"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import VoiceResponse from "twilio/lib/twiml/VoiceResponse"; export default class NotificationMiddleware { - public static async sendResponse( - req: ExpressRequest, - res: ExpressResponse, - onCallInputRequest: OnCallInputRequest - ): Promise<void> { - const response: VoiceResponse = new VoiceResponse(); + public static async sendResponse( + req: ExpressRequest, + res: ExpressResponse, + onCallInputRequest: OnCallInputRequest, + ): Promise<void> { + const response: VoiceResponse = new VoiceResponse(); - if (onCallInputRequest[req.body['Digits']]) { - response.say(onCallInputRequest[req.body['Digits']]!.sayMessage); - } else { - response.say(onCallInputRequest['default']!.sayMessage); - } - - return Response.sendXmlResponse(req, res, response.toString()); + if (onCallInputRequest[req.body["Digits"]]) { + response.say(onCallInputRequest[req.body["Digits"]]!.sayMessage); + } else { + response.say(onCallInputRequest["default"]!.sayMessage); } - public static async isValidCallNotificationRequest( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> { - req = req as OneUptimeRequest; + return Response.sendXmlResponse(req, res, response.toString()); + } - if (!req.body['Digits']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid input') - ); - } + public static async isValidCallNotificationRequest( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> { + req = req as OneUptimeRequest; - if (!req.query['token']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid token') - ); - } - - const token: string = req.query['token'] as string; - - try { - (req as any).callTokenData = JSONFunctions.deserialize( - JSONWebToken.decodeJsonPayload(token) - ); - } catch (e) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid token') - ); - } - - return next(); + if (!req.body["Digits"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid input"), + ); } + + if (!req.query["token"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid token"), + ); + } + + const token: string = req.query["token"] as string; + + try { + (req as any).callTokenData = JSONFunctions.deserialize( + JSONWebToken.decodeJsonPayload(token), + ); + } catch (e) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid token"), + ); + } + + return next(); + } } diff --git a/CommonServer/Middleware/ProjectAuthorization.ts b/CommonServer/Middleware/ProjectAuthorization.ts index 058f193cbe..4653f71c9a 100644 --- a/CommonServer/Middleware/ProjectAuthorization.ts +++ b/CommonServer/Middleware/ProjectAuthorization.ts @@ -1,187 +1,182 @@ -import AccessTokenService from '../Services/AccessTokenService'; -import ApiKeyService from '../Services/ApiKeyService'; -import GlobalConfigService from '../Services/GlobalConfigService'; -import UserService from '../Services/UserService'; -import QueryHelper from '../Types/Database/QueryHelper'; +import AccessTokenService from "../Services/AccessTokenService"; +import ApiKeyService from "../Services/ApiKeyService"; +import GlobalConfigService from "../Services/GlobalConfigService"; +import UserService from "../Services/UserService"; +import QueryHelper from "../Types/Database/QueryHelper"; import { - ExpressRequest, - ExpressResponse, - NextFunction, - OneUptimeRequest, -} from '../Utils/Express'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import { UserTenantAccessPermission } from 'Common/Types/Permission'; -import UserType from 'Common/Types/UserType'; -import ApiKey from 'Model/Models/ApiKey'; -import GlobalConfig from 'Model/Models/GlobalConfig'; -import User from 'Model/Models/User'; + ExpressRequest, + ExpressResponse, + NextFunction, + OneUptimeRequest, +} from "../Utils/Express"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import { UserTenantAccessPermission } from "Common/Types/Permission"; +import UserType from "Common/Types/UserType"; +import ApiKey from "Model/Models/ApiKey"; +import GlobalConfig from "Model/Models/GlobalConfig"; +import User from "Model/Models/User"; export default class ProjectMiddleware { - public static getProjectId(req: ExpressRequest): ObjectID | null { - let projectId: ObjectID | null = null; - if (req.params && req.params['tenantid']) { - projectId = new ObjectID(req.params['tenantid']); - } else if (req.query && req.query['tenantid']) { - projectId = new ObjectID(req.query['tenantid'] as string); - } else if (req.headers && req.headers['tenantid']) { - // Header keys are automatically transformed to lowercase - projectId = new ObjectID(req.headers['tenantid'] as string); - } else if (req.headers && req.headers['projectid']) { - // Header keys are automatically transformed to lowercase - projectId = new ObjectID(req.headers['projectid'] as string); - } else if (req.body && req.body.projectId) { - projectId = new ObjectID(req.body.projectId as string); + public static getProjectId(req: ExpressRequest): ObjectID | null { + let projectId: ObjectID | null = null; + if (req.params && req.params["tenantid"]) { + projectId = new ObjectID(req.params["tenantid"]); + } else if (req.query && req.query["tenantid"]) { + projectId = new ObjectID(req.query["tenantid"] as string); + } else if (req.headers && req.headers["tenantid"]) { + // Header keys are automatically transformed to lowercase + projectId = new ObjectID(req.headers["tenantid"] as string); + } else if (req.headers && req.headers["projectid"]) { + // Header keys are automatically transformed to lowercase + projectId = new ObjectID(req.headers["projectid"] as string); + } else if (req.body && req.body.projectId) { + projectId = new ObjectID(req.body.projectId as string); + } + + return projectId; + } + + public static getApiKey(req: ExpressRequest): ObjectID | null { + if (req.headers && req.headers["apikey"]) { + return new ObjectID(req.headers["apikey"] as string); + } + + return null; + } + + public static hasApiKey(req: ExpressRequest): boolean { + return Boolean(this.getApiKey(req)); + } + + public static hasProjectID(req: ExpressRequest): boolean { + return Boolean(this.getProjectId(req)); + } + + public static async isValidProjectIdAndApiKeyMiddleware( + req: ExpressRequest, + _res: ExpressResponse, + next: NextFunction, + ): Promise<void> { + try { + const tenantId: ObjectID | null = this.getProjectId(req); + + const apiKey: ObjectID | null = this.getApiKey(req); + + if (tenantId) { + (req as OneUptimeRequest).tenantId = tenantId; + } + + if (!apiKey) { + throw new BadDataException("ApiKey not found in the request"); + } + + let apiKeyModel: ApiKey | null = null; + + if (tenantId) { + apiKeyModel = await ApiKeyService.findOneBy({ + query: { + projectId: tenantId, + apiKey: apiKey, + expiresAt: QueryHelper.greaterThan(OneUptimeDate.getCurrentDate()), + }, + select: { + _id: true, + }, + props: { isRoot: true }, + }); + + if (apiKeyModel) { + (req as OneUptimeRequest).userType = UserType.API; + // TODO: Add API key permissions. + // (req as OneUptimeRequest).permissions = + // apiKeyModel.permissions || []; + (req as OneUptimeRequest).userGlobalAccessPermission = + await AccessTokenService.getDefaultApiGlobalPermission(tenantId); + + const userTenantAccessPermission: UserTenantAccessPermission | null = + await AccessTokenService.getApiTenantAccessPermission( + tenantId, + apiKeyModel.id!, + ); + + if (userTenantAccessPermission) { + (req as OneUptimeRequest).userTenantAccessPermission = {}; + ( + (req as OneUptimeRequest) + .userTenantAccessPermission as Dictionary<UserTenantAccessPermission> + )[tenantId.toString()] = userTenantAccessPermission; + + return next(); + } } + } - return projectId; - } + if (!apiKeyModel) { + // check master key. + const masterKeyGlobalConfig: GlobalConfig | null = + await GlobalConfigService.findOneBy({ + query: { + _id: ObjectID.getZeroObjectID().toString(), + isMasterApiKeyEnabled: true, + masterApiKey: apiKey, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); - public static getApiKey(req: ExpressRequest): ObjectID | null { - if (req.headers && req.headers['apikey']) { - return new ObjectID(req.headers['apikey'] as string); + if (masterKeyGlobalConfig) { + (req as OneUptimeRequest).userType = UserType.MasterAdmin; + + // get master admin user + + const user: User | null = await UserService.findOneBy({ + query: { + isMasterAdmin: true, + }, + select: { + _id: true, + email: true, + name: true, + }, + props: { + isRoot: true, + }, + }); + + if (!user) { + throw new BadDataException( + "Master Admin user not found. Please make sure you have created a master admin user.", + ); + } + + (req as OneUptimeRequest).userAuthorization = { + userId: user.id!, + isMasterAdmin: true, + email: user.email!, + name: user.name!, + isGlobalLogin: true, + }; + + return next(); } + } - return null; - } - - public static hasApiKey(req: ExpressRequest): boolean { - return Boolean(this.getApiKey(req)); - } - - public static hasProjectID(req: ExpressRequest): boolean { - return Boolean(this.getProjectId(req)); - } - - public static async isValidProjectIdAndApiKeyMiddleware( - req: ExpressRequest, - _res: ExpressResponse, - next: NextFunction - ): Promise<void> { - try { - const tenantId: ObjectID | null = this.getProjectId(req); - - const apiKey: ObjectID | null = this.getApiKey(req); - - if (tenantId) { - (req as OneUptimeRequest).tenantId = tenantId; - } - - if (!apiKey) { - throw new BadDataException('ApiKey not found in the request'); - } - - let apiKeyModel: ApiKey | null = null; - - if (tenantId) { - apiKeyModel = await ApiKeyService.findOneBy({ - query: { - projectId: tenantId, - apiKey: apiKey, - expiresAt: QueryHelper.greaterThan( - OneUptimeDate.getCurrentDate() - ), - }, - select: { - _id: true, - }, - props: { isRoot: true }, - }); - - if (apiKeyModel) { - (req as OneUptimeRequest).userType = UserType.API; - // TODO: Add API key permissions. - // (req as OneUptimeRequest).permissions = - // apiKeyModel.permissions || []; - (req as OneUptimeRequest).userGlobalAccessPermission = - await AccessTokenService.getDefaultApiGlobalPermission( - tenantId - ); - - const userTenantAccessPermission: UserTenantAccessPermission | null = - await AccessTokenService.getApiTenantAccessPermission( - tenantId, - apiKeyModel.id! - ); - - if (userTenantAccessPermission) { - (req as OneUptimeRequest).userTenantAccessPermission = - {}; - ( - (req as OneUptimeRequest) - .userTenantAccessPermission as Dictionary<UserTenantAccessPermission> - )[tenantId.toString()] = userTenantAccessPermission; - - return next(); - } - } - } - - if (!apiKeyModel) { - // check master key. - const masterKeyGlobalConfig: GlobalConfig | null = - await GlobalConfigService.findOneBy({ - query: { - _id: ObjectID.getZeroObjectID().toString(), - isMasterApiKeyEnabled: true, - masterApiKey: apiKey, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - if (masterKeyGlobalConfig) { - (req as OneUptimeRequest).userType = UserType.MasterAdmin; - - // get master admin user - - const user: User | null = await UserService.findOneBy({ - query: { - isMasterAdmin: true, - }, - select: { - _id: true, - email: true, - name: true, - }, - props: { - isRoot: true, - }, - }); - - if (!user) { - throw new BadDataException( - 'Master Admin user not found. Please make sure you have created a master admin user.' - ); - } - - (req as OneUptimeRequest).userAuthorization = { - userId: user.id!, - isMasterAdmin: true, - email: user.email!, - name: user.name!, - isGlobalLogin: true, - }; - - return next(); - } - } - - if (!tenantId) { - throw new BadDataException( - 'ProjectID not found in the request header.' - ); - } - - throw new BadDataException('Invalid Project ID or API Key'); - } catch (err) { - next(err); - } + if (!tenantId) { + throw new BadDataException( + "ProjectID not found in the request header.", + ); + } + + throw new BadDataException("Invalid Project ID or API Key"); + } catch (err) { + next(err); } + } } diff --git a/CommonServer/Middleware/UserAuthorization.ts b/CommonServer/Middleware/UserAuthorization.ts index bc2a891f41..6a458b2234 100644 --- a/CommonServer/Middleware/UserAuthorization.ts +++ b/CommonServer/Middleware/UserAuthorization.ts @@ -1,392 +1,363 @@ -import AccessTokenService from '../Services/AccessTokenService'; -import ProjectService from '../Services/ProjectService'; -import UserService from '../Services/UserService'; -import QueryHelper from '../Types/Database/QueryHelper'; -import CookieUtil from '../Utils/Cookie'; +import AccessTokenService from "../Services/AccessTokenService"; +import ProjectService from "../Services/ProjectService"; +import UserService from "../Services/UserService"; +import QueryHelper from "../Types/Database/QueryHelper"; +import CookieUtil from "../Utils/Cookie"; import { - ExpressRequest, - ExpressResponse, - NextFunction, - OneUptimeRequest, -} from '../Utils/Express'; -import JSONWebToken from '../Utils/JsonWebToken'; -import logger from '../Utils/Logger'; -import Response from '../Utils/Response'; -import ProjectMiddleware from './ProjectAuthorization'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import Exception from 'Common/Types/Exception/Exception'; -import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException'; -import SsoAuthorizationException from 'Common/Types/Exception/SsoAuthorizationException'; -import TenantNotFoundException from 'Common/Types/Exception/TenantNotFoundException'; -import HashedString from 'Common/Types/HashedString'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import JSONWebTokenData from 'Common/Types/JsonWebTokenData'; -import ObjectID from 'Common/Types/ObjectID'; + ExpressRequest, + ExpressResponse, + NextFunction, + OneUptimeRequest, +} from "../Utils/Express"; +import JSONWebToken from "../Utils/JsonWebToken"; +import logger from "../Utils/Logger"; +import Response from "../Utils/Response"; +import ProjectMiddleware from "./ProjectAuthorization"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import Exception from "Common/Types/Exception/Exception"; +import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException"; +import SsoAuthorizationException from "Common/Types/Exception/SsoAuthorizationException"; +import TenantNotFoundException from "Common/Types/Exception/TenantNotFoundException"; +import HashedString from "Common/Types/HashedString"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import JSONWebTokenData from "Common/Types/JsonWebTokenData"; +import ObjectID from "Common/Types/ObjectID"; import { - UserGlobalAccessPermission, - UserTenantAccessPermission, -} from 'Common/Types/Permission'; -import UserType from 'Common/Types/UserType'; -import Project from 'Model/Models/Project'; + UserGlobalAccessPermission, + UserTenantAccessPermission, +} from "Common/Types/Permission"; +import UserType from "Common/Types/UserType"; +import Project from "Model/Models/Project"; export default class UserMiddleware { - /* - * Description: Checking if user is authorized to access the page and decode jwt to get user data. - * Params: - * Param 1: req.headers-> {token} - * Returns: 401: User is unauthorized since unauthorized token was present. - */ + /* + * Description: Checking if user is authorized to access the page and decode jwt to get user data. + * Params: + * Param 1: req.headers-> {token} + * Returns: 401: User is unauthorized since unauthorized token was present. + */ - public static getAccessToken(req: ExpressRequest): string | undefined { - let accessToken: string | undefined = undefined; + public static getAccessToken(req: ExpressRequest): string | undefined { + let accessToken: string | undefined = undefined; - if (CookieUtil.getCookie(req, CookieUtil.getUserTokenKey())) { - accessToken = CookieUtil.getCookie( - req, - CookieUtil.getUserTokenKey() - ); - } - - return accessToken; + if (CookieUtil.getCookie(req, CookieUtil.getUserTokenKey())) { + accessToken = CookieUtil.getCookie(req, CookieUtil.getUserTokenKey()); } - public static getSsoTokens(req: ExpressRequest): Dictionary<string> { - const ssoTokens: Dictionary<string> = {}; + return accessToken; + } - // get sso tokens from cookies. + public static getSsoTokens(req: ExpressRequest): Dictionary<string> { + const ssoTokens: Dictionary<string> = {}; - const cookies: Dictionary<string> = CookieUtil.getAllCookies(req); + // get sso tokens from cookies. - for (const key of Object.keys(cookies)) { - if (key.startsWith(CookieUtil.getSSOKey())) { - const value: string | undefined | Array<string> = cookies[key]; - let projectId: string | undefined = undefined; + const cookies: Dictionary<string> = CookieUtil.getAllCookies(req); - try { - projectId = JSONWebToken.decode( - value as string - ).projectId?.toString(); - } catch (err) { - logger.error(err); - continue; - } - - if ( - projectId && - value && - typeof value === 'string' && - typeof projectId === 'string' - ) { - ssoTokens[projectId] = cookies[key] as string; - } - } - } - - return ssoTokens; - } - - public static doesSsoTokenForProjectExist( - req: ExpressRequest, - projectId: ObjectID, - userId: ObjectID - ): boolean { - const ssoTokens: Dictionary<string> = this.getSsoTokens(req); - - if (ssoTokens && ssoTokens[projectId.toString()]) { - const decodedData: JSONWebTokenData = JSONWebToken.decode( - ssoTokens[projectId.toString()] as string - ); - if ( - decodedData.projectId?.toString() === projectId.toString() && - decodedData.userId.toString() === userId.toString() - ) { - return true; - } - } - - return false; - } - - public static async getUserMiddleware( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> { - const tenantId: ObjectID | null = ProjectMiddleware.getProjectId(req); - const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest; - - if (tenantId) { - oneuptimeRequest.tenantId = tenantId; - - // update last active of project - await ProjectService.updateLastActive(tenantId); - } - - if (ProjectMiddleware.hasApiKey(req)) { - return await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( - req, - res, - next - ); - } - - const accessToken: string | undefined = - UserMiddleware.getAccessToken(req); - - if (!accessToken) { - oneuptimeRequest.userType = UserType.Public; - return next(); - } + for (const key of Object.keys(cookies)) { + if (key.startsWith(CookieUtil.getSSOKey())) { + const value: string | undefined | Array<string> = cookies[key]; + let projectId: string | undefined = undefined; try { - oneuptimeRequest.userAuthorization = - JSONWebToken.decode(accessToken); + projectId = JSONWebToken.decode( + value as string, + ).projectId?.toString(); } catch (err) { - // if the token is invalid or expired, it'll throw this error. - oneuptimeRequest.userType = UserType.Public; - return next(); + logger.error(err); + continue; } - if (oneuptimeRequest.userAuthorization.isMasterAdmin) { - oneuptimeRequest.userType = UserType.MasterAdmin; - } else { - oneuptimeRequest.userType = UserType.User; - } - - const userId: string = - oneuptimeRequest.userAuthorization.userId.toString(); - - await UserService.updateOneBy({ - query: { - _id: userId, - }, - props: { isRoot: true }, - data: { lastActive: OneUptimeDate.getCurrentDate() }, - }); - - const userGlobalAccessPermission: UserGlobalAccessPermission | null = - await AccessTokenService.getUserGlobalAccessPermission( - oneuptimeRequest.userAuthorization.userId - ); - - if (userGlobalAccessPermission) { - oneuptimeRequest.userGlobalAccessPermission = - userGlobalAccessPermission; - } - - if (tenantId) { - try { - const userTenantAccessPermission: UserTenantAccessPermission | null = - await UserMiddleware.getUserTenantAccessPermissionWithTenantId( - { - req, - tenantId, - userId: new ObjectID(userId), - isGlobalLogin: - oneuptimeRequest.userAuthorization - .isGlobalLogin, - } - ); - - if (userTenantAccessPermission) { - oneuptimeRequest.userTenantAccessPermission = {}; - oneuptimeRequest.userTenantAccessPermission[ - tenantId.toString() - ] = userTenantAccessPermission; - } - } catch (error) { - return Response.sendErrorResponse(req, res, error as Exception); - } - } - - if (req.headers['is-multi-tenant-query']) { - if ( - userGlobalAccessPermission && - userGlobalAccessPermission.projectIds && - userGlobalAccessPermission.projectIds.length > 0 - ) { - const userTenantAccessPermission: Dictionary<UserTenantAccessPermission> | null = - await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( - req, - new ObjectID(userId), - userGlobalAccessPermission.projectIds - ); - if (userTenantAccessPermission) { - oneuptimeRequest.userTenantAccessPermission = - userTenantAccessPermission; - } - } - } - - // set permission hash. - - if (oneuptimeRequest.userGlobalAccessPermission) { - const serializedValue: JSONObject = JSONFunctions.serialize( - oneuptimeRequest.userGlobalAccessPermission - ); - const globalValue: string = JSON.stringify(serializedValue); - const globalPermissionsHash: string = await HashedString.hashValue( - globalValue, - null - ); - res.set('global-permissions', globalValue); - res.set('global-permissions-hash', globalPermissionsHash); - } - - // set project permissions hash. if ( - oneuptimeRequest.userTenantAccessPermission && - tenantId && - oneuptimeRequest.userTenantAccessPermission[tenantId.toString()] + projectId && + value && + typeof value === "string" && + typeof projectId === "string" ) { - const projectValue: string = JSON.stringify( - JSONFunctions.serialize( - oneuptimeRequest.userTenantAccessPermission[ - tenantId.toString() - ]! - ) - ); - - const projectPermissionsHash: string = await HashedString.hashValue( - projectValue, - null - ); - - if ( - !( - req.headers && - req.headers['project-permissions-hash'] && - req.headers['project-permissions-hash'] === - projectPermissionsHash - ) - ) { - res.set('project-permissions', projectValue); - res.set('project-permissions-hash', projectPermissionsHash); - } + ssoTokens[projectId] = cookies[key] as string; } - - return next(); + } } - public static async getUserTenantAccessPermissionWithTenantId(data: { - req: ExpressRequest; - tenantId: ObjectID; - userId: ObjectID; - isGlobalLogin: boolean; - }): Promise<UserTenantAccessPermission | null> { - const { req, tenantId, userId, isGlobalLogin } = data; + return ssoTokens; + } - const project: Project | null = await ProjectService.findOneById({ - id: tenantId, - select: { - requireSsoForLogin: true, - }, - props: { - isRoot: true, - }, - }); + public static doesSsoTokenForProjectExist( + req: ExpressRequest, + projectId: ObjectID, + userId: ObjectID, + ): boolean { + const ssoTokens: Dictionary<string> = this.getSsoTokens(req); - if (!project) { - throw new TenantNotFoundException('Invalid tenantId'); + if (ssoTokens && ssoTokens[projectId.toString()]) { + const decodedData: JSONWebTokenData = JSONWebToken.decode( + ssoTokens[projectId.toString()] as string, + ); + if ( + decodedData.projectId?.toString() === projectId.toString() && + decodedData.userId.toString() === userId.toString() + ) { + return true; + } + } + + return false; + } + + public static async getUserMiddleware( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> { + const tenantId: ObjectID | null = ProjectMiddleware.getProjectId(req); + const oneuptimeRequest: OneUptimeRequest = req as OneUptimeRequest; + + if (tenantId) { + oneuptimeRequest.tenantId = tenantId; + + // update last active of project + await ProjectService.updateLastActive(tenantId); + } + + if (ProjectMiddleware.hasApiKey(req)) { + return await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( + req, + res, + next, + ); + } + + const accessToken: string | undefined = UserMiddleware.getAccessToken(req); + + if (!accessToken) { + oneuptimeRequest.userType = UserType.Public; + return next(); + } + + try { + oneuptimeRequest.userAuthorization = JSONWebToken.decode(accessToken); + } catch (err) { + // if the token is invalid or expired, it'll throw this error. + oneuptimeRequest.userType = UserType.Public; + return next(); + } + + if (oneuptimeRequest.userAuthorization.isMasterAdmin) { + oneuptimeRequest.userType = UserType.MasterAdmin; + } else { + oneuptimeRequest.userType = UserType.User; + } + + const userId: string = oneuptimeRequest.userAuthorization.userId.toString(); + + await UserService.updateOneBy({ + query: { + _id: userId, + }, + props: { isRoot: true }, + data: { lastActive: OneUptimeDate.getCurrentDate() }, + }); + + const userGlobalAccessPermission: UserGlobalAccessPermission | null = + await AccessTokenService.getUserGlobalAccessPermission( + oneuptimeRequest.userAuthorization.userId, + ); + + if (userGlobalAccessPermission) { + oneuptimeRequest.userGlobalAccessPermission = userGlobalAccessPermission; + } + + if (tenantId) { + try { + const userTenantAccessPermission: UserTenantAccessPermission | null = + await UserMiddleware.getUserTenantAccessPermissionWithTenantId({ + req, + tenantId, + userId: new ObjectID(userId), + isGlobalLogin: oneuptimeRequest.userAuthorization.isGlobalLogin, + }); + + if (userTenantAccessPermission) { + oneuptimeRequest.userTenantAccessPermission = {}; + oneuptimeRequest.userTenantAccessPermission[tenantId.toString()] = + userTenantAccessPermission; } + } catch (error) { + return Response.sendErrorResponse(req, res, error as Exception); + } + } - if (!isGlobalLogin) { - if ( - !UserMiddleware.doesSsoTokenForProjectExist( - req, - tenantId, - userId - ) - ) { - throw new NotAuthenticatedException( - 'This project requires OneUptime authentication. Please login to access this project.' - ); - } + if (req.headers["is-multi-tenant-query"]) { + if ( + userGlobalAccessPermission && + userGlobalAccessPermission.projectIds && + userGlobalAccessPermission.projectIds.length > 0 + ) { + const userTenantAccessPermission: Dictionary<UserTenantAccessPermission> | null = + await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( + req, + new ObjectID(userId), + userGlobalAccessPermission.projectIds, + ); + if (userTenantAccessPermission) { + oneuptimeRequest.userTenantAccessPermission = + userTenantAccessPermission; } + } + } - if ( - project.requireSsoForLogin && - !UserMiddleware.doesSsoTokenForProjectExist(req, tenantId, userId) - ) { - throw new SsoAuthorizationException(); - } + // set permission hash. - // get project level permissions if projectid exists in request. - return await AccessTokenService.getUserTenantAccessPermission( - userId, - tenantId + if (oneuptimeRequest.userGlobalAccessPermission) { + const serializedValue: JSONObject = JSONFunctions.serialize( + oneuptimeRequest.userGlobalAccessPermission, + ); + const globalValue: string = JSON.stringify(serializedValue); + const globalPermissionsHash: string = await HashedString.hashValue( + globalValue, + null, + ); + res.set("global-permissions", globalValue); + res.set("global-permissions-hash", globalPermissionsHash); + } + + // set project permissions hash. + if ( + oneuptimeRequest.userTenantAccessPermission && + tenantId && + oneuptimeRequest.userTenantAccessPermission[tenantId.toString()] + ) { + const projectValue: string = JSON.stringify( + JSONFunctions.serialize( + oneuptimeRequest.userTenantAccessPermission[tenantId.toString()]!, + ), + ); + + const projectPermissionsHash: string = await HashedString.hashValue( + projectValue, + null, + ); + + if ( + !( + req.headers && + req.headers["project-permissions-hash"] && + req.headers["project-permissions-hash"] === projectPermissionsHash + ) + ) { + res.set("project-permissions", projectValue); + res.set("project-permissions-hash", projectPermissionsHash); + } + } + + return next(); + } + + public static async getUserTenantAccessPermissionWithTenantId(data: { + req: ExpressRequest; + tenantId: ObjectID; + userId: ObjectID; + isGlobalLogin: boolean; + }): Promise<UserTenantAccessPermission | null> { + const { req, tenantId, userId, isGlobalLogin } = data; + + const project: Project | null = await ProjectService.findOneById({ + id: tenantId, + select: { + requireSsoForLogin: true, + }, + props: { + isRoot: true, + }, + }); + + if (!project) { + throw new TenantNotFoundException("Invalid tenantId"); + } + + if (!isGlobalLogin) { + if (!UserMiddleware.doesSsoTokenForProjectExist(req, tenantId, userId)) { + throw new NotAuthenticatedException( + "This project requires OneUptime authentication. Please login to access this project.", ); + } } - public static async getUserTenantAccessPermissionForMultiTenant( - req: ExpressRequest, - userId: ObjectID, - projectIds: ObjectID[] - ): Promise<Dictionary<UserTenantAccessPermission> | null> { - if (!projectIds.length) { - return null; - } - - const projects: Array<Project> = await ProjectService.findBy({ - query: { - _id: QueryHelper.any( - projectIds.map((i: ObjectID) => { - return i.toString(); - }) || [] - ), - }, - select: { - requireSsoForLogin: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - let result: Dictionary<UserTenantAccessPermission> | null = null; - for (const projectId of projectIds) { - // check if the force sso login is required. and if it is, then check then token. - - let userTenantAccessPermission: UserTenantAccessPermission | null; - if ( - projects.find((p: Project) => { - return ( - p._id === projectId.toString() && p.requireSsoForLogin - ); - }) && - !UserMiddleware.doesSsoTokenForProjectExist( - req, - projectId, - userId - ) - ) { - // Add default permissions. - userTenantAccessPermission = - AccessTokenService.getDefaultUserTenantAccessPermission( - projectId - ); - } else { - // get project level permissions if projectid exists in request. - userTenantAccessPermission = - await AccessTokenService.getUserTenantAccessPermission( - userId, - projectId - ); - } - - if (userTenantAccessPermission) { - if (!result) { - result = {}; - } - result[projectId.toString()] = userTenantAccessPermission; - } - } - - return result; + if ( + project.requireSsoForLogin && + !UserMiddleware.doesSsoTokenForProjectExist(req, tenantId, userId) + ) { + throw new SsoAuthorizationException(); } + + // get project level permissions if projectid exists in request. + return await AccessTokenService.getUserTenantAccessPermission( + userId, + tenantId, + ); + } + + public static async getUserTenantAccessPermissionForMultiTenant( + req: ExpressRequest, + userId: ObjectID, + projectIds: ObjectID[], + ): Promise<Dictionary<UserTenantAccessPermission> | null> { + if (!projectIds.length) { + return null; + } + + const projects: Array<Project> = await ProjectService.findBy({ + query: { + _id: QueryHelper.any( + projectIds.map((i: ObjectID) => { + return i.toString(); + }) || [], + ), + }, + select: { + requireSsoForLogin: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + let result: Dictionary<UserTenantAccessPermission> | null = null; + for (const projectId of projectIds) { + // check if the force sso login is required. and if it is, then check then token. + + let userTenantAccessPermission: UserTenantAccessPermission | null; + if ( + projects.find((p: Project) => { + return p._id === projectId.toString() && p.requireSsoForLogin; + }) && + !UserMiddleware.doesSsoTokenForProjectExist(req, projectId, userId) + ) { + // Add default permissions. + userTenantAccessPermission = + AccessTokenService.getDefaultUserTenantAccessPermission(projectId); + } else { + // get project level permissions if projectid exists in request. + userTenantAccessPermission = + await AccessTokenService.getUserTenantAccessPermission( + userId, + projectId, + ); + } + + if (userTenantAccessPermission) { + if (!result) { + result = {}; + } + result[projectId.toString()] = userTenantAccessPermission; + } + } + + return result; + } } diff --git a/CommonServer/Services/AccessTokenService.ts b/CommonServer/Services/AccessTokenService.ts index 59dffb7efb..e18ac53f3e 100644 --- a/CommonServer/Services/AccessTokenService.ts +++ b/CommonServer/Services/AccessTokenService.ts @@ -1,358 +1,355 @@ -import GlobalCache from '../Infrastructure/GlobalCache'; -import QueryHelper from '../Types/Database/QueryHelper'; -import ApiKeyPermissionService from './ApiKeyPermissionService'; -import BaseService from './BaseService'; -import TeamMemberService from './TeamMemberService'; -import TeamPermissionService from './TeamPermissionService'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; +import GlobalCache from "../Infrastructure/GlobalCache"; +import QueryHelper from "../Types/Database/QueryHelper"; +import ApiKeyPermissionService from "./ApiKeyPermissionService"; +import BaseService from "./BaseService"; +import TeamMemberService from "./TeamMemberService"; +import TeamPermissionService from "./TeamPermissionService"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; import Permission, { - UserGlobalAccessPermission, - UserPermission, - UserTenantAccessPermission, -} from 'Common/Types/Permission'; -import APIKeyPermission from 'Model/Models/ApiKeyPermission'; -import Label from 'Model/Models/Label'; -import TeamMember from 'Model/Models/TeamMember'; -import TeamPermission from 'Model/Models/TeamPermission'; + UserGlobalAccessPermission, + UserPermission, + UserTenantAccessPermission, +} from "Common/Types/Permission"; +import APIKeyPermission from "Model/Models/ApiKeyPermission"; +import Label from "Model/Models/Label"; +import TeamMember from "Model/Models/TeamMember"; +import TeamPermission from "Model/Models/TeamPermission"; enum PermissionNamespace { - GlobalPermission = 'global-permissions', - ProjectPermission = 'project-permissions', + GlobalPermission = "global-permissions", + ProjectPermission = "project-permissions", } export class AccessTokenService extends BaseService { - public constructor() { - super(); + public constructor() { + super(); + } + + public async refreshUserAllPermissions(userId: ObjectID): Promise<void> { + await this.refreshUserGlobalAccessPermission(userId); + + // query for all projects user belongs to. + const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ + query: { + userId: userId, + hasAcceptedInvitation: true, + }, + select: { + projectId: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + if (teamMembers.length === 0) { + return; } - public async refreshUserAllPermissions(userId: ObjectID): Promise<void> { - await this.refreshUserGlobalAccessPermission(userId); + const projectIds: Array<ObjectID> = teamMembers.map( + (teamMember: TeamMember) => { + return teamMember.projectId!; + }, + ); - // query for all projects user belongs to. - const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ - query: { - userId: userId, - hasAcceptedInvitation: true, - }, - select: { - projectId: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); + for (const projectId of projectIds) { + await this.refreshUserTenantAccessPermission(userId, projectId); + } + } - if (teamMembers.length === 0) { - return; - } + public async getDefaultApiGlobalPermission( + projectId: ObjectID, + ): Promise<UserGlobalAccessPermission> { + return { + projectIds: [projectId], + globalPermissions: [ + Permission.Public, + Permission.User, + Permission.CurrentUser, + ], + _type: "UserGlobalAccessPermission", + }; + } - const projectIds: Array<ObjectID> = teamMembers.map( - (teamMember: TeamMember) => { - return teamMember.projectId!; - } - ); + public async getMasterKeyApiGlobalPermission( + projectId: ObjectID, + ): Promise<UserGlobalAccessPermission> { + return { + projectIds: [projectId], + globalPermissions: [ + Permission.Public, + Permission.User, + Permission.CurrentUser, + Permission.ProjectOwner, + ], + _type: "UserGlobalAccessPermission", + }; + } - for (const projectId of projectIds) { - await this.refreshUserTenantAccessPermission(userId, projectId); - } + public async getMasterApiTenantAccessPermission( + projectId: ObjectID, + ): Promise<UserTenantAccessPermission> { + const userPermissions: Array<UserPermission> = []; + + userPermissions.push({ + permission: Permission.ProjectOwner, + labelIds: [], + _type: "UserPermission", + }); + + const permission: UserTenantAccessPermission = + this.getDefaultUserTenantAccessPermission(projectId); + + permission.permissions = permission.permissions.concat(userPermissions); + + return permission; + } + + public async getApiTenantAccessPermission( + projectId: ObjectID, + apiKeyId: ObjectID, + ): Promise<UserTenantAccessPermission> { + // get team permissions. + const apiKeyPermission: Array<APIKeyPermission> = + await ApiKeyPermissionService.findBy({ + query: { + apiKeyId: apiKeyId, + }, + select: { + permission: true, + labels: { + _id: true, + }, + isBlockPermission: true, + }, + + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + const userPermissions: Array<UserPermission> = []; + + for (const apiPermission of apiKeyPermission) { + if (!apiPermission.labels) { + apiPermission.labels = []; + } + + userPermissions.push({ + permission: apiPermission.permission!, + labelIds: apiPermission.labels.map((label: Label) => { + return label.id!; + }), + isBlockPermission: apiPermission.isBlockPermission, + _type: "UserPermission", + }); } - public async getDefaultApiGlobalPermission( - projectId: ObjectID - ): Promise<UserGlobalAccessPermission> { - return { - projectIds: [projectId], - globalPermissions: [ - Permission.Public, - Permission.User, - Permission.CurrentUser, - ], - _type: 'UserGlobalAccessPermission', - }; + const permission: UserTenantAccessPermission = + this.getDefaultUserTenantAccessPermission(projectId); + + permission.permissions = permission.permissions.concat(userPermissions); + + return permission; + } + + public async refreshUserGlobalAccessPermission( + userId: ObjectID, + ): Promise<UserGlobalAccessPermission> { + // query for all projects user belongs to. + const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ + query: { + userId: userId, + hasAcceptedInvitation: true, + }, + select: { + projectId: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + const projectIds: Array<ObjectID> = teamMembers.map( + (teamMember: TeamMember) => { + return teamMember.projectId!; + }, + ); + + const permissionToStore: UserGlobalAccessPermission = { + projectIds, + globalPermissions: [ + Permission.Public, + Permission.User, + Permission.CurrentUser, + ], + _type: "UserGlobalAccessPermission", + }; + + await GlobalCache.setJSON("user", userId.toString(), permissionToStore); + + return permissionToStore; + } + + public getDefaultUserTenantAccessPermission( + projectId: ObjectID, + ): UserTenantAccessPermission { + const userPermissions: Array<UserPermission> = []; + + userPermissions.push({ + permission: Permission.CurrentUser, + labelIds: [], + isBlockPermission: false, + _type: "UserPermission", + }); + + userPermissions.push({ + permission: Permission.UnAuthorizedSsoUser, + labelIds: [], + isBlockPermission: false, + _type: "UserPermission", + }); + + const permission: UserTenantAccessPermission = { + projectId, + permissions: userPermissions, + isBlockPermission: false, + _type: "UserTenantAccessPermission", + }; + + return permission; + } + + public async getUserGlobalAccessPermission( + userId: ObjectID, + ): Promise<UserGlobalAccessPermission | null> { + const json: JSONObject | null = await GlobalCache.getJSONObject( + "user", + userId.toString(), + ); + + if (!json) { + return await this.refreshUserGlobalAccessPermission(userId); } - public async getMasterKeyApiGlobalPermission( - projectId: ObjectID - ): Promise<UserGlobalAccessPermission> { - return { - projectIds: [projectId], - globalPermissions: [ - Permission.Public, - Permission.User, - Permission.CurrentUser, - Permission.ProjectOwner, - ], - _type: 'UserGlobalAccessPermission', - }; + const accessPermission: UserGlobalAccessPermission = + json as UserGlobalAccessPermission; + + accessPermission._type = "UserGlobalAccessPermission"; + + return accessPermission; + } + + public async refreshUserTenantAccessPermission( + userId: ObjectID, + projectId: ObjectID, + ): Promise<UserTenantAccessPermission | null> { + // query for all projects user belongs to. + const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ + query: { + userId: userId, + projectId: projectId, + hasAcceptedInvitation: true, + }, + select: { + teamId: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + const teamIds: Array<ObjectID> = teamMembers.map( + (teamMember: TeamMember) => { + return teamMember.teamId!; + }, + ); + + if (teamIds.length === 0) { + return null; } - public async getMasterApiTenantAccessPermission( - projectId: ObjectID - ): Promise<UserTenantAccessPermission> { - const userPermissions: Array<UserPermission> = []; + // get team permissions. + const teamPermissions: Array<TeamPermission> = + await TeamPermissionService.findBy({ + query: { + teamId: QueryHelper.any(teamIds), + }, + select: { + permission: true, + labels: { + _id: true, + }, + isBlockPermission: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); - userPermissions.push({ - permission: Permission.ProjectOwner, - labelIds: [], - _type: 'UserPermission', - }); + const userPermissions: Array<UserPermission> = []; - const permission: UserTenantAccessPermission = - this.getDefaultUserTenantAccessPermission(projectId); + for (const teamPermission of teamPermissions) { + if (!teamPermission.labels) { + teamPermission.labels = []; + } - permission.permissions = permission.permissions.concat(userPermissions); - - return permission; + userPermissions.push({ + permission: teamPermission.permission!, + labelIds: teamPermission.labels.map((label: Label) => { + return label.id!; + }), + isBlockPermission: teamPermission.isBlockPermission, + _type: "UserPermission", + }); } - public async getApiTenantAccessPermission( - projectId: ObjectID, - apiKeyId: ObjectID - ): Promise<UserTenantAccessPermission> { - // get team permissions. - const apiKeyPermission: Array<APIKeyPermission> = - await ApiKeyPermissionService.findBy({ - query: { - apiKeyId: apiKeyId, - }, - select: { - permission: true, - labels: { - _id: true, - }, - isBlockPermission: true, - }, + const permission: UserTenantAccessPermission = + this.getDefaultUserTenantAccessPermission(projectId); - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); + permission.permissions = permission.permissions.concat(userPermissions); - const userPermissions: Array<UserPermission> = []; + await GlobalCache.setJSON( + PermissionNamespace.ProjectPermission, + userId.toString() + projectId.toString(), + permission, + ); - for (const apiPermission of apiKeyPermission) { - if (!apiPermission.labels) { - apiPermission.labels = []; - } + return permission; + } - userPermissions.push({ - permission: apiPermission.permission!, - labelIds: apiPermission.labels.map((label: Label) => { - return label.id!; - }), - isBlockPermission: apiPermission.isBlockPermission, - _type: 'UserPermission', - }); - } + public async getUserTenantAccessPermission( + userId: ObjectID, + projectId: ObjectID, + ): Promise<UserTenantAccessPermission | null> { + const json: UserTenantAccessPermission | null = + (await GlobalCache.getJSONObject( + PermissionNamespace.ProjectPermission, + userId.toString() + projectId.toString(), + )) as UserTenantAccessPermission; - const permission: UserTenantAccessPermission = - this.getDefaultUserTenantAccessPermission(projectId); - - permission.permissions = permission.permissions.concat(userPermissions); - - return permission; + if (json) { + json._type = "UserTenantAccessPermission"; } - public async refreshUserGlobalAccessPermission( - userId: ObjectID - ): Promise<UserGlobalAccessPermission> { - // query for all projects user belongs to. - const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ - query: { - userId: userId, - hasAcceptedInvitation: true, - }, - select: { - projectId: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - const projectIds: Array<ObjectID> = teamMembers.map( - (teamMember: TeamMember) => { - return teamMember.projectId!; - } - ); - - const permissionToStore: UserGlobalAccessPermission = { - projectIds, - globalPermissions: [ - Permission.Public, - Permission.User, - Permission.CurrentUser, - ], - _type: 'UserGlobalAccessPermission', - }; - - await GlobalCache.setJSON('user', userId.toString(), permissionToStore); - - return permissionToStore; + if (!json) { + return await this.refreshUserTenantAccessPermission(userId, projectId); } - public getDefaultUserTenantAccessPermission( - projectId: ObjectID - ): UserTenantAccessPermission { - const userPermissions: Array<UserPermission> = []; - - userPermissions.push({ - permission: Permission.CurrentUser, - labelIds: [], - isBlockPermission: false, - _type: 'UserPermission', - }); - - userPermissions.push({ - permission: Permission.UnAuthorizedSsoUser, - labelIds: [], - isBlockPermission: false, - _type: 'UserPermission', - }); - - const permission: UserTenantAccessPermission = { - projectId, - permissions: userPermissions, - isBlockPermission: false, - _type: 'UserTenantAccessPermission', - }; - - return permission; - } - - public async getUserGlobalAccessPermission( - userId: ObjectID - ): Promise<UserGlobalAccessPermission | null> { - const json: JSONObject | null = await GlobalCache.getJSONObject( - 'user', - userId.toString() - ); - - if (!json) { - return await this.refreshUserGlobalAccessPermission(userId); - } - - const accessPermission: UserGlobalAccessPermission = - json as UserGlobalAccessPermission; - - accessPermission._type = 'UserGlobalAccessPermission'; - - return accessPermission; - } - - public async refreshUserTenantAccessPermission( - userId: ObjectID, - projectId: ObjectID - ): Promise<UserTenantAccessPermission | null> { - // query for all projects user belongs to. - const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ - query: { - userId: userId, - projectId: projectId, - hasAcceptedInvitation: true, - }, - select: { - teamId: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - const teamIds: Array<ObjectID> = teamMembers.map( - (teamMember: TeamMember) => { - return teamMember.teamId!; - } - ); - - if (teamIds.length === 0) { - return null; - } - - // get team permissions. - const teamPermissions: Array<TeamPermission> = - await TeamPermissionService.findBy({ - query: { - teamId: QueryHelper.any(teamIds), - }, - select: { - permission: true, - labels: { - _id: true, - }, - isBlockPermission: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - const userPermissions: Array<UserPermission> = []; - - for (const teamPermission of teamPermissions) { - if (!teamPermission.labels) { - teamPermission.labels = []; - } - - userPermissions.push({ - permission: teamPermission.permission!, - labelIds: teamPermission.labels.map((label: Label) => { - return label.id!; - }), - isBlockPermission: teamPermission.isBlockPermission, - _type: 'UserPermission', - }); - } - - const permission: UserTenantAccessPermission = - this.getDefaultUserTenantAccessPermission(projectId); - - permission.permissions = permission.permissions.concat(userPermissions); - - await GlobalCache.setJSON( - PermissionNamespace.ProjectPermission, - userId.toString() + projectId.toString(), - permission - ); - - return permission; - } - - public async getUserTenantAccessPermission( - userId: ObjectID, - projectId: ObjectID - ): Promise<UserTenantAccessPermission | null> { - const json: UserTenantAccessPermission | null = - (await GlobalCache.getJSONObject( - PermissionNamespace.ProjectPermission, - userId.toString() + projectId.toString() - )) as UserTenantAccessPermission; - - if (json) { - json._type = 'UserTenantAccessPermission'; - } - - if (!json) { - return await this.refreshUserTenantAccessPermission( - userId, - projectId - ); - } - - return json; - } + return json; + } } export default new AccessTokenService(); diff --git a/CommonServer/Services/AcmeCertificateService.ts b/CommonServer/Services/AcmeCertificateService.ts index 38579dc400..e0367400e4 100644 --- a/CommonServer/Services/AcmeCertificateService.ts +++ b/CommonServer/Services/AcmeCertificateService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/AcmeCertificate'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/AcmeCertificate"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/AcmeChallengeService.ts b/CommonServer/Services/AcmeChallengeService.ts index 3e51634762..afa0c74852 100644 --- a/CommonServer/Services/AcmeChallengeService.ts +++ b/CommonServer/Services/AcmeChallengeService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/AcmeChallenge'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/AcmeChallenge"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/AnalyticsDatabaseService.ts b/CommonServer/Services/AnalyticsDatabaseService.ts index 740b2fe8cf..fdd248380f 100644 --- a/CommonServer/Services/AnalyticsDatabaseService.ts +++ b/CommonServer/Services/AnalyticsDatabaseService.ts @@ -1,345 +1,331 @@ -import { AppApiHostname } from '../EnvironmentConfig'; +import { AppApiHostname } from "../EnvironmentConfig"; import ClickhouseDatabase, { - ClickhouseAppInstance, - ClickhouseClient, -} from '../Infrastructure/ClickhouseDatabase'; -import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization'; -import CountBy from '../Types/AnalyticsDatabase/CountBy'; -import CreateBy from '../Types/AnalyticsDatabase/CreateBy'; -import CreateManyBy from '../Types/AnalyticsDatabase/CreateManyBy'; -import DeleteBy from '../Types/AnalyticsDatabase/DeleteBy'; -import FindBy from '../Types/AnalyticsDatabase/FindBy'; -import FindOneBy from '../Types/AnalyticsDatabase/FindOneBy'; -import FindOneByID from '../Types/AnalyticsDatabase/FindOneByID'; + ClickhouseAppInstance, + ClickhouseClient, +} from "../Infrastructure/ClickhouseDatabase"; +import ClusterKeyAuthorization from "../Middleware/ClusterKeyAuthorization"; +import CountBy from "../Types/AnalyticsDatabase/CountBy"; +import CreateBy from "../Types/AnalyticsDatabase/CreateBy"; +import CreateManyBy from "../Types/AnalyticsDatabase/CreateManyBy"; +import DeleteBy from "../Types/AnalyticsDatabase/DeleteBy"; +import FindBy from "../Types/AnalyticsDatabase/FindBy"; +import FindOneBy from "../Types/AnalyticsDatabase/FindOneBy"; +import FindOneByID from "../Types/AnalyticsDatabase/FindOneByID"; import { - DatabaseTriggerType, - OnCreate, - OnDelete, - OnFind, - OnUpdate, -} from '../Types/AnalyticsDatabase/Hooks'; + DatabaseTriggerType, + OnCreate, + OnDelete, + OnFind, + OnUpdate, +} from "../Types/AnalyticsDatabase/Hooks"; import ModelPermission, { - CheckReadPermissionType, -} from '../Types/AnalyticsDatabase/ModelPermission'; -import Select from '../Types/AnalyticsDatabase/Select'; -import UpdateBy from '../Types/AnalyticsDatabase/UpdateBy'; -import { SQL, Statement } from '../Utils/AnalyticsDatabase/Statement'; -import StatementGenerator from '../Utils/AnalyticsDatabase/StatementGenerator'; -import logger from '../Utils/Logger'; -import Realtime from '../Utils/Realtime'; -import StreamUtil from '../Utils/Stream'; -import BaseService from './BaseService'; -import { ExecResult } from '@clickhouse/client'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import { WorkflowRoute } from 'Common/ServiceRoute'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Exception from 'Common/Types/Exception/Exception'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Text from 'Common/Types/Text'; -import Typeof from 'Common/Types/Typeof'; -import API from 'Common/Utils/API'; -import { ModelEventType } from 'Common/Utils/Realtime'; -import { Stream } from 'node:stream'; + CheckReadPermissionType, +} from "../Types/AnalyticsDatabase/ModelPermission"; +import Select from "../Types/AnalyticsDatabase/Select"; +import UpdateBy from "../Types/AnalyticsDatabase/UpdateBy"; +import { SQL, Statement } from "../Utils/AnalyticsDatabase/Statement"; +import StatementGenerator from "../Utils/AnalyticsDatabase/StatementGenerator"; +import logger from "../Utils/Logger"; +import Realtime from "../Utils/Realtime"; +import StreamUtil from "../Utils/Stream"; +import BaseService from "./BaseService"; +import { ExecResult } from "@clickhouse/client"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import { WorkflowRoute } from "Common/ServiceRoute"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Exception from "Common/Types/Exception/Exception"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Text from "Common/Types/Text"; +import Typeof from "Common/Types/Typeof"; +import API from "Common/Utils/API"; +import { ModelEventType } from "Common/Utils/Realtime"; +import { Stream } from "node:stream"; export default class AnalyticsDatabaseService< - TBaseModel extends AnalyticsBaseModel + TBaseModel extends AnalyticsBaseModel, > extends BaseService { - public modelType!: { new (): TBaseModel }; - public database!: ClickhouseDatabase; - public model!: TBaseModel; - public databaseClient!: ClickhouseClient; - public statementGenerator!: StatementGenerator<TBaseModel>; + public modelType!: { new (): TBaseModel }; + public database!: ClickhouseDatabase; + public model!: TBaseModel; + public databaseClient!: ClickhouseClient; + public statementGenerator!: StatementGenerator<TBaseModel>; - public constructor(data: { - modelType: { new (): TBaseModel }; - database?: ClickhouseDatabase | undefined; - }) { - super(); - this.modelType = data.modelType; - this.model = new this.modelType(); - if (data.database) { - this.database = data.database; // used for testing. - } else { - this.database = ClickhouseAppInstance; // default database - } - - this.databaseClient = this.database.getDataSource() as ClickhouseClient; - - this.statementGenerator = new StatementGenerator<TBaseModel>({ - modelType: this.modelType, - database: this.database, - }); + public constructor(data: { + modelType: { new (): TBaseModel }; + database?: ClickhouseDatabase | undefined; + }) { + super(); + this.modelType = data.modelType; + this.model = new this.modelType(); + if (data.database) { + this.database = data.database; // used for testing. + } else { + this.database = ClickhouseAppInstance; // default database } - public async doesColumnExistInDatabase( - columnName: string - ): Promise<boolean> { - const statement: string = - this.statementGenerator.toDoesColumnExistStatement(columnName); + this.databaseClient = this.database.getDataSource() as ClickhouseClient; - const dbResult: ExecResult<Stream> = await this.execute(statement); + this.statementGenerator = new StatementGenerator<TBaseModel>({ + modelType: this.modelType, + database: this.database, + }); + } - const strResult: string = await StreamUtil.convertStreamToText( - dbResult.stream + public async doesColumnExistInDatabase(columnName: string): Promise<boolean> { + const statement: string = + this.statementGenerator.toDoesColumnExistStatement(columnName); + + const dbResult: ExecResult<Stream> = await this.execute(statement); + + const strResult: string = await StreamUtil.convertStreamToText( + dbResult.stream, + ); + + return strResult.trim().length > 0; + } + + public async getColumnTypeInDatabase( + column: AnalyticsTableColumn, + ): Promise<TableColumnType | null> { + if (!column) { + return null; + } + + const columnName: string = column.key; + + if (!this.doesColumnExistInDatabase(columnName)) { + return null; + } + + const statement: string = + this.statementGenerator.getColumnTypesStatement(columnName); + + const dbResult: ExecResult<Stream> = await this.execute(statement); + + let strResult: string = await StreamUtil.convertStreamToText( + dbResult.stream, + ); + + // if strResult includes Nullable(type) then extract type. + + if (strResult.includes("Nullable")) { + let type: string = strResult.split("Nullable(")[1] as string; + type = type.split(")")[0] as string; + strResult = type; + } + + return ( + (this.statementGenerator.toTableColumnType( + strResult.trim(), + ) as TableColumnType) || null + ); + } + + public async countBy(countBy: CountBy<TBaseModel>): Promise<PositiveNumber> { + try { + const checkReadPermissionType: CheckReadPermissionType<TBaseModel> = + await ModelPermission.checkReadPermission( + this.modelType, + countBy.query, + null, + countBy.props, ); - return strResult.trim().length > 0; + countBy.query = checkReadPermissionType.query; + + const countStatement: Statement = this.toCountStatement(countBy); + + const dbResult: ExecResult<Stream> = await this.execute(countStatement); + + const strResult: string = await StreamUtil.convertStreamToText( + dbResult.stream, + ); + + let countPositive: PositiveNumber = new PositiveNumber(strResult); + + if (countBy.groupBy && Object.keys(countBy.groupBy).length > 0) { + // this usually happens when group by is used. In this case we count the total number of groups and not rows in those groups. + countPositive = new PositiveNumber(strResult.split("\n").length - 1); // -1 because the last line is empty. + } + + countPositive = await this.onCountSuccess(countPositive); + return countPositive; + } catch (error) { + await this.onCountError(error as Exception); + throw this.getException(error as Exception); } + } - public async getColumnTypeInDatabase( - column: AnalyticsTableColumn - ): Promise<TableColumnType | null> { - if (!column) { - return null; + public async addColumnInDatabase( + column: AnalyticsTableColumn, + ): Promise<void> { + const statement: Statement = + this.statementGenerator.toAddColumnStatement(column); + await this.execute(statement); + } + + public async dropColumnInDatabase(columnName: string): Promise<void> { + await this.execute( + this.statementGenerator.toDropColumnStatement(columnName), + ); + } + + public async findBy(findBy: FindBy<TBaseModel>): Promise<Array<TBaseModel>> { + return await this._findBy(findBy); + } + + private async _findBy( + findBy: FindBy<TBaseModel>, + ): Promise<Array<TBaseModel>> { + try { + if (!findBy.sort || Object.keys(findBy.sort).length === 0) { + findBy.sort = { + createdAt: SortOrder.Descending, + }; + + if (!findBy.select) { + findBy.select = {} as any; } + } - const columnName: string = column.key; + const onFind: OnFind<TBaseModel> = findBy.props.ignoreHooks + ? { findBy, carryForward: [] } + : await this.onBeforeFind(findBy); + const onBeforeFind: FindBy<TBaseModel> = { ...onFind.findBy }; + const carryForward: any = onFind.carryForward; - if (!this.doesColumnExistInDatabase(columnName)) { - return null; - } + if ( + !onBeforeFind.select || + Object.keys(onBeforeFind.select).length === 0 + ) { + onBeforeFind.select = {} as any; + } - const statement: string = - this.statementGenerator.getColumnTypesStatement(columnName); + if (!(onBeforeFind.select as any)["_id"]) { + (onBeforeFind.select as any)["_id"] = true; + } - const dbResult: ExecResult<Stream> = await this.execute(statement); - - let strResult: string = await StreamUtil.convertStreamToText( - dbResult.stream + const result: CheckReadPermissionType<TBaseModel> = + await ModelPermission.checkReadPermission( + this.modelType, + onBeforeFind.query, + onBeforeFind.select || null, + onBeforeFind.props, ); - // if strResult includes Nullable(type) then extract type. + onBeforeFind.query = result.query; + onBeforeFind.select = result.select || undefined; - if (strResult.includes('Nullable')) { - let type: string = strResult.split('Nullable(')[1] as string; - type = type.split(')')[0] as string; - strResult = type; - } + if (!(onBeforeFind.skip instanceof PositiveNumber)) { + onBeforeFind.skip = new PositiveNumber(onBeforeFind.skip); + } - return ( - (this.statementGenerator.toTableColumnType( - strResult.trim() - ) as TableColumnType) || null - ); + if (!(onBeforeFind.limit instanceof PositiveNumber)) { + onBeforeFind.limit = new PositiveNumber(onBeforeFind.limit); + } + + const findStatement: { + statement: Statement; + columns: Array<string>; + } = this.toFindStatement(onBeforeFind); + + const dbResult: ExecResult<Stream> = await this.execute( + findStatement.statement, + ); + + const strResult: string = await StreamUtil.convertStreamToText( + dbResult.stream, + ); + + const jsonItems: Array<JSONObject> = this.convertSelectReturnedDataToJson( + strResult, + findStatement.columns, + ); + + let items: Array<TBaseModel> = + AnalyticsBaseModel.fromJSONArray<TBaseModel>(jsonItems, this.modelType); + + if (!findBy.props.ignoreHooks) { + items = await ( + await this.onFindSuccess({ findBy, carryForward }, items) + ).carryForward; + } + + return items; + } catch (error) { + await this.onFindError(error as Exception); + throw this.getException(error as Exception); + } + } + + private convertSelectReturnedDataToJson( + strResult: string, + columns: string[], + ): JSONObject[] { + const jsonItems: Array<JSONObject> = []; + + const rows: Array<string> = strResult.split("\n"); + + for (const row of rows) { + if (!row) { + continue; + } + + const jsonItem: JSONObject = {}; + const values: Array<string> = row.split("\t"); + + for (let i: number = 0; i < columns.length; i++) { + jsonItem[columns[i]!] = values[i]; + } + + jsonItems.push(jsonItem); } - public async countBy( - countBy: CountBy<TBaseModel> - ): Promise<PositiveNumber> { - try { - const checkReadPermissionType: CheckReadPermissionType<TBaseModel> = - await ModelPermission.checkReadPermission( - this.modelType, - countBy.query, - null, - countBy.props - ); + return jsonItems; + } - countBy.query = checkReadPermissionType.query; + protected async onBeforeDelete( + deleteBy: DeleteBy<TBaseModel>, + ): Promise<OnDelete<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ deleteBy, carryForward: null }); + } - const countStatement: Statement = this.toCountStatement(countBy); + protected async onBeforeUpdate( + updateBy: UpdateBy<TBaseModel>, + ): Promise<OnUpdate<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ updateBy, carryForward: null }); + } - const dbResult: ExecResult<Stream> = await this.execute( - countStatement - ); + protected async onBeforeFind( + findBy: FindBy<TBaseModel>, + ): Promise<OnFind<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ findBy, carryForward: null }); + } - const strResult: string = await StreamUtil.convertStreamToText( - dbResult.stream - ); - - let countPositive: PositiveNumber = new PositiveNumber(strResult); - - if (countBy.groupBy && Object.keys(countBy.groupBy).length > 0) { - // this usually happens when group by is used. In this case we count the total number of groups and not rows in those groups. - countPositive = new PositiveNumber( - strResult.split('\n').length - 1 - ); // -1 because the last line is empty. - } - - countPositive = await this.onCountSuccess(countPositive); - return countPositive; - } catch (error) { - await this.onCountError(error as Exception); - throw this.getException(error as Exception); - } + public toCountStatement(countBy: CountBy<TBaseModel>): Statement { + if (!this.database) { + this.useDefaultDatabase(); } - public async addColumnInDatabase( - column: AnalyticsTableColumn - ): Promise<void> { - const statement: Statement = - this.statementGenerator.toAddColumnStatement(column); - await this.execute(statement); - } + const databaseName: string = this.database.getDatasourceOptions().database!; - public async dropColumnInDatabase(columnName: string): Promise<void> { - await this.execute( - this.statementGenerator.toDropColumnStatement(columnName) - ); - } + const whereStatement: Statement = this.statementGenerator.toWhereStatement( + countBy.query, + ); - public async findBy( - findBy: FindBy<TBaseModel> - ): Promise<Array<TBaseModel>> { - return await this._findBy(findBy); - } - - private async _findBy( - findBy: FindBy<TBaseModel> - ): Promise<Array<TBaseModel>> { - try { - if (!findBy.sort || Object.keys(findBy.sort).length === 0) { - findBy.sort = { - createdAt: SortOrder.Descending, - }; - - if (!findBy.select) { - findBy.select = {} as any; - } - } - - const onFind: OnFind<TBaseModel> = findBy.props.ignoreHooks - ? { findBy, carryForward: [] } - : await this.onBeforeFind(findBy); - const onBeforeFind: FindBy<TBaseModel> = { ...onFind.findBy }; - const carryForward: any = onFind.carryForward; - - if ( - !onBeforeFind.select || - Object.keys(onBeforeFind.select).length === 0 - ) { - onBeforeFind.select = {} as any; - } - - if (!(onBeforeFind.select as any)['_id']) { - (onBeforeFind.select as any)['_id'] = true; - } - - const result: CheckReadPermissionType<TBaseModel> = - await ModelPermission.checkReadPermission( - this.modelType, - onBeforeFind.query, - onBeforeFind.select || null, - onBeforeFind.props - ); - - onBeforeFind.query = result.query; - onBeforeFind.select = result.select || undefined; - - if (!(onBeforeFind.skip instanceof PositiveNumber)) { - onBeforeFind.skip = new PositiveNumber(onBeforeFind.skip); - } - - if (!(onBeforeFind.limit instanceof PositiveNumber)) { - onBeforeFind.limit = new PositiveNumber(onBeforeFind.limit); - } - - const findStatement: { - statement: Statement; - columns: Array<string>; - } = this.toFindStatement(onBeforeFind); - - const dbResult: ExecResult<Stream> = await this.execute( - findStatement.statement - ); - - const strResult: string = await StreamUtil.convertStreamToText( - dbResult.stream - ); - - const jsonItems: Array<JSONObject> = - this.convertSelectReturnedDataToJson( - strResult, - findStatement.columns - ); - - let items: Array<TBaseModel> = - AnalyticsBaseModel.fromJSONArray<TBaseModel>( - jsonItems, - this.modelType - ); - - if (!findBy.props.ignoreHooks) { - items = await ( - await this.onFindSuccess({ findBy, carryForward }, items) - ).carryForward; - } - - return items; - } catch (error) { - await this.onFindError(error as Exception); - throw this.getException(error as Exception); - } - } - - private convertSelectReturnedDataToJson( - strResult: string, - columns: string[] - ): JSONObject[] { - const jsonItems: Array<JSONObject> = []; - - const rows: Array<string> = strResult.split('\n'); - - for (const row of rows) { - if (!row) { - continue; - } - - const jsonItem: JSONObject = {}; - const values: Array<string> = row.split('\t'); - - for (let i: number = 0; i < columns.length; i++) { - jsonItem[columns[i]!] = values[i]; - } - - jsonItems.push(jsonItem); - } - - return jsonItems; - } - - protected async onBeforeDelete( - deleteBy: DeleteBy<TBaseModel> - ): Promise<OnDelete<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ deleteBy, carryForward: null }); - } - - protected async onBeforeUpdate( - updateBy: UpdateBy<TBaseModel> - ): Promise<OnUpdate<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ updateBy, carryForward: null }); - } - - protected async onBeforeFind( - findBy: FindBy<TBaseModel> - ): Promise<OnFind<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ findBy, carryForward: null }); - } - - public toCountStatement(countBy: CountBy<TBaseModel>): Statement { - if (!this.database) { - this.useDefaultDatabase(); - } - - const databaseName: string = - this.database.getDatasourceOptions().database!; - - const whereStatement: Statement = - this.statementGenerator.toWhereStatement(countBy.query); - - /* eslint-disable prettier/prettier */ + /* eslint-disable prettier/prettier */ const statement: Statement = SQL` SELECT count() @@ -347,603 +333,600 @@ export default class AnalyticsDatabaseService< WHERE TRUE `.append(whereStatement); /* eslint-enable prettier/prettier */ - if (countBy.groupBy && Object.keys(countBy.groupBy).length > 0) { - statement.append( - SQL` + if (countBy.groupBy && Object.keys(countBy.groupBy).length > 0) { + statement.append( + SQL` GROUP BY `.append( - this.statementGenerator.toGroupByStatement(countBy.groupBy) - ) - ); - } - - if (countBy.limit) { - statement.append(SQL` - LIMIT ${{ - value: Number(countBy.limit), - type: TableColumnType.Number, - }} - `); - } - - if (countBy.skip) { - statement.append(SQL` - OFFSET ${{ - value: Number(countBy.skip), - type: TableColumnType.Number, - }} - `); - } - logger.debug(`${this.model.tableName} Count Statement`); - logger.debug(statement); - - return statement; + this.statementGenerator.toGroupByStatement(countBy.groupBy), + ), + ); } - public toFindStatement(findBy: FindBy<TBaseModel>): { - statement: Statement; - columns: Array<string>; - } { - if (!this.database) { - this.useDefaultDatabase(); - } + if (countBy.limit) { + statement.append(SQL` + LIMIT ${{ + value: Number(countBy.limit), + type: TableColumnType.Number, + }} + `); + } - const databaseName: string = - this.database.getDatasourceOptions().database!; - let groupByStatement: Statement | null = null; + if (countBy.skip) { + statement.append(SQL` + OFFSET ${{ + value: Number(countBy.skip), + type: TableColumnType.Number, + }} + `); + } + logger.debug(`${this.model.tableName} Count Statement`); + logger.debug(statement); - if (findBy.groupBy && Object.keys(findBy.groupBy).length > 0) { - // overwrite select object - findBy.select = { - ...findBy.groupBy, - }; + return statement; + } - groupByStatement = this.statementGenerator.toGroupByStatement( - findBy.groupBy - ); - } + public toFindStatement(findBy: FindBy<TBaseModel>): { + statement: Statement; + columns: Array<string>; + } { + if (!this.database) { + this.useDefaultDatabase(); + } - const select: { statement: Statement; columns: Array<string> } = - this.statementGenerator.toSelectStatement(findBy.select!); + const databaseName: string = this.database.getDatasourceOptions().database!; + let groupByStatement: Statement | null = null; - const whereStatement: Statement = - this.statementGenerator.toWhereStatement(findBy.query); + if (findBy.groupBy && Object.keys(findBy.groupBy).length > 0) { + // overwrite select object + findBy.select = { + ...findBy.groupBy, + }; - const sortStatement: Statement = - this.statementGenerator.toSortStatement(findBy.sort!); + groupByStatement = this.statementGenerator.toGroupByStatement( + findBy.groupBy, + ); + } - const statement: Statement = SQL``; + const select: { statement: Statement; columns: Array<string> } = + this.statementGenerator.toSelectStatement(findBy.select!); - statement.append(SQL`SELECT `.append(select.statement)); - statement.append(SQL` FROM ${databaseName}.${this.model.tableName}`); - statement.append(SQL` WHERE TRUE `).append(whereStatement); + const whereStatement: Statement = this.statementGenerator.toWhereStatement( + findBy.query, + ); - if (groupByStatement) { - statement.append(SQL` GROUP BY `).append(groupByStatement); - } + const sortStatement: Statement = this.statementGenerator.toSortStatement( + findBy.sort!, + ); - statement.append(SQL` ORDER BY `).append(sortStatement); + const statement: Statement = SQL``; - statement.append( - SQL` LIMIT ${{ - value: Number(findBy.limit), - type: TableColumnType.Number, - }}` - ); + statement.append(SQL`SELECT `.append(select.statement)); + statement.append(SQL` FROM ${databaseName}.${this.model.tableName}`); + statement.append(SQL` WHERE TRUE `).append(whereStatement); - statement.append(SQL` OFFSET ${{ - value: Number(findBy.skip), - type: TableColumnType.Number, - }} + if (groupByStatement) { + statement.append(SQL` GROUP BY `).append(groupByStatement); + } + + statement.append(SQL` ORDER BY `).append(sortStatement); + + statement.append( + SQL` LIMIT ${{ + value: Number(findBy.limit), + type: TableColumnType.Number, + }}`, + ); + + statement.append(SQL` OFFSET ${{ + value: Number(findBy.skip), + type: TableColumnType.Number, + }} `); - /* eslint-enable prettier/prettier */ + /* eslint-enable prettier/prettier */ - logger.debug(`${this.model.tableName} Find Statement`); - logger.debug(statement); + logger.debug(`${this.model.tableName} Find Statement`); + logger.debug(statement); - return { statement, columns: select.columns }; + return { statement, columns: select.columns }; + } + + public toDeleteStatement(deleteBy: DeleteBy<TBaseModel>): Statement { + if (!this.database) { + this.useDefaultDatabase(); } - public toDeleteStatement(deleteBy: DeleteBy<TBaseModel>): Statement { - if (!this.database) { - this.useDefaultDatabase(); - } + const databaseName: string = this.database.getDatasourceOptions().database!; + const whereStatement: Statement = this.statementGenerator.toWhereStatement( + deleteBy.query, + ); - const databaseName: string = - this.database.getDatasourceOptions().database!; - const whereStatement: Statement = - this.statementGenerator.toWhereStatement(deleteBy.query); - - /* eslint-disable prettier/prettier */ + /* eslint-disable prettier/prettier */ const statement: Statement = SQL` ALTER TABLE ${databaseName}.${this.model.tableName} DELETE WHERE TRUE `.append(whereStatement); /* eslint-enable prettier/prettier */ - logger.debug(`${this.model.tableName} Delete Statement`); - logger.debug(statement); + logger.debug(`${this.model.tableName} Delete Statement`); + logger.debug(statement); - return statement; + return statement; + } + + public async findOneBy( + findOneBy: FindOneBy<TBaseModel>, + ): Promise<TBaseModel | null> { + const findBy: FindBy<TBaseModel> = findOneBy as FindBy<TBaseModel>; + findBy.limit = new PositiveNumber(1); + findBy.skip = new PositiveNumber(0); + + const documents: Array<TBaseModel> = await this._findBy(findBy); + + if (documents && documents[0]) { + return documents[0]; + } + return null; + } + + public async deleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<void> { + return await this._deleteBy(deleteBy); + } + + private async _deleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<void> { + try { + const onDelete: OnDelete<TBaseModel> = deleteBy.props.ignoreHooks + ? { deleteBy, carryForward: [] } + : await this.onBeforeDelete(deleteBy); + + const beforeDeleteBy: DeleteBy<TBaseModel> = onDelete.deleteBy; + + beforeDeleteBy.query = await ModelPermission.checkDeletePermission( + this.modelType, + beforeDeleteBy.query, + deleteBy.props, + ); + + const select: Select<TBaseModel> = {}; + + const tenantColumnName: string | null = + this.getModel().getTenantColumn()?.key || null; + + if (tenantColumnName) { + (select as any)[tenantColumnName] = true; + } + + await this.execute(this.toDeleteStatement(beforeDeleteBy)); + } catch (error) { + await this.onDeleteError(error as Exception); + throw this.getException(error as Exception); + } + } + + public async findOneById( + findOneById: FindOneByID<TBaseModel>, + ): Promise<TBaseModel | null> { + if (!findOneById.id) { + throw new BadDataException("findOneById.id is required"); } - public async findOneBy( - findOneBy: FindOneBy<TBaseModel> - ): Promise<TBaseModel | null> { - const findBy: FindBy<TBaseModel> = findOneBy as FindBy<TBaseModel>; - findBy.limit = new PositiveNumber(1); - findBy.skip = new PositiveNumber(0); + return await this.findOneBy({ + query: { + _id: findOneById.id, + }, + select: findOneById.select || {}, + props: findOneById.props, + }); + } - const documents: Array<TBaseModel> = await this._findBy(findBy); + public async updateBy(updateBy: UpdateBy<TBaseModel>): Promise<void> { + await this._updateBy(updateBy); + } - if (documents && documents[0]) { - return documents[0]; - } - return null; + private async _updateBy(updateBy: UpdateBy<TBaseModel>): Promise<void> { + try { + const onUpdate: OnUpdate<TBaseModel> = updateBy.props.ignoreHooks + ? { updateBy, carryForward: [] } + : await this.onBeforeUpdate(updateBy); + + const beforeUpdateBy: UpdateBy<TBaseModel> = onUpdate.updateBy; + + beforeUpdateBy.query = await ModelPermission.checkUpdatePermissions( + this.modelType, + beforeUpdateBy.query, + beforeUpdateBy.data, + beforeUpdateBy.props, + ); + + const select: Select<TBaseModel> = {}; + + const tenantColumnName: string | null = + this.getModel().getTenantColumn()?.key || null; + + if (tenantColumnName) { + (select as any)[tenantColumnName] = true; + } + + await this.execute( + this.statementGenerator.toUpdateStatement(beforeUpdateBy), + ); + } catch (error) { + await this.onUpdateError(error as Exception); + throw this.getException(error as Exception); + } + } + + protected generateDefaultValues(data: TBaseModel): TBaseModel { + const tableColumns: Array<AnalyticsTableColumn> = data.getTableColumns(); + + for (const column of tableColumns) { + if (column.forceGetDefaultValueOnCreate) { + data.setColumnValue(column.key, column.forceGetDefaultValueOnCreate()); + } } - public async deleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<void> { - return await this._deleteBy(deleteBy); + return data; + } + + public useDefaultDatabase(): void { + this.database = ClickhouseAppInstance; + this.databaseClient = this.database.getDataSource() as ClickhouseClient; + } + + public async execute( + statement: Statement | string, + ): Promise<ExecResult<Stream>> { + if (!this.databaseClient) { + this.useDefaultDatabase(); } - private async _deleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<void> { - try { - const onDelete: OnDelete<TBaseModel> = deleteBy.props.ignoreHooks - ? { deleteBy, carryForward: [] } - : await this.onBeforeDelete(deleteBy); + return await this.databaseClient.exec( + statement instanceof Statement + ? statement + : { + query: statement, // TODO remove and only accept Statements + }, + ); + } - const beforeDeleteBy: DeleteBy<TBaseModel> = onDelete.deleteBy; + protected async onUpdateSuccess( + onUpdate: OnUpdate<TBaseModel>, + _updatedItemIds: Array<ObjectID>, + ): Promise<OnUpdate<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve(onUpdate); + } - beforeDeleteBy.query = await ModelPermission.checkDeletePermission( - this.modelType, - beforeDeleteBy.query, - deleteBy.props - ); + protected async onUpdateError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } - const select: Select<TBaseModel> = {}; + protected async onDeleteSuccess( + onDelete: OnDelete<TBaseModel>, + _itemIdsBeforeDelete: Array<ObjectID>, + ): Promise<OnDelete<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve(onDelete); + } - const tenantColumnName: string | null = - this.getModel().getTenantColumn()?.key || null; + protected async onDeleteError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } - if (tenantColumnName) { - (select as any)[tenantColumnName] = true; - } + protected async onFindSuccess( + onFind: OnFind<TBaseModel>, + items: Array<TBaseModel>, + ): Promise<OnFind<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ ...onFind, carryForward: items }); + } - await this.execute(this.toDeleteStatement(beforeDeleteBy)); - } catch (error) { - await this.onDeleteError(error as Exception); - throw this.getException(error as Exception); - } + protected async onFindError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } + + protected async onCountSuccess( + count: PositiveNumber, + ): Promise<PositiveNumber> { + // A place holder method used for overriding. + return Promise.resolve(count); + } + + protected async onCountError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } + + protected async onCreateSuccess( + _onCreate: OnCreate<TBaseModel>, + createdItem: TBaseModel, + ): Promise<TBaseModel> { + // A place holder method used for overriding. + return Promise.resolve(createdItem); + } + + protected async onBeforeCreate( + createBy: CreateBy<TBaseModel>, + ): Promise<OnCreate<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ + createBy: createBy as CreateBy<TBaseModel>, + carryForward: undefined, + }); + } + + private async _onBeforeCreate( + createBy: CreateBy<TBaseModel>, + ): Promise<OnCreate<TBaseModel>> { + // Private method that runs before create. + const projectIdColumn: string | null = + this.model.getTenantColumn()?.key || null; + + if (projectIdColumn && createBy.props.tenantId) { + (createBy.data as any)[projectIdColumn] = createBy.props.tenantId; } - public async findOneById( - findOneById: FindOneByID<TBaseModel> - ): Promise<TBaseModel | null> { - if (!findOneById.id) { - throw new BadDataException('findOneById.id is required'); - } + return await this.onBeforeCreate(createBy); + } - return await this.findOneBy({ - query: { - _id: findOneById.id, + public async createMany( + createBy: CreateManyBy<TBaseModel>, + ): Promise<Array<TBaseModel>> { + // add tenantId if present. + const tenantColumnName: string | null = + this.model.getTenantColumn()?.key || null; + + const items: Array<TBaseModel> = []; + const carryForwards: Array<any> = []; + + for (const item of createBy.items) { + let data: TBaseModel = item; + + const onCreate: OnCreate<TBaseModel> = createBy.props.ignoreHooks + ? { + createBy: { + data: data, + props: createBy.props, }, - select: findOneById.select || {}, - props: findOneById.props, - }); - } - - public async updateBy(updateBy: UpdateBy<TBaseModel>): Promise<void> { - await this._updateBy(updateBy); - } - - private async _updateBy(updateBy: UpdateBy<TBaseModel>): Promise<void> { - try { - const onUpdate: OnUpdate<TBaseModel> = updateBy.props.ignoreHooks - ? { updateBy, carryForward: [] } - : await this.onBeforeUpdate(updateBy); - - const beforeUpdateBy: UpdateBy<TBaseModel> = onUpdate.updateBy; - - beforeUpdateBy.query = await ModelPermission.checkUpdatePermissions( - this.modelType, - beforeUpdateBy.query, - beforeUpdateBy.data, - beforeUpdateBy.props - ); - - const select: Select<TBaseModel> = {}; - - const tenantColumnName: string | null = - this.getModel().getTenantColumn()?.key || null; - - if (tenantColumnName) { - (select as any)[tenantColumnName] = true; - } - - await this.execute( - this.statementGenerator.toUpdateStatement(beforeUpdateBy) - ); - } catch (error) { - await this.onUpdateError(error as Exception); - throw this.getException(error as Exception); - } - } - - protected generateDefaultValues(data: TBaseModel): TBaseModel { - const tableColumns: Array<AnalyticsTableColumn> = - data.getTableColumns(); - - for (const column of tableColumns) { - if (column.forceGetDefaultValueOnCreate) { - data.setColumnValue( - column.key, - column.forceGetDefaultValueOnCreate() - ); - } - } - - return data; - } - - public useDefaultDatabase(): void { - this.database = ClickhouseAppInstance; - this.databaseClient = this.database.getDataSource() as ClickhouseClient; - } - - public async execute( - statement: Statement | string - ): Promise<ExecResult<Stream>> { - if (!this.databaseClient) { - this.useDefaultDatabase(); - } - - return await this.databaseClient.exec( - statement instanceof Statement - ? statement - : { - query: statement, // TODO remove and only accept Statements - } - ); - } - - protected async onUpdateSuccess( - onUpdate: OnUpdate<TBaseModel>, - _updatedItemIds: Array<ObjectID> - ): Promise<OnUpdate<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve(onUpdate); - } - - protected async onUpdateError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected async onDeleteSuccess( - onDelete: OnDelete<TBaseModel>, - _itemIdsBeforeDelete: Array<ObjectID> - ): Promise<OnDelete<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve(onDelete); - } - - protected async onDeleteError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected async onFindSuccess( - onFind: OnFind<TBaseModel>, - items: Array<TBaseModel> - ): Promise<OnFind<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ ...onFind, carryForward: items }); - } - - protected async onFindError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected async onCountSuccess( - count: PositiveNumber - ): Promise<PositiveNumber> { - // A place holder method used for overriding. - return Promise.resolve(count); - } - - protected async onCountError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected async onCreateSuccess( - _onCreate: OnCreate<TBaseModel>, - createdItem: TBaseModel - ): Promise<TBaseModel> { - // A place holder method used for overriding. - return Promise.resolve(createdItem); - } - - protected async onBeforeCreate( - createBy: CreateBy<TBaseModel> - ): Promise<OnCreate<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ - createBy: createBy as CreateBy<TBaseModel>, - carryForward: undefined, - }); - } - - private async _onBeforeCreate( - createBy: CreateBy<TBaseModel> - ): Promise<OnCreate<TBaseModel>> { - // Private method that runs before create. - const projectIdColumn: string | null = - this.model.getTenantColumn()?.key || null; - - if (projectIdColumn && createBy.props.tenantId) { - (createBy.data as any)[projectIdColumn] = createBy.props.tenantId; - } - - return await this.onBeforeCreate(createBy); - } - - public async createMany( - createBy: CreateManyBy<TBaseModel> - ): Promise<Array<TBaseModel>> { - // add tenantId if present. - const tenantColumnName: string | null = - this.model.getTenantColumn()?.key || null; - - const items: Array<TBaseModel> = []; - const carryForwards: Array<any> = []; - - for (const item of createBy.items) { - let data: TBaseModel = item; - - const onCreate: OnCreate<TBaseModel> = createBy.props.ignoreHooks - ? { - createBy: { - data: data, - props: createBy.props, - }, - carryForward: [], - } - : await this._onBeforeCreate({ - data: data, - props: createBy.props, - }); - - data = onCreate.createBy.data; - - const carryForward: any = onCreate.carryForward; - - carryForwards.push(carryForward); - - if (tenantColumnName && createBy.props.tenantId) { - data.setColumnValue(tenantColumnName, createBy.props.tenantId); - } - - data = this.sanitizeCreate(data); - data = this.generateDefaultValues(data); - data = this.checkRequiredFields(data); - - if (!this.isValid(data)) { - throw new BadDataException('Data is not valid'); - } - - // check total items by - - ModelPermission.checkCreatePermissions( - this.modelType, - data, - createBy.props - ); - - items.push(data); - } - - try { - const insertStatement: string = - this.statementGenerator.toCreateStatement({ item: items }); - - await this.execute(insertStatement); - - if (!createBy.props.ignoreHooks) { - for (let i: number = 0; i < items.length; i++) { - if (!items[i]) { - continue; - } - - items[i] = await this.onCreateSuccess( - { - createBy: { - data: items[i]!, - props: createBy.props, - }, - carryForward: carryForwards[i], - }, - items[i]! - ); - } - } - - // hit workflow.; - if (this.getModel().enableWorkflowOn?.create) { - let tenantId: ObjectID | undefined = createBy.props.tenantId; - - for (const item of items) { - if (!tenantId && this.getModel().getTenantColumn()) { - tenantId = item.getColumnValue<ObjectID>( - this.getModel().getTenantColumn()!.key - ); - } - - if (tenantId) { - await this.onTrigger(item.id!, tenantId, 'on-create'); - } - } - } - - // emit realtime events to the client. - if ( - this.getModel().enableRealtimeEventsOn?.create && - this.model.getTenantColumn() - ) { - if (Realtime.isInitialized()) { - const promises: Array<Promise<void>> = []; - - for (const item of items) { - const tenantId: ObjectID | null = - item.getTenantColumnValue(); - - if (!tenantId) { - continue; - } - - promises.push( - Realtime.emitModelEvent({ - model: item, - tenantId: tenantId, - eventType: ModelEventType.Create, - modelType: this.modelType, - }) - ); - } - - await Promise.allSettled(promises); - } else { - logger.warn( - `Realtime is not initialized. Skipping emitModelEvent for ${ - this.getModel().tableName - }` - ); - } - } - - return createBy.items; - } catch (error) { - await this.onCreateError(error as Exception); - throw this.getException(error as Exception); - } - } - - public async create(createBy: CreateBy<TBaseModel>): Promise<TBaseModel> { - const items: Array<TBaseModel> = await this.createMany({ + carryForward: [], + } + : await this._onBeforeCreate({ + data: data, props: createBy.props, - items: [createBy.data], - }); + }); - const item: TBaseModel | undefined = items[0]; + data = onCreate.createBy.data; - if (!item) { - throw new BadDataException('Item not created'); + const carryForward: any = onCreate.carryForward; + + carryForwards.push(carryForward); + + if (tenantColumnName && createBy.props.tenantId) { + data.setColumnValue(tenantColumnName, createBy.props.tenantId); + } + + data = this.sanitizeCreate(data); + data = this.generateDefaultValues(data); + data = this.checkRequiredFields(data); + + if (!this.isValid(data)) { + throw new BadDataException("Data is not valid"); + } + + // check total items by + + ModelPermission.checkCreatePermissions( + this.modelType, + data, + createBy.props, + ); + + items.push(data); + } + + try { + const insertStatement: string = this.statementGenerator.toCreateStatement( + { item: items }, + ); + + await this.execute(insertStatement); + + if (!createBy.props.ignoreHooks) { + for (let i: number = 0; i < items.length; i++) { + if (!items[i]) { + continue; + } + + items[i] = await this.onCreateSuccess( + { + createBy: { + data: items[i]!, + props: createBy.props, + }, + carryForward: carryForwards[i], + }, + items[i]!, + ); } + } - return item; - } + // hit workflow.; + if (this.getModel().enableWorkflowOn?.create) { + let tenantId: ObjectID | undefined = createBy.props.tenantId; - private sanitizeCreate<TBaseModel extends AnalyticsBaseModel>( - data: TBaseModel - ): TBaseModel { - if (!data.id) { - data.id = ObjectID.generate(); + for (const item of items) { + if (!tenantId && this.getModel().getTenantColumn()) { + tenantId = item.getColumnValue<ObjectID>( + this.getModel().getTenantColumn()!.key, + ); + } + + if (tenantId) { + await this.onTrigger(item.id!, tenantId, "on-create"); + } } + } - data.createdAt = OneUptimeDate.getCurrentDate(); - data.updatedAt = OneUptimeDate.getCurrentDate(); + // emit realtime events to the client. + if ( + this.getModel().enableRealtimeEventsOn?.create && + this.model.getTenantColumn() + ) { + if (Realtime.isInitialized()) { + const promises: Array<Promise<void>> = []; - return data; - } + for (const item of items) { + const tenantId: ObjectID | null = item.getTenantColumnValue(); - protected async getException(error: Exception): Promise<void> { - throw error; - } - - protected async onCreateError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected isValid(data: TBaseModel): boolean { - if (!data) { - throw new BadDataException('Data cannot be null'); - } - - return true; - } - - public async onTrigger( - id: ObjectID, - projectId: ObjectID, - triggerType: DatabaseTriggerType - ): Promise<void> { - if (this.getModel().enableWorkflowOn) { - API.post( - new URL( - Protocol.HTTP, - AppApiHostname, - new Route( - `/api${WorkflowRoute.toString()}/analytics-model/${projectId.toString()}/${Text.pascalCaseToDashes( - this.getModel().tableName! - )}/${triggerType}` - ) - ), - { - data: { - _id: id.toString(), - }, - }, - { - ...ClusterKeyAuthorization.getClusterKeyHeaders(), - } - ).catch((error: Error) => { - logger.error(error); - }); - } - } - - protected checkRequiredFields(data: TBaseModel): TBaseModel { - // Check required fields. - - for (const columns of data.getRequiredColumns()) { - const requiredField: string = columns.key; - if (typeof (data as any)[requiredField] === Typeof.Boolean) { - if ( - !(data as any)[requiredField] && - (data as any)[requiredField] !== false && - data.isDefaultValueColumn(requiredField) - ) { - data.setColumnValue( - requiredField, - data.getDefaultValueForColumn(requiredField) - ); - } else { - throw new BadDataException(`${requiredField} is required`); - } - } else if ( - ((data as any)[requiredField] === null || - (data as any)[requiredField] === undefined) && - data.isDefaultValueColumn(requiredField) - ) { - // add default value. - data.setColumnValue( - requiredField, - data.getDefaultValueForColumn(requiredField) - ); - } else if ( - ((data as any)[requiredField] === null || - (data as any)[requiredField] === undefined) && - !data.isDefaultValueColumn(requiredField) - ) { - throw new BadDataException(`${requiredField} is required`); + if (!tenantId) { + continue; } + + promises.push( + Realtime.emitModelEvent({ + model: item, + tenantId: tenantId, + eventType: ModelEventType.Create, + modelType: this.modelType, + }), + ); + } + + await Promise.allSettled(promises); + } else { + logger.warn( + `Realtime is not initialized. Skipping emitModelEvent for ${ + this.getModel().tableName + }`, + ); } + } - return data; + return createBy.items; + } catch (error) { + await this.onCreateError(error as Exception); + throw this.getException(error as Exception); + } + } + + public async create(createBy: CreateBy<TBaseModel>): Promise<TBaseModel> { + const items: Array<TBaseModel> = await this.createMany({ + props: createBy.props, + items: [createBy.data], + }); + + const item: TBaseModel | undefined = items[0]; + + if (!item) { + throw new BadDataException("Item not created"); } - public getModel(): TBaseModel { - return this.model; + return item; + } + + private sanitizeCreate<TBaseModel extends AnalyticsBaseModel>( + data: TBaseModel, + ): TBaseModel { + if (!data.id) { + data.id = ObjectID.generate(); } + + data.createdAt = OneUptimeDate.getCurrentDate(); + data.updatedAt = OneUptimeDate.getCurrentDate(); + + return data; + } + + protected async getException(error: Exception): Promise<void> { + throw error; + } + + protected async onCreateError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } + + protected isValid(data: TBaseModel): boolean { + if (!data) { + throw new BadDataException("Data cannot be null"); + } + + return true; + } + + public async onTrigger( + id: ObjectID, + projectId: ObjectID, + triggerType: DatabaseTriggerType, + ): Promise<void> { + if (this.getModel().enableWorkflowOn) { + API.post( + new URL( + Protocol.HTTP, + AppApiHostname, + new Route( + `/api${WorkflowRoute.toString()}/analytics-model/${projectId.toString()}/${Text.pascalCaseToDashes( + this.getModel().tableName!, + )}/${triggerType}`, + ), + ), + { + data: { + _id: id.toString(), + }, + }, + { + ...ClusterKeyAuthorization.getClusterKeyHeaders(), + }, + ).catch((error: Error) => { + logger.error(error); + }); + } + } + + protected checkRequiredFields(data: TBaseModel): TBaseModel { + // Check required fields. + + for (const columns of data.getRequiredColumns()) { + const requiredField: string = columns.key; + if (typeof (data as any)[requiredField] === Typeof.Boolean) { + if ( + !(data as any)[requiredField] && + (data as any)[requiredField] !== false && + data.isDefaultValueColumn(requiredField) + ) { + data.setColumnValue( + requiredField, + data.getDefaultValueForColumn(requiredField), + ); + } else { + throw new BadDataException(`${requiredField} is required`); + } + } else if ( + ((data as any)[requiredField] === null || + (data as any)[requiredField] === undefined) && + data.isDefaultValueColumn(requiredField) + ) { + // add default value. + data.setColumnValue( + requiredField, + data.getDefaultValueForColumn(requiredField), + ); + } else if ( + ((data as any)[requiredField] === null || + (data as any)[requiredField] === undefined) && + !data.isDefaultValueColumn(requiredField) + ) { + throw new BadDataException(`${requiredField} is required`); + } + } + + return data; + } + + public getModel(): TBaseModel { + return this.model; + } } diff --git a/CommonServer/Services/ApiKeyPermissionService.ts b/CommonServer/Services/ApiKeyPermissionService.ts index b68371e2a6..8a90685ea8 100644 --- a/CommonServer/Services/ApiKeyPermissionService.ts +++ b/CommonServer/Services/ApiKeyPermissionService.ts @@ -1,168 +1,157 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate, OnUpdate } from '../Types/Database/Hooks'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Model from 'Model/Models/ApiKeyPermission'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate, OnUpdate } from "../Types/Database/Hooks"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Model from "Model/Models/ApiKeyPermission"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.apiKeyId) { + throw new BadDataException("API Key ID is required to create permission"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.apiKeyId) { - throw new BadDataException( - 'API Key ID is required to create permission' - ); - } + if (!createBy.data.projectId) { + throw new BadDataException("Project Id is required to create permission"); + } - if (!createBy.data.projectId) { - throw new BadDataException( - 'Project Id is required to create permission' - ); - } + if (!createBy.data.permission) { + throw new BadDataException("Permission is required to create permission"); + } - if (!createBy.data.permission) { - throw new BadDataException( - 'Permission is required to create permission' - ); - } + // check if this permission is already assigned to this team and if yes then throw error. - // check if this permission is already assigned to this team and if yes then throw error. + const isBlockPermission: boolean = createBy.data.isBlockPermission || false; + + const existingPermission: Model | null = await this.findOneBy({ + query: { + apiKeyId: createBy.data.apiKeyId, + projectId: createBy.data.projectId, + permission: createBy.data.permission, + isBlockPermission: isBlockPermission, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); + + if (existingPermission) { + throw new BadDataException( + "This permission is already assigned to this API Key", + ); + } + + if (createBy.data.labels && createBy.data.labels.length > 0) { + // check if the + + const existingPermission: Model | null = await this.findOneBy({ + query: { + apiKeyId: createBy.data.apiKeyId, + projectId: createBy.data.projectId, + permission: createBy.data.permission, + isBlockPermission: !isBlockPermission, + }, + select: { + _id: true, + isBlockPermission: true, + labels: true, + }, + props: { + isRoot: true, + }, + }); + + if (existingPermission && (existingPermission.labels?.length || 0) > 0) { + // if the permission in another block has labels, this permission cannot have labels. + + const blockName: string = existingPermission.isBlockPermission + ? "block" + : "allow"; + + throw new BadDataException( + `Restriction labels are already assigned to this permission in the ${blockName} permission list. To assign restriction labels to this permission, remove the restriction labels from the ${blockName} permission`, + ); + } + } + + return { createBy, carryForward: null }; + } + + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.labels && updateBy.data.labels.length > 0) { + const existingPermissions: Array<Model> = await this.findBy({ + query: updateBy.query, + select: { + _id: true, + labels: true, + isBlockPermission: true, + projectId: true, + apiKeyId: true, + permission: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const alreadySavedPermission of existingPermissions) { + // check if the const isBlockPermission: boolean = - createBy.data.isBlockPermission || false; + alreadySavedPermission.isBlockPermission || false; const existingPermission: Model | null = await this.findOneBy({ - query: { - apiKeyId: createBy.data.apiKeyId, - projectId: createBy.data.projectId, - permission: createBy.data.permission, - isBlockPermission: isBlockPermission, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, + query: { + apiKeyId: alreadySavedPermission.apiKeyId!, + projectId: alreadySavedPermission.projectId!, + permission: alreadySavedPermission.permission!, + isBlockPermission: !isBlockPermission, + }, + select: { + _id: true, + isBlockPermission: true, + labels: true, + permission: true, + }, + props: { + isRoot: true, + }, }); - if (existingPermission) { - throw new BadDataException( - 'This permission is already assigned to this API Key' - ); + if ( + existingPermission && + (existingPermission.labels?.length || 0) > 0 + ) { + // if the permission in another block has labels, this permission cannot have labels. + + const blockName: string = existingPermission.isBlockPermission + ? "block" + : "allow"; + + throw new BadDataException( + `Restriction labels are already assigned to ${existingPermission.permission} in the ${blockName} permission list. To assign restriction labels to this permission, remove the restriction labels from the ${blockName} list.`, + ); } - - if (createBy.data.labels && createBy.data.labels.length > 0) { - // check if the - - const existingPermission: Model | null = await this.findOneBy({ - query: { - apiKeyId: createBy.data.apiKeyId, - projectId: createBy.data.projectId, - permission: createBy.data.permission, - isBlockPermission: !isBlockPermission, - }, - select: { - _id: true, - isBlockPermission: true, - labels: true, - }, - props: { - isRoot: true, - }, - }); - - if ( - existingPermission && - (existingPermission.labels?.length || 0) > 0 - ) { - // if the permission in another block has labels, this permission cannot have labels. - - const blockName: string = existingPermission.isBlockPermission - ? 'block' - : 'allow'; - - throw new BadDataException( - `Restriction labels are already assigned to this permission in the ${blockName} permission list. To assign restriction labels to this permission, remove the restriction labels from the ${blockName} permission` - ); - } - } - - return { createBy, carryForward: null }; + } } - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if (updateBy.data.labels && updateBy.data.labels.length > 0) { - const existingPermissions: Array<Model> = await this.findBy({ - query: updateBy.query, - select: { - _id: true, - labels: true, - isBlockPermission: true, - projectId: true, - apiKeyId: true, - permission: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const alreadySavedPermission of existingPermissions) { - // check if the - - const isBlockPermission: boolean = - alreadySavedPermission.isBlockPermission || false; - - const existingPermission: Model | null = await this.findOneBy({ - query: { - apiKeyId: alreadySavedPermission.apiKeyId!, - projectId: alreadySavedPermission.projectId!, - permission: alreadySavedPermission.permission!, - isBlockPermission: !isBlockPermission, - }, - select: { - _id: true, - isBlockPermission: true, - labels: true, - permission: true, - }, - props: { - isRoot: true, - }, - }); - - if ( - existingPermission && - (existingPermission.labels?.length || 0) > 0 - ) { - // if the permission in another block has labels, this permission cannot have labels. - - const blockName: string = - existingPermission.isBlockPermission - ? 'block' - : 'allow'; - - throw new BadDataException( - `Restriction labels are already assigned to ${existingPermission.permission} in the ${blockName} permission list. To assign restriction labels to this permission, remove the restriction labels from the ${blockName} list.` - ); - } - } - } - - return { updateBy, carryForward: null }; - } + return { updateBy, carryForward: null }; + } } export default new Service(); diff --git a/CommonServer/Services/ApiKeyService.ts b/CommonServer/Services/ApiKeyService.ts index 0144c68f7a..cd9e50771a 100644 --- a/CommonServer/Services/ApiKeyService.ts +++ b/CommonServer/Services/ApiKeyService.ts @@ -1,21 +1,21 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import ObjectID from 'Common/Types/ObjectID'; -import Model from 'Model/Models/ApiKey'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import ObjectID from "Common/Types/ObjectID"; +import Model from "Model/Models/ApiKey"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - createBy.data.apiKey = ObjectID.generate(); - return { createBy, carryForward: null }; - } + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + createBy.data.apiKey = ObjectID.generate(); + return { createBy, carryForward: null }; + } } export default new Service(); diff --git a/CommonServer/Services/BaseService.ts b/CommonServer/Services/BaseService.ts index 6307979ae6..e408084785 100644 --- a/CommonServer/Services/BaseService.ts +++ b/CommonServer/Services/BaseService.ts @@ -1,3 +1,3 @@ export default class BaseService { - public constructor() {} + public constructor() {} } diff --git a/CommonServer/Services/BillingInvoiceService.ts b/CommonServer/Services/BillingInvoiceService.ts index 120aafc6d7..11de368736 100644 --- a/CommonServer/Services/BillingInvoiceService.ts +++ b/CommonServer/Services/BillingInvoiceService.ts @@ -1,94 +1,92 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import FindBy from '../Types/Database/FindBy'; -import { OnFind } from '../Types/Database/Hooks'; -import BillingService, { Invoice } from './BillingService'; -import DatabaseService from './DatabaseService'; -import ProjectService from './ProjectService'; -import URL from 'Common/Types/API/URL'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Model, { InvoiceStatus } from 'Model/Models/BillingInvoice'; -import Project from 'Model/Models/Project'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import FindBy from "../Types/Database/FindBy"; +import { OnFind } from "../Types/Database/Hooks"; +import BillingService, { Invoice } from "./BillingService"; +import DatabaseService from "./DatabaseService"; +import ProjectService from "./ProjectService"; +import URL from "Common/Types/API/URL"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Model, { InvoiceStatus } from "Model/Models/BillingInvoice"; +import Project from "Model/Models/Project"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.setDoNotAllowDelete(true); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.setDoNotAllowDelete(true); + } + + protected override async onBeforeFind( + findBy: FindBy<Model>, + ): Promise<OnFind<Model>> { + if (!findBy.props.tenantId) { + throw new BadDataException("ProjectID not found."); } - protected override async onBeforeFind( - findBy: FindBy<Model> - ): Promise<OnFind<Model>> { - if (!findBy.props.tenantId) { - throw new BadDataException('ProjectID not found.'); - } + const project: Project | null = await ProjectService.findOneById({ + id: findBy.props.tenantId!, + props: { + ...findBy.props, + isRoot: true, + ignoreHooks: true, + }, + select: { + _id: true, + paymentProviderCustomerId: true, + }, + }); - const project: Project | null = await ProjectService.findOneById({ - id: findBy.props.tenantId!, - props: { - ...findBy.props, - isRoot: true, - ignoreHooks: true, - }, - select: { - _id: true, - paymentProviderCustomerId: true, - }, - }); - - if (!project) { - throw new BadDataException('Project not found'); - } - - if (!project.paymentProviderCustomerId) { - throw new BadDataException( - 'Payment provider customer id not found.' - ); - } - - const invoices: Array<Invoice> = await BillingService.getInvoices( - project.paymentProviderCustomerId - ); - - await this.deleteBy({ - query: { - projectId: findBy.props.tenantId!, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - for (const invoice of invoices) { - const billingInvoice: Model = new Model(); - - billingInvoice.projectId = project.id!; - - billingInvoice.amount = invoice.amount; - billingInvoice.downloadableLink = URL.fromString( - invoice.downloadableLink - ); - billingInvoice.currencyCode = invoice.currencyCode; - billingInvoice.paymentProviderCustomerId = invoice.customerId || ''; - billingInvoice.paymentProviderSubscriptionId = - invoice.subscriptionId || ''; - billingInvoice.status = - (invoice.status as InvoiceStatus) || InvoiceStatus.Undefined; - billingInvoice.paymentProviderInvoiceId = invoice.id; - - await this.create({ - data: billingInvoice, - props: { - isRoot: true, - }, - }); - } - - return { findBy, carryForward: invoices }; + if (!project) { + throw new BadDataException("Project not found"); } + + if (!project.paymentProviderCustomerId) { + throw new BadDataException("Payment provider customer id not found."); + } + + const invoices: Array<Invoice> = await BillingService.getInvoices( + project.paymentProviderCustomerId, + ); + + await this.deleteBy({ + query: { + projectId: findBy.props.tenantId!, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + for (const invoice of invoices) { + const billingInvoice: Model = new Model(); + + billingInvoice.projectId = project.id!; + + billingInvoice.amount = invoice.amount; + billingInvoice.downloadableLink = URL.fromString( + invoice.downloadableLink, + ); + billingInvoice.currencyCode = invoice.currencyCode; + billingInvoice.paymentProviderCustomerId = invoice.customerId || ""; + billingInvoice.paymentProviderSubscriptionId = + invoice.subscriptionId || ""; + billingInvoice.status = + (invoice.status as InvoiceStatus) || InvoiceStatus.Undefined; + billingInvoice.paymentProviderInvoiceId = invoice.id; + + await this.create({ + data: billingInvoice, + props: { + isRoot: true, + }, + }); + } + + return { findBy, carryForward: invoices }; + } } export default new Service(); diff --git a/CommonServer/Services/BillingPaymentMethodService.ts b/CommonServer/Services/BillingPaymentMethodService.ts index fa4ffb9ba5..207522c6b7 100644 --- a/CommonServer/Services/BillingPaymentMethodService.ts +++ b/CommonServer/Services/BillingPaymentMethodService.ts @@ -1,123 +1,118 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DeleteBy from '../Types/Database/DeleteBy'; -import FindBy from '../Types/Database/FindBy'; -import { OnDelete, OnFind } from '../Types/Database/Hooks'; -import BillingService, { PaymentMethod } from './BillingService'; -import DatabaseService from './DatabaseService'; -import ProjectService from './ProjectService'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Model from 'Model/Models/BillingPaymentMethod'; -import Project from 'Model/Models/Project'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DeleteBy from "../Types/Database/DeleteBy"; +import FindBy from "../Types/Database/FindBy"; +import { OnDelete, OnFind } from "../Types/Database/Hooks"; +import BillingService, { PaymentMethod } from "./BillingService"; +import DatabaseService from "./DatabaseService"; +import ProjectService from "./ProjectService"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Model from "Model/Models/BillingPaymentMethod"; +import Project from "Model/Models/Project"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeFind( + findBy: FindBy<Model>, + ): Promise<OnFind<Model>> { + if (!findBy.props.tenantId) { + throw new BadDataException("ProjectID not found."); } - protected override async onBeforeFind( - findBy: FindBy<Model> - ): Promise<OnFind<Model>> { - if (!findBy.props.tenantId) { - throw new BadDataException('ProjectID not found.'); - } + const project: Project | null = await ProjectService.findOneById({ + id: findBy.props.tenantId!, + props: { + ...findBy.props, + isRoot: true, + ignoreHooks: true, + }, + select: { + _id: true, + paymentProviderCustomerId: true, + }, + }); - const project: Project | null = await ProjectService.findOneById({ - id: findBy.props.tenantId!, - props: { - ...findBy.props, - isRoot: true, - ignoreHooks: true, - }, - select: { - _id: true, - paymentProviderCustomerId: true, - }, - }); - - if (!project) { - throw new BadDataException('Project not found'); - } - - if (!project.paymentProviderCustomerId) { - throw new BadDataException( - 'Payment provider customer id not found.' - ); - } - - const paymentMethods: Array<PaymentMethod> = - await BillingService.getPaymentMethods( - project.paymentProviderCustomerId - ); - - await this.deleteBy({ - query: { - projectId: findBy.props.tenantId!, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - for (const paymentMethod of paymentMethods) { - const billingPaymentMethod: Model = new Model(); - - billingPaymentMethod.projectId = project.id!; - - billingPaymentMethod.type = paymentMethod.type; - billingPaymentMethod.last4Digits = paymentMethod.last4Digits; - billingPaymentMethod.isDefault = paymentMethod.isDefault; - billingPaymentMethod.paymentProviderPaymentMethodId = - paymentMethod.id; - billingPaymentMethod.paymentProviderCustomerId = - project.paymentProviderCustomerId; - - await this.create({ - data: billingPaymentMethod, - props: { - isRoot: true, - }, - }); - } - - return { findBy, carryForward: paymentMethods }; + if (!project) { + throw new BadDataException("Project not found"); } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - const items: Array<Model> = await this.findBy({ - query: deleteBy.query, - select: { - _id: true, - paymentProviderPaymentMethodId: true, - paymentProviderCustomerId: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - for (const item of items) { - if ( - item.paymentProviderPaymentMethodId && - item.paymentProviderCustomerId - ) { - await BillingService.deletePaymentMethod( - item.paymentProviderCustomerId, - item.paymentProviderPaymentMethodId - ); - } - } - - return { deleteBy, carryForward: null }; + if (!project.paymentProviderCustomerId) { + throw new BadDataException("Payment provider customer id not found."); } + + const paymentMethods: Array<PaymentMethod> = + await BillingService.getPaymentMethods(project.paymentProviderCustomerId); + + await this.deleteBy({ + query: { + projectId: findBy.props.tenantId!, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + for (const paymentMethod of paymentMethods) { + const billingPaymentMethod: Model = new Model(); + + billingPaymentMethod.projectId = project.id!; + + billingPaymentMethod.type = paymentMethod.type; + billingPaymentMethod.last4Digits = paymentMethod.last4Digits; + billingPaymentMethod.isDefault = paymentMethod.isDefault; + billingPaymentMethod.paymentProviderPaymentMethodId = paymentMethod.id; + billingPaymentMethod.paymentProviderCustomerId = + project.paymentProviderCustomerId; + + await this.create({ + data: billingPaymentMethod, + props: { + isRoot: true, + }, + }); + } + + return { findBy, carryForward: paymentMethods }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + const items: Array<Model> = await this.findBy({ + query: deleteBy.query, + select: { + _id: true, + paymentProviderPaymentMethodId: true, + paymentProviderCustomerId: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + for (const item of items) { + if ( + item.paymentProviderPaymentMethodId && + item.paymentProviderCustomerId + ) { + await BillingService.deletePaymentMethod( + item.paymentProviderCustomerId, + item.paymentProviderPaymentMethodId, + ); + } + } + + return { deleteBy, carryForward: null }; + } } export default new Service(); diff --git a/CommonServer/Services/BillingService.ts b/CommonServer/Services/BillingService.ts index 06d1c3144a..2798987ebd 100644 --- a/CommonServer/Services/BillingService.ts +++ b/CommonServer/Services/BillingService.ts @@ -1,880 +1,815 @@ -import { BillingPrivateKey, IsBillingEnabled } from '../EnvironmentConfig'; -import ServerMeteredPlan from '../Types/Billing/MeteredPlan/ServerMeteredPlan'; -import Errors from '../Utils/Errors'; -import logger from '../Utils/Logger'; -import BaseService from './BaseService'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; +import { BillingPrivateKey, IsBillingEnabled } from "../EnvironmentConfig"; +import ServerMeteredPlan from "../Types/Billing/MeteredPlan/ServerMeteredPlan"; +import Errors from "../Utils/Errors"; +import logger from "../Utils/Logger"; +import BaseService from "./BaseService"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; import SubscriptionStatus, { - SubscriptionStatusUtil, -} from 'Common/Types/Billing/SubscriptionStatus'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import Email from 'Common/Types/Email'; -import APIException from 'Common/Types/Exception/ApiException'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import ObjectID from 'Common/Types/ObjectID'; -import Stripe from 'stripe'; + SubscriptionStatusUtil, +} from "Common/Types/Billing/SubscriptionStatus"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import Email from "Common/Types/Email"; +import APIException from "Common/Types/Exception/ApiException"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import ObjectID from "Common/Types/ObjectID"; +import Stripe from "stripe"; export type SubscriptionItem = Stripe.SubscriptionItem; export type Coupon = Stripe.Coupon; export interface PaymentMethod { - id: string; - type: string; - last4Digits: string; - isDefault: boolean; + id: string; + type: string; + last4Digits: string; + isDefault: boolean; } export interface Invoice { - id: string; - amount: number; - currencyCode: string; - subscriptionId?: string | undefined; - status: string; - downloadableLink: string; - customerId: string | undefined; + id: string; + amount: number; + currencyCode: string; + subscriptionId?: string | undefined; + status: string; + downloadableLink: string; + customerId: string | undefined; } export class BillingService extends BaseService { - public constructor() { - super(); + public constructor() { + super(); + } + + private stripe: Stripe = new Stripe(BillingPrivateKey, { + apiVersion: "2022-08-01", + }); + + // returns billing id of the customer. + public async createCustomer(data: { + name: string; + id: ObjectID; + email: Email; + }): Promise<string> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + const customer: Stripe.Response<Stripe.Customer> = + await this.stripe.customers.create({ + name: data.name, + email: data.email.toString(), + metadata: { + id: data.id.toString(), + }, + }); + return customer.id; + } + + public async updateCustomerName(id: string, newName: string): Promise<void> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); } - private stripe: Stripe = new Stripe(BillingPrivateKey, { - apiVersion: '2022-08-01', + await this.stripe.customers.update(id, { name: newName }); + } + + public async deleteCustomer(id: string): Promise<void> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + + await this.stripe.customers.del(id); + } + + public isBillingEnabled(): boolean { + return IsBillingEnabled; + } + + public isSubscriptionActive(status: SubscriptionStatus): boolean { + return SubscriptionStatusUtil.isSubscriptionActive(status); + } + + public async subscribeToMeteredPlan(data: { + projectId: ObjectID; + customerId: string; + serverMeteredPlans: Array<ServerMeteredPlan>; + trialDate: Date | null; + defaultPaymentMethodId?: string | undefined; + promoCode?: string | undefined; + }): Promise<{ + meteredSubscriptionId: string; + trialEndsAt: Date | null; + }> { + const meteredPlanSubscriptionParams: Stripe.SubscriptionCreateParams = { + customer: data.customerId, + + proration_behavior: "always_invoice", + + items: data.serverMeteredPlans.map((item: ServerMeteredPlan) => { + return { + price: item.getPriceId(), + }; + }), + + trial_end: + data.trialDate && OneUptimeDate.isInTheFuture(data.trialDate) + ? OneUptimeDate.toUnixTimestamp(data.trialDate) + : "now", + }; + + if (data.promoCode) { + meteredPlanSubscriptionParams.coupon = data.promoCode; + } + + if (data.defaultPaymentMethodId) { + meteredPlanSubscriptionParams.default_payment_method = + data.defaultPaymentMethodId; + } + + // Create metered subscriptions + const meteredSubscription: Stripe.Response<Stripe.Subscription> = + await this.stripe.subscriptions.create(meteredPlanSubscriptionParams); + + for (const serverMeteredPlan of data.serverMeteredPlans) { + await serverMeteredPlan.reportQuantityToBillingProvider(data.projectId, { + meteredPlanSubscriptionId: meteredSubscription.id, + }); + } + + return { + meteredSubscriptionId: meteredSubscription.id, + trialEndsAt: data.trialDate, + }; + } + + public isTestEnvironment(): boolean { + return BillingPrivateKey.startsWith("sk_test"); + } + + public async generateCouponCode(data: { + name: string; + metadata?: Dictionary<string> | undefined; + percentOff: number; + durationInMonths: number; + maxRedemptions: number; + }): Promise<string> { + const coupon: Coupon = await this.stripe.coupons.create({ + name: data.name, + percent_off: data.percentOff, + duration: "repeating", + duration_in_months: data.durationInMonths, + max_redemptions: data.maxRedemptions, + metadata: data.metadata || null, }); - // returns billing id of the customer. - public async createCustomer(data: { - name: string; - id: ObjectID; - email: Email; - }): Promise<string> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - const customer: Stripe.Response<Stripe.Customer> = - await this.stripe.customers.create({ - name: data.name, - email: data.email.toString(), - metadata: { - id: data.id.toString(), - }, - }); - return customer.id; + return coupon.id; + } + + public async subscribeToPlan(data: { + projectId: ObjectID; + customerId: string; + serverMeteredPlans: Array<ServerMeteredPlan>; + plan: SubscriptionPlan; + quantity: number; + isYearly: boolean; + trial: boolean | Date | undefined; + defaultPaymentMethodId?: string | undefined; + promoCode?: string | undefined; + }): Promise<{ + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt: Date | null; + }> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); } - public async updateCustomerName( - id: string, - newName: string - ): Promise<void> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } + let trialDate: Date | null = null; - await this.stripe.customers.update(id, { name: newName }); + if (typeof data.trial === "boolean") { + if (data.trial) { + trialDate = OneUptimeDate.getSomeDaysAfter(data.plan.getTrialPeriod()); + } else { + trialDate = null; + } + } else if (data.trial instanceof Date) { + trialDate = data.trial; } - public async deleteCustomer(id: string): Promise<void> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } + const subscriptionParams: Stripe.SubscriptionCreateParams = { + customer: data.customerId, - await this.stripe.customers.del(id); + items: [ + { + price: data.isYearly + ? data.plan.getYearlyPlanId() + : data.plan.getMonthlyPlanId(), + quantity: data.quantity, + }, + ], + + proration_behavior: "always_invoice", + + trial_end: + trialDate && data.plan.getTrialPeriod() > 0 + ? OneUptimeDate.toUnixTimestamp(trialDate) + : "now", + }; + + if (data.promoCode) { + subscriptionParams.coupon = data.promoCode; } - public isBillingEnabled(): boolean { - return IsBillingEnabled; + if (data.defaultPaymentMethodId) { + subscriptionParams.default_payment_method = data.defaultPaymentMethodId; } - public isSubscriptionActive(status: SubscriptionStatus): boolean { - return SubscriptionStatusUtil.isSubscriptionActive(status); + const subscription: Stripe.Response<Stripe.Subscription> = + await this.stripe.subscriptions.create(subscriptionParams); + + // Create metered subscriptions + const meteredSubscription: { + meteredSubscriptionId: string; + trialEndsAt: Date | null; + } = await this.subscribeToMeteredPlan({ + ...data, + trialDate, + }); + + return { + subscriptionId: subscription.id, + meteredSubscriptionId: meteredSubscription.meteredSubscriptionId, + trialEndsAt: + trialDate && data.plan.getTrialPeriod() > 0 ? trialDate : null, + }; + } + + public async changeQuantity( + subscriptionId: string, + quantity: number, + ): Promise<void> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); } - public async subscribeToMeteredPlan(data: { - projectId: ObjectID; - customerId: string; - serverMeteredPlans: Array<ServerMeteredPlan>; - trialDate: Date | null; - defaultPaymentMethodId?: string | undefined; - promoCode?: string | undefined; - }): Promise<{ - meteredSubscriptionId: string; - trialEndsAt: Date | null; - }> { - const meteredPlanSubscriptionParams: Stripe.SubscriptionCreateParams = { - customer: data.customerId, + const subscription: Stripe.Response<Stripe.Subscription> = + await this.stripe.subscriptions.retrieve(subscriptionId); - proration_behavior: 'always_invoice', - - items: data.serverMeteredPlans.map((item: ServerMeteredPlan) => { - return { - price: item.getPriceId(), - }; - }), - - trial_end: - data.trialDate && OneUptimeDate.isInTheFuture(data.trialDate) - ? OneUptimeDate.toUnixTimestamp(data.trialDate) - : 'now', - }; - - if (data.promoCode) { - meteredPlanSubscriptionParams.coupon = data.promoCode; - } - - if (data.defaultPaymentMethodId) { - meteredPlanSubscriptionParams.default_payment_method = - data.defaultPaymentMethodId; - } - - // Create metered subscriptions - const meteredSubscription: Stripe.Response<Stripe.Subscription> = - await this.stripe.subscriptions.create( - meteredPlanSubscriptionParams - ); - - for (const serverMeteredPlan of data.serverMeteredPlans) { - await serverMeteredPlan.reportQuantityToBillingProvider( - data.projectId, - { - meteredPlanSubscriptionId: meteredSubscription.id, - } - ); - } - - return { - meteredSubscriptionId: meteredSubscription.id, - trialEndsAt: data.trialDate, - }; + if (!subscription) { + throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); + } + if (subscription.status === "canceled") { + // subscription is canceled. + return; } - public isTestEnvironment(): boolean { - return BillingPrivateKey.startsWith('sk_test'); + const subscriptionItemId: string | undefined = + subscription.items.data[0]?.id; + + if (!subscriptionItemId) { + throw new BadDataException( + Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND, + ); } - public async generateCouponCode(data: { - name: string; - metadata?: Dictionary<string> | undefined; - percentOff: number; - durationInMonths: number; - maxRedemptions: number; - }): Promise<string> { - const coupon: Coupon = await this.stripe.coupons.create({ - name: data.name, - percent_off: data.percentOff, - duration: 'repeating', - duration_in_months: data.durationInMonths, - max_redemptions: data.maxRedemptions, - metadata: data.metadata || null, - }); + await this.stripe.subscriptionItems.update(subscriptionItemId, { + quantity: quantity, + }); - return coupon.id; + // add billing anchor, so that the billing cycle starts now. New quantity will be charged from now. https://stackoverflow.com/questions/44417047/immediately-charge-for-subscription-changes + await this.stripe.subscriptions.update(subscriptionId, { + proration_behavior: "always_invoice", + }); + } + + public async addOrUpdateMeteredPricingOnSubscription( + subscriptionId: string, + serverMeteredPlan: ServerMeteredPlan, + quantity: number, + ): Promise<void> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); } - public async subscribeToPlan(data: { - projectId: ObjectID; - customerId: string; - serverMeteredPlans: Array<ServerMeteredPlan>; - plan: SubscriptionPlan; - quantity: number; - isYearly: boolean; - trial: boolean | Date | undefined; - defaultPaymentMethodId?: string | undefined; - promoCode?: string | undefined; - }): Promise<{ - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt: Date | null; - }> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } + // get subscription. + const subscription: Stripe.Subscription = await this.getSubscription( + subscriptionId.toString(), + ); - let trialDate: Date | null = null; - - if (typeof data.trial === 'boolean') { - if (data.trial) { - trialDate = OneUptimeDate.getSomeDaysAfter( - data.plan.getTrialPeriod() - ); - } else { - trialDate = null; - } - } else if (data.trial instanceof Date) { - trialDate = data.trial; - } - - const subscriptionParams: Stripe.SubscriptionCreateParams = { - customer: data.customerId, - - items: [ - { - price: data.isYearly - ? data.plan.getYearlyPlanId() - : data.plan.getMonthlyPlanId(), - quantity: data.quantity, - }, - ], - - proration_behavior: 'always_invoice', - - trial_end: - trialDate && data.plan.getTrialPeriod() > 0 - ? OneUptimeDate.toUnixTimestamp(trialDate) - : 'now', - }; - - if (data.promoCode) { - subscriptionParams.coupon = data.promoCode; - } - - if (data.defaultPaymentMethodId) { - subscriptionParams.default_payment_method = - data.defaultPaymentMethodId; - } - - const subscription: Stripe.Response<Stripe.Subscription> = - await this.stripe.subscriptions.create(subscriptionParams); - - // Create metered subscriptions - const meteredSubscription: { - meteredSubscriptionId: string; - trialEndsAt: Date | null; - } = await this.subscribeToMeteredPlan({ - ...data, - trialDate, - }); - - return { - subscriptionId: subscription.id, - meteredSubscriptionId: meteredSubscription.meteredSubscriptionId, - trialEndsAt: - trialDate && data.plan.getTrialPeriod() > 0 ? trialDate : null, - }; + if (!subscription) { + throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); } - public async changeQuantity( - subscriptionId: string, - quantity: number - ): Promise<void> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - - const subscription: Stripe.Response<Stripe.Subscription> = - await this.stripe.subscriptions.retrieve(subscriptionId); - - if (!subscription) { - throw new BadDataException( - Errors.BillingService.SUBSCRIPTION_NOT_FOUND - ); - } - if (subscription.status === 'canceled') { - // subscription is canceled. - return; - } - - const subscriptionItemId: string | undefined = - subscription.items.data[0]?.id; - - if (!subscriptionItemId) { - throw new BadDataException( - Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND - ); - } - - await this.stripe.subscriptionItems.update(subscriptionItemId, { - quantity: quantity, - }); - - // add billing anchor, so that the billing cycle starts now. New quantity will be charged from now. https://stackoverflow.com/questions/44417047/immediately-charge-for-subscription-changes - await this.stripe.subscriptions.update(subscriptionId, { - proration_behavior: 'always_invoice', - }); - } - - public async addOrUpdateMeteredPricingOnSubscription( - subscriptionId: string, - serverMeteredPlan: ServerMeteredPlan, - quantity: number - ): Promise<void> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - - // get subscription. - const subscription: Stripe.Subscription = await this.getSubscription( - subscriptionId.toString() - ); - - if (!subscription) { - throw new BadDataException( - Errors.BillingService.SUBSCRIPTION_NOT_FOUND - ); - } - - // check if this pricing exists - - const pricingExists: boolean = subscription.items.data.some( - (item: SubscriptionItem) => { - return item.price?.id === serverMeteredPlan.getPriceId(); - } - ); - - if (pricingExists) { - // update the quantity. - const subscriptionItemId: string | undefined = - subscription.items.data.find((item: SubscriptionItem) => { - return item.price?.id === serverMeteredPlan.getPriceId(); - })?.id; - - if (!subscriptionItemId) { - throw new BadDataException( - Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND - ); - } - - // use stripe usage based api to update the quantity. - await this.stripe.subscriptionItems.createUsageRecord( - subscriptionItemId, - { - quantity: quantity, - } - ); - } else { - // add the pricing. - const subscriptionItem: SubscriptionItem = - await this.stripe.subscriptionItems.create({ - subscription: subscriptionId, - price: serverMeteredPlan.getPriceId(), - }); - - // use stripe usage based api to update the quantity. - await this.stripe.subscriptionItems.createUsageRecord( - subscriptionItem.id, - { - quantity: quantity, - } - ); - } - - // complete. - } - - public async isPromoCodeValid(promoCode: string): Promise<boolean> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - try { - const promoCodeResponse: Stripe.Response<Stripe.Coupon> = - await this.stripe.coupons.retrieve(promoCode); - - if (!promoCodeResponse) { - throw new BadDataException( - Errors.BillingService.PROMO_CODE_NOT_FOUND - ); - } - - return promoCodeResponse.valid; - } catch (err) { - throw new BadDataException( - (err as Error).message || - Errors.BillingService.PROMO_CODE_INVALID - ); - } - } - - public async removeSubscriptionItem( - subscriptionId: string, - subscriptionItemId: string, - isMeteredSubscriptionItem: boolean - ): Promise<void> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - - const subscription: Stripe.Response<Stripe.Subscription> = - await this.stripe.subscriptions.retrieve(subscriptionId); - - if (!subscription) { - throw new BadDataException( - Errors.BillingService.SUBSCRIPTION_NOT_FOUND - ); - } - - if (subscription.status === 'canceled') { - // subscription is canceled. - return; - } - - const subscriptionItemOptions: Stripe.SubscriptionItemDeleteParams = - isMeteredSubscriptionItem - ? { - proration_behavior: 'create_prorations', - clear_usage: true, - } - : {}; - - await this.stripe.subscriptionItems.del( - subscriptionItemId, - subscriptionItemOptions - ); - } - - public async getSubscriptionItems( - subscriptionId: string - ): Promise<Array<SubscriptionItem>> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - - const subscription: Stripe.Response<Stripe.Subscription> = - await this.stripe.subscriptions.retrieve(subscriptionId); - - if (!subscription) { - throw new BadDataException( - Errors.BillingService.SUBSCRIPTION_NOT_FOUND - ); - } - - return subscription.items.data; - } - - public async changePlan(data: { - projectId: ObjectID; - subscriptionId: string; - meteredSubscriptionId: string; - serverMeteredPlans: Array<ServerMeteredPlan>; - newPlan: SubscriptionPlan; - quantity: number; - isYearly: boolean; - endTrialAt?: Date | undefined; - }): Promise<{ - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt?: Date | undefined; - }> { - logger.debug('Changing plan'); - logger.debug(data); - - if (!this.isBillingEnabled()) { - logger.debug(Errors.BillingService.BILLING_NOT_ENABLED); - - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - - const subscription: Stripe.Response<Stripe.Subscription> = - await this.stripe.subscriptions.retrieve(data.subscriptionId); - - logger.debug('Subscription'); - logger.debug(subscription); - - if (!subscription) { - logger.debug(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); - throw new BadDataException( - Errors.BillingService.SUBSCRIPTION_NOT_FOUND - ); - } - - logger.debug('Subscription status'); - logger.debug(subscription.status); - - const paymentMethods: Array<PaymentMethod> = - await this.getPaymentMethods(subscription.customer.toString()); - - logger.debug('Payment methods'); - logger.debug(paymentMethods); - - if (paymentMethods.length === 0) { - logger.debug('No payment methods'); - - throw new BadDataException( - Errors.BillingService.NO_PAYMENTS_METHODS - ); - } - - logger.debug('Cancelling subscriptions'); - logger.debug(data.subscriptionId); - await this.cancelSubscription(data.subscriptionId); - - logger.debug('Cancelling metered subscriptions'); - logger.debug(data.meteredSubscriptionId); - await this.cancelSubscription(data.meteredSubscriptionId); - - if (data.endTrialAt && !OneUptimeDate.isInTheFuture(data.endTrialAt)) { - data.endTrialAt = undefined; - } - - logger.debug('Subscribing to plan'); - - const subscribeToPlan: { - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt: Date | null; - } = await this.subscribeToPlan({ - projectId: data.projectId, - customerId: subscription.customer.toString(), - serverMeteredPlans: data.serverMeteredPlans, - plan: data.newPlan, - quantity: data.quantity, - isYearly: data.isYearly, - trial: data.endTrialAt, - defaultPaymentMethodId: paymentMethods[0]?.id, - promoCode: undefined, - }); - - logger.debug('Subscribed to plan'); - - const value: { - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt?: Date | undefined; - } = { - subscriptionId: subscribeToPlan.subscriptionId, - meteredSubscriptionId: subscribeToPlan.meteredSubscriptionId, - trialEndsAt: subscribeToPlan.trialEndsAt || undefined, - }; - - return value; - } - - public async deletePaymentMethod( - customerId: string, - paymentMethodId: string - ): Promise<void> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - - const paymentMethods: Array<PaymentMethod> = - await this.getPaymentMethods(customerId); - - if (paymentMethods.length === 1) { - throw new BadDataException( - Errors.BillingService.MIN_REQUIRED_PAYMENT_METHOD_NOT_MET - ); - } - - await this.stripe.paymentMethods.detach(paymentMethodId); - } - - public async hasPaymentMethods(customerId: string): Promise<boolean> { - if ((await this.getPaymentMethods(customerId)).length > 0) { - return true; - } - - return false; - } - - public async setDefaultPaymentMethod( - customerId: string, - paymentMethodId: string - ): Promise<void> { - await this.stripe.customers.update(customerId, { - invoice_settings: { - default_payment_method: paymentMethodId, - }, - }); - } - - public async getPaymentMethods( - customerId: string - ): Promise<Array<PaymentMethod>> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - const paymentMethods: Array<PaymentMethod> = []; - - const cardPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> = - await this.stripe.paymentMethods.list({ - customer: customerId, - type: 'card', - }); - - const sepaPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> = - await this.stripe.paymentMethods.list({ - customer: customerId, - type: 'sepa_debit', - }); - - const usBankPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> = - await this.stripe.paymentMethods.list({ - customer: customerId, - type: 'us_bank_account', - }); - - const bacsPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> = - await this.stripe.paymentMethods.list({ - customer: customerId, - type: 'bacs_debit', - }); - - cardPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => { - paymentMethods.push({ - type: item.card?.brand || 'Card', - last4Digits: item.card?.last4 || 'xxxx', - isDefault: false, - id: item.id, - }); - }); - - bacsPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => { - paymentMethods.push({ - type: 'UK Bank Account', - last4Digits: item.bacs_debit?.last4 || 'xxxx', - isDefault: false, - id: item.id, - }); - }); - - usBankPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => { - paymentMethods.push({ - type: 'US Bank Account', - last4Digits: item.us_bank_account?.last4 || 'xxxx', - isDefault: false, - id: item.id, - }); - }); - - sepaPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => { - paymentMethods.push({ - type: 'EU Bank Account', - last4Digits: item.sepa_debit?.last4 || 'xxxx', - isDefault: false, - id: item.id, - }); - }); - - // check if there's a default payment method. - - const customer: Stripe.Response< - Stripe.Customer | Stripe.DeletedCustomer - > = await this.stripe.customers.retrieve(customerId); - - if ( - (customer as Stripe.Customer).invoice_settings && - !(customer as Stripe.Customer).invoice_settings - ?.default_payment_method - ) { - // set the first payment method as default. - if (paymentMethods.length > 0 && paymentMethods[0]?.id) { - await this.setDefaultPaymentMethod( - customerId, - paymentMethods[0]?.id - ); - } - } - - return paymentMethods; - } - - public async getSetupIntentSecret(customerId: string): Promise<string> { - const setupIntent: Stripe.Response<Stripe.SetupIntent> = - await this.stripe.setupIntents.create({ - customer: customerId, - }); - - if (!setupIntent.client_secret) { - throw new APIException(Errors.BillingService.CLIENT_SECRET_MISSING); - } - - return setupIntent.client_secret; - } - - public async cancelSubscription(subscriptionId: string): Promise<void> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - try { - await this.stripe.subscriptions.del(subscriptionId); - } catch (err) { - logger.error(err); - } - } - - public async getSubscriptionStatus( - subscriptionId: string - ): Promise<SubscriptionStatus> { - const subscription: Stripe.Subscription = await this.getSubscription( - subscriptionId - ); - return subscription.status as SubscriptionStatus; - } - - public async getSubscription( - subscriptionId: string - ): Promise<Stripe.Subscription> { - if (!this.isBillingEnabled()) { - throw new BadDataException( - Errors.BillingService.BILLING_NOT_ENABLED - ); - } - - const subscription: Stripe.Response<Stripe.Subscription> = - await this.stripe.subscriptions.retrieve(subscriptionId); - - return subscription; - } - - public async getInvoices(customerId: string): Promise<Array<Invoice>> { - const invoices: Stripe.ApiList<Stripe.Invoice> = - await this.stripe.invoices.list({ - customer: customerId, - limit: 100, - }); - - return invoices.data.map((invoice: Stripe.Invoice) => { - return { - id: invoice.id!, - amount: invoice.amount_due, - currencyCode: invoice.currency, - subscriptionId: invoice.subscription?.toString() || undefined, - status: invoice.status?.toString() || 'Unknown', - downloadableLink: invoice.invoice_pdf?.toString() || '', - customerId: invoice.customer?.toString() || '', - }; - }); - } - - public async generateInvoiceAndChargeCustomer( - customerId: string, - itemText: string, - amountInUsd: number - ): Promise<void> { - const invoice: Stripe.Invoice = await this.stripe.invoices.create({ - customer: customerId, - auto_advance: true, // do not automatically charge. - collection_method: 'charge_automatically', - }); - - if (!invoice || !invoice.id) { - throw new APIException(Errors.BillingService.INVOICE_NOT_GENERATED); - } - - await this.stripe.invoiceItems.create({ - invoice: invoice.id, - amount: amountInUsd * 100, - description: itemText, - customer: customerId, - }); - - await this.stripe.invoices.finalizeInvoice(invoice.id!); - - try { - await this.payInvoice(customerId, invoice.id!); - } catch (err) { - // mark invoice as failed and do not collect payment. - await this.voidInvoice(invoice.id!); - throw err; - } - } - - public async voidInvoice(invoiceId: string): Promise<Stripe.Invoice> { - const invoice: Stripe.Invoice = await this.stripe.invoices.voidInvoice( - invoiceId - ); - - return invoice; - } - - public async payInvoice( - customerId: string, - invoiceId: string - ): Promise<Invoice> { - // after the invoice is paid, // please fetch subscription and check the status. - const paymentMethods: Array<PaymentMethod> = - await this.getPaymentMethods(customerId); - - if (paymentMethods.length === 0) { - throw new BadDataException( - Errors.BillingService.NO_PAYMENTS_METHODS - ); - } - - const invoice: Stripe.Invoice = await this.stripe.invoices.pay( - invoiceId, - { - payment_method: paymentMethods[0]?.id || '', - } - ); - - return { - id: invoice.id!, - amount: invoice.amount_due, - currencyCode: invoice.currency, - subscriptionId: invoice.subscription?.toString() || undefined, - status: invoice.status?.toString() || 'Unknown', - downloadableLink: invoice.invoice_pdf?.toString() || '', - customerId: invoice.customer?.toString() || '', - }; - } - - public getMeteredPlanPriceId(productType: ProductType): string { - if (productType === ProductType.ActiveMonitoring) { - if (this.isTestEnvironment()) { - return 'price_1N6CHFANuQdJ93r7qDaLmb7S'; - } - - return 'price_1N6B9EANuQdJ93r7fj3bhcWP'; - } - - if (productType === ProductType.Logs) { - if (this.isTestEnvironment()) { - return 'price_1OPnB5ANuQdJ93r7jG4NLCJG'; - } - - return 'price_1OQ8gwANuQdJ93r74Pi85UQq'; - } - - if (productType === ProductType.Traces) { - if (this.isTestEnvironment()) { - return 'price_1OQ8i9ANuQdJ93r75J3wr0PX'; - } - - return 'price_1OQ8ivANuQdJ93r7NAR8KbH3'; - } - - if (productType === ProductType.Metrics) { - if (this.isTestEnvironment()) { - return 'price_1OQ8iqANuQdJ93r7wZ7gJ7Gb'; - } - - return 'price_1OQ8j0ANuQdJ93r7WGzR0p6j'; - } - + // check if this pricing exists + + const pricingExists: boolean = subscription.items.data.some( + (item: SubscriptionItem) => { + return item.price?.id === serverMeteredPlan.getPriceId(); + }, + ); + + if (pricingExists) { + // update the quantity. + const subscriptionItemId: string | undefined = + subscription.items.data.find((item: SubscriptionItem) => { + return item.price?.id === serverMeteredPlan.getPriceId(); + })?.id; + + if (!subscriptionItemId) { throw new BadDataException( - 'Plan with productType ' + productType + ' not found' + Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND, ); + } + + // use stripe usage based api to update the quantity. + await this.stripe.subscriptionItems.createUsageRecord( + subscriptionItemId, + { + quantity: quantity, + }, + ); + } else { + // add the pricing. + const subscriptionItem: SubscriptionItem = + await this.stripe.subscriptionItems.create({ + subscription: subscriptionId, + price: serverMeteredPlan.getPriceId(), + }); + + // use stripe usage based api to update the quantity. + await this.stripe.subscriptionItems.createUsageRecord( + subscriptionItem.id, + { + quantity: quantity, + }, + ); } + + // complete. + } + + public async isPromoCodeValid(promoCode: string): Promise<boolean> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + try { + const promoCodeResponse: Stripe.Response<Stripe.Coupon> = + await this.stripe.coupons.retrieve(promoCode); + + if (!promoCodeResponse) { + throw new BadDataException(Errors.BillingService.PROMO_CODE_NOT_FOUND); + } + + return promoCodeResponse.valid; + } catch (err) { + throw new BadDataException( + (err as Error).message || Errors.BillingService.PROMO_CODE_INVALID, + ); + } + } + + public async removeSubscriptionItem( + subscriptionId: string, + subscriptionItemId: string, + isMeteredSubscriptionItem: boolean, + ): Promise<void> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + + const subscription: Stripe.Response<Stripe.Subscription> = + await this.stripe.subscriptions.retrieve(subscriptionId); + + if (!subscription) { + throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); + } + + if (subscription.status === "canceled") { + // subscription is canceled. + return; + } + + const subscriptionItemOptions: Stripe.SubscriptionItemDeleteParams = + isMeteredSubscriptionItem + ? { + proration_behavior: "create_prorations", + clear_usage: true, + } + : {}; + + await this.stripe.subscriptionItems.del( + subscriptionItemId, + subscriptionItemOptions, + ); + } + + public async getSubscriptionItems( + subscriptionId: string, + ): Promise<Array<SubscriptionItem>> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + + const subscription: Stripe.Response<Stripe.Subscription> = + await this.stripe.subscriptions.retrieve(subscriptionId); + + if (!subscription) { + throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); + } + + return subscription.items.data; + } + + public async changePlan(data: { + projectId: ObjectID; + subscriptionId: string; + meteredSubscriptionId: string; + serverMeteredPlans: Array<ServerMeteredPlan>; + newPlan: SubscriptionPlan; + quantity: number; + isYearly: boolean; + endTrialAt?: Date | undefined; + }): Promise<{ + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt?: Date | undefined; + }> { + logger.debug("Changing plan"); + logger.debug(data); + + if (!this.isBillingEnabled()) { + logger.debug(Errors.BillingService.BILLING_NOT_ENABLED); + + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + + const subscription: Stripe.Response<Stripe.Subscription> = + await this.stripe.subscriptions.retrieve(data.subscriptionId); + + logger.debug("Subscription"); + logger.debug(subscription); + + if (!subscription) { + logger.debug(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); + throw new BadDataException(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); + } + + logger.debug("Subscription status"); + logger.debug(subscription.status); + + const paymentMethods: Array<PaymentMethod> = await this.getPaymentMethods( + subscription.customer.toString(), + ); + + logger.debug("Payment methods"); + logger.debug(paymentMethods); + + if (paymentMethods.length === 0) { + logger.debug("No payment methods"); + + throw new BadDataException(Errors.BillingService.NO_PAYMENTS_METHODS); + } + + logger.debug("Cancelling subscriptions"); + logger.debug(data.subscriptionId); + await this.cancelSubscription(data.subscriptionId); + + logger.debug("Cancelling metered subscriptions"); + logger.debug(data.meteredSubscriptionId); + await this.cancelSubscription(data.meteredSubscriptionId); + + if (data.endTrialAt && !OneUptimeDate.isInTheFuture(data.endTrialAt)) { + data.endTrialAt = undefined; + } + + logger.debug("Subscribing to plan"); + + const subscribeToPlan: { + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt: Date | null; + } = await this.subscribeToPlan({ + projectId: data.projectId, + customerId: subscription.customer.toString(), + serverMeteredPlans: data.serverMeteredPlans, + plan: data.newPlan, + quantity: data.quantity, + isYearly: data.isYearly, + trial: data.endTrialAt, + defaultPaymentMethodId: paymentMethods[0]?.id, + promoCode: undefined, + }); + + logger.debug("Subscribed to plan"); + + const value: { + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt?: Date | undefined; + } = { + subscriptionId: subscribeToPlan.subscriptionId, + meteredSubscriptionId: subscribeToPlan.meteredSubscriptionId, + trialEndsAt: subscribeToPlan.trialEndsAt || undefined, + }; + + return value; + } + + public async deletePaymentMethod( + customerId: string, + paymentMethodId: string, + ): Promise<void> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + + const paymentMethods: Array<PaymentMethod> = + await this.getPaymentMethods(customerId); + + if (paymentMethods.length === 1) { + throw new BadDataException( + Errors.BillingService.MIN_REQUIRED_PAYMENT_METHOD_NOT_MET, + ); + } + + await this.stripe.paymentMethods.detach(paymentMethodId); + } + + public async hasPaymentMethods(customerId: string): Promise<boolean> { + if ((await this.getPaymentMethods(customerId)).length > 0) { + return true; + } + + return false; + } + + public async setDefaultPaymentMethod( + customerId: string, + paymentMethodId: string, + ): Promise<void> { + await this.stripe.customers.update(customerId, { + invoice_settings: { + default_payment_method: paymentMethodId, + }, + }); + } + + public async getPaymentMethods( + customerId: string, + ): Promise<Array<PaymentMethod>> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + const paymentMethods: Array<PaymentMethod> = []; + + const cardPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> = + await this.stripe.paymentMethods.list({ + customer: customerId, + type: "card", + }); + + const sepaPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> = + await this.stripe.paymentMethods.list({ + customer: customerId, + type: "sepa_debit", + }); + + const usBankPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> = + await this.stripe.paymentMethods.list({ + customer: customerId, + type: "us_bank_account", + }); + + const bacsPaymentMethods: Stripe.ApiList<Stripe.PaymentMethod> = + await this.stripe.paymentMethods.list({ + customer: customerId, + type: "bacs_debit", + }); + + cardPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => { + paymentMethods.push({ + type: item.card?.brand || "Card", + last4Digits: item.card?.last4 || "xxxx", + isDefault: false, + id: item.id, + }); + }); + + bacsPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => { + paymentMethods.push({ + type: "UK Bank Account", + last4Digits: item.bacs_debit?.last4 || "xxxx", + isDefault: false, + id: item.id, + }); + }); + + usBankPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => { + paymentMethods.push({ + type: "US Bank Account", + last4Digits: item.us_bank_account?.last4 || "xxxx", + isDefault: false, + id: item.id, + }); + }); + + sepaPaymentMethods.data.forEach((item: Stripe.PaymentMethod) => { + paymentMethods.push({ + type: "EU Bank Account", + last4Digits: item.sepa_debit?.last4 || "xxxx", + isDefault: false, + id: item.id, + }); + }); + + // check if there's a default payment method. + + const customer: Stripe.Response<Stripe.Customer | Stripe.DeletedCustomer> = + await this.stripe.customers.retrieve(customerId); + + if ( + (customer as Stripe.Customer).invoice_settings && + !(customer as Stripe.Customer).invoice_settings?.default_payment_method + ) { + // set the first payment method as default. + if (paymentMethods.length > 0 && paymentMethods[0]?.id) { + await this.setDefaultPaymentMethod(customerId, paymentMethods[0]?.id); + } + } + + return paymentMethods; + } + + public async getSetupIntentSecret(customerId: string): Promise<string> { + const setupIntent: Stripe.Response<Stripe.SetupIntent> = + await this.stripe.setupIntents.create({ + customer: customerId, + }); + + if (!setupIntent.client_secret) { + throw new APIException(Errors.BillingService.CLIENT_SECRET_MISSING); + } + + return setupIntent.client_secret; + } + + public async cancelSubscription(subscriptionId: string): Promise<void> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + try { + await this.stripe.subscriptions.del(subscriptionId); + } catch (err) { + logger.error(err); + } + } + + public async getSubscriptionStatus( + subscriptionId: string, + ): Promise<SubscriptionStatus> { + const subscription: Stripe.Subscription = + await this.getSubscription(subscriptionId); + return subscription.status as SubscriptionStatus; + } + + public async getSubscription( + subscriptionId: string, + ): Promise<Stripe.Subscription> { + if (!this.isBillingEnabled()) { + throw new BadDataException(Errors.BillingService.BILLING_NOT_ENABLED); + } + + const subscription: Stripe.Response<Stripe.Subscription> = + await this.stripe.subscriptions.retrieve(subscriptionId); + + return subscription; + } + + public async getInvoices(customerId: string): Promise<Array<Invoice>> { + const invoices: Stripe.ApiList<Stripe.Invoice> = + await this.stripe.invoices.list({ + customer: customerId, + limit: 100, + }); + + return invoices.data.map((invoice: Stripe.Invoice) => { + return { + id: invoice.id!, + amount: invoice.amount_due, + currencyCode: invoice.currency, + subscriptionId: invoice.subscription?.toString() || undefined, + status: invoice.status?.toString() || "Unknown", + downloadableLink: invoice.invoice_pdf?.toString() || "", + customerId: invoice.customer?.toString() || "", + }; + }); + } + + public async generateInvoiceAndChargeCustomer( + customerId: string, + itemText: string, + amountInUsd: number, + ): Promise<void> { + const invoice: Stripe.Invoice = await this.stripe.invoices.create({ + customer: customerId, + auto_advance: true, // do not automatically charge. + collection_method: "charge_automatically", + }); + + if (!invoice || !invoice.id) { + throw new APIException(Errors.BillingService.INVOICE_NOT_GENERATED); + } + + await this.stripe.invoiceItems.create({ + invoice: invoice.id, + amount: amountInUsd * 100, + description: itemText, + customer: customerId, + }); + + await this.stripe.invoices.finalizeInvoice(invoice.id!); + + try { + await this.payInvoice(customerId, invoice.id!); + } catch (err) { + // mark invoice as failed and do not collect payment. + await this.voidInvoice(invoice.id!); + throw err; + } + } + + public async voidInvoice(invoiceId: string): Promise<Stripe.Invoice> { + const invoice: Stripe.Invoice = + await this.stripe.invoices.voidInvoice(invoiceId); + + return invoice; + } + + public async payInvoice( + customerId: string, + invoiceId: string, + ): Promise<Invoice> { + // after the invoice is paid, // please fetch subscription and check the status. + const paymentMethods: Array<PaymentMethod> = + await this.getPaymentMethods(customerId); + + if (paymentMethods.length === 0) { + throw new BadDataException(Errors.BillingService.NO_PAYMENTS_METHODS); + } + + const invoice: Stripe.Invoice = await this.stripe.invoices.pay(invoiceId, { + payment_method: paymentMethods[0]?.id || "", + }); + + return { + id: invoice.id!, + amount: invoice.amount_due, + currencyCode: invoice.currency, + subscriptionId: invoice.subscription?.toString() || undefined, + status: invoice.status?.toString() || "Unknown", + downloadableLink: invoice.invoice_pdf?.toString() || "", + customerId: invoice.customer?.toString() || "", + }; + } + + public getMeteredPlanPriceId(productType: ProductType): string { + if (productType === ProductType.ActiveMonitoring) { + if (this.isTestEnvironment()) { + return "price_1N6CHFANuQdJ93r7qDaLmb7S"; + } + + return "price_1N6B9EANuQdJ93r7fj3bhcWP"; + } + + if (productType === ProductType.Logs) { + if (this.isTestEnvironment()) { + return "price_1OPnB5ANuQdJ93r7jG4NLCJG"; + } + + return "price_1OQ8gwANuQdJ93r74Pi85UQq"; + } + + if (productType === ProductType.Traces) { + if (this.isTestEnvironment()) { + return "price_1OQ8i9ANuQdJ93r75J3wr0PX"; + } + + return "price_1OQ8ivANuQdJ93r7NAR8KbH3"; + } + + if (productType === ProductType.Metrics) { + if (this.isTestEnvironment()) { + return "price_1OQ8iqANuQdJ93r7wZ7gJ7Gb"; + } + + return "price_1OQ8j0ANuQdJ93r7WGzR0p6j"; + } + + throw new BadDataException( + "Plan with productType " + productType + " not found", + ); + } } export default new BillingService(); diff --git a/CommonServer/Services/CallLogService.ts b/CommonServer/Services/CallLogService.ts index b2c8425851..09ad6f5238 100644 --- a/CommonServer/Services/CallLogService.ts +++ b/CommonServer/Services/CallLogService.ts @@ -1,12 +1,12 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/CallLog'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/CallLog"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 3); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 3); + } } export default new Service(); diff --git a/CommonServer/Services/CallService.ts b/CommonServer/Services/CallService.ts index 3eba6c49bc..0e86d861c7 100644 --- a/CommonServer/Services/CallService.ts +++ b/CommonServer/Services/CallService.ts @@ -1,59 +1,57 @@ -import { AppApiHostname } from '../EnvironmentConfig'; -import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization'; -import BaseService from './BaseService'; -import EmptyResponseData from 'Common/Types/API/EmptyResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import CallRequest from 'Common/Types/Call/CallRequest'; -import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import API from 'Common/Utils/API'; +import { AppApiHostname } from "../EnvironmentConfig"; +import ClusterKeyAuthorization from "../Middleware/ClusterKeyAuthorization"; +import BaseService from "./BaseService"; +import EmptyResponseData from "Common/Types/API/EmptyResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import CallRequest from "Common/Types/Call/CallRequest"; +import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import API from "Common/Utils/API"; export class CallService extends BaseService { - public constructor() { - super(); - } + public constructor() { + super(); + } - public async makeCall( - callRequest: CallRequest, - options: { - projectId?: ObjectID | undefined; // project id for sms log - isSensitive?: boolean; // if true, message will not be logged - userOnCallLogTimelineId?: ObjectID; - customTwilioConfig?: TwilioConfig | undefined; - } - ): Promise<HTTPResponse<EmptyResponseData>> { - const body: JSONObject = { - callRequest: callRequest, - projectId: options.projectId?.toString(), - isSensitive: options.isSensitive, - userOnCallLogTimelineId: - options.userOnCallLogTimelineId?.toString(), - customTwilioConfig: options.customTwilioConfig - ? { - accountSid: options.customTwilioConfig.accountSid!, - authToken: options.customTwilioConfig.authToken!, - phoneNumber: - options.customTwilioConfig.phoneNumber.toString(), - } - : undefined, - }; + public async makeCall( + callRequest: CallRequest, + options: { + projectId?: ObjectID | undefined; // project id for sms log + isSensitive?: boolean; // if true, message will not be logged + userOnCallLogTimelineId?: ObjectID; + customTwilioConfig?: TwilioConfig | undefined; + }, + ): Promise<HTTPResponse<EmptyResponseData>> { + const body: JSONObject = { + callRequest: callRequest, + projectId: options.projectId?.toString(), + isSensitive: options.isSensitive, + userOnCallLogTimelineId: options.userOnCallLogTimelineId?.toString(), + customTwilioConfig: options.customTwilioConfig + ? { + accountSid: options.customTwilioConfig.accountSid!, + authToken: options.customTwilioConfig.authToken!, + phoneNumber: options.customTwilioConfig.phoneNumber.toString(), + } + : undefined, + }; - return await API.post<EmptyResponseData>( - new URL( - Protocol.HTTP, - AppApiHostname, - new Route('/api/notification/call/make-call') - ), - body, - { - ...ClusterKeyAuthorization.getClusterKeyHeaders(), - } - ); - } + return await API.post<EmptyResponseData>( + new URL( + Protocol.HTTP, + AppApiHostname, + new Route("/api/notification/call/make-call"), + ), + body, + { + ...ClusterKeyAuthorization.getClusterKeyHeaders(), + }, + ); + } } export default new CallService(); diff --git a/CommonServer/Services/CodeRepositoryService.ts b/CommonServer/Services/CodeRepositoryService.ts index de9a05ab34..5d5ca30d8b 100644 --- a/CommonServer/Services/CodeRepositoryService.ts +++ b/CommonServer/Services/CodeRepositoryService.ts @@ -1,25 +1,25 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import ObjectID from 'Common/Types/ObjectID'; -import Model from 'Model/Models/CodeRepository'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import ObjectID from "Common/Types/ObjectID"; +import Model from "Model/Models/CodeRepository"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - createBy.data.secretToken = ObjectID.generate(); + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + createBy.data.secretToken = ObjectID.generate(); - return { - carryForward: null, - createBy: createBy, - }; - } + return { + carryForward: null, + createBy: createBy, + }; + } } export default new Service(); diff --git a/CommonServer/Services/CopilotEventService.ts b/CommonServer/Services/CopilotEventService.ts index fde6cd88fd..0356999110 100644 --- a/CommonServer/Services/CopilotEventService.ts +++ b/CommonServer/Services/CopilotEventService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/CopilotEvent'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/CopilotEvent"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/DataMigrationService.ts b/CommonServer/Services/DataMigrationService.ts index b7d33cab78..982613abc1 100644 --- a/CommonServer/Services/DataMigrationService.ts +++ b/CommonServer/Services/DataMigrationService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/DataMigration'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/DataMigration"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/DatabaseService.ts b/CommonServer/Services/DatabaseService.ts index 7fac09fd30..801647a0e5 100644 --- a/CommonServer/Services/DatabaseService.ts +++ b/CommonServer/Services/DatabaseService.ts @@ -1,1567 +1,1500 @@ -import { AppApiHostname, EncryptionSecret } from '../EnvironmentConfig'; +import { AppApiHostname, EncryptionSecret } from "../EnvironmentConfig"; import PostgresDatabase, { - PostgresAppInstance, -} from '../Infrastructure/PostgresDatabase'; -import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization'; -import CountBy from '../Types/Database/CountBy'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import DeleteById from '../Types/Database/DeleteById'; -import DeleteOneBy from '../Types/Database/DeleteOneBy'; -import FindBy from '../Types/Database/FindBy'; -import FindOneBy from '../Types/Database/FindOneBy'; -import FindOneByID from '../Types/Database/FindOneByID'; + PostgresAppInstance, +} from "../Infrastructure/PostgresDatabase"; +import ClusterKeyAuthorization from "../Middleware/ClusterKeyAuthorization"; +import CountBy from "../Types/Database/CountBy"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import DeleteById from "../Types/Database/DeleteById"; +import DeleteOneBy from "../Types/Database/DeleteOneBy"; +import FindBy from "../Types/Database/FindBy"; +import FindOneBy from "../Types/Database/FindOneBy"; +import FindOneByID from "../Types/Database/FindOneByID"; import { - DatabaseTriggerType, - OnCreate, - OnDelete, - OnFind, - OnUpdate, -} from '../Types/Database/Hooks'; -import ModelPermission from '../Types/Database/Permissions/Index'; -import { CheckReadPermissionType } from '../Types/Database/Permissions/ReadPermission'; -import Query, { FindWhere } from '../Types/Database/Query'; -import QueryHelper from '../Types/Database/QueryHelper'; -import RelationSelect from '../Types/Database/RelationSelect'; -import SearchBy from '../Types/Database/SearchBy'; -import SearchResult from '../Types/Database/SearchResult'; -import Select from '../Types/Database/Select'; -import UpdateBy from '../Types/Database/UpdateBy'; -import UpdateByID from '../Types/Database/UpdateByID'; -import UpdateByIDAndFetch from '../Types/Database/UpdateByIDAndFetch'; -import UpdateOneBy from '../Types/Database/UpdateOneBy'; -import Encryption from '../Utils/Encryption'; -import logger from '../Utils/Logger'; -import BaseService from './BaseService'; -import BaseModel from 'Common/Models/BaseModel'; -import { WorkflowRoute } from 'Common/ServiceRoute'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { getMaxLengthFromTableColumnType } from 'Common/Types/Database/ColumnLength'; -import Columns from 'Common/Types/Database/Columns'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import PartialEntity from 'Common/Types/Database/PartialEntity'; -import { TableColumnMetadata } from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import { getUniqueColumnsBy } from 'Common/Types/Database/UniqueColumnBy'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import DatabaseNotConnectedException from 'Common/Types/Exception/DatabaseNotConnectedException'; -import Exception from 'Common/Types/Exception/Exception'; -import HashedString from 'Common/Types/HashedString'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Text from 'Common/Types/Text'; -import Typeof from 'Common/Types/Typeof'; -import API from 'Common/Utils/API'; -import Slug from 'Common/Utils/Slug'; -import { DataSource, Repository, SelectQueryBuilder } from 'typeorm'; -import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; + DatabaseTriggerType, + OnCreate, + OnDelete, + OnFind, + OnUpdate, +} from "../Types/Database/Hooks"; +import ModelPermission from "../Types/Database/Permissions/Index"; +import { CheckReadPermissionType } from "../Types/Database/Permissions/ReadPermission"; +import Query, { FindWhere } from "../Types/Database/Query"; +import QueryHelper from "../Types/Database/QueryHelper"; +import RelationSelect from "../Types/Database/RelationSelect"; +import SearchBy from "../Types/Database/SearchBy"; +import SearchResult from "../Types/Database/SearchResult"; +import Select from "../Types/Database/Select"; +import UpdateBy from "../Types/Database/UpdateBy"; +import UpdateByID from "../Types/Database/UpdateByID"; +import UpdateByIDAndFetch from "../Types/Database/UpdateByIDAndFetch"; +import UpdateOneBy from "../Types/Database/UpdateOneBy"; +import Encryption from "../Utils/Encryption"; +import logger from "../Utils/Logger"; +import BaseService from "./BaseService"; +import BaseModel from "Common/Models/BaseModel"; +import { WorkflowRoute } from "Common/ServiceRoute"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { getMaxLengthFromTableColumnType } from "Common/Types/Database/ColumnLength"; +import Columns from "Common/Types/Database/Columns"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import PartialEntity from "Common/Types/Database/PartialEntity"; +import { TableColumnMetadata } from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import { getUniqueColumnsBy } from "Common/Types/Database/UniqueColumnBy"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import DatabaseNotConnectedException from "Common/Types/Exception/DatabaseNotConnectedException"; +import Exception from "Common/Types/Exception/Exception"; +import HashedString from "Common/Types/HashedString"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Text from "Common/Types/Text"; +import Typeof from "Common/Types/Typeof"; +import API from "Common/Utils/API"; +import Slug from "Common/Utils/Slug"; +import { DataSource, Repository, SelectQueryBuilder } from "typeorm"; +import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"; class DatabaseService<TBaseModel extends BaseModel> extends BaseService { - private postgresDatabase!: PostgresDatabase; - public modelType!: { new (): TBaseModel }; - private model!: TBaseModel; - private modelName!: string; + private postgresDatabase!: PostgresDatabase; + public modelType!: { new (): TBaseModel }; + private model!: TBaseModel; + private modelName!: string; - private _hardDeleteItemByColumnName: string = ''; - public get hardDeleteItemByColumnName(): string { - return this._hardDeleteItemByColumnName; + private _hardDeleteItemByColumnName: string = ""; + public get hardDeleteItemByColumnName(): string { + return this._hardDeleteItemByColumnName; + } + public set hardDeleteItemByColumnName(v: string) { + this._hardDeleteItemByColumnName = v; + } + + private _hardDeleteItemsOlderThanDays: number = 0; + public get hardDeleteItemsOlderThanDays(): number { + return this._hardDeleteItemsOlderThanDays; + } + public set hardDeleteItemsOlderThanDays(v: number) { + this._hardDeleteItemsOlderThanDays = v; + } + + public doNotAllowDelete: boolean = false; + + public constructor( + modelType: { new (): TBaseModel }, + postgresDatabase?: PostgresDatabase, + ) { + super(); + this.modelType = modelType; + this.model = new modelType(); + this.modelName = modelType.name; + + if (postgresDatabase) { + this.postgresDatabase = postgresDatabase; } - public set hardDeleteItemByColumnName(v: string) { - this._hardDeleteItemByColumnName = v; + } + + public setDoNotAllowDelete(doNotAllowDelete: boolean): void { + this.doNotAllowDelete = doNotAllowDelete; + } + + public hardDeleteItemsOlderThanInDays( + columnName: string, + olderThan: number, + ): void { + this.hardDeleteItemByColumnName = columnName; + this.hardDeleteItemsOlderThanDays = olderThan; + } + + public getModel(): TBaseModel { + return this.model; + } + + public getQueryBuilder(modelName: string): SelectQueryBuilder<TBaseModel> { + return this.getRepository().createQueryBuilder(modelName); + } + + public getRepository(): Repository<TBaseModel> { + if (this.postgresDatabase && !this.postgresDatabase.isConnected()) { + throw new DatabaseNotConnectedException(); } - private _hardDeleteItemsOlderThanDays: number = 0; - public get hardDeleteItemsOlderThanDays(): number { - return this._hardDeleteItemsOlderThanDays; - } - public set hardDeleteItemsOlderThanDays(v: number) { - this._hardDeleteItemsOlderThanDays = v; + if (!this.postgresDatabase && !PostgresAppInstance.isConnected()) { + throw new DatabaseNotConnectedException(); } - public doNotAllowDelete: boolean = false; + const dataSource: DataSource | null = this.postgresDatabase + ? this.postgresDatabase.getDataSource() + : PostgresAppInstance.getDataSource(); - public constructor( - modelType: { new (): TBaseModel }, - postgresDatabase?: PostgresDatabase - ) { - super(); - this.modelType = modelType; - this.model = new modelType(); - this.modelName = modelType.name; - - if (postgresDatabase) { - this.postgresDatabase = postgresDatabase; - } + if (dataSource) { + return dataSource.getRepository<TBaseModel>(this.modelType.name); } - public setDoNotAllowDelete(doNotAllowDelete: boolean): void { - this.doNotAllowDelete = doNotAllowDelete; + throw new DatabaseNotConnectedException(); + } + + protected isValid(data: TBaseModel): boolean { + if (!data) { + throw new BadDataException("Data cannot be null"); } - public hardDeleteItemsOlderThanInDays( - columnName: string, - olderThan: number - ): void { - this.hardDeleteItemByColumnName = columnName; - this.hardDeleteItemsOlderThanDays = olderThan; + return true; + } + + protected generateDefaultValues(data: TBaseModel): TBaseModel { + const tableColumns: Array<string> = data.getTableColumns().columns; + + for (const column of tableColumns) { + const metadata: TableColumnMetadata = data.getTableColumnMetadata(column); + if (metadata.forceGetDefaultValueOnCreate) { + (data as any)[column] = metadata.forceGetDefaultValueOnCreate(); + } } - public getModel(): TBaseModel { - return this.model; - } + return data; + } - public getQueryBuilder(modelName: string): SelectQueryBuilder<TBaseModel> { - return this.getRepository().createQueryBuilder(modelName); - } + protected async checkForUniqueValues(data: TBaseModel): Promise<TBaseModel> { + const tableColumns: Array<string> = data.getTableColumns().columns; - public getRepository(): Repository<TBaseModel> { - if (this.postgresDatabase && !this.postgresDatabase.isConnected()) { - throw new DatabaseNotConnectedException(); - } - - if (!this.postgresDatabase && !PostgresAppInstance.isConnected()) { - throw new DatabaseNotConnectedException(); - } - - const dataSource: DataSource | null = this.postgresDatabase - ? this.postgresDatabase.getDataSource() - : PostgresAppInstance.getDataSource(); - - if (dataSource) { - return dataSource.getRepository<TBaseModel>(this.modelType.name); - } - - throw new DatabaseNotConnectedException(); - } - - protected isValid(data: TBaseModel): boolean { - if (!data) { - throw new BadDataException('Data cannot be null'); - } - - return true; - } - - protected generateDefaultValues(data: TBaseModel): TBaseModel { - const tableColumns: Array<string> = data.getTableColumns().columns; - - for (const column of tableColumns) { - const metadata: TableColumnMetadata = - data.getTableColumnMetadata(column); - if (metadata.forceGetDefaultValueOnCreate) { - (data as any)[column] = metadata.forceGetDefaultValueOnCreate(); - } - } - - return data; - } - - protected async checkForUniqueValues( - data: TBaseModel - ): Promise<TBaseModel> { - const tableColumns: Array<string> = data.getTableColumns().columns; - - for (const columnName of tableColumns) { - const metadata: TableColumnMetadata = - data.getTableColumnMetadata(columnName); - if (metadata.unique && data.getColumnValue(columnName)) { - // check for unique values. - const count: PositiveNumber = await this.countBy({ - query: { - [columnName]: data.getColumnValue(columnName), - } as any, - props: { - isRoot: true, - }, - }); - - if (count.toNumber() > 0) { - throw new BadDataException( - `${metadata.title} ${data - .getColumnValue(columnName) - ?.toString()} already exists. Please choose a different ${ - metadata.title - }` - ); - } - } - } - - return data; - } - - protected checkRequiredFields(data: TBaseModel): TBaseModel { - // Check required fields. - - const relationalColumns: Dictionary<string> = {}; - - const tableColumns: Array<string> = data.getTableColumns().columns; - - for (const column of tableColumns) { - const metadata: TableColumnMetadata = - data.getTableColumnMetadata(column); - if (metadata.manyToOneRelationColumn) { - relationalColumns[metadata.manyToOneRelationColumn] = column; - } - } - - for (const requiredField of data.getRequiredColumns().columns) { - if (typeof (data as any)[requiredField] === Typeof.Boolean) { - if ( - !(data as any)[requiredField] && - (data as any)[requiredField] !== false && - !data.isDefaultValueColumn(requiredField) - ) { - throw new BadDataException(`${requiredField} is required`); - } - } else if ( - !(data as any)[requiredField] && - !data.isDefaultValueColumn(requiredField) - ) { - const metadata: TableColumnMetadata = - data.getTableColumnMetadata(requiredField); - - if ( - metadata && - metadata.manyToOneRelationColumn && - metadata.type === TableColumnType.Entity && - data.getColumnValue(metadata.manyToOneRelationColumn) - ) { - continue; - } - - if ( - relationalColumns[requiredField] && - data.getColumnValue( - relationalColumns[requiredField] as string - ) - ) { - continue; - } - - throw new BadDataException(`${requiredField} is required`); - } - } - - return data; - } - - protected async onBeforeCreate( - createBy: CreateBy<TBaseModel> - ): Promise<OnCreate<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ - createBy: createBy as CreateBy<TBaseModel>, - carryForward: undefined, + for (const columnName of tableColumns) { + const metadata: TableColumnMetadata = + data.getTableColumnMetadata(columnName); + if (metadata.unique && data.getColumnValue(columnName)) { + // check for unique values. + const count: PositiveNumber = await this.countBy({ + query: { + [columnName]: data.getColumnValue(columnName), + } as any, + props: { + isRoot: true, + }, }); - } - private async _onBeforeCreate( - createBy: CreateBy<TBaseModel> - ): Promise<OnCreate<TBaseModel>> { - // Private method that runs before create. - const projectIdColumn: string | null = this.model.getTenantColumn(); - - if (projectIdColumn && createBy.props.tenantId) { - (createBy.data as any)[projectIdColumn] = createBy.props.tenantId; + if (count.toNumber() > 0) { + throw new BadDataException( + `${metadata.title} ${data + .getColumnValue(columnName) + ?.toString()} already exists. Please choose a different ${ + metadata.title + }`, + ); } - - return await this.onBeforeCreate(createBy); + } } - protected async encrypt( - data: TBaseModel | PartialEntity<TBaseModel> - ): Promise<TBaseModel | PartialEntity<TBaseModel>> { - for (const key of this.model.getEncryptedColumns().columns) { - if (!(data as any)[key]) { - continue; - } + return data; + } - // If data is an object. - if (typeof (data as any)[key] === Typeof.Object) { - const dataObj: JSONObject = (data as any)[key] as JSONObject; + protected checkRequiredFields(data: TBaseModel): TBaseModel { + // Check required fields. - for (const key in dataObj) { - dataObj[key] = await Encryption.encrypt( - dataObj[key] as string - ); - } + const relationalColumns: Dictionary<string> = {}; - (data as any)[key] = dataObj; - } else { - //If its string or other type. - (data as any)[key] = await Encryption.encrypt( - (data as any)[key] as string - ); - } + const tableColumns: Array<string> = data.getTableColumns().columns; + + for (const column of tableColumns) { + const metadata: TableColumnMetadata = data.getTableColumnMetadata(column); + if (metadata.manyToOneRelationColumn) { + relationalColumns[metadata.manyToOneRelationColumn] = column; + } + } + + for (const requiredField of data.getRequiredColumns().columns) { + if (typeof (data as any)[requiredField] === Typeof.Boolean) { + if ( + !(data as any)[requiredField] && + (data as any)[requiredField] !== false && + !data.isDefaultValueColumn(requiredField) + ) { + throw new BadDataException(`${requiredField} is required`); } - - return data; - } - - protected async hash(data: TBaseModel): Promise<TBaseModel> { - const columns: Columns = data.getHashedColumns(); - - for (const key of columns.columns) { - if ( - data.hasValue(key) && - !(data.getValue(key) as HashedString).isValueHashed() - ) { - await ((data as any)[key] as HashedString).hashValue( - EncryptionSecret - ); - } - } - - return data; - } - - protected async decrypt(data: TBaseModel): Promise<TBaseModel> { - for (const key of data.getEncryptedColumns().columns) { - if (!data.hasValue(key)) { - continue; - } - - // If data is an object. - if (typeof data.getValue(key) === Typeof.Object) { - const dataObj: JSONObject = data.getValue(key) as JSONObject; - - for (const key in dataObj) { - dataObj[key] = await Encryption.decrypt( - dataObj[key] as string - ); - } - - data.setValue(key, dataObj); - } else { - //If its string or other type. - data.setValue( - key, - await Encryption.decrypt((data as any)[key]) - ); - } - } - - return data; - } - - protected async onBeforeDelete( - deleteBy: DeleteBy<TBaseModel> - ): Promise<OnDelete<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ deleteBy, carryForward: null }); - } - - protected async onBeforeUpdate( - updateBy: UpdateBy<TBaseModel> - ): Promise<OnUpdate<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ updateBy, carryForward: null }); - } - - protected async onBeforeFind( - findBy: FindBy<TBaseModel> - ): Promise<OnFind<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ findBy, carryForward: null }); - } - - protected async onCreateSuccess( - _onCreate: OnCreate<TBaseModel>, - createdItem: TBaseModel - ): Promise<TBaseModel> { - // A place holder method used for overriding. - return Promise.resolve(createdItem); - } - - protected async onCreateError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected async onUpdateSuccess( - onUpdate: OnUpdate<TBaseModel>, - _updatedItemIds: Array<ObjectID> - ): Promise<OnUpdate<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve(onUpdate); - } - - protected async onUpdateError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected async onDeleteSuccess( - onDelete: OnDelete<TBaseModel>, - _itemIdsBeforeDelete: Array<ObjectID> - ): Promise<OnDelete<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve(onDelete); - } - - protected async onDeleteError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected async onFindSuccess( - onFind: OnFind<TBaseModel>, - items: Array<TBaseModel> - ): Promise<OnFind<TBaseModel>> { - // A place holder method used for overriding. - return Promise.resolve({ ...onFind, carryForward: items }); - } - - protected async onFindError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected async onCountSuccess( - count: PositiveNumber - ): Promise<PositiveNumber> { - // A place holder method used for overriding. - return Promise.resolve(count); - } - - protected async onCountError(error: Exception): Promise<Exception> { - // A place holder method used for overriding. - return Promise.resolve(error); - } - - protected async getException(error: Exception): Promise<void> { - throw error; - } - - private generateSlug(createBy: CreateBy<TBaseModel>): CreateBy<TBaseModel> { - if (createBy.data.getSlugifyColumn()) { - (createBy.data as any)[ - createBy.data.getSaveSlugToColumn() as string - ] = Slug.getSlug( - (createBy.data as any)[ - createBy.data.getSlugifyColumn() as string - ] - ? ((createBy.data as any)[ - createBy.data.getSlugifyColumn() as string - ] as string) - : null - ); - } - - return createBy; - } - - private async sanitizeCreateOrUpdate( - data: TBaseModel | QueryDeepPartialEntity<TBaseModel>, - props: DatabaseCommonInteractionProps, - isUpdate: boolean = false - ): Promise<TBaseModel | QueryDeepPartialEntity<TBaseModel>> { - data = this.checkMaxLengthOfFields(data as TBaseModel); - - const columns: Columns = this.model.getTableColumns(); - - for (const columnName of columns.columns) { - if (this.model.isEntityColumn(columnName)) { - const tableColumnMetadata: TableColumnMetadata = - this.model.getTableColumnMetadata(columnName); - - const columnValue: JSONValue = (data as any)[columnName]; - - if ( - data && - columnName && - tableColumnMetadata.modelType && - columnValue && - tableColumnMetadata.type === TableColumnType.Entity && - (typeof columnValue === 'string' || - columnValue instanceof ObjectID) - ) { - const relatedType: BaseModel = - new tableColumnMetadata.modelType(); - relatedType._id = columnValue.toString(); - (data as any)[columnName] = relatedType; - } - - if ( - data && - Array.isArray(columnValue) && - columnValue.length > 0 && - tableColumnMetadata.modelType && - columnValue && - tableColumnMetadata.type === TableColumnType.EntityArray - ) { - const itemsArray: Array<BaseModel> = []; - for (const item of columnValue) { - if ( - typeof item === 'string' || - item instanceof ObjectID - ) { - const basemodelItem: BaseModel = - new tableColumnMetadata.modelType(); - basemodelItem._id = item.toString(); - itemsArray.push(basemodelItem); - } else if ( - item && - typeof item === Typeof.Object && - (item as JSONObject)['_id'] && - typeof (item as JSONObject)['_id'] === Typeof.String - ) { - const basemodelItem: BaseModel = - new tableColumnMetadata.modelType(); - basemodelItem._id = ( - (item as JSONObject)['_id'] as string - ).toString(); - itemsArray.push(basemodelItem); - } else if (item instanceof BaseModel) { - itemsArray.push(item); - } - } - (data as any)[columnName] = itemsArray; - } - } - - if (this.model.isHashedStringColumn(columnName)) { - const columnValue: JSONValue = (data as any)[columnName]; - - if ( - data && - columnName && - columnValue && - columnValue instanceof HashedString - ) { - if (!columnValue.isValueHashed()) { - await columnValue.hashValue(EncryptionSecret); - } - - (data as any)[columnName] = columnValue.toString(); - } - } - } - - // check createByUserId. - - if (!isUpdate && props.userId) { - (data as any)['createdByUserId'] = props.userId; - } - - return data; - } - - public async onTrigger( - id: ObjectID, - projectId: ObjectID, - triggerType: DatabaseTriggerType, - miscData?: JSONObject | undefined // miscData is used for passing data to workflow. - ): Promise<void> { - if (this.getModel().enableWorkflowOn) { - API.post( - new URL( - Protocol.HTTP, - AppApiHostname, - new Route( - `/api${WorkflowRoute.toString()}/model/${projectId.toString()}/${Text.pascalCaseToDashes( - this.getModel().tableName! - )}/${triggerType}` - ) - ), - { - data: { - _id: id.toString(), - miscData: miscData, - }, - }, - { - ...ClusterKeyAuthorization.getClusterKeyHeaders(), - } - ).catch((error: Error) => { - logger.error(error); - }); - } - } - - public async create(createBy: CreateBy<TBaseModel>): Promise<TBaseModel> { - const onCreate: OnCreate<TBaseModel> = createBy.props.ignoreHooks - ? { createBy, carryForward: [] } - : await this._onBeforeCreate(createBy); - - let _createdBy: CreateBy<TBaseModel> = onCreate.createBy; - - const carryForward: any = onCreate.carryForward; - - _createdBy = this.generateSlug(_createdBy); - - let data: TBaseModel = _createdBy.data; - - // add tenantId if present. - const tenantColumnName: string | null = data.getTenantColumn(); - - if (tenantColumnName && _createdBy.props.tenantId) { - data.setColumnValue(tenantColumnName, _createdBy.props.tenantId); - } - - data = this.generateDefaultValues(data); - - data = this.checkRequiredFields(data); - - await this.checkForUniqueValues(data); - - if (!this.isValid(data)) { - throw new BadDataException('Data is not valid'); - } - - // check total items by. - - await this.checkTotalItemsBy(_createdBy); - - // Encrypt data - data = (await this.encrypt(data)) as TBaseModel; - - // hash data - data = await this.hash(data); - - ModelPermission.checkCreatePermissions( - this.modelType, - data, - _createdBy.props - ); - - createBy.data = data; - - // check uniqueColumns by: - createBy = await this.checkUniqueColumnBy(createBy); - - // serialize. - createBy.data = (await this.sanitizeCreateOrUpdate( - createBy.data, - createBy.props - )) as TBaseModel; - - try { - createBy.data = await this.getRepository().save(createBy.data); - - if (!createBy.props.ignoreHooks) { - createBy.data = await this.onCreateSuccess( - { - createBy, - carryForward, - }, - createBy.data - ); - } - - // hit workflow.; - if (this.getModel().enableWorkflowOn?.create) { - let tenantId: ObjectID | undefined = createBy.props.tenantId; - - if (!tenantId && this.getModel().getTenantColumn()) { - tenantId = createBy.data.getValue<ObjectID>( - this.getModel().getTenantColumn()! - ); - } - - if (tenantId) { - await this.onTrigger( - createBy.data.id!, - tenantId, - 'on-create' - ); - } - } - - return createBy.data; - } catch (error) { - await this.onCreateError(error as Exception); - throw this.getException(error as Exception); - } - } - - private checkMaxLengthOfFields<TBaseModel extends BaseModel>( - data: TBaseModel - ): TBaseModel { - // Check required fields. - - const tableColumns: Array<string> = - this.model.getTableColumns().columns; - - for (const column of tableColumns) { - const metadata: TableColumnMetadata = - this.model.getTableColumnMetadata(column); - if ( - (data as any)[column] && - metadata.type && - getMaxLengthFromTableColumnType(metadata.type) - ) { - if ( - (data as any)[column].toString().length > - getMaxLengthFromTableColumnType(metadata.type)! - ) { - throw new BadDataException( - `${column} length cannot be more than ${getMaxLengthFromTableColumnType( - metadata.type - )} characters` - ); - } - } - } - - return data; - } - - private async checkTotalItemsBy( - createdBy: CreateBy<TBaseModel> - ): Promise<void> { - const totalItemsColumnName: string | null = - this.model.getTotalItemsByColumnName(); - const totalItemsNumber: number | null = - this.model.getTotalItemsNumber(); - const errorMessage: string | null = - this.model.getTotalItemsByErrorMessage(); + } else if ( + !(data as any)[requiredField] && + !data.isDefaultValueColumn(requiredField) + ) { + const metadata: TableColumnMetadata = + data.getTableColumnMetadata(requiredField); if ( - totalItemsColumnName && - totalItemsNumber && - errorMessage && - createdBy.data.getColumnValue(totalItemsColumnName) + metadata && + metadata.manyToOneRelationColumn && + metadata.type === TableColumnType.Entity && + data.getColumnValue(metadata.manyToOneRelationColumn) ) { - const count: PositiveNumber = await this.countBy({ - query: { - [totalItemsColumnName]: - createdBy.data.getColumnValue(totalItemsColumnName), - } as FindWhere<TBaseModel>, - - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - if (count.positiveNumber > totalItemsNumber - 1) { - throw new BadDataException(errorMessage); - } - } - } - - private async checkUniqueColumnBy( - createBy: CreateBy<TBaseModel> - ): Promise<CreateBy<TBaseModel>> { - let existingItemsWithSameNameCount: number = 0; - - const uniqueColumnsBy: Dictionary<string | Array<string>> = - getUniqueColumnsBy(createBy.data); - - for (const key in uniqueColumnsBy) { - if (!uniqueColumnsBy[key]) { - continue; - } - - if (typeof uniqueColumnsBy[key] === Typeof.String) { - uniqueColumnsBy[key] = [uniqueColumnsBy[key] as string]; - } - - const query: Query<TBaseModel> = {}; - - for (const uniqueByColumnName of uniqueColumnsBy[ - key - ] as Array<string>) { - const columnValue: JSONValue = (createBy.data as any)[ - uniqueByColumnName as string - ]; - if (columnValue === null || columnValue === undefined) { - (query as any)[uniqueByColumnName] = QueryHelper.isNull(); - } else { - (query as any)[uniqueByColumnName] = columnValue; - } - } - - existingItemsWithSameNameCount = ( - await this.countBy({ - query: { - [key]: QueryHelper.findWithSameText( - (createBy.data as any)[key] - ? ((createBy.data as any)[key]! as string) - : '' - ), - ...query, - }, - props: { - isRoot: true, - }, - }) - ).toNumber(); - - if (existingItemsWithSameNameCount > 0) { - throw new BadDataException( - `${this.model.singularName} with the same ${key} already exists.` - ); - } - - existingItemsWithSameNameCount = 0; + continue; } - return Promise.resolve(createBy); + if ( + relationalColumns[requiredField] && + data.getColumnValue(relationalColumns[requiredField] as string) + ) { + continue; + } + + throw new BadDataException(`${requiredField} is required`); + } } - public async countBy({ + return data; + } + + protected async onBeforeCreate( + createBy: CreateBy<TBaseModel>, + ): Promise<OnCreate<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ + createBy: createBy as CreateBy<TBaseModel>, + carryForward: undefined, + }); + } + + private async _onBeforeCreate( + createBy: CreateBy<TBaseModel>, + ): Promise<OnCreate<TBaseModel>> { + // Private method that runs before create. + const projectIdColumn: string | null = this.model.getTenantColumn(); + + if (projectIdColumn && createBy.props.tenantId) { + (createBy.data as any)[projectIdColumn] = createBy.props.tenantId; + } + + return await this.onBeforeCreate(createBy); + } + + protected async encrypt( + data: TBaseModel | PartialEntity<TBaseModel>, + ): Promise<TBaseModel | PartialEntity<TBaseModel>> { + for (const key of this.model.getEncryptedColumns().columns) { + if (!(data as any)[key]) { + continue; + } + + // If data is an object. + if (typeof (data as any)[key] === Typeof.Object) { + const dataObj: JSONObject = (data as any)[key] as JSONObject; + + for (const key in dataObj) { + dataObj[key] = await Encryption.encrypt(dataObj[key] as string); + } + + (data as any)[key] = dataObj; + } else { + //If its string or other type. + (data as any)[key] = await Encryption.encrypt( + (data as any)[key] as string, + ); + } + } + + return data; + } + + protected async hash(data: TBaseModel): Promise<TBaseModel> { + const columns: Columns = data.getHashedColumns(); + + for (const key of columns.columns) { + if ( + data.hasValue(key) && + !(data.getValue(key) as HashedString).isValueHashed() + ) { + await ((data as any)[key] as HashedString).hashValue(EncryptionSecret); + } + } + + return data; + } + + protected async decrypt(data: TBaseModel): Promise<TBaseModel> { + for (const key of data.getEncryptedColumns().columns) { + if (!data.hasValue(key)) { + continue; + } + + // If data is an object. + if (typeof data.getValue(key) === Typeof.Object) { + const dataObj: JSONObject = data.getValue(key) as JSONObject; + + for (const key in dataObj) { + dataObj[key] = await Encryption.decrypt(dataObj[key] as string); + } + + data.setValue(key, dataObj); + } else { + //If its string or other type. + data.setValue(key, await Encryption.decrypt((data as any)[key])); + } + } + + return data; + } + + protected async onBeforeDelete( + deleteBy: DeleteBy<TBaseModel>, + ): Promise<OnDelete<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ deleteBy, carryForward: null }); + } + + protected async onBeforeUpdate( + updateBy: UpdateBy<TBaseModel>, + ): Promise<OnUpdate<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ updateBy, carryForward: null }); + } + + protected async onBeforeFind( + findBy: FindBy<TBaseModel>, + ): Promise<OnFind<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ findBy, carryForward: null }); + } + + protected async onCreateSuccess( + _onCreate: OnCreate<TBaseModel>, + createdItem: TBaseModel, + ): Promise<TBaseModel> { + // A place holder method used for overriding. + return Promise.resolve(createdItem); + } + + protected async onCreateError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } + + protected async onUpdateSuccess( + onUpdate: OnUpdate<TBaseModel>, + _updatedItemIds: Array<ObjectID>, + ): Promise<OnUpdate<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve(onUpdate); + } + + protected async onUpdateError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } + + protected async onDeleteSuccess( + onDelete: OnDelete<TBaseModel>, + _itemIdsBeforeDelete: Array<ObjectID>, + ): Promise<OnDelete<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve(onDelete); + } + + protected async onDeleteError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } + + protected async onFindSuccess( + onFind: OnFind<TBaseModel>, + items: Array<TBaseModel>, + ): Promise<OnFind<TBaseModel>> { + // A place holder method used for overriding. + return Promise.resolve({ ...onFind, carryForward: items }); + } + + protected async onFindError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } + + protected async onCountSuccess( + count: PositiveNumber, + ): Promise<PositiveNumber> { + // A place holder method used for overriding. + return Promise.resolve(count); + } + + protected async onCountError(error: Exception): Promise<Exception> { + // A place holder method used for overriding. + return Promise.resolve(error); + } + + protected async getException(error: Exception): Promise<void> { + throw error; + } + + private generateSlug(createBy: CreateBy<TBaseModel>): CreateBy<TBaseModel> { + if (createBy.data.getSlugifyColumn()) { + (createBy.data as any)[createBy.data.getSaveSlugToColumn() as string] = + Slug.getSlug( + (createBy.data as any)[createBy.data.getSlugifyColumn() as string] + ? ((createBy.data as any)[ + createBy.data.getSlugifyColumn() as string + ] as string) + : null, + ); + } + + return createBy; + } + + private async sanitizeCreateOrUpdate( + data: TBaseModel | QueryDeepPartialEntity<TBaseModel>, + props: DatabaseCommonInteractionProps, + isUpdate: boolean = false, + ): Promise<TBaseModel | QueryDeepPartialEntity<TBaseModel>> { + data = this.checkMaxLengthOfFields(data as TBaseModel); + + const columns: Columns = this.model.getTableColumns(); + + for (const columnName of columns.columns) { + if (this.model.isEntityColumn(columnName)) { + const tableColumnMetadata: TableColumnMetadata = + this.model.getTableColumnMetadata(columnName); + + const columnValue: JSONValue = (data as any)[columnName]; + + if ( + data && + columnName && + tableColumnMetadata.modelType && + columnValue && + tableColumnMetadata.type === TableColumnType.Entity && + (typeof columnValue === "string" || columnValue instanceof ObjectID) + ) { + const relatedType: BaseModel = new tableColumnMetadata.modelType(); + relatedType._id = columnValue.toString(); + (data as any)[columnName] = relatedType; + } + + if ( + data && + Array.isArray(columnValue) && + columnValue.length > 0 && + tableColumnMetadata.modelType && + columnValue && + tableColumnMetadata.type === TableColumnType.EntityArray + ) { + const itemsArray: Array<BaseModel> = []; + for (const item of columnValue) { + if (typeof item === "string" || item instanceof ObjectID) { + const basemodelItem: BaseModel = + new tableColumnMetadata.modelType(); + basemodelItem._id = item.toString(); + itemsArray.push(basemodelItem); + } else if ( + item && + typeof item === Typeof.Object && + (item as JSONObject)["_id"] && + typeof (item as JSONObject)["_id"] === Typeof.String + ) { + const basemodelItem: BaseModel = + new tableColumnMetadata.modelType(); + basemodelItem._id = ( + (item as JSONObject)["_id"] as string + ).toString(); + itemsArray.push(basemodelItem); + } else if (item instanceof BaseModel) { + itemsArray.push(item); + } + } + (data as any)[columnName] = itemsArray; + } + } + + if (this.model.isHashedStringColumn(columnName)) { + const columnValue: JSONValue = (data as any)[columnName]; + + if ( + data && + columnName && + columnValue && + columnValue instanceof HashedString + ) { + if (!columnValue.isValueHashed()) { + await columnValue.hashValue(EncryptionSecret); + } + + (data as any)[columnName] = columnValue.toString(); + } + } + } + + // check createByUserId. + + if (!isUpdate && props.userId) { + (data as any)["createdByUserId"] = props.userId; + } + + return data; + } + + public async onTrigger( + id: ObjectID, + projectId: ObjectID, + triggerType: DatabaseTriggerType, + miscData?: JSONObject | undefined, // miscData is used for passing data to workflow. + ): Promise<void> { + if (this.getModel().enableWorkflowOn) { + API.post( + new URL( + Protocol.HTTP, + AppApiHostname, + new Route( + `/api${WorkflowRoute.toString()}/model/${projectId.toString()}/${Text.pascalCaseToDashes( + this.getModel().tableName!, + )}/${triggerType}`, + ), + ), + { + data: { + _id: id.toString(), + miscData: miscData, + }, + }, + { + ...ClusterKeyAuthorization.getClusterKeyHeaders(), + }, + ).catch((error: Error) => { + logger.error(error); + }); + } + } + + public async create(createBy: CreateBy<TBaseModel>): Promise<TBaseModel> { + const onCreate: OnCreate<TBaseModel> = createBy.props.ignoreHooks + ? { createBy, carryForward: [] } + : await this._onBeforeCreate(createBy); + + let _createdBy: CreateBy<TBaseModel> = onCreate.createBy; + + const carryForward: any = onCreate.carryForward; + + _createdBy = this.generateSlug(_createdBy); + + let data: TBaseModel = _createdBy.data; + + // add tenantId if present. + const tenantColumnName: string | null = data.getTenantColumn(); + + if (tenantColumnName && _createdBy.props.tenantId) { + data.setColumnValue(tenantColumnName, _createdBy.props.tenantId); + } + + data = this.generateDefaultValues(data); + + data = this.checkRequiredFields(data); + + await this.checkForUniqueValues(data); + + if (!this.isValid(data)) { + throw new BadDataException("Data is not valid"); + } + + // check total items by. + + await this.checkTotalItemsBy(_createdBy); + + // Encrypt data + data = (await this.encrypt(data)) as TBaseModel; + + // hash data + data = await this.hash(data); + + ModelPermission.checkCreatePermissions( + this.modelType, + data, + _createdBy.props, + ); + + createBy.data = data; + + // check uniqueColumns by: + createBy = await this.checkUniqueColumnBy(createBy); + + // serialize. + createBy.data = (await this.sanitizeCreateOrUpdate( + createBy.data, + createBy.props, + )) as TBaseModel; + + try { + createBy.data = await this.getRepository().save(createBy.data); + + if (!createBy.props.ignoreHooks) { + createBy.data = await this.onCreateSuccess( + { + createBy, + carryForward, + }, + createBy.data, + ); + } + + // hit workflow.; + if (this.getModel().enableWorkflowOn?.create) { + let tenantId: ObjectID | undefined = createBy.props.tenantId; + + if (!tenantId && this.getModel().getTenantColumn()) { + tenantId = createBy.data.getValue<ObjectID>( + this.getModel().getTenantColumn()!, + ); + } + + if (tenantId) { + await this.onTrigger(createBy.data.id!, tenantId, "on-create"); + } + } + + return createBy.data; + } catch (error) { + await this.onCreateError(error as Exception); + throw this.getException(error as Exception); + } + } + + private checkMaxLengthOfFields<TBaseModel extends BaseModel>( + data: TBaseModel, + ): TBaseModel { + // Check required fields. + + const tableColumns: Array<string> = this.model.getTableColumns().columns; + + for (const column of tableColumns) { + const metadata: TableColumnMetadata = + this.model.getTableColumnMetadata(column); + if ( + (data as any)[column] && + metadata.type && + getMaxLengthFromTableColumnType(metadata.type) + ) { + if ( + (data as any)[column].toString().length > + getMaxLengthFromTableColumnType(metadata.type)! + ) { + throw new BadDataException( + `${column} length cannot be more than ${getMaxLengthFromTableColumnType( + metadata.type, + )} characters`, + ); + } + } + } + + return data; + } + + private async checkTotalItemsBy( + createdBy: CreateBy<TBaseModel>, + ): Promise<void> { + const totalItemsColumnName: string | null = + this.model.getTotalItemsByColumnName(); + const totalItemsNumber: number | null = this.model.getTotalItemsNumber(); + const errorMessage: string | null = + this.model.getTotalItemsByErrorMessage(); + + if ( + totalItemsColumnName && + totalItemsNumber && + errorMessage && + createdBy.data.getColumnValue(totalItemsColumnName) + ) { + const count: PositiveNumber = await this.countBy({ + query: { + [totalItemsColumnName]: + createdBy.data.getColumnValue(totalItemsColumnName), + } as FindWhere<TBaseModel>, + + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + if (count.positiveNumber > totalItemsNumber - 1) { + throw new BadDataException(errorMessage); + } + } + } + + private async checkUniqueColumnBy( + createBy: CreateBy<TBaseModel>, + ): Promise<CreateBy<TBaseModel>> { + let existingItemsWithSameNameCount: number = 0; + + const uniqueColumnsBy: Dictionary<string | Array<string>> = + getUniqueColumnsBy(createBy.data); + + for (const key in uniqueColumnsBy) { + if (!uniqueColumnsBy[key]) { + continue; + } + + if (typeof uniqueColumnsBy[key] === Typeof.String) { + uniqueColumnsBy[key] = [uniqueColumnsBy[key] as string]; + } + + const query: Query<TBaseModel> = {}; + + for (const uniqueByColumnName of uniqueColumnsBy[key] as Array<string>) { + const columnValue: JSONValue = (createBy.data as any)[ + uniqueByColumnName as string + ]; + if (columnValue === null || columnValue === undefined) { + (query as any)[uniqueByColumnName] = QueryHelper.isNull(); + } else { + (query as any)[uniqueByColumnName] = columnValue; + } + } + + existingItemsWithSameNameCount = ( + await this.countBy({ + query: { + [key]: QueryHelper.findWithSameText( + (createBy.data as any)[key] + ? ((createBy.data as any)[key]! as string) + : "", + ), + ...query, + }, + props: { + isRoot: true, + }, + }) + ).toNumber(); + + if (existingItemsWithSameNameCount > 0) { + throw new BadDataException( + `${this.model.singularName} with the same ${key} already exists.`, + ); + } + + existingItemsWithSameNameCount = 0; + } + + return Promise.resolve(createBy); + } + + public async countBy({ + query, + skip, + limit, + props, + groupBy, + distinctOn, + }: CountBy<TBaseModel>): Promise<PositiveNumber> { + try { + if (groupBy && Object.keys(groupBy).length > 0) { + throw new BadDataException("Group By is not supported for countBy"); + } + + if (!skip) { + skip = new PositiveNumber(0); + } + + if (!limit) { + limit = new PositiveNumber(Infinity); + } + + if (!(skip instanceof PositiveNumber)) { + skip = new PositiveNumber(skip); + } + + if (!(limit instanceof PositiveNumber)) { + limit = new PositiveNumber(limit); + } + + const findBy: FindBy<TBaseModel> = { query, skip, limit, props, - groupBy, - distinctOn, - }: CountBy<TBaseModel>): Promise<PositiveNumber> { - try { - if (groupBy && Object.keys(groupBy).length > 0) { - throw new BadDataException( - 'Group By is not supported for countBy' - ); - } + }; - if (!skip) { - skip = new PositiveNumber(0); - } + const checkReadPermissionType: CheckReadPermissionType<TBaseModel> = + await ModelPermission.checkReadQueryPermission( + this.modelType, + query, + null, + props, + ); - if (!limit) { - limit = new PositiveNumber(Infinity); - } + findBy.query = checkReadPermissionType.query; + let count: number = 0; - if (!(skip instanceof PositiveNumber)) { - skip = new PositiveNumber(skip); - } + if (distinctOn) { + const queryBuilder: SelectQueryBuilder<TBaseModel> = + this.getQueryBuilder(this.modelName) + .where(findBy.query) + .skip(skip.toNumber()) + .take(limit.toNumber()); - if (!(limit instanceof PositiveNumber)) { - limit = new PositiveNumber(limit); - } - - const findBy: FindBy<TBaseModel> = { - query, - skip, - limit, - props, - }; - - const checkReadPermissionType: CheckReadPermissionType<TBaseModel> = - await ModelPermission.checkReadQueryPermission( - this.modelType, - query, - null, - props - ); - - findBy.query = checkReadPermissionType.query; - let count: number = 0; - - if (distinctOn) { - const queryBuilder: SelectQueryBuilder<TBaseModel> = - this.getQueryBuilder(this.modelName) - .where(findBy.query) - .skip(skip.toNumber()) - .take(limit.toNumber()); - - if (distinctOn) { - queryBuilder.groupBy(`${this.modelName}.${distinctOn}`); - } - - count = await queryBuilder.getCount(); - } else { - count = await this.getRepository().count({ - where: findBy.query as any, - skip: (findBy.skip as PositiveNumber).toNumber(), - take: (findBy.limit as PositiveNumber).toNumber(), - }); - } - - let countPositive: PositiveNumber = new PositiveNumber(count); - countPositive = await this.onCountSuccess(countPositive); - return countPositive; - } catch (error) { - await this.onCountError(error as Exception); - throw this.getException(error as Exception); + if (distinctOn) { + queryBuilder.groupBy(`${this.modelName}.${distinctOn}`); } - } - public async deleteOneById(deleteById: DeleteById): Promise<number> { - await ModelPermission.checkDeletePermissionByModel({ - modelType: this.modelType, - fetchModelWithAccessControlIds: async () => { - const selectModel: Select<TBaseModel> = {}; - const accessControlColumn: string | null = - this.getModel().getAccessControlColumn(); - - if (accessControlColumn) { - (selectModel as any)[accessControlColumn] = { - _id: true, - name: true, - }; - } - - return await this.findOneById({ - id: deleteById.id, - select: selectModel, - props: { - isRoot: true, - }, - }); - }, - props: deleteById.props, + count = await queryBuilder.getCount(); + } else { + count = await this.getRepository().count({ + where: findBy.query as any, + skip: (findBy.skip as PositiveNumber).toNumber(), + take: (findBy.limit as PositiveNumber).toNumber(), }); + } - return await this.deleteOneBy({ - query: { - _id: deleteById.id.toString(), - } as any, - deletedByUser: deleteById.deletedByUser, - props: deleteById.props, + let countPositive: PositiveNumber = new PositiveNumber(count); + countPositive = await this.onCountSuccess(countPositive); + return countPositive; + } catch (error) { + await this.onCountError(error as Exception); + throw this.getException(error as Exception); + } + } + + public async deleteOneById(deleteById: DeleteById): Promise<number> { + await ModelPermission.checkDeletePermissionByModel({ + modelType: this.modelType, + fetchModelWithAccessControlIds: async () => { + const selectModel: Select<TBaseModel> = {}; + const accessControlColumn: string | null = + this.getModel().getAccessControlColumn(); + + if (accessControlColumn) { + (selectModel as any)[accessControlColumn] = { + _id: true, + name: true, + }; + } + + return await this.findOneById({ + id: deleteById.id, + select: selectModel, + props: { + isRoot: true, + }, }); + }, + props: deleteById.props, + }); + + return await this.deleteOneBy({ + query: { + _id: deleteById.id.toString(), + } as any, + deletedByUser: deleteById.deletedByUser, + props: deleteById.props, + }); + } + + public async deleteOneBy( + deleteOneBy: DeleteOneBy<TBaseModel>, + ): Promise<number> { + return await this._deleteBy({ ...deleteOneBy, limit: 1, skip: 0 }); + } + + public async deleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<number> { + return await this._deleteBy(deleteBy); + } + + public async hardDeleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<number> { + try { + const onDelete: OnDelete<TBaseModel> = deleteBy.props.ignoreHooks + ? { deleteBy, carryForward: [] } + : await this.onBeforeDelete(deleteBy); + const beforeDeleteBy: DeleteBy<TBaseModel> = onDelete.deleteBy; + + beforeDeleteBy.query = await ModelPermission.checkDeleteQueryPermission( + this.modelType, + beforeDeleteBy.query, + deleteBy.props, + ); + + if (!(beforeDeleteBy.skip instanceof PositiveNumber)) { + beforeDeleteBy.skip = new PositiveNumber(beforeDeleteBy.skip); + } + + if (!(beforeDeleteBy.limit instanceof PositiveNumber)) { + beforeDeleteBy.limit = new PositiveNumber(beforeDeleteBy.limit); + } + + const items: Array<TBaseModel> = await this._findBy( + { + query: beforeDeleteBy.query, + skip: beforeDeleteBy.skip.toNumber(), + limit: beforeDeleteBy.limit.toNumber(), + select: {}, + props: { ...beforeDeleteBy.props, ignoreHooks: true }, + }, + true, + ); + + let numberOfDocsAffected: number = 0; + + if (items.length > 0) { + beforeDeleteBy.query = { + ...beforeDeleteBy.query, + _id: QueryHelper.any( + items.map((i: TBaseModel) => { + return i.id!; + }), + ), + }; + + numberOfDocsAffected = + (await this.getRepository().delete(beforeDeleteBy.query as any)) + .affected || 0; + } + + return numberOfDocsAffected; + } catch (error) { + await this.onDeleteError(error as Exception); + throw this.getException(error as Exception); } + } - public async deleteOneBy( - deleteOneBy: DeleteOneBy<TBaseModel> - ): Promise<number> { - return await this._deleteBy({ ...deleteOneBy, limit: 1, skip: 0 }); - } + private async _deleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<number> { + try { + if (this.doNotAllowDelete && !deleteBy.props.isRoot) { + throw new BadDataException("Delete not allowed"); + } - public async deleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<number> { - return await this._deleteBy(deleteBy); - } + const onDelete: OnDelete<TBaseModel> = deleteBy.props.ignoreHooks + ? { deleteBy, carryForward: [] } + : await this.onBeforeDelete(deleteBy); - public async hardDeleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<number> { - try { - const onDelete: OnDelete<TBaseModel> = deleteBy.props.ignoreHooks - ? { deleteBy, carryForward: [] } - : await this.onBeforeDelete(deleteBy); - const beforeDeleteBy: DeleteBy<TBaseModel> = onDelete.deleteBy; + const beforeDeleteBy: DeleteBy<TBaseModel> = onDelete.deleteBy; - beforeDeleteBy.query = - await ModelPermission.checkDeleteQueryPermission( - this.modelType, - beforeDeleteBy.query, - deleteBy.props - ); + const carryForward: any = onDelete.carryForward; - if (!(beforeDeleteBy.skip instanceof PositiveNumber)) { - beforeDeleteBy.skip = new PositiveNumber(beforeDeleteBy.skip); + beforeDeleteBy.query = await ModelPermission.checkDeleteQueryPermission( + this.modelType, + beforeDeleteBy.query, + deleteBy.props, + ); + + if (!(beforeDeleteBy.skip instanceof PositiveNumber)) { + beforeDeleteBy.skip = new PositiveNumber(beforeDeleteBy.skip); + } + + if (!(beforeDeleteBy.limit instanceof PositiveNumber)) { + beforeDeleteBy.limit = new PositiveNumber(beforeDeleteBy.limit); + } + + const select: Select<TBaseModel> = {}; + + if (this.getModel().getTenantColumn()) { + (select as any)[this.getModel().getTenantColumn() as string] = true; + } + + const items: Array<TBaseModel> = await this._findBy({ + query: beforeDeleteBy.query, + skip: beforeDeleteBy.skip.toNumber(), + limit: beforeDeleteBy.limit.toNumber(), + select: select, + props: { + isRoot: true, // isRoot because query has already been checked for permissions. + ignoreHooks: true, + }, + }); + + // We are hard deleting anyway. So, this does not make sense. Please uncomment if + // we change the code to soft-delete. + + // await this._updateBy({ + // query: deleteBy.query, + // data: { + // deletedByUserId: deleteBy.props.userId, + // } as any, + // limit: deleteBy.limit, + // skip: deleteBy.skip, + // props: { + // isRoot: true, + // ignoreHooks: true, + // }, + // }); + + let numberOfDocsAffected: number = 0; + + if (items.length > 0) { + const query: Query<TBaseModel> = { + _id: QueryHelper.any( + items.map((i: TBaseModel) => { + return i.id!; + }), + ), + }; + + numberOfDocsAffected = + (await this.getRepository().delete(query as any)).affected || 0; + } + + // hit workflow. + if ( + this.getModel().enableWorkflowOn?.delete && + (deleteBy.props.tenantId || this.getModel().getTenantColumn()) + ) { + for (const item of items) { + if (this.getModel().enableWorkflowOn?.create) { + let tenantId: ObjectID | undefined = deleteBy.props.tenantId; + + if (!tenantId && this.getModel().getTenantColumn()) { + tenantId = item.getValue<ObjectID>( + this.getModel().getTenantColumn()!, + ); } - if (!(beforeDeleteBy.limit instanceof PositiveNumber)) { - beforeDeleteBy.limit = new PositiveNumber(beforeDeleteBy.limit); + if (tenantId) { + await this.onTrigger(item.id!, tenantId, "on-delete"); } - - const items: Array<TBaseModel> = await this._findBy( - { - query: beforeDeleteBy.query, - skip: beforeDeleteBy.skip.toNumber(), - limit: beforeDeleteBy.limit.toNumber(), - select: {}, - props: { ...beforeDeleteBy.props, ignoreHooks: true }, - }, - true - ); - - let numberOfDocsAffected: number = 0; - - if (items.length > 0) { - beforeDeleteBy.query = { - ...beforeDeleteBy.query, - _id: QueryHelper.any( - items.map((i: TBaseModel) => { - return i.id!; - }) - ), - }; - - numberOfDocsAffected = - ( - await this.getRepository().delete( - beforeDeleteBy.query as any - ) - ).affected || 0; - } - - return numberOfDocsAffected; - } catch (error) { - await this.onDeleteError(error as Exception); - throw this.getException(error as Exception); + } } + } + + if (!deleteBy.props.ignoreHooks) { + await this.onDeleteSuccess( + { deleteBy, carryForward }, + items.map((i: TBaseModel) => { + return new ObjectID(i._id!); + }), + ); + } + + return numberOfDocsAffected; + } catch (error) { + await this.onDeleteError(error as Exception); + throw this.getException(error as Exception); } + } - private async _deleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<number> { - try { - if (this.doNotAllowDelete && !deleteBy.props.isRoot) { - throw new BadDataException('Delete not allowed'); - } + public async findBy(findBy: FindBy<TBaseModel>): Promise<Array<TBaseModel>> { + return await this._findBy(findBy); + } - const onDelete: OnDelete<TBaseModel> = deleteBy.props.ignoreHooks - ? { deleteBy, carryForward: [] } - : await this.onBeforeDelete(deleteBy); + private async _findBy( + findBy: FindBy<TBaseModel>, + withDeleted?: boolean | undefined, + ): Promise<Array<TBaseModel>> { + try { + let automaticallyAddedCreatedAtInSelect: boolean = false; - const beforeDeleteBy: DeleteBy<TBaseModel> = onDelete.deleteBy; + if (!findBy.sort || Object.keys(findBy.sort).length === 0) { + findBy.sort = { + createdAt: SortOrder.Descending, + }; - const carryForward: any = onDelete.carryForward; - - beforeDeleteBy.query = - await ModelPermission.checkDeleteQueryPermission( - this.modelType, - beforeDeleteBy.query, - deleteBy.props - ); - - if (!(beforeDeleteBy.skip instanceof PositiveNumber)) { - beforeDeleteBy.skip = new PositiveNumber(beforeDeleteBy.skip); - } - - if (!(beforeDeleteBy.limit instanceof PositiveNumber)) { - beforeDeleteBy.limit = new PositiveNumber(beforeDeleteBy.limit); - } - - const select: Select<TBaseModel> = {}; - - if (this.getModel().getTenantColumn()) { - (select as any)[this.getModel().getTenantColumn() as string] = - true; - } - - const items: Array<TBaseModel> = await this._findBy({ - query: beforeDeleteBy.query, - skip: beforeDeleteBy.skip.toNumber(), - limit: beforeDeleteBy.limit.toNumber(), - select: select, - props: { - isRoot: true, // isRoot because query has already been checked for permissions. - ignoreHooks: true, - }, - }); - - // We are hard deleting anyway. So, this does not make sense. Please uncomment if - // we change the code to soft-delete. - - // await this._updateBy({ - // query: deleteBy.query, - // data: { - // deletedByUserId: deleteBy.props.userId, - // } as any, - // limit: deleteBy.limit, - // skip: deleteBy.skip, - // props: { - // isRoot: true, - // ignoreHooks: true, - // }, - // }); - - let numberOfDocsAffected: number = 0; - - if (items.length > 0) { - const query: Query<TBaseModel> = { - _id: QueryHelper.any( - items.map((i: TBaseModel) => { - return i.id!; - }) - ), - }; - - numberOfDocsAffected = - (await this.getRepository().delete(query as any)) - .affected || 0; - } - - // hit workflow. - if ( - this.getModel().enableWorkflowOn?.delete && - (deleteBy.props.tenantId || this.getModel().getTenantColumn()) - ) { - for (const item of items) { - if (this.getModel().enableWorkflowOn?.create) { - let tenantId: ObjectID | undefined = - deleteBy.props.tenantId; - - if (!tenantId && this.getModel().getTenantColumn()) { - tenantId = item.getValue<ObjectID>( - this.getModel().getTenantColumn()! - ); - } - - if (tenantId) { - await this.onTrigger( - item.id!, - tenantId, - 'on-delete' - ); - } - } - } - } - - if (!deleteBy.props.ignoreHooks) { - await this.onDeleteSuccess( - { deleteBy, carryForward }, - items.map((i: TBaseModel) => { - return new ObjectID(i._id!); - }) - ); - } - - return numberOfDocsAffected; - } catch (error) { - await this.onDeleteError(error as Exception); - throw this.getException(error as Exception); - } - } - - public async findBy( - findBy: FindBy<TBaseModel> - ): Promise<Array<TBaseModel>> { - return await this._findBy(findBy); - } - - private async _findBy( - findBy: FindBy<TBaseModel>, - withDeleted?: boolean | undefined - ): Promise<Array<TBaseModel>> { - try { - let automaticallyAddedCreatedAtInSelect: boolean = false; - - if (!findBy.sort || Object.keys(findBy.sort).length === 0) { - findBy.sort = { - createdAt: SortOrder.Descending, - }; - - if (!findBy.select) { - findBy.select = {} as any; - } - - if (!(findBy.select as any)['createdAt']) { - (findBy.select as any)['createdAt'] = true; - automaticallyAddedCreatedAtInSelect = true; - } - } - - const onFind: OnFind<TBaseModel> = findBy.props.ignoreHooks - ? { findBy, carryForward: [] } - : await this.onBeforeFind(findBy); - const onBeforeFind: FindBy<TBaseModel> = { ...onFind.findBy }; - const carryForward: any = onFind.carryForward; - - if ( - !onBeforeFind.select || - Object.keys(onBeforeFind.select).length === 0 - ) { - onBeforeFind.select = {} as any; - } - - if (!(onBeforeFind.select as any)['_id']) { - (onBeforeFind.select as any)['_id'] = true; - } - - const result: { - query: Query<TBaseModel>; - select: Select<TBaseModel> | null; - relationSelect: RelationSelect<TBaseModel> | null; - } = await ModelPermission.checkReadQueryPermission( - this.modelType, - onBeforeFind.query, - onBeforeFind.select || null, - onBeforeFind.props - ); - - onBeforeFind.query = result.query; - onBeforeFind.select = result.select || undefined; - - if (!(onBeforeFind.skip instanceof PositiveNumber)) { - onBeforeFind.skip = new PositiveNumber(onBeforeFind.skip); - } - - if (!(onBeforeFind.limit instanceof PositiveNumber)) { - onBeforeFind.limit = new PositiveNumber(onBeforeFind.limit); - } - - if ( - onBeforeFind.groupBy && - Object.keys(onBeforeFind.groupBy).length > 0 - ) { - throw new BadDataException( - 'GroupBy is currently not supported' - ); - } - - const items: Array<TBaseModel> = await this.getRepository().find({ - skip: onBeforeFind.skip.toNumber(), - take: onBeforeFind.limit.toNumber(), - where: onBeforeFind.query as any, - order: onBeforeFind.sort as any, - relations: result.relationSelect as any, - select: onBeforeFind.select as any, - withDeleted: withDeleted || false, - }); - - let decryptedItems: Array<TBaseModel> = []; - - for (const item of items) { - decryptedItems.push(await this.decrypt(item)); - } - - decryptedItems = this.sanitizeFindByItems( - decryptedItems, - onBeforeFind - ); - - for (const item of decryptedItems) { - if (automaticallyAddedCreatedAtInSelect) { - delete (item as any).createdAt; - } - } - - if (!findBy.props.ignoreHooks) { - decryptedItems = await ( - await this.onFindSuccess( - { findBy, carryForward }, - decryptedItems - ) - ).carryForward; - } - - return decryptedItems; - } catch (error) { - await this.onFindError(error as Exception); - throw this.getException(error as Exception); - } - } - - private sanitizeFindByItems( - items: Array<TBaseModel>, - findBy: FindBy<TBaseModel> - ): Array<TBaseModel> { - // if there's no select then there's nothing to do. if (!findBy.select) { - return items; + findBy.select = {} as any; } - for (const key in findBy.select) { - // for each key in select check if there's nested properties, this indicates there's a relation. - if (typeof findBy.select[key] === Typeof.Object) { - // get meta data to check if this column is an entity array. - const tableColumnMetadata: TableColumnMetadata = - this.model.getTableColumnMetadata(key); + if (!(findBy.select as any)["createdAt"]) { + (findBy.select as any)["createdAt"] = true; + automaticallyAddedCreatedAtInSelect = true; + } + } - if (!tableColumnMetadata.modelType) { - throw new BadDataException( - 'Select not supported on ' + - key + - ' of ' + - this.model.singularName + - ' because this column modelType is not found.' - ); + const onFind: OnFind<TBaseModel> = findBy.props.ignoreHooks + ? { findBy, carryForward: [] } + : await this.onBeforeFind(findBy); + const onBeforeFind: FindBy<TBaseModel> = { ...onFind.findBy }; + const carryForward: any = onFind.carryForward; + + if ( + !onBeforeFind.select || + Object.keys(onBeforeFind.select).length === 0 + ) { + onBeforeFind.select = {} as any; + } + + if (!(onBeforeFind.select as any)["_id"]) { + (onBeforeFind.select as any)["_id"] = true; + } + + const result: { + query: Query<TBaseModel>; + select: Select<TBaseModel> | null; + relationSelect: RelationSelect<TBaseModel> | null; + } = await ModelPermission.checkReadQueryPermission( + this.modelType, + onBeforeFind.query, + onBeforeFind.select || null, + onBeforeFind.props, + ); + + onBeforeFind.query = result.query; + onBeforeFind.select = result.select || undefined; + + if (!(onBeforeFind.skip instanceof PositiveNumber)) { + onBeforeFind.skip = new PositiveNumber(onBeforeFind.skip); + } + + if (!(onBeforeFind.limit instanceof PositiveNumber)) { + onBeforeFind.limit = new PositiveNumber(onBeforeFind.limit); + } + + if ( + onBeforeFind.groupBy && + Object.keys(onBeforeFind.groupBy).length > 0 + ) { + throw new BadDataException("GroupBy is currently not supported"); + } + + const items: Array<TBaseModel> = await this.getRepository().find({ + skip: onBeforeFind.skip.toNumber(), + take: onBeforeFind.limit.toNumber(), + where: onBeforeFind.query as any, + order: onBeforeFind.sort as any, + relations: result.relationSelect as any, + select: onBeforeFind.select as any, + withDeleted: withDeleted || false, + }); + + let decryptedItems: Array<TBaseModel> = []; + + for (const item of items) { + decryptedItems.push(await this.decrypt(item)); + } + + decryptedItems = this.sanitizeFindByItems(decryptedItems, onBeforeFind); + + for (const item of decryptedItems) { + if (automaticallyAddedCreatedAtInSelect) { + delete (item as any).createdAt; + } + } + + if (!findBy.props.ignoreHooks) { + decryptedItems = await ( + await this.onFindSuccess({ findBy, carryForward }, decryptedItems) + ).carryForward; + } + + return decryptedItems; + } catch (error) { + await this.onFindError(error as Exception); + throw this.getException(error as Exception); + } + } + + private sanitizeFindByItems( + items: Array<TBaseModel>, + findBy: FindBy<TBaseModel>, + ): Array<TBaseModel> { + // if there's no select then there's nothing to do. + if (!findBy.select) { + return items; + } + + for (const key in findBy.select) { + // for each key in select check if there's nested properties, this indicates there's a relation. + if (typeof findBy.select[key] === Typeof.Object) { + // get meta data to check if this column is an entity array. + const tableColumnMetadata: TableColumnMetadata = + this.model.getTableColumnMetadata(key); + + if (!tableColumnMetadata.modelType) { + throw new BadDataException( + "Select not supported on " + + key + + " of " + + this.model.singularName + + " because this column modelType is not found.", + ); + } + + const relatedModel: BaseModel = new tableColumnMetadata.modelType(); + if (tableColumnMetadata.type === TableColumnType.EntityArray) { + const tableColumns: Array<string> = + relatedModel.getTableColumns().columns; + const columnsToKeep: Array<string> = Object.keys( + (findBy.select as any)[key], + ); + + for (const item of items) { + if (item[key] && Array.isArray(item[key])) { + const relatedArray: Array<BaseModel> = item[key] as any; + const newArray: Array<BaseModel> = []; + // now we need to sanitize data. + + for (const relatedArrayItem of relatedArray) { + for (const column of tableColumns) { + if (!columnsToKeep.includes(column)) { + (relatedArrayItem as any)[column] = undefined; + } } + newArray.push(relatedArrayItem); + } - const relatedModel: BaseModel = - new tableColumnMetadata.modelType(); - if (tableColumnMetadata.type === TableColumnType.EntityArray) { - const tableColumns: Array<string> = - relatedModel.getTableColumns().columns; - const columnsToKeep: Array<string> = Object.keys( - (findBy.select as any)[key] - ); - - for (const item of items) { - if (item[key] && Array.isArray(item[key])) { - const relatedArray: Array<BaseModel> = item[ - key - ] as any; - const newArray: Array<BaseModel> = []; - // now we need to sanitize data. - - for (const relatedArrayItem of relatedArray) { - for (const column of tableColumns) { - if (!columnsToKeep.includes(column)) { - (relatedArrayItem as any)[column] = - undefined; - } - } - newArray.push(relatedArrayItem); - } - - (item[key] as any) = newArray; - } - } - } + (item[key] as any) = newArray; } + } } - - return items; + } } - public async findOneBy( - findOneBy: FindOneBy<TBaseModel> - ): Promise<TBaseModel | null> { - const findBy: FindBy<TBaseModel> = findOneBy as FindBy<TBaseModel>; - findBy.limit = new PositiveNumber(1); - findBy.skip = new PositiveNumber(0); + return items; + } - const documents: Array<TBaseModel> = await this._findBy(findBy); + public async findOneBy( + findOneBy: FindOneBy<TBaseModel>, + ): Promise<TBaseModel | null> { + const findBy: FindBy<TBaseModel> = findOneBy as FindBy<TBaseModel>; + findBy.limit = new PositiveNumber(1); + findBy.skip = new PositiveNumber(0); - if (documents && documents[0]) { - return documents[0]; - } - return null; + const documents: Array<TBaseModel> = await this._findBy(findBy); + + if (documents && documents[0]) { + return documents[0]; + } + return null; + } + + public async findOneById( + findOneById: FindOneByID<TBaseModel>, + ): Promise<TBaseModel | null> { + if (!findOneById.id) { + throw new BadDataException("findOneById.id is required"); } - public async findOneById( - findOneById: FindOneByID<TBaseModel> - ): Promise<TBaseModel | null> { - if (!findOneById.id) { - throw new BadDataException('findOneById.id is required'); - } + return await this.findOneBy({ + query: { + _id: findOneById.id.toString() as any, + }, + select: findOneById.select || {}, + props: findOneById.props, + }); + } - return await this.findOneBy({ - query: { - _id: findOneById.id.toString() as any, - }, - select: findOneById.select || {}, - props: findOneById.props, - }); - } + private async _updateBy(updateBy: UpdateBy<TBaseModel>): Promise<number> { + try { + const onUpdate: OnUpdate<TBaseModel> = updateBy.props.ignoreHooks + ? { updateBy, carryForward: [] } + : await this.onBeforeUpdate(updateBy); - private async _updateBy(updateBy: UpdateBy<TBaseModel>): Promise<number> { - try { - const onUpdate: OnUpdate<TBaseModel> = updateBy.props.ignoreHooks - ? { updateBy, carryForward: [] } - : await this.onBeforeUpdate(updateBy); + // Encrypt data + updateBy.data = (await this.encrypt( + updateBy.data, + )) as PartialEntity<TBaseModel>; - // Encrypt data - updateBy.data = (await this.encrypt( - updateBy.data - )) as PartialEntity<TBaseModel>; + const beforeUpdateBy: UpdateBy<TBaseModel> = onUpdate.updateBy; + const carryForward: any = onUpdate.carryForward; - const beforeUpdateBy: UpdateBy<TBaseModel> = onUpdate.updateBy; - const carryForward: any = onUpdate.carryForward; + beforeUpdateBy.query = await ModelPermission.checkUpdateQueryPermissions( + this.modelType, + beforeUpdateBy.query, + beforeUpdateBy.data, + beforeUpdateBy.props, + ); - beforeUpdateBy.query = - await ModelPermission.checkUpdateQueryPermissions( - this.modelType, - beforeUpdateBy.query, - beforeUpdateBy.data, - beforeUpdateBy.props - ); + const data: QueryDeepPartialEntity<TBaseModel> = + (await this.sanitizeCreateOrUpdate( + beforeUpdateBy.data, + updateBy.props, + true, + )) as QueryDeepPartialEntity<TBaseModel>; - const data: QueryDeepPartialEntity<TBaseModel> = - (await this.sanitizeCreateOrUpdate( - beforeUpdateBy.data, - updateBy.props, - true - )) as QueryDeepPartialEntity<TBaseModel>; + if (!(updateBy.skip instanceof PositiveNumber)) { + updateBy.skip = new PositiveNumber(updateBy.skip); + } - if (!(updateBy.skip instanceof PositiveNumber)) { - updateBy.skip = new PositiveNumber(updateBy.skip); - } + if (!(updateBy.limit instanceof PositiveNumber)) { + updateBy.limit = new PositiveNumber(updateBy.limit); + } - if (!(updateBy.limit instanceof PositiveNumber)) { - updateBy.limit = new PositiveNumber(updateBy.limit); - } + const dataColumns: [string, boolean][] = []; + const dataKeys: string[] = Object.keys(data); + for (const key of dataKeys) { + dataColumns.push([key, true]); + } + // Select the `_id` column and the columns in `data`. + // `_id` is used for locating database records for updates, and `data` + // columns are used for checking if the update causes a change in values. + const selectColumns: Select<TBaseModel> = { + _id: true, + ...Object.fromEntries(dataColumns), + }; - const dataColumns: [string, boolean][] = []; - const dataKeys: string[] = Object.keys(data); - for (const key of dataKeys) { - dataColumns.push([key, true]); - } - // Select the `_id` column and the columns in `data`. - // `_id` is used for locating database records for updates, and `data` - // columns are used for checking if the update causes a change in values. - const selectColumns: Select<TBaseModel> = { - _id: true, - ...Object.fromEntries(dataColumns), - }; + if (this.getModel().getTenantColumn()) { + (selectColumns as any)[this.getModel().getTenantColumn()!.toString()] = + true; + } - if (this.getModel().getTenantColumn()) { - (selectColumns as any)[ - this.getModel().getTenantColumn()!.toString() - ] = true; - } + const items: Array<TBaseModel> = await this._findBy({ + query: beforeUpdateBy.query, + skip: updateBy.skip.toNumber(), + limit: updateBy.limit.toNumber(), + select: selectColumns, + props: { isRoot: true, ignoreHooks: true }, + }); - const items: Array<TBaseModel> = await this._findBy({ - query: beforeUpdateBy.query, - skip: updateBy.skip.toNumber(), - limit: updateBy.limit.toNumber(), - select: selectColumns, - props: { isRoot: true, ignoreHooks: true }, + for (const item of items) { + const updatedItem: any = { + _id: item._id!, + ...data, + } as any; + + await this.getRepository().save(updatedItem); + + // hit workflow. + if ( + this.getModel().enableWorkflowOn?.update && + // Only trigger workflow if there's a change in values + !this.hasSameValues({ item, updatedItem }) + ) { + let tenantId: ObjectID | undefined = updateBy.props.tenantId; + + if (!tenantId && this.getModel().getTenantColumn()) { + tenantId = item.getValue<ObjectID>( + this.getModel().getTenantColumn()!, + ); + } + + if (tenantId) { + await this.onTrigger(item.id!, tenantId, "on-update", { + updatedFields: JSONFunctions.serialize(data as JSONObject), }); - - for (const item of items) { - const updatedItem: any = { - _id: item._id!, - ...data, - } as any; - - await this.getRepository().save(updatedItem); - - // hit workflow. - if ( - this.getModel().enableWorkflowOn?.update && - // Only trigger workflow if there's a change in values - !this.hasSameValues({ item, updatedItem }) - ) { - let tenantId: ObjectID | undefined = - updateBy.props.tenantId; - - if (!tenantId && this.getModel().getTenantColumn()) { - tenantId = item.getValue<ObjectID>( - this.getModel().getTenantColumn()! - ); - } - - if (tenantId) { - await this.onTrigger(item.id!, tenantId, 'on-update', { - updatedFields: JSONFunctions.serialize( - data as JSONObject - ), - }); - } - } - } - - // Cant Update relations. - // https://github.com/typeorm/typeorm/issues/2821 - - // const numberOfDocsAffected: number = - // ( - // await this.getRepository().update( - // query as any, - // data - // ) - // ).affected || 0; - - if (!updateBy.props.ignoreHooks) { - await this.onUpdateSuccess( - { updateBy, carryForward }, - items.map((i: TBaseModel) => { - return new ObjectID(i._id!); - }) - ); - } - - return items.length; - } catch (error) { - await this.onUpdateError(error as Exception); - throw this.getException(error as Exception); + } } + } + + // Cant Update relations. + // https://github.com/typeorm/typeorm/issues/2821 + + // const numberOfDocsAffected: number = + // ( + // await this.getRepository().update( + // query as any, + // data + // ) + // ).affected || 0; + + if (!updateBy.props.ignoreHooks) { + await this.onUpdateSuccess( + { updateBy, carryForward }, + items.map((i: TBaseModel) => { + return new ObjectID(i._id!); + }), + ); + } + + return items.length; + } catch (error) { + await this.onUpdateError(error as Exception); + throw this.getException(error as Exception); + } + } + + private hasSameValues(data: { item: TBaseModel; updatedItem: any }): boolean { + const { item, updatedItem } = data; + const columns: string[] = Object.keys(updatedItem); + for (const column of columns) { + if ( + // `toString()` is necessary so we can compare wrapped values + // (e.g. `ObjectID`) with raw values (e.g. `string`) + item.getColumnValue(column)?.toString() !== + updatedItem[column]?.toString() + ) { + return false; + } + } + return true; + } + + public async updateOneBy( + updateOneBy: UpdateOneBy<TBaseModel>, + ): Promise<number> { + return await this._updateBy({ ...updateOneBy, limit: 1, skip: 0 }); + } + + public async updateBy(updateBy: UpdateBy<TBaseModel>): Promise<number> { + return await this._updateBy(updateBy); + } + + public async updateOneById( + updateById: UpdateByID<TBaseModel>, + ): Promise<void> { + if (!updateById.id) { + throw new BadDataException("updateById.id is required"); } - private hasSameValues(data: { - item: TBaseModel; - updatedItem: any; - }): boolean { - const { item, updatedItem } = data; - const columns: string[] = Object.keys(updatedItem); - for (const column of columns) { - if ( - // `toString()` is necessary so we can compare wrapped values - // (e.g. `ObjectID`) with raw values (e.g. `string`) - item.getColumnValue(column)?.toString() !== - updatedItem[column]?.toString() - ) { - return false; - } - } - return true; - } + await ModelPermission.checkUpdatePermissionByModel({ + modelType: this.modelType, + fetchModelWithAccessControlIds: async () => { + const selectModel: Select<TBaseModel> = {}; + const accessControlColumn: string | null = + this.getModel().getAccessControlColumn(); - public async updateOneBy( - updateOneBy: UpdateOneBy<TBaseModel> - ): Promise<number> { - return await this._updateBy({ ...updateOneBy, limit: 1, skip: 0 }); - } - - public async updateBy(updateBy: UpdateBy<TBaseModel>): Promise<number> { - return await this._updateBy(updateBy); - } - - public async updateOneById( - updateById: UpdateByID<TBaseModel> - ): Promise<void> { - if (!updateById.id) { - throw new BadDataException('updateById.id is required'); + if (accessControlColumn) { + (selectModel as any)[accessControlColumn] = { + _id: true, + name: true, + }; } - await ModelPermission.checkUpdatePermissionByModel({ - modelType: this.modelType, - fetchModelWithAccessControlIds: async () => { - const selectModel: Select<TBaseModel> = {}; - const accessControlColumn: string | null = - this.getModel().getAccessControlColumn(); - - if (accessControlColumn) { - (selectModel as any)[accessControlColumn] = { - _id: true, - name: true, - }; - } - - return await this.findOneById({ - id: updateById.id, - select: selectModel, - props: { - isRoot: true, - }, - }); - }, - props: updateById.props, + return await this.findOneById({ + id: updateById.id, + select: selectModel, + props: { + isRoot: true, + }, }); + }, + props: updateById.props, + }); - await this.updateOneBy({ - query: { - _id: updateById.id.toString() as any, - }, - data: updateById.data as any, - props: updateById.props, - }); - } + await this.updateOneBy({ + query: { + _id: updateById.id.toString() as any, + }, + data: updateById.data as any, + props: updateById.props, + }); + } - public async updateOneByIdAndFetch( - updateById: UpdateByIDAndFetch<TBaseModel> - ): Promise<TBaseModel | null> { - await this.updateOneById(updateById); - return this.findOneById({ - id: updateById.id, - select: updateById.select, - props: updateById.props, - }); - } + public async updateOneByIdAndFetch( + updateById: UpdateByIDAndFetch<TBaseModel>, + ): Promise<TBaseModel | null> { + await this.updateOneById(updateById); + return this.findOneById({ + id: updateById.id, + select: updateById.select, + props: updateById.props, + }); + } - public async searchBy({ - skip, - limit, - select, - props, - }: SearchBy<TBaseModel>): Promise<SearchResult<TBaseModel>> { - const query: Query<TBaseModel> = {}; + public async searchBy({ + skip, + limit, + select, + props, + }: SearchBy<TBaseModel>): Promise<SearchResult<TBaseModel>> { + const query: Query<TBaseModel> = {}; - // query[column] = RegExp(`^${text}`, 'i'); + // query[column] = RegExp(`^${text}`, 'i'); - const [items, count]: [Array<TBaseModel>, PositiveNumber] = - await Promise.all([ - this.findBy({ - query, - skip, - limit, - select, - props: props, - }), - this.countBy({ - query, - skip: new PositiveNumber(0), - limit: new PositiveNumber(Infinity), - props: props, - }), - ]); + const [items, count]: [Array<TBaseModel>, PositiveNumber] = + await Promise.all([ + this.findBy({ + query, + skip, + limit, + select, + props: props, + }), + this.countBy({ + query, + skip: new PositiveNumber(0), + limit: new PositiveNumber(Infinity), + props: props, + }), + ]); - return { items, count }; - } + return { items, count }; + } } export default DatabaseService; diff --git a/CommonServer/Services/DomainService.ts b/CommonServer/Services/DomainService.ts index 94ff90b5f7..4504c3739e 100644 --- a/CommonServer/Services/DomainService.ts +++ b/CommonServer/Services/DomainService.ts @@ -1,94 +1,94 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate, OnUpdate } from '../Types/Database/Hooks'; -import UpdateBy from '../Types/Database/UpdateBy'; -import Domain from '../Types/Domain'; -import DatabaseService from './DatabaseService'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Text from 'Common/Types/Text'; -import Model from 'Model/Models/Domain'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate, OnUpdate } from "../Types/Database/Hooks"; +import UpdateBy from "../Types/Database/UpdateBy"; +import Domain from "../Types/Domain"; +import DatabaseService from "./DatabaseService"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Text from "Common/Types/Text"; +import Model from "Model/Models/Domain"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + createBy.data.domainVerificationText = + "oneuptime-verification-" + Text.generateRandomText(20); + return Promise.resolve({ createBy, carryForward: null }); + } + + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if ( + updateBy.data.isVerified && + updateBy.query._id && + !updateBy.props.isRoot + ) { + // check the verification of the domain. + + const items: Array<Model> = await this.findBy({ + query: { + _id: updateBy.query._id as string, + projectId: updateBy.props.tenantId!, + }, + select: { + domain: true, + domainVerificationText: true, + }, + + limit: 1, + skip: 0, + props: { + isRoot: true, + }, + }); + + if (items.length === 0) { + throw new BadDataException( + "Domain with id " + updateBy.query._id + " not found.", + ); + } + + const domain: string | undefined = items[0]?.domain?.toString(); + const verificationText: string | undefined = + items[0]?.domainVerificationText?.toString(); + + if (!domain) { + throw new BadDataException( + "Domain with id " + updateBy.query._id + " not found.", + ); + } + + if (!verificationText) { + throw new BadDataException( + "Domain verification text with id " + + updateBy.query._id + + " not found.", + ); + } + + const isVerified: boolean = await Domain.verifyTxtRecord( + domain, + verificationText, + ); + + if (!isVerified) { + throw new BadDataException( + "Verification TXT record " + + verificationText + + " not found in domain " + + domain + + ". Please add a TXT record to verify the domain. If you have already added the TXT record, please wait for few hours to let DNS to propagate.", + ); + } } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - createBy.data.domainVerificationText = - 'oneuptime-verification-' + Text.generateRandomText(20); - return Promise.resolve({ createBy, carryForward: null }); - } - - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if ( - updateBy.data.isVerified && - updateBy.query._id && - !updateBy.props.isRoot - ) { - // check the verification of the domain. - - const items: Array<Model> = await this.findBy({ - query: { - _id: updateBy.query._id as string, - projectId: updateBy.props.tenantId!, - }, - select: { - domain: true, - domainVerificationText: true, - }, - - limit: 1, - skip: 0, - props: { - isRoot: true, - }, - }); - - if (items.length === 0) { - throw new BadDataException( - 'Domain with id ' + updateBy.query._id + ' not found.' - ); - } - - const domain: string | undefined = items[0]?.domain?.toString(); - const verificationText: string | undefined = - items[0]?.domainVerificationText?.toString(); - - if (!domain) { - throw new BadDataException( - 'Domain with id ' + updateBy.query._id + ' not found.' - ); - } - - if (!verificationText) { - throw new BadDataException( - 'Domain verification text with id ' + - updateBy.query._id + - ' not found.' - ); - } - - const isVerified: boolean = await Domain.verifyTxtRecord( - domain, - verificationText - ); - - if (!isVerified) { - throw new BadDataException( - 'Verification TXT record ' + - verificationText + - ' not found in domain ' + - domain + - '. Please add a TXT record to verify the domain. If you have already added the TXT record, please wait for few hours to let DNS to propagate.' - ); - } - } - - return { carryForward: null, updateBy }; - } + return { carryForward: null, updateBy }; + } } export default new Service(); diff --git a/CommonServer/Services/EmailLogService.ts b/CommonServer/Services/EmailLogService.ts index a743d8884f..d1a3324b9e 100644 --- a/CommonServer/Services/EmailLogService.ts +++ b/CommonServer/Services/EmailLogService.ts @@ -1,12 +1,12 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/EmailLog'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/EmailLog"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 3); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 3); + } } export default new Service(); diff --git a/CommonServer/Services/EmailVerificationTokenService.ts b/CommonServer/Services/EmailVerificationTokenService.ts index d17ad545c5..6d38f82c1a 100644 --- a/CommonServer/Services/EmailVerificationTokenService.ts +++ b/CommonServer/Services/EmailVerificationTokenService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/EmailVerificationToken'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/EmailVerificationToken"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/FileService.ts b/CommonServer/Services/FileService.ts index b95d26ae80..a5289f0b31 100644 --- a/CommonServer/Services/FileService.ts +++ b/CommonServer/Services/FileService.ts @@ -1,52 +1,48 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DeleteBy from '../Types/Database/DeleteBy'; -import FindBy from '../Types/Database/FindBy'; -import { OnDelete, OnFind, OnUpdate } from '../Types/Database/Hooks'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; -import File from 'Model/Models/File'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DeleteBy from "../Types/Database/DeleteBy"; +import FindBy from "../Types/Database/FindBy"; +import { OnDelete, OnFind, OnUpdate } from "../Types/Database/Hooks"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; +import File from "Model/Models/File"; export class Service extends DatabaseService<File> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(File, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(File, postgresDatabase); + } + + protected override async onBeforeUpdate( + updateBy: UpdateBy<File>, + ): Promise<OnUpdate<File>> { + if (!updateBy.props.isRoot) { + throw new NotAuthorizedException("Not authorized to update a file."); } - protected override async onBeforeUpdate( - updateBy: UpdateBy<File> - ): Promise<OnUpdate<File>> { - if (!updateBy.props.isRoot) { - throw new NotAuthorizedException( - 'Not authorized to update a file.' - ); - } + return { updateBy, carryForward: null }; + } - return { updateBy, carryForward: null }; + protected override async onBeforeDelete( + deleteBy: DeleteBy<File>, + ): Promise<OnDelete<File>> { + if (!deleteBy.props.isRoot) { + throw new NotAuthorizedException("Not authorized to delete a file."); } - protected override async onBeforeDelete( - deleteBy: DeleteBy<File> - ): Promise<OnDelete<File>> { - if (!deleteBy.props.isRoot) { - throw new NotAuthorizedException( - 'Not authorized to delete a file.' - ); - } + return { deleteBy, carryForward: null }; + } - return { deleteBy, carryForward: null }; + protected override async onBeforeFind( + findBy: FindBy<File>, + ): Promise<OnFind<File>> { + if (!findBy.props.isRoot) { + findBy.query = { + ...findBy.query, + isPublic: true, + }; } - protected override async onBeforeFind( - findBy: FindBy<File> - ): Promise<OnFind<File>> { - if (!findBy.props.isRoot) { - findBy.query = { - ...findBy.query, - isPublic: true, - }; - } - - return { findBy, carryForward: null }; - } + return { findBy, carryForward: null }; + } } export default new Service(); diff --git a/CommonServer/Services/GlobalConfigService.ts b/CommonServer/Services/GlobalConfigService.ts index 20b68b99e0..3e9a937c24 100644 --- a/CommonServer/Services/GlobalConfigService.ts +++ b/CommonServer/Services/GlobalConfigService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/GlobalConfig'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/GlobalConfig"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/GreenlockCertificateService.ts b/CommonServer/Services/GreenlockCertificateService.ts index abf8ad6c5b..b7c489d05e 100644 --- a/CommonServer/Services/GreenlockCertificateService.ts +++ b/CommonServer/Services/GreenlockCertificateService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/GreenlockCertificate'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/GreenlockCertificate"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/GreenlockChallengeService.ts b/CommonServer/Services/GreenlockChallengeService.ts index 393d8ffd96..edcbe4ec90 100644 --- a/CommonServer/Services/GreenlockChallengeService.ts +++ b/CommonServer/Services/GreenlockChallengeService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/GreenlockChallenge'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/GreenlockChallenge"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/IncidentCustomFieldService.ts b/CommonServer/Services/IncidentCustomFieldService.ts index 2db5b307b2..fd36f783ff 100644 --- a/CommonServer/Services/IncidentCustomFieldService.ts +++ b/CommonServer/Services/IncidentCustomFieldService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/IncidentCustomField'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/IncidentCustomField"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/IncidentInternalNoteService.ts b/CommonServer/Services/IncidentInternalNoteService.ts index 1d3a4a921f..f6116491b2 100644 --- a/CommonServer/Services/IncidentInternalNoteService.ts +++ b/CommonServer/Services/IncidentInternalNoteService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/IncidentInternalNote'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/IncidentInternalNote"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/IncidentNoteTemplateService.ts b/CommonServer/Services/IncidentNoteTemplateService.ts index 7e97c20dbc..c7e5b8f73b 100644 --- a/CommonServer/Services/IncidentNoteTemplateService.ts +++ b/CommonServer/Services/IncidentNoteTemplateService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/IncidentNoteTemplate'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/IncidentNoteTemplate"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/IncidentOwnerTeamService.ts b/CommonServer/Services/IncidentOwnerTeamService.ts index 28521e94a6..b2e8e5a0c1 100644 --- a/CommonServer/Services/IncidentOwnerTeamService.ts +++ b/CommonServer/Services/IncidentOwnerTeamService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/IncidentOwnerTeam'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/IncidentOwnerTeam"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/IncidentOwnerUserService.ts b/CommonServer/Services/IncidentOwnerUserService.ts index 8f177bbaf9..40220add02 100644 --- a/CommonServer/Services/IncidentOwnerUserService.ts +++ b/CommonServer/Services/IncidentOwnerUserService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/IncidentOwnerUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/IncidentOwnerUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/IncidentPublicNoteService.ts b/CommonServer/Services/IncidentPublicNoteService.ts index 532b8f9fd3..37ea7463ba 100644 --- a/CommonServer/Services/IncidentPublicNoteService.ts +++ b/CommonServer/Services/IncidentPublicNoteService.ts @@ -1,27 +1,27 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import OneUptimeDate from 'Common/Types/Date'; -import Model from 'Model/Models/IncidentPublicNote'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import OneUptimeDate from "Common/Types/Date"; +import Model from "Model/Models/IncidentPublicNote"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.postedAt) { + createBy.data.postedAt = OneUptimeDate.getCurrentDate(); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.postedAt) { - createBy.data.postedAt = OneUptimeDate.getCurrentDate(); - } - - return { - createBy: createBy, - carryForward: null, - }; - } + return { + createBy: createBy, + carryForward: null, + }; + } } export default new Service(); diff --git a/CommonServer/Services/IncidentService.ts b/CommonServer/Services/IncidentService.ts index 78b35fa368..0057ba897e 100644 --- a/CommonServer/Services/IncidentService.ts +++ b/CommonServer/Services/IncidentService.ts @@ -1,678 +1,669 @@ -import DatabaseConfig from '../DatabaseConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import DatabaseService from './DatabaseService'; -import IncidentOwnerTeamService from './IncidentOwnerTeamService'; -import IncidentOwnerUserService from './IncidentOwnerUserService'; -import IncidentStateService from './IncidentStateService'; -import IncidentStateTimelineService from './IncidentStateTimelineService'; -import MonitorService from './MonitorService'; -import MonitorStatusService from './MonitorStatusService'; -import MonitorStatusTimelineService from './MonitorStatusTimelineService'; -import OnCallDutyPolicyService from './OnCallDutyPolicyService'; -import TeamMemberService from './TeamMemberService'; -import UserService from './UserService'; -import URL from 'Common/Types/API/URL'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Typeof from 'Common/Types/Typeof'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import Model from 'Model/Models/Incident'; -import IncidentOwnerTeam from 'Model/Models/IncidentOwnerTeam'; -import IncidentOwnerUser from 'Model/Models/IncidentOwnerUser'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import User from 'Model/Models/User'; +import DatabaseConfig from "../DatabaseConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import DatabaseService from "./DatabaseService"; +import IncidentOwnerTeamService from "./IncidentOwnerTeamService"; +import IncidentOwnerUserService from "./IncidentOwnerUserService"; +import IncidentStateService from "./IncidentStateService"; +import IncidentStateTimelineService from "./IncidentStateTimelineService"; +import MonitorService from "./MonitorService"; +import MonitorStatusService from "./MonitorStatusService"; +import MonitorStatusTimelineService from "./MonitorStatusTimelineService"; +import OnCallDutyPolicyService from "./OnCallDutyPolicyService"; +import TeamMemberService from "./TeamMemberService"; +import UserService from "./UserService"; +import URL from "Common/Types/API/URL"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Typeof from "Common/Types/Typeof"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import Model from "Model/Models/Incident"; +import IncidentOwnerTeam from "Model/Models/IncidentOwnerTeam"; +import IncidentOwnerUser from "Model/Models/IncidentOwnerUser"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import User from "Model/Models/User"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 120); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 120); + } + + public async acknowledgeIncident( + incidentId: ObjectID, + acknowledgedByUserId: ObjectID, + ): Promise<void> { + const incident: Model | null = await this.findOneById({ + id: incidentId, + select: { + projectId: true, + }, + props: { + isRoot: true, + }, + }); + + if (!incident || !incident.projectId) { + throw new BadDataException("Incident not found."); } - public async acknowledgeIncident( - incidentId: ObjectID, - acknowledgedByUserId: ObjectID - ): Promise<void> { - const incident: Model | null = await this.findOneById({ - id: incidentId, - select: { - projectId: true, - }, - props: { - isRoot: true, - }, - }); + const incidentState: IncidentState | null = + await IncidentStateService.findOneBy({ + query: { + projectId: incident.projectId, + isAcknowledgedState: true, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (!incident || !incident.projectId) { - throw new BadDataException('Incident not found.'); - } - - const incidentState: IncidentState | null = - await IncidentStateService.findOneBy({ - query: { - projectId: incident.projectId, - isAcknowledgedState: true, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); - - if (!incidentState || !incidentState.id) { - throw new BadDataException( - 'Acknowledged state not found for this project. Please add acknowledged state from settings.' - ); - } - - const incidentStateTimeline: IncidentStateTimeline = - new IncidentStateTimeline(); - incidentStateTimeline.projectId = incident.projectId; - incidentStateTimeline.incidentId = incidentId; - incidentStateTimeline.incidentStateId = incidentState.id; - incidentStateTimeline.createdByUserId = acknowledgedByUserId; - - await IncidentStateTimelineService.create({ - data: incidentStateTimeline, - props: { - isRoot: true, - }, - }); + if (!incidentState || !incidentState.id) { + throw new BadDataException( + "Acknowledged state not found for this project. Please add acknowledged state from settings.", + ); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.props.tenantId && !createBy.props.isRoot) { - throw new BadDataException( - 'ProjectId required to create incident.' - ); - } + const incidentStateTimeline: IncidentStateTimeline = + new IncidentStateTimeline(); + incidentStateTimeline.projectId = incident.projectId; + incidentStateTimeline.incidentId = incidentId; + incidentStateTimeline.incidentStateId = incidentState.id; + incidentStateTimeline.createdByUserId = acknowledgedByUserId; - const incidentState: IncidentState | null = - await IncidentStateService.findOneBy({ - query: { - projectId: - createBy.props.tenantId || createBy.data.projectId!, - isCreatedState: true, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); + await IncidentStateTimelineService.create({ + data: incidentStateTimeline, + props: { + isRoot: true, + }, + }); + } - if (!incidentState || !incidentState.id) { - throw new BadDataException( - 'Created incident state not found for this project. Please add created incident state from settings.' - ); - } - - createBy.data.currentIncidentStateId = incidentState.id; - - if ( - (createBy.data.createdByUserId || - createBy.data.createdByUser || - createBy.props.userId) && - !createBy.data.rootCause - ) { - let userId: ObjectID | undefined = createBy.data.createdByUserId; - - if (createBy.props.userId) { - userId = createBy.props.userId; - } - - if (createBy.data.createdByUser && createBy.data.createdByUser.id) { - userId = createBy.data.createdByUser.id; - } - - const user: User | null = await UserService.findOneBy({ - query: { - _id: userId?.toString() as string, - }, - select: { - _id: true, - name: true, - email: true, - }, - props: { - isRoot: true, - }, - }); - - if (user) { - createBy.data.rootCause = `Incident created by ${user.name} (${user.email})`; - } - } - - return { createBy, carryForward: null }; + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.props.tenantId && !createBy.props.isRoot) { + throw new BadDataException("ProjectId required to create incident."); } - protected override async onCreateSuccess( - onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - if (!createdItem.projectId) { - throw new BadDataException('projectId is required'); - } + const incidentState: IncidentState | null = + await IncidentStateService.findOneBy({ + query: { + projectId: createBy.props.tenantId || createBy.data.projectId!, + isCreatedState: true, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (!createdItem.id) { - throw new BadDataException('id is required'); - } - - if (!createdItem.currentIncidentStateId) { - throw new BadDataException('currentIncidentStateId is required'); - } - - if (createdItem.changeMonitorStatusToId && createdItem.projectId) { - // change status of all the monitors. - await MonitorService.changeMonitorStatus( - createdItem.projectId, - createdItem.monitors?.map((monitor: Monitor) => { - return new ObjectID(monitor._id || ''); - }) || [], - createdItem.changeMonitorStatusToId, - true, // notifyMonitorOwners - createdItem.rootCause || - 'Status was changed because incident ' + - createdItem.id.toString() + - ' was created.', - createdItem.createdStateLog, - onCreate.createBy.props - ); - } - - await this.changeIncidentState({ - projectId: createdItem.projectId, - incidentId: createdItem.id, - incidentStateId: createdItem.currentIncidentStateId, - shouldNotifyStatusPageSubscribers: Boolean( - createdItem.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated - ), - isSubscribersNotified: Boolean( - createdItem.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated - ), // we dont want to notify subscribers when incident state changes because they are already notified when the incident is created. - notifyOwners: false, - rootCause: createdItem.rootCause, - stateChangeLog: createdItem.createdStateLog, - props: { - isRoot: true, - }, - }); - - // add owners. - - if ( - onCreate.createBy.miscDataProps && - (onCreate.createBy.miscDataProps['ownerTeams'] || - onCreate.createBy.miscDataProps['ownerUsers']) - ) { - await this.addOwners( - createdItem.projectId, - createdItem.id, - (onCreate.createBy.miscDataProps[ - 'ownerUsers' - ] as Array<ObjectID>) || [], - (onCreate.createBy.miscDataProps[ - 'ownerTeams' - ] as Array<ObjectID>) || [], - false, - onCreate.createBy.props - ); - } - - if ( - createdItem.onCallDutyPolicies?.length && - createdItem.onCallDutyPolicies?.length > 0 - ) { - for (const policy of createdItem.onCallDutyPolicies) { - await OnCallDutyPolicyService.executePolicy( - new ObjectID(policy._id as string), - { - triggeredByIncidentId: createdItem.id!, - userNotificationEventType: - UserNotificationEventType.IncidentCreated, - } - ); - } - } - - // check if the incident is created manaull by a user and if thats the case, then disable active monitoting on that monitor. - - if (!createdItem.isCreatedAutomatically) { - const monitors: Array<Monitor> = createdItem.monitors || []; - - for (const monitor of monitors) { - await MonitorService.updateOneById({ - id: monitor.id!, - data: { - disableActiveMonitoringBecauseOfManualIncident: true, - }, - props: { - isRoot: true, - }, - }); - } - } - - return createdItem; + if (!incidentState || !incidentState.id) { + throw new BadDataException( + "Created incident state not found for this project. Please add created incident state from settings.", + ); } - public async findOwners(incidentId: ObjectID): Promise<Array<User>> { - if (!incidentId) { - throw new BadDataException('incidentId is required'); - } + createBy.data.currentIncidentStateId = incidentState.id; - const ownerUsers: Array<IncidentOwnerUser> = - await IncidentOwnerUserService.findBy({ - query: { - incidentId: incidentId, - }, - select: { - _id: true, - user: { - _id: true, - email: true, - name: true, - }, - }, - props: { - isRoot: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - }); + if ( + (createBy.data.createdByUserId || + createBy.data.createdByUser || + createBy.props.userId) && + !createBy.data.rootCause + ) { + let userId: ObjectID | undefined = createBy.data.createdByUserId; - const ownerTeams: Array<IncidentOwnerTeam> = - await IncidentOwnerTeamService.findBy({ - query: { - incidentId: incidentId, - }, - select: { - _id: true, - teamId: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); + if (createBy.props.userId) { + userId = createBy.props.userId; + } - const users: Array<User> = - ownerUsers.map((ownerUser: IncidentOwnerUser) => { - return ownerUser.user!; - }) || []; + if (createBy.data.createdByUser && createBy.data.createdByUser.id) { + userId = createBy.data.createdByUser.id; + } - if (ownerTeams.length > 0) { - const teamIds: Array<ObjectID> = - ownerTeams.map((ownerTeam: IncidentOwnerTeam) => { - return ownerTeam.teamId!; - }) || []; + const user: User | null = await UserService.findOneBy({ + query: { + _id: userId?.toString() as string, + }, + select: { + _id: true, + name: true, + email: true, + }, + props: { + isRoot: true, + }, + }); - const teamUsers: Array<User> = - await TeamMemberService.getUsersInTeams(teamIds); - - for (const teamUser of teamUsers) { - //check if the user is already added. - const isUserAlreadyAdded: User | undefined = users.find( - (user: User) => { - return user.id!.toString() === teamUser.id!.toString(); - } - ); - - if (!isUserAlreadyAdded) { - users.push(teamUser); - } - } - } - - return users; + if (user) { + createBy.data.rootCause = `Incident created by ${user.name} (${user.email})`; + } } - public async addOwners( - projectId: ObjectID, - incidentId: ObjectID, - userIds: Array<ObjectID>, - teamIds: Array<ObjectID>, - notifyOwners: boolean, - props: DatabaseCommonInteractionProps - ): Promise<void> { - for (let teamId of teamIds) { - if (typeof teamId === Typeof.String) { - teamId = new ObjectID(teamId.toString()); - } + return { createBy, carryForward: null }; + } - const teamOwner: IncidentOwnerTeam = new IncidentOwnerTeam(); - teamOwner.incidentId = incidentId; - teamOwner.projectId = projectId; - teamOwner.teamId = teamId; - teamOwner.isOwnerNotified = !notifyOwners; - - await IncidentOwnerTeamService.create({ - data: teamOwner, - props: props, - }); - } - - for (let userId of userIds) { - if (typeof userId === Typeof.String) { - userId = new ObjectID(userId.toString()); - } - const teamOwner: IncidentOwnerUser = new IncidentOwnerUser(); - teamOwner.incidentId = incidentId; - teamOwner.projectId = projectId; - teamOwner.userId = userId; - teamOwner.isOwnerNotified = !notifyOwners; - await IncidentOwnerUserService.create({ - data: teamOwner, - props: props, - }); - } + protected override async onCreateSuccess( + onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + if (!createdItem.projectId) { + throw new BadDataException("projectId is required"); } - public async getIncidentLinkInDashboard( - projectId: ObjectID, - incidentId: ObjectID - ): Promise<URL> { - const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl(); + if (!createdItem.id) { + throw new BadDataException("id is required"); + } - return URL.fromString(dashboardUrl.toString()).addRoute( - `/${projectId.toString()}/incidents/${incidentId.toString()}` + if (!createdItem.currentIncidentStateId) { + throw new BadDataException("currentIncidentStateId is required"); + } + + if (createdItem.changeMonitorStatusToId && createdItem.projectId) { + // change status of all the monitors. + await MonitorService.changeMonitorStatus( + createdItem.projectId, + createdItem.monitors?.map((monitor: Monitor) => { + return new ObjectID(monitor._id || ""); + }) || [], + createdItem.changeMonitorStatusToId, + true, // notifyMonitorOwners + createdItem.rootCause || + "Status was changed because incident " + + createdItem.id.toString() + + " was created.", + createdItem.createdStateLog, + onCreate.createBy.props, + ); + } + + await this.changeIncidentState({ + projectId: createdItem.projectId, + incidentId: createdItem.id, + incidentStateId: createdItem.currentIncidentStateId, + shouldNotifyStatusPageSubscribers: Boolean( + createdItem.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated, + ), + isSubscribersNotified: Boolean( + createdItem.shouldStatusPageSubscribersBeNotifiedOnIncidentCreated, + ), // we dont want to notify subscribers when incident state changes because they are already notified when the incident is created. + notifyOwners: false, + rootCause: createdItem.rootCause, + stateChangeLog: createdItem.createdStateLog, + props: { + isRoot: true, + }, + }); + + // add owners. + + if ( + onCreate.createBy.miscDataProps && + (onCreate.createBy.miscDataProps["ownerTeams"] || + onCreate.createBy.miscDataProps["ownerUsers"]) + ) { + await this.addOwners( + createdItem.projectId, + createdItem.id, + (onCreate.createBy.miscDataProps["ownerUsers"] as Array<ObjectID>) || + [], + (onCreate.createBy.miscDataProps["ownerTeams"] as Array<ObjectID>) || + [], + false, + onCreate.createBy.props, + ); + } + + if ( + createdItem.onCallDutyPolicies?.length && + createdItem.onCallDutyPolicies?.length > 0 + ) { + for (const policy of createdItem.onCallDutyPolicies) { + await OnCallDutyPolicyService.executePolicy( + new ObjectID(policy._id as string), + { + triggeredByIncidentId: createdItem.id!, + userNotificationEventType: + UserNotificationEventType.IncidentCreated, + }, ); + } } - protected override async onUpdateSuccess( - onUpdate: OnUpdate<Model>, - updatedItemIds: ObjectID[] - ): Promise<OnUpdate<Model>> { - if ( - onUpdate.updateBy.data.currentIncidentStateId && - onUpdate.updateBy.props.tenantId - ) { - for (const itemId of updatedItemIds) { - await this.changeIncidentState({ - projectId: onUpdate.updateBy.props.tenantId as ObjectID, - incidentId: itemId, - incidentStateId: onUpdate.updateBy.data - .currentIncidentStateId as ObjectID, - notifyOwners: true, - shouldNotifyStatusPageSubscribers: true, - isSubscribersNotified: false, - rootCause: - 'This status was changed when the incident was updated.', - stateChangeLog: undefined, - props: { - isRoot: true, - }, - }); - } + // check if the incident is created manaull by a user and if thats the case, then disable active monitoting on that monitor. + + if (!createdItem.isCreatedAutomatically) { + const monitors: Array<Monitor> = createdItem.monitors || []; + + for (const monitor of monitors) { + await MonitorService.updateOneById({ + id: monitor.id!, + data: { + disableActiveMonitoringBecauseOfManualIncident: true, + }, + props: { + isRoot: true, + }, + }); + } + } + + return createdItem; + } + + public async findOwners(incidentId: ObjectID): Promise<Array<User>> { + if (!incidentId) { + throw new BadDataException("incidentId is required"); + } + + const ownerUsers: Array<IncidentOwnerUser> = + await IncidentOwnerUserService.findBy({ + query: { + incidentId: incidentId, + }, + select: { + _id: true, + user: { + _id: true, + email: true, + name: true, + }, + }, + props: { + isRoot: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + }); + + const ownerTeams: Array<IncidentOwnerTeam> = + await IncidentOwnerTeamService.findBy({ + query: { + incidentId: incidentId, + }, + select: { + _id: true, + teamId: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const users: Array<User> = + ownerUsers.map((ownerUser: IncidentOwnerUser) => { + return ownerUser.user!; + }) || []; + + if (ownerTeams.length > 0) { + const teamIds: Array<ObjectID> = + ownerTeams.map((ownerTeam: IncidentOwnerTeam) => { + return ownerTeam.teamId!; + }) || []; + + const teamUsers: Array<User> = + await TeamMemberService.getUsersInTeams(teamIds); + + for (const teamUser of teamUsers) { + //check if the user is already added. + const isUserAlreadyAdded: User | undefined = users.find( + (user: User) => { + return user.id!.toString() === teamUser.id!.toString(); + }, + ); + + if (!isUserAlreadyAdded) { + users.push(teamUser); } - - return onUpdate; + } } - public async doesMonitorHasMoreActiveManualIncidents( - monitorId: ObjectID, - proojectId: ObjectID - ): Promise<boolean> { - const resolvedState: IncidentState | null = - await IncidentStateService.findOneBy({ - query: { - projectId: proojectId, - isResolvedState: true, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - }); + return users; + } - const incidentCount: PositiveNumber = await this.countBy({ - query: { - monitors: QueryHelper.inRelationArray([monitorId]), - currentIncidentState: { - order: QueryHelper.lessThan(resolvedState?.order as number), - }, - isCreatedAutomatically: false, + public async addOwners( + projectId: ObjectID, + incidentId: ObjectID, + userIds: Array<ObjectID>, + teamIds: Array<ObjectID>, + notifyOwners: boolean, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + for (let teamId of teamIds) { + if (typeof teamId === Typeof.String) { + teamId = new ObjectID(teamId.toString()); + } + + const teamOwner: IncidentOwnerTeam = new IncidentOwnerTeam(); + teamOwner.incidentId = incidentId; + teamOwner.projectId = projectId; + teamOwner.teamId = teamId; + teamOwner.isOwnerNotified = !notifyOwners; + + await IncidentOwnerTeamService.create({ + data: teamOwner, + props: props, + }); + } + + for (let userId of userIds) { + if (typeof userId === Typeof.String) { + userId = new ObjectID(userId.toString()); + } + const teamOwner: IncidentOwnerUser = new IncidentOwnerUser(); + teamOwner.incidentId = incidentId; + teamOwner.projectId = projectId; + teamOwner.userId = userId; + teamOwner.isOwnerNotified = !notifyOwners; + await IncidentOwnerUserService.create({ + data: teamOwner, + props: props, + }); + } + } + + public async getIncidentLinkInDashboard( + projectId: ObjectID, + incidentId: ObjectID, + ): Promise<URL> { + const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl(); + + return URL.fromString(dashboardUrl.toString()).addRoute( + `/${projectId.toString()}/incidents/${incidentId.toString()}`, + ); + } + + protected override async onUpdateSuccess( + onUpdate: OnUpdate<Model>, + updatedItemIds: ObjectID[], + ): Promise<OnUpdate<Model>> { + if ( + onUpdate.updateBy.data.currentIncidentStateId && + onUpdate.updateBy.props.tenantId + ) { + for (const itemId of updatedItemIds) { + await this.changeIncidentState({ + projectId: onUpdate.updateBy.props.tenantId as ObjectID, + incidentId: itemId, + incidentStateId: onUpdate.updateBy.data + .currentIncidentStateId as ObjectID, + notifyOwners: true, + shouldNotifyStatusPageSubscribers: true, + isSubscribersNotified: false, + rootCause: "This status was changed when the incident was updated.", + stateChangeLog: undefined, + props: { + isRoot: true, + }, + }); + } + } + + return onUpdate; + } + + public async doesMonitorHasMoreActiveManualIncidents( + monitorId: ObjectID, + proojectId: ObjectID, + ): Promise<boolean> { + const resolvedState: IncidentState | null = + await IncidentStateService.findOneBy({ + query: { + projectId: proojectId, + isResolvedState: true, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + }); + + const incidentCount: PositiveNumber = await this.countBy({ + query: { + monitors: QueryHelper.inRelationArray([monitorId]), + currentIncidentState: { + order: QueryHelper.lessThan(resolvedState?.order as number), + }, + isCreatedAutomatically: false, + }, + props: { + isRoot: true, + }, + }); + + return incidentCount.toNumber() > 0; + } + + public async markMonitorsActiveForMonitoring( + projectId: ObjectID, + monitors: Array<Monitor>, + ): Promise<void> { + // resolve all the monitors. + + if (monitors.length > 0) { + // get resolved monitor state. + const resolvedMonitorState: MonitorStatus | null = + await MonitorStatusService.findOneBy({ + query: { + projectId: projectId!, + isOperationalState: true, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + if (resolvedMonitorState) { + for (const monitor of monitors) { + //check state of the monitor. + + const doesMonitorHasMoreActiveManualIncidents: boolean = + await this.doesMonitorHasMoreActiveManualIncidents( + monitor.id!, + projectId!, + ); + + if (doesMonitorHasMoreActiveManualIncidents) { + continue; + } + + await MonitorService.updateOneById({ + id: monitor.id!, + data: { + disableActiveMonitoringBecauseOfManualIncident: false, }, props: { + isRoot: true, + }, + }); + + const latestState: MonitorStatusTimeline | null = + await MonitorStatusTimelineService.findOneBy({ + query: { + monitorId: monitor.id!, + projectId: projectId!, + }, + select: { + _id: true, + monitorStatusId: true, + }, + props: { isRoot: true, - }, - }); - - return incidentCount.toNumber() > 0; - } - - public async markMonitorsActiveForMonitoring( - projectId: ObjectID, - monitors: Array<Monitor> - ): Promise<void> { - // resolve all the monitors. - - if (monitors.length > 0) { - // get resolved monitor state. - const resolvedMonitorState: MonitorStatus | null = - await MonitorStatusService.findOneBy({ - query: { - projectId: projectId!, - isOperationalState: true, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - if (resolvedMonitorState) { - for (const monitor of monitors) { - //check state of the monitor. - - const doesMonitorHasMoreActiveManualIncidents: boolean = - await this.doesMonitorHasMoreActiveManualIncidents( - monitor.id!, - projectId! - ); - - if (doesMonitorHasMoreActiveManualIncidents) { - continue; - } - - await MonitorService.updateOneById({ - id: monitor.id!, - data: { - disableActiveMonitoringBecauseOfManualIncident: - false, - }, - props: { - isRoot: true, - }, - }); - - const latestState: MonitorStatusTimeline | null = - await MonitorStatusTimelineService.findOneBy({ - query: { - monitorId: monitor.id!, - projectId: projectId!, - }, - select: { - _id: true, - monitorStatusId: true, - }, - props: { - isRoot: true, - }, - sort: { - createdAt: SortOrder.Descending, - }, - }); - - if ( - latestState && - latestState.monitorStatusId?.toString() === - resolvedMonitorState.id!.toString() - ) { - // already on this state. Skip. - continue; - } - - const monitorStatusTimeline: MonitorStatusTimeline = - new MonitorStatusTimeline(); - monitorStatusTimeline.monitorId = monitor.id!; - monitorStatusTimeline.projectId = projectId!; - monitorStatusTimeline.monitorStatusId = - resolvedMonitorState.id!; - - await MonitorStatusTimelineService.create({ - data: monitorStatusTimeline, - props: { - isRoot: true, - }, - }); - } - } - } - } - - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - const incidents: Array<Model> = await this.findBy({ - query: deleteBy.query, - limit: LIMIT_MAX, - skip: 0, - select: { - projectId: true, - monitors: { - _id: true, - }, - }, - props: { - isRoot: true, - }, - }); - - return { - deleteBy, - carryForward: { - incidents: incidents, - }, - }; - } - - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - if (onDelete.carryForward && onDelete.carryForward.incidents) { - for (const incident of onDelete.carryForward.incidents) { - if (incident.monitors && incident.monitors.length > 0) { - await this.markMonitorsActiveForMonitoring( - incident.projectId!, - incident.monitors - ); - } - } - } - - return onDelete; - } - - public async changeIncidentState(data: { - projectId: ObjectID; - incidentId: ObjectID; - incidentStateId: ObjectID; - shouldNotifyStatusPageSubscribers: boolean; - isSubscribersNotified: boolean; - notifyOwners: boolean; - rootCause: string | undefined; - stateChangeLog: JSONObject | undefined; - props: DatabaseCommonInteractionProps | undefined; - }): Promise<void> { - const { - projectId, - incidentId, - incidentStateId, - shouldNotifyStatusPageSubscribers, - isSubscribersNotified, - notifyOwners, - rootCause, - stateChangeLog, - props, - } = data; - - // get last monitor status timeline. - const lastIncidentStatusTimeline: IncidentStateTimeline | null = - await IncidentStateTimelineService.findOneBy({ - query: { - incidentId: incidentId, - projectId: projectId, - }, - select: { - _id: true, - incidentStateId: true, - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, + }, + sort: { + createdAt: SortOrder.Descending, + }, }); - if ( - lastIncidentStatusTimeline && - lastIncidentStatusTimeline.incidentStateId && - lastIncidentStatusTimeline.incidentStateId.toString() === - incidentStateId.toString() - ) { - return; + if ( + latestState && + latestState.monitorStatusId?.toString() === + resolvedMonitorState.id!.toString() + ) { + // already on this state. Skip. + continue; + } + + const monitorStatusTimeline: MonitorStatusTimeline = + new MonitorStatusTimeline(); + monitorStatusTimeline.monitorId = monitor.id!; + monitorStatusTimeline.projectId = projectId!; + monitorStatusTimeline.monitorStatusId = resolvedMonitorState.id!; + + await MonitorStatusTimelineService.create({ + data: monitorStatusTimeline, + props: { + isRoot: true, + }, + }); } - - const statusTimeline: IncidentStateTimeline = - new IncidentStateTimeline(); - - statusTimeline.incidentId = incidentId; - statusTimeline.incidentStateId = incidentStateId; - statusTimeline.projectId = projectId; - statusTimeline.isOwnerNotified = !notifyOwners; - statusTimeline.shouldStatusPageSubscribersBeNotified = - shouldNotifyStatusPageSubscribers; - - statusTimeline.isStatusPageSubscribersNotified = isSubscribersNotified; - - if (stateChangeLog) { - statusTimeline.stateChangeLog = stateChangeLog; - } - if (rootCause) { - statusTimeline.rootCause = rootCause; - } - - await IncidentStateTimelineService.create({ - data: statusTimeline, - props: props || {}, - }); + } } + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + const incidents: Array<Model> = await this.findBy({ + query: deleteBy.query, + limit: LIMIT_MAX, + skip: 0, + select: { + projectId: true, + monitors: { + _id: true, + }, + }, + props: { + isRoot: true, + }, + }); + + return { + deleteBy, + carryForward: { + incidents: incidents, + }, + }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + if (onDelete.carryForward && onDelete.carryForward.incidents) { + for (const incident of onDelete.carryForward.incidents) { + if (incident.monitors && incident.monitors.length > 0) { + await this.markMonitorsActiveForMonitoring( + incident.projectId!, + incident.monitors, + ); + } + } + } + + return onDelete; + } + + public async changeIncidentState(data: { + projectId: ObjectID; + incidentId: ObjectID; + incidentStateId: ObjectID; + shouldNotifyStatusPageSubscribers: boolean; + isSubscribersNotified: boolean; + notifyOwners: boolean; + rootCause: string | undefined; + stateChangeLog: JSONObject | undefined; + props: DatabaseCommonInteractionProps | undefined; + }): Promise<void> { + const { + projectId, + incidentId, + incidentStateId, + shouldNotifyStatusPageSubscribers, + isSubscribersNotified, + notifyOwners, + rootCause, + stateChangeLog, + props, + } = data; + + // get last monitor status timeline. + const lastIncidentStatusTimeline: IncidentStateTimeline | null = + await IncidentStateTimelineService.findOneBy({ + query: { + incidentId: incidentId, + projectId: projectId, + }, + select: { + _id: true, + incidentStateId: true, + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + }); + + if ( + lastIncidentStatusTimeline && + lastIncidentStatusTimeline.incidentStateId && + lastIncidentStatusTimeline.incidentStateId.toString() === + incidentStateId.toString() + ) { + return; + } + + const statusTimeline: IncidentStateTimeline = new IncidentStateTimeline(); + + statusTimeline.incidentId = incidentId; + statusTimeline.incidentStateId = incidentStateId; + statusTimeline.projectId = projectId; + statusTimeline.isOwnerNotified = !notifyOwners; + statusTimeline.shouldStatusPageSubscribersBeNotified = + shouldNotifyStatusPageSubscribers; + + statusTimeline.isStatusPageSubscribersNotified = isSubscribersNotified; + + if (stateChangeLog) { + statusTimeline.stateChangeLog = stateChangeLog; + } + if (rootCause) { + statusTimeline.rootCause = rootCause; + } + + await IncidentStateTimelineService.create({ + data: statusTimeline, + props: props || {}, + }); + } } export default new Service(); diff --git a/CommonServer/Services/IncidentSeverityService.ts b/CommonServer/Services/IncidentSeverityService.ts index 1dc98c8b44..b3367b966a 100644 --- a/CommonServer/Services/IncidentSeverityService.ts +++ b/CommonServer/Services/IncidentSeverityService.ts @@ -1,161 +1,159 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Model from 'Model/Models/IncidentSeverity'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Model from "Model/Models/IncidentSeverity"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.order) { + throw new BadDataException("Incident severity order is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.order) { - throw new BadDataException('Incident severity order is required'); - } + if (!createBy.data.projectId) { + throw new BadDataException("Incident severity projectId is required"); + } - if (!createBy.data.projectId) { - throw new BadDataException( - 'Incident severity projectId is required' - ); - } + await this.rearrangeOrder( + createBy.data.order, + createBy.data.projectId, + true, + ); + return { + createBy: createBy, + carryForward: null, + }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting incident states. Please try the delete with objectId", + ); + } + + let incidentSeverity: Model | null = null; + + if (!deleteBy.props.isRoot) { + incidentSeverity = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + projectId: true, + }, + }); + } + + return { + deleteBy, + carryForward: incidentSeverity, + }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const incidentSeverity: Model | null = onDelete.carryForward; + + if (!deleteBy.props.isRoot && incidentSeverity) { + if ( + incidentSeverity && + incidentSeverity.order && + incidentSeverity.projectId + ) { await this.rearrangeOrder( - createBy.data.order, - createBy.data.projectId, - true + incidentSeverity.order, + incidentSeverity.projectId, + false, ); - - return { - createBy: createBy, - carryForward: null, - }; + } } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting incident states. Please try the delete with objectId' - ); - } + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - let incidentSeverity: Model | null = null; - - if (!deleteBy.props.isRoot) { - incidentSeverity = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - projectId: true, - }, - }); - } - - return { - deleteBy, - carryForward: incidentSeverity, - }; + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.order && !updateBy.props.isRoot) { + throw new BadDataException( + "Incident Severity order should not be updated. Delete this incident state and create a new state with the right order.", + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const incidentSeverity: Model | null = onDelete.carryForward; + return { updateBy, carryForward: null }; + } - if (!deleteBy.props.isRoot && incidentSeverity) { - if ( - incidentSeverity && - incidentSeverity.order && - incidentSeverity.projectId - ) { - await this.rearrangeOrder( - incidentSeverity.order, - incidentSeverity.projectId, - false - ); - } - } + private async rearrangeOrder( + currentOrder: number, + projectId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get incident with this order. + const incidentSeverities: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + projectId: projectId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); - return { - deleteBy: deleteBy, - carryForward: null, - }; - } - - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if (updateBy.data.order && !updateBy.props.isRoot) { - throw new BadDataException( - 'Incident Severity order should not be updated. Delete this incident state and create a new state with the right order.' - ); - } - - return { updateBy, carryForward: null }; - } - - private async rearrangeOrder( - currentOrder: number, - projectId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get incident with this order. - const incidentSeverities: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - projectId: projectId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); - - let newOrder: number = currentOrder; - - for (const incidentSeverity of incidentSeverities) { - if (increaseOrder) { - newOrder = incidentSeverity.order! + 1; - } else { - newOrder = incidentSeverity.order! - 1; - } - - await this.updateOneBy({ - query: { - _id: incidentSeverity._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, - }); - } + let newOrder: number = currentOrder; + + for (const incidentSeverity of incidentSeverities) { + if (increaseOrder) { + newOrder = incidentSeverity.order! + 1; + } else { + newOrder = incidentSeverity.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: incidentSeverity._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); } + } } export default new Service(); diff --git a/CommonServer/Services/IncidentStateService.ts b/CommonServer/Services/IncidentStateService.ts index 7776a03601..69b8d63dfa 100644 --- a/CommonServer/Services/IncidentStateService.ts +++ b/CommonServer/Services/IncidentStateService.ts @@ -1,193 +1,189 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import IncidentState from 'Model/Models/IncidentState'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import IncidentState from "Model/Models/IncidentState"; export class Service extends DatabaseService<IncidentState> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(IncidentState, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(IncidentState, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<IncidentState>, + ): Promise<OnCreate<IncidentState>> { + if (!createBy.data.order) { + throw new BadDataException("Incident State order is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<IncidentState> - ): Promise<OnCreate<IncidentState>> { - if (!createBy.data.order) { - throw new BadDataException('Incident State order is required'); - } + if (!createBy.data.projectId) { + throw new BadDataException("Incident State projectId is required"); + } - if (!createBy.data.projectId) { - throw new BadDataException('Incident State projectId is required'); - } + await this.rearrangeOrder( + createBy.data.order, + createBy.data.projectId, + true, + ); + return { + createBy: createBy, + carryForward: null, + }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<IncidentState>, + ): Promise<OnDelete<IncidentState>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting incident states. Please try the delete with objectId", + ); + } + + let incidentState: IncidentState | null = null; + + if (!deleteBy.props.isRoot) { + incidentState = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + projectId: true, + }, + }); + } + + return { + deleteBy, + carryForward: incidentState, + }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<IncidentState>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<IncidentState>> { + const deleteBy: DeleteBy<IncidentState> = onDelete.deleteBy; + const incidentState: IncidentState | null = onDelete.carryForward; + + if (!deleteBy.props.isRoot && incidentState) { + if (incidentState && incidentState.order && incidentState.projectId) { await this.rearrangeOrder( - createBy.data.order, - createBy.data.projectId, - true + incidentState.order, + incidentState.projectId, + false, ); - - return { - createBy: createBy, - carryForward: null, - }; + } } - protected override async onBeforeDelete( - deleteBy: DeleteBy<IncidentState> - ): Promise<OnDelete<IncidentState>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting incident states. Please try the delete with objectId' - ); - } + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - let incidentState: IncidentState | null = null; - - if (!deleteBy.props.isRoot) { - incidentState = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - projectId: true, - }, - }); - } - - return { - deleteBy, - carryForward: incidentState, - }; + protected override async onBeforeUpdate( + updateBy: UpdateBy<IncidentState>, + ): Promise<OnUpdate<IncidentState>> { + if (updateBy.data.order && !updateBy.props.isRoot) { + throw new BadDataException( + "Incident State order should not be updated. Delete this incident state and create a new state with the right order.", + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<IncidentState>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<IncidentState>> { - const deleteBy: DeleteBy<IncidentState> = onDelete.deleteBy; - const incidentState: IncidentState | null = onDelete.carryForward; + return { updateBy, carryForward: null }; + } - if (!deleteBy.props.isRoot && incidentState) { - if ( - incidentState && - incidentState.order && - incidentState.projectId - ) { - await this.rearrangeOrder( - incidentState.order, - incidentState.projectId, - false - ); - } - } + private async rearrangeOrder( + currentOrder: number, + projectId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get incident with this order. + const incidentStates: Array<IncidentState> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + projectId: projectId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); - return { - deleteBy: deleteBy, - carryForward: null, - }; + let newOrder: number = currentOrder; + + for (const incidentState of incidentStates) { + if (increaseOrder) { + newOrder = incidentState.order! + 1; + } else { + newOrder = incidentState.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: incidentState._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); + } + } + + public async getUnresolvedIncidentStates( + projectId: ObjectID, + props: DatabaseCommonInteractionProps, + ): Promise<IncidentState[]> { + const incidentStates: Array<IncidentState> = await this.findBy({ + query: { + projectId: projectId, + }, + skip: 0, + limit: LIMIT_MAX, + sort: { + order: SortOrder.Ascending, + }, + select: { + _id: true, + isResolvedState: true, + }, + props: props, + }); + + const unresolvedIncidentStates: Array<IncidentState> = []; + + for (const state of incidentStates) { + if (!state.isResolvedState) { + unresolvedIncidentStates.push(state); + } else { + break; // everything after resolved state is resolved + } } - protected override async onBeforeUpdate( - updateBy: UpdateBy<IncidentState> - ): Promise<OnUpdate<IncidentState>> { - if (updateBy.data.order && !updateBy.props.isRoot) { - throw new BadDataException( - 'Incident State order should not be updated. Delete this incident state and create a new state with the right order.' - ); - } - - return { updateBy, carryForward: null }; - } - - private async rearrangeOrder( - currentOrder: number, - projectId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get incident with this order. - const incidentStates: Array<IncidentState> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - projectId: projectId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); - - let newOrder: number = currentOrder; - - for (const incidentState of incidentStates) { - if (increaseOrder) { - newOrder = incidentState.order! + 1; - } else { - newOrder = incidentState.order! - 1; - } - - await this.updateOneBy({ - query: { - _id: incidentState._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, - }); - } - } - - public async getUnresolvedIncidentStates( - projectId: ObjectID, - props: DatabaseCommonInteractionProps - ): Promise<IncidentState[]> { - const incidentStates: Array<IncidentState> = await this.findBy({ - query: { - projectId: projectId, - }, - skip: 0, - limit: LIMIT_MAX, - sort: { - order: SortOrder.Ascending, - }, - select: { - _id: true, - isResolvedState: true, - }, - props: props, - }); - - const unresolvedIncidentStates: Array<IncidentState> = []; - - for (const state of incidentStates) { - if (!state.isResolvedState) { - unresolvedIncidentStates.push(state); - } else { - break; // everything after resolved state is resolved - } - } - - return unresolvedIncidentStates; - } + return unresolvedIncidentStates; + } } export default new Service(); diff --git a/CommonServer/Services/IncidentStateTimelineService.ts b/CommonServer/Services/IncidentStateTimelineService.ts index 94a3e6c8cb..ded2fa41c6 100644 --- a/CommonServer/Services/IncidentStateTimelineService.ts +++ b/CommonServer/Services/IncidentStateTimelineService.ts @@ -1,398 +1,388 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import DatabaseService from './DatabaseService'; -import IncidentPublicNoteService from './IncidentPublicNoteService'; -import IncidentService from './IncidentService'; -import IncidentStateService from './IncidentStateService'; -import UserService from './UserService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Incident from 'Model/Models/Incident'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import User from 'Model/Models/User'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import DatabaseService from "./DatabaseService"; +import IncidentPublicNoteService from "./IncidentPublicNoteService"; +import IncidentService from "./IncidentService"; +import IncidentStateService from "./IncidentStateService"; +import UserService from "./UserService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Incident from "Model/Models/Incident"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import User from "Model/Models/User"; export class Service extends DatabaseService<IncidentStateTimeline> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(IncidentStateTimeline, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 120); + public constructor(postgresDatabase?: PostgresDatabase) { + super(IncidentStateTimeline, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 120); + } + + public async getResolvedStateIdForProject( + projectId: ObjectID, + ): Promise<ObjectID> { + const resolvedState: IncidentState | null = + await IncidentStateService.findOneBy({ + query: { + projectId: projectId, + isResolvedState: true, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + if (!resolvedState) { + throw new BadDataException("No resolved state found for the project"); } - public async getResolvedStateIdForProject( - projectId: ObjectID - ): Promise<ObjectID> { - const resolvedState: IncidentState | null = - await IncidentStateService.findOneBy({ - query: { - projectId: projectId, - isResolvedState: true, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); + return resolvedState.id!; + } - if (!resolvedState) { - throw new BadDataException( - 'No resolved state found for the project' - ); - } - - return resolvedState.id!; + protected override async onBeforeCreate( + createBy: CreateBy<IncidentStateTimeline>, + ): Promise<OnCreate<IncidentStateTimeline>> { + if (!createBy.data.incidentId) { + throw new BadDataException("incidentId is null"); } - protected override async onBeforeCreate( - createBy: CreateBy<IncidentStateTimeline> - ): Promise<OnCreate<IncidentStateTimeline>> { - if (!createBy.data.incidentId) { - throw new BadDataException('incidentId is null'); - } - - if (!createBy.data.startsAt) { - createBy.data.startsAt = OneUptimeDate.getCurrentDate(); - } - - if ( - (createBy.data.createdByUserId || - createBy.data.createdByUser || - createBy.props.userId) && - !createBy.data.rootCause - ) { - let userId: ObjectID | undefined = createBy.data.createdByUserId; - - if (createBy.props.userId) { - userId = createBy.props.userId; - } - - if (createBy.data.createdByUser && createBy.data.createdByUser.id) { - userId = createBy.data.createdByUser.id; - } - - const user: User | null = await UserService.findOneBy({ - query: { - _id: userId?.toString() as string, - }, - select: { - _id: true, - name: true, - email: true, - }, - props: { - isRoot: true, - }, - }); - - if (user) { - createBy.data.rootCause = `Incident state created by ${user.name} (${user.email})`; - } - } - - const lastIncidentStateTimeline: IncidentStateTimeline | null = - await this.findOneBy({ - query: { - incidentId: createBy.data.incidentId, - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - const publicNote: string | undefined = ( - createBy.miscDataProps as JSONObject | undefined - )?.['publicNote'] as string | undefined; - - if (publicNote) { - const incidentPublicNote: IncidentPublicNote = - new IncidentPublicNote(); - incidentPublicNote.incidentId = createBy.data.incidentId; - incidentPublicNote.note = publicNote; - incidentPublicNote.postedAt = createBy.data.startsAt; - incidentPublicNote.createdAt = createBy.data.startsAt; - incidentPublicNote.projectId = createBy.data.projectId!; - incidentPublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated = - Boolean(createBy.data.shouldStatusPageSubscribersBeNotified); - - // mark status page subscribers as notified for this state change because we dont want to send duplicate (two) emails one for public note and one for state change. - if ( - incidentPublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated - ) { - createBy.data.isStatusPageSubscribersNotified = true; - } - - await IncidentPublicNoteService.create({ - data: incidentPublicNote, - props: createBy.props, - }); - } - - return { - createBy, - carryForward: { - lastIncidentStateTimelineId: - lastIncidentStateTimeline?.id || null, - }, - }; + if (!createBy.data.startsAt) { + createBy.data.startsAt = OneUptimeDate.getCurrentDate(); } - protected override async onCreateSuccess( - onCreate: OnCreate<IncidentStateTimeline>, - createdItem: IncidentStateTimeline - ): Promise<IncidentStateTimeline> { - if (!createdItem.incidentId) { - throw new BadDataException('incidentId is null'); - } + if ( + (createBy.data.createdByUserId || + createBy.data.createdByUser || + createBy.props.userId) && + !createBy.data.rootCause + ) { + let userId: ObjectID | undefined = createBy.data.createdByUserId; - if (!createdItem.incidentStateId) { - throw new BadDataException('incidentStateId is null'); - } + if (createBy.props.userId) { + userId = createBy.props.userId; + } - // update the last status as ended. + if (createBy.data.createdByUser && createBy.data.createdByUser.id) { + userId = createBy.data.createdByUser.id; + } - if (onCreate.carryForward.lastIncidentStateTimelineId) { - await this.updateOneById({ - id: onCreate.carryForward.lastIncidentStateTimelineId!, - data: { - endsAt: - createdItem.createdAt || OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); - } + const user: User | null = await UserService.findOneBy({ + query: { + _id: userId?.toString() as string, + }, + select: { + _id: true, + name: true, + email: true, + }, + props: { + isRoot: true, + }, + }); - await IncidentService.updateOneBy({ - query: { - _id: createdItem.incidentId?.toString(), - }, - data: { - currentIncidentStateId: createdItem.incidentStateId, - }, - props: onCreate.createBy.props, + if (user) { + createBy.data.rootCause = `Incident state created by ${user.name} (${user.email})`; + } + } + + const lastIncidentStateTimeline: IncidentStateTimeline | null = + await this.findOneBy({ + query: { + incidentId: createBy.data.incidentId, + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + const publicNote: string | undefined = ( + createBy.miscDataProps as JSONObject | undefined + )?.["publicNote"] as string | undefined; + + if (publicNote) { + const incidentPublicNote: IncidentPublicNote = new IncidentPublicNote(); + incidentPublicNote.incidentId = createBy.data.incidentId; + incidentPublicNote.note = publicNote; + incidentPublicNote.postedAt = createBy.data.startsAt; + incidentPublicNote.createdAt = createBy.data.startsAt; + incidentPublicNote.projectId = createBy.data.projectId!; + incidentPublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated = + Boolean(createBy.data.shouldStatusPageSubscribersBeNotified); + + // mark status page subscribers as notified for this state change because we dont want to send duplicate (two) emails one for public note and one for state change. + if ( + incidentPublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated + ) { + createBy.data.isStatusPageSubscribersNotified = true; + } + + await IncidentPublicNoteService.create({ + data: incidentPublicNote, + props: createBy.props, + }); + } + + return { + createBy, + carryForward: { + lastIncidentStateTimelineId: lastIncidentStateTimeline?.id || null, + }, + }; + } + + protected override async onCreateSuccess( + onCreate: OnCreate<IncidentStateTimeline>, + createdItem: IncidentStateTimeline, + ): Promise<IncidentStateTimeline> { + if (!createdItem.incidentId) { + throw new BadDataException("incidentId is null"); + } + + if (!createdItem.incidentStateId) { + throw new BadDataException("incidentStateId is null"); + } + + // update the last status as ended. + + if (onCreate.carryForward.lastIncidentStateTimelineId) { + await this.updateOneById({ + id: onCreate.carryForward.lastIncidentStateTimelineId!, + data: { + endsAt: createdItem.createdAt || OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); + } + + await IncidentService.updateOneBy({ + query: { + _id: createdItem.incidentId?.toString(), + }, + data: { + currentIncidentStateId: createdItem.incidentStateId, + }, + props: onCreate.createBy.props, + }); + + // TODO: DELETE THIS WHEN WORKFLOW IS IMPLEMENMTED. + // check if this is resolved state, and if it is then resolve all the monitors. + + const isResolvedState: IncidentState | null = + await IncidentStateService.findOneBy({ + query: { + _id: createdItem.incidentStateId.toString()!, + isResolvedState: true, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + if (isResolvedState) { + const incident: Incident | null = await IncidentService.findOneBy({ + query: { + _id: createdItem.incidentId.toString(), + }, + select: { + _id: true, + projectId: true, + monitors: { + _id: true, + }, + }, + props: { + isRoot: true, + }, + }); + + if (incident) { + await IncidentService.markMonitorsActiveForMonitoring( + incident.projectId!, + incident.monitors || [], + ); + } + } + + return createdItem; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<IncidentStateTimeline>, + ): Promise<OnDelete<IncidentStateTimeline>> { + if (deleteBy.query._id) { + const incidentStateTimelineToBeDeleted: IncidentStateTimeline | null = + await this.findOneById({ + id: new ObjectID(deleteBy.query._id as string), + select: { + incidentId: true, + startsAt: true, + }, + props: { + isRoot: true, + }, }); - // TODO: DELETE THIS WHEN WORKFLOW IS IMPLEMENMTED. - // check if this is resolved state, and if it is then resolve all the monitors. + const incidentId: ObjectID | undefined = + incidentStateTimelineToBeDeleted?.incidentId; - const isResolvedState: IncidentState | null = - await IncidentStateService.findOneBy({ - query: { - _id: createdItem.incidentStateId.toString()!, - isResolvedState: true, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, + if (incidentId) { + const incidentStateTimeline: PositiveNumber = await this.countBy({ + query: { + incidentId: incidentId, + }, + props: { + isRoot: true, + }, + }); + + if (incidentStateTimeline.isOne()) { + throw new BadDataException( + "Cannot delete the only state timeline. Incident should have at least one state in its timeline.", + ); + } + + if (incidentStateTimelineToBeDeleted?.startsAt) { + const beforeState: IncidentStateTimeline | null = + await this.findOneBy({ + query: { + incidentId: incidentId, + startsAt: QueryHelper.lessThan( + incidentStateTimelineToBeDeleted?.startsAt, + ), + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + startsAt: true, + }, }); - if (isResolvedState) { - const incident: Incident | null = await IncidentService.findOneBy({ + if (beforeState) { + const afterState: IncidentStateTimeline | null = + await this.findOneBy({ query: { - _id: createdItem.incidentId.toString(), + incidentId: incidentId, + startsAt: QueryHelper.greaterThan( + incidentStateTimelineToBeDeleted?.startsAt, + ), }, - select: { - _id: true, - projectId: true, - monitors: { - _id: true, - }, + sort: { + createdAt: SortOrder.Ascending, }, props: { - isRoot: true, + isRoot: true, }, - }); + select: { + _id: true, + startsAt: true, + }, + }); - if (incident) { - await IncidentService.markMonitorsActiveForMonitoring( - incident.projectId!, - incident.monitors || [] - ); + if (!afterState) { + // if there's nothing after then end date of before state is null. + + await this.updateOneById({ + id: beforeState.id!, + data: { + endsAt: null as any, + }, + props: { + isRoot: true, + }, + }); + } else { + // if there's something after then end date of before state is start date of after state. + + await this.updateOneById({ + id: beforeState.id!, + data: { + endsAt: afterState.startsAt!, + }, + props: { + isRoot: true, + }, + }); } + } } + } - return createdItem; + return { deleteBy, carryForward: incidentId }; } - protected override async onBeforeDelete( - deleteBy: DeleteBy<IncidentStateTimeline> - ): Promise<OnDelete<IncidentStateTimeline>> { - if (deleteBy.query._id) { - const incidentStateTimelineToBeDeleted: IncidentStateTimeline | null = - await this.findOneById({ - id: new ObjectID(deleteBy.query._id as string), - select: { - incidentId: true, - startsAt: true, - }, - props: { - isRoot: true, - }, - }); + return { deleteBy, carryForward: null }; + } - const incidentId: ObjectID | undefined = - incidentStateTimelineToBeDeleted?.incidentId; + protected override async onDeleteSuccess( + onDelete: OnDelete<IncidentStateTimeline>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<IncidentStateTimeline>> { + if (onDelete.carryForward) { + // this is incidentId. + const incidentId: ObjectID = onDelete.carryForward as ObjectID; - if (incidentId) { - const incidentStateTimeline: PositiveNumber = - await this.countBy({ - query: { - incidentId: incidentId, - }, - props: { - isRoot: true, - }, - }); + // get last status of this monitor. + const incidentStateTimeline: IncidentStateTimeline | null = + await this.findOneBy({ + query: { + incidentId: incidentId, + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + incidentStateId: true, + }, + }); - if (incidentStateTimeline.isOne()) { - throw new BadDataException( - 'Cannot delete the only state timeline. Incident should have at least one state in its timeline.' - ); - } - - if (incidentStateTimelineToBeDeleted?.startsAt) { - const beforeState: IncidentStateTimeline | null = - await this.findOneBy({ - query: { - incidentId: incidentId, - startsAt: QueryHelper.lessThan( - incidentStateTimelineToBeDeleted?.startsAt - ), - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - startsAt: true, - }, - }); - - if (beforeState) { - const afterState: IncidentStateTimeline | null = - await this.findOneBy({ - query: { - incidentId: incidentId, - startsAt: QueryHelper.greaterThan( - incidentStateTimelineToBeDeleted?.startsAt - ), - }, - sort: { - createdAt: SortOrder.Ascending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - startsAt: true, - }, - }); - - if (!afterState) { - // if there's nothing after then end date of before state is null. - - await this.updateOneById({ - id: beforeState.id!, - data: { - endsAt: null as any, - }, - props: { - isRoot: true, - }, - }); - } else { - // if there's something after then end date of before state is start date of after state. - - await this.updateOneById({ - id: beforeState.id!, - data: { - endsAt: afterState.startsAt!, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { deleteBy, carryForward: incidentId }; - } - - return { deleteBy, carryForward: null }; + if (incidentStateTimeline && incidentStateTimeline.incidentStateId) { + await IncidentService.updateOneBy({ + query: { + _id: incidentId.toString(), + }, + data: { + currentIncidentStateId: incidentStateTimeline.incidentStateId, + }, + props: { + isRoot: true, + }, + }); + } } - protected override async onDeleteSuccess( - onDelete: OnDelete<IncidentStateTimeline>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<IncidentStateTimeline>> { - if (onDelete.carryForward) { - // this is incidentId. - const incidentId: ObjectID = onDelete.carryForward as ObjectID; - - // get last status of this monitor. - const incidentStateTimeline: IncidentStateTimeline | null = - await this.findOneBy({ - query: { - incidentId: incidentId, - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - incidentStateId: true, - }, - }); - - if ( - incidentStateTimeline && - incidentStateTimeline.incidentStateId - ) { - await IncidentService.updateOneBy({ - query: { - _id: incidentId.toString(), - }, - data: { - currentIncidentStateId: - incidentStateTimeline.incidentStateId, - }, - props: { - isRoot: true, - }, - }); - } - } - - return onDelete; - } + return onDelete; + } } export default new Service(); diff --git a/CommonServer/Services/IncidentTemplateOwnerTeamService.ts b/CommonServer/Services/IncidentTemplateOwnerTeamService.ts index 8b65fc6cff..51811aab7d 100644 --- a/CommonServer/Services/IncidentTemplateOwnerTeamService.ts +++ b/CommonServer/Services/IncidentTemplateOwnerTeamService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/IncidentTemplateOwnerTeam'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/IncidentTemplateOwnerTeam"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/IncidentTemplateOwnerUserService.ts b/CommonServer/Services/IncidentTemplateOwnerUserService.ts index e9ce834121..b73464997b 100644 --- a/CommonServer/Services/IncidentTemplateOwnerUserService.ts +++ b/CommonServer/Services/IncidentTemplateOwnerUserService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/IncidentTemplateOwnerUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/IncidentTemplateOwnerUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/IncidentTemplateService.ts b/CommonServer/Services/IncidentTemplateService.ts index c6759f44f3..d27b71eda0 100644 --- a/CommonServer/Services/IncidentTemplateService.ts +++ b/CommonServer/Services/IncidentTemplateService.ts @@ -1,91 +1,89 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import IncidentTemplateOwnerTeamService from './IncidentTemplateOwnerTeamService'; -import IncidentTemplateOwnerUserService from './IncidentTemplateOwnerUserService'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import ObjectID from 'Common/Types/ObjectID'; -import Typeof from 'Common/Types/Typeof'; -import Model from 'Model/Models/IncidentTemplate'; -import IncidentTemplateOwnerTeam from 'Model/Models/IncidentTemplateOwnerTeam'; -import IncidentTemplateOwnerUser from 'Model/Models/IncidentTemplateOwnerUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import IncidentTemplateOwnerTeamService from "./IncidentTemplateOwnerTeamService"; +import IncidentTemplateOwnerUserService from "./IncidentTemplateOwnerUserService"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import ObjectID from "Common/Types/ObjectID"; +import Typeof from "Common/Types/Typeof"; +import Model from "Model/Models/IncidentTemplate"; +import IncidentTemplateOwnerTeam from "Model/Models/IncidentTemplateOwnerTeam"; +import IncidentTemplateOwnerUser from "Model/Models/IncidentTemplateOwnerUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onCreateSuccess( + onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + // add owners. + + if ( + createdItem.projectId && + createdItem.id && + onCreate.createBy.miscDataProps && + (onCreate.createBy.miscDataProps["ownerTeams"] || + onCreate.createBy.miscDataProps["ownerUsers"]) + ) { + await this.addOwners( + createdItem.projectId, + createdItem.id, + (onCreate.createBy.miscDataProps["ownerUsers"] as Array<ObjectID>) || + [], + (onCreate.createBy.miscDataProps["ownerTeams"] as Array<ObjectID>) || + [], + false, + onCreate.createBy.props, + ); } - protected override async onCreateSuccess( - onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - // add owners. + return createdItem; + } - if ( - createdItem.projectId && - createdItem.id && - onCreate.createBy.miscDataProps && - (onCreate.createBy.miscDataProps['ownerTeams'] || - onCreate.createBy.miscDataProps['ownerUsers']) - ) { - await this.addOwners( - createdItem.projectId, - createdItem.id, - (onCreate.createBy.miscDataProps[ - 'ownerUsers' - ] as Array<ObjectID>) || [], - (onCreate.createBy.miscDataProps[ - 'ownerTeams' - ] as Array<ObjectID>) || [], - false, - onCreate.createBy.props - ); - } + public async addOwners( + projectId: ObjectID, + incidentTemplateId: ObjectID, + userIds: Array<ObjectID>, + teamIds: Array<ObjectID>, + notifyOwners: boolean, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + for (let teamId of teamIds) { + if (typeof teamId === Typeof.String) { + teamId = new ObjectID(teamId.toString()); + } - return createdItem; + const teamOwner: IncidentTemplateOwnerTeam = + new IncidentTemplateOwnerTeam(); + teamOwner.incidentTemplateId = incidentTemplateId; + teamOwner.projectId = projectId; + teamOwner.teamId = teamId; + teamOwner.isOwnerNotified = !notifyOwners; + + await IncidentTemplateOwnerTeamService.create({ + data: teamOwner, + props: props, + }); } - public async addOwners( - projectId: ObjectID, - incidentTemplateId: ObjectID, - userIds: Array<ObjectID>, - teamIds: Array<ObjectID>, - notifyOwners: boolean, - props: DatabaseCommonInteractionProps - ): Promise<void> { - for (let teamId of teamIds) { - if (typeof teamId === Typeof.String) { - teamId = new ObjectID(teamId.toString()); - } - - const teamOwner: IncidentTemplateOwnerTeam = - new IncidentTemplateOwnerTeam(); - teamOwner.incidentTemplateId = incidentTemplateId; - teamOwner.projectId = projectId; - teamOwner.teamId = teamId; - teamOwner.isOwnerNotified = !notifyOwners; - - await IncidentTemplateOwnerTeamService.create({ - data: teamOwner, - props: props, - }); - } - - for (let userId of userIds) { - if (typeof userId === Typeof.String) { - userId = new ObjectID(userId.toString()); - } - const teamOwner: IncidentTemplateOwnerUser = - new IncidentTemplateOwnerUser(); - teamOwner.incidentTemplateId = incidentTemplateId; - teamOwner.projectId = projectId; - teamOwner.userId = userId; - teamOwner.isOwnerNotified = !notifyOwners; - await IncidentTemplateOwnerUserService.create({ - data: teamOwner, - props: props, - }); - } + for (let userId of userIds) { + if (typeof userId === Typeof.String) { + userId = new ObjectID(userId.toString()); + } + const teamOwner: IncidentTemplateOwnerUser = + new IncidentTemplateOwnerUser(); + teamOwner.incidentTemplateId = incidentTemplateId; + teamOwner.projectId = projectId; + teamOwner.userId = userId; + teamOwner.isOwnerNotified = !notifyOwners; + await IncidentTemplateOwnerUserService.create({ + data: teamOwner, + props: props, + }); } + } } export default new Service(); diff --git a/CommonServer/Services/Index.ts b/CommonServer/Services/Index.ts index 5fe6db8a38..4f5a0a4611 100644 --- a/CommonServer/Services/Index.ts +++ b/CommonServer/Services/Index.ts @@ -1,269 +1,269 @@ -import AccessTokenService from './AccessTokenService'; -import AcmeCertificateService from './AcmeCertificateService'; +import AccessTokenService from "./AccessTokenService"; +import AcmeCertificateService from "./AcmeCertificateService"; // import LogService from './LogService'; -import AnalyticsDatabaseService from './AnalyticsDatabaseService'; -import ApiKeyPermissionService from './ApiKeyPermissionService'; +import AnalyticsDatabaseService from "./AnalyticsDatabaseService"; +import ApiKeyPermissionService from "./ApiKeyPermissionService"; // API Keys -import ApiKeyService from './ApiKeyService'; -import BaseService from './BaseService'; -import BillingInvoiceService from './BillingInvoiceService'; -import BillingPaymentMethodsService from './BillingPaymentMethodService'; -import BillingService from './BillingService'; -import CallLogService from './CallLogService'; -import CallService from './CallService'; -import CodeRepositoryService from './CodeRepositoryService'; -import CopilotEventService from './CopilotEventService'; -import DataMigrationService from './DataMigrationService'; -import DomainService from './DomainService'; -import EmailLogService from './EmailLogService'; -import EmailVerificationTokenService from './EmailVerificationTokenService'; -import FileService from './FileService'; -import GreenlockCertificateService from './GreenlockCertificateService'; +import ApiKeyService from "./ApiKeyService"; +import BaseService from "./BaseService"; +import BillingInvoiceService from "./BillingInvoiceService"; +import BillingPaymentMethodsService from "./BillingPaymentMethodService"; +import BillingService from "./BillingService"; +import CallLogService from "./CallLogService"; +import CallService from "./CallService"; +import CodeRepositoryService from "./CodeRepositoryService"; +import CopilotEventService from "./CopilotEventService"; +import DataMigrationService from "./DataMigrationService"; +import DomainService from "./DomainService"; +import EmailLogService from "./EmailLogService"; +import EmailVerificationTokenService from "./EmailVerificationTokenService"; +import FileService from "./FileService"; +import GreenlockCertificateService from "./GreenlockCertificateService"; // Greenlock -import GreenlockChallengeService from './GreenlockChallengeService'; -import IncidentCustomFieldService from './IncidentCustomFieldService'; -import IncidentInternalNoteService from './IncidentInternalNoteService'; -import IncidentOwnerTeamService from './IncidentOwnerTeamService'; -import IncidentOwnerUserService from './IncidentOwnerUserService'; -import IncidentPublicNoteService from './IncidentPublicNoteService'; +import GreenlockChallengeService from "./GreenlockChallengeService"; +import IncidentCustomFieldService from "./IncidentCustomFieldService"; +import IncidentInternalNoteService from "./IncidentInternalNoteService"; +import IncidentOwnerTeamService from "./IncidentOwnerTeamService"; +import IncidentOwnerUserService from "./IncidentOwnerUserService"; +import IncidentPublicNoteService from "./IncidentPublicNoteService"; // Incidents -import IncidentService from './IncidentService'; -import IncidentSeverityService from './IncidentSeverityService'; -import IncidentStateService from './IncidentStateService'; -import IncidentStateTimelineService from './IncidentStateTimelineService'; +import IncidentService from "./IncidentService"; +import IncidentSeverityService from "./IncidentSeverityService"; +import IncidentStateService from "./IncidentStateService"; +import IncidentStateTimelineService from "./IncidentStateTimelineService"; //Labels. -import LabelService from './LabelService'; -import LogService from './LogService'; -import MailService from './MailService'; -import MetricService from './MetricService'; -import MonitorCustomFieldService from './MonitorCustomFieldService'; -import MonitorGroupOwnerTeamService from './MonitorGroupOwnerTeamService'; -import MonitorGroupOwnerUserService from './MonitorGroupOwnerUserService'; -import MonitorGroupResourceService from './MonitorGroupResourceService'; -import MonitorGroupService from './MonitorGroupService'; -import MonitorMetricsByMinuteService from './MonitorMetricsByMinuteService'; -import MonitorOwnerTeamService from './MonitorOwnerTeamService'; -import MonitorOwnerUserService from './MonitorOwnerUserService'; -import MonitorProbeService from './MonitorProbeService'; -import MonitorSecretService from './MonitorSecretService'; +import LabelService from "./LabelService"; +import LogService from "./LogService"; +import MailService from "./MailService"; +import MetricService from "./MetricService"; +import MonitorCustomFieldService from "./MonitorCustomFieldService"; +import MonitorGroupOwnerTeamService from "./MonitorGroupOwnerTeamService"; +import MonitorGroupOwnerUserService from "./MonitorGroupOwnerUserService"; +import MonitorGroupResourceService from "./MonitorGroupResourceService"; +import MonitorGroupService from "./MonitorGroupService"; +import MonitorMetricsByMinuteService from "./MonitorMetricsByMinuteService"; +import MonitorOwnerTeamService from "./MonitorOwnerTeamService"; +import MonitorOwnerUserService from "./MonitorOwnerUserService"; +import MonitorProbeService from "./MonitorProbeService"; +import MonitorSecretService from "./MonitorSecretService"; // Monitors -import MonitorService from './MonitorService'; -import MonitorStatusService from './MonitorStatusService'; -import MonitorStatusTimelineService from './MonitorStatusTimelineService'; -import NotificationService from './NotificationService'; -import OnCallDutyPolicyCustomFieldService from './OnCallDutyPolicyCustomFieldService'; -import OnCallDutyPolicyEscalationRuleScheduleService from './OnCallDutyPolicyEscalationRuleScheduleService'; -import OnCallDutyPolicyEscalationRuleService from './OnCallDutyPolicyEscalationRuleService'; -import OnCallDutyPolicyEscalationRuleTeamService from './OnCallDutyPolicyEscalationRuleTeamService'; -import OnCallDutyPolicyEscalationRuleUserService from './OnCallDutyPolicyEscalationRuleUserService'; -import OnCallDutyPolicyExecutionLogService from './OnCallDutyPolicyExecutionLogService'; -import OnCallDutyPolicyExecutionLogTimelineService from './OnCallDutyPolicyExecutionLogTimelineService'; -import OnCallDutyPolicyScheduleLayerService from './OnCallDutyPolicyScheduleLayerService'; -import OnCallDutyPolicyScheduleLayerUserService from './OnCallDutyPolicyScheduleLayerUserService'; -import OnCallDutyPolicyScheduleService from './OnCallDutyPolicyScheduleService'; +import MonitorService from "./MonitorService"; +import MonitorStatusService from "./MonitorStatusService"; +import MonitorStatusTimelineService from "./MonitorStatusTimelineService"; +import NotificationService from "./NotificationService"; +import OnCallDutyPolicyCustomFieldService from "./OnCallDutyPolicyCustomFieldService"; +import OnCallDutyPolicyEscalationRuleScheduleService from "./OnCallDutyPolicyEscalationRuleScheduleService"; +import OnCallDutyPolicyEscalationRuleService from "./OnCallDutyPolicyEscalationRuleService"; +import OnCallDutyPolicyEscalationRuleTeamService from "./OnCallDutyPolicyEscalationRuleTeamService"; +import OnCallDutyPolicyEscalationRuleUserService from "./OnCallDutyPolicyEscalationRuleUserService"; +import OnCallDutyPolicyExecutionLogService from "./OnCallDutyPolicyExecutionLogService"; +import OnCallDutyPolicyExecutionLogTimelineService from "./OnCallDutyPolicyExecutionLogTimelineService"; +import OnCallDutyPolicyScheduleLayerService from "./OnCallDutyPolicyScheduleLayerService"; +import OnCallDutyPolicyScheduleLayerUserService from "./OnCallDutyPolicyScheduleLayerUserService"; +import OnCallDutyPolicyScheduleService from "./OnCallDutyPolicyScheduleService"; // On-Call Duty -import OnCallDutyPolicyService from './OnCallDutyPolicyService'; -import ProbeService from './ProbeService'; -import ProjectCallSMSConfigService from './ProjectCallSMSConfigService'; -import ProjectService from './ProjectService'; +import OnCallDutyPolicyService from "./OnCallDutyPolicyService"; +import ProbeService from "./ProbeService"; +import ProjectCallSMSConfigService from "./ProjectCallSMSConfigService"; +import ProjectService from "./ProjectService"; // Project SMTP Config. -import ProjectSmtpConfigService from './ProjectSmtpConfigService'; -import ProjectSsoService from './ProjectSsoService'; -import PromoCodeService from './PromoCodeService'; -import ResellerPlanService from './ResellerPlanService'; -import ResellerService from './ResellerService'; -import ScheduledMaintenanceCustomFieldService from './ScheduledMaintenanceCustomFieldService'; -import ScheduledMaintenanceInternalNoteService from './ScheduledMaintenanceInternalNoteService'; -import ScheduledMaintenanceOwnerTeamService from './ScheduledMaintenanceOwnerTeamService'; -import ScheduledMaintenanceOwnerUserService from './ScheduledMaintenanceOwnerUserService'; -import ScheduledMaintenancePublicNoteService from './ScheduledMaintenancePublicNoteService'; +import ProjectSmtpConfigService from "./ProjectSmtpConfigService"; +import ProjectSsoService from "./ProjectSsoService"; +import PromoCodeService from "./PromoCodeService"; +import ResellerPlanService from "./ResellerPlanService"; +import ResellerService from "./ResellerService"; +import ScheduledMaintenanceCustomFieldService from "./ScheduledMaintenanceCustomFieldService"; +import ScheduledMaintenanceInternalNoteService from "./ScheduledMaintenanceInternalNoteService"; +import ScheduledMaintenanceOwnerTeamService from "./ScheduledMaintenanceOwnerTeamService"; +import ScheduledMaintenanceOwnerUserService from "./ScheduledMaintenanceOwnerUserService"; +import ScheduledMaintenancePublicNoteService from "./ScheduledMaintenancePublicNoteService"; // ScheduledMaintenances -import ScheduledMaintenanceService from './ScheduledMaintenanceService'; -import ScheduledMaintenanceStateService from './ScheduledMaintenanceStateService'; -import ScheduledMaintenanceStateTimelineService from './ScheduledMaintenanceStateTimelineService'; -import ServiceCatalogOwnerTeamService from './ServiceCatalogOwnerTeamService'; -import ServiceCatalogOwnerUserService from './ServiceCatalogOwnerUserService'; -import ServiceCatalogService from './ServiceCatalogService'; -import ServiceRepositoryService from './ServiceRepositoryService'; -import ShortLinkService from './ShortLinkService'; +import ScheduledMaintenanceService from "./ScheduledMaintenanceService"; +import ScheduledMaintenanceStateService from "./ScheduledMaintenanceStateService"; +import ScheduledMaintenanceStateTimelineService from "./ScheduledMaintenanceStateTimelineService"; +import ServiceCatalogOwnerTeamService from "./ServiceCatalogOwnerTeamService"; +import ServiceCatalogOwnerUserService from "./ServiceCatalogOwnerUserService"; +import ServiceCatalogService from "./ServiceCatalogService"; +import ServiceRepositoryService from "./ServiceRepositoryService"; +import ShortLinkService from "./ShortLinkService"; // SMS Log Service -import SmsLogService from './SmsLogService'; -import SmsService from './SmsService'; -import SpanService from './SpanService'; -import StatusPageAnnouncementService from './StatusPageAnnouncementService'; -import StatusPageCustomFieldService from './StatusPageCustomFieldService'; -import StatusPageDomainService from './StatusPageDomainService'; -import StatusPageFooterLinkService from './StatusPageFooterLinkService'; -import StatusPageGroupService from './StatusPageGroupService'; -import StatusPageHeaderLinkService from './StatusPageHeaderLinkService'; -import StatusPageHistoryChartBarColorRuleService from './StatusPageHistoryChartBarColorRuleService'; -import StatusPageOwnerTeamService from './StatusPageOwnerTeamService'; -import StatusPageOwnerUserService from './StatusPageOwnerUserService'; -import StatusPagePrivateUserService from './StatusPagePrivateUserService'; -import StatusPageResourceService from './StatusPageResourceService'; +import SmsLogService from "./SmsLogService"; +import SmsService from "./SmsService"; +import SpanService from "./SpanService"; +import StatusPageAnnouncementService from "./StatusPageAnnouncementService"; +import StatusPageCustomFieldService from "./StatusPageCustomFieldService"; +import StatusPageDomainService from "./StatusPageDomainService"; +import StatusPageFooterLinkService from "./StatusPageFooterLinkService"; +import StatusPageGroupService from "./StatusPageGroupService"; +import StatusPageHeaderLinkService from "./StatusPageHeaderLinkService"; +import StatusPageHistoryChartBarColorRuleService from "./StatusPageHistoryChartBarColorRuleService"; +import StatusPageOwnerTeamService from "./StatusPageOwnerTeamService"; +import StatusPageOwnerUserService from "./StatusPageOwnerUserService"; +import StatusPagePrivateUserService from "./StatusPagePrivateUserService"; +import StatusPageResourceService from "./StatusPageResourceService"; // Status Page -import StatusPageService from './StatusPageService'; -import StatusPageSsoService from './StatusPageSsoService'; -import StatusPageSubscriberService from './StatusPageSubscriberService'; -import TeamMemberService from './TeamMemberService'; -import TeamPermissionService from './TeamPermissionService'; +import StatusPageService from "./StatusPageService"; +import StatusPageSsoService from "./StatusPageSsoService"; +import StatusPageSubscriberService from "./StatusPageSubscriberService"; +import TeamMemberService from "./TeamMemberService"; +import TeamPermissionService from "./TeamPermissionService"; // Team -import TeamService from './TeamService'; -import TelemetryServiceService from './TelemetryServiceService'; -import UsageBillingService from './TelemetryUsageBillingService'; -import UserCallService from './UserCallService'; -import UserEmailService from './UserEmailService'; -import UserNotificationRuleService from './UserNotificationRuleService'; -import UserNotificationSettingService from './UserNotificationSettingService'; -import UserOnCallLogService from './UserOnCallLogService'; -import UserOnCallLogTimelineService from './UserOnCallLogTimelineService'; -import UserService from './UserService'; -import UserSmsService from './UserSmsService'; -import WorkflowLogService from './WorkflowLogService'; +import TeamService from "./TeamService"; +import TelemetryServiceService from "./TelemetryServiceService"; +import UsageBillingService from "./TelemetryUsageBillingService"; +import UserCallService from "./UserCallService"; +import UserEmailService from "./UserEmailService"; +import UserNotificationRuleService from "./UserNotificationRuleService"; +import UserNotificationSettingService from "./UserNotificationSettingService"; +import UserOnCallLogService from "./UserOnCallLogService"; +import UserOnCallLogTimelineService from "./UserOnCallLogTimelineService"; +import UserService from "./UserService"; +import UserSmsService from "./UserSmsService"; +import WorkflowLogService from "./WorkflowLogService"; // Workflows. -import WorkflowService from './WorkflowService'; -import WorkflowVariablesService from './WorkflowVariableService'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; +import WorkflowService from "./WorkflowService"; +import WorkflowVariablesService from "./WorkflowVariableService"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; const services: Array<BaseService> = [ - AcmeCertificateService, - PromoCodeService, + AcmeCertificateService, + PromoCodeService, - ResellerService, - ResellerPlanService, - // Import all services in current folder here. - AccessTokenService, - ApiKeyPermissionService, - ApiKeyService, + ResellerService, + ResellerPlanService, + // Import all services in current folder here. + AccessTokenService, + ApiKeyPermissionService, + ApiKeyService, - BillingInvoiceService, - BillingPaymentMethodsService, - BillingService, + BillingInvoiceService, + BillingPaymentMethodsService, + BillingService, - CallLogService, - CallService, + CallLogService, + CallService, - DataMigrationService, - DomainService, + DataMigrationService, + DomainService, - EmailLogService, - EmailVerificationTokenService, + EmailLogService, + EmailVerificationTokenService, - FileService, + FileService, - GreenlockCertificateService, - GreenlockChallengeService, + GreenlockCertificateService, + GreenlockChallengeService, - IncidentCustomFieldService, - IncidentInternalNoteService, - IncidentOwnerTeamService, - IncidentOwnerUserService, - IncidentPublicNoteService, - IncidentService, - IncidentSeverityService, - IncidentStateService, - IncidentStateTimelineService, + IncidentCustomFieldService, + IncidentInternalNoteService, + IncidentOwnerTeamService, + IncidentOwnerUserService, + IncidentPublicNoteService, + IncidentService, + IncidentSeverityService, + IncidentStateService, + IncidentStateTimelineService, - LabelService, + LabelService, - MailService, - MonitorCustomFieldService, - MonitorOwnerTeamService, - MonitorOwnerUserService, - MonitorProbeService, - MonitorService, - MonitorStatusService, - MonitorStatusTimelineService, - MonitorSecretService, + MailService, + MonitorCustomFieldService, + MonitorOwnerTeamService, + MonitorOwnerUserService, + MonitorProbeService, + MonitorService, + MonitorStatusService, + MonitorStatusTimelineService, + MonitorSecretService, - NotificationService, + NotificationService, - OnCallDutyPolicyCustomFieldService, - OnCallDutyPolicyEscalationRuleService, - OnCallDutyPolicyEscalationRuleTeamService, - OnCallDutyPolicyEscalationRuleUserService, - OnCallDutyPolicyExecutionLogService, - OnCallDutyPolicyExecutionLogTimelineService, - OnCallDutyPolicyService, + OnCallDutyPolicyCustomFieldService, + OnCallDutyPolicyEscalationRuleService, + OnCallDutyPolicyEscalationRuleTeamService, + OnCallDutyPolicyEscalationRuleUserService, + OnCallDutyPolicyExecutionLogService, + OnCallDutyPolicyExecutionLogTimelineService, + OnCallDutyPolicyService, - ProjectService, - ProjectSmtpConfigService, - ProbeService, - ProjectSsoService, + ProjectService, + ProjectSmtpConfigService, + ProbeService, + ProjectSsoService, - ScheduledMaintenanceCustomFieldService, - ScheduledMaintenanceInternalNoteService, - ScheduledMaintenanceOwnerTeamService, - ScheduledMaintenanceOwnerUserService, - ScheduledMaintenancePublicNoteService, - ScheduledMaintenanceService, - ScheduledMaintenanceStateService, - ScheduledMaintenanceStateTimelineService, + ScheduledMaintenanceCustomFieldService, + ScheduledMaintenanceInternalNoteService, + ScheduledMaintenanceOwnerTeamService, + ScheduledMaintenanceOwnerUserService, + ScheduledMaintenancePublicNoteService, + ScheduledMaintenanceService, + ScheduledMaintenanceStateService, + ScheduledMaintenanceStateTimelineService, - ShortLinkService, - SmsLogService, - SmsService, + ShortLinkService, + SmsLogService, + SmsService, - StatusPageAnnouncementService, - StatusPageCustomFieldService, - StatusPageDomainService, - StatusPageFooterLinkService, - StatusPageGroupService, - StatusPageHeaderLinkService, - StatusPageOwnerTeamService, - StatusPageOwnerUserService, - StatusPagePrivateUserService, - StatusPageResourceService, - StatusPageService, - StatusPageSsoService, - StatusPageSubscriberService, - StatusPageHistoryChartBarColorRuleService, + StatusPageAnnouncementService, + StatusPageCustomFieldService, + StatusPageDomainService, + StatusPageFooterLinkService, + StatusPageGroupService, + StatusPageHeaderLinkService, + StatusPageOwnerTeamService, + StatusPageOwnerUserService, + StatusPagePrivateUserService, + StatusPageResourceService, + StatusPageService, + StatusPageSsoService, + StatusPageSubscriberService, + StatusPageHistoryChartBarColorRuleService, - TeamMemberService, - TeamPermissionService, - TeamService, + TeamMemberService, + TeamPermissionService, + TeamService, - UserService, - UserCallService, - UserEmailService, - UserNotificationRuleService, - UserNotificationSettingService, - UserOnCallLogService, - UserOnCallLogTimelineService, - UserSmsService, + UserService, + UserCallService, + UserEmailService, + UserNotificationRuleService, + UserNotificationSettingService, + UserOnCallLogService, + UserOnCallLogTimelineService, + UserSmsService, - WorkflowLogService, - WorkflowService, - WorkflowVariablesService, + WorkflowLogService, + WorkflowService, + WorkflowVariablesService, - // Monitor Group Service - MonitorGroupService, - MonitorGroupResourceService, - MonitorGroupOwnerUserService, - MonitorGroupOwnerTeamService, + // Monitor Group Service + MonitorGroupService, + MonitorGroupResourceService, + MonitorGroupOwnerUserService, + MonitorGroupOwnerTeamService, - TelemetryServiceService, + TelemetryServiceService, - // On Call Duty Policy Schedule - OnCallDutyPolicyScheduleService, - OnCallDutyPolicyScheduleLayerUserService, - OnCallDutyPolicyScheduleLayerService, - OnCallDutyPolicyEscalationRuleScheduleService, + // On Call Duty Policy Schedule + OnCallDutyPolicyScheduleService, + OnCallDutyPolicyScheduleLayerUserService, + OnCallDutyPolicyScheduleLayerService, + OnCallDutyPolicyEscalationRuleScheduleService, - UsageBillingService, - ProjectCallSMSConfigService, + UsageBillingService, + ProjectCallSMSConfigService, - ServiceCatalogService, - ServiceCatalogOwnerTeamService, - ServiceCatalogOwnerUserService, + ServiceCatalogService, + ServiceCatalogOwnerTeamService, + ServiceCatalogOwnerUserService, - CodeRepositoryService, - CopilotEventService, - ServiceRepositoryService, + CodeRepositoryService, + CopilotEventService, + ServiceRepositoryService, ]; export const AnalyticsServices: Array< - AnalyticsDatabaseService<AnalyticsBaseModel> + AnalyticsDatabaseService<AnalyticsBaseModel> > = [LogService, SpanService, MetricService, MonitorMetricsByMinuteService]; export default services; diff --git a/CommonServer/Services/LabelService.ts b/CommonServer/Services/LabelService.ts index 7014ef9cbc..aa1445b6a5 100644 --- a/CommonServer/Services/LabelService.ts +++ b/CommonServer/Services/LabelService.ts @@ -1,41 +1,41 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import DatabaseService from './DatabaseService'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Model from 'Model/Models/Label'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import DatabaseService from "./DatabaseService"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Model from "Model/Models/Label"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + let existingProjectWithSameNameCount: number = 0; + + existingProjectWithSameNameCount = ( + await this.countBy({ + query: { + name: QueryHelper.findWithSameText(createBy.data.name!), + projectId: createBy.props.tenantId!, + }, + props: { + isRoot: true, + }, + }) + ).toNumber(); + + if (existingProjectWithSameNameCount > 0) { + throw new BadDataException( + "Label with the same name already exists in this project.", + ); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - let existingProjectWithSameNameCount: number = 0; - - existingProjectWithSameNameCount = ( - await this.countBy({ - query: { - name: QueryHelper.findWithSameText(createBy.data.name!), - projectId: createBy.props.tenantId!, - }, - props: { - isRoot: true, - }, - }) - ).toNumber(); - - if (existingProjectWithSameNameCount > 0) { - throw new BadDataException( - 'Label with the same name already exists in this project.' - ); - } - - return Promise.resolve({ createBy, carryForward: null }); - } + return Promise.resolve({ createBy, carryForward: null }); + } } export default new Service(); diff --git a/CommonServer/Services/LogService.ts b/CommonServer/Services/LogService.ts index 3a120f5a3e..f4946850a2 100644 --- a/CommonServer/Services/LogService.ts +++ b/CommonServer/Services/LogService.ts @@ -1,11 +1,11 @@ -import ClickhouseDatabase from '../Infrastructure/ClickhouseDatabase'; -import AnalyticsDatabaseService from './AnalyticsDatabaseService'; -import Log from 'Model/AnalyticsModels/Log'; +import ClickhouseDatabase from "../Infrastructure/ClickhouseDatabase"; +import AnalyticsDatabaseService from "./AnalyticsDatabaseService"; +import Log from "Model/AnalyticsModels/Log"; export class LogService extends AnalyticsDatabaseService<Log> { - public constructor(clickhouseDatabase?: ClickhouseDatabase | undefined) { - super({ modelType: Log, database: clickhouseDatabase }); - } + public constructor(clickhouseDatabase?: ClickhouseDatabase | undefined) { + super({ modelType: Log, database: clickhouseDatabase }); + } } export default new LogService(); diff --git a/CommonServer/Services/MailService.ts b/CommonServer/Services/MailService.ts index 5a92a1b9dc..d3cf1d3583 100644 --- a/CommonServer/Services/MailService.ts +++ b/CommonServer/Services/MailService.ts @@ -1,63 +1,63 @@ -import { AppApiHostname } from '../EnvironmentConfig'; -import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization'; -import BaseService from './BaseService'; -import EmptyResponseData from 'Common/Types/API/EmptyResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Email from 'Common/Types/Email/EmailMessage'; -import EmailServer from 'Common/Types/Email/EmailServer'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import API from 'Common/Utils/API'; +import { AppApiHostname } from "../EnvironmentConfig"; +import ClusterKeyAuthorization from "../Middleware/ClusterKeyAuthorization"; +import BaseService from "./BaseService"; +import EmptyResponseData from "Common/Types/API/EmptyResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Email from "Common/Types/Email/EmailMessage"; +import EmailServer from "Common/Types/Email/EmailServer"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import API from "Common/Utils/API"; export class MailService extends BaseService { - public async sendMail( - mail: Email, - options?: { - mailServer?: EmailServer | undefined; - userOnCallLogTimelineId?: ObjectID; - projectId?: ObjectID | undefined; - } - ): Promise<HTTPResponse<EmptyResponseData>> { - const body: JSONObject = { - ...mail, - toEmail: mail.toEmail.toString(), - }; + public async sendMail( + mail: Email, + options?: { + mailServer?: EmailServer | undefined; + userOnCallLogTimelineId?: ObjectID; + projectId?: ObjectID | undefined; + }, + ): Promise<HTTPResponse<EmptyResponseData>> { + const body: JSONObject = { + ...mail, + toEmail: mail.toEmail.toString(), + }; - if (options && options.mailServer) { - body['SMTP_ID'] = options.mailServer.id?.toString(); - body['SMTP_USERNAME'] = options.mailServer.username || undefined; - body['SMTP_EMAIL'] = options.mailServer.fromEmail.toString(); - body['SMTP_FROM_NAME'] = options.mailServer.fromName; - body['SMTP_IS_SECURE'] = options.mailServer.secure; - body['SMTP_PORT'] = options.mailServer.port.toNumber(); - body['SMTP_HOST'] = options.mailServer.host.toString(); - body['SMTP_PASSWORD'] = options.mailServer.password || undefined; - } - - if (options?.userOnCallLogTimelineId) { - body['userOnCallLogTimelineId'] = - options.userOnCallLogTimelineId.toString(); - } - - if (options?.projectId) { - body['projectId'] = options.projectId.toString(); - } - - return await API.post<EmptyResponseData>( - new URL( - Protocol.HTTP, - AppApiHostname, - new Route('/api/notification/email/send') - ), - body, - { - ...ClusterKeyAuthorization.getClusterKeyHeaders(), - } - ); + if (options && options.mailServer) { + body["SMTP_ID"] = options.mailServer.id?.toString(); + body["SMTP_USERNAME"] = options.mailServer.username || undefined; + body["SMTP_EMAIL"] = options.mailServer.fromEmail.toString(); + body["SMTP_FROM_NAME"] = options.mailServer.fromName; + body["SMTP_IS_SECURE"] = options.mailServer.secure; + body["SMTP_PORT"] = options.mailServer.port.toNumber(); + body["SMTP_HOST"] = options.mailServer.host.toString(); + body["SMTP_PASSWORD"] = options.mailServer.password || undefined; } + + if (options?.userOnCallLogTimelineId) { + body["userOnCallLogTimelineId"] = + options.userOnCallLogTimelineId.toString(); + } + + if (options?.projectId) { + body["projectId"] = options.projectId.toString(); + } + + return await API.post<EmptyResponseData>( + new URL( + Protocol.HTTP, + AppApiHostname, + new Route("/api/notification/email/send"), + ), + body, + { + ...ClusterKeyAuthorization.getClusterKeyHeaders(), + }, + ); + } } export default new MailService(); diff --git a/CommonServer/Services/MetricService.ts b/CommonServer/Services/MetricService.ts index becae2ccf7..039187fc40 100644 --- a/CommonServer/Services/MetricService.ts +++ b/CommonServer/Services/MetricService.ts @@ -1,11 +1,11 @@ -import ClickhouseDatabase from '../Infrastructure/ClickhouseDatabase'; -import AnalyticsDatabaseService from './AnalyticsDatabaseService'; -import MetricSum from 'Model/AnalyticsModels/Metric'; +import ClickhouseDatabase from "../Infrastructure/ClickhouseDatabase"; +import AnalyticsDatabaseService from "./AnalyticsDatabaseService"; +import MetricSum from "Model/AnalyticsModels/Metric"; export class MetricService extends AnalyticsDatabaseService<MetricSum> { - public constructor(clickhouseDatabase?: ClickhouseDatabase | undefined) { - super({ modelType: MetricSum, database: clickhouseDatabase }); - } + public constructor(clickhouseDatabase?: ClickhouseDatabase | undefined) { + super({ modelType: MetricSum, database: clickhouseDatabase }); + } } export default new MetricService(); diff --git a/CommonServer/Services/MonitorCustomFieldService.ts b/CommonServer/Services/MonitorCustomFieldService.ts index fcaac9c89f..ee5320bdc3 100644 --- a/CommonServer/Services/MonitorCustomFieldService.ts +++ b/CommonServer/Services/MonitorCustomFieldService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/MonitorCustomField'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/MonitorCustomField"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/MonitorGroupOwnerTeamService.ts b/CommonServer/Services/MonitorGroupOwnerTeamService.ts index 5c104c9cb5..baaeffe165 100644 --- a/CommonServer/Services/MonitorGroupOwnerTeamService.ts +++ b/CommonServer/Services/MonitorGroupOwnerTeamService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/MonitorGroupOwnerTeam'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/MonitorGroupOwnerTeam"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/MonitorGroupOwnerUserService.ts b/CommonServer/Services/MonitorGroupOwnerUserService.ts index b4bf2b0121..3e95cc87d8 100644 --- a/CommonServer/Services/MonitorGroupOwnerUserService.ts +++ b/CommonServer/Services/MonitorGroupOwnerUserService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/MonitorGroupOwnerUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/MonitorGroupOwnerUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/MonitorGroupResourceService.ts b/CommonServer/Services/MonitorGroupResourceService.ts index 106cb6cc89..df4382a9c0 100644 --- a/CommonServer/Services/MonitorGroupResourceService.ts +++ b/CommonServer/Services/MonitorGroupResourceService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/MonitorGroupResource'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/MonitorGroupResource"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/MonitorGroupService.ts b/CommonServer/Services/MonitorGroupService.ts index cb2a472317..f032b2eb01 100644 --- a/CommonServer/Services/MonitorGroupService.ts +++ b/CommonServer/Services/MonitorGroupService.ts @@ -1,185 +1,183 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import QueryHelper from '../Types/Database/QueryHelper'; -import DatabaseService from './DatabaseService'; -import MonitorGroupResourceService from './MonitorGroupResourceService'; -import MonitorStatusService from './MonitorStatusService'; -import MonitorStatusTimelineService from './MonitorStatusTimelineService'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import MonitorGroup from 'Model/Models/MonitorGroup'; -import MonitorGroupResource from 'Model/Models/MonitorGroupResource'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import QueryHelper from "../Types/Database/QueryHelper"; +import DatabaseService from "./DatabaseService"; +import MonitorGroupResourceService from "./MonitorGroupResourceService"; +import MonitorStatusService from "./MonitorStatusService"; +import MonitorStatusTimelineService from "./MonitorStatusTimelineService"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import MonitorGroup from "Model/Models/MonitorGroup"; +import MonitorGroupResource from "Model/Models/MonitorGroupResource"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; export class Service extends DatabaseService<MonitorGroup> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(MonitorGroup, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(MonitorGroup, postgresDatabase); + } + + public async getStatusTimeline( + monitorGroupId: ObjectID, + startDate: Date, + endDate: Date, + props: DatabaseCommonInteractionProps, + ): Promise<Array<MonitorStatusTimeline>> { + const monitorGroup: MonitorGroup | null = await this.findOneById({ + id: monitorGroupId, + select: { + _id: true, + projectId: true, + }, + props: props, + }); + + if (!monitorGroup) { + throw new BadDataException("Monitor group not found."); } - public async getStatusTimeline( - monitorGroupId: ObjectID, - startDate: Date, - endDate: Date, - props: DatabaseCommonInteractionProps - ): Promise<Array<MonitorStatusTimeline>> { - const monitorGroup: MonitorGroup | null = await this.findOneById({ - id: monitorGroupId, - select: { - _id: true, - projectId: true, - }, - props: props, - }); + const monitorGroupResources: Array<MonitorGroupResource> = + await MonitorGroupResourceService.findBy({ + query: { + monitorGroupId: monitorGroup.id!, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + monitorId: true, + }, + props: { + isRoot: true, + }, + }); - if (!monitorGroup) { - throw new BadDataException('Monitor group not found.'); - } - - const monitorGroupResources: Array<MonitorGroupResource> = - await MonitorGroupResourceService.findBy({ - query: { - monitorGroupId: monitorGroup.id!, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - monitorId: true, - }, - props: { - isRoot: true, - }, - }); - - if (monitorGroupResources.length === 0) { - return []; - } - - const monitorStatusTimelines: Array<MonitorStatusTimeline> = - await MonitorStatusTimelineService.findBy({ - query: { - monitorId: QueryHelper.any( - monitorGroupResources.map( - (monitorGroupResource: MonitorGroupResource) => { - return monitorGroupResource.monitorId!; - } - ) - ), - createdAt: QueryHelper.inBetween(startDate, endDate), - }, - select: { - createdAt: true, - monitorId: true, - monitorStatus: { - name: true, - color: true, - isOperationalState: true, - priority: true, - } as any, - }, - sort: { - createdAt: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_MAX, // This can be optimized. - props: { - isRoot: true, - }, - }); - - return monitorStatusTimelines; + if (monitorGroupResources.length === 0) { + return []; } - public async getCurrentStatus( - monitorGroupId: ObjectID, - props: DatabaseCommonInteractionProps - ): Promise<MonitorStatus> { - // get group id. + const monitorStatusTimelines: Array<MonitorStatusTimeline> = + await MonitorStatusTimelineService.findBy({ + query: { + monitorId: QueryHelper.any( + monitorGroupResources.map( + (monitorGroupResource: MonitorGroupResource) => { + return monitorGroupResource.monitorId!; + }, + ), + ), + createdAt: QueryHelper.inBetween(startDate, endDate), + }, + select: { + createdAt: true, + monitorId: true, + monitorStatus: { + name: true, + color: true, + isOperationalState: true, + priority: true, + } as any, + }, + sort: { + createdAt: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_MAX, // This can be optimized. + props: { + isRoot: true, + }, + }); - const monitorGroup: MonitorGroup | null = await this.findOneById({ - id: monitorGroupId, - select: { - _id: true, - projectId: true, - }, - props: props, - }); + return monitorStatusTimelines; + } - if (!monitorGroup) { - throw new BadDataException('Monitor group not found.'); - } + public async getCurrentStatus( + monitorGroupId: ObjectID, + props: DatabaseCommonInteractionProps, + ): Promise<MonitorStatus> { + // get group id. - // now get all the monitors in this group with current status. + const monitorGroup: MonitorGroup | null = await this.findOneById({ + id: monitorGroupId, + select: { + _id: true, + projectId: true, + }, + props: props, + }); - const monitorGroupResources: Array<MonitorGroupResource> = - await MonitorGroupResourceService.findBy({ - query: { - monitorGroupId: monitorGroup.id!, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - monitor: { - currentMonitorStatusId: true, - }, - }, - props: { - isRoot: true, - }, - }); - - const monitorStatuses: Array<MonitorStatus> = - await MonitorStatusService.findBy({ - query: { - projectId: monitorGroup.projectId!, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - color: true, - priority: true, - isOperationalState: true, - }, - props: { - isRoot: true, - }, - }); - - let currentStatus: MonitorStatus | undefined = monitorStatuses.find( - (monitorStatus: MonitorStatus) => { - return monitorStatus.isOperationalState; - } - ); - - if (!currentStatus) { - throw new BadDataException('Operational state not found.'); - } - - for (const monitorGroupResource of monitorGroupResources) { - if (!monitorGroupResource.monitor) { - continue; - } - - const monitorStatus: MonitorStatus | undefined = - monitorStatuses.find((monitorStatus: MonitorStatus) => { - return ( - monitorStatus.id?.toString() === - monitorGroupResource.monitor!.currentMonitorStatusId?.toString() - ); - }); - - if ( - monitorStatus && - currentStatus.priority! < monitorStatus.priority! - ) { - currentStatus = monitorStatus; - } - } - - return currentStatus; + if (!monitorGroup) { + throw new BadDataException("Monitor group not found."); } + + // now get all the monitors in this group with current status. + + const monitorGroupResources: Array<MonitorGroupResource> = + await MonitorGroupResourceService.findBy({ + query: { + monitorGroupId: monitorGroup.id!, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + monitor: { + currentMonitorStatusId: true, + }, + }, + props: { + isRoot: true, + }, + }); + + const monitorStatuses: Array<MonitorStatus> = + await MonitorStatusService.findBy({ + query: { + projectId: monitorGroup.projectId!, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + color: true, + priority: true, + isOperationalState: true, + }, + props: { + isRoot: true, + }, + }); + + let currentStatus: MonitorStatus | undefined = monitorStatuses.find( + (monitorStatus: MonitorStatus) => { + return monitorStatus.isOperationalState; + }, + ); + + if (!currentStatus) { + throw new BadDataException("Operational state not found."); + } + + for (const monitorGroupResource of monitorGroupResources) { + if (!monitorGroupResource.monitor) { + continue; + } + + const monitorStatus: MonitorStatus | undefined = monitorStatuses.find( + (monitorStatus: MonitorStatus) => { + return ( + monitorStatus.id?.toString() === + monitorGroupResource.monitor!.currentMonitorStatusId?.toString() + ); + }, + ); + + if (monitorStatus && currentStatus.priority! < monitorStatus.priority!) { + currentStatus = monitorStatus; + } + } + + return currentStatus; + } } export default new Service(); diff --git a/CommonServer/Services/MonitorMetricsByMinuteService.ts b/CommonServer/Services/MonitorMetricsByMinuteService.ts index ae45739e20..683dec970a 100644 --- a/CommonServer/Services/MonitorMetricsByMinuteService.ts +++ b/CommonServer/Services/MonitorMetricsByMinuteService.ts @@ -1,14 +1,14 @@ -import ClickhouseDatabase from '../Infrastructure/ClickhouseDatabase'; -import AnalyticsDatabaseService from './AnalyticsDatabaseService'; -import MonitorMetricsByMinute from 'Model/AnalyticsModels/MonitorMetricsByMinute'; +import ClickhouseDatabase from "../Infrastructure/ClickhouseDatabase"; +import AnalyticsDatabaseService from "./AnalyticsDatabaseService"; +import MonitorMetricsByMinute from "Model/AnalyticsModels/MonitorMetricsByMinute"; export class MonitorMetricsByMinuteService extends AnalyticsDatabaseService<MonitorMetricsByMinute> { - public constructor(clickhouseDatabase?: ClickhouseDatabase | undefined) { - super({ - modelType: MonitorMetricsByMinute, - database: clickhouseDatabase, - }); - } + public constructor(clickhouseDatabase?: ClickhouseDatabase | undefined) { + super({ + modelType: MonitorMetricsByMinute, + database: clickhouseDatabase, + }); + } } export default new MonitorMetricsByMinuteService(); diff --git a/CommonServer/Services/MonitorOwnerTeamService.ts b/CommonServer/Services/MonitorOwnerTeamService.ts index 3da51c2eaf..0a98f9a0fd 100644 --- a/CommonServer/Services/MonitorOwnerTeamService.ts +++ b/CommonServer/Services/MonitorOwnerTeamService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/MonitorOwnerTeam'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/MonitorOwnerTeam"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/MonitorOwnerUserService.ts b/CommonServer/Services/MonitorOwnerUserService.ts index 3858588202..2b32317889 100644 --- a/CommonServer/Services/MonitorOwnerUserService.ts +++ b/CommonServer/Services/MonitorOwnerUserService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/MonitorOwnerUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/MonitorOwnerUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/MonitorProbeService.ts b/CommonServer/Services/MonitorProbeService.ts index 789bb24a51..d0ed6d3b01 100644 --- a/CommonServer/Services/MonitorProbeService.ts +++ b/CommonServer/Services/MonitorProbeService.ts @@ -1,54 +1,51 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import MonitorProbe from 'Model/Models/MonitorProbe'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import MonitorProbe from "Model/Models/MonitorProbe"; export class Service extends DatabaseService<MonitorProbe> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(MonitorProbe, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(MonitorProbe, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<MonitorProbe>, + ): Promise<OnCreate<MonitorProbe>> { + if ( + (createBy.data.monitorId || createBy.data.monitor) && + (createBy.data.probeId || createBy.data.probe) + ) { + const monitorProbe: MonitorProbe | null = await this.findOneBy({ + query: { + monitorId: createBy.data.monitorId! || createBy.data.monitor?.id, + probeId: createBy.data.probeId! || createBy.data.probe?.id, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); + + if (monitorProbe) { + throw new BadDataException("Probe is already added to this monitor."); + } } - protected override async onBeforeCreate( - createBy: CreateBy<MonitorProbe> - ): Promise<OnCreate<MonitorProbe>> { - if ( - (createBy.data.monitorId || createBy.data.monitor) && - (createBy.data.probeId || createBy.data.probe) - ) { - const monitorProbe: MonitorProbe | null = await this.findOneBy({ - query: { - monitorId: - createBy.data.monitorId! || createBy.data.monitor?.id, - probeId: createBy.data.probeId! || createBy.data.probe?.id, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); - - if (monitorProbe) { - throw new BadDataException( - 'Probe is already added to this monitor.' - ); - } - } - - if (!createBy.data.nextPingAt) { - createBy.data.nextPingAt = OneUptimeDate.getCurrentDate(); - } - - if (!createBy.data.lastPingAt) { - createBy.data.lastPingAt = OneUptimeDate.getCurrentDate(); - } - - return { createBy, carryForward: null }; + if (!createBy.data.nextPingAt) { + createBy.data.nextPingAt = OneUptimeDate.getCurrentDate(); } + + if (!createBy.data.lastPingAt) { + createBy.data.lastPingAt = OneUptimeDate.getCurrentDate(); + } + + return { createBy, carryForward: null }; + } } export default new Service(); diff --git a/CommonServer/Services/MonitorSecretService.ts b/CommonServer/Services/MonitorSecretService.ts index a0d24002b5..01ba9651b2 100644 --- a/CommonServer/Services/MonitorSecretService.ts +++ b/CommonServer/Services/MonitorSecretService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import MonitorSecret from 'Model/Models/MonitorSecret'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import MonitorSecret from "Model/Models/MonitorSecret"; export class Service extends DatabaseService<MonitorSecret> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(MonitorSecret, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(MonitorSecret, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/MonitorService.ts b/CommonServer/Services/MonitorService.ts index 86a5b14b1f..7a37308e5f 100644 --- a/CommonServer/Services/MonitorService.ts +++ b/CommonServer/Services/MonitorService.ts @@ -1,519 +1,507 @@ -import DatabaseConfig from '../DatabaseConfig'; +import DatabaseConfig from "../DatabaseConfig"; import { - AllowedActiveMonitorCountInFreePlan, - IsBillingEnabled, -} from '../EnvironmentConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import { ActiveMonitoringMeteredPlan } from '../Types/Billing/MeteredPlan/AllMeteredPlans'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import MonitorOwnerTeamService from './MonitorOwnerTeamService'; -import MonitorOwnerUserService from './MonitorOwnerUserService'; -import MonitorProbeService from './MonitorProbeService'; -import MonitorStatusService from './MonitorStatusService'; -import MonitorStatusTimelineService from './MonitorStatusTimelineService'; -import ProbeService from './ProbeService'; -import ProjectService, { CurrentPlan } from './ProjectService'; -import TeamMemberService from './TeamMemberService'; -import URL from 'Common/Types/API/URL'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; + AllowedActiveMonitorCountInFreePlan, + IsBillingEnabled, +} from "../EnvironmentConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import { ActiveMonitoringMeteredPlan } from "../Types/Billing/MeteredPlan/AllMeteredPlans"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import MonitorOwnerTeamService from "./MonitorOwnerTeamService"; +import MonitorOwnerUserService from "./MonitorOwnerUserService"; +import MonitorProbeService from "./MonitorProbeService"; +import MonitorStatusService from "./MonitorStatusService"; +import MonitorStatusTimelineService from "./MonitorStatusTimelineService"; +import ProbeService from "./ProbeService"; +import ProjectService, { CurrentPlan } from "./ProjectService"; +import TeamMemberService from "./TeamMemberService"; +import URL from "Common/Types/API/URL"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; import MonitorType, { - MonitorTypeHelper, -} from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Typeof from 'Common/Types/Typeof'; -import Model from 'Model/Models/Monitor'; -import MonitorOwnerTeam from 'Model/Models/MonitorOwnerTeam'; -import MonitorOwnerUser from 'Model/Models/MonitorOwnerUser'; -import MonitorProbe from 'Model/Models/MonitorProbe'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import Probe from 'Model/Models/Probe'; -import User from 'Model/Models/User'; + MonitorTypeHelper, +} from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Typeof from "Common/Types/Typeof"; +import Model from "Model/Models/Monitor"; +import MonitorOwnerTeam from "Model/Models/MonitorOwnerTeam"; +import MonitorOwnerUser from "Model/Models/MonitorOwnerUser"; +import MonitorProbe from "Model/Models/MonitorProbe"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import Probe from "Model/Models/Probe"; +import User from "Model/Models/User"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + if (onDelete.deleteBy.props.tenantId && IsBillingEnabled) { + await ActiveMonitoringMeteredPlan.reportQuantityToBillingProvider( + onDelete.deleteBy.props.tenantId, + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - if (onDelete.deleteBy.props.tenantId && IsBillingEnabled) { - await ActiveMonitoringMeteredPlan.reportQuantityToBillingProvider( - onDelete.deleteBy.props.tenantId - ); - } + return onDelete; + } - return onDelete; + protected override async onUpdateSuccess( + onUpdate: OnUpdate<Model>, + updatedItemIds: ObjectID[], + ): Promise<OnUpdate<Model>> { + if ( + onUpdate.updateBy.data.currentMonitorStatusId && + onUpdate.updateBy.props.tenantId + ) { + await this.changeMonitorStatus( + onUpdate.updateBy.props.tenantId as ObjectID, + updatedItemIds as Array<ObjectID>, + onUpdate.updateBy.data.currentMonitorStatusId as ObjectID, + true, // notifyOwners = true + "This status was changed when the monitor was updated.", + undefined, + { + isRoot: true, + }, + ); } - protected override async onUpdateSuccess( - onUpdate: OnUpdate<Model>, - updatedItemIds: ObjectID[] - ): Promise<OnUpdate<Model>> { - if ( - onUpdate.updateBy.data.currentMonitorStatusId && - onUpdate.updateBy.props.tenantId - ) { - await this.changeMonitorStatus( - onUpdate.updateBy.props.tenantId as ObjectID, - updatedItemIds as Array<ObjectID>, - onUpdate.updateBy.data.currentMonitorStatusId as ObjectID, - true, // notifyOwners = true - 'This status was changed when the monitor was updated.', - undefined, - { - isRoot: true, - } - ); - } + return onUpdate; + } - return onUpdate; - } + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.disableActiveMonitoring !== undefined) { + const items: Array<Model> = await this.findBy({ + query: updateBy.query, + props: updateBy.props, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + monitorType: true, + }, + }); - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if (updateBy.data.disableActiveMonitoring !== undefined) { - const items: Array<Model> = await this.findBy({ - query: updateBy.query, - props: updateBy.props, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - monitorType: true, - }, - }); + // check if the monitor type is not manual. - // check if the monitor type is not manual. - - for (const item of items) { - if ( - item.monitorType && - item.monitorType === MonitorType.Manual - ) { - if (updateBy.data.disableActiveMonitoring === true) { - throw new BadDataException( - 'You can only disable monitoring for active monitors. Disabling monitoring for manual monitors is not allowed.' - ); - } else { - throw new BadDataException( - 'You can only enable monitoring for active monitors. Enabling monitoring for manual monitors is not allowed.' - ); - } - } - } - } - - return { updateBy, carryForward: null }; - } - - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.monitorType) { + for (const item of items) { + if (item.monitorType && item.monitorType === MonitorType.Manual) { + if (updateBy.data.disableActiveMonitoring === true) { throw new BadDataException( - 'Monitor type required to create monitor.' + "You can only disable monitoring for active monitors. Disabling monitoring for manual monitors is not allowed.", ); - } - - if (!Object.values(MonitorType).includes(createBy.data.monitorType)) { + } else { throw new BadDataException( - `Invalid monitor type "${ - createBy.data.monitorType - }". Valid monitor types are ${Object.values(MonitorType).join( - ', ' - )}.` + "You can only enable monitoring for active monitors. Enabling monitoring for manual monitors is not allowed.", ); + } } - - if (IsBillingEnabled && createBy.props.tenantId) { - const currentPlan: CurrentPlan = - await ProjectService.getCurrentPlan(createBy.props.tenantId); - - if (currentPlan.isSubscriptionUnpaid) { - throw new BadDataException( - 'Your subscription is unpaid. Please update your payment method and pay all the outstanding invoices to add more monitors.' - ); - } - - if ( - currentPlan.plan === PlanSelect.Free && - createBy.data.monitorType !== MonitorType.Manual - ) { - const monitorCount: PositiveNumber = await this.countBy({ - query: { - projectId: createBy.props.tenantId, - monitorType: QueryHelper.any( - MonitorTypeHelper.getActiveMonitorTypes() - ), - }, - props: { - isRoot: true, - }, - }); - - if ( - monitorCount.toNumber() >= - AllowedActiveMonitorCountInFreePlan - ) { - throw new BadDataException( - `You have reached the maximum allowed monitor limit for the free plan. Please upgrade your plan to add more monitors.` - ); - } - } - } - - if (createBy.data.monitorType === MonitorType.Server) { - createBy.data.serverMonitorSecretKey = ObjectID.generate(); - } - - if (createBy.data.monitorType === MonitorType.IncomingRequest) { - createBy.data.incomingRequestSecretKey = ObjectID.generate(); - } - - if (!createBy.props.tenantId) { - throw new BadDataException('ProjectId required to create monitor.'); - } - - const monitorStatus: MonitorStatus | null = - await MonitorStatusService.findOneBy({ - query: { - projectId: createBy.props.tenantId, - isOperationalState: true, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); - - if (!monitorStatus || !monitorStatus.id) { - throw new BadDataException( - 'Operational status not found for this project. Please add an operational status' - ); - } - - createBy.data.currentMonitorStatusId = monitorStatus.id; - - return { createBy, carryForward: null }; + } } - protected override async onCreateSuccess( - onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - if (!createdItem.projectId) { - throw new BadDataException('projectId is required'); - } + return { updateBy, carryForward: null }; + } - if (!createdItem.id) { - throw new BadDataException('id is required'); - } + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.monitorType) { + throw new BadDataException("Monitor type required to create monitor."); + } - if (!createdItem.currentMonitorStatusId) { - throw new BadDataException('currentMonitorStatusId is required'); - } + if (!Object.values(MonitorType).includes(createBy.data.monitorType)) { + throw new BadDataException( + `Invalid monitor type "${ + createBy.data.monitorType + }". Valid monitor types are ${Object.values(MonitorType).join(", ")}.`, + ); + } - await this.changeMonitorStatus( - createdItem.projectId, - [createdItem.id], - createdItem.currentMonitorStatusId, - false, // notifyOwners = false - 'This status was created when the monitor was created.', - undefined, - onCreate.createBy.props + if (IsBillingEnabled && createBy.props.tenantId) { + const currentPlan: CurrentPlan = await ProjectService.getCurrentPlan( + createBy.props.tenantId, + ); + + if (currentPlan.isSubscriptionUnpaid) { + throw new BadDataException( + "Your subscription is unpaid. Please update your payment method and pay all the outstanding invoices to add more monitors.", ); + } - if ( - createdItem.monitorType && - MonitorTypeHelper.isProbableMonitors(createdItem.monitorType) - ) { - await this.addDefaultProbesToMonitor( - createdItem.projectId, - createdItem.id - ); - } - - if (IsBillingEnabled) { - await ActiveMonitoringMeteredPlan.reportQuantityToBillingProvider( - createdItem.projectId - ); - } - - // add owners. - - if ( - onCreate.createBy.miscDataProps && - (onCreate.createBy.miscDataProps['ownerTeams'] || - onCreate.createBy.miscDataProps['ownerUsers']) - ) { - await this.addOwners( - createdItem.projectId, - createdItem.id, - (onCreate.createBy.miscDataProps[ - 'ownerUsers' - ] as Array<ObjectID>) || [], - (onCreate.createBy.miscDataProps[ - 'ownerTeams' - ] as Array<ObjectID>) || [], - false, - onCreate.createBy.props - ); - } - - return createdItem; - } - - public async getMonitorLinkInDashboard( - projectId: ObjectID, - monitorId: ObjectID - ): Promise<URL> { - const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl(); - - return URL.fromString(dashboardUrl.toString()).addRoute( - `/${projectId.toString()}/monitors/${monitorId.toString()}` - ); - } - - public async findOwners(monitorId: ObjectID): Promise<Array<User>> { - if (!monitorId) { - throw new BadDataException('monitorId is required'); - } - - const ownerUsers: Array<MonitorOwnerUser> = - await MonitorOwnerUserService.findBy({ - query: { - monitorId: monitorId, - }, - select: { - _id: true, - user: { - _id: true, - email: true, - name: true, - }, - }, - props: { - isRoot: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - }); - - const ownerTeams: Array<MonitorOwnerTeam> = - await MonitorOwnerTeamService.findBy({ - query: { - monitorId: monitorId, - }, - select: { - _id: true, - teamId: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - const users: Array<User> = - ownerUsers.map((ownerUser: MonitorOwnerUser) => { - return ownerUser.user!; - }) || []; - - if (ownerTeams.length > 0) { - const teamIds: Array<ObjectID> = - ownerTeams.map((ownerTeam: MonitorOwnerTeam) => { - return ownerTeam.teamId!; - }) || []; - - const teamUsers: Array<User> = - await TeamMemberService.getUsersInTeams(teamIds); - - for (const teamUser of teamUsers) { - //check if the user is already added. - const isUserAlreadyAdded: User | undefined = users.find( - (user: User) => { - return user.id!.toString() === teamUser.id!.toString(); - } - ); - - if (!isUserAlreadyAdded) { - users.push(teamUser); - } - } - } - - return users; - } - - public async addOwners( - projectId: ObjectID, - monitorId: ObjectID, - userIds: Array<ObjectID>, - teamIds: Array<ObjectID>, - notifyOwners: boolean, - props: DatabaseCommonInteractionProps - ): Promise<void> { - for (let teamId of teamIds) { - if (typeof teamId === Typeof.String) { - teamId = new ObjectID(teamId.toString()); - } - - const teamOwner: MonitorOwnerTeam = new MonitorOwnerTeam(); - teamOwner.monitorId = monitorId; - teamOwner.projectId = projectId; - teamOwner.teamId = teamId; - teamOwner.isOwnerNotified = !notifyOwners; - - await MonitorOwnerTeamService.create({ - data: teamOwner, - props: props, - }); - } - - for (let userId of userIds) { - if (typeof userId === Typeof.String) { - userId = new ObjectID(userId.toString()); - } - const teamOwner: MonitorOwnerUser = new MonitorOwnerUser(); - teamOwner.monitorId = monitorId; - teamOwner.projectId = projectId; - teamOwner.userId = userId; - teamOwner.isOwnerNotified = !notifyOwners; - await MonitorOwnerUserService.create({ - data: teamOwner, - props: props, - }); - } - } - - public async addDefaultProbesToMonitor( - projectId: ObjectID, - monitorId: ObjectID - ): Promise<void> { - const globalProbes: Array<Probe> = await ProbeService.findBy({ - query: { - isGlobalProbe: true, - shouldAutoEnableProbeOnNewMonitors: true, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, + if ( + currentPlan.plan === PlanSelect.Free && + createBy.data.monitorType !== MonitorType.Manual + ) { + const monitorCount: PositiveNumber = await this.countBy({ + query: { + projectId: createBy.props.tenantId, + monitorType: QueryHelper.any( + MonitorTypeHelper.getActiveMonitorTypes(), + ), + }, + props: { + isRoot: true, + }, }); - const projectProbes: Array<Probe> = await ProbeService.findBy({ - query: { - isGlobalProbe: false, - shouldAutoEnableProbeOnNewMonitors: true, - projectId: projectId, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, + if (monitorCount.toNumber() >= AllowedActiveMonitorCountInFreePlan) { + throw new BadDataException( + `You have reached the maximum allowed monitor limit for the free plan. Please upgrade your plan to add more monitors.`, + ); + } + } + } + + if (createBy.data.monitorType === MonitorType.Server) { + createBy.data.serverMonitorSecretKey = ObjectID.generate(); + } + + if (createBy.data.monitorType === MonitorType.IncomingRequest) { + createBy.data.incomingRequestSecretKey = ObjectID.generate(); + } + + if (!createBy.props.tenantId) { + throw new BadDataException("ProjectId required to create monitor."); + } + + const monitorStatus: MonitorStatus | null = + await MonitorStatusService.findOneBy({ + query: { + projectId: createBy.props.tenantId, + isOperationalState: true, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); + + if (!monitorStatus || !monitorStatus.id) { + throw new BadDataException( + "Operational status not found for this project. Please add an operational status", + ); + } + + createBy.data.currentMonitorStatusId = monitorStatus.id; + + return { createBy, carryForward: null }; + } + + protected override async onCreateSuccess( + onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + if (!createdItem.projectId) { + throw new BadDataException("projectId is required"); + } + + if (!createdItem.id) { + throw new BadDataException("id is required"); + } + + if (!createdItem.currentMonitorStatusId) { + throw new BadDataException("currentMonitorStatusId is required"); + } + + await this.changeMonitorStatus( + createdItem.projectId, + [createdItem.id], + createdItem.currentMonitorStatusId, + false, // notifyOwners = false + "This status was created when the monitor was created.", + undefined, + onCreate.createBy.props, + ); + + if ( + createdItem.monitorType && + MonitorTypeHelper.isProbableMonitors(createdItem.monitorType) + ) { + await this.addDefaultProbesToMonitor( + createdItem.projectId, + createdItem.id, + ); + } + + if (IsBillingEnabled) { + await ActiveMonitoringMeteredPlan.reportQuantityToBillingProvider( + createdItem.projectId, + ); + } + + // add owners. + + if ( + onCreate.createBy.miscDataProps && + (onCreate.createBy.miscDataProps["ownerTeams"] || + onCreate.createBy.miscDataProps["ownerUsers"]) + ) { + await this.addOwners( + createdItem.projectId, + createdItem.id, + (onCreate.createBy.miscDataProps["ownerUsers"] as Array<ObjectID>) || + [], + (onCreate.createBy.miscDataProps["ownerTeams"] as Array<ObjectID>) || + [], + false, + onCreate.createBy.props, + ); + } + + return createdItem; + } + + public async getMonitorLinkInDashboard( + projectId: ObjectID, + monitorId: ObjectID, + ): Promise<URL> { + const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl(); + + return URL.fromString(dashboardUrl.toString()).addRoute( + `/${projectId.toString()}/monitors/${monitorId.toString()}`, + ); + } + + public async findOwners(monitorId: ObjectID): Promise<Array<User>> { + if (!monitorId) { + throw new BadDataException("monitorId is required"); + } + + const ownerUsers: Array<MonitorOwnerUser> = + await MonitorOwnerUserService.findBy({ + query: { + monitorId: monitorId, + }, + select: { + _id: true, + user: { + _id: true, + email: true, + name: true, + }, + }, + props: { + isRoot: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + }); + + const ownerTeams: Array<MonitorOwnerTeam> = + await MonitorOwnerTeamService.findBy({ + query: { + monitorId: monitorId, + }, + select: { + _id: true, + teamId: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const users: Array<User> = + ownerUsers.map((ownerUser: MonitorOwnerUser) => { + return ownerUser.user!; + }) || []; + + if (ownerTeams.length > 0) { + const teamIds: Array<ObjectID> = + ownerTeams.map((ownerTeam: MonitorOwnerTeam) => { + return ownerTeam.teamId!; + }) || []; + + const teamUsers: Array<User> = + await TeamMemberService.getUsersInTeams(teamIds); + + for (const teamUser of teamUsers) { + //check if the user is already added. + const isUserAlreadyAdded: User | undefined = users.find( + (user: User) => { + return user.id!.toString() === teamUser.id!.toString(); + }, + ); + + if (!isUserAlreadyAdded) { + users.push(teamUser); + } + } + } + + return users; + } + + public async addOwners( + projectId: ObjectID, + monitorId: ObjectID, + userIds: Array<ObjectID>, + teamIds: Array<ObjectID>, + notifyOwners: boolean, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + for (let teamId of teamIds) { + if (typeof teamId === Typeof.String) { + teamId = new ObjectID(teamId.toString()); + } + + const teamOwner: MonitorOwnerTeam = new MonitorOwnerTeam(); + teamOwner.monitorId = monitorId; + teamOwner.projectId = projectId; + teamOwner.teamId = teamId; + teamOwner.isOwnerNotified = !notifyOwners; + + await MonitorOwnerTeamService.create({ + data: teamOwner, + props: props, + }); + } + + for (let userId of userIds) { + if (typeof userId === Typeof.String) { + userId = new ObjectID(userId.toString()); + } + const teamOwner: MonitorOwnerUser = new MonitorOwnerUser(); + teamOwner.monitorId = monitorId; + teamOwner.projectId = projectId; + teamOwner.userId = userId; + teamOwner.isOwnerNotified = !notifyOwners; + await MonitorOwnerUserService.create({ + data: teamOwner, + props: props, + }); + } + } + + public async addDefaultProbesToMonitor( + projectId: ObjectID, + monitorId: ObjectID, + ): Promise<void> { + const globalProbes: Array<Probe> = await ProbeService.findBy({ + query: { + isGlobalProbe: true, + shouldAutoEnableProbeOnNewMonitors: true, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const projectProbes: Array<Probe> = await ProbeService.findBy({ + query: { + isGlobalProbe: false, + shouldAutoEnableProbeOnNewMonitors: true, + projectId: projectId, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const totalProbes: Array<Probe> = [...globalProbes, ...projectProbes]; + + for (const probe of totalProbes) { + const monitorProbe: MonitorProbe = new MonitorProbe(); + + monitorProbe.monitorId = monitorId; + monitorProbe.probeId = probe.id!; + monitorProbe.projectId = projectId; + monitorProbe.isEnabled = true; + + await MonitorProbeService.create({ + data: monitorProbe, + props: { + isRoot: true, + }, + }); + } + } + + public async changeMonitorStatus( + projectId: ObjectID, + monitorIds: Array<ObjectID>, + monitorStatusId: ObjectID, + notifyOwners: boolean, + rootCause: string | undefined, + statusChangeLog: JSONObject | undefined, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + for (const monitorId of monitorIds) { + // get last monitor status timeline. + const lastMonitorStatusTimeline: MonitorStatusTimeline | null = + await MonitorStatusTimelineService.findOneBy({ + query: { + monitorId: monitorId, + projectId: projectId, + }, + select: { + _id: true, + monitorStatusId: true, + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, }); - const totalProbes: Array<Probe> = [...globalProbes, ...projectProbes]; + if ( + lastMonitorStatusTimeline && + lastMonitorStatusTimeline.monitorStatusId && + lastMonitorStatusTimeline.monitorStatusId.toString() === + monitorStatusId.toString() + ) { + continue; + } - for (const probe of totalProbes) { - const monitorProbe: MonitorProbe = new MonitorProbe(); + const statusTimeline: MonitorStatusTimeline = new MonitorStatusTimeline(); - monitorProbe.monitorId = monitorId; - monitorProbe.probeId = probe.id!; - monitorProbe.projectId = projectId; - monitorProbe.isEnabled = true; + statusTimeline.monitorId = monitorId; + statusTimeline.monitorStatusId = monitorStatusId; + statusTimeline.projectId = projectId; + statusTimeline.isOwnerNotified = !notifyOwners; - await MonitorProbeService.create({ - data: monitorProbe, - props: { - isRoot: true, - }, - }); - } - } - - public async changeMonitorStatus( - projectId: ObjectID, - monitorIds: Array<ObjectID>, - monitorStatusId: ObjectID, - notifyOwners: boolean, - rootCause: string | undefined, - statusChangeLog: JSONObject | undefined, - props: DatabaseCommonInteractionProps - ): Promise<void> { - for (const monitorId of monitorIds) { - // get last monitor status timeline. - const lastMonitorStatusTimeline: MonitorStatusTimeline | null = - await MonitorStatusTimelineService.findOneBy({ - query: { - monitorId: monitorId, - projectId: projectId, - }, - select: { - _id: true, - monitorStatusId: true, - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - }); - - if ( - lastMonitorStatusTimeline && - lastMonitorStatusTimeline.monitorStatusId && - lastMonitorStatusTimeline.monitorStatusId.toString() === - monitorStatusId.toString() - ) { - continue; - } - - const statusTimeline: MonitorStatusTimeline = - new MonitorStatusTimeline(); - - statusTimeline.monitorId = monitorId; - statusTimeline.monitorStatusId = monitorStatusId; - statusTimeline.projectId = projectId; - statusTimeline.isOwnerNotified = !notifyOwners; - - if (statusChangeLog) { - statusTimeline.statusChangeLog = statusChangeLog; - } - if (rootCause) { - statusTimeline.rootCause = rootCause; - } - - await MonitorStatusTimelineService.create({ - data: statusTimeline, - props: props, - }); - } + if (statusChangeLog) { + statusTimeline.statusChangeLog = statusChangeLog; + } + if (rootCause) { + statusTimeline.rootCause = rootCause; + } + + await MonitorStatusTimelineService.create({ + data: statusTimeline, + props: props, + }); } + } } export default new Service(); diff --git a/CommonServer/Services/MonitorStatusService.ts b/CommonServer/Services/MonitorStatusService.ts index fc2cb15db9..e36a53cafc 100644 --- a/CommonServer/Services/MonitorStatusService.ts +++ b/CommonServer/Services/MonitorStatusService.ts @@ -1,159 +1,155 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Model from 'Model/Models/MonitorStatus'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Model from "Model/Models/MonitorStatus"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.priority) { + throw new BadDataException("Monitor Status priority is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.priority) { - throw new BadDataException('Monitor Status priority is required'); - } + if (!createBy.data.projectId) { + throw new BadDataException("Monitor Status projectId is required"); + } - if (!createBy.data.projectId) { - throw new BadDataException('Monitor Status projectId is required'); - } + await this.rearrangePriority( + createBy.data.priority, + createBy.data.projectId, + true, + ); + return { + createBy: createBy, + carryForward: null, + }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting Monitor Status. Please try the delete with objectId", + ); + } + + let monitorStatus: Model | null = null; + + if (!deleteBy.props.isRoot) { + monitorStatus = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + priority: true, + projectId: true, + }, + }); + } + + return { + deleteBy, + carryForward: monitorStatus, + }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const monitorStatus: Model | null = onDelete.carryForward; + + if (!deleteBy.props.isRoot && monitorStatus) { + if (monitorStatus && monitorStatus.priority && monitorStatus.projectId) { await this.rearrangePriority( - createBy.data.priority, - createBy.data.projectId, - true + monitorStatus.priority, + monitorStatus.projectId, + false, ); - - return { - createBy: createBy, - carryForward: null, - }; + } } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting Monitor Status. Please try the delete with objectId' - ); - } + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - let monitorStatus: Model | null = null; - - if (!deleteBy.props.isRoot) { - monitorStatus = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - priority: true, - projectId: true, - }, - }); - } - - return { - deleteBy, - carryForward: monitorStatus, - }; + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.priority && !updateBy.props.isRoot) { + throw new BadDataException( + "Monitor Status priority should not be updated. Delete this monitor status and create a new state with the right priority.", + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const monitorStatus: Model | null = onDelete.carryForward; + return { updateBy, carryForward: null }; + } - if (!deleteBy.props.isRoot && monitorStatus) { - if ( - monitorStatus && - monitorStatus.priority && - monitorStatus.projectId - ) { - await this.rearrangePriority( - monitorStatus.priority, - monitorStatus.projectId, - false - ); - } - } + private async rearrangePriority( + currentPriority: number, + projectId: ObjectID, + increasePriority: boolean = true, + ): Promise<void> { + // get monitor status with this priority. + const monitorStatuses: Array<Model> = await this.findBy({ + query: { + priority: QueryHelper.greaterThanEqualTo(currentPriority), + projectId: projectId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + priority: true, + }, + sort: { + priority: SortOrder.Ascending, + }, + }); - return { - deleteBy: deleteBy, - carryForward: null, - }; - } - - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if (updateBy.data.priority && !updateBy.props.isRoot) { - throw new BadDataException( - 'Monitor Status priority should not be updated. Delete this monitor status and create a new state with the right priority.' - ); - } - - return { updateBy, carryForward: null }; - } - - private async rearrangePriority( - currentPriority: number, - projectId: ObjectID, - increasePriority: boolean = true - ): Promise<void> { - // get monitor status with this priority. - const monitorStatuses: Array<Model> = await this.findBy({ - query: { - priority: QueryHelper.greaterThanEqualTo(currentPriority), - projectId: projectId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - priority: true, - }, - sort: { - priority: SortOrder.Ascending, - }, - }); - - let newPriority: number = currentPriority; - - for (const monitorStatus of monitorStatuses) { - if (increasePriority) { - newPriority = monitorStatus.priority! + 1; - } else { - newPriority = monitorStatus.priority! - 1; - } - - await this.updateOneBy({ - query: { - _id: monitorStatus._id!, - }, - data: { - priority: newPriority, - }, - props: { - isRoot: true, - }, - }); - } + let newPriority: number = currentPriority; + + for (const monitorStatus of monitorStatuses) { + if (increasePriority) { + newPriority = monitorStatus.priority! + 1; + } else { + newPriority = monitorStatus.priority! - 1; + } + + await this.updateOneBy({ + query: { + _id: monitorStatus._id!, + }, + data: { + priority: newPriority, + }, + props: { + isRoot: true, + }, + }); } + } } export default new Service(); diff --git a/CommonServer/Services/MonitorStatusTimelineService.ts b/CommonServer/Services/MonitorStatusTimelineService.ts index a38d855d51..2609be58be 100644 --- a/CommonServer/Services/MonitorStatusTimelineService.ts +++ b/CommonServer/Services/MonitorStatusTimelineService.ts @@ -1,309 +1,302 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import Semaphore from '../Infrastructure/Semaphore'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import DatabaseService from './DatabaseService'; -import MonitorService from './MonitorService'; -import UserService from './UserService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import User from 'Model/Models/User'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import Semaphore from "../Infrastructure/Semaphore"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import DatabaseService from "./DatabaseService"; +import MonitorService from "./MonitorService"; +import UserService from "./UserService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import User from "Model/Models/User"; export class Service extends DatabaseService<MonitorStatusTimeline> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(MonitorStatusTimeline, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 120); + public constructor(postgresDatabase?: PostgresDatabase) { + super(MonitorStatusTimeline, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 120); + } + + protected override async onBeforeCreate( + createBy: CreateBy<MonitorStatusTimeline>, + ): Promise<OnCreate<MonitorStatusTimeline>> { + if (!createBy.data.monitorId) { + throw new BadDataException("monitorId is null"); } - protected override async onBeforeCreate( - createBy: CreateBy<MonitorStatusTimeline> - ): Promise<OnCreate<MonitorStatusTimeline>> { - if (!createBy.data.monitorId) { - throw new BadDataException('monitorId is null'); - } + const mutexId: ObjectID = await Semaphore.lock({ + key: createBy.data.monitorId.toString(), + }); - const mutexId: ObjectID = await Semaphore.lock({ - key: createBy.data.monitorId.toString(), + if (!createBy.data.startsAt) { + createBy.data.startsAt = OneUptimeDate.getCurrentDate(); + } + + if ( + (createBy.data.createdByUserId || + createBy.data.createdByUser || + createBy.props.userId) && + !createBy.data.rootCause + ) { + let userId: ObjectID | undefined = createBy.data.createdByUserId; + + if (createBy.props.userId) { + userId = createBy.props.userId; + } + + if (createBy.data.createdByUser && createBy.data.createdByUser.id) { + userId = createBy.data.createdByUser.id; + } + + const user: User | null = await UserService.findOneBy({ + query: { + _id: userId?.toString() as string, + }, + select: { + _id: true, + name: true, + email: true, + }, + props: { + isRoot: true, + }, + }); + + if (user) { + createBy.data.rootCause = `Monitor status created by ${user.name} (${user.email})`; + } + } + + const lastMonitorStatusTimeline: MonitorStatusTimeline | null = + await this.findOneBy({ + query: { + monitorId: createBy.data.monitorId, + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + return { + createBy, + carryForward: { + lastMonitorStatusTimelineId: lastMonitorStatusTimeline?.id || null, + mutexId: mutexId, + }, + }; + } + + protected override async onCreateSuccess( + onCreate: OnCreate<MonitorStatusTimeline>, + createdItem: MonitorStatusTimeline, + ): Promise<MonitorStatusTimeline> { + if (!createdItem.monitorId) { + throw new BadDataException("monitorId is null"); + } + + if (!createdItem.monitorStatusId) { + throw new BadDataException("monitorStatusId is null"); + } + + // update the last status as ended. + + if (onCreate.carryForward.lastMonitorStatusTimelineId) { + await this.updateOneById({ + id: onCreate.carryForward.lastMonitorStatusTimelineId!, + data: { + endsAt: createdItem.createdAt || OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); + } + + await MonitorService.updateOneBy({ + query: { + _id: createdItem.monitorId?.toString(), + }, + data: { + currentMonitorStatusId: createdItem.monitorStatusId, + }, + props: onCreate.createBy.props, + }); + + if (onCreate.carryForward.mutexId) { + const mutexId: ObjectID = onCreate.carryForward.mutexId; + await Semaphore.release(mutexId); + } + + return createdItem; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<MonitorStatusTimeline>, + ): Promise<OnDelete<MonitorStatusTimeline>> { + if (deleteBy.query._id) { + const monitorStatusTimelineToBeDeleted: MonitorStatusTimeline | null = + await this.findOneById({ + id: new ObjectID(deleteBy.query._id as string), + select: { + monitorId: true, + startsAt: true, + }, + props: { + isRoot: true, + }, }); - if (!createBy.data.startsAt) { - createBy.data.startsAt = OneUptimeDate.getCurrentDate(); + const monitorId: ObjectID | undefined = + monitorStatusTimelineToBeDeleted?.monitorId; + + if (monitorId) { + const monitorStatusTimeline: PositiveNumber = await this.countBy({ + query: { + monitorId: monitorId, + }, + props: { + isRoot: true, + }, + }); + + if (monitorStatusTimeline.isOne()) { + throw new BadDataException( + "Cannot delete the only status timeline. Monitor should have at least one status timeline.", + ); } - if ( - (createBy.data.createdByUserId || - createBy.data.createdByUser || - createBy.props.userId) && - !createBy.data.rootCause - ) { - let userId: ObjectID | undefined = createBy.data.createdByUserId; + // adjust times of other timeline events. get the state before this status timeline. - if (createBy.props.userId) { - userId = createBy.props.userId; - } - - if (createBy.data.createdByUser && createBy.data.createdByUser.id) { - userId = createBy.data.createdByUser.id; - } - - const user: User | null = await UserService.findOneBy({ - query: { - _id: userId?.toString() as string, - }, - select: { - _id: true, - name: true, - email: true, - }, - props: { - isRoot: true, - }, + if (monitorStatusTimelineToBeDeleted?.startsAt) { + const beforeState: MonitorStatusTimeline | null = + await this.findOneBy({ + query: { + monitorId: monitorId, + startsAt: QueryHelper.lessThan( + monitorStatusTimelineToBeDeleted?.startsAt, + ), + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + startsAt: true, + }, }); - if (user) { - createBy.data.rootCause = `Monitor status created by ${user.name} (${user.email})`; - } - } - - const lastMonitorStatusTimeline: MonitorStatusTimeline | null = - await this.findOneBy({ + if (beforeState) { + const afterState: MonitorStatusTimeline | null = + await this.findOneBy({ query: { - monitorId: createBy.data.monitorId, + monitorId: monitorId, + startsAt: QueryHelper.greaterThan( + monitorStatusTimelineToBeDeleted?.startsAt, + ), }, sort: { - createdAt: SortOrder.Descending, + createdAt: SortOrder.Ascending, }, props: { - isRoot: true, + isRoot: true, }, select: { - _id: true, + _id: true, + startsAt: true, }, - }); + }); - return { - createBy, - carryForward: { - lastMonitorStatusTimelineId: - lastMonitorStatusTimeline?.id || null, - mutexId: mutexId, - }, - }; - } + if (!afterState) { + // if there's nothing after then end date of before state is null. - protected override async onCreateSuccess( - onCreate: OnCreate<MonitorStatusTimeline>, - createdItem: MonitorStatusTimeline - ): Promise<MonitorStatusTimeline> { - if (!createdItem.monitorId) { - throw new BadDataException('monitorId is null'); - } - - if (!createdItem.monitorStatusId) { - throw new BadDataException('monitorStatusId is null'); - } - - // update the last status as ended. - - if (onCreate.carryForward.lastMonitorStatusTimelineId) { - await this.updateOneById({ - id: onCreate.carryForward.lastMonitorStatusTimelineId!, + await this.updateOneById({ + id: beforeState.id!, data: { - endsAt: - createdItem.createdAt || OneUptimeDate.getCurrentDate(), + endsAt: null as any, }, props: { - isRoot: true, + isRoot: true, }, - }); - } + }); + } else { + // if there's something after then end date of before state is start date of after state. - await MonitorService.updateOneBy({ - query: { - _id: createdItem.monitorId?.toString(), - }, - data: { - currentMonitorStatusId: createdItem.monitorStatusId, - }, - props: onCreate.createBy.props, + await this.updateOneById({ + id: beforeState.id!, + data: { + endsAt: afterState.startsAt!, + }, + props: { + isRoot: true, + }, + }); + } + } + } + } + + return { deleteBy, carryForward: monitorId }; + } + + return { deleteBy, carryForward: null }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<MonitorStatusTimeline>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<MonitorStatusTimeline>> { + if (onDelete.carryForward) { + // this is monitorId. + const monitorId: ObjectID = onDelete.carryForward as ObjectID; + + // get last status of this monitor. + const monitorStatusTimeline: MonitorStatusTimeline | null = + await this.findOneBy({ + query: { + monitorId: monitorId, + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + monitorStatusId: true, + }, }); - if (onCreate.carryForward.mutexId) { - const mutexId: ObjectID = onCreate.carryForward.mutexId; - await Semaphore.release(mutexId); - } - - return createdItem; + if (monitorStatusTimeline && monitorStatusTimeline.monitorStatusId) { + await MonitorService.updateOneBy({ + query: { + _id: monitorId.toString(), + }, + data: { + currentMonitorStatusId: monitorStatusTimeline.monitorStatusId, + }, + props: { + isRoot: true, + }, + }); + } } - protected override async onBeforeDelete( - deleteBy: DeleteBy<MonitorStatusTimeline> - ): Promise<OnDelete<MonitorStatusTimeline>> { - if (deleteBy.query._id) { - const monitorStatusTimelineToBeDeleted: MonitorStatusTimeline | null = - await this.findOneById({ - id: new ObjectID(deleteBy.query._id as string), - select: { - monitorId: true, - startsAt: true, - }, - props: { - isRoot: true, - }, - }); - - const monitorId: ObjectID | undefined = - monitorStatusTimelineToBeDeleted?.monitorId; - - if (monitorId) { - const monitorStatusTimeline: PositiveNumber = - await this.countBy({ - query: { - monitorId: monitorId, - }, - props: { - isRoot: true, - }, - }); - - if (monitorStatusTimeline.isOne()) { - throw new BadDataException( - 'Cannot delete the only status timeline. Monitor should have at least one status timeline.' - ); - } - - // adjust times of other timeline events. get the state before this status timeline. - - if (monitorStatusTimelineToBeDeleted?.startsAt) { - const beforeState: MonitorStatusTimeline | null = - await this.findOneBy({ - query: { - monitorId: monitorId, - startsAt: QueryHelper.lessThan( - monitorStatusTimelineToBeDeleted?.startsAt - ), - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - startsAt: true, - }, - }); - - if (beforeState) { - const afterState: MonitorStatusTimeline | null = - await this.findOneBy({ - query: { - monitorId: monitorId, - startsAt: QueryHelper.greaterThan( - monitorStatusTimelineToBeDeleted?.startsAt - ), - }, - sort: { - createdAt: SortOrder.Ascending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - startsAt: true, - }, - }); - - if (!afterState) { - // if there's nothing after then end date of before state is null. - - await this.updateOneById({ - id: beforeState.id!, - data: { - endsAt: null as any, - }, - props: { - isRoot: true, - }, - }); - } else { - // if there's something after then end date of before state is start date of after state. - - await this.updateOneById({ - id: beforeState.id!, - data: { - endsAt: afterState.startsAt!, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { deleteBy, carryForward: monitorId }; - } - - return { deleteBy, carryForward: null }; - } - - protected override async onDeleteSuccess( - onDelete: OnDelete<MonitorStatusTimeline>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<MonitorStatusTimeline>> { - if (onDelete.carryForward) { - // this is monitorId. - const monitorId: ObjectID = onDelete.carryForward as ObjectID; - - // get last status of this monitor. - const monitorStatusTimeline: MonitorStatusTimeline | null = - await this.findOneBy({ - query: { - monitorId: monitorId, - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - monitorStatusId: true, - }, - }); - - if ( - monitorStatusTimeline && - monitorStatusTimeline.monitorStatusId - ) { - await MonitorService.updateOneBy({ - query: { - _id: monitorId.toString(), - }, - data: { - currentMonitorStatusId: - monitorStatusTimeline.monitorStatusId, - }, - props: { - isRoot: true, - }, - }); - } - } - - return onDelete; - } + return onDelete; + } } export default new Service(); diff --git a/CommonServer/Services/NotificationService.ts b/CommonServer/Services/NotificationService.ts index abdb99a6af..6a60c02602 100644 --- a/CommonServer/Services/NotificationService.ts +++ b/CommonServer/Services/NotificationService.ts @@ -1,201 +1,196 @@ -import { IsBillingEnabled } from '../EnvironmentConfig'; -import logger from '../Utils/Logger'; -import BaseService from './BaseService'; -import BillingService from './BillingService'; -import ProjectService from './ProjectService'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Project from 'Model/Models/Project'; +import { IsBillingEnabled } from "../EnvironmentConfig"; +import logger from "../Utils/Logger"; +import BaseService from "./BaseService"; +import BillingService from "./BillingService"; +import ProjectService from "./ProjectService"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Project from "Model/Models/Project"; export class NotificationService extends BaseService { - public constructor() { - super(); + public constructor() { + super(); + } + + public async rechargeBalance( + projectId: ObjectID, + amountInUSD: number, + ): Promise<number> { + const project: Project | null = await ProjectService.findOneById({ + id: projectId, + select: { + smsOrCallCurrentBalanceInUSDCents: true, + enableAutoRechargeSmsOrCallBalance: true, + enableSmsNotifications: true, + autoRechargeSmsOrCallByBalanceInUSD: true, + autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: true, + paymentProviderCustomerId: true, + name: true, + failedCallAndSMSBalanceChargeNotificationSentToOwners: true, + }, + props: { + isRoot: true, + }, + }); + + if (!project) { + return 0; } - public async rechargeBalance( - projectId: ObjectID, - amountInUSD: number - ): Promise<number> { - const project: Project | null = await ProjectService.findOneById({ - id: projectId, - select: { - smsOrCallCurrentBalanceInUSDCents: true, - enableAutoRechargeSmsOrCallBalance: true, - enableSmsNotifications: true, - autoRechargeSmsOrCallByBalanceInUSD: true, - autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: true, - paymentProviderCustomerId: true, - name: true, - failedCallAndSMSBalanceChargeNotificationSentToOwners: true, + try { + if ( + !(await BillingService.hasPaymentMethods( + project.paymentProviderCustomerId!, + )) + ) { + if (!project.failedCallAndSMSBalanceChargeNotificationSentToOwners) { + await ProjectService.updateOneById({ + data: { + failedCallAndSMSBalanceChargeNotificationSentToOwners: true, }, + id: project.id!, props: { - isRoot: true, + isRoot: true, }, - }); - - if (!project) { - return 0; + }); + await ProjectService.sendEmailToProjectOwners( + project.id!, + "ACTION REQUIRED: SMS and Call Recharge Failed for project - " + + (project.name || ""), + `We have tried to recharge your SMS and Call balance for project - ${ + project.name || "" + } and failed. We could not find a payment method for the project. Please add a payment method in Project Settings.`, + ); } + throw new BadDataException( + "No payment methods found for the project. Please add a payment method in Project Settings to continue.", + ); + } - try { - if ( - !(await BillingService.hasPaymentMethods( - project.paymentProviderCustomerId! - )) - ) { - if ( - !project.failedCallAndSMSBalanceChargeNotificationSentToOwners - ) { - await ProjectService.updateOneById({ - data: { - failedCallAndSMSBalanceChargeNotificationSentToOwners: - true, - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - await ProjectService.sendEmailToProjectOwners( - project.id!, - 'ACTION REQUIRED: SMS and Call Recharge Failed for project - ' + - (project.name || ''), - `We have tried to recharge your SMS and Call balance for project - ${ - project.name || '' - } and failed. We could not find a payment method for the project. Please add a payment method in Project Settings.` - ); - } - throw new BadDataException( - 'No payment methods found for the project. Please add a payment method in Project Settings to continue.' - ); - } + // recharge balance + const updatedAmount: number = Math.floor( + (project.smsOrCallCurrentBalanceInUSDCents || 0) + amountInUSD * 100, + ); - // recharge balance - const updatedAmount: number = Math.floor( - (project.smsOrCallCurrentBalanceInUSDCents || 0) + - amountInUSD * 100 - ); + // If the recharge is successful, then update the project balance. + await BillingService.generateInvoiceAndChargeCustomer( + project.paymentProviderCustomerId!, + "SMS or Call Balance Recharge", + amountInUSD, + ); - // If the recharge is successful, then update the project balance. - await BillingService.generateInvoiceAndChargeCustomer( - project.paymentProviderCustomerId!, - 'SMS or Call Balance Recharge', - amountInUSD - ); + await ProjectService.updateOneById({ + data: { + smsOrCallCurrentBalanceInUSDCents: updatedAmount, + failedCallAndSMSBalanceChargeNotificationSentToOwners: false, // reset this flag + lowCallAndSMSBalanceNotificationSentToOwners: false, // reset this flag + notEnabledSmsOrCallNotificationSentToOwners: false, + }, + id: project.id!, + props: { + isRoot: true, + }, + }); - await ProjectService.updateOneById({ - data: { - smsOrCallCurrentBalanceInUSDCents: updatedAmount, - failedCallAndSMSBalanceChargeNotificationSentToOwners: - false, // reset this flag - lowCallAndSMSBalanceNotificationSentToOwners: false, // reset this flag - notEnabledSmsOrCallNotificationSentToOwners: false, - }, - id: project.id!, - props: { - isRoot: true, - }, - }); + await ProjectService.sendEmailToProjectOwners( + project.id!, + "SMS and Call Recharge Successful for project - " + + (project.name || ""), + `We have successfully recharged your SMS and Call balance for project - ${ + project.name || "" + } by ${amountInUSD} USD. Your current balance is ${ + updatedAmount / 100 + } USD.`, + ); - await ProjectService.sendEmailToProjectOwners( - project.id!, - 'SMS and Call Recharge Successful for project - ' + - (project.name || ''), - `We have successfully recharged your SMS and Call balance for project - ${ - project.name || '' - } by ${amountInUSD} USD. Your current balance is ${ - updatedAmount / 100 - } USD.` - ); + project.smsOrCallCurrentBalanceInUSDCents = updatedAmount; - project.smsOrCallCurrentBalanceInUSDCents = updatedAmount; + return updatedAmount; + } catch (err) { + await ProjectService.updateOneById({ + data: { + failedCallAndSMSBalanceChargeNotificationSentToOwners: true, + }, + id: project.id!, + props: { + isRoot: true, + }, + }); + await ProjectService.sendEmailToProjectOwners( + project.id!, + "ACTION REQUIRED: SMS and Call Recharge Failed for project - " + + (project.name || ""), + `We have tried recharged your SMS and Call balance for project - ${ + project.name || "" + } and failed. Please make sure your payment method is upto date and has sufficient balance. You can add new payment methods in Project Settings.`, + ); + logger.error(err); + throw err; + } + } - return updatedAmount; - } catch (err) { - await ProjectService.updateOneById({ - data: { - failedCallAndSMSBalanceChargeNotificationSentToOwners: true, - }, - id: project.id!, - props: { - isRoot: true, - }, - }); - await ProjectService.sendEmailToProjectOwners( - project.id!, - 'ACTION REQUIRED: SMS and Call Recharge Failed for project - ' + - (project.name || ''), - `We have tried recharged your SMS and Call balance for project - ${ - project.name || '' - } and failed. Please make sure your payment method is upto date and has sufficient balance. You can add new payment methods in Project Settings.` - ); - logger.error(err); - throw err; + public async rechargeIfBalanceIsLow( + projectId: ObjectID, + options?: { + autoRechargeSmsOrCallByBalanceInUSD: number; + autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: number; + enableAutoRechargeSmsOrCallBalance: boolean; + }, + ): Promise<number> { + let project: Project | null = null; + if (projectId && IsBillingEnabled) { + // check payment methods. + + project = await ProjectService.findOneById({ + id: projectId, + select: { + smsOrCallCurrentBalanceInUSDCents: true, + enableAutoRechargeSmsOrCallBalance: true, + autoRechargeSmsOrCallByBalanceInUSD: true, + autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: true, + }, + props: { + isRoot: true, + }, + }); + + const autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: number = + options?.autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD || + project?.autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD || + 0; + const autoRechargeSmsOrCallByBalanceInUSD: number = + options?.autoRechargeSmsOrCallByBalanceInUSD || + project?.autoRechargeSmsOrCallByBalanceInUSD || + 0; + + const enableAutoRechargeSmsOrCallBalance: boolean = options + ? options.enableAutoRechargeSmsOrCallBalance + : project?.enableAutoRechargeSmsOrCallBalance || false; + + if (!project) { + return 0; + } + + if ( + enableAutoRechargeSmsOrCallBalance && + autoRechargeSmsOrCallByBalanceInUSD && + autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD + ) { + if ( + (project.smsOrCallCurrentBalanceInUSDCents || 0) / 100 < + autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD + ) { + const updatedAmount: number = await this.rechargeBalance( + projectId, + autoRechargeSmsOrCallByBalanceInUSD, + ); + project.smsOrCallCurrentBalanceInUSDCents = updatedAmount; } + } } - public async rechargeIfBalanceIsLow( - projectId: ObjectID, - options?: { - autoRechargeSmsOrCallByBalanceInUSD: number; - autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: number; - enableAutoRechargeSmsOrCallBalance: boolean; - } - ): Promise<number> { - let project: Project | null = null; - if (projectId && IsBillingEnabled) { - // check payment methods. - - project = await ProjectService.findOneById({ - id: projectId, - select: { - smsOrCallCurrentBalanceInUSDCents: true, - enableAutoRechargeSmsOrCallBalance: true, - autoRechargeSmsOrCallByBalanceInUSD: true, - autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: true, - }, - props: { - isRoot: true, - }, - }); - - const autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: number = - options?.autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD || - project?.autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD || - 0; - const autoRechargeSmsOrCallByBalanceInUSD: number = - options?.autoRechargeSmsOrCallByBalanceInUSD || - project?.autoRechargeSmsOrCallByBalanceInUSD || - 0; - - const enableAutoRechargeSmsOrCallBalance: boolean = options - ? options.enableAutoRechargeSmsOrCallBalance - : project?.enableAutoRechargeSmsOrCallBalance || false; - - if (!project) { - return 0; - } - - if ( - enableAutoRechargeSmsOrCallBalance && - autoRechargeSmsOrCallByBalanceInUSD && - autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD - ) { - if ( - (project.smsOrCallCurrentBalanceInUSDCents || 0) / 100 < - autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD - ) { - const updatedAmount: number = await this.rechargeBalance( - projectId, - autoRechargeSmsOrCallByBalanceInUSD - ); - project.smsOrCallCurrentBalanceInUSDCents = updatedAmount; - } - } - } - - return project?.smsOrCallCurrentBalanceInUSDCents || 0; - } + return project?.smsOrCallCurrentBalanceInUSDCents || 0; + } } export default new NotificationService(); diff --git a/CommonServer/Services/OnCallDutyPolicyCustomFieldService.ts b/CommonServer/Services/OnCallDutyPolicyCustomFieldService.ts index 77c8b62218..de1837ac58 100644 --- a/CommonServer/Services/OnCallDutyPolicyCustomFieldService.ts +++ b/CommonServer/Services/OnCallDutyPolicyCustomFieldService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/OnCallDutyPolicyCustomField'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/OnCallDutyPolicyCustomField"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts b/CommonServer/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts index 3b50ac8e97..8cb80b1411 100644 --- a/CommonServer/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +++ b/CommonServer/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/OnCallDutyPolicyEscalationRuleSchedule'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/OnCallDutyPolicyEscalationRuleSchedule"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyEscalationRuleService.ts b/CommonServer/Services/OnCallDutyPolicyEscalationRuleService.ts index 29c8b422c6..f9372109e3 100644 --- a/CommonServer/Services/OnCallDutyPolicyEscalationRuleService.ts +++ b/CommonServer/Services/OnCallDutyPolicyEscalationRuleService.ts @@ -1,751 +1,726 @@ -import { IsBillingEnabled } from '../EnvironmentConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import Query from '../Types/Database/Query'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import OnCallDutyPolicyEscalationRuleScheduleService from './OnCallDutyPolicyEscalationRuleScheduleService'; -import OnCallDutyPolicyEscalationRuleTeamService from './OnCallDutyPolicyEscalationRuleTeamService'; -import OnCallDutyPolicyEscalationRuleUserService from './OnCallDutyPolicyEscalationRuleUserService'; -import OnCallDutyPolicyExecutionLogService from './OnCallDutyPolicyExecutionLogService'; -import OnCallDutyPolicyExecutionLogTimelineService from './OnCallDutyPolicyExecutionLogTimelineService'; -import OnCallDutyPolicyScheduleService from './OnCallDutyPolicyScheduleService'; -import TeamMemberService from './TeamMemberService'; -import UserNotificationRuleService from './UserNotificationRuleService'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import OnCallDutyExecutionLogTimelineStatus from 'Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import Model from 'Model/Models/OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicyEscalationRuleSchedule from 'Model/Models/OnCallDutyPolicyEscalationRuleSchedule'; -import OnCallDutyPolicyEscalationRuleTeam from 'Model/Models/OnCallDutyPolicyEscalationRuleTeam'; -import OnCallDutyPolicyEscalationRuleUser from 'Model/Models/OnCallDutyPolicyEscalationRuleUser'; -import OnCallDutyPolicyExecutionLogTimeline from 'Model/Models/OnCallDutyPolicyExecutionLogTimeline'; -import User from 'Model/Models/User'; +import { IsBillingEnabled } from "../EnvironmentConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import Query from "../Types/Database/Query"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import OnCallDutyPolicyEscalationRuleScheduleService from "./OnCallDutyPolicyEscalationRuleScheduleService"; +import OnCallDutyPolicyEscalationRuleTeamService from "./OnCallDutyPolicyEscalationRuleTeamService"; +import OnCallDutyPolicyEscalationRuleUserService from "./OnCallDutyPolicyEscalationRuleUserService"; +import OnCallDutyPolicyExecutionLogService from "./OnCallDutyPolicyExecutionLogService"; +import OnCallDutyPolicyExecutionLogTimelineService from "./OnCallDutyPolicyExecutionLogTimelineService"; +import OnCallDutyPolicyScheduleService from "./OnCallDutyPolicyScheduleService"; +import TeamMemberService from "./TeamMemberService"; +import UserNotificationRuleService from "./UserNotificationRuleService"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import OnCallDutyExecutionLogTimelineStatus from "Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import Model from "Model/Models/OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicyEscalationRuleSchedule from "Model/Models/OnCallDutyPolicyEscalationRuleSchedule"; +import OnCallDutyPolicyEscalationRuleTeam from "Model/Models/OnCallDutyPolicyEscalationRuleTeam"; +import OnCallDutyPolicyEscalationRuleUser from "Model/Models/OnCallDutyPolicyEscalationRuleUser"; +import OnCallDutyPolicyExecutionLogTimeline from "Model/Models/OnCallDutyPolicyExecutionLogTimeline"; +import User from "Model/Models/User"; export class Service extends DatabaseService<Model> { - public async startRuleExecution( - ruleId: ObjectID, - options: { - projectId: ObjectID; - triggeredByIncidentId?: ObjectID | undefined; - userNotificationEventType: UserNotificationEventType; - onCallPolicyExecutionLogId: ObjectID; - onCallPolicyId: ObjectID; - } - ): Promise<void> { - // add log timeline. + public async startRuleExecution( + ruleId: ObjectID, + options: { + projectId: ObjectID; + triggeredByIncidentId?: ObjectID | undefined; + userNotificationEventType: UserNotificationEventType; + onCallPolicyExecutionLogId: ObjectID; + onCallPolicyId: ObjectID; + }, + ): Promise<void> { + // add log timeline. - const rule: Model | null = await this.findOneById({ - id: ruleId, - select: { - _id: true, - order: true, - escalateAfterInMinutes: true, - }, - props: { - isRoot: true, - }, - }); + const rule: Model | null = await this.findOneById({ + id: ruleId, + select: { + _id: true, + order: true, + escalateAfterInMinutes: true, + }, + props: { + isRoot: true, + }, + }); - if (!rule) { - throw new BadDataException( - `On-Call Duty Policy Escalation Rule with id ${ruleId.toString()} not found` - ); - } - - await OnCallDutyPolicyExecutionLogService.updateOneById({ - id: options.onCallPolicyExecutionLogId, - data: { - lastEscalationRuleExecutedAt: OneUptimeDate.getCurrentDate(), - lastExecutedEscalationRuleId: ruleId, - lastExecutedEscalationRuleOrder: rule.order!, - executeNextEscalationRuleInMinutes: - rule.escalateAfterInMinutes || 0, - }, - props: { - isRoot: true, - }, - }); - - type GetNewLogFunction = () => OnCallDutyPolicyExecutionLogTimeline; - - const getNewLog: GetNewLogFunction = - (): OnCallDutyPolicyExecutionLogTimeline => { - const log: OnCallDutyPolicyExecutionLogTimeline = - new OnCallDutyPolicyExecutionLogTimeline(); - - log.projectId = options.projectId; - log.onCallDutyPolicyExecutionLogId = - options.onCallPolicyExecutionLogId; - log.onCallDutyPolicyId = options.onCallPolicyId; - log.onCallDutyPolicyEscalationRuleId = ruleId; - log.userNotificationEventType = - options.userNotificationEventType; - - if (options.triggeredByIncidentId) { - log.triggeredByIncidentId = options.triggeredByIncidentId; - } - - return log; - }; - - if ( - UserNotificationEventType.IncidentCreated === - options.userNotificationEventType && - !options.triggeredByIncidentId - ) { - throw new BadDataException( - 'triggeredByIncidentId is required when userNotificationEventType is IncidentCreated' - ); - } - - const usersInRule: Array<OnCallDutyPolicyEscalationRuleUser> = - await OnCallDutyPolicyEscalationRuleUserService.findBy({ - query: { - onCallDutyPolicyEscalationRuleId: ruleId, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - userId: true, - }, - }); - - const teamsInRule: Array<OnCallDutyPolicyEscalationRuleTeam> = - await OnCallDutyPolicyEscalationRuleTeamService.findBy({ - query: { - onCallDutyPolicyEscalationRuleId: ruleId, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - teamId: true, - }, - }); - - const schedulesInRule: Array<OnCallDutyPolicyEscalationRuleSchedule> = - await OnCallDutyPolicyEscalationRuleScheduleService.findBy({ - query: { - onCallDutyPolicyEscalationRuleId: ruleId, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - onCallDutyPolicyScheduleId: true, - }, - }); - - // get unique users and notify all the users. - - type StartUserNotificationRuleExecutionFunction = ( - userId: ObjectID, - teamId: ObjectID | null, - scheduleId: ObjectID | null - ) => Promise<void>; - - const startUserNotificationRuleExecution: StartUserNotificationRuleExecutionFunction = - async ( - userId: ObjectID, - teamId: ObjectID | null, - scheduleId: ObjectID | null - ): Promise<void> => { - // no users in this rule. Skipping. - let log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); - log.statusMessage = 'Sending notification to user.'; - log.status = OnCallDutyExecutionLogTimelineStatus.Executing; - log.alertSentToUserId = userId; - if (teamId) { - log.userBelongsToTeamId = teamId; - } - - if (scheduleId) { - log.onCallDutyScheduleId = scheduleId; - } - - log = await OnCallDutyPolicyExecutionLogTimelineService.create({ - data: log, - props: { - isRoot: true, - }, - }); - - await UserNotificationRuleService.startUserNotificationRulesExecution( - userId, - { - userNotificationEventType: - options.userNotificationEventType!, - triggeredByIncidentId: - options.triggeredByIncidentId || undefined, - onCallPolicyExecutionLogId: - options.onCallPolicyExecutionLogId, - onCallPolicyId: options.onCallPolicyId, - onCallPolicyEscalationRuleId: ruleId, - userBelongsToTeamId: teamId || undefined, - onCallDutyPolicyExecutionLogTimelineId: log.id!, - projectId: options.projectId, - onCallScheduleId: scheduleId || undefined, - } - ); - }; - - const uniqueUserIds: Array<ObjectID> = []; - - for (const teamInRule of teamsInRule) { - const usersInTeam: Array<User> = - await TeamMemberService.getUsersInTeam(teamInRule.teamId!); - - for (const user of usersInTeam) { - if ( - !uniqueUserIds.find((userId: ObjectID) => { - return user.id?.toString() === userId.toString(); - }) - ) { - uniqueUserIds.push(user.id!); - await startUserNotificationRuleExecution( - user.id!, - teamInRule.teamId!, - null - ); - } else { - // no users in this rule. Skipping. - const log: OnCallDutyPolicyExecutionLogTimeline = - getNewLog(); - log.statusMessage = - 'Skipped because notification sent to this user already.'; - log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; - log.alertSentToUserId = user.id!; - log.userBelongsToTeamId = teamInRule.teamId!; - - await OnCallDutyPolicyExecutionLogTimelineService.create({ - data: log, - props: { - isRoot: true, - }, - }); - } - } - } - - for (const userRule of usersInRule) { - if ( - !uniqueUserIds.find((userId: ObjectID) => { - return userRule.userId?.toString() === userId.toString(); - }) - ) { - uniqueUserIds.push(userRule.userId!); - await startUserNotificationRuleExecution( - userRule.userId!, - null, - null - ); - } else { - // no users in this rule. Skipping. - const log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); - log.statusMessage = - 'Skipped because notification sent to this user already.'; - log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; - log.alertSentToUserId = userRule.userId!; - - await OnCallDutyPolicyExecutionLogTimelineService.create({ - data: log, - props: { - isRoot: true, - }, - }); - } - } - - for (const scheduleRule of schedulesInRule) { - // get layers and users in this schedule and find a user to notify. - - const userIdInSchedule: ObjectID | null = - await OnCallDutyPolicyScheduleService.getCurrentUserIdInSchedule( - scheduleRule.onCallDutyPolicyScheduleId! - ); - - if (!userIdInSchedule) { - // no user active in this schedule. Skipping. - - const log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); - - log.statusMessage = - 'Skipped because no active users are found in this schedule.'; - - log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; - - log.onCallDutyScheduleId = - scheduleRule.onCallDutyPolicyScheduleId!; - - await OnCallDutyPolicyExecutionLogTimelineService.create({ - data: log, - props: { - isRoot: true, - }, - }); - - continue; - } - - if ( - !uniqueUserIds.find((userId: ObjectID) => { - return userIdInSchedule?.toString() === userId.toString(); - }) - ) { - uniqueUserIds.push(userIdInSchedule); - await startUserNotificationRuleExecution( - userIdInSchedule, - null, - scheduleRule.onCallDutyPolicyScheduleId! - ); - } else { - // no users in this rule. Skipping. - const log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); - log.statusMessage = - 'Skipped because notification sent to this user already.'; - log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; - log.alertSentToUserId = userIdInSchedule; - log.onCallDutyScheduleId = - scheduleRule.onCallDutyPolicyScheduleId!; - - await OnCallDutyPolicyExecutionLogTimelineService.create({ - data: log, - props: { - isRoot: true, - }, - }); - } - } - - if (uniqueUserIds.length === 0) { - // no users in this rule. Skipping. - const log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); - log.statusMessage = 'Skipped because no users in this rule.'; - log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; - - await OnCallDutyPolicyExecutionLogTimelineService.create({ - data: log, - props: { - isRoot: true, - }, - }); - } + if (!rule) { + throw new BadDataException( + `On-Call Duty Policy Escalation Rule with id ${ruleId.toString()} not found`, + ); } - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + await OnCallDutyPolicyExecutionLogService.updateOneById({ + id: options.onCallPolicyExecutionLogId, + data: { + lastEscalationRuleExecutedAt: OneUptimeDate.getCurrentDate(), + lastExecutedEscalationRuleId: ruleId, + lastExecutedEscalationRuleOrder: rule.order!, + executeNextEscalationRuleInMinutes: rule.escalateAfterInMinutes || 0, + }, + props: { + isRoot: true, + }, + }); + + type GetNewLogFunction = () => OnCallDutyPolicyExecutionLogTimeline; + + const getNewLog: GetNewLogFunction = + (): OnCallDutyPolicyExecutionLogTimeline => { + const log: OnCallDutyPolicyExecutionLogTimeline = + new OnCallDutyPolicyExecutionLogTimeline(); + + log.projectId = options.projectId; + log.onCallDutyPolicyExecutionLogId = options.onCallPolicyExecutionLogId; + log.onCallDutyPolicyId = options.onCallPolicyId; + log.onCallDutyPolicyEscalationRuleId = ruleId; + log.userNotificationEventType = options.userNotificationEventType; + + if (options.triggeredByIncidentId) { + log.triggeredByIncidentId = options.triggeredByIncidentId; + } + + return log; + }; + + if ( + UserNotificationEventType.IncidentCreated === + options.userNotificationEventType && + !options.triggeredByIncidentId + ) { + throw new BadDataException( + "triggeredByIncidentId is required when userNotificationEventType is IncidentCreated", + ); } - protected override async onCreateSuccess( - onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - if (!createdItem.projectId) { - throw new BadDataException('projectId is required'); - } + const usersInRule: Array<OnCallDutyPolicyEscalationRuleUser> = + await OnCallDutyPolicyEscalationRuleUserService.findBy({ + query: { + onCallDutyPolicyEscalationRuleId: ruleId, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + userId: true, + }, + }); - if (!createdItem.id) { - throw new BadDataException('id is required'); - } + const teamsInRule: Array<OnCallDutyPolicyEscalationRuleTeam> = + await OnCallDutyPolicyEscalationRuleTeamService.findBy({ + query: { + onCallDutyPolicyEscalationRuleId: ruleId, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + teamId: true, + }, + }); - // add people in escalation rule. + const schedulesInRule: Array<OnCallDutyPolicyEscalationRuleSchedule> = + await OnCallDutyPolicyEscalationRuleScheduleService.findBy({ + query: { + onCallDutyPolicyEscalationRuleId: ruleId, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + onCallDutyPolicyScheduleId: true, + }, + }); - if ( - onCreate.createBy.miscDataProps && - (onCreate.createBy.miscDataProps['teams'] || - onCreate.createBy.miscDataProps['users'] || - onCreate.createBy.miscDataProps['onCallSchedules']) - ) { - await this.addUsersTeamsAndSchedules( - createdItem.projectId, - createdItem.id, - createdItem.onCallDutyPolicyId!, - (onCreate.createBy.miscDataProps['users'] as Array<ObjectID>) || - [], - (onCreate.createBy.miscDataProps['teams'] as Array<ObjectID>) || - [], - (onCreate.createBy.miscDataProps[ - 'onCallSchedules' - ] as Array<ObjectID>) || [], - onCreate.createBy.props - ); - } + // get unique users and notify all the users. - return createdItem; - } + type StartUserNotificationRuleExecutionFunction = ( + userId: ObjectID, + teamId: ObjectID | null, + scheduleId: ObjectID | null, + ) => Promise<void>; - public async addUsersTeamsAndSchedules( - projectId: ObjectID, - escalationRuleId: ObjectID, - onCallDutyPolicyId: ObjectID, - usersIds: Array<ObjectID>, - teamIds: Array<ObjectID>, - onCallScheduleIds: Array<ObjectID>, - props: DatabaseCommonInteractionProps - ): Promise<void> { - for (const userId of usersIds) { - await this.addUser( - projectId, - escalationRuleId, - onCallDutyPolicyId, - userId, - props - ); - } - - for (const teamId of teamIds) { - await this.addTeam( - projectId, - escalationRuleId, - onCallDutyPolicyId, - teamId, - props - ); - } - - for (const scheduleId of onCallScheduleIds) { - await this.addOnCallSchedules( - projectId, - escalationRuleId, - onCallDutyPolicyId, - scheduleId, - props - ); - } - } - - public async addTeam( - projectId: ObjectID, - escalationRuleId: ObjectID, - onCallDutyPolicyId: ObjectID, - teamId: ObjectID, - props: DatabaseCommonInteractionProps - ): Promise<void> { - const teamInRule: OnCallDutyPolicyEscalationRuleTeam = - new OnCallDutyPolicyEscalationRuleTeam(); - teamInRule.projectId = projectId; - teamInRule.onCallDutyPolicyId = onCallDutyPolicyId; - teamInRule.onCallDutyPolicyEscalationRuleId = escalationRuleId; - teamInRule.teamId = teamId; - - await OnCallDutyPolicyEscalationRuleTeamService.create({ - data: teamInRule, - props, - }); - } - - public async addOnCallSchedules( - projectId: ObjectID, - escalationRuleId: ObjectID, - onCallDutyPolicyId: ObjectID, - onCallScheduleId: ObjectID, - props: DatabaseCommonInteractionProps - ): Promise<void> { - const scheduleInRule: OnCallDutyPolicyEscalationRuleSchedule = - new OnCallDutyPolicyEscalationRuleSchedule(); - scheduleInRule.projectId = projectId; - scheduleInRule.onCallDutyPolicyId = onCallDutyPolicyId; - scheduleInRule.onCallDutyPolicyEscalationRuleId = escalationRuleId; - scheduleInRule.onCallDutyPolicyScheduleId = onCallScheduleId; - - await OnCallDutyPolicyEscalationRuleScheduleService.create({ - data: scheduleInRule, - props, - }); - } - - public async addUser( - projectId: ObjectID, - escalationRuleId: ObjectID, - onCallDutyPolicyId: ObjectID, + const startUserNotificationRuleExecution: StartUserNotificationRuleExecutionFunction = + async ( userId: ObjectID, - props: DatabaseCommonInteractionProps - ): Promise<void> { - const userInRule: OnCallDutyPolicyEscalationRuleUser = - new OnCallDutyPolicyEscalationRuleUser(); - userInRule.projectId = projectId; - userInRule.onCallDutyPolicyId = onCallDutyPolicyId; - userInRule.onCallDutyPolicyEscalationRuleId = escalationRuleId; - userInRule.userId = userId; + teamId: ObjectID | null, + scheduleId: ObjectID | null, + ): Promise<void> => { + // no users in this rule. Skipping. + let log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); + log.statusMessage = "Sending notification to user."; + log.status = OnCallDutyExecutionLogTimelineStatus.Executing; + log.alertSentToUserId = userId; + if (teamId) { + log.userBelongsToTeamId = teamId; + } - await OnCallDutyPolicyEscalationRuleUserService.create({ - data: userInRule, - props, + if (scheduleId) { + log.onCallDutyScheduleId = scheduleId; + } + + log = await OnCallDutyPolicyExecutionLogTimelineService.create({ + data: log, + props: { + isRoot: true, + }, }); + + await UserNotificationRuleService.startUserNotificationRulesExecution( + userId, + { + userNotificationEventType: options.userNotificationEventType!, + triggeredByIncidentId: options.triggeredByIncidentId || undefined, + onCallPolicyExecutionLogId: options.onCallPolicyExecutionLogId, + onCallPolicyId: options.onCallPolicyId, + onCallPolicyEscalationRuleId: ruleId, + userBelongsToTeamId: teamId || undefined, + onCallDutyPolicyExecutionLogTimelineId: log.id!, + projectId: options.projectId, + onCallScheduleId: scheduleId || undefined, + }, + ); + }; + + const uniqueUserIds: Array<ObjectID> = []; + + for (const teamInRule of teamsInRule) { + const usersInTeam: Array<User> = await TeamMemberService.getUsersInTeam( + teamInRule.teamId!, + ); + + for (const user of usersInTeam) { + if ( + !uniqueUserIds.find((userId: ObjectID) => { + return user.id?.toString() === userId.toString(); + }) + ) { + uniqueUserIds.push(user.id!); + await startUserNotificationRuleExecution( + user.id!, + teamInRule.teamId!, + null, + ); + } else { + // no users in this rule. Skipping. + const log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); + log.statusMessage = + "Skipped because notification sent to this user already."; + log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; + log.alertSentToUserId = user.id!; + log.userBelongsToTeamId = teamInRule.teamId!; + + await OnCallDutyPolicyExecutionLogTimelineService.create({ + data: log, + props: { + isRoot: true, + }, + }); + } + } } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if ( - IsBillingEnabled && - createBy.props.currentPlan === PlanSelect.Free - ) { - // then check no of policies and if it is more than one, return error - const count: PositiveNumber = await this.countBy({ - query: { - projectId: createBy.data.projectId!, - onCallDutyPolicyId: - createBy.data.onCallDutyPolicyId! || - createBy.data.onCallDutyPolicy?._id, - }, - props: { - isRoot: true, - }, - }); + for (const userRule of usersInRule) { + if ( + !uniqueUserIds.find((userId: ObjectID) => { + return userRule.userId?.toString() === userId.toString(); + }) + ) { + uniqueUserIds.push(userRule.userId!); + await startUserNotificationRuleExecution(userRule.userId!, null, null); + } else { + // no users in this rule. Skipping. + const log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); + log.statusMessage = + "Skipped because notification sent to this user already."; + log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; + log.alertSentToUserId = userRule.userId!; - if (count.toNumber() >= 1) { - throw new BadDataException( - 'You can only create one escalation rule in free plan.' - ); - } - } + await OnCallDutyPolicyExecutionLogTimelineService.create({ + data: log, + props: { + isRoot: true, + }, + }); + } + } - if (!createBy.data.onCallDutyPolicyId) { - throw new BadDataException( - 'Status Page Resource onCallDutyPolicyId is required' - ); - } + for (const scheduleRule of schedulesInRule) { + // get layers and users in this schedule and find a user to notify. - if (!createBy.data.order) { - const query: Query<Model> = { - onCallDutyPolicyId: createBy.data.onCallDutyPolicyId, - }; - - const count: PositiveNumber = await this.countBy({ - query: query, - props: { - isRoot: true, - }, - }); - - createBy.data.order = count.toNumber() + 1; - } - - await this.rearrangeOrder( - createBy.data.order, - createBy.data.onCallDutyPolicyId, - true + const userIdInSchedule: ObjectID | null = + await OnCallDutyPolicyScheduleService.getCurrentUserIdInSchedule( + scheduleRule.onCallDutyPolicyScheduleId!, ); - return { - createBy: createBy, - carryForward: null, - }; - } + if (!userIdInSchedule) { + // no user active in this schedule. Skipping. - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting status page resource. Please try the delete with objectId' - ); - } + const log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); - let resource: Model | null = null; + log.statusMessage = + "Skipped because no active users are found in this schedule."; - if (!deleteBy.props.isRoot) { - resource = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - onCallDutyPolicyId: true, - }, - }); - } + log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; - return { - deleteBy, - carryForward: resource, - }; - } + log.onCallDutyScheduleId = scheduleRule.onCallDutyPolicyScheduleId!; - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const resource: Model | null = onDelete.carryForward; - - if (!deleteBy.props.isRoot && resource) { - if (resource && resource.order && resource.onCallDutyPolicyId) { - await this.rearrangeOrder( - resource.order, - resource.onCallDutyPolicyId, - - false - ); - } - } - - return { - deleteBy: deleteBy, - carryForward: null, - }; - } - - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if ( - updateBy.data.order && - !updateBy.props.isRoot && - updateBy.query._id - ) { - const resource: Model | null = await this.findOneBy({ - query: { - _id: updateBy.query._id!, - }, - props: { - isRoot: true, - }, - select: { - order: true, - onCallDutyPolicyId: true, - - _id: true, - }, - }); - - const currentOrder: number = resource?.order as number; - const newOrder: number = updateBy.data.order as number; - - const resources: Array<Model> = await this.findBy({ - query: { - onCallDutyPolicyId: - resource?.onCallDutyPolicyId as ObjectID, - }, - - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - order: true, - onCallDutyPolicyId: true, - - _id: true, - }, - }); - - if (currentOrder > newOrder) { - // moving up. - - for (const resource of resources) { - if ( - resource.order! >= newOrder && - resource.order! < currentOrder - ) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! + 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - - if (newOrder > currentOrder) { - // moving down. - - for (const resource of resources) { - if (resource.order! <= newOrder) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! - 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { updateBy, carryForward: null }; - } - - private async rearrangeOrder( - currentOrder: number, - onCallDutyPolicyId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get status page resource with this order. - const resources: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - onCallDutyPolicyId: onCallDutyPolicyId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, + await OnCallDutyPolicyExecutionLogTimelineService.create({ + data: log, + props: { + isRoot: true, + }, }); - let newOrder: number = currentOrder; + continue; + } + + if ( + !uniqueUserIds.find((userId: ObjectID) => { + return userIdInSchedule?.toString() === userId.toString(); + }) + ) { + uniqueUserIds.push(userIdInSchedule); + await startUserNotificationRuleExecution( + userIdInSchedule, + null, + scheduleRule.onCallDutyPolicyScheduleId!, + ); + } else { + // no users in this rule. Skipping. + const log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); + log.statusMessage = + "Skipped because notification sent to this user already."; + log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; + log.alertSentToUserId = userIdInSchedule; + log.onCallDutyScheduleId = scheduleRule.onCallDutyPolicyScheduleId!; + + await OnCallDutyPolicyExecutionLogTimelineService.create({ + data: log, + props: { + isRoot: true, + }, + }); + } + } + + if (uniqueUserIds.length === 0) { + // no users in this rule. Skipping. + const log: OnCallDutyPolicyExecutionLogTimeline = getNewLog(); + log.statusMessage = "Skipped because no users in this rule."; + log.status = OnCallDutyExecutionLogTimelineStatus.Skipped; + + await OnCallDutyPolicyExecutionLogTimelineService.create({ + data: log, + props: { + isRoot: true, + }, + }); + } + } + + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onCreateSuccess( + onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + if (!createdItem.projectId) { + throw new BadDataException("projectId is required"); + } + + if (!createdItem.id) { + throw new BadDataException("id is required"); + } + + // add people in escalation rule. + + if ( + onCreate.createBy.miscDataProps && + (onCreate.createBy.miscDataProps["teams"] || + onCreate.createBy.miscDataProps["users"] || + onCreate.createBy.miscDataProps["onCallSchedules"]) + ) { + await this.addUsersTeamsAndSchedules( + createdItem.projectId, + createdItem.id, + createdItem.onCallDutyPolicyId!, + (onCreate.createBy.miscDataProps["users"] as Array<ObjectID>) || [], + (onCreate.createBy.miscDataProps["teams"] as Array<ObjectID>) || [], + (onCreate.createBy.miscDataProps[ + "onCallSchedules" + ] as Array<ObjectID>) || [], + onCreate.createBy.props, + ); + } + + return createdItem; + } + + public async addUsersTeamsAndSchedules( + projectId: ObjectID, + escalationRuleId: ObjectID, + onCallDutyPolicyId: ObjectID, + usersIds: Array<ObjectID>, + teamIds: Array<ObjectID>, + onCallScheduleIds: Array<ObjectID>, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + for (const userId of usersIds) { + await this.addUser( + projectId, + escalationRuleId, + onCallDutyPolicyId, + userId, + props, + ); + } + + for (const teamId of teamIds) { + await this.addTeam( + projectId, + escalationRuleId, + onCallDutyPolicyId, + teamId, + props, + ); + } + + for (const scheduleId of onCallScheduleIds) { + await this.addOnCallSchedules( + projectId, + escalationRuleId, + onCallDutyPolicyId, + scheduleId, + props, + ); + } + } + + public async addTeam( + projectId: ObjectID, + escalationRuleId: ObjectID, + onCallDutyPolicyId: ObjectID, + teamId: ObjectID, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + const teamInRule: OnCallDutyPolicyEscalationRuleTeam = + new OnCallDutyPolicyEscalationRuleTeam(); + teamInRule.projectId = projectId; + teamInRule.onCallDutyPolicyId = onCallDutyPolicyId; + teamInRule.onCallDutyPolicyEscalationRuleId = escalationRuleId; + teamInRule.teamId = teamId; + + await OnCallDutyPolicyEscalationRuleTeamService.create({ + data: teamInRule, + props, + }); + } + + public async addOnCallSchedules( + projectId: ObjectID, + escalationRuleId: ObjectID, + onCallDutyPolicyId: ObjectID, + onCallScheduleId: ObjectID, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + const scheduleInRule: OnCallDutyPolicyEscalationRuleSchedule = + new OnCallDutyPolicyEscalationRuleSchedule(); + scheduleInRule.projectId = projectId; + scheduleInRule.onCallDutyPolicyId = onCallDutyPolicyId; + scheduleInRule.onCallDutyPolicyEscalationRuleId = escalationRuleId; + scheduleInRule.onCallDutyPolicyScheduleId = onCallScheduleId; + + await OnCallDutyPolicyEscalationRuleScheduleService.create({ + data: scheduleInRule, + props, + }); + } + + public async addUser( + projectId: ObjectID, + escalationRuleId: ObjectID, + onCallDutyPolicyId: ObjectID, + userId: ObjectID, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + const userInRule: OnCallDutyPolicyEscalationRuleUser = + new OnCallDutyPolicyEscalationRuleUser(); + userInRule.projectId = projectId; + userInRule.onCallDutyPolicyId = onCallDutyPolicyId; + userInRule.onCallDutyPolicyEscalationRuleId = escalationRuleId; + userInRule.userId = userId; + + await OnCallDutyPolicyEscalationRuleUserService.create({ + data: userInRule, + props, + }); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (IsBillingEnabled && createBy.props.currentPlan === PlanSelect.Free) { + // then check no of policies and if it is more than one, return error + const count: PositiveNumber = await this.countBy({ + query: { + projectId: createBy.data.projectId!, + onCallDutyPolicyId: + createBy.data.onCallDutyPolicyId! || + createBy.data.onCallDutyPolicy?._id, + }, + props: { + isRoot: true, + }, + }); + + if (count.toNumber() >= 1) { + throw new BadDataException( + "You can only create one escalation rule in free plan.", + ); + } + } + + if (!createBy.data.onCallDutyPolicyId) { + throw new BadDataException( + "Status Page Resource onCallDutyPolicyId is required", + ); + } + + if (!createBy.data.order) { + const query: Query<Model> = { + onCallDutyPolicyId: createBy.data.onCallDutyPolicyId, + }; + + const count: PositiveNumber = await this.countBy({ + query: query, + props: { + isRoot: true, + }, + }); + + createBy.data.order = count.toNumber() + 1; + } + + await this.rearrangeOrder( + createBy.data.order, + createBy.data.onCallDutyPolicyId, + true, + ); + + return { + createBy: createBy, + carryForward: null, + }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting status page resource. Please try the delete with objectId", + ); + } + + let resource: Model | null = null; + + if (!deleteBy.props.isRoot) { + resource = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + onCallDutyPolicyId: true, + }, + }); + } + + return { + deleteBy, + carryForward: resource, + }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const resource: Model | null = onDelete.carryForward; + + if (!deleteBy.props.isRoot && resource) { + if (resource && resource.order && resource.onCallDutyPolicyId) { + await this.rearrangeOrder( + resource.order, + resource.onCallDutyPolicyId, + + false, + ); + } + } + + return { + deleteBy: deleteBy, + carryForward: null, + }; + } + + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.order && !updateBy.props.isRoot && updateBy.query._id) { + const resource: Model | null = await this.findOneBy({ + query: { + _id: updateBy.query._id!, + }, + props: { + isRoot: true, + }, + select: { + order: true, + onCallDutyPolicyId: true, + + _id: true, + }, + }); + + const currentOrder: number = resource?.order as number; + const newOrder: number = updateBy.data.order as number; + + const resources: Array<Model> = await this.findBy({ + query: { + onCallDutyPolicyId: resource?.onCallDutyPolicyId as ObjectID, + }, + + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + order: true, + onCallDutyPolicyId: true, + + _id: true, + }, + }); + + if (currentOrder > newOrder) { + // moving up. for (const resource of resources) { - if (increaseOrder) { - newOrder = resource.order! + 1; - } else { - newOrder = resource.order! - 1; - } - + if (resource.order! >= newOrder && resource.order! < currentOrder) { + // increment order. await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, + query: { + _id: resource._id!, + }, + data: { + order: resource.order! + 1, + }, + props: { + isRoot: true, + }, }); + } } + } + + if (newOrder > currentOrder) { + // moving down. + + for (const resource of resources) { + if (resource.order! <= newOrder) { + // increment order. + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: resource.order! - 1, + }, + props: { + isRoot: true, + }, + }); + } + } + } } + + return { updateBy, carryForward: null }; + } + + private async rearrangeOrder( + currentOrder: number, + onCallDutyPolicyId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get status page resource with this order. + const resources: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + onCallDutyPolicyId: onCallDutyPolicyId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); + + let newOrder: number = currentOrder; + + for (const resource of resources) { + if (increaseOrder) { + newOrder = resource.order! + 1; + } else { + newOrder = resource.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); + } + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyEscalationRuleTeamService.ts b/CommonServer/Services/OnCallDutyPolicyEscalationRuleTeamService.ts index 8dd2274143..72db286450 100644 --- a/CommonServer/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +++ b/CommonServer/Services/OnCallDutyPolicyEscalationRuleTeamService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/OnCallDutyPolicyEscalationRuleTeam'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/OnCallDutyPolicyEscalationRuleTeam"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyEscalationRuleUserService.ts b/CommonServer/Services/OnCallDutyPolicyEscalationRuleUserService.ts index 848e0b69ef..3f422e7cf5 100644 --- a/CommonServer/Services/OnCallDutyPolicyEscalationRuleUserService.ts +++ b/CommonServer/Services/OnCallDutyPolicyEscalationRuleUserService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/OnCallDutyPolicyEscalationRuleUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/OnCallDutyPolicyEscalationRuleUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyExecutionLogService.ts b/CommonServer/Services/OnCallDutyPolicyExecutionLogService.ts index 698a51f4d9..45612a2688 100644 --- a/CommonServer/Services/OnCallDutyPolicyExecutionLogService.ts +++ b/CommonServer/Services/OnCallDutyPolicyExecutionLogService.ts @@ -1,100 +1,99 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import OnCallDutyPolicyEscalationRuleService from './OnCallDutyPolicyEscalationRuleService'; -import OnCallDutyPolicyStatus from 'Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import OnCallDutyPolicyEscalationRule from 'Model/Models/OnCallDutyPolicyEscalationRule'; -import Model from 'Model/Models/OnCallDutyPolicyExecutionLog'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import OnCallDutyPolicyEscalationRuleService from "./OnCallDutyPolicyEscalationRuleService"; +import OnCallDutyPolicyStatus from "Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import OnCallDutyPolicyEscalationRule from "Model/Models/OnCallDutyPolicyEscalationRule"; +import Model from "Model/Models/OnCallDutyPolicyExecutionLog"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 30); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 30); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.status) { + createBy.data.status = OnCallDutyPolicyStatus.Scheduled; } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.status) { - createBy.data.status = OnCallDutyPolicyStatus.Scheduled; - } + createBy.data.onCallPolicyExecutionRepeatCount = 1; - createBy.data.onCallPolicyExecutionRepeatCount = 1; + return { createBy, carryForward: null }; + } - return { createBy, carryForward: null }; + protected override async onCreateSuccess( + _onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + // get execution rules in this policy adn execute the first rule. + const executionRule: OnCallDutyPolicyEscalationRule | null = + await OnCallDutyPolicyEscalationRuleService.findOneBy({ + query: { + projectId: createdItem.projectId!, + onCallDutyPolicyId: createdItem.onCallDutyPolicyId!, + order: 1, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + if (executionRule) { + await this.updateOneById({ + id: createdItem.id!, + data: { + status: OnCallDutyPolicyStatus.Started, + statusMessage: "Execution started...", + }, + props: { + isRoot: true, + }, + }); + + await OnCallDutyPolicyEscalationRuleService.startRuleExecution( + executionRule.id!, + { + projectId: createdItem.projectId!, + triggeredByIncidentId: createdItem.triggeredByIncidentId, + userNotificationEventType: UserNotificationEventType.IncidentCreated, + onCallPolicyExecutionLogId: createdItem.id!, + onCallPolicyId: createdItem.onCallDutyPolicyId!, + }, + ); + + await this.updateOneById({ + id: createdItem.id!, + data: { + status: OnCallDutyPolicyStatus.Executing, + statusMessage: "First escalation rule executed....", + }, + props: { + isRoot: true, + }, + }); + } else { + await this.updateOneById({ + id: createdItem.id!, + data: { + status: OnCallDutyPolicyStatus.Error, + statusMessage: + "No Escalation Rules in Policy. Please add escalation rules to this policy.", + }, + props: { + isRoot: true, + }, + }); } - protected override async onCreateSuccess( - _onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - // get execution rules in this policy adn execute the first rule. - const executionRule: OnCallDutyPolicyEscalationRule | null = - await OnCallDutyPolicyEscalationRuleService.findOneBy({ - query: { - projectId: createdItem.projectId!, - onCallDutyPolicyId: createdItem.onCallDutyPolicyId!, - order: 1, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - if (executionRule) { - await this.updateOneById({ - id: createdItem.id!, - data: { - status: OnCallDutyPolicyStatus.Started, - statusMessage: 'Execution started...', - }, - props: { - isRoot: true, - }, - }); - - await OnCallDutyPolicyEscalationRuleService.startRuleExecution( - executionRule.id!, - { - projectId: createdItem.projectId!, - triggeredByIncidentId: createdItem.triggeredByIncidentId, - userNotificationEventType: - UserNotificationEventType.IncidentCreated, - onCallPolicyExecutionLogId: createdItem.id!, - onCallPolicyId: createdItem.onCallDutyPolicyId!, - } - ); - - await this.updateOneById({ - id: createdItem.id!, - data: { - status: OnCallDutyPolicyStatus.Executing, - statusMessage: 'First escalation rule executed....', - }, - props: { - isRoot: true, - }, - }); - } else { - await this.updateOneById({ - id: createdItem.id!, - data: { - status: OnCallDutyPolicyStatus.Error, - statusMessage: - 'No Escalation Rules in Policy. Please add escalation rules to this policy.', - }, - props: { - isRoot: true, - }, - }); - } - - return createdItem; - } + return createdItem; + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyExecutionLogTimelineService.ts b/CommonServer/Services/OnCallDutyPolicyExecutionLogTimelineService.ts index dd6e64f4b9..96822c5c48 100644 --- a/CommonServer/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +++ b/CommonServer/Services/OnCallDutyPolicyExecutionLogTimelineService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/OnCallDutyPolicyExecutionLogTimeline'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/OnCallDutyPolicyExecutionLogTimeline"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyScheduleLayerService.ts b/CommonServer/Services/OnCallDutyPolicyScheduleLayerService.ts index 3faf161a00..0c296513a6 100644 --- a/CommonServer/Services/OnCallDutyPolicyScheduleLayerService.ts +++ b/CommonServer/Services/OnCallDutyPolicyScheduleLayerService.ts @@ -1,132 +1,125 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Model from 'Model/Models/OnCallDutyPolicyScheduleLayer'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Model from "Model/Models/OnCallDutyPolicyScheduleLayer"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.onCallDutyPolicyScheduleId) { + throw new BadDataException("onCallDutyPolicyScheduleId is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.onCallDutyPolicyScheduleId) { - throw new BadDataException( - 'onCallDutyPolicyScheduleId is required' - ); - } + if (!createBy.data.order) { + // count number of users in this layer. - if (!createBy.data.order) { - // count number of users in this layer. + const count: PositiveNumber = await this.countBy({ + query: { + onCallDutyPolicyScheduleId: createBy.data.onCallDutyPolicyScheduleId!, + }, + props: { + isRoot: true, + }, + }); - const count: PositiveNumber = await this.countBy({ - query: { - onCallDutyPolicyScheduleId: - createBy.data.onCallDutyPolicyScheduleId!, - }, - props: { - isRoot: true, - }, - }); + createBy.data.order = count.toNumber() + 1; + } - createBy.data.order = count.toNumber() + 1; - } + await this.rearrangeOrder( + createBy.data.order, + createBy.data.onCallDutyPolicyScheduleId!, + true, + ); + return { + createBy, + carryForward: null, + }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const resource: Model | null = onDelete.carryForward; + + if (!deleteBy.props.isRoot && resource) { + if (resource && resource.order && resource.onCallDutyPolicyScheduleId) { await this.rearrangeOrder( - createBy.data.order, - createBy.data.onCallDutyPolicyScheduleId!, - true + resource.order, + resource.onCallDutyPolicyScheduleId, + false, ); - - return { - createBy, - carryForward: null, - }; + } } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const resource: Model | null = onDelete.carryForward; + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - if (!deleteBy.props.isRoot && resource) { - if ( - resource && - resource.order && - resource.onCallDutyPolicyScheduleId - ) { - await this.rearrangeOrder( - resource.order, - resource.onCallDutyPolicyScheduleId, - false - ); - } - } + private async rearrangeOrder( + currentOrder: number, + onCallDutyPolicyScheduleId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get status page resource with this order. + const resources: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + onCallDutyPolicyScheduleId: onCallDutyPolicyScheduleId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); - return { - deleteBy: deleteBy, - carryForward: null, - }; - } - - private async rearrangeOrder( - currentOrder: number, - onCallDutyPolicyScheduleId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get status page resource with this order. - const resources: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - onCallDutyPolicyScheduleId: onCallDutyPolicyScheduleId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); - - let newOrder: number = currentOrder; - - for (const resource of resources) { - if (increaseOrder) { - newOrder = resource.order! + 1; - } else { - newOrder = resource.order! - 1; - } - - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, - }); - } + let newOrder: number = currentOrder; + + for (const resource of resources) { + if (increaseOrder) { + newOrder = resource.order! + 1; + } else { + newOrder = resource.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); } + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyScheduleLayerUserService.ts b/CommonServer/Services/OnCallDutyPolicyScheduleLayerUserService.ts index e8c8f1e289..5df9698186 100644 --- a/CommonServer/Services/OnCallDutyPolicyScheduleLayerUserService.ts +++ b/CommonServer/Services/OnCallDutyPolicyScheduleLayerUserService.ts @@ -1,263 +1,253 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Model from 'Model/Models/OnCallDutyPolicyScheduleLayerUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Model from "Model/Models/OnCallDutyPolicyScheduleLayerUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.onCallDutyPolicyScheduleLayerId) { + throw new BadDataException("onCallDutyPolicyScheduleLayerId is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.onCallDutyPolicyScheduleLayerId) { - throw new BadDataException( - 'onCallDutyPolicyScheduleLayerId is required' - ); - } + const userId: ObjectID | undefined | null = + createBy.data.userId || createBy.data.user?.id; - const userId: ObjectID | undefined | null = - createBy.data.userId || createBy.data.user?.id; + if (!userId) { + throw new BadDataException("userId is required"); + } - if (!userId) { - throw new BadDataException('userId is required'); - } + if (!createBy.data.order) { + // count number of users in this layer. - if (!createBy.data.order) { - // count number of users in this layer. - - const count: PositiveNumber = await this.countBy({ - query: { - onCallDutyPolicyScheduleLayerId: - createBy.data.onCallDutyPolicyScheduleLayerId!, - }, - props: { - isRoot: true, - }, - }); - - createBy.data.order = count.toNumber() + 1; - } - - await this.rearrangeOrder( - createBy.data.order, + const count: PositiveNumber = await this.countBy({ + query: { + onCallDutyPolicyScheduleLayerId: createBy.data.onCallDutyPolicyScheduleLayerId!, - true + }, + props: { + isRoot: true, + }, + }); + + createBy.data.order = count.toNumber() + 1; + } + + await this.rearrangeOrder( + createBy.data.order, + createBy.data.onCallDutyPolicyScheduleLayerId!, + true, + ); + + return { + createBy, + carryForward: null, + }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const resource: Model | null = onDelete.carryForward; + + if (!deleteBy.props.isRoot && resource) { + if ( + resource && + resource.order && + resource.onCallDutyPolicyScheduleLayerId + ) { + await this.rearrangeOrder( + resource.order, + resource.onCallDutyPolicyScheduleLayerId, + false, ); - - return { - createBy, - carryForward: null, - }; + } } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const resource: Model | null = onDelete.carryForward; + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - if (!deleteBy.props.isRoot && resource) { - if ( - resource && - resource.order && - resource.onCallDutyPolicyScheduleLayerId - ) { - await this.rearrangeOrder( - resource.order, - resource.onCallDutyPolicyScheduleLayerId, - false - ); - } - } - - return { - deleteBy: deleteBy, - carryForward: null, - }; + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting status page resource. Please try the delete with objectId", + ); } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting status page resource. Please try the delete with objectId' - ); - } + let resource: Model | null = null; - let resource: Model | null = null; - - if (!deleteBy.props.isRoot) { - resource = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - onCallDutyPolicyScheduleLayerId: true, - }, - }); - } - - return { - deleteBy, - carryForward: resource, - }; + if (!deleteBy.props.isRoot) { + resource = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + onCallDutyPolicyScheduleLayerId: true, + }, + }); } - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if ( - updateBy.data.order && - !updateBy.props.isRoot && - updateBy.query._id - ) { - const resource: Model | null = await this.findOneBy({ - query: { - _id: updateBy.query._id!, - }, - props: { - isRoot: true, - }, - select: { - order: true, - onCallDutyPolicyScheduleLayerId: true, - _id: true, - }, - }); + return { + deleteBy, + carryForward: resource, + }; + } - const currentOrder: number = resource?.order as number; - const newOrder: number = updateBy.data.order as number; + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.order && !updateBy.props.isRoot && updateBy.query._id) { + const resource: Model | null = await this.findOneBy({ + query: { + _id: updateBy.query._id!, + }, + props: { + isRoot: true, + }, + select: { + order: true, + onCallDutyPolicyScheduleLayerId: true, + _id: true, + }, + }); - const resources: Array<Model> = await this.findBy({ - query: { - onCallDutyPolicyScheduleLayerId: - resource?.onCallDutyPolicyScheduleLayerId as ObjectID, - }, + const currentOrder: number = resource?.order as number; + const newOrder: number = updateBy.data.order as number; - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - order: true, - onCallDutyPolicyScheduleLayerId: true, - _id: true, - }, - }); + const resources: Array<Model> = await this.findBy({ + query: { + onCallDutyPolicyScheduleLayerId: + resource?.onCallDutyPolicyScheduleLayerId as ObjectID, + }, - if (currentOrder > newOrder) { - // moving up. + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + order: true, + onCallDutyPolicyScheduleLayerId: true, + _id: true, + }, + }); - for (const resource of resources) { - if ( - resource.order! >= newOrder && - resource.order! < currentOrder - ) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! + 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - - if (newOrder > currentOrder) { - // moving down. - - for (const resource of resources) { - if (resource.order! <= newOrder) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! - 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { updateBy, carryForward: null }; - } - - private async rearrangeOrder( - currentOrder: number, - onCallDutyPolicyScheduleLayerId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get status page resource with this order. - const resources: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - onCallDutyPolicyScheduleLayerId: - onCallDutyPolicyScheduleLayerId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); - - let newOrder: number = currentOrder; + if (currentOrder > newOrder) { + // moving up. for (const resource of resources) { - if (increaseOrder) { - newOrder = resource.order! + 1; - } else { - newOrder = resource.order! - 1; - } - + if (resource.order! >= newOrder && resource.order! < currentOrder) { + // increment order. await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, + query: { + _id: resource._id!, + }, + data: { + order: resource.order! + 1, + }, + props: { + isRoot: true, + }, }); + } } + } + + if (newOrder > currentOrder) { + // moving down. + + for (const resource of resources) { + if (resource.order! <= newOrder) { + // increment order. + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: resource.order! - 1, + }, + props: { + isRoot: true, + }, + }); + } + } + } } + + return { updateBy, carryForward: null }; + } + + private async rearrangeOrder( + currentOrder: number, + onCallDutyPolicyScheduleLayerId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get status page resource with this order. + const resources: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + onCallDutyPolicyScheduleLayerId: onCallDutyPolicyScheduleLayerId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); + + let newOrder: number = currentOrder; + + for (const resource of resources) { + if (increaseOrder) { + newOrder = resource.order! + 1; + } else { + newOrder = resource.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); + } + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyScheduleService.ts b/CommonServer/Services/OnCallDutyPolicyScheduleService.ts index d63ea5afa3..a7495ac165 100644 --- a/CommonServer/Services/OnCallDutyPolicyScheduleService.ts +++ b/CommonServer/Services/OnCallDutyPolicyScheduleService.ts @@ -1,125 +1,123 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import OnCallDutyPolicyScheduleLayerService from './OnCallDutyPolicyScheduleLayerService'; -import OnCallDutyPolicyScheduleLayerUserService from './OnCallDutyPolicyScheduleLayerUserService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import CalendarEvent from 'Common/Types/Calendar/CalendarEvent'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import ObjectID from 'Common/Types/ObjectID'; -import LayerUtil, { LayerProps } from 'Common/Types/OnCallDutyPolicy/Layer'; -import Model from 'Model/Models/OnCallDutyPolicySchedule'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import OnCallDutyPolicyScheduleLayerUser from 'Model/Models/OnCallDutyPolicyScheduleLayerUser'; -import User from 'Model/Models/User'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import OnCallDutyPolicyScheduleLayerService from "./OnCallDutyPolicyScheduleLayerService"; +import OnCallDutyPolicyScheduleLayerUserService from "./OnCallDutyPolicyScheduleLayerUserService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import CalendarEvent from "Common/Types/Calendar/CalendarEvent"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import ObjectID from "Common/Types/ObjectID"; +import LayerUtil, { LayerProps } from "Common/Types/OnCallDutyPolicy/Layer"; +import Model from "Model/Models/OnCallDutyPolicySchedule"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import OnCallDutyPolicyScheduleLayerUser from "Model/Models/OnCallDutyPolicyScheduleLayerUser"; +import User from "Model/Models/User"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + public async getCurrentUserIdInSchedule( + scheduleId: ObjectID, + ): Promise<ObjectID | null> { + // get schedule layers. + + const layers: Array<OnCallDutyPolicyScheduleLayer> = + await OnCallDutyPolicyScheduleLayerService.findBy({ + query: { + onCallDutyPolicyScheduleId: scheduleId, + }, + select: { + order: true, + name: true, + description: true, + startsAt: true, + restrictionTimes: true, + rotation: true, + onCallDutyPolicyScheduleId: true, + projectId: true, + handOffTime: true, + }, + sort: { + order: SortOrder.Ascending, + }, + props: { + isRoot: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + }); + + const layerUsers: Array<OnCallDutyPolicyScheduleLayerUser> = + await OnCallDutyPolicyScheduleLayerUserService.findBy({ + query: { + onCallDutyPolicyScheduleId: scheduleId, + }, + select: { + user: true, + order: true, + onCallDutyPolicyScheduleLayerId: true, + }, + sort: { + order: SortOrder.Ascending, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + const currentStartTime: Date = OneUptimeDate.getCurrentDate(); + const currentEndTime: Date = OneUptimeDate.addRemoveSeconds( + currentStartTime, + 1, + ); + + const layerProps: Array<LayerProps> = []; + + for (const layer of layers) { + layerProps.push({ + users: + layerUsers + .filter((layerUser: OnCallDutyPolicyScheduleLayerUser) => { + return ( + layerUser.onCallDutyPolicyScheduleLayerId?.toString() === + layer.id?.toString() + ); + }) + .map((layerUser: OnCallDutyPolicyScheduleLayerUser) => { + return layerUser.user!; + }) + .filter((user: User) => { + return Boolean(user); + }) || [], + startDateTimeOfLayer: layer.startsAt!, + restrictionTimes: layer.restrictionTimes!, + rotation: layer.rotation!, + handOffTime: layer.handOffTime!, + }); } - public async getCurrentUserIdInSchedule( - scheduleId: ObjectID - ): Promise<ObjectID | null> { - // get schedule layers. + const events: Array<CalendarEvent> = LayerUtil.getMultiLayerEvents({ + layers: layerProps, + calendarStartDate: currentStartTime, + calendarEndDate: currentEndTime, + }); - const layers: Array<OnCallDutyPolicyScheduleLayer> = - await OnCallDutyPolicyScheduleLayerService.findBy({ - query: { - onCallDutyPolicyScheduleId: scheduleId, - }, - select: { - order: true, - name: true, - description: true, - startsAt: true, - restrictionTimes: true, - rotation: true, - onCallDutyPolicyScheduleId: true, - projectId: true, - handOffTime: true, - }, - sort: { - order: SortOrder.Ascending, - }, - props: { - isRoot: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - }); - - const layerUsers: Array<OnCallDutyPolicyScheduleLayerUser> = - await OnCallDutyPolicyScheduleLayerUserService.findBy({ - query: { - onCallDutyPolicyScheduleId: scheduleId, - }, - select: { - user: true, - order: true, - onCallDutyPolicyScheduleLayerId: true, - }, - sort: { - order: SortOrder.Ascending, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - const currentStartTime: Date = OneUptimeDate.getCurrentDate(); - const currentEndTime: Date = OneUptimeDate.addRemoveSeconds( - currentStartTime, - 1 - ); - - const layerProps: Array<LayerProps> = []; - - for (const layer of layers) { - layerProps.push({ - users: - layerUsers - .filter( - (layerUser: OnCallDutyPolicyScheduleLayerUser) => { - return ( - layerUser.onCallDutyPolicyScheduleLayerId?.toString() === - layer.id?.toString() - ); - } - ) - .map((layerUser: OnCallDutyPolicyScheduleLayerUser) => { - return layerUser.user!; - }) - .filter((user: User) => { - return Boolean(user); - }) || [], - startDateTimeOfLayer: layer.startsAt!, - restrictionTimes: layer.restrictionTimes!, - rotation: layer.rotation!, - handOffTime: layer.handOffTime!, - }); - } - - const events: Array<CalendarEvent> = LayerUtil.getMultiLayerEvents({ - layers: layerProps, - calendarStartDate: currentStartTime, - calendarEndDate: currentEndTime, - }); - - if (events.length === 0) { - return null; - } - - const userId: string | undefined = events[0]?.title; // this is user id in string. - - if (!userId) { - return null; - } - - return new ObjectID(userId); + if (events.length === 0) { + return null; } + + const userId: string | undefined = events[0]?.title; // this is user id in string. + + if (!userId) { + return null; + } + + return new ObjectID(userId); + } } export default new Service(); diff --git a/CommonServer/Services/OnCallDutyPolicyService.ts b/CommonServer/Services/OnCallDutyPolicyService.ts index bea2ee546d..a929b5f489 100644 --- a/CommonServer/Services/OnCallDutyPolicyService.ts +++ b/CommonServer/Services/OnCallDutyPolicyService.ts @@ -1,74 +1,74 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import OnCallDutyPolicyExecutionLogService from './OnCallDutyPolicyExecutionLogService'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import OnCallDutyPolicyStatus from 'Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import OnCallDutyPolicyExecutionLog from 'Model/Models/OnCallDutyPolicyExecutionLog'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import OnCallDutyPolicyExecutionLogService from "./OnCallDutyPolicyExecutionLogService"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import OnCallDutyPolicyStatus from "Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import OnCallDutyPolicyExecutionLog from "Model/Models/OnCallDutyPolicyExecutionLog"; export class Service extends DatabaseService<OnCallDutyPolicy> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(OnCallDutyPolicy, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(OnCallDutyPolicy, postgresDatabase); + } + + public async executePolicy( + policyId: ObjectID, + options: { + triggeredByIncidentId?: ObjectID | undefined; + userNotificationEventType: UserNotificationEventType; + }, + ): Promise<void> { + // execute this policy + + if ( + UserNotificationEventType.IncidentCreated === + options.userNotificationEventType && + !options.triggeredByIncidentId + ) { + throw new BadDataException( + "triggeredByIncidentId is required when userNotificationEventType is IncidentCreated", + ); } - public async executePolicy( - policyId: ObjectID, - options: { - triggeredByIncidentId?: ObjectID | undefined; - userNotificationEventType: UserNotificationEventType; - } - ): Promise<void> { - // execute this policy + const policy: OnCallDutyPolicy | null = await this.findOneById({ + id: policyId, + select: { + _id: true, + projectId: true, + }, + props: { + isRoot: true, + }, + }); - if ( - UserNotificationEventType.IncidentCreated === - options.userNotificationEventType && - !options.triggeredByIncidentId - ) { - throw new BadDataException( - 'triggeredByIncidentId is required when userNotificationEventType is IncidentCreated' - ); - } - - const policy: OnCallDutyPolicy | null = await this.findOneById({ - id: policyId, - select: { - _id: true, - projectId: true, - }, - props: { - isRoot: true, - }, - }); - - if (!policy) { - throw new BadDataException( - `On-Call Duty Policy with id ${policyId.toString()} not found` - ); - } - - // add policy log. - const log: OnCallDutyPolicyExecutionLog = - new OnCallDutyPolicyExecutionLog(); - - log.projectId = policy.projectId!; - log.onCallDutyPolicyId = policyId; - log.userNotificationEventType = options.userNotificationEventType; - log.statusMessage = 'Scheduled.'; - log.status = OnCallDutyPolicyStatus.Scheduled; - - if (options.triggeredByIncidentId) { - log.triggeredByIncidentId = options.triggeredByIncidentId; - } - - await OnCallDutyPolicyExecutionLogService.create({ - data: log, - props: { - isRoot: true, - }, - }); + if (!policy) { + throw new BadDataException( + `On-Call Duty Policy with id ${policyId.toString()} not found`, + ); } + + // add policy log. + const log: OnCallDutyPolicyExecutionLog = + new OnCallDutyPolicyExecutionLog(); + + log.projectId = policy.projectId!; + log.onCallDutyPolicyId = policyId; + log.userNotificationEventType = options.userNotificationEventType; + log.statusMessage = "Scheduled."; + log.status = OnCallDutyPolicyStatus.Scheduled; + + if (options.triggeredByIncidentId) { + log.triggeredByIncidentId = options.triggeredByIncidentId; + } + + await OnCallDutyPolicyExecutionLogService.create({ + data: log, + props: { + isRoot: true, + }, + }); + } } export default new Service(); diff --git a/CommonServer/Services/ProbeService.ts b/CommonServer/Services/ProbeService.ts index c0ee03f42d..216475a803 100644 --- a/CommonServer/Services/ProbeService.ts +++ b/CommonServer/Services/ProbeService.ts @@ -1,29 +1,29 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import ObjectID from 'Common/Types/ObjectID'; -import Version from 'Common/Types/Version'; -import Model from 'Model/Models/Probe'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import ObjectID from "Common/Types/ObjectID"; +import Version from "Common/Types/Version"; +import Model from "Model/Models/Probe"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.key) { + createBy.data.key = ObjectID.generate().toString(); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.key) { - createBy.data.key = ObjectID.generate().toString(); - } - - if (!createBy.data.probeVersion) { - createBy.data.probeVersion = new Version('1.0.0'); - } - - return { createBy: createBy, carryForward: [] }; + if (!createBy.data.probeVersion) { + createBy.data.probeVersion = new Version("1.0.0"); } + + return { createBy: createBy, carryForward: [] }; + } } export default new Service(); diff --git a/CommonServer/Services/ProjectCallSMSConfigService.ts b/CommonServer/Services/ProjectCallSMSConfigService.ts index 8a81f241ed..c0a961140e 100644 --- a/CommonServer/Services/ProjectCallSMSConfigService.ts +++ b/CommonServer/Services/ProjectCallSMSConfigService.ts @@ -1,50 +1,48 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Model from 'Model/Models/ProjectCallSMSConfig'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Model from "Model/Models/ProjectCallSMSConfig"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + public toTwilioConfig( + projectCallSmsConfig: Model | undefined, + ): TwilioConfig | undefined { + if (!projectCallSmsConfig) { + return undefined; } - public toTwilioConfig( - projectCallSmsConfig: Model | undefined - ): TwilioConfig | undefined { - if (!projectCallSmsConfig) { - return undefined; - } - - if (!projectCallSmsConfig.id) { - throw new BadDataException( - 'Project Call and SMS Config id is not set' - ); - } - - if (!projectCallSmsConfig.twilioAccountSID) { - throw new BadDataException( - 'Project Call and SMS Config twilio account SID is not set' - ); - } - - if (!projectCallSmsConfig.twilioPhoneNumber) { - throw new BadDataException( - 'Project Call and SMS Config twilio phone number is not set' - ); - } - - if (!projectCallSmsConfig.twilioAuthToken) { - throw new BadDataException( - 'Project Call and SMS Config twilio auth token is not set' - ); - } - - return { - accountSid: projectCallSmsConfig.twilioAccountSID.toString(), - authToken: projectCallSmsConfig.twilioAuthToken.toString(), - phoneNumber: projectCallSmsConfig.twilioPhoneNumber, - }; + if (!projectCallSmsConfig.id) { + throw new BadDataException("Project Call and SMS Config id is not set"); } + + if (!projectCallSmsConfig.twilioAccountSID) { + throw new BadDataException( + "Project Call and SMS Config twilio account SID is not set", + ); + } + + if (!projectCallSmsConfig.twilioPhoneNumber) { + throw new BadDataException( + "Project Call and SMS Config twilio phone number is not set", + ); + } + + if (!projectCallSmsConfig.twilioAuthToken) { + throw new BadDataException( + "Project Call and SMS Config twilio auth token is not set", + ); + } + + return { + accountSid: projectCallSmsConfig.twilioAccountSID.toString(), + authToken: projectCallSmsConfig.twilioAuthToken.toString(), + phoneNumber: projectCallSmsConfig.twilioPhoneNumber, + }; + } } export default new Service(); diff --git a/CommonServer/Services/ProjectService.ts b/CommonServer/Services/ProjectService.ts index 837b9b14f9..676dec64cf 100755 --- a/CommonServer/Services/ProjectService.ts +++ b/CommonServer/Services/ProjectService.ts @@ -1,1169 +1,1139 @@ -import { IsBillingEnabled, getAllEnvVars } from '../EnvironmentConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import AllMeteredPlans from '../Types/Billing/MeteredPlan/AllMeteredPlans'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import FindBy from '../Types/Database/FindBy'; -import { OnCreate, OnDelete, OnFind, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import logger from '../Utils/Logger'; -import AccessTokenService from './AccessTokenService'; -import BillingService from './BillingService'; -import DatabaseService from './DatabaseService'; -import IncidentSeverityService from './IncidentSeverityService'; -import IncidentStateService from './IncidentStateService'; -import MailService from './MailService'; -import MonitorStatusService from './MonitorStatusService'; -import NotificationService from './NotificationService'; -import PromoCodeService from './PromoCodeService'; -import ScheduledMaintenanceStateService from './ScheduledMaintenanceStateService'; -import TeamMemberService from './TeamMemberService'; -import TeamPermissionService from './TeamPermissionService'; -import TeamService from './TeamService'; -import UserNotificationRuleService from './UserNotificationRuleService'; -import UserNotificationSettingService from './UserNotificationSettingService'; -import UserService from './UserService'; +import { IsBillingEnabled, getAllEnvVars } from "../EnvironmentConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import AllMeteredPlans from "../Types/Billing/MeteredPlan/AllMeteredPlans"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import FindBy from "../Types/Database/FindBy"; +import { OnCreate, OnDelete, OnFind, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import logger from "../Utils/Logger"; +import AccessTokenService from "./AccessTokenService"; +import BillingService from "./BillingService"; +import DatabaseService from "./DatabaseService"; +import IncidentSeverityService from "./IncidentSeverityService"; +import IncidentStateService from "./IncidentStateService"; +import MailService from "./MailService"; +import MonitorStatusService from "./MonitorStatusService"; +import NotificationService from "./NotificationService"; +import PromoCodeService from "./PromoCodeService"; +import ScheduledMaintenanceStateService from "./ScheduledMaintenanceStateService"; +import TeamMemberService from "./TeamMemberService"; +import TeamPermissionService from "./TeamPermissionService"; +import TeamService from "./TeamService"; +import UserNotificationRuleService from "./UserNotificationRuleService"; +import UserNotificationSettingService from "./UserNotificationSettingService"; +import UserService from "./UserService"; import SubscriptionPlan, { - PlanSelect, -} from 'Common/Types/Billing/SubscriptionPlan'; -import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus'; -import { Black, Green, Moroon500, Red, Yellow } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Email from 'Common/Types/Email'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import IncidentState from 'Model/Models/IncidentState'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import Model from 'Model/Models/Project'; -import PromoCode from 'Model/Models/PromoCode'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import Team from 'Model/Models/Team'; -import TeamMember from 'Model/Models/TeamMember'; -import TeamPermission from 'Model/Models/TeamPermission'; -import User from 'Model/Models/User'; -import { In } from 'typeorm'; + PlanSelect, +} from "Common/Types/Billing/SubscriptionPlan"; +import SubscriptionStatus from "Common/Types/Billing/SubscriptionStatus"; +import { Black, Green, Moroon500, Red, Yellow } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Email from "Common/Types/Email"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import IncidentState from "Model/Models/IncidentState"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import Model from "Model/Models/Project"; +import PromoCode from "Model/Models/PromoCode"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import Team from "Model/Models/Team"; +import TeamMember from "Model/Models/TeamMember"; +import TeamPermission from "Model/Models/TeamPermission"; +import User from "Model/Models/User"; +import { In } from "typeorm"; export interface CurrentPlan { - plan: PlanSelect | null; - isSubscriptionUnpaid: boolean; + plan: PlanSelect | null; + isSubscriptionUnpaid: boolean; } export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + data: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!data.data.name) { + throw new BadDataException("Project name is required"); } - protected override async onBeforeCreate( - data: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!data.data.name) { - throw new BadDataException('Project name is required'); - } + if (data.props.userId) { + data.data.createdByUserId = data.props.userId; + } else { + throw new NotAuthorizedException( + "User should be logged in to create the project.", + ); + } - if (data.props.userId) { - data.data.createdByUserId = data.props.userId; - } else { - throw new NotAuthorizedException( - 'User should be logged in to create the project.' - ); - } + const user: User | null = await UserService.findOneById({ + id: data.props.userId, + select: { + name: true, + email: true, + companyPhoneNumber: true, + companyName: true, + utmCampaign: true, + utmSource: true, + utmMedium: true, + utmTerm: true, + utmContent: true, + utmUrl: true, + }, + props: { + isRoot: true, + }, + }); - const user: User | null = await UserService.findOneById({ - id: data.props.userId, - select: { - name: true, - email: true, - companyPhoneNumber: true, - companyName: true, - utmCampaign: true, - utmSource: true, - utmMedium: true, - utmTerm: true, - utmContent: true, - utmUrl: true, - }, - props: { - isRoot: true, + if (!user) { + throw new BadDataException("User not found."); + } + + if (IsBillingEnabled) { + if (!data.data.paymentProviderPlanId) { + throw new BadDataException("Plan required to create the project."); + } + + if ( + !SubscriptionPlan.isValidPlanId( + data.data.paymentProviderPlanId, + getAllEnvVars(), + ) + ) { + throw new BadDataException("Plan is invalid."); + } + + data.data.planName = SubscriptionPlan.getPlanSelect( + data.data.paymentProviderPlanId, + ); + + if (data.data.paymentProviderPromoCode) { + // check if it exists in promcode table. Not all promocodes are in the table, only reseller ones are. + // If they are not in the table, allow projetc creation to proceed. + // If they are in the project table, then see if anyn restrictions on reseller plan apply and if it does, + // apply those restictions to the project. + + const promoCode: PromoCode | null = await PromoCodeService.findOneBy({ + query: { + promoCodeId: data.data.paymentProviderPromoCode, + }, + select: { + isPromoCodeUsed: true, + userEmail: true, + resellerPlan: { + _id: true, + planType: true, + monitorLimit: true, + teamMemberLimit: true, }, + resellerId: true, + resellerLicenseId: true, + planType: true, + resellerPlanId: true, + }, + props: { + isRoot: true, + }, }); - if (!user) { - throw new BadDataException('User not found.'); - } - - if (IsBillingEnabled) { - if (!data.data.paymentProviderPlanId) { - throw new BadDataException( - 'Plan required to create the project.' - ); - } - - if ( - !SubscriptionPlan.isValidPlanId( - data.data.paymentProviderPlanId, - getAllEnvVars() - ) - ) { - throw new BadDataException('Plan is invalid.'); - } - - data.data.planName = SubscriptionPlan.getPlanSelect( - data.data.paymentProviderPlanId - ); - - if (data.data.paymentProviderPromoCode) { - // check if it exists in promcode table. Not all promocodes are in the table, only reseller ones are. - // If they are not in the table, allow projetc creation to proceed. - // If they are in the project table, then see if anyn restrictions on reseller plan apply and if it does, - // apply those restictions to the project. - - const promoCode: PromoCode | null = - await PromoCodeService.findOneBy({ - query: { - promoCodeId: data.data.paymentProviderPromoCode, - }, - select: { - isPromoCodeUsed: true, - userEmail: true, - resellerPlan: { - _id: true, - planType: true, - monitorLimit: true, - teamMemberLimit: true, - }, - resellerId: true, - resellerLicenseId: true, - planType: true, - resellerPlanId: true, - }, - props: { - isRoot: true, - }, - }); - - if (promoCode) { - // check if the same user is creating the project. - if ( - promoCode.userEmail?.toString() !== - user.email?.toString() - ) { - throw new BadDataException( - 'This promocode is assigned to a different user and cannot be used.' - ); - } - - if (promoCode.isPromoCodeUsed) { - throw new BadDataException( - 'This promocode has already been used.' - ); - } - - if (promoCode.resellerPlan?.monitorLimit) { - data.data.activeMonitorsLimit = - promoCode.resellerPlan?.monitorLimit; - } - - if (promoCode.resellerPlan?.teamMemberLimit) { - data.data.seatLimit = - promoCode.resellerPlan?.teamMemberLimit; - } - - if (promoCode.planType !== data.data.planName) { - throw new BadDataException( - 'Promocode is not valid for this plan. Please select the ' + - promoCode.planType + - ' plan.' - ); - } - - if (promoCode.resellerLicenseId) { - data.data.resellerLicenseId = - promoCode.resellerLicenseId; - } - - if (promoCode.resellerId) { - data.data.resellerId = promoCode.resellerId; - } - - if (promoCode.resellerPlanId) { - data.data.resellerPlanId = promoCode.resellerPlanId; - } - } - } - - if ( - data.data.paymentProviderPromoCode && - !(await BillingService.isPromoCodeValid( - data.data.paymentProviderPromoCode - )) - ) { - throw new BadDataException('Promo code is invalid.'); - } - - // check if promocode is valid. - } - - // check if the user has the project with the same name. If yes, reject. - - let existingProjectWithSameNameCount: number = 0; - if ( - data.props.userGlobalAccessPermission && - data.props.userGlobalAccessPermission?.projectIds.length > 0 - ) { - existingProjectWithSameNameCount = ( - await this.countBy({ - query: { - _id: QueryHelper.any( - data.props.userGlobalAccessPermission?.projectIds.map( - (item: ObjectID) => { - return item.toString(); - } - ) || [] - ), - name: QueryHelper.findWithSameText(data.data.name!), - }, - props: { - isRoot: true, - }, - }) - ).toNumber(); - } - - if (existingProjectWithSameNameCount > 0) { + if (promoCode) { + // check if the same user is creating the project. + if (promoCode.userEmail?.toString() !== user.email?.toString()) { throw new BadDataException( - 'Project with the same name already exists' + "This promocode is assigned to a different user and cannot be used.", ); - } - - data.data.createdOwnerName = user.name!; - data.data.createdOwnerEmail = user.email!; - data.data.createdOwnerPhone = user.companyPhoneNumber!; - data.data.createdOwnerCompanyName = user.companyName!; - - // UTM info. - data.data.utmCampaign = user.utmCampaign!; - data.data.utmSource = user.utmSource!; - data.data.utmMedium = user.utmMedium!; - data.data.utmTerm = user.utmTerm!; - data.data.utmContent = user.utmContent!; - data.data.utmUrl = user.utmUrl!; - - return Promise.resolve({ createBy: data, carryForward: null }); - } - - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if (IsBillingEnabled) { - if (updateBy.data.enableAutoRechargeSmsOrCallBalance) { - await NotificationService.rechargeIfBalanceIsLow( - new ObjectID(updateBy.query._id! as string), - { - autoRechargeSmsOrCallByBalanceInUSD: updateBy.data - .autoRechargeSmsOrCallByBalanceInUSD as number, - autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: - updateBy.data - .autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD as number, - enableAutoRechargeSmsOrCallBalance: updateBy.data - .enableAutoRechargeSmsOrCallBalance as boolean, - } - ); - } - - if (updateBy.data.paymentProviderPlanId) { - // payment provider id changed. - const project: Model | null = await this.findOneById({ - id: new ObjectID(updateBy.query._id! as string), - select: { - paymentProviderSubscriptionId: true, - paymentProviderMeteredSubscriptionId: true, - paymentProviderSubscriptionSeats: true, - paymentProviderPlanId: true, - trialEndsAt: true, - }, - props: { - isRoot: true, - }, - }); - - if (!project) { - throw new BadDataException('Project not found'); - } - - if ( - project.paymentProviderPlanId !== - updateBy.data.paymentProviderPlanId - ) { - logger.debug('Changing plan for project ' + project.id); - - const plan: SubscriptionPlan | undefined = - SubscriptionPlan.getSubscriptionPlanById( - updateBy.data.paymentProviderPlanId! as string, - getAllEnvVars() - ); - - if (!plan) { - throw new BadDataException('Invalid plan'); - } - - logger.debug( - 'Changing plan for project ' + - project.id?.toString() + - ' to ' + - plan.getName() - ); - - if (!project.paymentProviderSubscriptionSeats) { - project.paymentProviderSubscriptionSeats = - await TeamMemberService.getUniqueTeamMemberCountInProject( - project.id! - ); - } - - logger.debug( - 'Changing plan for project ' + - project.id?.toString() + - ' to ' + - plan.getName() + - ' with seats ' + - project.paymentProviderSubscriptionSeats - ); - - const subscription: { - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt?: Date | undefined; - } = await BillingService.changePlan({ - projectId: project.id!, - subscriptionId: - project.paymentProviderSubscriptionId as string, - meteredSubscriptionId: - project.paymentProviderMeteredSubscriptionId as string, - serverMeteredPlans: AllMeteredPlans, - newPlan: plan, - quantity: - project.paymentProviderSubscriptionSeats as number, - isYearly: - plan.getYearlyPlanId() === - updateBy.data.paymentProviderPlanId, - endTrialAt: project.trialEndsAt, - }); - - logger.debug( - 'Changing plan for project ' + - project.id?.toString() + - ' to ' + - plan.getName() + - ' with seats ' + - project.paymentProviderSubscriptionSeats + - ' completed.' - ); - - await this.updateOneById({ - id: new ObjectID(updateBy.query._id! as string), - data: { - paymentProviderSubscriptionId: - subscription.subscriptionId, - paymentProviderMeteredSubscriptionId: - subscription.meteredSubscriptionId, - trialEndsAt: subscription.trialEndsAt || new Date(), - planName: SubscriptionPlan.getPlanSelect( - updateBy.data.paymentProviderPlanId! as string - ), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - logger.debug( - 'Changing plan for project ' + - project.id?.toString() + - ' to ' + - plan.getName() + - ' with seats ' + - project.paymentProviderSubscriptionSeats + - ' completed and project updated.' - ); - } - } - } - - return { updateBy, carryForward: [] }; - } - - private async addDefaultScheduledMaintenanceState( - createdItem: Model - ): Promise<Model> { - let createdScheduledMaintenanceState: ScheduledMaintenanceState = - new ScheduledMaintenanceState(); - createdScheduledMaintenanceState.name = 'Scheduled'; - createdScheduledMaintenanceState.description = - 'When an event is scheduled, it belongs to this state'; - createdScheduledMaintenanceState.color = Black; - createdScheduledMaintenanceState.isScheduledState = true; - createdScheduledMaintenanceState.projectId = createdItem.id!; - createdScheduledMaintenanceState.order = 1; - - createdScheduledMaintenanceState = - await ScheduledMaintenanceStateService.create({ - data: createdScheduledMaintenanceState, - props: { - isRoot: true, - }, - }); - - let ongoingScheduledMaintenanceState: ScheduledMaintenanceState = - new ScheduledMaintenanceState(); - ongoingScheduledMaintenanceState.name = 'Ongoing'; - ongoingScheduledMaintenanceState.description = - 'When an event is ongoing, it belongs to this state.'; - ongoingScheduledMaintenanceState.color = Yellow; - ongoingScheduledMaintenanceState.isOngoingState = true; - ongoingScheduledMaintenanceState.projectId = createdItem.id!; - ongoingScheduledMaintenanceState.order = 2; - - ongoingScheduledMaintenanceState = - await ScheduledMaintenanceStateService.create({ - data: ongoingScheduledMaintenanceState, - props: { - isRoot: true, - }, - }); - - let endedScheduledMaintenanceState: ScheduledMaintenanceState = - new ScheduledMaintenanceState(); - endedScheduledMaintenanceState.name = 'Ended'; - endedScheduledMaintenanceState.description = - 'Scheduled maintenance events switch to this state when they end.'; - endedScheduledMaintenanceState.color = new Color('#4A4A4A'); - endedScheduledMaintenanceState.isEndedState = true; - endedScheduledMaintenanceState.projectId = createdItem.id!; - endedScheduledMaintenanceState.order = 3; - - endedScheduledMaintenanceState = - await ScheduledMaintenanceStateService.create({ - data: endedScheduledMaintenanceState, - props: { - isRoot: true, - }, - }); - - let completedScheduledMaintenanceState: ScheduledMaintenanceState = - new ScheduledMaintenanceState(); - completedScheduledMaintenanceState.name = 'Completed'; - completedScheduledMaintenanceState.description = - 'When an event is completed, it belongs to this state.'; - completedScheduledMaintenanceState.color = Green; - completedScheduledMaintenanceState.isResolvedState = true; - completedScheduledMaintenanceState.projectId = createdItem.id!; - completedScheduledMaintenanceState.order = 4; - - completedScheduledMaintenanceState = - await ScheduledMaintenanceStateService.create({ - data: completedScheduledMaintenanceState, - props: { - isRoot: true, - }, - }); - - return createdItem; - } - - protected override async onCreateSuccess( - _onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - // Create billing. - - if (IsBillingEnabled) { - const customerId: string = await BillingService.createCustomer({ - name: createdItem.name!, - email: createdItem.createdOwnerEmail!, - id: createdItem.id!, - }); - - const plan: SubscriptionPlan | undefined = - SubscriptionPlan.getSubscriptionPlanById( - createdItem.paymentProviderPlanId!, - getAllEnvVars() - ); - - if (!plan) { - throw new BadDataException('Invalid plan.'); - } - // add subscription to this customer. - - const { subscriptionId, meteredSubscriptionId, trialEndsAt } = - await BillingService.subscribeToPlan({ - projectId: createdItem.id!, - customerId, - serverMeteredPlans: AllMeteredPlans, - plan, - quantity: 1, - isYearly: - plan.getYearlyPlanId() === - createdItem.paymentProviderPlanId!, - trial: true, - promoCode: createdItem.paymentProviderPromoCode, - }); - - await this.updateOneById({ - id: createdItem.id!, - data: { - paymentProviderCustomerId: customerId, - paymentProviderSubscriptionId: subscriptionId, - paymentProviderMeteredSubscriptionId: meteredSubscriptionId, - paymentProviderSubscriptionSeats: 1, - trialEndsAt: (trialEndsAt || null) as any, - }, - props: { - isRoot: true, - }, - }); - - // mark the promo code as used it it exists. - - if (createdItem.paymentProviderPromoCode) { - await PromoCodeService.updateOneBy({ - query: { - promoCodeId: createdItem.paymentProviderPromoCode, - }, - data: { - isPromoCodeUsed: true, - promoCodeUsedAt: OneUptimeDate.getCurrentDate(), - projectId: createdItem.id!, - }, - props: { - isRoot: true, - }, - }); - } - } - - createdItem = await this.addDefaultIncidentSeverity(createdItem); - createdItem = await this.addDefaultProjectTeams(createdItem); - createdItem = await this.addDefaultMonitorStatus(createdItem); - createdItem = await this.addDefaultIncidentState(createdItem); - createdItem = await this.addDefaultScheduledMaintenanceState( - createdItem - ); - - return createdItem; - } - - private async addDefaultIncidentState(createdItem: Model): Promise<Model> { - let createdIncidentState: IncidentState = new IncidentState(); - createdIncidentState.name = 'Identified'; - createdIncidentState.description = - 'When an incident is created, it belongs to this state'; - createdIncidentState.color = Red; - createdIncidentState.isCreatedState = true; - createdIncidentState.projectId = createdItem.id!; - createdIncidentState.order = 1; - - createdIncidentState = await IncidentStateService.create({ - data: createdIncidentState, - props: { - isRoot: true, - }, - }); - - let acknowledgedIncidentState: IncidentState = new IncidentState(); - acknowledgedIncidentState.name = 'Acknowledged'; - acknowledgedIncidentState.description = - 'When an incident is acknowledged, it belongs to this state.'; - acknowledgedIncidentState.color = Yellow; - acknowledgedIncidentState.isAcknowledgedState = true; - acknowledgedIncidentState.projectId = createdItem.id!; - acknowledgedIncidentState.order = 2; - - acknowledgedIncidentState = await IncidentStateService.create({ - data: acknowledgedIncidentState, - props: { - isRoot: true, - }, - }); - - let resolvedIncidentState: IncidentState = new IncidentState(); - resolvedIncidentState.name = 'Resolved'; - resolvedIncidentState.description = - 'When an incident is resolved, it belongs to this state.'; - resolvedIncidentState.color = Green; - resolvedIncidentState.isResolvedState = true; - resolvedIncidentState.projectId = createdItem.id!; - resolvedIncidentState.order = 3; - - resolvedIncidentState = await IncidentStateService.create({ - data: resolvedIncidentState, - props: { - isRoot: true, - }, - }); - - return createdItem; - } - - private async addDefaultIncidentSeverity( - createdItem: Model - ): Promise<Model> { - let criticalIncident: IncidentSeverity = new IncidentSeverity(); - criticalIncident.name = 'Critical Incident'; - criticalIncident.description = - 'Issues causing very high impact to customers. Immediate response is required. Examples include a full outage, or a data breach.'; - criticalIncident.color = Moroon500; - criticalIncident.projectId = createdItem.id!; - criticalIncident.order = 1; - - criticalIncident = await IncidentSeverityService.create({ - data: criticalIncident, - props: { - isRoot: true, - }, - }); - - let majorIncident: IncidentSeverity = new IncidentSeverity(); - majorIncident.name = 'Major Incident'; - majorIncident.description = - 'Issues causing significant impact. Immediate response is usually required. We might have some workarounds that mitigate the impact on customers. Examples include an important sub-system failing.'; - majorIncident.color = Red; - majorIncident.projectId = createdItem.id!; - majorIncident.order = 2; - - majorIncident = await IncidentSeverityService.create({ - data: majorIncident, - props: { - isRoot: true, - }, - }); - - let minorIncident: IncidentSeverity = new IncidentSeverity(); - minorIncident.name = 'Minor Incident'; - minorIncident.description = - 'Issues with low impact, which can usually be handled within working hours. Most customers are unlikely to notice any problems. Examples include a slight drop in application performance.'; - minorIncident.color = Yellow; - minorIncident.projectId = createdItem.id!; - minorIncident.order = 3; - - minorIncident = await IncidentSeverityService.create({ - data: minorIncident, - props: { - isRoot: true, - }, - }); - - return createdItem; - } - - private async addDefaultMonitorStatus(createdItem: Model): Promise<Model> { - let operationalStatus: MonitorStatus = new MonitorStatus(); - operationalStatus.name = 'Operational'; - operationalStatus.description = 'Monitor operating normally'; - operationalStatus.projectId = createdItem.id!; - operationalStatus.priority = 1; - operationalStatus.isOperationalState = true; - operationalStatus.color = Green; - - operationalStatus = await MonitorStatusService.create({ - data: operationalStatus, - props: { - isRoot: true, - }, - }); - - let degradedStatus: MonitorStatus = new MonitorStatus(); - degradedStatus.name = 'Degraded'; - degradedStatus.description = - 'Monitor is operating at reduced performance.'; - degradedStatus.priority = 2; - degradedStatus.projectId = createdItem.id!; - degradedStatus.color = Yellow; - - degradedStatus = await MonitorStatusService.create({ - data: degradedStatus, - props: { - isRoot: true, - }, - }); - - let downStatus: MonitorStatus = new MonitorStatus(); - downStatus.name = 'Offline'; - downStatus.description = 'Monitor is offline.'; - downStatus.isOfflineState = true; - downStatus.projectId = createdItem.id!; - downStatus.priority = 3; - downStatus.color = Red; - - downStatus = await MonitorStatusService.create({ - data: downStatus, - props: { - isRoot: true, - }, - }); - - return createdItem; - } - - private async addDefaultProjectTeams(createdItem: Model): Promise<Model> { - // add a team member. - - // Owner Team. - let ownerTeam: Team = new Team(); - ownerTeam.projectId = createdItem.id!; - ownerTeam.name = 'Owners'; - ownerTeam.shouldHaveAtLeastOneMember = true; - ownerTeam.isPermissionsEditable = false; - ownerTeam.isTeamEditable = false; - ownerTeam.isTeamDeleteable = false; - ownerTeam.description = - 'This team is for project owners. Adding team members to this team will give them root level permissions.'; - - ownerTeam = await TeamService.create({ - data: ownerTeam, - props: { - isRoot: true, - }, - }); - - // Add current user to owners team. - - let ownerTeamMember: TeamMember = new TeamMember(); - ownerTeamMember.projectId = createdItem.id!; - ownerTeamMember.userId = createdItem.createdByUserId!; - ownerTeamMember.hasAcceptedInvitation = true; - ownerTeamMember.invitationAcceptedAt = OneUptimeDate.getCurrentDate(); - ownerTeamMember.teamId = ownerTeam.id!; - - ownerTeamMember = await TeamMemberService.create({ - data: ownerTeamMember, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - // Add permissions for this team. - - const ownerPermissions: TeamPermission = new TeamPermission(); - ownerPermissions.permission = Permission.ProjectOwner; - ownerPermissions.teamId = ownerTeam.id!; - ownerPermissions.projectId = createdItem.id!; - - await TeamPermissionService.create({ - data: ownerPermissions, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - // Admin Team. - const adminTeam: Team = new Team(); - adminTeam.projectId = createdItem.id!; - adminTeam.name = 'Admin'; - adminTeam.isPermissionsEditable = false; - adminTeam.isTeamDeleteable = false; - adminTeam.isTeamEditable = false; - adminTeam.description = - 'This team is for project admins. Admins can invite members to any team and create project resources.'; - - await TeamService.create({ - data: adminTeam, - props: { - isRoot: true, - }, - }); - - const adminPermissions: TeamPermission = new TeamPermission(); - adminPermissions.permission = Permission.ProjectAdmin; - adminPermissions.teamId = adminTeam.id!; - adminPermissions.projectId = createdItem.id!; - - await TeamPermissionService.create({ - data: adminPermissions, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - // Members Team. - const memberTeam: Team = new Team(); - memberTeam.projectId = createdItem.id!; - memberTeam.isPermissionsEditable = true; - memberTeam.name = 'Members'; - memberTeam.isTeamDeleteable = true; - memberTeam.description = - 'This team is for project members. Members can interact with any project resources like monitors, incidents, etc.'; - - await TeamService.create({ - data: memberTeam, - props: { - isRoot: true, - }, - }); - - const memberPermissions: TeamPermission = new TeamPermission(); - memberPermissions.permission = Permission.ProjectMember; - memberPermissions.teamId = memberTeam.id!; - memberPermissions.projectId = createdItem.id!; - - await TeamPermissionService.create({ - data: memberPermissions, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - await AccessTokenService.refreshUserAllPermissions( - createdItem.createdByUserId! - ); - - const user: User | null = await UserService.findOneById({ - id: createdItem.createdByUserId!, - props: { - isRoot: true, - }, - select: { - isEmailVerified: true, - email: true, - }, - }); - - if (user && user.isEmailVerified) { - await UserNotificationRuleService.addDefaultNotificationRuleForUser( - createdItem.id!, - user.id!, - user.email! + } + + if (promoCode.isPromoCodeUsed) { + throw new BadDataException("This promocode has already been used."); + } + + if (promoCode.resellerPlan?.monitorLimit) { + data.data.activeMonitorsLimit = + promoCode.resellerPlan?.monitorLimit; + } + + if (promoCode.resellerPlan?.teamMemberLimit) { + data.data.seatLimit = promoCode.resellerPlan?.teamMemberLimit; + } + + if (promoCode.planType !== data.data.planName) { + throw new BadDataException( + "Promocode is not valid for this plan. Please select the " + + promoCode.planType + + " plan.", ); + } - await UserNotificationSettingService.addDefaultNotificationSettingsForUser( - user.id!, - createdItem.id! - ); + if (promoCode.resellerLicenseId) { + data.data.resellerLicenseId = promoCode.resellerLicenseId; + } + + if (promoCode.resellerId) { + data.data.resellerId = promoCode.resellerId; + } + + if (promoCode.resellerPlanId) { + data.data.resellerPlanId = promoCode.resellerPlanId; + } } + } - return createdItem; + if ( + data.data.paymentProviderPromoCode && + !(await BillingService.isPromoCodeValid( + data.data.paymentProviderPromoCode, + )) + ) { + throw new BadDataException("Promo code is invalid."); + } + + // check if promocode is valid. } - public async updateLastActive(projectId: ObjectID): Promise<void> { - await this.updateOneById({ - id: projectId, - data: { - lastActive: OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); + // check if the user has the project with the same name. If yes, reject. + + let existingProjectWithSameNameCount: number = 0; + if ( + data.props.userGlobalAccessPermission && + data.props.userGlobalAccessPermission?.projectIds.length > 0 + ) { + existingProjectWithSameNameCount = ( + await this.countBy({ + query: { + _id: QueryHelper.any( + data.props.userGlobalAccessPermission?.projectIds.map( + (item: ObjectID) => { + return item.toString(); + }, + ) || [], + ), + name: QueryHelper.findWithSameText(data.data.name!), + }, + props: { + isRoot: true, + }, + }) + ).toNumber(); } - public async getOwners(projectId: ObjectID): Promise<Array<User>> { - if (!projectId) { - throw new BadDataException('Project ID is required'); - } + if (existingProjectWithSameNameCount > 0) { + throw new BadDataException("Project with the same name already exists"); + } - // get teams with project owner permissions. - const teamPermissions: Array<TeamPermission> = - await TeamPermissionService.findBy({ - query: { - projectId: projectId, - permission: Permission.ProjectOwner, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - teamId: true, - }, - }); + data.data.createdOwnerName = user.name!; + data.data.createdOwnerEmail = user.email!; + data.data.createdOwnerPhone = user.companyPhoneNumber!; + data.data.createdOwnerCompanyName = user.companyName!; - if (teamPermissions.length === 0) { - return []; - } + // UTM info. + data.data.utmCampaign = user.utmCampaign!; + data.data.utmSource = user.utmSource!; + data.data.utmMedium = user.utmMedium!; + data.data.utmTerm = user.utmTerm!; + data.data.utmContent = user.utmContent!; + data.data.utmUrl = user.utmUrl!; - const teamIds: Array<ObjectID> = teamPermissions.map( - (item: TeamPermission) => { - return item.teamId!; - } + return Promise.resolve({ createBy: data, carryForward: null }); + } + + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (IsBillingEnabled) { + if (updateBy.data.enableAutoRechargeSmsOrCallBalance) { + await NotificationService.rechargeIfBalanceIsLow( + new ObjectID(updateBy.query._id! as string), + { + autoRechargeSmsOrCallByBalanceInUSD: updateBy.data + .autoRechargeSmsOrCallByBalanceInUSD as number, + autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: updateBy.data + .autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD as number, + enableAutoRechargeSmsOrCallBalance: updateBy.data + .enableAutoRechargeSmsOrCallBalance as boolean, + }, ); + } - return TeamMemberService.getUsersInTeams(teamIds); - } - - protected override async onBeforeFind( - findBy: FindBy<Model> - ): Promise<OnFind<Model>> { - // if user has no project id, then he should not be able to access any project. - if ( - (!findBy.props.isRoot && - !findBy.props.userGlobalAccessPermission?.projectIds) || - findBy.props.userGlobalAccessPermission?.projectIds.length === 0 - ) { - findBy.props.isRoot = true; - findBy.query._id = In([]); // should not get any projects. - } - - return { findBy, carryForward: null }; - } - - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (IsBillingEnabled) { - const projects: Array<Model> = await this.findBy({ - query: deleteBy.query, - props: deleteBy.props, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - paymentProviderSubscriptionId: true, - paymentProviderMeteredSubscriptionId: true, - }, - }); - - return { deleteBy, carryForward: projects }; - } - - return { deleteBy, carryForward: [] }; - } - - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - // get project id - if (IsBillingEnabled) { - for (const project of onDelete.carryForward) { - if (project.paymentProviderSubscriptionId) { - await BillingService.cancelSubscription( - project.paymentProviderSubscriptionId - ); - } - - if (project.paymentProviderMeteredSubscriptionId) { - await BillingService.cancelSubscription( - project.paymentProviderMeteredSubscriptionId - ); - } - } - } - - return onDelete; - } - - public async getCurrentPlan(projectId: ObjectID): Promise<CurrentPlan> { - if (!IsBillingEnabled) { - return { plan: null, isSubscriptionUnpaid: false }; - } - + if (updateBy.data.paymentProviderPlanId) { + // payment provider id changed. const project: Model | null = await this.findOneById({ - id: projectId, - select: { - paymentProviderPlanId: true, - paymentProviderSubscriptionStatus: true, - paymentProviderMeteredSubscriptionStatus: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, + id: new ObjectID(updateBy.query._id! as string), + select: { + paymentProviderSubscriptionId: true, + paymentProviderMeteredSubscriptionId: true, + paymentProviderSubscriptionSeats: true, + paymentProviderPlanId: true, + trialEndsAt: true, + }, + props: { + isRoot: true, + }, }); if (!project) { - throw new BadDataException('Project ID is invalid'); + throw new BadDataException("Project not found"); } - if (!project.paymentProviderPlanId) { - throw new BadDataException('Project does not have any plans'); - } + if ( + project.paymentProviderPlanId !== updateBy.data.paymentProviderPlanId + ) { + logger.debug("Changing plan for project " + project.id); - const plan: PlanSelect = SubscriptionPlan.getPlanSelect( - project.paymentProviderPlanId, - getAllEnvVars() - ); - - return { - plan: plan, - isSubscriptionUnpaid: - !BillingService.isSubscriptionActive( - project.paymentProviderSubscriptionStatus! - ) || - !BillingService.isSubscriptionActive( - project.paymentProviderMeteredSubscriptionStatus! - ), - }; - } - - public async sendEmailToProjectOwners( - projectId: ObjectID, - subject: string, - message: string - ): Promise<void> { - const owners: Array<User> = await this.getOwners(projectId); - - if (owners.length === 0) { - return; - } - - const emails: Array<Email> = owners.map((owner: User) => { - return owner.email!; - }); - - for (const email of emails) { - MailService.sendMail( - { - toEmail: email, - templateType: EmailTemplateType.SimpleMessage, - vars: { - subject: subject, - message: message, - }, - subject: subject, - }, - { - projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - - public async reactiveSubscription(projectId: ObjectID): Promise<void> { - logger.debug('Reactivating subscription for project ' + projectId); - - const project: Model | null = await this.findOneById({ - id: projectId!, - props: { - isRoot: true, - }, - select: { - _id: true, - paymentProviderCustomerId: true, - paymentProviderSubscriptionId: true, - paymentProviderMeteredSubscriptionId: true, - paymentProviderSubscriptionSeats: true, - paymentProviderPlanId: true, - }, - }); - - if (!project) { - throw new BadDataException('Project not found'); - } - - if (!project.paymentProviderCustomerId) { - throw new BadDataException('Payment Provider customer not found'); - } - - if (!project.paymentProviderSubscriptionId) { - throw new BadDataException( - 'Payment Provider subscription not found' - ); - } - - if (!project.paymentProviderMeteredSubscriptionId) { - throw new BadDataException( - 'Payment Provider metered subscription not found' - ); - } - - if (!project.paymentProviderSubscriptionSeats) { - throw new BadDataException( - 'Payment Provider subscription seats not found' - ); - } - - if (!project.paymentProviderPlanId) { - throw new BadDataException('Payment Provider plan id not found'); - } - - const subscriptionPlan: SubscriptionPlan | undefined = + const plan: SubscriptionPlan | undefined = SubscriptionPlan.getSubscriptionPlanById( - project.paymentProviderPlanId, - getAllEnvVars() + updateBy.data.paymentProviderPlanId! as string, + getAllEnvVars(), ); - if (!subscriptionPlan) { - throw new BadDataException('Subscription plan not found'); - } + if (!plan) { + throw new BadDataException("Invalid plan"); + } - const result: { + logger.debug( + "Changing plan for project " + + project.id?.toString() + + " to " + + plan.getName(), + ); + + if (!project.paymentProviderSubscriptionSeats) { + project.paymentProviderSubscriptionSeats = + await TeamMemberService.getUniqueTeamMemberCountInProject( + project.id!, + ); + } + + logger.debug( + "Changing plan for project " + + project.id?.toString() + + " to " + + plan.getName() + + " with seats " + + project.paymentProviderSubscriptionSeats, + ); + + const subscription: { subscriptionId: string; meteredSubscriptionId: string; trialEndsAt?: Date | undefined; - } = await BillingService.changePlan({ - projectId: project.id as ObjectID, - subscriptionId: project.paymentProviderSubscriptionId, - meteredSubscriptionId: project.paymentProviderMeteredSubscriptionId, + } = await BillingService.changePlan({ + projectId: project.id!, + subscriptionId: project.paymentProviderSubscriptionId as string, + meteredSubscriptionId: + project.paymentProviderMeteredSubscriptionId as string, serverMeteredPlans: AllMeteredPlans, - newPlan: subscriptionPlan, - quantity: project.paymentProviderSubscriptionSeats, - isYearly: SubscriptionPlan.isYearlyPlan( - project.paymentProviderPlanId - ), - endTrialAt: undefined, - }); + newPlan: plan, + quantity: project.paymentProviderSubscriptionSeats as number, + isYearly: + plan.getYearlyPlanId() === updateBy.data.paymentProviderPlanId, + endTrialAt: project.trialEndsAt, + }); - // refresh subscription status. - const subscriptionState: SubscriptionStatus = - await BillingService.getSubscriptionStatus( - result.subscriptionId as string - ); + logger.debug( + "Changing plan for project " + + project.id?.toString() + + " to " + + plan.getName() + + " with seats " + + project.paymentProviderSubscriptionSeats + + " completed.", + ); - const meteredSubscriptionState: SubscriptionStatus = - await BillingService.getSubscriptionStatus( - project.paymentProviderMeteredSubscriptionId as string - ); - - // now update project with new subscription id. - - await this.updateOneById({ - id: project.id!, + await this.updateOneById({ + id: new ObjectID(updateBy.query._id! as string), data: { - paymentProviderSubscriptionId: result.subscriptionId, - paymentProviderMeteredSubscriptionId: - result.meteredSubscriptionId, - paymentProviderSubscriptionStatus: subscriptionState, - paymentProviderMeteredSubscriptionStatus: - meteredSubscriptionState, + paymentProviderSubscriptionId: subscription.subscriptionId, + paymentProviderMeteredSubscriptionId: + subscription.meteredSubscriptionId, + trialEndsAt: subscription.trialEndsAt || new Date(), + planName: SubscriptionPlan.getPlanSelect( + updateBy.data.paymentProviderPlanId! as string, + ), }, props: { - isRoot: true, + isRoot: true, + ignoreHooks: true, }, - }); + }); + + logger.debug( + "Changing plan for project " + + project.id?.toString() + + " to " + + plan.getName() + + " with seats " + + project.paymentProviderSubscriptionSeats + + " completed and project updated.", + ); + } + } } - public async isSMSNotificationsEnabled( - projectId: ObjectID - ): Promise<boolean> { - const project: Model | null = await this.findOneById({ - id: projectId, - select: { - enableSmsNotifications: true, - }, - props: { - isRoot: true, - }, + return { updateBy, carryForward: [] }; + } + + private async addDefaultScheduledMaintenanceState( + createdItem: Model, + ): Promise<Model> { + let createdScheduledMaintenanceState: ScheduledMaintenanceState = + new ScheduledMaintenanceState(); + createdScheduledMaintenanceState.name = "Scheduled"; + createdScheduledMaintenanceState.description = + "When an event is scheduled, it belongs to this state"; + createdScheduledMaintenanceState.color = Black; + createdScheduledMaintenanceState.isScheduledState = true; + createdScheduledMaintenanceState.projectId = createdItem.id!; + createdScheduledMaintenanceState.order = 1; + + createdScheduledMaintenanceState = + await ScheduledMaintenanceStateService.create({ + data: createdScheduledMaintenanceState, + props: { + isRoot: true, + }, + }); + + let ongoingScheduledMaintenanceState: ScheduledMaintenanceState = + new ScheduledMaintenanceState(); + ongoingScheduledMaintenanceState.name = "Ongoing"; + ongoingScheduledMaintenanceState.description = + "When an event is ongoing, it belongs to this state."; + ongoingScheduledMaintenanceState.color = Yellow; + ongoingScheduledMaintenanceState.isOngoingState = true; + ongoingScheduledMaintenanceState.projectId = createdItem.id!; + ongoingScheduledMaintenanceState.order = 2; + + ongoingScheduledMaintenanceState = + await ScheduledMaintenanceStateService.create({ + data: ongoingScheduledMaintenanceState, + props: { + isRoot: true, + }, + }); + + let endedScheduledMaintenanceState: ScheduledMaintenanceState = + new ScheduledMaintenanceState(); + endedScheduledMaintenanceState.name = "Ended"; + endedScheduledMaintenanceState.description = + "Scheduled maintenance events switch to this state when they end."; + endedScheduledMaintenanceState.color = new Color("#4A4A4A"); + endedScheduledMaintenanceState.isEndedState = true; + endedScheduledMaintenanceState.projectId = createdItem.id!; + endedScheduledMaintenanceState.order = 3; + + endedScheduledMaintenanceState = + await ScheduledMaintenanceStateService.create({ + data: endedScheduledMaintenanceState, + props: { + isRoot: true, + }, + }); + + let completedScheduledMaintenanceState: ScheduledMaintenanceState = + new ScheduledMaintenanceState(); + completedScheduledMaintenanceState.name = "Completed"; + completedScheduledMaintenanceState.description = + "When an event is completed, it belongs to this state."; + completedScheduledMaintenanceState.color = Green; + completedScheduledMaintenanceState.isResolvedState = true; + completedScheduledMaintenanceState.projectId = createdItem.id!; + completedScheduledMaintenanceState.order = 4; + + completedScheduledMaintenanceState = + await ScheduledMaintenanceStateService.create({ + data: completedScheduledMaintenanceState, + props: { + isRoot: true, + }, + }); + + return createdItem; + } + + protected override async onCreateSuccess( + _onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + // Create billing. + + if (IsBillingEnabled) { + const customerId: string = await BillingService.createCustomer({ + name: createdItem.name!, + email: createdItem.createdOwnerEmail!, + id: createdItem.id!, + }); + + const plan: SubscriptionPlan | undefined = + SubscriptionPlan.getSubscriptionPlanById( + createdItem.paymentProviderPlanId!, + getAllEnvVars(), + ); + + if (!plan) { + throw new BadDataException("Invalid plan."); + } + // add subscription to this customer. + + const { subscriptionId, meteredSubscriptionId, trialEndsAt } = + await BillingService.subscribeToPlan({ + projectId: createdItem.id!, + customerId, + serverMeteredPlans: AllMeteredPlans, + plan, + quantity: 1, + isYearly: + plan.getYearlyPlanId() === createdItem.paymentProviderPlanId!, + trial: true, + promoCode: createdItem.paymentProviderPromoCode, }); - if (!project) { - throw new BadDataException('Project not found'); + await this.updateOneById({ + id: createdItem.id!, + data: { + paymentProviderCustomerId: customerId, + paymentProviderSubscriptionId: subscriptionId, + paymentProviderMeteredSubscriptionId: meteredSubscriptionId, + paymentProviderSubscriptionSeats: 1, + trialEndsAt: (trialEndsAt || null) as any, + }, + props: { + isRoot: true, + }, + }); + + // mark the promo code as used it it exists. + + if (createdItem.paymentProviderPromoCode) { + await PromoCodeService.updateOneBy({ + query: { + promoCodeId: createdItem.paymentProviderPromoCode, + }, + data: { + isPromoCodeUsed: true, + promoCodeUsedAt: OneUptimeDate.getCurrentDate(), + projectId: createdItem.id!, + }, + props: { + isRoot: true, + }, + }); + } + } + + createdItem = await this.addDefaultIncidentSeverity(createdItem); + createdItem = await this.addDefaultProjectTeams(createdItem); + createdItem = await this.addDefaultMonitorStatus(createdItem); + createdItem = await this.addDefaultIncidentState(createdItem); + createdItem = await this.addDefaultScheduledMaintenanceState(createdItem); + + return createdItem; + } + + private async addDefaultIncidentState(createdItem: Model): Promise<Model> { + let createdIncidentState: IncidentState = new IncidentState(); + createdIncidentState.name = "Identified"; + createdIncidentState.description = + "When an incident is created, it belongs to this state"; + createdIncidentState.color = Red; + createdIncidentState.isCreatedState = true; + createdIncidentState.projectId = createdItem.id!; + createdIncidentState.order = 1; + + createdIncidentState = await IncidentStateService.create({ + data: createdIncidentState, + props: { + isRoot: true, + }, + }); + + let acknowledgedIncidentState: IncidentState = new IncidentState(); + acknowledgedIncidentState.name = "Acknowledged"; + acknowledgedIncidentState.description = + "When an incident is acknowledged, it belongs to this state."; + acknowledgedIncidentState.color = Yellow; + acknowledgedIncidentState.isAcknowledgedState = true; + acknowledgedIncidentState.projectId = createdItem.id!; + acknowledgedIncidentState.order = 2; + + acknowledgedIncidentState = await IncidentStateService.create({ + data: acknowledgedIncidentState, + props: { + isRoot: true, + }, + }); + + let resolvedIncidentState: IncidentState = new IncidentState(); + resolvedIncidentState.name = "Resolved"; + resolvedIncidentState.description = + "When an incident is resolved, it belongs to this state."; + resolvedIncidentState.color = Green; + resolvedIncidentState.isResolvedState = true; + resolvedIncidentState.projectId = createdItem.id!; + resolvedIncidentState.order = 3; + + resolvedIncidentState = await IncidentStateService.create({ + data: resolvedIncidentState, + props: { + isRoot: true, + }, + }); + + return createdItem; + } + + private async addDefaultIncidentSeverity(createdItem: Model): Promise<Model> { + let criticalIncident: IncidentSeverity = new IncidentSeverity(); + criticalIncident.name = "Critical Incident"; + criticalIncident.description = + "Issues causing very high impact to customers. Immediate response is required. Examples include a full outage, or a data breach."; + criticalIncident.color = Moroon500; + criticalIncident.projectId = createdItem.id!; + criticalIncident.order = 1; + + criticalIncident = await IncidentSeverityService.create({ + data: criticalIncident, + props: { + isRoot: true, + }, + }); + + let majorIncident: IncidentSeverity = new IncidentSeverity(); + majorIncident.name = "Major Incident"; + majorIncident.description = + "Issues causing significant impact. Immediate response is usually required. We might have some workarounds that mitigate the impact on customers. Examples include an important sub-system failing."; + majorIncident.color = Red; + majorIncident.projectId = createdItem.id!; + majorIncident.order = 2; + + majorIncident = await IncidentSeverityService.create({ + data: majorIncident, + props: { + isRoot: true, + }, + }); + + let minorIncident: IncidentSeverity = new IncidentSeverity(); + minorIncident.name = "Minor Incident"; + minorIncident.description = + "Issues with low impact, which can usually be handled within working hours. Most customers are unlikely to notice any problems. Examples include a slight drop in application performance."; + minorIncident.color = Yellow; + minorIncident.projectId = createdItem.id!; + minorIncident.order = 3; + + minorIncident = await IncidentSeverityService.create({ + data: minorIncident, + props: { + isRoot: true, + }, + }); + + return createdItem; + } + + private async addDefaultMonitorStatus(createdItem: Model): Promise<Model> { + let operationalStatus: MonitorStatus = new MonitorStatus(); + operationalStatus.name = "Operational"; + operationalStatus.description = "Monitor operating normally"; + operationalStatus.projectId = createdItem.id!; + operationalStatus.priority = 1; + operationalStatus.isOperationalState = true; + operationalStatus.color = Green; + + operationalStatus = await MonitorStatusService.create({ + data: operationalStatus, + props: { + isRoot: true, + }, + }); + + let degradedStatus: MonitorStatus = new MonitorStatus(); + degradedStatus.name = "Degraded"; + degradedStatus.description = "Monitor is operating at reduced performance."; + degradedStatus.priority = 2; + degradedStatus.projectId = createdItem.id!; + degradedStatus.color = Yellow; + + degradedStatus = await MonitorStatusService.create({ + data: degradedStatus, + props: { + isRoot: true, + }, + }); + + let downStatus: MonitorStatus = new MonitorStatus(); + downStatus.name = "Offline"; + downStatus.description = "Monitor is offline."; + downStatus.isOfflineState = true; + downStatus.projectId = createdItem.id!; + downStatus.priority = 3; + downStatus.color = Red; + + downStatus = await MonitorStatusService.create({ + data: downStatus, + props: { + isRoot: true, + }, + }); + + return createdItem; + } + + private async addDefaultProjectTeams(createdItem: Model): Promise<Model> { + // add a team member. + + // Owner Team. + let ownerTeam: Team = new Team(); + ownerTeam.projectId = createdItem.id!; + ownerTeam.name = "Owners"; + ownerTeam.shouldHaveAtLeastOneMember = true; + ownerTeam.isPermissionsEditable = false; + ownerTeam.isTeamEditable = false; + ownerTeam.isTeamDeleteable = false; + ownerTeam.description = + "This team is for project owners. Adding team members to this team will give them root level permissions."; + + ownerTeam = await TeamService.create({ + data: ownerTeam, + props: { + isRoot: true, + }, + }); + + // Add current user to owners team. + + let ownerTeamMember: TeamMember = new TeamMember(); + ownerTeamMember.projectId = createdItem.id!; + ownerTeamMember.userId = createdItem.createdByUserId!; + ownerTeamMember.hasAcceptedInvitation = true; + ownerTeamMember.invitationAcceptedAt = OneUptimeDate.getCurrentDate(); + ownerTeamMember.teamId = ownerTeam.id!; + + ownerTeamMember = await TeamMemberService.create({ + data: ownerTeamMember, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + // Add permissions for this team. + + const ownerPermissions: TeamPermission = new TeamPermission(); + ownerPermissions.permission = Permission.ProjectOwner; + ownerPermissions.teamId = ownerTeam.id!; + ownerPermissions.projectId = createdItem.id!; + + await TeamPermissionService.create({ + data: ownerPermissions, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + // Admin Team. + const adminTeam: Team = new Team(); + adminTeam.projectId = createdItem.id!; + adminTeam.name = "Admin"; + adminTeam.isPermissionsEditable = false; + adminTeam.isTeamDeleteable = false; + adminTeam.isTeamEditable = false; + adminTeam.description = + "This team is for project admins. Admins can invite members to any team and create project resources."; + + await TeamService.create({ + data: adminTeam, + props: { + isRoot: true, + }, + }); + + const adminPermissions: TeamPermission = new TeamPermission(); + adminPermissions.permission = Permission.ProjectAdmin; + adminPermissions.teamId = adminTeam.id!; + adminPermissions.projectId = createdItem.id!; + + await TeamPermissionService.create({ + data: adminPermissions, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + // Members Team. + const memberTeam: Team = new Team(); + memberTeam.projectId = createdItem.id!; + memberTeam.isPermissionsEditable = true; + memberTeam.name = "Members"; + memberTeam.isTeamDeleteable = true; + memberTeam.description = + "This team is for project members. Members can interact with any project resources like monitors, incidents, etc."; + + await TeamService.create({ + data: memberTeam, + props: { + isRoot: true, + }, + }); + + const memberPermissions: TeamPermission = new TeamPermission(); + memberPermissions.permission = Permission.ProjectMember; + memberPermissions.teamId = memberTeam.id!; + memberPermissions.projectId = createdItem.id!; + + await TeamPermissionService.create({ + data: memberPermissions, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + await AccessTokenService.refreshUserAllPermissions( + createdItem.createdByUserId!, + ); + + const user: User | null = await UserService.findOneById({ + id: createdItem.createdByUserId!, + props: { + isRoot: true, + }, + select: { + isEmailVerified: true, + email: true, + }, + }); + + if (user && user.isEmailVerified) { + await UserNotificationRuleService.addDefaultNotificationRuleForUser( + createdItem.id!, + user.id!, + user.email!, + ); + + await UserNotificationSettingService.addDefaultNotificationSettingsForUser( + user.id!, + createdItem.id!, + ); + } + + return createdItem; + } + + public async updateLastActive(projectId: ObjectID): Promise<void> { + await this.updateOneById({ + id: projectId, + data: { + lastActive: OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); + } + + public async getOwners(projectId: ObjectID): Promise<Array<User>> { + if (!projectId) { + throw new BadDataException("Project ID is required"); + } + + // get teams with project owner permissions. + const teamPermissions: Array<TeamPermission> = + await TeamPermissionService.findBy({ + query: { + projectId: projectId, + permission: Permission.ProjectOwner, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + teamId: true, + }, + }); + + if (teamPermissions.length === 0) { + return []; + } + + const teamIds: Array<ObjectID> = teamPermissions.map( + (item: TeamPermission) => { + return item.teamId!; + }, + ); + + return TeamMemberService.getUsersInTeams(teamIds); + } + + protected override async onBeforeFind( + findBy: FindBy<Model>, + ): Promise<OnFind<Model>> { + // if user has no project id, then he should not be able to access any project. + if ( + (!findBy.props.isRoot && + !findBy.props.userGlobalAccessPermission?.projectIds) || + findBy.props.userGlobalAccessPermission?.projectIds.length === 0 + ) { + findBy.props.isRoot = true; + findBy.query._id = In([]); // should not get any projects. + } + + return { findBy, carryForward: null }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (IsBillingEnabled) { + const projects: Array<Model> = await this.findBy({ + query: deleteBy.query, + props: deleteBy.props, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + paymentProviderSubscriptionId: true, + paymentProviderMeteredSubscriptionId: true, + }, + }); + + return { deleteBy, carryForward: projects }; + } + + return { deleteBy, carryForward: [] }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + // get project id + if (IsBillingEnabled) { + for (const project of onDelete.carryForward) { + if (project.paymentProviderSubscriptionId) { + await BillingService.cancelSubscription( + project.paymentProviderSubscriptionId, + ); } - return Boolean(project.enableSmsNotifications); + if (project.paymentProviderMeteredSubscriptionId) { + await BillingService.cancelSubscription( + project.paymentProviderMeteredSubscriptionId, + ); + } + } } + + return onDelete; + } + + public async getCurrentPlan(projectId: ObjectID): Promise<CurrentPlan> { + if (!IsBillingEnabled) { + return { plan: null, isSubscriptionUnpaid: false }; + } + + const project: Model | null = await this.findOneById({ + id: projectId, + select: { + paymentProviderPlanId: true, + paymentProviderSubscriptionStatus: true, + paymentProviderMeteredSubscriptionStatus: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + if (!project) { + throw new BadDataException("Project ID is invalid"); + } + + if (!project.paymentProviderPlanId) { + throw new BadDataException("Project does not have any plans"); + } + + const plan: PlanSelect = SubscriptionPlan.getPlanSelect( + project.paymentProviderPlanId, + getAllEnvVars(), + ); + + return { + plan: plan, + isSubscriptionUnpaid: + !BillingService.isSubscriptionActive( + project.paymentProviderSubscriptionStatus!, + ) || + !BillingService.isSubscriptionActive( + project.paymentProviderMeteredSubscriptionStatus!, + ), + }; + } + + public async sendEmailToProjectOwners( + projectId: ObjectID, + subject: string, + message: string, + ): Promise<void> { + const owners: Array<User> = await this.getOwners(projectId); + + if (owners.length === 0) { + return; + } + + const emails: Array<Email> = owners.map((owner: User) => { + return owner.email!; + }); + + for (const email of emails) { + MailService.sendMail( + { + toEmail: email, + templateType: EmailTemplateType.SimpleMessage, + vars: { + subject: subject, + message: message, + }, + subject: subject, + }, + { + projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } + } + + public async reactiveSubscription(projectId: ObjectID): Promise<void> { + logger.debug("Reactivating subscription for project " + projectId); + + const project: Model | null = await this.findOneById({ + id: projectId!, + props: { + isRoot: true, + }, + select: { + _id: true, + paymentProviderCustomerId: true, + paymentProviderSubscriptionId: true, + paymentProviderMeteredSubscriptionId: true, + paymentProviderSubscriptionSeats: true, + paymentProviderPlanId: true, + }, + }); + + if (!project) { + throw new BadDataException("Project not found"); + } + + if (!project.paymentProviderCustomerId) { + throw new BadDataException("Payment Provider customer not found"); + } + + if (!project.paymentProviderSubscriptionId) { + throw new BadDataException("Payment Provider subscription not found"); + } + + if (!project.paymentProviderMeteredSubscriptionId) { + throw new BadDataException( + "Payment Provider metered subscription not found", + ); + } + + if (!project.paymentProviderSubscriptionSeats) { + throw new BadDataException( + "Payment Provider subscription seats not found", + ); + } + + if (!project.paymentProviderPlanId) { + throw new BadDataException("Payment Provider plan id not found"); + } + + const subscriptionPlan: SubscriptionPlan | undefined = + SubscriptionPlan.getSubscriptionPlanById( + project.paymentProviderPlanId, + getAllEnvVars(), + ); + + if (!subscriptionPlan) { + throw new BadDataException("Subscription plan not found"); + } + + const result: { + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt?: Date | undefined; + } = await BillingService.changePlan({ + projectId: project.id as ObjectID, + subscriptionId: project.paymentProviderSubscriptionId, + meteredSubscriptionId: project.paymentProviderMeteredSubscriptionId, + serverMeteredPlans: AllMeteredPlans, + newPlan: subscriptionPlan, + quantity: project.paymentProviderSubscriptionSeats, + isYearly: SubscriptionPlan.isYearlyPlan(project.paymentProviderPlanId), + endTrialAt: undefined, + }); + + // refresh subscription status. + const subscriptionState: SubscriptionStatus = + await BillingService.getSubscriptionStatus( + result.subscriptionId as string, + ); + + const meteredSubscriptionState: SubscriptionStatus = + await BillingService.getSubscriptionStatus( + project.paymentProviderMeteredSubscriptionId as string, + ); + + // now update project with new subscription id. + + await this.updateOneById({ + id: project.id!, + data: { + paymentProviderSubscriptionId: result.subscriptionId, + paymentProviderMeteredSubscriptionId: result.meteredSubscriptionId, + paymentProviderSubscriptionStatus: subscriptionState, + paymentProviderMeteredSubscriptionStatus: meteredSubscriptionState, + }, + props: { + isRoot: true, + }, + }); + } + + public async isSMSNotificationsEnabled( + projectId: ObjectID, + ): Promise<boolean> { + const project: Model | null = await this.findOneById({ + id: projectId, + select: { + enableSmsNotifications: true, + }, + props: { + isRoot: true, + }, + }); + + if (!project) { + throw new BadDataException("Project not found"); + } + + return Boolean(project.enableSmsNotifications); + } } export default new Service(); diff --git a/CommonServer/Services/ProjectSmtpConfigService.ts b/CommonServer/Services/ProjectSmtpConfigService.ts index 86fc4f9df8..9556e9fdc4 100755 --- a/CommonServer/Services/ProjectSmtpConfigService.ts +++ b/CommonServer/Services/ProjectSmtpConfigService.ts @@ -1,67 +1,59 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import EmailServer from 'Common/Types/Email/EmailServer'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Model from 'Model/Models/ProjectSmtpConfig'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import EmailServer from "Common/Types/Email/EmailServer"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Model from "Model/Models/ProjectSmtpConfig"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + public toEmailServer( + projectSmtpConfig: Model | undefined, + ): EmailServer | undefined { + if (!projectSmtpConfig) { + return undefined; } - public toEmailServer( - projectSmtpConfig: Model | undefined - ): EmailServer | undefined { - if (!projectSmtpConfig) { - return undefined; - } - - if (!projectSmtpConfig.id) { - throw new BadDataException('Project SMTP config id is not set'); - } - - if (!projectSmtpConfig.hostname) { - throw new BadDataException('Project SMTP config host is not set'); - } - - if (!projectSmtpConfig.port) { - throw new BadDataException('Project SMTP config port is not set'); - } - - if (!projectSmtpConfig.username) { - throw new BadDataException( - 'Project SMTP config username is not set' - ); - } - - if (!projectSmtpConfig.password) { - throw new BadDataException( - 'Project SMTP config password is not set' - ); - } - - if (!projectSmtpConfig.fromEmail) { - throw new BadDataException( - 'Project SMTP config from email is not set' - ); - } - - if (!projectSmtpConfig.fromName) { - throw new BadDataException( - 'Project SMTP config from name is not set' - ); - } - - return { - id: projectSmtpConfig.id!, - host: projectSmtpConfig.hostname, - port: projectSmtpConfig.port, - username: projectSmtpConfig.username, - password: projectSmtpConfig.password, - fromEmail: projectSmtpConfig.fromEmail, - fromName: projectSmtpConfig.fromName, - secure: Boolean(projectSmtpConfig.secure), - }; + if (!projectSmtpConfig.id) { + throw new BadDataException("Project SMTP config id is not set"); } + + if (!projectSmtpConfig.hostname) { + throw new BadDataException("Project SMTP config host is not set"); + } + + if (!projectSmtpConfig.port) { + throw new BadDataException("Project SMTP config port is not set"); + } + + if (!projectSmtpConfig.username) { + throw new BadDataException("Project SMTP config username is not set"); + } + + if (!projectSmtpConfig.password) { + throw new BadDataException("Project SMTP config password is not set"); + } + + if (!projectSmtpConfig.fromEmail) { + throw new BadDataException("Project SMTP config from email is not set"); + } + + if (!projectSmtpConfig.fromName) { + throw new BadDataException("Project SMTP config from name is not set"); + } + + return { + id: projectSmtpConfig.id!, + host: projectSmtpConfig.hostname, + port: projectSmtpConfig.port, + username: projectSmtpConfig.username, + password: projectSmtpConfig.password, + fromEmail: projectSmtpConfig.fromEmail, + fromName: projectSmtpConfig.fromName, + secure: Boolean(projectSmtpConfig.secure), + }; + } } export default new Service(); diff --git a/CommonServer/Services/ProjectSsoService.ts b/CommonServer/Services/ProjectSsoService.ts index 08e7f4e50b..bf540c7749 100644 --- a/CommonServer/Services/ProjectSsoService.ts +++ b/CommonServer/Services/ProjectSsoService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ProjectSso'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ProjectSso"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/PromoCodeService.ts b/CommonServer/Services/PromoCodeService.ts index ef260aa4e9..ae023d443d 100644 --- a/CommonServer/Services/PromoCodeService.ts +++ b/CommonServer/Services/PromoCodeService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/PromoCode'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/PromoCode"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ResellerPlanService.ts b/CommonServer/Services/ResellerPlanService.ts index f15517d6bc..3ad0ccb406 100644 --- a/CommonServer/Services/ResellerPlanService.ts +++ b/CommonServer/Services/ResellerPlanService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ResellerPlan'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ResellerPlan"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ResellerService.ts b/CommonServer/Services/ResellerService.ts index 4622e9319b..2252b16362 100644 --- a/CommonServer/Services/ResellerService.ts +++ b/CommonServer/Services/ResellerService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/Reseller'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/Reseller"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ScheduledMaintenanceCustomFieldService.ts b/CommonServer/Services/ScheduledMaintenanceCustomFieldService.ts index f949fa44c0..3c8989be6b 100644 --- a/CommonServer/Services/ScheduledMaintenanceCustomFieldService.ts +++ b/CommonServer/Services/ScheduledMaintenanceCustomFieldService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ScheduledMaintenanceCustomField'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ScheduledMaintenanceCustomField"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ScheduledMaintenanceInternalNoteService.ts b/CommonServer/Services/ScheduledMaintenanceInternalNoteService.ts index 5f29523f09..bd22c9258a 100644 --- a/CommonServer/Services/ScheduledMaintenanceInternalNoteService.ts +++ b/CommonServer/Services/ScheduledMaintenanceInternalNoteService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ScheduledMaintenanceInternalNote'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ScheduledMaintenanceInternalNote"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ScheduledMaintenanceNoteTemplateService.ts b/CommonServer/Services/ScheduledMaintenanceNoteTemplateService.ts index 17f6f5745c..1d2b7924b5 100644 --- a/CommonServer/Services/ScheduledMaintenanceNoteTemplateService.ts +++ b/CommonServer/Services/ScheduledMaintenanceNoteTemplateService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ScheduledMaintenanceNoteTemplate'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ScheduledMaintenanceNoteTemplate"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ScheduledMaintenanceOwnerTeamService.ts b/CommonServer/Services/ScheduledMaintenanceOwnerTeamService.ts index fdbad7142e..a987dcf701 100644 --- a/CommonServer/Services/ScheduledMaintenanceOwnerTeamService.ts +++ b/CommonServer/Services/ScheduledMaintenanceOwnerTeamService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ScheduledMaintenanceOwnerTeam'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ScheduledMaintenanceOwnerTeam"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ScheduledMaintenanceOwnerUserService.ts b/CommonServer/Services/ScheduledMaintenanceOwnerUserService.ts index 1181b72942..fb1af5807a 100644 --- a/CommonServer/Services/ScheduledMaintenanceOwnerUserService.ts +++ b/CommonServer/Services/ScheduledMaintenanceOwnerUserService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ScheduledMaintenanceOwnerUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ScheduledMaintenanceOwnerUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ScheduledMaintenancePublicNoteService.ts b/CommonServer/Services/ScheduledMaintenancePublicNoteService.ts index cacb996a55..d9e62e6d12 100644 --- a/CommonServer/Services/ScheduledMaintenancePublicNoteService.ts +++ b/CommonServer/Services/ScheduledMaintenancePublicNoteService.ts @@ -1,27 +1,27 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import OneUptimeDate from 'Common/Types/Date'; -import Model from 'Model/Models/ScheduledMaintenancePublicNote'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import OneUptimeDate from "Common/Types/Date"; +import Model from "Model/Models/ScheduledMaintenancePublicNote"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.postedAt) { + createBy.data.postedAt = OneUptimeDate.getCurrentDate(); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.postedAt) { - createBy.data.postedAt = OneUptimeDate.getCurrentDate(); - } - - return { - createBy: createBy, - carryForward: null, - }; - } + return { + createBy: createBy, + carryForward: null, + }; + } } export default new Service(); diff --git a/CommonServer/Services/ScheduledMaintenanceService.ts b/CommonServer/Services/ScheduledMaintenanceService.ts index 76f02e751e..f6a35dd651 100644 --- a/CommonServer/Services/ScheduledMaintenanceService.ts +++ b/CommonServer/Services/ScheduledMaintenanceService.ts @@ -1,440 +1,433 @@ -import DatabaseConfig from '../DatabaseConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import MonitorService from './MonitorService'; -import ScheduledMaintenanceOwnerTeamService from './ScheduledMaintenanceOwnerTeamService'; -import ScheduledMaintenanceOwnerUserService from './ScheduledMaintenanceOwnerUserService'; -import ScheduledMaintenanceStateService from './ScheduledMaintenanceStateService'; -import ScheduledMaintenanceStateTimelineService from './ScheduledMaintenanceStateTimelineService'; -import TeamMemberService from './TeamMemberService'; -import URL from 'Common/Types/API/URL'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Typeof from 'Common/Types/Typeof'; -import Monitor from 'Model/Models/Monitor'; -import Model from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceOwnerTeam from 'Model/Models/ScheduledMaintenanceOwnerTeam'; -import ScheduledMaintenanceOwnerUser from 'Model/Models/ScheduledMaintenanceOwnerUser'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import User from 'Model/Models/User'; +import DatabaseConfig from "../DatabaseConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import MonitorService from "./MonitorService"; +import ScheduledMaintenanceOwnerTeamService from "./ScheduledMaintenanceOwnerTeamService"; +import ScheduledMaintenanceOwnerUserService from "./ScheduledMaintenanceOwnerUserService"; +import ScheduledMaintenanceStateService from "./ScheduledMaintenanceStateService"; +import ScheduledMaintenanceStateTimelineService from "./ScheduledMaintenanceStateTimelineService"; +import TeamMemberService from "./TeamMemberService"; +import URL from "Common/Types/API/URL"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Typeof from "Common/Types/Typeof"; +import Monitor from "Model/Models/Monitor"; +import Model from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceOwnerTeam from "Model/Models/ScheduledMaintenanceOwnerTeam"; +import ScheduledMaintenanceOwnerUser from "Model/Models/ScheduledMaintenanceOwnerUser"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import User from "Model/Models/User"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 120); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 120); + } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - const scheduledMaintenanceEvents: Array<Model> = await this.findBy({ - query: deleteBy.query, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - projectId: true, - monitors: { - _id: true, - }, - }, - props: { - isRoot: true, - }, - }); + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + const scheduledMaintenanceEvents: Array<Model> = await this.findBy({ + query: deleteBy.query, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + projectId: true, + monitors: { + _id: true, + }, + }, + props: { + isRoot: true, + }, + }); - return { - carryForward: { - scheduledMaintenanceEvents: scheduledMaintenanceEvents, - }, - deleteBy: deleteBy, - }; - } + return { + carryForward: { + scheduledMaintenanceEvents: scheduledMaintenanceEvents, + }, + deleteBy: deleteBy, + }; + } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _deletedItemIds: ObjectID[] - ): Promise<OnDelete<Model>> { - if (onDelete.carryForward?.scheduledMaintenanceEvents) { - for (const scheduledMaintenanceEvent of onDelete?.carryForward - ?.scheduledMaintenanceEvents || []) { - await ScheduledMaintenanceStateTimelineService.enableActiveMonitoringForMonitors( - scheduledMaintenanceEvent - ); - } - } - - return onDelete; - } - - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.props.tenantId) { - throw new BadDataException('ProjectId required to create monitor.'); - } - - const scheduledMaintenanceState: ScheduledMaintenanceState | null = - await ScheduledMaintenanceStateService.findOneBy({ - query: { - projectId: createBy.props.tenantId, - isScheduledState: true, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); - - if (!scheduledMaintenanceState || !scheduledMaintenanceState.id) { - throw new BadDataException( - 'Scheduled state not found for this project. Please add an scheduled event state from settings.' - ); - } - - createBy.data.currentScheduledMaintenanceStateId = - scheduledMaintenanceState.id; - - return { createBy, carryForward: null }; - } - - protected override async onCreateSuccess( - onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - // create new scheduled maintenance state timeline. - - const timeline: ScheduledMaintenanceStateTimeline = - new ScheduledMaintenanceStateTimeline(); - timeline.projectId = createdItem.projectId!; - timeline.scheduledMaintenanceId = createdItem.id!; - timeline.isOwnerNotified = true; // ignore notifying owners because you already notify for Scheduled Event, you don't have to notify them for timeline event. - timeline.shouldStatusPageSubscribersBeNotified = Boolean( - createdItem.shouldStatusPageSubscribersBeNotifiedOnEventCreated + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _deletedItemIds: ObjectID[], + ): Promise<OnDelete<Model>> { + if (onDelete.carryForward?.scheduledMaintenanceEvents) { + for (const scheduledMaintenanceEvent of onDelete?.carryForward + ?.scheduledMaintenanceEvents || []) { + await ScheduledMaintenanceStateTimelineService.enableActiveMonitoringForMonitors( + scheduledMaintenanceEvent, ); - timeline.isStatusPageSubscribersNotified = Boolean( - createdItem.shouldStatusPageSubscribersBeNotifiedOnEventCreated - ); // ignore notifying subscribers because you already notify for Scheduled Event, you don't have to notify them for timeline event. - timeline.scheduledMaintenanceStateId = - createdItem.currentScheduledMaintenanceStateId!; - - await ScheduledMaintenanceStateTimelineService.create({ - data: timeline, - props: { - isRoot: true, - }, - }); - - if ( - createdItem.projectId && - createdItem.id && - onCreate.createBy.miscDataProps && - (onCreate.createBy.miscDataProps['ownerTeams'] || - onCreate.createBy.miscDataProps['ownerUsers']) - ) { - await this.addOwners( - createdItem.projectId!, - createdItem.id!, - (onCreate.createBy.miscDataProps[ - 'ownerUsers' - ] as Array<ObjectID>) || [], - (onCreate.createBy.miscDataProps[ - 'ownerTeams' - ] as Array<ObjectID>) || [], - false, - onCreate.createBy.props - ); - } - - return createdItem; + } } - public async addOwners( - projectId: ObjectID, - scheduledMaintenanceId: ObjectID, - userIds: Array<ObjectID>, - teamIds: Array<ObjectID>, - notifyOwners: boolean, - props: DatabaseCommonInteractionProps - ): Promise<void> { - for (let teamId of teamIds) { - if (typeof teamId === Typeof.String) { - teamId = new ObjectID(teamId.toString()); - } + return onDelete; + } - const teamOwner: ScheduledMaintenanceOwnerTeam = - new ScheduledMaintenanceOwnerTeam(); - teamOwner.scheduledMaintenanceId = scheduledMaintenanceId; - teamOwner.projectId = projectId; - teamOwner.teamId = teamId; - teamOwner.isOwnerNotified = !notifyOwners; - - await ScheduledMaintenanceOwnerTeamService.create({ - data: teamOwner, - props: props, - }); - } - - for (let userId of userIds) { - if (typeof userId === Typeof.String) { - userId = new ObjectID(userId.toString()); - } - const teamOwner: ScheduledMaintenanceOwnerUser = - new ScheduledMaintenanceOwnerUser(); - teamOwner.scheduledMaintenanceId = scheduledMaintenanceId; - teamOwner.projectId = projectId; - teamOwner.isOwnerNotified = !notifyOwners; - teamOwner.userId = userId; - await ScheduledMaintenanceOwnerUserService.create({ - data: teamOwner, - props: props, - }); - } + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.props.tenantId) { + throw new BadDataException("ProjectId required to create monitor."); } - public async getScheduledMaintenanceLinkInDashboard( - projectId: ObjectID, - scheduledMaintenanceId: ObjectID - ): Promise<URL> { - const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl(); + const scheduledMaintenanceState: ScheduledMaintenanceState | null = + await ScheduledMaintenanceStateService.findOneBy({ + query: { + projectId: createBy.props.tenantId, + isScheduledState: true, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - return URL.fromString(dashboardUrl.toString()).addRoute( - `/${projectId.toString()}/scheduled-maintenance-events/${scheduledMaintenanceId.toString()}` + if (!scheduledMaintenanceState || !scheduledMaintenanceState.id) { + throw new BadDataException( + "Scheduled state not found for this project. Please add an scheduled event state from settings.", + ); + } + + createBy.data.currentScheduledMaintenanceStateId = + scheduledMaintenanceState.id; + + return { createBy, carryForward: null }; + } + + protected override async onCreateSuccess( + onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + // create new scheduled maintenance state timeline. + + const timeline: ScheduledMaintenanceStateTimeline = + new ScheduledMaintenanceStateTimeline(); + timeline.projectId = createdItem.projectId!; + timeline.scheduledMaintenanceId = createdItem.id!; + timeline.isOwnerNotified = true; // ignore notifying owners because you already notify for Scheduled Event, you don't have to notify them for timeline event. + timeline.shouldStatusPageSubscribersBeNotified = Boolean( + createdItem.shouldStatusPageSubscribersBeNotifiedOnEventCreated, + ); + timeline.isStatusPageSubscribersNotified = Boolean( + createdItem.shouldStatusPageSubscribersBeNotifiedOnEventCreated, + ); // ignore notifying subscribers because you already notify for Scheduled Event, you don't have to notify them for timeline event. + timeline.scheduledMaintenanceStateId = + createdItem.currentScheduledMaintenanceStateId!; + + await ScheduledMaintenanceStateTimelineService.create({ + data: timeline, + props: { + isRoot: true, + }, + }); + + if ( + createdItem.projectId && + createdItem.id && + onCreate.createBy.miscDataProps && + (onCreate.createBy.miscDataProps["ownerTeams"] || + onCreate.createBy.miscDataProps["ownerUsers"]) + ) { + await this.addOwners( + createdItem.projectId!, + createdItem.id!, + (onCreate.createBy.miscDataProps["ownerUsers"] as Array<ObjectID>) || + [], + (onCreate.createBy.miscDataProps["ownerTeams"] as Array<ObjectID>) || + [], + false, + onCreate.createBy.props, + ); + } + + return createdItem; + } + + public async addOwners( + projectId: ObjectID, + scheduledMaintenanceId: ObjectID, + userIds: Array<ObjectID>, + teamIds: Array<ObjectID>, + notifyOwners: boolean, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + for (let teamId of teamIds) { + if (typeof teamId === Typeof.String) { + teamId = new ObjectID(teamId.toString()); + } + + const teamOwner: ScheduledMaintenanceOwnerTeam = + new ScheduledMaintenanceOwnerTeam(); + teamOwner.scheduledMaintenanceId = scheduledMaintenanceId; + teamOwner.projectId = projectId; + teamOwner.teamId = teamId; + teamOwner.isOwnerNotified = !notifyOwners; + + await ScheduledMaintenanceOwnerTeamService.create({ + data: teamOwner, + props: props, + }); + } + + for (let userId of userIds) { + if (typeof userId === Typeof.String) { + userId = new ObjectID(userId.toString()); + } + const teamOwner: ScheduledMaintenanceOwnerUser = + new ScheduledMaintenanceOwnerUser(); + teamOwner.scheduledMaintenanceId = scheduledMaintenanceId; + teamOwner.projectId = projectId; + teamOwner.isOwnerNotified = !notifyOwners; + teamOwner.userId = userId; + await ScheduledMaintenanceOwnerUserService.create({ + data: teamOwner, + props: props, + }); + } + } + + public async getScheduledMaintenanceLinkInDashboard( + projectId: ObjectID, + scheduledMaintenanceId: ObjectID, + ): Promise<URL> { + const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl(); + + return URL.fromString(dashboardUrl.toString()).addRoute( + `/${projectId.toString()}/scheduled-maintenance-events/${scheduledMaintenanceId.toString()}`, + ); + } + + public async findOwners( + scheduledMaintenanceId: ObjectID, + ): Promise<Array<User>> { + if (!scheduledMaintenanceId) { + throw new BadDataException("scheduledMaintenanceId is required"); + } + + const ownerUsers: Array<ScheduledMaintenanceOwnerUser> = + await ScheduledMaintenanceOwnerUserService.findBy({ + query: { + scheduledMaintenanceId: scheduledMaintenanceId, + }, + select: { + _id: true, + user: { + _id: true, + email: true, + name: true, + }, + }, + + props: { + isRoot: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + }); + + const ownerTeams: Array<ScheduledMaintenanceOwnerTeam> = + await ScheduledMaintenanceOwnerTeamService.findBy({ + query: { + scheduledMaintenanceId: scheduledMaintenanceId, + }, + select: { + _id: true, + teamId: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const users: Array<User> = + ownerUsers.map((ownerUser: ScheduledMaintenanceOwnerUser) => { + return ownerUser.user!; + }) || []; + + if (ownerTeams.length > 0) { + const teamIds: Array<ObjectID> = + ownerTeams.map((ownerTeam: ScheduledMaintenanceOwnerTeam) => { + return ownerTeam.teamId!; + }) || []; + + const teamUsers: Array<User> = + await TeamMemberService.getUsersInTeams(teamIds); + + for (const teamUser of teamUsers) { + //check if the user is already added. + const isUserAlreadyAdded: User | undefined = users.find( + (user: User) => { + return user.id!.toString() === teamUser.id!.toString(); + }, ); + + if (!isUserAlreadyAdded) { + users.push(teamUser); + } + } } - public async findOwners( - scheduledMaintenanceId: ObjectID - ): Promise<Array<User>> { - if (!scheduledMaintenanceId) { - throw new BadDataException('scheduledMaintenanceId is required'); - } + return users; + } - const ownerUsers: Array<ScheduledMaintenanceOwnerUser> = - await ScheduledMaintenanceOwnerUserService.findBy({ - query: { - scheduledMaintenanceId: scheduledMaintenanceId, - }, - select: { - _id: true, - user: { - _id: true, - email: true, - name: true, - }, - }, - - props: { - isRoot: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - }); - - const ownerTeams: Array<ScheduledMaintenanceOwnerTeam> = - await ScheduledMaintenanceOwnerTeamService.findBy({ - query: { - scheduledMaintenanceId: scheduledMaintenanceId, - }, - select: { - _id: true, - teamId: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - const users: Array<User> = - ownerUsers.map((ownerUser: ScheduledMaintenanceOwnerUser) => { - return ownerUser.user!; - }) || []; - - if (ownerTeams.length > 0) { - const teamIds: Array<ObjectID> = - ownerTeams.map((ownerTeam: ScheduledMaintenanceOwnerTeam) => { - return ownerTeam.teamId!; - }) || []; - - const teamUsers: Array<User> = - await TeamMemberService.getUsersInTeams(teamIds); - - for (const teamUser of teamUsers) { - //check if the user is already added. - const isUserAlreadyAdded: User | undefined = users.find( - (user: User) => { - return user.id!.toString() === teamUser.id!.toString(); - } - ); - - if (!isUserAlreadyAdded) { - users.push(teamUser); - } - } - } - - return users; + public async changeAttachedMonitorStates( + item: Model, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + if (!item.projectId) { + throw new BadDataException("projectId is required"); } - public async changeAttachedMonitorStates( - item: Model, - props: DatabaseCommonInteractionProps - ): Promise<void> { - if (!item.projectId) { - throw new BadDataException('projectId is required'); - } - - if (!item.id) { - throw new BadDataException('id is required'); - } - - if (item.changeMonitorStatusToId && item.projectId) { - // change status of all the monitors. - await MonitorService.changeMonitorStatus( - item.projectId, - item.monitors?.map((monitor: Monitor) => { - return new ObjectID(monitor._id || ''); - }) || [], - item.changeMonitorStatusToId, - true, // notify owners - 'Changed because of scheduled maintenance event: ' + - item.id.toString(), - undefined, - props - ); - } + if (!item.id) { + throw new BadDataException("id is required"); } - protected override async onUpdateSuccess( - onUpdate: OnUpdate<Model>, - updatedItemIds: ObjectID[] - ): Promise<OnUpdate<Model>> { - if ( - onUpdate.updateBy.data.currentScheduledMaintenanceStateId && - onUpdate.updateBy.props.tenantId - ) { - for (const itemId of updatedItemIds) { - await this.changeScheduledMaintenanceState({ - projectId: onUpdate.updateBy.props.tenantId as ObjectID, - scheduledMaintenanceId: itemId, - scheduledMaintenanceStateId: onUpdate.updateBy.data - .currentScheduledMaintenanceStateId as ObjectID, - shouldNotifyStatusPageSubscribers: true, - isSubscribersNotified: false, - notifyOwners: true, // notifyOwners = true - props: { - isRoot: true, - }, - }); - } - } - - return onUpdate; + if (item.changeMonitorStatusToId && item.projectId) { + // change status of all the monitors. + await MonitorService.changeMonitorStatus( + item.projectId, + item.monitors?.map((monitor: Monitor) => { + return new ObjectID(monitor._id || ""); + }) || [], + item.changeMonitorStatusToId, + true, // notify owners + "Changed because of scheduled maintenance event: " + item.id.toString(), + undefined, + props, + ); } + } - public async changeScheduledMaintenanceState(data: { - projectId: ObjectID; - scheduledMaintenanceId: ObjectID; - scheduledMaintenanceStateId: ObjectID; - shouldNotifyStatusPageSubscribers: boolean; - isSubscribersNotified: boolean; - notifyOwners: boolean; - props: DatabaseCommonInteractionProps; - }): Promise<void> { - const { - projectId, - scheduledMaintenanceId, - scheduledMaintenanceStateId, - notifyOwners, - shouldNotifyStatusPageSubscribers, - isSubscribersNotified, - props, - } = data; - - if (!projectId) { - throw new BadDataException('projectId is required'); - } - - if (!scheduledMaintenanceId) { - throw new BadDataException('scheduledMaintenanceId is required'); - } - - if (!scheduledMaintenanceStateId) { - throw new BadDataException( - 'scheduledMaintenanceStateId is required' - ); - } - - // get last scheduled status timeline. - const lastState: ScheduledMaintenanceStateTimeline | null = - await ScheduledMaintenanceStateTimelineService.findOneBy({ - query: { - scheduledMaintenanceId: scheduledMaintenanceId, - projectId: projectId, - }, - select: { - _id: true, - scheduledMaintenanceStateId: true, - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - }); - - if ( - lastState && - lastState.scheduledMaintenanceStateId && - lastState.scheduledMaintenanceStateId.toString() === - scheduledMaintenanceStateId.toString() - ) { - return; - } - - const statusTimeline: ScheduledMaintenanceStateTimeline = - new ScheduledMaintenanceStateTimeline(); - - statusTimeline.scheduledMaintenanceId = scheduledMaintenanceId; - statusTimeline.scheduledMaintenanceStateId = - scheduledMaintenanceStateId; - statusTimeline.projectId = projectId; - statusTimeline.isOwnerNotified = !notifyOwners; - statusTimeline.isStatusPageSubscribersNotified = isSubscribersNotified; - statusTimeline.shouldStatusPageSubscribersBeNotified = - shouldNotifyStatusPageSubscribers; - - await ScheduledMaintenanceStateTimelineService.create({ - data: statusTimeline, - props: props, - }); - - await this.updateBy({ - data: { - currentScheduledMaintenanceStateId: - scheduledMaintenanceStateId.id, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - query: { - _id: scheduledMaintenanceId.toString()!, - }, - props: { - isRoot: true, - }, + protected override async onUpdateSuccess( + onUpdate: OnUpdate<Model>, + updatedItemIds: ObjectID[], + ): Promise<OnUpdate<Model>> { + if ( + onUpdate.updateBy.data.currentScheduledMaintenanceStateId && + onUpdate.updateBy.props.tenantId + ) { + for (const itemId of updatedItemIds) { + await this.changeScheduledMaintenanceState({ + projectId: onUpdate.updateBy.props.tenantId as ObjectID, + scheduledMaintenanceId: itemId, + scheduledMaintenanceStateId: onUpdate.updateBy.data + .currentScheduledMaintenanceStateId as ObjectID, + shouldNotifyStatusPageSubscribers: true, + isSubscribersNotified: false, + notifyOwners: true, // notifyOwners = true + props: { + isRoot: true, + }, }); + } } + + return onUpdate; + } + + public async changeScheduledMaintenanceState(data: { + projectId: ObjectID; + scheduledMaintenanceId: ObjectID; + scheduledMaintenanceStateId: ObjectID; + shouldNotifyStatusPageSubscribers: boolean; + isSubscribersNotified: boolean; + notifyOwners: boolean; + props: DatabaseCommonInteractionProps; + }): Promise<void> { + const { + projectId, + scheduledMaintenanceId, + scheduledMaintenanceStateId, + notifyOwners, + shouldNotifyStatusPageSubscribers, + isSubscribersNotified, + props, + } = data; + + if (!projectId) { + throw new BadDataException("projectId is required"); + } + + if (!scheduledMaintenanceId) { + throw new BadDataException("scheduledMaintenanceId is required"); + } + + if (!scheduledMaintenanceStateId) { + throw new BadDataException("scheduledMaintenanceStateId is required"); + } + + // get last scheduled status timeline. + const lastState: ScheduledMaintenanceStateTimeline | null = + await ScheduledMaintenanceStateTimelineService.findOneBy({ + query: { + scheduledMaintenanceId: scheduledMaintenanceId, + projectId: projectId, + }, + select: { + _id: true, + scheduledMaintenanceStateId: true, + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + }); + + if ( + lastState && + lastState.scheduledMaintenanceStateId && + lastState.scheduledMaintenanceStateId.toString() === + scheduledMaintenanceStateId.toString() + ) { + return; + } + + const statusTimeline: ScheduledMaintenanceStateTimeline = + new ScheduledMaintenanceStateTimeline(); + + statusTimeline.scheduledMaintenanceId = scheduledMaintenanceId; + statusTimeline.scheduledMaintenanceStateId = scheduledMaintenanceStateId; + statusTimeline.projectId = projectId; + statusTimeline.isOwnerNotified = !notifyOwners; + statusTimeline.isStatusPageSubscribersNotified = isSubscribersNotified; + statusTimeline.shouldStatusPageSubscribersBeNotified = + shouldNotifyStatusPageSubscribers; + + await ScheduledMaintenanceStateTimelineService.create({ + data: statusTimeline, + props: props, + }); + + await this.updateBy({ + data: { + currentScheduledMaintenanceStateId: scheduledMaintenanceStateId.id, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + query: { + _id: scheduledMaintenanceId.toString()!, + }, + props: { + isRoot: true, + }, + }); + } } export default new Service(); diff --git a/CommonServer/Services/ScheduledMaintenanceStateService.ts b/CommonServer/Services/ScheduledMaintenanceStateService.ts index 698ceed9ad..e9187dfa75 100644 --- a/CommonServer/Services/ScheduledMaintenanceStateService.ts +++ b/CommonServer/Services/ScheduledMaintenanceStateService.ts @@ -1,159 +1,159 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Model from 'Model/Models/ScheduledMaintenanceState'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Model from "Model/Models/ScheduledMaintenanceState"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.order) { + throw new BadDataException("Incident State order is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.order) { - throw new BadDataException('Incident State order is required'); - } + if (!createBy.data.projectId) { + throw new BadDataException("Incident State projectId is required"); + } - if (!createBy.data.projectId) { - throw new BadDataException('Incident State projectId is required'); - } + await this.rearrangeOrder( + createBy.data.order, + createBy.data.projectId, + true, + ); + return { + createBy: createBy, + carryForward: null, + }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting scheduled maintenance states. Please try the delete with objectId", + ); + } + + let scheduledMaintenanceState: Model | null = null; + + if (!deleteBy.props.isRoot) { + scheduledMaintenanceState = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + projectId: true, + }, + }); + } + + return { + deleteBy, + carryForward: scheduledMaintenanceState, + }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const scheduledMaintenanceState: Model | null = onDelete.carryForward; + + if (!deleteBy.props.isRoot && scheduledMaintenanceState) { + if ( + scheduledMaintenanceState && + scheduledMaintenanceState.order && + scheduledMaintenanceState.projectId + ) { await this.rearrangeOrder( - createBy.data.order, - createBy.data.projectId, - true + scheduledMaintenanceState.order, + scheduledMaintenanceState.projectId, + false, ); - - return { - createBy: createBy, - carryForward: null, - }; + } } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting scheduled maintenance states. Please try the delete with objectId' - ); - } + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - let scheduledMaintenanceState: Model | null = null; - - if (!deleteBy.props.isRoot) { - scheduledMaintenanceState = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - projectId: true, - }, - }); - } - - return { - deleteBy, - carryForward: scheduledMaintenanceState, - }; + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.order && !updateBy.props.isRoot) { + throw new BadDataException( + "Scheduled Maintenance State order should not be updated. Delete this scheduled maintenance state and create a new state with the right order.", + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const scheduledMaintenanceState: Model | null = onDelete.carryForward; + return { updateBy, carryForward: null }; + } - if (!deleteBy.props.isRoot && scheduledMaintenanceState) { - if ( - scheduledMaintenanceState && - scheduledMaintenanceState.order && - scheduledMaintenanceState.projectId - ) { - await this.rearrangeOrder( - scheduledMaintenanceState.order, - scheduledMaintenanceState.projectId, - false - ); - } - } + private async rearrangeOrder( + currentOrder: number, + projectId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get scheduledMaintenance with this order. + const scheduledMaintenanceStates: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + projectId: projectId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); - return { - deleteBy: deleteBy, - carryForward: null, - }; - } - - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if (updateBy.data.order && !updateBy.props.isRoot) { - throw new BadDataException( - 'Scheduled Maintenance State order should not be updated. Delete this scheduled maintenance state and create a new state with the right order.' - ); - } - - return { updateBy, carryForward: null }; - } - - private async rearrangeOrder( - currentOrder: number, - projectId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get scheduledMaintenance with this order. - const scheduledMaintenanceStates: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - projectId: projectId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); - - let newOrder: number = currentOrder; - - for (const scheduledMaintenanceState of scheduledMaintenanceStates) { - if (increaseOrder) { - newOrder = scheduledMaintenanceState.order! + 1; - } else { - newOrder = scheduledMaintenanceState.order! - 1; - } - - await this.updateOneBy({ - query: { - _id: scheduledMaintenanceState._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, - }); - } + let newOrder: number = currentOrder; + + for (const scheduledMaintenanceState of scheduledMaintenanceStates) { + if (increaseOrder) { + newOrder = scheduledMaintenanceState.order! + 1; + } else { + newOrder = scheduledMaintenanceState.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: scheduledMaintenanceState._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); } + } } export default new Service(); diff --git a/CommonServer/Services/ScheduledMaintenanceStateTimelineService.ts b/CommonServer/Services/ScheduledMaintenanceStateTimelineService.ts index 11df401327..834c83ccf0 100644 --- a/CommonServer/Services/ScheduledMaintenanceStateTimelineService.ts +++ b/CommonServer/Services/ScheduledMaintenanceStateTimelineService.ts @@ -1,509 +1,497 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import DatabaseService from './DatabaseService'; -import MonitorService from './MonitorService'; -import MonitorStatusService from './MonitorStatusService'; -import MonitorStatusTimelineService from './MonitorStatusTimelineService'; -import ScheduledMaintenancePublicNoteService from './ScheduledMaintenancePublicNoteService'; -import ScheduledMaintenanceService from './ScheduledMaintenanceService'; -import ScheduledMaintenanceStateService from './ScheduledMaintenanceStateService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import DatabaseService from "./DatabaseService"; +import MonitorService from "./MonitorService"; +import MonitorStatusService from "./MonitorStatusService"; +import MonitorStatusTimelineService from "./MonitorStatusTimelineService"; +import ScheduledMaintenancePublicNoteService from "./ScheduledMaintenancePublicNoteService"; +import ScheduledMaintenanceService from "./ScheduledMaintenanceService"; +import ScheduledMaintenanceStateService from "./ScheduledMaintenanceStateService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(ScheduledMaintenanceStateTimeline, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 120); + public constructor(postgresDatabase?: PostgresDatabase) { + super(ScheduledMaintenanceStateTimeline, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 120); + } + + protected override async onBeforeCreate( + createBy: CreateBy<ScheduledMaintenanceStateTimeline>, + ): Promise<OnCreate<ScheduledMaintenanceStateTimeline>> { + if (!createBy.data.scheduledMaintenanceId) { + throw new BadDataException("scheduledMaintenanceId is null"); } - protected override async onBeforeCreate( - createBy: CreateBy<ScheduledMaintenanceStateTimeline> - ): Promise<OnCreate<ScheduledMaintenanceStateTimeline>> { - if (!createBy.data.scheduledMaintenanceId) { - throw new BadDataException('scheduledMaintenanceId is null'); - } - - if (!createBy.data.startsAt) { - createBy.data.startsAt = OneUptimeDate.getCurrentDate(); - } - - const lastScheduledMaintenanceStateTimeline: ScheduledMaintenanceStateTimeline | null = - await this.findOneBy({ - query: { - scheduledMaintenanceId: - createBy.data.scheduledMaintenanceId, - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - const publicNote: string | undefined = ( - createBy.miscDataProps as JSONObject | undefined - )?.['publicNote'] as string | undefined; - - if (publicNote) { - const scheduledMaintenancePublicNote: ScheduledMaintenancePublicNote = - new ScheduledMaintenancePublicNote(); - scheduledMaintenancePublicNote.scheduledMaintenanceId = - createBy.data.scheduledMaintenanceId; - scheduledMaintenancePublicNote.note = publicNote; - scheduledMaintenancePublicNote.postedAt = createBy.data.startsAt; - scheduledMaintenancePublicNote.createdAt = createBy.data.startsAt; - scheduledMaintenancePublicNote.projectId = createBy.data.projectId!; - scheduledMaintenancePublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated = - Boolean(createBy.data.shouldStatusPageSubscribersBeNotified); - - // mark status page subscribers as notified for this state change because we dont want to send duplicate (two) emails one for public note and one for state change. - if ( - scheduledMaintenancePublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated - ) { - createBy.data.isStatusPageSubscribersNotified = true; - } - - await ScheduledMaintenancePublicNoteService.create({ - data: scheduledMaintenancePublicNote, - props: createBy.props, - }); - } - - return { - createBy, - carryForward: { - lastScheduledMaintenanceStateTimelineId: - lastScheduledMaintenanceStateTimeline?.id || null, - }, - }; + if (!createBy.data.startsAt) { + createBy.data.startsAt = OneUptimeDate.getCurrentDate(); } - protected override async onCreateSuccess( - onCreate: OnCreate<ScheduledMaintenanceStateTimeline>, - createdItem: ScheduledMaintenanceStateTimeline - ): Promise<ScheduledMaintenanceStateTimeline> { - if (!createdItem.scheduledMaintenanceId) { - throw new BadDataException('scheduledMaintenanceId is null'); - } + const lastScheduledMaintenanceStateTimeline: ScheduledMaintenanceStateTimeline | null = + await this.findOneBy({ + query: { + scheduledMaintenanceId: createBy.data.scheduledMaintenanceId, + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); - if (!createdItem.scheduledMaintenanceStateId) { - throw new BadDataException('scheduledMaintenanceStateId is null'); - } + const publicNote: string | undefined = ( + createBy.miscDataProps as JSONObject | undefined + )?.["publicNote"] as string | undefined; - // update the last status as ended. + if (publicNote) { + const scheduledMaintenancePublicNote: ScheduledMaintenancePublicNote = + new ScheduledMaintenancePublicNote(); + scheduledMaintenancePublicNote.scheduledMaintenanceId = + createBy.data.scheduledMaintenanceId; + scheduledMaintenancePublicNote.note = publicNote; + scheduledMaintenancePublicNote.postedAt = createBy.data.startsAt; + scheduledMaintenancePublicNote.createdAt = createBy.data.startsAt; + scheduledMaintenancePublicNote.projectId = createBy.data.projectId!; + scheduledMaintenancePublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated = + Boolean(createBy.data.shouldStatusPageSubscribersBeNotified); - if (onCreate.carryForward.lastScheduledMaintenanceStateTimelineId) { - await this.updateOneById({ - id: onCreate.carryForward - .lastScheduledMaintenanceStateTimelineId!, - data: { - endsAt: - createdItem.createdAt || OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); - } + // mark status page subscribers as notified for this state change because we dont want to send duplicate (two) emails one for public note and one for state change. + if ( + scheduledMaintenancePublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated + ) { + createBy.data.isStatusPageSubscribersNotified = true; + } - await ScheduledMaintenanceService.updateOneBy({ - query: { - _id: createdItem.scheduledMaintenanceId?.toString(), - }, + await ScheduledMaintenancePublicNoteService.create({ + data: scheduledMaintenancePublicNote, + props: createBy.props, + }); + } + + return { + createBy, + carryForward: { + lastScheduledMaintenanceStateTimelineId: + lastScheduledMaintenanceStateTimeline?.id || null, + }, + }; + } + + protected override async onCreateSuccess( + onCreate: OnCreate<ScheduledMaintenanceStateTimeline>, + createdItem: ScheduledMaintenanceStateTimeline, + ): Promise<ScheduledMaintenanceStateTimeline> { + if (!createdItem.scheduledMaintenanceId) { + throw new BadDataException("scheduledMaintenanceId is null"); + } + + if (!createdItem.scheduledMaintenanceStateId) { + throw new BadDataException("scheduledMaintenanceStateId is null"); + } + + // update the last status as ended. + + if (onCreate.carryForward.lastScheduledMaintenanceStateTimelineId) { + await this.updateOneById({ + id: onCreate.carryForward.lastScheduledMaintenanceStateTimelineId!, + data: { + endsAt: createdItem.createdAt || OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); + } + + await ScheduledMaintenanceService.updateOneBy({ + query: { + _id: createdItem.scheduledMaintenanceId?.toString(), + }, + data: { + currentScheduledMaintenanceStateId: + createdItem.scheduledMaintenanceStateId, + }, + props: { + isRoot: true, + }, + }); + + // TODO: DELETE THIS WHEN WORKFLOW IS IMPLEMENMTED. + // check if this is resolved state, and if it is then resolve all the monitors. + + const isResolvedState: ScheduledMaintenanceState | null = + await ScheduledMaintenanceStateService.findOneBy({ + query: { + _id: createdItem.scheduledMaintenanceStateId.toString()!, + isResolvedState: true, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + const isEndedState: ScheduledMaintenanceState | null = + await ScheduledMaintenanceStateService.findOneBy({ + query: { + _id: createdItem.scheduledMaintenanceStateId.toString()!, + isEndedState: true, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + const isOngoingState: ScheduledMaintenanceState | null = + await ScheduledMaintenanceStateService.findOneBy({ + query: { + _id: createdItem.scheduledMaintenanceStateId.toString()!, + isOngoingState: true, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + const scheduledMaintenanceEvent: ScheduledMaintenance | null = + await ScheduledMaintenanceService.findOneBy({ + query: { + _id: createdItem.scheduledMaintenanceId?.toString(), + }, + select: { + _id: true, + projectId: true, + monitors: { + _id: true, + }, + }, + props: { + isRoot: true, + }, + }); + + if (isOngoingState) { + if ( + scheduledMaintenanceEvent && + scheduledMaintenanceEvent.monitors && + scheduledMaintenanceEvent.monitors.length > 0 + ) { + for (const monitor of scheduledMaintenanceEvent.monitors) { + await MonitorService.updateOneById({ + id: monitor.id!, data: { - currentScheduledMaintenanceStateId: - createdItem.scheduledMaintenanceStateId, + disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: true, /// This will stop active monitoring. }, props: { - isRoot: true, + isRoot: true, }, + }); + } + } + } + + if (isResolvedState || isEndedState) { + // resolve all the monitors. + await this.enableActiveMonitoringForMonitors(scheduledMaintenanceEvent!); + } + + return createdItem; + } + + public async enableActiveMonitoringForMonitors( + scheduledMaintenanceEvent: ScheduledMaintenance, + ): Promise<void> { + if ( + scheduledMaintenanceEvent && + scheduledMaintenanceEvent.monitors && + scheduledMaintenanceEvent.monitors.length > 0 + ) { + // get resolved monitor state. + const resolvedMonitorState: MonitorStatus | null = + await MonitorStatusService.findOneBy({ + query: { + projectId: scheduledMaintenanceEvent.projectId!, + isOperationalState: true, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, }); - // TODO: DELETE THIS WHEN WORKFLOW IS IMPLEMENMTED. - // check if this is resolved state, and if it is then resolve all the monitors. + // check if this monitor is not in this status already. - const isResolvedState: ScheduledMaintenanceState | null = - await ScheduledMaintenanceStateService.findOneBy({ - query: { - _id: createdItem.scheduledMaintenanceStateId.toString()!, - isResolvedState: true, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); + if (resolvedMonitorState) { + for (const monitor of scheduledMaintenanceEvent.monitors) { + // check if the monitor is not in this status already. - const isEndedState: ScheduledMaintenanceState | null = - await ScheduledMaintenanceStateService.findOneBy({ - query: { - _id: createdItem.scheduledMaintenanceStateId.toString()!, - isEndedState: true, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); + const dbMonitor: Monitor | null = await MonitorService.findOneById({ + id: monitor.id!, + select: { + currentMonitorStatusId: true, + }, + props: { + isRoot: true, + }, + }); - const isOngoingState: ScheduledMaintenanceState | null = - await ScheduledMaintenanceStateService.findOneBy({ - query: { - _id: createdItem.scheduledMaintenanceStateId.toString()!, - isOngoingState: true, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - const scheduledMaintenanceEvent: ScheduledMaintenance | null = - await ScheduledMaintenanceService.findOneBy({ - query: { - _id: createdItem.scheduledMaintenanceId?.toString(), - }, - select: { - _id: true, - projectId: true, - monitors: { - _id: true, - }, - }, - props: { - isRoot: true, - }, - }); - - if (isOngoingState) { - if ( - scheduledMaintenanceEvent && - scheduledMaintenanceEvent.monitors && - scheduledMaintenanceEvent.monitors.length > 0 - ) { - for (const monitor of scheduledMaintenanceEvent.monitors) { - await MonitorService.updateOneById({ - id: monitor.id!, - data: { - disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: - true, /// This will stop active monitoring. - }, - props: { - isRoot: true, - }, - }); - } - } - } - - if (isResolvedState || isEndedState) { - // resolve all the monitors. - await this.enableActiveMonitoringForMonitors( - scheduledMaintenanceEvent! + const hasMoreOngoingScheduledMaintenanceEvents: boolean = + await this.hasThisMonitorMoreOngoingScheduledMaintenanceEvents( + monitor.id!, ); - } - return createdItem; + if (hasMoreOngoingScheduledMaintenanceEvents) { + // dont do anything because other events are active at the same time. + continue; + } + + await MonitorService.updateOneById({ + id: monitor.id!, + data: { + disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: false, /// This will start active monitoring again. + }, + props: { + isRoot: true, + }, + }); + + if ( + dbMonitor?.currentMonitorStatusId?.toString() === + resolvedMonitorState.id?.toString() + ) { + // if already in resolved state then skip. + continue; + } + + const monitorStatusTimeline: MonitorStatusTimeline = + new MonitorStatusTimeline(); + monitorStatusTimeline.monitorId = monitor.id!; + monitorStatusTimeline.projectId = + scheduledMaintenanceEvent.projectId!; + monitorStatusTimeline.monitorStatusId = resolvedMonitorState.id!; + + await MonitorStatusTimelineService.create({ + data: monitorStatusTimeline, + props: { + isRoot: true, + }, + }); + } + } + } + } + + public async hasThisMonitorMoreOngoingScheduledMaintenanceEvents( + id: ObjectID, + ): Promise<boolean> { + const count: PositiveNumber = await ScheduledMaintenanceService.countBy({ + query: { + monitors: QueryHelper.inRelationArray([id]), + currentScheduledMaintenanceState: { + isOngoingState: true, + }, + }, + props: { + isRoot: true, + }, + }); + + if (count.toNumber() > 0) { + return true; } - public async enableActiveMonitoringForMonitors( - scheduledMaintenanceEvent: ScheduledMaintenance - ): Promise<void> { - if ( - scheduledMaintenanceEvent && - scheduledMaintenanceEvent.monitors && - scheduledMaintenanceEvent.monitors.length > 0 - ) { - // get resolved monitor state. - const resolvedMonitorState: MonitorStatus | null = - await MonitorStatusService.findOneBy({ - query: { - projectId: scheduledMaintenanceEvent.projectId!, - isOperationalState: true, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); + return false; + } - // check if this monitor is not in this status already. + protected override async onBeforeDelete( + deleteBy: DeleteBy<ScheduledMaintenanceStateTimeline>, + ): Promise<OnDelete<ScheduledMaintenanceStateTimeline>> { + if (deleteBy.query._id) { + const scheduledMaintenanceStateTimelineToBeDeleted: ScheduledMaintenanceStateTimeline | null = + await this.findOneById({ + id: new ObjectID(deleteBy.query._id as string), + select: { + scheduledMaintenanceId: true, + startsAt: true, + }, + props: { + isRoot: true, + }, + }); - if (resolvedMonitorState) { - for (const monitor of scheduledMaintenanceEvent.monitors) { - // check if the monitor is not in this status already. + const scheduledMaintenanceId: ObjectID | undefined = + scheduledMaintenanceStateTimelineToBeDeleted?.scheduledMaintenanceId; - const dbMonitor: Monitor | null = - await MonitorService.findOneById({ - id: monitor.id!, - select: { - currentMonitorStatusId: true, - }, - props: { - isRoot: true, - }, - }); + if (scheduledMaintenanceId) { + const scheduledMaintenanceStateTimeline: PositiveNumber = + await this.countBy({ + query: { + scheduledMaintenanceId: scheduledMaintenanceId, + }, + props: { + isRoot: true, + }, + }); - const hasMoreOngoingScheduledMaintenanceEvents: boolean = - await this.hasThisMonitorMoreOngoingScheduledMaintenanceEvents( - monitor.id! - ); - - if (hasMoreOngoingScheduledMaintenanceEvents) { - // dont do anything because other events are active at the same time. - continue; - } - - await MonitorService.updateOneById({ - id: monitor.id!, - data: { - disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: - false, /// This will start active monitoring again. - }, - props: { - isRoot: true, - }, - }); - - if ( - dbMonitor?.currentMonitorStatusId?.toString() === - resolvedMonitorState.id?.toString() - ) { - // if already in resolved state then skip. - continue; - } - - const monitorStatusTimeline: MonitorStatusTimeline = - new MonitorStatusTimeline(); - monitorStatusTimeline.monitorId = monitor.id!; - monitorStatusTimeline.projectId = - scheduledMaintenanceEvent.projectId!; - monitorStatusTimeline.monitorStatusId = - resolvedMonitorState.id!; - - await MonitorStatusTimelineService.create({ - data: monitorStatusTimeline, - props: { - isRoot: true, - }, - }); - } - } + if (scheduledMaintenanceStateTimeline.isOne()) { + throw new BadDataException( + "Cannot delete the only state timeline. Scheduled Maintenance should have at least one state in its timeline.", + ); } - } - public async hasThisMonitorMoreOngoingScheduledMaintenanceEvents( - id: ObjectID - ): Promise<boolean> { - const count: PositiveNumber = await ScheduledMaintenanceService.countBy( - { + if (scheduledMaintenanceStateTimelineToBeDeleted?.startsAt) { + const beforeState: ScheduledMaintenanceStateTimeline | null = + await this.findOneBy({ + query: { + scheduledMaintenanceId: scheduledMaintenanceId, + startsAt: QueryHelper.lessThan( + scheduledMaintenanceStateTimelineToBeDeleted?.startsAt, + ), + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + startsAt: true, + }, + }); + + if (beforeState) { + const afterState: ScheduledMaintenanceStateTimeline | null = + await this.findOneBy({ query: { - monitors: QueryHelper.inRelationArray([id]), - currentScheduledMaintenanceState: { - isOngoingState: true, - }, + scheduledMaintenanceId: scheduledMaintenanceId, + startsAt: QueryHelper.greaterThan( + scheduledMaintenanceStateTimelineToBeDeleted?.startsAt, + ), + }, + sort: { + createdAt: SortOrder.Ascending, }, props: { - isRoot: true, + isRoot: true, }, + select: { + _id: true, + startsAt: true, + }, + }); + + if (!afterState) { + // if there's nothing after then end date of before state is null. + + await this.updateOneById({ + id: beforeState.id!, + data: { + endsAt: null as any, + }, + props: { + isRoot: true, + }, + }); + } else { + // if there's something after then end date of before state is start date of after state. + + await this.updateOneById({ + id: beforeState.id!, + data: { + endsAt: afterState.startsAt!, + }, + props: { + isRoot: true, + }, + }); } - ); - - if (count.toNumber() > 0) { - return true; + } } + } - return false; + return { deleteBy, carryForward: scheduledMaintenanceId }; } - protected override async onBeforeDelete( - deleteBy: DeleteBy<ScheduledMaintenanceStateTimeline> - ): Promise<OnDelete<ScheduledMaintenanceStateTimeline>> { - if (deleteBy.query._id) { - const scheduledMaintenanceStateTimelineToBeDeleted: ScheduledMaintenanceStateTimeline | null = - await this.findOneById({ - id: new ObjectID(deleteBy.query._id as string), - select: { - scheduledMaintenanceId: true, - startsAt: true, - }, - props: { - isRoot: true, - }, - }); + return { deleteBy, carryForward: null }; + } - const scheduledMaintenanceId: ObjectID | undefined = - scheduledMaintenanceStateTimelineToBeDeleted?.scheduledMaintenanceId; + protected override async onDeleteSuccess( + onDelete: OnDelete<ScheduledMaintenanceStateTimeline>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<ScheduledMaintenanceStateTimeline>> { + if (onDelete.carryForward) { + // this is scheduledMaintenanceId. + const scheduledMaintenanceId: ObjectID = + onDelete.carryForward as ObjectID; - if (scheduledMaintenanceId) { - const scheduledMaintenanceStateTimeline: PositiveNumber = - await this.countBy({ - query: { - scheduledMaintenanceId: scheduledMaintenanceId, - }, - props: { - isRoot: true, - }, - }); + // get last status of this monitor. + const scheduledMaintenanceStateTimeline: ScheduledMaintenanceStateTimeline | null = + await this.findOneBy({ + query: { + scheduledMaintenanceId: scheduledMaintenanceId, + }, + sort: { + createdAt: SortOrder.Descending, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + scheduledMaintenanceStateId: true, + }, + }); - if (scheduledMaintenanceStateTimeline.isOne()) { - throw new BadDataException( - 'Cannot delete the only state timeline. Scheduled Maintenance should have at least one state in its timeline.' - ); - } - - if (scheduledMaintenanceStateTimelineToBeDeleted?.startsAt) { - const beforeState: ScheduledMaintenanceStateTimeline | null = - await this.findOneBy({ - query: { - scheduledMaintenanceId: scheduledMaintenanceId, - startsAt: QueryHelper.lessThan( - scheduledMaintenanceStateTimelineToBeDeleted?.startsAt - ), - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - startsAt: true, - }, - }); - - if (beforeState) { - const afterState: ScheduledMaintenanceStateTimeline | null = - await this.findOneBy({ - query: { - scheduledMaintenanceId: - scheduledMaintenanceId, - startsAt: QueryHelper.greaterThan( - scheduledMaintenanceStateTimelineToBeDeleted?.startsAt - ), - }, - sort: { - createdAt: SortOrder.Ascending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - startsAt: true, - }, - }); - - if (!afterState) { - // if there's nothing after then end date of before state is null. - - await this.updateOneById({ - id: beforeState.id!, - data: { - endsAt: null as any, - }, - props: { - isRoot: true, - }, - }); - } else { - // if there's something after then end date of before state is start date of after state. - - await this.updateOneById({ - id: beforeState.id!, - data: { - endsAt: afterState.startsAt!, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { deleteBy, carryForward: scheduledMaintenanceId }; - } - - return { deleteBy, carryForward: null }; + if ( + scheduledMaintenanceStateTimeline && + scheduledMaintenanceStateTimeline.scheduledMaintenanceStateId + ) { + await ScheduledMaintenanceService.updateOneBy({ + query: { + _id: scheduledMaintenanceId.toString(), + }, + data: { + currentScheduledMaintenanceStateId: + scheduledMaintenanceStateTimeline.scheduledMaintenanceStateId, + }, + props: { + isRoot: true, + }, + }); + } } - protected override async onDeleteSuccess( - onDelete: OnDelete<ScheduledMaintenanceStateTimeline>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<ScheduledMaintenanceStateTimeline>> { - if (onDelete.carryForward) { - // this is scheduledMaintenanceId. - const scheduledMaintenanceId: ObjectID = - onDelete.carryForward as ObjectID; - - // get last status of this monitor. - const scheduledMaintenanceStateTimeline: ScheduledMaintenanceStateTimeline | null = - await this.findOneBy({ - query: { - scheduledMaintenanceId: scheduledMaintenanceId, - }, - sort: { - createdAt: SortOrder.Descending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - scheduledMaintenanceStateId: true, - }, - }); - - if ( - scheduledMaintenanceStateTimeline && - scheduledMaintenanceStateTimeline.scheduledMaintenanceStateId - ) { - await ScheduledMaintenanceService.updateOneBy({ - query: { - _id: scheduledMaintenanceId.toString(), - }, - data: { - currentScheduledMaintenanceStateId: - scheduledMaintenanceStateTimeline.scheduledMaintenanceStateId, - }, - props: { - isRoot: true, - }, - }); - } - } - - return onDelete; - } + return onDelete; + } } export default new Service(); diff --git a/CommonServer/Services/ServiceCatalogOwnerTeamService.ts b/CommonServer/Services/ServiceCatalogOwnerTeamService.ts index 85e552f276..051294f155 100644 --- a/CommonServer/Services/ServiceCatalogOwnerTeamService.ts +++ b/CommonServer/Services/ServiceCatalogOwnerTeamService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ServiceCatalogOwnerTeam'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ServiceCatalogOwnerTeam"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ServiceCatalogOwnerUserService.ts b/CommonServer/Services/ServiceCatalogOwnerUserService.ts index b7262ac32f..6f085a0b41 100644 --- a/CommonServer/Services/ServiceCatalogOwnerUserService.ts +++ b/CommonServer/Services/ServiceCatalogOwnerUserService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ServiceCatalogOwnerUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ServiceCatalogOwnerUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ServiceCatalogService.ts b/CommonServer/Services/ServiceCatalogService.ts index 9ff2583796..cd4f01efe9 100644 --- a/CommonServer/Services/ServiceCatalogService.ts +++ b/CommonServer/Services/ServiceCatalogService.ts @@ -1,27 +1,27 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import ArrayUtil from 'Common/Types/ArrayUtil'; -import { BrightColors } from 'Common/Types/BrandColors'; -import Model from 'Model/Models/ServiceCatalog'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import ArrayUtil from "Common/Types/ArrayUtil"; +import { BrightColors } from "Common/Types/BrandColors"; +import Model from "Model/Models/ServiceCatalog"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - // select a random color. - createBy.data.serviceColor = ArrayUtil.selectItemByRandom(BrightColors); + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + // select a random color. + createBy.data.serviceColor = ArrayUtil.selectItemByRandom(BrightColors); - return { - carryForward: null, - createBy: createBy, - }; - } + return { + carryForward: null, + createBy: createBy, + }; + } } export default new Service(); diff --git a/CommonServer/Services/ServiceRepositoryService.ts b/CommonServer/Services/ServiceRepositoryService.ts index 8d59bad350..a48beb350e 100644 --- a/CommonServer/Services/ServiceRepositoryService.ts +++ b/CommonServer/Services/ServiceRepositoryService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/ServiceRepository'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/ServiceRepository"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/ShortLinkService.ts b/CommonServer/Services/ShortLinkService.ts index abf1e29b35..f63033339d 100644 --- a/CommonServer/Services/ShortLinkService.ts +++ b/CommonServer/Services/ShortLinkService.ts @@ -1,62 +1,62 @@ -import DatabaseConfig from '../DatabaseConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import { LinkShortenerRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Text from 'Common/Types/Text'; -import Model from 'Model/Models/ShortLink'; +import DatabaseConfig from "../DatabaseConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import { LinkShortenerRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Text from "Common/Types/Text"; +import Model from "Model/Models/ShortLink"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 3); //expire links in 3 days. - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 3); //expire links in 3 days. + } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - createBy.data.shortId = Text.generateRandomText(8); + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + createBy.data.shortId = Text.generateRandomText(8); - return { createBy: createBy, carryForward: [] }; - } + return { createBy: createBy, carryForward: [] }; + } - public async saveShortLinkFor(url: URL): Promise<Model> { - const model: Model = new Model(); - model.link = url; - return await this.create({ data: model, props: { isRoot: true } }); - } + public async saveShortLinkFor(url: URL): Promise<Model> { + const model: Model = new Model(); + model.link = url; + return await this.create({ data: model, props: { isRoot: true } }); + } - public async getShortenedUrl(model: Model): Promise<URL> { - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - return new URL( - httpProtocol, - host, - new Route(LinkShortenerRoute.toString()).addRoute( - '/' + model.shortId?.toString() - ) - ); - } + public async getShortenedUrl(model: Model): Promise<URL> { + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + return new URL( + httpProtocol, + host, + new Route(LinkShortenerRoute.toString()).addRoute( + "/" + model.shortId?.toString(), + ), + ); + } - public async getShortLinkFor(shortLinkId: string): Promise<Model | null> { - return await this.findOneBy({ - query: { - shortId: shortLinkId, - }, - select: { - _id: true, - link: true, - }, - props: { - isRoot: true, - }, - }); - } + public async getShortLinkFor(shortLinkId: string): Promise<Model | null> { + return await this.findOneBy({ + query: { + shortId: shortLinkId, + }, + select: { + _id: true, + link: true, + }, + props: { + isRoot: true, + }, + }); + } } export default new Service(); diff --git a/CommonServer/Services/SmsLogService.ts b/CommonServer/Services/SmsLogService.ts index 70ae990b6d..bee51cfbd5 100644 --- a/CommonServer/Services/SmsLogService.ts +++ b/CommonServer/Services/SmsLogService.ts @@ -1,12 +1,12 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/SmsLog'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/SmsLog"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 3); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 3); + } } export default new Service(); diff --git a/CommonServer/Services/SmsService.ts b/CommonServer/Services/SmsService.ts index 8dcc1427a1..b20bb213cf 100644 --- a/CommonServer/Services/SmsService.ts +++ b/CommonServer/Services/SmsService.ts @@ -1,60 +1,58 @@ -import { AppApiHostname } from '../EnvironmentConfig'; -import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization'; -import BaseService from './BaseService'; -import EmptyResponseData from 'Common/Types/API/EmptyResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import SMS from 'Common/Types/SMS/SMS'; -import API from 'Common/Utils/API'; +import { AppApiHostname } from "../EnvironmentConfig"; +import ClusterKeyAuthorization from "../Middleware/ClusterKeyAuthorization"; +import BaseService from "./BaseService"; +import EmptyResponseData from "Common/Types/API/EmptyResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import SMS from "Common/Types/SMS/SMS"; +import API from "Common/Utils/API"; export class SmsService extends BaseService { - public constructor() { - super(); - } + public constructor() { + super(); + } - public async sendSms( - sms: SMS, - options: { - projectId?: ObjectID | undefined; // project id for sms log - isSensitive?: boolean; // if true, message will not be logged - userOnCallLogTimelineId?: ObjectID; - customTwilioConfig?: TwilioConfig | undefined; - } - ): Promise<HTTPResponse<EmptyResponseData>> { - const body: JSONObject = { - to: sms.to.toString(), - message: sms.message, - projectId: options.projectId?.toString(), - isSensitive: options.isSensitive, - userOnCallLogTimelineId: - options.userOnCallLogTimelineId?.toString(), - customTwilioConfig: options.customTwilioConfig - ? { - accountSid: options.customTwilioConfig.accountSid!, - authToken: options.customTwilioConfig.authToken!, - phoneNumber: - options.customTwilioConfig.phoneNumber.toString(), - } - : undefined, - }; + public async sendSms( + sms: SMS, + options: { + projectId?: ObjectID | undefined; // project id for sms log + isSensitive?: boolean; // if true, message will not be logged + userOnCallLogTimelineId?: ObjectID; + customTwilioConfig?: TwilioConfig | undefined; + }, + ): Promise<HTTPResponse<EmptyResponseData>> { + const body: JSONObject = { + to: sms.to.toString(), + message: sms.message, + projectId: options.projectId?.toString(), + isSensitive: options.isSensitive, + userOnCallLogTimelineId: options.userOnCallLogTimelineId?.toString(), + customTwilioConfig: options.customTwilioConfig + ? { + accountSid: options.customTwilioConfig.accountSid!, + authToken: options.customTwilioConfig.authToken!, + phoneNumber: options.customTwilioConfig.phoneNumber.toString(), + } + : undefined, + }; - return await API.post<EmptyResponseData>( - new URL( - Protocol.HTTP, - AppApiHostname, - new Route('/api/notification/sms/send') - ), - body, - { - ...ClusterKeyAuthorization.getClusterKeyHeaders(), - } - ); - } + return await API.post<EmptyResponseData>( + new URL( + Protocol.HTTP, + AppApiHostname, + new Route("/api/notification/sms/send"), + ), + body, + { + ...ClusterKeyAuthorization.getClusterKeyHeaders(), + }, + ); + } } export default new SmsService(); diff --git a/CommonServer/Services/SpanService.ts b/CommonServer/Services/SpanService.ts index d8327a7deb..818f84a549 100644 --- a/CommonServer/Services/SpanService.ts +++ b/CommonServer/Services/SpanService.ts @@ -1,11 +1,11 @@ -import ClickhouseDatabase from '../Infrastructure/ClickhouseDatabase'; -import AnalyticsDatabaseService from './AnalyticsDatabaseService'; -import Span from 'Model/AnalyticsModels/Span'; +import ClickhouseDatabase from "../Infrastructure/ClickhouseDatabase"; +import AnalyticsDatabaseService from "./AnalyticsDatabaseService"; +import Span from "Model/AnalyticsModels/Span"; export class SpanService extends AnalyticsDatabaseService<Span> { - public constructor(clickhouseDatabase?: ClickhouseDatabase | undefined) { - super({ modelType: Span, database: clickhouseDatabase }); - } + public constructor(clickhouseDatabase?: ClickhouseDatabase | undefined) { + super({ modelType: Span, database: clickhouseDatabase }); + } } export default new SpanService(); diff --git a/CommonServer/Services/StatusPageAnnouncementService.ts b/CommonServer/Services/StatusPageAnnouncementService.ts index 4def2d3490..8483202bfa 100644 --- a/CommonServer/Services/StatusPageAnnouncementService.ts +++ b/CommonServer/Services/StatusPageAnnouncementService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/StatusPageAnnouncement'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/StatusPageAnnouncement"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageCertificateService.ts b/CommonServer/Services/StatusPageCertificateService.ts index b7b98373e7..01df1967e9 100644 --- a/CommonServer/Services/StatusPageCertificateService.ts +++ b/CommonServer/Services/StatusPageCertificateService.ts @@ -1,71 +1,59 @@ -import DatabaseConfig from '../DatabaseConfig'; -import { AppApiHostname } from '../EnvironmentConfig'; -import BaseService from './BaseService'; -import EmptyResponseData from 'Common/Types/API/EmptyResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { JSONObject } from 'Common/Types/JSON'; -import API from 'Common/Utils/API'; +import DatabaseConfig from "../DatabaseConfig"; +import { AppApiHostname } from "../EnvironmentConfig"; +import BaseService from "./BaseService"; +import EmptyResponseData from "Common/Types/API/EmptyResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { JSONObject } from "Common/Types/JSON"; +import API from "Common/Utils/API"; export class StatusPageCertificateService extends BaseService { - public constructor() { - super(); - } + public constructor() { + super(); + } - public async add(domain: string): Promise<HTTPResponse<EmptyResponseData>> { - const body: JSONObject = { - domain: domain, - }; + public async add(domain: string): Promise<HTTPResponse<EmptyResponseData>> { + const body: JSONObject = { + domain: domain, + }; - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - return await API.post<EmptyResponseData>( - new URL( - httpProtocol, - AppApiHostname, - new Route('/api/workers/cert') - ), - body - ); - } + return await API.post<EmptyResponseData>( + new URL(httpProtocol, AppApiHostname, new Route("/api/workers/cert")), + body, + ); + } - public async remove( - domain: string - ): Promise<HTTPResponse<EmptyResponseData>> { - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + public async remove( + domain: string, + ): Promise<HTTPResponse<EmptyResponseData>> { + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - const body: JSONObject = { - domain: domain, - }; + const body: JSONObject = { + domain: domain, + }; - return await API.delete<EmptyResponseData>( - new URL( - httpProtocol, - AppApiHostname, - new Route('/api/workers/cert') - ), - body - ); - } + return await API.delete<EmptyResponseData>( + new URL(httpProtocol, AppApiHostname, new Route("/api/workers/cert")), + body, + ); + } - public async get(domain: string): Promise<HTTPResponse<JSONObject>> { - const body: JSONObject = { - domain: domain, - }; + public async get(domain: string): Promise<HTTPResponse<JSONObject>> { + const body: JSONObject = { + domain: domain, + }; - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - return await API.get<JSONObject>( - new URL( - httpProtocol, - AppApiHostname, - new Route('/api/workers/cert') - ), - body - ); - } + return await API.get<JSONObject>( + new URL(httpProtocol, AppApiHostname, new Route("/api/workers/cert")), + body, + ); + } } export default new StatusPageCertificateService(); diff --git a/CommonServer/Services/StatusPageCustomFieldService.ts b/CommonServer/Services/StatusPageCustomFieldService.ts index b713c07b55..0d1abb3d5b 100644 --- a/CommonServer/Services/StatusPageCustomFieldService.ts +++ b/CommonServer/Services/StatusPageCustomFieldService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/StatusPageCustomField'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/StatusPageCustomField"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageDomainService.ts b/CommonServer/Services/StatusPageDomainService.ts index 8f7b9a679d..970dc94fd9 100644 --- a/CommonServer/Services/StatusPageDomainService.ts +++ b/CommonServer/Services/StatusPageDomainService.ts @@ -1,422 +1,418 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete } from '../Types/Database/Hooks'; -import GreenlockUtil from '../Utils/Greenlock/Greenlock'; -import logger from '../Utils/Logger'; -import DatabaseService from './DatabaseService'; -import DomainService from './DomainService'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import API from 'Common/Utils/API'; -import Domain from 'Model/Models/Domain'; -import StatusPageDomain from 'Model/Models/StatusPageDomain'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete } from "../Types/Database/Hooks"; +import GreenlockUtil from "../Utils/Greenlock/Greenlock"; +import logger from "../Utils/Logger"; +import DatabaseService from "./DatabaseService"; +import DomainService from "./DomainService"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import API from "Common/Utils/API"; +import Domain from "Model/Models/Domain"; +import StatusPageDomain from "Model/Models/StatusPageDomain"; export class Service extends DatabaseService<StatusPageDomain> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(StatusPageDomain, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(StatusPageDomain, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<StatusPageDomain>, + ): Promise<OnCreate<StatusPageDomain>> { + const domain: Domain | null = await DomainService.findOneBy({ + query: { + _id: + createBy.data.domainId?.toString() || createBy.data.domain?._id || "", + }, + select: { domain: true, isVerified: true }, + props: { + isRoot: true, + }, + }); + + if (!domain?.isVerified) { + throw new BadDataException( + "This domain is not verified. Please verify it by going to Settings > Domains", + ); } - protected override async onBeforeCreate( - createBy: CreateBy<StatusPageDomain> - ): Promise<OnCreate<StatusPageDomain>> { - const domain: Domain | null = await DomainService.findOneBy({ - query: { - _id: - createBy.data.domainId?.toString() || - createBy.data.domain?._id || - '', - }, - select: { domain: true, isVerified: true }, - props: { - isRoot: true, - }, + if (domain) { + createBy.data.fullDomain = createBy.data.subdomain + "." + domain.domain; + } + + createBy.data.cnameVerificationToken = ObjectID.generate().toString(); + + return { createBy, carryForward: null }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<StatusPageDomain>, + ): Promise<OnDelete<StatusPageDomain>> { + const domains: Array<StatusPageDomain> = await this.findBy({ + query: { + ...deleteBy.query, + }, + skip: 0, + limit: LIMIT_MAX, + select: { fullDomain: true }, + props: { + isRoot: true, + }, + }); + + return { deleteBy, carryForward: domains }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<StatusPageDomain>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<StatusPageDomain>> { + for (const domain of onDelete.carryForward) { + await this.removeDomainFromGreenlock(domain.fullDomain as string); + } + + return onDelete; + } + + public async removeDomainFromGreenlock(domain: string): Promise<void> { + await GreenlockUtil.removeDomain(domain); + } + + public async orderCert(statusPageDomain: StatusPageDomain): Promise<void> { + if (!statusPageDomain.fullDomain) { + const fetchedStatusPageDomain: StatusPageDomain | null = + await this.findOneBy({ + query: { + _id: statusPageDomain.id!.toString(), + }, + select: { + _id: true, + fullDomain: true, + }, + props: { + isRoot: true, + }, }); - if (!domain?.isVerified) { - throw new BadDataException( - 'This domain is not verified. Please verify it by going to Settings > Domains' - ); - } + if (!fetchedStatusPageDomain) { + throw new BadDataException("Domain not found"); + } - if (domain) { - createBy.data.fullDomain = - createBy.data.subdomain + '.' + domain.domain; - } - - createBy.data.cnameVerificationToken = ObjectID.generate().toString(); - - return { createBy, carryForward: null }; + statusPageDomain = fetchedStatusPageDomain; } - protected override async onBeforeDelete( - deleteBy: DeleteBy<StatusPageDomain> - ): Promise<OnDelete<StatusPageDomain>> { - const domains: Array<StatusPageDomain> = await this.findBy({ - query: { - ...deleteBy.query, - }, - skip: 0, - limit: LIMIT_MAX, - select: { fullDomain: true }, - props: { - isRoot: true, - }, - }); - - return { deleteBy, carryForward: domains }; + if (!statusPageDomain.fullDomain) { + throw new BadDataException( + "Unable to order certificate because domain is null", + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<StatusPageDomain>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<StatusPageDomain>> { - for (const domain of onDelete.carryForward) { - await this.removeDomainFromGreenlock(domain.fullDomain as string); - } + await GreenlockUtil.orderCert({ + domain: statusPageDomain.fullDomain as string, + validateCname: async (fullDomain: string) => { + return await this.isCnameValid(fullDomain); + }, + }); - return onDelete; + // update the order. + await this.updateOneById({ + id: statusPageDomain.id!, + data: { + isSslOrdered: true, + }, + props: { + isRoot: true, + }, + }); + } + + public async updateSslProvisioningStatusForAllDomains(): Promise<void> { + const domains: Array<StatusPageDomain> = await this.findBy({ + query: { + isSslOrdered: true, + }, + select: { + _id: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const domain of domains) { + await this.updateSslProvisioningStatus(domain); } + } - public async removeDomainFromGreenlock(domain: string): Promise<void> { - await GreenlockUtil.removeDomain(domain); - } - - public async orderCert(statusPageDomain: StatusPageDomain): Promise<void> { - if (!statusPageDomain.fullDomain) { - const fetchedStatusPageDomain: StatusPageDomain | null = - await this.findOneBy({ - query: { - _id: statusPageDomain.id!.toString(), - }, - select: { - _id: true, - fullDomain: true, - }, - props: { - isRoot: true, - }, - }); - - if (!fetchedStatusPageDomain) { - throw new BadDataException('Domain not found'); - } - - statusPageDomain = fetchedStatusPageDomain; - } - - if (!statusPageDomain.fullDomain) { - throw new BadDataException( - 'Unable to order certificate because domain is null' - ); - } - - await GreenlockUtil.orderCert({ - domain: statusPageDomain.fullDomain as string, - validateCname: async (fullDomain: string) => { - return await this.isCnameValid(fullDomain); - }, - }); - - // update the order. - await this.updateOneById({ - id: statusPageDomain.id!, - data: { - isSslOrdered: true, - }, - props: { - isRoot: true, - }, - }); - } - - public async updateSslProvisioningStatusForAllDomains(): Promise<void> { - const domains: Array<StatusPageDomain> = await this.findBy({ - query: { - isSslOrdered: true, - }, - select: { - _id: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const domain of domains) { - await this.updateSslProvisioningStatus(domain); - } - } - - private async isSSLProvisioned( - fulldomain: string, - token: string - ): Promise<boolean> { - try { - const result: HTTPErrorResponse | HTTPResponse<JSONObject> = - await API.get( - URL.fromString( - 'https://' + - fulldomain + - '/status-page-api/cname-verification/' + - token - ) - ); - - if (result.isFailure()) { - return false; - } - - return true; - } catch (err) { - return false; - } - } - - public async updateCnameStatusForStatusPageDomain(data: { - domain: string; - cnameStatus: boolean; - }): Promise<void> { - if (!data.cnameStatus) { - await this.updateOneBy({ - query: { - fullDomain: data.domain, - }, - data: { - isCnameVerified: false, - isSslOrdered: false, - isSslProvisioned: false, - }, - props: { - isRoot: true, - }, - }); - } else { - await this.updateOneBy({ - query: { - fullDomain: data.domain, - }, - data: { - isCnameVerified: true, - }, - props: { - isRoot: true, - }, - }); - } - } - - public async isCnameValid(fullDomain: string): Promise<boolean> { - try { - // get the token from the domain. - - const statusPageDomain: StatusPageDomain | null = - await this.findOneBy({ - query: { - fullDomain: fullDomain, - }, - select: { - _id: true, - cnameVerificationToken: true, - }, - props: { - isRoot: true, - }, - }); - - if (!statusPageDomain) { - return false; - } - - const token: string = statusPageDomain.cnameVerificationToken!; - - const result: HTTPErrorResponse | HTTPResponse<JSONObject> = - await API.get( - URL.fromString( - 'http://' + - fullDomain + - '/status-page-api/cname-verification/' + - token - ) - ); - - if (result.isSuccess()) { - await this.updateCnameStatusForStatusPageDomain({ - domain: fullDomain, - cnameStatus: true, - }); - - return true; - } - - // try with https - - const resultHttps: HTTPErrorResponse | HTTPResponse<JSONObject> = - await API.get( - URL.fromString( - 'https://' + - fullDomain + - '/status-page-api/cname-verification/' + - token - ) - ); - - if (resultHttps.isSuccess()) { - await this.updateCnameStatusForStatusPageDomain({ - domain: fullDomain, - cnameStatus: true, - }); - - return true; - } - - await this.updateCnameStatusForStatusPageDomain({ - domain: fullDomain, - cnameStatus: false, - }); - - return false; - } catch (err) { - logger.debug('Failed checking for CNAME ' + fullDomain); - logger.debug(err); - - await this.updateCnameStatusForStatusPageDomain({ - domain: fullDomain, - cnameStatus: false, - }); - - return false; - } - } - - public async updateSslProvisioningStatus( - domain: StatusPageDomain - ): Promise<void> { - if (!domain.id) { - throw new BadDataException('Domain ID is required'); - } - - const statusPageDomain: StatusPageDomain | null = await this.findOneBy({ - query: { - _id: domain.id?.toString(), - }, - select: { - _id: true, - fullDomain: true, - cnameVerificationToken: true, - }, - props: { - isRoot: true, - }, - }); - - if (!statusPageDomain) { - throw new BadDataException('Domain not found'); - } - - logger.debug( - `StatusPageCerts:RemoveCerts - Checking CNAME ${statusPageDomain.fullDomain}` + private async isSSLProvisioned( + fulldomain: string, + token: string, + ): Promise<boolean> { + try { + const result: HTTPErrorResponse | HTTPResponse<JSONObject> = + await API.get( + URL.fromString( + "https://" + + fulldomain + + "/status-page-api/cname-verification/" + + token, + ), ); - // Check CNAME validation and if that fails. Remove certs from Greenlock. - const isValid: boolean = await this.isSSLProvisioned( - statusPageDomain.fullDomain!, - statusPageDomain.cnameVerificationToken! + if (result.isFailure()) { + return false; + } + + return true; + } catch (err) { + return false; + } + } + + public async updateCnameStatusForStatusPageDomain(data: { + domain: string; + cnameStatus: boolean; + }): Promise<void> { + if (!data.cnameStatus) { + await this.updateOneBy({ + query: { + fullDomain: data.domain, + }, + data: { + isCnameVerified: false, + isSslOrdered: false, + isSslProvisioned: false, + }, + props: { + isRoot: true, + }, + }); + } else { + await this.updateOneBy({ + query: { + fullDomain: data.domain, + }, + data: { + isCnameVerified: true, + }, + props: { + isRoot: true, + }, + }); + } + } + + public async isCnameValid(fullDomain: string): Promise<boolean> { + try { + // get the token from the domain. + + const statusPageDomain: StatusPageDomain | null = await this.findOneBy({ + query: { + fullDomain: fullDomain, + }, + select: { + _id: true, + cnameVerificationToken: true, + }, + props: { + isRoot: true, + }, + }); + + if (!statusPageDomain) { + return false; + } + + const token: string = statusPageDomain.cnameVerificationToken!; + + const result: HTTPErrorResponse | HTTPResponse<JSONObject> = + await API.get( + URL.fromString( + "http://" + + fullDomain + + "/status-page-api/cname-verification/" + + token, + ), ); - if (!isValid) { - // check if cname is valid. - - await this.isCnameValid(statusPageDomain.fullDomain!); - - await this.updateOneById({ - id: statusPageDomain.id!, - data: { - isSslProvisioned: false, - }, - props: { - isRoot: true, - }, - }); - } else { - await this.updateOneById({ - id: statusPageDomain.id!, - data: { - isSslProvisioned: true, - }, - props: { - isRoot: true, - }, - }); - } - } - - public async orderSSLForDomainsWhichAreNotOrderedYet(): Promise<void> { - const domains: Array<StatusPageDomain> = await this.findBy({ - query: { - isSslOrdered: false, - }, - select: { - _id: true, - fullDomain: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, + if (result.isSuccess()) { + await this.updateCnameStatusForStatusPageDomain({ + domain: fullDomain, + cnameStatus: true, }); - for (const domain of domains) { - try { - await this.orderCert(domain); - } catch (e) { - logger.error(e); - } - } - } + return true; + } - public async verifyCnameWhoseCnameisNotVerified(): Promise<void> { - const domains: Array<StatusPageDomain> = await this.findBy({ - query: { - isCnameVerified: false, - }, - select: { - _id: true, - fullDomain: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, + // try with https + + const resultHttps: HTTPErrorResponse | HTTPResponse<JSONObject> = + await API.get( + URL.fromString( + "https://" + + fullDomain + + "/status-page-api/cname-verification/" + + token, + ), + ); + + if (resultHttps.isSuccess()) { + await this.updateCnameStatusForStatusPageDomain({ + domain: fullDomain, + cnameStatus: true, }); - for (const domain of domains) { - try { - await this.isCnameValid(domain.fullDomain as string); // this will also upate the status. - } catch (e) { - logger.error(e); - } - } + return true; + } + + await this.updateCnameStatusForStatusPageDomain({ + domain: fullDomain, + cnameStatus: false, + }); + + return false; + } catch (err) { + logger.debug("Failed checking for CNAME " + fullDomain); + logger.debug(err); + + await this.updateCnameStatusForStatusPageDomain({ + domain: fullDomain, + cnameStatus: false, + }); + + return false; + } + } + + public async updateSslProvisioningStatus( + domain: StatusPageDomain, + ): Promise<void> { + if (!domain.id) { + throw new BadDataException("Domain ID is required"); } - public async renewCertsWhichAreExpiringSoon(): Promise<void> { - await GreenlockUtil.renewAllCertsWhichAreExpiringSoon({ - validateCname: async (fullDomain: string) => { - return await this.isCnameValid(fullDomain); - }, - notifyDomainRemoved: async (domain: string) => { - logger.debug(`Domain removed from greenlock: ${domain}`); - }, - }); + const statusPageDomain: StatusPageDomain | null = await this.findOneBy({ + query: { + _id: domain.id?.toString(), + }, + select: { + _id: true, + fullDomain: true, + cnameVerificationToken: true, + }, + props: { + isRoot: true, + }, + }); + + if (!statusPageDomain) { + throw new BadDataException("Domain not found"); } + + logger.debug( + `StatusPageCerts:RemoveCerts - Checking CNAME ${statusPageDomain.fullDomain}`, + ); + + // Check CNAME validation and if that fails. Remove certs from Greenlock. + const isValid: boolean = await this.isSSLProvisioned( + statusPageDomain.fullDomain!, + statusPageDomain.cnameVerificationToken!, + ); + + if (!isValid) { + // check if cname is valid. + + await this.isCnameValid(statusPageDomain.fullDomain!); + + await this.updateOneById({ + id: statusPageDomain.id!, + data: { + isSslProvisioned: false, + }, + props: { + isRoot: true, + }, + }); + } else { + await this.updateOneById({ + id: statusPageDomain.id!, + data: { + isSslProvisioned: true, + }, + props: { + isRoot: true, + }, + }); + } + } + + public async orderSSLForDomainsWhichAreNotOrderedYet(): Promise<void> { + const domains: Array<StatusPageDomain> = await this.findBy({ + query: { + isSslOrdered: false, + }, + select: { + _id: true, + fullDomain: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const domain of domains) { + try { + await this.orderCert(domain); + } catch (e) { + logger.error(e); + } + } + } + + public async verifyCnameWhoseCnameisNotVerified(): Promise<void> { + const domains: Array<StatusPageDomain> = await this.findBy({ + query: { + isCnameVerified: false, + }, + select: { + _id: true, + fullDomain: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const domain of domains) { + try { + await this.isCnameValid(domain.fullDomain as string); // this will also upate the status. + } catch (e) { + logger.error(e); + } + } + } + + public async renewCertsWhichAreExpiringSoon(): Promise<void> { + await GreenlockUtil.renewAllCertsWhichAreExpiringSoon({ + validateCname: async (fullDomain: string) => { + return await this.isCnameValid(fullDomain); + }, + notifyDomainRemoved: async (domain: string) => { + logger.debug(`Domain removed from greenlock: ${domain}`); + }, + }); + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageFooterLinkService.ts b/CommonServer/Services/StatusPageFooterLinkService.ts index d409c6f03b..abc1db67ce 100644 --- a/CommonServer/Services/StatusPageFooterLinkService.ts +++ b/CommonServer/Services/StatusPageFooterLinkService.ts @@ -1,248 +1,237 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import Query from '../Types/Database/Query'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Model from 'Model/Models/StatusPageFooterLink'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import Query from "../Types/Database/Query"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Model from "Model/Models/StatusPageFooterLink"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.statusPageId) { + throw new BadDataException("statusPageId is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.statusPageId) { - throw new BadDataException('statusPageId is required'); - } + if (!createBy.data.order) { + const query: Query<Model> = { + statusPageId: createBy.data.statusPageId, + }; - if (!createBy.data.order) { - const query: Query<Model> = { - statusPageId: createBy.data.statusPageId, - }; + const count: PositiveNumber = await this.countBy({ + query: query, + props: { + isRoot: true, + }, + }); - const count: PositiveNumber = await this.countBy({ - query: query, - props: { - isRoot: true, - }, - }); - - createBy.data.order = count.toNumber() + 1; - } - - await this.rearrangeOrder( - createBy.data.order, - createBy.data.statusPageId, - true - ); - - return { - createBy: createBy, - carryForward: null, - }; + createBy.data.order = count.toNumber() + 1; } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting status page footer link. Please try the delete with objectId' - ); - } + await this.rearrangeOrder( + createBy.data.order, + createBy.data.statusPageId, + true, + ); - let resource: Model | null = null; + return { + createBy: createBy, + carryForward: null, + }; + } - if (!deleteBy.props.isRoot) { - resource = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - }, - }); - } - - return { - deleteBy, - carryForward: resource, - }; + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting status page footer link. Please try the delete with objectId", + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const resource: Model | null = onDelete.carryForward; + let resource: Model | null = null; - if (!deleteBy.props.isRoot && resource) { - if (resource && resource.order && resource.statusPageId) { - await this.rearrangeOrder( - resource.order, - resource.statusPageId, - false - ); - } - } - - return { - deleteBy: deleteBy, - carryForward: null, - }; + if (!deleteBy.props.isRoot) { + resource = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + }, + }); } - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if ( - updateBy.data.order && - !updateBy.props.isRoot && - updateBy.query._id - ) { - const resource: Model | null = await this.findOneBy({ - query: { - _id: updateBy.query._id!, - }, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - _id: true, - }, - }); + return { + deleteBy, + carryForward: resource, + }; + } - const currentOrder: number = resource?.order as number; - const newOrder: number = updateBy.data.order as number; + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const resource: Model | null = onDelete.carryForward; - const resources: Array<Model> = await this.findBy({ - query: { - statusPageId: resource?.statusPageId as ObjectID, - }, - - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - _id: true, - }, - }); - - if (currentOrder > newOrder) { - // moving up. - - for (const resource of resources) { - if ( - resource.order! >= newOrder && - resource.order! < currentOrder - ) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! + 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - - if (newOrder > currentOrder) { - // moving down. - - for (const resource of resources) { - if (resource.order! <= newOrder) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! - 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { updateBy, carryForward: null }; + if (!deleteBy.props.isRoot && resource) { + if (resource && resource.order && resource.statusPageId) { + await this.rearrangeOrder(resource.order, resource.statusPageId, false); + } } - private async rearrangeOrder( - currentOrder: number, - statusPageId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get status page resource with this order. - const resources: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - statusPageId: statusPageId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - let newOrder: number = currentOrder; + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.order && !updateBy.props.isRoot && updateBy.query._id) { + const resource: Model | null = await this.findOneBy({ + query: { + _id: updateBy.query._id!, + }, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + _id: true, + }, + }); + + const currentOrder: number = resource?.order as number; + const newOrder: number = updateBy.data.order as number; + + const resources: Array<Model> = await this.findBy({ + query: { + statusPageId: resource?.statusPageId as ObjectID, + }, + + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + _id: true, + }, + }); + + if (currentOrder > newOrder) { + // moving up. for (const resource of resources) { - if (increaseOrder) { - newOrder = resource.order! + 1; - } else { - newOrder = resource.order! - 1; - } - + if (resource.order! >= newOrder && resource.order! < currentOrder) { + // increment order. await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, + query: { + _id: resource._id!, + }, + data: { + order: resource.order! + 1, + }, + props: { + isRoot: true, + }, }); + } } + } + + if (newOrder > currentOrder) { + // moving down. + + for (const resource of resources) { + if (resource.order! <= newOrder) { + // increment order. + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: resource.order! - 1, + }, + props: { + isRoot: true, + }, + }); + } + } + } } + + return { updateBy, carryForward: null }; + } + + private async rearrangeOrder( + currentOrder: number, + statusPageId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get status page resource with this order. + const resources: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + statusPageId: statusPageId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); + + let newOrder: number = currentOrder; + + for (const resource of resources) { + if (increaseOrder) { + newOrder = resource.order! + 1; + } else { + newOrder = resource.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); + } + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageGroupService.ts b/CommonServer/Services/StatusPageGroupService.ts index fc77caf1f5..98a7b410c0 100644 --- a/CommonServer/Services/StatusPageGroupService.ts +++ b/CommonServer/Services/StatusPageGroupService.ts @@ -1,246 +1,233 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Model from 'Model/Models/StatusPageGroup'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Model from "Model/Models/StatusPageGroup"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.statusPageId) { + throw new BadDataException("Status Page Group statusPageId is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.statusPageId) { - throw new BadDataException( - 'Status Page Group statusPageId is required' - ); - } + if (!createBy.data.order) { + const count: PositiveNumber = await this.countBy({ + query: { + statusPageId: createBy.data.statusPageId, + }, + props: { + isRoot: true, + }, + }); - if (!createBy.data.order) { - const count: PositiveNumber = await this.countBy({ - query: { - statusPageId: createBy.data.statusPageId, - }, - props: { - isRoot: true, - }, - }); - - createBy.data.order = count.toNumber() + 1; - } - - await this.rearrangeOrder( - createBy.data.order, - createBy.data.statusPageId, - true - ); - - return { - createBy: createBy, - carryForward: null, - }; + createBy.data.order = count.toNumber() + 1; } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting status page group. Please try the delete with objectId' - ); - } + await this.rearrangeOrder( + createBy.data.order, + createBy.data.statusPageId, + true, + ); - let group: Model | null = null; + return { + createBy: createBy, + carryForward: null, + }; + } - if (!deleteBy.props.isRoot) { - group = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - }, - }); - } - - return { - deleteBy, - carryForward: group, - }; + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting status page group. Please try the delete with objectId", + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const group: Model | null = onDelete.carryForward; + let group: Model | null = null; - if (!deleteBy.props.isRoot && group) { - if (group && group.order && group.statusPageId) { - await this.rearrangeOrder( - group.order, - group.statusPageId, - false - ); - } - } - - return { - deleteBy: deleteBy, - carryForward: null, - }; + if (!deleteBy.props.isRoot) { + group = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + }, + }); } - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if ( - updateBy.data.order && - !updateBy.props.isRoot && - updateBy.query._id - ) { - const group: Model | null = await this.findOneBy({ - query: { - _id: updateBy.query._id!, - }, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - _id: true, - }, - }); + return { + deleteBy, + carryForward: group, + }; + } - const currentOrder: number = group?.order as number; - const newOrder: number = updateBy.data.order as number; + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const group: Model | null = onDelete.carryForward; - const groups: Array<Model> = await this.findBy({ - query: { - statusPageId: group?.statusPageId as ObjectID, - }, - - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - _id: true, - }, - }); - - if (currentOrder > newOrder) { - // moving up. - - for (const group of groups) { - if ( - group.order! >= newOrder && - group.order! < currentOrder - ) { - // increment order. - await this.updateOneBy({ - query: { - _id: group._id!, - }, - data: { - order: group.order! + 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - - if (newOrder > currentOrder) { - // moving down. - - for (const group of groups) { - if (group.order! <= newOrder) { - // increment order. - await this.updateOneBy({ - query: { - _id: group._id!, - }, - data: { - order: group.order! - 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { updateBy, carryForward: null }; + if (!deleteBy.props.isRoot && group) { + if (group && group.order && group.statusPageId) { + await this.rearrangeOrder(group.order, group.statusPageId, false); + } } - private async rearrangeOrder( - currentOrder: number, - statusPageId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get status page group with this order. - const groups: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - statusPageId: statusPageId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - let newOrder: number = currentOrder; + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.order && !updateBy.props.isRoot && updateBy.query._id) { + const group: Model | null = await this.findOneBy({ + query: { + _id: updateBy.query._id!, + }, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + _id: true, + }, + }); + + const currentOrder: number = group?.order as number; + const newOrder: number = updateBy.data.order as number; + + const groups: Array<Model> = await this.findBy({ + query: { + statusPageId: group?.statusPageId as ObjectID, + }, + + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + _id: true, + }, + }); + + if (currentOrder > newOrder) { + // moving up. for (const group of groups) { - if (increaseOrder) { - newOrder = group.order! + 1; - } else { - newOrder = group.order! - 1; - } - + if (group.order! >= newOrder && group.order! < currentOrder) { + // increment order. await this.updateOneBy({ - query: { - _id: group._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, + query: { + _id: group._id!, + }, + data: { + order: group.order! + 1, + }, + props: { + isRoot: true, + }, }); + } } + } + + if (newOrder > currentOrder) { + // moving down. + + for (const group of groups) { + if (group.order! <= newOrder) { + // increment order. + await this.updateOneBy({ + query: { + _id: group._id!, + }, + data: { + order: group.order! - 1, + }, + props: { + isRoot: true, + }, + }); + } + } + } } + + return { updateBy, carryForward: null }; + } + + private async rearrangeOrder( + currentOrder: number, + statusPageId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get status page group with this order. + const groups: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + statusPageId: statusPageId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); + + let newOrder: number = currentOrder; + + for (const group of groups) { + if (increaseOrder) { + newOrder = group.order! + 1; + } else { + newOrder = group.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: group._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); + } + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageHeaderLinkService.ts b/CommonServer/Services/StatusPageHeaderLinkService.ts index 1df963ef06..fc4fe1d16f 100644 --- a/CommonServer/Services/StatusPageHeaderLinkService.ts +++ b/CommonServer/Services/StatusPageHeaderLinkService.ts @@ -1,247 +1,236 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import Query from '../Types/Database/Query'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Model from 'Model/Models/StatusPageHeaderLink'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import Query from "../Types/Database/Query"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Model from "Model/Models/StatusPageHeaderLink"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.statusPageId) { + throw new BadDataException("statusPageId is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.statusPageId) { - throw new BadDataException('statusPageId is required'); - } + if (!createBy.data.order) { + const query: Query<Model> = { + statusPageId: createBy.data.statusPageId, + }; - if (!createBy.data.order) { - const query: Query<Model> = { - statusPageId: createBy.data.statusPageId, - }; + const count: PositiveNumber = await this.countBy({ + query: query, + props: { + isRoot: true, + }, + }); - const count: PositiveNumber = await this.countBy({ - query: query, - props: { - isRoot: true, - }, - }); - - createBy.data.order = count.toNumber() + 1; - } - - await this.rearrangeOrder( - createBy.data.order, - createBy.data.statusPageId, - true - ); - - return { - createBy: createBy, - carryForward: null, - }; + createBy.data.order = count.toNumber() + 1; } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting status page header link. Please try the delete with objectId' - ); - } + await this.rearrangeOrder( + createBy.data.order, + createBy.data.statusPageId, + true, + ); - let resource: Model | null = null; + return { + createBy: createBy, + carryForward: null, + }; + } - if (!deleteBy.props.isRoot) { - resource = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - }, - }); - } - - return { - deleteBy, - carryForward: resource, - }; + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting status page header link. Please try the delete with objectId", + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const resource: Model | null = onDelete.carryForward; + let resource: Model | null = null; - if (!deleteBy.props.isRoot && resource) { - if (resource && resource.order && resource.statusPageId) { - await this.rearrangeOrder( - resource.order, - resource.statusPageId, - false - ); - } - } - - return { - deleteBy: deleteBy, - carryForward: null, - }; + if (!deleteBy.props.isRoot) { + resource = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + }, + }); } - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if ( - updateBy.data.order && - !updateBy.props.isRoot && - updateBy.query._id - ) { - const resource: Model | null = await this.findOneBy({ - query: { - _id: updateBy.query._id!, - }, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - _id: true, - }, - }); + return { + deleteBy, + carryForward: resource, + }; + } - const currentOrder: number = resource?.order as number; - const newOrder: number = updateBy.data.order as number; + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const resource: Model | null = onDelete.carryForward; - const resources: Array<Model> = await this.findBy({ - query: { - statusPageId: resource?.statusPageId as ObjectID, - }, - - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - _id: true, - }, - }); - - if (currentOrder > newOrder) { - // moving up. - - for (const resource of resources) { - if ( - resource.order! >= newOrder && - resource.order! < currentOrder - ) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! + 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - - if (newOrder > currentOrder) { - // moving down. - - for (const resource of resources) { - if (resource.order! <= newOrder) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! - 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { updateBy, carryForward: null }; + if (!deleteBy.props.isRoot && resource) { + if (resource && resource.order && resource.statusPageId) { + await this.rearrangeOrder(resource.order, resource.statusPageId, false); + } } - private async rearrangeOrder( - currentOrder: number, - statusPageId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get status page resource with this order. - const resources: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - statusPageId: statusPageId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - let newOrder: number = currentOrder; + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.order && !updateBy.props.isRoot && updateBy.query._id) { + const resource: Model | null = await this.findOneBy({ + query: { + _id: updateBy.query._id!, + }, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + _id: true, + }, + }); + + const currentOrder: number = resource?.order as number; + const newOrder: number = updateBy.data.order as number; + + const resources: Array<Model> = await this.findBy({ + query: { + statusPageId: resource?.statusPageId as ObjectID, + }, + + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + _id: true, + }, + }); + + if (currentOrder > newOrder) { + // moving up. for (const resource of resources) { - if (increaseOrder) { - newOrder = resource.order! + 1; - } else { - newOrder = resource.order! - 1; - } - + if (resource.order! >= newOrder && resource.order! < currentOrder) { + // increment order. await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, + query: { + _id: resource._id!, + }, + data: { + order: resource.order! + 1, + }, + props: { + isRoot: true, + }, }); + } } + } + + if (newOrder > currentOrder) { + // moving down. + + for (const resource of resources) { + if (resource.order! <= newOrder) { + // increment order. + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: resource.order! - 1, + }, + props: { + isRoot: true, + }, + }); + } + } + } } + + return { updateBy, carryForward: null }; + } + + private async rearrangeOrder( + currentOrder: number, + statusPageId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get status page resource with this order. + const resources: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + statusPageId: statusPageId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); + + let newOrder: number = currentOrder; + + for (const resource of resources) { + if (increaseOrder) { + newOrder = resource.order! + 1; + } else { + newOrder = resource.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); + } + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageHistoryChartBarColorRuleService.ts b/CommonServer/Services/StatusPageHistoryChartBarColorRuleService.ts index 6ac523659d..b6bb8a921a 100644 --- a/CommonServer/Services/StatusPageHistoryChartBarColorRuleService.ts +++ b/CommonServer/Services/StatusPageHistoryChartBarColorRuleService.ts @@ -1,250 +1,239 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import Query from '../Types/Database/Query'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Model from 'Model/Models/StatusPageHistoryChartBarColorRule'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import Query from "../Types/Database/Query"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Model from "Model/Models/StatusPageHistoryChartBarColorRule"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.statusPageId) { + throw new BadDataException( + "Status Page Resource statusPageId is required", + ); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.statusPageId) { - throw new BadDataException( - 'Status Page Resource statusPageId is required' - ); - } + if (!createBy.data.order) { + const query: Query<Model> = { + statusPageId: createBy.data.statusPageId, + }; - if (!createBy.data.order) { - const query: Query<Model> = { - statusPageId: createBy.data.statusPageId, - }; + const count: PositiveNumber = await this.countBy({ + query: query, + props: { + isRoot: true, + }, + }); - const count: PositiveNumber = await this.countBy({ - query: query, - props: { - isRoot: true, - }, - }); - - createBy.data.order = count.toNumber() + 1; - } - - await this.rearrangeOrder( - createBy.data.order, - createBy.data.statusPageId, - true - ); - - return { - createBy: createBy, - carryForward: null, - }; + createBy.data.order = count.toNumber() + 1; } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting status page resource. Please try the delete with objectId' - ); - } + await this.rearrangeOrder( + createBy.data.order, + createBy.data.statusPageId, + true, + ); - let resource: Model | null = null; + return { + createBy: createBy, + carryForward: null, + }; + } - if (!deleteBy.props.isRoot) { - resource = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - }, - }); - } - - return { - deleteBy, - carryForward: resource, - }; + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting status page resource. Please try the delete with objectId", + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const resource: Model | null = onDelete.carryForward; + let resource: Model | null = null; - if (!deleteBy.props.isRoot && resource) { - if (resource && resource.order && resource.statusPageId) { - await this.rearrangeOrder( - resource.order, - resource.statusPageId, - false - ); - } - } - - return { - deleteBy: deleteBy, - carryForward: null, - }; + if (!deleteBy.props.isRoot) { + resource = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + }, + }); } - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if ( - updateBy.data.order && - !updateBy.props.isRoot && - updateBy.query._id - ) { - const resource: Model | null = await this.findOneBy({ - query: { - _id: updateBy.query._id!, - }, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - _id: true, - }, - }); + return { + deleteBy, + carryForward: resource, + }; + } - const currentOrder: number = resource?.order as number; - const newOrder: number = updateBy.data.order as number; + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const resource: Model | null = onDelete.carryForward; - const resources: Array<Model> = await this.findBy({ - query: { - statusPageId: resource?.statusPageId as ObjectID, - }, - - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - _id: true, - }, - }); - - if (currentOrder > newOrder) { - // moving up. - - for (const resource of resources) { - if ( - resource.order! >= newOrder && - resource.order! < currentOrder - ) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! + 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - - if (newOrder > currentOrder) { - // moving down. - - for (const resource of resources) { - if (resource.order! <= newOrder) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! - 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { updateBy, carryForward: null }; + if (!deleteBy.props.isRoot && resource) { + if (resource && resource.order && resource.statusPageId) { + await this.rearrangeOrder(resource.order, resource.statusPageId, false); + } } - private async rearrangeOrder( - currentOrder: number, - statusPageId: ObjectID, - increaseOrder: boolean = true - ): Promise<void> { - // get status page resource with this order. - const resources: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - statusPageId: statusPageId, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - let newOrder: number = currentOrder; + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.order && !updateBy.props.isRoot && updateBy.query._id) { + const resource: Model | null = await this.findOneBy({ + query: { + _id: updateBy.query._id!, + }, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + _id: true, + }, + }); + + const currentOrder: number = resource?.order as number; + const newOrder: number = updateBy.data.order as number; + + const resources: Array<Model> = await this.findBy({ + query: { + statusPageId: resource?.statusPageId as ObjectID, + }, + + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + _id: true, + }, + }); + + if (currentOrder > newOrder) { + // moving up. for (const resource of resources) { - if (increaseOrder) { - newOrder = resource.order! + 1; - } else { - newOrder = resource.order! - 1; - } - + if (resource.order! >= newOrder && resource.order! < currentOrder) { + // increment order. await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, + query: { + _id: resource._id!, + }, + data: { + order: resource.order! + 1, + }, + props: { + isRoot: true, + }, }); + } } + } + + if (newOrder > currentOrder) { + // moving down. + + for (const resource of resources) { + if (resource.order! <= newOrder) { + // increment order. + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: resource.order! - 1, + }, + props: { + isRoot: true, + }, + }); + } + } + } } + + return { updateBy, carryForward: null }; + } + + private async rearrangeOrder( + currentOrder: number, + statusPageId: ObjectID, + increaseOrder: boolean = true, + ): Promise<void> { + // get status page resource with this order. + const resources: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + statusPageId: statusPageId, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); + + let newOrder: number = currentOrder; + + for (const resource of resources) { + if (increaseOrder) { + newOrder = resource.order! + 1; + } else { + newOrder = resource.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); + } + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageOwnerTeamService.ts b/CommonServer/Services/StatusPageOwnerTeamService.ts index eba6fd1c1a..0fc3fd5619 100644 --- a/CommonServer/Services/StatusPageOwnerTeamService.ts +++ b/CommonServer/Services/StatusPageOwnerTeamService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/StatusPageOwnerTeam'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/StatusPageOwnerTeam"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageOwnerUserService.ts b/CommonServer/Services/StatusPageOwnerUserService.ts index 999965b7f0..220b246ea8 100644 --- a/CommonServer/Services/StatusPageOwnerUserService.ts +++ b/CommonServer/Services/StatusPageOwnerUserService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/StatusPageOwnerUser'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/StatusPageOwnerUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/StatusPagePrivateUserService.ts b/CommonServer/Services/StatusPagePrivateUserService.ts index 717a8c2f2b..179d62bdac 100644 --- a/CommonServer/Services/StatusPagePrivateUserService.ts +++ b/CommonServer/Services/StatusPagePrivateUserService.ts @@ -1,139 +1,138 @@ -import DatabaseConfig from '../DatabaseConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import logger from '../Utils/Logger'; -import DatabaseService from './DatabaseService'; -import MailService from './MailService'; -import StatusPageService from './StatusPageService'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import StatusPage from 'Model/Models/StatusPage'; -import Model from 'Model/Models/StatusPagePrivateUser'; +import DatabaseConfig from "../DatabaseConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import logger from "../Utils/Logger"; +import DatabaseService from "./DatabaseService"; +import MailService from "./MailService"; +import StatusPageService from "./StatusPageService"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import StatusPage from "Model/Models/StatusPage"; +import Model from "Model/Models/StatusPagePrivateUser"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - // check if this user is already invited. - if (createBy.data.statusPageId && createBy.data.email) { - const statusPageUser: Model | null = await this.findOneBy({ - query: { - email: createBy.data.email, - statusPageId: createBy.data.statusPageId, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + // check if this user is already invited. + if (createBy.data.statusPageId && createBy.data.email) { + const statusPageUser: Model | null = await this.findOneBy({ + query: { + email: createBy.data.email, + statusPageId: createBy.data.statusPageId, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); - if (statusPageUser) { - throw new BadDataException( - 'This user is already invited to this status page.' - ); - } - } - - return { - createBy: createBy, - carryForward: null, - }; - } - - protected override async onCreateSuccess( - _onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - // send email to the user. - const token: string = ObjectID.generate().toString(); - await this.updateOneById({ - id: createdItem.id!, - data: { - resetPasswordToken: token, - resetPasswordExpires: OneUptimeDate.getOneDayAfter(), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - if (createdItem.isSsoUser) { - return createdItem; - } - - const statusPage: StatusPage | null = - await StatusPageService.findOneById({ - id: createdItem.statusPageId!, - props: { - isRoot: true, - ignoreHooks: true, - }, - select: { - _id: true, - name: true, - pageTitle: true, - logoFileId: true, - projectId: true, - }, - }); - - if (!statusPage) { - throw new BadDataException('Status Page not found'); - } - - const statusPageName: string | undefined = - statusPage.pageTitle || statusPage.name; - - const statusPageURL: string = await StatusPageService.getStatusPageURL( - statusPage.id! + if (statusPageUser) { + throw new BadDataException( + "This user is already invited to this status page.", ); - - const host: Hostname = await DatabaseConfig.getHost(); - - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - - MailService.sendMail( - { - toEmail: createdItem.email!, - subject: 'You have been invited to ' + statusPageName, - templateType: EmailTemplateType.StatusPageWelcomeEmail, - vars: { - statusPageName: statusPageName!, - statusPageUrl: statusPageURL, - logoUrl: statusPage.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute('/image/' + statusPage.logoFileId) - .toString() - : '', - homeURL: statusPageURL, - tokenVerifyUrl: URL.fromString(statusPageURL) - .addRoute('/reset-password/' + token) - .toString(), - }, - }, - { - projectId: statusPage.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - - return createdItem; + } } + + return { + createBy: createBy, + carryForward: null, + }; + } + + protected override async onCreateSuccess( + _onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + // send email to the user. + const token: string = ObjectID.generate().toString(); + await this.updateOneById({ + id: createdItem.id!, + data: { + resetPasswordToken: token, + resetPasswordExpires: OneUptimeDate.getOneDayAfter(), + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + if (createdItem.isSsoUser) { + return createdItem; + } + + const statusPage: StatusPage | null = await StatusPageService.findOneById({ + id: createdItem.statusPageId!, + props: { + isRoot: true, + ignoreHooks: true, + }, + select: { + _id: true, + name: true, + pageTitle: true, + logoFileId: true, + projectId: true, + }, + }); + + if (!statusPage) { + throw new BadDataException("Status Page not found"); + } + + const statusPageName: string | undefined = + statusPage.pageTitle || statusPage.name; + + const statusPageURL: string = await StatusPageService.getStatusPageURL( + statusPage.id!, + ); + + const host: Hostname = await DatabaseConfig.getHost(); + + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + MailService.sendMail( + { + toEmail: createdItem.email!, + subject: "You have been invited to " + statusPageName, + templateType: EmailTemplateType.StatusPageWelcomeEmail, + vars: { + statusPageName: statusPageName!, + statusPageUrl: statusPageURL, + logoUrl: statusPage.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + statusPage.logoFileId) + .toString() + : "", + homeURL: statusPageURL, + tokenVerifyUrl: URL.fromString(statusPageURL) + .addRoute("/reset-password/" + token) + .toString(), + }, + }, + { + projectId: statusPage.projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); + + return createdItem; + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageResourceService.ts b/CommonServer/Services/StatusPageResourceService.ts index dee2864c4c..e3a1fcab35 100644 --- a/CommonServer/Services/StatusPageResourceService.ts +++ b/CommonServer/Services/StatusPageResourceService.ts @@ -1,269 +1,261 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import Query from '../Types/Database/Query'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Model from 'Model/Models/StatusPageResource'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import Query from "../Types/Database/Query"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Model from "Model/Models/StatusPageResource"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.statusPageId) { + throw new BadDataException( + "Status Page Resource statusPageId is required", + ); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.statusPageId) { - throw new BadDataException( - 'Status Page Resource statusPageId is required' - ); - } + if (!createBy.data.order) { + const query: Query<Model> = { + statusPageId: createBy.data.statusPageId, + statusPageGroupId: + createBy.data.statusPageGroupId || QueryHelper.isNull(), + }; - if (!createBy.data.order) { - const query: Query<Model> = { - statusPageId: createBy.data.statusPageId, - statusPageGroupId: - createBy.data.statusPageGroupId || QueryHelper.isNull(), - }; + if (createBy.data.statusPageGroupId) { + (query as any)["statusPageGroupId"] = createBy.data.statusPageGroupId; + } else { + (query as any)["statusPageGroupId"] = QueryHelper.isNull(); + } - if (createBy.data.statusPageGroupId) { - (query as any)['statusPageGroupId'] = - createBy.data.statusPageGroupId; - } else { - (query as any)['statusPageGroupId'] = QueryHelper.isNull(); - } + const count: PositiveNumber = await this.countBy({ + query: query, + props: { + isRoot: true, + }, + }); - const count: PositiveNumber = await this.countBy({ - query: query, - props: { - isRoot: true, - }, - }); + createBy.data.order = count.toNumber() + 1; + } - createBy.data.order = count.toNumber() + 1; - } + await this.rearrangeOrder( + createBy.data.order, + createBy.data.statusPageId, + createBy.data.statusPageGroupId || null, + true, + ); + return { + createBy: createBy, + carryForward: null, + }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + if (!deleteBy.query._id && !deleteBy.props.isRoot) { + throw new BadDataException( + "_id should be present when deleting status page resource. Please try the delete with objectId", + ); + } + + let resource: Model | null = null; + + if (!deleteBy.props.isRoot) { + resource = await this.findOneBy({ + query: deleteBy.query, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + statusPageGroupId: true, + }, + }); + } + + return { + deleteBy, + carryForward: resource, + }; + } + + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + const deleteBy: DeleteBy<Model> = onDelete.deleteBy; + const resource: Model | null = onDelete.carryForward; + + if (!deleteBy.props.isRoot && resource) { + if (resource && resource.order && resource.statusPageId) { await this.rearrangeOrder( - createBy.data.order, - createBy.data.statusPageId, - createBy.data.statusPageGroupId || null, - true + resource.order, + resource.statusPageId, + resource.statusPageGroupId || null, + false, ); - - return { - createBy: createBy, - carryForward: null, - }; + } } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - if (!deleteBy.query._id && !deleteBy.props.isRoot) { - throw new BadDataException( - '_id should be present when deleting status page resource. Please try the delete with objectId' - ); - } + return { + deleteBy: deleteBy, + carryForward: null, + }; + } - let resource: Model | null = null; + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.order && !updateBy.props.isRoot && updateBy.query._id) { + const resource: Model | null = await this.findOneBy({ + query: { + _id: updateBy.query._id!, + }, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + statusPageGroupId: true, + _id: true, + }, + }); - if (!deleteBy.props.isRoot) { - resource = await this.findOneBy({ - query: deleteBy.query, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - statusPageGroupId: true, - }, - }); - } + const currentOrder: number = resource?.order as number; + const newOrder: number = updateBy.data.order as number; - return { - deleteBy, - carryForward: resource, - }; - } + const resources: Array<Model> = await this.findBy({ + query: { + statusPageId: resource?.statusPageId as ObjectID, + statusPageGroupId: + resource?.statusPageGroupId || QueryHelper.isNull(), + }, - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - const deleteBy: DeleteBy<Model> = onDelete.deleteBy; - const resource: Model | null = onDelete.carryForward; + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + order: true, + statusPageId: true, + statusPageGroupId: true, + _id: true, + }, + }); - if (!deleteBy.props.isRoot && resource) { - if (resource && resource.order && resource.statusPageId) { - await this.rearrangeOrder( - resource.order, - resource.statusPageId, - resource.statusPageGroupId || null, - false - ); - } - } - - return { - deleteBy: deleteBy, - carryForward: null, - }; - } - - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if ( - updateBy.data.order && - !updateBy.props.isRoot && - updateBy.query._id - ) { - const resource: Model | null = await this.findOneBy({ - query: { - _id: updateBy.query._id!, - }, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - statusPageGroupId: true, - _id: true, - }, - }); - - const currentOrder: number = resource?.order as number; - const newOrder: number = updateBy.data.order as number; - - const resources: Array<Model> = await this.findBy({ - query: { - statusPageId: resource?.statusPageId as ObjectID, - statusPageGroupId: - resource?.statusPageGroupId || QueryHelper.isNull(), - }, - - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - order: true, - statusPageId: true, - statusPageGroupId: true, - _id: true, - }, - }); - - if (currentOrder > newOrder) { - // moving up. - - for (const resource of resources) { - if ( - resource.order! >= newOrder && - resource.order! < currentOrder - ) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! + 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - - if (newOrder > currentOrder) { - // moving down. - - for (const resource of resources) { - if (resource.order! <= newOrder) { - // increment order. - await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: resource.order! - 1, - }, - props: { - isRoot: true, - }, - }); - } - } - } - } - - return { updateBy, carryForward: null }; - } - - private async rearrangeOrder( - currentOrder: number, - statusPageId: ObjectID, - statusPageGroupId: ObjectID | null, - increaseOrder: boolean = true - ): Promise<void> { - // get status page resource with this order. - const resources: Array<Model> = await this.findBy({ - query: { - order: QueryHelper.greaterThanEqualTo(currentOrder), - statusPageId: statusPageId, - statusPageGroupId: statusPageGroupId - ? statusPageGroupId - : QueryHelper.isNull(), - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - select: { - _id: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); - - let newOrder: number = currentOrder; + if (currentOrder > newOrder) { + // moving up. for (const resource of resources) { - if (increaseOrder) { - newOrder = resource.order! + 1; - } else { - newOrder = resource.order! - 1; - } - + if (resource.order! >= newOrder && resource.order! < currentOrder) { + // increment order. await this.updateOneBy({ - query: { - _id: resource._id!, - }, - data: { - order: newOrder, - }, - props: { - isRoot: true, - }, + query: { + _id: resource._id!, + }, + data: { + order: resource.order! + 1, + }, + props: { + isRoot: true, + }, }); + } } + } + + if (newOrder > currentOrder) { + // moving down. + + for (const resource of resources) { + if (resource.order! <= newOrder) { + // increment order. + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: resource.order! - 1, + }, + props: { + isRoot: true, + }, + }); + } + } + } } + + return { updateBy, carryForward: null }; + } + + private async rearrangeOrder( + currentOrder: number, + statusPageId: ObjectID, + statusPageGroupId: ObjectID | null, + increaseOrder: boolean = true, + ): Promise<void> { + // get status page resource with this order. + const resources: Array<Model> = await this.findBy({ + query: { + order: QueryHelper.greaterThanEqualTo(currentOrder), + statusPageId: statusPageId, + statusPageGroupId: statusPageGroupId + ? statusPageGroupId + : QueryHelper.isNull(), + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + select: { + _id: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); + + let newOrder: number = currentOrder; + + for (const resource of resources) { + if (increaseOrder) { + newOrder = resource.order! + 1; + } else { + newOrder = resource.order! - 1; + } + + await this.updateOneBy({ + query: { + _id: resource._id!, + }, + data: { + order: newOrder, + }, + props: { + isRoot: true, + }, + }); + } + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageService.ts b/CommonServer/Services/StatusPageService.ts index 0babe3a586..9a2a14ec50 100755 --- a/CommonServer/Services/StatusPageService.ts +++ b/CommonServer/Services/StatusPageService.ts @@ -1,427 +1,414 @@ -import DatabaseConfig from '../DatabaseConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate, OnUpdate } from '../Types/Database/Hooks'; -import UpdateBy from '../Types/Database/UpdateBy'; -import CookieUtil from '../Utils/Cookie'; -import { ExpressRequest } from '../Utils/Express'; -import JSONWebToken from '../Utils/JsonWebToken'; -import logger from '../Utils/Logger'; -import DatabaseService from './DatabaseService'; -import MonitorStatusService from './MonitorStatusService'; -import ProjectService from './ProjectService'; -import StatusPageDomainService from './StatusPageDomainService'; -import StatusPageOwnerTeamService from './StatusPageOwnerTeamService'; -import StatusPageOwnerUserService from './StatusPageOwnerUserService'; -import TeamMemberService from './TeamMemberService'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import { Green } from 'Common/Types/BrandColors'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import JSONWebTokenData from 'Common/Types/JsonWebTokenData'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Typeof from 'Common/Types/Typeof'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageDomain from 'Model/Models/StatusPageDomain'; -import StatusPageOwnerTeam from 'Model/Models/StatusPageOwnerTeam'; -import StatusPageOwnerUser from 'Model/Models/StatusPageOwnerUser'; -import User from 'Model/Models/User'; +import DatabaseConfig from "../DatabaseConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate, OnUpdate } from "../Types/Database/Hooks"; +import UpdateBy from "../Types/Database/UpdateBy"; +import CookieUtil from "../Utils/Cookie"; +import { ExpressRequest } from "../Utils/Express"; +import JSONWebToken from "../Utils/JsonWebToken"; +import logger from "../Utils/Logger"; +import DatabaseService from "./DatabaseService"; +import MonitorStatusService from "./MonitorStatusService"; +import ProjectService from "./ProjectService"; +import StatusPageDomainService from "./StatusPageDomainService"; +import StatusPageOwnerTeamService from "./StatusPageOwnerTeamService"; +import StatusPageOwnerUserService from "./StatusPageOwnerUserService"; +import TeamMemberService from "./TeamMemberService"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import { Green } from "Common/Types/BrandColors"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import JSONWebTokenData from "Common/Types/JsonWebTokenData"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Typeof from "Common/Types/Typeof"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageDomain from "Model/Models/StatusPageDomain"; +import StatusPageOwnerTeam from "Model/Models/StatusPageOwnerTeam"; +import StatusPageOwnerUser from "Model/Models/StatusPageOwnerUser"; +import User from "Model/Models/User"; export class Service extends DatabaseService<StatusPage> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(StatusPage, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(StatusPage, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<StatusPage>, + ): Promise<OnCreate<StatusPage>> { + if (!createBy.data.projectId) { + throw new BadDataException("projectId is required"); } - protected override async onBeforeCreate( - createBy: CreateBy<StatusPage> - ): Promise<OnCreate<StatusPage>> { - if (!createBy.data.projectId) { - throw new BadDataException('projectId is required'); - } + if ( + !createBy.data.downtimeMonitorStatuses || + createBy.data.downtimeMonitorStatuses.length === 0 + ) { + const monitorStatuses: Array<MonitorStatus> = + await MonitorStatusService.findBy({ + query: { + projectId: createBy.data.projectId, + }, + select: { + _id: true, + isOperationalState: true, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + }); - if ( - !createBy.data.downtimeMonitorStatuses || - createBy.data.downtimeMonitorStatuses.length === 0 - ) { - const monitorStatuses: Array<MonitorStatus> = - await MonitorStatusService.findBy({ - query: { - projectId: createBy.data.projectId, - }, - select: { - _id: true, - isOperationalState: true, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - }); + const getNonOperationStatuses: Array<MonitorStatus> = + monitorStatuses.filter((monitorStatus: MonitorStatus) => { + return !monitorStatus.isOperationalState; + }); - const getNonOperationStatuses: Array<MonitorStatus> = - monitorStatuses.filter((monitorStatus: MonitorStatus) => { - return !monitorStatus.isOperationalState; - }); - - createBy.data.downtimeMonitorStatuses = getNonOperationStatuses; - } - - if (!createBy.data.defaultBarColor) { - createBy.data.defaultBarColor = Green; - } - - return { - createBy, - carryForward: null, - }; + createBy.data.downtimeMonitorStatuses = getNonOperationStatuses; } - protected override async onCreateSuccess( - onCreate: OnCreate<StatusPage>, - createdItem: StatusPage - ): Promise<StatusPage> { - // add owners. - - if ( - createdItem.projectId && - createdItem.id && - onCreate.createBy.miscDataProps && - (onCreate.createBy.miscDataProps['ownerTeams'] || - onCreate.createBy.miscDataProps['ownerUsers']) - ) { - await this.addOwners( - createdItem.projectId!, - createdItem.id!, - (onCreate.createBy.miscDataProps[ - 'ownerUsers' - ] as Array<ObjectID>) || [], - (onCreate.createBy.miscDataProps[ - 'ownerTeams' - ] as Array<ObjectID>) || [], - false, - onCreate.createBy.props - ); - } - - return createdItem; + if (!createBy.data.defaultBarColor) { + createBy.data.defaultBarColor = Green; } - public async findOwners(statusPageId: ObjectID): Promise<Array<User>> { - if (!statusPageId) { - throw new BadDataException('statusPageId is required'); - } + return { + createBy, + carryForward: null, + }; + } - const ownerUsers: Array<StatusPageOwnerUser> = - await StatusPageOwnerUserService.findBy({ - query: { - statusPageId: statusPageId, - }, - select: { - _id: true, - user: { - _id: true, - email: true, - name: true, - }, - }, - props: { - isRoot: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - }); + protected override async onCreateSuccess( + onCreate: OnCreate<StatusPage>, + createdItem: StatusPage, + ): Promise<StatusPage> { + // add owners. - const ownerTeams: Array<StatusPageOwnerTeam> = - await StatusPageOwnerTeamService.findBy({ - query: { - statusPageId: statusPageId, - }, - select: { - _id: true, - teamId: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - const users: Array<User> = - ownerUsers.map((ownerUser: StatusPageOwnerUser) => { - return ownerUser.user!; - }) || []; - - if (ownerTeams.length > 0) { - const teamIds: Array<ObjectID> = - ownerTeams.map((ownerTeam: StatusPageOwnerTeam) => { - return ownerTeam.teamId!; - }) || []; - - const teamUsers: Array<User> = - await TeamMemberService.getUsersInTeams(teamIds); - - for (const teamUser of teamUsers) { - //check if the user is already added. - const isUserAlreadyAdded: User | undefined = users.find( - (user: User) => { - return user.id!.toString() === teamUser.id!.toString(); - } - ); - - if (!isUserAlreadyAdded) { - users.push(teamUser); - } - } - } - - return users; + if ( + createdItem.projectId && + createdItem.id && + onCreate.createBy.miscDataProps && + (onCreate.createBy.miscDataProps["ownerTeams"] || + onCreate.createBy.miscDataProps["ownerUsers"]) + ) { + await this.addOwners( + createdItem.projectId!, + createdItem.id!, + (onCreate.createBy.miscDataProps["ownerUsers"] as Array<ObjectID>) || + [], + (onCreate.createBy.miscDataProps["ownerTeams"] as Array<ObjectID>) || + [], + false, + onCreate.createBy.props, + ); } - public async addOwners( - projectId: ObjectID, - statusPageId: ObjectID, - userIds: Array<ObjectID>, - teamIds: Array<ObjectID>, - notifyOwners: boolean, - props: DatabaseCommonInteractionProps - ): Promise<void> { - for (let teamId of teamIds) { - if (typeof teamId === Typeof.String) { - teamId = new ObjectID(teamId.toString()); - } + return createdItem; + } - const teamOwner: StatusPageOwnerTeam = new StatusPageOwnerTeam(); - teamOwner.statusPageId = statusPageId; - teamOwner.projectId = projectId; - teamOwner.teamId = teamId; - teamOwner.isOwnerNotified = !notifyOwners; - - await StatusPageOwnerTeamService.create({ - data: teamOwner, - props: props, - }); - } - - for (let userId of userIds) { - if (typeof userId === Typeof.String) { - userId = new ObjectID(userId.toString()); - } - const teamOwner: StatusPageOwnerUser = new StatusPageOwnerUser(); - teamOwner.statusPageId = statusPageId; - teamOwner.projectId = projectId; - teamOwner.userId = userId; - teamOwner.isOwnerNotified = !notifyOwners; - await StatusPageOwnerUserService.create({ - data: teamOwner, - props: props, - }); - } + public async findOwners(statusPageId: ObjectID): Promise<Array<User>> { + if (!statusPageId) { + throw new BadDataException("statusPageId is required"); } - public async getStatusPageLinkInDashboard( - projectId: ObjectID, - statusPageId: ObjectID - ): Promise<URL> { - const dahboardUrl: URL = await DatabaseConfig.getDashboardUrl(); + const ownerUsers: Array<StatusPageOwnerUser> = + await StatusPageOwnerUserService.findBy({ + query: { + statusPageId: statusPageId, + }, + select: { + _id: true, + user: { + _id: true, + email: true, + name: true, + }, + }, + props: { + isRoot: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + }); - return URL.fromString(dahboardUrl.toString()).addRoute( - `/${projectId.toString()}/status-pages/${statusPageId.toString()}` + const ownerTeams: Array<StatusPageOwnerTeam> = + await StatusPageOwnerTeamService.findBy({ + query: { + statusPageId: statusPageId, + }, + select: { + _id: true, + teamId: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + const users: Array<User> = + ownerUsers.map((ownerUser: StatusPageOwnerUser) => { + return ownerUser.user!; + }) || []; + + if (ownerTeams.length > 0) { + const teamIds: Array<ObjectID> = + ownerTeams.map((ownerTeam: StatusPageOwnerTeam) => { + return ownerTeam.teamId!; + }) || []; + + const teamUsers: Array<User> = + await TeamMemberService.getUsersInTeams(teamIds); + + for (const teamUser of teamUsers) { + //check if the user is already added. + const isUserAlreadyAdded: User | undefined = users.find( + (user: User) => { + return user.id!.toString() === teamUser.id!.toString(); + }, ); + + if (!isUserAlreadyAdded) { + users.push(teamUser); + } + } } - public async hasReadAccess( - statusPageId: ObjectID, - props: DatabaseCommonInteractionProps, - req: ExpressRequest - ): Promise<boolean> { + return users; + } + + public async addOwners( + projectId: ObjectID, + statusPageId: ObjectID, + userIds: Array<ObjectID>, + teamIds: Array<ObjectID>, + notifyOwners: boolean, + props: DatabaseCommonInteractionProps, + ): Promise<void> { + for (let teamId of teamIds) { + if (typeof teamId === Typeof.String) { + teamId = new ObjectID(teamId.toString()); + } + + const teamOwner: StatusPageOwnerTeam = new StatusPageOwnerTeam(); + teamOwner.statusPageId = statusPageId; + teamOwner.projectId = projectId; + teamOwner.teamId = teamId; + teamOwner.isOwnerNotified = !notifyOwners; + + await StatusPageOwnerTeamService.create({ + data: teamOwner, + props: props, + }); + } + + for (let userId of userIds) { + if (typeof userId === Typeof.String) { + userId = new ObjectID(userId.toString()); + } + const teamOwner: StatusPageOwnerUser = new StatusPageOwnerUser(); + teamOwner.statusPageId = statusPageId; + teamOwner.projectId = projectId; + teamOwner.userId = userId; + teamOwner.isOwnerNotified = !notifyOwners; + await StatusPageOwnerUserService.create({ + data: teamOwner, + props: props, + }); + } + } + + public async getStatusPageLinkInDashboard( + projectId: ObjectID, + statusPageId: ObjectID, + ): Promise<URL> { + const dahboardUrl: URL = await DatabaseConfig.getDashboardUrl(); + + return URL.fromString(dahboardUrl.toString()).addRoute( + `/${projectId.toString()}/status-pages/${statusPageId.toString()}`, + ); + } + + public async hasReadAccess( + statusPageId: ObjectID, + props: DatabaseCommonInteractionProps, + req: ExpressRequest, + ): Promise<boolean> { + try { + // token decode. + const token: string | undefined = CookieUtil.getCookie( + req, + CookieUtil.getUserTokenKey(statusPageId), + ); + + if (token) { try { - // token decode. - const token: string | undefined = CookieUtil.getCookie( - req, - CookieUtil.getUserTokenKey(statusPageId) - ); + const decoded: JSONWebTokenData = JSONWebToken.decode( + token as string, + ); - if (token) { - try { - const decoded: JSONWebTokenData = JSONWebToken.decode( - token as string - ); - - if ( - decoded.statusPageId?.toString() === - statusPageId.toString() - ) { - return true; - } - } catch (err) { - logger.error(err); - } - } - - const count: PositiveNumber = await this.countBy({ - query: { - _id: statusPageId.toString(), - isPublicStatusPage: true, - }, - skip: 0, - limit: 1, - props: { - isRoot: true, - }, - }); - - if (count.positiveNumber > 0) { - return true; - } - - // if it does not have public access, check if this user has access. - - const items: Array<StatusPage> = await this.findBy({ - query: { - _id: statusPageId.toString(), - }, - select: { - _id: true, - }, - skip: 0, - limit: 1, - props: props, - }); - - if (items.length > 0) { - return true; - } + if (decoded.statusPageId?.toString() === statusPageId.toString()) { + return true; + } } catch (err) { - logger.error(err); + logger.error(err); } + } - return false; + const count: PositiveNumber = await this.countBy({ + query: { + _id: statusPageId.toString(), + isPublicStatusPage: true, + }, + skip: 0, + limit: 1, + props: { + isRoot: true, + }, + }); + + if (count.positiveNumber > 0) { + return true; + } + + // if it does not have public access, check if this user has access. + + const items: Array<StatusPage> = await this.findBy({ + query: { + _id: statusPageId.toString(), + }, + select: { + _id: true, + }, + skip: 0, + limit: 1, + props: props, + }); + + if (items.length > 0) { + return true; + } + } catch (err) { + logger.error(err); } - public async getStatusPageURL(statusPageId: ObjectID): Promise<string> { - const domains: Array<StatusPageDomain> = - await StatusPageDomainService.findBy({ - query: { - statusPageId: statusPageId, - isSslProvisioned: true, - }, - select: { - fullDomain: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + return false; + } - let statusPageURL: string = domains - .map((d: StatusPageDomain) => { - return d.fullDomain; - }) - .join(', '); + public async getStatusPageURL(statusPageId: ObjectID): Promise<string> { + const domains: Array<StatusPageDomain> = + await StatusPageDomainService.findBy({ + query: { + statusPageId: statusPageId, + isSslProvisioned: true, + }, + select: { + fullDomain: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); - if (domains.length === 0) { - const host: Hostname = await DatabaseConfig.getHost(); + let statusPageURL: string = domains + .map((d: StatusPageDomain) => { + return d.fullDomain; + }) + .join(", "); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); + if (domains.length === 0) { + const host: Hostname = await DatabaseConfig.getHost(); - // 'https://local.oneuptime.com/status-page/40092fb5-cc33-4995-b532-b4e49c441c98' - statusPageURL = new URL(httpProtocol, host) - .addRoute('/status-page/' + statusPageId.toString()) - .toString(); - } + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - return statusPageURL; + // 'https://local.oneuptime.com/status-page/40092fb5-cc33-4995-b532-b4e49c441c98' + statusPageURL = new URL(httpProtocol, host) + .addRoute("/status-page/" + statusPageId.toString()) + .toString(); } - public async getStatusPageFirstURL( - statusPageId: ObjectID - ): Promise<string> { - const domains: Array<StatusPageDomain> = - await StatusPageDomainService.findBy({ - query: { - statusPageId: statusPageId, - isSslProvisioned: true, - }, - select: { - fullDomain: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + return statusPageURL; + } - let statusPageURL: string = ''; + public async getStatusPageFirstURL(statusPageId: ObjectID): Promise<string> { + const domains: Array<StatusPageDomain> = + await StatusPageDomainService.findBy({ + query: { + statusPageId: statusPageId, + isSslProvisioned: true, + }, + select: { + fullDomain: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); - if (domains.length === 0) { - const host: Hostname = await DatabaseConfig.getHost(); + let statusPageURL: string = ""; - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); + if (domains.length === 0) { + const host: Hostname = await DatabaseConfig.getHost(); - // 'https://local.oneuptime.com/status-page/40092fb5-cc33-4995-b532-b4e49c441c98' - statusPageURL = new URL(httpProtocol, host) - .addRoute('/status-page/' + statusPageId.toString()) - .toString(); - } else { - statusPageURL = domains[0]?.fullDomain || ''; - } + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - return statusPageURL; + // 'https://local.oneuptime.com/status-page/40092fb5-cc33-4995-b532-b4e49c441c98' + statusPageURL = new URL(httpProtocol, host) + .addRoute("/status-page/" + statusPageId.toString()) + .toString(); + } else { + statusPageURL = domains[0]?.fullDomain || ""; } - protected override async onBeforeUpdate( - updateBy: UpdateBy<StatusPage> - ): Promise<OnUpdate<StatusPage>> { - // is enabling SMS subscribers. + return statusPageURL; + } - if (updateBy.data.enableSmsSubscribers) { - const statusPagesToBeUpdated: Array<StatusPage> = await this.findBy( - { - query: updateBy.query, - select: { - _id: true, - projectId: true, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - } - ); + protected override async onBeforeUpdate( + updateBy: UpdateBy<StatusPage>, + ): Promise<OnUpdate<StatusPage>> { + // is enabling SMS subscribers. - for (const statusPage of statusPagesToBeUpdated) { - const isSMSEnabled: boolean = - await ProjectService.isSMSNotificationsEnabled( - statusPage.projectId! - ); + if (updateBy.data.enableSmsSubscribers) { + const statusPagesToBeUpdated: Array<StatusPage> = await this.findBy({ + query: updateBy.query, + select: { + _id: true, + projectId: true, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + }); - if (!isSMSEnabled) { - throw new BadDataException( - 'SMS notifications are not enabled for this project. Please enable SMS notifications in the Project Settings > Notifications Settings.' - ); - } - } + for (const statusPage of statusPagesToBeUpdated) { + const isSMSEnabled: boolean = + await ProjectService.isSMSNotificationsEnabled(statusPage.projectId!); + + if (!isSMSEnabled) { + throw new BadDataException( + "SMS notifications are not enabled for this project. Please enable SMS notifications in the Project Settings > Notifications Settings.", + ); } - - return { - carryForward: null, - updateBy: updateBy, - }; + } } + + return { + carryForward: null, + updateBy: updateBy, + }; + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageSsoService.ts b/CommonServer/Services/StatusPageSsoService.ts index fa80d80f85..def8e2584e 100644 --- a/CommonServer/Services/StatusPageSsoService.ts +++ b/CommonServer/Services/StatusPageSsoService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/StatusPageSso'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/StatusPageSso"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Services/StatusPageSubscriberService.ts b/CommonServer/Services/StatusPageSubscriberService.ts index cac690ec9b..36b3e2da20 100644 --- a/CommonServer/Services/StatusPageSubscriberService.ts +++ b/CommonServer/Services/StatusPageSubscriberService.ts @@ -1,377 +1,364 @@ -import DatabaseConfig from '../DatabaseConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import ProjectSMTPConfigService from '../Services/ProjectSmtpConfigService'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import logger from '../Utils/Logger'; -import DatabaseService from './DatabaseService'; -import MailService from './MailService'; -import ProjectCallSMSConfigService from './ProjectCallSMSConfigService'; -import ProjectService from './ProjectService'; -import SmsService from './SmsService'; -import StatusPageService from './StatusPageService'; -import { FileRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageResource from 'Model/Models/StatusPageResource'; -import Model from 'Model/Models/StatusPageSubscriber'; +import DatabaseConfig from "../DatabaseConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import ProjectSMTPConfigService from "../Services/ProjectSmtpConfigService"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import logger from "../Utils/Logger"; +import DatabaseService from "./DatabaseService"; +import MailService from "./MailService"; +import ProjectCallSMSConfigService from "./ProjectCallSMSConfigService"; +import ProjectService from "./ProjectService"; +import SmsService from "./SmsService"; +import StatusPageService from "./StatusPageService"; +import { FileRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageResource from "Model/Models/StatusPageResource"; +import Model from "Model/Models/StatusPageSubscriber"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + data: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!data.data.statusPageId) { + throw new BadDataException("Status Page ID is required."); } - protected override async onBeforeCreate( - data: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!data.data.statusPageId) { - throw new BadDataException('Status Page ID is required.'); - } - - if (!data.data.projectId) { - throw new BadDataException('Project ID is required.'); - } - - const projectId: ObjectID = data.data.projectId; - - let subscriber: Model | null = null; - - if (data.data.subscriberEmail) { - subscriber = await this.findOneBy({ - query: { - statusPageId: data.data.statusPageId, - subscriberEmail: data.data.subscriberEmail, - }, - select: { - _id: true, - isUnsubscribed: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - } - - if (data.data.subscriberPhone) { - // check if this project has SMS enabled. - - const isSMSEnabled: boolean = - await ProjectService.isSMSNotificationsEnabled(projectId); - - if (!isSMSEnabled) { - throw new BadDataException( - 'SMS notifications are not enabled for this project. Please enable SMS notifications in the Project Settings > Notifications Settings.' - ); - } - - subscriber = await this.findOneBy({ - query: { - statusPageId: data.data.statusPageId, - subscriberPhone: data.data.subscriberPhone, - }, - select: { - _id: true, - isUnsubscribed: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - } - - if (subscriber && !subscriber.isUnsubscribed) { - throw new BadDataException( - 'You are already subscribed to this status page.' - ); - } - - // if the user is unsubscribed, delete this record and it'll create a new one. - if (subscriber) { - await this.deleteOneBy({ - query: { - _id: subscriber?._id as string, - }, - props: { - ignoreHooks: true, - isRoot: true, - }, - }); - } - - const statuspages: Array<StatusPage> = - await this.getStatusPagesToSendNotification([ - data.data.statusPageId, - ]); - - const statuspage: StatusPage | undefined = statuspages.find( - (statuspage: StatusPage) => { - return ( - statuspage._id?.toString() === - data.data.statusPageId?.toString() - ); - } - ); - - if (statuspage && !statuspage.allowSubscribersToChooseResources) { - data.data.isSubscribedToAllResources = true; - } else if ( - !data.data.statusPageResources || - data.data.statusPageResources.length === 0 - ) { - if (!data.data.isSubscribedToAllResources) { - throw new BadDataException('Select resources to subscribe to.'); - } - } - - if (!statuspage || !statuspage.projectId) { - throw new BadDataException('Status Page not found'); - } - - data.data.projectId = statuspage.projectId; - - return { createBy: data, carryForward: statuspage }; + if (!data.data.projectId) { + throw new BadDataException("Project ID is required."); } - protected override async onCreateSuccess( - onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - if (!createdItem.statusPageId) { - return createdItem; - } + const projectId: ObjectID = data.data.projectId; - const statusPageURL: string = await StatusPageService.getStatusPageURL( - createdItem.statusPageId + let subscriber: Model | null = null; + + if (data.data.subscriberEmail) { + subscriber = await this.findOneBy({ + query: { + statusPageId: data.data.statusPageId, + subscriberEmail: data.data.subscriberEmail, + }, + select: { + _id: true, + isUnsubscribed: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + } + + if (data.data.subscriberPhone) { + // check if this project has SMS enabled. + + const isSMSEnabled: boolean = + await ProjectService.isSMSNotificationsEnabled(projectId); + + if (!isSMSEnabled) { + throw new BadDataException( + "SMS notifications are not enabled for this project. Please enable SMS notifications in the Project Settings > Notifications Settings.", ); + } - const statusPageName: string = - onCreate.carryForward.pageTitle || - onCreate.carryForward.name || - 'Status Page'; + subscriber = await this.findOneBy({ + query: { + statusPageId: data.data.statusPageId, + subscriberPhone: data.data.subscriberPhone, + }, + select: { + _id: true, + isUnsubscribed: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + } - const host: Hostname = await DatabaseConfig.getHost(); + if (subscriber && !subscriber.isUnsubscribed) { + throw new BadDataException( + "You are already subscribed to this status page.", + ); + } - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + // if the user is unsubscribed, delete this record and it'll create a new one. + if (subscriber) { + await this.deleteOneBy({ + query: { + _id: subscriber?._id as string, + }, + props: { + ignoreHooks: true, + isRoot: true, + }, + }); + } - const unsubscribeLink: string = this.getUnsubscribeLink( - URL.fromString(statusPageURL), - createdItem.id! - ).toString(); + const statuspages: Array<StatusPage> = + await this.getStatusPagesToSendNotification([data.data.statusPageId]); - if ( - createdItem.statusPageId && - createdItem.subscriberPhone && - createdItem._id && - createdItem.sendYouHaveSubscribedMessage - ) { - const statusPage: StatusPage | null = - await StatusPageService.findOneBy({ - query: { - _id: createdItem.statusPageId.toString(), - }, - select: { - callSmsConfig: { - _id: true, - twilioAccountSID: true, - twilioAuthToken: true, - twilioPhoneNumber: true, - }, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + const statuspage: StatusPage | undefined = statuspages.find( + (statuspage: StatusPage) => { + return ( + statuspage._id?.toString() === data.data.statusPageId?.toString() + ); + }, + ); - if (!statusPage) { - return createdItem; - } + if (statuspage && !statuspage.allowSubscribersToChooseResources) { + data.data.isSubscribedToAllResources = true; + } else if ( + !data.data.statusPageResources || + data.data.statusPageResources.length === 0 + ) { + if (!data.data.isSubscribedToAllResources) { + throw new BadDataException("Select resources to subscribe to."); + } + } - SmsService.sendSms( - { - to: createdItem.subscriberPhone, - message: `You have been subscribed to ${statusPageName}. To unsubscribe, click on the link: ${unsubscribeLink}`, - }, - { - projectId: createdItem.projectId, - isSensitive: false, - customTwilioConfig: - ProjectCallSMSConfigService.toTwilioConfig( - statusPage.callSmsConfig - ), - } - ).catch((err: Error) => { - logger.error(err); - }); - } + if (!statuspage || !statuspage.projectId) { + throw new BadDataException("Status Page not found"); + } - if ( - createdItem.statusPageId && - createdItem.subscriberEmail && - createdItem._id && - createdItem.sendYouHaveSubscribedMessage - ) { - // Call mail service and send an email. + data.data.projectId = statuspage.projectId; - // get status page domain for this status page. - // if the domain is not found, use the internal status page preview link. + return { createBy: data, carryForward: statuspage }; + } - MailService.sendMail( - { - toEmail: createdItem.subscriberEmail, - templateType: EmailTemplateType.SubscribedToStatusPage, - vars: { - statusPageName: statusPageName, - logoUrl: onCreate.carryForward.logoFileId - ? new URL(httpProtocol, host) - .addRoute(FileRoute) - .addRoute( - '/image/' + - onCreate.carryForward.logoFileId - ) - .toString() - : '', - statusPageUrl: statusPageURL, - isPublicStatusPage: onCreate.carryForward - .isPublicStatusPage - ? 'true' - : 'false', - unsubscribeUrl: unsubscribeLink, - }, - subject: 'You have been subscribed to ' + statusPageName, - }, - { - projectId: createdItem.projectId, - mailServer: ProjectSMTPConfigService.toEmailServer( - onCreate.carryForward.smtpConfig - ), - } - ).catch((err: Error) => { - logger.error(err); - }); - } + protected override async onCreateSuccess( + onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + if (!createdItem.statusPageId) { + return createdItem; + } + const statusPageURL: string = await StatusPageService.getStatusPageURL( + createdItem.statusPageId, + ); + + const statusPageName: string = + onCreate.carryForward.pageTitle || + onCreate.carryForward.name || + "Status Page"; + + const host: Hostname = await DatabaseConfig.getHost(); + + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + const unsubscribeLink: string = this.getUnsubscribeLink( + URL.fromString(statusPageURL), + createdItem.id!, + ).toString(); + + if ( + createdItem.statusPageId && + createdItem.subscriberPhone && + createdItem._id && + createdItem.sendYouHaveSubscribedMessage + ) { + const statusPage: StatusPage | null = await StatusPageService.findOneBy({ + query: { + _id: createdItem.statusPageId.toString(), + }, + select: { + callSmsConfig: { + _id: true, + twilioAccountSID: true, + twilioAuthToken: true, + twilioPhoneNumber: true, + }, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + if (!statusPage) { return createdItem; + } + + SmsService.sendSms( + { + to: createdItem.subscriberPhone, + message: `You have been subscribed to ${statusPageName}. To unsubscribe, click on the link: ${unsubscribeLink}`, + }, + { + projectId: createdItem.projectId, + isSensitive: false, + customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig( + statusPage.callSmsConfig, + ), + }, + ).catch((err: Error) => { + logger.error(err); + }); } - public async getSubscribersByStatusPage( - statusPageId: ObjectID, - props: DatabaseCommonInteractionProps - ): Promise<Array<Model>> { - return await this.findBy({ - query: { - statusPageId: statusPageId, - isUnsubscribed: false, - }, - select: { - _id: true, - subscriberEmail: true, - subscriberPhone: true, - subscriberWebhook: true, - isSubscribedToAllResources: true, - statusPageResources: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: props, - }); + if ( + createdItem.statusPageId && + createdItem.subscriberEmail && + createdItem._id && + createdItem.sendYouHaveSubscribedMessage + ) { + // Call mail service and send an email. + + // get status page domain for this status page. + // if the domain is not found, use the internal status page preview link. + + MailService.sendMail( + { + toEmail: createdItem.subscriberEmail, + templateType: EmailTemplateType.SubscribedToStatusPage, + vars: { + statusPageName: statusPageName, + logoUrl: onCreate.carryForward.logoFileId + ? new URL(httpProtocol, host) + .addRoute(FileRoute) + .addRoute("/image/" + onCreate.carryForward.logoFileId) + .toString() + : "", + statusPageUrl: statusPageURL, + isPublicStatusPage: onCreate.carryForward.isPublicStatusPage + ? "true" + : "false", + unsubscribeUrl: unsubscribeLink, + }, + subject: "You have been subscribed to " + statusPageName, + }, + { + projectId: createdItem.projectId, + mailServer: ProjectSMTPConfigService.toEmailServer( + onCreate.carryForward.smtpConfig, + ), + }, + ).catch((err: Error) => { + logger.error(err); + }); } - public getUnsubscribeLink( - statusPageUrl: URL, - statusPageSubscriberId: ObjectID - ): URL { - return URL.fromString(statusPageUrl.toString()).addRoute( - '/update-subscription/' + statusPageSubscriberId.toString() - ); + return createdItem; + } + + public async getSubscribersByStatusPage( + statusPageId: ObjectID, + props: DatabaseCommonInteractionProps, + ): Promise<Array<Model>> { + return await this.findBy({ + query: { + statusPageId: statusPageId, + isUnsubscribed: false, + }, + select: { + _id: true, + subscriberEmail: true, + subscriberPhone: true, + subscriberWebhook: true, + isSubscribedToAllResources: true, + statusPageResources: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: props, + }); + } + + public getUnsubscribeLink( + statusPageUrl: URL, + statusPageSubscriberId: ObjectID, + ): URL { + return URL.fromString(statusPageUrl.toString()).addRoute( + "/update-subscription/" + statusPageSubscriberId.toString(), + ); + } + + public shouldSendNotification(data: { + subscriber: Model; + statusPageResources: Array<StatusPageResource>; + statusPage: StatusPage; + }): boolean { + if (data.subscriber.isUnsubscribed) { + return false; } - public shouldSendNotification(data: { - subscriber: Model; - statusPageResources: Array<StatusPageResource>; - statusPage: StatusPage; - }): boolean { - if (data.subscriber.isUnsubscribed) { - return false; - } - - if (!data.statusPage.allowSubscribersToChooseResources) { - return true; - } - - if (data.subscriber.isSubscribedToAllResources) { - return true; - } - - const subscriberResourceIds: Array<string> = - data.subscriber.statusPageResources?.map( - (resource: StatusPageResource) => { - return resource.id?.toString() as string; - } - ) || []; - - for (const resource of data.statusPageResources) { - if ( - subscriberResourceIds.includes( - resource.id?.toString() as string - ) - ) { - return true; - } - } - - return false; + if (!data.statusPage.allowSubscribersToChooseResources) { + return true; } - public async getStatusPagesToSendNotification( - statusPageIds: Array<ObjectID> - ): Promise<Array<StatusPage>> { - return await StatusPageService.findBy({ - query: { - _id: QueryHelper.any(statusPageIds), - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { - _id: true, - name: true, - pageTitle: true, - projectId: true, - isPublicStatusPage: true, - logoFileId: true, - allowSubscribersToChooseResources: true, - smtpConfig: { - _id: true, - hostname: true, - port: true, - username: true, - password: true, - fromEmail: true, - fromName: true, - secure: true, - }, - callSmsConfig: { - _id: true, - twilioAccountSID: true, - twilioAuthToken: true, - twilioPhoneNumber: true, - }, - }, - }); + if (data.subscriber.isSubscribedToAllResources) { + return true; } + + const subscriberResourceIds: Array<string> = + data.subscriber.statusPageResources?.map( + (resource: StatusPageResource) => { + return resource.id?.toString() as string; + }, + ) || []; + + for (const resource of data.statusPageResources) { + if (subscriberResourceIds.includes(resource.id?.toString() as string)) { + return true; + } + } + + return false; + } + + public async getStatusPagesToSendNotification( + statusPageIds: Array<ObjectID>, + ): Promise<Array<StatusPage>> { + return await StatusPageService.findBy({ + query: { + _id: QueryHelper.any(statusPageIds), + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + _id: true, + name: true, + pageTitle: true, + projectId: true, + isPublicStatusPage: true, + logoFileId: true, + allowSubscribersToChooseResources: true, + smtpConfig: { + _id: true, + hostname: true, + port: true, + username: true, + password: true, + fromEmail: true, + fromName: true, + secure: true, + }, + callSmsConfig: { + _id: true, + twilioAccountSID: true, + twilioAuthToken: true, + twilioPhoneNumber: true, + }, + }, + }); + } } export default new Service(); diff --git a/CommonServer/Services/TeamMemberService.ts b/CommonServer/Services/TeamMemberService.ts index 75f80fa00d..72fb4378b1 100644 --- a/CommonServer/Services/TeamMemberService.ts +++ b/CommonServer/Services/TeamMemberService.ts @@ -1,477 +1,453 @@ -import DatabaseConfig from '../DatabaseConfig'; -import { IsBillingEnabled } from '../EnvironmentConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import QueryHelper from '../Types/Database/QueryHelper'; -import UpdateBy from '../Types/Database/UpdateBy'; -import Errors from '../Utils/Errors'; -import logger from '../Utils/Logger'; -import AccessTokenService from './AccessTokenService'; -import BillingService from './BillingService'; -import DatabaseService from './DatabaseService'; -import MailService from './MailService'; -import ProjectService from './ProjectService'; -import UserNotificationRuleService from './UserNotificationRuleService'; -import UserNotificationSettingService from './UserNotificationSettingService'; -import UserService from './UserService'; -import { AccountsRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; +import DatabaseConfig from "../DatabaseConfig"; +import { IsBillingEnabled } from "../EnvironmentConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import QueryHelper from "../Types/Database/QueryHelper"; +import UpdateBy from "../Types/Database/UpdateBy"; +import Errors from "../Utils/Errors"; +import logger from "../Utils/Logger"; +import AccessTokenService from "./AccessTokenService"; +import BillingService from "./BillingService"; +import DatabaseService from "./DatabaseService"; +import MailService from "./MailService"; +import ProjectService from "./ProjectService"; +import UserNotificationRuleService from "./UserNotificationRuleService"; +import UserNotificationSettingService from "./UserNotificationSettingService"; +import UserService from "./UserService"; +import { AccountsRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; import SubscriptionPlan, { - PlanSelect, -} from 'Common/Types/Billing/SubscriptionPlan'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import Email from 'Common/Types/Email'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Project from 'Model/Models/Project'; -import TeamMember from 'Model/Models/TeamMember'; -import User from 'Model/Models/User'; + PlanSelect, +} from "Common/Types/Billing/SubscriptionPlan"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import Email from "Common/Types/Email"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Project from "Model/Models/Project"; +import TeamMember from "Model/Models/TeamMember"; +import User from "Model/Models/User"; export class TeamMemberService extends DatabaseService<TeamMember> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(TeamMember, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(TeamMember, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<TeamMember>, + ): Promise<OnCreate<TeamMember>> { + // check if this project can have more members. + if (IsBillingEnabled && createBy.data.projectId) { + const project: Project | null = await ProjectService.findOneById({ + id: createBy.data.projectId!, + select: { + seatLimit: true, + paymentProviderSubscriptionSeats: true, + }, + props: { + isRoot: true, + }, + }); + + if ( + project && + project.seatLimit && + project.paymentProviderSubscriptionSeats && + project.paymentProviderSubscriptionSeats >= project.seatLimit + ) { + throw new BadDataException(Errors.TeamMemberService.LIMIT_REACHED); + } + + if ( + createBy.props.currentPlan === PlanSelect.Free && + project && + project.paymentProviderSubscriptionSeats && + project.paymentProviderSubscriptionSeats >= 1 + ) { + throw new BadDataException( + Errors.TeamMemberService.LIMIT_REACHED_FOR_FREE_PLAN, + ); + } } - protected override async onBeforeCreate( - createBy: CreateBy<TeamMember> - ): Promise<OnCreate<TeamMember>> { - // check if this project can have more members. - if (IsBillingEnabled && createBy.data.projectId) { - const project: Project | null = await ProjectService.findOneById({ - id: createBy.data.projectId!, - select: { - seatLimit: true, - paymentProviderSubscriptionSeats: true, - }, - props: { - isRoot: true, - }, - }); + createBy.data.hasAcceptedInvitation = false; - if ( - project && - project.seatLimit && - project.paymentProviderSubscriptionSeats && - project.paymentProviderSubscriptionSeats >= project.seatLimit - ) { - throw new BadDataException( - Errors.TeamMemberService.LIMIT_REACHED - ); - } + if (createBy.miscDataProps && createBy.miscDataProps["email"]) { + const email: Email = new Email(createBy.miscDataProps["email"] as string); - if ( - createBy.props.currentPlan === PlanSelect.Free && - project && - project.paymentProviderSubscriptionSeats && - project.paymentProviderSubscriptionSeats >= 1 - ) { - throw new BadDataException( - Errors.TeamMemberService.LIMIT_REACHED_FOR_FREE_PLAN - ); - } + let user: User | null = await UserService.findByEmail(email, { + isRoot: true, + }); + + let isNewUser: boolean = false; + + if (!user) { + isNewUser = true; + user = await UserService.createByEmail({ + email, + props: { + isRoot: true, + }, + }); + } + + createBy.data.userId = user.id!; + + const project: Project | null = await ProjectService.findOneById({ + id: createBy.data.projectId!, + select: { + name: true, + }, + props: { + isRoot: true, + }, + }); + + if (project) { + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + MailService.sendMail( + { + toEmail: email, + templateType: EmailTemplateType.InviteMember, + vars: { + signInLink: URL.fromString( + new URL(httpProtocol, host, AccountsRoute).toString(), + ).toString(), + registerLink: URL.fromString( + new URL(httpProtocol, host, AccountsRoute).toString(), + ) + .addRoute("/register") + .addQueryParam("email", email.toString(), true) + .toString(), + isNewUser: isNewUser.toString(), + projectName: project.name!, + homeUrl: new URL(httpProtocol, host).toString(), + }, + subject: "You have been invited to " + project.name, + }, + { + projectId: createBy.data.projectId!, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } + } + + //check if this user is already invited. + + const member: TeamMember | null = await this.findOneBy({ + query: { + userId: createBy.data.userId!, + teamId: createBy.data.teamId || new ObjectID(createBy.data.team!._id!), + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + if (member) { + throw new BadDataException(Errors.TeamMemberService.ALREADY_INVITED); + } + + return { createBy, carryForward: null }; + } + + public async refreshTokens( + userId: ObjectID, + projectId: ObjectID, + ): Promise<void> { + /// Refresh tokens. + await AccessTokenService.refreshUserGlobalAccessPermission(userId); + + await AccessTokenService.refreshUserTenantAccessPermission( + userId, + projectId, + ); + } + + protected override async onCreateSuccess( + onCreate: OnCreate<TeamMember>, + createdItem: TeamMember, + ): Promise<TeamMember> { + await this.refreshTokens( + onCreate.createBy.data.userId!, + onCreate.createBy.data.projectId!, + ); + + await this.updateSubscriptionSeatsByUniqueTeamMembersInProject( + onCreate.createBy.data.projectId!, + ); + + return createdItem; + } + + protected override async onUpdateSuccess( + onUpdate: OnUpdate<TeamMember>, + updatedItemIds: Array<ObjectID>, + ): Promise<OnUpdate<TeamMember>> { + const updateBy: UpdateBy<TeamMember> = onUpdate.updateBy; + const items: Array<TeamMember> = await this.findBy({ + query: { + _id: QueryHelper.any(updatedItemIds), + }, + select: { + userId: true, + user: { + email: true, + isEmailVerified: true, + }, + projectId: true, + }, + limit: LIMIT_MAX, + skip: 0, + + props: { + isRoot: true, + }, + }); + + for (const item of items) { + await this.refreshTokens(item.userId!, item.projectId!); + + if (updateBy.data.hasAcceptedInvitation && item.user?.isEmailVerified) { + await UserNotificationSettingService.addDefaultNotificationSettingsForUser( + item.userId!, + item.projectId!, + ); + await UserNotificationRuleService.addDefaultNotificationRuleForUser( + item.projectId!, + item.userId!, + item.user?.email as Email, + ); + } + } + + return { updateBy, carryForward: onUpdate.carryForward }; + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<TeamMember>, + ): Promise<OnDelete<TeamMember>> { + const members: Array<TeamMember> = await this.findBy({ + query: deleteBy.query, + select: { + userId: true, + projectId: true, + teamId: true, + hasAcceptedInvitation: true, + team: { + _id: true, + shouldHaveAtLeastOneMember: true, + }, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + // check if there's one member in the team. + for (const member of members) { + if (member.team?.shouldHaveAtLeastOneMember) { + if (!member.hasAcceptedInvitation) { + continue; } - createBy.data.hasAcceptedInvitation = false; - - if (createBy.miscDataProps && createBy.miscDataProps['email']) { - const email: Email = new Email( - createBy.miscDataProps['email'] as string - ); - - let user: User | null = await UserService.findByEmail(email, { - isRoot: true, - }); - - let isNewUser: boolean = false; - - if (!user) { - isNewUser = true; - user = await UserService.createByEmail({ - email, - props: { - isRoot: true, - }, - }); - } - - createBy.data.userId = user.id!; - - const project: Project | null = await ProjectService.findOneById({ - id: createBy.data.projectId!, - select: { - name: true, - }, - props: { - isRoot: true, - }, - }); - - if (project) { - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); - - MailService.sendMail( - { - toEmail: email, - templateType: EmailTemplateType.InviteMember, - vars: { - signInLink: URL.fromString( - new URL( - httpProtocol, - host, - AccountsRoute - ).toString() - ).toString(), - registerLink: URL.fromString( - new URL( - httpProtocol, - host, - AccountsRoute - ).toString() - ) - .addRoute('/register') - .addQueryParam('email', email.toString(), true) - .toString(), - isNewUser: isNewUser.toString(), - projectName: project.name!, - homeUrl: new URL(httpProtocol, host).toString(), - }, - subject: 'You have been invited to ' + project.name, - }, - { - projectId: createBy.data.projectId!, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - - //check if this user is already invited. - - const member: TeamMember | null = await this.findOneBy({ - query: { - userId: createBy.data.userId!, - teamId: - createBy.data.teamId || - new ObjectID(createBy.data.team!._id!), - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, + const membersInTeam: PositiveNumber = await this.countBy({ + query: { + teamId: member.teamId!, + hasAcceptedInvitation: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, }); - if (member) { - throw new BadDataException( - Errors.TeamMemberService.ALREADY_INVITED - ); + if (membersInTeam.toNumber() <= 1) { + throw new BadDataException( + Errors.TeamMemberService.ONE_MEMBER_REQUIRED, + ); } - - return { createBy, carryForward: null }; + } } - public async refreshTokens( - userId: ObjectID, - projectId: ObjectID - ): Promise<void> { - /// Refresh tokens. - await AccessTokenService.refreshUserGlobalAccessPermission(userId); + return { + deleteBy: deleteBy, + carryForward: members, + }; + } - await AccessTokenService.refreshUserTenantAccessPermission( - userId, - projectId - ); + protected override async onDeleteSuccess( + onDelete: OnDelete<TeamMember>, + ): Promise<OnDelete<TeamMember>> { + for (const item of onDelete.carryForward as Array<TeamMember>) { + await this.refreshTokens(item.userId!, item.projectId!); + await this.updateSubscriptionSeatsByUniqueTeamMembersInProject( + item.projectId!, + ); + await UserNotificationSettingService.removeDefaultNotificationSettingsForUser( + item.userId!, + item.projectId!, + ); } - protected override async onCreateSuccess( - onCreate: OnCreate<TeamMember>, - createdItem: TeamMember - ): Promise<TeamMember> { - await this.refreshTokens( - onCreate.createBy.data.userId!, - onCreate.createBy.data.projectId! + return onDelete; + } + + public async getUniqueTeamMemberCountInProject( + projectId: ObjectID, + ): Promise<number> { + const members: Array<TeamMember> = await this.findBy({ + query: { + projectId: projectId!, + }, + props: { + isRoot: true, + }, + select: { + userId: true, + }, + skip: 0, + limit: LIMIT_MAX, + }); + + const memberIds: Array<string | undefined> = members + .map((member: TeamMember) => { + return member.userId?.toString(); + }) + .filter((memberId: string | undefined) => { + return Boolean(memberId); + }); + + return [...new Set(memberIds)].length; //get unique member ids. + } + + public async getUsersInTeams(teamIds: Array<ObjectID>): Promise<Array<User>> { + const members: Array<TeamMember> = await this.findBy({ + query: { + teamId: QueryHelper.any(teamIds), + }, + props: { + isRoot: true, + }, + select: { + _id: true, + user: { + _id: true, + email: true, + name: true, + }, + }, + + skip: 0, + limit: LIMIT_MAX, + }); + + const uniqueUserIds: Set<string> = new Set<string>(); + const uniqueMembers: TeamMember[] = members.filter((member: TeamMember) => { + const userId: string | undefined = member.user?._id?.toString(); + if (userId && !uniqueUserIds.has(userId)) { + uniqueUserIds.add(userId); + return true; + } + return false; + }); + + return uniqueMembers.map((member: TeamMember) => { + return member.user!; + }); + } + + public async getUsersInTeam(teamId: ObjectID): Promise<Array<User>> { + const members: Array<TeamMember> = await this.findBy({ + query: { + teamId: teamId, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + user: { + _id: true, + email: true, + name: true, + }, + }, + + skip: 0, + limit: LIMIT_MAX, + }); + + return members.map((member: TeamMember) => { + return member.user!; + }); + } + + public async updateSubscriptionSeatsByUniqueTeamMembersInProject( + projectId: ObjectID, + ): Promise<void> { + if (!IsBillingEnabled) { + return; + } + + const numberOfMembers: number = + await this.getUniqueTeamMemberCountInProject(projectId); + const project: Project | null = await ProjectService.findOneById({ + id: projectId, + select: { + paymentProviderSubscriptionId: true, + paymentProviderPlanId: true, + }, + props: { + isRoot: true, + }, + }); + + if ( + project && + project.paymentProviderSubscriptionId && + project?.paymentProviderPlanId + ) { + const plan: SubscriptionPlan | undefined = + SubscriptionPlan.getSubscriptionPlanById( + project?.paymentProviderPlanId, ); - await this.updateSubscriptionSeatsByUniqueTeamMembersInProject( - onCreate.createBy.data.projectId! - ); + if (!plan) { + return; + } - return createdItem; - } - - protected override async onUpdateSuccess( - onUpdate: OnUpdate<TeamMember>, - updatedItemIds: Array<ObjectID> - ): Promise<OnUpdate<TeamMember>> { - const updateBy: UpdateBy<TeamMember> = onUpdate.updateBy; - const items: Array<TeamMember> = await this.findBy({ - query: { - _id: QueryHelper.any(updatedItemIds), - }, - select: { - userId: true, - user: { - email: true, - isEmailVerified: true, - }, - projectId: true, - }, - limit: LIMIT_MAX, - skip: 0, - - props: { - isRoot: true, - }, - }); - - for (const item of items) { - await this.refreshTokens(item.userId!, item.projectId!); - - if ( - updateBy.data.hasAcceptedInvitation && - item.user?.isEmailVerified - ) { - await UserNotificationSettingService.addDefaultNotificationSettingsForUser( - item.userId!, - item.projectId! - ); - await UserNotificationRuleService.addDefaultNotificationRuleForUser( - item.projectId!, - item.userId!, - item.user?.email as Email - ); - } - } - - return { updateBy, carryForward: onUpdate.carryForward }; - } - - protected override async onBeforeDelete( - deleteBy: DeleteBy<TeamMember> - ): Promise<OnDelete<TeamMember>> { - const members: Array<TeamMember> = await this.findBy({ - query: deleteBy.query, - select: { - userId: true, - projectId: true, - teamId: true, - hasAcceptedInvitation: true, - team: { - _id: true, - shouldHaveAtLeastOneMember: true, - }, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - // check if there's one member in the team. - for (const member of members) { - if (member.team?.shouldHaveAtLeastOneMember) { - if (!member.hasAcceptedInvitation) { - continue; - } - - const membersInTeam: PositiveNumber = await this.countBy({ - query: { - teamId: member.teamId!, - hasAcceptedInvitation: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); - - if (membersInTeam.toNumber() <= 1) { - throw new BadDataException( - Errors.TeamMemberService.ONE_MEMBER_REQUIRED - ); - } - } - } - - return { - deleteBy: deleteBy, - carryForward: members, - }; - } - - protected override async onDeleteSuccess( - onDelete: OnDelete<TeamMember> - ): Promise<OnDelete<TeamMember>> { - for (const item of onDelete.carryForward as Array<TeamMember>) { - await this.refreshTokens(item.userId!, item.projectId!); - await this.updateSubscriptionSeatsByUniqueTeamMembersInProject( - item.projectId! - ); - await UserNotificationSettingService.removeDefaultNotificationSettingsForUser( - item.userId!, - item.projectId! - ); - } - - return onDelete; - } - - public async getUniqueTeamMemberCountInProject( - projectId: ObjectID - ): Promise<number> { - const members: Array<TeamMember> = await this.findBy({ - query: { - projectId: projectId!, - }, - props: { - isRoot: true, - }, - select: { - userId: true, - }, - skip: 0, - limit: LIMIT_MAX, - }); - - const memberIds: Array<string | undefined> = members - .map((member: TeamMember) => { - return member.userId?.toString(); - }) - .filter((memberId: string | undefined) => { - return Boolean(memberId); - }); - - return [...new Set(memberIds)].length; //get unique member ids. - } - - public async getUsersInTeams( - teamIds: Array<ObjectID> - ): Promise<Array<User>> { - const members: Array<TeamMember> = await this.findBy({ - query: { - teamId: QueryHelper.any(teamIds), - }, - props: { - isRoot: true, - }, - select: { - _id: true, - user: { - _id: true, - email: true, - name: true, - }, - }, - - skip: 0, - limit: LIMIT_MAX, - }); - - const uniqueUserIds: Set<string> = new Set<string>(); - const uniqueMembers: TeamMember[] = members.filter( - (member: TeamMember) => { - const userId: string | undefined = member.user?._id?.toString(); - if (userId && !uniqueUserIds.has(userId)) { - uniqueUserIds.add(userId); - return true; - } - return false; - } - ); - - return uniqueMembers.map((member: TeamMember) => { - return member.user!; - }); - } - - public async getUsersInTeam(teamId: ObjectID): Promise<Array<User>> { - const members: Array<TeamMember> = await this.findBy({ - query: { - teamId: teamId, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - user: { - _id: true, - email: true, - name: true, - }, - }, - - skip: 0, - limit: LIMIT_MAX, - }); - - return members.map((member: TeamMember) => { - return member.user!; - }); - } - - public async updateSubscriptionSeatsByUniqueTeamMembersInProject( - projectId: ObjectID - ): Promise<void> { - if (!IsBillingEnabled) { - return; - } - - const numberOfMembers: number = - await this.getUniqueTeamMemberCountInProject(projectId); - const project: Project | null = await ProjectService.findOneById({ - id: projectId, - select: { - paymentProviderSubscriptionId: true, - paymentProviderPlanId: true, - }, - props: { - isRoot: true, - }, - }); - - if ( - project && - project.paymentProviderSubscriptionId && - project?.paymentProviderPlanId - ) { - const plan: SubscriptionPlan | undefined = - SubscriptionPlan.getSubscriptionPlanById( - project?.paymentProviderPlanId - ); - - if (!plan) { - return; - } - - await BillingService.changeQuantity( - project.paymentProviderSubscriptionId, - numberOfMembers - ); - - await ProjectService.updateOneById({ - id: projectId, - data: { - paymentProviderSubscriptionSeats: numberOfMembers, - }, - props: { - isRoot: true, - }, - }); - } + await BillingService.changeQuantity( + project.paymentProviderSubscriptionId, + numberOfMembers, + ); + + await ProjectService.updateOneById({ + id: projectId, + data: { + paymentProviderSubscriptionSeats: numberOfMembers, + }, + props: { + isRoot: true, + }, + }); } + } } export default new TeamMemberService(); diff --git a/CommonServer/Services/TeamPermissionService.ts b/CommonServer/Services/TeamPermissionService.ts index b3cf05d190..62f707cb2c 100644 --- a/CommonServer/Services/TeamPermissionService.ts +++ b/CommonServer/Services/TeamPermissionService.ts @@ -1,379 +1,367 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import UpdateBy from '../Types/Database/UpdateBy'; -import AccessTokenService from './AccessTokenService'; -import DatabaseService from './DatabaseService'; -import TeamMemberService from './TeamMemberService'; -import TeamService from './TeamService'; -import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Team from 'Model/Models/Team'; -import TeamMember from 'Model/Models/TeamMember'; -import Model from 'Model/Models/TeamPermission'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import UpdateBy from "../Types/Database/UpdateBy"; +import AccessTokenService from "./AccessTokenService"; +import DatabaseService from "./DatabaseService"; +import TeamMemberService from "./TeamMemberService"; +import TeamService from "./TeamService"; +import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Team from "Model/Models/Team"; +import TeamMember from "Model/Models/TeamMember"; +import Model from "Model/Models/TeamPermission"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.data.teamId) { + throw new BadDataException("Team Id is required to create permission"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.data.teamId) { - throw new BadDataException( - 'Team Id is required to create permission' - ); - } + if (!createBy.data.projectId) { + throw new BadDataException("Project Id is required to create permission"); + } - if (!createBy.data.projectId) { - throw new BadDataException( - 'Project Id is required to create permission' - ); - } + if (!createBy.data.permission) { + throw new BadDataException("Permission is required to create permission"); + } - if (!createBy.data.permission) { - throw new BadDataException( - 'Permission is required to create permission' - ); - } + // get team. + const team: Team | null = await TeamService.findOneById({ + id: createBy.data.teamId!, + select: { + isPermissionsEditable: true, + }, + props: { + isRoot: true, + }, + }); - // get team. - const team: Team | null = await TeamService.findOneById({ - id: createBy.data.teamId!, - select: { - isPermissionsEditable: true, - }, - props: { - isRoot: true, - }, - }); + if (!team) { + throw new BadDataException("Invalid Team ID"); + } - if (!team) { - throw new BadDataException('Invalid Team ID'); - } + if (!team.isPermissionsEditable) { + throw new BadDataException( + "You cannot create new permissions for this team because this team is not editable", + ); + } - if (!team.isPermissionsEditable) { - throw new BadDataException( - 'You cannot create new permissions for this team because this team is not editable' - ); - } + // check if this permission is already assigned to this team and if yes then throw error. - // check if this permission is already assigned to this team and if yes then throw error. + const isBlockPermission: boolean = createBy.data.isBlockPermission || false; + + const existingPermission: Model | null = await this.findOneBy({ + query: { + teamId: createBy.data.teamId, + projectId: createBy.data.projectId, + permission: createBy.data.permission, + isBlockPermission: isBlockPermission, + }, + select: { + _id: true, + isBlockPermission: true, + }, + props: { + isRoot: true, + }, + }); + + if (existingPermission) { + throw new BadDataException( + "This permission is already assigned to this team.", + ); + } + + if (createBy.data.labels && createBy.data.labels.length > 0) { + // check if the + + const existingPermission: Model | null = await this.findOneBy({ + query: { + teamId: createBy.data.teamId, + projectId: createBy.data.projectId, + permission: createBy.data.permission, + isBlockPermission: !isBlockPermission, + }, + select: { + _id: true, + isBlockPermission: true, + labels: true, + }, + props: { + isRoot: true, + }, + }); + + if (existingPermission && (existingPermission.labels?.length || 0) > 0) { + // if the permission in another block has labels, this permission cannot have labels. + + const blockName: string = existingPermission.isBlockPermission + ? "block" + : "allow"; + + throw new BadDataException( + `Restriction labels are already assigned to this permission in the ${blockName} permission list. To assign restriction labels to this permission, remove the restriction labels from the ${blockName} permission`, + ); + } + } + + return { createBy, carryForward: null }; + } + + protected override async onCreateSuccess( + onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + const createBy: CreateBy<Model> = onCreate.createBy; + + const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ + query: { + teamId: createBy.data.teamId!, + }, + select: { + userId: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); + + for (const member of teamMembers) { + /// Refresh tokens. + await AccessTokenService.refreshUserGlobalAccessPermission( + member.userId!, + ); + await AccessTokenService.refreshUserTenantAccessPermission( + member.userId!, + createBy.data.projectId!, + ); + } + + return createdItem; + } + + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + const teamPermissions: Array<Model> = await this.findBy({ + query: updateBy.query, + select: { + _id: true, + teamId: true, + projectId: true, + team: { + isPermissionsEditable: true, + }, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const permission of teamPermissions) { + if (!permission.team?.isPermissionsEditable) { + throw new BadDataException( + "Permissions for this team is not updateable. You can create a new team and add permissions to that team instead.", + ); + } + } + + if (updateBy.data.labels && updateBy.data.labels.length > 0) { + const existingPermissions: Array<Model> = await this.findBy({ + query: updateBy.query, + select: { + _id: true, + labels: true, + isBlockPermission: true, + projectId: true, + teamId: true, + permission: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const alreadySavedPermission of existingPermissions) { + // check if the const isBlockPermission: boolean = - createBy.data.isBlockPermission || false; + alreadySavedPermission.isBlockPermission || false; const existingPermission: Model | null = await this.findOneBy({ - query: { - teamId: createBy.data.teamId, - projectId: createBy.data.projectId, - permission: createBy.data.permission, - isBlockPermission: isBlockPermission, - }, - select: { - _id: true, - isBlockPermission: true, - }, - props: { - isRoot: true, - }, + query: { + teamId: alreadySavedPermission.teamId!, + projectId: alreadySavedPermission.projectId!, + permission: alreadySavedPermission.permission!, + isBlockPermission: !isBlockPermission, + }, + select: { + _id: true, + isBlockPermission: true, + labels: true, + permission: true, + }, + props: { + isRoot: true, + }, }); - if (existingPermission) { - throw new BadDataException( - 'This permission is already assigned to this team.' - ); + if ( + existingPermission && + (existingPermission.labels?.length || 0) > 0 + ) { + // if the permission in another block has labels, this permission cannot have labels. + + const blockName: string = existingPermission.isBlockPermission + ? "block" + : "allow"; + + throw new BadDataException( + `Restriction labels are already assigned to ${existingPermission.permission} in the ${blockName} permission list. To assign restriction labels to this permission, remove the restriction labels from the ${blockName} list.`, + ); } - - if (createBy.data.labels && createBy.data.labels.length > 0) { - // check if the - - const existingPermission: Model | null = await this.findOneBy({ - query: { - teamId: createBy.data.teamId, - projectId: createBy.data.projectId, - permission: createBy.data.permission, - isBlockPermission: !isBlockPermission, - }, - select: { - _id: true, - isBlockPermission: true, - labels: true, - }, - props: { - isRoot: true, - }, - }); - - if ( - existingPermission && - (existingPermission.labels?.length || 0) > 0 - ) { - // if the permission in another block has labels, this permission cannot have labels. - - const blockName: string = existingPermission.isBlockPermission - ? 'block' - : 'allow'; - - throw new BadDataException( - `Restriction labels are already assigned to this permission in the ${blockName} permission list. To assign restriction labels to this permission, remove the restriction labels from the ${blockName} permission` - ); - } - } - - return { createBy, carryForward: null }; + } } - protected override async onCreateSuccess( - onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - const createBy: CreateBy<Model> = onCreate.createBy; + return { updateBy, carryForward: teamPermissions }; + } - const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ - query: { - teamId: createBy.data.teamId!, - }, - select: { - userId: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); + protected override async onUpdateSuccess( + onUpdate: OnUpdate<Model>, + _updatedItemIds: ObjectID[], + ): Promise<OnUpdate<Model>> { + for (const permission of onUpdate.carryForward) { + const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ + query: { + teamId: permission.teamId!, + }, + select: { + userId: true, + projectId: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); - for (const member of teamMembers) { - /// Refresh tokens. - await AccessTokenService.refreshUserGlobalAccessPermission( - member.userId! - ); - await AccessTokenService.refreshUserTenantAccessPermission( - member.userId!, - createBy.data.projectId! - ); + for (const member of teamMembers) { + if (!member.userId) { + throw new BadDataException("Invalid User ID"); } - return createdItem; + if (!member.projectId) { + throw new BadDataException("Invalid Project ID"); + } + + /// Refresh tokens. + await AccessTokenService.refreshUserGlobalAccessPermission( + member.userId, + ); + await AccessTokenService.refreshUserTenantAccessPermission( + member.userId, + member.projectId, + ); + } } - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - const teamPermissions: Array<Model> = await this.findBy({ - query: updateBy.query, - select: { - _id: true, - teamId: true, - projectId: true, - team: { - isPermissionsEditable: true, - }, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); + return onUpdate; + } - for (const permission of teamPermissions) { - if (!permission.team?.isPermissionsEditable) { - throw new BadDataException( - 'Permissions for this team is not updateable. You can create a new team and add permissions to that team instead.' - ); - } - } + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + const teamPermissions: Array<Model> = await this.findBy({ + query: deleteBy.query, + select: { + _id: true, + teamId: true, + projectId: true, + team: { + isPermissionsEditable: true, + }, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); - if (updateBy.data.labels && updateBy.data.labels.length > 0) { - const existingPermissions: Array<Model> = await this.findBy({ - query: updateBy.query, - select: { - _id: true, - labels: true, - isBlockPermission: true, - projectId: true, - teamId: true, - permission: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const alreadySavedPermission of existingPermissions) { - // check if the - - const isBlockPermission: boolean = - alreadySavedPermission.isBlockPermission || false; - - const existingPermission: Model | null = await this.findOneBy({ - query: { - teamId: alreadySavedPermission.teamId!, - projectId: alreadySavedPermission.projectId!, - permission: alreadySavedPermission.permission!, - isBlockPermission: !isBlockPermission, - }, - select: { - _id: true, - isBlockPermission: true, - labels: true, - permission: true, - }, - props: { - isRoot: true, - }, - }); - - if ( - existingPermission && - (existingPermission.labels?.length || 0) > 0 - ) { - // if the permission in another block has labels, this permission cannot have labels. - - const blockName: string = - existingPermission.isBlockPermission - ? 'block' - : 'allow'; - - throw new BadDataException( - `Restriction labels are already assigned to ${existingPermission.permission} in the ${blockName} permission list. To assign restriction labels to this permission, remove the restriction labels from the ${blockName} list.` - ); - } - } - } - - return { updateBy, carryForward: teamPermissions }; + for (const permission of teamPermissions) { + if (!permission.team?.isPermissionsEditable) { + throw new BadDataException( + "Permissions for this team is not deleteable. You can create a new team and add permissions to that team instead.", + ); + } } - protected override async onUpdateSuccess( - onUpdate: OnUpdate<Model>, - _updatedItemIds: ObjectID[] - ): Promise<OnUpdate<Model>> { - for (const permission of onUpdate.carryForward) { - const teamMembers: Array<TeamMember> = - await TeamMemberService.findBy({ - query: { - teamId: permission.teamId!, - }, - select: { - userId: true, - projectId: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); + let teamMembers: Array<TeamMember> = []; - for (const member of teamMembers) { - if (!member.userId) { - throw new BadDataException('Invalid User ID'); - } + for (const permission of teamPermissions) { + const members: Array<TeamMember> = await TeamMemberService.findBy({ + query: { + teamId: permission.teamId!, + }, + select: { + userId: true, + projectId: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); - if (!member.projectId) { - throw new BadDataException('Invalid Project ID'); - } - - /// Refresh tokens. - await AccessTokenService.refreshUserGlobalAccessPermission( - member.userId - ); - await AccessTokenService.refreshUserTenantAccessPermission( - member.userId, - member.projectId - ); - } - } - - return onUpdate; + teamMembers = teamMembers.concat(members); } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - const teamPermissions: Array<Model> = await this.findBy({ - query: deleteBy.query, - select: { - _id: true, - teamId: true, - projectId: true, - team: { - isPermissionsEditable: true, - }, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); + return { deleteBy, carryForward: teamMembers }; + } - for (const permission of teamPermissions) { - if (!permission.team?.isPermissionsEditable) { - throw new BadDataException( - 'Permissions for this team is not deleteable. You can create a new team and add permissions to that team instead.' - ); - } - } + protected override async onDeleteSuccess( + onDelete: OnDelete<Model>, + _itemIdsBeforeDelete: ObjectID[], + ): Promise<OnDelete<Model>> { + for (const member of onDelete.carryForward) { + const teamMember: TeamMember = member as TeamMember; - let teamMembers: Array<TeamMember> = []; + if (!teamMember.userId) { + throw new BadDataException("Invalid User ID"); + } - for (const permission of teamPermissions) { - const members: Array<TeamMember> = await TeamMemberService.findBy({ - query: { - teamId: permission.teamId!, - }, - select: { - userId: true, - projectId: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); + if (!teamMember.projectId) { + throw new BadDataException("Invalid Project ID"); + } - teamMembers = teamMembers.concat(members); - } - - return { deleteBy, carryForward: teamMembers }; + /// Refresh tokens. + await AccessTokenService.refreshUserGlobalAccessPermission( + teamMember.userId, + ); + await AccessTokenService.refreshUserTenantAccessPermission( + teamMember.userId, + teamMember.projectId, + ); } - protected override async onDeleteSuccess( - onDelete: OnDelete<Model>, - _itemIdsBeforeDelete: ObjectID[] - ): Promise<OnDelete<Model>> { - for (const member of onDelete.carryForward) { - const teamMember: TeamMember = member as TeamMember; - - if (!teamMember.userId) { - throw new BadDataException('Invalid User ID'); - } - - if (!teamMember.projectId) { - throw new BadDataException('Invalid Project ID'); - } - - /// Refresh tokens. - await AccessTokenService.refreshUserGlobalAccessPermission( - teamMember.userId - ); - await AccessTokenService.refreshUserTenantAccessPermission( - teamMember.userId, - teamMember.projectId - ); - } - - return onDelete; - } + return onDelete; + } } export default new Service(); diff --git a/CommonServer/Services/TeamService.ts b/CommonServer/Services/TeamService.ts index 02a7f78920..08d3c3632f 100644 --- a/CommonServer/Services/TeamService.ts +++ b/CommonServer/Services/TeamService.ts @@ -1,73 +1,73 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnDelete, OnUpdate } from '../Types/Database/Hooks'; -import UpdateBy from '../Types/Database/UpdateBy'; -import DatabaseService from './DatabaseService'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Model from 'Model/Models/Team'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnDelete, OnUpdate } from "../Types/Database/Hooks"; +import UpdateBy from "../Types/Database/UpdateBy"; +import DatabaseService from "./DatabaseService"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Model from "Model/Models/Team"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + // get teams by query. + + const teams: Array<Model> = await this.findBy({ + query: updateBy.query, + limit: LIMIT_MAX, + skip: 0, + select: { + name: true, + isTeamEditable: true, + }, + + props: updateBy.props, + }); + + for (const team of teams) { + if (!team.isTeamEditable) { + throw new BadDataException( + `${ + team.name || "This" + } team cannot be updated because its a critical team for this project.`, + ); + } } - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - // get teams by query. + return { updateBy, carryForward: null }; + } - const teams: Array<Model> = await this.findBy({ - query: updateBy.query, - limit: LIMIT_MAX, - skip: 0, - select: { - name: true, - isTeamEditable: true, - }, + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + const teams: Array<Model> = await this.findBy({ + query: deleteBy.query, + limit: LIMIT_MAX, + skip: 0, + select: { + name: true, + isTeamDeleteable: true, + }, - props: updateBy.props, - }); + props: deleteBy.props, + }); - for (const team of teams) { - if (!team.isTeamEditable) { - throw new BadDataException( - `${ - team.name || 'This' - } team cannot be updated because its a critical team for this project.` - ); - } - } - - return { updateBy, carryForward: null }; + for (const team of teams) { + if (!team.isTeamDeleteable) { + throw new BadDataException( + `${ + team.name || "This" + } team cannot be deleted its a critical team for this project.`, + ); + } } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - const teams: Array<Model> = await this.findBy({ - query: deleteBy.query, - limit: LIMIT_MAX, - skip: 0, - select: { - name: true, - isTeamDeleteable: true, - }, - - props: deleteBy.props, - }); - - for (const team of teams) { - if (!team.isTeamDeleteable) { - throw new BadDataException( - `${ - team.name || 'This' - } team cannot be deleted its a critical team for this project.` - ); - } - } - - return { deleteBy, carryForward: null }; - } + return { deleteBy, carryForward: null }; + } } export default new Service(); diff --git a/CommonServer/Services/TelemetryServiceService.ts b/CommonServer/Services/TelemetryServiceService.ts index 6f4eecec4f..10587b4d91 100644 --- a/CommonServer/Services/TelemetryServiceService.ts +++ b/CommonServer/Services/TelemetryServiceService.ts @@ -1,51 +1,51 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import ArrayUtil from 'Common/Types/ArrayUtil'; -import { BrightColors } from 'Common/Types/BrandColors'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Model from 'Model/Models/TelemetryService'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import ArrayUtil from "Common/Types/ArrayUtil"; +import { BrightColors } from "Common/Types/BrandColors"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Model from "Model/Models/TelemetryService"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + createBy.data.telemetryServiceToken = ObjectID.generate(); + + // select a random color. + createBy.data.serviceColor = ArrayUtil.selectItemByRandom(BrightColors); + + return { + carryForward: null, + createBy: createBy, + }; + } + + public async getTelemetryDataRetentionInDays( + telemetryServiceId: ObjectID, + ): Promise<number> { + const project: Model | null = await this.findOneById({ + id: telemetryServiceId, + select: { + retainTelemetryDataForDays: true, + }, + props: { + isRoot: true, + }, + }); + + if (!project) { + throw new BadDataException("Project not found"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - createBy.data.telemetryServiceToken = ObjectID.generate(); - - // select a random color. - createBy.data.serviceColor = ArrayUtil.selectItemByRandom(BrightColors); - - return { - carryForward: null, - createBy: createBy, - }; - } - - public async getTelemetryDataRetentionInDays( - telemetryServiceId: ObjectID - ): Promise<number> { - const project: Model | null = await this.findOneById({ - id: telemetryServiceId, - select: { - retainTelemetryDataForDays: true, - }, - props: { - isRoot: true, - }, - }); - - if (!project) { - throw new BadDataException('Project not found'); - } - - return project.retainTelemetryDataForDays || 15; // default is 15 days. - } + return project.retainTelemetryDataForDays || 15; // default is 15 days. + } } export default new Service(); diff --git a/CommonServer/Services/TelemetryUsageBillingService.ts b/CommonServer/Services/TelemetryUsageBillingService.ts index d80d9a9fd7..32a145b89e 100644 --- a/CommonServer/Services/TelemetryUsageBillingService.ts +++ b/CommonServer/Services/TelemetryUsageBillingService.ts @@ -1,161 +1,151 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import { MeteredPlanUtil } from '../Types/Billing/MeteredPlan/AllMeteredPlans'; -import TelemetryMeteredPlan from '../Types/Billing/MeteredPlan/TelemetryMeteredPlan'; -import QueryHelper from '../Types/Database/QueryHelper'; -import DatabaseService from './DatabaseService'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Decimal from 'Common/Types/Decimal'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import ObjectID from 'Common/Types/ObjectID'; -import Model from 'Model/Models/TelemetryUsageBilling'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import { MeteredPlanUtil } from "../Types/Billing/MeteredPlan/AllMeteredPlans"; +import TelemetryMeteredPlan from "../Types/Billing/MeteredPlan/TelemetryMeteredPlan"; +import QueryHelper from "../Types/Database/QueryHelper"; +import DatabaseService from "./DatabaseService"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Decimal from "Common/Types/Decimal"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import ObjectID from "Common/Types/ObjectID"; +import Model from "Model/Models/TelemetryUsageBilling"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 120); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 120); + } + + public async getUnreportedUsageBilling(data: { + projectId: ObjectID; + productType: ProductType; + }): Promise<Model[]> { + return await this.findBy({ + query: { + projectId: data.projectId, + productType: data.productType, + isReportedToBillingProvider: false, + createdAt: QueryHelper.lessThan( + OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -1), + ), // we need to get everything that's not today. + }, + skip: 0, + limit: LIMIT_MAX, /// because a project can have MANY telemetry services. + select: { + _id: true, + totalCostInUSD: true, + }, + props: { + isRoot: true, + }, + }); + } + + public async updateUsageBilling(data: { + projectId: ObjectID; + productType: ProductType; + telemetryServiceId: ObjectID; + dataIngestedInGB: number; + retentionInDays: number; + }): Promise<void> { + if ( + data.productType !== ProductType.Traces && + data.productType !== ProductType.Metrics && + data.productType !== ProductType.Logs + ) { + throw new BadDataException( + "This product type is not a telemetry product type.", + ); } - public async getUnreportedUsageBilling(data: { - projectId: ObjectID; - productType: ProductType; - }): Promise<Model[]> { - return await this.findBy({ - query: { - projectId: data.projectId, - productType: data.productType, - isReportedToBillingProvider: false, - createdAt: QueryHelper.lessThan( - OneUptimeDate.addRemoveDays( - OneUptimeDate.getCurrentDate(), - -1 - ) - ), // we need to get everything that's not today. - }, - skip: 0, - limit: LIMIT_MAX, /// because a project can have MANY telemetry services. - select: { - _id: true, - totalCostInUSD: true, - }, - props: { - isRoot: true, - }, - }); - } - - public async updateUsageBilling(data: { - projectId: ObjectID; - productType: ProductType; - telemetryServiceId: ObjectID; - dataIngestedInGB: number; - retentionInDays: number; - }): Promise<void> { - if ( - data.productType !== ProductType.Traces && - data.productType !== ProductType.Metrics && - data.productType !== ProductType.Logs - ) { - throw new BadDataException( - 'This product type is not a telemetry product type.' - ); - } - - const serverMeteredPlan: TelemetryMeteredPlan = - MeteredPlanUtil.getMeteredPlanByProductType( - data.productType - ) as TelemetryMeteredPlan; - - const totalCostOfThisOperationInUSD: number = - serverMeteredPlan.getTotalCostInUSD({ - dataIngestedInGB: data.dataIngestedInGB, - retentionInDays: data.retentionInDays, - }); - - const usageBilling: Model | null = await this.findOneBy({ - query: { - projectId: data.projectId, - productType: data.productType, - telemetryServiceId: data.telemetryServiceId, - isReportedToBillingProvider: false, - createdAt: QueryHelper.inBetween( - OneUptimeDate.addRemoveDays( - OneUptimeDate.getCurrentDate(), - -1 - ), - OneUptimeDate.getCurrentDate() - ), - }, - select: { - _id: true, - dataIngestedInGB: true, - totalCostInUSD: true, - }, - props: { - isRoot: true, - }, - sort: { - createdAt: SortOrder.Descending, - }, - }); - - if (usageBilling && usageBilling.id) { - let totalCostInUSD: number = - usageBilling.totalCostInUSD?.value || 0; - - if ( - isNaN(totalCostInUSD) || - totalCostInUSD === undefined || - totalCostInUSD === null || - (typeof totalCostInUSD === 'string' && totalCostInUSD === 'NaN') - ) { - totalCostInUSD = 0; - } - - await this.updateOneById({ - id: usageBilling.id, - data: { - dataIngestedInGB: new Decimal( - (usageBilling.dataIngestedInGB?.value || 0) + - data.dataIngestedInGB - ), - totalCostInUSD: new Decimal( - totalCostInUSD + totalCostOfThisOperationInUSD - ), - retainTelemetryDataForDays: data.retentionInDays, - }, - props: { - isRoot: true, - }, - }); - } else { - const usageBilling: Model = new Model(); - usageBilling.projectId = data.projectId; - usageBilling.productType = data.productType; - usageBilling.dataIngestedInGB = new Decimal(data.dataIngestedInGB); - usageBilling.telemetryServiceId = data.telemetryServiceId; - usageBilling.retainTelemetryDataForDays = data.retentionInDays; - usageBilling.isReportedToBillingProvider = false; - usageBilling.createdAt = OneUptimeDate.getCurrentDate(); - - usageBilling.day = OneUptimeDate.getDateString( - OneUptimeDate.getCurrentDate() - ); - - usageBilling.totalCostInUSD = new Decimal( - totalCostOfThisOperationInUSD - ); - - await this.create({ - data: usageBilling, - props: { - isRoot: true, - }, - }); - } + const serverMeteredPlan: TelemetryMeteredPlan = + MeteredPlanUtil.getMeteredPlanByProductType( + data.productType, + ) as TelemetryMeteredPlan; + + const totalCostOfThisOperationInUSD: number = + serverMeteredPlan.getTotalCostInUSD({ + dataIngestedInGB: data.dataIngestedInGB, + retentionInDays: data.retentionInDays, + }); + + const usageBilling: Model | null = await this.findOneBy({ + query: { + projectId: data.projectId, + productType: data.productType, + telemetryServiceId: data.telemetryServiceId, + isReportedToBillingProvider: false, + createdAt: QueryHelper.inBetween( + OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), -1), + OneUptimeDate.getCurrentDate(), + ), + }, + select: { + _id: true, + dataIngestedInGB: true, + totalCostInUSD: true, + }, + props: { + isRoot: true, + }, + sort: { + createdAt: SortOrder.Descending, + }, + }); + + if (usageBilling && usageBilling.id) { + let totalCostInUSD: number = usageBilling.totalCostInUSD?.value || 0; + + if ( + isNaN(totalCostInUSD) || + totalCostInUSD === undefined || + totalCostInUSD === null || + (typeof totalCostInUSD === "string" && totalCostInUSD === "NaN") + ) { + totalCostInUSD = 0; + } + + await this.updateOneById({ + id: usageBilling.id, + data: { + dataIngestedInGB: new Decimal( + (usageBilling.dataIngestedInGB?.value || 0) + data.dataIngestedInGB, + ), + totalCostInUSD: new Decimal( + totalCostInUSD + totalCostOfThisOperationInUSD, + ), + retainTelemetryDataForDays: data.retentionInDays, + }, + props: { + isRoot: true, + }, + }); + } else { + const usageBilling: Model = new Model(); + usageBilling.projectId = data.projectId; + usageBilling.productType = data.productType; + usageBilling.dataIngestedInGB = new Decimal(data.dataIngestedInGB); + usageBilling.telemetryServiceId = data.telemetryServiceId; + usageBilling.retainTelemetryDataForDays = data.retentionInDays; + usageBilling.isReportedToBillingProvider = false; + usageBilling.createdAt = OneUptimeDate.getCurrentDate(); + + usageBilling.day = OneUptimeDate.getDateString( + OneUptimeDate.getCurrentDate(), + ); + + usageBilling.totalCostInUSD = new Decimal(totalCostOfThisOperationInUSD); + + await this.create({ + data: usageBilling, + props: { + isRoot: true, + }, + }); } + } } export default new Service(); diff --git a/CommonServer/Services/UserCallService.ts b/CommonServer/Services/UserCallService.ts index b4de2e905f..34f376db3c 100644 --- a/CommonServer/Services/UserCallService.ts +++ b/CommonServer/Services/UserCallService.ts @@ -1,190 +1,190 @@ -import { IsBillingEnabled } from '../EnvironmentConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete } from '../Types/Database/Hooks'; -import logger from '../Utils/Logger'; -import CallService from './CallService'; -import DatabaseService from './DatabaseService'; -import ProjectService from './ProjectService'; -import UserNotificationRuleService from './UserNotificationRuleService'; -import CallRequest from 'Common/Types/Call/CallRequest'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Text from 'Common/Types/Text'; -import Project from 'Model/Models/Project'; -import Model from 'Model/Models/UserCall'; +import { IsBillingEnabled } from "../EnvironmentConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete } from "../Types/Database/Hooks"; +import logger from "../Utils/Logger"; +import CallService from "./CallService"; +import DatabaseService from "./DatabaseService"; +import ProjectService from "./ProjectService"; +import UserNotificationRuleService from "./UserNotificationRuleService"; +import CallRequest from "Common/Types/Call/CallRequest"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Text from "Common/Types/Text"; +import Project from "Model/Models/Project"; +import Model from "Model/Models/UserCall"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + const itemsToDelete: Array<Model> = await this.findBy({ + query: deleteBy.query, + select: { + _id: true, + projectId: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const item of itemsToDelete) { + await UserNotificationRuleService.deleteBy({ + query: { + userCallId: item.id!, + projectId: item.projectId!, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - const itemsToDelete: Array<Model> = await this.findBy({ - query: deleteBy.query, - select: { - _id: true, - projectId: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); + return { + deleteBy, + carryForward: null, + }; + } - for (const item of itemsToDelete) { - await UserNotificationRuleService.deleteBy({ - query: { - userCallId: item.id!, - projectId: item.projectId!, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - } - - return { - deleteBy, - carryForward: null, - }; + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.props.isRoot && createBy.data.isVerified) { + throw new BadDataException("isVerified cannot be set to true"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.props.isRoot && createBy.data.isVerified) { - throw new BadDataException('isVerified cannot be set to true'); - } + // check if this project has SMS and Call mEnabled. - // check if this project has SMS and Call mEnabled. + const project: Project | null = await ProjectService.findOneById({ + id: createBy.data.projectId!, + props: { + isRoot: true, + }, + select: { + enableCallNotifications: true, + smsOrCallCurrentBalanceInUSDCents: true, + }, + }); - const project: Project | null = await ProjectService.findOneById({ - id: createBy.data.projectId!, - props: { - isRoot: true, - }, - select: { - enableCallNotifications: true, - smsOrCallCurrentBalanceInUSDCents: true, - }, - }); - - if (!project) { - throw new BadDataException('Project not found'); - } - - if (!project.enableCallNotifications) { - throw new BadDataException( - 'Call notifications are disabled for this project. Please enable them in Project Settings > Notification Settings.' - ); - } - - if ( - (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 && - IsBillingEnabled - ) { - throw new BadDataException( - 'Your SMS balance is low. Please recharge your SMS balance in Project Settings > Notification Settings.' - ); - } - - return { carryForward: null, createBy }; + if (!project) { + throw new BadDataException("Project not found"); } - protected override async onCreateSuccess( - _onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - if (!createdItem.isVerified) { - this.sendVerificationCode(createdItem); - } - return createdItem; + if (!project.enableCallNotifications) { + throw new BadDataException( + "Call notifications are disabled for this project. Please enable them in Project Settings > Notification Settings.", + ); } - public async resendVerificationCode(itemId: ObjectID): Promise<void> { - const item: Model | null = await this.findOneById({ - id: itemId, - props: { - isRoot: true, - }, - select: { - phone: true, - verificationCode: true, - isVerified: true, - projectId: true, - }, - }); - - if (!item) { - throw new BadDataException( - 'Item with ID ' + itemId.toString() + ' not found' - ); - } - - if (item.isVerified) { - throw new BadDataException('Phone Number already verified'); - } - - // generate new verification code - item.verificationCode = Text.generateRandomNumber(6); - - await this.updateOneById({ - id: item.id!, - props: { - isRoot: true, - }, - data: { - verificationCode: item.verificationCode, - }, - }); - - this.sendVerificationCode(item); + if ( + (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 && + IsBillingEnabled + ) { + throw new BadDataException( + "Your SMS balance is low. Please recharge your SMS balance in Project Settings > Notification Settings.", + ); } - public sendVerificationCode(item: Model): void { - const callRequest: CallRequest = { - to: item.phone!, - data: [ - { - sayMessage: 'This call is from One Uptime.', - }, - { - sayMessage: - 'Your verification code is ' + - item.verificationCode?.split('').join(' '), // add space to make it more clear and slow down the message - }, - { - sayMessage: - 'Your verification code is ' + - item.verificationCode?.split('').join(' '), // add space to make it more clear and slow down the message - }, - { - sayMessage: - 'Your verification code is ' + - item.verificationCode?.split('').join(' '), // add space to make it more clear and slow down the message - }, - { - sayMessage: 'Thank you for using One Uptime. Goodbye.', - }, - ], - }; + return { carryForward: null, createBy }; + } - // send verification sms. - CallService.makeCall(callRequest, { - projectId: item.projectId, - isSensitive: true, - }).catch((err: Error) => { - logger.error(err); - }); + protected override async onCreateSuccess( + _onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + if (!createdItem.isVerified) { + this.sendVerificationCode(createdItem); } + return createdItem; + } + + public async resendVerificationCode(itemId: ObjectID): Promise<void> { + const item: Model | null = await this.findOneById({ + id: itemId, + props: { + isRoot: true, + }, + select: { + phone: true, + verificationCode: true, + isVerified: true, + projectId: true, + }, + }); + + if (!item) { + throw new BadDataException( + "Item with ID " + itemId.toString() + " not found", + ); + } + + if (item.isVerified) { + throw new BadDataException("Phone Number already verified"); + } + + // generate new verification code + item.verificationCode = Text.generateRandomNumber(6); + + await this.updateOneById({ + id: item.id!, + props: { + isRoot: true, + }, + data: { + verificationCode: item.verificationCode, + }, + }); + + this.sendVerificationCode(item); + } + + public sendVerificationCode(item: Model): void { + const callRequest: CallRequest = { + to: item.phone!, + data: [ + { + sayMessage: "This call is from One Uptime.", + }, + { + sayMessage: + "Your verification code is " + + item.verificationCode?.split("").join(" "), // add space to make it more clear and slow down the message + }, + { + sayMessage: + "Your verification code is " + + item.verificationCode?.split("").join(" "), // add space to make it more clear and slow down the message + }, + { + sayMessage: + "Your verification code is " + + item.verificationCode?.split("").join(" "), // add space to make it more clear and slow down the message + }, + { + sayMessage: "Thank you for using One Uptime. Goodbye.", + }, + ], + }; + + // send verification sms. + CallService.makeCall(callRequest, { + projectId: item.projectId, + isSensitive: true, + }).catch((err: Error) => { + logger.error(err); + }); + } } export default new Service(); diff --git a/CommonServer/Services/UserEmailService.ts b/CommonServer/Services/UserEmailService.ts index ecf37a0a0c..fa509e5c17 100644 --- a/CommonServer/Services/UserEmailService.ts +++ b/CommonServer/Services/UserEmailService.ts @@ -1,141 +1,141 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete } from '../Types/Database/Hooks'; -import logger from '../Utils/Logger'; -import DatabaseService from './DatabaseService'; -import MailService from './MailService'; -import UserNotificationRuleService from './UserNotificationRuleService'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Text from 'Common/Types/Text'; -import Model from 'Model/Models/UserEmail'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete } from "../Types/Database/Hooks"; +import logger from "../Utils/Logger"; +import DatabaseService from "./DatabaseService"; +import MailService from "./MailService"; +import UserNotificationRuleService from "./UserNotificationRuleService"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Text from "Common/Types/Text"; +import Model from "Model/Models/UserEmail"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + const itemsToDelete: Array<Model> = await this.findBy({ + query: deleteBy.query, + select: { + _id: true, + projectId: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const item of itemsToDelete) { + await UserNotificationRuleService.deleteBy({ + query: { + userEmailId: item.id!, + projectId: item.projectId!, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - const itemsToDelete: Array<Model> = await this.findBy({ - query: deleteBy.query, - select: { - _id: true, - projectId: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); + return { + deleteBy, + carryForward: null, + }; + } - for (const item of itemsToDelete) { - await UserNotificationRuleService.deleteBy({ - query: { - userEmailId: item.id!, - projectId: item.projectId!, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - } - - return { - deleteBy, - carryForward: null, - }; + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if (!createBy.props.isRoot && createBy.data.isVerified) { + throw new BadDataException("isVerified cannot be set to true"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if (!createBy.props.isRoot && createBy.data.isVerified) { - throw new BadDataException('isVerified cannot be set to true'); - } + return { + createBy, + carryForward: null, + }; + } - return { - createBy, - carryForward: null, - }; + protected override async onCreateSuccess( + _onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + if (!createdItem.isVerified) { + // send verification code + this.sendVerificationCode(createdItem); } - protected override async onCreateSuccess( - _onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - if (!createdItem.isVerified) { - // send verification code - this.sendVerificationCode(createdItem); - } + return createdItem; + } - return createdItem; + public async resendVerificationCode(itemId: ObjectID): Promise<void> { + const item: Model | null = await this.findOneById({ + id: itemId, + props: { + isRoot: true, + }, + select: { + email: true, + verificationCode: true, + isVerified: true, + projectId: true, + }, + }); + + if (!item) { + throw new BadDataException( + "Item with ID " + itemId.toString() + " not found", + ); } - public async resendVerificationCode(itemId: ObjectID): Promise<void> { - const item: Model | null = await this.findOneById({ - id: itemId, - props: { - isRoot: true, - }, - select: { - email: true, - verificationCode: true, - isVerified: true, - projectId: true, - }, - }); - - if (!item) { - throw new BadDataException( - 'Item with ID ' + itemId.toString() + ' not found' - ); - } - - if (item.isVerified) { - throw new BadDataException('Email already verified'); - } - - // generate new verification code - item.verificationCode = Text.generateRandomNumber(6); - - await this.updateOneById({ - id: item.id!, - props: { - isRoot: true, - }, - data: { - verificationCode: item.verificationCode, - }, - }); - - this.sendVerificationCode(item); + if (item.isVerified) { + throw new BadDataException("Email already verified"); } - public sendVerificationCode(item: Model): void { - MailService.sendMail( - { - toEmail: item.email!, - templateType: EmailTemplateType.VerificationCode, - vars: { - code: item.verificationCode!, - subject: 'Verify this email address', - }, - subject: 'Verify this email address', - }, - { - projectId: item.projectId!, - } - ).catch((err: Error) => { - logger.error(err); - }); - } + // generate new verification code + item.verificationCode = Text.generateRandomNumber(6); + + await this.updateOneById({ + id: item.id!, + props: { + isRoot: true, + }, + data: { + verificationCode: item.verificationCode, + }, + }); + + this.sendVerificationCode(item); + } + + public sendVerificationCode(item: Model): void { + MailService.sendMail( + { + toEmail: item.email!, + templateType: EmailTemplateType.VerificationCode, + vars: { + code: item.verificationCode!, + subject: "Verify this email address", + }, + subject: "Verify this email address", + }, + { + projectId: item.projectId!, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } } export default new Service(); diff --git a/CommonServer/Services/UserNotificationRuleService.ts b/CommonServer/Services/UserNotificationRuleService.ts index ec1afcf1bc..f07bec6917 100644 --- a/CommonServer/Services/UserNotificationRuleService.ts +++ b/CommonServer/Services/UserNotificationRuleService.ts @@ -1,774 +1,760 @@ -import DatabaseConfig from '../DatabaseConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import Markdown, { MarkdownContentType } from '../Types/Markdown'; -import CallService from './CallService'; -import DatabaseService from './DatabaseService'; -import IncidentService from './IncidentService'; -import IncidentSeverityService from './IncidentSeverityService'; -import MailService from './MailService'; -import ShortLinkService from './ShortLinkService'; -import SmsService from './SmsService'; -import UserEmailService from './UserEmailService'; -import UserOnCallLogService from './UserOnCallLogService'; -import UserOnCallLogTimelineService from './UserOnCallLogTimelineService'; -import { AppApiRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import CallRequest from 'Common/Types/Call/CallRequest'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import Email from 'Common/Types/Email'; -import EmailMessage from 'Common/Types/Email/EmailMessage'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotificationRuleType from 'Common/Types/NotificationRule/NotificationRuleType'; -import ObjectID from 'Common/Types/ObjectID'; -import Phone from 'Common/Types/Phone'; -import SMS from 'Common/Types/SMS/SMS'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import UserNotificationExecutionStatus from 'Common/Types/UserNotification/UserNotificationExecutionStatus'; -import UserNotificationStatus from 'Common/Types/UserNotification/UserNotificationStatus'; -import Incident from 'Model/Models/Incident'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import ShortLink from 'Model/Models/ShortLink'; -import UserEmail from 'Model/Models/UserEmail'; -import Model from 'Model/Models/UserNotificationRule'; -import UserOnCallLog from 'Model/Models/UserOnCallLog'; -import UserOnCallLogTimeline from 'Model/Models/UserOnCallLogTimeline'; +import DatabaseConfig from "../DatabaseConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import Markdown, { MarkdownContentType } from "../Types/Markdown"; +import CallService from "./CallService"; +import DatabaseService from "./DatabaseService"; +import IncidentService from "./IncidentService"; +import IncidentSeverityService from "./IncidentSeverityService"; +import MailService from "./MailService"; +import ShortLinkService from "./ShortLinkService"; +import SmsService from "./SmsService"; +import UserEmailService from "./UserEmailService"; +import UserOnCallLogService from "./UserOnCallLogService"; +import UserOnCallLogTimelineService from "./UserOnCallLogTimelineService"; +import { AppApiRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import CallRequest from "Common/Types/Call/CallRequest"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import Email from "Common/Types/Email"; +import EmailMessage from "Common/Types/Email/EmailMessage"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotificationRuleType from "Common/Types/NotificationRule/NotificationRuleType"; +import ObjectID from "Common/Types/ObjectID"; +import Phone from "Common/Types/Phone"; +import SMS from "Common/Types/SMS/SMS"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import UserNotificationExecutionStatus from "Common/Types/UserNotification/UserNotificationExecutionStatus"; +import UserNotificationStatus from "Common/Types/UserNotification/UserNotificationStatus"; +import Incident from "Model/Models/Incident"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import ShortLink from "Model/Models/ShortLink"; +import UserEmail from "Model/Models/UserEmail"; +import Model from "Model/Models/UserNotificationRule"; +import UserOnCallLog from "Model/Models/UserOnCallLog"; +import UserOnCallLogTimeline from "Model/Models/UserOnCallLogTimeline"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + public async executeNotificationRuleItem( + userNotificationRuleId: ObjectID, + options: { + projectId: ObjectID; + triggeredByIncidentId?: ObjectID | undefined; + userNotificationEventType: UserNotificationEventType; + onCallPolicyExecutionLogId?: ObjectID | undefined; + onCallPolicyId: ObjectID | undefined; + onCallPolicyEscalationRuleId?: ObjectID | undefined; + userNotificationLogId: ObjectID; + userBelongsToTeamId?: ObjectID | undefined; + onCallDutyPolicyExecutionLogTimelineId?: ObjectID | undefined; + }, + ): Promise<void> { + // get user notification log and see if this rule has already been executed. If so then skip. + + const userOnCallLog: UserOnCallLog | null = + await UserOnCallLogService.findOneById({ + id: options.userNotificationLogId, + props: { + isRoot: true, + }, + select: { + _id: true, + executedNotificationRules: true, + }, + }); + + if (!userOnCallLog) { + throw new BadDataException("User notification log not found."); } - public async executeNotificationRuleItem( - userNotificationRuleId: ObjectID, - options: { - projectId: ObjectID; - triggeredByIncidentId?: ObjectID | undefined; - userNotificationEventType: UserNotificationEventType; - onCallPolicyExecutionLogId?: ObjectID | undefined; - onCallPolicyId: ObjectID | undefined; - onCallPolicyEscalationRuleId?: ObjectID | undefined; - userNotificationLogId: ObjectID; - userBelongsToTeamId?: ObjectID | undefined; - onCallDutyPolicyExecutionLogTimelineId?: ObjectID | undefined; - } - ): Promise<void> { - // get user notification log and see if this rule has already been executed. If so then skip. + if ( + Object.keys(userOnCallLog.executedNotificationRules || {}).includes( + userNotificationRuleId.toString(), + ) + ) { + // already executed. + return; + } - const userOnCallLog: UserOnCallLog | null = - await UserOnCallLogService.findOneById({ - id: options.userNotificationLogId, - props: { - isRoot: true, - }, - select: { - _id: true, - executedNotificationRules: true, - }, - }); + if (!userOnCallLog.executedNotificationRules) { + userOnCallLog.executedNotificationRules = {}; + } - if (!userOnCallLog) { - throw new BadDataException('User notification log not found.'); - } + userOnCallLog.executedNotificationRules[userNotificationRuleId.toString()] = + OneUptimeDate.getCurrentDate(); - if ( - Object.keys(userOnCallLog.executedNotificationRules || {}).includes( - userNotificationRuleId.toString() - ) - ) { - // already executed. - return; - } + await UserOnCallLogService.updateOneById({ + id: userOnCallLog.id!, + data: { + executedNotificationRules: { + ...userOnCallLog.executedNotificationRules, + }, + } as any, + props: { + isRoot: true, + }, + }); - if (!userOnCallLog.executedNotificationRules) { - userOnCallLog.executedNotificationRules = {}; - } + // find notification rule item. + const notificationRuleItem: Model | null = await this.findOneById({ + id: userNotificationRuleId!, + select: { + _id: true, + userId: true, + userCall: { + phone: true, + isVerified: true, + }, + userSms: { + phone: true, + isVerified: true, + }, + userEmail: { + email: true, + isVerified: true, + }, + }, + props: { + isRoot: true, + }, + }); - userOnCallLog.executedNotificationRules[ - userNotificationRuleId.toString() - ] = OneUptimeDate.getCurrentDate(); + if (!notificationRuleItem) { + throw new BadDataException("Notification rule item not found."); + } - await UserOnCallLogService.updateOneById({ - id: userOnCallLog.id!, + const logTimelineItem: UserOnCallLogTimeline = new UserOnCallLogTimeline(); + logTimelineItem.projectId = options.projectId; + logTimelineItem.userNotificationLogId = options.userNotificationLogId; + logTimelineItem.userNotificationRuleId = userNotificationRuleId; + logTimelineItem.userNotificationLogId = options.userNotificationLogId; + logTimelineItem.userId = notificationRuleItem.userId!; + logTimelineItem.userNotificationEventType = + options.userNotificationEventType; + + if (options.userBelongsToTeamId) { + logTimelineItem.userBelongsToTeamId = options.userBelongsToTeamId; + } + + if (options.onCallPolicyId) { + logTimelineItem.onCallDutyPolicyId = options.onCallPolicyId; + } + + if (options.onCallPolicyEscalationRuleId) { + logTimelineItem.onCallDutyPolicyEscalationRuleId = + options.onCallPolicyEscalationRuleId; + } + + if (options.onCallPolicyExecutionLogId) { + logTimelineItem.onCallDutyPolicyExecutionLogId = + options.onCallPolicyExecutionLogId; + } + + if (options.triggeredByIncidentId) { + logTimelineItem.triggeredByIncidentId = options.triggeredByIncidentId; + } + + if (options.onCallDutyPolicyExecutionLogTimelineId) { + logTimelineItem.onCallDutyPolicyExecutionLogTimelineId = + options.onCallDutyPolicyExecutionLogTimelineId; + } + + // add status and status message and save. + + let incident: Incident | null = null; + + if ( + options.userNotificationEventType === + UserNotificationEventType.IncidentCreated && + options.triggeredByIncidentId + ) { + incident = await IncidentService.findOneById({ + id: options.triggeredByIncidentId!, + props: { + isRoot: true, + }, + select: { + _id: true, + title: true, + description: true, + projectId: true, + project: { + name: true, + }, + currentIncidentState: { + name: true, + }, + incidentSeverity: { + name: true, + }, + rootCause: true, + }, + }); + } + + if (!incident) { + throw new BadDataException("Incident not found."); + } + + if ( + notificationRuleItem.userEmail?.email && + notificationRuleItem.userEmail?.isVerified + ) { + // send email. + if ( + options.userNotificationEventType === + UserNotificationEventType.IncidentCreated && + incident + ) { + // create an error log. + logTimelineItem.status = UserNotificationStatus.Sending; + logTimelineItem.statusMessage = `Sending email to ${notificationRuleItem.userEmail?.email.toString()}`; + logTimelineItem.userEmailId = notificationRuleItem.userEmail.id!; + + const updatedLog: UserOnCallLogTimeline = + await UserOnCallLogTimelineService.create({ + data: logTimelineItem, + props: { + isRoot: true, + }, + }); + + const emailMessage: EmailMessage = + await this.generateEmailTemplateForIncidentCreated( + notificationRuleItem.userEmail?.email, + incident, + updatedLog.id!, + ); + + // send email. + + MailService.sendMail(emailMessage, { + userOnCallLogTimelineId: updatedLog.id!, + projectId: options.projectId, + }).catch(async (err: Error) => { + await UserOnCallLogTimelineService.updateOneById({ + id: updatedLog.id!, data: { - executedNotificationRules: { - ...userOnCallLog.executedNotificationRules, - }, - } as any, - props: { - isRoot: true, - }, - }); - - // find notification rule item. - const notificationRuleItem: Model | null = await this.findOneById({ - id: userNotificationRuleId!, - select: { - _id: true, - userId: true, - userCall: { - phone: true, - isVerified: true, - }, - userSms: { - phone: true, - isVerified: true, - }, - userEmail: { - email: true, - isVerified: true, - }, + status: UserNotificationStatus.Error, + statusMessage: err.message || "Error sending email.", }, props: { - isRoot: true, + isRoot: true, }, + }); + }); + } + } + + // if you have an email but is not verified, then create a log. + if ( + notificationRuleItem.userEmail?.email && + !notificationRuleItem.userEmail?.isVerified + ) { + // create an error log. + logTimelineItem.status = UserNotificationStatus.Error; + logTimelineItem.statusMessage = `Email notification not sent because email ${notificationRuleItem.userEmail?.email.toString()} is not verified.`; + + await UserOnCallLogTimelineService.create({ + data: logTimelineItem, + props: { + isRoot: true, + }, + }); + } + + // send sms. + if ( + notificationRuleItem.userSms?.phone && + notificationRuleItem.userSms?.isVerified + ) { + // send sms. + if ( + options.userNotificationEventType === + UserNotificationEventType.IncidentCreated && + incident + ) { + // create an error log. + logTimelineItem.status = UserNotificationStatus.Sending; + logTimelineItem.statusMessage = `Sending SMS to ${notificationRuleItem.userSms?.phone.toString()}.`; + logTimelineItem.userSmsId = notificationRuleItem.userSms.id!; + + const updatedLog: UserOnCallLogTimeline = + await UserOnCallLogTimelineService.create({ + data: logTimelineItem, + props: { + isRoot: true, + }, + }); + + const smsMessage: SMS = + await this.generateSmsTemplateForIncidentCreated( + notificationRuleItem.userSms.phone, + incident, + updatedLog.id!, + ); + + // send email. + + SmsService.sendSms(smsMessage, { + projectId: incident.projectId, + userOnCallLogTimelineId: updatedLog.id!, + }).catch(async (err: Error) => { + await UserOnCallLogTimelineService.updateOneById({ + id: updatedLog.id!, + data: { + status: UserNotificationStatus.Error, + statusMessage: err.message || "Error sending SMS.", + }, + props: { + isRoot: true, + }, + }); + }); + } + } + + if ( + notificationRuleItem.userSms?.phone && + !notificationRuleItem.userSms?.isVerified + ) { + // create a log. + logTimelineItem.status = UserNotificationStatus.Error; + logTimelineItem.statusMessage = `SMS not sent because phone ${notificationRuleItem.userSms?.phone.toString()} is not verified.`; + + await UserOnCallLogTimelineService.create({ + data: logTimelineItem, + props: { + isRoot: true, + }, + }); + } + + // send call. + if ( + notificationRuleItem.userCall?.phone && + notificationRuleItem.userCall?.isVerified + ) { + // send call. + logTimelineItem.status = UserNotificationStatus.Sending; + logTimelineItem.statusMessage = `Making a call to ${notificationRuleItem.userCall?.phone.toString()}.`; + logTimelineItem.userCallId = notificationRuleItem.userCall.id!; + + const updatedLog: UserOnCallLogTimeline = + await UserOnCallLogTimelineService.create({ + data: logTimelineItem, + props: { + isRoot: true, + }, }); - if (!notificationRuleItem) { - throw new BadDataException('Notification rule item not found.'); - } - - const logTimelineItem: UserOnCallLogTimeline = - new UserOnCallLogTimeline(); - logTimelineItem.projectId = options.projectId; - logTimelineItem.userNotificationLogId = options.userNotificationLogId; - logTimelineItem.userNotificationRuleId = userNotificationRuleId; - logTimelineItem.userNotificationLogId = options.userNotificationLogId; - logTimelineItem.userId = notificationRuleItem.userId!; - logTimelineItem.userNotificationEventType = - options.userNotificationEventType; - - if (options.userBelongsToTeamId) { - logTimelineItem.userBelongsToTeamId = options.userBelongsToTeamId; - } - - if (options.onCallPolicyId) { - logTimelineItem.onCallDutyPolicyId = options.onCallPolicyId; - } - - if (options.onCallPolicyEscalationRuleId) { - logTimelineItem.onCallDutyPolicyEscalationRuleId = - options.onCallPolicyEscalationRuleId; - } - - if (options.onCallPolicyExecutionLogId) { - logTimelineItem.onCallDutyPolicyExecutionLogId = - options.onCallPolicyExecutionLogId; - } - - if (options.triggeredByIncidentId) { - logTimelineItem.triggeredByIncidentId = - options.triggeredByIncidentId; - } - - if (options.onCallDutyPolicyExecutionLogTimelineId) { - logTimelineItem.onCallDutyPolicyExecutionLogTimelineId = - options.onCallDutyPolicyExecutionLogTimelineId; - } - - // add status and status message and save. - - let incident: Incident | null = null; - - if ( - options.userNotificationEventType === - UserNotificationEventType.IncidentCreated && - options.triggeredByIncidentId - ) { - incident = await IncidentService.findOneById({ - id: options.triggeredByIncidentId!, - props: { - isRoot: true, - }, - select: { - _id: true, - title: true, - description: true, - projectId: true, - project: { - name: true, - }, - currentIncidentState: { - name: true, - }, - incidentSeverity: { - name: true, - }, - rootCause: true, - }, - }); - } - - if (!incident) { - throw new BadDataException('Incident not found.'); - } - - if ( - notificationRuleItem.userEmail?.email && - notificationRuleItem.userEmail?.isVerified - ) { - // send email. - if ( - options.userNotificationEventType === - UserNotificationEventType.IncidentCreated && - incident - ) { - // create an error log. - logTimelineItem.status = UserNotificationStatus.Sending; - logTimelineItem.statusMessage = `Sending email to ${notificationRuleItem.userEmail?.email.toString()}`; - logTimelineItem.userEmailId = - notificationRuleItem.userEmail.id!; - - const updatedLog: UserOnCallLogTimeline = - await UserOnCallLogTimelineService.create({ - data: logTimelineItem, - props: { - isRoot: true, - }, - }); - - const emailMessage: EmailMessage = - await this.generateEmailTemplateForIncidentCreated( - notificationRuleItem.userEmail?.email, - incident, - updatedLog.id! - ); - - // send email. - - MailService.sendMail(emailMessage, { - userOnCallLogTimelineId: updatedLog.id!, - projectId: options.projectId, - }).catch(async (err: Error) => { - await UserOnCallLogTimelineService.updateOneById({ - id: updatedLog.id!, - data: { - status: UserNotificationStatus.Error, - statusMessage: - err.message || 'Error sending email.', - }, - props: { - isRoot: true, - }, - }); - }); - } - } - - // if you have an email but is not verified, then create a log. - if ( - notificationRuleItem.userEmail?.email && - !notificationRuleItem.userEmail?.isVerified - ) { - // create an error log. - logTimelineItem.status = UserNotificationStatus.Error; - logTimelineItem.statusMessage = `Email notification not sent because email ${notificationRuleItem.userEmail?.email.toString()} is not verified.`; - - await UserOnCallLogTimelineService.create({ - data: logTimelineItem, - props: { - isRoot: true, - }, - }); - } - - // send sms. - if ( - notificationRuleItem.userSms?.phone && - notificationRuleItem.userSms?.isVerified - ) { - // send sms. - if ( - options.userNotificationEventType === - UserNotificationEventType.IncidentCreated && - incident - ) { - // create an error log. - logTimelineItem.status = UserNotificationStatus.Sending; - logTimelineItem.statusMessage = `Sending SMS to ${notificationRuleItem.userSms?.phone.toString()}.`; - logTimelineItem.userSmsId = notificationRuleItem.userSms.id!; - - const updatedLog: UserOnCallLogTimeline = - await UserOnCallLogTimelineService.create({ - data: logTimelineItem, - props: { - isRoot: true, - }, - }); - - const smsMessage: SMS = - await this.generateSmsTemplateForIncidentCreated( - notificationRuleItem.userSms.phone, - incident, - updatedLog.id! - ); - - // send email. - - SmsService.sendSms(smsMessage, { - projectId: incident.projectId, - userOnCallLogTimelineId: updatedLog.id!, - }).catch(async (err: Error) => { - await UserOnCallLogTimelineService.updateOneById({ - id: updatedLog.id!, - data: { - status: UserNotificationStatus.Error, - statusMessage: err.message || 'Error sending SMS.', - }, - props: { - isRoot: true, - }, - }); - }); - } - } - - if ( - notificationRuleItem.userSms?.phone && - !notificationRuleItem.userSms?.isVerified - ) { - // create a log. - logTimelineItem.status = UserNotificationStatus.Error; - logTimelineItem.statusMessage = `SMS not sent because phone ${notificationRuleItem.userSms?.phone.toString()} is not verified.`; - - await UserOnCallLogTimelineService.create({ - data: logTimelineItem, - props: { - isRoot: true, - }, - }); - } - - // send call. - if ( - notificationRuleItem.userCall?.phone && - notificationRuleItem.userCall?.isVerified - ) { - // send call. - logTimelineItem.status = UserNotificationStatus.Sending; - logTimelineItem.statusMessage = `Making a call to ${notificationRuleItem.userCall?.phone.toString()}.`; - logTimelineItem.userCallId = notificationRuleItem.userCall.id!; - - const updatedLog: UserOnCallLogTimeline = - await UserOnCallLogTimelineService.create({ - data: logTimelineItem, - props: { - isRoot: true, - }, - }); - - const callRequest: CallRequest = - await this.generateCallTemplateForIncidentCreated( - notificationRuleItem.userCall?.phone, - incident, - updatedLog.id! - ); - - // send email. - - CallService.makeCall(callRequest, { - projectId: incident.projectId, - userOnCallLogTimelineId: updatedLog.id!, - }).catch(async (err: Error) => { - await UserOnCallLogTimelineService.updateOneById({ - id: updatedLog.id!, - data: { - status: UserNotificationStatus.Error, - statusMessage: err.message || 'Error making call.', - }, - props: { - isRoot: true, - }, - }); - }); - } - - if ( - notificationRuleItem.userCall?.phone && - !notificationRuleItem.userCall?.isVerified - ) { - // create a log. - logTimelineItem.status = UserNotificationStatus.Error; - logTimelineItem.statusMessage = `Call not sent because phone ${notificationRuleItem.userCall?.phone.toString()} is not verified.`; - - await UserOnCallLogTimelineService.create({ - data: logTimelineItem, - props: { - isRoot: true, - }, - }); - } - } - - public async generateCallTemplateForIncidentCreated( - to: Phone, - incident: Incident, - userOnCallLogTimelineId: ObjectID - ): Promise<CallRequest> { - const host: Hostname = await DatabaseConfig.getHost(); - - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - - const callRequest: CallRequest = { - to: to, - data: [ - { - sayMessage: 'This is a call from OneUptime', - }, - { - sayMessage: 'A new incident has been created', - }, - { - sayMessage: incident.title!, - }, - { - introMessage: 'To acknowledge this incident press 1', - numDigits: 1, - timeoutInSeconds: 10, - noInputMessage: 'You have not entered any input. Good bye', - onInputCallRequest: { - '1': { - sayMessage: - 'You have acknowledged this incident. Good bye', - }, - default: { - sayMessage: 'Invalid input. Good bye', - }, - }, - responseUrl: new URL( - httpProtocol, - host, - new Route(AppApiRoute.toString()) - .addRoute(new UserOnCallLogTimeline().crudApiPath!) - .addRoute( - '/call/gather-input/' + - userOnCallLogTimelineId.toString() - ) - ), - }, - ], - }; - - return callRequest; - } - - public async generateSmsTemplateForIncidentCreated( - to: Phone, - incident: Incident, - userOnCallLogTimelineId: ObjectID - ): Promise<SMS> { - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - - const shortUrl: ShortLink = await ShortLinkService.saveShortLinkFor( - new URL( - httpProtocol, - host, - new Route(AppApiRoute.toString()) - .addRoute(new UserOnCallLogTimeline().crudApiPath!) - .addRoute( - '/acknowledge/' + userOnCallLogTimelineId.toString() - ) - ) + const callRequest: CallRequest = + await this.generateCallTemplateForIncidentCreated( + notificationRuleItem.userCall?.phone, + incident, + updatedLog.id!, ); - const url: URL = await ShortLinkService.getShortenedUrl(shortUrl); - const sms: SMS = { - to, - message: `This is a message from OneUptime. A new incident has been created. ${ - incident.title - }. To acknowledge this incident, please click on the following link ${url.toString()}`, - }; + // send email. - return sms; - } - - public async generateEmailTemplateForIncidentCreated( - to: Email, - incident: Incident, - userOnCallLogTimelineId: ObjectID - ): Promise<EmailMessage> { - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); - - const vars: Dictionary<string> = { - incidentTitle: incident.title!, - projectName: incident.project!.name!, - currentState: incident.currentIncidentState!.name!, - incidentDescription: await Markdown.convertToHTML( - incident.description! || '', - MarkdownContentType.Email - ), - incidentSeverity: incident.incidentSeverity!.name!, - rootCause: - incident.rootCause || - 'No root cause identified for this incident', - incidentViewLink: ( - await IncidentService.getIncidentLinkInDashboard( - incident.projectId!, - incident.id! - ) - ).toString(), - acknowledgeIncidentLink: new URL( - httpProtocol, - host, - new Route(AppApiRoute.toString()) - .addRoute(new UserOnCallLogTimeline().crudApiPath!) - .addRoute( - '/acknowledge/' + userOnCallLogTimelineId.toString() - ) - ).toString(), - }; - - const emailMessage: EmailMessage = { - toEmail: to!, - templateType: EmailTemplateType.AcknowledgeIncident, - vars: vars, - subject: 'ACTION REQUIRED: Incident created - ' + incident.title!, - }; - - return emailMessage; - } - - public async startUserNotificationRulesExecution( - userId: ObjectID, - options: { - projectId: ObjectID; - triggeredByIncidentId?: ObjectID | undefined; - userNotificationEventType: UserNotificationEventType; - onCallPolicyExecutionLogId?: ObjectID | undefined; - onCallPolicyId: ObjectID | undefined; - onCallPolicyEscalationRuleId?: ObjectID | undefined; - userBelongsToTeamId?: ObjectID | undefined; - onCallDutyPolicyExecutionLogTimelineId?: ObjectID | undefined; - onCallScheduleId?: ObjectID | undefined; - } - ): Promise<void> { - // add user notification log. - const userOnCallLog: UserOnCallLog = new UserOnCallLog(); - - userOnCallLog.userId = userId; - userOnCallLog.projectId = options.projectId; - - if (options.triggeredByIncidentId) { - userOnCallLog.triggeredByIncidentId = options.triggeredByIncidentId; - } - - userOnCallLog.userNotificationEventType = - options.userNotificationEventType; - - if (options.onCallPolicyExecutionLogId) { - userOnCallLog.onCallDutyPolicyExecutionLogId = - options.onCallPolicyExecutionLogId; - } - - if (options.onCallPolicyId) { - userOnCallLog.onCallDutyPolicyId = options.onCallPolicyId; - } - - if (options.onCallDutyPolicyExecutionLogTimelineId) { - userOnCallLog.onCallDutyPolicyExecutionLogTimelineId = - options.onCallDutyPolicyExecutionLogTimelineId; - } - - if (options.onCallPolicyEscalationRuleId) { - userOnCallLog.onCallDutyPolicyEscalationRuleId = - options.onCallPolicyEscalationRuleId; - } - - if (options.userBelongsToTeamId) { - userOnCallLog.userBelongsToTeamId = options.userBelongsToTeamId; - } - - if (options.onCallScheduleId) { - userOnCallLog.onCallDutyScheduleId = options.onCallScheduleId; - } - - userOnCallLog.status = UserNotificationExecutionStatus.Scheduled; - userOnCallLog.statusMessage = 'Scheduled'; - - await UserOnCallLogService.create({ - data: userOnCallLog, - props: { - isRoot: true, - }, + CallService.makeCall(callRequest, { + projectId: incident.projectId, + userOnCallLogTimelineId: updatedLog.id!, + }).catch(async (err: Error) => { + await UserOnCallLogTimelineService.updateOneById({ + id: updatedLog.id!, + data: { + status: UserNotificationStatus.Error, + statusMessage: err.message || "Error making call.", + }, + props: { + isRoot: true, + }, }); + }); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - if ( - !createBy.data.userCallId && - !createBy.data.userCall && - !createBy.data.userEmail && - !createBy.data.userSms && - !createBy.data.userSmsId && - !createBy.data.userEmailId - ) { - throw new BadDataException('Call, SMS, or Email is required'); - } + if ( + notificationRuleItem.userCall?.phone && + !notificationRuleItem.userCall?.isVerified + ) { + // create a log. + logTimelineItem.status = UserNotificationStatus.Error; + logTimelineItem.statusMessage = `Call not sent because phone ${notificationRuleItem.userCall?.phone.toString()} is not verified.`; - return { - createBy, - carryForward: null, - }; + await UserOnCallLogTimelineService.create({ + data: logTimelineItem, + props: { + isRoot: true, + }, + }); + } + } + + public async generateCallTemplateForIncidentCreated( + to: Phone, + incident: Incident, + userOnCallLogTimelineId: ObjectID, + ): Promise<CallRequest> { + const host: Hostname = await DatabaseConfig.getHost(); + + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + const callRequest: CallRequest = { + to: to, + data: [ + { + sayMessage: "This is a call from OneUptime", + }, + { + sayMessage: "A new incident has been created", + }, + { + sayMessage: incident.title!, + }, + { + introMessage: "To acknowledge this incident press 1", + numDigits: 1, + timeoutInSeconds: 10, + noInputMessage: "You have not entered any input. Good bye", + onInputCallRequest: { + "1": { + sayMessage: "You have acknowledged this incident. Good bye", + }, + default: { + sayMessage: "Invalid input. Good bye", + }, + }, + responseUrl: new URL( + httpProtocol, + host, + new Route(AppApiRoute.toString()) + .addRoute(new UserOnCallLogTimeline().crudApiPath!) + .addRoute( + "/call/gather-input/" + userOnCallLogTimelineId.toString(), + ), + ), + }, + ], + }; + + return callRequest; + } + + public async generateSmsTemplateForIncidentCreated( + to: Phone, + incident: Incident, + userOnCallLogTimelineId: ObjectID, + ): Promise<SMS> { + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + const shortUrl: ShortLink = await ShortLinkService.saveShortLinkFor( + new URL( + httpProtocol, + host, + new Route(AppApiRoute.toString()) + .addRoute(new UserOnCallLogTimeline().crudApiPath!) + .addRoute("/acknowledge/" + userOnCallLogTimelineId.toString()), + ), + ); + const url: URL = await ShortLinkService.getShortenedUrl(shortUrl); + + const sms: SMS = { + to, + message: `This is a message from OneUptime. A new incident has been created. ${ + incident.title + }. To acknowledge this incident, please click on the following link ${url.toString()}`, + }; + + return sms; + } + + public async generateEmailTemplateForIncidentCreated( + to: Email, + incident: Incident, + userOnCallLogTimelineId: ObjectID, + ): Promise<EmailMessage> { + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + const vars: Dictionary<string> = { + incidentTitle: incident.title!, + projectName: incident.project!.name!, + currentState: incident.currentIncidentState!.name!, + incidentDescription: await Markdown.convertToHTML( + incident.description! || "", + MarkdownContentType.Email, + ), + incidentSeverity: incident.incidentSeverity!.name!, + rootCause: + incident.rootCause || "No root cause identified for this incident", + incidentViewLink: ( + await IncidentService.getIncidentLinkInDashboard( + incident.projectId!, + incident.id!, + ) + ).toString(), + acknowledgeIncidentLink: new URL( + httpProtocol, + host, + new Route(AppApiRoute.toString()) + .addRoute(new UserOnCallLogTimeline().crudApiPath!) + .addRoute("/acknowledge/" + userOnCallLogTimelineId.toString()), + ).toString(), + }; + + const emailMessage: EmailMessage = { + toEmail: to!, + templateType: EmailTemplateType.AcknowledgeIncident, + vars: vars, + subject: "ACTION REQUIRED: Incident created - " + incident.title!, + }; + + return emailMessage; + } + + public async startUserNotificationRulesExecution( + userId: ObjectID, + options: { + projectId: ObjectID; + triggeredByIncidentId?: ObjectID | undefined; + userNotificationEventType: UserNotificationEventType; + onCallPolicyExecutionLogId?: ObjectID | undefined; + onCallPolicyId: ObjectID | undefined; + onCallPolicyEscalationRuleId?: ObjectID | undefined; + userBelongsToTeamId?: ObjectID | undefined; + onCallDutyPolicyExecutionLogTimelineId?: ObjectID | undefined; + onCallScheduleId?: ObjectID | undefined; + }, + ): Promise<void> { + // add user notification log. + const userOnCallLog: UserOnCallLog = new UserOnCallLog(); + + userOnCallLog.userId = userId; + userOnCallLog.projectId = options.projectId; + + if (options.triggeredByIncidentId) { + userOnCallLog.triggeredByIncidentId = options.triggeredByIncidentId; } - public async addDefaultNotificationRuleForUser( - projectId: ObjectID, - userId: ObjectID, - email: Email - ): Promise<void> { - const incidentSeverities: Array<IncidentSeverity> = - await IncidentSeverityService.findBy({ - query: { - projectId, - }, - props: { - isRoot: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - }, - }); + userOnCallLog.userNotificationEventType = options.userNotificationEventType; - //check userEmail - - let userEmail: UserEmail | null = await UserEmailService.findOneBy({ - query: { - projectId, - userId, - email, - }, - props: { - isRoot: true, - }, - }); - - if (!userEmail) { - userEmail = new UserEmail(); - userEmail.projectId = projectId; - userEmail.userId = userId; - userEmail.email = email; - userEmail.isVerified = true; - - userEmail = await UserEmailService.create({ - data: userEmail, - props: { - isRoot: true, - }, - }); - } - - // create for incident severities. - for (const incidentSeverity of incidentSeverities) { - //check if this rule already exists. - const existingRule: Model | null = await this.findOneBy({ - query: { - projectId, - userId, - userEmailId: userEmail.id!, - incidentSeverityId: incidentSeverity.id!, - ruleType: NotificationRuleType.ON_CALL_INCIDENT_CREATED, - }, - props: { - isRoot: true, - }, - }); - - if (existingRule) { - continue; // skip this rule. - } - - const notificationRule: Model = new Model(); - - notificationRule.projectId = projectId; - notificationRule.userId = userId; - notificationRule.userEmailId = userEmail.id!; - notificationRule.incidentSeverityId = incidentSeverity.id!; - notificationRule.notifyAfterMinutes = 0; - notificationRule.ruleType = - NotificationRuleType.ON_CALL_INCIDENT_CREATED; - - await this.create({ - data: notificationRule, - props: { - isRoot: true, - }, - }); - } - - //check if this rule already exists. - const existingRuleOnCall: Model | null = await this.findOneBy({ - query: { - projectId, - userId, - userEmailId: userEmail.id!, - ruleType: NotificationRuleType.WHEN_USER_GOES_ON_CALL, - }, - props: { - isRoot: true, - }, - }); - - if (!existingRuleOnCall) { - // on and off call. - const onCallRule: Model = new Model(); - - onCallRule.projectId = projectId; - onCallRule.userId = userId; - onCallRule.userEmailId = userEmail.id!; - onCallRule.notifyAfterMinutes = 0; - onCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_ON_CALL; - - await this.create({ - data: onCallRule, - props: { - isRoot: true, - }, - }); - } - - //check if this rule already exists. - const existingRuleOffCall: Model | null = await this.findOneBy({ - query: { - projectId, - userId, - userEmailId: userEmail.id!, - ruleType: NotificationRuleType.WHEN_USER_GOES_OFF_CALL, - }, - props: { - isRoot: true, - }, - }); - - if (!existingRuleOffCall) { - // on and off call. - const offCallRule: Model = new Model(); - - offCallRule.projectId = projectId; - offCallRule.userId = userId; - offCallRule.userEmailId = userEmail.id!; - offCallRule.notifyAfterMinutes = 0; - offCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_OFF_CALL; - - await this.create({ - data: offCallRule, - props: { - isRoot: true, - }, - }); - } + if (options.onCallPolicyExecutionLogId) { + userOnCallLog.onCallDutyPolicyExecutionLogId = + options.onCallPolicyExecutionLogId; } + + if (options.onCallPolicyId) { + userOnCallLog.onCallDutyPolicyId = options.onCallPolicyId; + } + + if (options.onCallDutyPolicyExecutionLogTimelineId) { + userOnCallLog.onCallDutyPolicyExecutionLogTimelineId = + options.onCallDutyPolicyExecutionLogTimelineId; + } + + if (options.onCallPolicyEscalationRuleId) { + userOnCallLog.onCallDutyPolicyEscalationRuleId = + options.onCallPolicyEscalationRuleId; + } + + if (options.userBelongsToTeamId) { + userOnCallLog.userBelongsToTeamId = options.userBelongsToTeamId; + } + + if (options.onCallScheduleId) { + userOnCallLog.onCallDutyScheduleId = options.onCallScheduleId; + } + + userOnCallLog.status = UserNotificationExecutionStatus.Scheduled; + userOnCallLog.statusMessage = "Scheduled"; + + await UserOnCallLogService.create({ + data: userOnCallLog, + props: { + isRoot: true, + }, + }); + } + + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + if ( + !createBy.data.userCallId && + !createBy.data.userCall && + !createBy.data.userEmail && + !createBy.data.userSms && + !createBy.data.userSmsId && + !createBy.data.userEmailId + ) { + throw new BadDataException("Call, SMS, or Email is required"); + } + + return { + createBy, + carryForward: null, + }; + } + + public async addDefaultNotificationRuleForUser( + projectId: ObjectID, + userId: ObjectID, + email: Email, + ): Promise<void> { + const incidentSeverities: Array<IncidentSeverity> = + await IncidentSeverityService.findBy({ + query: { + projectId, + }, + props: { + isRoot: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + }, + }); + + //check userEmail + + let userEmail: UserEmail | null = await UserEmailService.findOneBy({ + query: { + projectId, + userId, + email, + }, + props: { + isRoot: true, + }, + }); + + if (!userEmail) { + userEmail = new UserEmail(); + userEmail.projectId = projectId; + userEmail.userId = userId; + userEmail.email = email; + userEmail.isVerified = true; + + userEmail = await UserEmailService.create({ + data: userEmail, + props: { + isRoot: true, + }, + }); + } + + // create for incident severities. + for (const incidentSeverity of incidentSeverities) { + //check if this rule already exists. + const existingRule: Model | null = await this.findOneBy({ + query: { + projectId, + userId, + userEmailId: userEmail.id!, + incidentSeverityId: incidentSeverity.id!, + ruleType: NotificationRuleType.ON_CALL_INCIDENT_CREATED, + }, + props: { + isRoot: true, + }, + }); + + if (existingRule) { + continue; // skip this rule. + } + + const notificationRule: Model = new Model(); + + notificationRule.projectId = projectId; + notificationRule.userId = userId; + notificationRule.userEmailId = userEmail.id!; + notificationRule.incidentSeverityId = incidentSeverity.id!; + notificationRule.notifyAfterMinutes = 0; + notificationRule.ruleType = NotificationRuleType.ON_CALL_INCIDENT_CREATED; + + await this.create({ + data: notificationRule, + props: { + isRoot: true, + }, + }); + } + + //check if this rule already exists. + const existingRuleOnCall: Model | null = await this.findOneBy({ + query: { + projectId, + userId, + userEmailId: userEmail.id!, + ruleType: NotificationRuleType.WHEN_USER_GOES_ON_CALL, + }, + props: { + isRoot: true, + }, + }); + + if (!existingRuleOnCall) { + // on and off call. + const onCallRule: Model = new Model(); + + onCallRule.projectId = projectId; + onCallRule.userId = userId; + onCallRule.userEmailId = userEmail.id!; + onCallRule.notifyAfterMinutes = 0; + onCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_ON_CALL; + + await this.create({ + data: onCallRule, + props: { + isRoot: true, + }, + }); + } + + //check if this rule already exists. + const existingRuleOffCall: Model | null = await this.findOneBy({ + query: { + projectId, + userId, + userEmailId: userEmail.id!, + ruleType: NotificationRuleType.WHEN_USER_GOES_OFF_CALL, + }, + props: { + isRoot: true, + }, + }); + + if (!existingRuleOffCall) { + // on and off call. + const offCallRule: Model = new Model(); + + offCallRule.projectId = projectId; + offCallRule.userId = userId; + offCallRule.userEmailId = userEmail.id!; + offCallRule.notifyAfterMinutes = 0; + offCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_OFF_CALL; + + await this.create({ + data: offCallRule, + props: { + isRoot: true, + }, + }); + } + } } export default new Service(); diff --git a/CommonServer/Services/UserNotificationSettingService.ts b/CommonServer/Services/UserNotificationSettingService.ts index 18b7092121..daa864fd0d 100644 --- a/CommonServer/Services/UserNotificationSettingService.ts +++ b/CommonServer/Services/UserNotificationSettingService.ts @@ -1,336 +1,334 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate } from '../Types/Database/Hooks'; -import logger from '../Utils/Logger'; -import CallService from './CallService'; -import DatabaseService from './DatabaseService'; -import MailService from './MailService'; -import SmsService from './SmsService'; -import TeamMemberService from './TeamMemberService'; -import UserCallService from './UserCallService'; -import UserEmailService from './UserEmailService'; -import UserSmsService from './UserSmsService'; -import { CallRequestMessage } from 'Common/Types/Call/CallRequest'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import { EmailEnvelope } from 'Common/Types/Email/EmailMessage'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import { SMSMessage } from 'Common/Types/SMS/SMS'; -import UserCall from 'Model/Models/UserCall'; -import UserEmail from 'Model/Models/UserEmail'; -import UserNotificationSetting from 'Model/Models/UserNotificationSetting'; -import UserSMS from 'Model/Models/UserSMS'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate } from "../Types/Database/Hooks"; +import logger from "../Utils/Logger"; +import CallService from "./CallService"; +import DatabaseService from "./DatabaseService"; +import MailService from "./MailService"; +import SmsService from "./SmsService"; +import TeamMemberService from "./TeamMemberService"; +import UserCallService from "./UserCallService"; +import UserEmailService from "./UserEmailService"; +import UserSmsService from "./UserSmsService"; +import { CallRequestMessage } from "Common/Types/Call/CallRequest"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import { EmailEnvelope } from "Common/Types/Email/EmailMessage"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import { SMSMessage } from "Common/Types/SMS/SMS"; +import UserCall from "Model/Models/UserCall"; +import UserEmail from "Model/Models/UserEmail"; +import UserNotificationSetting from "Model/Models/UserNotificationSetting"; +import UserSMS from "Model/Models/UserSMS"; export class Service extends DatabaseService<UserNotificationSetting> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(UserNotificationSetting, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(UserNotificationSetting, postgresDatabase); + } + + public async sendUserNotification(data: { + userId: ObjectID; + projectId: ObjectID; + eventType: NotificationSettingEventType; + emailEnvelope: EmailEnvelope; + smsMessage: SMSMessage; + callRequestMessage: CallRequestMessage; + }): Promise<void> { + if (!data.projectId) { + throw new BadDataException( + "ProjectId is required for SendUserNotification", + ); } - public async sendUserNotification(data: { - userId: ObjectID; - projectId: ObjectID; - eventType: NotificationSettingEventType; - emailEnvelope: EmailEnvelope; - smsMessage: SMSMessage; - callRequestMessage: CallRequestMessage; - }): Promise<void> { - if (!data.projectId) { - throw new BadDataException( - 'ProjectId is required for SendUserNotification' - ); - } + const notificationSettings: UserNotificationSetting | null = + await this.findOneBy({ + query: { + userId: data.userId, + projectId: data.projectId, + eventType: data.eventType, + }, + select: { + alertByEmail: true, + alertBySMS: true, + alertByCall: true, + }, + props: { + isRoot: true, + }, + }); - const notificationSettings: UserNotificationSetting | null = - await this.findOneBy({ - query: { - userId: data.userId, - projectId: data.projectId, - eventType: data.eventType, - }, - select: { - alertByEmail: true, - alertBySMS: true, - alertByCall: true, - }, - props: { - isRoot: true, - }, - }); - - if (notificationSettings) { - if (notificationSettings.alertByEmail) { - // get all the emails of the user. - const userEmails: Array<UserEmail> = - await UserEmailService.findBy({ - query: { - userId: data.userId, - projectId: data.projectId, - isVerified: true, - }, - select: { - email: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const userEmail of userEmails) { - MailService.sendMail( - { - ...data.emailEnvelope, - toEmail: userEmail.email!, - }, - { - projectId: data.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - - if (notificationSettings.alertBySMS) { - const userSmses: Array<UserSMS> = await UserSmsService.findBy({ - query: { - userId: data.userId, - projectId: data.projectId, - isVerified: true, - }, - select: { - phone: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const userSms of userSmses) { - SmsService.sendSms( - { - ...data.smsMessage, - to: userSms.phone!, - }, - { - projectId: data.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - - if (notificationSettings.alertByCall) { - const userCalls: Array<UserCall> = await UserCallService.findBy( - { - query: { - userId: data.userId, - projectId: data.projectId, - isVerified: true, - }, - select: { - phone: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - } - ); - - for (const userCall of userCalls) { - CallService.makeCall( - { - ...data.callRequestMessage, - to: userCall.phone!, - }, - { - projectId: data.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - } - } - - public async removeDefaultNotificationSettingsForUser( - userId: ObjectID, - projectId: ObjectID - ): Promise<void> { - // check if this user is not in the project anymore. - const count: PositiveNumber = await TeamMemberService.countBy({ - query: { - projectId, - userId, - hasAcceptedInvitation: true, - }, - props: { - isRoot: true, - }, + if (notificationSettings) { + if (notificationSettings.alertByEmail) { + // get all the emails of the user. + const userEmails: Array<UserEmail> = await UserEmailService.findBy({ + query: { + userId: data.userId, + projectId: data.projectId, + isVerified: true, + }, + select: { + email: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, }); - if (count.toNumber() === 0) { - await this.deleteBy({ - query: { - projectId, - userId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - } - } - - public async addDefaultNotificationSettingsForUser( - userId: ObjectID, - projectId: ObjectID - ): Promise<void> { - const incidentCreatedNotificationEvent: PositiveNumber = - await this.countBy({ - query: { - userId, - projectId, - eventType: - NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION, - }, - props: { - isRoot: true, - }, - }); - - if (incidentCreatedNotificationEvent.toNumber() === 0) { - const item: UserNotificationSetting = new UserNotificationSetting(); - item.userId = userId; - item.projectId = projectId; - item.eventType = - NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION; - item.alertByEmail = true; - - await this.create({ - data: item, - props: { - isRoot: true, - }, - }); - } - - // check monitor state changed notification - const monitorStateChangedNotificationEvent: PositiveNumber = - await this.countBy({ - query: { - userId, - projectId, - eventType: - NotificationSettingEventType.SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION, - }, - props: { - isRoot: true, - }, - }); - - if (monitorStateChangedNotificationEvent.toNumber() === 0) { - const item: UserNotificationSetting = new UserNotificationSetting(); - item.userId = userId; - item.projectId = projectId; - item.eventType = - NotificationSettingEventType.SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION; - item.alertByEmail = true; - - await this.create({ - data: item, - props: { - isRoot: true, - }, - }); - } - - // check incident state changed notification - const incidentStateChangedNotificationEvent: PositiveNumber = - await this.countBy({ - query: { - userId, - projectId, - eventType: - NotificationSettingEventType.SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION, - }, - props: { - isRoot: true, - }, - }); - - if (incidentStateChangedNotificationEvent.toNumber() === 0) { - const item: UserNotificationSetting = new UserNotificationSetting(); - item.userId = userId; - item.projectId = projectId; - item.eventType = - NotificationSettingEventType.SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION; - item.alertByEmail = true; - - await this.create({ - data: item, - props: { - isRoot: true, - }, - }); - } - } - - protected override async onBeforeCreate( - createBy: CreateBy<UserNotificationSetting> - ): Promise<OnCreate<UserNotificationSetting>> { - // check if the same event for same user is added. - if (!createBy.data.projectId) { - throw new BadDataException( - 'ProjectId is required for UserNotificationSetting' - ); - } - - if (!createBy.data.userId) { - throw new BadDataException( - 'UserId is required for UserNotificationSetting' - ); - } - - if (!createBy.data.eventType) { - throw new BadDataException( - 'EventType is required for UserNotificationSetting' - ); - } - - const count: PositiveNumber = await this.countBy({ - query: { - projectId: createBy.data.projectId, - userId: createBy.data.userId, - eventType: createBy.data.eventType, + for (const userEmail of userEmails) { + MailService.sendMail( + { + ...data.emailEnvelope, + toEmail: userEmail.email!, }, - props: { - isRoot: true, + { + projectId: data.projectId, }, + ).catch((err: Error) => { + logger.error(err); + }); + } + } + + if (notificationSettings.alertBySMS) { + const userSmses: Array<UserSMS> = await UserSmsService.findBy({ + query: { + userId: data.userId, + projectId: data.projectId, + isVerified: true, + }, + select: { + phone: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, }); - if (count.toNumber() > 0) { - throw new BadDataException( - 'Notification Setting of the same event type already exists for the user.' - ); + for (const userSms of userSmses) { + SmsService.sendSms( + { + ...data.smsMessage, + to: userSms.phone!, + }, + { + projectId: data.projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); } + } - return { - createBy, - carryForward: undefined, - }; + if (notificationSettings.alertByCall) { + const userCalls: Array<UserCall> = await UserCallService.findBy({ + query: { + userId: data.userId, + projectId: data.projectId, + isVerified: true, + }, + select: { + phone: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const userCall of userCalls) { + CallService.makeCall( + { + ...data.callRequestMessage, + to: userCall.phone!, + }, + { + projectId: data.projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } + } } + } + + public async removeDefaultNotificationSettingsForUser( + userId: ObjectID, + projectId: ObjectID, + ): Promise<void> { + // check if this user is not in the project anymore. + const count: PositiveNumber = await TeamMemberService.countBy({ + query: { + projectId, + userId, + hasAcceptedInvitation: true, + }, + props: { + isRoot: true, + }, + }); + + if (count.toNumber() === 0) { + await this.deleteBy({ + query: { + projectId, + userId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + } + } + + public async addDefaultNotificationSettingsForUser( + userId: ObjectID, + projectId: ObjectID, + ): Promise<void> { + const incidentCreatedNotificationEvent: PositiveNumber = await this.countBy( + { + query: { + userId, + projectId, + eventType: + NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION, + }, + props: { + isRoot: true, + }, + }, + ); + + if (incidentCreatedNotificationEvent.toNumber() === 0) { + const item: UserNotificationSetting = new UserNotificationSetting(); + item.userId = userId; + item.projectId = projectId; + item.eventType = + NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION; + item.alertByEmail = true; + + await this.create({ + data: item, + props: { + isRoot: true, + }, + }); + } + + // check monitor state changed notification + const monitorStateChangedNotificationEvent: PositiveNumber = + await this.countBy({ + query: { + userId, + projectId, + eventType: + NotificationSettingEventType.SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION, + }, + props: { + isRoot: true, + }, + }); + + if (monitorStateChangedNotificationEvent.toNumber() === 0) { + const item: UserNotificationSetting = new UserNotificationSetting(); + item.userId = userId; + item.projectId = projectId; + item.eventType = + NotificationSettingEventType.SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION; + item.alertByEmail = true; + + await this.create({ + data: item, + props: { + isRoot: true, + }, + }); + } + + // check incident state changed notification + const incidentStateChangedNotificationEvent: PositiveNumber = + await this.countBy({ + query: { + userId, + projectId, + eventType: + NotificationSettingEventType.SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION, + }, + props: { + isRoot: true, + }, + }); + + if (incidentStateChangedNotificationEvent.toNumber() === 0) { + const item: UserNotificationSetting = new UserNotificationSetting(); + item.userId = userId; + item.projectId = projectId; + item.eventType = + NotificationSettingEventType.SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION; + item.alertByEmail = true; + + await this.create({ + data: item, + props: { + isRoot: true, + }, + }); + } + } + + protected override async onBeforeCreate( + createBy: CreateBy<UserNotificationSetting>, + ): Promise<OnCreate<UserNotificationSetting>> { + // check if the same event for same user is added. + if (!createBy.data.projectId) { + throw new BadDataException( + "ProjectId is required for UserNotificationSetting", + ); + } + + if (!createBy.data.userId) { + throw new BadDataException( + "UserId is required for UserNotificationSetting", + ); + } + + if (!createBy.data.eventType) { + throw new BadDataException( + "EventType is required for UserNotificationSetting", + ); + } + + const count: PositiveNumber = await this.countBy({ + query: { + projectId: createBy.data.projectId, + userId: createBy.data.userId, + eventType: createBy.data.eventType, + }, + props: { + isRoot: true, + }, + }); + + if (count.toNumber() > 0) { + throw new BadDataException( + "Notification Setting of the same event type already exists for the user.", + ); + } + + return { + createBy, + carryForward: undefined, + }; + } } export default new Service(); diff --git a/CommonServer/Services/UserOnCallLogService.ts b/CommonServer/Services/UserOnCallLogService.ts index 1e01e8191b..81f24c88e4 100644 --- a/CommonServer/Services/UserOnCallLogService.ts +++ b/CommonServer/Services/UserOnCallLogService.ts @@ -1,263 +1,252 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import { OnCreate, OnUpdate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import IncidentService from './IncidentService'; -import OnCallDutyPolicyExecutionLogTimelineService from './OnCallDutyPolicyExecutionLogTimelineService'; -import UserNotificationRuleService from './UserNotificationRuleService'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotificationRuleType from 'Common/Types/NotificationRule/NotificationRuleType'; -import ObjectID from 'Common/Types/ObjectID'; -import OnCallDutyExecutionLogTimelineStatus from 'Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import UserNotificationExecutionStatus from 'Common/Types/UserNotification/UserNotificationExecutionStatus'; -import Incident from 'Model/Models/Incident'; -import UserNotificationRule from 'Model/Models/UserNotificationRule'; -import Model from 'Model/Models/UserOnCallLog'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import { OnCreate, OnUpdate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import IncidentService from "./IncidentService"; +import OnCallDutyPolicyExecutionLogTimelineService from "./OnCallDutyPolicyExecutionLogTimelineService"; +import UserNotificationRuleService from "./UserNotificationRuleService"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotificationRuleType from "Common/Types/NotificationRule/NotificationRuleType"; +import ObjectID from "Common/Types/ObjectID"; +import OnCallDutyExecutionLogTimelineStatus from "Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import UserNotificationExecutionStatus from "Common/Types/UserNotification/UserNotificationExecutionStatus"; +import Incident from "Model/Models/Incident"; +import UserNotificationRule from "Model/Models/UserNotificationRule"; +import Model from "Model/Models/UserOnCallLog"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 30); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 30); + } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - createBy.data.status = UserNotificationExecutionStatus.Scheduled; + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + createBy.data.status = UserNotificationExecutionStatus.Scheduled; - return { - createBy, - carryForward: null, - }; - } + return { + createBy, + carryForward: null, + }; + } - protected override async onUpdateSuccess( - onUpdate: OnUpdate<Model>, - _updatedItemIds: ObjectID[] - ): Promise<OnUpdate<Model>> { - if (onUpdate.updateBy.data.status) { - //update the corresponding oncallTimeline. - const items: Array<Model> = await this.findBy({ - query: onUpdate.updateBy.query, - select: { - onCallDutyPolicyExecutionLogTimelineId: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); + protected override async onUpdateSuccess( + onUpdate: OnUpdate<Model>, + _updatedItemIds: ObjectID[], + ): Promise<OnUpdate<Model>> { + if (onUpdate.updateBy.data.status) { + //update the corresponding oncallTimeline. + const items: Array<Model> = await this.findBy({ + query: onUpdate.updateBy.query, + select: { + onCallDutyPolicyExecutionLogTimelineId: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); - let status: OnCallDutyExecutionLogTimelineStatus | undefined = - undefined; + let status: OnCallDutyExecutionLogTimelineStatus | undefined = undefined; - switch (onUpdate.updateBy.data.status) { - case UserNotificationExecutionStatus.Completed: - status = - OnCallDutyExecutionLogTimelineStatus.NotificationSent; - break; - case UserNotificationExecutionStatus.Error: - status = OnCallDutyExecutionLogTimelineStatus.Error; - break; - case UserNotificationExecutionStatus.Executing: - status = OnCallDutyExecutionLogTimelineStatus.Executing; - break; - case UserNotificationExecutionStatus.Scheduled: - status = OnCallDutyExecutionLogTimelineStatus.Started; - break; - case UserNotificationExecutionStatus.Started: - status = OnCallDutyExecutionLogTimelineStatus.Started; - break; - default: - throw new BadDataException('Invalid status'); - } + switch (onUpdate.updateBy.data.status) { + case UserNotificationExecutionStatus.Completed: + status = OnCallDutyExecutionLogTimelineStatus.NotificationSent; + break; + case UserNotificationExecutionStatus.Error: + status = OnCallDutyExecutionLogTimelineStatus.Error; + break; + case UserNotificationExecutionStatus.Executing: + status = OnCallDutyExecutionLogTimelineStatus.Executing; + break; + case UserNotificationExecutionStatus.Scheduled: + status = OnCallDutyExecutionLogTimelineStatus.Started; + break; + case UserNotificationExecutionStatus.Started: + status = OnCallDutyExecutionLogTimelineStatus.Started; + break; + default: + throw new BadDataException("Invalid status"); + } - for (const item of items) { - await OnCallDutyPolicyExecutionLogTimelineService.updateOneById( - { - id: item.onCallDutyPolicyExecutionLogTimelineId!, - data: { - status: status!, - statusMessage: - onUpdate.updateBy.data.statusMessage!, - }, - props: { - isRoot: true, - }, - } - ); - } - } - - return onUpdate; - } - - protected override async onCreateSuccess( - _onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - // update this item to be processed. - await this.updateOneById({ - id: createdItem.id!, - data: { - status: UserNotificationExecutionStatus.Started, - }, - props: { - isRoot: true, - }, - }); - - const notificationRuleType: NotificationRuleType = - this.getNotificationRuleType( - createdItem.userNotificationEventType! - ); - - const incident: Incident | null = await IncidentService.findOneById({ - id: createdItem.triggeredByIncidentId!, - props: { - isRoot: true, - }, - select: { - incidentSeverityId: true, - }, - }); - - // Check if there are any rules . - const ruleCount: PositiveNumber = - await UserNotificationRuleService.countBy({ - query: { - userId: createdItem.userId!, - projectId: createdItem.projectId!, - ruleType: notificationRuleType, - incidentSeverityId: - incident?.incidentSeverityId as ObjectID, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - if (ruleCount.toNumber() === 0) { - // update this item to be processed. - await this.updateOneById({ - id: createdItem.id!, - data: { - status: UserNotificationExecutionStatus.Error, // now the worker will pick this up and complete this or mark this as failed. - statusMessage: - 'No notification rules found. Please add rules in User Settings > On-Call Rules.', - }, - props: { - isRoot: true, - }, - }); - - // update oncall timeline item as well. - await OnCallDutyPolicyExecutionLogTimelineService.updateOneById({ - id: createdItem.onCallDutyPolicyExecutionLogTimelineId!, - data: { - status: OnCallDutyExecutionLogTimelineStatus.Error, - statusMessage: - 'No notification rules found. Please add rules in User Settings > On-Call Rules.', - }, - props: { - isRoot: true, - }, - }); - - return createdItem; - } - - // find immediate notification rule and alert the user. - const immediateNotificationRule: Array<UserNotificationRule> = - await UserNotificationRuleService.findBy({ - query: { - userId: createdItem.userId!, - projectId: createdItem.projectId!, - notifyAfterMinutes: 0, - ruleType: notificationRuleType, - incidentSeverityId: - incident?.incidentSeverityId as ObjectID, - }, - select: { - _id: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); - - for (const immediateNotificationRuleItem of immediateNotificationRule) { - await UserNotificationRuleService.executeNotificationRuleItem( - immediateNotificationRuleItem.id!, - { - userNotificationLogId: createdItem.id!, - projectId: createdItem.projectId!, - triggeredByIncidentId: createdItem.triggeredByIncidentId, - userNotificationEventType: - createdItem.userNotificationEventType!, - onCallPolicyExecutionLogId: - createdItem.onCallDutyPolicyExecutionLogId, - onCallPolicyId: createdItem.onCallDutyPolicyId, - onCallPolicyEscalationRuleId: - createdItem.onCallDutyPolicyEscalationRuleId, - userBelongsToTeamId: createdItem.userBelongsToTeamId, - onCallDutyPolicyExecutionLogTimelineId: - createdItem.onCallDutyPolicyExecutionLogTimelineId, - } - ); - } - - // update this item to be processed. - await this.updateOneById({ - id: createdItem.id!, - data: { - status: UserNotificationExecutionStatus.Executing, // now the worker will pick this up and complete this or mark this as failed. - }, - props: { - isRoot: true, - }, - }); - - // update oncall timeline item as well. + for (const item of items) { await OnCallDutyPolicyExecutionLogTimelineService.updateOneById({ - id: createdItem.onCallDutyPolicyExecutionLogTimelineId!, - data: { - status: OnCallDutyExecutionLogTimelineStatus.NotificationSent, - statusMessage: 'Initial notification sent to the user.', - }, - props: { - isRoot: true, - }, + id: item.onCallDutyPolicyExecutionLogTimelineId!, + data: { + status: status!, + statusMessage: onUpdate.updateBy.data.statusMessage!, + }, + props: { + isRoot: true, + }, }); - - return createdItem; + } } - public getNotificationRuleType( - userNotificationEventType: UserNotificationEventType - ): NotificationRuleType { - let notificationRuleType: NotificationRuleType = - NotificationRuleType.ON_CALL_INCIDENT_CREATED; + return onUpdate; + } - if ( - userNotificationEventType === - UserNotificationEventType.IncidentCreated - ) { - notificationRuleType = - NotificationRuleType.ON_CALL_INCIDENT_CREATED; - } else { - // Invalid user notification event type. - throw new BadDataException('Invalid user notification event type.'); - } - return notificationRuleType; + protected override async onCreateSuccess( + _onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + // update this item to be processed. + await this.updateOneById({ + id: createdItem.id!, + data: { + status: UserNotificationExecutionStatus.Started, + }, + props: { + isRoot: true, + }, + }); + + const notificationRuleType: NotificationRuleType = + this.getNotificationRuleType(createdItem.userNotificationEventType!); + + const incident: Incident | null = await IncidentService.findOneById({ + id: createdItem.triggeredByIncidentId!, + props: { + isRoot: true, + }, + select: { + incidentSeverityId: true, + }, + }); + + // Check if there are any rules . + const ruleCount: PositiveNumber = await UserNotificationRuleService.countBy( + { + query: { + userId: createdItem.userId!, + projectId: createdItem.projectId!, + ruleType: notificationRuleType, + incidentSeverityId: incident?.incidentSeverityId as ObjectID, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }, + ); + + if (ruleCount.toNumber() === 0) { + // update this item to be processed. + await this.updateOneById({ + id: createdItem.id!, + data: { + status: UserNotificationExecutionStatus.Error, // now the worker will pick this up and complete this or mark this as failed. + statusMessage: + "No notification rules found. Please add rules in User Settings > On-Call Rules.", + }, + props: { + isRoot: true, + }, + }); + + // update oncall timeline item as well. + await OnCallDutyPolicyExecutionLogTimelineService.updateOneById({ + id: createdItem.onCallDutyPolicyExecutionLogTimelineId!, + data: { + status: OnCallDutyExecutionLogTimelineStatus.Error, + statusMessage: + "No notification rules found. Please add rules in User Settings > On-Call Rules.", + }, + props: { + isRoot: true, + }, + }); + + return createdItem; } + + // find immediate notification rule and alert the user. + const immediateNotificationRule: Array<UserNotificationRule> = + await UserNotificationRuleService.findBy({ + query: { + userId: createdItem.userId!, + projectId: createdItem.projectId!, + notifyAfterMinutes: 0, + ruleType: notificationRuleType, + incidentSeverityId: incident?.incidentSeverityId as ObjectID, + }, + select: { + _id: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); + + for (const immediateNotificationRuleItem of immediateNotificationRule) { + await UserNotificationRuleService.executeNotificationRuleItem( + immediateNotificationRuleItem.id!, + { + userNotificationLogId: createdItem.id!, + projectId: createdItem.projectId!, + triggeredByIncidentId: createdItem.triggeredByIncidentId, + userNotificationEventType: createdItem.userNotificationEventType!, + onCallPolicyExecutionLogId: + createdItem.onCallDutyPolicyExecutionLogId, + onCallPolicyId: createdItem.onCallDutyPolicyId, + onCallPolicyEscalationRuleId: + createdItem.onCallDutyPolicyEscalationRuleId, + userBelongsToTeamId: createdItem.userBelongsToTeamId, + onCallDutyPolicyExecutionLogTimelineId: + createdItem.onCallDutyPolicyExecutionLogTimelineId, + }, + ); + } + + // update this item to be processed. + await this.updateOneById({ + id: createdItem.id!, + data: { + status: UserNotificationExecutionStatus.Executing, // now the worker will pick this up and complete this or mark this as failed. + }, + props: { + isRoot: true, + }, + }); + + // update oncall timeline item as well. + await OnCallDutyPolicyExecutionLogTimelineService.updateOneById({ + id: createdItem.onCallDutyPolicyExecutionLogTimelineId!, + data: { + status: OnCallDutyExecutionLogTimelineStatus.NotificationSent, + statusMessage: "Initial notification sent to the user.", + }, + props: { + isRoot: true, + }, + }); + + return createdItem; + } + + public getNotificationRuleType( + userNotificationEventType: UserNotificationEventType, + ): NotificationRuleType { + let notificationRuleType: NotificationRuleType = + NotificationRuleType.ON_CALL_INCIDENT_CREATED; + + if ( + userNotificationEventType === UserNotificationEventType.IncidentCreated + ) { + notificationRuleType = NotificationRuleType.ON_CALL_INCIDENT_CREATED; + } else { + // Invalid user notification event type. + throw new BadDataException("Invalid user notification event type."); + } + return notificationRuleType; + } } export default new Service(); diff --git a/CommonServer/Services/UserOnCallLogTimelineService.ts b/CommonServer/Services/UserOnCallLogTimelineService.ts index 8d57b326aa..d899eb342e 100644 --- a/CommonServer/Services/UserOnCallLogTimelineService.ts +++ b/CommonServer/Services/UserOnCallLogTimelineService.ts @@ -1,142 +1,128 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import { OnUpdate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import IncidentService from './IncidentService'; -import OnCallDutyPolicyExecutionLogService from './OnCallDutyPolicyExecutionLogService'; -import OnCallDutyPolicyExecutionLogTimelineService from './OnCallDutyPolicyExecutionLogTimelineService'; -import UserOnCallLogService from './UserOnCallLogService'; -import UserService from './UserService'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import OnCallDutyExecutionLogTimelineStatus from 'Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus'; -import OnCallDutyPolicyStatus from 'Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus'; -import UserNotificationExecutionStatus from 'Common/Types/UserNotification/UserNotificationExecutionStatus'; -import User from 'Model/Models/User'; -import Model from 'Model/Models/UserOnCallLogTimeline'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import { OnUpdate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import IncidentService from "./IncidentService"; +import OnCallDutyPolicyExecutionLogService from "./OnCallDutyPolicyExecutionLogService"; +import OnCallDutyPolicyExecutionLogTimelineService from "./OnCallDutyPolicyExecutionLogTimelineService"; +import UserOnCallLogService from "./UserOnCallLogService"; +import UserService from "./UserService"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import OnCallDutyExecutionLogTimelineStatus from "Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus"; +import OnCallDutyPolicyStatus from "Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus"; +import UserNotificationExecutionStatus from "Common/Types/UserNotification/UserNotificationExecutionStatus"; +import User from "Model/Models/User"; +import Model from "Model/Models/UserOnCallLogTimeline"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } - protected override async onUpdateSuccess( - onUpdate: OnUpdate<Model>, - _updatedItemIds: ObjectID[] - ): Promise<OnUpdate<Model>> { - if ( - onUpdate.updateBy.data.acknowledgedAt && - onUpdate.updateBy.data.isAcknowledged - ) { - const items: Array<Model> = await this.findBy({ - query: onUpdate.updateBy.query, - select: { - _id: true, - projectId: true, - userId: true, - userNotificationLogId: true, - onCallDutyPolicyExecutionLogId: true, - triggeredByIncidentId: true, - onCallDutyPolicyExecutionLogTimelineId: true, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - props: { - isRoot: true, - }, - }); + protected override async onUpdateSuccess( + onUpdate: OnUpdate<Model>, + _updatedItemIds: ObjectID[], + ): Promise<OnUpdate<Model>> { + if ( + onUpdate.updateBy.data.acknowledgedAt && + onUpdate.updateBy.data.isAcknowledged + ) { + const items: Array<Model> = await this.findBy({ + query: onUpdate.updateBy.query, + select: { + _id: true, + projectId: true, + userId: true, + userNotificationLogId: true, + onCallDutyPolicyExecutionLogId: true, + triggeredByIncidentId: true, + onCallDutyPolicyExecutionLogTimelineId: true, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + props: { + isRoot: true, + }, + }); - for (const item of items) { - // this incident is acknowledged. + for (const item of items) { + // this incident is acknowledged. - // now we need to ack the parent log. + // now we need to ack the parent log. - const user: User | null = await UserService.findOneById({ - id: item.userId!, - select: { - _id: true, - name: true, - email: true, - }, - props: { - isRoot: true, - }, - }); + const user: User | null = await UserService.findOneById({ + id: item.userId!, + select: { + _id: true, + name: true, + email: true, + }, + props: { + isRoot: true, + }, + }); - if (!user) { - throw new BadDataException('User not found.'); - } - - await UserOnCallLogService.updateOneById({ - id: item.userNotificationLogId!, - data: { - acknowledgedAt: onUpdate.updateBy.data.acknowledgedAt, - acknowledgedByUserId: item.userId!, - status: UserNotificationExecutionStatus.Completed, - statusMessage: - 'Incident acknowledged by ' + - user.name + - ' (' + - user.email + - ')', - }, - props: { - isRoot: true, - }, - }); - - // and then oncall log. - - await OnCallDutyPolicyExecutionLogService.updateOneById({ - id: item.onCallDutyPolicyExecutionLogId!, - data: { - acknowledgedAt: onUpdate.updateBy.data.acknowledgedAt, - acknowledgedByUserId: item.userId!, - status: OnCallDutyPolicyStatus.Completed, - statusMessage: - 'Incident acknowledged by ' + - user.name + - ' (' + - user.email + - ')', - }, - props: { - isRoot: true, - }, - }); - - // and then oncall log timeline. - await OnCallDutyPolicyExecutionLogTimelineService.updateOneById( - { - id: item.onCallDutyPolicyExecutionLogTimelineId!, - data: { - acknowledgedAt: - onUpdate.updateBy.data.acknowledgedAt, - isAcknowledged: true, - status: OnCallDutyExecutionLogTimelineStatus.SuccessfullyAcknowledged, - statusMessage: - 'Incident acknowledged by ' + - user.name + - ' (' + - user.email + - ')', - }, - props: { - isRoot: true, - }, - } - ); - - // incident. - await IncidentService.acknowledgeIncident( - item.triggeredByIncidentId!, - item.userId! - ); - } + if (!user) { + throw new BadDataException("User not found."); } - return onUpdate; + await UserOnCallLogService.updateOneById({ + id: item.userNotificationLogId!, + data: { + acknowledgedAt: onUpdate.updateBy.data.acknowledgedAt, + acknowledgedByUserId: item.userId!, + status: UserNotificationExecutionStatus.Completed, + statusMessage: + "Incident acknowledged by " + user.name + " (" + user.email + ")", + }, + props: { + isRoot: true, + }, + }); + + // and then oncall log. + + await OnCallDutyPolicyExecutionLogService.updateOneById({ + id: item.onCallDutyPolicyExecutionLogId!, + data: { + acknowledgedAt: onUpdate.updateBy.data.acknowledgedAt, + acknowledgedByUserId: item.userId!, + status: OnCallDutyPolicyStatus.Completed, + statusMessage: + "Incident acknowledged by " + user.name + " (" + user.email + ")", + }, + props: { + isRoot: true, + }, + }); + + // and then oncall log timeline. + await OnCallDutyPolicyExecutionLogTimelineService.updateOneById({ + id: item.onCallDutyPolicyExecutionLogTimelineId!, + data: { + acknowledgedAt: onUpdate.updateBy.data.acknowledgedAt, + isAcknowledged: true, + status: + OnCallDutyExecutionLogTimelineStatus.SuccessfullyAcknowledged, + statusMessage: + "Incident acknowledged by " + user.name + " (" + user.email + ")", + }, + props: { + isRoot: true, + }, + }); + + // incident. + await IncidentService.acknowledgeIncident( + item.triggeredByIncidentId!, + item.userId!, + ); + } } + + return onUpdate; + } } export default new Service(); diff --git a/CommonServer/Services/UserService.ts b/CommonServer/Services/UserService.ts index a7f1072a7c..6722de4c49 100755 --- a/CommonServer/Services/UserService.ts +++ b/CommonServer/Services/UserService.ts @@ -1,259 +1,251 @@ -import DatabaseConfig from '../DatabaseConfig'; -import { IsBillingEnabled } from '../EnvironmentConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import { OnUpdate } from '../Types/Database/Hooks'; -import UpdateBy from '../Types/Database/UpdateBy'; -import logger from '../Utils/Logger'; -import DatabaseService from './DatabaseService'; -import EmailVerificationTokenService from './EmailVerificationTokenService'; -import MailService from './MailService'; -import TeamMemberService from './TeamMemberService'; -import UserNotificationRuleService from './UserNotificationRuleService'; -import UserNotificationSettingService from './UserNotificationSettingService'; -import { AccountsRoute } from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Email from 'Common/Types/Email'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import HashedString from 'Common/Types/HashedString'; -import ObjectID from 'Common/Types/ObjectID'; -import Text from 'Common/Types/Text'; -import EmailVerificationToken from 'Model/Models/EmailVerificationToken'; -import TeamMember from 'Model/Models/TeamMember'; -import Model from 'Model/Models/User'; +import DatabaseConfig from "../DatabaseConfig"; +import { IsBillingEnabled } from "../EnvironmentConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import { OnUpdate } from "../Types/Database/Hooks"; +import UpdateBy from "../Types/Database/UpdateBy"; +import logger from "../Utils/Logger"; +import DatabaseService from "./DatabaseService"; +import EmailVerificationTokenService from "./EmailVerificationTokenService"; +import MailService from "./MailService"; +import TeamMemberService from "./TeamMemberService"; +import UserNotificationRuleService from "./UserNotificationRuleService"; +import UserNotificationSettingService from "./UserNotificationSettingService"; +import { AccountsRoute } from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Email from "Common/Types/Email"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import HashedString from "Common/Types/HashedString"; +import ObjectID from "Common/Types/ObjectID"; +import Text from "Common/Types/Text"; +import EmailVerificationToken from "Model/Models/EmailVerificationToken"; +import TeamMember from "Model/Models/TeamMember"; +import Model from "Model/Models/User"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + public async findByEmail( + email: Email, + props: DatabaseCommonInteractionProps, + ): Promise<Model | null> { + return await this.findOneBy({ + query: { + email: email, + }, + select: { + _id: true, + }, + props: props, + }); + } + + protected override async onBeforeUpdate( + updateBy: UpdateBy<Model>, + ): Promise<OnUpdate<Model>> { + if (updateBy.data.password || updateBy.data.email) { + const users: Array<Model> = await this.findBy({ + query: updateBy.query, + select: { + _id: true, + email: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); + + return { updateBy, carryForward: users }; + } + return { updateBy, carryForward: [] }; + } + + protected override async onUpdateSuccess( + onUpdate: OnUpdate<Model>, + _updatedItemIds: ObjectID[], + ): Promise<OnUpdate<Model>> { + if (onUpdate && onUpdate.updateBy.data.password) { + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + for (const user of onUpdate.carryForward) { + // password changed, send password changed mail + MailService.sendMail({ + toEmail: user.email!, + subject: "Password Changed.", + templateType: EmailTemplateType.PasswordChanged, + vars: { + homeURL: new URL(httpProtocol, host).toString(), + }, + }).catch((err: Error) => { + logger.error(err); + }); + } } - public async findByEmail( - email: Email, - props: DatabaseCommonInteractionProps - ): Promise<Model | null> { - return await this.findOneBy({ + if (onUpdate && onUpdate.updateBy.data.isEmailVerified) { + // if the email is verified then create default policies for this user. + + const newUsers: Array<Model> = await this.findBy({ + query: onUpdate.updateBy.query, + select: { + _id: true, + email: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); + + for (const user of newUsers) { + // emai is verified. create default policies for this user. + const teamMembers: Array<TeamMember> = await TeamMemberService.findBy({ + query: { + userId: user.id!, + hasAcceptedInvitation: true, + }, + select: { + projectId: true, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); + + for (const member of teamMembers) { + // create default policies for this user. + await UserNotificationRuleService.addDefaultNotificationRuleForUser( + member.projectId!, + user.id!, + user.email!, + ); + + await UserNotificationSettingService.addDefaultNotificationSettingsForUser( + user.id!, + member.projectId!, + ); + } + } + } + + if (onUpdate && onUpdate.updateBy.data.email) { + const newUsers: Array<Model> = await this.findBy({ + query: onUpdate.updateBy.query, + select: { + _id: true, + email: true, + name: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); + + for (const user of onUpdate.carryForward) { + const newUser: Model | undefined = newUsers.find((u: Model) => { + return u._id?.toString() === user._id.toString(); + }); + + if (newUser && newUser.email?.toString() !== user.email.toString()) { + // password changed, send password changed mail + const generatedToken: ObjectID = ObjectID.generate(); + + const emailVerificationToken: EmailVerificationToken = + new EmailVerificationToken(); + emailVerificationToken.userId = user.id; + emailVerificationToken.email = newUser.email!; + emailVerificationToken.token = generatedToken; + emailVerificationToken.expires = OneUptimeDate.getOneDayAfter(); + + await EmailVerificationTokenService.create({ + data: emailVerificationToken, + props: { + isRoot: true, + }, + }); + + const host: Hostname = await DatabaseConfig.getHost(); + const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol(); + + MailService.sendMail({ + toEmail: newUser.email!, + subject: "You have changed your email. Please verify your email.", + templateType: EmailTemplateType.EmailChanged, + vars: { + name: newUser.name!.toString(), + tokenVerifyUrl: new URL( + httpProtocol, + host, + new Route(AccountsRoute.toString()).addRoute( + "/verify-email/" + generatedToken.toString(), + ), + ).toString(), + homeUrl: new URL(httpProtocol, host).toString(), + }, + }).catch((err: Error) => { + logger.error(err); + }); + + await this.updateOneBy({ query: { - email: email, + _id: user.id.toString(), }, - select: { - _id: true, + data: { + isEmailVerified: false, }, - props: props, - }); + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + } + } } - protected override async onBeforeUpdate( - updateBy: UpdateBy<Model> - ): Promise<OnUpdate<Model>> { - if (updateBy.data.password || updateBy.data.email) { - const users: Array<Model> = await this.findBy({ - query: updateBy.query, - select: { - _id: true, - email: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); + return onUpdate; + } - return { updateBy, carryForward: users }; - } - return { updateBy, carryForward: [] }; + public async createByEmail(data: { + email: Email; + isEmailVerified?: boolean; + generateRandomPassword?: boolean; + props: DatabaseCommonInteractionProps; + }): Promise<Model> { + const { email, props } = data; + + const user: Model = new Model(); + user.email = email; + user.isEmailVerified = data.isEmailVerified || false; + + if (data.generateRandomPassword) { + user.password = new HashedString(Text.generateRandomText(20)); } - protected override async onUpdateSuccess( - onUpdate: OnUpdate<Model>, - _updatedItemIds: ObjectID[] - ): Promise<OnUpdate<Model>> { - if (onUpdate && onUpdate.updateBy.data.password) { - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); - - for (const user of onUpdate.carryForward) { - // password changed, send password changed mail - MailService.sendMail({ - toEmail: user.email!, - subject: 'Password Changed.', - templateType: EmailTemplateType.PasswordChanged, - vars: { - homeURL: new URL(httpProtocol, host).toString(), - }, - }).catch((err: Error) => { - logger.error(err); - }); - } - } - - if (onUpdate && onUpdate.updateBy.data.isEmailVerified) { - // if the email is verified then create default policies for this user. - - const newUsers: Array<Model> = await this.findBy({ - query: onUpdate.updateBy.query, - select: { - _id: true, - email: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); - - for (const user of newUsers) { - // emai is verified. create default policies for this user. - const teamMembers: Array<TeamMember> = - await TeamMemberService.findBy({ - query: { - userId: user.id!, - hasAcceptedInvitation: true, - }, - select: { - projectId: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const member of teamMembers) { - // create default policies for this user. - await UserNotificationRuleService.addDefaultNotificationRuleForUser( - member.projectId!, - user.id!, - user.email! - ); - - await UserNotificationSettingService.addDefaultNotificationSettingsForUser( - user.id!, - member.projectId! - ); - } - } - } - - if (onUpdate && onUpdate.updateBy.data.email) { - const newUsers: Array<Model> = await this.findBy({ - query: onUpdate.updateBy.query, - select: { - _id: true, - email: true, - name: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); - - for (const user of onUpdate.carryForward) { - const newUser: Model | undefined = newUsers.find((u: Model) => { - return u._id?.toString() === user._id.toString(); - }); - - if ( - newUser && - newUser.email?.toString() !== user.email.toString() - ) { - // password changed, send password changed mail - const generatedToken: ObjectID = ObjectID.generate(); - - const emailVerificationToken: EmailVerificationToken = - new EmailVerificationToken(); - emailVerificationToken.userId = user.id; - emailVerificationToken.email = newUser.email!; - emailVerificationToken.token = generatedToken; - emailVerificationToken.expires = - OneUptimeDate.getOneDayAfter(); - - await EmailVerificationTokenService.create({ - data: emailVerificationToken, - props: { - isRoot: true, - }, - }); - - const host: Hostname = await DatabaseConfig.getHost(); - const httpProtocol: Protocol = - await DatabaseConfig.getHttpProtocol(); - - MailService.sendMail({ - toEmail: newUser.email!, - subject: - 'You have changed your email. Please verify your email.', - templateType: EmailTemplateType.EmailChanged, - vars: { - name: newUser.name!.toString(), - tokenVerifyUrl: new URL( - httpProtocol, - host, - new Route(AccountsRoute.toString()).addRoute( - '/verify-email/' + generatedToken.toString() - ) - ).toString(), - homeUrl: new URL(httpProtocol, host).toString(), - }, - }).catch((err: Error) => { - logger.error(err); - }); - - await this.updateOneBy({ - query: { - _id: user.id.toString(), - }, - data: { - isEmailVerified: false, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - } - } - } - - return onUpdate; + if (!IsBillingEnabled) { + // if billing is not enabled, then email is verified by default. + user.isEmailVerified = true; } - public async createByEmail(data: { - email: Email; - isEmailVerified?: boolean; - generateRandomPassword?: boolean; - props: DatabaseCommonInteractionProps; - }): Promise<Model> { - const { email, props } = data; - - const user: Model = new Model(); - user.email = email; - user.isEmailVerified = data.isEmailVerified || false; - - if (data.generateRandomPassword) { - user.password = new HashedString(Text.generateRandomText(20)); - } - - if (!IsBillingEnabled) { - // if billing is not enabled, then email is verified by default. - user.isEmailVerified = true; - } - - return await this.create({ - data: user, - props: props, - }); - } + return await this.create({ + data: user, + props: props, + }); + } } export default new Service(); diff --git a/CommonServer/Services/UserSmsService.ts b/CommonServer/Services/UserSmsService.ts index 3af18be75d..e713e1bc4d 100644 --- a/CommonServer/Services/UserSmsService.ts +++ b/CommonServer/Services/UserSmsService.ts @@ -1,170 +1,170 @@ -import { IsBillingEnabled } from '../EnvironmentConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import CreateBy from '../Types/Database/CreateBy'; -import DeleteBy from '../Types/Database/DeleteBy'; -import { OnCreate, OnDelete } from '../Types/Database/Hooks'; -import logger from '../Utils/Logger'; -import DatabaseService from './DatabaseService'; -import ProjectService from './ProjectService'; -import SmsService from './SmsService'; -import UserNotificationRuleService from './UserNotificationRuleService'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Text from 'Common/Types/Text'; -import Project from 'Model/Models/Project'; -import Model from 'Model/Models/UserSMS'; +import { IsBillingEnabled } from "../EnvironmentConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import CreateBy from "../Types/Database/CreateBy"; +import DeleteBy from "../Types/Database/DeleteBy"; +import { OnCreate, OnDelete } from "../Types/Database/Hooks"; +import logger from "../Utils/Logger"; +import DatabaseService from "./DatabaseService"; +import ProjectService from "./ProjectService"; +import SmsService from "./SmsService"; +import UserNotificationRuleService from "./UserNotificationRuleService"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Text from "Common/Types/Text"; +import Project from "Model/Models/Project"; +import Model from "Model/Models/UserSMS"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } + + protected override async onBeforeDelete( + deleteBy: DeleteBy<Model>, + ): Promise<OnDelete<Model>> { + const itemsToDelete: Array<Model> = await this.findBy({ + query: deleteBy.query, + select: { + _id: true, + projectId: true, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); + + for (const item of itemsToDelete) { + await UserNotificationRuleService.deleteBy({ + query: { + userSmsId: item.id!, + projectId: item.projectId!, + }, + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, + }, + }); } - protected override async onBeforeDelete( - deleteBy: DeleteBy<Model> - ): Promise<OnDelete<Model>> { - const itemsToDelete: Array<Model> = await this.findBy({ - query: deleteBy.query, - select: { - _id: true, - projectId: true, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); + return { + deleteBy, + carryForward: null, + }; + } - for (const item of itemsToDelete) { - await UserNotificationRuleService.deleteBy({ - query: { - userSmsId: item.id!, - projectId: item.projectId!, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - } + protected override async onBeforeCreate( + createBy: CreateBy<Model>, + ): Promise<OnCreate<Model>> { + // check if this project has SMS and Call mEnabled. - return { - deleteBy, - carryForward: null, - }; + if (!createBy.props.isRoot && createBy.data.isVerified) { + throw new BadDataException("isVerified cannot be set to true"); } - protected override async onBeforeCreate( - createBy: CreateBy<Model> - ): Promise<OnCreate<Model>> { - // check if this project has SMS and Call mEnabled. + const project: Project | null = await ProjectService.findOneById({ + id: createBy.data.projectId!, + props: { + isRoot: true, + }, + select: { + enableSmsNotifications: true, + smsOrCallCurrentBalanceInUSDCents: true, + }, + }); - if (!createBy.props.isRoot && createBy.data.isVerified) { - throw new BadDataException('isVerified cannot be set to true'); - } - - const project: Project | null = await ProjectService.findOneById({ - id: createBy.data.projectId!, - props: { - isRoot: true, - }, - select: { - enableSmsNotifications: true, - smsOrCallCurrentBalanceInUSDCents: true, - }, - }); - - if (!project) { - throw new BadDataException('Project not found'); - } - - if (!project.enableSmsNotifications) { - throw new BadDataException( - 'SMS notifications are disabled for this project. Please enable them in Project Settings > Notification Settings.' - ); - } - - if ( - (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 && - IsBillingEnabled - ) { - throw new BadDataException( - 'Your SMS balance is low. Please recharge your SMS balance in Project Settings > Notification Settings.' - ); - } - - return { carryForward: null, createBy }; + if (!project) { + throw new BadDataException("Project not found"); } - protected override async onCreateSuccess( - _onCreate: OnCreate<Model>, - createdItem: Model - ): Promise<Model> { - if (!createdItem.isVerified) { - this.sendVerificationCode(createdItem); - } - - return createdItem; + if (!project.enableSmsNotifications) { + throw new BadDataException( + "SMS notifications are disabled for this project. Please enable them in Project Settings > Notification Settings.", + ); } - public async resendVerificationCode(itemId: ObjectID): Promise<void> { - const item: Model | null = await this.findOneById({ - id: itemId, - props: { - isRoot: true, - }, - select: { - phone: true, - verificationCode: true, - isVerified: true, - projectId: true, - }, - }); - - if (!item) { - throw new BadDataException( - 'Item with ID ' + itemId.toString() + ' not found' - ); - } - - if (item.isVerified) { - throw new BadDataException('Phone Number already verified'); - } - - // generate new verification code - item.verificationCode = Text.generateRandomNumber(6); - - await this.updateOneById({ - id: item.id!, - props: { - isRoot: true, - }, - data: { - verificationCode: item.verificationCode, - }, - }); - - this.sendVerificationCode(item); + if ( + (project.smsOrCallCurrentBalanceInUSDCents as number) <= 100 && + IsBillingEnabled + ) { + throw new BadDataException( + "Your SMS balance is low. Please recharge your SMS balance in Project Settings > Notification Settings.", + ); } - public sendVerificationCode(item: Model): void { - // send verification sms. - SmsService.sendSms( - { - to: item.phone!, - message: - 'This message is from OneUptime. Your verification code is ' + - item.verificationCode, - }, - { - projectId: item.projectId, - isSensitive: true, - } - ).catch((err: Error) => { - logger.error(err); - }); + return { carryForward: null, createBy }; + } + + protected override async onCreateSuccess( + _onCreate: OnCreate<Model>, + createdItem: Model, + ): Promise<Model> { + if (!createdItem.isVerified) { + this.sendVerificationCode(createdItem); } + + return createdItem; + } + + public async resendVerificationCode(itemId: ObjectID): Promise<void> { + const item: Model | null = await this.findOneById({ + id: itemId, + props: { + isRoot: true, + }, + select: { + phone: true, + verificationCode: true, + isVerified: true, + projectId: true, + }, + }); + + if (!item) { + throw new BadDataException( + "Item with ID " + itemId.toString() + " not found", + ); + } + + if (item.isVerified) { + throw new BadDataException("Phone Number already verified"); + } + + // generate new verification code + item.verificationCode = Text.generateRandomNumber(6); + + await this.updateOneById({ + id: item.id!, + props: { + isRoot: true, + }, + data: { + verificationCode: item.verificationCode, + }, + }); + + this.sendVerificationCode(item); + } + + public sendVerificationCode(item: Model): void { + // send verification sms. + SmsService.sendSms( + { + to: item.phone!, + message: + "This message is from OneUptime. Your verification code is " + + item.verificationCode, + }, + { + projectId: item.projectId, + isSensitive: true, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } } export default new Service(); diff --git a/CommonServer/Services/WorkflowLogService.ts b/CommonServer/Services/WorkflowLogService.ts index 60ba79e2ce..35e8c94d3b 100644 --- a/CommonServer/Services/WorkflowLogService.ts +++ b/CommonServer/Services/WorkflowLogService.ts @@ -1,11 +1,11 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/WorkflowLog'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/WorkflowLog"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - this.hardDeleteItemsOlderThanInDays('createdAt', 3); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + this.hardDeleteItemsOlderThanInDays("createdAt", 3); + } } export default new Service(); diff --git a/CommonServer/Services/WorkflowService.ts b/CommonServer/Services/WorkflowService.ts index 22b9e1e95a..39384caf9d 100644 --- a/CommonServer/Services/WorkflowService.ts +++ b/CommonServer/Services/WorkflowService.ts @@ -1,84 +1,82 @@ -import { AppApiHostname } from '../EnvironmentConfig'; -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization'; -import { OnUpdate } from '../Types/Database/Hooks'; -import DatabaseService from './DatabaseService'; -import EmptyResponseData from 'Common/Types/API/EmptyResponse'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; +import { AppApiHostname } from "../EnvironmentConfig"; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import ClusterKeyAuthorization from "../Middleware/ClusterKeyAuthorization"; +import { OnUpdate } from "../Types/Database/Hooks"; +import DatabaseService from "./DatabaseService"; +import EmptyResponseData from "Common/Types/API/EmptyResponse"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; import { - ComponentType, - NodeDataProp, - NodeType, -} from 'Common/Types/Workflow/Component'; -import API from 'Common/Utils/API'; -import Model from 'Model/Models/Workflow'; + ComponentType, + NodeDataProp, + NodeType, +} from "Common/Types/Workflow/Component"; +import API from "Common/Utils/API"; +import Model from "Model/Models/Workflow"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } - protected override async onUpdateSuccess( - onUpdate: OnUpdate<Model>, - _updatedItemIds: ObjectID[] - ): Promise<OnUpdate<Model>> { - /// save trigger and trigger args. + protected override async onUpdateSuccess( + onUpdate: OnUpdate<Model>, + _updatedItemIds: ObjectID[], + ): Promise<OnUpdate<Model>> { + /// save trigger and trigger args. + if ( + onUpdate.updateBy.data && + (onUpdate.updateBy.data as any).graph && + (((onUpdate.updateBy.data as any).graph as any)[ + "nodes" + ] as Array<JSONObject>) + ) { + let trigger: NodeDataProp | null = null; + + // check if it has a trigger node. + for (const node of ((onUpdate.updateBy.data as any).graph as any)[ + "nodes" + ] as Array<JSONObject>) { + const nodeData: NodeDataProp = node["data"] as any; if ( - onUpdate.updateBy.data && - (onUpdate.updateBy.data as any).graph && - (((onUpdate.updateBy.data as any).graph as any)[ - 'nodes' - ] as Array<JSONObject>) + nodeData.componentType === ComponentType.Trigger && + nodeData.nodeType === NodeType.Node ) { - let trigger: NodeDataProp | null = null; - - // check if it has a trigger node. - for (const node of ((onUpdate.updateBy.data as any).graph as any)[ - 'nodes' - ] as Array<JSONObject>) { - const nodeData: NodeDataProp = node['data'] as any; - if ( - nodeData.componentType === ComponentType.Trigger && - nodeData.nodeType === NodeType.Node - ) { - // found the trigger; - trigger = nodeData; - } - } - - await this.updateOneById({ - id: new ObjectID(onUpdate.updateBy.query._id! as any), - data: { - triggerId: trigger?.metadataId || null, - triggerArguments: trigger?.arguments || {}, - } as any, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); + // found the trigger; + trigger = nodeData; } + } - await API.post<EmptyResponseData>( - new URL( - Protocol.HTTP, - AppApiHostname, - new Route( - '/api/workflow/update/' + onUpdate.updateBy.query._id! - ) - ), - {}, - { - ...ClusterKeyAuthorization.getClusterKeyHeaders(), - } - ); - - return onUpdate; + await this.updateOneById({ + id: new ObjectID(onUpdate.updateBy.query._id! as any), + data: { + triggerId: trigger?.metadataId || null, + triggerArguments: trigger?.arguments || {}, + } as any, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); } + + await API.post<EmptyResponseData>( + new URL( + Protocol.HTTP, + AppApiHostname, + new Route("/api/workflow/update/" + onUpdate.updateBy.query._id!), + ), + {}, + { + ...ClusterKeyAuthorization.getClusterKeyHeaders(), + }, + ); + + return onUpdate; + } } export default new Service(); diff --git a/CommonServer/Services/WorkflowVariableService.ts b/CommonServer/Services/WorkflowVariableService.ts index 3d5d593558..9ab8fc8495 100644 --- a/CommonServer/Services/WorkflowVariableService.ts +++ b/CommonServer/Services/WorkflowVariableService.ts @@ -1,10 +1,10 @@ -import PostgresDatabase from '../Infrastructure/PostgresDatabase'; -import DatabaseService from './DatabaseService'; -import Model from 'Model/Models/WorkflowVariable'; +import PostgresDatabase from "../Infrastructure/PostgresDatabase"; +import DatabaseService from "./DatabaseService"; +import Model from "Model/Models/WorkflowVariable"; export class Service extends DatabaseService<Model> { - public constructor(postgresDatabase?: PostgresDatabase) { - super(Model, postgresDatabase); - } + public constructor(postgresDatabase?: PostgresDatabase) { + super(Model, postgresDatabase); + } } export default new Service(); diff --git a/CommonServer/Tests/API/BaseAPI.test.ts b/CommonServer/Tests/API/BaseAPI.test.ts index 732616e2ae..7984706c06 100644 --- a/CommonServer/Tests/API/BaseAPI.test.ts +++ b/CommonServer/Tests/API/BaseAPI.test.ts @@ -1,128 +1,128 @@ /* eslint-disable no-loop-func */ -import BaseAPI from '../../API/BaseAPI'; -import CommonAPI from '../../API/CommonAPI'; -import UserMiddleware from '../../Middleware/UserAuthorization'; -import DatabaseService from '../../Services/DatabaseService'; -import ProjectService from '../../Services/ProjectService'; +import BaseAPI from "../../API/BaseAPI"; +import CommonAPI from "../../API/CommonAPI"; +import UserMiddleware from "../../Middleware/UserAuthorization"; +import DatabaseService from "../../Services/DatabaseService"; +import ProjectService from "../../Services/ProjectService"; import Express, { - ExpressResponse, - ExpressRouter, - NextFunction, - OneUptimeRequest, -} from '../../Utils/Express'; -import Response from '../../Utils/Response'; -import { mockRouter } from './Helpers'; -import { describe, expect, it } from '@jest/globals'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import BadRequestException from 'Common/Types/Exception/BadRequestException'; -import GenericObject from 'Common/Types/GenericObject'; -import ObjectID from 'Common/Types/ObjectID'; -import { UserPermission } from 'Common/Types/Permission'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import UserType from 'Common/Types/UserType'; + ExpressResponse, + ExpressRouter, + NextFunction, + OneUptimeRequest, +} from "../../Utils/Express"; +import Response from "../../Utils/Response"; +import { mockRouter } from "./Helpers"; +import { describe, expect, it } from "@jest/globals"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import BadRequestException from "Common/Types/Exception/BadRequestException"; +import GenericObject from "Common/Types/GenericObject"; +import ObjectID from "Common/Types/ObjectID"; +import { UserPermission } from "Common/Types/Permission"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import UserType from "Common/Types/UserType"; -jest.mock('../../Utils/Express', () => { +jest.mock("../../Utils/Express", () => { + return { + getRouter: () => { + return mockRouter; + }, + }; +}); + +jest.mock("../../Utils/Response", () => { + return { + sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + sendEmptySuccessResponse: jest.fn(), + sendEntityResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + }; +}); + +jest.mock("../../Services/DatabaseService", () => { + return jest.fn().mockImplementation(() => { return { - getRouter: () => { - return mockRouter; - }, + countBy: () => { + return new PositiveNumber(42); + }, + findBy: () => { + return [{ id: "mock" }]; + }, + findOneById: jest.fn().mockImplementation(() => { + return { id: "mock" }; + }), + deleteOneBy: jest.fn(), + deleteOneById: jest.fn(), + updateOneById: jest.fn(), + updateOneBy: jest.fn(), + create: jest.fn().mockImplementation((...args: []) => { + return args; + }), + hardDeleteItemsOlderThanInDays: jest.fn(), }; + }); }); -jest.mock('../../Utils/Response', () => { +jest.mock("Common/Models/BaseModel", () => { + return jest.fn().mockImplementation((initObject: GenericObject) => { return { - sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - sendEmptySuccessResponse: jest.fn(), - sendEntityResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), + ...initObject, + getCrudApiPath: jest.fn().mockImplementation(() => { + return "/mock"; + }), + getTableColumnMetadata: jest.fn().mockImplementation(() => { + return null; + }), }; + }); }); -jest.mock('../../Services/DatabaseService', () => { - return jest.fn().mockImplementation(() => { - return { - countBy: () => { - return new PositiveNumber(42); - }, - findBy: () => { - return [{ id: 'mock' }]; - }, - findOneById: jest.fn().mockImplementation(() => { - return { id: 'mock' }; - }), - deleteOneBy: jest.fn(), - deleteOneById: jest.fn(), - updateOneById: jest.fn(), - updateOneBy: jest.fn(), - create: jest.fn().mockImplementation((...args: []) => { - return args; - }), - hardDeleteItemsOlderThanInDays: jest.fn(), - }; - }); +jest.mock("../../Services/ProjectService", () => { + return { + getCurrentPlan: () => { + return { + currentPlan: "Free", + isSubscriptionUnpaid: false, + }; + }, + }; }); -jest.mock('Common/Models/BaseModel', () => { - return jest.fn().mockImplementation((initObject: GenericObject) => { - return { - ...initObject, - getCrudApiPath: jest.fn().mockImplementation(() => { - return '/mock'; - }), - getTableColumnMetadata: jest.fn().mockImplementation(() => { - return null; - }), - }; - }); -}); - -jest.mock('../../Services/ProjectService', () => { - return { - getCurrentPlan: () => { - return { - currentPlan: 'Free', - isSubscriptionUnpaid: false, - }; - }, - }; -}); - -jest.mock('../../EnvironmentConfig', () => { - return { - IsBillingEnabled: true, - }; +jest.mock("../../EnvironmentConfig", () => { + return { + IsBillingEnabled: true, + }; }); // eslint-disable-next-line @typescript-eslint/typedef const res = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - send: jest.fn().mockReturnThis(), + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + send: jest.fn().mockReturnThis(), } as any as ExpressResponse; const deleteRequest: OneUptimeRequest = { - params: { id: 'delete-me' }, - headers: {}, + params: { id: "delete-me" }, + headers: {}, } as unknown as OneUptimeRequest; const countRequest: OneUptimeRequest = { - body: { query: { id: 'count-me' } }, - headers: {}, + body: { query: { id: "count-me" } }, + headers: {}, } as unknown as OneUptimeRequest; const getRequest: OneUptimeRequest = { - params: { id: 'get-me' }, - headers: {}, + params: { id: "get-me" }, + headers: {}, } as unknown as OneUptimeRequest; const next: NextFunction = jest.fn(); @@ -130,703 +130,674 @@ const next: NextFunction = jest.fn(); const TestService: DatabaseService<BaseModel> = new DatabaseService(BaseModel); let emptyDatabaseCommonInteractionProps: DatabaseCommonInteractionProps = {}; -describe('BaseAPI', () => { - let baseApiInstance: BaseAPI<BaseModel, DatabaseService<BaseModel>>; - let emptyRequest: OneUptimeRequest = { - query: {}, - headers: {}, +describe("BaseAPI", () => { + let baseApiInstance: BaseAPI<BaseModel, DatabaseService<BaseModel>>; + let emptyRequest: OneUptimeRequest = { + query: {}, + headers: {}, + } as OneUptimeRequest; + + beforeAll(async () => { + mockRouter.post.mockClear(); + mockRouter.get.mockClear(); + mockRouter.put.mockClear(); + mockRouter.delete.mockClear(); + + baseApiInstance = new BaseAPI(BaseModel, TestService); + emptyDatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(emptyRequest); + }); + + afterEach(() => { + jest.clearAllMocks(); + + emptyRequest = { + query: {}, + headers: {}, } as OneUptimeRequest; + }); - beforeAll(async () => { - mockRouter.post.mockClear(); - mockRouter.get.mockClear(); - mockRouter.put.mockClear(); - mockRouter.delete.mockClear(); + describe("constructor", () => { + it("should construct and set up routes", () => { + expect(Express.getRouter().post).toHaveBeenCalledWith( + "/mock", + UserMiddleware.getUserMiddleware, + expect.any(Function), + ); - baseApiInstance = new BaseAPI(BaseModel, TestService); - emptyDatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(emptyRequest); + expect(Express.getRouter().post).toHaveBeenCalledWith( + "/mock/get-list", + UserMiddleware.getUserMiddleware, + expect.any(Function), + ); + + expect(Express.getRouter().get).toHaveBeenCalledWith( + "/mock/get-list", + UserMiddleware.getUserMiddleware, + expect.any(Function), + ); + + expect(Express.getRouter().post).toHaveBeenCalledWith( + "/mock/count", + UserMiddleware.getUserMiddleware, + expect.any(Function), + ); + + expect(Express.getRouter().post).toHaveBeenCalledWith( + "/mock/:id/get-item", + UserMiddleware.getUserMiddleware, + expect.any(Function), + ); + + expect(Express.getRouter().put).toHaveBeenCalledWith( + "/mock/:id", + UserMiddleware.getUserMiddleware, + expect.any(Function), + ); + + expect(Express.getRouter().delete).toHaveBeenCalledWith( + "/mock/:id", + UserMiddleware.getUserMiddleware, + expect.any(Function), + ); }); + }); - afterEach(() => { - jest.clearAllMocks(); + describe("routes", () => { + // eslint-disable-next-line @typescript-eslint/typedef + const checkRoutes = [ + ["POST", "/mock", "createItem"], + ["POST", "/mock/get-list", "getList"], + ["GET", "/mock/get-list", "getList"], + ["POST", "/mock/count", "count"], + ["POST", "/mock/:id/get-item", "getItem"], + ["GET", "/mock/:id/get-item", "getItem"], + ["PUT", "/mock/:id", "updateItem"], + ["DELETE", "/mock/:id", "deleteItem"], + ] as [string, string, string][]; - emptyRequest = { - query: {}, - headers: {}, - } as OneUptimeRequest; - }); - - describe('constructor', () => { - it('should construct and set up routes', () => { - expect(Express.getRouter().post).toHaveBeenCalledWith( - '/mock', - UserMiddleware.getUserMiddleware, - expect.any(Function) - ); - - expect(Express.getRouter().post).toHaveBeenCalledWith( - '/mock/get-list', - UserMiddleware.getUserMiddleware, - expect.any(Function) - ); - - expect(Express.getRouter().get).toHaveBeenCalledWith( - '/mock/get-list', - UserMiddleware.getUserMiddleware, - expect.any(Function) - ); - - expect(Express.getRouter().post).toHaveBeenCalledWith( - '/mock/count', - UserMiddleware.getUserMiddleware, - expect.any(Function) - ); - - expect(Express.getRouter().post).toHaveBeenCalledWith( - '/mock/:id/get-item', - UserMiddleware.getUserMiddleware, - expect.any(Function) - ); - - expect(Express.getRouter().put).toHaveBeenCalledWith( - '/mock/:id', - UserMiddleware.getUserMiddleware, - expect.any(Function) - ); - - expect(Express.getRouter().delete).toHaveBeenCalledWith( - '/mock/:id', - UserMiddleware.getUserMiddleware, - expect.any(Function) - ); + for (const [method, uri, shouldBeCalled] of checkRoutes) { + describe(`${method} ${uri}`, () => { + it(`should call ${shouldBeCalled}`, async () => { + const spy: jest.SpyInstance = jest.spyOn( + baseApiInstance as any, + shouldBeCalled as any, + ); + await mockRouter + .match(method, uri) + .handlerFunction(emptyRequest, res, next); + expect(spy).toHaveBeenCalledWith(emptyRequest, res); }); + + it("should call next(err) on exception", async () => { + const error: Error = new Error("Mocked Error"); + jest + .spyOn(baseApiInstance as any, shouldBeCalled as any) + .mockImplementationOnce(() => { + throw error; + }); + const next: jest.Mock = jest.fn(); + await mockRouter + .match(method, uri) + .handlerFunction(emptyRequest, res, next); + expect(next).toHaveBeenCalledWith(error); + }); + }); + } + }); + + describe("BaseAPI.getPermissionsForTenant", () => { + it("should return empty permissions if userTenantAccessPermission is not set", async () => { + jest + .spyOn(CommonAPI, "getDatabaseCommonInteractionProps") + .mockResolvedValueOnce({}); + const permissions: UserPermission[] = + await baseApiInstance.getPermissionsForTenant(emptyRequest); + expect(permissions).toEqual([]); }); - describe('routes', () => { + it("should return permissions if userTenantAccessPermission is set and tenantId is available", async () => { + // eslint-disable-next-line @typescript-eslint/typedef + const mockPermissions = [{ permission: "granted" }]; + jest + .spyOn(CommonAPI, "getDatabaseCommonInteractionProps") + .mockResolvedValueOnce({ + userTenantAccessPermission: { + tenantId: { permissions: mockPermissions }, + }, + tenantId: "tenantId", + } as any); + + const permissions: UserPermission[] = + await baseApiInstance.getPermissionsForTenant(emptyRequest); + expect(permissions).toEqual(mockPermissions); + }); + + it("should return empty permissions if tenantId is not available", async () => { + jest + .spyOn(CommonAPI, "getDatabaseCommonInteractionProps") + .mockResolvedValueOnce({ + userTenantAccessPermission: { + tenantId: { permissions: [{ doesnt: "matter" }] }, + }, + tenantId: null, + } as any); + + const permissions: UserPermission[] = + await baseApiInstance.getPermissionsForTenant(emptyRequest); + expect(permissions).toEqual([]); + }); + }); + + describe("getTenantId", () => { + it("should return null if there is no tennatId", () => { + expect(baseApiInstance.getTenantId(emptyRequest)).toEqual(null); + }); + + it("should return ObjectID if tennantId is passed", () => { + const tenantId: ObjectID = new ObjectID("123"); + const tenantRequest: OneUptimeRequest = { + tenantId, + } as OneUptimeRequest; + expect(baseApiInstance.getTenantId(tenantRequest)).toEqual(tenantId); + }); + }); + + describe("getDatabaseCommonInteractionProps", () => { + let request: OneUptimeRequest; + + beforeEach(() => { + request = { + userType: undefined, + userAuthorization: undefined, + userGlobalAccessPermission: undefined, + userTenantAccessPermission: undefined, + tenantId: undefined, + headers: {}, + } as unknown as OneUptimeRequest; + }); + + it("should initialize props with undefined values", async () => { + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(request); + expect(props).toEqual( + expect.objectContaining({ + tenantId: undefined, + userGlobalAccessPermission: undefined, + userTenantAccessPermission: undefined, + userId: undefined, + userType: undefined, + isMultiTenantRequest: undefined, + }), + ); + }); + + it("should set userId if userAuthorization is present", async () => { + request.userAuthorization = { userId: new ObjectID("123") } as any; + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(request); + expect(props.userId).toEqual(new ObjectID("123")); + }); + + it("should set userGlobalAccessPermission if present in the request", async () => { + request.userGlobalAccessPermission = { canEdit: true } as any; + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(request); + expect(props.userGlobalAccessPermission).toEqual({ canEdit: true }); + }); + + it("should set userTenantAccessPermission if present in the request", async () => { + request.userTenantAccessPermission = { canView: true } as any; + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(request); + expect(props.userTenantAccessPermission).toEqual({ canView: true }); + }); + + it("should set tenantId if present in the request", async () => { + request.tenantId = new ObjectID("456"); + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(request); + expect(props.tenantId).toEqual(new ObjectID("456")); + }); + + it("should set isMultiTenantRequest based on headers", async () => { + request.headers["is-multi-tenant-query"] = "true"; + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(request); + expect(props.isMultiTenantRequest).toBe(true); + }); + + describe("when billing is enabled", () => { + it("should set currentPlan and isSubscriptionUnpaid if tenantId is present", async () => { + request.tenantId = new ObjectID("789"); // eslint-disable-next-line @typescript-eslint/typedef - const checkRoutes = [ - ['POST', '/mock', 'createItem'], - ['POST', '/mock/get-list', 'getList'], - ['GET', '/mock/get-list', 'getList'], - ['POST', '/mock/count', 'count'], - ['POST', '/mock/:id/get-item', 'getItem'], - ['GET', '/mock/:id/get-item', 'getItem'], - ['PUT', '/mock/:id', 'updateItem'], - ['DELETE', '/mock/:id', 'deleteItem'], - ] as [string, string, string][]; + const plan = { + plan: "Free" as PlanSelect, + isSubscriptionUnpaid: false, + }; + jest.spyOn(ProjectService, "getCurrentPlan").mockResolvedValue(plan); - for (const [method, uri, shouldBeCalled] of checkRoutes) { - describe(`${method} ${uri}`, () => { - it(`should call ${shouldBeCalled}`, async () => { - const spy: jest.SpyInstance = jest.spyOn( - baseApiInstance as any, - shouldBeCalled as any - ); - await mockRouter - .match(method, uri) - .handlerFunction(emptyRequest, res, next); - expect(spy).toHaveBeenCalledWith(emptyRequest, res); - }); + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(request); + expect(props.currentPlan).toBe("Free"); + expect(props.isSubscriptionUnpaid).toBe(false); + }); - it('should call next(err) on exception', async () => { - const error: Error = new Error('Mocked Error'); - jest.spyOn( - baseApiInstance as any, - shouldBeCalled as any - ).mockImplementationOnce(() => { - throw error; - }); - const next: jest.Mock = jest.fn(); - await mockRouter - .match(method, uri) - .handlerFunction(emptyRequest, res, next); - expect(next).toHaveBeenCalledWith(error); - }); - }); - } + it("should set currentPlan and isSubscriptionUnpaid to undefined if tenantId is not present", async () => { + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(request); + expect(props.currentPlan).toBeUndefined(); + expect(props.isSubscriptionUnpaid).toBeUndefined(); + }); }); - describe('BaseAPI.getPermissionsForTenant', () => { - it('should return empty permissions if userTenantAccessPermission is not set', async () => { - jest.spyOn( - CommonAPI, - 'getDatabaseCommonInteractionProps' - ).mockResolvedValueOnce({}); - const permissions: UserPermission[] = - await baseApiInstance.getPermissionsForTenant(emptyRequest); - expect(permissions).toEqual([]); - }); + it("should set isMasterAdmin if userType is MasterAdmin", async () => { + request.userType = UserType.MasterAdmin; + const props: DatabaseCommonInteractionProps = + await CommonAPI.getDatabaseCommonInteractionProps(request); + expect(props.isMasterAdmin).toBe(true); + }); + }); - it('should return permissions if userTenantAccessPermission is set and tenantId is available', async () => { - // eslint-disable-next-line @typescript-eslint/typedef - const mockPermissions = [{ permission: 'granted' }]; - jest.spyOn( - CommonAPI, - 'getDatabaseCommonInteractionProps' - ).mockResolvedValueOnce({ - userTenantAccessPermission: { - tenantId: { permissions: mockPermissions }, - }, - tenantId: 'tenantId', - } as any); - - const permissions: UserPermission[] = - await baseApiInstance.getPermissionsForTenant(emptyRequest); - expect(permissions).toEqual(mockPermissions); - }); - - it('should return empty permissions if tenantId is not available', async () => { - jest.spyOn( - CommonAPI, - 'getDatabaseCommonInteractionProps' - ).mockResolvedValueOnce({ - userTenantAccessPermission: { - tenantId: { permissions: [{ doesnt: 'matter' }] }, - }, - tenantId: null, - } as any); - - const permissions: UserPermission[] = - await baseApiInstance.getPermissionsForTenant(emptyRequest); - expect(permissions).toEqual([]); - }); + describe("getList", () => { + it("should call onBeforeList", async () => { + const onBeforeListSpy: jest.SpyInstance = jest.spyOn( + baseApiInstance as any, + "onBeforeList", + ); + await baseApiInstance.getList(emptyRequest, res); + expect(onBeforeListSpy).toHaveBeenCalledWith(emptyRequest, res); }); - describe('getTenantId', () => { - it('should return null if there is no tennatId', () => { - expect(baseApiInstance.getTenantId(emptyRequest)).toEqual(null); - }); - - it('should return ObjectID if tennantId is passed', () => { - const tenantId: ObjectID = new ObjectID('123'); - const tenantRequest: OneUptimeRequest = { - tenantId, - } as OneUptimeRequest; - expect(baseApiInstance.getTenantId(tenantRequest)).toEqual( - tenantId - ); - }); + it("should call service.findBy with the correct parameters", async () => { + const findBySpy: jest.SpyInstance = jest.spyOn(TestService, "findBy"); + await baseApiInstance.getList(emptyRequest, res); + expect(findBySpy).toHaveBeenCalledWith( + expect.objectContaining({ + skip: expect.any(PositiveNumber), + limit: expect.any(PositiveNumber), + query: {}, + select: {}, + sort: {}, + props: emptyDatabaseCommonInteractionProps, + }), + ); }); - describe('getDatabaseCommonInteractionProps', () => { - let request: OneUptimeRequest; - - beforeEach(() => { - request = { - userType: undefined, - userAuthorization: undefined, - userGlobalAccessPermission: undefined, - userTenantAccessPermission: undefined, - tenantId: undefined, - headers: {}, - } as unknown as OneUptimeRequest; - }); - - it('should initialize props with undefined values', async () => { - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(request); - expect(props).toEqual( - expect.objectContaining({ - tenantId: undefined, - userGlobalAccessPermission: undefined, - userTenantAccessPermission: undefined, - userId: undefined, - userType: undefined, - isMultiTenantRequest: undefined, - }) - ); - }); - - it('should set userId if userAuthorization is present', async () => { - request.userAuthorization = { userId: new ObjectID('123') } as any; - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(request); - expect(props.userId).toEqual(new ObjectID('123')); - }); - - it('should set userGlobalAccessPermission if present in the request', async () => { - request.userGlobalAccessPermission = { canEdit: true } as any; - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(request); - expect(props.userGlobalAccessPermission).toEqual({ canEdit: true }); - }); - - it('should set userTenantAccessPermission if present in the request', async () => { - request.userTenantAccessPermission = { canView: true } as any; - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(request); - expect(props.userTenantAccessPermission).toEqual({ canView: true }); - }); - - it('should set tenantId if present in the request', async () => { - request.tenantId = new ObjectID('456'); - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(request); - expect(props.tenantId).toEqual(new ObjectID('456')); - }); - - it('should set isMultiTenantRequest based on headers', async () => { - request.headers['is-multi-tenant-query'] = 'true'; - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(request); - expect(props.isMultiTenantRequest).toBe(true); - }); - - describe('when billing is enabled', () => { - it('should set currentPlan and isSubscriptionUnpaid if tenantId is present', async () => { - request.tenantId = new ObjectID('789'); - // eslint-disable-next-line @typescript-eslint/typedef - const plan = { - plan: 'Free' as PlanSelect, - isSubscriptionUnpaid: false, - }; - jest.spyOn(ProjectService, 'getCurrentPlan').mockResolvedValue( - plan - ); - - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(request); - expect(props.currentPlan).toBe('Free'); - expect(props.isSubscriptionUnpaid).toBe(false); - }); - - it('should set currentPlan and isSubscriptionUnpaid to undefined if tenantId is not present', async () => { - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(request); - expect(props.currentPlan).toBeUndefined(); - expect(props.isSubscriptionUnpaid).toBeUndefined(); - }); - }); - - it('should set isMasterAdmin if userType is MasterAdmin', async () => { - request.userType = UserType.MasterAdmin; - const props: DatabaseCommonInteractionProps = - await CommonAPI.getDatabaseCommonInteractionProps(request); - expect(props.isMasterAdmin).toBe(true); - }); + it("should call service.count with the correct parameters", async () => { + const findBySpy: jest.SpyInstance = jest.spyOn(TestService, "countBy"); + await baseApiInstance.getList(emptyRequest, res); + expect(findBySpy).toHaveBeenCalledWith( + expect.objectContaining({ + query: {}, + props: emptyDatabaseCommonInteractionProps, + }), + ); }); - describe('getList', () => { - it('should call onBeforeList', async () => { - const onBeforeListSpy: jest.SpyInstance = jest.spyOn( - baseApiInstance as any, - 'onBeforeList' - ); - await baseApiInstance.getList(emptyRequest, res); - expect(onBeforeListSpy).toHaveBeenCalledWith(emptyRequest, res); - }); - - it('should call service.findBy with the correct parameters', async () => { - const findBySpy: jest.SpyInstance = jest.spyOn( - TestService, - 'findBy' - ); - await baseApiInstance.getList(emptyRequest, res); - expect(findBySpy).toHaveBeenCalledWith( - expect.objectContaining({ - skip: expect.any(PositiveNumber), - limit: expect.any(PositiveNumber), - query: {}, - select: {}, - sort: {}, - props: emptyDatabaseCommonInteractionProps, - }) - ); - }); - - it('should call service.count with the correct parameters', async () => { - const findBySpy: jest.SpyInstance = jest.spyOn( - TestService, - 'countBy' - ); - await baseApiInstance.getList(emptyRequest, res); - expect(findBySpy).toHaveBeenCalledWith( - expect.objectContaining({ - query: {}, - props: emptyDatabaseCommonInteractionProps, - }) - ); - }); - - it('should return values retrieved from service', async () => { - const result: [ - Request, - Response, - GenericObject, - PositiveNumber, - GenericObject - ] = (await baseApiInstance.getList(emptyRequest, res)) as any; - expect(result[2]).toEqual([{ id: 'mock' }]); - expect(result[3]).toEqual(new PositiveNumber(42)); - }); - - it('should parse query, select, sort from body', async () => { - emptyRequest.body = { - query: { skip: 10, limit: 10, query: 'query' }, - select: { id: ['selected'] }, - sort: { _id: 'Descending' }, - }; - const findBySpy: jest.SpyInstance = jest.spyOn( - TestService, - 'findBy' - ); - await baseApiInstance.getList(emptyRequest, res); - expect(findBySpy).toHaveBeenCalledWith( - expect.objectContaining({ - skip: expect.any(PositiveNumber), - limit: expect.any(PositiveNumber), - query: { limit: 10, query: 'query', skip: 10 }, - select: { id: ['selected'] }, - sort: { _id: 'Descending' }, - props: emptyDatabaseCommonInteractionProps, - }) - ); - }); - - it('should throw BadRequestException if limit is greater than LIMIT_PER_PROJECT', async () => { - emptyRequest.query['limit'] = (LIMIT_PER_PROJECT + 1).toString(); - await expect( - baseApiInstance.getList(emptyRequest, res) - ).rejects.toThrow(BadRequestException); - }); - - it('should throw BadDataException if limit is less than 0', async () => { - emptyRequest.query['limit'] = '-1'; - await expect( - baseApiInstance.getList(emptyRequest, res) - ).rejects.toThrow(BadDataException); - }); - - it('should throw BadRequestException if limit is 0', async () => { - emptyRequest.query['limit'] = '0'; - await expect( - baseApiInstance.getList(emptyRequest, res) - ).rejects.toThrow(BadRequestException); - }); + it("should return values retrieved from service", async () => { + const result: [ + Request, + Response, + GenericObject, + PositiveNumber, + GenericObject, + ] = (await baseApiInstance.getList(emptyRequest, res)) as any; + expect(result[2]).toEqual([{ id: "mock" }]); + expect(result[3]).toEqual(new PositiveNumber(42)); }); - describe('getCount', () => { - it('should call onBeforeCount lifecycle method', async () => { - const onBeforeCountSpy: jest.SpyInstance = jest.spyOn( - baseApiInstance as any, - 'onBeforeCount' - ); - - await baseApiInstance.count(emptyRequest, res); - expect(onBeforeCountSpy).toHaveBeenCalledWith(emptyRequest, res); - }); - - it('should process empty query if no body is provided', async () => { - const countBySpy: jest.SpyInstance = jest.spyOn( - TestService, - 'countBy' - ); - await baseApiInstance.count(emptyRequest, res); - expect(countBySpy).toHaveBeenCalledWith({ - query: {}, - props: emptyDatabaseCommonInteractionProps, - }); - }); - - it('should process provided query', async () => { - const countBySpy: jest.SpyInstance = jest.spyOn( - TestService, - 'countBy' - ); - await baseApiInstance.count(countRequest, res); - expect(countBySpy).toHaveBeenCalledWith({ - query: { id: 'count-me' }, - props: emptyDatabaseCommonInteractionProps, - }); - }); - - it('should call the countBy method of the service with the correct parameters', async () => { - const findBySpy: jest.SpyInstance = jest.spyOn( - TestService, - 'countBy' - ); - await baseApiInstance.count(countRequest, res); - expect(findBySpy).toHaveBeenCalledWith({ - query: { id: 'count-me' }, - props: emptyDatabaseCommonInteractionProps, - }); - }); - - it('should send a json response with the count', async () => { - await baseApiInstance.count(emptyRequest, res); - expect(Response.sendJsonObjectResponse).toHaveBeenCalledWith( - emptyRequest, - res, - { - count: 42, - } - ); - }); + it("should parse query, select, sort from body", async () => { + emptyRequest.body = { + query: { skip: 10, limit: 10, query: "query" }, + select: { id: ["selected"] }, + sort: { _id: "Descending" }, + }; + const findBySpy: jest.SpyInstance = jest.spyOn(TestService, "findBy"); + await baseApiInstance.getList(emptyRequest, res); + expect(findBySpy).toHaveBeenCalledWith( + expect.objectContaining({ + skip: expect.any(PositiveNumber), + limit: expect.any(PositiveNumber), + query: { limit: 10, query: "query", skip: 10 }, + select: { id: ["selected"] }, + sort: { _id: "Descending" }, + props: emptyDatabaseCommonInteractionProps, + }), + ); }); - describe('getItem', () => { - it('should call onBeforeGet lifecycle method', async () => { - const onBeforeGetSpy: jest.SpyInstance = jest.spyOn( - baseApiInstance as any, - 'onBeforeGet' - ); - await baseApiInstance.getItem(getRequest, res); - expect(onBeforeGetSpy).toHaveBeenCalledWith(getRequest, res); - }); - - it('should call service.findOneById', async () => { - const findOneByIdSpy: jest.SpyInstance = jest.spyOn( - baseApiInstance.service, - 'findOneById' - ); - await baseApiInstance.getItem(getRequest, res); - expect(findOneByIdSpy).toHaveBeenCalledWith( - expect.objectContaining({ - id: new ObjectID('get-me'), - props: emptyDatabaseCommonInteractionProps, - select: {}, - }) - ); - }); - - it('should interpret body.select', async () => { - const findOneByIdSpy: jest.SpyInstance = jest.spyOn( - baseApiInstance.service, - 'findOneById' - ); - const getRequestWithSelect: OneUptimeRequest = { - ...getRequest, - ...{ body: { select: { id: true } } }, - } as unknown as OneUptimeRequest; - - await baseApiInstance.getItem(getRequestWithSelect, res); - expect(findOneByIdSpy).toHaveBeenCalledWith( - expect.objectContaining({ - id: new ObjectID('get-me'), - props: emptyDatabaseCommonInteractionProps, - select: { id: true }, - }) - ); - }); - - it('should return EntityResponse', async () => { - const sendEntityResponseSpy: jest.SpyInstance = jest.spyOn( - Response as any, - 'sendEntityResponse' - ); - await baseApiInstance.getItem(getRequest, res); - expect(sendEntityResponseSpy).toHaveBeenCalledWith( - getRequest, - res, - { id: 'mock' }, - BaseModel - ); - }); + it("should throw BadRequestException if limit is greater than LIMIT_PER_PROJECT", async () => { + emptyRequest.query["limit"] = (LIMIT_PER_PROJECT + 1).toString(); + await expect(baseApiInstance.getList(emptyRequest, res)).rejects.toThrow( + BadRequestException, + ); }); - describe('deleteItem', () => { - it('should call onBeforeDelete lifecycle method', async () => { - const onBeforeDeleteSpy: jest.SpyInstance = jest.spyOn( - baseApiInstance as any, - 'onBeforeDelete' - ); - await baseApiInstance.deleteItem(deleteRequest, res); - expect(onBeforeDeleteSpy).toHaveBeenCalledWith(deleteRequest, res); - }); - - it('should convert request param id to query', async () => { - const deleteOneBySpy: jest.SpyInstance = jest.spyOn( - baseApiInstance.service, - 'deleteOneById' - ); - - await baseApiInstance.deleteItem(deleteRequest, res); - - expect(deleteOneBySpy).toHaveBeenCalledWith( - expect.objectContaining({ - id: new ObjectID('delete-me'), - props: emptyDatabaseCommonInteractionProps, - }) - ); - }); - - it('should send empty response on success', async () => { - await baseApiInstance.deleteItem(deleteRequest, res); - const sendEmptyResponseSpy: jest.SpyInstance = jest.spyOn( - Response as any, - 'sendEmptySuccessResponse' - ); - expect(sendEmptyResponseSpy).toHaveBeenCalledWith( - deleteRequest, - res - ); - }); + it("should throw BadDataException if limit is less than 0", async () => { + emptyRequest.query["limit"] = "-1"; + await expect(baseApiInstance.getList(emptyRequest, res)).rejects.toThrow( + BadDataException, + ); }); - describe('updateItem', () => { - let updateRequest: OneUptimeRequest; - let updateResponse: ExpressResponse; - let emptyProps: DatabaseCommonInteractionProps; + it("should throw BadRequestException if limit is 0", async () => { + emptyRequest.query["limit"] = "0"; + await expect(baseApiInstance.getList(emptyRequest, res)).rejects.toThrow( + BadRequestException, + ); + }); + }); - beforeEach(() => { - updateRequest = { - params: { id: 'update-me' }, - body: { data: { name: 'updatedName' } }, - headers: {}, - } as unknown as OneUptimeRequest; + describe("getCount", () => { + it("should call onBeforeCount lifecycle method", async () => { + const onBeforeCountSpy: jest.SpyInstance = jest.spyOn( + baseApiInstance as any, + "onBeforeCount", + ); - updateResponse = { - status: jest.fn().mockReturnThis(), - json: jest.fn().mockReturnThis(), - send: jest.fn().mockReturnThis(), - } as unknown as ExpressResponse; - - emptyProps = {}; - }); - - it('should call onBeforeUpdate lifecycle method', async () => { - const onBeforeUpdateSpy: jest.SpyInstance = jest.spyOn( - baseApiInstance as any, - 'onBeforeUpdate' - ); - await baseApiInstance.updateItem(updateRequest, updateResponse); - expect(onBeforeUpdateSpy).toHaveBeenCalledWith( - updateRequest, - updateResponse - ); - }); - - it('should remove forbidden fields from the item', async () => { - // eslint-disable-next-line @typescript-eslint/typedef - const itemWithForbiddenFields = { - _id: 'should-be-removed', - createdAt: 'should-be-removed', - updatedAt: 'should-be-removed', - name: 'updatedName', - }; - - updateRequest.body.data = itemWithForbiddenFields; - - const updateOneByIdSpy: jest.SpyInstance = jest.spyOn( - baseApiInstance.service, - 'updateOneById' - ); - - await baseApiInstance.updateItem(updateRequest, updateResponse); - - expect(updateOneByIdSpy).toHaveBeenCalledWith( - expect.objectContaining({ - data: { name: 'updatedName' }, - }) - ); - }); - - it('should convert request param id to ObjectID for query', async () => { - await baseApiInstance.updateItem(updateRequest, updateResponse); - - const updateOneBySpy: jest.SpyInstance = jest.spyOn( - baseApiInstance.service, - 'updateOneById' - ); - - expect(updateOneBySpy).toHaveBeenCalledWith( - expect.objectContaining({ - id: new ObjectID('update-me'), - }) - ); - }); - - it('should call the updateOneBy method of the service with correct parameters', async () => { - await baseApiInstance.updateItem(updateRequest, updateResponse); - const updateOneBySpy: jest.SpyInstance = jest.spyOn( - baseApiInstance.service, - 'updateOneById' - ); - expect(updateOneBySpy).toHaveBeenCalledWith({ - id: new ObjectID('update-me'), - data: { name: 'updatedName' }, - props: emptyProps, - }); - }); - - it('should send empty response on success', async () => { - await baseApiInstance.updateItem(updateRequest, updateResponse); - const sendEmptyResponseSpy: jest.SpyInstance = jest.spyOn( - Response as any, - 'sendEmptySuccessResponse' - ); - expect(sendEmptyResponseSpy).toHaveBeenCalledWith( - updateRequest, - updateResponse - ); - }); + await baseApiInstance.count(emptyRequest, res); + expect(onBeforeCountSpy).toHaveBeenCalledWith(emptyRequest, res); }); - // describe('createItem', () => { - // let createRequest: OneUptimeRequest; - // let createResponse: ExpressResponse; - // let savedItem: BaseModel; - - // beforeEach(() => { - // createRequest = { - // body: { - // data: { version: '1' }, - // miscDataProps: { additional: 'info' }, - // }, - // headers: {}, - // } as unknown as OneUptimeRequest; - - // createResponse = { - // status: jest.fn().mockReturnThis(), - // json: jest.fn().mockReturnThis(), - // send: jest.fn().mockReturnThis(), - // } as unknown as ExpressResponse; - - // savedItem = new BaseModel(); - // }); - - // it('should call onBeforeCreate lifecycle method', async () => { - // const onBeforeCreateSpy: jest.SpyInstance = jest.spyOn( - // baseApiInstance as any, - // 'onBeforeCreate' - // ); - // await baseApiInstance.createItem(createRequest, createResponse); - // expect(onBeforeCreateSpy).toHaveBeenCalledWith( - // createRequest, - // createResponse - // ); - // }); - - // it('should return EntityResponse with the saved item', async () => { - // jest.spyOn(baseApiInstance.service, 'create').mockResolvedValue( - // savedItem - // ); - // await baseApiInstance.createItem(createRequest, createResponse); - // const sendEntityResponseSpy: jest.SpyInstance = jest.spyOn( - // Response as any, - // 'sendEntityResponse' - // ); - // expect(sendEntityResponseSpy).toHaveBeenCalledWith( - // createRequest, - // createResponse, - // savedItem, - // BaseModel - // ); - // }); - // }); - - describe('getRouter', () => { - it('should return an ExpressRouter instance', () => { - const router: ExpressRouter = baseApiInstance.getRouter(); - expect(router).toBeDefined(); - }); + it("should process empty query if no body is provided", async () => { + const countBySpy: jest.SpyInstance = jest.spyOn(TestService, "countBy"); + await baseApiInstance.count(emptyRequest, res); + expect(countBySpy).toHaveBeenCalledWith({ + query: {}, + props: emptyDatabaseCommonInteractionProps, + }); }); - describe('getEntityName', () => { - it('should return the name of the entity', () => { - const entityName: string = baseApiInstance.getEntityName(); - expect(entityName).toBe('mockConstructor'); - }); + it("should process provided query", async () => { + const countBySpy: jest.SpyInstance = jest.spyOn(TestService, "countBy"); + await baseApiInstance.count(countRequest, res); + expect(countBySpy).toHaveBeenCalledWith({ + query: { id: "count-me" }, + props: emptyDatabaseCommonInteractionProps, + }); }); + + it("should call the countBy method of the service with the correct parameters", async () => { + const findBySpy: jest.SpyInstance = jest.spyOn(TestService, "countBy"); + await baseApiInstance.count(countRequest, res); + expect(findBySpy).toHaveBeenCalledWith({ + query: { id: "count-me" }, + props: emptyDatabaseCommonInteractionProps, + }); + }); + + it("should send a json response with the count", async () => { + await baseApiInstance.count(emptyRequest, res); + expect(Response.sendJsonObjectResponse).toHaveBeenCalledWith( + emptyRequest, + res, + { + count: 42, + }, + ); + }); + }); + + describe("getItem", () => { + it("should call onBeforeGet lifecycle method", async () => { + const onBeforeGetSpy: jest.SpyInstance = jest.spyOn( + baseApiInstance as any, + "onBeforeGet", + ); + await baseApiInstance.getItem(getRequest, res); + expect(onBeforeGetSpy).toHaveBeenCalledWith(getRequest, res); + }); + + it("should call service.findOneById", async () => { + const findOneByIdSpy: jest.SpyInstance = jest.spyOn( + baseApiInstance.service, + "findOneById", + ); + await baseApiInstance.getItem(getRequest, res); + expect(findOneByIdSpy).toHaveBeenCalledWith( + expect.objectContaining({ + id: new ObjectID("get-me"), + props: emptyDatabaseCommonInteractionProps, + select: {}, + }), + ); + }); + + it("should interpret body.select", async () => { + const findOneByIdSpy: jest.SpyInstance = jest.spyOn( + baseApiInstance.service, + "findOneById", + ); + const getRequestWithSelect: OneUptimeRequest = { + ...getRequest, + ...{ body: { select: { id: true } } }, + } as unknown as OneUptimeRequest; + + await baseApiInstance.getItem(getRequestWithSelect, res); + expect(findOneByIdSpy).toHaveBeenCalledWith( + expect.objectContaining({ + id: new ObjectID("get-me"), + props: emptyDatabaseCommonInteractionProps, + select: { id: true }, + }), + ); + }); + + it("should return EntityResponse", async () => { + const sendEntityResponseSpy: jest.SpyInstance = jest.spyOn( + Response as any, + "sendEntityResponse", + ); + await baseApiInstance.getItem(getRequest, res); + expect(sendEntityResponseSpy).toHaveBeenCalledWith( + getRequest, + res, + { id: "mock" }, + BaseModel, + ); + }); + }); + + describe("deleteItem", () => { + it("should call onBeforeDelete lifecycle method", async () => { + const onBeforeDeleteSpy: jest.SpyInstance = jest.spyOn( + baseApiInstance as any, + "onBeforeDelete", + ); + await baseApiInstance.deleteItem(deleteRequest, res); + expect(onBeforeDeleteSpy).toHaveBeenCalledWith(deleteRequest, res); + }); + + it("should convert request param id to query", async () => { + const deleteOneBySpy: jest.SpyInstance = jest.spyOn( + baseApiInstance.service, + "deleteOneById", + ); + + await baseApiInstance.deleteItem(deleteRequest, res); + + expect(deleteOneBySpy).toHaveBeenCalledWith( + expect.objectContaining({ + id: new ObjectID("delete-me"), + props: emptyDatabaseCommonInteractionProps, + }), + ); + }); + + it("should send empty response on success", async () => { + await baseApiInstance.deleteItem(deleteRequest, res); + const sendEmptyResponseSpy: jest.SpyInstance = jest.spyOn( + Response as any, + "sendEmptySuccessResponse", + ); + expect(sendEmptyResponseSpy).toHaveBeenCalledWith(deleteRequest, res); + }); + }); + + describe("updateItem", () => { + let updateRequest: OneUptimeRequest; + let updateResponse: ExpressResponse; + let emptyProps: DatabaseCommonInteractionProps; + + beforeEach(() => { + updateRequest = { + params: { id: "update-me" }, + body: { data: { name: "updatedName" } }, + headers: {}, + } as unknown as OneUptimeRequest; + + updateResponse = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + send: jest.fn().mockReturnThis(), + } as unknown as ExpressResponse; + + emptyProps = {}; + }); + + it("should call onBeforeUpdate lifecycle method", async () => { + const onBeforeUpdateSpy: jest.SpyInstance = jest.spyOn( + baseApiInstance as any, + "onBeforeUpdate", + ); + await baseApiInstance.updateItem(updateRequest, updateResponse); + expect(onBeforeUpdateSpy).toHaveBeenCalledWith( + updateRequest, + updateResponse, + ); + }); + + it("should remove forbidden fields from the item", async () => { + // eslint-disable-next-line @typescript-eslint/typedef + const itemWithForbiddenFields = { + _id: "should-be-removed", + createdAt: "should-be-removed", + updatedAt: "should-be-removed", + name: "updatedName", + }; + + updateRequest.body.data = itemWithForbiddenFields; + + const updateOneByIdSpy: jest.SpyInstance = jest.spyOn( + baseApiInstance.service, + "updateOneById", + ); + + await baseApiInstance.updateItem(updateRequest, updateResponse); + + expect(updateOneByIdSpy).toHaveBeenCalledWith( + expect.objectContaining({ + data: { name: "updatedName" }, + }), + ); + }); + + it("should convert request param id to ObjectID for query", async () => { + await baseApiInstance.updateItem(updateRequest, updateResponse); + + const updateOneBySpy: jest.SpyInstance = jest.spyOn( + baseApiInstance.service, + "updateOneById", + ); + + expect(updateOneBySpy).toHaveBeenCalledWith( + expect.objectContaining({ + id: new ObjectID("update-me"), + }), + ); + }); + + it("should call the updateOneBy method of the service with correct parameters", async () => { + await baseApiInstance.updateItem(updateRequest, updateResponse); + const updateOneBySpy: jest.SpyInstance = jest.spyOn( + baseApiInstance.service, + "updateOneById", + ); + expect(updateOneBySpy).toHaveBeenCalledWith({ + id: new ObjectID("update-me"), + data: { name: "updatedName" }, + props: emptyProps, + }); + }); + + it("should send empty response on success", async () => { + await baseApiInstance.updateItem(updateRequest, updateResponse); + const sendEmptyResponseSpy: jest.SpyInstance = jest.spyOn( + Response as any, + "sendEmptySuccessResponse", + ); + expect(sendEmptyResponseSpy).toHaveBeenCalledWith( + updateRequest, + updateResponse, + ); + }); + }); + + // describe('createItem', () => { + // let createRequest: OneUptimeRequest; + // let createResponse: ExpressResponse; + // let savedItem: BaseModel; + + // beforeEach(() => { + // createRequest = { + // body: { + // data: { version: '1' }, + // miscDataProps: { additional: 'info' }, + // }, + // headers: {}, + // } as unknown as OneUptimeRequest; + + // createResponse = { + // status: jest.fn().mockReturnThis(), + // json: jest.fn().mockReturnThis(), + // send: jest.fn().mockReturnThis(), + // } as unknown as ExpressResponse; + + // savedItem = new BaseModel(); + // }); + + // it('should call onBeforeCreate lifecycle method', async () => { + // const onBeforeCreateSpy: jest.SpyInstance = jest.spyOn( + // baseApiInstance as any, + // 'onBeforeCreate' + // ); + // await baseApiInstance.createItem(createRequest, createResponse); + // expect(onBeforeCreateSpy).toHaveBeenCalledWith( + // createRequest, + // createResponse + // ); + // }); + + // it('should return EntityResponse with the saved item', async () => { + // jest.spyOn(baseApiInstance.service, 'create').mockResolvedValue( + // savedItem + // ); + // await baseApiInstance.createItem(createRequest, createResponse); + // const sendEntityResponseSpy: jest.SpyInstance = jest.spyOn( + // Response as any, + // 'sendEntityResponse' + // ); + // expect(sendEntityResponseSpy).toHaveBeenCalledWith( + // createRequest, + // createResponse, + // savedItem, + // BaseModel + // ); + // }); + // }); + + describe("getRouter", () => { + it("should return an ExpressRouter instance", () => { + const router: ExpressRouter = baseApiInstance.getRouter(); + expect(router).toBeDefined(); + }); + }); + + describe("getEntityName", () => { + it("should return the name of the entity", () => { + const entityName: string = baseApiInstance.getEntityName(); + expect(entityName).toBe("mockConstructor"); + }); + }); }); diff --git a/CommonServer/Tests/API/Helpers.ts b/CommonServer/Tests/API/Helpers.ts index 9643cc9d8e..eb7bdce106 100644 --- a/CommonServer/Tests/API/Helpers.ts +++ b/CommonServer/Tests/API/Helpers.ts @@ -1,71 +1,67 @@ import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../../Utils/Express'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../../Utils/Express"; type RouterFunction = ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, ) => void | Promise<void>; type Route = { - method: string; - uri: string; - middleware: RouterFunction; - handlerFunction: RouterFunction; + method: string; + uri: string; + middleware: RouterFunction; + handlerFunction: RouterFunction; }; type MockRouterForMethodFunction = ( - method: string + method: string, ) => ( - uri: string, - middleware: RouterFunction, - handlerFunction: RouterFunction + uri: string, + middleware: RouterFunction, + handlerFunction: RouterFunction, ) => void; const mockRouterForMethod: MockRouterForMethodFunction = (method: string) => { - return ( - uri: string, - middleware: RouterFunction, - handlerFunction: RouterFunction - ): void => { - mockRouter.routes.push({ - method: method.toUpperCase(), - uri, - middleware, - handlerFunction, - }); - }; + return ( + uri: string, + middleware: RouterFunction, + handlerFunction: RouterFunction, + ): void => { + mockRouter.routes.push({ + method: method.toUpperCase(), + uri, + middleware, + handlerFunction, + }); + }; }; type MockRouter = { - get: jest.Mock; - post: jest.Mock; - put: jest.Mock; - delete: jest.Mock; - routes: Route[]; - match: (method: string, uri: string) => Route; + get: jest.Mock; + post: jest.Mock; + put: jest.Mock; + delete: jest.Mock; + routes: Route[]; + match: (method: string, uri: string) => Route; }; export const mockRouter: MockRouter = { - get: jest.fn().mockImplementation(mockRouterForMethod('get')), - post: jest.fn().mockImplementation(mockRouterForMethod('post')), - put: jest.fn().mockImplementation(mockRouterForMethod('put')), - delete: jest.fn().mockImplementation(mockRouterForMethod('delete')), - routes: [] as any as Route[], - match: (method: string, uri: string) => { - const route: Route | undefined = mockRouter.routes.find( - (route: Route) => { - return ( - method.toUpperCase() === route.method && uri === route.uri - ); - } - ); - if (!route) { - throw 'not found'; - } - return route; - }, + get: jest.fn().mockImplementation(mockRouterForMethod("get")), + post: jest.fn().mockImplementation(mockRouterForMethod("post")), + put: jest.fn().mockImplementation(mockRouterForMethod("put")), + delete: jest.fn().mockImplementation(mockRouterForMethod("delete")), + routes: [] as any as Route[], + match: (method: string, uri: string) => { + const route: Route | undefined = mockRouter.routes.find((route: Route) => { + return method.toUpperCase() === route.method && uri === route.uri; + }); + if (!route) { + throw "not found"; + } + return route; + }, }; diff --git a/CommonServer/Tests/API/ProbeAPI.test.ts b/CommonServer/Tests/API/ProbeAPI.test.ts index d7d5b58a43..5cd9ed29c1 100644 --- a/CommonServer/Tests/API/ProbeAPI.test.ts +++ b/CommonServer/Tests/API/ProbeAPI.test.ts @@ -1,106 +1,106 @@ -import Ingestor from '../../API/ProbeAPI'; -import ProbeService from '../../Services/ProbeService'; +import Ingestor from "../../API/ProbeAPI"; +import ProbeService from "../../Services/ProbeService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../../Utils/Express'; -import Response from '../../Utils/Response'; -import { mockRouter } from './Helpers'; -import { describe, expect, it } from '@jest/globals'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Probe from 'Model/Models/Probe'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../../Utils/Express"; +import Response from "../../Utils/Response"; +import { mockRouter } from "./Helpers"; +import { describe, expect, it } from "@jest/globals"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Probe from "Model/Models/Probe"; -jest.mock('../../Utils/Express', () => { - return { - getRouter: () => { - return mockRouter; - }, - }; +jest.mock("../../Utils/Express", () => { + return { + getRouter: () => { + return mockRouter; + }, + }; }); -jest.mock('../../Utils/Response', () => { - return { - sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - sendEmptySuccessResponse: jest.fn(), - sendEntityResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - }; +jest.mock("../../Utils/Response", () => { + return { + sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + sendEmptySuccessResponse: jest.fn(), + sendEntityResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + }; }); -jest.mock('../../Services/ProbeService'); +jest.mock("../../Services/ProbeService"); -describe('Ingestor', () => { - let mockRequest: ExpressRequest; - let mockResponse: ExpressResponse; - let nextFunction: NextFunction; +describe("Ingestor", () => { + let mockRequest: ExpressRequest; + let mockResponse: ExpressResponse; + let nextFunction: NextFunction; - beforeEach(() => { - new Ingestor(); - mockRequest = {} as ExpressRequest; - mockResponse = { - send: jest.fn(), - json: jest.fn(), - status: jest.fn().mockReturnThis(), - } as unknown as ExpressResponse; - nextFunction = jest.fn(); + beforeEach(() => { + new Ingestor(); + mockRequest = {} as ExpressRequest; + mockResponse = { + send: jest.fn(), + json: jest.fn(), + status: jest.fn().mockReturnThis(), + } as unknown as ExpressResponse; + nextFunction = jest.fn(); + }); + + it("should correctly handle global probes request", async () => { + // eslint-disable-next-line @typescript-eslint/typedef + const mockProbes = [{ id: "probe" }]; + ProbeService.findBy = jest.fn().mockResolvedValue(mockProbes); + await mockRouter + .match("post", "/probe/global-probes") + .handlerFunction(mockRequest, mockResponse, nextFunction); + + expect(ProbeService.findBy).toHaveBeenCalledWith({ + query: { + isGlobalProbe: true, + }, + select: { + name: true, + description: true, + lastAlive: true, + iconFileId: true, + }, + props: { + isRoot: true, + }, + skip: 0, + limit: LIMIT_MAX, }); - it('should correctly handle global probes request', async () => { - // eslint-disable-next-line @typescript-eslint/typedef - const mockProbes = [{ id: 'probe' }]; - ProbeService.findBy = jest.fn().mockResolvedValue(mockProbes); - await mockRouter - .match('post', '/probe/global-probes') - .handlerFunction(mockRequest, mockResponse, nextFunction); + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendEntityArrayResponse", + ); + expect(response).toHaveBeenCalledWith( + mockRequest, + mockResponse, + mockProbes, + expect.any(PositiveNumber), + Probe, + ); + }); - expect(ProbeService.findBy).toHaveBeenCalledWith({ - query: { - isGlobalProbe: true, - }, - select: { - name: true, - description: true, - lastAlive: true, - iconFileId: true, - }, - props: { - isRoot: true, - }, - skip: 0, - limit: LIMIT_MAX, - }); + it("should call next with an error if findBy throws", async () => { + const testError: Error = new Error("Test error"); + ProbeService.findBy = jest.fn().mockRejectedValue(testError); + await mockRouter + .match("post", "/probe/global-probes") + .handlerFunction(mockRequest, mockResponse, nextFunction); + expect(nextFunction).toHaveBeenCalledWith(testError); + }); - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendEntityArrayResponse' - ); - expect(response).toHaveBeenCalledWith( - mockRequest, - mockResponse, - mockProbes, - expect.any(PositiveNumber), - Probe - ); - }); - - it('should call next with an error if findBy throws', async () => { - const testError: Error = new Error('Test error'); - ProbeService.findBy = jest.fn().mockRejectedValue(testError); - await mockRouter - .match('post', '/probe/global-probes') - .handlerFunction(mockRequest, mockResponse, nextFunction); - expect(nextFunction).toHaveBeenCalledWith(testError); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); + afterEach(() => { + jest.restoreAllMocks(); + }); }); diff --git a/CommonServer/Tests/API/ProjectAPI.test.ts b/CommonServer/Tests/API/ProjectAPI.test.ts index d462be3b6d..44770b73eb 100644 --- a/CommonServer/Tests/API/ProjectAPI.test.ts +++ b/CommonServer/Tests/API/ProjectAPI.test.ts @@ -1,223 +1,219 @@ -import ProjectAPI from '../../API/ProjectAPI'; -import TeamMemberService from '../../Services/TeamMemberService'; +import ProjectAPI from "../../API/ProjectAPI"; +import TeamMemberService from "../../Services/TeamMemberService"; import { - NextFunction, - OneUptimeRequest, - OneUptimeResponse, -} from '../../Utils/Express'; -import Response from '../../Utils/Response'; -import { mockRouter } from './Helpers'; -import { describe, expect, it } from '@jest/globals'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException'; -import JSONWebTokenData from 'Common/Types/JsonWebTokenData'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Project from 'Model/Models/Project'; -import TeamMember from 'Model/Models/TeamMember'; + NextFunction, + OneUptimeRequest, + OneUptimeResponse, +} from "../../Utils/Express"; +import Response from "../../Utils/Response"; +import { mockRouter } from "./Helpers"; +import { describe, expect, it } from "@jest/globals"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException"; +import JSONWebTokenData from "Common/Types/JsonWebTokenData"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Project from "Model/Models/Project"; +import TeamMember from "Model/Models/TeamMember"; -jest.mock('../../Utils/Express', () => { - return { - getRouter: () => { - return mockRouter; +jest.mock("../../Utils/Express", () => { + return { + getRouter: () => { + return mockRouter; + }, + }; +}); + +jest.mock("../../Utils/Response", () => { + return { + sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + sendEmptySuccessResponse: jest.fn(), + sendEntityResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + }; +}); + +jest.mock("../../Services/TeamMemberService"); +jest.mock("../../Services/ProjectService"); + +describe("ProjectAPI", () => { + let mockRequest: OneUptimeRequest; + let mockResponse: OneUptimeResponse; + let nextFunction: NextFunction; + + beforeEach(() => { + new ProjectAPI(); + mockRequest = {} as OneUptimeRequest; + mockResponse = { + send: jest.fn(), + json: jest.fn(), + status: jest.fn().mockReturnThis(), + } as unknown as OneUptimeResponse; + nextFunction = jest.fn(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe("POST /project/list-user-projects", () => { + it("should respond with a empty list", async () => { + const mockUserId: ObjectID = new ObjectID("123"); + mockRequest.userAuthorization = { + userId: mockUserId, + } as JSONWebTokenData; + + const mockTeamMembers: Array<TeamMember> = [ + { + userId: mockUserId, + hasAcceptedInvitation: true, + } as TeamMember, + ]; + + const projects: Array<Project> = []; + + TeamMemberService.findBy = jest.fn().mockResolvedValue(mockTeamMembers); + + await mockRouter + .match("post", "/project/list-user-projects") + .handlerFunction(mockRequest, mockResponse, nextFunction); + + expect(TeamMemberService.findBy).toHaveBeenCalledWith({ + query: { + userId: mockUserId, + hasAcceptedInvitation: true, }, - }; -}); + select: { + project: { + _id: true, + name: true, + trialEndsAt: true, + paymentProviderPlanId: true, + resellerId: true, + isFeatureFlagMonitorGroupsEnabled: true, + paymentProviderMeteredSubscriptionStatus: true, + paymentProviderSubscriptionStatus: true, + }, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); -jest.mock('../../Utils/Response', () => { - return { - sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - sendEmptySuccessResponse: jest.fn(), - sendEntityResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - }; -}); - -jest.mock('../../Services/TeamMemberService'); -jest.mock('../../Services/ProjectService'); - -describe('ProjectAPI', () => { - let mockRequest: OneUptimeRequest; - let mockResponse: OneUptimeResponse; - let nextFunction: NextFunction; - - beforeEach(() => { - new ProjectAPI(); - mockRequest = {} as OneUptimeRequest; - mockResponse = { - send: jest.fn(), - json: jest.fn(), - status: jest.fn().mockReturnThis(), - } as unknown as OneUptimeResponse; - nextFunction = jest.fn(); + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendEntityArrayResponse", + ); + expect(response).toHaveBeenCalledWith( + mockRequest, + mockResponse, + projects, + new PositiveNumber(projects.length), + Project, + ); }); - afterEach(() => { - jest.restoreAllMocks(); + it("should respond with a list of projects by project", async () => { + const mockUserId: ObjectID = new ObjectID("123"); + mockRequest.userAuthorization = { + userId: mockUserId, + } as JSONWebTokenData; + + const mockTeamMembers: Array<TeamMember> = [ + { + userId: mockUserId, + hasAcceptedInvitation: true, + project: { + _id: "project1", + name: "Project 1", + slug: "Project 1", + }, + } as TeamMember, + { + userId: mockUserId, + hasAcceptedInvitation: true, + project: { + _id: "project2", + name: "Project 2", + slug: "Project 2", + }, + } as TeamMember, + ]; + + const projects: Array<Project> = [ + { + _id: "project1", + name: "Project 1", + slug: "Project 1", + } as Project, + { + _id: "project2", + name: "Project 2", + slug: "Project 2", + } as Project, + ]; + + TeamMemberService.findBy = jest.fn().mockResolvedValue(mockTeamMembers); + + await mockRouter + .match("post", "/project/list-user-projects") + .handlerFunction(mockRequest, mockResponse, nextFunction); + + expect(TeamMemberService.findBy).toHaveBeenCalledWith({ + query: { + userId: mockUserId, + hasAcceptedInvitation: true, + }, + select: { + project: { + _id: true, + name: true, + trialEndsAt: true, + paymentProviderPlanId: true, + resellerId: true, + isFeatureFlagMonitorGroupsEnabled: true, + paymentProviderMeteredSubscriptionStatus: true, + paymentProviderSubscriptionStatus: true, + }, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); + + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendEntityArrayResponse", + ); + expect(response).toHaveBeenCalledWith( + mockRequest, + mockResponse, + projects, + new PositiveNumber(projects.length), + Project, + ); }); - describe('POST /project/list-user-projects', () => { - it('should respond with a empty list', async () => { - const mockUserId: ObjectID = new ObjectID('123'); - mockRequest.userAuthorization = { - userId: mockUserId, - } as JSONWebTokenData; + it("should handle authentication error", async () => { + const authError: NotAuthenticatedException = + new NotAuthenticatedException( + "User should be logged in to access this API", + ); + await mockRouter + .match("post", "/project/list-user-projects") + .handlerFunction(mockRequest, mockResponse, nextFunction); - const mockTeamMembers: Array<TeamMember> = [ - { - userId: mockUserId, - hasAcceptedInvitation: true, - } as TeamMember, - ]; - - const projects: Array<Project> = []; - - TeamMemberService.findBy = jest - .fn() - .mockResolvedValue(mockTeamMembers); - - await mockRouter - .match('post', '/project/list-user-projects') - .handlerFunction(mockRequest, mockResponse, nextFunction); - - expect(TeamMemberService.findBy).toHaveBeenCalledWith({ - query: { - userId: mockUserId, - hasAcceptedInvitation: true, - }, - select: { - project: { - _id: true, - name: true, - trialEndsAt: true, - paymentProviderPlanId: true, - resellerId: true, - isFeatureFlagMonitorGroupsEnabled: true, - paymentProviderMeteredSubscriptionStatus: true, - paymentProviderSubscriptionStatus: true, - }, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendEntityArrayResponse' - ); - expect(response).toHaveBeenCalledWith( - mockRequest, - mockResponse, - projects, - new PositiveNumber(projects.length), - Project - ); - }); - - it('should respond with a list of projects by project', async () => { - const mockUserId: ObjectID = new ObjectID('123'); - mockRequest.userAuthorization = { - userId: mockUserId, - } as JSONWebTokenData; - - const mockTeamMembers: Array<TeamMember> = [ - { - userId: mockUserId, - hasAcceptedInvitation: true, - project: { - _id: 'project1', - name: 'Project 1', - slug: 'Project 1', - }, - } as TeamMember, - { - userId: mockUserId, - hasAcceptedInvitation: true, - project: { - _id: 'project2', - name: 'Project 2', - slug: 'Project 2', - }, - } as TeamMember, - ]; - - const projects: Array<Project> = [ - { - _id: 'project1', - name: 'Project 1', - slug: 'Project 1', - } as Project, - { - _id: 'project2', - name: 'Project 2', - slug: 'Project 2', - } as Project, - ]; - - TeamMemberService.findBy = jest - .fn() - .mockResolvedValue(mockTeamMembers); - - await mockRouter - .match('post', '/project/list-user-projects') - .handlerFunction(mockRequest, mockResponse, nextFunction); - - expect(TeamMemberService.findBy).toHaveBeenCalledWith({ - query: { - userId: mockUserId, - hasAcceptedInvitation: true, - }, - select: { - project: { - _id: true, - name: true, - trialEndsAt: true, - paymentProviderPlanId: true, - resellerId: true, - isFeatureFlagMonitorGroupsEnabled: true, - paymentProviderMeteredSubscriptionStatus: true, - paymentProviderSubscriptionStatus: true, - }, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); - - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendEntityArrayResponse' - ); - expect(response).toHaveBeenCalledWith( - mockRequest, - mockResponse, - projects, - new PositiveNumber(projects.length), - Project - ); - }); - - it('should handle authentication error', async () => { - const authError: NotAuthenticatedException = - new NotAuthenticatedException( - 'User should be logged in to access this API' - ); - await mockRouter - .match('post', '/project/list-user-projects') - .handlerFunction(mockRequest, mockResponse, nextFunction); - - expect(nextFunction).toHaveBeenCalledWith(authError); - }); + expect(nextFunction).toHaveBeenCalledWith(authError); }); + }); }); diff --git a/CommonServer/Tests/API/UserSmsApi.test.ts b/CommonServer/Tests/API/UserSmsApi.test.ts index 17d64c0bdc..6be72400d4 100644 --- a/CommonServer/Tests/API/UserSmsApi.test.ts +++ b/CommonServer/Tests/API/UserSmsApi.test.ts @@ -1,278 +1,242 @@ -import UserSmsAPI from '../../API/UserSmsAPI'; -import UserSmsService from '../../Services/UserSmsService'; +import UserSmsAPI from "../../API/UserSmsAPI"; +import UserSmsService from "../../Services/UserSmsService"; import { - NextFunction, - OneUptimeRequest, - OneUptimeResponse, -} from '../../Utils/Express'; -import Response from '../../Utils/Response'; -import { mockRouter } from './Helpers'; -import { describe, expect, it } from '@jest/globals'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import JSONWebTokenData from 'Common/Types/JsonWebTokenData'; -import ObjectID from 'Common/Types/ObjectID'; -import UserSMS from 'Model/Models/UserSMS'; + NextFunction, + OneUptimeRequest, + OneUptimeResponse, +} from "../../Utils/Express"; +import Response from "../../Utils/Response"; +import { mockRouter } from "./Helpers"; +import { describe, expect, it } from "@jest/globals"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import JSONWebTokenData from "Common/Types/JsonWebTokenData"; +import ObjectID from "Common/Types/ObjectID"; +import UserSMS from "Model/Models/UserSMS"; -jest.mock('../../Utils/Express', () => { - return { - getRouter: () => { - return mockRouter; - }, - }; +jest.mock("../../Utils/Express", () => { + return { + getRouter: () => { + return mockRouter; + }, + }; }); -jest.mock('../../Utils/Response', () => { - return { - sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - sendEmptySuccessResponse: jest.fn(), - sendEntityResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - sendErrorResponse: jest.fn().mockImplementation((...args: []) => { - return args; - }), - }; +jest.mock("../../Utils/Response", () => { + return { + sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + sendEmptySuccessResponse: jest.fn(), + sendEntityResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + sendErrorResponse: jest.fn().mockImplementation((...args: []) => { + return args; + }), + }; }); -jest.mock('../../Services/UserSmsService'); +jest.mock("../../Services/UserSmsService"); -describe('UserSmsAPI', () => { - let mockRequest: OneUptimeRequest; - let mockResponse: OneUptimeResponse; - let nextFunction: NextFunction; +describe("UserSmsAPI", () => { + let mockRequest: OneUptimeRequest; + let mockResponse: OneUptimeResponse; + let nextFunction: NextFunction; - beforeEach(() => { - new UserSmsAPI(); - mockRequest = {} as OneUptimeRequest; - mockResponse = { - send: jest.fn(), - json: jest.fn(), - status: jest.fn().mockReturnThis(), - } as unknown as OneUptimeResponse; - nextFunction = jest.fn(); + beforeEach(() => { + new UserSmsAPI(); + mockRequest = {} as OneUptimeRequest; + mockResponse = { + send: jest.fn(), + json: jest.fn(), + status: jest.fn().mockReturnThis(), + } as unknown as OneUptimeResponse; + nextFunction = jest.fn(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe("POST /user-sms/verify", () => { + it("should handle required item ID", async () => { + const error: BadDataException = new BadDataException("Invalid item ID"); + mockRequest.body = {}; + await mockRouter + .match("post", "/user-sms/verify") + .handlerFunction(mockRequest, mockResponse, nextFunction); + + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendErrorResponse", + ); + expect(response).toHaveBeenCalledWith(mockRequest, mockResponse, error); }); - afterEach(() => { - jest.restoreAllMocks(); + it("should handle required code", async () => { + const error: BadDataException = new BadDataException("Invalid code"); + mockRequest.body = { + itemId: "item1", + }; + await mockRouter + .match("post", "/user-sms/verify") + .handlerFunction(mockRequest, mockResponse, nextFunction); + + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendErrorResponse", + ); + expect(response).toHaveBeenCalledWith(mockRequest, mockResponse, error); }); - describe('POST /user-sms/verify', () => { - it('should handle required item ID', async () => { - const error: BadDataException = new BadDataException( - 'Invalid item ID' - ); - mockRequest.body = {}; - await mockRouter - .match('post', '/user-sms/verify') - .handlerFunction(mockRequest, mockResponse, nextFunction); + it("should handle Item not found", async () => { + const error: BadDataException = new BadDataException("Item not found"); + mockRequest.body = { + itemId: "item1", + code: 123456, + }; + UserSmsService.findOneById = jest.fn().mockResolvedValue(null); - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendErrorResponse' - ); - expect(response).toHaveBeenCalledWith( - mockRequest, - mockResponse, - error - ); - }); + await mockRouter + .match("post", "/user-sms/verify") + .handlerFunction(mockRequest, mockResponse, nextFunction); - it('should handle required code', async () => { - const error: BadDataException = new BadDataException( - 'Invalid code' - ); - mockRequest.body = { - itemId: 'item1', - }; - await mockRouter - .match('post', '/user-sms/verify') - .handlerFunction(mockRequest, mockResponse, nextFunction); - - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendErrorResponse' - ); - expect(response).toHaveBeenCalledWith( - mockRequest, - mockResponse, - error - ); - }); - - it('should handle Item not found', async () => { - const error: BadDataException = new BadDataException( - 'Item not found' - ); - mockRequest.body = { - itemId: 'item1', - code: 123456, - }; - UserSmsService.findOneById = jest.fn().mockResolvedValue(null); - - await mockRouter - .match('post', '/user-sms/verify') - .handlerFunction(mockRequest, mockResponse, nextFunction); - - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendErrorResponse' - ); - expect(response).toHaveBeenCalledWith( - mockRequest, - mockResponse, - error - ); - }); - - it('should handle Invalid user ID', async () => { - const error: BadDataException = new BadDataException( - 'Invalid user ID' - ); - mockRequest.body = { - itemId: 'item1', - code: '123456', - }; - mockRequest.userAuthorization = { - userId: new ObjectID('user123'), - } as JSONWebTokenData; - - const item: UserSMS = { - _id: '123', - userId: new ObjectID('user321'), - } as UserSMS; - - UserSmsService.findOneById = jest.fn().mockResolvedValue(item); - - await mockRouter - .match('post', '/user-sms/verify') - .handlerFunction(mockRequest, mockResponse, nextFunction); - - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendErrorResponse' - ); - expect(response).toHaveBeenCalledWith( - mockRequest, - mockResponse, - error - ); - }); - - it('should handle Invalid code', async () => { - const error: BadDataException = new BadDataException( - 'Invalid code' - ); - mockRequest.body = { - itemId: 'item1', - code: '123456', - }; - mockRequest.userAuthorization = { - userId: new ObjectID('user123'), - } as JSONWebTokenData; - - const item: UserSMS = { - _id: '123', - userId: new ObjectID('user123'), - verificationCode: '123457', - } as UserSMS; - - UserSmsService.findOneById = jest.fn().mockResolvedValue(item); - - await mockRouter - .match('post', '/user-sms/verify') - .handlerFunction(mockRequest, mockResponse, nextFunction); - - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendErrorResponse' - ); - expect(response).toHaveBeenCalledWith( - mockRequest, - mockResponse, - error - ); - }); - - it('should handle valid response on verify', async () => { - mockRequest.body = { - itemId: 'item1', - code: '123456', - }; - mockRequest.userAuthorization = { - userId: new ObjectID('user123'), - } as JSONWebTokenData; - - const item: UserSMS = { - _id: '123', - userId: new ObjectID('user123'), - verificationCode: '123456', - } as UserSMS; - - UserSmsService.findOneById = jest.fn().mockResolvedValue(item); - - await mockRouter - .match('post', '/user-sms/verify') - .handlerFunction(mockRequest, mockResponse, nextFunction); - - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendEmptySuccessResponse' - ); - expect(response).toHaveBeenCalledWith(mockRequest, mockResponse); - }); + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendErrorResponse", + ); + expect(response).toHaveBeenCalledWith(mockRequest, mockResponse, error); }); - describe('POST /user-sms/resend-verification-code', () => { - it('should handle required item ID', async () => { - const error: BadDataException = new BadDataException( - 'Invalid item ID' - ); - mockRequest.body = {}; - await mockRouter - .match('post', '/user-sms/resend-verification-code') - .handlerFunction(mockRequest, mockResponse, nextFunction); + it("should handle Invalid user ID", async () => { + const error: BadDataException = new BadDataException("Invalid user ID"); + mockRequest.body = { + itemId: "item1", + code: "123456", + }; + mockRequest.userAuthorization = { + userId: new ObjectID("user123"), + } as JSONWebTokenData; - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendErrorResponse' - ); - expect(response).toHaveBeenCalledWith( - mockRequest, - mockResponse, - error - ); - }); + const item: UserSMS = { + _id: "123", + userId: new ObjectID("user321"), + } as UserSMS; - it('should handle valid response resend', async () => { - mockRequest.body = { - itemId: 'item1', - code: '123456', - }; + UserSmsService.findOneById = jest.fn().mockResolvedValue(item); - mockRequest.userAuthorization = { - userId: new ObjectID('user123'), - } as JSONWebTokenData; + await mockRouter + .match("post", "/user-sms/verify") + .handlerFunction(mockRequest, mockResponse, nextFunction); - UserSmsService.resendVerificationCode = jest - .fn() - .mockImplementation(() => { - return Promise.resolve(); - }); - - await mockRouter - .match('post', '/user-sms/verify') - .handlerFunction(mockRequest, mockResponse, nextFunction); - - const response: jest.SpyInstance = jest.spyOn( - Response, - 'sendEmptySuccessResponse' - ); - expect(response).toHaveBeenCalledWith(mockRequest, mockResponse); - }); + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendErrorResponse", + ); + expect(response).toHaveBeenCalledWith(mockRequest, mockResponse, error); }); + + it("should handle Invalid code", async () => { + const error: BadDataException = new BadDataException("Invalid code"); + mockRequest.body = { + itemId: "item1", + code: "123456", + }; + mockRequest.userAuthorization = { + userId: new ObjectID("user123"), + } as JSONWebTokenData; + + const item: UserSMS = { + _id: "123", + userId: new ObjectID("user123"), + verificationCode: "123457", + } as UserSMS; + + UserSmsService.findOneById = jest.fn().mockResolvedValue(item); + + await mockRouter + .match("post", "/user-sms/verify") + .handlerFunction(mockRequest, mockResponse, nextFunction); + + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendErrorResponse", + ); + expect(response).toHaveBeenCalledWith(mockRequest, mockResponse, error); + }); + + it("should handle valid response on verify", async () => { + mockRequest.body = { + itemId: "item1", + code: "123456", + }; + mockRequest.userAuthorization = { + userId: new ObjectID("user123"), + } as JSONWebTokenData; + + const item: UserSMS = { + _id: "123", + userId: new ObjectID("user123"), + verificationCode: "123456", + } as UserSMS; + + UserSmsService.findOneById = jest.fn().mockResolvedValue(item); + + await mockRouter + .match("post", "/user-sms/verify") + .handlerFunction(mockRequest, mockResponse, nextFunction); + + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendEmptySuccessResponse", + ); + expect(response).toHaveBeenCalledWith(mockRequest, mockResponse); + }); + }); + + describe("POST /user-sms/resend-verification-code", () => { + it("should handle required item ID", async () => { + const error: BadDataException = new BadDataException("Invalid item ID"); + mockRequest.body = {}; + await mockRouter + .match("post", "/user-sms/resend-verification-code") + .handlerFunction(mockRequest, mockResponse, nextFunction); + + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendErrorResponse", + ); + expect(response).toHaveBeenCalledWith(mockRequest, mockResponse, error); + }); + + it("should handle valid response resend", async () => { + mockRequest.body = { + itemId: "item1", + code: "123456", + }; + + mockRequest.userAuthorization = { + userId: new ObjectID("user123"), + } as JSONWebTokenData; + + UserSmsService.resendVerificationCode = jest + .fn() + .mockImplementation(() => { + return Promise.resolve(); + }); + + await mockRouter + .match("post", "/user-sms/verify") + .handlerFunction(mockRequest, mockResponse, nextFunction); + + const response: jest.SpyInstance = jest.spyOn( + Response, + "sendEmptySuccessResponse", + ); + expect(response).toHaveBeenCalledWith(mockRequest, mockResponse); + }); + }); }); diff --git a/CommonServer/Tests/Middleware/BearerTokenAuthorization.test.ts b/CommonServer/Tests/Middleware/BearerTokenAuthorization.test.ts index c72bbbbd9e..338c5a57ae 100644 --- a/CommonServer/Tests/Middleware/BearerTokenAuthorization.test.ts +++ b/CommonServer/Tests/Middleware/BearerTokenAuthorization.test.ts @@ -1,96 +1,70 @@ -import BearerTokenAuthorization from '../../Middleware/BearerTokenAuthorization'; -import { ExpressResponse, OneUptimeRequest } from '../../Utils/Express'; -import JSONWebToken from '../../Utils/JsonWebToken'; -import { describe, expect, it } from '@jest/globals'; -import { JSONObject } from 'Common/Types/JSON'; +import BearerTokenAuthorization from "../../Middleware/BearerTokenAuthorization"; +import { ExpressResponse, OneUptimeRequest } from "../../Utils/Express"; +import JSONWebToken from "../../Utils/JsonWebToken"; +import { describe, expect, it } from "@jest/globals"; +import { JSONObject } from "Common/Types/JSON"; -describe('BearerTokenAuthorization', () => { - describe('isAuthorizedBearerToken', () => { - it('adds decoded token data to request', () => { - const jsonObj: JSONObject = { test: 'test' }; - const req: OneUptimeRequest = { - headers: { - authorization: `Bearer ${JSONWebToken.signJsonPayload( - jsonObj, - 5 - )}`, - }, - } as OneUptimeRequest; - const res: ExpressResponse = {} as ExpressResponse; - const next: jest.Mock = jest.fn(); - void BearerTokenAuthorization.isAuthorizedBearerToken( - req, - res, - next - ); - const jsonObjResult: JSONObject = req.bearerTokenData as JSONObject; - expect(jsonObjResult['test']).toMatchInlineSnapshot(`"test"`); - }); - it('calls next without arguments if token is valid', () => { - const jsonObj: JSONObject = { test: 'test' }; - const req: OneUptimeRequest = { - headers: { - authorization: `Bearer ${JSONWebToken.signJsonPayload( - jsonObj, - 5 - )}`, - }, - } as OneUptimeRequest; - const res: ExpressResponse = {} as ExpressResponse; - const next: jest.Mock = jest.fn(); - void BearerTokenAuthorization.isAuthorizedBearerToken( - req, - res, - next - ); - expect(next.mock.calls[0][0]).toMatchInlineSnapshot(`undefined`); - }); - it('calls next with exception if token is empty', () => { - const req: OneUptimeRequest = { - headers: { - authorization: '', - }, - } as OneUptimeRequest; - const res: ExpressResponse = {} as ExpressResponse; - const next: jest.Mock = jest.fn(); - void BearerTokenAuthorization.isAuthorizedBearerToken( - req, - res, - next - ); - expect(next.mock.calls[0][0]).toMatchInlineSnapshot( - `[Error: Invalid bearer token, or bearer token not provided.]` - ); - }); - it('calls next with exception if token is invalid', () => { - const req: OneUptimeRequest = { - headers: { - authorization: 'Bearer ', - }, - } as OneUptimeRequest; - const res: ExpressResponse = {} as ExpressResponse; - const next: jest.Mock = jest.fn(); - void BearerTokenAuthorization.isAuthorizedBearerToken( - req, - res, - next - ); - expect(next.mock.calls[0][0]).toMatchInlineSnapshot( - `[Error: Invalid bearer token, or bearer token not provided.]` - ); - }); - it('calls next with exception if token header is not present', () => { - const req: OneUptimeRequest = {} as OneUptimeRequest; - const res: ExpressResponse = {} as ExpressResponse; - const next: jest.Mock = jest.fn(); - void BearerTokenAuthorization.isAuthorizedBearerToken( - req, - res, - next - ); - expect(next.mock.calls[0][0]).toMatchInlineSnapshot( - `[Error: Invalid bearer token, or bearer token not provided.]` - ); - }); +describe("BearerTokenAuthorization", () => { + describe("isAuthorizedBearerToken", () => { + it("adds decoded token data to request", () => { + const jsonObj: JSONObject = { test: "test" }; + const req: OneUptimeRequest = { + headers: { + authorization: `Bearer ${JSONWebToken.signJsonPayload(jsonObj, 5)}`, + }, + } as OneUptimeRequest; + const res: ExpressResponse = {} as ExpressResponse; + const next: jest.Mock = jest.fn(); + void BearerTokenAuthorization.isAuthorizedBearerToken(req, res, next); + const jsonObjResult: JSONObject = req.bearerTokenData as JSONObject; + expect(jsonObjResult["test"]).toMatchInlineSnapshot(`"test"`); }); + it("calls next without arguments if token is valid", () => { + const jsonObj: JSONObject = { test: "test" }; + const req: OneUptimeRequest = { + headers: { + authorization: `Bearer ${JSONWebToken.signJsonPayload(jsonObj, 5)}`, + }, + } as OneUptimeRequest; + const res: ExpressResponse = {} as ExpressResponse; + const next: jest.Mock = jest.fn(); + void BearerTokenAuthorization.isAuthorizedBearerToken(req, res, next); + expect(next.mock.calls[0][0]).toMatchInlineSnapshot(`undefined`); + }); + it("calls next with exception if token is empty", () => { + const req: OneUptimeRequest = { + headers: { + authorization: "", + }, + } as OneUptimeRequest; + const res: ExpressResponse = {} as ExpressResponse; + const next: jest.Mock = jest.fn(); + void BearerTokenAuthorization.isAuthorizedBearerToken(req, res, next); + expect(next.mock.calls[0][0]).toMatchInlineSnapshot( + `[Error: Invalid bearer token, or bearer token not provided.]`, + ); + }); + it("calls next with exception if token is invalid", () => { + const req: OneUptimeRequest = { + headers: { + authorization: "Bearer ", + }, + } as OneUptimeRequest; + const res: ExpressResponse = {} as ExpressResponse; + const next: jest.Mock = jest.fn(); + void BearerTokenAuthorization.isAuthorizedBearerToken(req, res, next); + expect(next.mock.calls[0][0]).toMatchInlineSnapshot( + `[Error: Invalid bearer token, or bearer token not provided.]`, + ); + }); + it("calls next with exception if token header is not present", () => { + const req: OneUptimeRequest = {} as OneUptimeRequest; + const res: ExpressResponse = {} as ExpressResponse; + const next: jest.Mock = jest.fn(); + void BearerTokenAuthorization.isAuthorizedBearerToken(req, res, next); + expect(next.mock.calls[0][0]).toMatchInlineSnapshot( + `[Error: Invalid bearer token, or bearer token not provided.]`, + ); + }); + }); }); diff --git a/CommonServer/Tests/Middleware/ClusterKeyAuthorization.test.ts b/CommonServer/Tests/Middleware/ClusterKeyAuthorization.test.ts index 1558bd6f8b..dd2487d6bf 100644 --- a/CommonServer/Tests/Middleware/ClusterKeyAuthorization.test.ts +++ b/CommonServer/Tests/Middleware/ClusterKeyAuthorization.test.ts @@ -1,103 +1,102 @@ -import { ClusterKey as ONEUPTIME_SECRET } from '../../EnvironmentConfig'; -import ClusterKeyAuthorization from '../../Middleware/ClusterKeyAuthorization'; +import { ClusterKey as ONEUPTIME_SECRET } from "../../EnvironmentConfig"; +import ClusterKeyAuthorization from "../../Middleware/ClusterKeyAuthorization"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../../Utils/Express'; -import Response from '../../Utils/Response'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../../Utils/Express"; +import Response from "../../Utils/Response"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; -describe('ClusterKeyAuthorization', () => { - describe('getClusterKeyHeaders', () => { - test('should return cluster key headers', () => { - const mockedResult: Dictionary<string> = { - clusterkey: ONEUPTIME_SECRET.toString(), - }; +describe("ClusterKeyAuthorization", () => { + describe("getClusterKeyHeaders", () => { + test("should return cluster key headers", () => { + const mockedResult: Dictionary<string> = { + clusterkey: ONEUPTIME_SECRET.toString(), + }; - const result: Dictionary<string> = - ClusterKeyAuthorization.getClusterKeyHeaders(); + const result: Dictionary<string> = + ClusterKeyAuthorization.getClusterKeyHeaders(); - expect(result).toStrictEqual(mockedResult); - }); + expect(result).toStrictEqual(mockedResult); + }); + }); + + describe("isAuthorizedServiceMiddleware", () => { + const clusterKey: string = ONEUPTIME_SECRET.toString(); + + const mockedValidRequestFields: string[] = [ + "params", + "query", + "headers", + "body", + ]; + const res: ExpressResponse = {} as ExpressResponse; + const next: NextFunction = jest.fn(); + + describe("should call function 'next' when valid clusterKey is passed as request's", () => { + test.each(mockedValidRequestFields)("%s", async (field: string) => { + const data: Dictionary<string> = { + [field === "headers" ? "clusterkey" : "clusterKey"]: clusterKey, + }; + + const req: Partial<ExpressRequest> = { [field]: data }; + + await ClusterKeyAuthorization.isAuthorizedServiceMiddleware( + req as ExpressRequest, + res, + next, + ); + + expect(next).toBeCalled(); + }); }); - describe('isAuthorizedServiceMiddleware', () => { - const clusterKey: string = ONEUPTIME_SECRET.toString(); + test("should call Response.sendErrorResponse when clusterKey is not passed", async () => { + const req: ExpressRequest = {} as ExpressRequest; - const mockedValidRequestFields: string[] = [ - 'params', - 'query', - 'headers', - 'body', - ]; - const res: ExpressResponse = {} as ExpressResponse; - const next: NextFunction = jest.fn(); + const spySendErrorResponse: jest.SpyInstance = jest + .spyOn(Response, "sendErrorResponse") + .mockImplementation(jest.fn()); - describe("should call function 'next' when valid clusterKey is passed as request's", () => { - test.each(mockedValidRequestFields)('%s', async (field: string) => { - const data: Dictionary<string> = { - [field === 'headers' ? 'clusterkey' : 'clusterKey']: - clusterKey, - }; + await ClusterKeyAuthorization.isAuthorizedServiceMiddleware( + req, + res, + next, + ); - const req: Partial<ExpressRequest> = { [field]: data }; - - await ClusterKeyAuthorization.isAuthorizedServiceMiddleware( - req as ExpressRequest, - res, - next - ); - - expect(next).toBeCalled(); - }); - }); - - test('should call Response.sendErrorResponse when clusterKey is not passed', async () => { - const req: ExpressRequest = {} as ExpressRequest; - - const spySendErrorResponse: jest.SpyInstance = jest - .spyOn(Response, 'sendErrorResponse') - .mockImplementation(jest.fn()); - - await ClusterKeyAuthorization.isAuthorizedServiceMiddleware( - req, - res, - next - ); - - expect(spySendErrorResponse).toHaveBeenCalledWith( - req, - res, - new BadDataException('Cluster key not found.') - ); - }); - - describe("should call Response.sendErrorResponse when invalid clusterKey is passed as request's", () => { - test.each(mockedValidRequestFields)('%s', async (field: string) => { - const data: Dictionary<string> = { - [field === 'headers' ? 'clusterkey' : 'clusterKey']: 'sec', - }; - - const req: Partial<ExpressRequest> = { [field]: data }; - - const spySendErrorResponse: jest.SpyInstance = jest - .spyOn(Response, 'sendErrorResponse') - .mockImplementation(jest.fn()); - - await ClusterKeyAuthorization.isAuthorizedServiceMiddleware( - req as ExpressRequest, - res, - next - ); - - expect(spySendErrorResponse).toHaveBeenCalledWith( - req, - res, - new BadDataException('Invalid cluster key provided') - ); - }); - }); + expect(spySendErrorResponse).toHaveBeenCalledWith( + req, + res, + new BadDataException("Cluster key not found."), + ); }); + + describe("should call Response.sendErrorResponse when invalid clusterKey is passed as request's", () => { + test.each(mockedValidRequestFields)("%s", async (field: string) => { + const data: Dictionary<string> = { + [field === "headers" ? "clusterkey" : "clusterKey"]: "sec", + }; + + const req: Partial<ExpressRequest> = { [field]: data }; + + const spySendErrorResponse: jest.SpyInstance = jest + .spyOn(Response, "sendErrorResponse") + .mockImplementation(jest.fn()); + + await ClusterKeyAuthorization.isAuthorizedServiceMiddleware( + req as ExpressRequest, + res, + next, + ); + + expect(spySendErrorResponse).toHaveBeenCalledWith( + req, + res, + new BadDataException("Invalid cluster key provided"), + ); + }); + }); + }); }); diff --git a/CommonServer/Tests/Middleware/NotificationMiddleware.test.ts b/CommonServer/Tests/Middleware/NotificationMiddleware.test.ts index 8180cfd735..a84a2388de 100644 --- a/CommonServer/Tests/Middleware/NotificationMiddleware.test.ts +++ b/CommonServer/Tests/Middleware/NotificationMiddleware.test.ts @@ -1,184 +1,182 @@ -import NotificationMiddleware from '../../Middleware/NotificationMiddleware'; +import NotificationMiddleware from "../../Middleware/NotificationMiddleware"; // Types import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../../Utils/Express'; -import JSONWebToken from '../../Utils/JsonWebToken'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../../Utils/Express"; +import JSONWebToken from "../../Utils/JsonWebToken"; // Helpers -import Response from '../../Utils/Response'; -import { OnCallInputRequest } from 'Common/Types/Call/CallRequest'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import VoiceResponse from 'twilio/lib/twiml/VoiceResponse'; +import Response from "../../Utils/Response"; +import { OnCallInputRequest } from "Common/Types/Call/CallRequest"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import VoiceResponse from "twilio/lib/twiml/VoiceResponse"; -jest.mock('twilio/lib/twiml/VoiceResponse'); -jest.mock('../../Utils/Response'); -jest.mock('../../Utils/JsonWebToken', () => { - return { - decodeJsonPayload: jest.fn(), - }; +jest.mock("twilio/lib/twiml/VoiceResponse"); +jest.mock("../../Utils/Response"); +jest.mock("../../Utils/JsonWebToken", () => { + return { + decodeJsonPayload: jest.fn(), + }; }); -jest.mock('Common/Types/JSONFunctions', () => { - return { - deserialize: jest.fn(), - }; +jest.mock("Common/Types/JSONFunctions", () => { + return { + deserialize: jest.fn(), + }; }); -describe('NotificationMiddleware', () => { - describe('sendResponse', () => { - let mockRequest: ExpressRequest; - let mockResponse: ExpressResponse; - let onCallInputRequest: OnCallInputRequest; +describe("NotificationMiddleware", () => { + describe("sendResponse", () => { + let mockRequest: ExpressRequest; + let mockResponse: ExpressResponse; + let onCallInputRequest: OnCallInputRequest; - beforeEach(() => { - mockRequest = { body: { Digits: '1234' } } as ExpressRequest; - mockResponse = {} as ExpressResponse; - onCallInputRequest = { - default: { sayMessage: 'default message' }, - }; - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - test('Should return correct message for a valid Digits value', async () => { - onCallInputRequest['1234'] = { sayMessage: 'message 1' }; - const responseInstance: VoiceResponse = new VoiceResponse(); - - ( - VoiceResponse as jest.MockedClass<typeof VoiceResponse> - ).mockImplementation(() => { - return responseInstance; - }); - await NotificationMiddleware.sendResponse( - mockRequest, - mockResponse, - onCallInputRequest - ); - - expect(responseInstance.say).toHaveBeenCalledWith( - (onCallInputRequest['1234'] as any).sayMessage - ); - expect(Response.sendXmlResponse).toHaveBeenCalledWith( - mockRequest, - mockResponse, - responseInstance.toString() - ); - }); - - test('Should return default message for an invalid Digits value', async () => { - const responseInstance: VoiceResponse = new VoiceResponse(); - - ( - VoiceResponse as jest.MockedClass<typeof VoiceResponse> - ).mockImplementation(() => { - return responseInstance; - }); - await NotificationMiddleware.sendResponse( - mockRequest, - mockResponse, - onCallInputRequest - ); - - expect(responseInstance.say).toHaveBeenCalledWith( - onCallInputRequest['default']?.sayMessage - ); - expect(Response.sendXmlResponse).toHaveBeenCalledWith( - mockRequest, - mockResponse, - responseInstance.toString() - ); - }); + beforeEach(() => { + mockRequest = { body: { Digits: "1234" } } as ExpressRequest; + mockResponse = {} as ExpressResponse; + onCallInputRequest = { + default: { sayMessage: "default message" }, + }; }); - describe('isValidCallNotificationRequest', () => { - let mockRequest: ExpressRequest; - let mockResponse: ExpressResponse; - let mockNext: NextFunction; - - beforeEach(() => { - mockRequest = { - body: {}, - query: {}, - } as ExpressRequest; - mockResponse = {} as ExpressResponse; - mockNext = jest.fn() as NextFunction; - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - test('Should return error if Digits is not in req body', async () => { - await NotificationMiddleware.isValidCallNotificationRequest( - mockRequest, - mockResponse, - mockNext - ); - - expect(Response.sendErrorResponse).toHaveBeenCalledWith( - mockRequest, - mockResponse, - new BadDataException('Invalid input') - ); - }); - - test('Should return error if Token is not in req query', async () => { - mockRequest.body['Digits'] = '1234'; - - await NotificationMiddleware.isValidCallNotificationRequest( - mockRequest, - mockResponse, - mockNext - ); - - expect(Response.sendErrorResponse).toHaveBeenCalledWith( - mockRequest, - mockResponse, - new BadDataException('Invalid token') - ); - }); - - test('Should return error if token decoding fails', async () => { - mockRequest.body['Digits'] = '1234'; - mockRequest.query['token'] = 'token'; - - jest.spyOn(JSONWebToken, 'decodeJsonPayload').mockImplementation( - () => { - throw new Error('Decoding error'); - } - ); - await NotificationMiddleware.isValidCallNotificationRequest( - mockRequest, - mockResponse, - mockNext - ); - - expect(Response.sendErrorResponse).toHaveBeenCalledWith( - mockRequest, - mockResponse, - new BadDataException('Invalid token') - ); - }); - - test("Should call 'next' if data is valid", async () => { - mockRequest.body['Digits'] = '1234'; - mockRequest.query['token'] = 'token'; - const tokenData: JSONObject = { id: 1 }; - - jest.spyOn(JSONFunctions, 'deserialize').mockReturnValue(tokenData); - await NotificationMiddleware.isValidCallNotificationRequest( - mockRequest, - mockResponse, - mockNext - ); - - expect(mockNext).toHaveBeenCalled(); - expect((mockRequest as any).callTokenData).toEqual(tokenData); - }); + afterEach(() => { + jest.resetAllMocks(); }); + + test("Should return correct message for a valid Digits value", async () => { + onCallInputRequest["1234"] = { sayMessage: "message 1" }; + const responseInstance: VoiceResponse = new VoiceResponse(); + + ( + VoiceResponse as jest.MockedClass<typeof VoiceResponse> + ).mockImplementation(() => { + return responseInstance; + }); + await NotificationMiddleware.sendResponse( + mockRequest, + mockResponse, + onCallInputRequest, + ); + + expect(responseInstance.say).toHaveBeenCalledWith( + (onCallInputRequest["1234"] as any).sayMessage, + ); + expect(Response.sendXmlResponse).toHaveBeenCalledWith( + mockRequest, + mockResponse, + responseInstance.toString(), + ); + }); + + test("Should return default message for an invalid Digits value", async () => { + const responseInstance: VoiceResponse = new VoiceResponse(); + + ( + VoiceResponse as jest.MockedClass<typeof VoiceResponse> + ).mockImplementation(() => { + return responseInstance; + }); + await NotificationMiddleware.sendResponse( + mockRequest, + mockResponse, + onCallInputRequest, + ); + + expect(responseInstance.say).toHaveBeenCalledWith( + onCallInputRequest["default"]?.sayMessage, + ); + expect(Response.sendXmlResponse).toHaveBeenCalledWith( + mockRequest, + mockResponse, + responseInstance.toString(), + ); + }); + }); + + describe("isValidCallNotificationRequest", () => { + let mockRequest: ExpressRequest; + let mockResponse: ExpressResponse; + let mockNext: NextFunction; + + beforeEach(() => { + mockRequest = { + body: {}, + query: {}, + } as ExpressRequest; + mockResponse = {} as ExpressResponse; + mockNext = jest.fn() as NextFunction; + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test("Should return error if Digits is not in req body", async () => { + await NotificationMiddleware.isValidCallNotificationRequest( + mockRequest, + mockResponse, + mockNext, + ); + + expect(Response.sendErrorResponse).toHaveBeenCalledWith( + mockRequest, + mockResponse, + new BadDataException("Invalid input"), + ); + }); + + test("Should return error if Token is not in req query", async () => { + mockRequest.body["Digits"] = "1234"; + + await NotificationMiddleware.isValidCallNotificationRequest( + mockRequest, + mockResponse, + mockNext, + ); + + expect(Response.sendErrorResponse).toHaveBeenCalledWith( + mockRequest, + mockResponse, + new BadDataException("Invalid token"), + ); + }); + + test("Should return error if token decoding fails", async () => { + mockRequest.body["Digits"] = "1234"; + mockRequest.query["token"] = "token"; + + jest.spyOn(JSONWebToken, "decodeJsonPayload").mockImplementation(() => { + throw new Error("Decoding error"); + }); + await NotificationMiddleware.isValidCallNotificationRequest( + mockRequest, + mockResponse, + mockNext, + ); + + expect(Response.sendErrorResponse).toHaveBeenCalledWith( + mockRequest, + mockResponse, + new BadDataException("Invalid token"), + ); + }); + + test("Should call 'next' if data is valid", async () => { + mockRequest.body["Digits"] = "1234"; + mockRequest.query["token"] = "token"; + const tokenData: JSONObject = { id: 1 }; + + jest.spyOn(JSONFunctions, "deserialize").mockReturnValue(tokenData); + await NotificationMiddleware.isValidCallNotificationRequest( + mockRequest, + mockResponse, + mockNext, + ); + + expect(mockNext).toHaveBeenCalled(); + expect((mockRequest as any).callTokenData).toEqual(tokenData); + }); + }); }); diff --git a/CommonServer/Tests/Middleware/ProjectAuthorization.test.ts b/CommonServer/Tests/Middleware/ProjectAuthorization.test.ts index 58a52023cc..05c573bbc5 100644 --- a/CommonServer/Tests/Middleware/ProjectAuthorization.test.ts +++ b/CommonServer/Tests/Middleware/ProjectAuthorization.test.ts @@ -1,293 +1,283 @@ -import ProjectMiddleware from '../../Middleware/ProjectAuthorization'; -import AccessTokenService from '../../Services/AccessTokenService'; -import ApiKeyService from '../../Services/ApiKeyService'; -import GlobalConfigService from '../../Services/GlobalConfigService'; -import QueryHelper from '../../Types/Database/QueryHelper'; +import ProjectMiddleware from "../../Middleware/ProjectAuthorization"; +import AccessTokenService from "../../Services/AccessTokenService"; +import ApiKeyService from "../../Services/ApiKeyService"; +import GlobalConfigService from "../../Services/GlobalConfigService"; +import QueryHelper from "../../Types/Database/QueryHelper"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../../Utils/Express'; -import Database from '../TestingUtils/Database'; -import '../TestingUtils/Init'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import { UserTenantAccessPermission } from 'Common/Types/Permission'; -import ApiKey from 'Model/Models/ApiKey'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../../Utils/Express"; +import Database from "../TestingUtils/Database"; +import "../TestingUtils/Init"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import { UserTenantAccessPermission } from "Common/Types/Permission"; +import ApiKey from "Model/Models/ApiKey"; -jest.mock('../../Services/ApiKeyService'); -jest.mock('../../Services/AccessTokenService'); +jest.mock("../../Services/ApiKeyService"); +jest.mock("../../Services/AccessTokenService"); type ObjectIdOrNull = ObjectID | null; -describe('ProjectMiddleware', () => { - const mockedObjectId: ObjectID = ObjectID.generate(); +describe("ProjectMiddleware", () => { + const mockedObjectId: ObjectID = ObjectID.generate(); - describe('getProjectId', () => { - describe("should return value when tenantid is passed in the request's", () => { - const reqFields: string[] = ['params', 'query', 'headers']; - test.each(reqFields)('%s', (field: string) => { - const req: Partial<ExpressRequest> = { - [field]: { tenantid: mockedObjectId.toString() }, - }; + describe("getProjectId", () => { + describe("should return value when tenantid is passed in the request's", () => { + const reqFields: string[] = ["params", "query", "headers"]; + test.each(reqFields)("%s", (field: string) => { + const req: Partial<ExpressRequest> = { + [field]: { tenantid: mockedObjectId.toString() }, + }; - const result: ObjectIdOrNull = ProjectMiddleware.getProjectId( - req as ExpressRequest - ); - - expect(result).toEqual(mockedObjectId); - }); - }); - - test("should return value when projectid is passed in the request's header", () => { - const req: Partial<ExpressRequest> = { - headers: { projectid: mockedObjectId.toString() }, - }; - - const result: ObjectIdOrNull = ProjectMiddleware.getProjectId( - req as ExpressRequest - ); - - expect(result).toEqual(mockedObjectId); - }); - - test("should return value when projectId is passed in the request's body", () => { - const req: Partial<ExpressRequest> = { - body: { projectId: mockedObjectId.toString() }, - }; - - const result: ObjectIdOrNull = ProjectMiddleware.getProjectId( - req as ExpressRequest - ); - - expect(result).toEqual(mockedObjectId); - }); - - test('should return null when projectId is not passed in the request', () => { - const result: ObjectIdOrNull = ProjectMiddleware.getProjectId( - {} as ExpressRequest - ); - - expect(result).toBeNull(); - }); - }); - - describe('getApiKey', () => { - test("should return apiKey when apikey is passed in the request's header", () => { - const req: Partial<ExpressRequest> = { - headers: { apikey: mockedObjectId.toString() }, - }; - - const result: ObjectIdOrNull = ProjectMiddleware.getApiKey( - req as ExpressRequest - ); - - expect(result).toEqual(mockedObjectId); - }); - - test("should return null when apikey is not passed in the request's header", () => { - const result: ObjectIdOrNull = ProjectMiddleware.getApiKey( - {} as ExpressRequest - ); - - expect(result).toBeNull(); - }); - }); - - describe('hasApiKey', () => { - const req: ExpressRequest = { headers: {} } as ExpressRequest; - - test('should return true when getApiKey returns a non-null value', () => { - req.headers['apikey'] = mockedObjectId.toString(); - - const result: boolean = ProjectMiddleware.hasApiKey(req); - - expect(result).toStrictEqual(true); - }); - - test('should return false when getApiKey returns null', () => { - req.headers['apikey'] = undefined; - - const result: boolean = ProjectMiddleware.hasApiKey(req); - - expect(result).toStrictEqual(false); - }); - }); - - describe('hasProjectID', () => { - const req: ExpressRequest = { headers: {} } as ExpressRequest; - test('should return true when getProjectId returns a non-null value', () => { - req.headers['tenantid'] = mockedObjectId.toString(); - - const result: boolean = ProjectMiddleware.hasProjectID(req); - - expect(result).toStrictEqual(true); - }); - - test('should return false when getProjectId returns null', () => { - req.headers['tenantid'] = undefined; - - const result: boolean = ProjectMiddleware.hasProjectID(req); - - expect(result).toStrictEqual(false); - }); - }); - - describe('isValidProjectIdAndApiKeyMiddleware', () => { - const req: ExpressRequest = {} as ExpressRequest; - const res: ExpressResponse = {} as ExpressResponse; - let next: NextFunction = jest.fn(); - - const mockedApiModel: ApiKey = { - id: mockedObjectId, - } as ApiKey; - - let database!: Database; - - beforeEach( - async () => { - jest.clearAllMocks(); - next = jest.fn(); - database = new Database(); - await database.createAndConnect(); - - if (req.headers === undefined) { - req.headers = {}; - } - - req.headers['tenantid'] = mockedObjectId.toString(); - req.headers['apikey'] = mockedObjectId.toString(); - }, - 10 * 1000 // 10 second timeout because setting up the DB is slow + const result: ObjectIdOrNull = ProjectMiddleware.getProjectId( + req as ExpressRequest, ); - afterEach(async () => { - await database.disconnectAndDropDatabase(); - }); - - test('should throw BadDataException when getProjectId returns null', async () => { - const spyFindOneBy: jest.SpyInstance = jest - .spyOn(GlobalConfigService, 'findOneBy') - .mockResolvedValue(null); - - req.headers['tenantid'] = undefined; - req.headers['apikey'] = mockedObjectId.toString(); - - await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( - req, - res, - next - ); - - expect(spyFindOneBy).toHaveBeenCalledWith({ - query: { - _id: ObjectID.getZeroObjectID().toString(), - isMasterApiKeyEnabled: true, - masterApiKey: mockedObjectId, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - expect(next).toHaveBeenCalledWith( - new BadDataException( - 'ProjectID not found in the request header.' - ) - ); - }); - - test('should throw BadDataException when getApiKey returns null', async () => { - req.headers['apikey'] = undefined; - - await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( - req, - res, - next - ); - - expect(next).toHaveBeenCalledWith( - new BadDataException('ApiKey not found in the request') - ); - }); - - test('should call Response.sendErrorResponse when apiKeyModel is null', async () => { - const spyFindOneBy: jest.SpyInstance = jest - .spyOn(ApiKeyService, 'findOneBy') - .mockResolvedValue(null); - - jest.spyOn(QueryHelper, 'greaterThan').mockImplementation( - jest.fn() - ); - - await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( - req, - res, - next - ); - - expect(spyFindOneBy).toHaveBeenCalledWith({ - query: { - projectId: mockedObjectId, - apiKey: mockedObjectId, - expiresAt: QueryHelper.greaterThan( - OneUptimeDate.getCurrentDate() - ), - }, - select: { - _id: true, - }, - props: { isRoot: true }, - }); - - expect(next).toHaveBeenCalledWith( - new BadDataException('Invalid Project ID or API Key') - ); - }); - - test('should call Response.sendErrorResponse when apiKeyModel is not null but getApiTenantAccessPermission returned null', async () => { - jest.spyOn(ApiKeyService, 'findOneBy').mockResolvedValue( - mockedApiModel - ); - const spyGetApiTenantAccessPermission: jest.SpyInstance = jest - .spyOn(AccessTokenService, 'getApiTenantAccessPermission') - .mockImplementationOnce(jest.fn().mockResolvedValue(null)); - - await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( - req, - res, - next - ); - - expect(spyGetApiTenantAccessPermission).toHaveBeenCalled(); - // check first param of next - expect(next).toHaveBeenCalledWith( - new BadDataException('Invalid Project ID or API Key') - ); - }); - - test("should call function 'next' when apiKeyModel is not null and getApiTenantAccessPermission returned userTenantAccessPermission", async () => { - const mockedUserTenantAccessPermission: UserTenantAccessPermission = - {} as UserTenantAccessPermission; - jest.spyOn(ApiKeyService, 'findOneBy').mockResolvedValue( - mockedApiModel - ); - const spyGetApiTenantAccessPermission: jest.SpyInstance = jest - .spyOn(AccessTokenService, 'getApiTenantAccessPermission') - .mockResolvedValue(mockedUserTenantAccessPermission); - - await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( - req, - res, - next - ); - - expect(spyGetApiTenantAccessPermission).toHaveBeenCalled(); - expect(next).toHaveBeenCalled(); - }); + expect(result).toEqual(mockedObjectId); + }); }); + + test("should return value when projectid is passed in the request's header", () => { + const req: Partial<ExpressRequest> = { + headers: { projectid: mockedObjectId.toString() }, + }; + + const result: ObjectIdOrNull = ProjectMiddleware.getProjectId( + req as ExpressRequest, + ); + + expect(result).toEqual(mockedObjectId); + }); + + test("should return value when projectId is passed in the request's body", () => { + const req: Partial<ExpressRequest> = { + body: { projectId: mockedObjectId.toString() }, + }; + + const result: ObjectIdOrNull = ProjectMiddleware.getProjectId( + req as ExpressRequest, + ); + + expect(result).toEqual(mockedObjectId); + }); + + test("should return null when projectId is not passed in the request", () => { + const result: ObjectIdOrNull = ProjectMiddleware.getProjectId( + {} as ExpressRequest, + ); + + expect(result).toBeNull(); + }); + }); + + describe("getApiKey", () => { + test("should return apiKey when apikey is passed in the request's header", () => { + const req: Partial<ExpressRequest> = { + headers: { apikey: mockedObjectId.toString() }, + }; + + const result: ObjectIdOrNull = ProjectMiddleware.getApiKey( + req as ExpressRequest, + ); + + expect(result).toEqual(mockedObjectId); + }); + + test("should return null when apikey is not passed in the request's header", () => { + const result: ObjectIdOrNull = ProjectMiddleware.getApiKey( + {} as ExpressRequest, + ); + + expect(result).toBeNull(); + }); + }); + + describe("hasApiKey", () => { + const req: ExpressRequest = { headers: {} } as ExpressRequest; + + test("should return true when getApiKey returns a non-null value", () => { + req.headers["apikey"] = mockedObjectId.toString(); + + const result: boolean = ProjectMiddleware.hasApiKey(req); + + expect(result).toStrictEqual(true); + }); + + test("should return false when getApiKey returns null", () => { + req.headers["apikey"] = undefined; + + const result: boolean = ProjectMiddleware.hasApiKey(req); + + expect(result).toStrictEqual(false); + }); + }); + + describe("hasProjectID", () => { + const req: ExpressRequest = { headers: {} } as ExpressRequest; + test("should return true when getProjectId returns a non-null value", () => { + req.headers["tenantid"] = mockedObjectId.toString(); + + const result: boolean = ProjectMiddleware.hasProjectID(req); + + expect(result).toStrictEqual(true); + }); + + test("should return false when getProjectId returns null", () => { + req.headers["tenantid"] = undefined; + + const result: boolean = ProjectMiddleware.hasProjectID(req); + + expect(result).toStrictEqual(false); + }); + }); + + describe("isValidProjectIdAndApiKeyMiddleware", () => { + const req: ExpressRequest = {} as ExpressRequest; + const res: ExpressResponse = {} as ExpressResponse; + let next: NextFunction = jest.fn(); + + const mockedApiModel: ApiKey = { + id: mockedObjectId, + } as ApiKey; + + let database!: Database; + + beforeEach( + async () => { + jest.clearAllMocks(); + next = jest.fn(); + database = new Database(); + await database.createAndConnect(); + + if (req.headers === undefined) { + req.headers = {}; + } + + req.headers["tenantid"] = mockedObjectId.toString(); + req.headers["apikey"] = mockedObjectId.toString(); + }, + 10 * 1000, // 10 second timeout because setting up the DB is slow + ); + + afterEach(async () => { + await database.disconnectAndDropDatabase(); + }); + + test("should throw BadDataException when getProjectId returns null", async () => { + const spyFindOneBy: jest.SpyInstance = jest + .spyOn(GlobalConfigService, "findOneBy") + .mockResolvedValue(null); + + req.headers["tenantid"] = undefined; + req.headers["apikey"] = mockedObjectId.toString(); + + await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( + req, + res, + next, + ); + + expect(spyFindOneBy).toHaveBeenCalledWith({ + query: { + _id: ObjectID.getZeroObjectID().toString(), + isMasterApiKeyEnabled: true, + masterApiKey: mockedObjectId, + }, + props: { + isRoot: true, + }, + select: { + _id: true, + }, + }); + + expect(next).toHaveBeenCalledWith( + new BadDataException("ProjectID not found in the request header."), + ); + }); + + test("should throw BadDataException when getApiKey returns null", async () => { + req.headers["apikey"] = undefined; + + await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( + req, + res, + next, + ); + + expect(next).toHaveBeenCalledWith( + new BadDataException("ApiKey not found in the request"), + ); + }); + + test("should call Response.sendErrorResponse when apiKeyModel is null", async () => { + const spyFindOneBy: jest.SpyInstance = jest + .spyOn(ApiKeyService, "findOneBy") + .mockResolvedValue(null); + + jest.spyOn(QueryHelper, "greaterThan").mockImplementation(jest.fn()); + + await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( + req, + res, + next, + ); + + expect(spyFindOneBy).toHaveBeenCalledWith({ + query: { + projectId: mockedObjectId, + apiKey: mockedObjectId, + expiresAt: QueryHelper.greaterThan(OneUptimeDate.getCurrentDate()), + }, + select: { + _id: true, + }, + props: { isRoot: true }, + }); + + expect(next).toHaveBeenCalledWith( + new BadDataException("Invalid Project ID or API Key"), + ); + }); + + test("should call Response.sendErrorResponse when apiKeyModel is not null but getApiTenantAccessPermission returned null", async () => { + jest.spyOn(ApiKeyService, "findOneBy").mockResolvedValue(mockedApiModel); + const spyGetApiTenantAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getApiTenantAccessPermission") + .mockImplementationOnce(jest.fn().mockResolvedValue(null)); + + await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( + req, + res, + next, + ); + + expect(spyGetApiTenantAccessPermission).toHaveBeenCalled(); + // check first param of next + expect(next).toHaveBeenCalledWith( + new BadDataException("Invalid Project ID or API Key"), + ); + }); + + test("should call function 'next' when apiKeyModel is not null and getApiTenantAccessPermission returned userTenantAccessPermission", async () => { + const mockedUserTenantAccessPermission: UserTenantAccessPermission = + {} as UserTenantAccessPermission; + jest.spyOn(ApiKeyService, "findOneBy").mockResolvedValue(mockedApiModel); + const spyGetApiTenantAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getApiTenantAccessPermission") + .mockResolvedValue(mockedUserTenantAccessPermission); + + await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware( + req, + res, + next, + ); + + expect(spyGetApiTenantAccessPermission).toHaveBeenCalled(); + expect(next).toHaveBeenCalled(); + }); + }); }); diff --git a/CommonServer/Tests/Middleware/UserAuthorization.test.ts b/CommonServer/Tests/Middleware/UserAuthorization.test.ts index 1e720181de..0f660fc061 100644 --- a/CommonServer/Tests/Middleware/UserAuthorization.test.ts +++ b/CommonServer/Tests/Middleware/UserAuthorization.test.ts @@ -1,830 +1,787 @@ -import ProjectMiddleware from '../../Middleware/ProjectAuthorization'; -import UserMiddleware from '../../Middleware/UserAuthorization'; -import AccessTokenService from '../../Services/AccessTokenService'; -import ProjectService from '../../Services/ProjectService'; -import UserService from '../../Services/UserService'; +import ProjectMiddleware from "../../Middleware/ProjectAuthorization"; +import UserMiddleware from "../../Middleware/UserAuthorization"; +import AccessTokenService from "../../Services/AccessTokenService"; +import ProjectService from "../../Services/ProjectService"; +import UserService from "../../Services/UserService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from '../../Utils/Express'; -import JSONWebToken from '../../Utils/JsonWebToken'; -import logger from '../../Utils/Logger'; -import Response from '../../Utils/Response'; -import Dictionary from 'Common/Types/Dictionary'; -import Email from 'Common/Types/Email'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import SsoAuthorizationException from 'Common/Types/Exception/SsoAuthorizationException'; -import HashedString from 'Common/Types/HashedString'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import JSONWebTokenData from 'Common/Types/JsonWebTokenData'; -import ObjectID from 'Common/Types/ObjectID'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "../../Utils/Express"; +import JSONWebToken from "../../Utils/JsonWebToken"; +import logger from "../../Utils/Logger"; +import Response from "../../Utils/Response"; +import Dictionary from "Common/Types/Dictionary"; +import Email from "Common/Types/Email"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import SsoAuthorizationException from "Common/Types/Exception/SsoAuthorizationException"; +import HashedString from "Common/Types/HashedString"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import JSONWebTokenData from "Common/Types/JsonWebTokenData"; +import ObjectID from "Common/Types/ObjectID"; import { - UserGlobalAccessPermission, - UserTenantAccessPermission, -} from 'Common/Types/Permission'; -import Project from 'Model/Models/Project'; + UserGlobalAccessPermission, + UserTenantAccessPermission, +} from "Common/Types/Permission"; +import Project from "Model/Models/Project"; -jest.mock('../../Utils/Logger'); -jest.mock('../../Middleware/ProjectAuthorization'); -jest.mock('../../Utils/JsonWebToken'); -jest.mock('../../Services/UserService'); -jest.mock('../../Services/AccessTokenService'); -jest.mock('../../Utils/Response'); -jest.mock('../../Services/ProjectService'); -jest.mock('Common/Types/HashedString'); -jest.mock('Common/Types/JSONFunctions'); +jest.mock("../../Utils/Logger"); +jest.mock("../../Middleware/ProjectAuthorization"); +jest.mock("../../Utils/JsonWebToken"); +jest.mock("../../Services/UserService"); +jest.mock("../../Services/AccessTokenService"); +jest.mock("../../Utils/Response"); +jest.mock("../../Services/ProjectService"); +jest.mock("Common/Types/HashedString"); +jest.mock("Common/Types/JSONFunctions"); type StringOrUndefined = string | undefined; -describe('UserMiddleware', () => { - const mockedAccessToken: string = ObjectID.generate().toString(); - const projectId: ObjectID = ObjectID.generate(); - const userId: ObjectID = ObjectID.generate(); - const mockedProject: Project = { _id: projectId.toString() } as Project; +describe("UserMiddleware", () => { + const mockedAccessToken: string = ObjectID.generate().toString(); + const projectId: ObjectID = ObjectID.generate(); + const userId: ObjectID = ObjectID.generate(); + const mockedProject: Project = { _id: projectId.toString() } as Project; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("getAccessToken", () => { + test("should return access token when authorization token is passed in the cookie", () => { + const req: ExpressRequest = { + cookies: { "user-token": mockedAccessToken }, + query: {}, + } as ExpressRequest; + + const result: StringOrUndefined = UserMiddleware.getAccessToken(req); + + expect(result).toEqual(mockedAccessToken); + }); + + test("should return null when authorization nor accessToken is passed", () => { + const req: ExpressRequest = { + cookies: {}, + headers: {}, + query: {}, + } as ExpressRequest; + + const result: StringOrUndefined = UserMiddleware.getAccessToken(req); + + expect(result).toBeUndefined(); + }); + }); + + describe("getSsoTokens", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const req: Partial<ExpressRequest> = { + cookies: { "sso-token": mockedAccessToken }, + }; + + test("should return an empty object when ssoToken is not passed", () => { + const result: Dictionary<string> = UserMiddleware.getSsoTokens({ + headers: {}, + } as ExpressRequest); + + expect(result).toEqual({}); + }); + + test("should return an empty object when ssoToken cannot be decoded", () => { + const error: Error = new Error("Invalid token"); + const spyDecode: jest.SpyInstance = jest + .spyOn(JSONWebToken, "decode") + .mockImplementationOnce((_: string) => { + throw error; + }); + const spyErrorLogger: jest.SpyInstance = jest.spyOn(logger, "error"); + + const result: Dictionary<string> = UserMiddleware.getSsoTokens( + req as ExpressRequest, + ); + + expect(result).toEqual({}); + expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken); + expect(spyErrorLogger).toHaveBeenCalledWith(error); + }); + + test("should return an empty object when the decoded sso-token object doesn't have projectId property", () => { + const spyDecode: jest.SpyInstance = jest + .spyOn(JSONWebToken, "decode") + .mockReturnValueOnce({} as JSONWebTokenData); + const spyErrorLogger: jest.SpyInstance = jest.spyOn(logger, "error"); + + const result: Dictionary<string> = UserMiddleware.getSsoTokens( + req as ExpressRequest, + ); + + expect(result).toEqual({}); + expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken); + expect(spyErrorLogger).not.toBeCalled(); + }); + + test("should return a dictionary of string with projectId key", () => { + jest.spyOn(JSONWebToken, "decode").mockReturnValueOnce({ + projectId, + } as JSONWebTokenData); + + const result: Dictionary<string> = UserMiddleware.getSsoTokens( + req as ExpressRequest, + ); + + expect(result).toEqual({ + [projectId.toString()]: mockedAccessToken, + }); + }); + }); + + describe("doesSsoTokenForProjectExist", () => { + const req: ExpressRequest = {} as ExpressRequest; + + beforeAll(() => { + jest.spyOn(UserMiddleware, "getSsoTokens").mockReturnValue({ + [projectId.toString()]: mockedAccessToken, + }); + }); + + test("should return false, when getSsoTokens does not return a value", () => { + const spyGetSsoTokens: jest.SpyInstance = jest + .spyOn(UserMiddleware, "getSsoTokens") + .mockImplementationOnce(jest.fn().mockReturnValue(null)); + + const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( + req, + projectId, + userId, + ); + + expect(result).toStrictEqual(false); + expect(spyGetSsoTokens).toHaveBeenCalled(); + }); + + test("should return false, when getSsoTokens returns a dictionary that does not contain the projectId's value as key", () => { + const spyGetSsoTokens: jest.SpyInstance = jest + .spyOn(UserMiddleware, "getSsoTokens") + .mockReturnValueOnce({}); + + const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( + req, + projectId, + userId, + ); + + expect(result).toStrictEqual(false); + expect(spyGetSsoTokens).toHaveBeenCalledWith(req); + }); + + test("should return false, when decoded JWT object's projectId value does not match with projectId passed as parameter", () => { + const objectId: ObjectID = ObjectID.generate(); + + const spyDecode: jest.SpyInstance = jest + .spyOn(JSONWebToken, "decode") + .mockReturnValueOnce({ + projectId: objectId, + userId, + } as JSONWebTokenData); + + const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( + req, + projectId, + userId, + ); + + expect(result).toStrictEqual(false); + expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken); + }); + + test("should return false, when decoded JWT object's userId does not match with userId passed as parameter", () => { + const objectId: ObjectID = ObjectID.generate(); + + const spyDecode: jest.SpyInstance = jest + .spyOn(JSONWebToken, "decode") + .mockReturnValueOnce({ + userId: objectId, + projectId, + } as JSONWebTokenData); + + const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( + req, + projectId, + userId, + ); + + expect(result).toStrictEqual(false); + expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken); + }); + + test("should return true", () => { + jest.spyOn(JSONWebToken, "decode").mockReturnValueOnce({ + userId, + projectId, + } as JSONWebTokenData); + + const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( + req, + projectId, + userId, + ); + + expect(result).toStrictEqual(true); + }); + }); + + describe("getUserMiddleware", () => { + let req: ExpressRequest; + let res: ExpressResponse; + const next: NextFunction = jest.fn(); + + const hashValue: string = "hash-value"; + const mockedTenantAccessPermission: UserTenantAccessPermission = { + projectId, + } as UserTenantAccessPermission; + const mockedGlobalAccessPermission: UserGlobalAccessPermission = { + projectIds: [projectId], + } as UserGlobalAccessPermission; + + const jwtTokenData: JSONWebTokenData = { + userId, + isMasterAdmin: true, + email: new Email("test@gmail.com"), + isGlobalLogin: true, + }; + + beforeAll(() => { + jest.spyOn(ProjectMiddleware, "getProjectId").mockReturnValue(projectId); + jest.spyOn(ProjectMiddleware, "hasApiKey").mockReturnValue(false); + jest + .spyOn(UserMiddleware, "getAccessToken") + .mockReturnValue(mockedAccessToken); + jest.spyOn(JSONWebToken, "decode").mockReturnValue(jwtTokenData); + jest.spyOn(HashedString, "hashValue").mockResolvedValue(hashValue); + }); beforeEach(() => { - jest.clearAllMocks(); + req = { headers: {} } as ExpressRequest; + + res = {} as ExpressResponse; + res.set = jest.fn(); }); - describe('getAccessToken', () => { - test('should return access token when authorization token is passed in the cookie', () => { - const req: ExpressRequest = { - cookies: { 'user-token': mockedAccessToken }, - query: {}, - } as ExpressRequest; - - const result: StringOrUndefined = - UserMiddleware.getAccessToken(req); - - expect(result).toEqual(mockedAccessToken); - }); - - test('should return null when authorization nor accessToken is passed', () => { - const req: ExpressRequest = { - cookies: {}, - headers: {}, - query: {}, - } as ExpressRequest; - - const result: StringOrUndefined = - UserMiddleware.getAccessToken(req); - - expect(result).toBeUndefined(); - }); + afterEach(() => { + jest.clearAllMocks(); }); - describe('getSsoTokens', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); + test("should call isValidProjectIdAndApiKeyMiddleware and return when hasApiKey returns true", async () => { + jest.spyOn(ProjectMiddleware, "hasApiKey").mockReturnValueOnce(true); - const req: Partial<ExpressRequest> = { - cookies: { 'sso-token': mockedAccessToken }, - }; + const spyGetAccessToken: jest.SpyInstance = jest.spyOn( + UserMiddleware, + "getAccessToken", + ); - test('should return an empty object when ssoToken is not passed', () => { - const result: Dictionary<string> = UserMiddleware.getSsoTokens({ - headers: {}, - } as ExpressRequest); + await UserMiddleware.getUserMiddleware(req, res, next); - expect(result).toEqual({}); - }); - - test('should return an empty object when ssoToken cannot be decoded', () => { - const error: Error = new Error('Invalid token'); - const spyDecode: jest.SpyInstance = jest - .spyOn(JSONWebToken, 'decode') - .mockImplementationOnce((_: string) => { - throw error; - }); - const spyErrorLogger: jest.SpyInstance = jest.spyOn( - logger, - 'error' - ); - - const result: Dictionary<string> = UserMiddleware.getSsoTokens( - req as ExpressRequest - ); - - expect(result).toEqual({}); - expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken); - expect(spyErrorLogger).toHaveBeenCalledWith(error); - }); - - test("should return an empty object when the decoded sso-token object doesn't have projectId property", () => { - const spyDecode: jest.SpyInstance = jest - .spyOn(JSONWebToken, 'decode') - .mockReturnValueOnce({} as JSONWebTokenData); - const spyErrorLogger: jest.SpyInstance = jest.spyOn( - logger, - 'error' - ); - - const result: Dictionary<string> = UserMiddleware.getSsoTokens( - req as ExpressRequest - ); - - expect(result).toEqual({}); - expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken); - expect(spyErrorLogger).not.toBeCalled(); - }); - - test('should return a dictionary of string with projectId key', () => { - jest.spyOn(JSONWebToken, 'decode').mockReturnValueOnce({ - projectId, - } as JSONWebTokenData); - - const result: Dictionary<string> = UserMiddleware.getSsoTokens( - req as ExpressRequest - ); - - expect(result).toEqual({ - [projectId.toString()]: mockedAccessToken, - }); - }); + expect( + ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware, + ).toHaveBeenCalledWith(req, res, next); + expect(spyGetAccessToken).not.toHaveBeenCalled(); }); - describe('doesSsoTokenForProjectExist', () => { - const req: ExpressRequest = {} as ExpressRequest; + test("should call function 'next' and return, when getAccessToken returns a null value", async () => { + const spyGetAccessToken: jest.SpyInstance = jest + .spyOn(UserMiddleware, "getAccessToken") + .mockReturnValueOnce(undefined); - beforeAll(() => { - jest.spyOn(UserMiddleware, 'getSsoTokens').mockReturnValue({ - [projectId.toString()]: mockedAccessToken, - }); - }); + await UserMiddleware.getUserMiddleware(req, res, next); - test('should return false, when getSsoTokens does not return a value', () => { - const spyGetSsoTokens: jest.SpyInstance = jest - .spyOn(UserMiddleware, 'getSsoTokens') - .mockImplementationOnce(jest.fn().mockReturnValue(null)); - - const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( - req, - projectId, - userId - ); - - expect(result).toStrictEqual(false); - expect(spyGetSsoTokens).toHaveBeenCalled(); - }); - - test("should return false, when getSsoTokens returns a dictionary that does not contain the projectId's value as key", () => { - const spyGetSsoTokens: jest.SpyInstance = jest - .spyOn(UserMiddleware, 'getSsoTokens') - .mockReturnValueOnce({}); - - const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( - req, - projectId, - userId - ); - - expect(result).toStrictEqual(false); - expect(spyGetSsoTokens).toHaveBeenCalledWith(req); - }); - - test("should return false, when decoded JWT object's projectId value does not match with projectId passed as parameter", () => { - const objectId: ObjectID = ObjectID.generate(); - - const spyDecode: jest.SpyInstance = jest - .spyOn(JSONWebToken, 'decode') - .mockReturnValueOnce({ - projectId: objectId, - userId, - } as JSONWebTokenData); - - const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( - req, - projectId, - userId - ); - - expect(result).toStrictEqual(false); - expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken); - }); - - test("should return false, when decoded JWT object's userId does not match with userId passed as parameter", () => { - const objectId: ObjectID = ObjectID.generate(); - - const spyDecode: jest.SpyInstance = jest - .spyOn(JSONWebToken, 'decode') - .mockReturnValueOnce({ - userId: objectId, - projectId, - } as JSONWebTokenData); - - const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( - req, - projectId, - userId - ); - - expect(result).toStrictEqual(false); - expect(spyDecode).toHaveBeenCalledWith(mockedAccessToken); - }); - - test('should return true', () => { - jest.spyOn(JSONWebToken, 'decode').mockReturnValueOnce({ - userId, - projectId, - } as JSONWebTokenData); - - const result: boolean = UserMiddleware.doesSsoTokenForProjectExist( - req, - projectId, - userId - ); - - expect(result).toStrictEqual(true); - }); + expect(next).toHaveBeenCalled(); + expect(spyGetAccessToken).toHaveBeenCalledWith(req); + expect(JSONWebToken.decode).not.toHaveBeenCalled(); }); - describe('getUserMiddleware', () => { - let req: ExpressRequest; - let res: ExpressResponse; - const next: NextFunction = jest.fn(); + test("should call function 'next' and return, when accessToken can not be decoded", async () => { + const error: Error = new Error("Invalid access token"); - const hashValue: string = 'hash-value'; - const mockedTenantAccessPermission: UserTenantAccessPermission = { - projectId, - } as UserTenantAccessPermission; - const mockedGlobalAccessPermission: UserGlobalAccessPermission = { - projectIds: [projectId], - } as UserGlobalAccessPermission; - - const jwtTokenData: JSONWebTokenData = { - userId, - isMasterAdmin: true, - email: new Email('test@gmail.com'), - isGlobalLogin: true, - }; - - beforeAll(() => { - jest.spyOn(ProjectMiddleware, 'getProjectId').mockReturnValue( - projectId - ); - jest.spyOn(ProjectMiddleware, 'hasApiKey').mockReturnValue(false); - jest.spyOn(UserMiddleware, 'getAccessToken').mockReturnValue( - mockedAccessToken - ); - jest.spyOn(JSONWebToken, 'decode').mockReturnValue(jwtTokenData); - jest.spyOn(HashedString, 'hashValue').mockResolvedValue(hashValue); + const spyJWTDecode: jest.SpyInstance = jest + .spyOn(JSONWebToken, "decode") + .mockImplementationOnce((_: string) => { + throw error; }); - beforeEach(() => { - req = { headers: {} } as ExpressRequest; + await UserMiddleware.getUserMiddleware(req, res, next); - res = {} as ExpressResponse; - res.set = jest.fn(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('should call isValidProjectIdAndApiKeyMiddleware and return when hasApiKey returns true', async () => { - jest.spyOn(ProjectMiddleware, 'hasApiKey').mockReturnValueOnce( - true - ); - - const spyGetAccessToken: jest.SpyInstance = jest.spyOn( - UserMiddleware, - 'getAccessToken' - ); - - await UserMiddleware.getUserMiddleware(req, res, next); - - expect( - ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware - ).toHaveBeenCalledWith(req, res, next); - expect(spyGetAccessToken).not.toHaveBeenCalled(); - }); - - test("should call function 'next' and return, when getAccessToken returns a null value", async () => { - const spyGetAccessToken: jest.SpyInstance = jest - .spyOn(UserMiddleware, 'getAccessToken') - .mockReturnValueOnce(undefined); - - await UserMiddleware.getUserMiddleware(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(spyGetAccessToken).toHaveBeenCalledWith(req); - expect(JSONWebToken.decode).not.toHaveBeenCalled(); - }); - - test("should call function 'next' and return, when accessToken can not be decoded", async () => { - const error: Error = new Error('Invalid access token'); - - const spyJWTDecode: jest.SpyInstance = jest - .spyOn(JSONWebToken, 'decode') - .mockImplementationOnce((_: string) => { - throw error; - }); - - await UserMiddleware.getUserMiddleware(req, res, next); - - expect(next).toHaveBeenCalled(); - expect(spyJWTDecode).toHaveBeenCalledWith(mockedAccessToken); - expect(UserService.updateOneBy).not.toHaveBeenCalled(); - }); - - test('should set global-permissions and global-permissions-hash in the response header, when user has global access permission', async () => { - jest.spyOn(ProjectMiddleware, 'getProjectId').mockReturnValueOnce( - null - ); - jest.spyOn(JSONFunctions, 'serialize').mockReturnValueOnce({}); - const spyGetUserGlobalAccessPermission: jest.SpyInstance = jest - .spyOn(AccessTokenService, 'getUserGlobalAccessPermission') - .mockResolvedValueOnce(mockedGlobalAccessPermission); - - await UserMiddleware.getUserMiddleware(req, res, next); - - expect(res.set).toHaveBeenCalledWith( - 'global-permissions', - JSON.stringify({}) - ); - expect(res.set).toHaveBeenCalledWith( - 'global-permissions-hash', - hashValue - ); - expect(next).toHaveBeenCalled(); - expect(spyGetUserGlobalAccessPermission).toHaveBeenCalledWith( - userId - ); - }); - - test('should not set global-permissions and global-permissions-hash in the response header, when user does not have global access permission', async () => { - jest.spyOn(ProjectMiddleware, 'getProjectId').mockReturnValueOnce( - null - ); - const spyGetUserGlobalAccessPermission: jest.SpyInstance = jest - .spyOn(AccessTokenService, 'getUserGlobalAccessPermission') - .mockResolvedValueOnce(null); - - await UserMiddleware.getUserMiddleware(req, res, next); - - expect(res.set).not.toHaveBeenCalledWith( - 'global-permissions', - expect.anything() - ); - expect(res.set).not.toHaveBeenCalledWith( - 'global-permissions-hash', - expect.anything() - ); - expect(next).toHaveBeenCalled(); - expect(spyGetUserGlobalAccessPermission).toHaveBeenCalledWith( - userId - ); - }); - - test('should call Response.sendErrorResponse, when tenantId is passed in the header and getUserTenantAccessPermissionWithTenantId throws an exception', async () => { - const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance = - jest - .spyOn( - UserMiddleware, - 'getUserTenantAccessPermissionWithTenantId' - ) - .mockRejectedValueOnce(new SsoAuthorizationException()); - - await UserMiddleware.getUserMiddleware(req, res, next); - - expect(Response.sendErrorResponse).toHaveBeenCalledWith( - req, - res, - new SsoAuthorizationException() - ); - expect( - spyGetUserTenantAccessPermissionWithTenantId - ).toHaveBeenCalledWith({ - req, - tenantId: projectId, - userId, - isGlobalLogin: true, - }); - expect(next).not.toBeCalled(); - }); - - test('should set project-permissions and project-permissions-hash in the response header, when tenantId is passed in the header and user has tenant access permission', async () => { - jest.spyOn(JSONFunctions, 'serialize').mockReturnValueOnce({}); - const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance = - jest - .spyOn( - UserMiddleware, - 'getUserTenantAccessPermissionWithTenantId' - ) - .mockResolvedValueOnce(mockedTenantAccessPermission); - - await UserMiddleware.getUserMiddleware(req, res, next); - - expect(res.set).toHaveBeenCalledWith( - 'project-permissions', - JSON.stringify({}) - ); - expect(res.set).toHaveBeenCalledWith( - 'project-permissions-hash', - hashValue - ); - expect(next).toHaveBeenCalled(); - - expect( - spyGetUserTenantAccessPermissionWithTenantId - ).toHaveBeenCalledWith({ - req, - tenantId: projectId, - userId, - isGlobalLogin: true, - }); - }); - - test("should not call getUserTenantAccessPermissionForMultiTenant, when is-multi-tenant-query is set in the request header and but userGlobalAccessPermission's projectIds length is zero", async () => { - const mockedRequest: Partial<ExpressRequest> = { - headers: { 'is-multi-tenant-query': 'yes' }, - }; - - jest.spyOn( - AccessTokenService, - 'getUserGlobalAccessPermission' - ).mockResolvedValueOnce({ - ...mockedGlobalAccessPermission, - projectIds: [], - }); - jest.spyOn( - UserMiddleware, - 'getUserTenantAccessPermissionWithTenantId' - ).mockResolvedValueOnce(null); - const spyGetUserTenantAccessPermissionForMultiTenant: jest.SpyInstance = - jest.spyOn( - UserMiddleware, - 'getUserTenantAccessPermissionForMultiTenant' - ); - - await UserMiddleware.getUserMiddleware( - mockedRequest as ExpressRequest, - res, - next - ); - - expect(res.set).not.toHaveBeenCalledWith( - 'project-permissions', - expect.anything() - ); - expect(res.set).not.toHaveBeenCalledWith( - 'project-permissions-hash', - expect.anything() - ); - expect(next).toHaveBeenCalled(); - expect( - spyGetUserTenantAccessPermissionForMultiTenant - ).not.toHaveBeenCalled(); - }); - - test('should set project-permissions and project-permissions-hash in the response header, when is-multi-tenant-query is set in the request header and getUserTenantAccessPermissionForMultiTenant returned access permission', async () => { - const mockedRequest: Partial<ExpressRequest> = { - headers: { 'is-multi-tenant-query': 'yes' }, - }; - - jest.spyOn(JSONFunctions, 'serialize').mockReturnValue({}); - jest.spyOn( - AccessTokenService, - 'getUserGlobalAccessPermission' - ).mockResolvedValueOnce(mockedGlobalAccessPermission); - jest.spyOn( - UserMiddleware, - 'getUserTenantAccessPermissionWithTenantId' - ).mockResolvedValueOnce(null); - const spyGetUserTenantAccessPermissionForMultiTenant: jest.SpyInstance = - jest - .spyOn( - UserMiddleware, - 'getUserTenantAccessPermissionForMultiTenant' - ) - .mockResolvedValueOnce({ - [projectId.toString()]: mockedTenantAccessPermission, - }); - - await UserMiddleware.getUserMiddleware( - mockedRequest as ExpressRequest, - res, - next - ); - - expect(res.set).toHaveBeenCalledWith( - 'project-permissions', - JSON.stringify({}) - ); - expect(res.set).toHaveBeenCalledWith( - 'project-permissions-hash', - hashValue - ); - expect(next).toHaveBeenCalled(); - expect( - spyGetUserTenantAccessPermissionForMultiTenant - ).toHaveBeenCalledWith( - mockedRequest, - userId, - mockedGlobalAccessPermission.projectIds - ); - }); - - test('should not set project-permissions and project-permissions-hash in the response header, when the project-permissions-hash set in the request header equals the projectPermissionsHash computed from userTenantAccessPermission', async () => { - const mockedRequest: Partial<ExpressRequest> = { - headers: { 'project-permissions-hash': hashValue }, - }; - - const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance = - jest - .spyOn( - UserMiddleware, - 'getUserTenantAccessPermissionWithTenantId' - ) - .mockResolvedValueOnce(mockedTenantAccessPermission); - - await UserMiddleware.getUserMiddleware( - mockedRequest as ExpressRequest, - res, - next - ); - - expect(res.set).not.toHaveBeenCalledWith( - 'project-permissions', - expect.anything() - ); - expect(res.set).not.toHaveBeenCalledWith( - 'project-permissions-hash', - expect.anything() - ); - expect(next).toHaveBeenCalled(); - - expect( - spyGetUserTenantAccessPermissionWithTenantId - ).toHaveBeenCalledWith({ - req: mockedRequest, - tenantId: projectId, - userId, - isGlobalLogin: true, - }); - }); + expect(next).toHaveBeenCalled(); + expect(spyJWTDecode).toHaveBeenCalledWith(mockedAccessToken); + expect(UserService.updateOneBy).not.toHaveBeenCalled(); }); - describe('getUserTenantAccessPermissionWithTenantId', () => { - const req: ExpressRequest = {} as ExpressRequest; + test("should set global-permissions and global-permissions-hash in the response header, when user has global access permission", async () => { + jest.spyOn(ProjectMiddleware, "getProjectId").mockReturnValueOnce(null); + jest.spyOn(JSONFunctions, "serialize").mockReturnValueOnce({}); + const spyGetUserGlobalAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getUserGlobalAccessPermission") + .mockResolvedValueOnce(mockedGlobalAccessPermission); - const spyFindOneById: jest.SpyInstance = jest.spyOn( - ProjectService, - 'findOneById' - ); - const spyDoesSsoTokenForProjectExist: jest.SpyInstance = jest.spyOn( - UserMiddleware, - 'doesSsoTokenForProjectExist' + await UserMiddleware.getUserMiddleware(req, res, next); + + expect(res.set).toHaveBeenCalledWith( + "global-permissions", + JSON.stringify({}), + ); + expect(res.set).toHaveBeenCalledWith( + "global-permissions-hash", + hashValue, + ); + expect(next).toHaveBeenCalled(); + expect(spyGetUserGlobalAccessPermission).toHaveBeenCalledWith(userId); + }); + + test("should not set global-permissions and global-permissions-hash in the response header, when user does not have global access permission", async () => { + jest.spyOn(ProjectMiddleware, "getProjectId").mockReturnValueOnce(null); + const spyGetUserGlobalAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getUserGlobalAccessPermission") + .mockResolvedValueOnce(null); + + await UserMiddleware.getUserMiddleware(req, res, next); + + expect(res.set).not.toHaveBeenCalledWith( + "global-permissions", + expect.anything(), + ); + expect(res.set).not.toHaveBeenCalledWith( + "global-permissions-hash", + expect.anything(), + ); + expect(next).toHaveBeenCalled(); + expect(spyGetUserGlobalAccessPermission).toHaveBeenCalledWith(userId); + }); + + test("should call Response.sendErrorResponse, when tenantId is passed in the header and getUserTenantAccessPermissionWithTenantId throws an exception", async () => { + const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance = + jest + .spyOn(UserMiddleware, "getUserTenantAccessPermissionWithTenantId") + .mockRejectedValueOnce(new SsoAuthorizationException()); + + await UserMiddleware.getUserMiddleware(req, res, next); + + expect(Response.sendErrorResponse).toHaveBeenCalledWith( + req, + res, + new SsoAuthorizationException(), + ); + expect(spyGetUserTenantAccessPermissionWithTenantId).toHaveBeenCalledWith( + { + req, + tenantId: projectId, + userId, + isGlobalLogin: true, + }, + ); + expect(next).not.toBeCalled(); + }); + + test("should set project-permissions and project-permissions-hash in the response header, when tenantId is passed in the header and user has tenant access permission", async () => { + jest.spyOn(JSONFunctions, "serialize").mockReturnValueOnce({}); + const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance = + jest + .spyOn(UserMiddleware, "getUserTenantAccessPermissionWithTenantId") + .mockResolvedValueOnce(mockedTenantAccessPermission); + + await UserMiddleware.getUserMiddleware(req, res, next); + + expect(res.set).toHaveBeenCalledWith( + "project-permissions", + JSON.stringify({}), + ); + expect(res.set).toHaveBeenCalledWith( + "project-permissions-hash", + hashValue, + ); + expect(next).toHaveBeenCalled(); + + expect(spyGetUserTenantAccessPermissionWithTenantId).toHaveBeenCalledWith( + { + req, + tenantId: projectId, + userId, + isGlobalLogin: true, + }, + ); + }); + + test("should not call getUserTenantAccessPermissionForMultiTenant, when is-multi-tenant-query is set in the request header and but userGlobalAccessPermission's projectIds length is zero", async () => { + const mockedRequest: Partial<ExpressRequest> = { + headers: { "is-multi-tenant-query": "yes" }, + }; + + jest + .spyOn(AccessTokenService, "getUserGlobalAccessPermission") + .mockResolvedValueOnce({ + ...mockedGlobalAccessPermission, + projectIds: [], + }); + jest + .spyOn(UserMiddleware, "getUserTenantAccessPermissionWithTenantId") + .mockResolvedValueOnce(null); + const spyGetUserTenantAccessPermissionForMultiTenant: jest.SpyInstance = + jest.spyOn( + UserMiddleware, + "getUserTenantAccessPermissionForMultiTenant", ); - afterEach(() => { - jest.clearAllMocks(); - }); + await UserMiddleware.getUserMiddleware( + mockedRequest as ExpressRequest, + res, + next, + ); - test("should throw 'Invalid tenantId' error, when project is not found for the tenantId", async () => { - spyFindOneById.mockResolvedValueOnce(null); - - await expect( - UserMiddleware.getUserTenantAccessPermissionWithTenantId({ - req, - tenantId: projectId, - userId, - isGlobalLogin: true, - }) - ).rejects.toThrowError(new BadDataException('Invalid tenantId')); - expect(spyFindOneById).toHaveBeenCalledWith({ - id: projectId, - select: { - requireSsoForLogin: true, - }, - props: { - isRoot: true, - }, - }); - }); - - test('should throw SsoAuthorizationException error, when sso login is required but sso token for the projectId does not exist', async () => { - spyFindOneById.mockResolvedValueOnce({ - ...mockedProject, - requireSsoForLogin: true, - }); - - spyDoesSsoTokenForProjectExist.mockReturnValueOnce(false); - - await expect( - UserMiddleware.getUserTenantAccessPermissionWithTenantId({ - req, - tenantId: projectId, - userId, - isGlobalLogin: true, - }) - ).rejects.toThrowError(new SsoAuthorizationException()); - expect(spyDoesSsoTokenForProjectExist).toHaveBeenCalledWith( - req, - projectId, - userId - ); - }); - - test('should return null when getUserTenantAccessPermission returns null', async () => { - spyFindOneById.mockResolvedValueOnce(mockedProject); - - const spyGetUserTenantAccessPermission: jest.SpyInstance = jest - .spyOn(AccessTokenService, 'getUserTenantAccessPermission') - .mockResolvedValueOnce(null); - - const result: UserTenantAccessPermission | null = - await UserMiddleware.getUserTenantAccessPermissionWithTenantId({ - req, - tenantId: projectId, - userId, - isGlobalLogin: true, - }); - - expect(result).toBeNull(); - expect(spyGetUserTenantAccessPermission).toHaveBeenLastCalledWith( - userId, - projectId - ); - }); - - test('should return UserTenantAccessPermission', async () => { - const mockedUserTenantAccessPermission: UserTenantAccessPermission = - { projectId } as UserTenantAccessPermission; - - spyFindOneById.mockResolvedValueOnce(mockedProject); - - const spyGetUserTenantAccessPermission: jest.SpyInstance = jest - .spyOn(AccessTokenService, 'getUserTenantAccessPermission') - .mockResolvedValueOnce(mockedUserTenantAccessPermission); - - const result: UserTenantAccessPermission | null = - await UserMiddleware.getUserTenantAccessPermissionWithTenantId({ - req, - tenantId: projectId, - userId, - isGlobalLogin: true, - }); - - expect(result).toEqual(mockedUserTenantAccessPermission); - expect(spyGetUserTenantAccessPermission).toHaveBeenLastCalledWith( - userId, - projectId - ); - }); + expect(res.set).not.toHaveBeenCalledWith( + "project-permissions", + expect.anything(), + ); + expect(res.set).not.toHaveBeenCalledWith( + "project-permissions-hash", + expect.anything(), + ); + expect(next).toHaveBeenCalled(); + expect( + spyGetUserTenantAccessPermissionForMultiTenant, + ).not.toHaveBeenCalled(); }); - describe('getUserTenantAccessPermissionForMultiTenant', () => { - const req: ExpressRequest = {} as ExpressRequest; - const mockedUserTenantAccessPermission: UserTenantAccessPermission = { - projectId, - } as UserTenantAccessPermission; + test("should set project-permissions and project-permissions-hash in the response header, when is-multi-tenant-query is set in the request header and getUserTenantAccessPermissionForMultiTenant returned access permission", async () => { + const mockedRequest: Partial<ExpressRequest> = { + headers: { "is-multi-tenant-query": "yes" }, + }; - const spyFindBy: jest.SpyInstance = jest.spyOn( - ProjectService, - 'findBy' - ); - const spyDoesSsoTokenForProjectExist: jest.SpyInstance = jest.spyOn( - UserMiddleware, - 'doesSsoTokenForProjectExist' - ); + jest.spyOn(JSONFunctions, "serialize").mockReturnValue({}); + jest + .spyOn(AccessTokenService, "getUserGlobalAccessPermission") + .mockResolvedValueOnce(mockedGlobalAccessPermission); + jest + .spyOn(UserMiddleware, "getUserTenantAccessPermissionWithTenantId") + .mockResolvedValueOnce(null); + const spyGetUserTenantAccessPermissionForMultiTenant: jest.SpyInstance = + jest + .spyOn(UserMiddleware, "getUserTenantAccessPermissionForMultiTenant") + .mockResolvedValueOnce({ + [projectId.toString()]: mockedTenantAccessPermission, + }); - afterEach(() => { - jest.clearAllMocks(); - }); + await UserMiddleware.getUserMiddleware( + mockedRequest as ExpressRequest, + res, + next, + ); - test('should return null, when projectIds length is zero', async () => { - const result: Dictionary<UserTenantAccessPermission> | null = - await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( - req, - userId, - [] - ); - - expect(result).toBeNull(); - expect(spyFindBy).not.toBeCalled(); - }); - - test('should return default tenant access permission, when project for a projectId is found, sso is required for login, but sso token does not exist for that projectId', async () => { - spyDoesSsoTokenForProjectExist.mockReturnValueOnce(false); - spyFindBy.mockResolvedValueOnce([ - { ...mockedProject, requireSsoForLogin: true }, - ]); - - const spyGetDefaultUserTenantAccessPermission: jest.SpyInstance = - jest - .spyOn( - AccessTokenService, - 'getDefaultUserTenantAccessPermission' - ) - .mockReturnValueOnce(mockedUserTenantAccessPermission); - - const result: Dictionary<UserTenantAccessPermission> | null = - await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( - req, - userId, - [projectId] - ); - - expect(result).toEqual({ - [projectId.toString()]: mockedUserTenantAccessPermission, - }); - expect(spyDoesSsoTokenForProjectExist).toHaveBeenCalledWith( - req, - projectId, - userId - ); - expect( - spyGetDefaultUserTenantAccessPermission - ).toHaveBeenCalledWith(projectId); - }); - - test('should return user tenant access permission, when project for a projectId is found, sso is not required for login and project level permission exist for the projectId', async () => { - spyFindBy.mockResolvedValueOnce([mockedProject]); - - const spyGetUserTenantAccessPermission: jest.SpyInstance = jest - .spyOn(AccessTokenService, 'getUserTenantAccessPermission') - .mockResolvedValueOnce(mockedUserTenantAccessPermission); - - const result: Dictionary<UserTenantAccessPermission> | null = - await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( - req, - userId, - [projectId] - ); - - expect(result).toEqual({ - [projectId.toString()]: mockedUserTenantAccessPermission, - }); - expect(spyDoesSsoTokenForProjectExist).not.toBeCalled(); - expect(spyGetUserTenantAccessPermission).toHaveBeenCalledWith( - userId, - projectId - ); - }); - - test('should return null, when project for a projectId is found, sso is not required for login but project level permission does not exist for the projectId', async () => { - spyFindBy.mockResolvedValueOnce([mockedProject]); - - const spyGetUserTenantAccessPermission: jest.SpyInstance = jest - .spyOn(AccessTokenService, 'getUserTenantAccessPermission') - .mockResolvedValueOnce(null); - - const result: Dictionary<UserTenantAccessPermission> | null = - await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( - req, - userId, - [projectId] - ); - - expect(result).toBeNull(); - expect(spyGetUserTenantAccessPermission).toHaveBeenCalledWith( - userId, - projectId - ); - }); - - test('should return user tenant access permission, when project for a projectId is not found, but project level permission exist for the projectId', async () => { - spyFindBy.mockResolvedValueOnce([]); - - jest.spyOn( - AccessTokenService, - 'getUserTenantAccessPermission' - ).mockResolvedValueOnce(mockedUserTenantAccessPermission); - - const result: Dictionary<UserTenantAccessPermission> | null = - await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( - req, - userId, - [projectId] - ); - - expect(result).toEqual({ - [projectId.toString()]: mockedUserTenantAccessPermission, - }); - }); - - test('should return null, when project for a projectId is not found, and project level permission does not exist for the projectId', async () => { - spyFindBy.mockResolvedValueOnce([]); - - const spyGetUserTenantAccessPermission: jest.SpyInstance = jest - .spyOn(AccessTokenService, 'getUserTenantAccessPermission') - .mockResolvedValueOnce(null); - - const result: Dictionary<UserTenantAccessPermission> | null = - await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( - req, - userId, - [projectId] - ); - - expect(result).toBeNull(); - expect(spyGetUserTenantAccessPermission).toHaveBeenCalledWith( - userId, - projectId - ); - }); + expect(res.set).toHaveBeenCalledWith( + "project-permissions", + JSON.stringify({}), + ); + expect(res.set).toHaveBeenCalledWith( + "project-permissions-hash", + hashValue, + ); + expect(next).toHaveBeenCalled(); + expect( + spyGetUserTenantAccessPermissionForMultiTenant, + ).toHaveBeenCalledWith( + mockedRequest, + userId, + mockedGlobalAccessPermission.projectIds, + ); }); + + test("should not set project-permissions and project-permissions-hash in the response header, when the project-permissions-hash set in the request header equals the projectPermissionsHash computed from userTenantAccessPermission", async () => { + const mockedRequest: Partial<ExpressRequest> = { + headers: { "project-permissions-hash": hashValue }, + }; + + const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance = + jest + .spyOn(UserMiddleware, "getUserTenantAccessPermissionWithTenantId") + .mockResolvedValueOnce(mockedTenantAccessPermission); + + await UserMiddleware.getUserMiddleware( + mockedRequest as ExpressRequest, + res, + next, + ); + + expect(res.set).not.toHaveBeenCalledWith( + "project-permissions", + expect.anything(), + ); + expect(res.set).not.toHaveBeenCalledWith( + "project-permissions-hash", + expect.anything(), + ); + expect(next).toHaveBeenCalled(); + + expect(spyGetUserTenantAccessPermissionWithTenantId).toHaveBeenCalledWith( + { + req: mockedRequest, + tenantId: projectId, + userId, + isGlobalLogin: true, + }, + ); + }); + }); + + describe("getUserTenantAccessPermissionWithTenantId", () => { + const req: ExpressRequest = {} as ExpressRequest; + + const spyFindOneById: jest.SpyInstance = jest.spyOn( + ProjectService, + "findOneById", + ); + const spyDoesSsoTokenForProjectExist: jest.SpyInstance = jest.spyOn( + UserMiddleware, + "doesSsoTokenForProjectExist", + ); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test("should throw 'Invalid tenantId' error, when project is not found for the tenantId", async () => { + spyFindOneById.mockResolvedValueOnce(null); + + await expect( + UserMiddleware.getUserTenantAccessPermissionWithTenantId({ + req, + tenantId: projectId, + userId, + isGlobalLogin: true, + }), + ).rejects.toThrowError(new BadDataException("Invalid tenantId")); + expect(spyFindOneById).toHaveBeenCalledWith({ + id: projectId, + select: { + requireSsoForLogin: true, + }, + props: { + isRoot: true, + }, + }); + }); + + test("should throw SsoAuthorizationException error, when sso login is required but sso token for the projectId does not exist", async () => { + spyFindOneById.mockResolvedValueOnce({ + ...mockedProject, + requireSsoForLogin: true, + }); + + spyDoesSsoTokenForProjectExist.mockReturnValueOnce(false); + + await expect( + UserMiddleware.getUserTenantAccessPermissionWithTenantId({ + req, + tenantId: projectId, + userId, + isGlobalLogin: true, + }), + ).rejects.toThrowError(new SsoAuthorizationException()); + expect(spyDoesSsoTokenForProjectExist).toHaveBeenCalledWith( + req, + projectId, + userId, + ); + }); + + test("should return null when getUserTenantAccessPermission returns null", async () => { + spyFindOneById.mockResolvedValueOnce(mockedProject); + + const spyGetUserTenantAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getUserTenantAccessPermission") + .mockResolvedValueOnce(null); + + const result: UserTenantAccessPermission | null = + await UserMiddleware.getUserTenantAccessPermissionWithTenantId({ + req, + tenantId: projectId, + userId, + isGlobalLogin: true, + }); + + expect(result).toBeNull(); + expect(spyGetUserTenantAccessPermission).toHaveBeenLastCalledWith( + userId, + projectId, + ); + }); + + test("should return UserTenantAccessPermission", async () => { + const mockedUserTenantAccessPermission: UserTenantAccessPermission = { + projectId, + } as UserTenantAccessPermission; + + spyFindOneById.mockResolvedValueOnce(mockedProject); + + const spyGetUserTenantAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getUserTenantAccessPermission") + .mockResolvedValueOnce(mockedUserTenantAccessPermission); + + const result: UserTenantAccessPermission | null = + await UserMiddleware.getUserTenantAccessPermissionWithTenantId({ + req, + tenantId: projectId, + userId, + isGlobalLogin: true, + }); + + expect(result).toEqual(mockedUserTenantAccessPermission); + expect(spyGetUserTenantAccessPermission).toHaveBeenLastCalledWith( + userId, + projectId, + ); + }); + }); + + describe("getUserTenantAccessPermissionForMultiTenant", () => { + const req: ExpressRequest = {} as ExpressRequest; + const mockedUserTenantAccessPermission: UserTenantAccessPermission = { + projectId, + } as UserTenantAccessPermission; + + const spyFindBy: jest.SpyInstance = jest.spyOn(ProjectService, "findBy"); + const spyDoesSsoTokenForProjectExist: jest.SpyInstance = jest.spyOn( + UserMiddleware, + "doesSsoTokenForProjectExist", + ); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test("should return null, when projectIds length is zero", async () => { + const result: Dictionary<UserTenantAccessPermission> | null = + await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( + req, + userId, + [], + ); + + expect(result).toBeNull(); + expect(spyFindBy).not.toBeCalled(); + }); + + test("should return default tenant access permission, when project for a projectId is found, sso is required for login, but sso token does not exist for that projectId", async () => { + spyDoesSsoTokenForProjectExist.mockReturnValueOnce(false); + spyFindBy.mockResolvedValueOnce([ + { ...mockedProject, requireSsoForLogin: true }, + ]); + + const spyGetDefaultUserTenantAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getDefaultUserTenantAccessPermission") + .mockReturnValueOnce(mockedUserTenantAccessPermission); + + const result: Dictionary<UserTenantAccessPermission> | null = + await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( + req, + userId, + [projectId], + ); + + expect(result).toEqual({ + [projectId.toString()]: mockedUserTenantAccessPermission, + }); + expect(spyDoesSsoTokenForProjectExist).toHaveBeenCalledWith( + req, + projectId, + userId, + ); + expect(spyGetDefaultUserTenantAccessPermission).toHaveBeenCalledWith( + projectId, + ); + }); + + test("should return user tenant access permission, when project for a projectId is found, sso is not required for login and project level permission exist for the projectId", async () => { + spyFindBy.mockResolvedValueOnce([mockedProject]); + + const spyGetUserTenantAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getUserTenantAccessPermission") + .mockResolvedValueOnce(mockedUserTenantAccessPermission); + + const result: Dictionary<UserTenantAccessPermission> | null = + await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( + req, + userId, + [projectId], + ); + + expect(result).toEqual({ + [projectId.toString()]: mockedUserTenantAccessPermission, + }); + expect(spyDoesSsoTokenForProjectExist).not.toBeCalled(); + expect(spyGetUserTenantAccessPermission).toHaveBeenCalledWith( + userId, + projectId, + ); + }); + + test("should return null, when project for a projectId is found, sso is not required for login but project level permission does not exist for the projectId", async () => { + spyFindBy.mockResolvedValueOnce([mockedProject]); + + const spyGetUserTenantAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getUserTenantAccessPermission") + .mockResolvedValueOnce(null); + + const result: Dictionary<UserTenantAccessPermission> | null = + await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( + req, + userId, + [projectId], + ); + + expect(result).toBeNull(); + expect(spyGetUserTenantAccessPermission).toHaveBeenCalledWith( + userId, + projectId, + ); + }); + + test("should return user tenant access permission, when project for a projectId is not found, but project level permission exist for the projectId", async () => { + spyFindBy.mockResolvedValueOnce([]); + + jest + .spyOn(AccessTokenService, "getUserTenantAccessPermission") + .mockResolvedValueOnce(mockedUserTenantAccessPermission); + + const result: Dictionary<UserTenantAccessPermission> | null = + await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( + req, + userId, + [projectId], + ); + + expect(result).toEqual({ + [projectId.toString()]: mockedUserTenantAccessPermission, + }); + }); + + test("should return null, when project for a projectId is not found, and project level permission does not exist for the projectId", async () => { + spyFindBy.mockResolvedValueOnce([]); + + const spyGetUserTenantAccessPermission: jest.SpyInstance = jest + .spyOn(AccessTokenService, "getUserTenantAccessPermission") + .mockResolvedValueOnce(null); + + const result: Dictionary<UserTenantAccessPermission> | null = + await UserMiddleware.getUserTenantAccessPermissionForMultiTenant( + req, + userId, + [projectId], + ); + + expect(result).toBeNull(); + expect(spyGetUserTenantAccessPermission).toHaveBeenCalledWith( + userId, + projectId, + ); + }); + }); }); diff --git a/CommonServer/Tests/Services/AnalyticsDatabaseService.test.ts b/CommonServer/Tests/Services/AnalyticsDatabaseService.test.ts index 00e4aeb3ec..a983bd4e12 100644 --- a/CommonServer/Tests/Services/AnalyticsDatabaseService.test.ts +++ b/CommonServer/Tests/Services/AnalyticsDatabaseService.test.ts @@ -1,249 +1,246 @@ -import AnalyticsDatabaseService from '../../Services/AnalyticsDatabaseService'; -import { SQL, Statement } from '../../Utils/AnalyticsDatabase/Statement'; -import logger from '../../Utils/Logger'; -import '../TestingUtils/Init'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AnalyticsTableEngine from 'Common/Types/AnalyticsDatabase/AnalyticsTableEngine'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import GenericObject from 'Common/Types/GenericObject'; +import AnalyticsDatabaseService from "../../Services/AnalyticsDatabaseService"; +import { SQL, Statement } from "../../Utils/AnalyticsDatabase/Statement"; +import logger from "../../Utils/Logger"; +import "../TestingUtils/Init"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import Route from "Common/Types/API/Route"; +import AnalyticsTableEngine from "Common/Types/AnalyticsDatabase/AnalyticsTableEngine"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import GenericObject from "Common/Types/GenericObject"; -describe('AnalyticsDatabaseService', () => { - class TestModel extends AnalyticsBaseModel { - public constructor() { - super({ - tableName: '<table-name>', - singularName: '<singular-name>', - pluralName: '<plural-name>', - tableColumns: [ - new AnalyticsTableColumn({ - key: `column_ObjectID`, - title: '<title>', - description: '<description>', - required: true, - type: TableColumnType.ObjectID, - }), - new AnalyticsTableColumn({ - key: `column_1`, - title: '<title>', - description: '<description>', - required: false, - type: TableColumnType.Text, - }), - new AnalyticsTableColumn({ - key: `column_2`, - title: '<title>', - description: '<description>', - required: false, - type: TableColumnType.Number, - }), - ], - crudApiPath: new Route('route'), - primaryKeys: ['column_ObjectID'], - tableEngine: AnalyticsTableEngine.MergeTree, - }); - } +describe("AnalyticsDatabaseService", () => { + class TestModel extends AnalyticsBaseModel { + public constructor() { + super({ + tableName: "<table-name>", + singularName: "<singular-name>", + pluralName: "<plural-name>", + tableColumns: [ + new AnalyticsTableColumn({ + key: `column_ObjectID`, + title: "<title>", + description: "<description>", + required: true, + type: TableColumnType.ObjectID, + }), + new AnalyticsTableColumn({ + key: `column_1`, + title: "<title>", + description: "<description>", + required: false, + type: TableColumnType.Text, + }), + new AnalyticsTableColumn({ + key: `column_2`, + title: "<title>", + description: "<description>", + required: false, + type: TableColumnType.Number, + }), + ], + crudApiPath: new Route("route"), + primaryKeys: ["column_ObjectID"], + tableEngine: AnalyticsTableEngine.MergeTree, + }); } + } - let service: AnalyticsDatabaseService<TestModel>; + let service: AnalyticsDatabaseService<TestModel>; + beforeEach(() => { + service = new AnalyticsDatabaseService({ + modelType: TestModel, + }); + }); + + describe("toCountStatement", () => { beforeEach(() => { - service = new AnalyticsDatabaseService({ - modelType: TestModel, - }); + service.statementGenerator.toWhereStatement = jest.fn(() => { + return SQL`<where-statement>`; + }); + jest.spyOn(logger, "debug").mockImplementation(() => { + return undefined!; + }); }); - describe('toCountStatement', () => { - beforeEach(() => { - service.statementGenerator.toWhereStatement = jest.fn(() => { - return SQL`<where-statement>`; - }); - jest.spyOn(logger, 'debug').mockImplementation(() => { - return undefined!; - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - test('should return count statement', () => { - const statement: Statement = service.toCountStatement({ - query: '<query>' as GenericObject, - props: '<props>' as GenericObject, - }); - - expect(service.statementGenerator.toWhereStatement).toBeCalledWith( - '<query>' - ); - - expect(logger.debug).toHaveBeenCalledTimes(2); - expect(logger.debug).toHaveBeenNthCalledWith( - 1, - '<table-name> Count Statement' - ); - expect(logger.debug).toHaveBeenNthCalledWith(2, statement); - - expect(statement.query).toBe( - 'SELECT\n' + - ' count()\n' + - 'FROM {p0:Identifier}.{p1:Identifier}\n' + - 'WHERE TRUE <where-statement>' - ); - expect(statement.query_params).toStrictEqual({ - p0: 'oneuptime', - p1: '<table-name>', - }); - }); - - test('optionally adds LIMIT', () => { - const statement: Statement = service.toCountStatement({ - query: '<query>' as GenericObject, - props: '<props>' as GenericObject, - limit: 123, - }); - - expect(statement.query).toBe( - 'SELECT\n' + - ' count()\n' + - 'FROM {p0:Identifier}.{p1:Identifier}\n' + - 'WHERE TRUE <where-statement>\n' + - 'LIMIT {p2:Int32}' - ); - expect(statement.query_params).toStrictEqual({ - p0: 'oneuptime', - p1: '<table-name>', - p2: 123, - }); - }); - - test('optionally adds OFFSET', () => { - const statement: Statement = service.toCountStatement({ - query: '<query>' as GenericObject, - props: '<props>' as GenericObject, - skip: 123, - }); - - expect(statement.query).toBe( - 'SELECT\n' + - ' count()\n' + - 'FROM {p0:Identifier}.{p1:Identifier}\n' + - 'WHERE TRUE <where-statement>\n' + - 'OFFSET {p2:Int32}' - ); - expect(statement.query_params).toStrictEqual({ - p0: 'oneuptime', - p1: '<table-name>', - p2: 123, - }); - }); + afterEach(() => { + jest.restoreAllMocks(); }); - describe('toFindStatement', () => { - beforeEach(() => { - service.statementGenerator.toSelectStatement = jest.fn(() => { - return { - statement: SQL`<select-statement>`, - columns: ['<column-1>', '<column-2>'], - }; - }); - service.statementGenerator.toWhereStatement = jest.fn(() => { - return SQL`<where-statement>`; - }); - service.statementGenerator.toSortStatement = jest.fn(() => { - return SQL`<sort-statement>`; - }); - jest.spyOn(logger, 'debug').mockImplementation(() => { - return undefined!; - }); - }); + test("should return count statement", () => { + const statement: Statement = service.toCountStatement({ + query: "<query>" as GenericObject, + props: "<props>" as GenericObject, + }); - afterEach(() => { - jest.restoreAllMocks(); - }); + expect(service.statementGenerator.toWhereStatement).toBeCalledWith( + "<query>", + ); - test('should return find statement', () => { - const { statement, columns } = service.toFindStatement({ - select: '<select>' as GenericObject, - query: '<query>' as GenericObject, - props: '<props>' as GenericObject, - sort: '<sort>' as GenericObject, - limit: 123, - skip: 234, - }); + expect(logger.debug).toHaveBeenCalledTimes(2); + expect(logger.debug).toHaveBeenNthCalledWith( + 1, + "<table-name> Count Statement", + ); + expect(logger.debug).toHaveBeenNthCalledWith(2, statement); - expect(service.statementGenerator.toSelectStatement).toBeCalledWith( - '<select>' - ); - expect(service.statementGenerator.toWhereStatement).toBeCalledWith( - '<query>' - ); - expect(service.statementGenerator.toSortStatement).toBeCalledWith( - '<sort>' - ); - - expect(jest.mocked(logger.debug)).toHaveBeenCalledTimes(2); - expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith( - 1, - '<table-name> Find Statement' - ); - expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith( - 2, - statement - ); - - expect(statement.query).toBe( - 'SELECT <select-statement> FROM {p0:Identifier}.{p1:Identifier} WHERE TRUE <where-statement> ORDER BY <sort-statement> LIMIT {p2:Int32} OFFSET {p3:Int32}' - ); - expect(statement.query_params).toStrictEqual({ - p0: 'oneuptime', - p1: '<table-name>', - p2: 123, // limit - p3: 234, // offset - }); - expect(columns).toStrictEqual(['<column-1>', '<column-2>']); - }); - - describe('toDeleteStatement', () => { - beforeEach(() => { - service.statementGenerator.toWhereStatement = jest.fn(() => { - return SQL`<where-statement>`; - }); - jest.spyOn(logger, 'debug').mockImplementation(() => { - return undefined!; - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - test('should return delete statement', () => { - const statement: Statement = service.toDeleteStatement({ - query: '<query>' as GenericObject, - props: '<props>' as GenericObject, - }); - - expect( - service.statementGenerator.toWhereStatement - ).toBeCalledWith('<query>'); - - expect(logger.debug).toHaveBeenCalledTimes(2); - expect(logger.debug).toHaveBeenNthCalledWith( - 1, - '<table-name> Delete Statement' - ); - expect(logger.debug).toHaveBeenNthCalledWith(2, statement); - - expect(statement.query).toBe( - 'ALTER TABLE {p0:Identifier}.{p1:Identifier}\n' + - 'DELETE WHERE TRUE <where-statement>' - ); - expect(statement.query_params).toStrictEqual({ - p0: 'oneuptime', - p1: '<table-name>', - }); - }); - }); + expect(statement.query).toBe( + "SELECT\n" + + " count()\n" + + "FROM {p0:Identifier}.{p1:Identifier}\n" + + "WHERE TRUE <where-statement>", + ); + expect(statement.query_params).toStrictEqual({ + p0: "oneuptime", + p1: "<table-name>", + }); }); + + test("optionally adds LIMIT", () => { + const statement: Statement = service.toCountStatement({ + query: "<query>" as GenericObject, + props: "<props>" as GenericObject, + limit: 123, + }); + + expect(statement.query).toBe( + "SELECT\n" + + " count()\n" + + "FROM {p0:Identifier}.{p1:Identifier}\n" + + "WHERE TRUE <where-statement>\n" + + "LIMIT {p2:Int32}", + ); + expect(statement.query_params).toStrictEqual({ + p0: "oneuptime", + p1: "<table-name>", + p2: 123, + }); + }); + + test("optionally adds OFFSET", () => { + const statement: Statement = service.toCountStatement({ + query: "<query>" as GenericObject, + props: "<props>" as GenericObject, + skip: 123, + }); + + expect(statement.query).toBe( + "SELECT\n" + + " count()\n" + + "FROM {p0:Identifier}.{p1:Identifier}\n" + + "WHERE TRUE <where-statement>\n" + + "OFFSET {p2:Int32}", + ); + expect(statement.query_params).toStrictEqual({ + p0: "oneuptime", + p1: "<table-name>", + p2: 123, + }); + }); + }); + + describe("toFindStatement", () => { + beforeEach(() => { + service.statementGenerator.toSelectStatement = jest.fn(() => { + return { + statement: SQL`<select-statement>`, + columns: ["<column-1>", "<column-2>"], + }; + }); + service.statementGenerator.toWhereStatement = jest.fn(() => { + return SQL`<where-statement>`; + }); + service.statementGenerator.toSortStatement = jest.fn(() => { + return SQL`<sort-statement>`; + }); + jest.spyOn(logger, "debug").mockImplementation(() => { + return undefined!; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test("should return find statement", () => { + const { statement, columns } = service.toFindStatement({ + select: "<select>" as GenericObject, + query: "<query>" as GenericObject, + props: "<props>" as GenericObject, + sort: "<sort>" as GenericObject, + limit: 123, + skip: 234, + }); + + expect(service.statementGenerator.toSelectStatement).toBeCalledWith( + "<select>", + ); + expect(service.statementGenerator.toWhereStatement).toBeCalledWith( + "<query>", + ); + expect(service.statementGenerator.toSortStatement).toBeCalledWith( + "<sort>", + ); + + expect(jest.mocked(logger.debug)).toHaveBeenCalledTimes(2); + expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith( + 1, + "<table-name> Find Statement", + ); + expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith(2, statement); + + expect(statement.query).toBe( + "SELECT <select-statement> FROM {p0:Identifier}.{p1:Identifier} WHERE TRUE <where-statement> ORDER BY <sort-statement> LIMIT {p2:Int32} OFFSET {p3:Int32}", + ); + expect(statement.query_params).toStrictEqual({ + p0: "oneuptime", + p1: "<table-name>", + p2: 123, // limit + p3: 234, // offset + }); + expect(columns).toStrictEqual(["<column-1>", "<column-2>"]); + }); + + describe("toDeleteStatement", () => { + beforeEach(() => { + service.statementGenerator.toWhereStatement = jest.fn(() => { + return SQL`<where-statement>`; + }); + jest.spyOn(logger, "debug").mockImplementation(() => { + return undefined!; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test("should return delete statement", () => { + const statement: Statement = service.toDeleteStatement({ + query: "<query>" as GenericObject, + props: "<props>" as GenericObject, + }); + + expect(service.statementGenerator.toWhereStatement).toBeCalledWith( + "<query>", + ); + + expect(logger.debug).toHaveBeenCalledTimes(2); + expect(logger.debug).toHaveBeenNthCalledWith( + 1, + "<table-name> Delete Statement", + ); + expect(logger.debug).toHaveBeenNthCalledWith(2, statement); + + expect(statement.query).toBe( + "ALTER TABLE {p0:Identifier}.{p1:Identifier}\n" + + "DELETE WHERE TRUE <where-statement>", + ); + expect(statement.query_params).toStrictEqual({ + p0: "oneuptime", + p1: "<table-name>", + }); + }); + }); + }); }); diff --git a/CommonServer/Tests/Services/BillingService.test.ts b/CommonServer/Tests/Services/BillingService.test.ts index aff3e33454..b0a2b9fcc3 100644 --- a/CommonServer/Tests/Services/BillingService.test.ts +++ b/CommonServer/Tests/Services/BillingService.test.ts @@ -1,1558 +1,1440 @@ import { - BillingService, - Invoice, - PaymentMethod, - SubscriptionItem, -} from '../../Services/BillingService'; -import { ActiveMonitoringMeteredPlan } from '../../Types/Billing/MeteredPlan/AllMeteredPlans'; -import Errors from '../../Utils/Errors'; + BillingService, + Invoice, + PaymentMethod, + SubscriptionItem, +} from "../../Services/BillingService"; +import { ActiveMonitoringMeteredPlan } from "../../Types/Billing/MeteredPlan/AllMeteredPlans"; +import Errors from "../../Utils/Errors"; import { - getChangePlanData, - getCouponData, - getCustomerData, - getMeteredSubscription, - getStripeCustomer, - getStripeInvoice, - getStripeSubscription, - getSubscriptionData, - getSubscriptionPlanData, - mockIsBillingEnabled, -} from '../TestingUtils/Services/Helpers'; + getChangePlanData, + getCouponData, + getCustomerData, + getMeteredSubscription, + getStripeCustomer, + getStripeInvoice, + getStripeSubscription, + getSubscriptionData, + getSubscriptionPlanData, + mockIsBillingEnabled, +} from "../TestingUtils/Services/Helpers"; import { - ChangePlan, - CouponData, - CustomerData, - MeteredSubscription, - PaymentMethodsResponse, - Subscription, -} from '../TestingUtils/Services/Types'; -import { Stripe, mockStripe } from '../TestingUtils/__mocks__/Stripe.mock'; -import { describe, expect, it } from '@jest/globals'; -import MeteredPlan from 'Common/Types/Billing/MeteredPlan'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus'; + ChangePlan, + CouponData, + CustomerData, + MeteredSubscription, + PaymentMethodsResponse, + Subscription, +} from "../TestingUtils/Services/Types"; +import { Stripe, mockStripe } from "../TestingUtils/__mocks__/Stripe.mock"; +import { describe, expect, it } from "@jest/globals"; +import MeteredPlan from "Common/Types/Billing/MeteredPlan"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import SubscriptionStatus from "Common/Types/Billing/SubscriptionStatus"; -describe('BillingService', () => { - let billingService: BillingService; - const customer: CustomerData = getCustomerData(); - const mockCustomer: Stripe.Customer = getStripeCustomer( - customer.id.toString() - ); +describe("BillingService", () => { + let billingService: BillingService; + const customer: CustomerData = getCustomerData(); + const mockCustomer: Stripe.Customer = getStripeCustomer( + customer.id.toString(), + ); - beforeEach( - async () => { - jest.clearAllMocks(); - billingService = mockIsBillingEnabled(true); - }, - 10 * 1000 // 10 second timeout because setting up the DB is slow - ); + beforeEach( + async () => { + jest.clearAllMocks(); + billingService = mockIsBillingEnabled(true); + }, + 10 * 1000, // 10 second timeout because setting up the DB is slow + ); - describe('Customer Management', () => { - describe('createCustomer', () => { - it('should create a customer when valid data is provided', async () => { - mockStripe.customers.create = jest - .fn() - .mockResolvedValue(mockCustomer); + describe("Customer Management", () => { + describe("createCustomer", () => { + it("should create a customer when valid data is provided", async () => { + mockStripe.customers.create = jest.fn().mockResolvedValue(mockCustomer); - const result: string = await billingService.createCustomer( - customer - ); + const result: string = await billingService.createCustomer(customer); - expect(result).toEqual(mockCustomer.id); - expect(mockStripe.customers.create).toHaveBeenCalledWith({ - name: customer.name, - email: customer.email.toString(), - metadata: { - id: customer.id.toString(), - }, - }); - expect(result).toBe(mockCustomer.id); - }); - - it('should throw an exception if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.createCustomer(customer) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); + expect(result).toEqual(mockCustomer.id); + expect(mockStripe.customers.create).toHaveBeenCalledWith({ + name: customer.name, + email: customer.email.toString(), + metadata: { + id: customer.id.toString(), + }, }); + expect(result).toBe(mockCustomer.id); + }); - describe('updateCustomerName', () => { - it('should successfully update a customer name', async () => { - const newName: string = 'newName'; - await billingService.updateCustomerName( - customer.id.toString(), - newName - ); - expect(mockStripe.customers.update).toHaveBeenCalledWith( - customer.id.toString(), - { name: newName } - ); - }); + it("should throw an exception if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); - it('should throw an exception if billing is not enabled for updating customer name', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.updateCustomerName('cust_123', 'Jane Doe') - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - }); - - describe('deleteCustomer', () => { - it('should successfully delete a customer', async () => { - await billingService.deleteCustomer(customer.id.toString()); - - expect(mockStripe.customers.del).toHaveBeenCalledWith( - customer.id.toString() - ); - }); - - it('should throw an exception if billing is not enabled for deleting customer', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.deleteCustomer('cust_123') - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - }); + await expect(billingService.createCustomer(customer)).rejects.toThrow( + Errors.BillingService.BILLING_NOT_ENABLED, + ); + }); }); - describe('Subscription Management', () => { - jest.useFakeTimers(); - let mockDate: Date = new Date(2023, 3, 1); // April 1, 2023 - jest.setSystemTime(mockDate); - - let mockSubscription: Stripe.Subscription; - const subscription: Subscription = getSubscriptionData(); - const subscriptionPlan: SubscriptionPlan = getSubscriptionPlanData(); - const meteredSubscription: MeteredSubscription = - getMeteredSubscription(subscriptionPlan); - - beforeEach(() => { - mockSubscription = getStripeSubscription(); - mockDate = new Date(2023, 3, 1); - }); - - describe('subscribeToMeteredPlan', () => { - it('should successfully create a metered plan subscription with all required parameters', async () => { - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValue(mockSubscription); - - const result: { - meteredSubscriptionId: string; - trialEndsAt: Date | null; - } = await billingService.subscribeToMeteredPlan(subscription); - - expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( - expect.objectContaining({ - customer: subscription.customerId, - items: [], - trial_end: 'now', - }) - ); - expect(result.meteredSubscriptionId).toBe(mockSubscription.id); - expect(result.trialEndsAt).toBe(subscription.trialDate); - }); - - it('should create a metered plan subscription with a trial date in the future', async () => { - const futureDate: Date = new Date(); - futureDate.setDate(futureDate.getDate() + 10); // 10 days in the future - subscription.trialDate = futureDate; - - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValue(mockSubscription); - - await billingService.subscribeToMeteredPlan(subscription); - - expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( - expect.objectContaining({ - trial_end: Math.floor( - subscription.trialDate.getTime() / 1000 - ), - }) - ); - }); - - it('should create a subscription without a trial when the trial date is not in the future', async () => { - const pastDate: Date = new Date('2020-01-01'); - subscription.trialDate = pastDate; - - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValue(mockSubscription); - - await billingService.subscribeToMeteredPlan(subscription); - - expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( - expect.objectContaining({ - trial_end: 'now', - }) - ); - }); - - it('should handle API errors during subscription creation', async () => { - mockStripe.subscriptions.create = jest - .fn() - .mockImplementation(() => { - throw new Error('Stripe API error'); - }); - - await expect( - billingService.subscribeToMeteredPlan(subscription) - ).rejects.toThrowError('Stripe API error'); - }); - - it('should correctly handle the promo code', async () => { - subscription.promoCode = 'VALIDPROMO'; - - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValue(mockSubscription); - - await billingService.subscribeToMeteredPlan(subscription); - - expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( - expect.objectContaining({ - coupon: subscription.promoCode, - }) - ); - }); - - it('should set the default payment method if provided', async () => { - subscription.defaultPaymentMethodId = 'pm_123'; - - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValue(mockSubscription); - - await billingService.subscribeToMeteredPlan(subscription); - - expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( - expect.objectContaining({ - default_payment_method: - subscription.defaultPaymentMethodId, - }) - ); - }); - }); - - describe('subscribeToPlan', () => { - it('should not subscribe to plan if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.subscribeToPlan(meteredSubscription) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should successfully subscribe a customer to a plan', async () => { - const mockSubscription2: Stripe.Subscription = - getStripeSubscription(); - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValueOnce(mockSubscription) - .mockResolvedValueOnce(mockSubscription2); - - const result: { - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt: Date | null; - } = await billingService.subscribeToPlan(meteredSubscription); - - expect(result.subscriptionId).toEqual(mockSubscription.id); - expect(result.meteredSubscriptionId).toEqual( - mockSubscription2.id - ); - const datePlusTrialDays: number = mockDate.setDate( - mockDate.getDate() + subscriptionPlan.getTrialPeriod() - ); - expect(result.trialEndsAt).toEqual(new Date(datePlusTrialDays)); - - expect(mockStripe.subscriptions.create).toHaveBeenCalledTimes( - 2 - ); - expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - customer: meteredSubscription.customerId, - items: expect.arrayContaining([ - expect.objectContaining({ - price: meteredSubscription.isYearly - ? subscriptionPlan.getYearlyPlanId() - : subscriptionPlan.getMonthlyPlanId(), - quantity: meteredSubscription.quantity, - }), - ]), - trial_end: datePlusTrialDays / 1000, - }) - ); - expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - customer: meteredSubscription.customerId, - trial_end: datePlusTrialDays / 1000, - }) - ); - }); - - it('should subscribe without a trial when trial is false', async () => { - meteredSubscription.trial = false; - const mockSubscription2: Stripe.Subscription = - getStripeSubscription(); - - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValueOnce(mockSubscription) - .mockResolvedValueOnce(mockSubscription2); - - const result: { - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt: Date | null; - } = await billingService.subscribeToPlan(meteredSubscription); - - expect(result.subscriptionId).toEqual(mockSubscription.id); - expect(result.meteredSubscriptionId).toEqual( - mockSubscription2.id - ); - expect(result.trialEndsAt).toBeNull(); - - expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - customer: meteredSubscription.customerId, - items: expect.arrayContaining([ - expect.objectContaining({ - price: meteredSubscription.isYearly - ? subscriptionPlan.getYearlyPlanId() - : subscriptionPlan.getMonthlyPlanId(), - quantity: meteredSubscription.quantity, - }), - ]), - trial_end: 'now', - }) - ); - expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - customer: meteredSubscription.customerId, - trial_end: 'now', - }) - ); - }); - - it('should apply a promo code if provided', async () => { - meteredSubscription.promoCode = 'PROMO123'; - const mockSubscription2: Stripe.Subscription = - getStripeSubscription(); - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValueOnce(mockSubscription) - .mockResolvedValueOnce(mockSubscription2); - - const result: { - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt: Date | null; - } = await billingService.subscribeToPlan(meteredSubscription); - - expect(result.subscriptionId).toEqual(mockSubscription.id); - expect(result.meteredSubscriptionId).toEqual( - mockSubscription2.id - ); - - expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - customer: meteredSubscription.customerId, - items: expect.arrayContaining([ - expect.objectContaining({ - price: meteredSubscription.isYearly - ? subscriptionPlan.getYearlyPlanId() - : subscriptionPlan.getMonthlyPlanId(), - quantity: meteredSubscription.quantity, - }), - ]), - coupon: meteredSubscription.promoCode, - }) - ); - expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - customer: meteredSubscription.customerId, - coupon: meteredSubscription.promoCode, - }) - ); - }); - - it('should set the default payment method if provided', async () => { - meteredSubscription.defaultPaymentMethodId = 'pm_123'; - const mockSubscription2: Stripe.Subscription = - getStripeSubscription(); - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValueOnce(mockSubscription) - .mockResolvedValueOnce(mockSubscription2); - - const result: { - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt: Date | null; - } = await billingService.subscribeToPlan(meteredSubscription); - - expect(result.subscriptionId).toEqual(mockSubscription.id); - expect(result.meteredSubscriptionId).toEqual( - mockSubscription2.id - ); - - expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - customer: meteredSubscription.customerId, - items: expect.arrayContaining([ - expect.objectContaining({ - price: meteredSubscription.isYearly - ? subscriptionPlan.getYearlyPlanId() - : subscriptionPlan.getMonthlyPlanId(), - quantity: meteredSubscription.quantity, - }), - ]), - default_payment_method: - meteredSubscription.defaultPaymentMethodId, - }) - ); - expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - customer: meteredSubscription.customerId, - default_payment_method: - meteredSubscription.defaultPaymentMethodId, - }) - ); - }); - }); - - describe('changeQuantity', () => { - it('should not change quantity if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.changeQuantity(mockSubscription.id, 1) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should successfully change the quantity of a subscription', async () => { - const newQuantity: number = 2; - - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - mockStripe.subscriptions.update = jest - .fn() - .mockResolvedValue({}); - - await billingService.changeQuantity( - mockSubscription.id, - newQuantity - ); - - expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( - mockSubscription.id - ); - expect( - mockStripe.subscriptionItems.update - ).toHaveBeenCalledWith(mockSubscription.items?.data[0]?.id, { - quantity: newQuantity, - }); - }); - - it('should handle subscription not found scenario in change quantity', async () => { - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(null); - - await expect( - billingService.changeQuantity('invalid_id', 2) - ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); - }); - - it('should not change quantity if the subscription is canceled', async () => { - mockSubscription.status = 'canceled'; - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - - await billingService.changeQuantity(mockSubscription.id, 2); - - expect(mockStripe.subscriptions.retrieve).toHaveBeenCalled(); - expect(mockStripe.subscriptions.update).not.toHaveBeenCalled(); - }); - - it('should handle missing subscription item ID in the subscription', async () => { - mockSubscription.items.data = []; - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - - await expect( - billingService.changeQuantity(mockSubscription.id, 2) - ).rejects.toThrow( - Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND - ); - }); - }); - - describe('changePlan', () => { - const newPlan: ChangePlan = getChangePlanData( - getSubscriptionPlanData() - ); - - it('should throw if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.changePlan(newPlan) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should successfully change the plan', async () => { - // mocks - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - - mockStripe.subscriptions.del = jest.fn().mockResolvedValue({}); - - const newMockSubscription: Stripe.Subscription = - getStripeSubscription(); - const newMockMeteredSubscription: Stripe.Subscription = - getStripeSubscription(); - - mockStripe.subscriptions.create = jest - .fn() - .mockResolvedValueOnce(newMockSubscription) - .mockResolvedValueOnce(newMockMeteredSubscription); - - const mockPaymentMethods: Array<PaymentMethod> = [ - { - id: 'pm_123', - type: 'card', - last4Digits: '4242', - isDefault: true, - }, - ]; - billingService.getPaymentMethods = jest - .fn() - .mockResolvedValue(mockPaymentMethods); - - const result: { - subscriptionId: string; - meteredSubscriptionId: string; - trialEndsAt?: Date | undefined; - } = await billingService.changePlan(newPlan); - - expect(result.subscriptionId).toEqual(newMockSubscription.id); - expect(result.meteredSubscriptionId).toEqual( - newMockMeteredSubscription.id - ); - - expect(mockStripe.subscriptions.del).toHaveBeenCalled(); - expect(mockStripe.subscriptions.del).toHaveBeenCalled(); - expect(mockStripe.subscriptions.create).toHaveBeenCalled(); - }); - - it('should handle errors when the current subscription is not found', async () => { - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(null); - - await expect( - billingService.changePlan(newPlan) - ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); - }); - - it('should check for active payment methods before changing the plan', async () => { - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - - const mockPaymentMethods: Array<PaymentMethod> = - Array<PaymentMethod>(); - billingService.getPaymentMethods = jest - .fn() - .mockResolvedValue(mockPaymentMethods); - - await expect( - billingService.changePlan(newPlan) - ).rejects.toThrow(Errors.BillingService.NO_PAYMENTS_METHODS); - }); - }); - - describe('isSubscriptionActive', () => { - it('should return true for an active subscription status', () => { - const activeStatuses: Array<SubscriptionStatus> = [ - SubscriptionStatus.Active, - SubscriptionStatus.Trialing, - ]; - - activeStatuses.forEach((status: SubscriptionStatus) => { - expect( - billingService.isSubscriptionActive(status) - ).toBeTruthy(); - }); - }); - - it('should return false for an inactive subscription status', () => { - const inactiveStatuses: Array<SubscriptionStatus> = [ - SubscriptionStatus.Incomplete, - SubscriptionStatus.IncompleteExpired, - SubscriptionStatus.PastDue, - SubscriptionStatus.Canceled, - SubscriptionStatus.Unpaid, - ]; - - inactiveStatuses.forEach((status: SubscriptionStatus) => { - expect( - billingService.isSubscriptionActive(status) - ).toBeFalsy(); - }); - }); - }); - - describe('addOrUpdateMeteredPricingOnSubscription', () => { - const quantity: number = 10; - - it('should throw if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.addOrUpdateMeteredPricingOnSubscription( - mockSubscription.id, - ActiveMonitoringMeteredPlan, - quantity - ) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should successfully add metered pricing to a subscription', async () => { - const subscriptionItem: SubscriptionItem | undefined = - mockSubscription.items.data[0]; - const meteredPlan: MeteredPlan = new MeteredPlan({ - priceId: subscriptionItem?.price?.id || '', - pricePerUnitInUSD: 100, - unitName: 'unit', - }); - - mockSubscription.items.data = []; - - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - mockStripe.subscriptionItems.create = jest - .fn() - .mockResolvedValue({ id: 'sub_item_123' }); - mockStripe.subscriptionItems.createUsageRecord = jest - .fn() - .mockResolvedValue({}); - - await billingService.addOrUpdateMeteredPricingOnSubscription( - mockSubscription.id, - ActiveMonitoringMeteredPlan, - quantity - ); - - expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( - mockSubscription.id - ); - expect( - mockStripe.subscriptionItems.create - ).toHaveBeenCalledWith({ - subscription: mockSubscription.id, - price: meteredPlan.getPriceId(), - }); - expect( - mockStripe.subscriptionItems.createUsageRecord - ).toHaveBeenCalledWith('sub_item_123', { quantity }); - }); - - it('should successfully update existing metered pricing on a subscription', async () => { - const subscriptionItem: SubscriptionItem | undefined = - mockSubscription.items.data[0]; - - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - mockStripe.subscriptionItems.createUsageRecord = jest - .fn() - .mockResolvedValue({}); - - await billingService.addOrUpdateMeteredPricingOnSubscription( - mockSubscription.id, - ActiveMonitoringMeteredPlan, - quantity - ); - - expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( - mockSubscription.id - ); - expect( - mockStripe.subscriptionItems.createUsageRecord - ).toHaveBeenCalledWith(subscriptionItem?.id, { - quantity: quantity, - }); - }); - - it('should handle non-existent subscription', async () => { - const subscriptionId: string = 'sub_nonexistent'; - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(null); - - await expect( - billingService.addOrUpdateMeteredPricingOnSubscription( - subscriptionId, - ActiveMonitoringMeteredPlan, - quantity - ) - ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); - }); - }); - - describe('isPromoCodeValid', () => { - it('should throw if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.isPromoCodeValid('INVALID_PROMO_CODE') - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should return true for a valid promo code', async () => { - const promoCode: string = 'VALIDPROMO'; - const mockCoupon: { valid: boolean } = { valid: true }; - - mockStripe.coupons.retrieve = jest - .fn() - .mockResolvedValue(mockCoupon); - - const isValid: boolean = await billingService.isPromoCodeValid( - promoCode - ); - - expect(isValid).toBeTruthy(); - expect(mockStripe.coupons.retrieve).toHaveBeenCalledWith( - promoCode - ); - }); - - it('should return false for an invalid or expired promo code', async () => { - const promoCode: string = 'INVALIDPROMO'; - const mockCoupon: { valid: boolean } = { - valid: false, - }; - mockStripe.coupons.retrieve = jest - .fn() - .mockResolvedValue(mockCoupon); - - const isValid: boolean = await billingService.isPromoCodeValid( - promoCode - ); - - expect(isValid).toBeFalsy(); - expect(mockStripe.coupons.retrieve).toHaveBeenCalledWith( - promoCode - ); - }); - - it('should handle non-existent promo code', async () => { - const promoCode: string = 'NONEXISTENTPROMO'; - - mockStripe.coupons.retrieve = jest.fn().mockResolvedValue(null); - - await expect( - billingService.isPromoCodeValid(promoCode) - ).rejects.toThrow(Errors.BillingService.PROMO_CODE_NOT_FOUND); - - expect(mockStripe.coupons.retrieve).toHaveBeenCalledWith( - promoCode - ); - }); - - it('should handle errors from the Stripe API', async () => { - const promoCode: string = 'ERRORPROMO'; - - mockStripe.coupons.retrieve = jest - .fn() - .mockImplementation(() => { - throw new Error(); - }); - - await expect( - billingService.isPromoCodeValid(promoCode) - ).rejects.toThrow(Errors.BillingService.PROMO_CODE_INVALID); - }); - }); - - describe('removeSubscriptionItem', () => { - const subscriptionId: string = 'sub_123'; - const subscriptionItemId: string = 'si_123'; - const isMeteredSubscriptionItem: boolean = false; - - it('should throw if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.removeSubscriptionItem( - subscriptionId, - subscriptionItemId, - isMeteredSubscriptionItem - ) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should successfully remove a metered subscription item', async () => { - const isMeteredSubscriptionItem: boolean = true; - - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - mockStripe.subscriptionItems.del = jest - .fn() - .mockResolvedValue({}); - - await billingService.removeSubscriptionItem( - subscriptionId, - subscriptionItemId, - isMeteredSubscriptionItem - ); - - expect(mockStripe.subscriptionItems.del).toHaveBeenCalledWith( - subscriptionItemId, - { - proration_behavior: 'create_prorations', - clear_usage: true, - } - ); - }); - - it('should successfully remove a metered subscription item when isMeteredSubscriptionItem', async () => { - const subscriptionItemId: string = - mockSubscription.items.data[0]?.id || ''; - - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - mockStripe.subscriptionItems.del = jest - .fn() - .mockResolvedValue({}); - - await billingService.removeSubscriptionItem( - mockSubscription.id, - subscriptionItemId, - isMeteredSubscriptionItem - ); - - expect(mockStripe.subscriptionItems.del).toHaveBeenCalledWith( - subscriptionItemId, - {} - ); - }); - - it('should handle non-existent subscription or subscription item', async () => { - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(null); - - await expect( - billingService.removeSubscriptionItem( - subscriptionId, - subscriptionItemId, - isMeteredSubscriptionItem - ) - ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); - }); - - it('should handle errors from the Stripe API', async () => { - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - mockStripe.subscriptionItems.del = jest - .fn() - .mockImplementation(() => { - throw new Error('Stripe API error'); - }); - - await expect( - billingService.removeSubscriptionItem( - subscriptionId, - subscriptionItemId, - isMeteredSubscriptionItem - ) - ).rejects.toThrow('Stripe API error'); - }); - - it('should not remove an item if the subscription is canceled', async () => { - const isMeteredSubscriptionItem: boolean = false; - mockSubscription.status = 'canceled'; - - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - mockStripe.subscriptionItems.del = jest.fn(); - - await billingService.removeSubscriptionItem( - subscriptionId, - subscriptionItemId, - isMeteredSubscriptionItem - ); - expect(mockStripe.subscriptions.del).not.toHaveBeenCalled(); - }); - }); - - describe('getSubscriptionItems', () => { - it('should throw if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.getSubscriptionItems(mockSubscription.id) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should successfully retrieve subscription items for a given subscription', async () => { - mockSubscription.items.data = [ - // @ts-ignore - { id: 'item_1', price: { id: 'price_123' } }, - // @ts-ignore - { id: 'item_2', price: { id: 'price_456' } }, - ]; - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - - const items: SubscriptionItem[] = - await billingService.getSubscriptionItems( - mockSubscription.id - ); - - expect(items).toEqual(mockSubscription.items.data); - expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( - mockSubscription.id - ); - }); - - it('should handle the case where the subscription does not exist', async () => { - const subscriptionId: string = 'sub_nonexistent'; - - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(null); - - await expect( - billingService.getSubscriptionItems(subscriptionId) - ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); - }); - }); + describe("updateCustomerName", () => { + it("should successfully update a customer name", async () => { + const newName: string = "newName"; + await billingService.updateCustomerName( + customer.id.toString(), + newName, + ); + expect(mockStripe.customers.update).toHaveBeenCalledWith( + customer.id.toString(), + { name: newName }, + ); + }); + + it("should throw an exception if billing is not enabled for updating customer name", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.updateCustomerName("cust_123", "Jane Doe"), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); }); - describe('Payment & Billing', () => { - const customerId: string = 'cust_123'; - const invoiceId: string = 'inv_123'; - const paymentMethodId: string = 'pm_123'; - const subscriptionId: string = 'sub_123'; + describe("deleteCustomer", () => { + it("should successfully delete a customer", async () => { + await billingService.deleteCustomer(customer.id.toString()); + + expect(mockStripe.customers.del).toHaveBeenCalledWith( + customer.id.toString(), + ); + }); + + it("should throw an exception if billing is not enabled for deleting customer", async () => { + billingService = mockIsBillingEnabled(false); + + await expect(billingService.deleteCustomer("cust_123")).rejects.toThrow( + Errors.BillingService.BILLING_NOT_ENABLED, + ); + }); + }); + }); + + describe("Subscription Management", () => { + jest.useFakeTimers(); + let mockDate: Date = new Date(2023, 3, 1); // April 1, 2023 + jest.setSystemTime(mockDate); + + let mockSubscription: Stripe.Subscription; + const subscription: Subscription = getSubscriptionData(); + const subscriptionPlan: SubscriptionPlan = getSubscriptionPlanData(); + const meteredSubscription: MeteredSubscription = + getMeteredSubscription(subscriptionPlan); + + beforeEach(() => { + mockSubscription = getStripeSubscription(); + mockDate = new Date(2023, 3, 1); + }); + + describe("subscribeToMeteredPlan", () => { + it("should successfully create a metered plan subscription with all required parameters", async () => { + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValue(mockSubscription); + + const result: { + meteredSubscriptionId: string; + trialEndsAt: Date | null; + } = await billingService.subscribeToMeteredPlan(subscription); + + expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( + expect.objectContaining({ + customer: subscription.customerId, + items: [], + trial_end: "now", + }), + ); + expect(result.meteredSubscriptionId).toBe(mockSubscription.id); + expect(result.trialEndsAt).toBe(subscription.trialDate); + }); + + it("should create a metered plan subscription with a trial date in the future", async () => { + const futureDate: Date = new Date(); + futureDate.setDate(futureDate.getDate() + 10); // 10 days in the future + subscription.trialDate = futureDate; + + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValue(mockSubscription); + + await billingService.subscribeToMeteredPlan(subscription); + + expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( + expect.objectContaining({ + trial_end: Math.floor(subscription.trialDate.getTime() / 1000), + }), + ); + }); + + it("should create a subscription without a trial when the trial date is not in the future", async () => { + const pastDate: Date = new Date("2020-01-01"); + subscription.trialDate = pastDate; + + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValue(mockSubscription); + + await billingService.subscribeToMeteredPlan(subscription); + + expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( + expect.objectContaining({ + trial_end: "now", + }), + ); + }); + + it("should handle API errors during subscription creation", async () => { + mockStripe.subscriptions.create = jest.fn().mockImplementation(() => { + throw new Error("Stripe API error"); + }); + + await expect( + billingService.subscribeToMeteredPlan(subscription), + ).rejects.toThrowError("Stripe API error"); + }); + + it("should correctly handle the promo code", async () => { + subscription.promoCode = "VALIDPROMO"; + + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValue(mockSubscription); + + await billingService.subscribeToMeteredPlan(subscription); + + expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( + expect.objectContaining({ + coupon: subscription.promoCode, + }), + ); + }); + + it("should set the default payment method if provided", async () => { + subscription.defaultPaymentMethodId = "pm_123"; + + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValue(mockSubscription); + + await billingService.subscribeToMeteredPlan(subscription); + + expect(mockStripe.subscriptions.create).toHaveBeenCalledWith( + expect.objectContaining({ + default_payment_method: subscription.defaultPaymentMethodId, + }), + ); + }); + }); + + describe("subscribeToPlan", () => { + it("should not subscribe to plan if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.subscribeToPlan(meteredSubscription), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should successfully subscribe a customer to a plan", async () => { + const mockSubscription2: Stripe.Subscription = getStripeSubscription(); + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValueOnce(mockSubscription) + .mockResolvedValueOnce(mockSubscription2); + + const result: { + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt: Date | null; + } = await billingService.subscribeToPlan(meteredSubscription); + + expect(result.subscriptionId).toEqual(mockSubscription.id); + expect(result.meteredSubscriptionId).toEqual(mockSubscription2.id); + const datePlusTrialDays: number = mockDate.setDate( + mockDate.getDate() + subscriptionPlan.getTrialPeriod(), + ); + expect(result.trialEndsAt).toEqual(new Date(datePlusTrialDays)); + + expect(mockStripe.subscriptions.create).toHaveBeenCalledTimes(2); + expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + customer: meteredSubscription.customerId, + items: expect.arrayContaining([ + expect.objectContaining({ + price: meteredSubscription.isYearly + ? subscriptionPlan.getYearlyPlanId() + : subscriptionPlan.getMonthlyPlanId(), + quantity: meteredSubscription.quantity, + }), + ]), + trial_end: datePlusTrialDays / 1000, + }), + ); + expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + customer: meteredSubscription.customerId, + trial_end: datePlusTrialDays / 1000, + }), + ); + }); + + it("should subscribe without a trial when trial is false", async () => { + meteredSubscription.trial = false; + const mockSubscription2: Stripe.Subscription = getStripeSubscription(); + + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValueOnce(mockSubscription) + .mockResolvedValueOnce(mockSubscription2); + + const result: { + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt: Date | null; + } = await billingService.subscribeToPlan(meteredSubscription); + + expect(result.subscriptionId).toEqual(mockSubscription.id); + expect(result.meteredSubscriptionId).toEqual(mockSubscription2.id); + expect(result.trialEndsAt).toBeNull(); + + expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + customer: meteredSubscription.customerId, + items: expect.arrayContaining([ + expect.objectContaining({ + price: meteredSubscription.isYearly + ? subscriptionPlan.getYearlyPlanId() + : subscriptionPlan.getMonthlyPlanId(), + quantity: meteredSubscription.quantity, + }), + ]), + trial_end: "now", + }), + ); + expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + customer: meteredSubscription.customerId, + trial_end: "now", + }), + ); + }); + + it("should apply a promo code if provided", async () => { + meteredSubscription.promoCode = "PROMO123"; + const mockSubscription2: Stripe.Subscription = getStripeSubscription(); + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValueOnce(mockSubscription) + .mockResolvedValueOnce(mockSubscription2); + + const result: { + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt: Date | null; + } = await billingService.subscribeToPlan(meteredSubscription); + + expect(result.subscriptionId).toEqual(mockSubscription.id); + expect(result.meteredSubscriptionId).toEqual(mockSubscription2.id); + + expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + customer: meteredSubscription.customerId, + items: expect.arrayContaining([ + expect.objectContaining({ + price: meteredSubscription.isYearly + ? subscriptionPlan.getYearlyPlanId() + : subscriptionPlan.getMonthlyPlanId(), + quantity: meteredSubscription.quantity, + }), + ]), + coupon: meteredSubscription.promoCode, + }), + ); + expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + customer: meteredSubscription.customerId, + coupon: meteredSubscription.promoCode, + }), + ); + }); + + it("should set the default payment method if provided", async () => { + meteredSubscription.defaultPaymentMethodId = "pm_123"; + const mockSubscription2: Stripe.Subscription = getStripeSubscription(); + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValueOnce(mockSubscription) + .mockResolvedValueOnce(mockSubscription2); + + const result: { + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt: Date | null; + } = await billingService.subscribeToPlan(meteredSubscription); + + expect(result.subscriptionId).toEqual(mockSubscription.id); + expect(result.meteredSubscriptionId).toEqual(mockSubscription2.id); + + expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + customer: meteredSubscription.customerId, + items: expect.arrayContaining([ + expect.objectContaining({ + price: meteredSubscription.isYearly + ? subscriptionPlan.getYearlyPlanId() + : subscriptionPlan.getMonthlyPlanId(), + quantity: meteredSubscription.quantity, + }), + ]), + default_payment_method: meteredSubscription.defaultPaymentMethodId, + }), + ); + expect(mockStripe.subscriptions.create).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + customer: meteredSubscription.customerId, + default_payment_method: meteredSubscription.defaultPaymentMethodId, + }), + ); + }); + }); + + describe("changeQuantity", () => { + it("should not change quantity if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.changeQuantity(mockSubscription.id, 1), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should successfully change the quantity of a subscription", async () => { + const newQuantity: number = 2; + + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + mockStripe.subscriptions.update = jest.fn().mockResolvedValue({}); + + await billingService.changeQuantity(mockSubscription.id, newQuantity); + + expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( + mockSubscription.id, + ); + expect(mockStripe.subscriptionItems.update).toHaveBeenCalledWith( + mockSubscription.items?.data[0]?.id, + { + quantity: newQuantity, + }, + ); + }); + + it("should handle subscription not found scenario in change quantity", async () => { + mockStripe.subscriptions.retrieve = jest.fn().mockResolvedValue(null); + + await expect( + billingService.changeQuantity("invalid_id", 2), + ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); + }); + + it("should not change quantity if the subscription is canceled", async () => { + mockSubscription.status = "canceled"; + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + + await billingService.changeQuantity(mockSubscription.id, 2); + + expect(mockStripe.subscriptions.retrieve).toHaveBeenCalled(); + expect(mockStripe.subscriptions.update).not.toHaveBeenCalled(); + }); + + it("should handle missing subscription item ID in the subscription", async () => { + mockSubscription.items.data = []; + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + + await expect( + billingService.changeQuantity(mockSubscription.id, 2), + ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND); + }); + }); + + describe("changePlan", () => { + const newPlan: ChangePlan = getChangePlanData(getSubscriptionPlanData()); + + it("should throw if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect(billingService.changePlan(newPlan)).rejects.toThrow( + Errors.BillingService.BILLING_NOT_ENABLED, + ); + }); + + it("should successfully change the plan", async () => { + // mocks + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + + mockStripe.subscriptions.del = jest.fn().mockResolvedValue({}); + + const newMockSubscription: Stripe.Subscription = + getStripeSubscription(); + const newMockMeteredSubscription: Stripe.Subscription = + getStripeSubscription(); + + mockStripe.subscriptions.create = jest + .fn() + .mockResolvedValueOnce(newMockSubscription) + .mockResolvedValueOnce(newMockMeteredSubscription); const mockPaymentMethods: Array<PaymentMethod> = [ - { - id: 'pm_123', - type: 'card', - last4Digits: '4242', - isDefault: true, - }, - { - id: 'pm_456', - type: 'card', - last4Digits: '4343', - isDefault: false, - }, + { + id: "pm_123", + type: "card", + last4Digits: "4242", + isDefault: true, + }, + ]; + billingService.getPaymentMethods = jest + .fn() + .mockResolvedValue(mockPaymentMethods); + + const result: { + subscriptionId: string; + meteredSubscriptionId: string; + trialEndsAt?: Date | undefined; + } = await billingService.changePlan(newPlan); + + expect(result.subscriptionId).toEqual(newMockSubscription.id); + expect(result.meteredSubscriptionId).toEqual( + newMockMeteredSubscription.id, + ); + + expect(mockStripe.subscriptions.del).toHaveBeenCalled(); + expect(mockStripe.subscriptions.del).toHaveBeenCalled(); + expect(mockStripe.subscriptions.create).toHaveBeenCalled(); + }); + + it("should handle errors when the current subscription is not found", async () => { + mockStripe.subscriptions.retrieve = jest.fn().mockResolvedValue(null); + + await expect(billingService.changePlan(newPlan)).rejects.toThrow( + Errors.BillingService.SUBSCRIPTION_NOT_FOUND, + ); + }); + + it("should check for active payment methods before changing the plan", async () => { + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + + const mockPaymentMethods: Array<PaymentMethod> = Array<PaymentMethod>(); + billingService.getPaymentMethods = jest + .fn() + .mockResolvedValue(mockPaymentMethods); + + await expect(billingService.changePlan(newPlan)).rejects.toThrow( + Errors.BillingService.NO_PAYMENTS_METHODS, + ); + }); + }); + + describe("isSubscriptionActive", () => { + it("should return true for an active subscription status", () => { + const activeStatuses: Array<SubscriptionStatus> = [ + SubscriptionStatus.Active, + SubscriptionStatus.Trialing, ]; - describe('generateCouponCode', () => { - const couponData: CouponData = getCouponData(); - const mockCoupon: { id: string; valid: boolean } = { - id: 'coupon_123', - valid: true, - }; - - it('should successfully generate a coupon code', async () => { - mockStripe.coupons.create = jest - .fn() - .mockResolvedValue(mockCoupon); - - const result: string = await billingService.generateCouponCode( - couponData - ); - - expect(result).toEqual(mockCoupon.id); - expect(mockStripe.coupons.create).toHaveBeenCalledWith( - expect.objectContaining({ - name: couponData.name, - percent_off: couponData.percentOff, - duration: 'repeating', - duration_in_months: couponData.durationInMonths, - max_redemptions: couponData.maxRedemptions, - metadata: couponData.metadata, - }) - ); - }); + activeStatuses.forEach((status: SubscriptionStatus) => { + expect(billingService.isSubscriptionActive(status)).toBeTruthy(); }); + }); - describe('deletePaymentMethod', () => { - it('should throw if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); + it("should return false for an inactive subscription status", () => { + const inactiveStatuses: Array<SubscriptionStatus> = [ + SubscriptionStatus.Incomplete, + SubscriptionStatus.IncompleteExpired, + SubscriptionStatus.PastDue, + SubscriptionStatus.Canceled, + SubscriptionStatus.Unpaid, + ]; - await expect( - billingService.deletePaymentMethod( - customerId, - paymentMethodId - ) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should successfully delete a payment method', async () => { - billingService.getPaymentMethods = jest - .fn() - .mockResolvedValue(mockPaymentMethods); - - mockStripe.paymentMethods.detach = jest - .fn() - .mockResolvedValue({}); - - await billingService.deletePaymentMethod( - customerId, - paymentMethodId - ); - - expect(mockStripe.paymentMethods.detach).toHaveBeenCalledWith( - paymentMethodId - ); - }); - - it("should throw an exception if it's the only payment method", async () => { - // mock a single payment method to simulate a scenario where deletion is not allowed - const mockSinglePaymentMethod: Array<PaymentMethod> = [ - { - id: paymentMethodId, - type: 'card', - last4Digits: '4242', - isDefault: true, - }, - ]; - billingService.getPaymentMethods = jest - .fn() - .mockResolvedValue(mockSinglePaymentMethod); - - await expect( - billingService.deletePaymentMethod( - customerId, - paymentMethodId - ) - ).rejects.toThrow( - Errors.BillingService.MIN_REQUIRED_PAYMENT_METHOD_NOT_MET - ); - }); - }); - - describe('hasPaymentMethods', () => { - it('should return true if the customer has payment methods', async () => { - billingService.getPaymentMethods = jest - .fn() - .mockResolvedValue(mockPaymentMethods); - - const result: boolean = await billingService.hasPaymentMethods( - customerId - ); - - expect(result).toBeTruthy(); - expect(billingService.getPaymentMethods).toHaveBeenCalledWith( - customerId - ); - }); - - it('should return false if the customer does not have payment methods', async () => { - const mockEmptyPaymentMethods: PaymentMethod[] = - Array<PaymentMethod>(); - billingService.getPaymentMethods = jest - .fn() - .mockResolvedValue(mockEmptyPaymentMethods); - - const result: boolean = await billingService.hasPaymentMethods( - customerId - ); - - expect(result).toBeFalsy(); - expect(billingService.getPaymentMethods).toHaveBeenCalledWith( - customerId - ); - }); - }); - - describe('getPaymentMethods', () => { - it('should throw if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.getPaymentMethods(customerId) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should return all payment methods for a customer', async () => { - const mockPaymentMethodsResponse: { - data: Array<Stripe.PaymentMethod>; - } = { - data: [ - { - id: 'pm_123', - type: 'card', - // @ts-ignore - card: { last4: '4242', brand: 'mastercard' }, - isDefault: true, - }, - { - id: 'pm_456', - type: 'card', - // @ts-ignore - card: { last4: '4343', brand: 'mastercard' }, - isDefault: true, - }, - ], - }; - mockStripe.paymentMethods.list = jest - .fn() - .mockResolvedValueOnce(mockPaymentMethodsResponse) - .mockResolvedValue({ data: [] }); - mockStripe.customers.retrieve = jest - .fn() - .mockResolvedValue(mockCustomer); - - const paymentMethods: PaymentMethod[] = - await billingService.getPaymentMethods(customerId); - - expect(paymentMethods).toHaveLength(2); - expect(paymentMethods[0]?.id).toBe('pm_123'); - expect(paymentMethods[0]?.last4Digits).toBe('4242'); - - expect(mockStripe.paymentMethods.list).toHaveBeenCalledWith({ - customer: customerId, - type: 'card', - }); - }); - - it('should return an empty array if no payment methods are present', async () => { - const mockEmptyPaymentMethodsResponse: { - data: Array<PaymentMethod>; - } = { data: [] }; - - mockStripe.paymentMethods.list = jest - .fn() - .mockResolvedValue(mockEmptyPaymentMethodsResponse); - mockStripe.customers.retrieve = jest - .fn() - .mockResolvedValue(mockCustomer); - - const paymentMethods: PaymentMethod[] = - await billingService.getPaymentMethods(customerId); - - expect(paymentMethods).toEqual([]); - expect(mockStripe.paymentMethods.list).toHaveBeenCalledWith({ - customer: customerId, - type: 'card', - }); - }); - }); - - describe('getSetupIntentSecret', () => { - it('should successfully return a setup intent secret', async () => { - const mockSetupIntent: { client_secret: string } = { - client_secret: 'seti_123_secret_xyz', - }; - mockStripe.setupIntents.create = jest - .fn() - .mockResolvedValue(mockSetupIntent); - - const secret: string = - await billingService.getSetupIntentSecret(customerId); - - expect(secret).toBe(mockSetupIntent.client_secret); - expect(mockStripe.setupIntents.create).toHaveBeenCalledWith({ - customer: customerId, - }); - }); - - it('should handle missing client secret in the response', async () => { - mockStripe.setupIntents.create = jest - .fn() - .mockResolvedValue({}); - - await expect( - billingService.getSetupIntentSecret(customerId) - ).rejects.toThrow(Errors.BillingService.CLIENT_SECRET_MISSING); - }); - }); - - describe('cancelSubscription', () => { - it('should successfully cancel a subscription', async () => { - mockStripe.subscriptions.del = jest.fn().mockResolvedValue({ - id: subscriptionId, - status: 'canceled', - }); - - await billingService.cancelSubscription(subscriptionId); - - expect(mockStripe.subscriptions.del).toHaveBeenCalledWith( - subscriptionId - ); - }); - - it('should handle errors from the Stripe API', async () => { - const subscriptionId: string = 'sub_123'; - - // mock an error response from the Stripe API - mockStripe.subscriptions.del = jest - .fn() - .mockImplementation(() => { - throw new Error('Stripe API error'); - }); - - await billingService.cancelSubscription(subscriptionId); - // todo: we could expect the error to be logged - }); - - it('should not cancel a subscription if billing is not enabled', async () => { - const subscriptionId: string = 'sub_123'; - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.cancelSubscription(subscriptionId) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - expect(mockStripe.subscriptions.del).not.toHaveBeenCalled(); - }); - }); - - describe('getSubscriptionStatus', () => { - const expectedStatus: SubscriptionStatus = - SubscriptionStatus.Active; - - it('should successfully retrieve the subscription status', async () => { - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue({ - id: subscriptionId, - status: expectedStatus, - }); - - const status: SubscriptionStatus = - await billingService.getSubscriptionStatus(subscriptionId); - - expect(status).toBe(expectedStatus); - expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( - subscriptionId - ); - }); - - it('should successfully retrieve the subscription status', async () => { - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue({ - id: subscriptionId, - status: expectedStatus, - }); - - const status: SubscriptionStatus = - await billingService.getSubscriptionStatus(subscriptionId); - - expect(status).toBe(expectedStatus); - expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( - subscriptionId - ); - }); - }); - - describe('getSubscription', () => { - it('should throw if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.getSubscription(subscriptionId) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should successfully retrieve subscription data', async () => { - const subscriptionId: string = 'sub_123'; - const mockSubscription: Stripe.Subscription = - getStripeSubscription(); - - mockStripe.subscriptions.retrieve = jest - .fn() - .mockResolvedValue(mockSubscription); - - const subscription: Stripe.Subscription = - await billingService.getSubscription(subscriptionId); - - expect(subscription).toEqual(mockSubscription); - expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( - subscriptionId - ); - }); - }); - - describe('getInvoices', () => { - it('should successfully retrieve a list of invoices for a customer', async () => { - const mockInvoices: { data: Array<Stripe.Invoice> } = { - data: [getStripeInvoice(), getStripeInvoice()], - }; - mockStripe.invoices.list = jest - .fn() - .mockResolvedValue(mockInvoices); - - const invoices: Array<Invoice> = - await billingService.getInvoices(customerId); - - expect(invoices).toEqual( - mockInvoices.data.map((invoice: Stripe.Invoice) => { - return { - id: invoice.id, - amount: invoice.amount_due, - currencyCode: invoice.currency, - customerId: invoice.customer, - downloadableLink: '', - status: 'paid', - subscriptionId: invoice.subscription, - }; - }) - ); - expect(mockStripe.invoices.list).toHaveBeenCalledWith({ - customer: customerId, - limit: 100, - }); - }); - - it('should return an empty array if no invoices are found for the customer', async () => { - const mockEmptyInvoices: { data: Invoice[] } = { data: [] }; - mockStripe.invoices.list = jest - .fn() - .mockResolvedValue(mockEmptyInvoices); - - const invoices: Array<Invoice> = - await billingService.getInvoices(customerId); - - expect(invoices).toEqual([]); - expect(mockStripe.invoices.list).toHaveBeenCalledWith({ - customer: customerId, - limit: 100, - }); - }); - }); - - describe('generateInvoiceAndChargeCustomer', () => { - const itemText: string = 'Service Charge'; - const amountInUsd: number = 100; - const mockPaymentMethodsResponse: PaymentMethodsResponse = { - data: [ - { - id: 'pm_123', - type: 'card', - last4Digits: '4242', - isDefault: true, - }, - ], - }; - - it('should successfully generate an invoice and charge the customer', async () => { - mockStripe.paymentMethods.list = jest - .fn() - .mockResolvedValueOnce(mockPaymentMethodsResponse) - .mockResolvedValue({ data: [] }); - mockStripe.customers.retrieve = jest - .fn() - .mockResolvedValue(mockCustomer); - - // mock responses for invoice creation, adding an item, and finalizing - const mockInvoice: any = getStripeInvoice(); - mockStripe.invoices.create = jest - .fn() - .mockResolvedValue(mockInvoice); - mockStripe.invoiceItems.create = jest - .fn() - .mockResolvedValue({}); - mockStripe.invoices.finalizeInvoice = jest - .fn() - .mockResolvedValue({}); - - // mock response for paying the invoice - mockStripe.invoices.pay = jest.fn().mockResolvedValue({}); - - await billingService.generateInvoiceAndChargeCustomer( - customerId, - itemText, - amountInUsd - ); - - expect(mockStripe.invoices.create).toHaveBeenCalledWith( - expect.objectContaining({ customer: customerId }) - ); - expect(mockStripe.invoiceItems.create).toHaveBeenCalledWith( - expect.objectContaining({ invoice: mockInvoice.id }) - ); - expect( - mockStripe.invoices.finalizeInvoice - ).toHaveBeenCalledWith(mockInvoice.id); - expect(mockStripe.invoices.pay).toHaveBeenCalledWith( - mockInvoice.id, - { payment_method: mockPaymentMethodsResponse.data[0]?.id } - ); - }); - - it('should handle payment method errors when creating the invoice', async () => { - mockStripe.invoices.create = jest.fn().mockResolvedValue(null); - - await expect( - billingService.generateInvoiceAndChargeCustomer( - customerId, - itemText, - amountInUsd - ) - ).rejects.toThrow(Errors.BillingService.INVOICE_NOT_GENERATED); - }); - - it('should handle payment method errors when charging the invoice', async () => { - mockStripe.paymentMethods.list = jest - .fn() - .mockResolvedValueOnce(mockPaymentMethodsResponse) - .mockResolvedValue({ data: [] }); - mockStripe.customers.retrieve = jest - .fn() - .mockResolvedValue(mockCustomer); - - // mock successful invoice creation and finalization - const mockInvoice: any = getStripeInvoice(); - mockStripe.invoices.create = jest - .fn() - .mockResolvedValue(mockInvoice); - mockStripe.invoiceItems.create = jest - .fn() - .mockResolvedValue({}); - mockStripe.invoices.finalizeInvoice = jest - .fn() - .mockResolvedValue({}); - - billingService.voidInvoice = jest.fn(); - - // mock an error during invoice payment - mockStripe.invoices.pay = jest.fn().mockImplementation(() => { - throw new Error('Payment method error'); - }); - - await expect( - billingService.generateInvoiceAndChargeCustomer( - customerId, - itemText, - amountInUsd - ) - ).rejects.toThrow(); - expect(billingService.voidInvoice).toHaveBeenCalled(); - }); - }); - - describe('voidInvoice', () => { - it('should successfully void an invoice', async () => { - const mockVoidedInvoice: Stripe.Invoice = getStripeInvoice(); - mockVoidedInvoice.status = 'void'; - - mockStripe.invoices.voidInvoice = jest - .fn() - .mockResolvedValue(mockVoidedInvoice); - - const voidedInvoice: Stripe.Invoice = - await billingService.voidInvoice(invoiceId); - - expect(voidedInvoice).toEqual(mockVoidedInvoice); - expect(mockStripe.invoices.voidInvoice).toHaveBeenCalledWith( - invoiceId - ); - }); - }); - - describe('payInvoice', () => { - const mockPaymentMethodsResponse: PaymentMethodsResponse = { - data: [ - { - id: 'pm_123', - type: 'card', - last4Digits: '4242', - isDefault: true, - }, - ], - }; - - it('should throw if billing is not enabled', async () => { - billingService = mockIsBillingEnabled(false); - - await expect( - billingService.payInvoice(customerId, invoiceId) - ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); - }); - - it('should throw if no payments methods exist', async () => { - mockStripe.paymentMethods.list = jest - .fn() - .mockResolvedValue({ data: [] }); - mockStripe.customers.retrieve = jest - .fn() - .mockResolvedValue(mockCustomer); - - await expect( - billingService.payInvoice(customerId, invoiceId) - ).rejects.toThrow(Errors.BillingService.NO_PAYMENTS_METHODS); - }); - - it('should successfully pay an invoice', async () => { - mockStripe.paymentMethods.list = jest - .fn() - .mockResolvedValueOnce(mockPaymentMethodsResponse) - .mockResolvedValue({ data: [] }); - mockStripe.customers.retrieve = jest - .fn() - .mockResolvedValue(mockCustomer); - - const mockPaidInvoice: Stripe.Invoice = getStripeInvoice(); - mockStripe.invoices.pay = jest - .fn() - .mockResolvedValue(mockPaidInvoice); - - const paidInvoice: Invoice = await billingService.payInvoice( - customerId, - mockPaidInvoice.id || '' - ); - - expect(paidInvoice).toEqual({ - id: mockPaidInvoice.id, - amount: mockPaidInvoice.amount_due, - currencyCode: mockPaidInvoice.currency, - customerId: mockPaidInvoice.customer, - status: mockPaidInvoice.status, - downloadableLink: '', - subscriptionId: mockPaidInvoice.subscription, - }); - expect(mockStripe.invoices.pay).toHaveBeenCalledWith( - mockPaidInvoice.id, - { - payment_method: paymentMethodId, - } - ); - }); + inactiveStatuses.forEach((status: SubscriptionStatus) => { + expect(billingService.isSubscriptionActive(status)).toBeFalsy(); }); + }); }); + + describe("addOrUpdateMeteredPricingOnSubscription", () => { + const quantity: number = 10; + + it("should throw if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.addOrUpdateMeteredPricingOnSubscription( + mockSubscription.id, + ActiveMonitoringMeteredPlan, + quantity, + ), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should successfully add metered pricing to a subscription", async () => { + const subscriptionItem: SubscriptionItem | undefined = + mockSubscription.items.data[0]; + const meteredPlan: MeteredPlan = new MeteredPlan({ + priceId: subscriptionItem?.price?.id || "", + pricePerUnitInUSD: 100, + unitName: "unit", + }); + + mockSubscription.items.data = []; + + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + mockStripe.subscriptionItems.create = jest + .fn() + .mockResolvedValue({ id: "sub_item_123" }); + mockStripe.subscriptionItems.createUsageRecord = jest + .fn() + .mockResolvedValue({}); + + await billingService.addOrUpdateMeteredPricingOnSubscription( + mockSubscription.id, + ActiveMonitoringMeteredPlan, + quantity, + ); + + expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( + mockSubscription.id, + ); + expect(mockStripe.subscriptionItems.create).toHaveBeenCalledWith({ + subscription: mockSubscription.id, + price: meteredPlan.getPriceId(), + }); + expect( + mockStripe.subscriptionItems.createUsageRecord, + ).toHaveBeenCalledWith("sub_item_123", { quantity }); + }); + + it("should successfully update existing metered pricing on a subscription", async () => { + const subscriptionItem: SubscriptionItem | undefined = + mockSubscription.items.data[0]; + + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + mockStripe.subscriptionItems.createUsageRecord = jest + .fn() + .mockResolvedValue({}); + + await billingService.addOrUpdateMeteredPricingOnSubscription( + mockSubscription.id, + ActiveMonitoringMeteredPlan, + quantity, + ); + + expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( + mockSubscription.id, + ); + expect( + mockStripe.subscriptionItems.createUsageRecord, + ).toHaveBeenCalledWith(subscriptionItem?.id, { + quantity: quantity, + }); + }); + + it("should handle non-existent subscription", async () => { + const subscriptionId: string = "sub_nonexistent"; + mockStripe.subscriptions.retrieve = jest.fn().mockResolvedValue(null); + + await expect( + billingService.addOrUpdateMeteredPricingOnSubscription( + subscriptionId, + ActiveMonitoringMeteredPlan, + quantity, + ), + ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); + }); + }); + + describe("isPromoCodeValid", () => { + it("should throw if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.isPromoCodeValid("INVALID_PROMO_CODE"), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should return true for a valid promo code", async () => { + const promoCode: string = "VALIDPROMO"; + const mockCoupon: { valid: boolean } = { valid: true }; + + mockStripe.coupons.retrieve = jest.fn().mockResolvedValue(mockCoupon); + + const isValid: boolean = + await billingService.isPromoCodeValid(promoCode); + + expect(isValid).toBeTruthy(); + expect(mockStripe.coupons.retrieve).toHaveBeenCalledWith(promoCode); + }); + + it("should return false for an invalid or expired promo code", async () => { + const promoCode: string = "INVALIDPROMO"; + const mockCoupon: { valid: boolean } = { + valid: false, + }; + mockStripe.coupons.retrieve = jest.fn().mockResolvedValue(mockCoupon); + + const isValid: boolean = + await billingService.isPromoCodeValid(promoCode); + + expect(isValid).toBeFalsy(); + expect(mockStripe.coupons.retrieve).toHaveBeenCalledWith(promoCode); + }); + + it("should handle non-existent promo code", async () => { + const promoCode: string = "NONEXISTENTPROMO"; + + mockStripe.coupons.retrieve = jest.fn().mockResolvedValue(null); + + await expect( + billingService.isPromoCodeValid(promoCode), + ).rejects.toThrow(Errors.BillingService.PROMO_CODE_NOT_FOUND); + + expect(mockStripe.coupons.retrieve).toHaveBeenCalledWith(promoCode); + }); + + it("should handle errors from the Stripe API", async () => { + const promoCode: string = "ERRORPROMO"; + + mockStripe.coupons.retrieve = jest.fn().mockImplementation(() => { + throw new Error(); + }); + + await expect( + billingService.isPromoCodeValid(promoCode), + ).rejects.toThrow(Errors.BillingService.PROMO_CODE_INVALID); + }); + }); + + describe("removeSubscriptionItem", () => { + const subscriptionId: string = "sub_123"; + const subscriptionItemId: string = "si_123"; + const isMeteredSubscriptionItem: boolean = false; + + it("should throw if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.removeSubscriptionItem( + subscriptionId, + subscriptionItemId, + isMeteredSubscriptionItem, + ), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should successfully remove a metered subscription item", async () => { + const isMeteredSubscriptionItem: boolean = true; + + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + mockStripe.subscriptionItems.del = jest.fn().mockResolvedValue({}); + + await billingService.removeSubscriptionItem( + subscriptionId, + subscriptionItemId, + isMeteredSubscriptionItem, + ); + + expect(mockStripe.subscriptionItems.del).toHaveBeenCalledWith( + subscriptionItemId, + { + proration_behavior: "create_prorations", + clear_usage: true, + }, + ); + }); + + it("should successfully remove a metered subscription item when isMeteredSubscriptionItem", async () => { + const subscriptionItemId: string = + mockSubscription.items.data[0]?.id || ""; + + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + mockStripe.subscriptionItems.del = jest.fn().mockResolvedValue({}); + + await billingService.removeSubscriptionItem( + mockSubscription.id, + subscriptionItemId, + isMeteredSubscriptionItem, + ); + + expect(mockStripe.subscriptionItems.del).toHaveBeenCalledWith( + subscriptionItemId, + {}, + ); + }); + + it("should handle non-existent subscription or subscription item", async () => { + mockStripe.subscriptions.retrieve = jest.fn().mockResolvedValue(null); + + await expect( + billingService.removeSubscriptionItem( + subscriptionId, + subscriptionItemId, + isMeteredSubscriptionItem, + ), + ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); + }); + + it("should handle errors from the Stripe API", async () => { + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + mockStripe.subscriptionItems.del = jest.fn().mockImplementation(() => { + throw new Error("Stripe API error"); + }); + + await expect( + billingService.removeSubscriptionItem( + subscriptionId, + subscriptionItemId, + isMeteredSubscriptionItem, + ), + ).rejects.toThrow("Stripe API error"); + }); + + it("should not remove an item if the subscription is canceled", async () => { + const isMeteredSubscriptionItem: boolean = false; + mockSubscription.status = "canceled"; + + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + mockStripe.subscriptionItems.del = jest.fn(); + + await billingService.removeSubscriptionItem( + subscriptionId, + subscriptionItemId, + isMeteredSubscriptionItem, + ); + expect(mockStripe.subscriptions.del).not.toHaveBeenCalled(); + }); + }); + + describe("getSubscriptionItems", () => { + it("should throw if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.getSubscriptionItems(mockSubscription.id), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should successfully retrieve subscription items for a given subscription", async () => { + mockSubscription.items.data = [ + // @ts-ignore + { id: "item_1", price: { id: "price_123" } }, + // @ts-ignore + { id: "item_2", price: { id: "price_456" } }, + ]; + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + + const items: SubscriptionItem[] = + await billingService.getSubscriptionItems(mockSubscription.id); + + expect(items).toEqual(mockSubscription.items.data); + expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( + mockSubscription.id, + ); + }); + + it("should handle the case where the subscription does not exist", async () => { + const subscriptionId: string = "sub_nonexistent"; + + mockStripe.subscriptions.retrieve = jest.fn().mockResolvedValue(null); + + await expect( + billingService.getSubscriptionItems(subscriptionId), + ).rejects.toThrow(Errors.BillingService.SUBSCRIPTION_NOT_FOUND); + }); + }); + }); + + describe("Payment & Billing", () => { + const customerId: string = "cust_123"; + const invoiceId: string = "inv_123"; + const paymentMethodId: string = "pm_123"; + const subscriptionId: string = "sub_123"; + + const mockPaymentMethods: Array<PaymentMethod> = [ + { + id: "pm_123", + type: "card", + last4Digits: "4242", + isDefault: true, + }, + { + id: "pm_456", + type: "card", + last4Digits: "4343", + isDefault: false, + }, + ]; + + describe("generateCouponCode", () => { + const couponData: CouponData = getCouponData(); + const mockCoupon: { id: string; valid: boolean } = { + id: "coupon_123", + valid: true, + }; + + it("should successfully generate a coupon code", async () => { + mockStripe.coupons.create = jest.fn().mockResolvedValue(mockCoupon); + + const result: string = + await billingService.generateCouponCode(couponData); + + expect(result).toEqual(mockCoupon.id); + expect(mockStripe.coupons.create).toHaveBeenCalledWith( + expect.objectContaining({ + name: couponData.name, + percent_off: couponData.percentOff, + duration: "repeating", + duration_in_months: couponData.durationInMonths, + max_redemptions: couponData.maxRedemptions, + metadata: couponData.metadata, + }), + ); + }); + }); + + describe("deletePaymentMethod", () => { + it("should throw if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.deletePaymentMethod(customerId, paymentMethodId), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should successfully delete a payment method", async () => { + billingService.getPaymentMethods = jest + .fn() + .mockResolvedValue(mockPaymentMethods); + + mockStripe.paymentMethods.detach = jest.fn().mockResolvedValue({}); + + await billingService.deletePaymentMethod(customerId, paymentMethodId); + + expect(mockStripe.paymentMethods.detach).toHaveBeenCalledWith( + paymentMethodId, + ); + }); + + it("should throw an exception if it's the only payment method", async () => { + // mock a single payment method to simulate a scenario where deletion is not allowed + const mockSinglePaymentMethod: Array<PaymentMethod> = [ + { + id: paymentMethodId, + type: "card", + last4Digits: "4242", + isDefault: true, + }, + ]; + billingService.getPaymentMethods = jest + .fn() + .mockResolvedValue(mockSinglePaymentMethod); + + await expect( + billingService.deletePaymentMethod(customerId, paymentMethodId), + ).rejects.toThrow( + Errors.BillingService.MIN_REQUIRED_PAYMENT_METHOD_NOT_MET, + ); + }); + }); + + describe("hasPaymentMethods", () => { + it("should return true if the customer has payment methods", async () => { + billingService.getPaymentMethods = jest + .fn() + .mockResolvedValue(mockPaymentMethods); + + const result: boolean = + await billingService.hasPaymentMethods(customerId); + + expect(result).toBeTruthy(); + expect(billingService.getPaymentMethods).toHaveBeenCalledWith( + customerId, + ); + }); + + it("should return false if the customer does not have payment methods", async () => { + const mockEmptyPaymentMethods: PaymentMethod[] = Array<PaymentMethod>(); + billingService.getPaymentMethods = jest + .fn() + .mockResolvedValue(mockEmptyPaymentMethods); + + const result: boolean = + await billingService.hasPaymentMethods(customerId); + + expect(result).toBeFalsy(); + expect(billingService.getPaymentMethods).toHaveBeenCalledWith( + customerId, + ); + }); + }); + + describe("getPaymentMethods", () => { + it("should throw if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.getPaymentMethods(customerId), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should return all payment methods for a customer", async () => { + const mockPaymentMethodsResponse: { + data: Array<Stripe.PaymentMethod>; + } = { + data: [ + { + id: "pm_123", + type: "card", + // @ts-ignore + card: { last4: "4242", brand: "mastercard" }, + isDefault: true, + }, + { + id: "pm_456", + type: "card", + // @ts-ignore + card: { last4: "4343", brand: "mastercard" }, + isDefault: true, + }, + ], + }; + mockStripe.paymentMethods.list = jest + .fn() + .mockResolvedValueOnce(mockPaymentMethodsResponse) + .mockResolvedValue({ data: [] }); + mockStripe.customers.retrieve = jest + .fn() + .mockResolvedValue(mockCustomer); + + const paymentMethods: PaymentMethod[] = + await billingService.getPaymentMethods(customerId); + + expect(paymentMethods).toHaveLength(2); + expect(paymentMethods[0]?.id).toBe("pm_123"); + expect(paymentMethods[0]?.last4Digits).toBe("4242"); + + expect(mockStripe.paymentMethods.list).toHaveBeenCalledWith({ + customer: customerId, + type: "card", + }); + }); + + it("should return an empty array if no payment methods are present", async () => { + const mockEmptyPaymentMethodsResponse: { + data: Array<PaymentMethod>; + } = { data: [] }; + + mockStripe.paymentMethods.list = jest + .fn() + .mockResolvedValue(mockEmptyPaymentMethodsResponse); + mockStripe.customers.retrieve = jest + .fn() + .mockResolvedValue(mockCustomer); + + const paymentMethods: PaymentMethod[] = + await billingService.getPaymentMethods(customerId); + + expect(paymentMethods).toEqual([]); + expect(mockStripe.paymentMethods.list).toHaveBeenCalledWith({ + customer: customerId, + type: "card", + }); + }); + }); + + describe("getSetupIntentSecret", () => { + it("should successfully return a setup intent secret", async () => { + const mockSetupIntent: { client_secret: string } = { + client_secret: "seti_123_secret_xyz", + }; + mockStripe.setupIntents.create = jest + .fn() + .mockResolvedValue(mockSetupIntent); + + const secret: string = + await billingService.getSetupIntentSecret(customerId); + + expect(secret).toBe(mockSetupIntent.client_secret); + expect(mockStripe.setupIntents.create).toHaveBeenCalledWith({ + customer: customerId, + }); + }); + + it("should handle missing client secret in the response", async () => { + mockStripe.setupIntents.create = jest.fn().mockResolvedValue({}); + + await expect( + billingService.getSetupIntentSecret(customerId), + ).rejects.toThrow(Errors.BillingService.CLIENT_SECRET_MISSING); + }); + }); + + describe("cancelSubscription", () => { + it("should successfully cancel a subscription", async () => { + mockStripe.subscriptions.del = jest.fn().mockResolvedValue({ + id: subscriptionId, + status: "canceled", + }); + + await billingService.cancelSubscription(subscriptionId); + + expect(mockStripe.subscriptions.del).toHaveBeenCalledWith( + subscriptionId, + ); + }); + + it("should handle errors from the Stripe API", async () => { + const subscriptionId: string = "sub_123"; + + // mock an error response from the Stripe API + mockStripe.subscriptions.del = jest.fn().mockImplementation(() => { + throw new Error("Stripe API error"); + }); + + await billingService.cancelSubscription(subscriptionId); + // todo: we could expect the error to be logged + }); + + it("should not cancel a subscription if billing is not enabled", async () => { + const subscriptionId: string = "sub_123"; + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.cancelSubscription(subscriptionId), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + expect(mockStripe.subscriptions.del).not.toHaveBeenCalled(); + }); + }); + + describe("getSubscriptionStatus", () => { + const expectedStatus: SubscriptionStatus = SubscriptionStatus.Active; + + it("should successfully retrieve the subscription status", async () => { + mockStripe.subscriptions.retrieve = jest.fn().mockResolvedValue({ + id: subscriptionId, + status: expectedStatus, + }); + + const status: SubscriptionStatus = + await billingService.getSubscriptionStatus(subscriptionId); + + expect(status).toBe(expectedStatus); + expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( + subscriptionId, + ); + }); + + it("should successfully retrieve the subscription status", async () => { + mockStripe.subscriptions.retrieve = jest.fn().mockResolvedValue({ + id: subscriptionId, + status: expectedStatus, + }); + + const status: SubscriptionStatus = + await billingService.getSubscriptionStatus(subscriptionId); + + expect(status).toBe(expectedStatus); + expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( + subscriptionId, + ); + }); + }); + + describe("getSubscription", () => { + it("should throw if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.getSubscription(subscriptionId), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should successfully retrieve subscription data", async () => { + const subscriptionId: string = "sub_123"; + const mockSubscription: Stripe.Subscription = getStripeSubscription(); + + mockStripe.subscriptions.retrieve = jest + .fn() + .mockResolvedValue(mockSubscription); + + const subscription: Stripe.Subscription = + await billingService.getSubscription(subscriptionId); + + expect(subscription).toEqual(mockSubscription); + expect(mockStripe.subscriptions.retrieve).toHaveBeenCalledWith( + subscriptionId, + ); + }); + }); + + describe("getInvoices", () => { + it("should successfully retrieve a list of invoices for a customer", async () => { + const mockInvoices: { data: Array<Stripe.Invoice> } = { + data: [getStripeInvoice(), getStripeInvoice()], + }; + mockStripe.invoices.list = jest.fn().mockResolvedValue(mockInvoices); + + const invoices: Array<Invoice> = + await billingService.getInvoices(customerId); + + expect(invoices).toEqual( + mockInvoices.data.map((invoice: Stripe.Invoice) => { + return { + id: invoice.id, + amount: invoice.amount_due, + currencyCode: invoice.currency, + customerId: invoice.customer, + downloadableLink: "", + status: "paid", + subscriptionId: invoice.subscription, + }; + }), + ); + expect(mockStripe.invoices.list).toHaveBeenCalledWith({ + customer: customerId, + limit: 100, + }); + }); + + it("should return an empty array if no invoices are found for the customer", async () => { + const mockEmptyInvoices: { data: Invoice[] } = { data: [] }; + mockStripe.invoices.list = jest + .fn() + .mockResolvedValue(mockEmptyInvoices); + + const invoices: Array<Invoice> = + await billingService.getInvoices(customerId); + + expect(invoices).toEqual([]); + expect(mockStripe.invoices.list).toHaveBeenCalledWith({ + customer: customerId, + limit: 100, + }); + }); + }); + + describe("generateInvoiceAndChargeCustomer", () => { + const itemText: string = "Service Charge"; + const amountInUsd: number = 100; + const mockPaymentMethodsResponse: PaymentMethodsResponse = { + data: [ + { + id: "pm_123", + type: "card", + last4Digits: "4242", + isDefault: true, + }, + ], + }; + + it("should successfully generate an invoice and charge the customer", async () => { + mockStripe.paymentMethods.list = jest + .fn() + .mockResolvedValueOnce(mockPaymentMethodsResponse) + .mockResolvedValue({ data: [] }); + mockStripe.customers.retrieve = jest + .fn() + .mockResolvedValue(mockCustomer); + + // mock responses for invoice creation, adding an item, and finalizing + const mockInvoice: any = getStripeInvoice(); + mockStripe.invoices.create = jest.fn().mockResolvedValue(mockInvoice); + mockStripe.invoiceItems.create = jest.fn().mockResolvedValue({}); + mockStripe.invoices.finalizeInvoice = jest.fn().mockResolvedValue({}); + + // mock response for paying the invoice + mockStripe.invoices.pay = jest.fn().mockResolvedValue({}); + + await billingService.generateInvoiceAndChargeCustomer( + customerId, + itemText, + amountInUsd, + ); + + expect(mockStripe.invoices.create).toHaveBeenCalledWith( + expect.objectContaining({ customer: customerId }), + ); + expect(mockStripe.invoiceItems.create).toHaveBeenCalledWith( + expect.objectContaining({ invoice: mockInvoice.id }), + ); + expect(mockStripe.invoices.finalizeInvoice).toHaveBeenCalledWith( + mockInvoice.id, + ); + expect(mockStripe.invoices.pay).toHaveBeenCalledWith(mockInvoice.id, { + payment_method: mockPaymentMethodsResponse.data[0]?.id, + }); + }); + + it("should handle payment method errors when creating the invoice", async () => { + mockStripe.invoices.create = jest.fn().mockResolvedValue(null); + + await expect( + billingService.generateInvoiceAndChargeCustomer( + customerId, + itemText, + amountInUsd, + ), + ).rejects.toThrow(Errors.BillingService.INVOICE_NOT_GENERATED); + }); + + it("should handle payment method errors when charging the invoice", async () => { + mockStripe.paymentMethods.list = jest + .fn() + .mockResolvedValueOnce(mockPaymentMethodsResponse) + .mockResolvedValue({ data: [] }); + mockStripe.customers.retrieve = jest + .fn() + .mockResolvedValue(mockCustomer); + + // mock successful invoice creation and finalization + const mockInvoice: any = getStripeInvoice(); + mockStripe.invoices.create = jest.fn().mockResolvedValue(mockInvoice); + mockStripe.invoiceItems.create = jest.fn().mockResolvedValue({}); + mockStripe.invoices.finalizeInvoice = jest.fn().mockResolvedValue({}); + + billingService.voidInvoice = jest.fn(); + + // mock an error during invoice payment + mockStripe.invoices.pay = jest.fn().mockImplementation(() => { + throw new Error("Payment method error"); + }); + + await expect( + billingService.generateInvoiceAndChargeCustomer( + customerId, + itemText, + amountInUsd, + ), + ).rejects.toThrow(); + expect(billingService.voidInvoice).toHaveBeenCalled(); + }); + }); + + describe("voidInvoice", () => { + it("should successfully void an invoice", async () => { + const mockVoidedInvoice: Stripe.Invoice = getStripeInvoice(); + mockVoidedInvoice.status = "void"; + + mockStripe.invoices.voidInvoice = jest + .fn() + .mockResolvedValue(mockVoidedInvoice); + + const voidedInvoice: Stripe.Invoice = + await billingService.voidInvoice(invoiceId); + + expect(voidedInvoice).toEqual(mockVoidedInvoice); + expect(mockStripe.invoices.voidInvoice).toHaveBeenCalledWith(invoiceId); + }); + }); + + describe("payInvoice", () => { + const mockPaymentMethodsResponse: PaymentMethodsResponse = { + data: [ + { + id: "pm_123", + type: "card", + last4Digits: "4242", + isDefault: true, + }, + ], + }; + + it("should throw if billing is not enabled", async () => { + billingService = mockIsBillingEnabled(false); + + await expect( + billingService.payInvoice(customerId, invoiceId), + ).rejects.toThrow(Errors.BillingService.BILLING_NOT_ENABLED); + }); + + it("should throw if no payments methods exist", async () => { + mockStripe.paymentMethods.list = jest + .fn() + .mockResolvedValue({ data: [] }); + mockStripe.customers.retrieve = jest + .fn() + .mockResolvedValue(mockCustomer); + + await expect( + billingService.payInvoice(customerId, invoiceId), + ).rejects.toThrow(Errors.BillingService.NO_PAYMENTS_METHODS); + }); + + it("should successfully pay an invoice", async () => { + mockStripe.paymentMethods.list = jest + .fn() + .mockResolvedValueOnce(mockPaymentMethodsResponse) + .mockResolvedValue({ data: [] }); + mockStripe.customers.retrieve = jest + .fn() + .mockResolvedValue(mockCustomer); + + const mockPaidInvoice: Stripe.Invoice = getStripeInvoice(); + mockStripe.invoices.pay = jest.fn().mockResolvedValue(mockPaidInvoice); + + const paidInvoice: Invoice = await billingService.payInvoice( + customerId, + mockPaidInvoice.id || "", + ); + + expect(paidInvoice).toEqual({ + id: mockPaidInvoice.id, + amount: mockPaidInvoice.amount_due, + currencyCode: mockPaidInvoice.currency, + customerId: mockPaidInvoice.customer, + status: mockPaidInvoice.status, + downloadableLink: "", + subscriptionId: mockPaidInvoice.subscription, + }); + expect(mockStripe.invoices.pay).toHaveBeenCalledWith( + mockPaidInvoice.id, + { + payment_method: paymentMethodId, + }, + ); + }); + }); + }); }); diff --git a/CommonServer/Tests/Services/ProbeService.test.ts b/CommonServer/Tests/Services/ProbeService.test.ts index 1ef0134186..065d042ce0 100644 --- a/CommonServer/Tests/Services/ProbeService.test.ts +++ b/CommonServer/Tests/Services/ProbeService.test.ts @@ -1,855 +1,820 @@ -import { Service as ProbeService } from '../../Services/ProbeService'; -import Database from '../TestingUtils/Database'; -import '../TestingUtils/Init'; -import UserTestService from '../TestingUtils/Services/UserServiceHelper'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Version from 'Common/Types/Version'; -import Faker from 'Common/Utils/Faker'; -import Probe from 'Model/Models/Probe'; -import User from 'Model/Models/User'; -import { fail } from 'assert'; +import { Service as ProbeService } from "../../Services/ProbeService"; +import Database from "../TestingUtils/Database"; +import "../TestingUtils/Init"; +import UserTestService from "../TestingUtils/Services/UserServiceHelper"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Version from "Common/Types/Version"; +import Faker from "Common/Utils/Faker"; +import Probe from "Model/Models/Probe"; +import User from "Model/Models/User"; +import { fail } from "assert"; -describe('probeService', () => { - let database!: Database; - beforeEach( - async () => { - database = new Database(); - await database.createAndConnect(); - }, - 10 * 1000 // 10 second timeout because setting up the DB is slow +describe("probeService", () => { + let database!: Database; + beforeEach( + async () => { + database = new Database(); + await database.createAndConnect(); + }, + 10 * 1000, // 10 second timeout because setting up the DB is slow + ); + + afterEach(async () => { + await database.disconnectAndDropDatabase(); + }); + + test("create a new probe", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.1"); + const key: ObjectID = ObjectID.generate(); + + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; + + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, + }); + + expect(savedProbe.name).toEqual(name); + expect(savedProbe.probeVersion?.toString()).toEqual( + probeVersion?.toString(), ); + expect(savedProbe.createdAt).toBeTruthy(); + expect(savedProbe.version).toBeTruthy(); + expect(savedProbe._id).toBeTruthy(); + expect(savedProbe.key?.toString()).toEqual(key?.toString()); + }); - afterEach(async () => { - await database.disconnectAndDropDatabase(); + test("findOneBy probe by name", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.1"); + const key: ObjectID = ObjectID.generate(); + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; + + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, }); - test('create a new probe', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.1'); - const key: ObjectID = ObjectID.generate(); + if (!savedProbe.name) { + fail("savedprobe name is null"); + } - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; - - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); - - expect(savedProbe.name).toEqual(name); - expect(savedProbe.probeVersion?.toString()).toEqual( - probeVersion?.toString() - ); - expect(savedProbe.createdAt).toBeTruthy(); - expect(savedProbe.version).toBeTruthy(); - expect(savedProbe._id).toBeTruthy(); - expect(savedProbe.key?.toString()).toEqual(key?.toString()); + const fetchedProbe: Probe | null = await probeService.findOneBy({ + query: { + name: savedProbe.name, + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + props: { isRoot: true }, }); - test('findOneBy probe by name', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.1'); - const key: ObjectID = ObjectID.generate(); - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; + if (!fetchedProbe) { + // fail the test. + fail("Probe not found in the database"); + } - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); + expect(fetchedProbe.name).toEqual(name); + expect(fetchedProbe._id).toEqual(savedProbe._id); + expect(fetchedProbe.probeVersion?.toString()).toEqual( + probeVersion?.toString(), + ); + expect(fetchedProbe.createdAt).toBeTruthy(); + expect(fetchedProbe.version).toBeTruthy(); + expect(fetchedProbe._id).toBeTruthy(); + expect(fetchedProbe.key?.toString()).toEqual(key?.toString()); + }); - if (!savedProbe.name) { - fail('savedprobe name is null'); - } + test("findOneBy by probeVersion", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; - const fetchedProbe: Probe | null = await probeService.findOneBy({ - query: { - name: savedProbe.name, - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - props: { isRoot: true }, - }); - - if (!fetchedProbe) { - // fail the test. - fail('Probe not found in the database'); - } - - expect(fetchedProbe.name).toEqual(name); - expect(fetchedProbe._id).toEqual(savedProbe._id); - expect(fetchedProbe.probeVersion?.toString()).toEqual( - probeVersion?.toString() - ); - expect(fetchedProbe.createdAt).toBeTruthy(); - expect(fetchedProbe.version).toBeTruthy(); - expect(fetchedProbe._id).toBeTruthy(); - expect(fetchedProbe.key?.toString()).toEqual(key?.toString()); + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, }); - test('findOneBy by probeVersion', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; - - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); - - const fetchedProbe: Probe | null = await probeService.findOneBy({ - query: { - probeVersion: new Version('1.0.2'), - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - props: { isRoot: true }, - }); - - if (!fetchedProbe) { - // fail the test. - fail('Probe not found in the database'); - } - - expect(fetchedProbe._id).toEqual(savedProbe._id); - expect(fetchedProbe.name).toEqual(name); - expect(fetchedProbe.probeVersion?.toString()).toEqual( - probeVersion?.toString() - ); - expect(fetchedProbe.createdAt).toBeTruthy(); - expect(fetchedProbe.version).toBeTruthy(); - expect(fetchedProbe._id).toBeTruthy(); - expect(fetchedProbe.key?.toString()).toEqual(key?.toString()); + const fetchedProbe: Probe | null = await probeService.findOneBy({ + query: { + probeVersion: new Version("1.0.2"), + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + props: { isRoot: true }, }); - test('findOneBy by invalid name', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; + if (!fetchedProbe) { + // fail the test. + fail("Probe not found in the database"); + } - await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); + expect(fetchedProbe._id).toEqual(savedProbe._id); + expect(fetchedProbe.name).toEqual(name); + expect(fetchedProbe.probeVersion?.toString()).toEqual( + probeVersion?.toString(), + ); + expect(fetchedProbe.createdAt).toBeTruthy(); + expect(fetchedProbe.version).toBeTruthy(); + expect(fetchedProbe._id).toBeTruthy(); + expect(fetchedProbe.key?.toString()).toEqual(key?.toString()); + }); - const fetchedProbe: Probe | null = await probeService.findOneBy({ - query: { - name: name + '-invalid', - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - props: { isRoot: true }, - }); + test("findOneBy by invalid name", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; - expect(fetchedProbe).toBeNull(); + await probeService.create({ + data: probe, + props: { + isRoot: true, + }, }); - test('select columns should work', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; - - await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); - - const fetchedProbe: Probe | null = await probeService.findOneBy({ - query: { - name: name, - }, - select: { - _id: true, - name: true, - createdAt: true, - }, - props: { isRoot: true }, - }); - - expect(fetchedProbe).toBeTruthy(); - expect(fetchedProbe!.name).toBe(name); - expect(fetchedProbe?._id).toBeTruthy(); - expect(fetchedProbe?.key).toBeFalsy(); - expect(fetchedProbe?.createdAt).toBeTruthy(); // this is the default column and it should be always truthy - expect(fetchedProbe?.createdByUserId).toBeFalsy(); - expect(fetchedProbe?.probeVersion).toBeFalsy(); + const fetchedProbe: Probe | null = await probeService.findOneBy({ + query: { + name: name + "-invalid", + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + props: { isRoot: true }, }); - test('findOneBy by key', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; + expect(fetchedProbe).toBeNull(); + }); - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); + test("select columns should work", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; - const fetchedProbe: Probe | null = await probeService.findOneBy({ - query: { - key: key.toString(), - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - props: { isRoot: true }, - }); - - if (!fetchedProbe) { - // fail the test. - fail('Probe not found in the database'); - } - - expect(fetchedProbe._id).toEqual(savedProbe._id); - expect(fetchedProbe.name).toEqual(name); - expect(fetchedProbe.probeVersion?.toString()).toEqual( - probeVersion?.toString() - ); - expect(fetchedProbe.createdAt).toBeTruthy(); - expect(fetchedProbe.version).toBeTruthy(); - expect(fetchedProbe._id).toBeTruthy(); - expect(fetchedProbe.key?.toString()).toEqual(key?.toString()); + await probeService.create({ + data: probe, + props: { + isRoot: true, + }, }); - test('findBy all entities', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name1: string = Faker.generateName(); - const probeVersion1: Version = new Version('1.0.2'); - const key1: ObjectID = ObjectID.generate(); - const probe: Probe = new Probe(); - probe.name = name1; - probe.probeVersion = probeVersion1; - probe.key = key1.toString(); - probe.description = 'test description'; - - const savedProbe1: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); - - const name2: string = Faker.generateName(); - const probeVersion2: Version = new Version('1.0.1'); - const key2: ObjectID = ObjectID.generate(); - const probe2: Probe = new Probe(); - probe2.name = name2; - probe2.probeVersion = probeVersion2; - probe2.key = key2.toString(); - probe2.description = 'test description'; - - const savedProbe2: Probe = await probeService.create({ - data: probe2, - props: { - isRoot: true, - }, - }); - - const fetchedProbes: Array<Probe> = await probeService.findBy({ - query: {}, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - limit: new PositiveNumber(10), - skip: new PositiveNumber(0), - props: { isRoot: true }, - }); - - if (fetchedProbes.length !== 2) { - // fail the test. - fail('Probe not found in the database'); - } - - expect(fetchedProbes[0]?._id).toEqual(savedProbe2._id); - expect(fetchedProbes[0]?.name).toEqual(name2); - expect(fetchedProbes[0]?.probeVersion?.toString()).toEqual( - probeVersion2.toString() - ); - expect(fetchedProbes[0]?.createdAt).toBeTruthy(); - expect(fetchedProbes[0]?.version).toBeTruthy(); - expect(fetchedProbes[0]?._id).toBeTruthy(); - expect(fetchedProbes[0]?.key?.toString()).toEqual(key2.toString()); - - expect(fetchedProbes[1]?._id).toEqual(savedProbe1._id); - expect(fetchedProbes[1]?.name).toEqual(name1); - expect(fetchedProbes[1]?.probeVersion?.toString()).toEqual( - probeVersion1.toString() - ); - expect(fetchedProbes[1]?.createdAt).toBeTruthy(); - expect(fetchedProbes[1]?.version).toBeTruthy(); - expect(fetchedProbes[1]?._id).toBeTruthy(); - expect(fetchedProbes[1]?.key?.toString()).toEqual(key1.toString()); + const fetchedProbe: Probe | null = await probeService.findOneBy({ + query: { + name: name, + }, + select: { + _id: true, + name: true, + createdAt: true, + }, + props: { isRoot: true }, }); - test('findBy limit', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const savedProbes: Array<Probe> = []; + expect(fetchedProbe).toBeTruthy(); + expect(fetchedProbe!.name).toBe(name); + expect(fetchedProbe?._id).toBeTruthy(); + expect(fetchedProbe?.key).toBeFalsy(); + expect(fetchedProbe?.createdAt).toBeTruthy(); // this is the default column and it should be always truthy + expect(fetchedProbe?.createdByUserId).toBeFalsy(); + expect(fetchedProbe?.probeVersion).toBeFalsy(); + }); - for (let i: number = 0; i < 20; i++) { - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); + test("findOneBy by key", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; - - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); - - savedProbes.push(savedProbe); - } - - const fetchedProbes: Array<Probe> = await probeService.findBy({ - query: {}, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - limit: new PositiveNumber(10), - skip: new PositiveNumber(0), - props: { isRoot: true }, - }); - - if (savedProbes.length !== 20) { - // fail the test. - fail('Probe not saved successfully'); - } - - if (fetchedProbes.length !== 10) { - // fail the test. - fail('Probe fetch limit breached'); - } - - for (let i: number = 0; i < fetchedProbes.length; i++) { - expect(fetchedProbes[i]?._id).toEqual(savedProbes[19 - i]?._id); - expect(fetchedProbes[i]?.name).toEqual(savedProbes[19 - i]?.name); - expect(fetchedProbes[i]?.probeVersion?.toString()).toEqual( - savedProbes[19 - i]?.probeVersion?.toString() - ); - expect(fetchedProbes[i]?.createdAt).toBeTruthy(); - expect(fetchedProbes[i]?.version).toBeTruthy(); - expect(fetchedProbes[i]?._id).toBeTruthy(); - expect(fetchedProbes[i]?.key?.toString()).toEqual( - savedProbes[19 - i]?.key?.toString() - ); - } + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, }); - test('findBy skip', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const savedProbes: Array<Probe> = []; - - for (let i: number = 0; i < 20; i++) { - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); - - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; - - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); - - savedProbes.push(savedProbe); - } - - const fetchedProbes: Array<Probe> = await probeService.findBy({ - query: {}, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - limit: new PositiveNumber(10), - skip: new PositiveNumber(10), - props: { isRoot: true }, - }); - - if (savedProbes.length !== 20) { - // fail the test. - fail('Probe not saved successfully'); - } - - if (fetchedProbes.length !== 10) { - // fail the test. - fail('Probe fetch limit breached'); - } - - for (let i: number = 0; i < fetchedProbes.length; i++) { - expect(fetchedProbes[i]?._id).toEqual(savedProbes[9 - i]?._id); - expect(fetchedProbes[i]?.name).toEqual(savedProbes[9 - i]?.name); - expect(fetchedProbes[i]?.probeVersion?.toString()).toEqual( - savedProbes[9 - i]?.probeVersion?.toString() - ); - expect(fetchedProbes[i]?.createdAt).toBeTruthy(); - expect(fetchedProbes[i]?.version).toBeTruthy(); - expect(fetchedProbes[i]?._id).toBeTruthy(); - expect(fetchedProbes[i]?.key?.toString()).toEqual( - savedProbes[9 - i]?.key?.toString() - ); - } + const fetchedProbe: Probe | null = await probeService.findOneBy({ + query: { + key: key.toString(), + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + props: { isRoot: true }, }); - test('delete probe by query', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; + if (!fetchedProbe) { + // fail the test. + fail("Probe not found in the database"); + } - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); + expect(fetchedProbe._id).toEqual(savedProbe._id); + expect(fetchedProbe.name).toEqual(name); + expect(fetchedProbe.probeVersion?.toString()).toEqual( + probeVersion?.toString(), + ); + expect(fetchedProbe.createdAt).toBeTruthy(); + expect(fetchedProbe.version).toBeTruthy(); + expect(fetchedProbe._id).toBeTruthy(); + expect(fetchedProbe.key?.toString()).toEqual(key?.toString()); + }); - expect(savedProbe).toBeTruthy(); + test("findBy all entities", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name1: string = Faker.generateName(); + const probeVersion1: Version = new Version("1.0.2"); + const key1: ObjectID = ObjectID.generate(); + const probe: Probe = new Probe(); + probe.name = name1; + probe.probeVersion = probeVersion1; + probe.key = key1.toString(); + probe.description = "test description"; - await probeService.deleteBy({ - query: { - key: key.toString(), - }, - skip: 0, - limit: 1, - props: { isRoot: true }, - }); - - const fetchedProbe: Probe | null = await probeService.findOneBy({ - query: { - key: key.toString(), - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - props: { isRoot: true }, - }); - - expect(fetchedProbe).toBeNull(); + const savedProbe1: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, }); - test('update probe by query', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); + const name2: string = Faker.generateName(); + const probeVersion2: Version = new Version("1.0.1"); + const key2: ObjectID = ObjectID.generate(); + const probe2: Probe = new Probe(); + probe2.name = name2; + probe2.probeVersion = probeVersion2; + probe2.key = key2.toString(); + probe2.description = "test description"; - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; - - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); - - expect(savedProbe).toBeTruthy(); - - const updatedName: string = Faker.generateName(); - await probeService.updateOneBy({ - query: { - key: key.toString(), - }, - data: { - name: updatedName, - }, - props: { isRoot: true }, - }); - - const fetchedProbe: Probe | null = await probeService.findOneBy({ - query: { - key: key.toString(), - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - props: { isRoot: true }, - }); - - expect(fetchedProbe).toBeTruthy(); - expect(fetchedProbe?.name).toBe(updatedName); + const savedProbe2: Probe = await probeService.create({ + data: probe2, + props: { + isRoot: true, + }, }); - test('update probe by query', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); - - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; - - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); - - expect(savedProbe).toBeTruthy(); - - const updatedName: string = Faker.generateName(); - await probeService.updateBy({ - query: { - key: key.toString(), - }, - skip: 0, - limit: 1, - data: { - name: updatedName, - }, - props: { isRoot: true }, - }); - - const fetchedProbe: Probe | null = await probeService.findOneBy({ - query: { - key: key.toString(), - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - }, - props: { isRoot: true }, - }); - - expect(fetchedProbe).toBeTruthy(); - expect(fetchedProbe?.name).toBe(updatedName); + const fetchedProbes: Array<Probe> = await probeService.findBy({ + query: {}, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + limit: new PositiveNumber(10), + skip: new PositiveNumber(0), + props: { isRoot: true }, }); - test('slugify column', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); + if (fetchedProbes.length !== 2) { + // fail the test. + fail("Probe not found in the database"); + } - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; + expect(fetchedProbes[0]?._id).toEqual(savedProbe2._id); + expect(fetchedProbes[0]?.name).toEqual(name2); + expect(fetchedProbes[0]?.probeVersion?.toString()).toEqual( + probeVersion2.toString(), + ); + expect(fetchedProbes[0]?.createdAt).toBeTruthy(); + expect(fetchedProbes[0]?.version).toBeTruthy(); + expect(fetchedProbes[0]?._id).toBeTruthy(); + expect(fetchedProbes[0]?.key?.toString()).toEqual(key2.toString()); - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); + expect(fetchedProbes[1]?._id).toEqual(savedProbe1._id); + expect(fetchedProbes[1]?.name).toEqual(name1); + expect(fetchedProbes[1]?.probeVersion?.toString()).toEqual( + probeVersion1.toString(), + ); + expect(fetchedProbes[1]?.createdAt).toBeTruthy(); + expect(fetchedProbes[1]?.version).toBeTruthy(); + expect(fetchedProbes[1]?._id).toBeTruthy(); + expect(fetchedProbes[1]?.key?.toString()).toEqual(key1.toString()); + }); - expect(savedProbe).toBeTruthy(); - expect(savedProbe.slug).toContain(name.toLowerCase() + '-'); + test("findBy limit", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const savedProbes: Array<Probe> = []; + + for (let i: number = 0; i < 20; i++) { + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; + + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, + }); + + savedProbes.push(savedProbe); + } + + const fetchedProbes: Array<Probe> = await probeService.findBy({ + query: {}, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + limit: new PositiveNumber(10), + skip: new PositiveNumber(0), + props: { isRoot: true }, }); - test('add user to createdBy column', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const user: User = - await UserTestService.generateRandomUser().data.save(); + if (savedProbes.length !== 20) { + // fail the test. + fail("Probe not saved successfully"); + } - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); + if (fetchedProbes.length !== 10) { + // fail the test. + fail("Probe fetch limit breached"); + } - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; + for (let i: number = 0; i < fetchedProbes.length; i++) { + expect(fetchedProbes[i]?._id).toEqual(savedProbes[19 - i]?._id); + expect(fetchedProbes[i]?.name).toEqual(savedProbes[19 - i]?.name); + expect(fetchedProbes[i]?.probeVersion?.toString()).toEqual( + savedProbes[19 - i]?.probeVersion?.toString(), + ); + expect(fetchedProbes[i]?.createdAt).toBeTruthy(); + expect(fetchedProbes[i]?.version).toBeTruthy(); + expect(fetchedProbes[i]?._id).toBeTruthy(); + expect(fetchedProbes[i]?.key?.toString()).toEqual( + savedProbes[19 - i]?.key?.toString(), + ); + } + }); - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); + test("findBy skip", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const savedProbes: Array<Probe> = []; - savedProbe.createdByUser = user; - const updatedProbe: Probe = await savedProbe.save(); + for (let i: number = 0; i < 20; i++) { + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); - if (!updatedProbe._id) { - fail('updatedProbe._id is null'); - } + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; - const findProbe: Probe | null = await probeService.findOneBy({ - query: { - _id: updatedProbe._id, - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - createdByUserId: true, - }, - props: { isRoot: true }, - }); + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, + }); - expect(findProbe).toBeTruthy(); - expect(findProbe?.createdByUserId?.toString()).toContain(user._id); + savedProbes.push(savedProbe); + } + + const fetchedProbes: Array<Probe> = await probeService.findBy({ + query: {}, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + limit: new PositiveNumber(10), + skip: new PositiveNumber(10), + props: { isRoot: true }, }); - test('include user in relation', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const user: User = - await UserTestService.generateRandomUser().data.save(); + if (savedProbes.length !== 20) { + // fail the test. + fail("Probe not saved successfully"); + } - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); + if (fetchedProbes.length !== 10) { + // fail the test. + fail("Probe fetch limit breached"); + } - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; + for (let i: number = 0; i < fetchedProbes.length; i++) { + expect(fetchedProbes[i]?._id).toEqual(savedProbes[9 - i]?._id); + expect(fetchedProbes[i]?.name).toEqual(savedProbes[9 - i]?.name); + expect(fetchedProbes[i]?.probeVersion?.toString()).toEqual( + savedProbes[9 - i]?.probeVersion?.toString(), + ); + expect(fetchedProbes[i]?.createdAt).toBeTruthy(); + expect(fetchedProbes[i]?.version).toBeTruthy(); + expect(fetchedProbes[i]?._id).toBeTruthy(); + expect(fetchedProbes[i]?.key?.toString()).toEqual( + savedProbes[9 - i]?.key?.toString(), + ); + } + }); - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); + test("delete probe by query", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; - savedProbe.createdByUser = user; - const updatedProbe: Probe = await savedProbe.save(); - - if (!updatedProbe._id) { - fail('updatedProbe._id not found'); - } - - const findProbe: Probe | null = await probeService.findOneBy({ - query: { - _id: updatedProbe._id, - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - createdByUser: { - _id: true, - name: true, - }, - }, - props: { isRoot: true }, - }); - - expect(findProbe).toBeTruthy(); - expect(findProbe?.createdByUser?._id).toContain(user._id); - expect(findProbe?.createdByUser?.name?.toString()).toBeTruthy(); - expect(user.name?.toString()).toBeTruthy(); - expect(findProbe?.createdByUser?.name?.toString()).toContain( - user.name?.toString() - ); + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, }); - test('find a probe by user relation', async () => { - const probeService: ProbeService = new ProbeService( - database.getDatabase() - ); - const user: User = - await UserTestService.generateRandomUser().data.save(); + expect(savedProbe).toBeTruthy(); - const name: string = Faker.generateName(); - const probeVersion: Version = new Version('1.0.2'); - const key: ObjectID = ObjectID.generate(); - - const probe: Probe = new Probe(); - probe.name = name; - probe.probeVersion = probeVersion; - probe.key = key.toString(); - probe.description = 'test description'; - - const savedProbe: Probe = await probeService.create({ - data: probe, - props: { - isRoot: true, - }, - }); - - savedProbe.createdByUser = user; - const updatedProbe: Probe = await savedProbe.save(); - - expect(updatedProbe).toBeTruthy(); - - if (!user.id) { - fail('user.id not found'); - } - - const findProbe: Probe | null = await probeService.findOneBy({ - query: { - createdByUserId: user.id, - }, - select: { - _id: true, - name: true, - version: true, - probeVersion: true, - createdAt: true, - key: true, - createdByUser: { - _id: true, - name: true, - }, - }, - props: { isRoot: true }, - }); - - expect(findProbe).toBeTruthy(); - expect(findProbe?.createdByUser?._id).toContain(user._id); - expect(findProbe?.createdByUser?.name?.toString()).toContain( - user.name?.toString() - ); + await probeService.deleteBy({ + query: { + key: key.toString(), + }, + skip: 0, + limit: 1, + props: { isRoot: true }, }); + + const fetchedProbe: Probe | null = await probeService.findOneBy({ + query: { + key: key.toString(), + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + props: { isRoot: true }, + }); + + expect(fetchedProbe).toBeNull(); + }); + + test("update probe by query", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; + + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, + }); + + expect(savedProbe).toBeTruthy(); + + const updatedName: string = Faker.generateName(); + await probeService.updateOneBy({ + query: { + key: key.toString(), + }, + data: { + name: updatedName, + }, + props: { isRoot: true }, + }); + + const fetchedProbe: Probe | null = await probeService.findOneBy({ + query: { + key: key.toString(), + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + props: { isRoot: true }, + }); + + expect(fetchedProbe).toBeTruthy(); + expect(fetchedProbe?.name).toBe(updatedName); + }); + + test("update probe by query", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; + + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, + }); + + expect(savedProbe).toBeTruthy(); + + const updatedName: string = Faker.generateName(); + await probeService.updateBy({ + query: { + key: key.toString(), + }, + skip: 0, + limit: 1, + data: { + name: updatedName, + }, + props: { isRoot: true }, + }); + + const fetchedProbe: Probe | null = await probeService.findOneBy({ + query: { + key: key.toString(), + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + }, + props: { isRoot: true }, + }); + + expect(fetchedProbe).toBeTruthy(); + expect(fetchedProbe?.name).toBe(updatedName); + }); + + test("slugify column", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; + + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, + }); + + expect(savedProbe).toBeTruthy(); + expect(savedProbe.slug).toContain(name.toLowerCase() + "-"); + }); + + test("add user to createdBy column", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const user: User = await UserTestService.generateRandomUser().data.save(); + + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; + + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, + }); + + savedProbe.createdByUser = user; + const updatedProbe: Probe = await savedProbe.save(); + + if (!updatedProbe._id) { + fail("updatedProbe._id is null"); + } + + const findProbe: Probe | null = await probeService.findOneBy({ + query: { + _id: updatedProbe._id, + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + createdByUserId: true, + }, + props: { isRoot: true }, + }); + + expect(findProbe).toBeTruthy(); + expect(findProbe?.createdByUserId?.toString()).toContain(user._id); + }); + + test("include user in relation", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const user: User = await UserTestService.generateRandomUser().data.save(); + + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; + + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, + }); + + savedProbe.createdByUser = user; + const updatedProbe: Probe = await savedProbe.save(); + + if (!updatedProbe._id) { + fail("updatedProbe._id not found"); + } + + const findProbe: Probe | null = await probeService.findOneBy({ + query: { + _id: updatedProbe._id, + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + createdByUser: { + _id: true, + name: true, + }, + }, + props: { isRoot: true }, + }); + + expect(findProbe).toBeTruthy(); + expect(findProbe?.createdByUser?._id).toContain(user._id); + expect(findProbe?.createdByUser?.name?.toString()).toBeTruthy(); + expect(user.name?.toString()).toBeTruthy(); + expect(findProbe?.createdByUser?.name?.toString()).toContain( + user.name?.toString(), + ); + }); + + test("find a probe by user relation", async () => { + const probeService: ProbeService = new ProbeService(database.getDatabase()); + const user: User = await UserTestService.generateRandomUser().data.save(); + + const name: string = Faker.generateName(); + const probeVersion: Version = new Version("1.0.2"); + const key: ObjectID = ObjectID.generate(); + + const probe: Probe = new Probe(); + probe.name = name; + probe.probeVersion = probeVersion; + probe.key = key.toString(); + probe.description = "test description"; + + const savedProbe: Probe = await probeService.create({ + data: probe, + props: { + isRoot: true, + }, + }); + + savedProbe.createdByUser = user; + const updatedProbe: Probe = await savedProbe.save(); + + expect(updatedProbe).toBeTruthy(); + + if (!user.id) { + fail("user.id not found"); + } + + const findProbe: Probe | null = await probeService.findOneBy({ + query: { + createdByUserId: user.id, + }, + select: { + _id: true, + name: true, + version: true, + probeVersion: true, + createdAt: true, + key: true, + createdByUser: { + _id: true, + name: true, + }, + }, + props: { isRoot: true }, + }); + + expect(findProbe).toBeTruthy(); + expect(findProbe?.createdByUser?._id).toContain(user._id); + expect(findProbe?.createdByUser?.name?.toString()).toContain( + user.name?.toString(), + ); + }); }); diff --git a/CommonServer/Tests/Services/ScheduledMaintenanceService.test.ts b/CommonServer/Tests/Services/ScheduledMaintenanceService.test.ts index 569ff69dd5..eaa83be109 100644 --- a/CommonServer/Tests/Services/ScheduledMaintenanceService.test.ts +++ b/CommonServer/Tests/Services/ScheduledMaintenanceService.test.ts @@ -1,90 +1,88 @@ -import ScheduledMaintenanceService from '../../Services/ScheduledMaintenanceService'; -import Database from '../TestingUtils/Database'; -import '../TestingUtils/Init'; -import ProjectServiceHelper from '../TestingUtils/Services/ProjectServiceHelper'; -import ScheduledMaintenanceServiceHelper from '../TestingUtils/Services/ScheduledMaintenanceServiceHelper'; -import ScheduledMaintenanceStateServiceHelper from '../TestingUtils/Services/ScheduledMaintenanceStateServiceHelper'; -import UserServiceHelper from '../TestingUtils/Services/UserServiceHelper'; -import { describe, expect, it } from '@jest/globals'; -import Project from 'Model/Models/Project'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import User from 'Model/Models/User'; +import ScheduledMaintenanceService from "../../Services/ScheduledMaintenanceService"; +import Database from "../TestingUtils/Database"; +import "../TestingUtils/Init"; +import ProjectServiceHelper from "../TestingUtils/Services/ProjectServiceHelper"; +import ScheduledMaintenanceServiceHelper from "../TestingUtils/Services/ScheduledMaintenanceServiceHelper"; +import ScheduledMaintenanceStateServiceHelper from "../TestingUtils/Services/ScheduledMaintenanceStateServiceHelper"; +import UserServiceHelper from "../TestingUtils/Services/UserServiceHelper"; +import { describe, expect, it } from "@jest/globals"; +import Project from "Model/Models/Project"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import User from "Model/Models/User"; // mock PostgresDatabase const testDatabase: Database = new Database(); -jest.mock('../../Infrastructure/PostgresDatabase', () => { - const actualModule: any = jest.requireActual( - '../../Infrastructure/PostgresDatabase' - ); - return { - __esModule: true, - default: actualModule.default, - PostgresAppInstance: { - getDataSource: () => { - return testDatabase.getDatabase().getDataSource(); - }, - isConnected: () => { - return testDatabase.getDatabase().isConnected(); - }, +jest.mock("../../Infrastructure/PostgresDatabase", () => { + const actualModule: any = jest.requireActual( + "../../Infrastructure/PostgresDatabase", + ); + return { + __esModule: true, + default: actualModule.default, + PostgresAppInstance: { + getDataSource: () => { + return testDatabase.getDatabase().getDataSource(); + }, + isConnected: () => { + return testDatabase.getDatabase().isConnected(); + }, + }, + }; +}); + +describe("ScheduledMaintenanceService", () => { + beforeEach(async () => { + await testDatabase.createAndConnect(); + }); + + afterEach(async () => { + await testDatabase.disconnectAndDropDatabase(); + jest.resetAllMocks(); + }); + + describe("changeScheduledMaintenanceState", () => { + it("should trigger workflows only once", async () => { + // Prepare scheduled maintenance + const user: User = UserServiceHelper.generateRandomUser().data; + await user.save(); + const project: Project = ProjectServiceHelper.generateRandomProject( + user.id!, + ).data; + await project.save(); + const scheduledState: ScheduledMaintenanceState = + ScheduledMaintenanceStateServiceHelper.generateScheduledState( + project.id!, + ).data; + await scheduledState.save(); + const maintenance: ScheduledMaintenance = + ScheduledMaintenanceServiceHelper.generateRandomScheduledMaintenance( + project.id!, + scheduledState.id!, + ).data; + await maintenance.save(); + // Change state + const ongoingState: ScheduledMaintenanceState = + ScheduledMaintenanceStateServiceHelper.generateOngoingState( + project.id!, + ).data; + await ongoingState.save(); + jest.spyOn(ScheduledMaintenanceService, "onTrigger"); + await ScheduledMaintenanceService.changeScheduledMaintenanceState({ + projectId: project.id!, + scheduledMaintenanceId: maintenance.id!, + scheduledMaintenanceStateId: ongoingState.id!, + shouldNotifyStatusPageSubscribers: Boolean( + maintenance.shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded, + ), + isSubscribersNotified: false, + notifyOwners: true, + props: { + isRoot: true, }, - }; -}); - -describe('ScheduledMaintenanceService', () => { - beforeEach(async () => { - await testDatabase.createAndConnect(); - }); - - afterEach(async () => { - await testDatabase.disconnectAndDropDatabase(); - jest.resetAllMocks(); - }); - - describe('changeScheduledMaintenanceState', () => { - it('should trigger workflows only once', async () => { - // Prepare scheduled maintenance - const user: User = UserServiceHelper.generateRandomUser().data; - await user.save(); - const project: Project = ProjectServiceHelper.generateRandomProject( - user.id! - ).data; - await project.save(); - const scheduledState: ScheduledMaintenanceState = - ScheduledMaintenanceStateServiceHelper.generateScheduledState( - project.id! - ).data; - await scheduledState.save(); - const maintenance: ScheduledMaintenance = - ScheduledMaintenanceServiceHelper.generateRandomScheduledMaintenance( - project.id!, - scheduledState.id! - ).data; - await maintenance.save(); - // Change state - const ongoingState: ScheduledMaintenanceState = - ScheduledMaintenanceStateServiceHelper.generateOngoingState( - project.id! - ).data; - await ongoingState.save(); - jest.spyOn(ScheduledMaintenanceService, 'onTrigger'); - await ScheduledMaintenanceService.changeScheduledMaintenanceState({ - projectId: project.id!, - scheduledMaintenanceId: maintenance.id!, - scheduledMaintenanceStateId: ongoingState.id!, - shouldNotifyStatusPageSubscribers: Boolean( - maintenance.shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded - ), - isSubscribersNotified: false, - notifyOwners: true, - props: { - isRoot: true, - }, - }); - // Assert triggering workflows only once - expect(ScheduledMaintenanceService.onTrigger).toHaveBeenCalledTimes( - 1 - ); - }); - }); + }); + // Assert triggering workflows only once + expect(ScheduledMaintenanceService.onTrigger).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/CommonServer/Tests/Services/TeamMemberService.test.ts b/CommonServer/Tests/Services/TeamMemberService.test.ts index b606ae5331..4dbecf3afd 100644 --- a/CommonServer/Tests/Services/TeamMemberService.test.ts +++ b/CommonServer/Tests/Services/TeamMemberService.test.ts @@ -1,784 +1,763 @@ -import { Host, HttpProtocol } from '../../EnvironmentConfig'; -import AccessTokenService from '../../Services/AccessTokenService'; -import BillingService from '../../Services/BillingService'; -import MailService from '../../Services/MailService'; -import ProjectService from '../../Services/ProjectService'; -import { TeamMemberService } from '../../Services/TeamMemberService'; -import TeamService from '../../Services/TeamService'; -import UserNotificationRuleService from '../../Services/UserNotificationRuleService'; -import UserNotificationSettingService from '../../Services/UserNotificationSettingService'; -import CreateBy from '../../Types/Database/CreateBy'; -import Errors from '../../Utils/Errors'; -import Database from '../TestingUtils/Database'; -import '../TestingUtils/Init'; -import ProjectServiceHelper from '../TestingUtils/Services/ProjectServiceHelper'; -import TeamMemberServiceHelper from '../TestingUtils/Services/TeamMemberServiceHelper'; -import TeamServiceHelper from '../TestingUtils/Services/TeamServiceHelper'; -import UserServiceHelper from '../TestingUtils/Services/UserServiceHelper'; -import { faker } from '@faker-js/faker'; -import { describe, expect, it } from '@jest/globals'; -import Email from 'Common/Types/Email'; -import ObjectID from 'Common/Types/ObjectID'; -import Project from 'Model/Models/Project'; -import Team from 'Model/Models/Team'; -import TeamMember from 'Model/Models/TeamMember'; -import User from 'Model/Models/User'; +import { Host, HttpProtocol } from "../../EnvironmentConfig"; +import AccessTokenService from "../../Services/AccessTokenService"; +import BillingService from "../../Services/BillingService"; +import MailService from "../../Services/MailService"; +import ProjectService from "../../Services/ProjectService"; +import { TeamMemberService } from "../../Services/TeamMemberService"; +import TeamService from "../../Services/TeamService"; +import UserNotificationRuleService from "../../Services/UserNotificationRuleService"; +import UserNotificationSettingService from "../../Services/UserNotificationSettingService"; +import CreateBy from "../../Types/Database/CreateBy"; +import Errors from "../../Utils/Errors"; +import Database from "../TestingUtils/Database"; +import "../TestingUtils/Init"; +import ProjectServiceHelper from "../TestingUtils/Services/ProjectServiceHelper"; +import TeamMemberServiceHelper from "../TestingUtils/Services/TeamMemberServiceHelper"; +import TeamServiceHelper from "../TestingUtils/Services/TeamServiceHelper"; +import UserServiceHelper from "../TestingUtils/Services/UserServiceHelper"; +import { faker } from "@faker-js/faker"; +import { describe, expect, it } from "@jest/globals"; +import Email from "Common/Types/Email"; +import ObjectID from "Common/Types/ObjectID"; +import Project from "Model/Models/Project"; +import Team from "Model/Models/Team"; +import TeamMember from "Model/Models/TeamMember"; +import User from "Model/Models/User"; jest.setTimeout(60000); // Increase test timeout to 60 seconds becuase GitHub runners are slow const testDatabase: Database = new Database(); // mock PostgresDatabase because we need it across all services -jest.mock('../../Infrastructure/PostgresDatabase', () => { - const actualModule: any = jest.requireActual( - '../../Infrastructure/PostgresDatabase' - ); - return { - __esModule: true, - default: actualModule.default, - PostgresAppInstance: { - getDataSource: () => { - return testDatabase.getDatabase().getDataSource(); - }, - isConnected: () => { - return testDatabase.getDatabase().isConnected(); - }, - }, - }; +jest.mock("../../Infrastructure/PostgresDatabase", () => { + const actualModule: any = jest.requireActual( + "../../Infrastructure/PostgresDatabase", + ); + return { + __esModule: true, + default: actualModule.default, + PostgresAppInstance: { + getDataSource: () => { + return testDatabase.getDatabase().getDataSource(); + }, + isConnected: () => { + return testDatabase.getDatabase().isConnected(); + }, + }, + }; }); // mock Redis -jest.mock('../../Infrastructure/GlobalCache'); -jest.mock('../../Services/AccessTokenService'); -jest.mock('../../Services/BillingService'); -jest.mock('../../Services/ProjectService'); +jest.mock("../../Infrastructure/GlobalCache"); +jest.mock("../../Services/AccessTokenService"); +jest.mock("../../Services/BillingService"); +jest.mock("../../Services/ProjectService"); -describe('TeamMemberService', () => { - let teamMemberService!: TeamMemberService; +describe("TeamMemberService", () => { + let teamMemberService!: TeamMemberService; - let user!: User; - let user2!: User; - let project!: Project; - let team!: Team; + let user!: User; + let user2!: User; + let project!: Project; + let team!: Team; - beforeEach(async () => { - await testDatabase.createAndConnect(); + beforeEach(async () => { + await testDatabase.createAndConnect(); - teamMemberService = new TeamMemberService(testDatabase.getDatabase()); + teamMemberService = new TeamMemberService(testDatabase.getDatabase()); - user = UserServiceHelper.generateRandomUser().data; - user = await user.save(); + user = UserServiceHelper.generateRandomUser().data; + user = await user.save(); - user2 = UserServiceHelper.generateRandomUser().data; - user2 = await user2.save(); + user2 = UserServiceHelper.generateRandomUser().data; + user2 = await user2.save(); - project = ProjectServiceHelper.generateRandomProject( - new ObjectID(user._id!) - ).data; - project = await project.save(); + project = ProjectServiceHelper.generateRandomProject( + new ObjectID(user._id!), + ).data; + project = await project.save(); - team = TeamServiceHelper.generateRandomTeam( + team = TeamServiceHelper.generateRandomTeam( + new ObjectID(project._id!), + new ObjectID(user._id!), + ).data; + team = await team.save(); + }); + + afterEach(async () => { + await testDatabase.disconnectAndDropDatabase(); + jest.resetAllMocks(); + }); + + describe("create tests", () => { + it("should create a new team member", async () => { + process.env["SUBSCRIPTION_PLAN_1"] = undefined; + process.env["SUBSCRIPTION_PLAN_2"] = undefined; + + ProjectService.findOneById = jest.fn().mockResolvedValue({ + _id: project._id, + }); + + TeamService.findOneById = jest.fn().mockResolvedValue(team); + + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + ); + + const teamMember: TeamMember = await teamMemberService.create(tm); + + expect(teamMember.userId).toEqual(new ObjectID(user._id!)); + expect(teamMember.projectId).toEqual(new ObjectID(project._id!)); + expect(teamMember.hasAcceptedInvitation).toBeFalsy(); + }); + + describe("onBeforeCreate", () => { + it("should throw exception if the user limit for a project is reached", async () => { + const SEATS_LIMIT: number = 5; + + ProjectService.findOneById = jest.fn().mockResolvedValue({ + seatLimit: SEATS_LIMIT, + paymentProviderSubscriptionSeats: SEATS_LIMIT, + _id: project._id, + }); + + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( new ObjectID(project._id!), - new ObjectID(user._id!) + new ObjectID(user._id!), + team, + ); + + await expect(teamMemberService.create(tm)).rejects.toThrow( + Errors.TeamMemberService.LIMIT_REACHED, + ); + }); + + it("should throw exception if the user has already been invited", async () => { + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + ); + + jest + .spyOn(AccessTokenService, "refreshUserGlobalAccessPermission") + .mockResolvedValue(null!); + + jest + .spyOn(AccessTokenService, "refreshUserTenantAccessPermission") + .mockResolvedValue(null); + + await teamMemberService.create(tm); + + await expect(teamMemberService.create(tm)).rejects.toThrow( + Errors.TeamMemberService.ALREADY_INVITED, + ); + }); + + it("should create user if the invited user does not exist in the system", async () => { + jest.spyOn(MailService, "sendMail").mockResolvedValue(null!); + + const nonExistingUserEmail: string = faker.internet.email(); + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + { email: nonExistingUserEmail }, + ); + const teamMember: TeamMember = await teamMemberService.create(tm); + + expect(teamMember).toBeDefined(); + expect(teamMember.userId).toBeDefined(); + }); + + it("should send email when inviting non existing user", async () => { + jest.spyOn(MailService, "sendMail").mockResolvedValue(null!); + + ProjectService.findOneById = jest.fn().mockResolvedValue({ + name: project.name, + _id: project._id, + }); + + const nonExistingUserEmail: string = faker.internet + .email() + .toLowerCase(); + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + { email: nonExistingUserEmail }, + ); + + jest + .spyOn(AccessTokenService, "refreshUserGlobalAccessPermission") + .mockResolvedValue(null!); + + jest + .spyOn(AccessTokenService, "refreshUserTenantAccessPermission") + .mockResolvedValue(null); + + await teamMemberService.create(tm); + + expect(MailService.sendMail).toHaveBeenCalledWith( + { + subject: `You have been invited to ${project.name}`, + templateType: "InviteMember.hbs", + toEmail: new Email(nonExistingUserEmail), + vars: { + homeUrl: `${HttpProtocol}${Host}/`, + isNewUser: "true", + projectName: project.name, + registerLink: `${HttpProtocol}${Host}/accounts/register?email=${nonExistingUserEmail.replace( + "@", + "%40", + )}`, + signInLink: `${HttpProtocol}${Host}/accounts`, + }, + }, + { + projectId: new ObjectID(project._id!), + }, + ); + }); + + it("should handle unexpected errors", async () => { + jest + .spyOn(teamMemberService, "create") + .mockRejectedValue(new Error("Unexpected error")); + + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + ); + await expect(teamMemberService.create(tm)).rejects.toThrow( + "Unexpected error", + ); + }); + }); + + describe("onCreateSuccess", () => { + it("should call functions to refresh tokens and update subscription seats on success", async () => { + const refreshTokensSpy: jest.SpyInstance = jest + .spyOn(TeamMemberService.prototype, "refreshTokens") + .mockResolvedValue(); + const updateSeatsSpy: jest.SpyInstance = jest + .spyOn( + TeamMemberService.prototype, + "updateSubscriptionSeatsByUniqueTeamMembersInProject", + ) + .mockResolvedValue(); + + const user: User = + await UserServiceHelper.generateRandomUser().data.save(); + + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + ); + const teamMember: TeamMember = await teamMemberService.create(tm); + + expect(refreshTokensSpy).toHaveBeenCalledWith( + teamMember.userId, + teamMember.projectId, + ); + expect(updateSeatsSpy).toHaveBeenCalledWith(new ObjectID(project._id!)); + }); + }); + }); + + describe("update tests", () => { + it("should update team member", async () => { + // (1) create new team member + const user: User = + await UserServiceHelper.generateRandomUser().data.save(); + + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + ); + + const teamMember: TeamMember = await teamMemberService.create(tm); + + expect(teamMember?.hasAcceptedInvitation).toBe(false); + + // (2) update team member + const updatedInfo: { hasAcceptedInvitation: boolean } = { + hasAcceptedInvitation: true, + }; + + const updatedCount: number = await teamMemberService.updateOneBy({ + query: { + _id: teamMember._id!, + }, + data: updatedInfo, + props: { isRoot: true }, + }); + + // check update was successful (1 document should be affected) + expect(updatedCount).toBe(1); + + // (3) retrieve the updated team member and validate changes + const updatedTeamMember: TeamMember | null = + await teamMemberService.findOneById({ + id: new ObjectID(teamMember._id!), + select: { hasAcceptedInvitation: true }, + props: { isRoot: true }, + }); + + expect(updatedTeamMember).toBeTruthy(); + expect(updatedTeamMember?.hasAcceptedInvitation).toBe( + updatedInfo.hasAcceptedInvitation, + ); + }); + + describe("onUpdateSuccess", () => { + it("should refresh tokens and handle user notification settings on update", async () => { + const user: User = + await UserServiceHelper.generateRandomUser().data.save(); + + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + ); + const teamMember: TeamMember = await teamMemberService.create(tm); + + const refreshTokensSpy: jest.SpyInstance = jest + .spyOn(TeamMemberService.prototype, "refreshTokens") + .mockResolvedValue(); + const addDefaultNotificationSettingsSpy: jest.SpyInstance = jest + .spyOn( + UserNotificationSettingService, + "addDefaultNotificationSettingsForUser", + ) + .mockResolvedValue(); + const addDefaultNotificationRuleForUserSpy: jest.SpyInstance = jest + .spyOn( + UserNotificationRuleService, + "addDefaultNotificationRuleForUser", + ) + .mockResolvedValue(); + + const updatedInfo: { hasAcceptedInvitation: boolean } = { + hasAcceptedInvitation: true, + }; + await teamMemberService.updateOneBy({ + query: { _id: teamMember._id! }, + data: updatedInfo, + props: { isRoot: true }, + }); + + expect(refreshTokensSpy).toHaveBeenCalledWith( + teamMember.userId, + teamMember.projectId, + ); + expect(addDefaultNotificationSettingsSpy).toHaveBeenCalledWith( + new ObjectID(user._id!), + new ObjectID(project._id!), + ); + expect(addDefaultNotificationRuleForUserSpy).toHaveBeenCalledWith( + new ObjectID(project._id!), + new ObjectID(user._id!), + user.email, + ); + }); + }); + }); + + describe("delete tests", () => { + it("should delete team member", async () => { + // (1) create new team member + const user: User = + await UserServiceHelper.generateRandomUser().data.save(); + + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + ); + + const teamMember: TeamMember = await teamMemberService.create(tm); + + // (2) delete team member + const deleteCount: number = await teamMemberService.deleteOneBy({ + query: { _id: teamMember._id! }, + props: { isRoot: true }, + }); + + // ensure deletion was successful (1 document should be affected) + expect(deleteCount).toBe(1); + + // (3) verify that the team member no longer exists + const deletedTeamMember: TeamMember | null = + await teamMemberService.findOneBy({ + query: { _id: teamMember._id! }, + props: { isRoot: true }, + }); + + expect(deletedTeamMember).toBeNull(); + }); + + describe("onBeforeDelete", () => { + it("should throw error when one member and team has at least one member requirement", async () => { + const team: Team = TeamServiceHelper.generateRandomTeam( + new ObjectID(project._id!), + new ObjectID(user._id!), ).data; - team = await team.save(); - }); + team.shouldHaveAtLeastOneMember = true; + await team.save(); - afterEach(async () => { - await testDatabase.disconnectAndDropDatabase(); - jest.resetAllMocks(); - }); + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + ); + const teamMember: TeamMember = await teamMemberService.create(tm); - describe('create tests', () => { - it('should create a new team member', async () => { - process.env['SUBSCRIPTION_PLAN_1'] = undefined; - process.env['SUBSCRIPTION_PLAN_2'] = undefined; + // accept invitation + teamMember.hasAcceptedInvitation = true; + await teamMember.save(); - ProjectService.findOneById = jest.fn().mockResolvedValue({ - _id: project._id, - }); - - TeamService.findOneById = jest.fn().mockResolvedValue(team); - - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); - - const teamMember: TeamMember = await teamMemberService.create(tm); - - expect(teamMember.userId).toEqual(new ObjectID(user._id!)); - expect(teamMember.projectId).toEqual(new ObjectID(project._id!)); - expect(teamMember.hasAcceptedInvitation).toBeFalsy(); - }); - - describe('onBeforeCreate', () => { - it('should throw exception if the user limit for a project is reached', async () => { - const SEATS_LIMIT: number = 5; - - ProjectService.findOneById = jest.fn().mockResolvedValue({ - seatLimit: SEATS_LIMIT, - paymentProviderSubscriptionSeats: SEATS_LIMIT, - _id: project._id, - }); - - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); - - await expect(teamMemberService.create(tm)).rejects.toThrow( - Errors.TeamMemberService.LIMIT_REACHED - ); - }); - - it('should throw exception if the user has already been invited', async () => { - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); - - jest.spyOn( - AccessTokenService, - 'refreshUserGlobalAccessPermission' - ).mockResolvedValue(null!); - - jest.spyOn( - AccessTokenService, - 'refreshUserTenantAccessPermission' - ).mockResolvedValue(null); - - await teamMemberService.create(tm); - - await expect(teamMemberService.create(tm)).rejects.toThrow( - Errors.TeamMemberService.ALREADY_INVITED - ); - }); - - it('should create user if the invited user does not exist in the system', async () => { - jest.spyOn(MailService, 'sendMail').mockResolvedValue(null!); - - const nonExistingUserEmail: string = faker.internet.email(); - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team, - { email: nonExistingUserEmail } - ); - const teamMember: TeamMember = await teamMemberService.create( - tm - ); - - expect(teamMember).toBeDefined(); - expect(teamMember.userId).toBeDefined(); - }); - - it('should send email when inviting non existing user', async () => { - jest.spyOn(MailService, 'sendMail').mockResolvedValue(null!); - - ProjectService.findOneById = jest.fn().mockResolvedValue({ - name: project.name, - _id: project._id, - }); - - const nonExistingUserEmail: string = faker.internet - .email() - .toLowerCase(); - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team, - { email: nonExistingUserEmail } - ); - - jest.spyOn( - AccessTokenService, - 'refreshUserGlobalAccessPermission' - ).mockResolvedValue(null!); - - jest.spyOn( - AccessTokenService, - 'refreshUserTenantAccessPermission' - ).mockResolvedValue(null); - - await teamMemberService.create(tm); - - expect(MailService.sendMail).toHaveBeenCalledWith( - { - subject: `You have been invited to ${project.name}`, - templateType: 'InviteMember.hbs', - toEmail: new Email(nonExistingUserEmail), - vars: { - homeUrl: `${HttpProtocol}${Host}/`, - isNewUser: 'true', - projectName: project.name, - registerLink: `${HttpProtocol}${Host}/accounts/register?email=${nonExistingUserEmail.replace( - '@', - '%40' - )}`, - signInLink: `${HttpProtocol}${Host}/accounts`, - }, - }, - { - projectId: new ObjectID(project._id!), - } - ); - }); - - it('should handle unexpected errors', async () => { - jest.spyOn(teamMemberService, 'create').mockRejectedValue( - new Error('Unexpected error') - ); - - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); - await expect(teamMemberService.create(tm)).rejects.toThrow( - 'Unexpected error' - ); - }); - }); - - describe('onCreateSuccess', () => { - it('should call functions to refresh tokens and update subscription seats on success', async () => { - const refreshTokensSpy: jest.SpyInstance = jest - .spyOn(TeamMemberService.prototype, 'refreshTokens') - .mockResolvedValue(); - const updateSeatsSpy: jest.SpyInstance = jest - .spyOn( - TeamMemberService.prototype, - 'updateSubscriptionSeatsByUniqueTeamMembersInProject' - ) - .mockResolvedValue(); - - const user: User = - await UserServiceHelper.generateRandomUser().data.save(); - - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); - const teamMember: TeamMember = await teamMemberService.create( - tm - ); - - expect(refreshTokensSpy).toHaveBeenCalledWith( - teamMember.userId, - teamMember.projectId - ); - expect(updateSeatsSpy).toHaveBeenCalledWith( - new ObjectID(project._id!) - ); - }); - }); - }); - - describe('update tests', () => { - it('should update team member', async () => { - // (1) create new team member - const user: User = - await UserServiceHelper.generateRandomUser().data.save(); - - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); - - const teamMember: TeamMember = await teamMemberService.create(tm); - - expect(teamMember?.hasAcceptedInvitation).toBe(false); - - // (2) update team member - const updatedInfo: { hasAcceptedInvitation: boolean } = { - hasAcceptedInvitation: true, - }; - - const updatedCount: number = await teamMemberService.updateOneBy({ - query: { - _id: teamMember._id!, - }, - data: updatedInfo, - props: { isRoot: true }, - }); - - // check update was successful (1 document should be affected) - expect(updatedCount).toBe(1); - - // (3) retrieve the updated team member and validate changes - const updatedTeamMember: TeamMember | null = - await teamMemberService.findOneById({ - id: new ObjectID(teamMember._id!), - select: { hasAcceptedInvitation: true }, - props: { isRoot: true }, - }); - - expect(updatedTeamMember).toBeTruthy(); - expect(updatedTeamMember?.hasAcceptedInvitation).toBe( - updatedInfo.hasAcceptedInvitation + try { + await teamMemberService.deleteOneBy({ + query: { + _id: teamMember._id!, + }, + props: { + isRoot: true, + }, + }); + } catch (errorPromise) { + try { + await errorPromise; + } catch (err: any) { + expect(err.message).toEqual( + Errors.TeamMemberService.ONE_MEMBER_REQUIRED, ); + } + } + }); + + it("should not delete when shouldHaveAtLeastOneMember is true and member has not accepted invitation", async () => { + const team: Team = TeamServiceHelper.generateRandomTeam( + new ObjectID(project._id!), + new ObjectID(user._id!), + ).data; + team.shouldHaveAtLeastOneMember = true; + await team.save(); + + const tm: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user._id!), + team, + ); + const teamMember: TeamMember = await teamMemberService.create(tm); + + await teamMemberService.deleteOneBy({ + query: { _id: teamMember._id! }, + props: { isRoot: true }, }); - describe('onUpdateSuccess', () => { - it('should refresh tokens and handle user notification settings on update', async () => { - const user: User = - await UserServiceHelper.generateRandomUser().data.save(); + const remainingMember: TeamMember | null = + await teamMemberService.findOneBy({ + query: { _id: teamMember._id! }, + props: { isRoot: true }, + }); + expect(remainingMember).toBeDefined(); + }); - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); - const teamMember: TeamMember = await teamMemberService.create( - tm - ); + it("should handle unexpected errors", async () => { + jest + .spyOn(teamMemberService, "deleteOneBy") + .mockRejectedValue(new Error("Unexpected error")); - const refreshTokensSpy: jest.SpyInstance = jest - .spyOn(TeamMemberService.prototype, 'refreshTokens') - .mockResolvedValue(); - const addDefaultNotificationSettingsSpy: jest.SpyInstance = jest - .spyOn( - UserNotificationSettingService, - 'addDefaultNotificationSettingsForUser' - ) - .mockResolvedValue(); - const addDefaultNotificationRuleForUserSpy: jest.SpyInstance = - jest - .spyOn( - UserNotificationRuleService, - 'addDefaultNotificationRuleForUser' - ) - .mockResolvedValue(); + await expect( + teamMemberService.deleteOneBy({ + query: { id: new ObjectID("") }, + props: { isRoot: true }, + }), + ).rejects.toThrow("Unexpected error"); + }); + }); + }); - const updatedInfo: { hasAcceptedInvitation: boolean } = { - hasAcceptedInvitation: true, - }; - await teamMemberService.updateOneBy({ - query: { _id: teamMember._id! }, - data: updatedInfo, - props: { isRoot: true }, - }); + describe("refreshTokens", () => { + it("should refresh user global and tenant access permissions", async () => { + jest.restoreAllMocks(); - expect(refreshTokensSpy).toHaveBeenCalledWith( - teamMember.userId, - teamMember.projectId - ); - expect(addDefaultNotificationSettingsSpy).toHaveBeenCalledWith( - new ObjectID(user._id!), - new ObjectID(project._id!) - ); - expect( - addDefaultNotificationRuleForUserSpy - ).toHaveBeenCalledWith( - new ObjectID(project._id!), - new ObjectID(user._id!), - user.email - ); - }); - }); + const userId: ObjectID = new ObjectID(faker.datatype.uuid()); + const projectId: ObjectID = new ObjectID(faker.datatype.uuid()); + + await teamMemberService.refreshTokens(userId, projectId); + + expect( + AccessTokenService.refreshUserGlobalAccessPermission, + ).toHaveBeenCalledWith(userId); + expect( + AccessTokenService.refreshUserTenantAccessPermission, + ).toHaveBeenCalledWith(userId, projectId); + }); + }); + + describe("getUniqueTeamMemberCountInProject", () => { + it("should return the count of unique team members in a project", async () => { + // make findBy to return 4 team members: 1 normal, 2 with the same id and 1 without a user ID + // total should be 2 unique team members + teamMemberService.findBy = jest.fn().mockResolvedValue([ + { + _id: faker.datatype.uuid(), + userId: faker.datatype.uuid(), + memberId: faker.datatype.uuid(), + }, + { + _id: faker.datatype.uuid(), + userId: "duplicated_id", + memberId: faker.datatype.uuid(), + }, + { + _id: faker.datatype.uuid(), + userId: "duplicated_id", + memberId: faker.datatype.uuid(), + }, + { + _id: faker.datatype.uuid(), + memberId: faker.datatype.uuid(), + }, + ]); + + const count: number = + await teamMemberService.getUniqueTeamMemberCountInProject( + new ObjectID(project._id!), + ); + expect(count).toBe(2); + }); + }); + + describe("getUsersInTeam(s)", () => { + it("should return users in specified team", async () => { + // team A members: user1 & user2 + // team B members: user2 & user3 + // team C members: user 3 + + const user1: User = + await UserServiceHelper.generateRandomUser().data.save(); + const user2: User = + await UserServiceHelper.generateRandomUser().data.save(); + const user3: User = + await UserServiceHelper.generateRandomUser().data.save(); + + const teamA: Team = await TeamServiceHelper.generateRandomTeam( + new ObjectID(project._id!), + new ObjectID(user._id!), + ).data.save(); + + const teamMemberA1: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user1._id!), + teamA, + ); + await teamMemberService.create(teamMemberA1); + const teamMemberA2: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user2._id!), + teamA, + ); + await teamMemberService.create(teamMemberA2); + + const teamB: Team = await TeamServiceHelper.generateRandomTeam( + new ObjectID(project._id!), + new ObjectID(user._id!), + ).data.save(); + const teamMemberB2: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user2._id!), + teamB, + ); + await teamMemberService.create(teamMemberB2); + const teamMemberB3: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user3._id!), + teamB, + ); + await teamMemberService.create(teamMemberB3); + + const teamC: Team = await TeamServiceHelper.generateRandomTeam( + new ObjectID(project._id!), + new ObjectID(user._id!), + ).data.save(); + const teamMemberC3: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user3._id!), + teamC, + ); + await teamMemberService.create(teamMemberC3); + + expect( + await teamMemberService.getUsersInTeam(new ObjectID(teamA._id!)), + ).toHaveLength(2); + expect( + await teamMemberService.getUsersInTeam(new ObjectID(teamB._id!)), + ).toHaveLength(2); + expect( + await teamMemberService.getUsersInTeam(new ObjectID(teamC._id!)), + ).toHaveLength(1); }); - describe('delete tests', () => { - it('should delete team member', async () => { - // (1) create new team member - const user: User = - await UserServiceHelper.generateRandomUser().data.save(); + it("should return users in multiple teams", async () => { + // team A members: user1 & user2 + // team B members: user2 & user3 + // team C members: user 3 - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); + const user1: User = + await UserServiceHelper.generateRandomUser().data.save(); + const user2: User = + await UserServiceHelper.generateRandomUser().data.save(); + const user3: User = + await UserServiceHelper.generateRandomUser().data.save(); - const teamMember: TeamMember = await teamMemberService.create(tm); + const teamA: Team = await TeamServiceHelper.generateRandomTeam( + new ObjectID(project._id!), + new ObjectID(user._id!), + ).data.save(); + const teamMemberA1: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user1._id!), + teamA, + ); + await teamMemberService.create(teamMemberA1); + const teamMemberA2: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user2._id!), + teamA, + ); + await teamMemberService.create(teamMemberA2); - // (2) delete team member - const deleteCount: number = await teamMemberService.deleteOneBy({ - query: { _id: teamMember._id! }, - props: { isRoot: true }, - }); + const teamB: Team = await TeamServiceHelper.generateRandomTeam( + new ObjectID(project._id!), + new ObjectID(user._id!), + ).data.save(); + const teamMemberB2: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user2._id!), + teamB, + ); + await teamMemberService.create(teamMemberB2); + const teamMemberB3: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user3._id!), + teamB, + ); + await teamMemberService.create(teamMemberB3); - // ensure deletion was successful (1 document should be affected) - expect(deleteCount).toBe(1); + const teamC: Team = await TeamServiceHelper.generateRandomTeam( + new ObjectID(project._id!), + new ObjectID(user._id!), + ).data.save(); + const teamMemberC3: CreateBy<TeamMember> = + TeamMemberServiceHelper.generateRandomTeamMember( + new ObjectID(project._id!), + new ObjectID(user3._id!), + teamC, + ); + await teamMemberService.create(teamMemberC3); - // (3) verify that the team member no longer exists - const deletedTeamMember: TeamMember | null = - await teamMemberService.findOneBy({ - query: { _id: teamMember._id! }, - props: { isRoot: true }, - }); + expect( + await teamMemberService.getUsersInTeams([ + new ObjectID(teamA._id!), + new ObjectID(teamB._id!), + new ObjectID(teamC._id!), + ]), + ).toHaveLength(3); + }); + }); - expect(deletedTeamMember).toBeNull(); - }); + describe("updateSubscriptionSeatsByUniqueTeamMembersInProject", () => { + const PROJECT_ID: string = "projectId"; + const SUBSCRIPTION_ID: string = "subscriptionId"; - describe('onBeforeDelete', () => { - it('should throw error when one member and team has at least one member requirement', async () => { - const team: Team = TeamServiceHelper.generateRandomTeam( - new ObjectID(project._id!), - new ObjectID(user._id!) - ).data; - team.shouldHaveAtLeastOneMember = true; - await team.save(); + it("should update subscription seats based on unique team members", async () => { + jest.restoreAllMocks(); - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); - const teamMember: TeamMember = await teamMemberService.create( - tm - ); + process.env["SUBSCRIPTION_PLAN_1"] = + "Free,monthly_plan_id,yearly_plan_id,0,0,1,7"; + process.env["SUBSCRIPTION_PLAN_2"] = + "Growth,growth_monthly_plan_id,growth_yearly_plan_id,9,99,2,14"; - // accept invitation - teamMember.hasAcceptedInvitation = true; - await teamMember.save(); + const NUM_MEMBERS: number = 5; + jest + .spyOn(teamMemberService, "getUniqueTeamMemberCountInProject") + .mockResolvedValue(NUM_MEMBERS); - try { - await teamMemberService.deleteOneBy({ - query: { - _id: teamMember._id!, - }, - props: { - isRoot: true, - }, - }); - } catch (errorPromise) { - try { - await errorPromise; - } catch (err: any) { - expect(err.message).toEqual( - Errors.TeamMemberService.ONE_MEMBER_REQUIRED - ); - } - } - }); + ProjectService.findOneById = jest.fn().mockResolvedValue({ + paymentProviderSubscriptionId: SUBSCRIPTION_ID, + paymentProviderPlanId: "monthly_plan_id", + _id: PROJECT_ID, + }); - it('should not delete when shouldHaveAtLeastOneMember is true and member has not accepted invitation', async () => { - const team: Team = TeamServiceHelper.generateRandomTeam( - new ObjectID(project._id!), - new ObjectID(user._id!) - ).data; - team.shouldHaveAtLeastOneMember = true; - await team.save(); + jest.spyOn(ProjectService, "updateOneById").mockResolvedValue(); - const tm: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user._id!), - team - ); - const teamMember: TeamMember = await teamMemberService.create( - tm - ); + await teamMemberService.updateSubscriptionSeatsByUniqueTeamMembersInProject( + new ObjectID(PROJECT_ID), + ); - await teamMemberService.deleteOneBy({ - query: { _id: teamMember._id! }, - props: { isRoot: true }, - }); + expect(BillingService.changeQuantity).toHaveBeenCalledWith( + SUBSCRIPTION_ID, + NUM_MEMBERS, + ); - const remainingMember: TeamMember | null = - await teamMemberService.findOneBy({ - query: { _id: teamMember._id! }, - props: { isRoot: true }, - }); - expect(remainingMember).toBeDefined(); - }); - - it('should handle unexpected errors', async () => { - jest.spyOn(teamMemberService, 'deleteOneBy').mockRejectedValue( - new Error('Unexpected error') - ); - - await expect( - teamMemberService.deleteOneBy({ - query: { id: new ObjectID('') }, - props: { isRoot: true }, - }) - ).rejects.toThrow('Unexpected error'); - }); - }); + expect(ProjectService.updateOneById).toHaveBeenCalledWith({ + id: new ObjectID(PROJECT_ID), + data: { paymentProviderSubscriptionSeats: NUM_MEMBERS }, + props: { isRoot: true }, + }); }); - describe('refreshTokens', () => { - it('should refresh user global and tenant access permissions', async () => { - jest.restoreAllMocks(); + it("should not update subscription seats if there are no plans", async () => { + process.env["SUBSCRIPTION_PLAN_1"] = undefined; + process.env["SUBSCRIPTION_PLAN_2"] = undefined; - const userId: ObjectID = new ObjectID(faker.datatype.uuid()); - const projectId: ObjectID = new ObjectID(faker.datatype.uuid()); + const NUM_MEMBERS: number = 5; + jest + .spyOn(teamMemberService, "getUniqueTeamMemberCountInProject") + .mockResolvedValue(NUM_MEMBERS); - await teamMemberService.refreshTokens(userId, projectId); + ProjectService.findOneById = jest.fn().mockResolvedValue({ + paymentProviderSubscriptionId: SUBSCRIPTION_ID, + paymentProviderPlanId: "monthly_plan_id", + _id: PROJECT_ID, + }); - expect( - AccessTokenService.refreshUserGlobalAccessPermission - ).toHaveBeenCalledWith(userId); - expect( - AccessTokenService.refreshUserTenantAccessPermission - ).toHaveBeenCalledWith(userId, projectId); - }); - }); - - describe('getUniqueTeamMemberCountInProject', () => { - it('should return the count of unique team members in a project', async () => { - // make findBy to return 4 team members: 1 normal, 2 with the same id and 1 without a user ID - // total should be 2 unique team members - teamMemberService.findBy = jest.fn().mockResolvedValue([ - { - _id: faker.datatype.uuid(), - userId: faker.datatype.uuid(), - memberId: faker.datatype.uuid(), - }, - { - _id: faker.datatype.uuid(), - userId: 'duplicated_id', - memberId: faker.datatype.uuid(), - }, - { - _id: faker.datatype.uuid(), - userId: 'duplicated_id', - memberId: faker.datatype.uuid(), - }, - { - _id: faker.datatype.uuid(), - memberId: faker.datatype.uuid(), - }, - ]); - - const count: number = - await teamMemberService.getUniqueTeamMemberCountInProject( - new ObjectID(project._id!) - ); - expect(count).toBe(2); - }); - }); - - describe('getUsersInTeam(s)', () => { - it('should return users in specified team', async () => { - // team A members: user1 & user2 - // team B members: user2 & user3 - // team C members: user 3 - - const user1: User = - await UserServiceHelper.generateRandomUser().data.save(); - const user2: User = - await UserServiceHelper.generateRandomUser().data.save(); - const user3: User = - await UserServiceHelper.generateRandomUser().data.save(); - - const teamA: Team = await TeamServiceHelper.generateRandomTeam( - new ObjectID(project._id!), - new ObjectID(user._id!) - ).data.save(); - - const teamMemberA1: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user1._id!), - teamA - ); - await teamMemberService.create(teamMemberA1); - const teamMemberA2: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user2._id!), - teamA - ); - await teamMemberService.create(teamMemberA2); - - const teamB: Team = await TeamServiceHelper.generateRandomTeam( - new ObjectID(project._id!), - new ObjectID(user._id!) - ).data.save(); - const teamMemberB2: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user2._id!), - teamB - ); - await teamMemberService.create(teamMemberB2); - const teamMemberB3: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user3._id!), - teamB - ); - await teamMemberService.create(teamMemberB3); - - const teamC: Team = await TeamServiceHelper.generateRandomTeam( - new ObjectID(project._id!), - new ObjectID(user._id!) - ).data.save(); - const teamMemberC3: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user3._id!), - teamC - ); - await teamMemberService.create(teamMemberC3); - - expect( - await teamMemberService.getUsersInTeam(new ObjectID(teamA._id!)) - ).toHaveLength(2); - expect( - await teamMemberService.getUsersInTeam(new ObjectID(teamB._id!)) - ).toHaveLength(2); - expect( - await teamMemberService.getUsersInTeam(new ObjectID(teamC._id!)) - ).toHaveLength(1); - }); - - it('should return users in multiple teams', async () => { - // team A members: user1 & user2 - // team B members: user2 & user3 - // team C members: user 3 - - const user1: User = - await UserServiceHelper.generateRandomUser().data.save(); - const user2: User = - await UserServiceHelper.generateRandomUser().data.save(); - const user3: User = - await UserServiceHelper.generateRandomUser().data.save(); - - const teamA: Team = await TeamServiceHelper.generateRandomTeam( - new ObjectID(project._id!), - new ObjectID(user._id!) - ).data.save(); - const teamMemberA1: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user1._id!), - teamA - ); - await teamMemberService.create(teamMemberA1); - const teamMemberA2: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user2._id!), - teamA - ); - await teamMemberService.create(teamMemberA2); - - const teamB: Team = await TeamServiceHelper.generateRandomTeam( - new ObjectID(project._id!), - new ObjectID(user._id!) - ).data.save(); - const teamMemberB2: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user2._id!), - teamB - ); - await teamMemberService.create(teamMemberB2); - const teamMemberB3: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user3._id!), - teamB - ); - await teamMemberService.create(teamMemberB3); - - const teamC: Team = await TeamServiceHelper.generateRandomTeam( - new ObjectID(project._id!), - new ObjectID(user._id!) - ).data.save(); - const teamMemberC3: CreateBy<TeamMember> = - TeamMemberServiceHelper.generateRandomTeamMember( - new ObjectID(project._id!), - new ObjectID(user3._id!), - teamC - ); - await teamMemberService.create(teamMemberC3); - - expect( - await teamMemberService.getUsersInTeams([ - new ObjectID(teamA._id!), - new ObjectID(teamB._id!), - new ObjectID(teamC._id!), - ]) - ).toHaveLength(3); - }); - }); - - describe('updateSubscriptionSeatsByUniqueTeamMembersInProject', () => { - const PROJECT_ID: string = 'projectId'; - const SUBSCRIPTION_ID: string = 'subscriptionId'; - - it('should update subscription seats based on unique team members', async () => { - jest.restoreAllMocks(); - - process.env['SUBSCRIPTION_PLAN_1'] = - 'Free,monthly_plan_id,yearly_plan_id,0,0,1,7'; - process.env['SUBSCRIPTION_PLAN_2'] = - 'Growth,growth_monthly_plan_id,growth_yearly_plan_id,9,99,2,14'; - - const NUM_MEMBERS: number = 5; - jest.spyOn( - teamMemberService, - 'getUniqueTeamMemberCountInProject' - ).mockResolvedValue(NUM_MEMBERS); - - ProjectService.findOneById = jest.fn().mockResolvedValue({ - paymentProviderSubscriptionId: SUBSCRIPTION_ID, - paymentProviderPlanId: 'monthly_plan_id', - _id: PROJECT_ID, - }); - - jest.spyOn(ProjectService, 'updateOneById').mockResolvedValue(); - - await teamMemberService.updateSubscriptionSeatsByUniqueTeamMembersInProject( - new ObjectID(PROJECT_ID) - ); - - expect(BillingService.changeQuantity).toHaveBeenCalledWith( - SUBSCRIPTION_ID, - NUM_MEMBERS - ); - - expect(ProjectService.updateOneById).toHaveBeenCalledWith({ - id: new ObjectID(PROJECT_ID), - data: { paymentProviderSubscriptionSeats: NUM_MEMBERS }, - props: { isRoot: true }, - }); - }); - - it('should not update subscription seats if there are no plans', async () => { - process.env['SUBSCRIPTION_PLAN_1'] = undefined; - process.env['SUBSCRIPTION_PLAN_2'] = undefined; - - const NUM_MEMBERS: number = 5; - jest.spyOn( - teamMemberService, - 'getUniqueTeamMemberCountInProject' - ).mockResolvedValue(NUM_MEMBERS); - - ProjectService.findOneById = jest.fn().mockResolvedValue({ - paymentProviderSubscriptionId: SUBSCRIPTION_ID, - paymentProviderPlanId: 'monthly_plan_id', - _id: PROJECT_ID, - }); - - await teamMemberService.updateSubscriptionSeatsByUniqueTeamMembersInProject( - new ObjectID(PROJECT_ID) - ); - - expect(BillingService.changeQuantity).not.toHaveBeenCalled(); - expect(ProjectService.updateOneById).not.toHaveBeenCalled(); - }); + await teamMemberService.updateSubscriptionSeatsByUniqueTeamMembersInProject( + new ObjectID(PROJECT_ID), + ); + + expect(BillingService.changeQuantity).not.toHaveBeenCalled(); + expect(ProjectService.updateOneById).not.toHaveBeenCalled(); }); + }); }); diff --git a/CommonServer/Tests/TestingUtils/Database.ts b/CommonServer/Tests/TestingUtils/Database.ts index a6a7a22203..17f8b6b3c1 100644 --- a/CommonServer/Tests/TestingUtils/Database.ts +++ b/CommonServer/Tests/TestingUtils/Database.ts @@ -1,59 +1,57 @@ -import PostgresDatabase from '../../Infrastructure/PostgresDatabase'; -import { DataSource, DataSourceOptions } from 'typeorm'; -import { createDatabase, dropDatabase } from 'typeorm-extension'; +import PostgresDatabase from "../../Infrastructure/PostgresDatabase"; +import { DataSource, DataSourceOptions } from "typeorm"; +import { createDatabase, dropDatabase } from "typeorm-extension"; export default class DatabaseConnect { - private database!: PostgresDatabase; - private dataSourceOptions!: DataSourceOptions; + private database!: PostgresDatabase; + private dataSourceOptions!: DataSourceOptions; - public constructor() { - this.database = new PostgresDatabase(); - } + public constructor() { + this.database = new PostgresDatabase(); + } - public getDatabase(): PostgresDatabase { - return this.database; - } + public getDatabase(): PostgresDatabase { + return this.database; + } - public async createAndConnect(): Promise<DataSource> { - const dataSourceOptions: DataSourceOptions = - await this.createDatabase(); - return await this.connectDatabase(dataSourceOptions); - } + public async createAndConnect(): Promise<DataSource> { + const dataSourceOptions: DataSourceOptions = await this.createDatabase(); + return await this.connectDatabase(dataSourceOptions); + } - public async disconnectAndDropDatabase(): Promise<void> { - await this.disconnectDatabase(); - await this.dropDatabase(); - } + public async disconnectAndDropDatabase(): Promise<void> { + await this.disconnectDatabase(); + await this.dropDatabase(); + } - public async createDatabase(): Promise<DataSourceOptions> { - const dataSourceOptions: DataSourceOptions = - this.database.getTestDatasourceOptions(); - this.dataSourceOptions = dataSourceOptions; - await createDatabase({ - options: dataSourceOptions, - ifNotExist: true, - }); + public async createDatabase(): Promise<DataSourceOptions> { + const dataSourceOptions: DataSourceOptions = + this.database.getTestDatasourceOptions(); + this.dataSourceOptions = dataSourceOptions; + await createDatabase({ + options: dataSourceOptions, + ifNotExist: true, + }); - return dataSourceOptions; - } - public async connectDatabase( - dataSourceOptions: DataSourceOptions - ): Promise<DataSource> { - const connection: DataSource = await this.database.connect( - dataSourceOptions - ); - await connection.synchronize(); - return connection; - } + return dataSourceOptions; + } + public async connectDatabase( + dataSourceOptions: DataSourceOptions, + ): Promise<DataSource> { + const connection: DataSource = + await this.database.connect(dataSourceOptions); + await connection.synchronize(); + return connection; + } - public async disconnectDatabase(): Promise<void> { - await this.database.disconnect(); - } + public async disconnectDatabase(): Promise<void> { + await this.database.disconnect(); + } - public async dropDatabase(): Promise<void> { - await dropDatabase({ - options: this.dataSourceOptions, - ifExist: true, - }); - } + public async dropDatabase(): Promise<void> { + await dropDatabase({ + options: this.dataSourceOptions, + ifExist: true, + }); + } } diff --git a/CommonServer/Tests/TestingUtils/Init.ts b/CommonServer/Tests/TestingUtils/Init.ts index 75af4cebcd..d24090dc93 100644 --- a/CommonServer/Tests/TestingUtils/Init.ts +++ b/CommonServer/Tests/TestingUtils/Init.ts @@ -1,5 +1,5 @@ -import '../../Utils/Environment'; +import "../../Utils/Environment"; // Env vars are actually changed in CommonServer/test-setup.sh -process.env['NODE_ENV'] = 'test'; +process.env["NODE_ENV"] = "test"; diff --git a/CommonServer/Tests/TestingUtils/Services/Helpers.ts b/CommonServer/Tests/TestingUtils/Services/Helpers.ts index a8bcdfc197..7f6003576e 100644 --- a/CommonServer/Tests/TestingUtils/Services/Helpers.ts +++ b/CommonServer/Tests/TestingUtils/Services/Helpers.ts @@ -1,200 +1,200 @@ -import { BillingService } from '../../../Services/BillingService'; +import { BillingService } from "../../../Services/BillingService"; import { - ChangePlan, - CouponData, - CustomerData, - MeteredSubscription, - Subscription, -} from '../../TestingUtils/Services/Types'; -import { faker } from '@faker-js/faker'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import Email from 'Common/Types/Email'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import ObjectID from 'Common/Types/ObjectID'; -import { Stripe } from 'stripe'; + ChangePlan, + CouponData, + CustomerData, + MeteredSubscription, + Subscription, +} from "../../TestingUtils/Services/Types"; +import { faker } from "@faker-js/faker"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import Email from "Common/Types/Email"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import ObjectID from "Common/Types/ObjectID"; +import { Stripe } from "stripe"; /// @dev consider modifyfing the EnvirontmentConfig to use functions instead of constants so that we can mock them type MockIsBillingEnabledFunction = (value: boolean) => BillingService; const mockIsBillingEnabled: MockIsBillingEnabledFunction = ( - value: boolean + value: boolean, ): BillingService => { - jest.resetModules(); - jest.doMock('../../../BillingConfig', () => { - return { - IsBillingEnabled: value, - }; - }); - const { BillingService } = require('../../../Services/BillingService'); - return new BillingService(); + jest.resetModules(); + jest.doMock("../../../BillingConfig", () => { + return { + IsBillingEnabled: value, + }; + }); + const { BillingService } = require("../../../Services/BillingService"); + return new BillingService(); }; type GetStripeCustomerFunction = (id?: string) => Stripe.Customer; const getStripeCustomer: GetStripeCustomerFunction = ( - id?: string + id?: string, ): Stripe.Customer => { - id = id || faker.datatype.uuid(); - return { - id, - object: 'customer', - balance: faker.datatype.number(), - created: 1, - default_source: null, - description: null, - email: null, - invoice_settings: { - custom_fields: null, - default_payment_method: null, - footer: null, - rendering_options: null, - }, - livemode: true, - metadata: {}, - shipping: null, - }; + id = id || faker.datatype.uuid(); + return { + id, + object: "customer", + balance: faker.datatype.number(), + created: 1, + default_source: null, + description: null, + email: null, + invoice_settings: { + custom_fields: null, + default_payment_method: null, + footer: null, + rendering_options: null, + }, + livemode: true, + metadata: {}, + shipping: null, + }; }; type GetStripeSubscriptionFunction = () => Stripe.Subscription; const getStripeSubscription: GetStripeSubscriptionFunction = - (): Stripe.Subscription => { - return { + (): Stripe.Subscription => { + return { + id: faker.datatype.uuid(), + items: { + data: [ + { id: faker.datatype.uuid(), - items: { - data: [ - { - id: faker.datatype.uuid(), - // @ts-ignore - price: { - id: new BillingService().getMeteredPlanPriceId( - ProductType.ActiveMonitoring - ), - }, - }, - ], + // @ts-ignore + price: { + id: new BillingService().getMeteredPlanPriceId( + ProductType.ActiveMonitoring, + ), }, - status: 'active', - customer: getStripeCustomer(), - }; + }, + ], + }, + status: "active", + customer: getStripeCustomer(), }; + }; type GetSubscriptionPlanDataFunction = () => SubscriptionPlan; const getSubscriptionPlanData: GetSubscriptionPlanDataFunction = - (): SubscriptionPlan => { - return new SubscriptionPlan( - faker.datatype.uuid(), // monthlyPlanId - faker.datatype.uuid(), // yearlyPlanId - faker.commerce.productName(), // name - faker.datatype.number(), // monthlySubscriptionAmountInUSD - faker.datatype.number({ min: 1, max: 100 }), // yearlySubscriptionAmountInUSD - faker.datatype.number({ min: 1, max: 100 }), // order - faker.datatype.number({ min: 1, max: 100 }) // trial period days - ); - }; + (): SubscriptionPlan => { + return new SubscriptionPlan( + faker.datatype.uuid(), // monthlyPlanId + faker.datatype.uuid(), // yearlyPlanId + faker.commerce.productName(), // name + faker.datatype.number(), // monthlySubscriptionAmountInUSD + faker.datatype.number({ min: 1, max: 100 }), // yearlySubscriptionAmountInUSD + faker.datatype.number({ min: 1, max: 100 }), // order + faker.datatype.number({ min: 1, max: 100 }), // trial period days + ); + }; type GetStripeInvoiceFunction = () => Stripe.Invoice; const getStripeInvoice: GetStripeInvoiceFunction = (): Stripe.Invoice => { - // @ts-ignore - return { - id: faker.datatype.uuid(), - amount_due: faker.datatype.number(), - currency: 'usd', - customer: faker.datatype.uuid(), - subscription: faker.datatype.uuid(), - status: 'paid', - }; + // @ts-ignore + return { + id: faker.datatype.uuid(), + amount_due: faker.datatype.number(), + currency: "usd", + customer: faker.datatype.uuid(), + subscription: faker.datatype.uuid(), + status: "paid", + }; }; type GetCustomerDataFunction = (id?: ObjectID) => CustomerData; const getCustomerData: GetCustomerDataFunction = ( - id?: ObjectID + id?: ObjectID, ): CustomerData => { - return { - id: id || new ObjectID('customer_id'), - name: 'John Doe', - email: new Email('test@example.com'), - }; + return { + id: id || new ObjectID("customer_id"), + name: "John Doe", + email: new Email("test@example.com"), + }; }; type GetSubscriptionDataFunction = (id?: ObjectID) => Subscription; const getSubscriptionData: GetSubscriptionDataFunction = ( - id?: ObjectID + id?: ObjectID, ): Subscription => { - return { - projectId: id || new ObjectID('project_id'), - customerId: 'cust_123', - serverMeteredPlans: [], - trialDate: new Date(), - }; + return { + projectId: id || new ObjectID("project_id"), + customerId: "cust_123", + serverMeteredPlans: [], + trialDate: new Date(), + }; }; type GetMeteredSubscriptionFunction = ( - subscriptionPlan: SubscriptionPlan, - id?: ObjectID + subscriptionPlan: SubscriptionPlan, + id?: ObjectID, ) => MeteredSubscription; const getMeteredSubscription: GetMeteredSubscriptionFunction = ( - subscriptionPlan: SubscriptionPlan, - id?: ObjectID + subscriptionPlan: SubscriptionPlan, + id?: ObjectID, ): MeteredSubscription => { - return { - projectId: id || new ObjectID('project_id'), - customerId: 'cust_123', - serverMeteredPlans: [], - plan: subscriptionPlan, - quantity: 1, - isYearly: false, - trial: true, - }; + return { + projectId: id || new ObjectID("project_id"), + customerId: "cust_123", + serverMeteredPlans: [], + plan: subscriptionPlan, + quantity: 1, + isYearly: false, + trial: true, + }; }; type GetChangePlanDataFunction = ( - subscriptionPlan: SubscriptionPlan, - id?: ObjectID + subscriptionPlan: SubscriptionPlan, + id?: ObjectID, ) => ChangePlan; const getChangePlanData: GetChangePlanDataFunction = ( - subscriptionPlan: SubscriptionPlan, - id?: ObjectID + subscriptionPlan: SubscriptionPlan, + id?: ObjectID, ): ChangePlan => { - return { - projectId: id || new ObjectID('project_id'), - subscriptionId: 'sub_123', - meteredSubscriptionId: 'sub_456', - serverMeteredPlans: [], - newPlan: subscriptionPlan, - quantity: 1, - isYearly: false, - }; + return { + projectId: id || new ObjectID("project_id"), + subscriptionId: "sub_123", + meteredSubscriptionId: "sub_456", + serverMeteredPlans: [], + newPlan: subscriptionPlan, + quantity: 1, + isYearly: false, + }; }; type GetCouponDataFunction = () => CouponData; const getCouponData: GetCouponDataFunction = (): CouponData => { - return { - name: 'TESTCOUPON', - metadata: { description: 'Test coupon' }, - percentOff: 10, - durationInMonths: 3, - maxRedemptions: 100, - }; + return { + name: "TESTCOUPON", + metadata: { description: "Test coupon" }, + percentOff: 10, + durationInMonths: 3, + maxRedemptions: 100, + }; }; export { - mockIsBillingEnabled, - getStripeCustomer, - getStripeSubscription, - getSubscriptionPlanData, - getCustomerData, - getSubscriptionData, - getMeteredSubscription, - getChangePlanData, - getCouponData, - getStripeInvoice, + mockIsBillingEnabled, + getStripeCustomer, + getStripeSubscription, + getSubscriptionPlanData, + getCustomerData, + getSubscriptionData, + getMeteredSubscription, + getChangePlanData, + getCouponData, + getStripeInvoice, }; diff --git a/CommonServer/Tests/TestingUtils/Services/ProjectServiceHelper.ts b/CommonServer/Tests/TestingUtils/Services/ProjectServiceHelper.ts index cacde23722..9e59e9a355 100644 --- a/CommonServer/Tests/TestingUtils/Services/ProjectServiceHelper.ts +++ b/CommonServer/Tests/TestingUtils/Services/ProjectServiceHelper.ts @@ -1,30 +1,30 @@ -import CreateBy from '../../../Types/Database/CreateBy'; -import ObjectID from 'Common/Types/ObjectID'; -import Faker from 'Common/Utils/Faker'; -import Project from 'Model/Models/Project'; +import CreateBy from "../../../Types/Database/CreateBy"; +import ObjectID from "Common/Types/ObjectID"; +import Faker from "Common/Utils/Faker"; +import Project from "Model/Models/Project"; export default class ProjectTestService { - public static generateRandomProject(userId?: ObjectID): CreateBy<Project> { - const project: Project = new Project(); + public static generateRandomProject(userId?: ObjectID): CreateBy<Project> { + const project: Project = new Project(); - // required fields - project.name = Faker.generateCompanyName(); - project.slug = project.name; - project.isBlocked = false; - project.requireSsoForLogin = false; - project.smsOrCallCurrentBalanceInUSDCents = 0; - project.autoRechargeSmsOrCallByBalanceInUSD = 0; - project.autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD = 0; - project.enableSmsNotifications = true; - project.enableCallNotifications = true; - project.enableAutoRechargeSmsOrCallBalance = true; - project.lowCallAndSMSBalanceNotificationSentToOwners = true; - project.failedCallAndSMSBalanceChargeNotificationSentToOwners = true; - project.notEnabledSmsOrCallNotificationSentToOwners = true; + // required fields + project.name = Faker.generateCompanyName(); + project.slug = project.name; + project.isBlocked = false; + project.requireSsoForLogin = false; + project.smsOrCallCurrentBalanceInUSDCents = 0; + project.autoRechargeSmsOrCallByBalanceInUSD = 0; + project.autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD = 0; + project.enableSmsNotifications = true; + project.enableCallNotifications = true; + project.enableAutoRechargeSmsOrCallBalance = true; + project.lowCallAndSMSBalanceNotificationSentToOwners = true; + project.failedCallAndSMSBalanceChargeNotificationSentToOwners = true; + project.notEnabledSmsOrCallNotificationSentToOwners = true; - return { - data: project, - props: { isRoot: true, userId }, - }; - } + return { + data: project, + props: { isRoot: true, userId }, + }; + } } diff --git a/CommonServer/Tests/TestingUtils/Services/ScheduledMaintenanceServiceHelper.ts b/CommonServer/Tests/TestingUtils/Services/ScheduledMaintenanceServiceHelper.ts index 42038b2204..eef3789ffe 100644 --- a/CommonServer/Tests/TestingUtils/Services/ScheduledMaintenanceServiceHelper.ts +++ b/CommonServer/Tests/TestingUtils/Services/ScheduledMaintenanceServiceHelper.ts @@ -1,30 +1,30 @@ -import CreateBy from '../../../Types/Database/CreateBy'; -import faker from '@faker-js/faker'; -import ObjectID from 'Common/Types/ObjectID'; -import Faker from 'Common/Utils/Faker'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; +import CreateBy from "../../../Types/Database/CreateBy"; +import faker from "@faker-js/faker"; +import ObjectID from "Common/Types/ObjectID"; +import Faker from "Common/Utils/Faker"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; export default class ScheduledMaintenanceTestService { - public static generateRandomScheduledMaintenance( - projectId: ObjectID, - currentScheduledMaintenanceStateId: ObjectID - ): CreateBy<ScheduledMaintenance> { - const maintenance: ScheduledMaintenance = new ScheduledMaintenance(); + public static generateRandomScheduledMaintenance( + projectId: ObjectID, + currentScheduledMaintenanceStateId: ObjectID, + ): CreateBy<ScheduledMaintenance> { + const maintenance: ScheduledMaintenance = new ScheduledMaintenance(); - // required fields - maintenance.projectId = projectId; - maintenance.currentScheduledMaintenanceStateId = - currentScheduledMaintenanceStateId; - maintenance.title = Faker.generateName(); - maintenance.description = Faker.generateName(); - maintenance.startsAt = faker.date.soon(1); - maintenance.endsAt = faker.date.soon(2); - maintenance.isOwnerNotifiedOfResourceCreation = false; - maintenance.slug = maintenance.title; + // required fields + maintenance.projectId = projectId; + maintenance.currentScheduledMaintenanceStateId = + currentScheduledMaintenanceStateId; + maintenance.title = Faker.generateName(); + maintenance.description = Faker.generateName(); + maintenance.startsAt = faker.date.soon(1); + maintenance.endsAt = faker.date.soon(2); + maintenance.isOwnerNotifiedOfResourceCreation = false; + maintenance.slug = maintenance.title; - return { - data: maintenance, - props: { isRoot: true }, - }; - } + return { + data: maintenance, + props: { isRoot: true }, + }; + } } diff --git a/CommonServer/Tests/TestingUtils/Services/ScheduledMaintenanceStateServiceHelper.ts b/CommonServer/Tests/TestingUtils/Services/ScheduledMaintenanceStateServiceHelper.ts index 7840ab98a0..f185dd5573 100644 --- a/CommonServer/Tests/TestingUtils/Services/ScheduledMaintenanceStateServiceHelper.ts +++ b/CommonServer/Tests/TestingUtils/Services/ScheduledMaintenanceStateServiceHelper.ts @@ -1,50 +1,50 @@ -import CreateBy from '../../../Types/Database/CreateBy'; -import { Black, Yellow } from 'Common/Types/BrandColors'; -import ObjectID from 'Common/Types/ObjectID'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; +import CreateBy from "../../../Types/Database/CreateBy"; +import { Black, Yellow } from "Common/Types/BrandColors"; +import ObjectID from "Common/Types/ObjectID"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; export default class ScheduledMaintenanceStateTestService { - public static generateScheduledState( - projectId: ObjectID - ): CreateBy<ScheduledMaintenanceState> { - const scheduledState: ScheduledMaintenanceState = - new ScheduledMaintenanceState(); + public static generateScheduledState( + projectId: ObjectID, + ): CreateBy<ScheduledMaintenanceState> { + const scheduledState: ScheduledMaintenanceState = + new ScheduledMaintenanceState(); - // required fields - scheduledState.name = 'Scheduled'; - scheduledState.description = - 'When an event is scheduled, it belongs to this state'; - scheduledState.color = Black; - scheduledState.isScheduledState = true; - scheduledState.projectId = projectId; - scheduledState.order = 1; - scheduledState.slug = scheduledState.name; + // required fields + scheduledState.name = "Scheduled"; + scheduledState.description = + "When an event is scheduled, it belongs to this state"; + scheduledState.color = Black; + scheduledState.isScheduledState = true; + scheduledState.projectId = projectId; + scheduledState.order = 1; + scheduledState.slug = scheduledState.name; - return { - data: scheduledState, - props: { isRoot: true }, - }; - } + return { + data: scheduledState, + props: { isRoot: true }, + }; + } - public static generateOngoingState( - projectId: ObjectID - ): CreateBy<ScheduledMaintenanceState> { - const ongoingState: ScheduledMaintenanceState = - new ScheduledMaintenanceState(); + public static generateOngoingState( + projectId: ObjectID, + ): CreateBy<ScheduledMaintenanceState> { + const ongoingState: ScheduledMaintenanceState = + new ScheduledMaintenanceState(); - // required fields - ongoingState.name = 'Ongoing'; - ongoingState.description = - 'When an event is ongoing, it belongs to this state.'; - ongoingState.color = Yellow; - ongoingState.isOngoingState = true; - ongoingState.projectId = projectId; - ongoingState.order = 2; - ongoingState.slug = ongoingState.name; + // required fields + ongoingState.name = "Ongoing"; + ongoingState.description = + "When an event is ongoing, it belongs to this state."; + ongoingState.color = Yellow; + ongoingState.isOngoingState = true; + ongoingState.projectId = projectId; + ongoingState.order = 2; + ongoingState.slug = ongoingState.name; - return { - data: ongoingState, - props: { isRoot: true }, - }; - } + return { + data: ongoingState, + props: { isRoot: true }, + }; + } } diff --git a/CommonServer/Tests/TestingUtils/Services/TeamMemberServiceHelper.ts b/CommonServer/Tests/TestingUtils/Services/TeamMemberServiceHelper.ts index fd5204e7b2..3f449d277f 100644 --- a/CommonServer/Tests/TestingUtils/Services/TeamMemberServiceHelper.ts +++ b/CommonServer/Tests/TestingUtils/Services/TeamMemberServiceHelper.ts @@ -1,27 +1,27 @@ -import CreateBy from '../../../Types/Database/CreateBy'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Team from 'Model/Models/Team'; -import TeamMember from 'Model/Models/TeamMember'; +import CreateBy from "../../../Types/Database/CreateBy"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Team from "Model/Models/Team"; +import TeamMember from "Model/Models/TeamMember"; export default class TeamMemberTestService { - public static generateRandomTeamMember( - projectId: ObjectID, - userId: ObjectID, - team: Team, - miscDataProps?: JSONObject - ): CreateBy<TeamMember> { - const teamMember: TeamMember = new TeamMember(); + public static generateRandomTeamMember( + projectId: ObjectID, + userId: ObjectID, + team: Team, + miscDataProps?: JSONObject, + ): CreateBy<TeamMember> { + const teamMember: TeamMember = new TeamMember(); - // required fields - teamMember.userId = userId; - teamMember.projectId = projectId; - teamMember.team = team; + // required fields + teamMember.userId = userId; + teamMember.projectId = projectId; + teamMember.team = team; - return { - data: teamMember, - props: { isRoot: true, userId }, - miscDataProps: miscDataProps as JSONObject, - }; - } + return { + data: teamMember, + props: { isRoot: true, userId }, + miscDataProps: miscDataProps as JSONObject, + }; + } } diff --git a/CommonServer/Tests/TestingUtils/Services/TeamServiceHelper.ts b/CommonServer/Tests/TestingUtils/Services/TeamServiceHelper.ts index f0365a0dd1..6e1f0af1a1 100644 --- a/CommonServer/Tests/TestingUtils/Services/TeamServiceHelper.ts +++ b/CommonServer/Tests/TestingUtils/Services/TeamServiceHelper.ts @@ -1,23 +1,23 @@ -import CreateBy from '../../../Types/Database/CreateBy'; -import faker from '@faker-js/faker'; -import ObjectID from 'Common/Types/ObjectID'; -import Team from 'Model/Models/Team'; +import CreateBy from "../../../Types/Database/CreateBy"; +import faker from "@faker-js/faker"; +import ObjectID from "Common/Types/ObjectID"; +import Team from "Model/Models/Team"; export default class TeamTestService { - public static generateRandomTeam( - projectId: ObjectID, - userId?: ObjectID - ): CreateBy<Team> { - const team: Team = new Team(); + public static generateRandomTeam( + projectId: ObjectID, + userId?: ObjectID, + ): CreateBy<Team> { + const team: Team = new Team(); - // required fields - team.name = faker.random.alphaNumeric(10); - team.slug = team.name; - team.projectId = projectId; + // required fields + team.name = faker.random.alphaNumeric(10); + team.slug = team.name; + team.projectId = projectId; - return { - data: team, - props: { isRoot: true, userId }, - }; - } + return { + data: team, + props: { isRoot: true, userId }, + }; + } } diff --git a/CommonServer/Tests/TestingUtils/Services/Types.ts b/CommonServer/Tests/TestingUtils/Services/Types.ts index 04ad538370..a47b706721 100644 --- a/CommonServer/Tests/TestingUtils/Services/Types.ts +++ b/CommonServer/Tests/TestingUtils/Services/Types.ts @@ -1,57 +1,57 @@ -import { PaymentMethod } from '../../../Services/BillingService'; -import ServerMeteredPlan from '../../../Types/Billing/MeteredPlan/ServerMeteredPlan'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import Dictionary from 'Common/Types/Dictionary'; -import Email from 'Common/Types/Email'; -import ObjectID from 'Common/Types/ObjectID'; +import { PaymentMethod } from "../../../Services/BillingService"; +import ServerMeteredPlan from "../../../Types/Billing/MeteredPlan/ServerMeteredPlan"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import Dictionary from "Common/Types/Dictionary"; +import Email from "Common/Types/Email"; +import ObjectID from "Common/Types/ObjectID"; export type CustomerData = { - id: ObjectID; - name: string; - email: Email; + id: ObjectID; + name: string; + email: Email; }; export type CouponData = { - name: string; - metadata?: Dictionary<string> | undefined; - percentOff: number; - durationInMonths: number; - maxRedemptions: number; + name: string; + metadata?: Dictionary<string> | undefined; + percentOff: number; + durationInMonths: number; + maxRedemptions: number; }; export type Subscription = { - projectId: ObjectID; - customerId: string; - serverMeteredPlans: Array<ServerMeteredPlan>; - promoCode?: string; - defaultPaymentMethodId?: string; - trialDate: Date; + projectId: ObjectID; + customerId: string; + serverMeteredPlans: Array<ServerMeteredPlan>; + promoCode?: string; + defaultPaymentMethodId?: string; + trialDate: Date; }; export type MeteredSubscription = { - projectId: ObjectID; - customerId: string; - serverMeteredPlans: Array<ServerMeteredPlan>; - plan: SubscriptionPlan; - quantity: number; - isYearly: boolean; - trial: boolean | Date | undefined; - defaultPaymentMethodId?: string | undefined; - promoCode?: string | undefined; + projectId: ObjectID; + customerId: string; + serverMeteredPlans: Array<ServerMeteredPlan>; + plan: SubscriptionPlan; + quantity: number; + isYearly: boolean; + trial: boolean | Date | undefined; + defaultPaymentMethodId?: string | undefined; + promoCode?: string | undefined; }; export type ChangePlan = { - projectId: ObjectID; - subscriptionId: string; - meteredSubscriptionId: string; - serverMeteredPlans: Array<ServerMeteredPlan>; - newPlan: SubscriptionPlan; - quantity: number; - isYearly: boolean; - endTrialAt?: Date | undefined; + projectId: ObjectID; + subscriptionId: string; + meteredSubscriptionId: string; + serverMeteredPlans: Array<ServerMeteredPlan>; + newPlan: SubscriptionPlan; + quantity: number; + isYearly: boolean; + endTrialAt?: Date | undefined; }; export type PaymentMethodsResponse = { - data: PaymentMethod[]; - defaultPaymentMethodId?: string | undefined; + data: PaymentMethod[]; + defaultPaymentMethodId?: string | undefined; }; diff --git a/CommonServer/Tests/TestingUtils/Services/UserServiceHelper.ts b/CommonServer/Tests/TestingUtils/Services/UserServiceHelper.ts index d1d0aac49f..9f40200d4a 100644 --- a/CommonServer/Tests/TestingUtils/Services/UserServiceHelper.ts +++ b/CommonServer/Tests/TestingUtils/Services/UserServiceHelper.ts @@ -1,29 +1,29 @@ -import CreateBy from '../../../Types/Database/CreateBy'; -import CompanySize from 'Common/Types/Company/CompanySize'; -import JobRole from 'Common/Types/Company/JobRole'; -import Faker from 'Common/Utils/Faker'; -import User from 'Model/Models/User'; +import CreateBy from "../../../Types/Database/CreateBy"; +import CompanySize from "Common/Types/Company/CompanySize"; +import JobRole from "Common/Types/Company/JobRole"; +import Faker from "Common/Utils/Faker"; +import User from "Model/Models/User"; export default class UserTestService { - public static generateRandomUser(): CreateBy<User> { - const user: User = new User(); - user.name = Faker.generateUserFullName(); - user.slug = user.name.toString(); - user.email = Faker.generateEmail(); - user.companyName = Faker.generateCompanyName(); - user.companySize = CompanySize.OneToTen; - user.jobRole = JobRole.CEO; - user.companyPhoneNumber = Faker.generatePhone(); - user.twoFactorAuthEnabled = false; - user.lastActive = new Date(); - user.isDisabled = false; - user.isBlocked = false; - user.isMasterAdmin = false; - user.isEmailVerified = true; + public static generateRandomUser(): CreateBy<User> { + const user: User = new User(); + user.name = Faker.generateUserFullName(); + user.slug = user.name.toString(); + user.email = Faker.generateEmail(); + user.companyName = Faker.generateCompanyName(); + user.companySize = CompanySize.OneToTen; + user.jobRole = JobRole.CEO; + user.companyPhoneNumber = Faker.generatePhone(); + user.twoFactorAuthEnabled = false; + user.lastActive = new Date(); + user.isDisabled = false; + user.isBlocked = false; + user.isMasterAdmin = false; + user.isEmailVerified = true; - return { - data: user, - props: { isRoot: true }, - }; - } + return { + data: user, + props: { isRoot: true }, + }; + } } diff --git a/CommonServer/Tests/Utils/AnalyticsDatabase/Statement.test.ts b/CommonServer/Tests/Utils/AnalyticsDatabase/Statement.test.ts index 6bfa200963..7cebcd5dfd 100644 --- a/CommonServer/Tests/Utils/AnalyticsDatabase/Statement.test.ts +++ b/CommonServer/Tests/Utils/AnalyticsDatabase/Statement.test.ts @@ -1,102 +1,102 @@ -import { SQL, Statement } from '../../../Utils/AnalyticsDatabase/Statement'; -import '../../TestingUtils/Init'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; +import { SQL, Statement } from "../../../Utils/AnalyticsDatabase/Statement"; +import "../../TestingUtils/Init"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; -describe('Statement', () => { - describe('constructor', () => { - test('Should default to empty', () => { - const statement: Statement = new Statement(); - expect(statement.query).toBe(''); - expect(statement.query_params).toStrictEqual({}); - }); +describe("Statement", () => { + describe("constructor", () => { + test("Should default to empty", () => { + const statement: Statement = new Statement(); + expect(statement.query).toBe(""); + expect(statement.query_params).toStrictEqual({}); + }); + }); + + describe("SQL", () => { + test("should produce ClickHouse query params", () => { + const statement: Statement = SQL`SELECT * FROM table`; + expect(statement.query).toBe("SELECT * FROM table"); + expect(statement.query_params).toStrictEqual({}); }); - describe('SQL', () => { - test('should produce ClickHouse query params', () => { - const statement: Statement = SQL`SELECT * FROM table`; - expect(statement.query).toBe('SELECT * FROM table'); - expect(statement.query_params).toStrictEqual({}); - }); - - test('should use ClickHouse query param substitution', () => { - const statement: Statement = SQL` + test("should use ClickHouse query param substitution", () => { + const statement: Statement = SQL` SELECT * FROM - ${'table'} + ${"table"} WHERE TRUE AND number_col = ${{ - value: 123, - type: TableColumnType.Number, + value: 123, + type: TableColumnType.Number, }} AND text_col = ${{ - value: '<text>', - type: TableColumnType.Text, + value: "<text>", + type: TableColumnType.Text, }} `; - expect(statement.query).toBe( - 'SELECT\n' + - ' *\n' + - 'FROM\n' + - ' {p0:Identifier}\n' + - 'WHERE TRUE\n' + - ' AND number_col = {p1:Int32}\n' + - ' AND text_col = {p2:String}' - ); - expect(statement.query_params).toStrictEqual({ - p0: 'table', - p1: 123, - p2: '<text>', - }); - }); + expect(statement.query).toBe( + "SELECT\n" + + " *\n" + + "FROM\n" + + " {p0:Identifier}\n" + + "WHERE TRUE\n" + + " AND number_col = {p1:Int32}\n" + + " AND text_col = {p2:String}", + ); + expect(statement.query_params).toStrictEqual({ + p0: "table", + p1: 123, + p2: "<text>", + }); + }); - describe('append', () => { - test('should append a Statement', () => { - const statement: Statement = SQL` + describe("append", () => { + test("should append a Statement", () => { + const statement: Statement = SQL` SELECT * FROM - ${'table'} + ${"table"} WHERE TRUE AND number_col = ${{ - value: 123, - type: TableColumnType.Number, + value: 123, + type: TableColumnType.Number, }} AND text_col = ${{ - value: '<text>', - type: TableColumnType.Text, + value: "<text>", + type: TableColumnType.Text, }} `.append(SQL` AND boolean_col = ${{ - value: false, - type: TableColumnType.Boolean, + value: false, + type: TableColumnType.Boolean, }} AND decimal_col = ${{ - value: 234.5, - type: TableColumnType.Decimal, + value: 234.5, + type: TableColumnType.Decimal, }} `); - expect(statement.query).toBe( - 'SELECT\n' + - ' *\n' + - 'FROM\n' + - ' {p0:Identifier}\n' + - 'WHERE TRUE\n' + - ' AND number_col = {p1:Int32}\n' + - ' AND text_col = {p2:String}\n' + - '\n' + - ' AND boolean_col = {p3:Bool}\n' + - ' AND decimal_col = {p4:Double}' - ); - expect(statement.query_params).toStrictEqual({ - p0: 'table', - p1: 123, - p2: '<text>', - p3: false, - p4: 234.5, - }); - }); + expect(statement.query).toBe( + "SELECT\n" + + " *\n" + + "FROM\n" + + " {p0:Identifier}\n" + + "WHERE TRUE\n" + + " AND number_col = {p1:Int32}\n" + + " AND text_col = {p2:String}\n" + + "\n" + + " AND boolean_col = {p3:Bool}\n" + + " AND decimal_col = {p4:Double}", + ); + expect(statement.query_params).toStrictEqual({ + p0: "table", + p1: 123, + p2: "<text>", + p3: false, + p4: 234.5, }); + }); }); + }); }); diff --git a/CommonServer/Tests/Utils/AnalyticsDatabase/StatementGenerator.test.ts b/CommonServer/Tests/Utils/AnalyticsDatabase/StatementGenerator.test.ts index 36ead0619a..834de36718 100644 --- a/CommonServer/Tests/Utils/AnalyticsDatabase/StatementGenerator.test.ts +++ b/CommonServer/Tests/Utils/AnalyticsDatabase/StatementGenerator.test.ts @@ -1,346 +1,340 @@ -import { ClickhouseAppInstance } from '../../../Infrastructure/ClickhouseDatabase'; -import UpdateBy from '../../../Types/AnalyticsDatabase/UpdateBy'; -import { SQL, Statement } from '../../../Utils/AnalyticsDatabase/Statement'; -import StatementGenerator from '../../../Utils/AnalyticsDatabase/StatementGenerator'; -import logger from '../../../Utils/Logger'; -import '../../TestingUtils/Init'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import NestedModel from 'Common/AnalyticsModels/NestedModel'; -import Route from 'Common/Types/API/Route'; -import AnalyticsTableEngine from 'Common/Types/AnalyticsDatabase/AnalyticsTableEngine'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import OneUptimeDate from 'Common/Types/Date'; -import GenericObject from 'Common/Types/GenericObject'; +import { ClickhouseAppInstance } from "../../../Infrastructure/ClickhouseDatabase"; +import UpdateBy from "../../../Types/AnalyticsDatabase/UpdateBy"; +import { SQL, Statement } from "../../../Utils/AnalyticsDatabase/Statement"; +import StatementGenerator from "../../../Utils/AnalyticsDatabase/StatementGenerator"; +import logger from "../../../Utils/Logger"; +import "../../TestingUtils/Init"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import NestedModel from "Common/AnalyticsModels/NestedModel"; +import Route from "Common/Types/API/Route"; +import AnalyticsTableEngine from "Common/Types/AnalyticsDatabase/AnalyticsTableEngine"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import OneUptimeDate from "Common/Types/Date"; +import GenericObject from "Common/Types/GenericObject"; function expectStatement(actual: Statement, expected: Statement): void { - expect(actual.query).toBe(expected.query); - expect(actual.query_params).toStrictEqual(expected.query_params); + expect(actual.query).toBe(expected.query); + expect(actual.query_params).toStrictEqual(expected.query_params); } -describe('StatementGenerator', () => { - class TestModel extends AnalyticsBaseModel { - public constructor() { - super({ - tableName: '<table-name>', - singularName: '<singular-name>', - pluralName: '<plural-name>', - tableColumns: [ - new AnalyticsTableColumn({ - key: `column_ObjectID`, - title: '<title>', - description: '<description>', - required: true, - type: TableColumnType.ObjectID, - }), - new AnalyticsTableColumn({ - key: `column_1`, - title: '<title>', - description: '<description>', - required: false, - type: TableColumnType.Text, - }), - new AnalyticsTableColumn({ - key: `column_2`, - title: '<title>', - description: '<description>', - required: false, - type: TableColumnType.Number, - }), - ], - crudApiPath: new Route('route'), - primaryKeys: ['column_ObjectID'], - tableEngine: AnalyticsTableEngine.MergeTree, - }); - } +describe("StatementGenerator", () => { + class TestModel extends AnalyticsBaseModel { + public constructor() { + super({ + tableName: "<table-name>", + singularName: "<singular-name>", + pluralName: "<plural-name>", + tableColumns: [ + new AnalyticsTableColumn({ + key: `column_ObjectID`, + title: "<title>", + description: "<description>", + required: true, + type: TableColumnType.ObjectID, + }), + new AnalyticsTableColumn({ + key: `column_1`, + title: "<title>", + description: "<description>", + required: false, + type: TableColumnType.Text, + }), + new AnalyticsTableColumn({ + key: `column_2`, + title: "<title>", + description: "<description>", + required: false, + type: TableColumnType.Number, + }), + ], + crudApiPath: new Route("route"), + primaryKeys: ["column_ObjectID"], + tableEngine: AnalyticsTableEngine.MergeTree, + }); } + } - let generator: StatementGenerator<TestModel>; - beforeEach(async () => { - generator = new StatementGenerator<TestModel>({ - modelType: TestModel, - database: ClickhouseAppInstance, - }); + let generator: StatementGenerator<TestModel>; + beforeEach(async () => { + generator = new StatementGenerator<TestModel>({ + modelType: TestModel, + database: ClickhouseAppInstance, + }); + }); + + describe("toUpdateStatement", () => { + let updateBy: UpdateBy<TestModel>; + beforeEach(() => { + updateBy = { + data: new TestModel(), + query: "<query>" as GenericObject, + props: "<props>" as GenericObject, + }; + generator.toSetStatement = jest.fn(() => { + return SQL`<set-statement>`; + }); + generator.toWhereStatement = jest.fn(() => { + return SQL`<where-statement>`; + }); + jest.spyOn(logger, "debug").mockImplementation(() => { + return undefined!; + }); }); - describe('toUpdateStatement', () => { - let updateBy: UpdateBy<TestModel>; - beforeEach(() => { - updateBy = { - data: new TestModel(), - query: '<query>' as GenericObject, - props: '<props>' as GenericObject, - }; - generator.toSetStatement = jest.fn(() => { - return SQL`<set-statement>`; - }); - generator.toWhereStatement = jest.fn(() => { - return SQL`<where-statement>`; - }); - jest.spyOn(logger, 'debug').mockImplementation(() => { - return undefined!; - }); - }); + afterEach(() => { + jest.restoreAllMocks(); + }); - afterEach(() => { - jest.restoreAllMocks(); - }); + test("should return ALTER TABLE UPDATE statement", () => { + const statement: Statement = generator.toUpdateStatement(updateBy); - test('should return ALTER TABLE UPDATE statement', () => { - const statement: Statement = generator.toUpdateStatement(updateBy); + expect(generator.toSetStatement).toBeCalledWith(updateBy.data); + expect(generator.toWhereStatement).toBeCalledWith("<query>"); - expect(generator.toSetStatement).toBeCalledWith(updateBy.data); - expect(generator.toWhereStatement).toBeCalledWith('<query>'); + expect(jest.mocked(logger.debug)).toHaveBeenCalledTimes(2); + expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith( + 1, + "<table-name> Update Statement", + ); + expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith(2, statement); - expect(jest.mocked(logger.debug)).toHaveBeenCalledTimes(2); - expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith( - 1, - '<table-name> Update Statement' - ); - expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith( - 2, - statement - ); - - /* eslint-disable prettier/prettier */ + /* eslint-disable prettier/prettier */ expectStatement(statement, SQL` ALTER TABLE ${'oneuptime'}.${'<table-name>'} UPDATE <set-statement> WHERE TRUE <where-statement> `); /* eslint-enable prettier/prettier */ - }); + }); + }); + + describe("toSetStatement", () => { + let model: TestModel; + beforeEach(() => { + model = new TestModel(); }); - describe('toSetStatement', () => { - let model: TestModel; - beforeEach(() => { - model = new TestModel(); - }); - - test('should return the contents of a SET statement', () => { - model.setColumnValue('column_1', '<value>'); - const statement: Statement = generator.toSetStatement(model); - expect(statement.query).toBe('column_1 = {p0:String}'); - expect(statement.query_params).toStrictEqual({ - p0: '<value>', - }); - }); - - test('should set multiple columns', () => { - model.setColumnValue('column_1', '<value>'); - model.setColumnValue('column_2', 123); - const statement: Statement = generator.toSetStatement(model); - expect(statement.query).toBe( - 'column_1 = {p0:String}, column_2 = {p1:Int32}' - ); - expect(statement.query_params).toStrictEqual({ - p0: '<value>', - p1: 123, - }); - }); - - test('should set column to NULL', () => { - model.setColumnValue('column_1', null); - const statement: Statement = generator.toSetStatement(model); - expect(statement.query).toBe('column_1 = {p0:String}'); - expect(statement.query_params).toStrictEqual({ - p0: null, - }); - }); + test("should return the contents of a SET statement", () => { + model.setColumnValue("column_1", "<value>"); + const statement: Statement = generator.toSetStatement(model); + expect(statement.query).toBe("column_1 = {p0:String}"); + expect(statement.query_params).toStrictEqual({ + p0: "<value>", + }); }); - describe('toWhereStatement', () => { - test('should return the contents of a WHERE statement', () => { - const statement: Statement = generator.toWhereStatement({ - _id: '<value>', - }); - expect(statement.query).toBe('AND {p0:Identifier} = {p1:String}'); - expect(statement.query_params).toStrictEqual({ - p0: '_id', - p1: '<value>', - }); - }); - - test('should check multiple columns', () => { - const date: Date = new Date(9876543210); - - const statement: Statement = generator.toWhereStatement({ - _id: '<value>', - updatedAt: date, - }); - expect(statement.query).toBe( - 'AND {p0:Identifier} = {p1:String} AND {p2:Identifier} = {p3:DateTime}' - ); - expect(statement.query_params).toStrictEqual({ - p0: '_id', - p1: '<value>', - p2: 'updatedAt', - p3: OneUptimeDate.toDatabaseDate(date), - }); - }); + test("should set multiple columns", () => { + model.setColumnValue("column_1", "<value>"); + model.setColumnValue("column_2", 123); + const statement: Statement = generator.toSetStatement(model); + expect(statement.query).toBe( + "column_1 = {p0:String}, column_2 = {p1:Int32}", + ); + expect(statement.query_params).toStrictEqual({ + p0: "<value>", + p1: 123, + }); }); - describe('toSelectStatement', () => { - test('should return the contents of a SELECT statement', () => { - const { statement, columns } = generator.toSelectStatement({ - _id: true, - }); - expect(statement.query).toBe('{p0:Identifier}'); - expect(statement.query_params).toStrictEqual({ - p0: '_id', - }); - expect(columns).toStrictEqual(['_id']); - }); + test("should set column to NULL", () => { + model.setColumnValue("column_1", null); + const statement: Statement = generator.toSetStatement(model); + expect(statement.query).toBe("column_1 = {p0:String}"); + expect(statement.query_params).toStrictEqual({ + p0: null, + }); + }); + }); - test('should SELECT multiple columns', () => { - const { statement, columns } = generator.toSelectStatement({ - _id: true, - createdAt: true, - updatedAt: true, - }); - expect(statement.query).toBe( - '{p0:Identifier}, {p1:Identifier}, {p2:Identifier}' - ); - expect(statement.query_params).toStrictEqual({ - p0: '_id', - p1: 'createdAt', - p2: 'updatedAt', - }); - expect(columns).toStrictEqual(['_id', 'createdAt', 'updatedAt']); - }); + describe("toWhereStatement", () => { + test("should return the contents of a WHERE statement", () => { + const statement: Statement = generator.toWhereStatement({ + _id: "<value>", + }); + expect(statement.query).toBe("AND {p0:Identifier} = {p1:String}"); + expect(statement.query_params).toStrictEqual({ + p0: "_id", + p1: "<value>", + }); }); - describe('toColumnsCreateStatement', () => { - test('should return the columns of a CREATE TABLE statement', () => { - const statement: Statement = generator.toColumnsCreateStatement([ - new AnalyticsTableColumn({ - key: 'column_1', - title: '<title>', - description: '<description>', - required: true, - type: TableColumnType.Text, - }), - new AnalyticsTableColumn({ - key: 'column_2', - title: '<title>', - description: '<description>', - required: false, - type: TableColumnType.Number, - }), - ]); + test("should check multiple columns", () => { + const date: Date = new Date(9876543210); - expectStatement( - statement, - SQL`column_1 String, column_2 Nullable(Int32)` - ); - }); + const statement: Statement = generator.toWhereStatement({ + _id: "<value>", + updatedAt: date, + }); + expect(statement.query).toBe( + "AND {p0:Identifier} = {p1:String} AND {p2:Identifier} = {p3:DateTime}", + ); + expect(statement.query_params).toStrictEqual({ + p0: "_id", + p1: "<value>", + p2: "updatedAt", + p3: OneUptimeDate.toDatabaseDate(date), + }); + }); + }); - test('should support nested models', () => { - class TestNestedModel extends NestedModel { - public constructor() { - super({ - tableColumns: [ - new AnalyticsTableColumn({ - key: 'nested_column_1', - title: '<title>', - description: '<description>', - required: true, - type: TableColumnType.Text, - }), - new AnalyticsTableColumn({ - key: 'nested_column_2', - title: '<title>', - description: '<description>', - required: false, - type: TableColumnType.Number, - }), - ], - }); - } - } - - const statement: Statement = generator.toColumnsCreateStatement([ - new AnalyticsTableColumn({ - key: 'column_1', - title: '<title>', - description: '<description>', - required: true, - type: TableColumnType.Text, - }), - new AnalyticsTableColumn({ - key: 'column_2', - title: '<title>', - description: '<description>', - required: false, - type: TableColumnType.NestedModel, - nestedModelType: TestNestedModel, - }), - ]); - - expectStatement( - statement, - SQL`column_1 String, column_2 Nullable(Nested) (nested_column_1 String, nested_column_2 Nullable(Int32))` - ); - }); - - test('should not add NULL|NOT NULL to Array types', () => { - const statement: Statement = generator.toColumnsCreateStatement([ - new AnalyticsTableColumn({ - key: 'column_1', - title: '<title>', - description: '<description>', - required: true, - type: TableColumnType.ArrayText, - }), - new AnalyticsTableColumn({ - key: 'column_2', - title: '<title>', - description: '<description>', - required: false, - type: TableColumnType.ArrayNumber, - }), - ]); - - expectStatement( - statement, - SQL`column_1 Array(String), column_2 Nullable(Array(Int32))` - ); - }); + describe("toSelectStatement", () => { + test("should return the contents of a SELECT statement", () => { + const { statement, columns } = generator.toSelectStatement({ + _id: true, + }); + expect(statement.query).toBe("{p0:Identifier}"); + expect(statement.query_params).toStrictEqual({ + p0: "_id", + }); + expect(columns).toStrictEqual(["_id"]); }); - describe('toTableCreateStatement', () => { - beforeEach(() => { - generator.toColumnsCreateStatement = jest.fn(() => { - return SQL` <columns-create-statement>`; - }); - jest.spyOn(logger, 'debug').mockImplementation(() => { - return undefined!; - }); - }); + test("should SELECT multiple columns", () => { + const { statement, columns } = generator.toSelectStatement({ + _id: true, + createdAt: true, + updatedAt: true, + }); + expect(statement.query).toBe( + "{p0:Identifier}, {p1:Identifier}, {p2:Identifier}", + ); + expect(statement.query_params).toStrictEqual({ + p0: "_id", + p1: "createdAt", + p2: "updatedAt", + }); + expect(columns).toStrictEqual(["_id", "createdAt", "updatedAt"]); + }); + }); - afterEach(() => { - jest.restoreAllMocks(); - }); + describe("toColumnsCreateStatement", () => { + test("should return the columns of a CREATE TABLE statement", () => { + const statement: Statement = generator.toColumnsCreateStatement([ + new AnalyticsTableColumn({ + key: "column_1", + title: "<title>", + description: "<description>", + required: true, + type: TableColumnType.Text, + }), + new AnalyticsTableColumn({ + key: "column_2", + title: "<title>", + description: "<description>", + required: false, + type: TableColumnType.Number, + }), + ]); - test('should return CREATE TABLE statement', () => { - const statement: Statement = generator.toTableCreateStatement(); + expectStatement( + statement, + SQL`column_1 String, column_2 Nullable(Int32)`, + ); + }); - expect(generator.toColumnsCreateStatement).toBeCalledWith( - generator.model.tableColumns - ); + test("should support nested models", () => { + class TestNestedModel extends NestedModel { + public constructor() { + super({ + tableColumns: [ + new AnalyticsTableColumn({ + key: "nested_column_1", + title: "<title>", + description: "<description>", + required: true, + type: TableColumnType.Text, + }), + new AnalyticsTableColumn({ + key: "nested_column_2", + title: "<title>", + description: "<description>", + required: false, + type: TableColumnType.Number, + }), + ], + }); + } + } - expect(jest.mocked(logger.debug)).toHaveBeenCalledTimes(2); - expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith( - 1, - '<table-name> Table Create Statement' - ); - expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith( - 2, - statement - ); + const statement: Statement = generator.toColumnsCreateStatement([ + new AnalyticsTableColumn({ + key: "column_1", + title: "<title>", + description: "<description>", + required: true, + type: TableColumnType.Text, + }), + new AnalyticsTableColumn({ + key: "column_2", + title: "<title>", + description: "<description>", + required: false, + type: TableColumnType.NestedModel, + nestedModelType: TestNestedModel, + }), + ]); - /* eslint-disable prettier/prettier */ + expectStatement( + statement, + SQL`column_1 String, column_2 Nullable(Nested) (nested_column_1 String, nested_column_2 Nullable(Int32))`, + ); + }); + + test("should not add NULL|NOT NULL to Array types", () => { + const statement: Statement = generator.toColumnsCreateStatement([ + new AnalyticsTableColumn({ + key: "column_1", + title: "<title>", + description: "<description>", + required: true, + type: TableColumnType.ArrayText, + }), + new AnalyticsTableColumn({ + key: "column_2", + title: "<title>", + description: "<description>", + required: false, + type: TableColumnType.ArrayNumber, + }), + ]); + + expectStatement( + statement, + SQL`column_1 Array(String), column_2 Nullable(Array(Int32))`, + ); + }); + }); + + describe("toTableCreateStatement", () => { + beforeEach(() => { + generator.toColumnsCreateStatement = jest.fn(() => { + return SQL` <columns-create-statement>`; + }); + jest.spyOn(logger, "debug").mockImplementation(() => { + return undefined!; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test("should return CREATE TABLE statement", () => { + const statement: Statement = generator.toTableCreateStatement(); + + expect(generator.toColumnsCreateStatement).toBeCalledWith( + generator.model.tableColumns, + ); + + expect(jest.mocked(logger.debug)).toHaveBeenCalledTimes(2); + expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith( + 1, + "<table-name> Table Create Statement", + ); + expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith(2, statement); + + /* eslint-disable prettier/prettier */ const expectedStatement: Statement = SQL` CREATE TABLE IF NOT EXISTS ${'oneuptime'}.${'<table-name>'} ( @@ -351,10 +345,10 @@ describe('StatementGenerator', () => { `; /* eslint-enable prettier/prettier */ - expect(statement.query).toBe(expectedStatement.query); - expect(statement.query_params).toStrictEqual( - expectedStatement.query_params - ); - }); + expect(statement.query).toBe(expectedStatement.query); + expect(statement.query_params).toStrictEqual( + expectedStatement.query_params, + ); }); + }); }); diff --git a/CommonServer/Tests/Utils/Cookie.test.ts b/CommonServer/Tests/Utils/Cookie.test.ts index 0c9d3eade7..48cdfc0c68 100644 --- a/CommonServer/Tests/Utils/Cookie.test.ts +++ b/CommonServer/Tests/Utils/Cookie.test.ts @@ -1,123 +1,121 @@ -import CookieUtil from '../../Utils/Cookie'; -import { ExpressRequest, ExpressResponse } from '../../Utils/Express'; -import Dictionary from 'Common/Types/Dictionary'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; +import CookieUtil from "../../Utils/Cookie"; +import { ExpressRequest, ExpressResponse } from "../../Utils/Express"; +import Dictionary from "Common/Types/Dictionary"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; -describe('CookieUtils', () => { - let mockRequest: ExpressRequest; - let mockResponse: ExpressResponse; +describe("CookieUtils", () => { + let mockRequest: ExpressRequest; + let mockResponse: ExpressResponse; - beforeEach(() => { - mockRequest = { - cookies: {}, - } as ExpressRequest; + beforeEach(() => { + mockRequest = { + cookies: {}, + } as ExpressRequest; - mockResponse = {} as ExpressResponse; - }); + mockResponse = {} as ExpressResponse; + }); - afterEach(() => { - jest.clearAllMocks(); - }); + afterEach(() => { + jest.clearAllMocks(); + }); - test('Should set a cookie', () => { - const cookie: JSONObject = { - name: 'testName', - value: 'testValue', - options: {}, - }; + test("Should set a cookie", () => { + const cookie: JSONObject = { + name: "testName", + value: "testValue", + options: {}, + }; - mockResponse.cookie = jest.fn(); - CookieUtil.setCookie( - mockResponse, - cookie['name'] as string, - cookie['value'] as string, - cookie['options'] as JSONObject - ); + mockResponse.cookie = jest.fn(); + CookieUtil.setCookie( + mockResponse, + cookie["name"] as string, + cookie["value"] as string, + cookie["options"] as JSONObject, + ); - expect(mockResponse.cookie).toHaveBeenCalledWith( - cookie['name'] as string, - cookie['value'] as string, - cookie['options'] as JSONObject - ); - }); + expect(mockResponse.cookie).toHaveBeenCalledWith( + cookie["name"] as string, + cookie["value"] as string, + cookie["options"] as JSONObject, + ); + }); - test('Should return a cookie', () => { - const cookieName: string = 'testName'; - const cookieValue: string = 'testValue'; + test("Should return a cookie", () => { + const cookieName: string = "testName"; + const cookieValue: string = "testValue"; - mockRequest.cookies[cookieName] = cookieValue; - const value: string | undefined = CookieUtil.getCookie( - mockRequest, - cookieName - ); + mockRequest.cookies[cookieName] = cookieValue; + const value: string | undefined = CookieUtil.getCookie( + mockRequest, + cookieName, + ); - expect(value).toBe(value); - }); + expect(value).toBe(value); + }); - test('Should remove a cookie', () => { - const cookieName: string = 'testName'; + test("Should remove a cookie", () => { + const cookieName: string = "testName"; - mockResponse.clearCookie = jest.fn(); - CookieUtil.removeCookie(mockResponse, cookieName); + mockResponse.clearCookie = jest.fn(); + CookieUtil.removeCookie(mockResponse, cookieName); - expect(mockResponse.clearCookie).toHaveBeenCalledWith(cookieName); - }); + expect(mockResponse.clearCookie).toHaveBeenCalledWith(cookieName); + }); - test('Should return all cookies', () => { - const value: string = 'testValue'; - mockRequest.cookies = { testName: value }; - const cookies: Dictionary<string> = - CookieUtil.getAllCookies(mockRequest); + test("Should return all cookies", () => { + const value: string = "testValue"; + mockRequest.cookies = { testName: value }; + const cookies: Dictionary<string> = CookieUtil.getAllCookies(mockRequest); - expect(cookies).toEqual({ testName: value }); - }); + expect(cookies).toEqual({ testName: value }); + }); - test('Should return empty object if there are no cookies', () => { - mockRequest.cookies = undefined; - const cookies: Dictionary<string> = - CookieUtil.getAllCookies(mockRequest); + test("Should return empty object if there are no cookies", () => { + mockRequest.cookies = undefined; + const cookies: Dictionary<string> = CookieUtil.getAllCookies(mockRequest); - expect(cookies).toEqual({}); - }); + expect(cookies).toEqual({}); + }); - test('Should return user token key', () => { - const id: string = '123456789'; - const keyWithId: string = CookieUtil.getUserTokenKey(new ObjectID(id)); - const keyWithoutId: string = CookieUtil.getUserTokenKey(); + test("Should return user token key", () => { + const id: string = "123456789"; + const keyWithId: string = CookieUtil.getUserTokenKey(new ObjectID(id)); + const keyWithoutId: string = CookieUtil.getUserTokenKey(); - expect(keyWithId).toBe(`user-token-${id}`); - expect(keyWithoutId).toBe('user-token'); - }); + expect(keyWithId).toBe(`user-token-${id}`); + expect(keyWithoutId).toBe("user-token"); + }); - test('Should return SSO key', () => { - const ssoKey: string = CookieUtil.getSSOKey(); + test("Should return SSO key", () => { + const ssoKey: string = CookieUtil.getSSOKey(); - expect(ssoKey).toBe('sso-'); - }); + expect(ssoKey).toBe("sso-"); + }); - test('Should return user SSO key', () => { - const id: string = '123456789'; - const userSsoKey: string = CookieUtil.getUserSSOKey(new ObjectID(id)); + test("Should return user SSO key", () => { + const id: string = "123456789"; + const userSsoKey: string = CookieUtil.getUserSSOKey(new ObjectID(id)); - expect(userSsoKey).toBe(`sso-${id}`); - }); + expect(userSsoKey).toBe(`sso-${id}`); + }); - test('Should remove all cookies', () => { - const cookies: Dictionary<string> = { - testName1: 'testValue1', - testName2: 'testValue2', - }; + test("Should remove all cookies", () => { + const cookies: Dictionary<string> = { + testName1: "testValue1", + testName2: "testValue2", + }; - mockRequest.cookies = cookies; - mockResponse.clearCookie = jest.fn(); - CookieUtil.removeAllCookies(mockRequest, mockResponse); + mockRequest.cookies = cookies; + mockResponse.clearCookie = jest.fn(); + CookieUtil.removeAllCookies(mockRequest, mockResponse); - expect(mockResponse.clearCookie).toHaveBeenCalledWith( - Object.keys(cookies)[0] - ); - expect(mockResponse.clearCookie).toHaveBeenCalledWith( - Object.keys(cookies)[1] - ); - }); + expect(mockResponse.clearCookie).toHaveBeenCalledWith( + Object.keys(cookies)[0], + ); + expect(mockResponse.clearCookie).toHaveBeenCalledWith( + Object.keys(cookies)[1], + ); + }); }); diff --git a/CommonServer/Tests/Utils/CronTab.test.ts b/CommonServer/Tests/Utils/CronTab.test.ts index 83684aa03d..856a76d4f1 100644 --- a/CommonServer/Tests/Utils/CronTab.test.ts +++ b/CommonServer/Tests/Utils/CronTab.test.ts @@ -1,44 +1,44 @@ -import CronTab from '../../Utils/CronTab'; -import { describe, expect, it } from '@jest/globals'; +import CronTab from "../../Utils/CronTab"; +import { describe, expect, it } from "@jest/globals"; -describe('CronTab', () => { - it('should return the next execution time for a given cron expression', () => { - const crontab: string = '*/5 * * * *'; +describe("CronTab", () => { + it("should return the next execution time for a given cron expression", () => { + const crontab: string = "*/5 * * * *"; - const nextExecutionTime: Date = CronTab.getNextExecutionTime(crontab); + const nextExecutionTime: Date = CronTab.getNextExecutionTime(crontab); - const now: Date = new Date(); - const expectedNextExecutionTime: Date = new Date( - now.getTime() + 5 * 60 * 1000 - ); + const now: Date = new Date(); + const expectedNextExecutionTime: Date = new Date( + now.getTime() + 5 * 60 * 1000, + ); - const toleranceInMilliseconds: number = 5000; - const differenceInMilliseconds: number = - nextExecutionTime.getTime() - expectedNextExecutionTime.getTime(); - expect(differenceInMilliseconds).toBeLessThan(toleranceInMilliseconds); - }); + const toleranceInMilliseconds: number = 5000; + const differenceInMilliseconds: number = + nextExecutionTime.getTime() - expectedNextExecutionTime.getTime(); + expect(differenceInMilliseconds).toBeLessThan(toleranceInMilliseconds); + }); - it('should return the next execution time for a daily cron expression', () => { - const crontab: string = '0 0 * * *'; + it("should return the next execution time for a daily cron expression", () => { + const crontab: string = "0 0 * * *"; - const nextExecutionTime: Date = CronTab.getNextExecutionTime(crontab); + const nextExecutionTime: Date = CronTab.getNextExecutionTime(crontab); - const now: Date = new Date(); - const expectedNextExecutionTime: Date = new Date( - now.getTime() + 24 * 60 * 60 * 1000 - ); + const now: Date = new Date(); + const expectedNextExecutionTime: Date = new Date( + now.getTime() + 24 * 60 * 60 * 1000, + ); - const toleranceInMilliseconds: number = 5000; - const differenceInMilliseconds: number = - nextExecutionTime.getTime() - expectedNextExecutionTime.getTime(); - expect(differenceInMilliseconds).toBeLessThan(toleranceInMilliseconds); - }); + const toleranceInMilliseconds: number = 5000; + const differenceInMilliseconds: number = + nextExecutionTime.getTime() - expectedNextExecutionTime.getTime(); + expect(differenceInMilliseconds).toBeLessThan(toleranceInMilliseconds); + }); - it('should throw an error when the cron expression is invalid', () => { - const crontab: string = 'invalid'; + it("should throw an error when the cron expression is invalid", () => { + const crontab: string = "invalid"; - expect(() => { - CronTab.getNextExecutionTime(crontab); - }).toThrowError(`Invalid cron expression: ${crontab}`); - }); + expect(() => { + CronTab.getNextExecutionTime(crontab); + }).toThrowError(`Invalid cron expression: ${crontab}`); + }); }); diff --git a/CommonServer/Tests/Utils/JsonToCsv.test.ts b/CommonServer/Tests/Utils/JsonToCsv.test.ts index 269580b6e8..326de98081 100644 --- a/CommonServer/Tests/Utils/JsonToCsv.test.ts +++ b/CommonServer/Tests/Utils/JsonToCsv.test.ts @@ -1,147 +1,147 @@ -import csvConverter from '../../Utils/JsonToCsv'; -import { describe, expect, it } from '@jest/globals'; -import { JSONArray } from 'Common/Types/JSON'; +import csvConverter from "../../Utils/JsonToCsv"; +import { describe, expect, it } from "@jest/globals"; +import { JSONArray } from "Common/Types/JSON"; -describe('CSV Converter', () => { - it('throws an error when the input JSON array is empty', () => { - const emptyJson: JSONArray = []; - expect(() => { - return csvConverter.ToCsv(emptyJson); - }).toThrowError('Cannot convert to CSV when the object length is 0'); - }); +describe("CSV Converter", () => { + it("throws an error when the input JSON array is empty", () => { + const emptyJson: JSONArray = []; + expect(() => { + return csvConverter.ToCsv(emptyJson); + }).toThrowError("Cannot convert to CSV when the object length is 0"); + }); - it('converts a JSON array to CSV', () => { - const json: JSONArray = [ - { - id: '1', - name: 'test1', - }, - { - id: '2', - name: 'test2', - }, - ]; + it("converts a JSON array to CSV", () => { + const json: JSONArray = [ + { + id: "1", + name: "test1", + }, + { + id: "2", + name: "test2", + }, + ]; - const csv: string = csvConverter.ToCsv(json); + const csv: string = csvConverter.ToCsv(json); - const headerRow: string = csv.split('\n')[0]!; - expect(headerRow.trim()).toBe(`"id","name"`); + const headerRow: string = csv.split("\n")[0]!; + expect(headerRow.trim()).toBe(`"id","name"`); - const dataRows: string[] = csv.split('\n').slice(1); - expect(dataRows.length).toBe(2); + const dataRows: string[] = csv.split("\n").slice(1); + expect(dataRows.length).toBe(2); - expect(dataRows[0]?.trim()).toBe(`"1","test1"`); - expect(dataRows[1]?.trim()).toBe(`"2","test2"`); - }); + expect(dataRows[0]?.trim()).toBe(`"1","test1"`); + expect(dataRows[1]?.trim()).toBe(`"2","test2"`); + }); - it('handles an empty JSON object', () => { - const json: JSONArray = [{}]; + it("handles an empty JSON object", () => { + const json: JSONArray = [{}]; - const csv: string = csvConverter.ToCsv(json); + const csv: string = csvConverter.ToCsv(json); - expect(csv).toBe(''); - }); + expect(csv).toBe(""); + }); - it('handles large JSON arrays', () => { - const json: JSONArray = []; + it("handles large JSON arrays", () => { + const json: JSONArray = []; - for (let i: number = 0; i < 100; i++) { - json.push({ - id: i.toString(), - name: `test${i}`, - }); - } + for (let i: number = 0; i < 100; i++) { + json.push({ + id: i.toString(), + name: `test${i}`, + }); + } - const csv: string = csvConverter.ToCsv(json); + const csv: string = csvConverter.ToCsv(json); - const headerRow: string = csv.split('\n')[0]!; - expect(headerRow.trim()).toBe(`"id","name"`); + const headerRow: string = csv.split("\n")[0]!; + expect(headerRow.trim()).toBe(`"id","name"`); - const dataRows: string[] = csv.split('\n').slice(1); - expect(dataRows.length).toBe(100); + const dataRows: string[] = csv.split("\n").slice(1); + expect(dataRows.length).toBe(100); - for (let i: number = 0; i < 100; i++) { - expect(dataRows[i]?.trim()).toBe(`"${i}","test${i}"`); - } - }); + for (let i: number = 0; i < 100; i++) { + expect(dataRows[i]?.trim()).toBe(`"${i}","test${i}"`); + } + }); - it('handles a JSON object with an array', () => { - const json: JSONArray = [ - { - id: '1', - name: 'test1', - array: [1, 2, 3], - }, - ]; + it("handles a JSON object with an array", () => { + const json: JSONArray = [ + { + id: "1", + name: "test1", + array: [1, 2, 3], + }, + ]; - const csv: string = csvConverter.ToCsv(json); + const csv: string = csvConverter.ToCsv(json); - const headerRow: string = csv.split('\n')[0]!; - expect(headerRow.trim()).toBe(`"id","name","array"`); + const headerRow: string = csv.split("\n")[0]!; + expect(headerRow.trim()).toBe(`"id","name","array"`); - const dataRows: string[] = csv.split('\n').slice(1); - expect(dataRows.length).toBe(1); + const dataRows: string[] = csv.split("\n").slice(1); + expect(dataRows.length).toBe(1); - expect(dataRows[0]).toBe(`"1","test1","[1,2,3]"`); - }); + expect(dataRows[0]).toBe(`"1","test1","[1,2,3]"`); + }); - it('handles a JSON object with an object', () => { - const json: JSONArray = [ - { - id: '1', - name: 'test1', - object: { test: 'test' }, - }, - ]; + it("handles a JSON object with an object", () => { + const json: JSONArray = [ + { + id: "1", + name: "test1", + object: { test: "test" }, + }, + ]; - const csv: string = csvConverter.ToCsv(json); + const csv: string = csvConverter.ToCsv(json); - const headerRow: string = csv.split('\n')[0]!; - expect(headerRow.trim()).toBe(`"id","name","object"`); + const headerRow: string = csv.split("\n")[0]!; + expect(headerRow.trim()).toBe(`"id","name","object"`); - const dataRows: string[] = csv.split('\n').slice(1); - expect(dataRows.length).toBe(1); + const dataRows: string[] = csv.split("\n").slice(1); + expect(dataRows.length).toBe(1); - expect(dataRows[0]).toBe(`"1","test1","{""test"":""test""}"`); - }); + expect(dataRows[0]).toBe(`"1","test1","{""test"":""test""}"`); + }); - it('handles a JSON object with an empty array', () => { - const json: JSONArray = [ - { - id: '1', - name: 'test1', - array: [], - }, - ]; + it("handles a JSON object with an empty array", () => { + const json: JSONArray = [ + { + id: "1", + name: "test1", + array: [], + }, + ]; - const csv: string = csvConverter.ToCsv(json); + const csv: string = csvConverter.ToCsv(json); - const headerRow: string = csv.split('\n')[0]!; - expect(headerRow.trim()).toBe(`"id","name","array"`); + const headerRow: string = csv.split("\n")[0]!; + expect(headerRow.trim()).toBe(`"id","name","array"`); - const dataRows: string[] = csv.split('\n').slice(1); - expect(dataRows.length).toBe(1); + const dataRows: string[] = csv.split("\n").slice(1); + expect(dataRows.length).toBe(1); - expect(dataRows[0]).toBe(`"1","test1","[]"`); - }); + expect(dataRows[0]).toBe(`"1","test1","[]"`); + }); - it('handles a JSON object with an empty object', () => { - const json: JSONArray = [ - { - id: '1', - name: 'test1', - object: {}, - }, - ]; + it("handles a JSON object with an empty object", () => { + const json: JSONArray = [ + { + id: "1", + name: "test1", + object: {}, + }, + ]; - const csv: string = csvConverter.ToCsv(json); + const csv: string = csvConverter.ToCsv(json); - const headerRow: string = csv.split('\n')[0]!; - expect(headerRow.trim()).toBe(`"id","name","object"`); + const headerRow: string = csv.split("\n")[0]!; + expect(headerRow.trim()).toBe(`"id","name","object"`); - const dataRows: string[] = csv.split('\n').slice(1); - expect(dataRows.length).toBe(1); + const dataRows: string[] = csv.split("\n").slice(1); + expect(dataRows.length).toBe(1); - expect(dataRows[0]).toBe(`"1","test1","{}"`); - }); + expect(dataRows[0]).toBe(`"1","test1","{}"`); + }); }); diff --git a/CommonServer/Types/AnalyticsDatabase/CountBy.ts b/CommonServer/Types/AnalyticsDatabase/CountBy.ts index 2390841c68..c39c338dba 100644 --- a/CommonServer/Types/AnalyticsDatabase/CountBy.ts +++ b/CommonServer/Types/AnalyticsDatabase/CountBy.ts @@ -1,13 +1,13 @@ -import GroupBy from './GroupBy'; -import Query from './Query'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import PositiveNumber from 'Common/Types/PositiveNumber'; +import GroupBy from "./GroupBy"; +import Query from "./Query"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import PositiveNumber from "Common/Types/PositiveNumber"; export default interface CountBy<TBaseModel extends AnalyticsBaseModel> { - query: Query<TBaseModel>; - skip?: PositiveNumber | number; - limit?: PositiveNumber | number; - groupBy?: GroupBy<TBaseModel> | undefined; - props: DatabaseCommonInteractionProps; + query: Query<TBaseModel>; + skip?: PositiveNumber | number; + limit?: PositiveNumber | number; + groupBy?: GroupBy<TBaseModel> | undefined; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/AnalyticsDatabase/CreateBy.ts b/CommonServer/Types/AnalyticsDatabase/CreateBy.ts index b8749d53bb..4477eec98d 100644 --- a/CommonServer/Types/AnalyticsDatabase/CreateBy.ts +++ b/CommonServer/Types/AnalyticsDatabase/CreateBy.ts @@ -1,7 +1,7 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default interface CreateBy<TBaseModel extends AnalyticsBaseModel> { - data: TBaseModel; - props: DatabaseCommonInteractionProps; + data: TBaseModel; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/AnalyticsDatabase/CreateManyBy.ts b/CommonServer/Types/AnalyticsDatabase/CreateManyBy.ts index ad1df66648..e021791ec4 100644 --- a/CommonServer/Types/AnalyticsDatabase/CreateManyBy.ts +++ b/CommonServer/Types/AnalyticsDatabase/CreateManyBy.ts @@ -1,7 +1,7 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default interface CreateBy<TBaseModel extends AnalyticsBaseModel> { - items: Array<TBaseModel>; - props: DatabaseCommonInteractionProps; + items: Array<TBaseModel>; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/AnalyticsDatabase/DeleteBy.ts b/CommonServer/Types/AnalyticsDatabase/DeleteBy.ts index c47eab3038..45104df6c4 100644 --- a/CommonServer/Types/AnalyticsDatabase/DeleteBy.ts +++ b/CommonServer/Types/AnalyticsDatabase/DeleteBy.ts @@ -1,8 +1,8 @@ -import Query from './Query'; -import BaseModel from 'Common/AnalyticsModels/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import Query from "./Query"; +import BaseModel from "Common/AnalyticsModels/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default interface DeleteBy<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; - props: DatabaseCommonInteractionProps; + query: Query<TBaseModel>; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/AnalyticsDatabase/FindBy.ts b/CommonServer/Types/AnalyticsDatabase/FindBy.ts index 0214f5aebe..b3c2f1f397 100644 --- a/CommonServer/Types/AnalyticsDatabase/FindBy.ts +++ b/CommonServer/Types/AnalyticsDatabase/FindBy.ts @@ -1,9 +1,9 @@ -import FindOneBy from './FindOneBy'; -import BaseModel from 'Common/AnalyticsModels/BaseModel'; -import PositiveNumber from 'Common/Types/PositiveNumber'; +import FindOneBy from "./FindOneBy"; +import BaseModel from "Common/AnalyticsModels/BaseModel"; +import PositiveNumber from "Common/Types/PositiveNumber"; export default interface FindBy<TBaseModel extends BaseModel> - extends FindOneBy<TBaseModel> { - limit: PositiveNumber | number; - skip: PositiveNumber | number; + extends FindOneBy<TBaseModel> { + limit: PositiveNumber | number; + skip: PositiveNumber | number; } diff --git a/CommonServer/Types/AnalyticsDatabase/FindOneBy.ts b/CommonServer/Types/AnalyticsDatabase/FindOneBy.ts index 3eb52d20e0..982736e08e 100644 --- a/CommonServer/Types/AnalyticsDatabase/FindOneBy.ts +++ b/CommonServer/Types/AnalyticsDatabase/FindOneBy.ts @@ -1,14 +1,14 @@ -import GroupBy from './GroupBy'; -import Query from './Query'; -import Select from './Select'; -import Sort from './Sort'; -import BaseModel from 'Common/AnalyticsModels/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import GroupBy from "./GroupBy"; +import Query from "./Query"; +import Select from "./Select"; +import Sort from "./Sort"; +import BaseModel from "Common/AnalyticsModels/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default interface FindOneBy<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; - select?: Select<TBaseModel> | undefined; - sort?: Sort<TBaseModel> | undefined; - groupBy?: GroupBy<TBaseModel> | undefined; - props: DatabaseCommonInteractionProps; + query: Query<TBaseModel>; + select?: Select<TBaseModel> | undefined; + sort?: Sort<TBaseModel> | undefined; + groupBy?: GroupBy<TBaseModel> | undefined; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/AnalyticsDatabase/FindOneByID.ts b/CommonServer/Types/AnalyticsDatabase/FindOneByID.ts index d0f9767396..1e862336c4 100644 --- a/CommonServer/Types/AnalyticsDatabase/FindOneByID.ts +++ b/CommonServer/Types/AnalyticsDatabase/FindOneByID.ts @@ -1,10 +1,10 @@ -import Select from './Select'; -import BaseModel from 'Common/AnalyticsModels/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import ObjectID from 'Common/Types/ObjectID'; +import Select from "./Select"; +import BaseModel from "Common/AnalyticsModels/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import ObjectID from "Common/Types/ObjectID"; export default interface FindOneByID<TBaseModel extends BaseModel> { - id: ObjectID; - select?: Select<TBaseModel> | undefined; - props: DatabaseCommonInteractionProps; + id: ObjectID; + select?: Select<TBaseModel> | undefined; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/AnalyticsDatabase/GroupBy.ts b/CommonServer/Types/AnalyticsDatabase/GroupBy.ts index 8a0d397c6c..e460a5a85b 100644 --- a/CommonServer/Types/AnalyticsDatabase/GroupBy.ts +++ b/CommonServer/Types/AnalyticsDatabase/GroupBy.ts @@ -2,7 +2,7 @@ * GroupBy find options. */ declare type GroupBy<Entity> = { - [P in keyof Entity]?: true; + [P in keyof Entity]?: true; }; export default GroupBy; diff --git a/CommonServer/Types/AnalyticsDatabase/Hooks.ts b/CommonServer/Types/AnalyticsDatabase/Hooks.ts index 779532eed8..768e335033 100644 --- a/CommonServer/Types/AnalyticsDatabase/Hooks.ts +++ b/CommonServer/Types/AnalyticsDatabase/Hooks.ts @@ -1,27 +1,27 @@ -import CreateBy from './CreateBy'; -import DeleteBy from './DeleteBy'; -import FindBy from './FindBy'; -import UpdateBy from './UpdateBy'; -import BaseModel from 'Common/AnalyticsModels/BaseModel'; +import CreateBy from "./CreateBy"; +import DeleteBy from "./DeleteBy"; +import FindBy from "./FindBy"; +import UpdateBy from "./UpdateBy"; +import BaseModel from "Common/AnalyticsModels/BaseModel"; -export type DatabaseTriggerType = 'on-create' | 'on-update' | 'on-delete'; +export type DatabaseTriggerType = "on-create" | "on-update" | "on-delete"; export interface OnCreate<TBaseModel extends BaseModel> { - createBy: CreateBy<TBaseModel>; - carryForward: any; + createBy: CreateBy<TBaseModel>; + carryForward: any; } export interface OnFind<TBaseModel extends BaseModel> { - findBy: FindBy<TBaseModel>; - carryForward: any; + findBy: FindBy<TBaseModel>; + carryForward: any; } export interface OnDelete<TBaseModel extends BaseModel> { - deleteBy: DeleteBy<TBaseModel>; - carryForward: any; + deleteBy: DeleteBy<TBaseModel>; + carryForward: any; } export interface OnUpdate<TBaseModel extends BaseModel> { - updateBy: UpdateBy<TBaseModel>; - carryForward: any; + updateBy: UpdateBy<TBaseModel>; + carryForward: any; } diff --git a/CommonServer/Types/AnalyticsDatabase/ModelPermission.ts b/CommonServer/Types/AnalyticsDatabase/ModelPermission.ts index 585dcbf81d..6bef16a980 100644 --- a/CommonServer/Types/AnalyticsDatabase/ModelPermission.ts +++ b/CommonServer/Types/AnalyticsDatabase/ModelPermission.ts @@ -1,763 +1,729 @@ -import { IsBillingEnabled, getAllEnvVars } from '../../EnvironmentConfig'; -import DatabaseRequestType from '../BaseDatabase/DatabaseRequestType'; -import Query from './Query'; -import Select from './Select'; +import { IsBillingEnabled, getAllEnvVars } from "../../EnvironmentConfig"; +import DatabaseRequestType from "../BaseDatabase/DatabaseRequestType"; +import Query from "./Query"; +import Select from "./Select"; import BaseModel, { - AnalyticsBaseModelType, -} from 'Common/AnalyticsModels/BaseModel'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import ColumnBillingAccessControl from 'Common/Types/BaseDatabase/ColumnBillingAccessControl'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; + AnalyticsBaseModelType, +} from "Common/AnalyticsModels/BaseModel"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import ColumnBillingAccessControl from "Common/Types/BaseDatabase/ColumnBillingAccessControl"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; import DatabaseCommonInteractionPropsUtil, { - PermissionType, -} from 'Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import Columns from 'Common/Types/Database/Columns'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; -import PaymentRequiredException from 'Common/Types/Exception/PaymentRequiredException'; -import ObjectID from 'Common/Types/ObjectID'; + PermissionType, +} from "Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import Columns from "Common/Types/Database/Columns"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; +import PaymentRequiredException from "Common/Types/Exception/PaymentRequiredException"; +import ObjectID from "Common/Types/ObjectID"; import Permission, { - PermissionHelper, - UserPermission, -} from 'Common/Types/Permission'; -import UserType from 'Common/Types/UserType'; + PermissionHelper, + UserPermission, +} from "Common/Types/Permission"; +import UserType from "Common/Types/UserType"; export interface CheckReadPermissionType<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; - select: Select<TBaseModel> | null; + query: Query<TBaseModel>; + select: Select<TBaseModel> | null; } export default class ModelPermission { - public static async checkDeletePermission<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - if (props.isRoot || props.isMasterAdmin) { - query = await this.addTenantScopeToQueryAsRoot( - modelType, - query, - props - ); - } - - if (!props.isRoot && !props.isMasterAdmin) { - this.checkModelLevelPermissions( - modelType, - props, - DatabaseRequestType.Delete - ); - query = await this.addTenantScopeToQuery( - modelType, - query, - null, - props - ); - } - - return query; + public static async checkDeletePermission<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + if (props.isRoot || props.isMasterAdmin) { + query = await this.addTenantScopeToQueryAsRoot(modelType, query, props); } - public static async checkUpdatePermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - data: TBaseModel, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - if (props.isRoot || props.isMasterAdmin) { - return query; - } - - this.checkModelLevelPermissions( - modelType, - props, - DatabaseRequestType.Update - ); - - const checkReadPermissionType: CheckReadPermissionType<TBaseModel> = - await this.checkReadPermission(modelType, query, null, props); - - query = checkReadPermissionType.query; - - this.checkDataColumnPermissions( - modelType, - data as any, - props, - DatabaseRequestType.Update - ); - - return query; + if (!props.isRoot && !props.isMasterAdmin) { + this.checkModelLevelPermissions( + modelType, + props, + DatabaseRequestType.Delete, + ); + query = await this.addTenantScopeToQuery(modelType, query, null, props); } - public static checkCreatePermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - data: TBaseModel, - props: DatabaseCommonInteractionProps - ): void { - // If system is making this query then let the query run! - if (props.isRoot || props.isMasterAdmin) { - return; - } + return query; + } - this.checkModelLevelPermissions( - modelType, - props, - DatabaseRequestType.Create - ); - - this.checkDataColumnPermissions( - modelType, - data, - props, - DatabaseRequestType.Create - ); + public static async checkUpdatePermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + data: TBaseModel, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + if (props.isRoot || props.isMasterAdmin) { + return query; } - private static checkDataColumnPermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - data: TBaseModel, - props: DatabaseCommonInteractionProps, - requestType: DatabaseRequestType - ): void { - const model: BaseModel = new modelType(); - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow - ); + this.checkModelLevelPermissions( + modelType, + props, + DatabaseRequestType.Update, + ); - const permissionColumns: Columns = this.getModelColumnsByPermissions( - modelType, - userPermissions, - requestType - ); + const checkReadPermissionType: CheckReadPermissionType<TBaseModel> = + await this.checkReadPermission(modelType, query, null, props); - const excludedColumnNames: Array<string> = - ModelPermission.getExcludedColumnNames(); + query = checkReadPermissionType.query; - const tableColumns: Array<AnalyticsTableColumn> = - model.getTableColumns(); + this.checkDataColumnPermissions( + modelType, + data as any, + props, + DatabaseRequestType.Update, + ); - for (const column of tableColumns) { - const key: string = column.key; - if ((data as any)[key] === undefined) { - continue; - } + return query; + } - if (excludedColumnNames.includes(key)) { - continue; - } - - if (!permissionColumns.columns.includes(key)) { - if ( - requestType === DatabaseRequestType.Create && - column.forceGetDefaultValueOnCreate - ) { - continue; // this is a special case where we want to force the default value on create. - } - - throw new BadDataException( - `User is not allowed to ${requestType} on ${key} column of ${model.singularName}` - ); - } - - const billingAccessControl: ColumnBillingAccessControl | null = - model.getColumnBillingAccessControl(key); - - if (IsBillingEnabled && props.currentPlan && billingAccessControl) { - if ( - requestType === DatabaseRequestType.Create && - billingAccessControl.create - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - billingAccessControl.create, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - billingAccessControl.create + - ' to access this feature' - ); - } - } - - if ( - requestType === DatabaseRequestType.Read && - billingAccessControl.read - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - billingAccessControl.read, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - billingAccessControl.read + - ' to access this feature' - ); - } - } - - if ( - requestType === DatabaseRequestType.Update && - billingAccessControl.update - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - billingAccessControl.update, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - billingAccessControl.update + - ' to access this feature' - ); - } - } - } - } + public static checkCreatePermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + data: TBaseModel, + props: DatabaseCommonInteractionProps, + ): void { + // If system is making this query then let the query run! + if (props.isRoot || props.isMasterAdmin) { + return; } - public static async checkReadPermission<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - select: Select<TBaseModel> | null, - props: DatabaseCommonInteractionProps - ): Promise<CheckReadPermissionType<TBaseModel>> { - if (props.isRoot || props.isMasterAdmin) { - query = await this.addTenantScopeToQueryAsRoot( - modelType, - query, - props - ); - } + this.checkModelLevelPermissions( + modelType, + props, + DatabaseRequestType.Create, + ); - if (!props.isRoot && !props.isMasterAdmin) { - //check if the user is logged in. - this.checkIfUserIsLoggedIn( - modelType, - props, - DatabaseRequestType.Read - ); + this.checkDataColumnPermissions( + modelType, + data, + props, + DatabaseRequestType.Create, + ); + } - // add tenant scope. - query = await this.addTenantScopeToQuery( - modelType, - query, - select, - props - ); + private static checkDataColumnPermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + data: TBaseModel, + props: DatabaseCommonInteractionProps, + requestType: DatabaseRequestType, + ): void { + const model: BaseModel = new modelType(); + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ); - if (!props.isMultiTenantRequest) { - // We will check for this permission in recursive function. + const permissionColumns: Columns = this.getModelColumnsByPermissions( + modelType, + userPermissions, + requestType, + ); - // check model level permissions. - this.checkModelLevelPermissions( - modelType, - props, - DatabaseRequestType.Read - ); + const excludedColumnNames: Array<string> = + ModelPermission.getExcludedColumnNames(); - // We will check for this permission in recursive function. - // check query permissions. - this.checkQueryPermission(modelType, query, props); + const tableColumns: Array<AnalyticsTableColumn> = model.getTableColumns(); - if (select) { - // check query permission. - this.checkSelectPermission(modelType, select, props); - } - } - } + for (const column of tableColumns) { + const key: string = column.key; + if ((data as any)[key] === undefined) { + continue; + } - query = this.serializeQuery(query); + if (excludedColumnNames.includes(key)) { + continue; + } - if (select) { - const result: { - select: Select<TBaseModel>; - } = this.sanitizeSelect(select); - select = result.select; - } - - return { query, select }; - } - - private static serializeQuery<TBaseModel extends BaseModel>( - query: Query<TBaseModel> - ): Query<TBaseModel> { - query = query as Query<TBaseModel>; - - return query; - } - - private static sanitizeSelect<TBaseModel extends BaseModel>( - select: Select<TBaseModel> - ): { - select: Select<TBaseModel>; - } { - return { select }; - } - - private static getExcludedColumnNames(): string[] { - const returnArr: Array<string> = [ - '_id', - 'createdAt', - 'deletedAt', - 'updatedAt', - 'version', - ]; - - return returnArr; - } - - private static checkQueryPermission<TBaseModel extends BaseModel>( - modelType: AnalyticsBaseModelType, - query: Query<TBaseModel>, - props: DatabaseCommonInteractionProps - ): void { - const model: BaseModel = new modelType(); - - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow - ); - - const canReadOnTheseColumns: Columns = - this.getModelColumnsByPermissions( - modelType, - userPermissions || [], - DatabaseRequestType.Read - ); - - const tableColumns: Array<AnalyticsTableColumn> = - model.getTableColumns(); - - const excludedColumnNames: Array<string> = - ModelPermission.getExcludedColumnNames(); - - // Now we need to check all columns. - - for (const key in query) { - if (excludedColumnNames.includes(key)) { - continue; - } - - if (!canReadOnTheseColumns.columns.includes(key)) { - const column: AnalyticsTableColumn | undefined = - tableColumns.find((item: AnalyticsTableColumn) => { - return item.key === key; - }); - - if (!column) { - throw new BadDataException( - `Invalid column on ${model.singularName} - ${key}. Column does not exist.` - ); - } - - throw new NotAuthorizedException( - `You do not have permissions to query on - ${key}. You need any one of these permissions: ${PermissionHelper.getPermissionTitles( - column.accessControl?.read || [] - ).join(', ')}` - ); - } - } - } - - private static async addTenantScopeToQueryAsRoot< - TBaseModel extends BaseModel - >( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - const model: BaseModel = new modelType(); - - const tenantColumn: string | null = - model.getTenantColumn()?.key || null; - - // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query. - if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) { - (query as any)[tenantColumn] = props.tenantId; - } - - return query; - } - - private static async addTenantScopeToQuery<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - select: Select<TBaseModel> | null, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - const model: BaseModel = new modelType(); - - const tenantColumn: string | null = - model.getTenantColumn()?.key || null; - - // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query. - if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) { - (query as any)[tenantColumn] = props.tenantId; - } else if ( - tenantColumn && - !props.tenantId && - props.userGlobalAccessPermission + if (!permissionColumns.columns.includes(key)) { + if ( + requestType === DatabaseRequestType.Create && + column.forceGetDefaultValueOnCreate ) { - // for each of these projectIds, - // check if they have valid permissions for these projects - // and if they do, include them in the query. - - const queries: Array<Query<TBaseModel>> = []; - - let projectIDs: Array<ObjectID> = []; - - if ( - props.userGlobalAccessPermission && - props.userGlobalAccessPermission.projectIds - ) { - projectIDs = props.userGlobalAccessPermission?.projectIds; - } - - let lastException: Error | null = null; - - for (const projectId of projectIDs) { - if (!props.userId) { - continue; - } - - try { - const checkReadPermissionType: CheckReadPermissionType<TBaseModel> = - await this.checkReadPermission( - modelType, - query, - select, - { - ...props, - isMultiTenantRequest: false, - tenantId: projectId, - userTenantAccessPermission: - props.userTenantAccessPermission, - } - ); - queries.push({ - ...(checkReadPermissionType.query as Query<TBaseModel>), - }); - } catch (e) { - // do nothing here. Ignore. - lastException = e as Error; - } - } - - if (queries.length === 0) { - throw new NotAuthorizedException( - lastException?.message || - 'Does not have permission to read ' + model.singularName - ); - } - - return queries as any; + continue; // this is a special case where we want to force the default value on create. } - return query; - } - - private static getModelColumnsByPermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - userPermissions: Array<UserPermission>, - requestType: DatabaseRequestType - ): Columns { - const model: BaseModel = new modelType(); - const tableColumns: Array<AnalyticsTableColumn> = - model.getTableColumns(); - - const columns: Array<string> = []; - - const permissions: Array<Permission> = userPermissions.map( - (item: UserPermission) => { - return item.permission; - } + throw new BadDataException( + `User is not allowed to ${requestType} on ${key} column of ${model.singularName}`, ); + } - for (const column of tableColumns) { - let columnPermissions: Array<Permission> = []; + const billingAccessControl: ColumnBillingAccessControl | null = + model.getColumnBillingAccessControl(key); - if (requestType === DatabaseRequestType.Read) { - columnPermissions = column.accessControl?.read || []; - } - - if (requestType === DatabaseRequestType.Create) { - columnPermissions = column.accessControl?.create || []; - } - - if (requestType === DatabaseRequestType.Update) { - columnPermissions = column.accessControl?.update || []; - } - - if (requestType === DatabaseRequestType.Delete) { - throw new BadDataException('Invalid request type delete'); - } - - if ( - columnPermissions && - PermissionHelper.doesPermissionsIntersect( - permissions, - columnPermissions - ) - ) { - columns.push(column.key); - } - } - - return new Columns(columns); - } - - private static checkSelectPermission<TBaseModel extends BaseModel>( - modelType: AnalyticsBaseModelType, - select: Select<TBaseModel>, - props: DatabaseCommonInteractionProps - ): void { - const model: BaseModel = new modelType(); - - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow + if (IsBillingEnabled && props.currentPlan && billingAccessControl) { + if ( + requestType === DatabaseRequestType.Create && + billingAccessControl.create + ) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + billingAccessControl.create, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + billingAccessControl.create + + " to access this feature", ); - - const canReadOnTheseColumns: Columns = - this.getModelColumnsByPermissions( - modelType, - userPermissions || [], - DatabaseRequestType.Read - ); - - const tableColumns: Array<AnalyticsTableColumn> = - model.getTableColumns(); - - const excludedColumnNames: Array<string> = - ModelPermission.getExcludedColumnNames(); - - for (const key in select) { - if (excludedColumnNames.includes(key)) { - continue; - } - - if (!canReadOnTheseColumns.columns.includes(key)) { - const column: AnalyticsTableColumn | undefined = - tableColumns.find((column: AnalyticsTableColumn) => { - return column.key === key; - }); - if (!column) { - throw new BadDataException( - `Invalid select clause. Cannot select on "${key}". This column does not exist on ${ - model.singularName - }. Here are the columns you can select on instead: ${tableColumns.join( - ', ' - )}` - ); - } - - throw new NotAuthorizedException( - `You do not have permissions to select on - ${key}. - You need any one of these permissions: ${PermissionHelper.getPermissionTitles( - column.accessControl?.read || [] - ).join(', ')}` - ); - } + } } - } - - private static getModelPermissions( - modelType: AnalyticsBaseModelType, - type: DatabaseRequestType - ): Array<Permission> { - let modelPermissions: Array<Permission> = []; - const model: BaseModel = new modelType(); - - if (type === DatabaseRequestType.Create) { - modelPermissions = model.accessControl?.create || []; - } - - if (type === DatabaseRequestType.Update) { - modelPermissions = model.accessControl?.update || []; - } - - if (type === DatabaseRequestType.Delete) { - modelPermissions = model.accessControl?.delete || []; - } - - if (type === DatabaseRequestType.Read) { - modelPermissions = model.accessControl?.read || []; - } - - return modelPermissions; - } - - private static isPublicPermissionAllowed( - modelType: AnalyticsBaseModelType, - type: DatabaseRequestType - ): boolean { - let isPublicAllowed: boolean = false; - isPublicAllowed = this.getModelPermissions(modelType, type).includes( - Permission.Public - ); - return isPublicAllowed; - } - - public static checkIfUserIsLoggedIn( - modelType: AnalyticsBaseModelType, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): void { - // 1 CHECK: PUBLIC check -- Check if this is a public request and if public is allowed. - - if (!this.isPublicPermissionAllowed(modelType, type) && !props.userId) { - if (props.userType === UserType.API) { - // if its an API request then continue. - return; - } - - // this means the record is not publicly createable and the user is not logged in. - throw new NotAuthenticatedException( - `A user should be logged in to ${type} record of ${ - new modelType().singularName - }.` - ); - } - } - - private static checkModelLevelPermissions( - modelType: AnalyticsBaseModelType, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): void { - this.checkIfUserIsLoggedIn(modelType, props, type); - - // 2nd CHECK: Does user have access to CRUD data on this model. - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow - ); - const modelPermissions: Array<Permission> = this.getModelPermissions( - modelType, - type - ); if ( - !PermissionHelper.doesPermissionsIntersect( - userPermissions.map((userPermission: UserPermission) => { - return userPermission.permission; - }) || [], - modelPermissions - ) + requestType === DatabaseRequestType.Read && + billingAccessControl.read ) { - throw new NotAuthorizedException( - `You do not have permissions to ${type} ${ - new modelType().singularName - }. You need one of these permissions: ${PermissionHelper.getPermissionTitles( - modelPermissions - ).join(', ')}` + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + billingAccessControl.read, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + billingAccessControl.read + + " to access this feature", ); + } } - /// Check billing permissions. - - if (IsBillingEnabled && props.currentPlan) { - const model: BaseModel = new modelType(); - - if ( - props.isSubscriptionUnpaid && - !model.allowAccessIfSubscriptionIsUnpaid - ) { - throw new PaymentRequiredException( - 'Your current subscription is in an unpaid state. Looks like your payment method failed. Please add a new payment method in Project Settings > Invoices to pay unpaid invoices.' - ); - } - - if ( - type === DatabaseRequestType.Create && - model.tableBillingAccessControl?.create - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - model.tableBillingAccessControl.create, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - model.tableBillingAccessControl.create + - ' to access this feature' - ); - } - } - - if ( - type === DatabaseRequestType.Update && - model.tableBillingAccessControl?.update - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - model.tableBillingAccessControl.update, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - model.tableBillingAccessControl.create + - ' to access this feature' - ); - } - } - - if ( - type === DatabaseRequestType.Delete && - model.tableBillingAccessControl?.delete - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - model.tableBillingAccessControl.delete, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - model.tableBillingAccessControl.create + - ' to access this feature' - ); - } - } - - if ( - type === DatabaseRequestType.Read && - model.tableBillingAccessControl?.read - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - model.tableBillingAccessControl?.read, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - model.tableBillingAccessControl?.read + - ' to access this feature' - ); - } - } + if ( + requestType === DatabaseRequestType.Update && + billingAccessControl.update + ) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + billingAccessControl.update, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + billingAccessControl.update + + " to access this feature", + ); + } } + } } + } + + public static async checkReadPermission<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + select: Select<TBaseModel> | null, + props: DatabaseCommonInteractionProps, + ): Promise<CheckReadPermissionType<TBaseModel>> { + if (props.isRoot || props.isMasterAdmin) { + query = await this.addTenantScopeToQueryAsRoot(modelType, query, props); + } + + if (!props.isRoot && !props.isMasterAdmin) { + //check if the user is logged in. + this.checkIfUserIsLoggedIn(modelType, props, DatabaseRequestType.Read); + + // add tenant scope. + query = await this.addTenantScopeToQuery(modelType, query, select, props); + + if (!props.isMultiTenantRequest) { + // We will check for this permission in recursive function. + + // check model level permissions. + this.checkModelLevelPermissions( + modelType, + props, + DatabaseRequestType.Read, + ); + + // We will check for this permission in recursive function. + // check query permissions. + this.checkQueryPermission(modelType, query, props); + + if (select) { + // check query permission. + this.checkSelectPermission(modelType, select, props); + } + } + } + + query = this.serializeQuery(query); + + if (select) { + const result: { + select: Select<TBaseModel>; + } = this.sanitizeSelect(select); + select = result.select; + } + + return { query, select }; + } + + private static serializeQuery<TBaseModel extends BaseModel>( + query: Query<TBaseModel>, + ): Query<TBaseModel> { + query = query as Query<TBaseModel>; + + return query; + } + + private static sanitizeSelect<TBaseModel extends BaseModel>( + select: Select<TBaseModel>, + ): { + select: Select<TBaseModel>; + } { + return { select }; + } + + private static getExcludedColumnNames(): string[] { + const returnArr: Array<string> = [ + "_id", + "createdAt", + "deletedAt", + "updatedAt", + "version", + ]; + + return returnArr; + } + + private static checkQueryPermission<TBaseModel extends BaseModel>( + modelType: AnalyticsBaseModelType, + query: Query<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): void { + const model: BaseModel = new modelType(); + + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ); + + const canReadOnTheseColumns: Columns = this.getModelColumnsByPermissions( + modelType, + userPermissions || [], + DatabaseRequestType.Read, + ); + + const tableColumns: Array<AnalyticsTableColumn> = model.getTableColumns(); + + const excludedColumnNames: Array<string> = + ModelPermission.getExcludedColumnNames(); + + // Now we need to check all columns. + + for (const key in query) { + if (excludedColumnNames.includes(key)) { + continue; + } + + if (!canReadOnTheseColumns.columns.includes(key)) { + const column: AnalyticsTableColumn | undefined = tableColumns.find( + (item: AnalyticsTableColumn) => { + return item.key === key; + }, + ); + + if (!column) { + throw new BadDataException( + `Invalid column on ${model.singularName} - ${key}. Column does not exist.`, + ); + } + + throw new NotAuthorizedException( + `You do not have permissions to query on - ${key}. You need any one of these permissions: ${PermissionHelper.getPermissionTitles( + column.accessControl?.read || [], + ).join(", ")}`, + ); + } + } + } + + private static async addTenantScopeToQueryAsRoot< + TBaseModel extends BaseModel, + >( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + const model: BaseModel = new modelType(); + + const tenantColumn: string | null = model.getTenantColumn()?.key || null; + + // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query. + if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) { + (query as any)[tenantColumn] = props.tenantId; + } + + return query; + } + + private static async addTenantScopeToQuery<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + select: Select<TBaseModel> | null, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + const model: BaseModel = new modelType(); + + const tenantColumn: string | null = model.getTenantColumn()?.key || null; + + // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query. + if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) { + (query as any)[tenantColumn] = props.tenantId; + } else if ( + tenantColumn && + !props.tenantId && + props.userGlobalAccessPermission + ) { + // for each of these projectIds, + // check if they have valid permissions for these projects + // and if they do, include them in the query. + + const queries: Array<Query<TBaseModel>> = []; + + let projectIDs: Array<ObjectID> = []; + + if ( + props.userGlobalAccessPermission && + props.userGlobalAccessPermission.projectIds + ) { + projectIDs = props.userGlobalAccessPermission?.projectIds; + } + + let lastException: Error | null = null; + + for (const projectId of projectIDs) { + if (!props.userId) { + continue; + } + + try { + const checkReadPermissionType: CheckReadPermissionType<TBaseModel> = + await this.checkReadPermission(modelType, query, select, { + ...props, + isMultiTenantRequest: false, + tenantId: projectId, + userTenantAccessPermission: props.userTenantAccessPermission, + }); + queries.push({ + ...(checkReadPermissionType.query as Query<TBaseModel>), + }); + } catch (e) { + // do nothing here. Ignore. + lastException = e as Error; + } + } + + if (queries.length === 0) { + throw new NotAuthorizedException( + lastException?.message || + "Does not have permission to read " + model.singularName, + ); + } + + return queries as any; + } + + return query; + } + + private static getModelColumnsByPermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + userPermissions: Array<UserPermission>, + requestType: DatabaseRequestType, + ): Columns { + const model: BaseModel = new modelType(); + const tableColumns: Array<AnalyticsTableColumn> = model.getTableColumns(); + + const columns: Array<string> = []; + + const permissions: Array<Permission> = userPermissions.map( + (item: UserPermission) => { + return item.permission; + }, + ); + + for (const column of tableColumns) { + let columnPermissions: Array<Permission> = []; + + if (requestType === DatabaseRequestType.Read) { + columnPermissions = column.accessControl?.read || []; + } + + if (requestType === DatabaseRequestType.Create) { + columnPermissions = column.accessControl?.create || []; + } + + if (requestType === DatabaseRequestType.Update) { + columnPermissions = column.accessControl?.update || []; + } + + if (requestType === DatabaseRequestType.Delete) { + throw new BadDataException("Invalid request type delete"); + } + + if ( + columnPermissions && + PermissionHelper.doesPermissionsIntersect( + permissions, + columnPermissions, + ) + ) { + columns.push(column.key); + } + } + + return new Columns(columns); + } + + private static checkSelectPermission<TBaseModel extends BaseModel>( + modelType: AnalyticsBaseModelType, + select: Select<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): void { + const model: BaseModel = new modelType(); + + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ); + + const canReadOnTheseColumns: Columns = this.getModelColumnsByPermissions( + modelType, + userPermissions || [], + DatabaseRequestType.Read, + ); + + const tableColumns: Array<AnalyticsTableColumn> = model.getTableColumns(); + + const excludedColumnNames: Array<string> = + ModelPermission.getExcludedColumnNames(); + + for (const key in select) { + if (excludedColumnNames.includes(key)) { + continue; + } + + if (!canReadOnTheseColumns.columns.includes(key)) { + const column: AnalyticsTableColumn | undefined = tableColumns.find( + (column: AnalyticsTableColumn) => { + return column.key === key; + }, + ); + if (!column) { + throw new BadDataException( + `Invalid select clause. Cannot select on "${key}". This column does not exist on ${ + model.singularName + }. Here are the columns you can select on instead: ${tableColumns.join( + ", ", + )}`, + ); + } + + throw new NotAuthorizedException( + `You do not have permissions to select on - ${key}. + You need any one of these permissions: ${PermissionHelper.getPermissionTitles( + column.accessControl?.read || [], + ).join(", ")}`, + ); + } + } + } + + private static getModelPermissions( + modelType: AnalyticsBaseModelType, + type: DatabaseRequestType, + ): Array<Permission> { + let modelPermissions: Array<Permission> = []; + const model: BaseModel = new modelType(); + + if (type === DatabaseRequestType.Create) { + modelPermissions = model.accessControl?.create || []; + } + + if (type === DatabaseRequestType.Update) { + modelPermissions = model.accessControl?.update || []; + } + + if (type === DatabaseRequestType.Delete) { + modelPermissions = model.accessControl?.delete || []; + } + + if (type === DatabaseRequestType.Read) { + modelPermissions = model.accessControl?.read || []; + } + + return modelPermissions; + } + + private static isPublicPermissionAllowed( + modelType: AnalyticsBaseModelType, + type: DatabaseRequestType, + ): boolean { + let isPublicAllowed: boolean = false; + isPublicAllowed = this.getModelPermissions(modelType, type).includes( + Permission.Public, + ); + return isPublicAllowed; + } + + public static checkIfUserIsLoggedIn( + modelType: AnalyticsBaseModelType, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): void { + // 1 CHECK: PUBLIC check -- Check if this is a public request and if public is allowed. + + if (!this.isPublicPermissionAllowed(modelType, type) && !props.userId) { + if (props.userType === UserType.API) { + // if its an API request then continue. + return; + } + + // this means the record is not publicly createable and the user is not logged in. + throw new NotAuthenticatedException( + `A user should be logged in to ${type} record of ${ + new modelType().singularName + }.`, + ); + } + } + + private static checkModelLevelPermissions( + modelType: AnalyticsBaseModelType, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): void { + this.checkIfUserIsLoggedIn(modelType, props, type); + + // 2nd CHECK: Does user have access to CRUD data on this model. + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ); + const modelPermissions: Array<Permission> = this.getModelPermissions( + modelType, + type, + ); + + if ( + !PermissionHelper.doesPermissionsIntersect( + userPermissions.map((userPermission: UserPermission) => { + return userPermission.permission; + }) || [], + modelPermissions, + ) + ) { + throw new NotAuthorizedException( + `You do not have permissions to ${type} ${ + new modelType().singularName + }. You need one of these permissions: ${PermissionHelper.getPermissionTitles( + modelPermissions, + ).join(", ")}`, + ); + } + + /// Check billing permissions. + + if (IsBillingEnabled && props.currentPlan) { + const model: BaseModel = new modelType(); + + if ( + props.isSubscriptionUnpaid && + !model.allowAccessIfSubscriptionIsUnpaid + ) { + throw new PaymentRequiredException( + "Your current subscription is in an unpaid state. Looks like your payment method failed. Please add a new payment method in Project Settings > Invoices to pay unpaid invoices.", + ); + } + + if ( + type === DatabaseRequestType.Create && + model.tableBillingAccessControl?.create + ) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + model.tableBillingAccessControl.create, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + model.tableBillingAccessControl.create + + " to access this feature", + ); + } + } + + if ( + type === DatabaseRequestType.Update && + model.tableBillingAccessControl?.update + ) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + model.tableBillingAccessControl.update, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + model.tableBillingAccessControl.create + + " to access this feature", + ); + } + } + + if ( + type === DatabaseRequestType.Delete && + model.tableBillingAccessControl?.delete + ) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + model.tableBillingAccessControl.delete, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + model.tableBillingAccessControl.create + + " to access this feature", + ); + } + } + + if ( + type === DatabaseRequestType.Read && + model.tableBillingAccessControl?.read + ) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + model.tableBillingAccessControl?.read, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + model.tableBillingAccessControl?.read + + " to access this feature", + ); + } + } + } + } } diff --git a/CommonServer/Types/AnalyticsDatabase/Query.ts b/CommonServer/Types/AnalyticsDatabase/Query.ts index 83a8a33b50..897f32e100 100644 --- a/CommonServer/Types/AnalyticsDatabase/Query.ts +++ b/CommonServer/Types/AnalyticsDatabase/Query.ts @@ -1,10 +1,10 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; export type QueryPropertyOptions = JSONValue | JSONObject; export declare type QueryOptions<Entity> = { - [P in keyof Entity]?: QueryPropertyOptions; + [P in keyof Entity]?: QueryPropertyOptions; }; type Query<TBaseModel extends AnalyticsBaseModel> = QueryOptions<TBaseModel>; diff --git a/CommonServer/Types/AnalyticsDatabase/QueryHelper.ts b/CommonServer/Types/AnalyticsDatabase/QueryHelper.ts index 5b2e9f8561..c6db07da38 100644 --- a/CommonServer/Types/AnalyticsDatabase/QueryHelper.ts +++ b/CommonServer/Types/AnalyticsDatabase/QueryHelper.ts @@ -1,7 +1,7 @@ -import LessThan from 'Common/Types/BaseDatabase/LessThan'; +import LessThan from "Common/Types/BaseDatabase/LessThan"; export default class QueryHelper { - public static lessThan(value: Date | number): LessThan { - return new LessThan(value); - } + public static lessThan(value: Date | number): LessThan { + return new LessThan(value); + } } diff --git a/CommonServer/Types/AnalyticsDatabase/Select.ts b/CommonServer/Types/AnalyticsDatabase/Select.ts index 324d74f0db..7a4fb5b89c 100644 --- a/CommonServer/Types/AnalyticsDatabase/Select.ts +++ b/CommonServer/Types/AnalyticsDatabase/Select.ts @@ -1,5 +1,5 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import Dictionary from 'Common/Types/Dictionary'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import Dictionary from "Common/Types/Dictionary"; export type SelectPropertyOptions = true | Dictionary<true>; @@ -8,7 +8,7 @@ export type SelectPropertyOptions = true | Dictionary<true>; */ export declare type SelectOptions<Entity> = { - [P in keyof Entity]?: SelectPropertyOptions; + [P in keyof Entity]?: SelectPropertyOptions; }; type Select<TBaseModel extends AnalyticsBaseModel> = SelectOptions<TBaseModel>; diff --git a/CommonServer/Types/AnalyticsDatabase/Sort.ts b/CommonServer/Types/AnalyticsDatabase/Sort.ts index 05502bcfdd..145e72bd44 100644 --- a/CommonServer/Types/AnalyticsDatabase/Sort.ts +++ b/CommonServer/Types/AnalyticsDatabase/Sort.ts @@ -1,11 +1,11 @@ -import BaseModel from 'Common/AnalyticsModels/BaseModel'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; +import BaseModel from "Common/AnalyticsModels/BaseModel"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; /** * Order by find options. */ export declare type FindOrder<Entity> = { - [P in keyof Entity]?: SortOrder; + [P in keyof Entity]?: SortOrder; }; type Sort<TBaseModel extends BaseModel> = FindOrder<TBaseModel>; diff --git a/CommonServer/Types/AnalyticsDatabase/UpdateBy.ts b/CommonServer/Types/AnalyticsDatabase/UpdateBy.ts index fdda7e761a..5e4b4c5dca 100644 --- a/CommonServer/Types/AnalyticsDatabase/UpdateBy.ts +++ b/CommonServer/Types/AnalyticsDatabase/UpdateBy.ts @@ -1,9 +1,9 @@ -import Query from './Query'; -import BaseModel from 'Common/AnalyticsModels/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import Query from "./Query"; +import BaseModel from "Common/AnalyticsModels/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default interface UpdateBy<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; - data: TBaseModel; - props: DatabaseCommonInteractionProps; + query: Query<TBaseModel>; + data: TBaseModel; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/AnalyticsDatabase/UpdateByID.ts b/CommonServer/Types/AnalyticsDatabase/UpdateByID.ts index 9de4bc96d5..e8edf795dd 100644 --- a/CommonServer/Types/AnalyticsDatabase/UpdateByID.ts +++ b/CommonServer/Types/AnalyticsDatabase/UpdateByID.ts @@ -1,7 +1,7 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default interface UpdateBy<TBaseModel extends AnalyticsBaseModel> { - data: TBaseModel; - props: DatabaseCommonInteractionProps; + data: TBaseModel; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/BaseDatabase/DatabaseRequestType.ts b/CommonServer/Types/BaseDatabase/DatabaseRequestType.ts index 70dfdeae23..f25cf1ec79 100644 --- a/CommonServer/Types/BaseDatabase/DatabaseRequestType.ts +++ b/CommonServer/Types/BaseDatabase/DatabaseRequestType.ts @@ -1,8 +1,8 @@ enum DatabaseRequestType { - Create = 'create', - Read = 'read', - Update = 'update', - Delete = 'delete', + Create = "create", + Read = "read", + Update = "update", + Delete = "delete", } export default DatabaseRequestType; diff --git a/CommonServer/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts b/CommonServer/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts index efbd7da31f..0b6839af93 100644 --- a/CommonServer/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts +++ b/CommonServer/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts @@ -1,70 +1,70 @@ -import BillingService from '../../../Services/BillingService'; -import MonitorService from '../../../Services/MonitorService'; -import ProjectService from '../../../Services/ProjectService'; -import QueryHelper from '../../Database/QueryHelper'; -import ServerMeteredPlan from './ServerMeteredPlan'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Project from 'Model/Models/Project'; +import BillingService from "../../../Services/BillingService"; +import MonitorService from "../../../Services/MonitorService"; +import ProjectService from "../../../Services/ProjectService"; +import QueryHelper from "../../Database/QueryHelper"; +import ServerMeteredPlan from "./ServerMeteredPlan"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Project from "Model/Models/Project"; export default class ActiveMonitoringMeteredPlan extends ServerMeteredPlan { - public override getProductType(): ProductType { - return ProductType.ActiveMonitoring; - } - - public override async reportQuantityToBillingProvider( - projectId: ObjectID, - options?: { - meteredPlanSubscriptionId?: string | undefined; - } - ): Promise<void> { - const count: PositiveNumber = await MonitorService.countBy({ - query: { - projectId: projectId, - monitorType: QueryHelper.notEquals(MonitorType.Manual), - }, - props: { - isRoot: true, - }, - }); - - // update this count in project as well. - await ProjectService.updateOneById({ - id: projectId, - data: { - currentActiveMonitorsCount: count.toNumber(), - }, - props: { - isRoot: true, - }, - }); - - // update this count in project as well. - const project: Project | null = await ProjectService.findOneById({ - id: projectId, - select: { - paymentProviderMeteredSubscriptionId: true, - paymentProviderPlanId: true, - }, - props: { - isRoot: true, - }, - }); - - if ( - project && - (options?.meteredPlanSubscriptionId || - project.paymentProviderMeteredSubscriptionId) && - project.paymentProviderPlanId - ) { - await BillingService.addOrUpdateMeteredPricingOnSubscription( - (options?.meteredPlanSubscriptionId as string) || - (project.paymentProviderMeteredSubscriptionId as string), - this, - count.toNumber() - ); - } + public override getProductType(): ProductType { + return ProductType.ActiveMonitoring; + } + + public override async reportQuantityToBillingProvider( + projectId: ObjectID, + options?: { + meteredPlanSubscriptionId?: string | undefined; + }, + ): Promise<void> { + const count: PositiveNumber = await MonitorService.countBy({ + query: { + projectId: projectId, + monitorType: QueryHelper.notEquals(MonitorType.Manual), + }, + props: { + isRoot: true, + }, + }); + + // update this count in project as well. + await ProjectService.updateOneById({ + id: projectId, + data: { + currentActiveMonitorsCount: count.toNumber(), + }, + props: { + isRoot: true, + }, + }); + + // update this count in project as well. + const project: Project | null = await ProjectService.findOneById({ + id: projectId, + select: { + paymentProviderMeteredSubscriptionId: true, + paymentProviderPlanId: true, + }, + props: { + isRoot: true, + }, + }); + + if ( + project && + (options?.meteredPlanSubscriptionId || + project.paymentProviderMeteredSubscriptionId) && + project.paymentProviderPlanId + ) { + await BillingService.addOrUpdateMeteredPricingOnSubscription( + (options?.meteredPlanSubscriptionId as string) || + (project.paymentProviderMeteredSubscriptionId as string), + this, + count.toNumber(), + ); } + } } diff --git a/CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans.ts b/CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans.ts index 4ec2bcb1ee..e81df300de 100644 --- a/CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans.ts +++ b/CommonServer/Types/Billing/MeteredPlan/AllMeteredPlans.ts @@ -1,53 +1,53 @@ -import ActiveMonitoringMeteredPlanType from './ActiveMonitoringMeteredPlan'; -import ServerMeteredPlan from './ServerMeteredPlan'; -import TelemetryMeteredPlanType from './TelemetryMeteredPlan'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; +import ActiveMonitoringMeteredPlanType from "./ActiveMonitoringMeteredPlan"; +import ServerMeteredPlan from "./ServerMeteredPlan"; +import TelemetryMeteredPlanType from "./TelemetryMeteredPlan"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; export const ActiveMonitoringMeteredPlan: ActiveMonitoringMeteredPlanType = - new ActiveMonitoringMeteredPlanType(); + new ActiveMonitoringMeteredPlanType(); export const LogDataIngestMeteredPlan: TelemetryMeteredPlanType = - new TelemetryMeteredPlanType({ - productType: ProductType.Logs, - unitCostInUSD: 0.1 / 15, // 0.10 per 15 days per GB - }); + new TelemetryMeteredPlanType({ + productType: ProductType.Logs, + unitCostInUSD: 0.1 / 15, // 0.10 per 15 days per GB + }); export const MetricsDataIngestMeteredPlan: TelemetryMeteredPlanType = - new TelemetryMeteredPlanType({ - productType: ProductType.Metrics, - unitCostInUSD: 0.1 / 15, // 0.10 per 15 days per GB - }); + new TelemetryMeteredPlanType({ + productType: ProductType.Metrics, + unitCostInUSD: 0.1 / 15, // 0.10 per 15 days per GB + }); export const TracesDataIngestMetredPlan: TelemetryMeteredPlanType = - new TelemetryMeteredPlanType({ - productType: ProductType.Traces, - unitCostInUSD: 0.1 / 15, // 0.10 per 15 days per GB - }); + new TelemetryMeteredPlanType({ + productType: ProductType.Traces, + unitCostInUSD: 0.1 / 15, // 0.10 per 15 days per GB + }); const AllMeteredPlans: Array<ServerMeteredPlan> = [ - ActiveMonitoringMeteredPlan, - LogDataIngestMeteredPlan, - MetricsDataIngestMeteredPlan, - TracesDataIngestMetredPlan, + ActiveMonitoringMeteredPlan, + LogDataIngestMeteredPlan, + MetricsDataIngestMeteredPlan, + TracesDataIngestMetredPlan, ]; export class MeteredPlanUtil { - public static getMeteredPlanByProductType( - productType: ProductType - ): ServerMeteredPlan { - if (productType === ProductType.Logs) { - return LogDataIngestMeteredPlan; - } else if (productType === ProductType.Metrics) { - return MetricsDataIngestMeteredPlan; - } else if (productType === ProductType.Traces) { - return TracesDataIngestMetredPlan; - } else if (productType === ProductType.ActiveMonitoring) { - return ActiveMonitoringMeteredPlan; - } - - throw new BadDataException(`Unknown product type ${productType}`); + public static getMeteredPlanByProductType( + productType: ProductType, + ): ServerMeteredPlan { + if (productType === ProductType.Logs) { + return LogDataIngestMeteredPlan; + } else if (productType === ProductType.Metrics) { + return MetricsDataIngestMeteredPlan; + } else if (productType === ProductType.Traces) { + return TracesDataIngestMetredPlan; + } else if (productType === ProductType.ActiveMonitoring) { + return ActiveMonitoringMeteredPlan; } + + throw new BadDataException(`Unknown product type ${productType}`); + } } export default AllMeteredPlans; diff --git a/CommonServer/Types/Billing/MeteredPlan/ServerMeteredPlan.ts b/CommonServer/Types/Billing/MeteredPlan/ServerMeteredPlan.ts index 39f798bf92..3069d0112f 100644 --- a/CommonServer/Types/Billing/MeteredPlan/ServerMeteredPlan.ts +++ b/CommonServer/Types/Billing/MeteredPlan/ServerMeteredPlan.ts @@ -1,31 +1,31 @@ -import BillingService from '../../../Services/BillingService'; -import MeteredPlan from 'Common/Types/Billing/MeteredPlan'; -import NotImplementedException from 'Common/Types/Exception/NotImplementedException'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import ObjectID from 'Common/Types/ObjectID'; +import BillingService from "../../../Services/BillingService"; +import MeteredPlan from "Common/Types/Billing/MeteredPlan"; +import NotImplementedException from "Common/Types/Exception/NotImplementedException"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import ObjectID from "Common/Types/ObjectID"; export default class ServerMeteredPlan { - public getProductType(): ProductType { - throw new NotImplementedException(); - } + public getProductType(): ProductType { + throw new NotImplementedException(); + } - public getCostByMeteredPlan( - meteredPlan: MeteredPlan, - quantity: number - ): number { - return meteredPlan.getPricePerUnit() * quantity; - } + public getCostByMeteredPlan( + meteredPlan: MeteredPlan, + quantity: number, + ): number { + return meteredPlan.getPricePerUnit() * quantity; + } - public async reportQuantityToBillingProvider( - _projectId: ObjectID, - _options: { - meteredPlanSubscriptionId?: string | undefined; - } - ): Promise<void> { - throw new NotImplementedException(); - } + public async reportQuantityToBillingProvider( + _projectId: ObjectID, + _options: { + meteredPlanSubscriptionId?: string | undefined; + }, + ): Promise<void> { + throw new NotImplementedException(); + } - public getPriceId(): string { - return BillingService.getMeteredPlanPriceId(this.getProductType()); - } + public getPriceId(): string { + return BillingService.getMeteredPlanPriceId(this.getProductType()); + } } diff --git a/CommonServer/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts b/CommonServer/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts index f2f2fce974..ffdba53920 100644 --- a/CommonServer/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +++ b/CommonServer/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts @@ -1,137 +1,134 @@ -import BillingService from '../../../Services/BillingService'; -import ProjectService from '../../../Services/ProjectService'; -import TelemetryUsageBillingService from '../../../Services/TelemetryUsageBillingService'; -import ServerMeteredPlan from './ServerMeteredPlan'; -import OneUptimeDate from 'Common/Types/Date'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import ObjectID from 'Common/Types/ObjectID'; -import Project from 'Model/Models/Project'; -import TelemetryUsageBilling from 'Model/Models/TelemetryUsageBilling'; +import BillingService from "../../../Services/BillingService"; +import ProjectService from "../../../Services/ProjectService"; +import TelemetryUsageBillingService from "../../../Services/TelemetryUsageBillingService"; +import ServerMeteredPlan from "./ServerMeteredPlan"; +import OneUptimeDate from "Common/Types/Date"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import ObjectID from "Common/Types/ObjectID"; +import Project from "Model/Models/Project"; +import TelemetryUsageBilling from "Model/Models/TelemetryUsageBilling"; export default class TelemetryMeteredPlan extends ServerMeteredPlan { - private _productType!: ProductType; - public get productType(): ProductType { - return this._productType; - } - public set productType(v: ProductType) { - this._productType = v; + private _productType!: ProductType; + public get productType(): ProductType { + return this._productType; + } + public set productType(v: ProductType) { + this._productType = v; + } + + private _unitCostInUSD: number = 0; + public get unitCostInUSD(): number { + return this._unitCostInUSD; + } + public set unitCostInUSD(v: number) { + this._unitCostInUSD = v; + } + + public constructor(data: { + productType: ProductType; + unitCostInUSD: number; + }) { + super(); + this.productType = data.productType; + this.unitCostInUSD = data.unitCostInUSD; + } + + public getTotalCostInUSD(data: { + dataIngestedInGB: number; + retentionInDays: number; + }): number { + return data.dataIngestedInGB * data.retentionInDays * this.unitCostInUSD; + } + + public override getProductType(): ProductType { + return this.productType; + } + + public override async reportQuantityToBillingProvider( + projectId: ObjectID, + options?: { + meteredPlanSubscriptionId?: string | undefined; + }, + ): Promise<void> { + // get all unreported logs + + const usageBillings: Array<TelemetryUsageBilling> = + await TelemetryUsageBillingService.getUnreportedUsageBilling({ + projectId: projectId, + productType: this.productType, + }); + + if (usageBillings.length === 0) { + return; } - private _unitCostInUSD: number = 0; - public get unitCostInUSD(): number { - return this._unitCostInUSD; - } - public set unitCostInUSD(v: number) { - this._unitCostInUSD = v; + // calculate all the total usage count and report it to billing provider. + + let totalCostInUSD: number = 0; + + for (const usageBilling of usageBillings) { + if ( + usageBilling?.totalCostInUSD?.value && + usageBilling?.totalCostInUSD.value > 0 + ) { + totalCostInUSD += usageBilling.totalCostInUSD.value; + } } - public constructor(data: { - productType: ProductType; - unitCostInUSD: number; - }) { - super(); - this.productType = data.productType; - this.unitCostInUSD = data.unitCostInUSD; + if (totalCostInUSD < 1) { + return; // too low to report. } - public getTotalCostInUSD(data: { - dataIngestedInGB: number; - retentionInDays: number; - }): number { - return ( - data.dataIngestedInGB * data.retentionInDays * this.unitCostInUSD - ); - } + // convert USD to cents. - public override getProductType(): ProductType { - return this.productType; - } + let totalCostInCents: number = totalCostInUSD * 100; - public override async reportQuantityToBillingProvider( - projectId: ObjectID, - options?: { - meteredPlanSubscriptionId?: string | undefined; - } - ): Promise<void> { - // get all unreported logs + // convert this to integer. - const usageBillings: Array<TelemetryUsageBilling> = - await TelemetryUsageBillingService.getUnreportedUsageBilling({ - projectId: projectId, - productType: this.productType, - }); + totalCostInCents = Math.ceil(totalCostInCents); - if (usageBillings.length === 0) { - return; - } + // update this count in project as well. + const project: Project | null = await ProjectService.findOneById({ + id: projectId, + select: { + paymentProviderMeteredSubscriptionId: true, + paymentProviderPlanId: true, + }, + props: { + isRoot: true, + }, + }); - // calculate all the total usage count and report it to billing provider. + if ( + project && + (options?.meteredPlanSubscriptionId || + project.paymentProviderMeteredSubscriptionId) && + project.paymentProviderPlanId + ) { + await BillingService.addOrUpdateMeteredPricingOnSubscription( + (options?.meteredPlanSubscriptionId as string) || + (project.paymentProviderMeteredSubscriptionId as string), + this, + totalCostInCents, + ); - let totalCostInUSD: number = 0; + for (const usageBilling of usageBillings) { + if (usageBilling.id) { + // now mark it as reported. - for (const usageBilling of usageBillings) { - if ( - usageBilling?.totalCostInUSD?.value && - usageBilling?.totalCostInUSD.value > 0 - ) { - totalCostInUSD += usageBilling.totalCostInUSD.value; - } - } - - if (totalCostInUSD < 1) { - return; // too low to report. - } - - // convert USD to cents. - - let totalCostInCents: number = totalCostInUSD * 100; - - // convert this to integer. - - totalCostInCents = Math.ceil(totalCostInCents); - - // update this count in project as well. - const project: Project | null = await ProjectService.findOneById({ - id: projectId, - select: { - paymentProviderMeteredSubscriptionId: true, - paymentProviderPlanId: true, + await TelemetryUsageBillingService.updateOneById({ + id: usageBilling.id, + data: { + isReportedToBillingProvider: true, + reportedToBillingProviderAt: OneUptimeDate.getCurrentDate(), }, props: { - isRoot: true, + isRoot: true, }, - }); - - if ( - project && - (options?.meteredPlanSubscriptionId || - project.paymentProviderMeteredSubscriptionId) && - project.paymentProviderPlanId - ) { - await BillingService.addOrUpdateMeteredPricingOnSubscription( - (options?.meteredPlanSubscriptionId as string) || - (project.paymentProviderMeteredSubscriptionId as string), - this, - totalCostInCents - ); - - for (const usageBilling of usageBillings) { - if (usageBilling.id) { - // now mark it as reported. - - await TelemetryUsageBillingService.updateOneById({ - id: usageBilling.id, - data: { - isReportedToBillingProvider: true, - reportedToBillingProviderAt: - OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); - } - } + }); } + } } + } } diff --git a/CommonServer/Types/Database/CountBy.ts b/CommonServer/Types/Database/CountBy.ts index fafe34cbe6..4694eab76a 100644 --- a/CommonServer/Types/Database/CountBy.ts +++ b/CommonServer/Types/Database/CountBy.ts @@ -1,14 +1,14 @@ -import GroupBy from './GroupBy'; -import Query from './Query'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import PositiveNumber from 'Common/Types/PositiveNumber'; +import GroupBy from "./GroupBy"; +import Query from "./Query"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import PositiveNumber from "Common/Types/PositiveNumber"; export default interface CountBy<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; - skip?: PositiveNumber | number; - groupBy?: GroupBy<TBaseModel> | undefined; - limit?: PositiveNumber | number; - props: DatabaseCommonInteractionProps; - distinctOn?: string | undefined; + query: Query<TBaseModel>; + skip?: PositiveNumber | number; + groupBy?: GroupBy<TBaseModel> | undefined; + limit?: PositiveNumber | number; + props: DatabaseCommonInteractionProps; + distinctOn?: string | undefined; } diff --git a/CommonServer/Types/Database/CreateBy.ts b/CommonServer/Types/Database/CreateBy.ts index 5d32509e8e..2c5dd951c6 100644 --- a/CommonServer/Types/Database/CreateBy.ts +++ b/CommonServer/Types/Database/CreateBy.ts @@ -1,9 +1,9 @@ -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import { JSONObject } from 'Common/Types/JSON'; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import { JSONObject } from "Common/Types/JSON"; export default interface CreateBy<TBaseModel extends BaseModel> { - data: TBaseModel; - miscDataProps?: JSONObject; - props: DatabaseCommonInteractionProps; + data: TBaseModel; + miscDataProps?: JSONObject; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/Database/DeleteBy.ts b/CommonServer/Types/Database/DeleteBy.ts index 210ad4345a..b96bda7781 100644 --- a/CommonServer/Types/Database/DeleteBy.ts +++ b/CommonServer/Types/Database/DeleteBy.ts @@ -1,11 +1,11 @@ -import DeleteOneBy from './DeleteOneBy'; -import BaseModel from 'Common/Models/BaseModel'; -import PositiveNumber from 'Common/Types/PositiveNumber'; +import DeleteOneBy from "./DeleteOneBy"; +import BaseModel from "Common/Models/BaseModel"; +import PositiveNumber from "Common/Types/PositiveNumber"; interface DeleteBy<TBaseModel extends BaseModel> - extends DeleteOneBy<TBaseModel> { - limit: PositiveNumber | number; - skip: PositiveNumber | number; + extends DeleteOneBy<TBaseModel> { + limit: PositiveNumber | number; + skip: PositiveNumber | number; } export default DeleteBy; diff --git a/CommonServer/Types/Database/DeleteById.ts b/CommonServer/Types/Database/DeleteById.ts index 165302fdb4..7180e111e4 100644 --- a/CommonServer/Types/Database/DeleteById.ts +++ b/CommonServer/Types/Database/DeleteById.ts @@ -1,9 +1,9 @@ -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import ObjectID from 'Common/Types/ObjectID'; -import User from 'Model/Models/User'; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import ObjectID from "Common/Types/ObjectID"; +import User from "Model/Models/User"; export default interface DeleteById { - id: ObjectID; - deletedByUser?: User; - props: DatabaseCommonInteractionProps; + id: ObjectID; + deletedByUser?: User; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/Database/DeleteOneBy.ts b/CommonServer/Types/Database/DeleteOneBy.ts index 58bad6a7d5..ff7dc602ed 100644 --- a/CommonServer/Types/Database/DeleteOneBy.ts +++ b/CommonServer/Types/Database/DeleteOneBy.ts @@ -1,10 +1,10 @@ -import Query from './Query'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import User from 'Model/Models/User'; +import Query from "./Query"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import User from "Model/Models/User"; export default interface DeleteOneBy<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; - deletedByUser?: User | undefined; - props: DatabaseCommonInteractionProps; + query: Query<TBaseModel>; + deletedByUser?: User | undefined; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/Database/FindBy.ts b/CommonServer/Types/Database/FindBy.ts index 6b7fdd90c6..c72b0661dc 100644 --- a/CommonServer/Types/Database/FindBy.ts +++ b/CommonServer/Types/Database/FindBy.ts @@ -1,9 +1,9 @@ -import FindOneBy from './FindOneBy'; -import BaseModel from 'Common/Models/BaseModel'; -import PositiveNumber from 'Common/Types/PositiveNumber'; +import FindOneBy from "./FindOneBy"; +import BaseModel from "Common/Models/BaseModel"; +import PositiveNumber from "Common/Types/PositiveNumber"; export default interface FindBy<TBaseModel extends BaseModel> - extends FindOneBy<TBaseModel> { - limit: PositiveNumber | number; - skip: PositiveNumber | number; + extends FindOneBy<TBaseModel> { + limit: PositiveNumber | number; + skip: PositiveNumber | number; } diff --git a/CommonServer/Types/Database/FindOneBy.ts b/CommonServer/Types/Database/FindOneBy.ts index f37ebb57bc..4bf3fd5202 100644 --- a/CommonServer/Types/Database/FindOneBy.ts +++ b/CommonServer/Types/Database/FindOneBy.ts @@ -1,14 +1,14 @@ -import GroupBy from './GroupBy'; -import Query from './Query'; -import Select from './Select'; -import Sort from './Sort'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import GroupBy from "./GroupBy"; +import Query from "./Query"; +import Select from "./Select"; +import Sort from "./Sort"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default interface FindOneBy<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; - select?: Select<TBaseModel> | undefined; - sort?: Sort<TBaseModel> | undefined; - groupBy?: GroupBy<TBaseModel> | undefined; - props: DatabaseCommonInteractionProps; + query: Query<TBaseModel>; + select?: Select<TBaseModel> | undefined; + sort?: Sort<TBaseModel> | undefined; + groupBy?: GroupBy<TBaseModel> | undefined; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/Database/FindOneByID.ts b/CommonServer/Types/Database/FindOneByID.ts index 86689a6a14..c119a0349f 100644 --- a/CommonServer/Types/Database/FindOneByID.ts +++ b/CommonServer/Types/Database/FindOneByID.ts @@ -1,10 +1,10 @@ -import Select from './Select'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import ObjectID from 'Common/Types/ObjectID'; +import Select from "./Select"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import ObjectID from "Common/Types/ObjectID"; export default interface FindOneByID<TBaseModel extends BaseModel> { - id: ObjectID; - select?: Select<TBaseModel> | undefined; - props: DatabaseCommonInteractionProps; + id: ObjectID; + select?: Select<TBaseModel> | undefined; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/Database/GroupBy.ts b/CommonServer/Types/Database/GroupBy.ts index 8a0d397c6c..e460a5a85b 100644 --- a/CommonServer/Types/Database/GroupBy.ts +++ b/CommonServer/Types/Database/GroupBy.ts @@ -2,7 +2,7 @@ * GroupBy find options. */ declare type GroupBy<Entity> = { - [P in keyof Entity]?: true; + [P in keyof Entity]?: true; }; export default GroupBy; diff --git a/CommonServer/Types/Database/HardDeleteBy.ts b/CommonServer/Types/Database/HardDeleteBy.ts index 2ba2e16fbd..c464d4c5bd 100644 --- a/CommonServer/Types/Database/HardDeleteBy.ts +++ b/CommonServer/Types/Database/HardDeleteBy.ts @@ -1,8 +1,8 @@ -import Query from './Query'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import Query from "./Query"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default interface HardDeleteBy<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; - props: DatabaseCommonInteractionProps; + query: Query<TBaseModel>; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/Database/Hooks.ts b/CommonServer/Types/Database/Hooks.ts index 1aeb865354..431554ecde 100644 --- a/CommonServer/Types/Database/Hooks.ts +++ b/CommonServer/Types/Database/Hooks.ts @@ -1,27 +1,27 @@ -import CreateBy from './CreateBy'; -import DeleteBy from './DeleteBy'; -import FindBy from './FindBy'; -import UpdateBy from './UpdateBy'; -import BaseModel from 'Common/Models/BaseModel'; +import CreateBy from "./CreateBy"; +import DeleteBy from "./DeleteBy"; +import FindBy from "./FindBy"; +import UpdateBy from "./UpdateBy"; +import BaseModel from "Common/Models/BaseModel"; -export type DatabaseTriggerType = 'on-create' | 'on-update' | 'on-delete'; +export type DatabaseTriggerType = "on-create" | "on-update" | "on-delete"; export interface OnCreate<TBaseModel extends BaseModel> { - createBy: CreateBy<TBaseModel>; - carryForward: any; + createBy: CreateBy<TBaseModel>; + carryForward: any; } export interface OnFind<TBaseModel extends BaseModel> { - findBy: FindBy<TBaseModel>; - carryForward: any; + findBy: FindBy<TBaseModel>; + carryForward: any; } export interface OnDelete<TBaseModel extends BaseModel> { - deleteBy: DeleteBy<TBaseModel>; - carryForward: any; + deleteBy: DeleteBy<TBaseModel>; + carryForward: any; } export interface OnUpdate<TBaseModel extends BaseModel> { - updateBy: UpdateBy<TBaseModel>; - carryForward: any; + updateBy: UpdateBy<TBaseModel>; + carryForward: any; } diff --git a/CommonServer/Types/Database/Permissions/AccessControlPermission.ts b/CommonServer/Types/Database/Permissions/AccessControlPermission.ts index c39fc134ea..51a679aa34 100644 --- a/CommonServer/Types/Database/Permissions/AccessControlPermission.ts +++ b/CommonServer/Types/Database/Permissions/AccessControlPermission.ts @@ -1,389 +1,362 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import Query from '../Query'; -import Select from '../Select'; -import TablePermission from './TablePermission'; -import AccessControlModel from 'Common/Models/AccessControlModel'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import ArrayUtil from 'Common/Types/ArrayUtil'; -import { ColumnAccessControl } from 'Common/Types/BaseDatabase/AccessControl'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import Query from "../Query"; +import Select from "../Select"; +import TablePermission from "./TablePermission"; +import AccessControlModel from "Common/Models/AccessControlModel"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import ArrayUtil from "Common/Types/ArrayUtil"; +import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; import DatabaseCommonInteractionPropsUtil, { - PermissionType, -} from 'Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; -import ObjectID from 'Common/Types/ObjectID'; + PermissionType, +} from "Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; +import ObjectID from "Common/Types/ObjectID"; import Permission, { - PermissionHelper, - UserPermission, -} from 'Common/Types/Permission'; + PermissionHelper, + UserPermission, +} from "Common/Types/Permission"; export default class AccessControlPermission { - public static async checkAccessControlBlockPermissionByModel< - TBaseModel extends BaseModel - >(data: { - fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; - modelType: { new (): TBaseModel }; - type: DatabaseRequestType; - props: DatabaseCommonInteractionProps; - }): Promise<void> { - const { modelType, props, type } = data; + public static async checkAccessControlBlockPermissionByModel< + TBaseModel extends BaseModel, + >(data: { + fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; + modelType: { new (): TBaseModel }; + type: DatabaseRequestType; + props: DatabaseCommonInteractionProps; + }): Promise<void> { + const { modelType, props, type } = data; - if (props.isRoot || props.isMasterAdmin) { - return; - } - - TablePermission.checkTableLevelBlockPermissions(modelType, props, type); - - const blockPermissionWithLabels: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Block - ).filter((permission: UserPermission) => { - return permission.labelIds && permission.labelIds.length > 0; - }); - - if (blockPermissionWithLabels.length === 0) { - return; - } - - const modelPermissions: Array<Permission> = - TablePermission.getTablePermission(modelType, type); - - const blockPermissionsBelongToThisModel: Array<UserPermission> = - blockPermissionWithLabels.filter( - (blockPermission: UserPermission) => { - let isModelPermission: boolean = false; - - for (const permission of modelPermissions) { - if ( - permission.toString() === - blockPermission.permission.toString() - ) { - isModelPermission = true; - break; - } - } - - return isModelPermission; - } - ); - - if (blockPermissionsBelongToThisModel.length === 0) { - return; - } - - // now check if the user has any of these labels in the block list, for this we need to fetch the model first. - const fetchedModel: TBaseModel | null = - await data.fetchModelWithAccessControlIds(); - - if (!fetchedModel) { - throw new BadDataException(`${modelType.name} not found.`); - } - - for (const blockPermissionBelongToThisModel of blockPermissionsBelongToThisModel) { - const blockPermissionLabelIds: Array<ObjectID> = - blockPermissionBelongToThisModel.labelIds || []; - - const blockPermissionLabelIdAsString: Array<string> = - blockPermissionLabelIds.map((id: ObjectID) => { - return id.toString(); - }); - - if (blockPermissionLabelIds.length === 0) { - continue; - } - - const model: TBaseModel = fetchedModel; - - const modelAccessControlColumnName: string | null = - model.getAccessControlColumn(); - - if (modelAccessControlColumnName) { - const modelAccessControl: Array<AccessControlModel> = - (model.getColumnValue( - modelAccessControlColumnName - ) as Array<AccessControlModel>) || []; - - for (const accessControl of modelAccessControl) { - if (!accessControl.id) { - continue; - } - - if ( - blockPermissionLabelIdAsString.includes( - accessControl.id.toString() - ) - ) { - throw new NotAuthorizedException( - `You are not authorized to ${type.toLowerCase()} this ${ - model.singularName - } because ${ - blockPermissionBelongToThisModel.permission - } is in your team's permission block list.` - ); - } - } - } - } + if (props.isRoot || props.isMasterAdmin) { + return; } - public static async checkAccessControlPermissionByModel< - TBaseModel extends BaseModel - >(data: { - fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; - modelType: { new (): TBaseModel }; - props: DatabaseCommonInteractionProps; - type: DatabaseRequestType; - }): Promise<void> { - const { modelType, props, type } = data; + TablePermission.checkTableLevelBlockPermissions(modelType, props, type); - if (props.isRoot || props.isMasterAdmin) { - return; + const blockPermissionWithLabels: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Block, + ).filter((permission: UserPermission) => { + return permission.labelIds && permission.labelIds.length > 0; + }); + + if (blockPermissionWithLabels.length === 0) { + return; + } + + const modelPermissions: Array<Permission> = + TablePermission.getTablePermission(modelType, type); + + const blockPermissionsBelongToThisModel: Array<UserPermission> = + blockPermissionWithLabels.filter((blockPermission: UserPermission) => { + let isModelPermission: boolean = false; + + for (const permission of modelPermissions) { + if (permission.toString() === blockPermission.permission.toString()) { + isModelPermission = true; + break; + } } - // Check if the user has permission to delete or update the object in this table. - TablePermission.checkTableLevelPermissions(modelType, props, type); + return isModelPermission; + }); - // if the control is here, then the user has table level permissions. - const model: TBaseModel = new modelType(); - const modelAccessControlColumnName: string | null = - model.getAccessControlColumn(); + if (blockPermissionsBelongToThisModel.length === 0) { + return; + } - if (modelAccessControlColumnName) { - const accessControlIdsWhcihUserHasAccessTo: Array<ObjectID> = - this.getAccessControlIdsForModel(modelType, props, type); + // now check if the user has any of these labels in the block list, for this we need to fetch the model first. + const fetchedModel: TBaseModel | null = + await data.fetchModelWithAccessControlIds(); - if (accessControlIdsWhcihUserHasAccessTo.length === 0) { - return; // The user has access to all resources, if no labels are specified. - } + if (!fetchedModel) { + throw new BadDataException(`${modelType.name} not found.`); + } - const fetchedModel: TBaseModel | null = - await data.fetchModelWithAccessControlIds(); + for (const blockPermissionBelongToThisModel of blockPermissionsBelongToThisModel) { + const blockPermissionLabelIds: Array<ObjectID> = + blockPermissionBelongToThisModel.labelIds || []; - if (!fetchedModel) { - throw new BadDataException(`${model.singularName} not found.`); - } + const blockPermissionLabelIdAsString: Array<string> = + blockPermissionLabelIds.map((id: ObjectID) => { + return id.toString(); + }); - const accessControlIdsWhichUserHasAccessToAsStrings: Array<string> = - accessControlIdsWhcihUserHasAccessTo.map((id: ObjectID) => { - return id.toString(); - }) || []; + if (blockPermissionLabelIds.length === 0) { + continue; + } - // Check if the object has any of these access control ids. if not, then throw an error. - const modelAccessControl: Array<AccessControlModel> = - (fetchedModel.getColumnValue( - modelAccessControlColumnName - ) as Array<AccessControlModel>) || []; + const model: TBaseModel = fetchedModel; - const modelAccessControlNames: Array<string> = []; + const modelAccessControlColumnName: string | null = + model.getAccessControlColumn(); - for (const accessControl of modelAccessControl) { - if (!accessControl.id) { - continue; - } + if (modelAccessControlColumnName) { + const modelAccessControl: Array<AccessControlModel> = + (model.getColumnValue( + modelAccessControlColumnName, + ) as Array<AccessControlModel>) || []; - if ( - accessControlIdsWhichUserHasAccessToAsStrings.includes( - accessControl.id.toString() - ) - ) { - return; - } + for (const accessControl of modelAccessControl) { + if (!accessControl.id) { + continue; + } - const accessControlName: string = accessControl.getColumnValue( - 'name' - ) as string; - - if (accessControlName) { - modelAccessControlNames.push(accessControlName); - } - } - - let errorString: string = `You do not have permission to ${type.toLowerCase()} this ${ + if ( + blockPermissionLabelIdAsString.includes(accessControl.id.toString()) + ) { + throw new NotAuthorizedException( + `You are not authorized to ${type.toLowerCase()} this ${ model.singularName - }.`; - - if (modelAccessControlNames.length > 0) { - errorString += ` You need to have one of the following labels: ${modelAccessControlNames.join( - ', ' - )}.`; - } else { - errorString = ` You do not have permission to ${type.toLowerCase()} ${ - model.singularName - } without any labels.`; - } - - throw new NotAuthorizedException(errorString); - } - } - - public static async addAccessControlIdsToQuery< - TBaseModel extends BaseModel - >( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - select: Select<TBaseModel> | null, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): Promise<Query<TBaseModel>> { - const model: BaseModel = new modelType(); - - // if the model has access control column, then add the access control labels to the query. - if (model.getAccessControlColumn()) { - const accessControlIds: Array<ObjectID> = - this.getAccessControlIdsForQuery( - modelType, - query, - select, - props, - type - ); - - if (accessControlIds.length > 0) { - (query as any)[model.getAccessControlColumn() as string] = - accessControlIds; - } - } - - return query; - } - - public static getAccessControlIdsForModel( - modelType: BaseModelType, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): Array<ObjectID> { - let labelIds: Array<ObjectID> = []; - - // check model level permissions. - - const modelLevelPermissions: Array<Permission> = - TablePermission.getTablePermission(modelType, type); - - const modelLevelLabelIds: Array<ObjectID> = - this.getAccessControlIdsByPermissions(modelLevelPermissions, props); - - labelIds = [...labelIds, ...modelLevelLabelIds]; - - // get distinct labelIds - const distinctLabelIds: Array<ObjectID> = - ArrayUtil.removeDuplicatesFromObjectIDArray(labelIds); - - return distinctLabelIds; - } - - public static getAccessControlIdsForQuery<TBaseModel extends BaseModel>( - modelType: BaseModelType, - query: Query<TBaseModel>, - select: Select<TBaseModel> | null, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): Array<ObjectID> { - const model: BaseModel = new modelType(); - - let labelIds: Array<ObjectID> = []; - - let columnsToCheckPermissionFor: Array<string> = Object.keys(query); - - if (select) { - columnsToCheckPermissionFor = [ - ...columnsToCheckPermissionFor, - ...Object.keys(select), - ]; - } - - labelIds = this.getAccessControlIdsForModel(modelType, props, type); - - for (const column of columnsToCheckPermissionFor) { - const accessControl: ColumnAccessControl | null = - model.getColumnAccessControlFor(column); - - if (!accessControl) { - continue; - } - - if (type === DatabaseRequestType.Read && accessControl.read) { - const columnReadLabelIds: Array<ObjectID> = - this.getAccessControlIdsByPermissions( - accessControl.read, - props - ); - - labelIds = [...labelIds, ...columnReadLabelIds]; - } - - if (type === DatabaseRequestType.Create && accessControl.create) { - const columnCreateLabelIds: Array<ObjectID> = - this.getAccessControlIdsByPermissions( - accessControl.create, - props - ); - - labelIds = [...labelIds, ...columnCreateLabelIds]; - } - - if (type === DatabaseRequestType.Update && accessControl.update) { - const columnUpdateLabelIds: Array<ObjectID> = - this.getAccessControlIdsByPermissions( - accessControl.update, - props - ); - - labelIds = [...labelIds, ...columnUpdateLabelIds]; - } - } - - // get distinct labelIds - const distinctLabelIds: Array<ObjectID> = - ArrayUtil.removeDuplicatesFromObjectIDArray(labelIds); - return distinctLabelIds; - } - - private static getAccessControlIdsByPermissions( - permissions: Array<Permission>, - props: DatabaseCommonInteractionProps - ): Array<ObjectID> { - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow + } because ${ + blockPermissionBelongToThisModel.permission + } is in your team's permission block list.`, ); + } + } + } + } + } - const nonAccessControlPermissionPermission: Array<Permission> = - PermissionHelper.getNonAccessControlPermissions(userPermissions); + public static async checkAccessControlPermissionByModel< + TBaseModel extends BaseModel, + >(data: { + fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; + modelType: { new (): TBaseModel }; + props: DatabaseCommonInteractionProps; + type: DatabaseRequestType; + }): Promise<void> { + const { modelType, props, type } = data; - const accessControlPermissions: Array<UserPermission> = - PermissionHelper.getAccessControlPermissions(userPermissions); + if (props.isRoot || props.isMasterAdmin) { + return; + } - let labelIds: Array<ObjectID> = []; + // Check if the user has permission to delete or update the object in this table. + TablePermission.checkTableLevelPermissions(modelType, props, type); + + // if the control is here, then the user has table level permissions. + const model: TBaseModel = new modelType(); + const modelAccessControlColumnName: string | null = + model.getAccessControlColumn(); + + if (modelAccessControlColumnName) { + const accessControlIdsWhcihUserHasAccessTo: Array<ObjectID> = + this.getAccessControlIdsForModel(modelType, props, type); + + if (accessControlIdsWhcihUserHasAccessTo.length === 0) { + return; // The user has access to all resources, if no labels are specified. + } + + const fetchedModel: TBaseModel | null = + await data.fetchModelWithAccessControlIds(); + + if (!fetchedModel) { + throw new BadDataException(`${model.singularName} not found.`); + } + + const accessControlIdsWhichUserHasAccessToAsStrings: Array<string> = + accessControlIdsWhcihUserHasAccessTo.map((id: ObjectID) => { + return id.toString(); + }) || []; + + // Check if the object has any of these access control ids. if not, then throw an error. + const modelAccessControl: Array<AccessControlModel> = + (fetchedModel.getColumnValue( + modelAccessControlColumnName, + ) as Array<AccessControlModel>) || []; + + const modelAccessControlNames: Array<string> = []; + + for (const accessControl of modelAccessControl) { + if (!accessControl.id) { + continue; + } if ( - PermissionHelper.doesPermissionsIntersect( - permissions, - nonAccessControlPermissionPermission - ) + accessControlIdsWhichUserHasAccessToAsStrings.includes( + accessControl.id.toString(), + ) ) { - return []; // if this is intersecting, then return empty array. We dont need to check for access control. + return; } - for (const permission of permissions) { - for (const accessControlPermission of accessControlPermissions) { - if ( - accessControlPermission.permission === permission && - accessControlPermission.labelIds.length > 0 - ) { - labelIds = [ - ...labelIds, - ...accessControlPermission.labelIds, - ]; - } - } + const accessControlName: string = accessControl.getColumnValue( + "name", + ) as string; + + if (accessControlName) { + modelAccessControlNames.push(accessControlName); } + } - // remove duplicates - labelIds = ArrayUtil.removeDuplicatesFromObjectIDArray(labelIds); + let errorString: string = `You do not have permission to ${type.toLowerCase()} this ${ + model.singularName + }.`; - return labelIds; + if (modelAccessControlNames.length > 0) { + errorString += ` You need to have one of the following labels: ${modelAccessControlNames.join( + ", ", + )}.`; + } else { + errorString = ` You do not have permission to ${type.toLowerCase()} ${ + model.singularName + } without any labels.`; + } + + throw new NotAuthorizedException(errorString); } + } + + public static async addAccessControlIdsToQuery<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + select: Select<TBaseModel> | null, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): Promise<Query<TBaseModel>> { + const model: BaseModel = new modelType(); + + // if the model has access control column, then add the access control labels to the query. + if (model.getAccessControlColumn()) { + const accessControlIds: Array<ObjectID> = + this.getAccessControlIdsForQuery(modelType, query, select, props, type); + + if (accessControlIds.length > 0) { + (query as any)[model.getAccessControlColumn() as string] = + accessControlIds; + } + } + + return query; + } + + public static getAccessControlIdsForModel( + modelType: BaseModelType, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): Array<ObjectID> { + let labelIds: Array<ObjectID> = []; + + // check model level permissions. + + const modelLevelPermissions: Array<Permission> = + TablePermission.getTablePermission(modelType, type); + + const modelLevelLabelIds: Array<ObjectID> = + this.getAccessControlIdsByPermissions(modelLevelPermissions, props); + + labelIds = [...labelIds, ...modelLevelLabelIds]; + + // get distinct labelIds + const distinctLabelIds: Array<ObjectID> = + ArrayUtil.removeDuplicatesFromObjectIDArray(labelIds); + + return distinctLabelIds; + } + + public static getAccessControlIdsForQuery<TBaseModel extends BaseModel>( + modelType: BaseModelType, + query: Query<TBaseModel>, + select: Select<TBaseModel> | null, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): Array<ObjectID> { + const model: BaseModel = new modelType(); + + let labelIds: Array<ObjectID> = []; + + let columnsToCheckPermissionFor: Array<string> = Object.keys(query); + + if (select) { + columnsToCheckPermissionFor = [ + ...columnsToCheckPermissionFor, + ...Object.keys(select), + ]; + } + + labelIds = this.getAccessControlIdsForModel(modelType, props, type); + + for (const column of columnsToCheckPermissionFor) { + const accessControl: ColumnAccessControl | null = + model.getColumnAccessControlFor(column); + + if (!accessControl) { + continue; + } + + if (type === DatabaseRequestType.Read && accessControl.read) { + const columnReadLabelIds: Array<ObjectID> = + this.getAccessControlIdsByPermissions(accessControl.read, props); + + labelIds = [...labelIds, ...columnReadLabelIds]; + } + + if (type === DatabaseRequestType.Create && accessControl.create) { + const columnCreateLabelIds: Array<ObjectID> = + this.getAccessControlIdsByPermissions(accessControl.create, props); + + labelIds = [...labelIds, ...columnCreateLabelIds]; + } + + if (type === DatabaseRequestType.Update && accessControl.update) { + const columnUpdateLabelIds: Array<ObjectID> = + this.getAccessControlIdsByPermissions(accessControl.update, props); + + labelIds = [...labelIds, ...columnUpdateLabelIds]; + } + } + + // get distinct labelIds + const distinctLabelIds: Array<ObjectID> = + ArrayUtil.removeDuplicatesFromObjectIDArray(labelIds); + return distinctLabelIds; + } + + private static getAccessControlIdsByPermissions( + permissions: Array<Permission>, + props: DatabaseCommonInteractionProps, + ): Array<ObjectID> { + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ); + + const nonAccessControlPermissionPermission: Array<Permission> = + PermissionHelper.getNonAccessControlPermissions(userPermissions); + + const accessControlPermissions: Array<UserPermission> = + PermissionHelper.getAccessControlPermissions(userPermissions); + + let labelIds: Array<ObjectID> = []; + + if ( + PermissionHelper.doesPermissionsIntersect( + permissions, + nonAccessControlPermissionPermission, + ) + ) { + return []; // if this is intersecting, then return empty array. We dont need to check for access control. + } + + for (const permission of permissions) { + for (const accessControlPermission of accessControlPermissions) { + if ( + accessControlPermission.permission === permission && + accessControlPermission.labelIds.length > 0 + ) { + labelIds = [...labelIds, ...accessControlPermission.labelIds]; + } + } + } + + // remove duplicates + labelIds = ArrayUtil.removeDuplicatesFromObjectIDArray(labelIds); + + return labelIds; + } } diff --git a/CommonServer/Types/Database/Permissions/BasePermission.ts b/CommonServer/Types/Database/Permissions/BasePermission.ts index 38d302cf99..2135936f86 100644 --- a/CommonServer/Types/Database/Permissions/BasePermission.ts +++ b/CommonServer/Types/Database/Permissions/BasePermission.ts @@ -1,143 +1,129 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import Query from '../Query'; -import QueryUtil from '../QueryUtil'; -import Select from '../Select'; -import AccessControlPermission from './AccessControlPermission'; -import PermissionUtil from './PermissionsUtil'; -import PublicPermission from './PublicPermission'; -import QueryPermission from './QueryPermission'; -import SelectPermission from './SelectPermission'; -import TablePermission from './TablePermission'; -import TenantPermission from './TenantPermission'; -import UserPermissions from './UserPermission'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import { TableColumnMetadata } from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import ObjectID from 'Common/Types/ObjectID'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import Query from "../Query"; +import QueryUtil from "../QueryUtil"; +import Select from "../Select"; +import AccessControlPermission from "./AccessControlPermission"; +import PermissionUtil from "./PermissionsUtil"; +import PublicPermission from "./PublicPermission"; +import QueryPermission from "./QueryPermission"; +import SelectPermission from "./SelectPermission"; +import TablePermission from "./TablePermission"; +import TenantPermission from "./TenantPermission"; +import UserPermissions from "./UserPermission"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import { TableColumnMetadata } from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import ObjectID from "Common/Types/ObjectID"; export interface CheckPermissionBaseInterface<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; + query: Query<TBaseModel>; } export default class BasePermission { - public static async checkPermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - select: Select<TBaseModel> | null, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): Promise<CheckPermissionBaseInterface<TBaseModel>> { - const model: BaseModel = new modelType(); + public static async checkPermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + select: Select<TBaseModel> | null, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): Promise<CheckPermissionBaseInterface<TBaseModel>> { + const model: BaseModel = new modelType(); - if (props.isRoot || props.isMasterAdmin) { - query = await PermissionUtil.addTenantScopeToQueryAsRoot( - modelType, - query, - props - ); - } - - if (!props.isRoot && !props.isMasterAdmin) { - //check if the user is logged in. - PublicPermission.checkIfUserIsLoggedIn(modelType, props, type); - - // add tenant scope. - query = await TenantPermission.addTenantScopeToQuery( - modelType, - query, - select, - props, - type - ); - - // add user scope if any - query = await UserPermissions.addUserScopeToQuery( - modelType, - query, - props - ); - - if (!props.isMultiTenantRequest) { - // We will check for this permission in recursive function. - - // check model level permissions. - TablePermission.checkTableLevelPermissions( - modelType, - props, - type - ); - - // We will check for this permission in recursive function. - // check query permissions. - QueryPermission.checkQueryPermission(modelType, query, props); - - query = - await AccessControlPermission.addAccessControlIdsToQuery( - modelType, - query, - select, - props, - type - ); - - /// Implement Related Permissions. - if (model.canAccessIfCanReadOn) { - const tableColumnMetadata: TableColumnMetadata = - model.getTableColumnMetadata( - model.canAccessIfCanReadOn - ); - - if ( - tableColumnMetadata && - tableColumnMetadata.modelType && - (tableColumnMetadata.type === TableColumnType.Entity || - tableColumnMetadata.type === - TableColumnType.EntityArray) - ) { - const accessControlIds: Array<ObjectID> = - AccessControlPermission.getAccessControlIdsForQuery( - tableColumnMetadata.modelType, - {}, - { - _id: true, - }, - props, - type - ); - - if (accessControlIds.length > 0) { - const tableColumnMetadataModel: BaseModel = - new tableColumnMetadata.modelType(); - - (query as any)[ - model.canAccessIfCanReadOn as string - ] = { - [tableColumnMetadataModel.getAccessControlColumn() as string]: - accessControlIds, - }; - } - } - } - - if (select) { - // check query permission. - SelectPermission.checkSelectPermission( - modelType, - select, - props - ); - QueryPermission.checkRelationQueryPermission( - modelType, - select, - props - ); - } - } - } - - query = QueryUtil.serializeQuery(modelType, query); - - return { query }; + if (props.isRoot || props.isMasterAdmin) { + query = await PermissionUtil.addTenantScopeToQueryAsRoot( + modelType, + query, + props, + ); } + + if (!props.isRoot && !props.isMasterAdmin) { + //check if the user is logged in. + PublicPermission.checkIfUserIsLoggedIn(modelType, props, type); + + // add tenant scope. + query = await TenantPermission.addTenantScopeToQuery( + modelType, + query, + select, + props, + type, + ); + + // add user scope if any + query = await UserPermissions.addUserScopeToQuery( + modelType, + query, + props, + ); + + if (!props.isMultiTenantRequest) { + // We will check for this permission in recursive function. + + // check model level permissions. + TablePermission.checkTableLevelPermissions(modelType, props, type); + + // We will check for this permission in recursive function. + // check query permissions. + QueryPermission.checkQueryPermission(modelType, query, props); + + query = await AccessControlPermission.addAccessControlIdsToQuery( + modelType, + query, + select, + props, + type, + ); + + /// Implement Related Permissions. + if (model.canAccessIfCanReadOn) { + const tableColumnMetadata: TableColumnMetadata = + model.getTableColumnMetadata(model.canAccessIfCanReadOn); + + if ( + tableColumnMetadata && + tableColumnMetadata.modelType && + (tableColumnMetadata.type === TableColumnType.Entity || + tableColumnMetadata.type === TableColumnType.EntityArray) + ) { + const accessControlIds: Array<ObjectID> = + AccessControlPermission.getAccessControlIdsForQuery( + tableColumnMetadata.modelType, + {}, + { + _id: true, + }, + props, + type, + ); + + if (accessControlIds.length > 0) { + const tableColumnMetadataModel: BaseModel = + new tableColumnMetadata.modelType(); + + (query as any)[model.canAccessIfCanReadOn as string] = { + [tableColumnMetadataModel.getAccessControlColumn() as string]: + accessControlIds, + }; + } + } + } + + if (select) { + // check query permission. + SelectPermission.checkSelectPermission(modelType, select, props); + QueryPermission.checkRelationQueryPermission( + modelType, + select, + props, + ); + } + } + } + + query = QueryUtil.serializeQuery(modelType, query); + + return { query }; + } } diff --git a/CommonServer/Types/Database/Permissions/BillingPermission.ts b/CommonServer/Types/Database/Permissions/BillingPermission.ts index e168dd9bb8..56dfdb3b36 100644 --- a/CommonServer/Types/Database/Permissions/BillingPermission.ts +++ b/CommonServer/Types/Database/Permissions/BillingPermission.ts @@ -1,102 +1,93 @@ -import { IsBillingEnabled, getAllEnvVars } from '../../../EnvironmentConfig'; -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import PaymentRequiredException from 'Common/Types/Exception/PaymentRequiredException'; +import { IsBillingEnabled, getAllEnvVars } from "../../../EnvironmentConfig"; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import PaymentRequiredException from "Common/Types/Exception/PaymentRequiredException"; export default class BillingPermissions { - public static checkBillingPermissions( - modelType: BaseModelType, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): void { - /// Check billing permissions. + public static checkBillingPermissions( + modelType: BaseModelType, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): void { + /// Check billing permissions. - if (IsBillingEnabled && props.currentPlan) { - const model: BaseModel = new modelType(); + if (IsBillingEnabled && props.currentPlan) { + const model: BaseModel = new modelType(); - if ( - props.isSubscriptionUnpaid && - !model.allowAccessIfSubscriptionIsUnpaid - ) { - throw new PaymentRequiredException( - 'Your current subscription is in an unpaid state. Looks like your payment method failed. Please add a new payment method in Project Settings > Invoices to pay unpaid invoices.' - ); - } + if ( + props.isSubscriptionUnpaid && + !model.allowAccessIfSubscriptionIsUnpaid + ) { + throw new PaymentRequiredException( + "Your current subscription is in an unpaid state. Looks like your payment method failed. Please add a new payment method in Project Settings > Invoices to pay unpaid invoices.", + ); + } - if ( - type === DatabaseRequestType.Create && - model.createBillingPlan - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - model.createBillingPlan, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - model.createBillingPlan + - ' to access this feature' - ); - } - } - - if ( - type === DatabaseRequestType.Update && - model.updateBillingPlan - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - model.updateBillingPlan, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - model.createBillingPlan + - ' to access this feature' - ); - } - } - - if ( - type === DatabaseRequestType.Delete && - model.deleteBillingPlan - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - model.deleteBillingPlan, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - model.createBillingPlan + - ' to access this feature' - ); - } - } - - if (type === DatabaseRequestType.Read && model.readBillingPlan) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - model.readBillingPlan, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - model.createBillingPlan + - ' to access this feature' - ); - } - } + if (type === DatabaseRequestType.Create && model.createBillingPlan) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + model.createBillingPlan, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + model.createBillingPlan + + " to access this feature", + ); } + } + + if (type === DatabaseRequestType.Update && model.updateBillingPlan) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + model.updateBillingPlan, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + model.createBillingPlan + + " to access this feature", + ); + } + } + + if (type === DatabaseRequestType.Delete && model.deleteBillingPlan) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + model.deleteBillingPlan, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + model.createBillingPlan + + " to access this feature", + ); + } + } + + if (type === DatabaseRequestType.Read && model.readBillingPlan) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + model.readBillingPlan, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + model.createBillingPlan + + " to access this feature", + ); + } + } } + } } diff --git a/CommonServer/Types/Database/Permissions/ColumnPermission.ts b/CommonServer/Types/Database/Permissions/ColumnPermission.ts index 0fd54ef06d..0b7a24030c 100644 --- a/CommonServer/Types/Database/Permissions/ColumnPermission.ts +++ b/CommonServer/Types/Database/Permissions/ColumnPermission.ts @@ -1,218 +1,217 @@ -import { IsBillingEnabled, getAllEnvVars } from '../../../EnvironmentConfig'; -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import BaseModel from 'Common/Models/BaseModel'; -import { ColumnAccessControl } from 'Common/Types/BaseDatabase/AccessControl'; -import ColumnBillingAccessControl from 'Common/Types/BaseDatabase/ColumnBillingAccessControl'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import { IsBillingEnabled, getAllEnvVars } from "../../../EnvironmentConfig"; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import BaseModel from "Common/Models/BaseModel"; +import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl"; +import ColumnBillingAccessControl from "Common/Types/BaseDatabase/ColumnBillingAccessControl"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; import DatabaseCommonInteractionPropsUtil, { - PermissionType, -} from 'Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import Columns from 'Common/Types/Database/Columns'; -import { TableColumnMetadata } from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import PaymentRequiredException from 'Common/Types/Exception/PaymentRequiredException'; + PermissionType, +} from "Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import Columns from "Common/Types/Database/Columns"; +import { TableColumnMetadata } from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import PaymentRequiredException from "Common/Types/Exception/PaymentRequiredException"; import Permission, { - PermissionHelper, - UserPermission, -} from 'Common/Types/Permission'; + PermissionHelper, + UserPermission, +} from "Common/Types/Permission"; export default class ColumnPermissions { - public static getExcludedColumnNames(): string[] { - const returnArr: Array<string> = [ - '_id', - 'createdAt', - 'deletedAt', - 'updatedAt', - 'version', - ]; + public static getExcludedColumnNames(): string[] { + const returnArr: Array<string> = [ + "_id", + "createdAt", + "deletedAt", + "updatedAt", + "version", + ]; - return returnArr; + return returnArr; + } + + public static getModelColumnsByPermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + userPermissions: Array<UserPermission>, + requestType: DatabaseRequestType, + ): Columns { + const model: BaseModel = new modelType(); + const accessControl: Dictionary<ColumnAccessControl> = + model.getColumnAccessControlForAllColumns(); + + const columns: Array<string> = []; + + const permissions: Array<Permission> = userPermissions.map( + (item: UserPermission) => { + return item.permission; + }, + ); + + for (const key in accessControl) { + let columnPermissions: Array<Permission> = []; + + if (requestType === DatabaseRequestType.Read) { + columnPermissions = accessControl[key]?.read || []; + } + + if (requestType === DatabaseRequestType.Create) { + columnPermissions = accessControl[key]?.create || []; + } + + if (requestType === DatabaseRequestType.Update) { + columnPermissions = accessControl[key]?.update || []; + } + + if (requestType === DatabaseRequestType.Delete) { + throw new BadDataException("Invalid request type delete"); + } + + if ( + columnPermissions && + PermissionHelper.doesPermissionsIntersect( + permissions, + columnPermissions, + ) + ) { + columns.push(key); + } } - public static getModelColumnsByPermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - userPermissions: Array<UserPermission>, - requestType: DatabaseRequestType - ): Columns { - const model: BaseModel = new modelType(); - const accessControl: Dictionary<ColumnAccessControl> = - model.getColumnAccessControlForAllColumns(); + return new Columns(columns); + } - const columns: Array<string> = []; + public static checkDataColumnPermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + data: TBaseModel, + props: DatabaseCommonInteractionProps, + requestType: DatabaseRequestType, + ): void { + const model: BaseModel = new modelType(); + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ); - const permissions: Array<Permission> = userPermissions.map( - (item: UserPermission) => { - return item.permission; - } + const permissionColumns: Columns = this.getModelColumnsByPermissions( + modelType, + userPermissions, + requestType, + ); + + const excludedColumnNames: Array<string> = this.getExcludedColumnNames(); + + const tableColumns: Array<string> = model.getTableColumns().columns; + + for (const key of Object.keys(data)) { + if ((data as any)[key] === undefined) { + continue; + } + + if (excludedColumnNames.includes(key)) { + continue; + } + + if (!tableColumns.includes(key)) { + continue; + } + + const tableColumnMetadata: TableColumnMetadata = + model.getTableColumnMetadata(key); + + if (!tableColumnMetadata) { + throw new BadDataException( + `No TableColumnMetadata found for ${key} column of ${model.singularName}`, ); + } - for (const key in accessControl) { - let columnPermissions: Array<Permission> = []; + if (tableColumnMetadata.type === TableColumnType.Slug) { + continue; + } - if (requestType === DatabaseRequestType.Read) { - columnPermissions = accessControl[key]?.read || []; - } - - if (requestType === DatabaseRequestType.Create) { - columnPermissions = accessControl[key]?.create || []; - } - - if (requestType === DatabaseRequestType.Update) { - columnPermissions = accessControl[key]?.update || []; - } - - if (requestType === DatabaseRequestType.Delete) { - throw new BadDataException('Invalid request type delete'); - } - - if ( - columnPermissions && - PermissionHelper.doesPermissionsIntersect( - permissions, - columnPermissions - ) - ) { - columns.push(key); - } + if ( + !permissionColumns.columns.includes(key) && + tableColumns.includes(key) + ) { + if ( + requestType === DatabaseRequestType.Create && + tableColumnMetadata.forceGetDefaultValueOnCreate + ) { + continue; // this is a special case where we want to force the default value on create. } - return new Columns(columns); - } + throw new BadDataException( + `User is not allowed to ${requestType} on ${key} column of ${model.singularName}`, + ); + } - public static checkDataColumnPermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - data: TBaseModel, - props: DatabaseCommonInteractionProps, - requestType: DatabaseRequestType - ): void { - const model: BaseModel = new modelType(); - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow + if ( + IsBillingEnabled && + props.currentPlan && + model.getColumnBillingAccessControl(key) + ) { + const billingAccessControl: ColumnBillingAccessControl = + model.getColumnBillingAccessControl(key); + + if ( + requestType === DatabaseRequestType.Create && + billingAccessControl.create + ) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + billingAccessControl.create, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + billingAccessControl.create + + " to access this feature", ); - - const permissionColumns: Columns = this.getModelColumnsByPermissions( - modelType, - userPermissions, - requestType - ); - - const excludedColumnNames: Array<string> = - this.getExcludedColumnNames(); - - const tableColumns: Array<string> = model.getTableColumns().columns; - - for (const key of Object.keys(data)) { - if ((data as any)[key] === undefined) { - continue; - } - - if (excludedColumnNames.includes(key)) { - continue; - } - - if (!tableColumns.includes(key)) { - continue; - } - - const tableColumnMetadata: TableColumnMetadata = - model.getTableColumnMetadata(key); - - if (!tableColumnMetadata) { - throw new BadDataException( - `No TableColumnMetadata found for ${key} column of ${model.singularName}` - ); - } - - if (tableColumnMetadata.type === TableColumnType.Slug) { - continue; - } - - if ( - !permissionColumns.columns.includes(key) && - tableColumns.includes(key) - ) { - if ( - requestType === DatabaseRequestType.Create && - tableColumnMetadata.forceGetDefaultValueOnCreate - ) { - continue; // this is a special case where we want to force the default value on create. - } - - throw new BadDataException( - `User is not allowed to ${requestType} on ${key} column of ${model.singularName}` - ); - } - - if ( - IsBillingEnabled && - props.currentPlan && - model.getColumnBillingAccessControl(key) - ) { - const billingAccessControl: ColumnBillingAccessControl = - model.getColumnBillingAccessControl(key); - - if ( - requestType === DatabaseRequestType.Create && - billingAccessControl.create - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - billingAccessControl.create, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - billingAccessControl.create + - ' to access this feature' - ); - } - } - - if ( - requestType === DatabaseRequestType.Read && - billingAccessControl.read - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - billingAccessControl.read, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - billingAccessControl.read + - ' to access this feature' - ); - } - } - - if ( - requestType === DatabaseRequestType.Update && - billingAccessControl.update - ) { - if ( - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - billingAccessControl.update, - props.currentPlan, - getAllEnvVars() - ) - ) { - throw new PaymentRequiredException( - 'Please upgrade your plan to ' + - billingAccessControl.update + - ' to access this feature' - ); - } - } - } + } } + + if ( + requestType === DatabaseRequestType.Read && + billingAccessControl.read + ) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + billingAccessControl.read, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + billingAccessControl.read + + " to access this feature", + ); + } + } + + if ( + requestType === DatabaseRequestType.Update && + billingAccessControl.update + ) { + if ( + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + billingAccessControl.update, + props.currentPlan, + getAllEnvVars(), + ) + ) { + throw new PaymentRequiredException( + "Please upgrade your plan to " + + billingAccessControl.update + + " to access this feature", + ); + } + } + } } + } } diff --git a/CommonServer/Types/Database/Permissions/CreatePermission.ts b/CommonServer/Types/Database/Permissions/CreatePermission.ts index fb1de56346..fe1cac12c0 100644 --- a/CommonServer/Types/Database/Permissions/CreatePermission.ts +++ b/CommonServer/Types/Database/Permissions/CreatePermission.ts @@ -1,50 +1,50 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import ColumnPermissions from './ColumnPermission'; -import TablePermission from './TablePermission'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import ColumnPermissions from "./ColumnPermission"; +import TablePermission from "./TablePermission"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default class CreatePermission { - public static checkCreatePermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - data: TBaseModel, - props: DatabaseCommonInteractionProps - ): void { - // If system is making this query then let the query run! - if (props.isRoot || props.isMasterAdmin) { - return; - } - - // check block permissions, if any. Block permission get precedence over allow permissions. - this.checkCreateBlockPermissions(modelType, props); - - TablePermission.checkTableLevelPermissions( - modelType, - props, - DatabaseRequestType.Create - ); - - ColumnPermissions.checkDataColumnPermissions( - modelType, - data, - props, - DatabaseRequestType.Create - ); + public static checkCreatePermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + data: TBaseModel, + props: DatabaseCommonInteractionProps, + ): void { + // If system is making this query then let the query run! + if (props.isRoot || props.isMasterAdmin) { + return; } - public static checkCreateBlockPermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - props: DatabaseCommonInteractionProps - ): void { - // If system is making this query then let the query run! - if (props.isRoot || props.isMasterAdmin) { - return; - } + // check block permissions, if any. Block permission get precedence over allow permissions. + this.checkCreateBlockPermissions(modelType, props); - TablePermission.checkTableLevelBlockPermissions( - modelType, - props, - DatabaseRequestType.Create - ); + TablePermission.checkTableLevelPermissions( + modelType, + props, + DatabaseRequestType.Create, + ); + + ColumnPermissions.checkDataColumnPermissions( + modelType, + data, + props, + DatabaseRequestType.Create, + ); + } + + public static checkCreateBlockPermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + props: DatabaseCommonInteractionProps, + ): void { + // If system is making this query then let the query run! + if (props.isRoot || props.isMasterAdmin) { + return; } + + TablePermission.checkTableLevelBlockPermissions( + modelType, + props, + DatabaseRequestType.Create, + ); + } } diff --git a/CommonServer/Types/Database/Permissions/DeletePermission.ts b/CommonServer/Types/Database/Permissions/DeletePermission.ts index 89fa1ff23c..f7054b9d64 100644 --- a/CommonServer/Types/Database/Permissions/DeletePermission.ts +++ b/CommonServer/Types/Database/Permissions/DeletePermission.ts @@ -1,70 +1,71 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import Query from '../Query'; -import AccessControlUtil from './AccessControlPermission'; -import PermissionUtil from './PermissionsUtil'; -import TablePermission from './TablePermission'; -import TenantPermission from './TenantPermission'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import Query from "../Query"; +import AccessControlUtil from "./AccessControlPermission"; +import PermissionUtil from "./PermissionsUtil"; +import TablePermission from "./TablePermission"; +import TenantPermission from "./TenantPermission"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default class DeletePermission { - public static async checkDeletePermissionByModel< - TBaseModel extends BaseModel - >(data: { - fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; - modelType: { new (): TBaseModel }; - props: DatabaseCommonInteractionProps; - }): Promise<void> { - // check block permission first - await AccessControlUtil.checkAccessControlBlockPermissionByModel<TBaseModel>( - { ...data, type: DatabaseRequestType.Delete } - ); + public static async checkDeletePermissionByModel< + TBaseModel extends BaseModel, + >(data: { + fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; + modelType: { new (): TBaseModel }; + props: DatabaseCommonInteractionProps; + }): Promise<void> { + // check block permission first + await AccessControlUtil.checkAccessControlBlockPermissionByModel<TBaseModel>( + { ...data, type: DatabaseRequestType.Delete }, + ); - await AccessControlUtil.checkAccessControlPermissionByModel<TBaseModel>( - { ...data, type: DatabaseRequestType.Delete } - ); + await AccessControlUtil.checkAccessControlPermissionByModel<TBaseModel>({ + ...data, + type: DatabaseRequestType.Delete, + }); + } + + public static async checkDeletePermission<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + if (props.isRoot || props.isMasterAdmin) { + query = await PermissionUtil.addTenantScopeToQueryAsRoot( + modelType, + query, + props, + ); } - public static async checkDeletePermission<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - if (props.isRoot || props.isMasterAdmin) { - query = await PermissionUtil.addTenantScopeToQueryAsRoot( - modelType, - query, - props - ); - } + if (!props.isRoot && !props.isMasterAdmin) { + // Does the user have permission to delete the object in this table? If no, then throw an error. + TablePermission.checkTableLevelPermissions( + modelType, + props, + DatabaseRequestType.Delete, + ); - if (!props.isRoot && !props.isMasterAdmin) { - // Does the user have permission to delete the object in this table? If no, then throw an error. - TablePermission.checkTableLevelPermissions( - modelType, - props, - DatabaseRequestType.Delete - ); + // Add tenant scope to query. + query = await TenantPermission.addTenantScopeToQuery( + modelType, + query, + null, + props, + DatabaseRequestType.Delete, + ); - // Add tenant scope to query. - query = await TenantPermission.addTenantScopeToQuery( - modelType, - query, - null, - props, - DatabaseRequestType.Delete - ); - - // add access control ids to query - query = await AccessControlUtil.addAccessControlIdsToQuery( - modelType, - query, - null, - props, - DatabaseRequestType.Delete - ); - } - - return query; + // add access control ids to query + query = await AccessControlUtil.addAccessControlIdsToQuery( + modelType, + query, + null, + props, + DatabaseRequestType.Delete, + ); } + + return query; + } } diff --git a/CommonServer/Types/Database/Permissions/Index.ts b/CommonServer/Types/Database/Permissions/Index.ts index a1edb91d6b..24c91cf392 100644 --- a/CommonServer/Types/Database/Permissions/Index.ts +++ b/CommonServer/Types/Database/Permissions/Index.ts @@ -1,79 +1,70 @@ -import Query from '../Query'; -import Select from '../Select'; -import CreatePermission from './CreatePermission'; -import DeletePermission from './DeletePermission'; -import ReadPermission, { CheckReadPermissionType } from './ReadPermission'; -import UpdatePermission from './UpdatePermission'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import Query from "../Query"; +import Select from "../Select"; +import CreatePermission from "./CreatePermission"; +import DeletePermission from "./DeletePermission"; +import ReadPermission, { CheckReadPermissionType } from "./ReadPermission"; +import UpdatePermission from "./UpdatePermission"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"; export default class ModelPermission { - public static async checkDeletePermissionByModel< - TBaseModel extends BaseModel - >(data: { - modelType: { new (): TBaseModel }; - fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; - props: DatabaseCommonInteractionProps; - }): Promise<void> { - return DeletePermission.checkDeletePermissionByModel(data); - } + public static async checkDeletePermissionByModel< + TBaseModel extends BaseModel, + >(data: { + modelType: { new (): TBaseModel }; + fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; + props: DatabaseCommonInteractionProps; + }): Promise<void> { + return DeletePermission.checkDeletePermissionByModel(data); + } - public static async checkUpdatePermissionByModel< - TBaseModel extends BaseModel - >(data: { - modelType: { new (): TBaseModel }; - fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; - props: DatabaseCommonInteractionProps; - }): Promise<void> { - return UpdatePermission.checkUpdatePermissionByModel(data); - } + public static async checkUpdatePermissionByModel< + TBaseModel extends BaseModel, + >(data: { + modelType: { new (): TBaseModel }; + fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; + props: DatabaseCommonInteractionProps; + }): Promise<void> { + return UpdatePermission.checkUpdatePermissionByModel(data); + } - public static async checkDeleteQueryPermission< - TBaseModel extends BaseModel - >( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - return DeletePermission.checkDeletePermission(modelType, query, props); - } + public static async checkDeleteQueryPermission<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + return DeletePermission.checkDeletePermission(modelType, query, props); + } - public static async checkUpdateQueryPermissions< - TBaseModel extends BaseModel - >( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - data: QueryDeepPartialEntity<TBaseModel>, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - return UpdatePermission.checkUpdatePermissions( - modelType, - query, - data, - props - ); - } + public static async checkUpdateQueryPermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + data: QueryDeepPartialEntity<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + return UpdatePermission.checkUpdatePermissions( + modelType, + query, + data, + props, + ); + } - public static checkCreatePermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - data: TBaseModel, - props: DatabaseCommonInteractionProps - ): void { - return CreatePermission.checkCreatePermissions(modelType, data, props); - } + public static checkCreatePermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + data: TBaseModel, + props: DatabaseCommonInteractionProps, + ): void { + return CreatePermission.checkCreatePermissions(modelType, data, props); + } - public static async checkReadQueryPermission<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - select: Select<TBaseModel> | null, - props: DatabaseCommonInteractionProps - ): Promise<CheckReadPermissionType<TBaseModel>> { - return ReadPermission.checkReadPermission( - modelType, - query, - select, - props - ); - } + public static async checkReadQueryPermission<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + select: Select<TBaseModel> | null, + props: DatabaseCommonInteractionProps, + ): Promise<CheckReadPermissionType<TBaseModel>> { + return ReadPermission.checkReadPermission(modelType, query, select, props); + } } diff --git a/CommonServer/Types/Database/Permissions/PermissionsUtil.ts b/CommonServer/Types/Database/Permissions/PermissionsUtil.ts index 1955da8084..d52fb19b32 100644 --- a/CommonServer/Types/Database/Permissions/PermissionsUtil.ts +++ b/CommonServer/Types/Database/Permissions/PermissionsUtil.ts @@ -1,24 +1,22 @@ -import Query from '../Query'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import Query from "../Query"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; export default class PermissionUtil { - public static async addTenantScopeToQueryAsRoot< - TBaseModel extends BaseModel - >( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - const model: BaseModel = new modelType(); + public static async addTenantScopeToQueryAsRoot<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + const model: BaseModel = new modelType(); - const tenantColumn: string | null = model.getTenantColumn(); + const tenantColumn: string | null = model.getTenantColumn(); - // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query. - if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) { - (query as any)[tenantColumn] = props.tenantId; - } - - return query; + // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query. + if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) { + (query as any)[tenantColumn] = props.tenantId; } + + return query; + } } diff --git a/CommonServer/Types/Database/Permissions/PublicPermission.ts b/CommonServer/Types/Database/Permissions/PublicPermission.ts index 7396494656..068928b0b9 100644 --- a/CommonServer/Types/Database/Permissions/PublicPermission.ts +++ b/CommonServer/Types/Database/Permissions/PublicPermission.ts @@ -1,43 +1,43 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import TablePermission from './TablePermission'; -import { BaseModelType } from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException'; -import Permission from 'Common/Types/Permission'; -import UserType from 'Common/Types/UserType'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import TablePermission from "./TablePermission"; +import { BaseModelType } from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import NotAuthenticatedException from "Common/Types/Exception/NotAuthenticatedException"; +import Permission from "Common/Types/Permission"; +import UserType from "Common/Types/UserType"; export default class PublicPermission { - public static isPublicPermissionAllowed( - modelType: BaseModelType, - type: DatabaseRequestType - ): boolean { - let isPublicAllowed: boolean = false; - isPublicAllowed = TablePermission.getTablePermission( - modelType, - type - ).includes(Permission.Public); - return isPublicAllowed; - } - - public static checkIfUserIsLoggedIn( - modelType: BaseModelType, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): void { - // 1 CHECK: PUBLIC check -- Check if this is a public request and if public is allowed. - - if (!this.isPublicPermissionAllowed(modelType, type) && !props.userId) { - if (props.userType === UserType.API) { - // if its an API request then continue. - return; - } - - // this means the record is not publicly createable and the user is not logged in. - throw new NotAuthenticatedException( - `A user should be logged in to ${type} record of ${ - new modelType().singularName - }.` - ); - } + public static isPublicPermissionAllowed( + modelType: BaseModelType, + type: DatabaseRequestType, + ): boolean { + let isPublicAllowed: boolean = false; + isPublicAllowed = TablePermission.getTablePermission( + modelType, + type, + ).includes(Permission.Public); + return isPublicAllowed; + } + + public static checkIfUserIsLoggedIn( + modelType: BaseModelType, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): void { + // 1 CHECK: PUBLIC check -- Check if this is a public request and if public is allowed. + + if (!this.isPublicPermissionAllowed(modelType, type) && !props.userId) { + if (props.userType === UserType.API) { + // if its an API request then continue. + return; + } + + // this means the record is not publicly createable and the user is not logged in. + throw new NotAuthenticatedException( + `A user should be logged in to ${type} record of ${ + new modelType().singularName + }.`, + ); } + } } diff --git a/CommonServer/Types/Database/Permissions/QueryPermission.ts b/CommonServer/Types/Database/Permissions/QueryPermission.ts index e5ce04253d..747310c175 100644 --- a/CommonServer/Types/Database/Permissions/QueryPermission.ts +++ b/CommonServer/Types/Database/Permissions/QueryPermission.ts @@ -1,184 +1,171 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import Query from '../Query'; -import Select from '../Select'; -import ColumnPermissions from './ColumnPermission'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import Query from "../Query"; +import Select from "../Select"; +import ColumnPermissions from "./ColumnPermission"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; import DatabaseCommonInteractionPropsUtil, { - PermissionType, -} from 'Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil'; -import Columns from 'Common/Types/Database/Columns'; -import { TableColumnMetadata } from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; -import { JSONObject } from 'Common/Types/JSON'; + PermissionType, +} from "Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil"; +import Columns from "Common/Types/Database/Columns"; +import { TableColumnMetadata } from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; +import { JSONObject } from "Common/Types/JSON"; import Permission, { - PermissionHelper, - UserPermission, -} from 'Common/Types/Permission'; -import Typeof from 'Common/Types/Typeof'; + PermissionHelper, + UserPermission, +} from "Common/Types/Permission"; +import Typeof from "Common/Types/Typeof"; export default class QueryPermission { - public static checkRelationQueryPermission<TBaseModel extends BaseModel>( - modelType: BaseModelType, - select: Select<TBaseModel>, - props: DatabaseCommonInteractionProps - ): void { - const model: BaseModel = new modelType(); - const userPermissions: Array<Permission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow - ).map((i: UserPermission) => { - return i.permission; - }); + public static checkRelationQueryPermission<TBaseModel extends BaseModel>( + modelType: BaseModelType, + select: Select<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): void { + const model: BaseModel = new modelType(); + const userPermissions: Array<Permission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ).map((i: UserPermission) => { + return i.permission; + }); - const excludedColumnNames: Array<string> = - ColumnPermissions.getExcludedColumnNames(); + const excludedColumnNames: Array<string> = + ColumnPermissions.getExcludedColumnNames(); - for (const key in select) { - if (typeof (select as JSONObject)[key] === Typeof.Object) { - const tableColumnMetadata: TableColumnMetadata = - model.getTableColumnMetadata(key); + for (const key in select) { + if (typeof (select as JSONObject)[key] === Typeof.Object) { + const tableColumnMetadata: TableColumnMetadata = + model.getTableColumnMetadata(key); - if (!tableColumnMetadata.modelType) { - throw new BadDataException( - 'Select not supported on ' + - key + - ' of ' + - model.singularName + - ' because this column modelType is not found.' - ); - } - - const relatedModel: BaseModel = - new tableColumnMetadata.modelType(); - - if ( - tableColumnMetadata.type === TableColumnType.Entity || - tableColumnMetadata.type === TableColumnType.EntityArray - ) { - for (const innerKey in (select as any)[key]) { - // check for permissions. - if ( - typeof (select as any)[key][innerKey] === - Typeof.Object - ) { - throw new BadDataException( - 'You cannot query deep relations. Querying deep relations is not supported.' - ); - } - - const getRelatedTableColumnMetadata: TableColumnMetadata = - relatedModel.getTableColumnMetadata(innerKey); - - if (!getRelatedTableColumnMetadata) { - throw new BadDataException( - `Column ${innerKey} not found on ${relatedModel.singularName}` - ); - } - - if ( - !getRelatedTableColumnMetadata.canReadOnRelationQuery && - !excludedColumnNames.includes(innerKey) - ) { - throw new BadDataException( - `Column ${innerKey} on ${relatedModel.singularName} does not support read on relation query.` - ); - } - - if ( - getRelatedTableColumnMetadata.canReadOnRelationQuery - ) { - continue; - } - - // check if the user has permission to read this column - if (userPermissions) { - const hasPermission: boolean = - relatedModel.hasReadPermissions( - userPermissions, - innerKey - ); - - if (!hasPermission) { - let readPermissions: Array<Permission> = []; - if ( - relatedModel.getColumnAccessControlFor( - innerKey - ) - ) { - readPermissions = - relatedModel.getColumnAccessControlFor( - innerKey - )!.read; - } - - throw new NotAuthorizedException( - `You do not have permissions to read ${ - relatedModel.singularName - } on ${ - model.singularName - }. You need one of these permissions: ${PermissionHelper.getPermissionTitles( - readPermissions - ).join(', ')}` - ); - } - } - } - } - } + if (!tableColumnMetadata.modelType) { + throw new BadDataException( + "Select not supported on " + + key + + " of " + + model.singularName + + " because this column modelType is not found.", + ); } - } - public static checkQueryPermission<TBaseModel extends BaseModel>( - modelType: BaseModelType, - query: Query<TBaseModel>, - props: DatabaseCommonInteractionProps - ): void { - const model: BaseModel = new modelType(); + const relatedModel: BaseModel = new tableColumnMetadata.modelType(); - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow - ); - - const canReadOnTheseColumns: Columns = - ColumnPermissions.getModelColumnsByPermissions( - modelType, - userPermissions || [], - DatabaseRequestType.Read - ); - - const tableColumns: Array<string> = model.getTableColumns().columns; - - const excludedColumnNames: Array<string> = - ColumnPermissions.getExcludedColumnNames(); - - // Now we need to check all columns. - - for (const key in query) { - if (excludedColumnNames.includes(key)) { - continue; + if ( + tableColumnMetadata.type === TableColumnType.Entity || + tableColumnMetadata.type === TableColumnType.EntityArray + ) { + for (const innerKey in (select as any)[key]) { + // check for permissions. + if (typeof (select as any)[key][innerKey] === Typeof.Object) { + throw new BadDataException( + "You cannot query deep relations. Querying deep relations is not supported.", + ); } - if (!canReadOnTheseColumns.columns.includes(key)) { - if (!tableColumns.includes(key)) { - throw new BadDataException( - `Invalid column on ${model.singularName} - ${key}. Column does not exist.` - ); + const getRelatedTableColumnMetadata: TableColumnMetadata = + relatedModel.getTableColumnMetadata(innerKey); + + if (!getRelatedTableColumnMetadata) { + throw new BadDataException( + `Column ${innerKey} not found on ${relatedModel.singularName}`, + ); + } + + if ( + !getRelatedTableColumnMetadata.canReadOnRelationQuery && + !excludedColumnNames.includes(innerKey) + ) { + throw new BadDataException( + `Column ${innerKey} on ${relatedModel.singularName} does not support read on relation query.`, + ); + } + + if (getRelatedTableColumnMetadata.canReadOnRelationQuery) { + continue; + } + + // check if the user has permission to read this column + if (userPermissions) { + const hasPermission: boolean = relatedModel.hasReadPermissions( + userPermissions, + innerKey, + ); + + if (!hasPermission) { + let readPermissions: Array<Permission> = []; + if (relatedModel.getColumnAccessControlFor(innerKey)) { + readPermissions = + relatedModel.getColumnAccessControlFor(innerKey)!.read; } throw new NotAuthorizedException( - `You do not have permissions to query on - ${key}. You need any one of these permissions: ${PermissionHelper.getPermissionTitles( - model.getColumnAccessControlFor(key) - ? model.getColumnAccessControlFor(key)!.read - : [] - ).join(', ')}` + `You do not have permissions to read ${ + relatedModel.singularName + } on ${ + model.singularName + }. You need one of these permissions: ${PermissionHelper.getPermissionTitles( + readPermissions, + ).join(", ")}`, ); + } } + } } + } } + } + + public static checkQueryPermission<TBaseModel extends BaseModel>( + modelType: BaseModelType, + query: Query<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): void { + const model: BaseModel = new modelType(); + + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ); + + const canReadOnTheseColumns: Columns = + ColumnPermissions.getModelColumnsByPermissions( + modelType, + userPermissions || [], + DatabaseRequestType.Read, + ); + + const tableColumns: Array<string> = model.getTableColumns().columns; + + const excludedColumnNames: Array<string> = + ColumnPermissions.getExcludedColumnNames(); + + // Now we need to check all columns. + + for (const key in query) { + if (excludedColumnNames.includes(key)) { + continue; + } + + if (!canReadOnTheseColumns.columns.includes(key)) { + if (!tableColumns.includes(key)) { + throw new BadDataException( + `Invalid column on ${model.singularName} - ${key}. Column does not exist.`, + ); + } + + throw new NotAuthorizedException( + `You do not have permissions to query on - ${key}. You need any one of these permissions: ${PermissionHelper.getPermissionTitles( + model.getColumnAccessControlFor(key) + ? model.getColumnAccessControlFor(key)!.read + : [], + ).join(", ")}`, + ); + } + } + } } diff --git a/CommonServer/Types/Database/Permissions/ReadPermission.ts b/CommonServer/Types/Database/Permissions/ReadPermission.ts index 32ce120f5e..bbb2908b4a 100644 --- a/CommonServer/Types/Database/Permissions/ReadPermission.ts +++ b/CommonServer/Types/Database/Permissions/ReadPermission.ts @@ -1,137 +1,126 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import Query from '../Query'; -import QueryHelper from '../QueryHelper'; -import RelationSelect from '../RelationSelect'; -import Select from '../Select'; -import SelectUtil from '../SelectUtil'; -import BasePermission, { CheckPermissionBaseInterface } from './BasePermission'; -import TablePermission from './TablePermission'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import Query from "../Query"; +import QueryHelper from "../QueryHelper"; +import RelationSelect from "../RelationSelect"; +import Select from "../Select"; +import SelectUtil from "../SelectUtil"; +import BasePermission, { CheckPermissionBaseInterface } from "./BasePermission"; +import TablePermission from "./TablePermission"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; import DatabaseCommonInteractionPropsUtil, { - PermissionType, -} from 'Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission, { UserPermission } from 'Common/Types/Permission'; + PermissionType, +} from "Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil"; +import ObjectID from "Common/Types/ObjectID"; +import Permission, { UserPermission } from "Common/Types/Permission"; export interface CheckReadPermissionType<TBaseModel extends BaseModel> - extends CheckPermissionBaseInterface<TBaseModel> { - select: Select<TBaseModel> | null; - relationSelect: RelationSelect<TBaseModel> | null; + extends CheckPermissionBaseInterface<TBaseModel> { + select: Select<TBaseModel> | null; + relationSelect: RelationSelect<TBaseModel> | null; } export default class ReadPermission { - public static async checkReadPermission<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - select: Select<TBaseModel> | null, - props: DatabaseCommonInteractionProps - ): Promise<CheckReadPermissionType<TBaseModel>> { - // check block permission first. - await this.checkReadBlockPermission(modelType, query, props); + public static async checkReadPermission<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + select: Select<TBaseModel> | null, + props: DatabaseCommonInteractionProps, + ): Promise<CheckReadPermissionType<TBaseModel>> { + // check block permission first. + await this.checkReadBlockPermission(modelType, query, props); - const baseFunctionReturn: CheckPermissionBaseInterface<TBaseModel> = - await BasePermission.checkPermissions( - modelType, - query, - select, - props, - DatabaseRequestType.Read - ); + const baseFunctionReturn: CheckPermissionBaseInterface<TBaseModel> = + await BasePermission.checkPermissions( + modelType, + query, + select, + props, + DatabaseRequestType.Read, + ); - // upate query - query = baseFunctionReturn.query; + // upate query + query = baseFunctionReturn.query; - let relationSelect: RelationSelect<TBaseModel> = {}; + let relationSelect: RelationSelect<TBaseModel> = {}; - if (select) { - const result: { - select: Select<TBaseModel>; - relationSelect: RelationSelect<TBaseModel>; - } = SelectUtil.sanitizeSelect(modelType, select); - select = result.select; - relationSelect = result.relationSelect; - } - - return { query, select, relationSelect }; + if (select) { + const result: { + select: Select<TBaseModel>; + relationSelect: RelationSelect<TBaseModel>; + } = SelectUtil.sanitizeSelect(modelType, select); + select = result.select; + relationSelect = result.relationSelect; } - public static async checkReadBlockPermission<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - props: DatabaseCommonInteractionProps - ): Promise<CheckPermissionBaseInterface<TBaseModel>> { - // If system is making this query then let the query run! - if (props.isRoot || props.isMasterAdmin) { - return { query }; - } + return { query, select, relationSelect }; + } - TablePermission.checkTableLevelBlockPermissions( - modelType, - props, - DatabaseRequestType.Read - ); - - const blockPermissionWithLabels: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Block - ).filter((permission: UserPermission) => { - return permission.labelIds && permission.labelIds.length > 0; - }); - - if (blockPermissionWithLabels.length === 0) { - return { query }; - } - - const modelPermissions: Array<Permission> = - TablePermission.getTablePermission( - modelType, - DatabaseRequestType.Read - ); - - const blockPermissionsBelongToThisModel: Array<UserPermission> = - blockPermissionWithLabels.filter( - (blockPermission: UserPermission) => { - let isModelPermission: boolean = false; - - for (const permission of modelPermissions) { - if ( - permission.toString() === - blockPermission.permission.toString() - ) { - isModelPermission = true; - break; - } - } - - return isModelPermission; - } - ); - - if (blockPermissionsBelongToThisModel.length === 0) { - return { query }; - } - - let labelIds: Array<ObjectID> = []; - - for (const blockPermissionBelongToThisModel of blockPermissionsBelongToThisModel) { - if (blockPermissionBelongToThisModel.labelIds) { - labelIds = [ - ...labelIds, - ...blockPermissionBelongToThisModel.labelIds, - ]; - } - } - - // now add these to query - - const model: TBaseModel = new modelType(); - - (query as any)[model.getAccessControlColumn() as string] = { - _id: QueryHelper.notInOrNull(labelIds), - }; - - return { query }; + public static async checkReadBlockPermission<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): Promise<CheckPermissionBaseInterface<TBaseModel>> { + // If system is making this query then let the query run! + if (props.isRoot || props.isMasterAdmin) { + return { query }; } + + TablePermission.checkTableLevelBlockPermissions( + modelType, + props, + DatabaseRequestType.Read, + ); + + const blockPermissionWithLabels: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Block, + ).filter((permission: UserPermission) => { + return permission.labelIds && permission.labelIds.length > 0; + }); + + if (blockPermissionWithLabels.length === 0) { + return { query }; + } + + const modelPermissions: Array<Permission> = + TablePermission.getTablePermission(modelType, DatabaseRequestType.Read); + + const blockPermissionsBelongToThisModel: Array<UserPermission> = + blockPermissionWithLabels.filter((blockPermission: UserPermission) => { + let isModelPermission: boolean = false; + + for (const permission of modelPermissions) { + if (permission.toString() === blockPermission.permission.toString()) { + isModelPermission = true; + break; + } + } + + return isModelPermission; + }); + + if (blockPermissionsBelongToThisModel.length === 0) { + return { query }; + } + + let labelIds: Array<ObjectID> = []; + + for (const blockPermissionBelongToThisModel of blockPermissionsBelongToThisModel) { + if (blockPermissionBelongToThisModel.labelIds) { + labelIds = [...labelIds, ...blockPermissionBelongToThisModel.labelIds]; + } + } + + // now add these to query + + const model: TBaseModel = new modelType(); + + (query as any)[model.getAccessControlColumn() as string] = { + _id: QueryHelper.notInOrNull(labelIds), + }; + + return { query }; + } } diff --git a/CommonServer/Types/Database/Permissions/SelectPermission.ts b/CommonServer/Types/Database/Permissions/SelectPermission.ts index e18ef65136..0ffe1a9471 100644 --- a/CommonServer/Types/Database/Permissions/SelectPermission.ts +++ b/CommonServer/Types/Database/Permissions/SelectPermission.ts @@ -1,67 +1,67 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import Select from '../Select'; -import ColumnPermissions from './ColumnPermission'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import Select from "../Select"; +import ColumnPermissions from "./ColumnPermission"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; import DatabaseCommonInteractionPropsUtil, { - PermissionType, -} from 'Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil'; -import Columns from 'Common/Types/Database/Columns'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; -import { PermissionHelper, UserPermission } from 'Common/Types/Permission'; + PermissionType, +} from "Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil"; +import Columns from "Common/Types/Database/Columns"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; +import { PermissionHelper, UserPermission } from "Common/Types/Permission"; export default class SelectPermission { - public static checkSelectPermission<TBaseModel extends BaseModel>( - modelType: BaseModelType, - select: Select<TBaseModel>, - props: DatabaseCommonInteractionProps - ): void { - const model: BaseModel = new modelType(); + public static checkSelectPermission<TBaseModel extends BaseModel>( + modelType: BaseModelType, + select: Select<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): void { + const model: BaseModel = new modelType(); - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow - ); + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ); - const canReadOnTheseColumns: Columns = - ColumnPermissions.getModelColumnsByPermissions( - modelType, - userPermissions || [], - DatabaseRequestType.Read - ); + const canReadOnTheseColumns: Columns = + ColumnPermissions.getModelColumnsByPermissions( + modelType, + userPermissions || [], + DatabaseRequestType.Read, + ); - const tableColumns: Array<string> = model.getTableColumns().columns; + const tableColumns: Array<string> = model.getTableColumns().columns; - const excludedColumnNames: Array<string> = - ColumnPermissions.getExcludedColumnNames(); + const excludedColumnNames: Array<string> = + ColumnPermissions.getExcludedColumnNames(); - for (const key in select) { - if (excludedColumnNames.includes(key)) { - continue; - } + for (const key in select) { + if (excludedColumnNames.includes(key)) { + continue; + } - if (!canReadOnTheseColumns.columns.includes(key)) { - if (!tableColumns.includes(key)) { - throw new BadDataException( - `Invalid select clause. Cannot select on "${key}". This column does not exist on ${ - model.singularName - }. Here are the columns you can select on instead: ${tableColumns.join( - ', ' - )}` - ); - } - - throw new NotAuthorizedException( - `You do not have permissions to select on - ${key}. - You need any one of these permissions: ${PermissionHelper.getPermissionTitles( - model.getColumnAccessControlFor(key) - ? model.getColumnAccessControlFor(key)!.read - : [] - ).join(', ')}` - ); - } + if (!canReadOnTheseColumns.columns.includes(key)) { + if (!tableColumns.includes(key)) { + throw new BadDataException( + `Invalid select clause. Cannot select on "${key}". This column does not exist on ${ + model.singularName + }. Here are the columns you can select on instead: ${tableColumns.join( + ", ", + )}`, + ); } + + throw new NotAuthorizedException( + `You do not have permissions to select on - ${key}. + You need any one of these permissions: ${PermissionHelper.getPermissionTitles( + model.getColumnAccessControlFor(key) + ? model.getColumnAccessControlFor(key)!.read + : [], + ).join(", ")}`, + ); + } } + } } diff --git a/CommonServer/Types/Database/Permissions/TablePermission.ts b/CommonServer/Types/Database/Permissions/TablePermission.ts index 5b5a5cf689..0410ed301c 100644 --- a/CommonServer/Types/Database/Permissions/TablePermission.ts +++ b/CommonServer/Types/Database/Permissions/TablePermission.ts @@ -1,131 +1,130 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import BillingPermissions from './BillingPermission'; -import PublicPermission from './PublicPermission'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import BillingPermissions from "./BillingPermission"; +import PublicPermission from "./PublicPermission"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; import DatabaseCommonInteractionPropsUtil, { - PermissionType, -} from 'Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; + PermissionType, +} from "Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; import Permission, { - PermissionHelper, - UserPermission, -} from 'Common/Types/Permission'; + PermissionHelper, + UserPermission, +} from "Common/Types/Permission"; export default class TablePermission { - public static getTablePermission( - modelType: BaseModelType, - type: DatabaseRequestType - ): Array<Permission> { - let modelPermissions: Array<Permission> = []; - const model: BaseModel = new modelType(); + public static getTablePermission( + modelType: BaseModelType, + type: DatabaseRequestType, + ): Array<Permission> { + let modelPermissions: Array<Permission> = []; + const model: BaseModel = new modelType(); - if (type === DatabaseRequestType.Create) { - modelPermissions = model.createRecordPermissions; - } - - if (type === DatabaseRequestType.Update) { - modelPermissions = model.updateRecordPermissions; - } - - if (type === DatabaseRequestType.Delete) { - modelPermissions = model.deleteRecordPermissions; - } - - if (type === DatabaseRequestType.Read) { - modelPermissions = model.readRecordPermissions; - } - - return modelPermissions; + if (type === DatabaseRequestType.Create) { + modelPermissions = model.createRecordPermissions; } - public static checkTableLevelPermissions( - modelType: BaseModelType, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): void { - // 1 CHECK: PUBLIC check -- Check if this is a public request and if public is allowed. - PublicPermission.checkIfUserIsLoggedIn(modelType, props, type); + if (type === DatabaseRequestType.Update) { + modelPermissions = model.updateRecordPermissions; + } - // 2nd CHECK: Is user project in active state? - BillingPermissions.checkBillingPermissions(modelType, props, type); + if (type === DatabaseRequestType.Delete) { + modelPermissions = model.deleteRecordPermissions; + } - // 2nd CHECK: Does user have access to CRUD data on this model. - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Allow - ); + if (type === DatabaseRequestType.Read) { + modelPermissions = model.readRecordPermissions; + } - const modelPermissions: Array<Permission> = - TablePermission.getTablePermission(modelType, type); + return modelPermissions; + } + + public static checkTableLevelPermissions( + modelType: BaseModelType, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): void { + // 1 CHECK: PUBLIC check -- Check if this is a public request and if public is allowed. + PublicPermission.checkIfUserIsLoggedIn(modelType, props, type); + + // 2nd CHECK: Is user project in active state? + BillingPermissions.checkBillingPermissions(modelType, props, type); + + // 2nd CHECK: Does user have access to CRUD data on this model. + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Allow, + ); + + const modelPermissions: Array<Permission> = + TablePermission.getTablePermission(modelType, type); + + if ( + !PermissionHelper.doesPermissionsIntersect( + userPermissions.map((userPermission: UserPermission) => { + return userPermission.permission; + }) || [], + modelPermissions, + ) + ) { + throw new NotAuthorizedException( + `You do not have permissions to ${type} ${ + new modelType().singularName + }. You need one of these permissions: ${PermissionHelper.getPermissionTitles( + modelPermissions, + ).join(", ")}`, + ); + } + } + + public static checkTableLevelBlockPermissions( + modelType: BaseModelType, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): void { + // 1 CHECK: PUBLIC check -- Check if this is a public request and if public is allowed. + PublicPermission.checkIfUserIsLoggedIn(modelType, props, type); + + // 2nd CHECK: Does user have access to CRUD data on this model. + const userPermissions: Array<UserPermission> = + DatabaseCommonInteractionPropsUtil.getUserPermissions( + props, + PermissionType.Block, + ); + + const modelPermissions: Array<Permission> = + TablePermission.getTablePermission(modelType, type); + + const intersectingPermissions: Array<Permission> = + PermissionHelper.getIntersectingPermissions( + userPermissions.map((userPermission: UserPermission) => { + return userPermission.permission; + }) || [], + modelPermissions, + ); + + if (intersectingPermissions && intersectingPermissions.length > 0) { + for (const permission of intersectingPermissions) { + const userPermission: UserPermission = userPermissions.find( + (userPermission: UserPermission) => { + return userPermission.permission === permission; + }, + ) as UserPermission; if ( - !PermissionHelper.doesPermissionsIntersect( - userPermissions.map((userPermission: UserPermission) => { - return userPermission.permission; - }) || [], - modelPermissions - ) + userPermission && + (!userPermission.labelIds || userPermission.labelIds.length === 0) ) { - throw new NotAuthorizedException( - `You do not have permissions to ${type} ${ - new modelType().singularName - }. You need one of these permissions: ${PermissionHelper.getPermissionTitles( - modelPermissions - ).join(', ')}` - ); - } - } - - public static checkTableLevelBlockPermissions( - modelType: BaseModelType, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): void { - // 1 CHECK: PUBLIC check -- Check if this is a public request and if public is allowed. - PublicPermission.checkIfUserIsLoggedIn(modelType, props, type); - - // 2nd CHECK: Does user have access to CRUD data on this model. - const userPermissions: Array<UserPermission> = - DatabaseCommonInteractionPropsUtil.getUserPermissions( - props, - PermissionType.Block - ); - - const modelPermissions: Array<Permission> = - TablePermission.getTablePermission(modelType, type); - - const intersectingPermissions: Array<Permission> = - PermissionHelper.getIntersectingPermissions( - userPermissions.map((userPermission: UserPermission) => { - return userPermission.permission; - }) || [], - modelPermissions - ); - - if (intersectingPermissions && intersectingPermissions.length > 0) { - for (const permission of intersectingPermissions) { - const userPermission: UserPermission = userPermissions.find( - (userPermission: UserPermission) => { - return userPermission.permission === permission; - } - ) as UserPermission; - - if ( - userPermission && - (!userPermission.labelIds || - userPermission.labelIds.length === 0) - ) { - throw new NotAuthorizedException( - `You are not authorized to ${type} ${ - new modelType().singularName - } because ${ - userPermission.permission - } is in your team's permission block list.` - ); - } - } + throw new NotAuthorizedException( + `You are not authorized to ${type} ${ + new modelType().singularName + } because ${ + userPermission.permission + } is in your team's permission block list.`, + ); } + } } + } } diff --git a/CommonServer/Types/Database/Permissions/TenantPermission.ts b/CommonServer/Types/Database/Permissions/TenantPermission.ts index 50ae49563f..a6f029fab2 100644 --- a/CommonServer/Types/Database/Permissions/TenantPermission.ts +++ b/CommonServer/Types/Database/Permissions/TenantPermission.ts @@ -1,104 +1,103 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import Query from '../Query'; -import Select from '../Select'; -import BasePermission, { CheckPermissionBaseInterface } from './BasePermission'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; -import ObjectID from 'Common/Types/ObjectID'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import Query from "../Query"; +import Select from "../Select"; +import BasePermission, { CheckPermissionBaseInterface } from "./BasePermission"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; +import ObjectID from "Common/Types/ObjectID"; export default class TenantPermission { - public static async addTenantScopeToQuery<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - select: Select<TBaseModel> | null, - props: DatabaseCommonInteractionProps, - type: DatabaseRequestType - ): Promise<Query<TBaseModel>> { - const model: BaseModel = new modelType(); + public static async addTenantScopeToQuery<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + select: Select<TBaseModel> | null, + props: DatabaseCommonInteractionProps, + type: DatabaseRequestType, + ): Promise<Query<TBaseModel>> { + const model: BaseModel = new modelType(); - const tenantColumn: string | null = model.getTenantColumn(); + const tenantColumn: string | null = model.getTenantColumn(); - if (props.isMultiTenantRequest && !model.canQueryMultiTenant()) { - throw new BadDataException( - `isMultiTenantRequest not allowed on ${model.singularName}` - ); - } - - // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query. - if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) { - (query as any)[tenantColumn] = props.tenantId; - } - // if model allows user query without tenant, and user column is present, and userId is present, then add userId to query. - else if ( - model.isUserQueryWithoutTenantAllowed() && - model.getUserColumn() && - props.userId - ) { - (query as any)[model.getUserColumn() as string] = props.userId; - } else if ( - tenantColumn && - !props.tenantId && - props.userGlobalAccessPermission - ) { - // for each of these projectIds, - // check if they have valid permissions for these projects - // and if they do, include them in the query. - - const queries: Array<Query<TBaseModel>> = []; - - let projectIDs: Array<ObjectID> = []; - - if ( - props.userGlobalAccessPermission && - props.userGlobalAccessPermission.projectIds - ) { - projectIDs = props.userGlobalAccessPermission?.projectIds; - } - - let lastException: Error | null = null; - - for (const projectId of projectIDs) { - if (!props.userId) { - continue; - } - - try { - const checkBasePermissions: CheckPermissionBaseInterface<TBaseModel> = - await BasePermission.checkPermissions( - modelType, - query, - select, - { - ...props, - isMultiTenantRequest: false, - tenantId: projectId, - userTenantAccessPermission: - props.userTenantAccessPermission, - }, - type - ); - - queries.push({ - ...checkBasePermissions.query, - }); - } catch (e) { - // do nothing here. Ignore. - lastException = e as Error; - } - } - - if (queries.length === 0) { - throw new NotAuthorizedException( - lastException?.message || - 'Does not have permission to read ' + model.singularName - ); - } - - return queries as any; - } - - return query; + if (props.isMultiTenantRequest && !model.canQueryMultiTenant()) { + throw new BadDataException( + `isMultiTenantRequest not allowed on ${model.singularName}`, + ); } + + // If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query. + if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) { + (query as any)[tenantColumn] = props.tenantId; + } + // if model allows user query without tenant, and user column is present, and userId is present, then add userId to query. + else if ( + model.isUserQueryWithoutTenantAllowed() && + model.getUserColumn() && + props.userId + ) { + (query as any)[model.getUserColumn() as string] = props.userId; + } else if ( + tenantColumn && + !props.tenantId && + props.userGlobalAccessPermission + ) { + // for each of these projectIds, + // check if they have valid permissions for these projects + // and if they do, include them in the query. + + const queries: Array<Query<TBaseModel>> = []; + + let projectIDs: Array<ObjectID> = []; + + if ( + props.userGlobalAccessPermission && + props.userGlobalAccessPermission.projectIds + ) { + projectIDs = props.userGlobalAccessPermission?.projectIds; + } + + let lastException: Error | null = null; + + for (const projectId of projectIDs) { + if (!props.userId) { + continue; + } + + try { + const checkBasePermissions: CheckPermissionBaseInterface<TBaseModel> = + await BasePermission.checkPermissions( + modelType, + query, + select, + { + ...props, + isMultiTenantRequest: false, + tenantId: projectId, + userTenantAccessPermission: props.userTenantAccessPermission, + }, + type, + ); + + queries.push({ + ...checkBasePermissions.query, + }); + } catch (e) { + // do nothing here. Ignore. + lastException = e as Error; + } + } + + if (queries.length === 0) { + throw new NotAuthorizedException( + lastException?.message || + "Does not have permission to read " + model.singularName, + ); + } + + return queries as any; + } + + return query; + } } diff --git a/CommonServer/Types/Database/Permissions/UpdatePermission.ts b/CommonServer/Types/Database/Permissions/UpdatePermission.ts index 8873c2f221..7c16b84a79 100644 --- a/CommonServer/Types/Database/Permissions/UpdatePermission.ts +++ b/CommonServer/Types/Database/Permissions/UpdatePermission.ts @@ -1,65 +1,66 @@ -import DatabaseRequestType from '../../BaseDatabase/DatabaseRequestType'; -import Query from '../Query'; -import AccessControlUtil from './AccessControlPermission'; -import BasePermission, { CheckPermissionBaseInterface } from './BasePermission'; -import ColumnPermissions from './ColumnPermission'; -import TablePermission from './TablePermission'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import QueryDeepPartialEntity from 'Common/Types/Database/PartialEntity'; +import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType"; +import Query from "../Query"; +import AccessControlUtil from "./AccessControlPermission"; +import BasePermission, { CheckPermissionBaseInterface } from "./BasePermission"; +import ColumnPermissions from "./ColumnPermission"; +import TablePermission from "./TablePermission"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import QueryDeepPartialEntity from "Common/Types/Database/PartialEntity"; export default class UpdatePermission { - public static async checkUpdatePermissionByModel< - TBaseModel extends BaseModel - >(data: { - fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; - modelType: { new (): TBaseModel }; - props: DatabaseCommonInteractionProps; - }): Promise<void> { - await AccessControlUtil.checkAccessControlBlockPermissionByModel<TBaseModel>( - { ...data, type: DatabaseRequestType.Update } - ); + public static async checkUpdatePermissionByModel< + TBaseModel extends BaseModel, + >(data: { + fetchModelWithAccessControlIds: () => Promise<TBaseModel | null>; + modelType: { new (): TBaseModel }; + props: DatabaseCommonInteractionProps; + }): Promise<void> { + await AccessControlUtil.checkAccessControlBlockPermissionByModel<TBaseModel>( + { ...data, type: DatabaseRequestType.Update }, + ); - await AccessControlUtil.checkAccessControlPermissionByModel<TBaseModel>( - { ...data, type: DatabaseRequestType.Update } - ); + await AccessControlUtil.checkAccessControlPermissionByModel<TBaseModel>({ + ...data, + type: DatabaseRequestType.Update, + }); + } + + public static async checkUpdatePermissions<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + data: QueryDeepPartialEntity<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + if (props.isRoot || props.isMasterAdmin) { + // If system is making this query then let the query run! + return query; } - public static async checkUpdatePermissions<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - data: QueryDeepPartialEntity<TBaseModel>, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - if (props.isRoot || props.isMasterAdmin) { - // If system is making this query then let the query run! - return query; - } + TablePermission.checkTableLevelPermissions( + modelType, + props, + DatabaseRequestType.Update, + ); - TablePermission.checkTableLevelPermissions( - modelType, - props, - DatabaseRequestType.Update - ); + const checkBasePermission: CheckPermissionBaseInterface<TBaseModel> = + await BasePermission.checkPermissions( + modelType, + query, + null, + props, + DatabaseRequestType.Update, + ); - const checkBasePermission: CheckPermissionBaseInterface<TBaseModel> = - await BasePermission.checkPermissions( - modelType, - query, - null, - props, - DatabaseRequestType.Update - ); + query = checkBasePermission.query; - query = checkBasePermission.query; + ColumnPermissions.checkDataColumnPermissions( + modelType, + data as any, + props, + DatabaseRequestType.Update, + ); - ColumnPermissions.checkDataColumnPermissions( - modelType, - data as any, - props, - DatabaseRequestType.Update - ); - - return query; - } + return query; + } } diff --git a/CommonServer/Types/Database/Permissions/UserPermission.ts b/CommonServer/Types/Database/Permissions/UserPermission.ts index f0ec881e43..3d9c8869f3 100644 --- a/CommonServer/Types/Database/Permissions/UserPermission.ts +++ b/CommonServer/Types/Database/Permissions/UserPermission.ts @@ -1,27 +1,27 @@ -import Query from '../Query'; -import BaseModel from 'Common/Models/BaseModel'; -import UserModel from 'Common/Models/UserModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException'; +import Query from "../Query"; +import BaseModel from "Common/Models/BaseModel"; +import UserModel from "Common/Models/UserModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException"; export default class UserPermissions { - public static async addUserScopeToQuery<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel>, - props: DatabaseCommonInteractionProps - ): Promise<Query<TBaseModel>> { - const model: BaseModel = new modelType(); + public static async addUserScopeToQuery<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + props: DatabaseCommonInteractionProps, + ): Promise<Query<TBaseModel>> { + const model: BaseModel = new modelType(); - if (model instanceof UserModel) { - if (props.userId) { - (query as any)['_id'] = props.userId; - } else if (!props.isRoot && !props.isMasterAdmin) { - throw new NotAuthorizedException( - `You do not have permissions to query on - ${model.singularName}.` - ); - } - } - - return query; + if (model instanceof UserModel) { + if (props.userId) { + (query as any)["_id"] = props.userId; + } else if (!props.isRoot && !props.isMasterAdmin) { + throw new NotAuthorizedException( + `You do not have permissions to query on - ${model.singularName}.`, + ); + } } + + return query; + } } diff --git a/CommonServer/Types/Database/Query.ts b/CommonServer/Types/Database/Query.ts index bc4f1a2109..dca77bfd26 100644 --- a/CommonServer/Types/Database/Query.ts +++ b/CommonServer/Types/Database/Query.ts @@ -1,25 +1,25 @@ -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseProperty from 'Common/Types/Database/DatabaseProperty'; -import { FindOperator, FindOptionsWhereProperty } from 'typeorm'; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseProperty from "Common/Types/Database/DatabaseProperty"; +import { FindOperator, FindOptionsWhereProperty } from "typeorm"; export declare type FindWhereProperty<Property> = - Property extends DatabaseProperty - ? Property | FindOperator<Property> | Array<Property> - : FindOptionsWhereProperty<Property>; + Property extends DatabaseProperty + ? Property | FindOperator<Property> | Array<Property> + : FindOptionsWhereProperty<Property>; /** * : * Used for find operations. */ export declare type FindWhere<Entity> = { - [P in keyof Entity]?: - | FindWhereProperty<NonNullable<Entity[P]>> - | FindOperator<NonNullable<Entity[P]>>; + [P in keyof Entity]?: + | FindWhereProperty<NonNullable<Entity[P]>> + | FindOperator<NonNullable<Entity[P]>>; }; declare type Query<TBaseModel extends BaseModel> = FindWhere<TBaseModel>; export declare type OrQuery<TBaseModel extends BaseModel> = Array< - Query<TBaseModel> + Query<TBaseModel> >; export default Query; diff --git a/CommonServer/Types/Database/QueryHelper.ts b/CommonServer/Types/Database/QueryHelper.ts index 086c4814c4..eed80c6c51 100644 --- a/CommonServer/Types/Database/QueryHelper.ts +++ b/CommonServer/Types/Database/QueryHelper.ts @@ -1,430 +1,417 @@ -import BaseModel from 'Common/Models/BaseModel'; -import Dictionary from 'Common/Types/Dictionary'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Text from 'Common/Types/Text'; -import Typeof from 'Common/Types/Typeof'; -import { FindOperator, Raw } from 'typeorm'; +import BaseModel from "Common/Models/BaseModel"; +import Dictionary from "Common/Types/Dictionary"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Text from "Common/Types/Text"; +import Typeof from "Common/Types/Typeof"; +import { FindOperator, Raw } from "typeorm"; export default class QueryHelper { - public static findWithSameText(text: string | number): FindOperator<any> { - let isString: boolean = false; + public static findWithSameText(text: string | number): FindOperator<any> { + let isString: boolean = false; - if (typeof text === Typeof.String) { - text = (text as string).toLowerCase().trim(); - isString = true; - } - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return isString - ? `(LOWER(${alias}) = :${rid})` - : `(${alias} = :${rid})`; - }, - { - [rid]: `${text}`, - } - ); + if (typeof text === Typeof.String) { + text = (text as string).toLowerCase().trim(); + isString = true; + } + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return isString + ? `(LOWER(${alias}) = :${rid})` + : `(${alias} = :${rid})`; + }, + { + [rid]: `${text}`, + }, + ); + } + + public static isNull(): any { + return Raw((alias: string) => { + return `(${alias} IS NULL)`; + }); + } + + public static notNull(): any { + return Raw((alias: string) => { + return `(${alias} IS NOT NULL)`; + }); + } + + public static equalToOrNull( + value: string | ObjectID | Array<string | ObjectID>, + ): FindOperator<any> { + const rid: Array<string> = []; + const valuesObj: Dictionary<string> = {}; + + if (Array.isArray(value)) { + for (const item of value) { + const temp: string = Text.generateRandomText(10); + rid.push(temp); + valuesObj[temp] = item.toString(); + } + } else { + const temp: string = Text.generateRandomText(10); + rid.push(temp); + valuesObj[temp] = value.toString(); } - public static isNull(): any { - return Raw((alias: string) => { - return `(${alias} IS NULL)`; - }); + // construct string + + type ConstructQueryFunction = (alias: string) => string; + + const constructQuery: ConstructQueryFunction = (alias: string): string => { + let query: string = "("; + + query += rid + .map((item: string) => { + return `${alias} = :${item}`; + }) + .join(" or "); + + query += ` or ${alias} IS NULL)`; + + return query; + }; + + return Raw( + (alias: string) => { + return constructQuery(alias); + }, + { + ...valuesObj, + }, + ); + } + + public static notEquals(value: string | ObjectID): FindOperator<any> { + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} != :${rid})`; + }, + { + [rid]: value.toString(), + }, + ); + } + + public static search(name: string): FindOperator<any> { + name = name.toLowerCase().trim(); + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `((CAST(${alias} AS TEXT) ILIKE :${rid}))`; + }, + { + [rid]: `%${name}%`, + }, + ); + } + + public static all(values: Array<string | ObjectID>): FindOperator<any> { + values = values.map((value: string | ObjectID) => { + return value.toString(); + }); + const rid: string = Text.generateRandomText(10); + + if (!values || values.length === 0) { + return Raw(() => { + return `TRUE = FALSE`; // this will always return false + }, {}); } - public static notNull(): any { - return Raw((alias: string) => { - return `(${alias} IS NOT NULL)`; - }); + return Raw( + (alias: string) => { + return `(${alias} = ALL(:${rid}))`; + }, + { + [rid]: values, + }, + ); + } + + public static any(values: Array<string | ObjectID>): FindOperator<any> { + return this.in(values); // any and in are the same + } + + private static in(values: Array<string | ObjectID>): FindOperator<any> { + values = values.map((value: string | ObjectID) => { + return value.toString(); + }); + const rid: string = Text.generateRandomText(10); + + if (!values || values.length === 0) { + return Raw(() => { + return `TRUE = FALSE`; // this will always return false + }, {}); } - public static equalToOrNull( - value: string | ObjectID | Array<string | ObjectID> - ): FindOperator<any> { - const rid: Array<string> = []; - const valuesObj: Dictionary<string> = {}; + return Raw( + (alias: string) => { + return `(${alias} IN (:...${rid}))`; + }, + { + [rid]: values, + }, + ); + } - if (Array.isArray(value)) { - for (const item of value) { - const temp: string = Text.generateRandomText(10); - rid.push(temp); - valuesObj[temp] = item.toString(); - } - } else { - const temp: string = Text.generateRandomText(10); - rid.push(temp); - valuesObj[temp] = value.toString(); - } + public static notIn(values: Array<string | ObjectID>): FindOperator<any> { + values = values.map((value: string | ObjectID) => { + return value.toString(); + }); + const rid: string = Text.generateRandomText(10); - // construct string - - type ConstructQueryFunction = (alias: string) => string; - - const constructQuery: ConstructQueryFunction = ( - alias: string - ): string => { - let query: string = '('; - - query += rid - .map((item: string) => { - return `${alias} = :${item}`; - }) - .join(' or '); - - query += ` or ${alias} IS NULL)`; - - return query; - }; - - return Raw( - (alias: string) => { - return constructQuery(alias); - }, - { - ...valuesObj, - } - ); + if (!values || values.length === 0) { + return Raw(() => { + return `TRUE = TRUE`; // this will always return true + }, {}); } - public static notEquals(value: string | ObjectID): FindOperator<any> { - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} != :${rid})`; - }, - { - [rid]: value.toString(), - } - ); + return Raw( + (alias: string) => { + return `(${alias} NOT IN (:...${rid}))`; + }, + { + [rid]: values, + }, + ); + } + + public static notInOrNull( + values: Array<string | ObjectID>, + ): FindOperator<any> { + values = values.map((value: string | ObjectID) => { + return value.toString(); + }); + const rid: string = Text.generateRandomText(10); + + if (!values || values.length === 0) { + return Raw(() => { + return `TRUE = TRUE`; // this will always return true + }, {}); } - public static search(name: string): FindOperator<any> { - name = name.toLowerCase().trim(); - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `((CAST(${alias} AS TEXT) ILIKE :${rid}))`; - }, - { - [rid]: `%${name}%`, - } - ); + return Raw( + (alias: string) => { + return `(${alias} NOT IN (:...${rid}) or ${alias} IS NULL)`; + }, + { + [rid]: values, + }, + ); + } + + public static inRelationArray( + values: Array<BaseModel | ObjectID>, + ): Array<any> { + return values.map((item: BaseModel | ObjectID) => { + if (item instanceof ObjectID) { + return item; + } + + return item.id!; + }); + } + + public static equalTo(value: string): FindOperator<any> { + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} = :${rid})`; + }, + { + [rid]: value.toString(), + }, + ); + } + + public static greaterThanEqualTo(value: number | Date): FindOperator<any> { + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} >= :${rid})`; + }, + { + [rid]: value, + }, + ); + } + + public static greaterThanEqualToOrNull( + value: number | Date, + ): FindOperator<any> { + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} >= :${rid} or ${alias} IS NULL)`; + }, + { + [rid]: value, + }, + ); + } + + public static lessThanEqualTo(value: number | Date): FindOperator<any> { + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} <= :${rid})`; + }, + { + [rid]: value, + }, + ); + } + + public static lessThanEqualToOrNull(value: number | Date): FindOperator<any> { + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} <= :${rid} or ${alias} IS NULL)`; + }, + { + [rid]: value, + }, + ); + } + + public static greaterThan(value: number | Date): FindOperator<any> { + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} > :${rid})`; + }, + { + [rid]: value, + }, + ); + } + + public static greaterThanOrNull(value: number | Date): FindOperator<any> { + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} <= :${rid} or ${alias} IS NULL)`; + }, + { + [rid]: value, + }, + ); + } + + public static inBetween( + startValue: number | Date, + endValue: number | Date, + ): FindOperator<any> { + const rid1: string = Text.generateRandomText(10); + const rid2: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} >= :${rid1} and ${alias} <= :${rid2})`; + }, + { + [rid1]: startValue, + [rid2]: endValue, + }, + ); + } + + public static inBetweenOrNull( + startValue: number | Date, + endValue: number | Date, + ): FindOperator<any> { + const rid1: string = Text.generateRandomText(10); + const rid2: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(((${alias} >= :${rid1} and ${alias} <= :${rid2})) or (${alias} IS NULL))`; + }, + { + [rid1]: startValue, + [rid2]: endValue, + }, + ); + } + + public static notInBetween( + startValue: number | Date, + endValue: number | Date, + ): FindOperator<any> { + const rid1: string = Text.generateRandomText(10); + const rid2: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} < :${rid1} or ${alias} > :${rid2})`; + }, + { + [rid1]: startValue, + [rid2]: endValue, + }, + ); + } + + public static queryJson(value: JSONObject): FindOperator<any> { + // seed random text + const values: JSONObject = {}; + + let queryText: string = ""; + + if (typeof value === Typeof.String) { + value = JSON.parse(value.toString()); } - public static all(values: Array<string | ObjectID>): FindOperator<any> { - values = values.map((value: string | ObjectID) => { - return value.toString(); - }); - const rid: string = Text.generateRandomText(10); - - if (!values || values.length === 0) { - return Raw(() => { - return `TRUE = FALSE`; // this will always return false - }, {}); - } - - return Raw( - (alias: string) => { - return `(${alias} = ALL(:${rid}))`; - }, - { - [rid]: values, - } - ); + if (value instanceof FindOperator) { + return value; } - public static any(values: Array<string | ObjectID>): FindOperator<any> { - return this.in(values); // any and in are the same + const hasValue: boolean = value && Object.keys(value).length > 0; + + for (const key in value) { + const temp: string = Text.generateRandomText(10); + + values[temp] = value[key]; + + if (!queryText) { + queryText = `(COLUMN_NAME_ALIAS->>'${key}' = :${temp as string}`; + } else { + queryText += ` AND COLUMN_NAME_ALIAS->>'${key}' = :${temp as string}`; + } } - private static in(values: Array<string | ObjectID>): FindOperator<any> { - values = values.map((value: string | ObjectID) => { - return value.toString(); - }); - const rid: string = Text.generateRandomText(10); - - if (!values || values.length === 0) { - return Raw(() => { - return `TRUE = FALSE`; // this will always return false - }, {}); - } - - return Raw( - (alias: string) => { - return `(${alias} IN (:...${rid}))`; - }, - { - [rid]: values, - } - ); + if (hasValue) { + queryText += ")"; + } else { + queryText = "(COLUMN_NAME_ALIAS IS NULL OR COLUMN_NAME_ALIAS = '{}')"; } - public static notIn(values: Array<string | ObjectID>): FindOperator<any> { - values = values.map((value: string | ObjectID) => { - return value.toString(); - }); - const rid: string = Text.generateRandomText(10); + return Raw((alias: string) => { + // alias is table name + column name like tableName.columnName + // we need to convert this to "tableName"."columnName" - if (!values || values.length === 0) { - return Raw(() => { - return `TRUE = TRUE`; // this will always return true - }, {}); - } + alias = alias + .split(".") + .map((item: string) => { + return `"${item}"`; + }) + .join("."); - return Raw( - (alias: string) => { - return `(${alias} NOT IN (:...${rid}))`; - }, - { - [rid]: values, - } - ); - } + queryText = Text.replaceAll(queryText, "COLUMN_NAME_ALIAS", `${alias}`); + return queryText; + }, values); + } - public static notInOrNull( - values: Array<string | ObjectID> - ): FindOperator<any> { - values = values.map((value: string | ObjectID) => { - return value.toString(); - }); - const rid: string = Text.generateRandomText(10); - - if (!values || values.length === 0) { - return Raw(() => { - return `TRUE = TRUE`; // this will always return true - }, {}); - } - - return Raw( - (alias: string) => { - return `(${alias} NOT IN (:...${rid}) or ${alias} IS NULL)`; - }, - { - [rid]: values, - } - ); - } - - public static inRelationArray( - values: Array<BaseModel | ObjectID> - ): Array<any> { - return values.map((item: BaseModel | ObjectID) => { - if (item instanceof ObjectID) { - return item; - } - - return item.id!; - }); - } - - public static equalTo(value: string): FindOperator<any> { - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} = :${rid})`; - }, - { - [rid]: value.toString(), - } - ); - } - - public static greaterThanEqualTo(value: number | Date): FindOperator<any> { - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} >= :${rid})`; - }, - { - [rid]: value, - } - ); - } - - public static greaterThanEqualToOrNull( - value: number | Date - ): FindOperator<any> { - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} >= :${rid} or ${alias} IS NULL)`; - }, - { - [rid]: value, - } - ); - } - - public static lessThanEqualTo(value: number | Date): FindOperator<any> { - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} <= :${rid})`; - }, - { - [rid]: value, - } - ); - } - - public static lessThanEqualToOrNull( - value: number | Date - ): FindOperator<any> { - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} <= :${rid} or ${alias} IS NULL)`; - }, - { - [rid]: value, - } - ); - } - - public static greaterThan(value: number | Date): FindOperator<any> { - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} > :${rid})`; - }, - { - [rid]: value, - } - ); - } - - public static greaterThanOrNull(value: number | Date): FindOperator<any> { - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} <= :${rid} or ${alias} IS NULL)`; - }, - { - [rid]: value, - } - ); - } - - public static inBetween( - startValue: number | Date, - endValue: number | Date - ): FindOperator<any> { - const rid1: string = Text.generateRandomText(10); - const rid2: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} >= :${rid1} and ${alias} <= :${rid2})`; - }, - { - [rid1]: startValue, - [rid2]: endValue, - } - ); - } - - public static inBetweenOrNull( - startValue: number | Date, - endValue: number | Date - ): FindOperator<any> { - const rid1: string = Text.generateRandomText(10); - const rid2: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(((${alias} >= :${rid1} and ${alias} <= :${rid2})) or (${alias} IS NULL))`; - }, - { - [rid1]: startValue, - [rid2]: endValue, - } - ); - } - - public static notInBetween( - startValue: number | Date, - endValue: number | Date - ): FindOperator<any> { - const rid1: string = Text.generateRandomText(10); - const rid2: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} < :${rid1} or ${alias} > :${rid2})`; - }, - { - [rid1]: startValue, - [rid2]: endValue, - } - ); - } - - public static queryJson(value: JSONObject): FindOperator<any> { - // seed random text - const values: JSONObject = {}; - - let queryText: string = ''; - - if (typeof value === Typeof.String) { - value = JSON.parse(value.toString()); - } - - if (value instanceof FindOperator) { - return value; - } - - const hasValue: boolean = value && Object.keys(value).length > 0; - - for (const key in value) { - const temp: string = Text.generateRandomText(10); - - values[temp] = value[key]; - - if (!queryText) { - queryText = `(COLUMN_NAME_ALIAS->>'${key}' = :${ - temp as string - }`; - } else { - queryText += ` AND COLUMN_NAME_ALIAS->>'${key}' = :${ - temp as string - }`; - } - } - - if (hasValue) { - queryText += ')'; - } else { - queryText = - "(COLUMN_NAME_ALIAS IS NULL OR COLUMN_NAME_ALIAS = '{}')"; - } - - return Raw((alias: string) => { - // alias is table name + column name like tableName.columnName - // we need to convert this to "tableName"."columnName" - - alias = alias - .split('.') - .map((item: string) => { - return `"${item}"`; - }) - .join('.'); - - queryText = Text.replaceAll( - queryText, - 'COLUMN_NAME_ALIAS', - `${alias}` - ); - return queryText; - }, values); - } - - public static lessThan(value: number | Date): FindOperator<any> { - const rid: string = Text.generateRandomText(10); - return Raw( - (alias: string) => { - return `(${alias} < :${rid})`; - }, - { - [rid]: value, - } - ); - } + public static lessThan(value: number | Date): FindOperator<any> { + const rid: string = Text.generateRandomText(10); + return Raw( + (alias: string) => { + return `(${alias} < :${rid})`; + }, + { + [rid]: value, + }, + ); + } } diff --git a/CommonServer/Types/Database/QueryUtil.ts b/CommonServer/Types/Database/QueryUtil.ts index de813a6c13..206c340784 100644 --- a/CommonServer/Types/Database/QueryUtil.ts +++ b/CommonServer/Types/Database/QueryUtil.ts @@ -1,204 +1,203 @@ -import Query from './Query'; -import QueryHelper from './QueryHelper'; -import BaseModel from 'Common/Models/BaseModel'; -import EqualToOrNull from 'Common/Types/BaseDatabase/EqualToOrNull'; -import GreaterThan from 'Common/Types/BaseDatabase/GreaterThan'; -import GreaterThanOrEqual from 'Common/Types/BaseDatabase/GreaterThanOrEqual'; -import InBetween from 'Common/Types/BaseDatabase/InBetween'; -import Includes from 'Common/Types/BaseDatabase/Includes'; -import IsNull from 'Common/Types/BaseDatabase/IsNull'; -import LessThan from 'Common/Types/BaseDatabase/LessThan'; -import LessThanOrEqual from 'Common/Types/BaseDatabase/LessThanOrEqual'; -import NotEqual from 'Common/Types/BaseDatabase/NotEqual'; -import NotNull from 'Common/Types/BaseDatabase/NotNull'; -import Search from 'Common/Types/BaseDatabase/Search'; -import { TableColumnMetadata } from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Typeof from 'Common/Types/Typeof'; -import { FindOperator } from 'typeorm/find-options/FindOperator'; +import Query from "./Query"; +import QueryHelper from "./QueryHelper"; +import BaseModel from "Common/Models/BaseModel"; +import EqualToOrNull from "Common/Types/BaseDatabase/EqualToOrNull"; +import GreaterThan from "Common/Types/BaseDatabase/GreaterThan"; +import GreaterThanOrEqual from "Common/Types/BaseDatabase/GreaterThanOrEqual"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import Includes from "Common/Types/BaseDatabase/Includes"; +import IsNull from "Common/Types/BaseDatabase/IsNull"; +import LessThan from "Common/Types/BaseDatabase/LessThan"; +import LessThanOrEqual from "Common/Types/BaseDatabase/LessThanOrEqual"; +import NotEqual from "Common/Types/BaseDatabase/NotEqual"; +import NotNull from "Common/Types/BaseDatabase/NotNull"; +import Search from "Common/Types/BaseDatabase/Search"; +import { TableColumnMetadata } from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Typeof from "Common/Types/Typeof"; +import { FindOperator } from "typeorm/find-options/FindOperator"; export default class QueryUtil { - public static serializeQuery<TBaseModel extends BaseModel>( - modelType: { new (): TBaseModel }, - query: Query<TBaseModel> - ): Query<TBaseModel> { - const model: BaseModel = new modelType(); + public static serializeQuery<TBaseModel extends BaseModel>( + modelType: { new (): TBaseModel }, + query: Query<TBaseModel>, + ): Query<TBaseModel> { + const model: BaseModel = new modelType(); - query = query as Query<TBaseModel>; + query = query as Query<TBaseModel>; - for (const key in query) { - const tableColumnMetadata: TableColumnMetadata = - model.getTableColumnMetadata(key); + for (const key in query) { + const tableColumnMetadata: TableColumnMetadata = + model.getTableColumnMetadata(key); - if (tableColumnMetadata && query[key] === null) { - query[key] = QueryHelper.isNull(); - } else if ( - query[key] && - query[key] instanceof NotNull && - tableColumnMetadata - ) { - query[key] = QueryHelper.notNull(); - } else if ( - query[key] && - query[key] instanceof EqualToOrNull && - tableColumnMetadata - ) { - query[key] = QueryHelper.equalToOrNull( - query[key] as any - ) as FindOperator<any> as any; - } else if ( - query[key] && - tableColumnMetadata && - tableColumnMetadata.type === TableColumnType.JSON - ) { - query[key] = QueryHelper.queryJson( - query[key] as any - ) as FindOperator<any> as any; - } else if ( - query[key] && - query[key] instanceof NotEqual && - tableColumnMetadata - ) { - query[key] = QueryHelper.notEquals( - query[key] as any - ) as FindOperator<any> as any; - } else if ( - query[key] && - (query[key] as any)._value && - Array.isArray((query[key] as any)._value) && - (query[key] as any)._value.length > 0 && - tableColumnMetadata - ) { - let counter: number = 0; - for (const item of (query[key] as any)._value) { - if (item instanceof ObjectID) { - ((query[key] as any)._value as any)[counter] = ( - (query[key] as any)._value as any - )[counter].toString(); - } - counter++; - } - } else if ( - query[key] && - query[key] instanceof ObjectID && - tableColumnMetadata && - tableColumnMetadata.type !== TableColumnType.EntityArray - ) { - query[key] = QueryHelper.equalTo( - (query[key] as ObjectID).toString() as any - ) as any; - } else if ( - query[key] && - query[key] instanceof Search && - tableColumnMetadata - ) { - query[key] = QueryHelper.search( - (query[key] as Search).toString() as any - ) as any; - } else if ( - query[key] && - query[key] instanceof LessThan && - tableColumnMetadata - ) { - query[key] = QueryHelper.lessThan( - (query[key] as LessThan).toString() as any - ) as any; - } else if ( - query[key] && - query[key] instanceof IsNull && - tableColumnMetadata - ) { - query[key] = QueryHelper.isNull() as any; - } else if ( - query[key] && - query[key] instanceof InBetween && - tableColumnMetadata - ) { - query[key] = QueryHelper.inBetween( - (query[key] as InBetween).startValue as any, - (query[key] as InBetween).endValue as any - ) as any; - } else if ( - query[key] && - query[key] instanceof GreaterThan && - tableColumnMetadata - ) { - query[key] = QueryHelper.greaterThan( - (query[key] as GreaterThan).toString() as any - ) as any; - } else if ( - query[key] && - query[key] instanceof Includes && - tableColumnMetadata - ) { - query[key] = QueryHelper.any( - (query[key] as Includes).values - ) as any; - } else if ( - query[key] && - query[key] instanceof GreaterThanOrEqual && - tableColumnMetadata - ) { - query[key] = QueryHelper.greaterThanEqualTo( - (query[key] as GreaterThanOrEqual).toString() as any - ) as any; - } else if ( - query[key] && - query[key] instanceof LessThanOrEqual && - tableColumnMetadata - ) { - query[key] = QueryHelper.lessThanEqualTo( - (query[key] as LessThanOrEqual).toString() as any - ) as any; - } else if ( - query[key] && - Array.isArray(query[key]) && - tableColumnMetadata && - tableColumnMetadata.type !== TableColumnType.EntityArray - ) { - query[key] = QueryHelper.any( - query[key] as any - ) as FindOperator<any> as any; - } - - if ( - tableColumnMetadata && - tableColumnMetadata.manyToOneRelationColumn && - typeof query[key] === Typeof.String - ) { - (query as any)[tableColumnMetadata.manyToOneRelationColumn] = - query[key] as string; - delete query[key]; - } - - if ( - tableColumnMetadata && - tableColumnMetadata.modelType && - tableColumnMetadata.type === TableColumnType.EntityArray && - Array.isArray(query[key]) - ) { - query[key] = (query[key] as Array<string | JSONObject>).map( - (item: string | JSONObject) => { - if (typeof item === Typeof.String) { - return item; - } - - if (item && (item as JSONObject)['_id']) { - return (item as JSONObject)['_id'] as string; - } - - return item; - } - ) as any; - - (query as any)[key] = { - _id: QueryHelper.any(query[key] as Array<string>), - }; - } + if (tableColumnMetadata && query[key] === null) { + query[key] = QueryHelper.isNull(); + } else if ( + query[key] && + query[key] instanceof NotNull && + tableColumnMetadata + ) { + query[key] = QueryHelper.notNull(); + } else if ( + query[key] && + query[key] instanceof EqualToOrNull && + tableColumnMetadata + ) { + query[key] = QueryHelper.equalToOrNull( + query[key] as any, + ) as FindOperator<any> as any; + } else if ( + query[key] && + tableColumnMetadata && + tableColumnMetadata.type === TableColumnType.JSON + ) { + query[key] = QueryHelper.queryJson( + query[key] as any, + ) as FindOperator<any> as any; + } else if ( + query[key] && + query[key] instanceof NotEqual && + tableColumnMetadata + ) { + query[key] = QueryHelper.notEquals( + query[key] as any, + ) as FindOperator<any> as any; + } else if ( + query[key] && + (query[key] as any)._value && + Array.isArray((query[key] as any)._value) && + (query[key] as any)._value.length > 0 && + tableColumnMetadata + ) { + let counter: number = 0; + for (const item of (query[key] as any)._value) { + if (item instanceof ObjectID) { + ((query[key] as any)._value as any)[counter] = ( + (query[key] as any)._value as any + )[counter].toString(); + } + counter++; } + } else if ( + query[key] && + query[key] instanceof ObjectID && + tableColumnMetadata && + tableColumnMetadata.type !== TableColumnType.EntityArray + ) { + query[key] = QueryHelper.equalTo( + (query[key] as ObjectID).toString() as any, + ) as any; + } else if ( + query[key] && + query[key] instanceof Search && + tableColumnMetadata + ) { + query[key] = QueryHelper.search( + (query[key] as Search).toString() as any, + ) as any; + } else if ( + query[key] && + query[key] instanceof LessThan && + tableColumnMetadata + ) { + query[key] = QueryHelper.lessThan( + (query[key] as LessThan).toString() as any, + ) as any; + } else if ( + query[key] && + query[key] instanceof IsNull && + tableColumnMetadata + ) { + query[key] = QueryHelper.isNull() as any; + } else if ( + query[key] && + query[key] instanceof InBetween && + tableColumnMetadata + ) { + query[key] = QueryHelper.inBetween( + (query[key] as InBetween).startValue as any, + (query[key] as InBetween).endValue as any, + ) as any; + } else if ( + query[key] && + query[key] instanceof GreaterThan && + tableColumnMetadata + ) { + query[key] = QueryHelper.greaterThan( + (query[key] as GreaterThan).toString() as any, + ) as any; + } else if ( + query[key] && + query[key] instanceof Includes && + tableColumnMetadata + ) { + query[key] = QueryHelper.any((query[key] as Includes).values) as any; + } else if ( + query[key] && + query[key] instanceof GreaterThanOrEqual && + tableColumnMetadata + ) { + query[key] = QueryHelper.greaterThanEqualTo( + (query[key] as GreaterThanOrEqual).toString() as any, + ) as any; + } else if ( + query[key] && + query[key] instanceof LessThanOrEqual && + tableColumnMetadata + ) { + query[key] = QueryHelper.lessThanEqualTo( + (query[key] as LessThanOrEqual).toString() as any, + ) as any; + } else if ( + query[key] && + Array.isArray(query[key]) && + tableColumnMetadata && + tableColumnMetadata.type !== TableColumnType.EntityArray + ) { + query[key] = QueryHelper.any( + query[key] as any, + ) as FindOperator<any> as any; + } - return query; + if ( + tableColumnMetadata && + tableColumnMetadata.manyToOneRelationColumn && + typeof query[key] === Typeof.String + ) { + (query as any)[tableColumnMetadata.manyToOneRelationColumn] = query[ + key + ] as string; + delete query[key]; + } + + if ( + tableColumnMetadata && + tableColumnMetadata.modelType && + tableColumnMetadata.type === TableColumnType.EntityArray && + Array.isArray(query[key]) + ) { + query[key] = (query[key] as Array<string | JSONObject>).map( + (item: string | JSONObject) => { + if (typeof item === Typeof.String) { + return item; + } + + if (item && (item as JSONObject)["_id"]) { + return (item as JSONObject)["_id"] as string; + } + + return item; + }, + ) as any; + + (query as any)[key] = { + _id: QueryHelper.any(query[key] as Array<string>), + }; + } } + + return query; + } } diff --git a/CommonServer/Types/Database/RelationSelect.ts b/CommonServer/Types/Database/RelationSelect.ts index c73271e740..92486fe9e3 100644 --- a/CommonServer/Types/Database/RelationSelect.ts +++ b/CommonServer/Types/Database/RelationSelect.ts @@ -1,9 +1,9 @@ -import BaseModel from 'Common/Models/BaseModel'; -import { JSONObject } from 'Common/Types/JSON'; -import { FindOptionsRelations } from 'typeorm'; +import BaseModel from "Common/Models/BaseModel"; +import { JSONObject } from "Common/Types/JSON"; +import { FindOptionsRelations } from "typeorm"; type RelationSelect<TBaseModel extends BaseModel> = - | FindOptionsRelations<TBaseModel> - | JSONObject; + | FindOptionsRelations<TBaseModel> + | JSONObject; export default RelationSelect; diff --git a/CommonServer/Types/Database/SearchBy.ts b/CommonServer/Types/Database/SearchBy.ts index 2cfe39f3ea..d44748a229 100644 --- a/CommonServer/Types/Database/SearchBy.ts +++ b/CommonServer/Types/Database/SearchBy.ts @@ -1,13 +1,13 @@ -import Select from './Select'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import PositiveNumber from 'Common/Types/PositiveNumber'; +import Select from "./Select"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import PositiveNumber from "Common/Types/PositiveNumber"; export default interface SearchBy<TBaseModel extends BaseModel> { - text: string; - column: keyof TBaseModel; - limit: PositiveNumber; - skip: PositiveNumber; - select: Select<TBaseModel>; - props: DatabaseCommonInteractionProps; + text: string; + column: keyof TBaseModel; + limit: PositiveNumber; + skip: PositiveNumber; + select: Select<TBaseModel>; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/Database/SearchResult.ts b/CommonServer/Types/Database/SearchResult.ts index 5e2786560d..00bc344f85 100644 --- a/CommonServer/Types/Database/SearchResult.ts +++ b/CommonServer/Types/Database/SearchResult.ts @@ -1,7 +1,7 @@ -import BaseModel from 'Common/Models/BaseModel'; -import PositiveNumber from 'Common/Types/PositiveNumber'; +import BaseModel from "Common/Models/BaseModel"; +import PositiveNumber from "Common/Types/PositiveNumber"; export default interface SearchResult<TBaseModel extends BaseModel> { - items: Array<TBaseModel>; - count: PositiveNumber; + items: Array<TBaseModel>; + count: PositiveNumber; } diff --git a/CommonServer/Types/Database/Select.ts b/CommonServer/Types/Database/Select.ts index 1e6d0542fa..9d6ffed9c3 100644 --- a/CommonServer/Types/Database/Select.ts +++ b/CommonServer/Types/Database/Select.ts @@ -1,5 +1,5 @@ -import BaseModel from 'Common/Models/BaseModel'; -import Dictionary from 'Common/Types/Dictionary'; +import BaseModel from "Common/Models/BaseModel"; +import Dictionary from "Common/Types/Dictionary"; export type SelectPropertyOptions = boolean | Dictionary<boolean>; @@ -8,7 +8,7 @@ export type SelectPropertyOptions = boolean | Dictionary<boolean>; */ export declare type SelectOptions<Entity> = { - [P in keyof Entity]?: SelectPropertyOptions; + [P in keyof Entity]?: SelectPropertyOptions; }; type Select<TBaseModel extends BaseModel> = SelectOptions<TBaseModel>; diff --git a/CommonServer/Types/Database/SelectUtil.ts b/CommonServer/Types/Database/SelectUtil.ts index 5167cd0b81..7d4149c5cb 100644 --- a/CommonServer/Types/Database/SelectUtil.ts +++ b/CommonServer/Types/Database/SelectUtil.ts @@ -1,39 +1,39 @@ -import RelationSelect from './RelationSelect'; -import Select from './Select'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import { JSONObject } from 'Common/Types/JSON'; -import Typeof from 'Common/Types/Typeof'; +import RelationSelect from "./RelationSelect"; +import Select from "./Select"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import { JSONObject } from "Common/Types/JSON"; +import Typeof from "Common/Types/Typeof"; export default class SelectUtil { - public static sanitizeSelect<TBaseModel extends BaseModel>( - modelType: BaseModelType, - select: Select<TBaseModel> - ): { - select: Select<TBaseModel>; - relationSelect: RelationSelect<TBaseModel>; - } { - const model: BaseModel = new modelType(); - const relationSelect: RelationSelect<TBaseModel> = {}; + public static sanitizeSelect<TBaseModel extends BaseModel>( + modelType: BaseModelType, + select: Select<TBaseModel>, + ): { + select: Select<TBaseModel>; + relationSelect: RelationSelect<TBaseModel>; + } { + const model: BaseModel = new modelType(); + const relationSelect: RelationSelect<TBaseModel> = {}; - for (const key in select) { - if (model.isEntityColumn(key)) { - if (typeof (select as JSONObject)[key] === Typeof.Object) { - (relationSelect as any)[key] = true; - (select as any)[key] = { - ...(select as any)[key], - _id: true, - }; - } else { - // if you want to relationSelect the whole object, you only do the id because of security. - (select as any)[key] = { - ...(select as any)[key], - _id: true, - } as any; - (relationSelect as any)[key] = true; - } - } + for (const key in select) { + if (model.isEntityColumn(key)) { + if (typeof (select as JSONObject)[key] === Typeof.Object) { + (relationSelect as any)[key] = true; + (select as any)[key] = { + ...(select as any)[key], + _id: true, + }; + } else { + // if you want to relationSelect the whole object, you only do the id because of security. + (select as any)[key] = { + ...(select as any)[key], + _id: true, + } as any; + (relationSelect as any)[key] = true; } - - return { select, relationSelect }; + } } + + return { select, relationSelect }; + } } diff --git a/CommonServer/Types/Database/Sort.ts b/CommonServer/Types/Database/Sort.ts index 03eabfb7c8..968a084efa 100644 --- a/CommonServer/Types/Database/Sort.ts +++ b/CommonServer/Types/Database/Sort.ts @@ -1,19 +1,19 @@ -import BaseModel from 'Common/Models/BaseModel'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import DatabaseProperty from 'Common/Types/Database/DatabaseProperty'; -import { FindOptionsOrderProperty, FindOptionsOrderValue } from 'typeorm'; +import BaseModel from "Common/Models/BaseModel"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import DatabaseProperty from "Common/Types/Database/DatabaseProperty"; +import { FindOptionsOrderProperty, FindOptionsOrderValue } from "typeorm"; export declare type FindOrderProperty<Property> = - Property extends DatabaseProperty - ? SortOrder - : FindOptionsOrderProperty<Property> extends FindOptionsOrderValue - ? SortOrder - : never; + Property extends DatabaseProperty + ? SortOrder + : FindOptionsOrderProperty<Property> extends FindOptionsOrderValue + ? SortOrder + : never; /** * Order by find options. */ export declare type FindOrder<Entity> = { - [P in keyof Entity]?: SortOrder; + [P in keyof Entity]?: SortOrder; }; type Sort<TBaseModel extends BaseModel> = FindOrder<TBaseModel>; diff --git a/CommonServer/Types/Database/UpdateBy.ts b/CommonServer/Types/Database/UpdateBy.ts index 610c751bb9..7e420a0da9 100644 --- a/CommonServer/Types/Database/UpdateBy.ts +++ b/CommonServer/Types/Database/UpdateBy.ts @@ -1,11 +1,11 @@ -import UpdateOneBy from './UpdateOneBy'; -import BaseModel from 'Common/Models/BaseModel'; -import PositiveNumber from 'Common/Types/PositiveNumber'; +import UpdateOneBy from "./UpdateOneBy"; +import BaseModel from "Common/Models/BaseModel"; +import PositiveNumber from "Common/Types/PositiveNumber"; interface UpdateBy<TBaseModel extends BaseModel> - extends UpdateOneBy<TBaseModel> { - limit: PositiveNumber | number; - skip: PositiveNumber | number; + extends UpdateOneBy<TBaseModel> { + limit: PositiveNumber | number; + skip: PositiveNumber | number; } export default UpdateBy; diff --git a/CommonServer/Types/Database/UpdateByID.ts b/CommonServer/Types/Database/UpdateByID.ts index 4c193b5318..33f498c98a 100644 --- a/CommonServer/Types/Database/UpdateByID.ts +++ b/CommonServer/Types/Database/UpdateByID.ts @@ -1,10 +1,10 @@ -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import ObjectID from 'Common/Types/ObjectID'; -import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import ObjectID from "Common/Types/ObjectID"; +import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"; export default interface UpdateBy<TBaseModel extends BaseModel> { - id: ObjectID; - data: QueryDeepPartialEntity<TBaseModel>; - props: DatabaseCommonInteractionProps; + id: ObjectID; + data: QueryDeepPartialEntity<TBaseModel>; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/Database/UpdateByIDAndFetch.ts b/CommonServer/Types/Database/UpdateByIDAndFetch.ts index a76c07939d..356fe407fc 100644 --- a/CommonServer/Types/Database/UpdateByIDAndFetch.ts +++ b/CommonServer/Types/Database/UpdateByIDAndFetch.ts @@ -1,8 +1,8 @@ -import Select from './Select'; -import UpdateByID from './UpdateByID'; -import BaseModel from 'Common/Models/BaseModel'; +import Select from "./Select"; +import UpdateByID from "./UpdateByID"; +import BaseModel from "Common/Models/BaseModel"; export default interface UpdateByIDAndFetch<TBaseModel extends BaseModel> - extends UpdateByID<TBaseModel> { - select?: Select<TBaseModel> | undefined; + extends UpdateByID<TBaseModel> { + select?: Select<TBaseModel> | undefined; } diff --git a/CommonServer/Types/Database/UpdateOneBy.ts b/CommonServer/Types/Database/UpdateOneBy.ts index 4d35b5068e..973d62b1a8 100644 --- a/CommonServer/Types/Database/UpdateOneBy.ts +++ b/CommonServer/Types/Database/UpdateOneBy.ts @@ -1,10 +1,10 @@ -import Query from './Query'; -import BaseModel from 'Common/Models/BaseModel'; -import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps'; -import PartialEntity from 'Common/Types/Database/PartialEntity'; +import Query from "./Query"; +import BaseModel from "Common/Models/BaseModel"; +import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps"; +import PartialEntity from "Common/Types/Database/PartialEntity"; export default interface UpdateOneBy<TBaseModel extends BaseModel> { - query: Query<TBaseModel>; - data: PartialEntity<TBaseModel>; - props: DatabaseCommonInteractionProps; + query: Query<TBaseModel>; + data: PartialEntity<TBaseModel>; + props: DatabaseCommonInteractionProps; } diff --git a/CommonServer/Types/Domain.ts b/CommonServer/Types/Domain.ts index 4bf8d1f5c1..1296966d8c 100644 --- a/CommonServer/Types/Domain.ts +++ b/CommonServer/Types/Domain.ts @@ -1,50 +1,47 @@ -import logger from '../Utils/Logger'; -import DomainCommon from 'Common/Types/Domain'; -import { PromiseRejectErrorFunction } from 'Common/Types/FunctionTypes'; -import dns from 'dns'; +import logger from "../Utils/Logger"; +import DomainCommon from "Common/Types/Domain"; +import { PromiseRejectErrorFunction } from "Common/Types/FunctionTypes"; +import dns from "dns"; export default class Domain extends DomainCommon { - public static verifyTxtRecord( - domain: Domain | string, - verificationText: string - ): Promise<boolean> { - return new Promise( - ( - resolve: (isVerfified: boolean) => void, - reject: PromiseRejectErrorFunction - ) => { - dns.resolveTxt( - domain.toString(), - ( - err: NodeJS.ErrnoException | null, - data: Array<Array<string>> - ) => { - if (err) { - return reject(err); - } - - logger.debug('Verify TXT Record'); - logger.debug('Domain ' + domain.toString()); - logger.debug('Data: '); - logger.debug(data); - - let isVerified: boolean = false; - for (const item of data) { - let txt: Array<string> | string = item; - if (Array.isArray(txt)) { - txt = (txt as Array<string>).join(''); - } - - if (txt === verificationText) { - isVerified = true; - break; - } - } - - resolve(isVerified); - } - ); + public static verifyTxtRecord( + domain: Domain | string, + verificationText: string, + ): Promise<boolean> { + return new Promise( + ( + resolve: (isVerfified: boolean) => void, + reject: PromiseRejectErrorFunction, + ) => { + dns.resolveTxt( + domain.toString(), + (err: NodeJS.ErrnoException | null, data: Array<Array<string>>) => { + if (err) { + return reject(err); } + + logger.debug("Verify TXT Record"); + logger.debug("Domain " + domain.toString()); + logger.debug("Data: "); + logger.debug(data); + + let isVerified: boolean = false; + for (const item of data) { + let txt: Array<string> | string = item; + if (Array.isArray(txt)) { + txt = (txt as Array<string>).join(""); + } + + if (txt === verificationText) { + isVerified = true; + break; + } + } + + resolve(isVerified); + }, ); - } + }, + ); + } } diff --git a/CommonServer/Types/FeatureSet.ts b/CommonServer/Types/FeatureSet.ts index 448917d9b4..59c8a2c6cf 100644 --- a/CommonServer/Types/FeatureSet.ts +++ b/CommonServer/Types/FeatureSet.ts @@ -1,3 +1,3 @@ export default interface FeatureSet { - init: () => Promise<void>; + init: () => Promise<void>; } diff --git a/CommonServer/Types/Markdown.ts b/CommonServer/Types/Markdown.ts index a2ff4aa7ff..c6c4e612d1 100644 --- a/CommonServer/Types/Markdown.ts +++ b/CommonServer/Types/Markdown.ts @@ -1,138 +1,138 @@ -import { Renderer, marked } from 'marked'; +import { Renderer, marked } from "marked"; export type MarkdownRenderer = Renderer; export enum MarkdownContentType { - Docs, - Blog, - Email, + Docs, + Blog, + Email, } export default class Markdown { - private static blogRenderer: Renderer | null = null; - private static docsRenderer: Renderer | null = null; - private static emailRenderer: Renderer | null = null; + private static blogRenderer: Renderer | null = null; + private static docsRenderer: Renderer | null = null; + private static emailRenderer: Renderer | null = null; - public static async convertToHTML( - markdown: string, - contentType: MarkdownContentType - ): Promise<string> { - let renderer: Renderer | null = null; + public static async convertToHTML( + markdown: string, + contentType: MarkdownContentType, + ): Promise<string> { + let renderer: Renderer | null = null; - if (contentType === MarkdownContentType.Blog) { - renderer = this.getBlogRenderer(); - } - - if (contentType === MarkdownContentType.Docs) { - renderer = this.getDocsRenderer(); - } - - if (contentType === MarkdownContentType.Email) { - renderer = this.getEmailRenderer(); - } - - const htmlBody: string = await marked(markdown, { - renderer: renderer, - }); - - return htmlBody; + if (contentType === MarkdownContentType.Blog) { + renderer = this.getBlogRenderer(); } - private static getEmailRenderer(): Renderer { - if (this.emailRenderer !== null) { - return this.emailRenderer; - } - - const renderer: Renderer = new Renderer(); - - this.emailRenderer = renderer; - - return renderer; + if (contentType === MarkdownContentType.Docs) { + renderer = this.getDocsRenderer(); } - private static getDocsRenderer(): Renderer { - if (this.docsRenderer !== null) { - return this.docsRenderer; - } + if (contentType === MarkdownContentType.Email) { + renderer = this.getEmailRenderer(); + } - const renderer: Renderer = new Renderer(); + const htmlBody: string = await marked(markdown, { + renderer: renderer, + }); - renderer.paragraph = function (text) { - return `<p class="mt-2 mb-2 leading-8 text-gray-600">${text}</p>`; - }; + return htmlBody; + } - renderer.blockquote = function (quote) { - return `<blockquote class="p-4 pt-1 pb-1 my-4 border-s-4 border-indigo-500"> + private static getEmailRenderer(): Renderer { + if (this.emailRenderer !== null) { + return this.emailRenderer; + } + + const renderer: Renderer = new Renderer(); + + this.emailRenderer = renderer; + + return renderer; + } + + private static getDocsRenderer(): Renderer { + if (this.docsRenderer !== null) { + return this.docsRenderer; + } + + const renderer: Renderer = new Renderer(); + + renderer.paragraph = function (text) { + return `<p class="mt-2 mb-2 leading-8 text-gray-600">${text}</p>`; + }; + + renderer.blockquote = function (quote) { + return `<blockquote class="p-4 pt-1 pb-1 my-4 border-s-4 border-indigo-500"> <div class="leading-8 text-gray-600">${quote}</div> </blockquote>`; - }; + }; - renderer.image = function (href, _title, text) { - return `<img src="${href}" alt="${text}" class="rounded-md shadow-md" />`; - }; + renderer.image = function (href, _title, text) { + return `<img src="${href}" alt="${text}" class="rounded-md shadow-md" />`; + }; - renderer.code = function (code, language) { - return `<pre class="language-${language} rounded-md"><code class="language-${language} rounded-md">${code}</code></pre>`; - }; + renderer.code = function (code, language) { + return `<pre class="language-${language} rounded-md"><code class="language-${language} rounded-md">${code}</code></pre>`; + }; - renderer.heading = function (text, level) { - if (level === 1) { - return `<h1 class="my-5 mt-8 text-4xl font-bold tracking-tight text-gray-800">${text}</h1>`; - } else if (level === 2) { - return `<h2 class="my-5 mt-8 text-3xl font-bold tracking-tight text-gray-800">${text}</h2>`; - } else if (level === 3) { - return `<h3 class="my-5 mt-8 text-2xl font-bold tracking-tight text-gray-800">${text}</h3>`; - } else if (level === 4) { - return `<h4 class="my-5 mt-8 text-xl font-bold tracking-tight text-gray-800">${text}</h4>`; - } else if (level === 5) { - return `<h5 class="my-5 mt-8 text-lg font-bold tracking-tight text-gray-800">${text}</h5>`; - } - return `<h6 class="my-5 tracking-tight font-bold text-gray-800">${text}</h6>`; - }; + renderer.heading = function (text, level) { + if (level === 1) { + return `<h1 class="my-5 mt-8 text-4xl font-bold tracking-tight text-gray-800">${text}</h1>`; + } else if (level === 2) { + return `<h2 class="my-5 mt-8 text-3xl font-bold tracking-tight text-gray-800">${text}</h2>`; + } else if (level === 3) { + return `<h3 class="my-5 mt-8 text-2xl font-bold tracking-tight text-gray-800">${text}</h3>`; + } else if (level === 4) { + return `<h4 class="my-5 mt-8 text-xl font-bold tracking-tight text-gray-800">${text}</h4>`; + } else if (level === 5) { + return `<h5 class="my-5 mt-8 text-lg font-bold tracking-tight text-gray-800">${text}</h5>`; + } + return `<h6 class="my-5 tracking-tight font-bold text-gray-800">${text}</h6>`; + }; - this.docsRenderer = renderer; + this.docsRenderer = renderer; - return renderer; + return renderer; + } + + private static getBlogRenderer(): Renderer { + if (this.blogRenderer !== null) { + return this.blogRenderer; } - private static getBlogRenderer(): Renderer { - if (this.blogRenderer !== null) { - return this.blogRenderer; - } + const renderer: Renderer = new Renderer(); - const renderer: Renderer = new Renderer(); + renderer.paragraph = function (text) { + return `<p class="mt-2 mb-2 leading-8 text-gray-600">${text}</p>`; + }; - renderer.paragraph = function (text) { - return `<p class="mt-2 mb-2 leading-8 text-gray-600">${text}</p>`; - }; - - renderer.blockquote = function (quote) { - return `<blockquote class="p-4 pt-1 pb-1 my-4 border-s-4 border-indigo-500"> + renderer.blockquote = function (quote) { + return `<blockquote class="p-4 pt-1 pb-1 my-4 border-s-4 border-indigo-500"> <div class="leading-8 text-gray-600">${quote}</div> </blockquote>`; - }; + }; - renderer.code = function (code, language) { - return `<pre class="language-${language} rounded-md"><code class="language-${language} rounded-md">${code}</code></pre>`; - }; + renderer.code = function (code, language) { + return `<pre class="language-${language} rounded-md"><code class="language-${language} rounded-md">${code}</code></pre>`; + }; - renderer.heading = function (text, level) { - if (level === 1) { - return `<h1 class="my-5 mt-8 text-4xl font-bold tracking-tight text-gray-800">${text}</h1>`; - } else if (level === 2) { - return `<h2 class="my-5 mt-8 text-3xl font-bold tracking-tight text-gray-800">${text}</h2>`; - } else if (level === 3) { - return `<h3 class="my-5 mt-8 text-2xl font-bold tracking-tight text-gray-800">${text}</h3>`; - } else if (level === 4) { - return `<h4 class="my-5 mt-8 text-xl font-bold tracking-tight text-gray-800">${text}</h4>`; - } else if (level === 5) { - return `<h5 class="my-5 mt-8 text-lg font-bold tracking-tight text-gray-800">${text}</h5>`; - } - return `<h6 class="my-5 tracking-tight font-bold text-gray-800">${text}</h6>`; - }; + renderer.heading = function (text, level) { + if (level === 1) { + return `<h1 class="my-5 mt-8 text-4xl font-bold tracking-tight text-gray-800">${text}</h1>`; + } else if (level === 2) { + return `<h2 class="my-5 mt-8 text-3xl font-bold tracking-tight text-gray-800">${text}</h2>`; + } else if (level === 3) { + return `<h3 class="my-5 mt-8 text-2xl font-bold tracking-tight text-gray-800">${text}</h3>`; + } else if (level === 4) { + return `<h4 class="my-5 mt-8 text-xl font-bold tracking-tight text-gray-800">${text}</h4>`; + } else if (level === 5) { + return `<h5 class="my-5 mt-8 text-lg font-bold tracking-tight text-gray-800">${text}</h5>`; + } + return `<h6 class="my-5 tracking-tight font-bold text-gray-800">${text}</h6>`; + }; - this.blogRenderer = renderer; + this.blogRenderer = renderer; - return renderer; - } + return renderer; + } } diff --git a/CommonServer/Types/Workflow/ComponentCode.ts b/CommonServer/Types/Workflow/ComponentCode.ts index 0629549b97..33e642ccff 100644 --- a/CommonServer/Types/Workflow/ComponentCode.ts +++ b/CommonServer/Types/Workflow/ComponentCode.ts @@ -1,48 +1,48 @@ // this class is the base class that all the component can implement // -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Exception from 'Common/Types/Exception/Exception'; -import { JSONArray, JSONObject, JSONValue } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Exception from "Common/Types/Exception/Exception"; +import { JSONArray, JSONObject, JSONValue } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; export interface RunOptions { - log: (item: string | JSONObject | Error | JSONArray | JSONValue) => void; - workflowLogId: ObjectID; - workflowId: ObjectID; - projectId: ObjectID; - onError: (exception: Exception) => Exception; + log: (item: string | JSONObject | Error | JSONArray | JSONValue) => void; + workflowLogId: ObjectID; + workflowId: ObjectID; + projectId: ObjectID; + onError: (exception: Exception) => Exception; } export interface RunReturnType { - returnValues: JSONObject; - executePort?: Port | undefined; + returnValues: JSONObject; + executePort?: Port | undefined; } export default class ComponentCode { - private metadata: ComponentMetadata | null = null; + private metadata: ComponentMetadata | null = null; - public constructor() {} + public constructor() {} - public setMetadata(metadata: ComponentMetadata): void { - this.metadata = metadata; + public setMetadata(metadata: ComponentMetadata): void { + this.metadata = metadata; + } + + public getMetadata(): ComponentMetadata { + if (!this.metadata) { + throw new BadDataException("ComponentMetadata not found"); } - public getMetadata(): ComponentMetadata { - if (!this.metadata) { - throw new BadDataException('ComponentMetadata not found'); - } + return this.metadata; + } - return this.metadata; - } - - public async run( - _args: JSONObject, - _options: RunOptions - ): Promise<RunReturnType> { - return await Promise.resolve({ - returnValues: {}, - port: undefined, - }); - } + public async run( + _args: JSONObject, + _options: RunOptions, + ): Promise<RunReturnType> { + return await Promise.resolve({ + returnValues: {}, + port: undefined, + }); + } } diff --git a/CommonServer/Types/Workflow/Components/API/Delete.ts b/CommonServer/Types/Workflow/Components/API/Delete.ts index f9de35c695..46d2178683 100644 --- a/CommonServer/Types/Workflow/Components/API/Delete.ts +++ b/CommonServer/Types/Workflow/Components/API/Delete.ts @@ -1,73 +1,70 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import { ApiComponentUtils } from './Utils'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import APIException from 'Common/Types/Exception/ApiException'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import APIComponents from 'Common/Types/Workflow/Components/API'; -import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import { ApiComponentUtils } from "./Utils"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import APIException from "Common/Types/Exception/ApiException"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import APIComponents from "Common/Types/Workflow/Components/API"; +import API from "Common/Utils/API"; export default class ApiDelete extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = APIComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.ApiDelete; - } - ); + const Component: ComponentMetadata | undefined = APIComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.ApiDelete; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const result: { args: JSONObject; successPort: Port; errorPort: Port } = - ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); + this.setMetadata(Component); + } - let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = - null; + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const result: { args: JSONObject; successPort: Port; errorPort: Port } = + ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); - try { - apiResult = await API.delete( - args['url'] as URL, - args['request-body'] as JSONObject, - args['request-headers'] as Dictionary<string> - ); + let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = null; - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(apiResult), - executePort: result.successPort, - }); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(err), - executePort: result.successPort, - }); - } + try { + apiResult = await API.delete( + args["url"] as URL, + args["request-body"] as JSONObject, + args["request-headers"] as Dictionary<string>, + ); - if (apiResult) { - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(apiResult), - executePort: result.successPort, - }); - } + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(err), + executePort: result.successPort, + }); + } - throw options.onError( - new APIException('Something wrong happened.') - ); - } + if (apiResult) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } + + throw options.onError(new APIException("Something wrong happened.")); } + } } diff --git a/CommonServer/Types/Workflow/Components/API/Get.ts b/CommonServer/Types/Workflow/Components/API/Get.ts index 9c20ee5c9b..2f3307412e 100644 --- a/CommonServer/Types/Workflow/Components/API/Get.ts +++ b/CommonServer/Types/Workflow/Components/API/Get.ts @@ -1,73 +1,70 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import { ApiComponentUtils } from './Utils'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import APIException from 'Common/Types/Exception/ApiException'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import APIComponents from 'Common/Types/Workflow/Components/API'; -import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import { ApiComponentUtils } from "./Utils"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import APIException from "Common/Types/Exception/ApiException"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import APIComponents from "Common/Types/Workflow/Components/API"; +import API from "Common/Utils/API"; export default class ApiGet extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = APIComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.ApiGet; - } - ); + const Component: ComponentMetadata | undefined = APIComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.ApiGet; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const result: { args: JSONObject; successPort: Port; errorPort: Port } = - ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); + this.setMetadata(Component); + } - let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = - null; + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const result: { args: JSONObject; successPort: Port; errorPort: Port } = + ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); - try { - apiResult = await API.get( - args['url'] as URL, - args['request-body'] as JSONObject, - args['request-headers'] as Dictionary<string> - ); + let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = null; - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(apiResult), - executePort: result.successPort, - }); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(err), - executePort: result.successPort, - }); - } + try { + apiResult = await API.get( + args["url"] as URL, + args["request-body"] as JSONObject, + args["request-headers"] as Dictionary<string>, + ); - if (apiResult) { - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(apiResult), - executePort: result.successPort, - }); - } + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(err), + executePort: result.successPort, + }); + } - throw options.onError( - new APIException('Something wrong happened.') - ); - } + if (apiResult) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } + + throw options.onError(new APIException("Something wrong happened.")); } + } } diff --git a/CommonServer/Types/Workflow/Components/API/Post.ts b/CommonServer/Types/Workflow/Components/API/Post.ts index 2280b8e514..d16cdf4167 100644 --- a/CommonServer/Types/Workflow/Components/API/Post.ts +++ b/CommonServer/Types/Workflow/Components/API/Post.ts @@ -1,73 +1,70 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import { ApiComponentUtils } from './Utils'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import APIException from 'Common/Types/Exception/ApiException'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import APIComponents from 'Common/Types/Workflow/Components/API'; -import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import { ApiComponentUtils } from "./Utils"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import APIException from "Common/Types/Exception/ApiException"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import APIComponents from "Common/Types/Workflow/Components/API"; +import API from "Common/Utils/API"; export default class ApiPost extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = APIComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.ApiPost; - } - ); + const Component: ComponentMetadata | undefined = APIComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.ApiPost; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const result: { args: JSONObject; successPort: Port; errorPort: Port } = - ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); + this.setMetadata(Component); + } - let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = - null; + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const result: { args: JSONObject; successPort: Port; errorPort: Port } = + ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); - try { - apiResult = await API.post( - args['url'] as URL, - args['request-body'] as JSONObject, - args['request-headers'] as Dictionary<string> - ); + let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = null; - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(apiResult), - executePort: result.successPort, - }); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(err), - executePort: result.successPort, - }); - } + try { + apiResult = await API.post( + args["url"] as URL, + args["request-body"] as JSONObject, + args["request-headers"] as Dictionary<string>, + ); - if (apiResult) { - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(apiResult), - executePort: result.successPort, - }); - } + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(err), + executePort: result.successPort, + }); + } - throw options.onError( - new APIException('Something wrong happened.') - ); - } + if (apiResult) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } + + throw options.onError(new APIException("Something wrong happened.")); } + } } diff --git a/CommonServer/Types/Workflow/Components/API/Put.ts b/CommonServer/Types/Workflow/Components/API/Put.ts index eb0b95be0a..cf8c51eb92 100644 --- a/CommonServer/Types/Workflow/Components/API/Put.ts +++ b/CommonServer/Types/Workflow/Components/API/Put.ts @@ -1,73 +1,70 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import { ApiComponentUtils } from './Utils'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import APIException from 'Common/Types/Exception/ApiException'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import APIComponents from 'Common/Types/Workflow/Components/API'; -import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import { ApiComponentUtils } from "./Utils"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import APIException from "Common/Types/Exception/ApiException"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import APIComponents from "Common/Types/Workflow/Components/API"; +import API from "Common/Utils/API"; export default class ApiPut extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = APIComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.ApiPut; - } - ); + const Component: ComponentMetadata | undefined = APIComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.ApiPut; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const result: { args: JSONObject; successPort: Port; errorPort: Port } = - ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); + this.setMetadata(Component); + } - let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = - null; + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const result: { args: JSONObject; successPort: Port; errorPort: Port } = + ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); - try { - apiResult = await API.put( - args['url'] as URL, - args['request-body'] as JSONObject, - args['request-headers'] as Dictionary<string> - ); + let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = null; - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(apiResult), - executePort: result.successPort, - }); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(err), - executePort: result.successPort, - }); - } + try { + apiResult = await API.put( + args["url"] as URL, + args["request-body"] as JSONObject, + args["request-headers"] as Dictionary<string>, + ); - if (apiResult) { - return Promise.resolve({ - returnValues: ApiComponentUtils.getReturnValues(apiResult), - executePort: result.successPort, - }); - } + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(err), + executePort: result.successPort, + }); + } - throw options.onError( - new APIException('Something wrong happened.') - ); - } + if (apiResult) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } + + throw options.onError(new APIException("Something wrong happened.")); } + } } diff --git a/CommonServer/Types/Workflow/Components/API/Utils.ts b/CommonServer/Types/Workflow/Components/API/Utils.ts index f31fa6f92b..4ee3272f59 100644 --- a/CommonServer/Types/Workflow/Components/API/Utils.ts +++ b/CommonServer/Types/Workflow/Components/API/Utils.ts @@ -1,87 +1,79 @@ -import { RunOptions } from '../../ComponentCode'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import { RunOptions } from "../../ComponentCode"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; export class ApiComponentUtils { - public static getReturnValues( - response: HTTPResponse<JSONObject> | HTTPErrorResponse - ): JSONObject { - if (response instanceof HTTPErrorResponse) { - return { - 'response-status': response.statusCode, - 'response-body': response.jsonData, - 'response-headers': response.headers, - error: response.message || 'Server Error.', - }; - } - - return { - 'response-status': response.statusCode, - 'response-body': response.jsonData, - 'response-headers': response.headers, - error: null, - }; + public static getReturnValues( + response: HTTPResponse<JSONObject> | HTTPErrorResponse, + ): JSONObject { + if (response instanceof HTTPErrorResponse) { + return { + "response-status": response.statusCode, + "response-body": response.jsonData, + "response-headers": response.headers, + error: response.message || "Server Error.", + }; } - public static sanitizeArgs( - metadata: ComponentMetadata, - args: JSONObject, - options: RunOptions - ): { args: JSONObject; successPort: Port; errorPort: Port } { - const successPort: Port | undefined = metadata.outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + return { + "response-status": response.statusCode, + "response-body": response.jsonData, + "response-headers": response.headers, + error: null, + }; + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public static sanitizeArgs( + metadata: ComponentMetadata, + args: JSONObject, + options: RunOptions, + ): { args: JSONObject; successPort: Port; errorPort: Port } { + const successPort: Port | undefined = metadata.outPorts.find((p: Port) => { + return p.id === "success"; + }); - const errorPort: Port | undefined = metadata.outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - if (args['request-body'] && typeof args['request-body'] === 'string') { - args['request-body'] = JSONFunctions.parse( - `${args['request-body'] as string}` - ); - } - - if ( - args['request-headers'] && - typeof args['request-headers'] === 'string' - ) { - args['request-headers'] = JSONFunctions.parse( - args['request-headers'] as string - ); - } - - if (!args['url']) { - throw options.onError(new BadDataException('URL not found')); - } - - if (args['url'] && typeof args['url'] !== 'string') { - throw options.onError( - new BadDataException('URL is not type of string') - ); - } - - args['url'] = URL.fromString(args['url'] as string); - - return { args, successPort, errorPort }; + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } + + const errorPort: Port | undefined = metadata.outPorts.find((p: Port) => { + return p.id === "error"; + }); + + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); + } + + if (args["request-body"] && typeof args["request-body"] === "string") { + args["request-body"] = JSONFunctions.parse( + `${args["request-body"] as string}`, + ); + } + + if ( + args["request-headers"] && + typeof args["request-headers"] === "string" + ) { + args["request-headers"] = JSONFunctions.parse( + args["request-headers"] as string, + ); + } + + if (!args["url"]) { + throw options.onError(new BadDataException("URL not found")); + } + + if (args["url"] && typeof args["url"] !== "string") { + throw options.onError(new BadDataException("URL is not type of string")); + } + + args["url"] = URL.fromString(args["url"] as string); + + return { args, successPort, errorPort }; + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts index bd9c72a175..414817371c 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts @@ -1,139 +1,126 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BaseModel from 'Common/Models/BaseModel'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Text from 'Common/Types/Text'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BaseModel from "Common/Models/BaseModel"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Text from "Common/Types/Text"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; export default class CreateManyBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends ComponentCode { - private modelService: DatabaseService<TBaseModel> | null = null; + private modelService: DatabaseService<TBaseModel> | null = null; - public constructor(modelService: DatabaseService<TBaseModel>) { - super(); + public constructor(modelService: DatabaseService<TBaseModel>) { + super(); - const BaseModelComponent: ComponentMetadata | undefined = - BaseModelComponents.getComponents(modelService.getModel()).find( - (i: ComponentMetadata) => { - return ( - i.id === - `${Text.pascalCaseToDashes( - modelService.getModel().tableName! - )}-create-many` - ); - } - ); + const BaseModelComponent: ComponentMetadata | undefined = + BaseModelComponents.getComponents(modelService.getModel()).find( + (i: ComponentMetadata) => { + return ( + i.id === + `${Text.pascalCaseToDashes( + modelService.getModel().tableName!, + )}-create-many` + ); + }, + ); - if (!BaseModelComponent) { - throw new BadDataException( - 'Create many component for ' + - modelService.getModel().tableName + - ' not found.' - ); - } - - this.setMetadata(BaseModelComponent); - this.modelService = modelService; + if (!BaseModelComponent) { + throw new BadDataException( + "Create many component for " + + modelService.getModel().tableName + + " not found.", + ); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + this.setMetadata(BaseModelComponent); + this.modelService = modelService; + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - try { - if (!this.modelService) { - throw options.onError( - new BadDataException('modelService is undefined.') - ); - } - - if (!args['json-array']) { - throw options.onError( - new BadDataException('json-array is undefined.') - ); - } - - if (typeof args['json-array'] === 'string') { - args['json-array'] = JSONFunctions.parse( - args['json-array'] as string - ); - } - - if (!Array.isArray(args['json-array'])) { - throw options.onError( - new BadDataException( - 'json-array is should be of type object.' - ) - ); - } - - const array: Array<TBaseModel> = []; - - if (this.modelService.getModel().getTenantColumn()) { - for (const item of args['json-array']) { - (item as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - - array.push( - (await this.modelService.create({ - data: BaseModel.fromJSON<TBaseModel>( - (item as JSONObject) || {}, - this.modelService.modelType - ) as TBaseModel, - props: { - isRoot: true, - tenantId: options.projectId, - }, - })) as TBaseModel - ); - } - } - - return { - returnValues: { - models: BaseModel.toJSONArray( - array, - this.modelService.modelType - ), - }, - executePort: successPort, - }; - } catch (err: any) { - options.log('Error running component'); - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - return { - returnValues: {}, - executePort: errorPort, - }; - } + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); + + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); + } + + try { + if (!this.modelService) { + throw options.onError( + new BadDataException("modelService is undefined."), + ); + } + + if (!args["json-array"]) { + throw options.onError(new BadDataException("json-array is undefined.")); + } + + if (typeof args["json-array"] === "string") { + args["json-array"] = JSONFunctions.parse(args["json-array"] as string); + } + + if (!Array.isArray(args["json-array"])) { + throw options.onError( + new BadDataException("json-array is should be of type object."), + ); + } + + const array: Array<TBaseModel> = []; + + if (this.modelService.getModel().getTenantColumn()) { + for (const item of args["json-array"]) { + (item as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + + array.push( + (await this.modelService.create({ + data: BaseModel.fromJSON<TBaseModel>( + (item as JSONObject) || {}, + this.modelService.modelType, + ) as TBaseModel, + props: { + isRoot: true, + tenantId: options.projectId, + }, + })) as TBaseModel, + ); + } + } + + return { + returnValues: { + models: BaseModel.toJSONArray(array, this.modelService.modelType), + }, + executePort: successPort, + }; + } catch (err: any) { + options.log("Error running component"); + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + return { + returnValues: {}, + executePort: errorPort, + }; + } + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts index 85b44f6f7f..ab4cf38ccc 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts @@ -1,133 +1,127 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import logger from '../../../../Utils/Logger'; -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BaseModel from 'Common/Models/BaseModel'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Exception from 'Common/Types/Exception/Exception'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Text from 'Common/Types/Text'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import logger from "../../../../Utils/Logger"; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BaseModel from "Common/Models/BaseModel"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Exception from "Common/Types/Exception/Exception"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Text from "Common/Types/Text"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; export default class CreateOneBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends ComponentCode { - private modelService: DatabaseService<TBaseModel> | null = null; + private modelService: DatabaseService<TBaseModel> | null = null; - public constructor(modelService: DatabaseService<TBaseModel>) { - super(); + public constructor(modelService: DatabaseService<TBaseModel>) { + super(); - const BaseModelComponent: ComponentMetadata | undefined = - BaseModelComponents.getComponents(modelService.getModel()).find( - (i: ComponentMetadata) => { - return ( - i.id === - `${Text.pascalCaseToDashes( - modelService.getModel().tableName! - )}-create-one` - ); - } - ); + const BaseModelComponent: ComponentMetadata | undefined = + BaseModelComponents.getComponents(modelService.getModel()).find( + (i: ComponentMetadata) => { + return ( + i.id === + `${Text.pascalCaseToDashes( + modelService.getModel().tableName!, + )}-create-one` + ); + }, + ); - if (!BaseModelComponent) { - throw new BadDataException( - 'Create one component for ' + - modelService.getModel().tableName + - ' not found.' - ); - } - this.setMetadata(BaseModelComponent); - this.modelService = modelService; + if (!BaseModelComponent) { + throw new BadDataException( + "Create one component for " + + modelService.getModel().tableName + + " not found.", + ); + } + this.setMetadata(BaseModelComponent); + this.modelService = modelService; + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } - - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - try { - if (!this.modelService) { - throw options.onError( - new BadDataException('modelService is undefined.') - ); - } - - if (!args['json']) { - throw options.onError( - new BadDataException('JSON is undefined.') - ); - } - - if (typeof args['json'] === 'string') { - args['json'] = JSONFunctions.parse(args['json'] as string); - } - - if (typeof args['json'] !== 'object') { - throw options.onError( - new BadDataException('JSON is should be of type object.') - ); - } - - if (this.modelService.getModel().getTenantColumn()) { - (args['json'] as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - } - - const model: TBaseModel = (await this.modelService.create({ - data: BaseModel.fromJSON<TBaseModel>( - (args['json'] as JSONObject) || {}, - this.modelService.modelType - ) as TBaseModel, - props: { - isRoot: true, - tenantId: options.projectId, - }, - })) as TBaseModel; - - return { - returnValues: { - model: BaseModel.toJSON(model, this.modelService.modelType), - }, - executePort: successPort, - }; - } catch (err: any) { - logger.error(err); - - if (err instanceof Exception) { - options.log(err.getMessage()); - } else { - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - } - - return { - returnValues: {}, - executePort: errorPort, - }; - } + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); } + + try { + if (!this.modelService) { + throw options.onError( + new BadDataException("modelService is undefined."), + ); + } + + if (!args["json"]) { + throw options.onError(new BadDataException("JSON is undefined.")); + } + + if (typeof args["json"] === "string") { + args["json"] = JSONFunctions.parse(args["json"] as string); + } + + if (typeof args["json"] !== "object") { + throw options.onError( + new BadDataException("JSON is should be of type object."), + ); + } + + if (this.modelService.getModel().getTenantColumn()) { + (args["json"] as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + } + + const model: TBaseModel = (await this.modelService.create({ + data: BaseModel.fromJSON<TBaseModel>( + (args["json"] as JSONObject) || {}, + this.modelService.modelType, + ) as TBaseModel, + props: { + isRoot: true, + tenantId: options.projectId, + }, + })) as TBaseModel; + + return { + returnValues: { + model: BaseModel.toJSON(model, this.modelService.modelType), + }, + executePort: successPort, + }; + } catch (err: any) { + logger.error(err); + + if (err instanceof Exception) { + options.log(err.getMessage()); + } else { + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + } + + return { + returnValues: {}, + executePort: errorPort, + }; + } + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts index 16796fb7d1..f6abbfbb17 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts @@ -1,158 +1,152 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import Query from '../../../Database/Query'; -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BaseModel from 'Common/Models/BaseModel'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Text from 'Common/Types/Text'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import Query from "../../../Database/Query"; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BaseModel from "Common/Models/BaseModel"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Text from "Common/Types/Text"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; export default class DeleteManyBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends ComponentCode { - private modelService: DatabaseService<TBaseModel> | null = null; + private modelService: DatabaseService<TBaseModel> | null = null; - public constructor(modelService: DatabaseService<TBaseModel>) { - super(); + public constructor(modelService: DatabaseService<TBaseModel>) { + super(); - const BaseModelComponent: ComponentMetadata | undefined = - BaseModelComponents.getComponents(modelService.getModel()).find( - (i: ComponentMetadata) => { - return ( - i.id === - `${Text.pascalCaseToDashes( - modelService.getModel().tableName! - )}-delete-many` - ); - } - ); + const BaseModelComponent: ComponentMetadata | undefined = + BaseModelComponents.getComponents(modelService.getModel()).find( + (i: ComponentMetadata) => { + return ( + i.id === + `${Text.pascalCaseToDashes( + modelService.getModel().tableName!, + )}-delete-many` + ); + }, + ); - if (!BaseModelComponent) { - throw new BadDataException( - 'Delete many component for ' + - modelService.getModel().tableName + - ' not found.' - ); - } - this.setMetadata(BaseModelComponent); - this.modelService = modelService; + if (!BaseModelComponent) { + throw new BadDataException( + "Delete many component for " + + modelService.getModel().tableName + + " not found.", + ); + } + this.setMetadata(BaseModelComponent); + this.modelService = modelService; + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } - - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - try { - if (!this.modelService) { - throw options.onError( - new BadDataException('modelService is undefined.') - ); - } - - if (!args['query']) { - throw options.onError( - new BadDataException('Query is undefined.') - ); - } - - if (typeof args['query'] === 'string') { - args['query'] = JSONFunctions.parse(args['query'] as string); - } - - if (typeof args['query'] !== 'object') { - throw options.onError( - new BadDataException('Query is should be of type object.') - ); - } - - if (args['skip'] && typeof args['skip'] === 'string') { - args['skip'] = parseInt(args['skip']); - } - - if (args['limit'] && typeof args['limit'] === 'string') { - args['limit'] = parseInt(args['limit']); - } - - if (typeof args['skip'] !== 'number') { - args['skip'] = 0; - } - - if (typeof args['limit'] !== 'number') { - args['limit'] = 10; - } - - if ( - typeof args['limit'] === 'number' && - args['limit'] > LIMIT_PER_PROJECT - ) { - options.log('Limit cannot be ' + args['limit']); - options.log('Setting the limit to ' + LIMIT_PER_PROJECT); - args['limit'] = LIMIT_PER_PROJECT; - } - - if (this.modelService.getModel().getTenantColumn()) { - (args['query'] as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - } - - let query: Query<TBaseModel> = args['query'] as Query<TBaseModel>; - - if (query) { - query = JSONFunctions.deserialize( - args['query'] as JSONObject - ) as Query<TBaseModel>; - } - - await this.modelService.deleteBy({ - query: query || {}, - limit: new PositiveNumber(args['limit'] as number), - skip: new PositiveNumber(args['skip'] as number), - props: { - isRoot: true, - tenantId: options.projectId, - }, - }); - - return { - returnValues: {}, - executePort: successPort, - }; - } catch (err: any) { - options.log('Error running component'); - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - return { - returnValues: {}, - executePort: errorPort, - }; - } + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); } + + try { + if (!this.modelService) { + throw options.onError( + new BadDataException("modelService is undefined."), + ); + } + + if (!args["query"]) { + throw options.onError(new BadDataException("Query is undefined.")); + } + + if (typeof args["query"] === "string") { + args["query"] = JSONFunctions.parse(args["query"] as string); + } + + if (typeof args["query"] !== "object") { + throw options.onError( + new BadDataException("Query is should be of type object."), + ); + } + + if (args["skip"] && typeof args["skip"] === "string") { + args["skip"] = parseInt(args["skip"]); + } + + if (args["limit"] && typeof args["limit"] === "string") { + args["limit"] = parseInt(args["limit"]); + } + + if (typeof args["skip"] !== "number") { + args["skip"] = 0; + } + + if (typeof args["limit"] !== "number") { + args["limit"] = 10; + } + + if ( + typeof args["limit"] === "number" && + args["limit"] > LIMIT_PER_PROJECT + ) { + options.log("Limit cannot be " + args["limit"]); + options.log("Setting the limit to " + LIMIT_PER_PROJECT); + args["limit"] = LIMIT_PER_PROJECT; + } + + if (this.modelService.getModel().getTenantColumn()) { + (args["query"] as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + } + + let query: Query<TBaseModel> = args["query"] as Query<TBaseModel>; + + if (query) { + query = JSONFunctions.deserialize( + args["query"] as JSONObject, + ) as Query<TBaseModel>; + } + + await this.modelService.deleteBy({ + query: query || {}, + limit: new PositiveNumber(args["limit"] as number), + skip: new PositiveNumber(args["skip"] as number), + props: { + isRoot: true, + tenantId: options.projectId, + }, + }); + + return { + returnValues: {}, + executePort: successPort, + }; + } catch (err: any) { + options.log("Error running component"); + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + return { + returnValues: {}, + executePort: errorPort, + }; + } + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts index ad40cf47a7..d57132e34e 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts @@ -1,129 +1,123 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import Query from '../../../Database/Query'; -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BaseModel from 'Common/Models/BaseModel'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Text from 'Common/Types/Text'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import Query from "../../../Database/Query"; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BaseModel from "Common/Models/BaseModel"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Text from "Common/Types/Text"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; export default class DeleteOneBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends ComponentCode { - private modelService: DatabaseService<TBaseModel> | null = null; + private modelService: DatabaseService<TBaseModel> | null = null; - public constructor(modelService: DatabaseService<TBaseModel>) { - super(); + public constructor(modelService: DatabaseService<TBaseModel>) { + super(); - const BaseModelComponent: ComponentMetadata | undefined = - BaseModelComponents.getComponents(modelService.getModel()).find( - (i: ComponentMetadata) => { - return ( - i.id === - `${Text.pascalCaseToDashes( - modelService.getModel().tableName! - )}-delete-one` - ); - } - ); + const BaseModelComponent: ComponentMetadata | undefined = + BaseModelComponents.getComponents(modelService.getModel()).find( + (i: ComponentMetadata) => { + return ( + i.id === + `${Text.pascalCaseToDashes( + modelService.getModel().tableName!, + )}-delete-one` + ); + }, + ); - if (!BaseModelComponent) { - throw new BadDataException( - 'Delete one component for ' + - modelService.getModel().tableName + - ' not found.' - ); - } - this.setMetadata(BaseModelComponent); - this.modelService = modelService; + if (!BaseModelComponent) { + throw new BadDataException( + "Delete one component for " + + modelService.getModel().tableName + + " not found.", + ); + } + this.setMetadata(BaseModelComponent); + this.modelService = modelService; + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } - - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - try { - if (!this.modelService) { - throw options.onError( - new BadDataException('modelService is undefined.') - ); - } - - if (!args['query']) { - throw options.onError( - new BadDataException('Query is undefined.') - ); - } - - if (typeof args['query'] === 'string') { - args['query'] = JSONFunctions.parse(args['query'] as string); - } - - if (typeof args['query'] !== 'object') { - throw options.onError( - new BadDataException('Query is should be of type object.') - ); - } - - let query: Query<TBaseModel> = args['query'] as Query<TBaseModel>; - - if (query) { - query = JSONFunctions.deserialize( - args['query'] as JSONObject - ) as Query<TBaseModel>; - } - - if (this.modelService.getModel().getTenantColumn()) { - (args['query'] as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - } - - await this.modelService.deleteOneBy({ - query: (query as Query<TBaseModel>) || {}, - props: { - isRoot: true, - tenantId: options.projectId, - }, - }); - - return { - returnValues: {}, - executePort: successPort, - }; - } catch (err: any) { - options.log('Error running component'); - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - return { - returnValues: {}, - executePort: errorPort, - }; - } + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); } + + try { + if (!this.modelService) { + throw options.onError( + new BadDataException("modelService is undefined."), + ); + } + + if (!args["query"]) { + throw options.onError(new BadDataException("Query is undefined.")); + } + + if (typeof args["query"] === "string") { + args["query"] = JSONFunctions.parse(args["query"] as string); + } + + if (typeof args["query"] !== "object") { + throw options.onError( + new BadDataException("Query is should be of type object."), + ); + } + + let query: Query<TBaseModel> = args["query"] as Query<TBaseModel>; + + if (query) { + query = JSONFunctions.deserialize( + args["query"] as JSONObject, + ) as Query<TBaseModel>; + } + + if (this.modelService.getModel().getTenantColumn()) { + (args["query"] as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + } + + await this.modelService.deleteOneBy({ + query: (query as Query<TBaseModel>) || {}, + props: { + isRoot: true, + tenantId: options.projectId, + }, + }); + + return { + returnValues: {}, + executePort: successPort, + }; + } catch (err: any) { + options.log("Error running component"); + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + return { + returnValues: {}, + executePort: errorPort, + }; + } + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts index e368d636cf..38507c08f2 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts @@ -1,193 +1,180 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import Query from '../../../Database/Query'; -import Select from '../../../Database/Select'; -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BaseModel from 'Common/Models/BaseModel'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Text from 'Common/Types/Text'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import Query from "../../../Database/Query"; +import Select from "../../../Database/Select"; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BaseModel from "Common/Models/BaseModel"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Text from "Common/Types/Text"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; export default class FindManyBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends ComponentCode { - private modelService: DatabaseService<TBaseModel> | null = null; + private modelService: DatabaseService<TBaseModel> | null = null; - public constructor(modelService: DatabaseService<TBaseModel>) { - super(); + public constructor(modelService: DatabaseService<TBaseModel>) { + super(); - const BaseModelComponent: ComponentMetadata | undefined = - BaseModelComponents.getComponents(modelService.getModel()).find( - (i: ComponentMetadata) => { - return ( - i.id === - `${Text.pascalCaseToDashes( - modelService.getModel().tableName! - )}-find-many` - ); - } - ); + const BaseModelComponent: ComponentMetadata | undefined = + BaseModelComponents.getComponents(modelService.getModel()).find( + (i: ComponentMetadata) => { + return ( + i.id === + `${Text.pascalCaseToDashes( + modelService.getModel().tableName!, + )}-find-many` + ); + }, + ); - if (!BaseModelComponent) { - throw new BadDataException( - 'Find many component for ' + - modelService.getModel().tableName + - ' not found.' - ); - } - this.setMetadata(BaseModelComponent); - this.modelService = modelService; + if (!BaseModelComponent) { + throw new BadDataException( + "Find many component for " + + modelService.getModel().tableName + + " not found.", + ); + } + this.setMetadata(BaseModelComponent); + this.modelService = modelService; + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } - - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - try { - if (!this.modelService) { - throw options.onError( - new BadDataException('modelService is undefined.') - ); - } - - if (!args['query']) { - throw options.onError( - new BadDataException('Query is undefined.') - ); - } - - if (typeof args['query'] === 'string') { - args['query'] = JSONFunctions.parse(args['query'] as string); - } - - if (typeof args['query'] !== 'object') { - throw options.onError( - new BadDataException('Query is should be of type object.') - ); - } - - if (this.modelService.getModel().getTenantColumn()) { - (args['query'] as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - } - - if (!args['select']) { - throw options.onError( - new BadDataException('Select Fields is undefined.') - ); - } - - if (typeof args['select'] === 'string') { - args['select'] = JSONFunctions.parse(args['select'] as string); - } - - if (typeof args['select'] !== 'object') { - throw options.onError( - new BadDataException( - 'Select Fields is should be of type object.' - ) - ); - } - - if (args['skip'] && typeof args['skip'] === 'string') { - args['skip'] = parseInt(args['skip']); - } - - if (args['limit'] && typeof args['limit'] === 'string') { - args['limit'] = parseInt(args['limit']); - } - - if (typeof args['skip'] !== 'number') { - args['skip'] = 0; - } - - if (typeof args['limit'] !== 'number') { - args['limit'] = 10; - } - - if ( - typeof args['limit'] === 'number' && - args['limit'] > LIMIT_PER_PROJECT - ) { - options.log('Limit cannot be ' + args['limit']); - options.log('Setting the limit to ' + LIMIT_PER_PROJECT); - args['limit'] = LIMIT_PER_PROJECT; - } - - let query: Query<TBaseModel> = args['query'] as Query<TBaseModel>; - - if (query) { - query = JSONFunctions.deserialize( - args['query'] as JSONObject - ) as Query<TBaseModel>; - } - - let select: Select<TBaseModel> = args[ - 'select' - ] as Select<TBaseModel>; - - if (select) { - select = JSONFunctions.deserialize( - args['select'] as JSONObject - ) as Select<TBaseModel>; - } - - const models: Array<TBaseModel> = await this.modelService.findBy({ - query: query || {}, - select: select, - limit: new PositiveNumber(args['limit'] as number), - skip: new PositiveNumber(args['skip'] as number), - props: { - isRoot: true, - tenantId: options.projectId, - }, - }); - - return { - returnValues: { - models: BaseModel.toJSONArray( - models, - this.modelService.modelType - ), - }, - executePort: successPort, - }; - } catch (err: any) { - options.log('Error running component'); - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - return { - returnValues: {}, - executePort: errorPort, - }; - } + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); } + + try { + if (!this.modelService) { + throw options.onError( + new BadDataException("modelService is undefined."), + ); + } + + if (!args["query"]) { + throw options.onError(new BadDataException("Query is undefined.")); + } + + if (typeof args["query"] === "string") { + args["query"] = JSONFunctions.parse(args["query"] as string); + } + + if (typeof args["query"] !== "object") { + throw options.onError( + new BadDataException("Query is should be of type object."), + ); + } + + if (this.modelService.getModel().getTenantColumn()) { + (args["query"] as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + } + + if (!args["select"]) { + throw options.onError( + new BadDataException("Select Fields is undefined."), + ); + } + + if (typeof args["select"] === "string") { + args["select"] = JSONFunctions.parse(args["select"] as string); + } + + if (typeof args["select"] !== "object") { + throw options.onError( + new BadDataException("Select Fields is should be of type object."), + ); + } + + if (args["skip"] && typeof args["skip"] === "string") { + args["skip"] = parseInt(args["skip"]); + } + + if (args["limit"] && typeof args["limit"] === "string") { + args["limit"] = parseInt(args["limit"]); + } + + if (typeof args["skip"] !== "number") { + args["skip"] = 0; + } + + if (typeof args["limit"] !== "number") { + args["limit"] = 10; + } + + if ( + typeof args["limit"] === "number" && + args["limit"] > LIMIT_PER_PROJECT + ) { + options.log("Limit cannot be " + args["limit"]); + options.log("Setting the limit to " + LIMIT_PER_PROJECT); + args["limit"] = LIMIT_PER_PROJECT; + } + + let query: Query<TBaseModel> = args["query"] as Query<TBaseModel>; + + if (query) { + query = JSONFunctions.deserialize( + args["query"] as JSONObject, + ) as Query<TBaseModel>; + } + + let select: Select<TBaseModel> = args["select"] as Select<TBaseModel>; + + if (select) { + select = JSONFunctions.deserialize( + args["select"] as JSONObject, + ) as Select<TBaseModel>; + } + + const models: Array<TBaseModel> = await this.modelService.findBy({ + query: query || {}, + select: select, + limit: new PositiveNumber(args["limit"] as number), + skip: new PositiveNumber(args["skip"] as number), + props: { + isRoot: true, + tenantId: options.projectId, + }, + }); + + return { + returnValues: { + models: BaseModel.toJSONArray(models, this.modelService.modelType), + }, + executePort: successPort, + }; + } catch (err: any) { + options.log("Error running component"); + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + return { + returnValues: {}, + executePort: errorPort, + }; + } + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts index 9fb4a21ea2..94681f0a4d 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts @@ -1,165 +1,155 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import Query from '../../../Database/Query'; -import Select from '../../../Database/Select'; -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BaseModel from 'Common/Models/BaseModel'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Text from 'Common/Types/Text'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import Query from "../../../Database/Query"; +import Select from "../../../Database/Select"; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BaseModel from "Common/Models/BaseModel"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Text from "Common/Types/Text"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; export default class FindOneBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends ComponentCode { - private modelService: DatabaseService<TBaseModel> | null = null; + private modelService: DatabaseService<TBaseModel> | null = null; - public constructor(modelService: DatabaseService<TBaseModel>) { - super(); + public constructor(modelService: DatabaseService<TBaseModel>) { + super(); - const BaseModelComponent: ComponentMetadata | undefined = - BaseModelComponents.getComponents(modelService.getModel()).find( - (i: ComponentMetadata) => { - return ( - i.id === - `${Text.pascalCaseToDashes( - modelService.getModel().tableName! - )}-find-one` - ); - } - ); + const BaseModelComponent: ComponentMetadata | undefined = + BaseModelComponents.getComponents(modelService.getModel()).find( + (i: ComponentMetadata) => { + return ( + i.id === + `${Text.pascalCaseToDashes( + modelService.getModel().tableName!, + )}-find-one` + ); + }, + ); - if (!BaseModelComponent) { - throw new BadDataException( - 'Find one component for ' + - modelService.getModel().tableName + - ' not found.' - ); - } - this.setMetadata(BaseModelComponent); - this.modelService = modelService; + if (!BaseModelComponent) { + throw new BadDataException( + "Find one component for " + + modelService.getModel().tableName + + " not found.", + ); + } + this.setMetadata(BaseModelComponent); + this.modelService = modelService; + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } - - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - try { - if (!this.modelService) { - throw options.onError( - new BadDataException('modelService is undefined.') - ); - } - - if (!args['query']) { - throw options.onError( - new BadDataException('Query is undefined.') - ); - } - - if (typeof args['query'] === 'string') { - args['query'] = JSONFunctions.parse(args['query'] as string); - } - - if (typeof args['query'] !== 'object') { - throw options.onError( - new BadDataException('Query is should be of type object.') - ); - } - - if (this.modelService.getModel().getTenantColumn()) { - (args['query'] as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - } - - if (!args['select']) { - throw options.onError( - new BadDataException('Select Fields is undefined.') - ); - } - - if (typeof args['select'] === 'string') { - args['select'] = JSONFunctions.parse(args['select'] as string); - } - - if (typeof args['select'] !== 'object') { - throw options.onError( - new BadDataException( - 'Select Fields is should be of type object.' - ) - ); - } - - let query: Query<TBaseModel> = args['query'] as Query<TBaseModel>; - - if (query) { - query = JSONFunctions.deserialize( - args['query'] as JSONObject - ) as Query<TBaseModel>; - } - - let select: Select<TBaseModel> = args[ - 'select' - ] as Select<TBaseModel>; - - if (select) { - select = JSONFunctions.deserialize( - args['select'] as JSONObject - ) as Select<TBaseModel>; - } - - const model: TBaseModel | null = await this.modelService.findOneBy({ - query: query || {}, - select: select, - props: { - isRoot: true, - tenantId: options.projectId, - }, - }); - - return { - returnValues: { - model: model - ? BaseModel.toJSON(model, this.modelService.modelType) - : null, - }, - executePort: successPort, - }; - } catch (err: any) { - options.log('Error running component'); - - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - - return { - returnValues: {}, - executePort: errorPort, - }; - } + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); } + + try { + if (!this.modelService) { + throw options.onError( + new BadDataException("modelService is undefined."), + ); + } + + if (!args["query"]) { + throw options.onError(new BadDataException("Query is undefined.")); + } + + if (typeof args["query"] === "string") { + args["query"] = JSONFunctions.parse(args["query"] as string); + } + + if (typeof args["query"] !== "object") { + throw options.onError( + new BadDataException("Query is should be of type object."), + ); + } + + if (this.modelService.getModel().getTenantColumn()) { + (args["query"] as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + } + + if (!args["select"]) { + throw options.onError( + new BadDataException("Select Fields is undefined."), + ); + } + + if (typeof args["select"] === "string") { + args["select"] = JSONFunctions.parse(args["select"] as string); + } + + if (typeof args["select"] !== "object") { + throw options.onError( + new BadDataException("Select Fields is should be of type object."), + ); + } + + let query: Query<TBaseModel> = args["query"] as Query<TBaseModel>; + + if (query) { + query = JSONFunctions.deserialize( + args["query"] as JSONObject, + ) as Query<TBaseModel>; + } + + let select: Select<TBaseModel> = args["select"] as Select<TBaseModel>; + + if (select) { + select = JSONFunctions.deserialize( + args["select"] as JSONObject, + ) as Select<TBaseModel>; + } + + const model: TBaseModel | null = await this.modelService.findOneBy({ + query: query || {}, + select: select, + props: { + isRoot: true, + tenantId: options.projectId, + }, + }); + + return { + returnValues: { + model: model + ? BaseModel.toJSON(model, this.modelService.modelType) + : null, + }, + executePort: successPort, + }; + } catch (err: any) { + options.log("Error running component"); + + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + + return { + returnValues: {}, + executePort: errorPort, + }; + } + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/OnCreateBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/OnCreateBaseModel.ts index 680dcd2f55..129eca9c1c 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/OnCreateBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/OnCreateBaseModel.ts @@ -1,11 +1,11 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import OnTriggerBaseModel from './OnTriggerBaseModel'; -import BaseModel from 'Common/Models/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import OnTriggerBaseModel from "./OnTriggerBaseModel"; +import BaseModel from "Common/Models/BaseModel"; export default class OnCreateBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends OnTriggerBaseModel<TBaseModel> { - public constructor(modelService: DatabaseService<TBaseModel>) { - super(modelService, 'on-create'); - } + public constructor(modelService: DatabaseService<TBaseModel>) { + super(modelService, "on-create"); + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/OnDeleteBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/OnDeleteBaseModel.ts index 5a0e3dd633..ca51aaf768 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/OnDeleteBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/OnDeleteBaseModel.ts @@ -1,11 +1,11 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import OnTriggerBaseModel from './OnTriggerBaseModel'; -import BaseModel from 'Common/Models/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import OnTriggerBaseModel from "./OnTriggerBaseModel"; +import BaseModel from "Common/Models/BaseModel"; export default class OnDeleteBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends OnTriggerBaseModel<TBaseModel> { - public constructor(modelService: DatabaseService<TBaseModel>) { - super(modelService, 'on-delete'); - } + public constructor(modelService: DatabaseService<TBaseModel>) { + super(modelService, "on-delete"); + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts index c6711b46f5..0265feb8a1 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts @@ -1,240 +1,232 @@ -import ClusterKeyAuthorization from '../../../../Middleware/ClusterKeyAuthorization'; -import DatabaseService from '../../../../Services/DatabaseService'; -import WorkflowService from '../../../../Services/WorkflowService'; -import { ExpressRequest, ExpressResponse } from '../../../../Utils/Express'; -import logger from '../../../../Utils/Logger'; -import Response from '../../../../Utils/Response'; -import Select from '../../../Database/Select'; -import { RunOptions, RunReturnType } from '../../ComponentCode'; -import TriggerCode, { ExecuteWorkflowType, InitProps } from '../../TriggerCode'; -import BaseModel from 'Common/Models/BaseModel'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import Text from 'Common/Types/Text'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; -import Workflow from 'Model/Models/Workflow'; +import ClusterKeyAuthorization from "../../../../Middleware/ClusterKeyAuthorization"; +import DatabaseService from "../../../../Services/DatabaseService"; +import WorkflowService from "../../../../Services/WorkflowService"; +import { ExpressRequest, ExpressResponse } from "../../../../Utils/Express"; +import logger from "../../../../Utils/Logger"; +import Response from "../../../../Utils/Response"; +import Select from "../../../Database/Select"; +import { RunOptions, RunReturnType } from "../../ComponentCode"; +import TriggerCode, { ExecuteWorkflowType, InitProps } from "../../TriggerCode"; +import BaseModel from "Common/Models/BaseModel"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import Text from "Common/Types/Text"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; +import Workflow from "Model/Models/Workflow"; export default class OnTriggerBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends TriggerCode { - public modelId: string = ''; - public type: string = ''; + public modelId: string = ""; + public type: string = ""; - public service: DatabaseService<TBaseModel> | null = null; + public service: DatabaseService<TBaseModel> | null = null; - public constructor( - modelService: DatabaseService<TBaseModel>, - type: string + public constructor(modelService: DatabaseService<TBaseModel>, type: string) { + super(); + this.service = modelService; + this.modelId = `${Text.pascalCaseToDashes( + modelService.getModel().tableName!, + )}`; + + this.type = type; + + const BaseModelComponent: ComponentMetadata | undefined = + BaseModelComponents.getComponents(modelService.getModel()).find( + (i: ComponentMetadata) => { + return i.id === `${this.modelId}-${this.type}`; + }, + ); + + if (!BaseModelComponent) { + throw new BadDataException( + "On Create trigger component for " + + modelService.getModel().tableName + + " not found.", + ); + } + this.setMetadata(BaseModelComponent); + } + + public override async init(props: InitProps): Promise<void> { + props.router.get( + `/model/:projectId/${this.modelId}/${this.type}`, + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + await this.initTrigger(req, res, props); + }, + ); + + props.router.post( + `/model/:projectId/${this.modelId}/${this.type}`, + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + async (req: ExpressRequest, res: ExpressResponse) => { + await this.initTrigger(req, res, props); + }, + ); + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const data: JSONObject = args["data"] as JSONObject; + + const miscData: JSONObject = (data?.["miscData"] as JSONObject) || {}; + + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); + } + + if ( + !data["_id"] || + !args["select"] || + Object.keys(args["select"]).length === 0 ) { - super(); - this.service = modelService; - this.modelId = `${Text.pascalCaseToDashes( - modelService.getModel().tableName! - )}`; - - this.type = type; - - const BaseModelComponent: ComponentMetadata | undefined = - BaseModelComponents.getComponents(modelService.getModel()).find( - (i: ComponentMetadata) => { - return i.id === `${this.modelId}-${this.type}`; - } - ); - - if (!BaseModelComponent) { - throw new BadDataException( - 'On Create trigger component for ' + - modelService.getModel().tableName + - ' not found.' - ); - } - this.setMetadata(BaseModelComponent); + return { + returnValues: { + model: data + ? BaseModel.toJSON(data as any, this.service!.modelType) + : null, + ...(miscData || {}), + }, + executePort: successPort, + }; } - public override async init(props: InitProps): Promise<void> { - props.router.get( - `/model/:projectId/${this.modelId}/${this.type}`, - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - await this.initTrigger(req, res, props); - } - ); + let select: Select<TBaseModel> = args["select"] as Select<TBaseModel>; - props.router.post( - `/model/:projectId/${this.modelId}/${this.type}`, - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - async (req: ExpressRequest, res: ExpressResponse) => { - await this.initTrigger(req, res, props); - } - ); + if (select) { + select = JSONFunctions.deserialize( + args["select"] as JSONObject, + ) as Select<TBaseModel>; } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const data: JSONObject = args['data'] as JSONObject; + const model: TBaseModel | null = await this.service!.findOneById({ + id: new ObjectID(data["_id"] as string), + props: { + isRoot: true, + }, + select: { + _id: true, + ...select, + }, + }); - const miscData: JSONObject = (data?.['miscData'] as JSONObject) || {}; + if (!model) { + throw new BadDataException( + ("Model not found with id " + args["_id"]) as string, + ); + } - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + return { + returnValues: { + model: data + ? BaseModel.toJSON(model as any, this.service!.modelType) + : null, + }, + executePort: successPort, + }; + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public async initTrigger( + req: ExpressRequest, + res: ExpressResponse, + props: InitProps, + ): Promise<void> { + // get all the enabled workflows with this trigger. + Response.sendJsonObjectResponse(req, res, { status: "Triggered" }); + + const workflows: Array<Workflow> = await WorkflowService.findBy({ + query: { + triggerId: this.getMetadata().id, + projectId: new ObjectID(req.params["projectId"] as string), + isEnabled: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + select: { + _id: true, + triggerArguments: true, + }, + }); + + const promises: Array<Promise<void>> = []; + + const requestData: JSONObject = req.body.data; + + for (const workflow of workflows) { + /// Run Graph. + + if ( + this.type === "on-update" && + workflow.triggerArguments?.["listen-on"] + ) { + let listenOn: JSONObject = workflow.triggerArguments?.[ + "listen-on" + ] as JSONObject; + const miscData: JSONObject = + (requestData?.["miscData"] as JSONObject) || {}; if ( - !data['_id'] || - !args['select'] || - Object.keys(args['select']).length === 0 + listenOn && + Object.keys(listenOn).length > 0 && + miscData && + Object.keys(miscData).length > 0 ) { - return { - returnValues: { - model: data - ? BaseModel.toJSON(data as any, this.service!.modelType) - : null, - ...(miscData || {}), - }, - executePort: successPort, - }; - } + try { + if (typeof listenOn === "string") { + listenOn = JSON.parse(listenOn); + } + } catch (err) { + logger.error(err); + continue; + } - let select: Select<TBaseModel> = args['select'] as Select<TBaseModel>; + const updatedFields: JSONObject = miscData[ + "updatedFields" + ] as JSONObject; - if (select) { - select = JSONFunctions.deserialize( - args['select'] as JSONObject - ) as Select<TBaseModel>; - } + if (updatedFields && Object.keys(updatedFields).length > 0) { + let isUpdated: boolean = false; - const model: TBaseModel | null = await this.service!.findOneById({ - id: new ObjectID(data['_id'] as string), - props: { - isRoot: true, - }, - select: { - _id: true, - ...select, - }, - }); - - if (!model) { - throw new BadDataException( - ('Model not found with id ' + args['_id']) as string - ); - } - - return { - returnValues: { - model: data - ? BaseModel.toJSON(model as any, this.service!.modelType) - : null, - }, - executePort: successPort, - }; - } - - public async initTrigger( - req: ExpressRequest, - res: ExpressResponse, - props: InitProps - ): Promise<void> { - // get all the enabled workflows with this trigger. - Response.sendJsonObjectResponse(req, res, { status: 'Triggered' }); - - const workflows: Array<Workflow> = await WorkflowService.findBy({ - query: { - triggerId: this.getMetadata().id, - projectId: new ObjectID(req.params['projectId'] as string), - isEnabled: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - select: { - _id: true, - triggerArguments: true, - }, - }); - - const promises: Array<Promise<void>> = []; - - const requestData: JSONObject = req.body.data; - - for (const workflow of workflows) { - /// Run Graph. - - if ( - this.type === 'on-update' && - workflow.triggerArguments?.['listen-on'] - ) { - let listenOn: JSONObject = workflow.triggerArguments?.[ - 'listen-on' - ] as JSONObject; - const miscData: JSONObject = - (requestData?.['miscData'] as JSONObject) || {}; - - if ( - listenOn && - Object.keys(listenOn).length > 0 && - miscData && - Object.keys(miscData).length > 0 - ) { - try { - if (typeof listenOn === 'string') { - listenOn = JSON.parse(listenOn); - } - } catch (err) { - logger.error(err); - continue; - } - - const updatedFields: JSONObject = miscData[ - 'updatedFields' - ] as JSONObject; - - if ( - updatedFields && - Object.keys(updatedFields).length > 0 - ) { - let isUpdated: boolean = false; - - for (const key in listenOn) { - if (updatedFields[key]) { - isUpdated = true; - break; - } - } - - if (!isUpdated) { - continue; - } - } - } + for (const key in listenOn) { + if (updatedFields[key]) { + isUpdated = true; + break; + } } - const executeWorkflow: ExecuteWorkflowType = { - workflowId: workflow.id!, - returnValues: { - data: requestData, - }, - }; - - promises.push(props.executeWorkflow(executeWorkflow)); + if (!isUpdated) { + continue; + } + } } + } - await Promise.all(promises); + const executeWorkflow: ExecuteWorkflowType = { + workflowId: workflow.id!, + returnValues: { + data: requestData, + }, + }; + + promises.push(props.executeWorkflow(executeWorkflow)); } + + await Promise.all(promises); + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/OnUpdateBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/OnUpdateBaseModel.ts index ac30a4f56a..395b51fc90 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/OnUpdateBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/OnUpdateBaseModel.ts @@ -1,11 +1,11 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import OnTriggerBaseModel from './OnTriggerBaseModel'; -import BaseModel from 'Common/Models/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import OnTriggerBaseModel from "./OnTriggerBaseModel"; +import BaseModel from "Common/Models/BaseModel"; export default class OnUpdateBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends OnTriggerBaseModel<TBaseModel> { - public constructor(modelService: DatabaseService<TBaseModel>) { - super(modelService, 'on-update'); - } + public constructor(modelService: DatabaseService<TBaseModel>) { + super(modelService, "on-update"); + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts index 0026418379..0e1d186d29 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts @@ -1,182 +1,174 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import Query from '../../../Database/Query'; -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BaseModel from 'Common/Models/BaseModel'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import QueryDeepPartialEntity from 'Common/Types/Database/PartialEntity'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Text from 'Common/Types/Text'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import Query from "../../../Database/Query"; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BaseModel from "Common/Models/BaseModel"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import QueryDeepPartialEntity from "Common/Types/Database/PartialEntity"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Text from "Common/Types/Text"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; export default class UpdateManyBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends ComponentCode { - private modelService: DatabaseService<TBaseModel> | null = null; + private modelService: DatabaseService<TBaseModel> | null = null; - public constructor(modelService: DatabaseService<TBaseModel>) { - super(); + public constructor(modelService: DatabaseService<TBaseModel>) { + super(); - const BaseModelComponent: ComponentMetadata | undefined = - BaseModelComponents.getComponents(modelService.getModel()).find( - (i: ComponentMetadata) => { - return ( - i.id === - `${Text.pascalCaseToDashes( - modelService.getModel().tableName! - )}-update-many` - ); - } - ); + const BaseModelComponent: ComponentMetadata | undefined = + BaseModelComponents.getComponents(modelService.getModel()).find( + (i: ComponentMetadata) => { + return ( + i.id === + `${Text.pascalCaseToDashes( + modelService.getModel().tableName!, + )}-update-many` + ); + }, + ); - if (!BaseModelComponent) { - throw new BadDataException( - 'Update many component for ' + - modelService.getModel().tableName + - ' not found.' - ); - } - this.setMetadata(BaseModelComponent); - this.modelService = modelService; + if (!BaseModelComponent) { + throw new BadDataException( + "Update many component for " + + modelService.getModel().tableName + + " not found.", + ); + } + this.setMetadata(BaseModelComponent); + this.modelService = modelService; + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } - - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - try { - if (!this.modelService) { - throw options.onError( - new BadDataException('modelService is undefined.') - ); - } - - if (!args['data']) { - throw options.onError( - new BadDataException('JSON is undefined.') - ); - } - - if (typeof args['data'] === 'string') { - args['data'] = JSONFunctions.parse(args['data'] as string); - } - - if (typeof args['data'] !== 'object') { - throw options.onError( - new BadDataException('JSON is should be of type object.') - ); - } - - if (this.modelService.getModel().getTenantColumn()) { - (args['data'] as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - } - - if (!args['query']) { - throw options.onError( - new BadDataException('Query is undefined.') - ); - } - - if (typeof args['query'] === 'string') { - args['query'] = JSONFunctions.parse(args['query'] as string); - } - - if (typeof args['query'] !== 'object') { - throw options.onError( - new BadDataException('Query is should be of type object.') - ); - } - - if (args['skip'] && typeof args['skip'] === 'string') { - args['skip'] = parseInt(args['skip']); - } - - if (args['limit'] && typeof args['limit'] === 'string') { - args['limit'] = parseInt(args['limit']); - } - - if (typeof args['skip'] !== 'number') { - args['skip'] = 0; - } - - if (typeof args['limit'] !== 'number') { - args['limit'] = 10; - } - - if ( - typeof args['limit'] === 'number' && - args['limit'] > LIMIT_PER_PROJECT - ) { - options.log('Limit cannot be ' + args['limit']); - options.log('Setting the limit to ' + LIMIT_PER_PROJECT); - args['limit'] = LIMIT_PER_PROJECT; - } - - let query: Query<TBaseModel> = args['query'] as Query<TBaseModel>; - - if (query) { - query = JSONFunctions.deserialize( - args['query'] as JSONObject - ) as Query<TBaseModel>; - } - - if (this.modelService.getModel().getTenantColumn()) { - (args['query'] as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - } - - await this.modelService.updateBy({ - query: query || {}, - data: args['data'] as QueryDeepPartialEntity<TBaseModel>, - limit: new PositiveNumber(args['limit'] as number), - skip: new PositiveNumber(args['skip'] as number), - props: { - isRoot: true, - tenantId: options.projectId, - }, - }); - - return { - returnValues: {}, - executePort: successPort, - }; - } catch (err: any) { - options.log('Error running component'); - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - return { - returnValues: {}, - executePort: errorPort, - }; - } + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); } + + try { + if (!this.modelService) { + throw options.onError( + new BadDataException("modelService is undefined."), + ); + } + + if (!args["data"]) { + throw options.onError(new BadDataException("JSON is undefined.")); + } + + if (typeof args["data"] === "string") { + args["data"] = JSONFunctions.parse(args["data"] as string); + } + + if (typeof args["data"] !== "object") { + throw options.onError( + new BadDataException("JSON is should be of type object."), + ); + } + + if (this.modelService.getModel().getTenantColumn()) { + (args["data"] as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + } + + if (!args["query"]) { + throw options.onError(new BadDataException("Query is undefined.")); + } + + if (typeof args["query"] === "string") { + args["query"] = JSONFunctions.parse(args["query"] as string); + } + + if (typeof args["query"] !== "object") { + throw options.onError( + new BadDataException("Query is should be of type object."), + ); + } + + if (args["skip"] && typeof args["skip"] === "string") { + args["skip"] = parseInt(args["skip"]); + } + + if (args["limit"] && typeof args["limit"] === "string") { + args["limit"] = parseInt(args["limit"]); + } + + if (typeof args["skip"] !== "number") { + args["skip"] = 0; + } + + if (typeof args["limit"] !== "number") { + args["limit"] = 10; + } + + if ( + typeof args["limit"] === "number" && + args["limit"] > LIMIT_PER_PROJECT + ) { + options.log("Limit cannot be " + args["limit"]); + options.log("Setting the limit to " + LIMIT_PER_PROJECT); + args["limit"] = LIMIT_PER_PROJECT; + } + + let query: Query<TBaseModel> = args["query"] as Query<TBaseModel>; + + if (query) { + query = JSONFunctions.deserialize( + args["query"] as JSONObject, + ) as Query<TBaseModel>; + } + + if (this.modelService.getModel().getTenantColumn()) { + (args["query"] as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + } + + await this.modelService.updateBy({ + query: query || {}, + data: args["data"] as QueryDeepPartialEntity<TBaseModel>, + limit: new PositiveNumber(args["limit"] as number), + skip: new PositiveNumber(args["skip"] as number), + props: { + isRoot: true, + tenantId: options.projectId, + }, + }); + + return { + returnValues: {}, + executePort: successPort, + }; + } catch (err: any) { + options.log("Error running component"); + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + return { + returnValues: {}, + executePort: errorPort, + }; + } + } } diff --git a/CommonServer/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts index 8743505707..89ad4dddbc 100644 --- a/CommonServer/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts @@ -1,153 +1,145 @@ -import DatabaseService from '../../../../Services/DatabaseService'; -import Query from '../../../Database/Query'; -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BaseModel from 'Common/Models/BaseModel'; -import QueryDeepPartialEntity from 'Common/Types/Database/PartialEntity'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Text from 'Common/Types/Text'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; +import DatabaseService from "../../../../Services/DatabaseService"; +import Query from "../../../Database/Query"; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BaseModel from "Common/Models/BaseModel"; +import QueryDeepPartialEntity from "Common/Types/Database/PartialEntity"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Text from "Common/Types/Text"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; export default class UpdateOneBaseModel< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends ComponentCode { - private modelService: DatabaseService<TBaseModel> | null = null; + private modelService: DatabaseService<TBaseModel> | null = null; - public constructor(modelService: DatabaseService<TBaseModel>) { - super(); + public constructor(modelService: DatabaseService<TBaseModel>) { + super(); - const BaseModelComponent: ComponentMetadata | undefined = - BaseModelComponents.getComponents(modelService.getModel()).find( - (i: ComponentMetadata) => { - return ( - i.id === - `${Text.pascalCaseToDashes( - modelService.getModel().tableName! - )}-update-one` - ); - } - ); + const BaseModelComponent: ComponentMetadata | undefined = + BaseModelComponents.getComponents(modelService.getModel()).find( + (i: ComponentMetadata) => { + return ( + i.id === + `${Text.pascalCaseToDashes( + modelService.getModel().tableName!, + )}-update-one` + ); + }, + ); - if (!BaseModelComponent) { - throw new BadDataException( - 'Update one component for ' + - modelService.getModel().tableName + - ' not found.' - ); - } - this.setMetadata(BaseModelComponent); - this.modelService = modelService; + if (!BaseModelComponent) { + throw new BadDataException( + "Update one component for " + + modelService.getModel().tableName + + " not found.", + ); + } + this.setMetadata(BaseModelComponent); + this.modelService = modelService; + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } - - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - try { - if (!this.modelService) { - throw options.onError( - new BadDataException('modelService is undefined.') - ); - } - - if (!args['data']) { - throw options.onError( - new BadDataException('JSON is undefined.') - ); - } - - if (typeof args['data'] === 'string') { - args['data'] = JSONFunctions.parse(args['data'] as string); - } - - if (typeof args['data'] !== 'object') { - throw options.onError( - new BadDataException('JSON is should be of type object.') - ); - } - - if (this.modelService.getModel().getTenantColumn()) { - (args['data'] as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - } - - if (!args['query']) { - throw options.onError( - new BadDataException('Query is undefined.') - ); - } - - if (typeof args['query'] === 'string') { - args['query'] = JSONFunctions.parse(args['query'] as string); - } - - if (typeof args['query'] !== 'object') { - throw options.onError( - new BadDataException('Query is should be of type object.') - ); - } - - if (this.modelService.getModel().getTenantColumn()) { - (args['query'] as JSONObject)[ - this.modelService.getModel().getTenantColumn() as string - ] = options.projectId; - } - - let query: Query<TBaseModel> = args['query'] as Query<TBaseModel>; - - if (query) { - query = JSONFunctions.deserialize( - args['query'] as JSONObject - ) as Query<TBaseModel>; - } - - await this.modelService.updateOneBy({ - query: query || {}, - data: args['data'] as QueryDeepPartialEntity<TBaseModel>, - props: { - isRoot: true, - tenantId: options.projectId, - }, - }); - - return { - returnValues: {}, - executePort: successPort, - }; - } catch (err: any) { - options.log('Error running component'); - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - return { - returnValues: {}, - executePort: errorPort, - }; - } + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); } + + try { + if (!this.modelService) { + throw options.onError( + new BadDataException("modelService is undefined."), + ); + } + + if (!args["data"]) { + throw options.onError(new BadDataException("JSON is undefined.")); + } + + if (typeof args["data"] === "string") { + args["data"] = JSONFunctions.parse(args["data"] as string); + } + + if (typeof args["data"] !== "object") { + throw options.onError( + new BadDataException("JSON is should be of type object."), + ); + } + + if (this.modelService.getModel().getTenantColumn()) { + (args["data"] as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + } + + if (!args["query"]) { + throw options.onError(new BadDataException("Query is undefined.")); + } + + if (typeof args["query"] === "string") { + args["query"] = JSONFunctions.parse(args["query"] as string); + } + + if (typeof args["query"] !== "object") { + throw options.onError( + new BadDataException("Query is should be of type object."), + ); + } + + if (this.modelService.getModel().getTenantColumn()) { + (args["query"] as JSONObject)[ + this.modelService.getModel().getTenantColumn() as string + ] = options.projectId; + } + + let query: Query<TBaseModel> = args["query"] as Query<TBaseModel>; + + if (query) { + query = JSONFunctions.deserialize( + args["query"] as JSONObject, + ) as Query<TBaseModel>; + } + + await this.modelService.updateOneBy({ + query: query || {}, + data: args["data"] as QueryDeepPartialEntity<TBaseModel>, + props: { + isRoot: true, + tenantId: options.projectId, + }, + }); + + return { + returnValues: {}, + executePort: successPort, + }; + } catch (err: any) { + options.log("Error running component"); + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + return { + returnValues: {}, + executePort: errorPort, + }; + } + } } diff --git a/CommonServer/Types/Workflow/Components/Conditions/IfElse.ts b/CommonServer/Types/Workflow/Components/Conditions/IfElse.ts index f098f67b68..5515f8b94f 100644 --- a/CommonServer/Types/Workflow/Components/Conditions/IfElse.ts +++ b/CommonServer/Types/Workflow/Components/Conditions/IfElse.ts @@ -1,138 +1,134 @@ -import VMUtil from '../../../../Utils/VM/VMAPI'; -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ReturnResult from 'Common/Types/IsolatedVM/ReturnResult'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import Components from 'Common/Types/Workflow/Components/Condition'; +import VMUtil from "../../../../Utils/VM/VMAPI"; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ReturnResult from "Common/Types/IsolatedVM/ReturnResult"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import Components from "Common/Types/Workflow/Components/Condition"; export default class IfElse extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = Components.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.IfElse; - } - ); + const Component: ComponentMetadata | undefined = Components.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.IfElse; + }, + ); - if (!Component) { - throw new BadDataException( - 'Custom JavaScript Component not found.' - ); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Custom JavaScript Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const yesPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'yes'; - } - ); + this.setMetadata(Component); + } - if (!yesPort) { - throw options.onError(new BadDataException('Yes port not found')); + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const yesPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "yes"; + }, + ); + + if (!yesPort) { + throw options.onError(new BadDataException("Yes port not found")); + } + + const noPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "no"; + }, + ); + + if (!noPort) { + throw options.onError(new BadDataException("No port not found")); + } + + try { + // Set timeout + // Inject args + // Inject dependencies + + for (const key in args) { + if (key === "operator") { + continue; } - const noPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'no'; - } - ); + const value: JSONValue = args[key]; - if (!noPort) { - throw options.onError(new BadDataException('No port not found')); + let shouldHaveQuotes: boolean = false; + + if ( + typeof value === "string" && + value !== "null" && + value !== "undefined" + ) { + shouldHaveQuotes = true; } - try { - // Set timeout - // Inject args - // Inject dependencies + if (typeof value === "object") { + args[key] = JSON.stringify(args[key]); + } - for (const key in args) { - if (key === 'operator') { - continue; - } + args[key] = shouldHaveQuotes ? `"${args[key]}"` : args[key]; + } - const value: JSONValue = args[key]; + type SerializeFunction = (arg: string) => string; - let shouldHaveQuotes: boolean = false; + const serialize: SerializeFunction = (arg: string): string => { + if (typeof arg === "string") { + return arg.replace(/\n/g, "--newline--"); + } - if ( - typeof value === 'string' && - value !== 'null' && - value !== 'undefined' - ) { - shouldHaveQuotes = true; - } + return arg; + }; - if (typeof value === 'object') { - args[key] = JSON.stringify(args[key]); - } - - args[key] = shouldHaveQuotes ? `"${args[key]}"` : args[key]; - } - - type SerializeFunction = (arg: string) => string; - - const serialize: SerializeFunction = (arg: string): string => { - if (typeof arg === 'string') { - return arg.replace(/\n/g, '--newline--'); - } - - return arg; - }; - - const code: string = ` + const code: string = ` const input1 = ${ - serialize(args['input-1'] as string) || '' + serialize(args["input-1"] as string) || "" }; const input2 = ${ - serialize(args['input-2'] as string) || '' + serialize(args["input-2"] as string) || "" }; return input1 ${ - (args['operator'] as string) || '==' + (args["operator"] as string) || "==" } input2`; - const returnResult: ReturnResult = await VMUtil.runCodeInSandbox({ - code, - options: { - args: args as JSONObject, - }, - }); + const returnResult: ReturnResult = await VMUtil.runCodeInSandbox({ + code, + options: { + args: args as JSONObject, + }, + }); - const logMessages: string[] = returnResult.logMessages; + const logMessages: string[] = returnResult.logMessages; - // add to option.log - logMessages.forEach((msg: string) => { - options.log(msg); - }); + // add to option.log + logMessages.forEach((msg: string) => { + options.log(msg); + }); - if (returnResult.returnValue) { - return { - returnValues: {}, - executePort: yesPort, - }; - } + if (returnResult.returnValue) { + return { + returnValues: {}, + executePort: yesPort, + }; + } - return { - returnValues: {}, - executePort: noPort, - }; - } catch (err: any) { - options.log('Error running script'); - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - throw options.onError(err); - } + return { + returnValues: {}, + executePort: noPort, + }; + } catch (err: any) { + options.log("Error running script"); + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + throw options.onError(err); } + } } diff --git a/CommonServer/Types/Workflow/Components/Email.ts b/CommonServer/Types/Workflow/Components/Email.ts index 467fc13758..d5671ba966 100644 --- a/CommonServer/Types/Workflow/Components/Email.ts +++ b/CommonServer/Types/Workflow/Components/Email.ts @@ -1,154 +1,142 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import Components from 'Common/Types/Workflow/Components/Email'; -import nodemailer, { Transporter } from 'nodemailer'; +import ComponentCode, { RunOptions, RunReturnType } from "../ComponentCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import Components from "Common/Types/Workflow/Components/Email"; +import nodemailer, { Transporter } from "nodemailer"; export default class Email extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = Components.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.SendEmail; - } - ); + const Component: ComponentMetadata | undefined = Components.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.SendEmail; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + this.setMetadata(Component); + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - if (!args['to']) { - throw options.onError(new BadDataException('to not found')); - } - - if (args['to'] && typeof args['to'] !== 'string') { - throw options.onError( - new BadDataException('to is not type of string') - ); - } - - if (!args['from']) { - throw options.onError(new BadDataException('from not found')); - } - - if (args['from'] && typeof args['from'] !== 'string') { - throw options.onError( - new BadDataException('from is not type of string') - ); - } - - if (!args['smtp-username']) { - throw options.onError(new BadDataException('email not found')); - } - - if ( - args['smtp-username'] && - typeof args['smtp-username'] !== 'string' - ) { - throw options.onError( - new BadDataException('smtp-username is not type of string') - ); - } - - if (!args['smtp-password']) { - throw options.onError(new BadDataException('email not found')); - } - - if ( - args['smtp-password'] && - typeof args['smtp-password'] !== 'string' - ) { - throw options.onError( - new BadDataException('smtp-username is not type of string') - ); - } - - if (!args['smtp-host']) { - throw options.onError(new BadDataException('email not found')); - } - - if (args['smtp-host'] && typeof args['smtp-host'] !== 'string') { - throw options.onError( - new BadDataException('smtp-host is not type of string') - ); - } - - if (!args['smtp-port']) { - throw options.onError(new BadDataException('email not found')); - } - - if (args['smtp-port'] && typeof args['smtp-port'] === 'string') { - args['smtp-port'] = parseInt(args['smtp-port']); - } - - if (args['smtp-port'] && typeof args['smtp-port'] !== 'number') { - throw options.onError( - new BadDataException('smtp-host is not type of number') - ); - } - - try { - const mailer: Transporter = nodemailer.createTransport({ - host: args['smtp-host']?.toString(), - port: args['smtp-port'] as number, - secure: Boolean(args['secure']), - auth: { - user: args['smtp-username'] as string, - pass: args['smtp-password'] as string, - }, - }); - - await mailer.sendMail({ - from: args['from'].toString(), - to: args['to'].toString(), - subject: args['subject']?.toString() || '', - html: args['email-body']?.toString() || '', - }); - - options.log('Email sent.'); - - return Promise.resolve({ - returnValues: {}, - executePort: successPort, - }); - } catch (err: unknown) { - options.log(err as Error); - return Promise.resolve({ - returnValues: {}, - executePort: successPort, - }); - } + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); + + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); + } + + if (!args["to"]) { + throw options.onError(new BadDataException("to not found")); + } + + if (args["to"] && typeof args["to"] !== "string") { + throw options.onError(new BadDataException("to is not type of string")); + } + + if (!args["from"]) { + throw options.onError(new BadDataException("from not found")); + } + + if (args["from"] && typeof args["from"] !== "string") { + throw options.onError(new BadDataException("from is not type of string")); + } + + if (!args["smtp-username"]) { + throw options.onError(new BadDataException("email not found")); + } + + if (args["smtp-username"] && typeof args["smtp-username"] !== "string") { + throw options.onError( + new BadDataException("smtp-username is not type of string"), + ); + } + + if (!args["smtp-password"]) { + throw options.onError(new BadDataException("email not found")); + } + + if (args["smtp-password"] && typeof args["smtp-password"] !== "string") { + throw options.onError( + new BadDataException("smtp-username is not type of string"), + ); + } + + if (!args["smtp-host"]) { + throw options.onError(new BadDataException("email not found")); + } + + if (args["smtp-host"] && typeof args["smtp-host"] !== "string") { + throw options.onError( + new BadDataException("smtp-host is not type of string"), + ); + } + + if (!args["smtp-port"]) { + throw options.onError(new BadDataException("email not found")); + } + + if (args["smtp-port"] && typeof args["smtp-port"] === "string") { + args["smtp-port"] = parseInt(args["smtp-port"]); + } + + if (args["smtp-port"] && typeof args["smtp-port"] !== "number") { + throw options.onError( + new BadDataException("smtp-host is not type of number"), + ); + } + + try { + const mailer: Transporter = nodemailer.createTransport({ + host: args["smtp-host"]?.toString(), + port: args["smtp-port"] as number, + secure: Boolean(args["secure"]), + auth: { + user: args["smtp-username"] as string, + pass: args["smtp-password"] as string, + }, + }); + + await mailer.sendMail({ + from: args["from"].toString(), + to: args["to"].toString(), + subject: args["subject"]?.toString() || "", + html: args["email-body"]?.toString() || "", + }); + + options.log("Email sent."); + + return Promise.resolve({ + returnValues: {}, + executePort: successPort, + }); + } catch (err: unknown) { + options.log(err as Error); + return Promise.resolve({ + returnValues: {}, + executePort: successPort, + }); + } + } } diff --git a/CommonServer/Types/Workflow/Components/Index.ts b/CommonServer/Types/Workflow/Components/Index.ts index 4f99ac4ba3..32185bdd92 100644 --- a/CommonServer/Types/Workflow/Components/Index.ts +++ b/CommonServer/Types/Workflow/Components/Index.ts @@ -1,115 +1,115 @@ -import DatabaseService from '../../../Services/DatabaseService'; -import Services from '../../../Services/Index'; -import ComponentCode from '../ComponentCode'; -import ApiDelete from './API/Delete'; -import ApiGet from './API/Get'; -import ApiPost from './API/Post'; -import ApiPut from './API/Put'; -import CreateManyBaseModel from './BaseModel/CreateManyBaseModel'; -import CreateOneBaseModel from './BaseModel/CreateOneBaseModel'; -import DeleteManyBaseModel from './BaseModel/DeleteManyBaseModel'; -import DeleteOneBaseModel from './BaseModel/DeleteOneBaseModel'; -import FindManyBaseModel from './BaseModel/FindManyBaseModel'; -import FindOneBaseModel from './BaseModel/FindOneBaseModel'; -import OnCreateBaseModel from './BaseModel/OnCreateBaseModel'; -import OnDeleteBaseModel from './BaseModel/OnDeleteBaseModel'; -import OnUpdateBaseModel from './BaseModel/OnUpdateBaseModel'; -import UpdateManyBaseModel from './BaseModel/UpdateManyBaseModel'; -import UpdateOneBaseModel from './BaseModel/UpdateOneBaseModel'; -import IfElse from './Conditions/IfElse'; -import Email from './Email'; -import JsonToText from './JSON/JsonToText'; -import MergeJSON from './JSON/MergeJson'; -import TextToJSON from './JSON/TextToJson'; -import JavaScriptCode from './JavaScript'; -import Log from './Log'; -import ManualTrigger from './Manual'; -import MicrosoftTeamsSendMessageToChannel from './MicrosoftTeams/SendMessageToChannel'; -import Schedule from './Schedule'; -import SlackSendMessageToChannel from './Slack/SendMessageToChannel'; -import WebhookTrigger from './Webhook'; -import BaseModel from 'Common/Models/BaseModel'; -import Dictionary from 'Common/Types/Dictionary'; -import Text from 'Common/Types/Text'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; +import DatabaseService from "../../../Services/DatabaseService"; +import Services from "../../../Services/Index"; +import ComponentCode from "../ComponentCode"; +import ApiDelete from "./API/Delete"; +import ApiGet from "./API/Get"; +import ApiPost from "./API/Post"; +import ApiPut from "./API/Put"; +import CreateManyBaseModel from "./BaseModel/CreateManyBaseModel"; +import CreateOneBaseModel from "./BaseModel/CreateOneBaseModel"; +import DeleteManyBaseModel from "./BaseModel/DeleteManyBaseModel"; +import DeleteOneBaseModel from "./BaseModel/DeleteOneBaseModel"; +import FindManyBaseModel from "./BaseModel/FindManyBaseModel"; +import FindOneBaseModel from "./BaseModel/FindOneBaseModel"; +import OnCreateBaseModel from "./BaseModel/OnCreateBaseModel"; +import OnDeleteBaseModel from "./BaseModel/OnDeleteBaseModel"; +import OnUpdateBaseModel from "./BaseModel/OnUpdateBaseModel"; +import UpdateManyBaseModel from "./BaseModel/UpdateManyBaseModel"; +import UpdateOneBaseModel from "./BaseModel/UpdateOneBaseModel"; +import IfElse from "./Conditions/IfElse"; +import Email from "./Email"; +import JsonToText from "./JSON/JsonToText"; +import MergeJSON from "./JSON/MergeJson"; +import TextToJSON from "./JSON/TextToJson"; +import JavaScriptCode from "./JavaScript"; +import Log from "./Log"; +import ManualTrigger from "./Manual"; +import MicrosoftTeamsSendMessageToChannel from "./MicrosoftTeams/SendMessageToChannel"; +import Schedule from "./Schedule"; +import SlackSendMessageToChannel from "./Slack/SendMessageToChannel"; +import WebhookTrigger from "./Webhook"; +import BaseModel from "Common/Models/BaseModel"; +import Dictionary from "Common/Types/Dictionary"; +import Text from "Common/Types/Text"; +import ComponentID from "Common/Types/Workflow/ComponentID"; const Components: Dictionary<ComponentCode> = { - [ComponentID.Webhook]: new WebhookTrigger(), - [ComponentID.SlackSendMessageToChannel]: new SlackSendMessageToChannel(), - [ComponentID.MicrosoftTeamsSendMessageToChannel]: - new MicrosoftTeamsSendMessageToChannel(), - [ComponentID.Log]: new Log(), - [ComponentID.Schedule]: new Schedule(), - [ComponentID.JavaScriptCode]: new JavaScriptCode(), - [ComponentID.Manual]: new ManualTrigger(), - [ComponentID.JsonToText]: new JsonToText(), - [ComponentID.MergeJson]: new MergeJSON(), - [ComponentID.TextToJson]: new TextToJSON(), - [ComponentID.ApiGet]: new ApiGet(), - [ComponentID.ApiPost]: new ApiPost(), - [ComponentID.ApiDelete]: new ApiDelete(), - [ComponentID.ApiPut]: new ApiPut(), - [ComponentID.SendEmail]: new Email(), - [ComponentID.IfElse]: new IfElse(), + [ComponentID.Webhook]: new WebhookTrigger(), + [ComponentID.SlackSendMessageToChannel]: new SlackSendMessageToChannel(), + [ComponentID.MicrosoftTeamsSendMessageToChannel]: + new MicrosoftTeamsSendMessageToChannel(), + [ComponentID.Log]: new Log(), + [ComponentID.Schedule]: new Schedule(), + [ComponentID.JavaScriptCode]: new JavaScriptCode(), + [ComponentID.Manual]: new ManualTrigger(), + [ComponentID.JsonToText]: new JsonToText(), + [ComponentID.MergeJson]: new MergeJSON(), + [ComponentID.TextToJson]: new TextToJSON(), + [ComponentID.ApiGet]: new ApiGet(), + [ComponentID.ApiPost]: new ApiPost(), + [ComponentID.ApiDelete]: new ApiDelete(), + [ComponentID.ApiPut]: new ApiPut(), + [ComponentID.SendEmail]: new Email(), + [ComponentID.IfElse]: new IfElse(), }; for (const baseModelService of Services) { - if (!(baseModelService instanceof DatabaseService)) { - continue; - } + if (!(baseModelService instanceof DatabaseService)) { + continue; + } - const model: BaseModel = baseModelService.getModel(); + const model: BaseModel = baseModelService.getModel(); - if (!model.enableWorkflowOn) { - continue; - } + if (!model.enableWorkflowOn) { + continue; + } - const modelId: string = `${Text.pascalCaseToDashes(model.tableName!)}`; + const modelId: string = `${Text.pascalCaseToDashes(model.tableName!)}`; - if (model.enableWorkflowOn.create) { - Components[`${modelId}-on-create`] = new OnCreateBaseModel( - baseModelService as any - ); - Components[`${modelId}-create-one`] = new CreateOneBaseModel( - baseModelService as any - ); - Components[`${modelId}-create-many`] = new CreateManyBaseModel( - baseModelService as any - ); - } + if (model.enableWorkflowOn.create) { + Components[`${modelId}-on-create`] = new OnCreateBaseModel( + baseModelService as any, + ); + Components[`${modelId}-create-one`] = new CreateOneBaseModel( + baseModelService as any, + ); + Components[`${modelId}-create-many`] = new CreateManyBaseModel( + baseModelService as any, + ); + } - if (model.enableWorkflowOn.read) { - Components[`${modelId}-find-one`] = new FindOneBaseModel( - baseModelService as any - ); - Components[`${modelId}-find-many`] = new FindManyBaseModel( - baseModelService as any - ); - } + if (model.enableWorkflowOn.read) { + Components[`${modelId}-find-one`] = new FindOneBaseModel( + baseModelService as any, + ); + Components[`${modelId}-find-many`] = new FindManyBaseModel( + baseModelService as any, + ); + } - if (model.enableWorkflowOn.update) { - Components[`${modelId}-on-update`] = new OnUpdateBaseModel( - baseModelService as any - ); - Components[`${modelId}-update-one`] = new UpdateOneBaseModel( - baseModelService as any - ); - Components[`${modelId}-update-many`] = new UpdateManyBaseModel( - baseModelService as any - ); - } + if (model.enableWorkflowOn.update) { + Components[`${modelId}-on-update`] = new OnUpdateBaseModel( + baseModelService as any, + ); + Components[`${modelId}-update-one`] = new UpdateOneBaseModel( + baseModelService as any, + ); + Components[`${modelId}-update-many`] = new UpdateManyBaseModel( + baseModelService as any, + ); + } - if (model.enableWorkflowOn.delete) { - Components[`${modelId}-on-delete`] = new OnDeleteBaseModel( - baseModelService as any - ); - Components[`${modelId}-delete-one`] = new DeleteOneBaseModel( - baseModelService as any - ); - Components[`${modelId}-delete-many`] = new DeleteManyBaseModel( - baseModelService as any - ); - } + if (model.enableWorkflowOn.delete) { + Components[`${modelId}-on-delete`] = new OnDeleteBaseModel( + baseModelService as any, + ); + Components[`${modelId}-delete-one`] = new DeleteOneBaseModel( + baseModelService as any, + ); + Components[`${modelId}-delete-many`] = new DeleteManyBaseModel( + baseModelService as any, + ); + } } export default Components; diff --git a/CommonServer/Types/Workflow/Components/JSON/JsonToText.ts b/CommonServer/Types/Workflow/Components/JSON/JsonToText.ts index eb61fff058..7f7f5dc057 100644 --- a/CommonServer/Types/Workflow/Components/JSON/JsonToText.ts +++ b/CommonServer/Types/Workflow/Components/JSON/JsonToText.ts @@ -1,84 +1,80 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import JSONComponents from 'Common/Types/Workflow/Components/JSON'; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import JSONComponents from "Common/Types/Workflow/Components/JSON"; export default class JsonToText extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = JSONComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.JsonToText; - } - ); + const Component: ComponentMetadata | undefined = JSONComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.JsonToText; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + this.setMetadata(Component); + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - if (!args['json']) { - throw options.onError(new BadDataException('JSON is undefined.')); - } - - if (typeof args['json'] === 'string') { - args['json'] = JSONFunctions.parse(args['json'] as string); - } - - if (typeof args['json'] !== 'object') { - throw options.onError( - new BadDataException('JSON is should be of type object.') - ); - } - - try { - const returnValue: string = JSON.stringify( - args['json'] as JSONObject - ); - return Promise.resolve({ - returnValues: { - text: returnValue, - }, - executePort: successPort, - }); - } catch (err) { - options.log('JSON is not in the correct format.'); - return Promise.resolve({ - returnValues: {}, - executePort: errorPort, - }); - } + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); + + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); + } + + if (!args["json"]) { + throw options.onError(new BadDataException("JSON is undefined.")); + } + + if (typeof args["json"] === "string") { + args["json"] = JSONFunctions.parse(args["json"] as string); + } + + if (typeof args["json"] !== "object") { + throw options.onError( + new BadDataException("JSON is should be of type object."), + ); + } + + try { + const returnValue: string = JSON.stringify(args["json"] as JSONObject); + return Promise.resolve({ + returnValues: { + text: returnValue, + }, + executePort: successPort, + }); + } catch (err) { + options.log("JSON is not in the correct format."); + return Promise.resolve({ + returnValues: {}, + executePort: errorPort, + }); + } + } } diff --git a/CommonServer/Types/Workflow/Components/JSON/MergeJson.ts b/CommonServer/Types/Workflow/Components/JSON/MergeJson.ts index c8eddc7f4f..5004892e8f 100644 --- a/CommonServer/Types/Workflow/Components/JSON/MergeJson.ts +++ b/CommonServer/Types/Workflow/Components/JSON/MergeJson.ts @@ -1,90 +1,88 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import JSONComponents from 'Common/Types/Workflow/Components/JSON'; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import JSONComponents from "Common/Types/Workflow/Components/JSON"; export default class MergeJSON extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = JSONComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.MergeJson; - } - ); + const Component: ComponentMetadata | undefined = JSONComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.MergeJson; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + this.setMetadata(Component); + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - if (!args['json1']) { - throw options.onError(new BadDataException('JSON1 is undefined.')); - } - - if (typeof args['json1'] === 'string') { - args['json1'] = JSONFunctions.parse(args['json1'] as string); - } - - if (typeof args['json1'] !== 'object') { - throw options.onError( - new BadDataException('JSON1 is should be of type object.') - ); - } - - if (!args['json2']) { - throw options.onError(new BadDataException('JSON2 is undefined.')); - } - - if (typeof args['json2'] === 'string') { - args['json2'] = JSONFunctions.parse(args['json2'] as string); - } - - if (typeof args['json2'] !== 'object') { - throw options.onError( - new BadDataException('JSON2 is should be of type object.') - ); - } - - return Promise.resolve({ - returnValues: { - json: { - ...(args['json1'] as JSONObject), - ...(args['json2'] as JSONObject), - }, - }, - executePort: successPort, - }); + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); + + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); + } + + if (!args["json1"]) { + throw options.onError(new BadDataException("JSON1 is undefined.")); + } + + if (typeof args["json1"] === "string") { + args["json1"] = JSONFunctions.parse(args["json1"] as string); + } + + if (typeof args["json1"] !== "object") { + throw options.onError( + new BadDataException("JSON1 is should be of type object."), + ); + } + + if (!args["json2"]) { + throw options.onError(new BadDataException("JSON2 is undefined.")); + } + + if (typeof args["json2"] === "string") { + args["json2"] = JSONFunctions.parse(args["json2"] as string); + } + + if (typeof args["json2"] !== "object") { + throw options.onError( + new BadDataException("JSON2 is should be of type object."), + ); + } + + return Promise.resolve({ + returnValues: { + json: { + ...(args["json1"] as JSONObject), + ...(args["json2"] as JSONObject), + }, + }, + executePort: successPort, + }); + } } diff --git a/CommonServer/Types/Workflow/Components/JSON/TextToJson.ts b/CommonServer/Types/Workflow/Components/JSON/TextToJson.ts index d7cc27260e..65ba82090d 100644 --- a/CommonServer/Types/Workflow/Components/JSON/TextToJson.ts +++ b/CommonServer/Types/Workflow/Components/JSON/TextToJson.ts @@ -1,80 +1,78 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import JSONComponents from 'Common/Types/Workflow/Components/JSON'; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import JSONComponents from "Common/Types/Workflow/Components/JSON"; export default class TextToJSON extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = JSONComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.TextToJson; - } - ); + const Component: ComponentMetadata | undefined = JSONComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.TextToJson; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + this.setMetadata(Component); + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - if (!args['text']) { - throw options.onError(new BadDataException('text is undefined.')); - } - - if (typeof args['text'] !== 'string') { - throw options.onError( - new BadDataException('text is should be of type string.') - ); - } - - try { - const returnValue: JSONObject = JSONFunctions.parseJSONObject( - args['text'] as string - ); - return Promise.resolve({ - returnValues: { - json: returnValue, - }, - executePort: successPort, - }); - } catch (err) { - options.log('text is not in the correct format.'); - return Promise.resolve({ - returnValues: {}, - executePort: errorPort, - }); - } + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); + + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); + } + + if (!args["text"]) { + throw options.onError(new BadDataException("text is undefined.")); + } + + if (typeof args["text"] !== "string") { + throw options.onError( + new BadDataException("text is should be of type string."), + ); + } + + try { + const returnValue: JSONObject = JSONFunctions.parseJSONObject( + args["text"] as string, + ); + return Promise.resolve({ + returnValues: { + json: returnValue, + }, + executePort: successPort, + }); + } catch (err) { + options.log("text is not in the correct format."); + return Promise.resolve({ + returnValues: {}, + executePort: errorPort, + }); + } + } } diff --git a/CommonServer/Types/Workflow/Components/JavaScript.ts b/CommonServer/Types/Workflow/Components/JavaScript.ts index 86bc9f1448..7d6cdfba43 100644 --- a/CommonServer/Types/Workflow/Components/JavaScript.ts +++ b/CommonServer/Types/Workflow/Components/JavaScript.ts @@ -1,103 +1,97 @@ -import { WorkflowScriptTimeoutInMS } from '../../../EnvironmentConfig'; -import VMUtil from '../../../Utils/VM/VMAPI'; -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ReturnResult from 'Common/Types/IsolatedVM/ReturnResult'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import JavaScriptComponents from 'Common/Types/Workflow/Components/JavaScript'; +import { WorkflowScriptTimeoutInMS } from "../../../EnvironmentConfig"; +import VMUtil from "../../../Utils/VM/VMAPI"; +import ComponentCode, { RunOptions, RunReturnType } from "../ComponentCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ReturnResult from "Common/Types/IsolatedVM/ReturnResult"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import JavaScriptComponents from "Common/Types/Workflow/Components/JavaScript"; export default class JavaScriptCode extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const JavaScriptComponent: ComponentMetadata | undefined = - JavaScriptComponents.find((i: ComponentMetadata) => { - return i.id === ComponentID.JavaScriptCode; - }); + const JavaScriptComponent: ComponentMetadata | undefined = + JavaScriptComponents.find((i: ComponentMetadata) => { + return i.id === ComponentID.JavaScriptCode; + }); - if (!JavaScriptComponent) { - throw new BadDataException( - 'Custom JavaScript Component not found.' - ); - } - - this.setMetadata(JavaScriptComponent); + if (!JavaScriptComponent) { + throw new BadDataException("Custom JavaScript Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + this.setMetadata(JavaScriptComponent); + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - try { - // Set timeout - // Inject args - // Inject dependencies - - let scriptArgs: JSONObject | string = - (args['arguments'] as JSONObject | string) || {}; - - if (typeof scriptArgs === 'string') { - scriptArgs = JSON.parse(scriptArgs); - } - - const code: string = (args['code'] as string) || ''; - - const returnResult: ReturnResult = await VMUtil.runCodeInSandbox({ - code, - options: { - args: scriptArgs as JSONObject, - timeout: WorkflowScriptTimeoutInMS, - }, - }); - - const logMessages: string[] = returnResult.logMessages; - - // add to option.log - logMessages.forEach((msg: string) => { - options.log(msg); - }); - - const returnVal: JSONValue = returnResult.returnValue; - - return { - returnValues: { - returnValue: returnVal, - }, - executePort: successPort, - }; - } catch (err: any) { - options.log('Error running script'); - options.log( - err.message ? err.message : JSON.stringify(err, null, 2) - ); - return { - returnValues: {}, - executePort: errorPort, - }; - } + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); + + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); + } + + try { + // Set timeout + // Inject args + // Inject dependencies + + let scriptArgs: JSONObject | string = + (args["arguments"] as JSONObject | string) || {}; + + if (typeof scriptArgs === "string") { + scriptArgs = JSON.parse(scriptArgs); + } + + const code: string = (args["code"] as string) || ""; + + const returnResult: ReturnResult = await VMUtil.runCodeInSandbox({ + code, + options: { + args: scriptArgs as JSONObject, + timeout: WorkflowScriptTimeoutInMS, + }, + }); + + const logMessages: string[] = returnResult.logMessages; + + // add to option.log + logMessages.forEach((msg: string) => { + options.log(msg); + }); + + const returnVal: JSONValue = returnResult.returnValue; + + return { + returnValues: { + returnValue: returnVal, + }, + executePort: successPort, + }; + } catch (err: any) { + options.log("Error running script"); + options.log(err.message ? err.message : JSON.stringify(err, null, 2)); + return { + returnValues: {}, + executePort: errorPort, + }; + } + } } diff --git a/CommonServer/Types/Workflow/Components/Log.ts b/CommonServer/Types/Workflow/Components/Log.ts index 32bdda66db..c79e2d9d45 100644 --- a/CommonServer/Types/Workflow/Components/Log.ts +++ b/CommonServer/Types/Workflow/Components/Log.ts @@ -1,47 +1,47 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import LogComponents from 'Common/Types/Workflow/Components/Log'; +import ComponentCode, { RunOptions, RunReturnType } from "../ComponentCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import LogComponents from "Common/Types/Workflow/Components/Log"; export default class Log extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const LogComponent: ComponentMetadata | undefined = LogComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.Log; - } - ); + const LogComponent: ComponentMetadata | undefined = LogComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.Log; + }, + ); - if (!LogComponent) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(LogComponent); + if (!LogComponent) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const outPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'out'; - } - ); + this.setMetadata(LogComponent); + } - if (!outPort) { - throw options.onError(new BadDataException('Out port not found')); - } + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const outPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "out"; + }, + ); - options.log('Value: '); - options.log(args['value']); - - return Promise.resolve({ - returnValues: {}, - executePort: outPort, - }); + if (!outPort) { + throw options.onError(new BadDataException("Out port not found")); } + + options.log("Value: "); + options.log(args["value"]); + + return Promise.resolve({ + returnValues: {}, + executePort: outPort, + }); + } } diff --git a/CommonServer/Types/Workflow/Components/Manual.ts b/CommonServer/Types/Workflow/Components/Manual.ts index fe726ee657..3029f8a2ec 100644 --- a/CommonServer/Types/Workflow/Components/Manual.ts +++ b/CommonServer/Types/Workflow/Components/Manual.ts @@ -1,21 +1,21 @@ -import TriggerCode from '../TriggerCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ComponentMetadata from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import ManualComponents from 'Common/Types/Workflow/Components/Manual'; +import TriggerCode from "../TriggerCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ComponentMetadata from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import ManualComponents from "Common/Types/Workflow/Components/Manual"; export default class ManualTrigger extends TriggerCode { - public constructor() { - super(); - const Component: ComponentMetadata | undefined = ManualComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.Manual; - } - ); + public constructor() { + super(); + const Component: ComponentMetadata | undefined = ManualComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.Manual; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } + this.setMetadata(Component); + } } diff --git a/CommonServer/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts b/CommonServer/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts index ffb63315d5..8ee124e760 100644 --- a/CommonServer/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +++ b/CommonServer/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts @@ -1,129 +1,123 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import APIException from 'Common/Types/Exception/ApiException'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import MicrosoftTeamComponents from 'Common/Types/Workflow/Components/MicrosoftTeams'; -import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import APIException from "Common/Types/Exception/ApiException"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import MicrosoftTeamComponents from "Common/Types/Workflow/Components/MicrosoftTeams"; +import API from "Common/Utils/API"; export default class SendMessageToChannel extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = - MicrosoftTeamComponents.find((i: ComponentMetadata) => { - return i.id === ComponentID.MicrosoftTeamsSendMessageToChannel; - }); + const Component: ComponentMetadata | undefined = + MicrosoftTeamComponents.find((i: ComponentMetadata) => { + return i.id === ComponentID.MicrosoftTeamsSendMessageToChannel; + }); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + this.setMetadata(Component); + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - if (!args['text']) { - throw options.onError( - new BadDataException('Microsoft teams message not found') - ); - } - - if (!args['webhook-url']) { - throw options.onError( - new BadDataException('Microsoft teams Webhook URL not found') - ); - } - - args['webhook-url'] = URL.fromString( - args['webhook-url']?.toString() as string - ); - - let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = - null; - - try { - // https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/incoming-webhook/nodejs/api/server/index.js#L28 - apiResult = await API.post(args['webhook-url'] as URL, { - type: 'message', - attachments: [ - { - contentType: 'application/vnd.microsoft.card.adaptive', - contentUrl: null, - content: { - $schema: - 'http://adaptivecards.io/schemas/adaptive-card.json', - msteams: { - width: 'Full', - }, - type: 'AdaptiveCard', - version: '1.2', - body: [ - { - type: 'TextBlock', - wrap: 'true', - text: `${args['text']}`, - }, - ], - }, - }, - ], - }); - - if (apiResult instanceof HTTPErrorResponse) { - return Promise.resolve({ - returnValues: { - error: apiResult.message || 'Server Error.', - }, - executePort: errorPort, - }); - } - return Promise.resolve({ - returnValues: {}, - executePort: successPort, - }); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - return Promise.resolve({ - returnValues: { - error: err.message || 'Server Error.', - }, - executePort: errorPort, - }); - } - - throw options.onError( - new APIException('Something wrong happened.') - ); - } + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); + + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); + } + + if (!args["text"]) { + throw options.onError( + new BadDataException("Microsoft teams message not found"), + ); + } + + if (!args["webhook-url"]) { + throw options.onError( + new BadDataException("Microsoft teams Webhook URL not found"), + ); + } + + args["webhook-url"] = URL.fromString( + args["webhook-url"]?.toString() as string, + ); + + let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = null; + + try { + // https://github.com/OfficeDev/Microsoft-Teams-Samples/blob/main/samples/incoming-webhook/nodejs/api/server/index.js#L28 + apiResult = await API.post(args["webhook-url"] as URL, { + type: "message", + attachments: [ + { + contentType: "application/vnd.microsoft.card.adaptive", + contentUrl: null, + content: { + $schema: "http://adaptivecards.io/schemas/adaptive-card.json", + msteams: { + width: "Full", + }, + type: "AdaptiveCard", + version: "1.2", + body: [ + { + type: "TextBlock", + wrap: "true", + text: `${args["text"]}`, + }, + ], + }, + }, + ], + }); + + if (apiResult instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: { + error: apiResult.message || "Server Error.", + }, + executePort: errorPort, + }); + } + return Promise.resolve({ + returnValues: {}, + executePort: successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: { + error: err.message || "Server Error.", + }, + executePort: errorPort, + }); + } + + throw options.onError(new APIException("Something wrong happened.")); + } + } } diff --git a/CommonServer/Types/Workflow/Components/Schedule.ts b/CommonServer/Types/Workflow/Components/Schedule.ts index e0819213d4..b037d16715 100644 --- a/CommonServer/Types/Workflow/Components/Schedule.ts +++ b/CommonServer/Types/Workflow/Components/Schedule.ts @@ -1,147 +1,146 @@ -import WorkflowService from '../../../Services/WorkflowService'; -import QueryHelper from '../../Database/QueryHelper'; -import { RunOptions, RunReturnType } from '../ComponentCode'; +import WorkflowService from "../../../Services/WorkflowService"; +import QueryHelper from "../../Database/QueryHelper"; +import { RunOptions, RunReturnType } from "../ComponentCode"; import TriggerCode, { - ExecuteWorkflowType, - InitProps, - UpdateProps, -} from '../TriggerCode'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import ScheduleComponents from 'Common/Types/Workflow/Components/Schedule'; -import Workflow from 'Model/Models/Workflow'; + ExecuteWorkflowType, + InitProps, + UpdateProps, +} from "../TriggerCode"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import ScheduleComponents from "Common/Types/Workflow/Components/Schedule"; +import Workflow from "Model/Models/Workflow"; export default class WebhookTrigger extends TriggerCode { - public constructor() { - const component: ComponentMetadata | undefined = - ScheduleComponents.find((i: ComponentMetadata) => { - return i.id === ComponentID.Schedule; - }); + public constructor() { + const component: ComponentMetadata | undefined = ScheduleComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.Schedule; + }, + ); - if (!component) { - throw new BadDataException('Trigger not found.'); - } - super(); - this.setMetadata(component); + if (!component) { + throw new BadDataException("Trigger not found."); } + super(); + this.setMetadata(component); + } - public override async init(props: InitProps): Promise<void> { - const workflows: Array<Workflow> = await WorkflowService.findBy({ - query: { - triggerId: ComponentID.Schedule as string, - triggerArguments: QueryHelper.notNull(), - }, - select: { - _id: true, - triggerArguments: true, - isEnabled: true, - }, - props: { - isRoot: true, - }, - limit: LIMIT_MAX, - skip: 0, - }); + public override async init(props: InitProps): Promise<void> { + const workflows: Array<Workflow> = await WorkflowService.findBy({ + query: { + triggerId: ComponentID.Schedule as string, + triggerArguments: QueryHelper.notNull(), + }, + select: { + _id: true, + triggerArguments: true, + isEnabled: true, + }, + props: { + isRoot: true, + }, + limit: LIMIT_MAX, + skip: 0, + }); - // query all workflows. - for (const workflow of workflows) { - const executeWorkflow: ExecuteWorkflowType = { - workflowId: new ObjectID(workflow._id!), - returnValues: {}, - }; + // query all workflows. + for (const workflow of workflows) { + const executeWorkflow: ExecuteWorkflowType = { + workflowId: new ObjectID(workflow._id!), + returnValues: {}, + }; - if ( - workflow.triggerArguments && - workflow.triggerArguments['schedule'] && - workflow.isEnabled - ) { - await props.scheduleWorkflow( - executeWorkflow, - workflow.triggerArguments['schedule'] as string - ); - } - - if (!workflow.isEnabled) { - await props.removeWorkflow(workflow.id!); - } - } - } - - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'execute'; - } + if ( + workflow.triggerArguments && + workflow.triggerArguments["schedule"] && + workflow.isEnabled + ) { + await props.scheduleWorkflow( + executeWorkflow, + workflow.triggerArguments["schedule"] as string, ); + } - if (!successPort) { - throw options.onError( - new BadDataException('Execute port not found') - ); - } + if (!workflow.isEnabled) { + await props.removeWorkflow(workflow.id!); + } + } + } - return { - returnValues: { - ...args, - }, - executePort: successPort, - }; + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "execute"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Execute port not found")); } - public override async update(props: UpdateProps): Promise<void> { - const workflow: Workflow | null = await WorkflowService.findOneBy({ - query: { - triggerId: ComponentID.Schedule, - _id: props.workflowId.toString(), - triggerArguments: QueryHelper.notNull(), - }, - select: { - _id: true, - triggerArguments: true, - isEnabled: true, - }, - props: { - isRoot: true, - }, - }); + return { + returnValues: { + ...args, + }, + executePort: successPort, + }; + } - if (!workflow) { - return; - } + public override async update(props: UpdateProps): Promise<void> { + const workflow: Workflow | null = await WorkflowService.findOneBy({ + query: { + triggerId: ComponentID.Schedule, + _id: props.workflowId.toString(), + triggerArguments: QueryHelper.notNull(), + }, + select: { + _id: true, + triggerArguments: true, + isEnabled: true, + }, + props: { + isRoot: true, + }, + }); - if (!this.scheduleWorkflow) { - return; - } - - const executeWorkflow: ExecuteWorkflowType = { - workflowId: new ObjectID(workflow._id!), - returnValues: {}, - }; - - if ( - workflow.triggerArguments && - workflow.triggerArguments['schedule'] && - workflow.isEnabled - ) { - await this.scheduleWorkflow( - executeWorkflow, - workflow.triggerArguments['schedule'] as string - ); - } - - if (!this.removeWorkflow) { - return; - } - - if (!workflow.isEnabled) { - await this.removeWorkflow(workflow.id!); - } + if (!workflow) { + return; } + + if (!this.scheduleWorkflow) { + return; + } + + const executeWorkflow: ExecuteWorkflowType = { + workflowId: new ObjectID(workflow._id!), + returnValues: {}, + }; + + if ( + workflow.triggerArguments && + workflow.triggerArguments["schedule"] && + workflow.isEnabled + ) { + await this.scheduleWorkflow( + executeWorkflow, + workflow.triggerArguments["schedule"] as string, + ); + } + + if (!this.removeWorkflow) { + return; + } + + if (!workflow.isEnabled) { + await this.removeWorkflow(workflow.id!); + } + } } diff --git a/CommonServer/Types/Workflow/Components/Slack/SendMessageToChannel.ts b/CommonServer/Types/Workflow/Components/Slack/SendMessageToChannel.ts index 555d0338d7..9b767ddc7a 100644 --- a/CommonServer/Types/Workflow/Components/Slack/SendMessageToChannel.ts +++ b/CommonServer/Types/Workflow/Components/Slack/SendMessageToChannel.ts @@ -1,116 +1,109 @@ -import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import APIException from 'Common/Types/Exception/ApiException'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import SlackComponents from 'Common/Types/Workflow/Components/Slack'; -import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from "../../ComponentCode"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import APIException from "Common/Types/Exception/ApiException"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import SlackComponents from "Common/Types/Workflow/Components/Slack"; +import API from "Common/Utils/API"; export default class SendMessageToChannel extends ComponentCode { - public constructor() { - super(); + public constructor() { + super(); - const Component: ComponentMetadata | undefined = SlackComponents.find( - (i: ComponentMetadata) => { - return i.id === ComponentID.SlackSendMessageToChannel; - } - ); + const Component: ComponentMetadata | undefined = SlackComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.SlackSendMessageToChannel; + }, + ); - if (!Component) { - throw new BadDataException('Component not found.'); - } - - this.setMetadata(Component); + if (!Component) { + throw new BadDataException("Component not found."); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + this.setMetadata(Component); + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); - const errorPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'error'; - } - ); - - if (!errorPort) { - throw options.onError(new BadDataException('Error port not found')); - } - - if (!args['text']) { - throw options.onError( - new BadDataException('Slack message not found') - ); - } - - if (!args['webhook-url']) { - throw options.onError( - new BadDataException('Slack Webhook URL not found') - ); - } - - args['webhook-url'] = URL.fromString( - args['webhook-url']?.toString() as string - ); - - let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = - null; - - try { - // https://api.slack.com/messaging/webhooks#advanced_message_formatting - apiResult = await API.post(args['webhook-url'] as URL, { - blocks: [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: `${args['text']}`, - }, - }, - ], - }); - - if (apiResult instanceof HTTPErrorResponse) { - return Promise.resolve({ - returnValues: { - error: apiResult.message || 'Server Error.', - }, - executePort: errorPort, - }); - } - return Promise.resolve({ - returnValues: {}, - executePort: successPort, - }); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - return Promise.resolve({ - returnValues: { - error: err.message || 'Server Error.', - }, - executePort: errorPort, - }); - } - - throw options.onError( - new APIException('Something wrong happened.') - ); - } + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "error"; + }, + ); + + if (!errorPort) { + throw options.onError(new BadDataException("Error port not found")); + } + + if (!args["text"]) { + throw options.onError(new BadDataException("Slack message not found")); + } + + if (!args["webhook-url"]) { + throw options.onError( + new BadDataException("Slack Webhook URL not found"), + ); + } + + args["webhook-url"] = URL.fromString( + args["webhook-url"]?.toString() as string, + ); + + let apiResult: HTTPResponse<JSONObject> | HTTPErrorResponse | null = null; + + try { + // https://api.slack.com/messaging/webhooks#advanced_message_formatting + apiResult = await API.post(args["webhook-url"] as URL, { + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: `${args["text"]}`, + }, + }, + ], + }); + + if (apiResult instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: { + error: apiResult.message || "Server Error.", + }, + executePort: errorPort, + }); + } + return Promise.resolve({ + returnValues: {}, + executePort: successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: { + error: err.message || "Server Error.", + }, + executePort: errorPort, + }); + } + + throw options.onError(new APIException("Something wrong happened.")); + } + } } diff --git a/CommonServer/Types/Workflow/Components/Webhook.ts b/CommonServer/Types/Workflow/Components/Webhook.ts index d3af6d59f1..0d659f303e 100644 --- a/CommonServer/Types/Workflow/Components/Webhook.ts +++ b/CommonServer/Types/Workflow/Components/Webhook.ts @@ -1,86 +1,86 @@ -import { ExpressRequest, ExpressResponse } from '../../../Utils/Express'; -import Response from '../../../Utils/Response'; -import { RunOptions, RunReturnType } from '../ComponentCode'; -import TriggerCode, { ExecuteWorkflowType, InitProps } from '../TriggerCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import ComponentID from 'Common/Types/Workflow/ComponentID'; -import WebhookComponents from 'Common/Types/Workflow/Components/Webhook'; +import { ExpressRequest, ExpressResponse } from "../../../Utils/Express"; +import Response from "../../../Utils/Response"; +import { RunOptions, RunReturnType } from "../ComponentCode"; +import TriggerCode, { ExecuteWorkflowType, InitProps } from "../TriggerCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; +import ComponentID from "Common/Types/Workflow/ComponentID"; +import WebhookComponents from "Common/Types/Workflow/Components/Webhook"; export default class WebhookTrigger extends TriggerCode { - public constructor() { - super(); - const WebhookComponent: ComponentMetadata | undefined = - WebhookComponents.find((i: ComponentMetadata) => { - return i.id === ComponentID.Webhook; - }); + public constructor() { + super(); + const WebhookComponent: ComponentMetadata | undefined = + WebhookComponents.find((i: ComponentMetadata) => { + return i.id === ComponentID.Webhook; + }); - if (!WebhookComponent) { - throw new BadDataException('Webhook trigger not found.'); - } - this.setMetadata(WebhookComponent); + if (!WebhookComponent) { + throw new BadDataException("Webhook trigger not found."); + } + this.setMetadata(WebhookComponent); + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "out"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Out port not found")); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'out'; - } - ); + return { + returnValues: { + ...args, + }, + executePort: successPort, + }; + } - if (!successPort) { - throw options.onError(new BadDataException('Out port not found')); - } + public override async init(props: InitProps): Promise<void> { + props.router.get( + `/trigger/:workflowId`, + async (req: ExpressRequest, res: ExpressResponse) => { + await this.initTrigger(req, res, props); + }, + ); - return { - returnValues: { - ...args, - }, - executePort: successPort, - }; - } + props.router.post( + `/trigger/:workflowId`, + async (req: ExpressRequest, res: ExpressResponse) => { + await this.initTrigger(req, res, props); + }, + ); + } - public override async init(props: InitProps): Promise<void> { - props.router.get( - `/trigger/:workflowId`, - async (req: ExpressRequest, res: ExpressResponse) => { - await this.initTrigger(req, res, props); - } - ); + public async initTrigger( + req: ExpressRequest, + res: ExpressResponse, + props: InitProps, + ): Promise<void> { + /// Run Graph. - props.router.post( - `/trigger/:workflowId`, - async (req: ExpressRequest, res: ExpressResponse) => { - await this.initTrigger(req, res, props); - } - ); - } + // check if this workflow has the trigger enabled. - public async initTrigger( - req: ExpressRequest, - res: ExpressResponse, - props: InitProps - ): Promise<void> { - /// Run Graph. + const executeWorkflow: ExecuteWorkflowType = { + workflowId: new ObjectID(req.params["workflowId"] as string), + returnValues: { + "request-headers": req.headers, + "request-params": req.query, + "request-body": req.body, + }, + }; - // check if this workflow has the trigger enabled. + await props.executeWorkflow(executeWorkflow); - const executeWorkflow: ExecuteWorkflowType = { - workflowId: new ObjectID(req.params['workflowId'] as string), - returnValues: { - 'request-headers': req.headers, - 'request-params': req.query, - 'request-body': req.body, - }, - }; - - await props.executeWorkflow(executeWorkflow); - - Response.sendJsonObjectResponse(req, res, { status: 'Scheduled' }); - } + Response.sendJsonObjectResponse(req, res, { status: "Scheduled" }); + } } diff --git a/CommonServer/Types/Workflow/TriggerCode.ts b/CommonServer/Types/Workflow/TriggerCode.ts index c54a516035..ffcc073709 100644 --- a/CommonServer/Types/Workflow/TriggerCode.ts +++ b/CommonServer/Types/Workflow/TriggerCode.ts @@ -1,87 +1,85 @@ // this class is the base class that all the component can implement // -import { ExpressRouter } from '../../Utils/Express'; -import ComponentCode, { RunOptions, RunReturnType } from './ComponentCode'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { Port } from 'Common/Types/Workflow/Component'; +import { ExpressRouter } from "../../Utils/Express"; +import ComponentCode, { RunOptions, RunReturnType } from "./ComponentCode"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { Port } from "Common/Types/Workflow/Component"; export interface ExecuteWorkflowType { - workflowId: ObjectID; - returnValues: JSONObject; + workflowId: ObjectID; + returnValues: JSONObject; } export interface InitProps { - router: ExpressRouter; - executeWorkflow: (executeWorkflow: ExecuteWorkflowType) => Promise<void>; - scheduleWorkflow: ( - executeWorkflow: ExecuteWorkflowType, - scheduleAt: string - ) => Promise<void>; - removeWorkflow: (workflowId: ObjectID) => Promise<void>; + router: ExpressRouter; + executeWorkflow: (executeWorkflow: ExecuteWorkflowType) => Promise<void>; + scheduleWorkflow: ( + executeWorkflow: ExecuteWorkflowType, + scheduleAt: string, + ) => Promise<void>; + removeWorkflow: (workflowId: ObjectID) => Promise<void>; } export interface UpdateProps { - workflowId: ObjectID; + workflowId: ObjectID; } export default class TriggerCode extends ComponentCode { - public executeWorkflow: - | ((executeWorkflow: ExecuteWorkflowType) => Promise<void>) - | null = null; + public executeWorkflow: + | ((executeWorkflow: ExecuteWorkflowType) => Promise<void>) + | null = null; - public scheduleWorkflow: - | (( - executeWorkflow: ExecuteWorkflowType, - scheduleAt: string - ) => Promise<void>) - | null = null; + public scheduleWorkflow: + | (( + executeWorkflow: ExecuteWorkflowType, + scheduleAt: string, + ) => Promise<void>) + | null = null; - public removeWorkflow: ((workflowId: ObjectID) => Promise<void>) | null = - null; + public removeWorkflow: ((workflowId: ObjectID) => Promise<void>) | null = + null; - public constructor() { - super(); + public constructor() { + super(); + } + + public override async run( + args: JSONObject, + options: RunOptions, + ): Promise<RunReturnType> { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === "success"; + }, + ); + + if (!successPort) { + throw options.onError(new BadDataException("Success port not found")); } - public override async run( - args: JSONObject, - options: RunOptions - ): Promise<RunReturnType> { - const successPort: Port | undefined = this.getMetadata().outPorts.find( - (p: Port) => { - return p.id === 'success'; - } - ); + return { + returnValues: { + ...args, + }, + executePort: successPort, + }; + } - if (!successPort) { - throw options.onError( - new BadDataException('Success port not found') - ); - } + public async setupComponent(props: InitProps): Promise<void> { + this.executeWorkflow = props.executeWorkflow; + this.scheduleWorkflow = props.scheduleWorkflow; + this.removeWorkflow = props.removeWorkflow; - return { - returnValues: { - ...args, - }, - executePort: successPort, - }; - } + return await this.init(props); + } - public async setupComponent(props: InitProps): Promise<void> { - this.executeWorkflow = props.executeWorkflow; - this.scheduleWorkflow = props.scheduleWorkflow; - this.removeWorkflow = props.removeWorkflow; + public async init(_props: InitProps): Promise<void> { + return await Promise.resolve(); + } - return await this.init(props); - } - - public async init(_props: InitProps): Promise<void> { - return await Promise.resolve(); - } - - public async update(_props: UpdateProps): Promise<void> { - return await Promise.resolve(); - } + public async update(_props: UpdateProps): Promise<void> { + return await Promise.resolve(); + } } diff --git a/CommonServer/Types/Workflow/Workflow.ts b/CommonServer/Types/Workflow/Workflow.ts index 7b62532835..7bf37d0030 100644 --- a/CommonServer/Types/Workflow/Workflow.ts +++ b/CommonServer/Types/Workflow/Workflow.ts @@ -1,9 +1,9 @@ -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; export interface RunProps { - arguments: JSONObject; - workflowId: ObjectID; - workflowLogId: ObjectID | null; - timeout: number; + arguments: JSONObject; + workflowId: ObjectID; + workflowLogId: ObjectID | null; + timeout: number; } diff --git a/CommonServer/Utils/Airtable.ts b/CommonServer/Utils/Airtable.ts index 5d1fcc6c56..e175a7aa89 100644 --- a/CommonServer/Utils/Airtable.ts +++ b/CommonServer/Utils/Airtable.ts @@ -1,43 +1,43 @@ -import { AirtableApiKey, AirtableBaseId } from '../EnvironmentConfig'; -import Dictionary from 'Common/Types/Dictionary'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import AirtableLib, { FieldSet, Records } from 'airtable'; +import { AirtableApiKey, AirtableBaseId } from "../EnvironmentConfig"; +import Dictionary from "Common/Types/Dictionary"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import AirtableLib, { FieldSet, Records } from "airtable"; export type AirtableRecords = Records<FieldSet>; class Airtable { - private static base = new AirtableLib({ apiKey: AirtableApiKey }).base( - AirtableBaseId - ); + private static base = new AirtableLib({ apiKey: AirtableApiKey }).base( + AirtableBaseId, + ); - public static async find( - tableName: string, - airtableView: string, - limit: PositiveNumber - ): Promise<AirtableRecords> { - return this.base(tableName) - .select({ view: airtableView, pageSize: limit.toNumber() }) - .firstPage(); - } + public static async find( + tableName: string, + airtableView: string, + limit: PositiveNumber, + ): Promise<AirtableRecords> { + return this.base(tableName) + .select({ view: airtableView, pageSize: limit.toNumber() }) + .firstPage(); + } - public static async update( - tableName: string, - id: string, - fields: Dictionary<string> - ): Promise<void> { - await this.base(tableName).update(id, fields); - } + public static async update( + tableName: string, + id: string, + fields: Dictionary<string>, + ): Promise<void> { + await this.base(tableName).update(id, fields); + } - public static async create( - tableName: string, - fields: Dictionary<string> - ): Promise<void> { - await this.base(tableName).create(fields); - } + public static async create( + tableName: string, + fields: Dictionary<string>, + ): Promise<void> { + await this.base(tableName).create(fields); + } - public static async delete(tableName: string, id: string): Promise<void> { - await this.base(tableName).destroy(id); - } + public static async delete(tableName: string, id: string): Promise<void> { + await this.base(tableName).destroy(id); + } } export default Airtable; diff --git a/CommonServer/Utils/AnalyticsDatabase/Statement.ts b/CommonServer/Utils/AnalyticsDatabase/Statement.ts index 923f898f36..4a85cc3433 100644 --- a/CommonServer/Utils/AnalyticsDatabase/Statement.ts +++ b/CommonServer/Utils/AnalyticsDatabase/Statement.ts @@ -1,186 +1,182 @@ -import { BaseQueryParams } from '@clickhouse/client'; -import { integer } from '@elastic/elasticsearch/lib/api/types'; -import { RecordValue } from 'Common/AnalyticsModels/CommonModel'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import GreaterThan from 'Common/Types/BaseDatabase/GreaterThan'; -import GreaterThanOrEqual from 'Common/Types/BaseDatabase/GreaterThanOrEqual'; -import Includes from 'Common/Types/BaseDatabase/Includes'; -import LessThan from 'Common/Types/BaseDatabase/LessThan'; -import LessThanOrEqual from 'Common/Types/BaseDatabase/LessThanOrEqual'; -import Search from 'Common/Types/BaseDatabase/Search'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; -import { inspect } from 'util'; +import { BaseQueryParams } from "@clickhouse/client"; +import { integer } from "@elastic/elasticsearch/lib/api/types"; +import { RecordValue } from "Common/AnalyticsModels/CommonModel"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import GreaterThan from "Common/Types/BaseDatabase/GreaterThan"; +import GreaterThanOrEqual from "Common/Types/BaseDatabase/GreaterThanOrEqual"; +import Includes from "Common/Types/BaseDatabase/Includes"; +import LessThan from "Common/Types/BaseDatabase/LessThan"; +import LessThanOrEqual from "Common/Types/BaseDatabase/LessThanOrEqual"; +import Search from "Common/Types/BaseDatabase/Search"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; +import { inspect } from "util"; /** * This file based on the sql-template-strings package, adapted for ClickHouse */ export type StatementParameter = { - type: TableColumnType; - value: RecordValue; + type: TableColumnType; + value: RecordValue; }; export class Statement implements BaseQueryParams { - public constructor( - private strings: string[] = [''], - private values: Array<StatementParameter | string> = [] - ) {} + public constructor( + private strings: string[] = [""], + private values: Array<StatementParameter | string> = [], + ) {} - public get query(): string { - let query: string = this.strings.reduce( - (prev: string, curr: string, i: integer) => { - const param: StatementParameter | string = this.values[i - 1]!; + public get query(): string { + let query: string = this.strings.reduce( + (prev: string, curr: string, i: integer) => { + const param: StatementParameter | string = this.values[i - 1]!; - const dataType: string = - typeof param === 'string' - ? 'Identifier' - : Statement.toColumnType(param); + const dataType: string = + typeof param === "string" + ? "Identifier" + : Statement.toColumnType(param); - return prev + `{p${i - 1}:${dataType}}` + curr; - } + return prev + `{p${i - 1}:${dataType}}` + curr; + }, + ); + + // dedent lines + query = query.trimEnd(); + const minIndent: number = + query.match(/\n( *)/g)?.reduce((minIndent: number, indent: string) => { + return Math.min(minIndent, indent.length - 1); + }, Infinity) ?? 0; + query = query.replace(new RegExp(`\n {${minIndent}}`, "g"), "\n"); + query = query.trimStart(); + + return query; + } + + public get query_params(): Record<string, unknown> { + let index: number = 0; + + const returnObject: Record<string, unknown> = {}; + + for (const v of this.values) { + const finalValue: RecordValue | Array<RecordValue> = + this.serializseValue(v); + returnObject[`p${index}`] = finalValue; + index++; + } + + return returnObject; + } + + public serializseValue( + v: StatementParameter | string, + ): RecordValue | Array<RecordValue> { + let finalValue: RecordValue | Array<RecordValue> = v as RecordValue; + + // if of type date, convert to database date + + if (typeof v === "string") { + finalValue = v; + } else if (Array.isArray(v.value)) { + const tempArr: Array<RecordValue> = []; + + for (const val of v.value) { + tempArr.push( + this.serializseValue({ + type: v.type, + value: val, + }) as RecordValue, ); + } - // dedent lines - query = query.trimEnd(); - const minIndent: number = - query - .match(/\n( *)/g) - ?.reduce((minIndent: number, indent: string) => { - return Math.min(minIndent, indent.length - 1); - }, Infinity) ?? 0; - query = query.replace(new RegExp(`\n {${minIndent}}`, 'g'), '\n'); - query = query.trimStart(); - - return query; + finalValue = tempArr; + } else if (v.value instanceof ObjectID) { + finalValue = v.value.toString(); + } else if (v.value instanceof Search) { + finalValue = `%${v.value.toString()}%`; + } else if ( + v.value instanceof LessThan || + v.value instanceof LessThanOrEqual || + v.value instanceof GreaterThan || + v.value instanceof GreaterThanOrEqual + ) { + finalValue = v.value.value; + } else if (v.value instanceof Includes) { + if ( + v.type === TableColumnType.Text || + v.type === TableColumnType.ObjectID + ) { + finalValue = v.value.values.map((val: string | ObjectID) => { + return `${val.toString()}`; + }); + } else { + finalValue = v.value.values; + } + } else if (v.value instanceof Date) { + finalValue = OneUptimeDate.toDatabaseDate(v.value); + } else { + finalValue = v.value; } - public get query_params(): Record<string, unknown> { - let index: number = 0; + // serialize to date. - const returnObject: Record<string, unknown> = {}; - - for (const v of this.values) { - const finalValue: RecordValue | Array<RecordValue> = - this.serializseValue(v); - returnObject[`p${index}`] = finalValue; - index++; - } - - return returnObject; + if (typeof v !== "string" && v.type === TableColumnType.Date) { + finalValue = OneUptimeDate.fromString(finalValue as string); + finalValue = OneUptimeDate.toDatabaseDate(finalValue); } - public serializseValue( - v: StatementParameter | string - ): RecordValue | Array<RecordValue> { - let finalValue: RecordValue | Array<RecordValue> = v as RecordValue; + return finalValue; + } - // if of type date, convert to database date + /** + * Append an escaped SQL fragment. If you pass a raw String + * it is appended as trusted SQL! + */ + public append(statement: Statement | string): Statement { + if (statement instanceof Statement) { + this.strings[this.strings.length - 1] += statement.strings[0]; + this.strings.push(...statement.strings.slice(1)); + this.values.push(...statement.values); + } else { + this.strings[this.strings.length - 1] += statement; + } + return this; + } - if (typeof v === 'string') { - finalValue = v; - } else if (Array.isArray(v.value)) { - const tempArr: Array<RecordValue> = []; + // custom inspect for logging + public [Symbol.for("nodejs.util.inspect.custom")](): string { + return `Statement ${inspect({ + query: this.query, + query_params: this.query_params, + })}`; + } - for (const val of v.value) { - tempArr.push( - this.serializseValue({ - type: v.type, - value: val, - }) as RecordValue - ); - } + private static toColumnType( + statementParam: StatementParameter | string, + ): string { + // ensure we have a mapping for all types (a missing mapping will + // be a compile error) + const columnTypes: Dictionary<string> = { + [TableColumnType.Text]: "String", + [TableColumnType.ObjectID]: "String", + [TableColumnType.Boolean]: "Bool", + [TableColumnType.Number]: "Int32", + [TableColumnType.Decimal]: "Double", + [TableColumnType.Date]: "DateTime", + [TableColumnType.JSON]: "JSON", + [TableColumnType.NestedModel]: "Nested", + [TableColumnType.ArrayNumber]: "Array(Int32)", + [TableColumnType.ArrayText]: "Array(String)", + [TableColumnType.LongNumber]: "Int128", + }; - finalValue = tempArr; - } else if (v.value instanceof ObjectID) { - finalValue = v.value.toString(); - } else if (v.value instanceof Search) { - finalValue = `%${v.value.toString()}%`; - } else if ( - v.value instanceof LessThan || - v.value instanceof LessThanOrEqual || - v.value instanceof GreaterThan || - v.value instanceof GreaterThanOrEqual - ) { - finalValue = v.value.value; - } else if (v.value instanceof Includes) { - if ( - v.type === TableColumnType.Text || - v.type === TableColumnType.ObjectID - ) { - finalValue = v.value.values.map((val: string | ObjectID) => { - return `${val.toString()}`; - }); - } else { - finalValue = v.value.values; - } - } else if (v.value instanceof Date) { - finalValue = OneUptimeDate.toDatabaseDate(v.value); - } else { - finalValue = v.value; - } - - // serialize to date. - - if (typeof v !== 'string' && v.type === TableColumnType.Date) { - finalValue = OneUptimeDate.fromString(finalValue as string); - finalValue = OneUptimeDate.toDatabaseDate(finalValue); - } - - return finalValue; + if ((statementParam as StatementParameter).value instanceof Includes) { + return "Array(String)"; } - /** - * Append an escaped SQL fragment. If you pass a raw String - * it is appended as trusted SQL! - */ - public append(statement: Statement | string): Statement { - if (statement instanceof Statement) { - this.strings[this.strings.length - 1] += statement.strings[0]; - this.strings.push(...statement.strings.slice(1)); - this.values.push(...statement.values); - } else { - this.strings[this.strings.length - 1] += statement; - } - return this; - } - - // custom inspect for logging - public [Symbol.for('nodejs.util.inspect.custom')](): string { - return `Statement ${inspect({ - query: this.query, - query_params: this.query_params, - })}`; - } - - private static toColumnType( - statementParam: StatementParameter | string - ): string { - // ensure we have a mapping for all types (a missing mapping will - // be a compile error) - const columnTypes: Dictionary<string> = { - [TableColumnType.Text]: 'String', - [TableColumnType.ObjectID]: 'String', - [TableColumnType.Boolean]: 'Bool', - [TableColumnType.Number]: 'Int32', - [TableColumnType.Decimal]: 'Double', - [TableColumnType.Date]: 'DateTime', - [TableColumnType.JSON]: 'JSON', - [TableColumnType.NestedModel]: 'Nested', - [TableColumnType.ArrayNumber]: 'Array(Int32)', - [TableColumnType.ArrayText]: 'Array(String)', - [TableColumnType.LongNumber]: 'Int128', - }; - - if ((statementParam as StatementParameter).value instanceof Includes) { - return 'Array(String)'; - } - - return ( - columnTypes[(statementParam as StatementParameter).type] || 'String' - ); - } + return columnTypes[(statementParam as StatementParameter).type] || "String"; + } } /** @@ -190,8 +186,8 @@ export class Statement implements BaseQueryParams { * other substitution values must be StatementParameters. */ export function SQL( - strings: TemplateStringsArray, - ...values: Array<StatementParameter | string> + strings: TemplateStringsArray, + ...values: Array<StatementParameter | string> ): Statement { - return new Statement(strings.slice(0), values.slice(0)); + return new Statement(strings.slice(0), values.slice(0)); } diff --git a/CommonServer/Utils/AnalyticsDatabase/StatementGenerator.ts b/CommonServer/Utils/AnalyticsDatabase/StatementGenerator.ts index 468e1dc80b..fe912f3e5d 100644 --- a/CommonServer/Utils/AnalyticsDatabase/StatementGenerator.ts +++ b/CommonServer/Utils/AnalyticsDatabase/StatementGenerator.ts @@ -1,52 +1,52 @@ -import ClickhouseDatabase from '../../Infrastructure/ClickhouseDatabase'; -import GroupBy from '../../Types/AnalyticsDatabase/GroupBy'; -import Query from '../../Types/AnalyticsDatabase/Query'; -import Select from '../../Types/AnalyticsDatabase/Select'; -import Sort from '../../Types/AnalyticsDatabase/Sort'; -import UpdateBy from '../../Types/AnalyticsDatabase/UpdateBy'; -import logger from '../Logger'; -import { SQL, Statement } from './Statement'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; +import ClickhouseDatabase from "../../Infrastructure/ClickhouseDatabase"; +import GroupBy from "../../Types/AnalyticsDatabase/GroupBy"; +import Query from "../../Types/AnalyticsDatabase/Query"; +import Select from "../../Types/AnalyticsDatabase/Select"; +import Sort from "../../Types/AnalyticsDatabase/Sort"; +import UpdateBy from "../../Types/AnalyticsDatabase/UpdateBy"; +import logger from "../Logger"; +import { SQL, Statement } from "./Statement"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; import CommonModel, { - Record, - RecordValue, -} from 'Common/AnalyticsModels/CommonModel'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import GreaterThan from 'Common/Types/BaseDatabase/GreaterThan'; -import GreaterThanOrEqual from 'Common/Types/BaseDatabase/GreaterThanOrEqual'; -import InBetween from 'Common/Types/BaseDatabase/InBetween'; -import Includes from 'Common/Types/BaseDatabase/Includes'; -import IsNull from 'Common/Types/BaseDatabase/IsNull'; -import LessThan from 'Common/Types/BaseDatabase/LessThan'; -import LessThanOrEqual from 'Common/Types/BaseDatabase/LessThanOrEqual'; -import NotEqual from 'Common/Types/BaseDatabase/NotEqual'; -import Search from 'Common/Types/BaseDatabase/Search'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; + Record, + RecordValue, +} from "Common/AnalyticsModels/CommonModel"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import GreaterThan from "Common/Types/BaseDatabase/GreaterThan"; +import GreaterThanOrEqual from "Common/Types/BaseDatabase/GreaterThanOrEqual"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import Includes from "Common/Types/BaseDatabase/Includes"; +import IsNull from "Common/Types/BaseDatabase/IsNull"; +import LessThan from "Common/Types/BaseDatabase/LessThan"; +import LessThanOrEqual from "Common/Types/BaseDatabase/LessThanOrEqual"; +import NotEqual from "Common/Types/BaseDatabase/NotEqual"; +import Search from "Common/Types/BaseDatabase/Search"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> { - public model!: TBaseModel; - public modelType!: { new (): TBaseModel }; - public database!: ClickhouseDatabase; + public model!: TBaseModel; + public modelType!: { new (): TBaseModel }; + public database!: ClickhouseDatabase; - public constructor(data: { - modelType: { new (): TBaseModel }; - database: ClickhouseDatabase; - }) { - this.modelType = data.modelType; - this.model = new this.modelType(); - this.database = data.database; - } + public constructor(data: { + modelType: { new (): TBaseModel }; + database: ClickhouseDatabase; + }) { + this.modelType = data.modelType; + this.model = new this.modelType(); + this.database = data.database; + } - public toUpdateStatement(updateBy: UpdateBy<TBaseModel>): Statement { - const setStatement: Statement = this.toSetStatement(updateBy.data); - const whereStatement: Statement = this.toWhereStatement(updateBy.query); + public toUpdateStatement(updateBy: UpdateBy<TBaseModel>): Statement { + const setStatement: Statement = this.toSetStatement(updateBy.data); + const whereStatement: Statement = this.toWhereStatement(updateBy.query); - /* eslint-disable prettier/prettier */ + /* eslint-disable prettier/prettier */ const statement: Statement = SQL` ALTER TABLE ${this.database.getDatasourceOptions().database!}.${this.model.tableName } @@ -54,646 +54,626 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> { WHERE TRUE `).append(whereStatement); /* eslint-enable prettier/prettier */ - logger.debug(`${this.model.tableName} Update Statement`); - logger.debug(statement); + logger.debug(`${this.model.tableName} Update Statement`); + logger.debug(statement); - return statement; - } + return statement; + } - public getColumnNames( - tableColumns: Array<AnalyticsTableColumn> - ): Array<string> { - const columnNames: Array<string> = []; - for (const column of tableColumns) { - if (column.type === TableColumnType.NestedModel) { - // Example of nested model query: + public getColumnNames( + tableColumns: Array<AnalyticsTableColumn>, + ): Array<string> { + const columnNames: Array<string> = []; + for (const column of tableColumns) { + if (column.type === TableColumnType.NestedModel) { + // Example of nested model query: - /** + /** * * INSERT INTO opentelemetry_spans (trace_id, span_id, attributes.key, attributes.value) VALUES ('trace1', 'span1', ['key1', 'key2'], ['value1', 'value2']), ('trace2', 'span2', ['keyA', 'keyB'], ['valueA', 'valueB']); */ - // Nested Model Support. - const nestedModelColumnNames: Array<string> = - this.getColumnNames(column.nestedModel!.tableColumns); - - for (const nestedModelColumnName of nestedModelColumnNames) { - columnNames.push(`${column.key}.${nestedModelColumnName}`); - } - } else { - columnNames.push(column.key); - } - } - - return columnNames; - } - - public getRecordValuesStatement(record: Record): string { - let valueStatement: string = ''; - - for (const value of record) { - if (Array.isArray(value)) { - if (value.length === 0) { - valueStatement += `[], `; - continue; - } - - valueStatement += `[${value.join(',')}], `; - } else { - valueStatement += `${value}, `; - } - } - - valueStatement = valueStatement.substring(0, valueStatement.length - 2); // remove last comma. - - return valueStatement; - } - - public getValuesStatement(records: Array<Record>): string { - let statement: string = ''; - for (const record of records) { - statement += `(${this.getRecordValuesStatement(record)}), `; - } - - statement = statement.substring(0, statement.length - 2); // remove last comma. - - return statement; - } - - public toCreateStatement(data: { item: Array<TBaseModel> }): string { - if (!data.item) { - throw new BadDataException('Item cannot be null'); - } - - const columnNames: Array<string> = this.getColumnNames( - this.model.getTableColumns() + // Nested Model Support. + const nestedModelColumnNames: Array<string> = this.getColumnNames( + column.nestedModel!.tableColumns, ); - const records: Array<Record> = []; + for (const nestedModelColumnName of nestedModelColumnNames) { + columnNames.push(`${column.key}.${nestedModelColumnName}`); + } + } else { + columnNames.push(column.key); + } + } - for (const item of data.item) { - const record: Record = this.getRecord(item); - records.push(record); + return columnNames; + } + + public getRecordValuesStatement(record: Record): string { + let valueStatement: string = ""; + + for (const value of record) { + if (Array.isArray(value)) { + if (value.length === 0) { + valueStatement += `[], `; + continue; } - const statement: string = `INSERT INTO ${ - this.database.getDatasourceOptions().database - }.${this.model.tableName} + valueStatement += `[${value.join(",")}], `; + } else { + valueStatement += `${value}, `; + } + } + + valueStatement = valueStatement.substring(0, valueStatement.length - 2); // remove last comma. + + return valueStatement; + } + + public getValuesStatement(records: Array<Record>): string { + let statement: string = ""; + for (const record of records) { + statement += `(${this.getRecordValuesStatement(record)}), `; + } + + statement = statement.substring(0, statement.length - 2); // remove last comma. + + return statement; + } + + public toCreateStatement(data: { item: Array<TBaseModel> }): string { + if (!data.item) { + throw new BadDataException("Item cannot be null"); + } + + const columnNames: Array<string> = this.getColumnNames( + this.model.getTableColumns(), + ); + + const records: Array<Record> = []; + + for (const item of data.item) { + const record: Record = this.getRecord(item); + records.push(record); + } + + const statement: string = `INSERT INTO ${ + this.database.getDatasourceOptions().database + }.${this.model.tableName} ( - ${columnNames.join(', ')} + ${columnNames.join(", ")} ) VALUES ${this.getValuesStatement(records)} `; - logger.debug(`${this.model.tableName} Create Statement`); - logger.debug(statement); + logger.debug(`${this.model.tableName} Create Statement`); + logger.debug(statement); - return statement; - } + return statement; + } - private getRecord(item: CommonModel): Record { - const record: Record = []; + private getRecord(item: CommonModel): Record { + const record: Record = []; - for (const column of item.getTableColumns()) { - if (column.type === TableColumnType.NestedModel) { - // Nested Model Support. + for (const column of item.getTableColumns()) { + if (column.type === TableColumnType.NestedModel) { + // Nested Model Support. - // THis is very werid, but the output should work in a query like this: + // THis is very werid, but the output should work in a query like this: - /** + /** * * INSERT INTO opentelemetry_spans (trace_id, span_id, attributes.key, attributes.value) VALUES ('trace1', 'span1', ['key1', 'key2'], ['value1', 'value2']), ('trace2', 'span2', ['keyA', 'keyB'], ['valueA', 'valueB']); */ - for (const subColumn of column.nestedModel!.tableColumns) { - const subRecord: Record = []; + for (const subColumn of column.nestedModel!.tableColumns) { + const subRecord: Record = []; - for (const nestedModelItem of item.getColumnValue( - column.key - ) as Array<CommonModel>) { - const value: RecordValue = this.sanitizeValue( - nestedModelItem.getColumnValue(subColumn.key), - subColumn, - { - isNestedModel: true, - } - ); + for (const nestedModelItem of item.getColumnValue( + column.key, + ) as Array<CommonModel>) { + const value: RecordValue = this.sanitizeValue( + nestedModelItem.getColumnValue(subColumn.key), + subColumn, + { + isNestedModel: true, + }, + ); - subRecord.push(value); - } + subRecord.push(value); + } - record.push(subRecord); - } - } else { - const value: RecordValue | undefined = this.sanitizeValue( - item.getColumnValue(column.key), - column - ); - - record.push(value); - } + record.push(subRecord); } + } else { + const value: RecordValue | undefined = this.sanitizeValue( + item.getColumnValue(column.key), + column, + ); - return record; + record.push(value); + } } - private escapeStringLiteral(raw: string): string { - // escape String literal based on https://clickhouse.com/docs/en/sql-reference/syntax#string - return `'${raw.replace(/'|\\/g, '\\$&')}'`; - } + return record; + } - private sanitizeValue( - value: RecordValue | undefined, - column: AnalyticsTableColumn, - options?: { - isNestedModel?: boolean; - } - ): RecordValue { - if (!value && value !== 0 && value !== false) { - if (options?.isNestedModel) { - if (column.type === TableColumnType.Text) { - return `''`; - } + private escapeStringLiteral(raw: string): string { + // escape String literal based on https://clickhouse.com/docs/en/sql-reference/syntax#string + return `'${raw.replace(/'|\\/g, "\\$&")}'`; + } - if (column.type === TableColumnType.Number) { - return 0; - } - } - - return 'NULL'; - } - - if ( - column.type === TableColumnType.ObjectID || - column.type === TableColumnType.Text - ) { - value = this.escapeStringLiteral(value?.toString()); - } - - if (column.type === TableColumnType.Date && value instanceof Date) { - value = `parseDateTimeBestEffortOrNull('${OneUptimeDate.toString( - value as Date - )}')`; + private sanitizeValue( + value: RecordValue | undefined, + column: AnalyticsTableColumn, + options?: { + isNestedModel?: boolean; + }, + ): RecordValue { + if (!value && value !== 0 && value !== false) { + if (options?.isNestedModel) { + if (column.type === TableColumnType.Text) { + return `''`; } if (column.type === TableColumnType.Number) { - if (typeof value === 'string') { - value = parseInt(value); - } + return 0; } + } - if (column.type === TableColumnType.Decimal) { - if (typeof value === 'string') { - value = parseFloat(value); - } - } - - if (column.type === TableColumnType.ArrayNumber) { - value = `[${(value as Array<number>) - .map((v: number) => { - if (v && typeof v !== 'number') { - v = parseFloat(v); - return isNaN(v) ? 'NULL' : v; - } - return v; - }) - .join(', ')}]`; - } - - if (column.type === TableColumnType.ArrayText) { - value = `[${(value as Array<string>) - .map((v: string) => { - return this.escapeStringLiteral(v); - }) - .join(', ')}]`; - } - - if ( - column.type === TableColumnType.JSON || - column.type === TableColumnType.JSONArray - ) { - value = this.escapeStringLiteral(JSON.stringify(value)); - } - - if (column.type === TableColumnType.LongNumber) { - value = `CAST(${this.escapeStringLiteral( - value.toString() - )} AS Int128)`; - } - - return value; + return "NULL"; } - public toSetStatement(data: TBaseModel): Statement { - const setStatement: Statement = new Statement(); + if ( + column.type === TableColumnType.ObjectID || + column.type === TableColumnType.Text + ) { + value = this.escapeStringLiteral(value?.toString()); + } - let first: boolean = true; - for (const column of data.getTableColumns()) { - const value: RecordValue | undefined = data.getColumnValue( - column.key + if (column.type === TableColumnType.Date && value instanceof Date) { + value = `parseDateTimeBestEffortOrNull('${OneUptimeDate.toString( + value as Date, + )}')`; + } + + if (column.type === TableColumnType.Number) { + if (typeof value === "string") { + value = parseInt(value); + } + } + + if (column.type === TableColumnType.Decimal) { + if (typeof value === "string") { + value = parseFloat(value); + } + } + + if (column.type === TableColumnType.ArrayNumber) { + value = `[${(value as Array<number>) + .map((v: number) => { + if (v && typeof v !== "number") { + v = parseFloat(v); + return isNaN(v) ? "NULL" : v; + } + return v; + }) + .join(", ")}]`; + } + + if (column.type === TableColumnType.ArrayText) { + value = `[${(value as Array<string>) + .map((v: string) => { + return this.escapeStringLiteral(v); + }) + .join(", ")}]`; + } + + if ( + column.type === TableColumnType.JSON || + column.type === TableColumnType.JSONArray + ) { + value = this.escapeStringLiteral(JSON.stringify(value)); + } + + if (column.type === TableColumnType.LongNumber) { + value = `CAST(${this.escapeStringLiteral(value.toString())} AS Int128)`; + } + + return value; + } + + public toSetStatement(data: TBaseModel): Statement { + const setStatement: Statement = new Statement(); + + let first: boolean = true; + for (const column of data.getTableColumns()) { + const value: RecordValue | undefined = data.getColumnValue(column.key); + if (value !== undefined) { + if (first) { + first = false; + } else { + setStatement.append(SQL`, `); + } + + // special case - ClickHouse does not support using query + // parameters for column names in the SET statement so we + // have to trust the column names here. + const keyStatement: string = column.key; + + setStatement.append(keyStatement).append( + SQL` = ${{ + value, + type: column.type, + }}`, + ); + } + } + + return setStatement; + } + + /** + * Conditions to append to "WHERE TRUE" + */ + public toWhereStatement(query: Query<TBaseModel>): Statement { + const whereStatement: Statement = new Statement(); + + let first: boolean = true; + for (const key in query) { + const value: any = query[key]; + const tableColumn: AnalyticsTableColumn | null = + this.model.getTableColumn(key); + + if (!tableColumn) { + throw new BadDataException(`Unknown column: ${key}`); + } + + if (first) { + first = false; + } else { + whereStatement.append(SQL` `); + } + + if (value instanceof Search) { + whereStatement.append( + SQL`AND ${key} ILIKE ${{ + value: value, + type: tableColumn.type, + }}`, + ); + } else if (value instanceof NotEqual) { + whereStatement.append( + SQL`AND ${key} != ${{ + value: value, + type: tableColumn.type, + }}`, + ); + } else if (value instanceof GreaterThan) { + whereStatement.append( + SQL`AND ${key} > ${{ + value: value, + type: tableColumn.type, + }}`, + ); + } else if (value instanceof LessThan) { + whereStatement.append( + SQL`AND ${key} < ${{ + value: value, + type: tableColumn.type, + }}`, + ); + } else if (value instanceof LessThanOrEqual) { + whereStatement.append( + SQL`AND ${key} <= ${{ + value: value, + type: tableColumn.type, + }}`, + ); + } else if (value instanceof GreaterThanOrEqual) { + whereStatement.append( + SQL`AND ${key} >= ${{ + value: value, + type: tableColumn.type, + }}`, + ); + } else if (value instanceof InBetween) { + whereStatement.append( + SQL`AND ${key} >= ${{ + value: value.startValue, + type: tableColumn.type, + }} AND ${key} <= ${{ + value: value.endValue, + type: tableColumn.type, + }}`, + ); + } else if (value instanceof Includes) { + whereStatement.append( + SQL`AND ${key} IN ${{ + value: value, + type: tableColumn.type, + }}`, + ); + } else if (value instanceof IsNull) { + if (tableColumn.type === TableColumnType.Text) { + whereStatement.append(SQL`AND (${key} IS NULL OR ${key} = '')`); + } else { + whereStatement.append(SQL`AND ${key} IS NULL`); + } + } else if ( + (tableColumn.type === TableColumnType.JSON || + tableColumn.type === TableColumnType.JSONArray) && + typeof value === "object" + ) { + const flatValue: JSONObject = JSONFunctions.flattenObject(value); + + for (const objKey in flatValue) { + if (flatValue[objKey] === undefined) { + continue; + } + + if (flatValue[objKey] && typeof flatValue[objKey] === "string") { + whereStatement.append( + SQL`AND JSONExtractString(${key}, ${{ + value: objKey, + type: TableColumnType.Text, + }}) = ${{ + value: flatValue[objKey] as string, + type: TableColumnType.Text, + }}`, ); - if (value !== undefined) { - if (first) { - first = false; - } else { - setStatement.append(SQL`, `); - } + continue; + } - // special case - ClickHouse does not support using query - // parameters for column names in the SET statement so we - // have to trust the column names here. - const keyStatement: string = column.key; - - setStatement.append(keyStatement).append( - SQL` = ${{ - value, - type: column.type, - }}` - ); - } - } - - return setStatement; - } - - /** - * Conditions to append to "WHERE TRUE" - */ - public toWhereStatement(query: Query<TBaseModel>): Statement { - const whereStatement: Statement = new Statement(); - - let first: boolean = true; - for (const key in query) { - const value: any = query[key]; - const tableColumn: AnalyticsTableColumn | null = - this.model.getTableColumn(key); - - if (!tableColumn) { - throw new BadDataException(`Unknown column: ${key}`); - } - - if (first) { - first = false; - } else { - whereStatement.append(SQL` `); - } - - if (value instanceof Search) { - whereStatement.append( - SQL`AND ${key} ILIKE ${{ - value: value, - type: tableColumn.type, - }}` - ); - } else if (value instanceof NotEqual) { - whereStatement.append( - SQL`AND ${key} != ${{ - value: value, - type: tableColumn.type, - }}` - ); - } else if (value instanceof GreaterThan) { - whereStatement.append( - SQL`AND ${key} > ${{ - value: value, - type: tableColumn.type, - }}` - ); - } else if (value instanceof LessThan) { - whereStatement.append( - SQL`AND ${key} < ${{ - value: value, - type: tableColumn.type, - }}` - ); - } else if (value instanceof LessThanOrEqual) { - whereStatement.append( - SQL`AND ${key} <= ${{ - value: value, - type: tableColumn.type, - }}` - ); - } else if (value instanceof GreaterThanOrEqual) { - whereStatement.append( - SQL`AND ${key} >= ${{ - value: value, - type: tableColumn.type, - }}` - ); - } else if (value instanceof InBetween) { - whereStatement.append( - SQL`AND ${key} >= ${{ - value: value.startValue, - type: tableColumn.type, - }} AND ${key} <= ${{ - value: value.endValue, - type: tableColumn.type, - }}` - ); - } else if (value instanceof Includes) { - whereStatement.append( - SQL`AND ${key} IN ${{ - value: value, - type: tableColumn.type, - }}` - ); - } else if (value instanceof IsNull) { - if (tableColumn.type === TableColumnType.Text) { - whereStatement.append( - SQL`AND (${key} IS NULL OR ${key} = '')` - ); - } else { - whereStatement.append(SQL`AND ${key} IS NULL`); - } - } else if ( - (tableColumn.type === TableColumnType.JSON || - tableColumn.type === TableColumnType.JSONArray) && - typeof value === 'object' - ) { - const flatValue: JSONObject = - JSONFunctions.flattenObject(value); - - for (const objKey in flatValue) { - if (flatValue[objKey] === undefined) { - continue; - } - - if ( - flatValue[objKey] && - typeof flatValue[objKey] === 'string' - ) { - whereStatement.append( - SQL`AND JSONExtractString(${key}, ${{ - value: objKey, - type: TableColumnType.Text, - }}) = ${{ - value: flatValue[objKey] as string, - type: TableColumnType.Text, - }}` - ); - continue; - } - - if ( - flatValue[objKey] && - typeof flatValue[objKey] === 'number' - ) { - whereStatement.append( - SQL`AND JSONExtractInt(${key}, ${{ - value: objKey, - type: TableColumnType.Text, - }}) = ${{ - value: flatValue[objKey] as number, - type: TableColumnType.Number, - }}` - ); - continue; - } - - if ( - flatValue[objKey] && - typeof flatValue[objKey] === 'boolean' - ) { - whereStatement.append( - SQL`AND JSONExtractBool(${key}, ${{ - value: objKey, - type: TableColumnType.Text, - }}) = ${{ - value: flatValue[objKey] as number, - type: TableColumnType.Boolean, - }}` - ); - continue; - } - } - } else { - whereStatement.append( - SQL`AND ${key} = ${{ value, type: tableColumn.type }}` - ); - } - } - - return whereStatement; - } - - public toGroupByStatement(groupBy: GroupBy<TBaseModel>): Statement { - const groupByStatement: Statement = new Statement(); - - let first: boolean = true; - for (const key in groupBy) { - if (first) { - first = false; - } else { - groupByStatement.append(SQL`, `); - } - groupByStatement.append(SQL`${key}`); - } - - return groupByStatement; - } - - public toSortStatement(sort: Sort<TBaseModel>): Statement { - const sortStatement: Statement = new Statement(); - - for (const key in sort) { - const value: SortOrder = sort[key]!; - sortStatement.append(SQL`${key} `).append( - { - [SortOrder.Ascending]: SQL`ASC`, - [SortOrder.Descending]: SQL`DESC`, - }[value] + if (flatValue[objKey] && typeof flatValue[objKey] === "number") { + whereStatement.append( + SQL`AND JSONExtractInt(${key}, ${{ + value: objKey, + type: TableColumnType.Text, + }}) = ${{ + value: flatValue[objKey] as number, + type: TableColumnType.Number, + }}`, ); + continue; + } + + if (flatValue[objKey] && typeof flatValue[objKey] === "boolean") { + whereStatement.append( + SQL`AND JSONExtractBool(${key}, ${{ + value: objKey, + type: TableColumnType.Text, + }}) = ${{ + value: flatValue[objKey] as number, + type: TableColumnType.Boolean, + }}`, + ); + continue; + } } - - return sortStatement; + } else { + whereStatement.append( + SQL`AND ${key} = ${{ value, type: tableColumn.type }}`, + ); + } } - public toSelectStatement(select: Select<TBaseModel>): { - statement: Statement; - columns: Array<string>; - } { - const selectStatement: Statement = new Statement(); - const columns: Array<string> = []; + return whereStatement; + } - let first: boolean = true; - for (const key in select) { - const value: any = select[key]; - if (value) { - columns.push(key); - if (first) { - first = false; - } else { - selectStatement.append(SQL`, `); - } - selectStatement.append(SQL`${key}`); - } + public toGroupByStatement(groupBy: GroupBy<TBaseModel>): Statement { + const groupByStatement: Statement = new Statement(); + + let first: boolean = true; + for (const key in groupBy) { + if (first) { + first = false; + } else { + groupByStatement.append(SQL`, `); + } + groupByStatement.append(SQL`${key}`); + } + + return groupByStatement; + } + + public toSortStatement(sort: Sort<TBaseModel>): Statement { + const sortStatement: Statement = new Statement(); + + for (const key in sort) { + const value: SortOrder = sort[key]!; + sortStatement.append(SQL`${key} `).append( + { + [SortOrder.Ascending]: SQL`ASC`, + [SortOrder.Descending]: SQL`DESC`, + }[value], + ); + } + + return sortStatement; + } + + public toSelectStatement(select: Select<TBaseModel>): { + statement: Statement; + columns: Array<string>; + } { + const selectStatement: Statement = new Statement(); + const columns: Array<string> = []; + + let first: boolean = true; + for (const key in select) { + const value: any = select[key]; + if (value) { + columns.push(key); + if (first) { + first = false; + } else { + selectStatement.append(SQL`, `); } - - return { - columns: columns, - statement: selectStatement, - }; + selectStatement.append(SQL`${key}`); + } } - public getColumnTypesStatement(columnName: string): string { - return `SELECT type FROM system.columns WHERE table = '${ - this.model.tableName - }' AND database = '${ - this.database.getDatasourceOptions().database - }' AND name = '${columnName}'`; + return { + columns: columns, + statement: selectStatement, + }; + } + + public getColumnTypesStatement(columnName: string): string { + return `SELECT type FROM system.columns WHERE table = '${ + this.model.tableName + }' AND database = '${ + this.database.getDatasourceOptions().database + }' AND name = '${columnName}'`; + } + + public async toRenameColumnStatement( + oldColumnName: string, + newColumnName: string, + ): Promise<Statement> { + const statement: string = `ALTER TABLE ${ + this.database.getDatasourceOptions().database + }.${ + this.model.tableName + } RENAME COLUMN IF EXISTS ${oldColumnName} TO ${newColumnName}`; + + return SQL`${statement}`; + } + + public toColumnsCreateStatement( + tableColumns: Array<AnalyticsTableColumn>, + ): Statement { + const columns: Statement = new Statement(); + + for (let i: number = 0; i < tableColumns.length; i++) { + const column: AnalyticsTableColumn = tableColumns[i]!; + + if (i !== 0) { + columns.append(SQL`, `); + } + + let nestedModelColumns: Statement | null = null; + + if (column.type === TableColumnType.NestedModel) { + nestedModelColumns = SQL`(` + .append( + this.toColumnsCreateStatement(column.nestedModel!.tableColumns), + ) + .append(SQL`)`); + } + + // special case - ClickHouse does not support using an a query parameter + // to specify the column name when creating the table + const keyStatement: string = column.key; + + columns + .append(keyStatement) + .append(SQL` `) + .append( + column.required + ? this.toColumnType(column.type) + : SQL`Nullable(` + .append(this.toColumnType(column.type)) + .append(SQL`)`), + ); + + if (nestedModelColumns) { + columns.append(SQL` `).append(nestedModelColumns); + } } - public async toRenameColumnStatement( - oldColumnName: string, - newColumnName: string - ): Promise<Statement> { - const statement: string = `ALTER TABLE ${ - this.database.getDatasourceOptions().database - }.${ - this.model.tableName - } RENAME COLUMN IF EXISTS ${oldColumnName} TO ${newColumnName}`; + return columns; + } - return SQL`${statement}`; - } + public toTableColumnType( + clickhouseType: string, + ): TableColumnType | undefined { + return { + String: TableColumnType.Text, + Int32: TableColumnType.Number, + Int64: TableColumnType.LongNumber, + Int128: TableColumnType.LongNumber, + Float32: TableColumnType.Decimal, + Float64: TableColumnType.Decimal, + DateTime: TableColumnType.Date, + "Array(String)": TableColumnType.ArrayText, + "Array(Int32)": TableColumnType.ArrayNumber, + JSON: TableColumnType.JSON, //JSONArray is also JSON + Nested: TableColumnType.NestedModel, + Bool: TableColumnType.Boolean, + }[clickhouseType]; + } - public toColumnsCreateStatement( - tableColumns: Array<AnalyticsTableColumn> - ): Statement { - const columns: Statement = new Statement(); + public toColumnType(type: TableColumnType): Statement { + return { + [TableColumnType.Text]: SQL`String`, + [TableColumnType.ObjectID]: SQL`String`, + [TableColumnType.Boolean]: SQL`Bool`, + [TableColumnType.Number]: SQL`Int32`, + [TableColumnType.Decimal]: SQL`Double`, + [TableColumnType.Date]: SQL`DateTime`, + [TableColumnType.JSON]: SQL`String`, // we use JSON as a string because ClickHouse has really good JSON support for string types + [TableColumnType.JSONArray]: SQL`String`, // we use JSON as a string because ClickHouse has really good JSON support for string types + [TableColumnType.NestedModel]: SQL`Nested`, + [TableColumnType.ArrayNumber]: SQL`Array(Int32)`, + [TableColumnType.ArrayText]: SQL`Array(String)`, + [TableColumnType.LongNumber]: SQL`Int128`, + }[type]; + } - for (let i: number = 0; i < tableColumns.length; i++) { - const column: AnalyticsTableColumn = tableColumns[i]!; + public toDoesColumnExistStatement(columnName: string): string { + const statement: string = `SELECT name FROM system.columns WHERE table = '${ + this.model.tableName + }' AND database = '${this.database.getDatasourceOptions() + .database!}' AND name = '${columnName}'`; - if (i !== 0) { - columns.append(SQL`, `); - } + logger.debug(`${this.model.tableName} Does Column Exist Statement`); + logger.debug(statement); - let nestedModelColumns: Statement | null = null; + return statement; + } - if (column.type === TableColumnType.NestedModel) { - nestedModelColumns = SQL`(` - .append( - this.toColumnsCreateStatement( - column.nestedModel!.tableColumns - ) - ) - .append(SQL`)`); - } - - // special case - ClickHouse does not support using an a query parameter - // to specify the column name when creating the table - const keyStatement: string = column.key; - - columns - .append(keyStatement) - .append(SQL` `) - .append( - column.required - ? this.toColumnType(column.type) - : SQL`Nullable(` - .append(this.toColumnType(column.type)) - .append(SQL`)`) - ); - - if (nestedModelColumns) { - columns.append(SQL` `).append(nestedModelColumns); - } - } - - return columns; - } - - public toTableColumnType( - clickhouseType: string - ): TableColumnType | undefined { - return { - String: TableColumnType.Text, - Int32: TableColumnType.Number, - Int64: TableColumnType.LongNumber, - Int128: TableColumnType.LongNumber, - Float32: TableColumnType.Decimal, - Float64: TableColumnType.Decimal, - DateTime: TableColumnType.Date, - 'Array(String)': TableColumnType.ArrayText, - 'Array(Int32)': TableColumnType.ArrayNumber, - JSON: TableColumnType.JSON, //JSONArray is also JSON - Nested: TableColumnType.NestedModel, - Bool: TableColumnType.Boolean, - }[clickhouseType]; - } - - public toColumnType(type: TableColumnType): Statement { - return { - [TableColumnType.Text]: SQL`String`, - [TableColumnType.ObjectID]: SQL`String`, - [TableColumnType.Boolean]: SQL`Bool`, - [TableColumnType.Number]: SQL`Int32`, - [TableColumnType.Decimal]: SQL`Double`, - [TableColumnType.Date]: SQL`DateTime`, - [TableColumnType.JSON]: SQL`String`, // we use JSON as a string because ClickHouse has really good JSON support for string types - [TableColumnType.JSONArray]: SQL`String`, // we use JSON as a string because ClickHouse has really good JSON support for string types - [TableColumnType.NestedModel]: SQL`Nested`, - [TableColumnType.ArrayNumber]: SQL`Array(Int32)`, - [TableColumnType.ArrayText]: SQL`Array(String)`, - [TableColumnType.LongNumber]: SQL`Int128`, - }[type]; - } - - public toDoesColumnExistStatement(columnName: string): string { - const statement: string = `SELECT name FROM system.columns WHERE table = '${ - this.model.tableName - }' AND database = '${this.database.getDatasourceOptions() - .database!}' AND name = '${columnName}'`; - - logger.debug(`${this.model.tableName} Does Column Exist Statement`); - logger.debug(statement); - - return statement; - } - - public toAddColumnStatement(column: AnalyticsTableColumn): Statement { - const statement: Statement = SQL` + public toAddColumnStatement(column: AnalyticsTableColumn): Statement { + const statement: Statement = SQL` ALTER TABLE ${this.database.getDatasourceOptions().database!}.${ - this.model.tableName - } ADD COLUMN IF NOT EXISTS `.append( - this.toColumnsCreateStatement([column]) - ); + this.model.tableName + } ADD COLUMN IF NOT EXISTS `.append( + this.toColumnsCreateStatement([column]), + ); - logger.debug(`${this.model.tableName} Add Column Statement`); - logger.debug(statement); + logger.debug(`${this.model.tableName} Add Column Statement`); + logger.debug(statement); - return statement; - } + return statement; + } - public toDropColumnStatement(columnName: string): string { - const statement: string = `ALTER TABLE ${this.database.getDatasourceOptions() - .database!}.${ - this.model.tableName - } DROP COLUMN IF EXISTS ${columnName}`; + public toDropColumnStatement(columnName: string): string { + const statement: string = `ALTER TABLE ${this.database.getDatasourceOptions() + .database!}.${this.model.tableName} DROP COLUMN IF EXISTS ${columnName}`; - logger.debug(`${this.model.tableName} Drop Column Statement`); - logger.debug(statement); + logger.debug(`${this.model.tableName} Drop Column Statement`); + logger.debug(statement); - return statement; - } + return statement; + } - public toTableCreateStatement(): Statement { - const databaseName: string = - this.database.getDatasourceOptions().database!; - const columnsStatement: Statement = this.toColumnsCreateStatement( - this.model.tableColumns - ); + public toTableCreateStatement(): Statement { + const databaseName: string = this.database.getDatasourceOptions().database!; + const columnsStatement: Statement = this.toColumnsCreateStatement( + this.model.tableColumns, + ); - // special case - ClickHouse does not support using a query parameter - // to specify the table engine - const tableEngineStatement: string = this.model.tableEngine; + // special case - ClickHouse does not support using a query parameter + // to specify the table engine + const tableEngineStatement: string = this.model.tableEngine; - /* eslint-disable prettier/prettier */ + /* eslint-disable prettier/prettier */ const statement: Statement = SQL` CREATE TABLE IF NOT EXISTS ${databaseName}.${this.model.tableName} (\n` @@ -713,9 +693,9 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> { statement.append(SQL`)`); /* eslint-enable prettier/prettier */ - logger.debug(`${this.model.tableName} Table Create Statement`); - logger.debug(statement); + logger.debug(`${this.model.tableName} Table Create Statement`); + logger.debug(statement); - return statement; - } + return statement; + } } diff --git a/CommonServer/Utils/BasicCron.ts b/CommonServer/Utils/BasicCron.ts index 3f27764f76..834b99eda6 100644 --- a/CommonServer/Utils/BasicCron.ts +++ b/CommonServer/Utils/BasicCron.ts @@ -1,38 +1,38 @@ -import logger from './Logger'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import cron from 'node-cron'; +import logger from "./Logger"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import cron from "node-cron"; type BasicCronProps = { - jobName: string; - options: { - schedule: string; - runOnStartup: boolean; - }; - runFunction: PromiseVoidFunction; + jobName: string; + options: { + schedule: string; + runOnStartup: boolean; + }; + runFunction: PromiseVoidFunction; }; type BasicCronFunction = (props: BasicCronProps) => void; const BasicCron: BasicCronFunction = async ( - props: BasicCronProps + props: BasicCronProps, ): Promise<void> => { - const { jobName, options, runFunction } = props; + const { jobName, options, runFunction } = props; - cron.schedule(options.schedule, async () => { - try { - logger.debug(`Job ${jobName} Start`); - await runFunction(); - logger.debug(`Job ${jobName} End`); - } catch (e) { - logger.debug(`Job ${jobName} Error`); - logger.error(e); - } - }); - - if (options.runOnStartup) { - logger.debug(`Job ${jobName} - Start on Startup`); - await runFunction(); + cron.schedule(options.schedule, async () => { + try { + logger.debug(`Job ${jobName} Start`); + await runFunction(); + logger.debug(`Job ${jobName} End`); + } catch (e) { + logger.debug(`Job ${jobName} Error`); + logger.error(e); } + }); + + if (options.runOnStartup) { + logger.debug(`Job ${jobName} - Start on Startup`); + await runFunction(); + } }; export default BasicCron; diff --git a/CommonServer/Utils/CodeRepository/CodeRepository.ts b/CommonServer/Utils/CodeRepository/CodeRepository.ts index fb9893dd60..1973c7004d 100644 --- a/CommonServer/Utils/CodeRepository/CodeRepository.ts +++ b/CommonServer/Utils/CodeRepository/CodeRepository.ts @@ -1,345 +1,339 @@ -import Execute from '../Execute'; -import LocalFile from '../LocalFile'; -import logger from '../Logger'; -import CodeRepositoryFile from './CodeRepositoryFile'; -import Dictionary from 'Common/Types/Dictionary'; +import Execute from "../Execute"; +import LocalFile from "../LocalFile"; +import logger from "../Logger"; +import CodeRepositoryFile from "./CodeRepositoryFile"; +import Dictionary from "Common/Types/Dictionary"; export default class CodeRepositoryUtil { - public static async createOrCheckoutBranch(data: { - repoPath: string; - branchName: string; - }): Promise<void> { - const command: string = `cd ${data.repoPath} && git checkout ${data.branchName} || git checkout -b ${data.branchName}`; + public static async createOrCheckoutBranch(data: { + repoPath: string; + branchName: string; + }): Promise<void> { + const command: string = `cd ${data.repoPath} && git checkout ${data.branchName} || git checkout -b ${data.branchName}`; - logger.debug('Executing command: ' + command); + logger.debug("Executing command: " + command); - const stdout: string = await Execute.executeCommand(command); + const stdout: string = await Execute.executeCommand(command); - logger.debug(stdout); + logger.debug(stdout); + } + + // discard all changes in the working directory + public static async discardChanges(data: { + repoPath: string; + }): Promise<void> { + const command: string = `cd ${data.repoPath} && git checkout .`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async writeToFile(data: { + filePath: string; + repoPath: string; + content: string; + }): Promise<void> { + const totalPath: string = LocalFile.sanitizeFilePath( + `${data.repoPath}/${data.filePath}`, + ); + + const command: string = `echo "${data.content}" > ${totalPath}`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async createDirectory(data: { + repoPath: string; + directoryPath: string; + }): Promise<void> { + const totalPath: string = LocalFile.sanitizeFilePath( + `${data.repoPath}/${data.directoryPath}`, + ); + + const command: string = `mkdir ${totalPath}`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async deleteFile(data: { + repoPath: string; + filePath: string; + }): Promise<void> { + const totalPath: string = LocalFile.sanitizeFilePath( + `${data.repoPath}/${data.filePath}`, + ); + + const command: string = `rm ${totalPath}`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async deleteDirectory(data: { + repoPath: string; + directoryPath: string; + }): Promise<void> { + const totalPath: string = LocalFile.sanitizeFilePath( + `${data.repoPath}/${data.directoryPath}`, + ); + + const command: string = `rm -rf ${totalPath}`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async createBranch(data: { + repoPath: string; + branchName: string; + }): Promise<void> { + const command: string = `cd ${data.repoPath} && git checkout -b ${data.branchName}`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async checkoutBranch(data: { + repoPath: string; + branchName: string; + }): Promise<void> { + const command: string = `cd ${data.repoPath} && git checkout ${data.branchName}`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async addFilesToGit(data: { + repoPath: string; + filePaths: Array<string>; + }): Promise<void> { + const filePaths: Array<string> = data.filePaths.map((filePath: string) => { + if (filePath.startsWith("/")) { + // remove the leading slash and return + return filePath.substring(1); + } + + return filePath; + }); + + const command: string = `cd ${ + data.repoPath + } && git add ${filePaths.join(" ")}`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async setUsername(data: { + repoPath: string; + username: string; + }): Promise<void> { + const command: string = `cd ${data.repoPath} && git config user.name "${data.username}"`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async commitChanges(data: { + repoPath: string; + message: string; + username: string; + }): Promise<void> { + // set the username and email + + await this.setUsername({ + repoPath: data.repoPath, + username: data.username, + }); + + const command: string = `cd ${data.repoPath} && git commit -m "${data.message}"`; + + logger.debug("Executing command: " + command); + + const stdout: string = await Execute.executeCommand(command); + + logger.debug(stdout); + } + + public static async getGitCommitHashForFile(data: { + repoPath: string; + filePath: string; + }): Promise<string> { + if (!data.filePath.startsWith("/")) { + data.filePath = "/" + data.filePath; } - // discard all changes in the working directory - public static async discardChanges(data: { - repoPath: string; - }): Promise<void> { - const command: string = `cd ${data.repoPath} && git checkout .`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); + if (!data.repoPath.startsWith("/")) { + data.repoPath = "/" + data.repoPath; } - public static async writeToFile(data: { - filePath: string; - repoPath: string; - content: string; - }): Promise<void> { - const totalPath: string = LocalFile.sanitizeFilePath( - `${data.repoPath}/${data.filePath}` + const { repoPath, filePath } = data; + + const command: string = `cd ${repoPath} && git log -1 --pretty=format:"%H" ".${filePath}"`; + + logger.debug("Executing command: " + command); + + const hash: string = await Execute.executeCommand(command); + + logger.debug(hash); + + return hash; + } + + public static async getFilesInDirectory(data: { + directoryPath: string; + repoPath: string; + acceptedFileExtensions?: Array<string>; + ignoreFilesOrDirectories: Array<string>; + }): Promise<{ + files: Dictionary<CodeRepositoryFile>; + subDirectories: Array<string>; + }> { + if (!data.directoryPath.startsWith("/")) { + data.directoryPath = "/" + data.directoryPath; + } + + if (!data.repoPath.startsWith("/")) { + data.repoPath = "/" + data.repoPath; + } + + const { directoryPath, repoPath } = data; + + let totalPath: string = `${repoPath}/${directoryPath}`; + + totalPath = LocalFile.sanitizeFilePath(totalPath); // clean up the path + + const files: Dictionary<CodeRepositoryFile> = {}; + const output: string = await Execute.executeCommand(`ls ${totalPath}`); + + const fileNames: Array<string> = output.split("\n"); + + const subDirectories: Array<string> = []; + + for (const fileName of fileNames) { + if (fileName === "") { + continue; + } + + const filePath: string = LocalFile.sanitizeFilePath( + `${directoryPath}/${fileName}`, + ); + + if (data.ignoreFilesOrDirectories.includes(fileName)) { + continue; + } + + const isDirectory: boolean = ( + await Execute.executeCommand( + `file "${LocalFile.sanitizeFilePath(`${totalPath}/${fileName}`)}"`, + ) + ).includes("directory"); + + if (isDirectory) { + subDirectories.push( + LocalFile.sanitizeFilePath(`${directoryPath}/${fileName}`), ); + continue; + } else if ( + data.acceptedFileExtensions && + data.acceptedFileExtensions.length > 0 + ) { + let shouldSkip: boolean = true; - const command: string = `echo "${data.content}" > ${totalPath}`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); - } - - public static async createDirectory(data: { - repoPath: string; - directoryPath: string; - }): Promise<void> { - const totalPath: string = LocalFile.sanitizeFilePath( - `${data.repoPath}/${data.directoryPath}` - ); - - const command: string = `mkdir ${totalPath}`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); - } - - public static async deleteFile(data: { - repoPath: string; - filePath: string; - }): Promise<void> { - const totalPath: string = LocalFile.sanitizeFilePath( - `${data.repoPath}/${data.filePath}` - ); - - const command: string = `rm ${totalPath}`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); - } - - public static async deleteDirectory(data: { - repoPath: string; - directoryPath: string; - }): Promise<void> { - const totalPath: string = LocalFile.sanitizeFilePath( - `${data.repoPath}/${data.directoryPath}` - ); - - const command: string = `rm -rf ${totalPath}`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); - } - - public static async createBranch(data: { - repoPath: string; - branchName: string; - }): Promise<void> { - const command: string = `cd ${data.repoPath} && git checkout -b ${data.branchName}`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); - } - - public static async checkoutBranch(data: { - repoPath: string; - branchName: string; - }): Promise<void> { - const command: string = `cd ${data.repoPath} && git checkout ${data.branchName}`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); - } - - public static async addFilesToGit(data: { - repoPath: string; - filePaths: Array<string>; - }): Promise<void> { - const filePaths: Array<string> = data.filePaths.map( - (filePath: string) => { - if (filePath.startsWith('/')) { - // remove the leading slash and return - return filePath.substring(1); - } - - return filePath; - } - ); - - const command: string = `cd ${ - data.repoPath - } && git add ${filePaths.join(' ')}`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); - } - - public static async setUsername(data: { - repoPath: string; - username: string; - }): Promise<void> { - const command: string = `cd ${data.repoPath} && git config user.name "${data.username}"`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); - } - - public static async commitChanges(data: { - repoPath: string; - message: string; - username: string; - }): Promise<void> { - // set the username and email - - await this.setUsername({ - repoPath: data.repoPath, - username: data.username, - }); - - const command: string = `cd ${data.repoPath} && git commit -m "${data.message}"`; - - logger.debug('Executing command: ' + command); - - const stdout: string = await Execute.executeCommand(command); - - logger.debug(stdout); - } - - public static async getGitCommitHashForFile(data: { - repoPath: string; - filePath: string; - }): Promise<string> { - if (!data.filePath.startsWith('/')) { - data.filePath = '/' + data.filePath; + for (const fileExtension of data.acceptedFileExtensions) { + if (fileName.endsWith(fileExtension)) { + shouldSkip = false; + break; + } } - if (!data.repoPath.startsWith('/')) { - data.repoPath = '/' + data.repoPath; + if (shouldSkip) { + continue; } + } - const { repoPath, filePath } = data; + const gitCommitHash: string = await this.getGitCommitHashForFile({ + filePath, + repoPath, + }); - const command: string = `cd ${repoPath} && git log -1 --pretty=format:"%H" ".${filePath}"`; - - logger.debug('Executing command: ' + command); - - const hash: string = await Execute.executeCommand(command); - - logger.debug(hash); - - return hash; + const fileExtension: string = fileName.split(".").pop() || ""; + files[filePath] = { + filePath: LocalFile.sanitizeFilePath(`${directoryPath}/${fileName}`), + gitCommitHash, + fileExtension, + fileName, + }; } - public static async getFilesInDirectory(data: { - directoryPath: string; - repoPath: string; - acceptedFileExtensions?: Array<string>; - ignoreFilesOrDirectories: Array<string>; - }): Promise<{ - files: Dictionary<CodeRepositoryFile>; - subDirectories: Array<string>; - }> { - if (!data.directoryPath.startsWith('/')) { - data.directoryPath = '/' + data.directoryPath; - } + return { + files, + subDirectories: subDirectories, + }; + } - if (!data.repoPath.startsWith('/')) { - data.repoPath = '/' + data.repoPath; - } + public static async getFilesInDirectoryRecursive(data: { + repoPath: string; + directoryPath: string; + acceptedFileExtensions: Array<string>; + ignoreFilesOrDirectories: Array<string>; + }): Promise<Dictionary<CodeRepositoryFile>> { + let files: Dictionary<CodeRepositoryFile> = {}; - const { directoryPath, repoPath } = data; + const { files: filesInDirectory, subDirectories } = + await this.getFilesInDirectory({ + directoryPath: data.directoryPath, + repoPath: data.repoPath, + acceptedFileExtensions: data.acceptedFileExtensions, + ignoreFilesOrDirectories: data.ignoreFilesOrDirectories, + }); - let totalPath: string = `${repoPath}/${directoryPath}`; + files = { + ...files, + ...filesInDirectory, + }; - totalPath = LocalFile.sanitizeFilePath(totalPath); // clean up the path - - const files: Dictionary<CodeRepositoryFile> = {}; - const output: string = await Execute.executeCommand(`ls ${totalPath}`); - - const fileNames: Array<string> = output.split('\n'); - - const subDirectories: Array<string> = []; - - for (const fileName of fileNames) { - if (fileName === '') { - continue; - } - - const filePath: string = LocalFile.sanitizeFilePath( - `${directoryPath}/${fileName}` - ); - - if (data.ignoreFilesOrDirectories.includes(fileName)) { - continue; - } - - const isDirectory: boolean = ( - await Execute.executeCommand( - `file "${LocalFile.sanitizeFilePath( - `${totalPath}/${fileName}` - )}"` - ) - ).includes('directory'); - - if (isDirectory) { - subDirectories.push( - LocalFile.sanitizeFilePath(`${directoryPath}/${fileName}`) - ); - continue; - } else if ( - data.acceptedFileExtensions && - data.acceptedFileExtensions.length > 0 - ) { - let shouldSkip: boolean = true; - - for (const fileExtension of data.acceptedFileExtensions) { - if (fileName.endsWith(fileExtension)) { - shouldSkip = false; - break; - } - } - - if (shouldSkip) { - continue; - } - } - - const gitCommitHash: string = await this.getGitCommitHashForFile({ - filePath, - repoPath, - }); - - const fileExtension: string = fileName.split('.').pop() || ''; - files[filePath] = { - filePath: LocalFile.sanitizeFilePath( - `${directoryPath}/${fileName}` - ), - gitCommitHash, - fileExtension, - fileName, - }; - } - - return { - files, - subDirectories: subDirectories, - }; + for (const subDirectory of subDirectories) { + files = { + ...files, + ...(await this.getFilesInDirectoryRecursive({ + repoPath: data.repoPath, + directoryPath: subDirectory, + acceptedFileExtensions: data.acceptedFileExtensions, + ignoreFilesOrDirectories: data.ignoreFilesOrDirectories, + })), + }; } - public static async getFilesInDirectoryRecursive(data: { - repoPath: string; - directoryPath: string; - acceptedFileExtensions: Array<string>; - ignoreFilesOrDirectories: Array<string>; - }): Promise<Dictionary<CodeRepositoryFile>> { - let files: Dictionary<CodeRepositoryFile> = {}; - - const { files: filesInDirectory, subDirectories } = - await this.getFilesInDirectory({ - directoryPath: data.directoryPath, - repoPath: data.repoPath, - acceptedFileExtensions: data.acceptedFileExtensions, - ignoreFilesOrDirectories: data.ignoreFilesOrDirectories, - }); - - files = { - ...files, - ...filesInDirectory, - }; - - for (const subDirectory of subDirectories) { - files = { - ...files, - ...(await this.getFilesInDirectoryRecursive({ - repoPath: data.repoPath, - directoryPath: subDirectory, - acceptedFileExtensions: data.acceptedFileExtensions, - ignoreFilesOrDirectories: data.ignoreFilesOrDirectories, - })), - }; - } - - return files; - } + return files; + } } diff --git a/CommonServer/Utils/CodeRepository/CodeRepositoryFile.ts b/CommonServer/Utils/CodeRepository/CodeRepositoryFile.ts index 1f70114530..a8d2227488 100644 --- a/CommonServer/Utils/CodeRepository/CodeRepositoryFile.ts +++ b/CommonServer/Utils/CodeRepository/CodeRepositoryFile.ts @@ -1,6 +1,6 @@ export default interface CodeRepositoryFile { - filePath: string; - gitCommitHash: string; - fileExtension: string; - fileName: string; + filePath: string; + gitCommitHash: string; + fileExtension: string; + fileName: string; } diff --git a/CommonServer/Utils/CodeRepository/GitHub/GitHub.ts b/CommonServer/Utils/CodeRepository/GitHub/GitHub.ts index b5aa9900e9..094def8ff4 100644 --- a/CommonServer/Utils/CodeRepository/GitHub/GitHub.ts +++ b/CommonServer/Utils/CodeRepository/GitHub/GitHub.ts @@ -1,238 +1,231 @@ -import Execute from '../../Execute'; -import logger from '../../Logger'; -import HostedCodeRepository from '../HostedCodeRepository/HostedCodeRepository'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import PullRequest from 'Common/Types/CodeRepository/PullRequest'; -import PullRequestState from 'Common/Types/CodeRepository/PullRequestState'; -import OneUptimeDate from 'Common/Types/Date'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import API from 'Common/Utils/API'; +import Execute from "../../Execute"; +import logger from "../../Logger"; +import HostedCodeRepository from "../HostedCodeRepository/HostedCodeRepository"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import PullRequest from "Common/Types/CodeRepository/PullRequest"; +import PullRequestState from "Common/Types/CodeRepository/PullRequestState"; +import OneUptimeDate from "Common/Types/Date"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import API from "Common/Utils/API"; export default class GitHubUtil extends HostedCodeRepository { - private getPullRequestFromJSONObject(data: { - pullRequest: JSONObject; - organizationName: string; - repositoryName: string; - }): PullRequest { - return { - pullRequestId: data.pullRequest['id'] as number, - pullRequestNumber: data.pullRequest['number'] as number, - title: data.pullRequest['title'] as string, - body: data.pullRequest['body'] as string, - url: URL.fromString(data.pullRequest['url'] as string), - state: data.pullRequest['state'] as PullRequestState, - createdAt: OneUptimeDate.fromString( - data.pullRequest['created_at'] as string - ), - updatedAt: OneUptimeDate.fromString( - data.pullRequest['updated_at'] as string - ), - repoOrganizationName: data.organizationName, - repoName: data.repositoryName, - headRefName: - data.pullRequest['head'] && - (data.pullRequest['head'] as JSONObject)['ref'] - ? ((data.pullRequest['head'] as JSONObject)[ - 'ref' - ] as string) - : '', - }; + private getPullRequestFromJSONObject(data: { + pullRequest: JSONObject; + organizationName: string; + repositoryName: string; + }): PullRequest { + return { + pullRequestId: data.pullRequest["id"] as number, + pullRequestNumber: data.pullRequest["number"] as number, + title: data.pullRequest["title"] as string, + body: data.pullRequest["body"] as string, + url: URL.fromString(data.pullRequest["url"] as string), + state: data.pullRequest["state"] as PullRequestState, + createdAt: OneUptimeDate.fromString( + data.pullRequest["created_at"] as string, + ), + updatedAt: OneUptimeDate.fromString( + data.pullRequest["updated_at"] as string, + ), + repoOrganizationName: data.organizationName, + repoName: data.repositoryName, + headRefName: + data.pullRequest["head"] && + (data.pullRequest["head"] as JSONObject)["ref"] + ? ((data.pullRequest["head"] as JSONObject)["ref"] as string) + : "", + }; + } + + public async getPullRequestByNumber(data: { + organizationName: string; + repositoryName: string; + pullRequestNumber: number; + }): Promise<PullRequest> { + const gitHubToken: string = this.authToken; + + const url: URL = URL.fromString( + `https://api.github.com/repos/${data.organizationName}/${data.repositoryName}/pulls/${data.pullRequestNumber}`, + ); + + const result: HTTPErrorResponse | HTTPResponse<JSONObject> = await API.get( + url, + {}, + { + Authorization: `Bearer ${gitHubToken}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + ); + + if (result instanceof HTTPErrorResponse) { + throw result; } - public async getPullRequestByNumber(data: { - organizationName: string; - repositoryName: string; - pullRequestNumber: number; - }): Promise<PullRequest> { - const gitHubToken: string = this.authToken; + return this.getPullRequestFromJSONObject({ + pullRequest: result.data, + organizationName: data.organizationName, + repositoryName: data.repositoryName, + }); + } - const url: URL = URL.fromString( - `https://api.github.com/repos/${data.organizationName}/${data.repositoryName}/pulls/${data.pullRequestNumber}` - ); + private async getPullRequestsByPage(data: { + organizationName: string; + repositoryName: string; + pullRequestState: PullRequestState; + baseBranchName: string; + page: number; + }): Promise<Array<PullRequest>> { + const gitHubToken: string = this.authToken; - const result: HTTPErrorResponse | HTTPResponse<JSONObject> = - await API.get( - url, - {}, - { - Authorization: `Bearer ${gitHubToken}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - } - ); + const url: URL = URL.fromString( + `https://api.github.com/repos/${data.organizationName}/${data.repositoryName}/pulls?base=${data.baseBranchName}&state=${data.pullRequestState}&per_page=100&page=${data.page}`, + ); - if (result instanceof HTTPErrorResponse) { - throw result; - } + const result: HTTPErrorResponse | HTTPResponse<JSONArray> = await API.get( + url, + {}, + { + Authorization: `Bearer ${gitHubToken}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + ); + if (result instanceof HTTPErrorResponse) { + throw result; + } + + const pullRequests: Array<PullRequest> = result.data.map( + (pullRequest: JSONObject) => { return this.getPullRequestFromJSONObject({ - pullRequest: result.data, - organizationName: data.organizationName, - repositoryName: data.repositoryName, + pullRequest: pullRequest, + organizationName: data.organizationName, + repositoryName: data.repositoryName, }); + }, + ); + + return pullRequests; + } + + public override async getPullRequests(data: { + pullRequestState: PullRequestState; + baseBranchName: string; + organizationName: string; + repositoryName: string; + }): Promise<Array<PullRequest>> { + const allPullRequests: Array<PullRequest> = []; + + let page: number = 1; + + let pullRequests: Array<PullRequest> = await this.getPullRequestsByPage({ + organizationName: data.organizationName, + repositoryName: data.repositoryName, + pullRequestState: data.pullRequestState, + baseBranchName: data.baseBranchName, + page: page, + }); + + // Fetch all pull requests by paginating through the results + // 100 pull requests per page is the limit of the GitHub API + while (pullRequests.length === page * 100 || page === 1) { + pullRequests = await this.getPullRequestsByPage({ + organizationName: data.organizationName, + repositoryName: data.repositoryName, + pullRequestState: data.pullRequestState, + baseBranchName: data.baseBranchName, + page: page, + }); + page++; + allPullRequests.push(...pullRequests); } - private async getPullRequestsByPage(data: { - organizationName: string; - repositoryName: string; - pullRequestState: PullRequestState; - baseBranchName: string; - page: number; - }): Promise<Array<PullRequest>> { - const gitHubToken: string = this.authToken; + return allPullRequests; + } - const url: URL = URL.fromString( - `https://api.github.com/repos/${data.organizationName}/${data.repositoryName}/pulls?base=${data.baseBranchName}&state=${data.pullRequestState}&per_page=100&page=${data.page}` - ); + public override async addRemote(data: { + remoteName: string; + organizationName: string; + repositoryName: string; + }): Promise<void> { + const url: URL = URL.fromString( + `https://github.com/${data.organizationName}/${data.repositoryName}.git`, + ); - const result: HTTPErrorResponse | HTTPResponse<JSONArray> = - await API.get( - url, - {}, - { - Authorization: `Bearer ${gitHubToken}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - } - ); + const command: string = `git remote add ${ + data.remoteName + } ${url.toString()}`; - if (result instanceof HTTPErrorResponse) { - throw result; - } + logger.debug("Executing command: " + command); - const pullRequests: Array<PullRequest> = result.data.map( - (pullRequest: JSONObject) => { - return this.getPullRequestFromJSONObject({ - pullRequest: pullRequest, - organizationName: data.organizationName, - repositoryName: data.repositoryName, - }); - } - ); + const result: string = await Execute.executeCommand(command); - return pullRequests; + logger.debug(result); + } + + public override async pushChanges(data: { + branchName: string; + organizationName: string; + repositoryName: string; + repoPath: string; + }): Promise<void> { + const branchName: string = data.branchName; + + const username: string = this.username; + const password: string = this.authToken; + + logger.debug( + "Pushing changes to remote repository with username: " + username, + ); + + const command: string = `cd ${data.repoPath} && git push -u https://${username}:${password}@github.com/${data.organizationName}/${data.repositoryName}.git ${branchName}`; + logger.debug("Executing command: " + command); + + const result: string = await Execute.executeCommand(command); + + logger.debug(result); + } + + public override async createPullRequest(data: { + baseBranchName: string; + headBranchName: string; + organizationName: string; + repositoryName: string; + title: string; + body: string; + }): Promise<PullRequest> { + const gitHubToken: string = this.authToken; + + const url: URL = URL.fromString( + `https://api.github.com/repos/${data.organizationName}/${data.repositoryName}/pulls`, + ); + + const result: HTTPErrorResponse | HTTPResponse<JSONObject> = await API.post( + url, + { + base: data.baseBranchName, + head: data.headBranchName, + title: data.title, + body: data.body, + }, + { + Authorization: `Bearer ${gitHubToken}`, + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + ); + + if (result instanceof HTTPErrorResponse) { + throw result; } - public override async getPullRequests(data: { - pullRequestState: PullRequestState; - baseBranchName: string; - organizationName: string; - repositoryName: string; - }): Promise<Array<PullRequest>> { - const allPullRequests: Array<PullRequest> = []; - - let page: number = 1; - - let pullRequests: Array<PullRequest> = await this.getPullRequestsByPage( - { - organizationName: data.organizationName, - repositoryName: data.repositoryName, - pullRequestState: data.pullRequestState, - baseBranchName: data.baseBranchName, - page: page, - } - ); - - // Fetch all pull requests by paginating through the results - // 100 pull requests per page is the limit of the GitHub API - while (pullRequests.length === page * 100 || page === 1) { - pullRequests = await this.getPullRequestsByPage({ - organizationName: data.organizationName, - repositoryName: data.repositoryName, - pullRequestState: data.pullRequestState, - baseBranchName: data.baseBranchName, - page: page, - }); - page++; - allPullRequests.push(...pullRequests); - } - - return allPullRequests; - } - - public override async addRemote(data: { - remoteName: string; - organizationName: string; - repositoryName: string; - }): Promise<void> { - const url: URL = URL.fromString( - `https://github.com/${data.organizationName}/${data.repositoryName}.git` - ); - - const command: string = `git remote add ${ - data.remoteName - } ${url.toString()}`; - - logger.debug('Executing command: ' + command); - - const result: string = await Execute.executeCommand(command); - - logger.debug(result); - } - - public override async pushChanges(data: { - branchName: string; - organizationName: string; - repositoryName: string; - repoPath: string; - }): Promise<void> { - const branchName: string = data.branchName; - - const username: string = this.username; - const password: string = this.authToken; - - logger.debug( - 'Pushing changes to remote repository with username: ' + username - ); - - const command: string = `cd ${data.repoPath} && git push -u https://${username}:${password}@github.com/${data.organizationName}/${data.repositoryName}.git ${branchName}`; - logger.debug('Executing command: ' + command); - - const result: string = await Execute.executeCommand(command); - - logger.debug(result); - } - - public override async createPullRequest(data: { - baseBranchName: string; - headBranchName: string; - organizationName: string; - repositoryName: string; - title: string; - body: string; - }): Promise<PullRequest> { - const gitHubToken: string = this.authToken; - - const url: URL = URL.fromString( - `https://api.github.com/repos/${data.organizationName}/${data.repositoryName}/pulls` - ); - - const result: HTTPErrorResponse | HTTPResponse<JSONObject> = - await API.post( - url, - { - base: data.baseBranchName, - head: data.headBranchName, - title: data.title, - body: data.body, - }, - { - Authorization: `Bearer ${gitHubToken}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - } - ); - - if (result instanceof HTTPErrorResponse) { - throw result; - } - - return this.getPullRequestFromJSONObject({ - pullRequest: result.data, - organizationName: data.organizationName, - repositoryName: data.repositoryName, - }); - } + return this.getPullRequestFromJSONObject({ + pullRequest: result.data, + organizationName: data.organizationName, + repositoryName: data.repositoryName, + }); + } } diff --git a/CommonServer/Utils/CodeRepository/HostedCodeRepository/HostedCodeRepository.ts b/CommonServer/Utils/CodeRepository/HostedCodeRepository/HostedCodeRepository.ts index 8066fc00a7..6dd800e14b 100644 --- a/CommonServer/Utils/CodeRepository/HostedCodeRepository/HostedCodeRepository.ts +++ b/CommonServer/Utils/CodeRepository/HostedCodeRepository/HostedCodeRepository.ts @@ -1,115 +1,115 @@ -import PullRequest from 'Common/Types/CodeRepository/PullRequest'; -import PullRequestState from 'Common/Types/CodeRepository/PullRequestState'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import NotImplementedException from 'Common/Types/Exception/NotImplementedException'; -import ServiceRepository from 'Model/Models/ServiceRepository'; +import PullRequest from "Common/Types/CodeRepository/PullRequest"; +import PullRequestState from "Common/Types/CodeRepository/PullRequestState"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import NotImplementedException from "Common/Types/Exception/NotImplementedException"; +import ServiceRepository from "Model/Models/ServiceRepository"; export default class HostedCodeRepository { - public constructor(data: { authToken: string; username: string }) { - if (!data.authToken) { - throw new BadDataException('authToken is required'); - } - - if (!data.username) { - throw new BadDataException('username is required'); - } - - this.username = data.username; - - this.authToken = data.authToken; + public constructor(data: { authToken: string; username: string }) { + if (!data.authToken) { + throw new BadDataException("authToken is required"); } - public authToken: string = ''; - public username: string = ''; - - public async getNumberOfPullRequestsExistForService(data: { - serviceRepository: ServiceRepository; - pullRequestState: PullRequestState; - baseBranchName: string | undefined; - organizationName: string; - repositoryName: string; - }): Promise<number> { - return ( - await this.getPullRequestsByService({ - serviceRepository: data.serviceRepository, - pullRequestState: data.pullRequestState, - baseBranchName: data.baseBranchName, - organizationName: data.organizationName, - repositoryName: data.repositoryName, - }) - ).length; + if (!data.username) { + throw new BadDataException("username is required"); } - public async getPullRequestsByService(data: { - serviceRepository: ServiceRepository; - pullRequestState: PullRequestState; - baseBranchName?: string | undefined; - organizationName: string; - repositoryName: string; - }): Promise<Array<PullRequest>> { - if (!data.serviceRepository) { - throw new BadDataException('serviceRepository is required'); - } + this.username = data.username; - if (!data.serviceRepository.serviceCatalog) { - throw new BadDataException( - 'serviceRepository.serviceCatalog is required' - ); - } + this.authToken = data.authToken; + } - if (!data.serviceRepository.serviceCatalog.name) { - throw new BadDataException( - 'serviceRepository.serviceCatalog.name is required' - ); - } + public authToken: string = ""; + public username: string = ""; - const pullRequests: Array<PullRequest> = await this.getPullRequests({ - pullRequestState: data.pullRequestState, - baseBranchName: data.baseBranchName, - organizationName: data.organizationName, - repositoryName: data.repositoryName, - }); + public async getNumberOfPullRequestsExistForService(data: { + serviceRepository: ServiceRepository; + pullRequestState: PullRequestState; + baseBranchName: string | undefined; + organizationName: string; + repositoryName: string; + }): Promise<number> { + return ( + await this.getPullRequestsByService({ + serviceRepository: data.serviceRepository, + pullRequestState: data.pullRequestState, + baseBranchName: data.baseBranchName, + organizationName: data.organizationName, + repositoryName: data.repositoryName, + }) + ).length; + } - return pullRequests.filter((pullRequest: PullRequest) => { - return pullRequest.headRefName.includes( - `oneuptime-${data.serviceRepository.serviceCatalog?.name?.toLowerCase()}` - ); - }); + public async getPullRequestsByService(data: { + serviceRepository: ServiceRepository; + pullRequestState: PullRequestState; + baseBranchName?: string | undefined; + organizationName: string; + repositoryName: string; + }): Promise<Array<PullRequest>> { + if (!data.serviceRepository) { + throw new BadDataException("serviceRepository is required"); } - public async getPullRequests(_data: { - pullRequestState: PullRequestState; - baseBranchName?: string | undefined; - organizationName: string; - repositoryName: string; - }): Promise<Array<PullRequest>> { - throw new NotImplementedException(); + if (!data.serviceRepository.serviceCatalog) { + throw new BadDataException( + "serviceRepository.serviceCatalog is required", + ); } - public async createPullRequest(_data: { - baseBranchName: string; - headBranchName: string; - organizationName: string; - repositoryName: string; - title: string; - body: string; - }): Promise<PullRequest> { - throw new NotImplementedException(); + if (!data.serviceRepository.serviceCatalog.name) { + throw new BadDataException( + "serviceRepository.serviceCatalog.name is required", + ); } - public async pushChanges(_data: { - branchName: string; - organizationName: string; - repositoryName: string; - }): Promise<void> { - throw new NotImplementedException(); - } + const pullRequests: Array<PullRequest> = await this.getPullRequests({ + pullRequestState: data.pullRequestState, + baseBranchName: data.baseBranchName, + organizationName: data.organizationName, + repositoryName: data.repositoryName, + }); - public async addRemote(_data: { - remoteName: string; - organizationName: string; - repositoryName: string; - }): Promise<void> { - throw new NotImplementedException(); - } + return pullRequests.filter((pullRequest: PullRequest) => { + return pullRequest.headRefName.includes( + `oneuptime-${data.serviceRepository.serviceCatalog?.name?.toLowerCase()}`, + ); + }); + } + + public async getPullRequests(_data: { + pullRequestState: PullRequestState; + baseBranchName?: string | undefined; + organizationName: string; + repositoryName: string; + }): Promise<Array<PullRequest>> { + throw new NotImplementedException(); + } + + public async createPullRequest(_data: { + baseBranchName: string; + headBranchName: string; + organizationName: string; + repositoryName: string; + title: string; + body: string; + }): Promise<PullRequest> { + throw new NotImplementedException(); + } + + public async pushChanges(_data: { + branchName: string; + organizationName: string; + repositoryName: string; + }): Promise<void> { + throw new NotImplementedException(); + } + + public async addRemote(_data: { + remoteName: string; + organizationName: string; + repositoryName: string; + }): Promise<void> { + throw new NotImplementedException(); + } } diff --git a/CommonServer/Utils/Cookie.ts b/CommonServer/Utils/Cookie.ts index c2ebb57149..86f4ad5817 100644 --- a/CommonServer/Utils/Cookie.ts +++ b/CommonServer/Utils/Cookie.ts @@ -1,64 +1,64 @@ -import { ExpressRequest, ExpressResponse } from './Express'; -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; -import { CookieOptions } from 'express'; +import { ExpressRequest, ExpressResponse } from "./Express"; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; +import { CookieOptions } from "express"; export default class CookieUtil { - // set cookie with express response + // set cookie with express response - public static setCookie( - res: ExpressResponse, - name: string, - value: string, - options: CookieOptions - ): void { - res.cookie(name, value, options); + public static setCookie( + res: ExpressResponse, + name: string, + value: string, + options: CookieOptions, + ): void { + res.cookie(name, value, options); + } + + // get cookie with express request + + public static getCookie( + req: ExpressRequest, + name: string, + ): string | undefined { + return req.cookies[name]; + } + + // delete cookie with express response + + public static removeCookie(res: ExpressResponse, name: string): void { + res.clearCookie(name); + } + + // get all cookies with express request + public static getAllCookies(req: ExpressRequest): Dictionary<string> { + return req.cookies || {}; + } + + public static getUserTokenKey(id?: ObjectID): string { + if (!id) { + return `user-token`; } - // get cookie with express request + return `user-token-${id.toString()}`; + } - public static getCookie( - req: ExpressRequest, - name: string - ): string | undefined { - return req.cookies[name]; - } - - // delete cookie with express response - - public static removeCookie(res: ExpressResponse, name: string): void { - res.clearCookie(name); - } - - // get all cookies with express request - public static getAllCookies(req: ExpressRequest): Dictionary<string> { - return req.cookies || {}; - } - - public static getUserTokenKey(id?: ObjectID): string { - if (!id) { - return `user-token`; - } - - return `user-token-${id.toString()}`; - } - - public static getUserSSOKey(id: ObjectID): string { - return `${this.getSSOKey()}${id.toString()}`; - } - - public static getSSOKey(): string { - return `sso-`; - } - - // delete all cookies. - public static removeAllCookies( - req: ExpressRequest, - res: ExpressResponse - ): void { - const cookies: Dictionary<string> = this.getAllCookies(req); - for (const key in cookies) { - this.removeCookie(res, key); - } + public static getUserSSOKey(id: ObjectID): string { + return `${this.getSSOKey()}${id.toString()}`; + } + + public static getSSOKey(): string { + return `sso-`; + } + + // delete all cookies. + public static removeAllCookies( + req: ExpressRequest, + res: ExpressResponse, + ): void { + const cookies: Dictionary<string> = this.getAllCookies(req); + for (const key in cookies) { + this.removeCookie(res, key); } + } } diff --git a/CommonServer/Utils/CronTab.ts b/CommonServer/Utils/CronTab.ts index 1069126387..c5b2041fd2 100644 --- a/CommonServer/Utils/CronTab.ts +++ b/CommonServer/Utils/CronTab.ts @@ -1,15 +1,14 @@ -import BadDataException from 'Common/Types/Exception/BadDataException'; -import CronParser, { CronExpression } from 'cron-parser'; +import BadDataException from "Common/Types/Exception/BadDataException"; +import CronParser, { CronExpression } from "cron-parser"; export default class CronTab { - public static getNextExecutionTime(crontab: string): Date { - try { - const interval: CronExpression = - CronParser.parseExpression(crontab); - const nextExecutionTime: Date = interval.next().toDate(); - return nextExecutionTime; - } catch (error) { - throw new BadDataException(`Invalid cron expression: ${crontab}`); - } + public static getNextExecutionTime(crontab: string): Date { + try { + const interval: CronExpression = CronParser.parseExpression(crontab); + const nextExecutionTime: Date = interval.next().toDate(); + return nextExecutionTime; + } catch (error) { + throw new BadDataException(`Invalid cron expression: ${crontab}`); } + } } diff --git a/CommonServer/Utils/Encryption.ts b/CommonServer/Utils/Encryption.ts index 718aa3294c..e3df1920fc 100644 --- a/CommonServer/Utils/Encryption.ts +++ b/CommonServer/Utils/Encryption.ts @@ -1,35 +1,32 @@ -import { EncryptionSecret } from '../EnvironmentConfig'; -import CryptoJS from 'crypto-js'; +import { EncryptionSecret } from "../EnvironmentConfig"; +import CryptoJS from "crypto-js"; export default class Encryption { - public static async encrypt(text: string): Promise<string> { - if (!text) { - return ''; - } - - const secret: string = await this.getEncryptionSecret(); - const encryptedText: string = CryptoJS.AES.encrypt( - text, - secret - ).toString(); - return encryptedText; + public static async encrypt(text: string): Promise<string> { + if (!text) { + return ""; } - public static async decrypt(encryptedText: string): Promise<string> { - if (!encryptedText) { - return ''; - } + const secret: string = await this.getEncryptionSecret(); + const encryptedText: string = CryptoJS.AES.encrypt(text, secret).toString(); + return encryptedText; + } - const secret: string = await this.getEncryptionSecret(); - const decryptedText: string = CryptoJS.AES.decrypt( - encryptedText, - secret - ).toString(CryptoJS.enc.Utf8); - return decryptedText; + public static async decrypt(encryptedText: string): Promise<string> { + if (!encryptedText) { + return ""; } - private static async getEncryptionSecret(): Promise<string> { - const encryptionKey: string = EncryptionSecret.toString(); - return CryptoJS.SHA256(encryptionKey).toString(); - } + const secret: string = await this.getEncryptionSecret(); + const decryptedText: string = CryptoJS.AES.decrypt( + encryptedText, + secret, + ).toString(CryptoJS.enc.Utf8); + return decryptedText; + } + + private static async getEncryptionSecret(): Promise<string> { + const encryptionKey: string = EncryptionSecret.toString(); + return CryptoJS.SHA256(encryptionKey).toString(); + } } diff --git a/CommonServer/Utils/Environment.ts b/CommonServer/Utils/Environment.ts index c0c4c0de2b..088ae910dc 100644 --- a/CommonServer/Utils/Environment.ts +++ b/CommonServer/Utils/Environment.ts @@ -1,20 +1,20 @@ -import AppEnvironment from 'Common/Types/AppEnvironment'; -import dotenv from 'dotenv'; +import AppEnvironment from "Common/Types/AppEnvironment"; +import dotenv from "dotenv"; -const Env: string = process.env['NODE_ENV'] || AppEnvironment.Development; +const Env: string = process.env["NODE_ENV"] || AppEnvironment.Development; if (Env === AppEnvironment.Development || Env === AppEnvironment.Test) { - /* - * Load env vars from /.env, do this only on development. - * Production values are supplied by Kubernetes Helm charts or docker compose files. - */ - dotenv.config({ - path: '../Common/.env', - }); - dotenv.config({ - path: '../CommonServer/.env', - }); - dotenv.config({ - path: './.env', - }); + /* + * Load env vars from /.env, do this only on development. + * Production values are supplied by Kubernetes Helm charts or docker compose files. + */ + dotenv.config({ + path: "../Common/.env", + }); + dotenv.config({ + path: "../CommonServer/.env", + }); + dotenv.config({ + path: "./.env", + }); } diff --git a/CommonServer/Utils/Errors.ts b/CommonServer/Utils/Errors.ts index 59cafc1a12..05caa6108b 100644 --- a/CommonServer/Utils/Errors.ts +++ b/CommonServer/Utils/Errors.ts @@ -1,25 +1,24 @@ export default { - BillingService: { - BILLING_NOT_ENABLED: 'Billing is not enabled for this server.', - CLIENT_SECRET_MISSING: - 'client_secret not returned by payment provider.', - INVOICE_NOT_GENERATED: 'Invoice not generated.', - MIN_REQUIRED_PAYMENT_METHOD_NOT_MET: - "There's only one payment method associated with this account. It cannot be deleted. To delete this payment method please add more payment methods to your account.", - NO_PAYMENTS_METHODS: - 'Payment Method not added. Please go to Project Settings > Billing and add a payment method.', - PROMO_CODE_NOT_FOUND: 'Promo code not found', - PROMO_CODE_INVALID: 'Invalid promo code', - SUBSCRIPTION_ITEM_NOT_FOUND: 'Subscription item not found.', - SUBSCRIPTION_NOT_FOUND: 'Subscription not found.', - }, - TeamMemberService: { - ALREADY_INVITED: 'This user has already been invited to this team', - ONE_MEMBER_REQUIRED: - 'This team should have at least 1 member who has accepted the invitation.', - LIMIT_REACHED: - 'You have reached the user limit. You cannot invite any more users to this project. Please contact billing@oneuptime.com to increase your user limit.', - LIMIT_REACHED_FOR_FREE_PLAN: - 'You have reached the user limit for the free plan. Please upgrade your plan to Growth to invite more users.', - }, + BillingService: { + BILLING_NOT_ENABLED: "Billing is not enabled for this server.", + CLIENT_SECRET_MISSING: "client_secret not returned by payment provider.", + INVOICE_NOT_GENERATED: "Invoice not generated.", + MIN_REQUIRED_PAYMENT_METHOD_NOT_MET: + "There's only one payment method associated with this account. It cannot be deleted. To delete this payment method please add more payment methods to your account.", + NO_PAYMENTS_METHODS: + "Payment Method not added. Please go to Project Settings > Billing and add a payment method.", + PROMO_CODE_NOT_FOUND: "Promo code not found", + PROMO_CODE_INVALID: "Invalid promo code", + SUBSCRIPTION_ITEM_NOT_FOUND: "Subscription item not found.", + SUBSCRIPTION_NOT_FOUND: "Subscription not found.", + }, + TeamMemberService: { + ALREADY_INVITED: "This user has already been invited to this team", + ONE_MEMBER_REQUIRED: + "This team should have at least 1 member who has accepted the invitation.", + LIMIT_REACHED: + "You have reached the user limit. You cannot invite any more users to this project. Please contact billing@oneuptime.com to increase your user limit.", + LIMIT_REACHED_FOR_FREE_PLAN: + "You have reached the user limit for the free plan. Please upgrade your plan to Growth to invite more users.", + }, }; diff --git a/CommonServer/Utils/Execute.ts b/CommonServer/Utils/Execute.ts index dd5f5a8e1d..e7edcb9c04 100644 --- a/CommonServer/Utils/Execute.ts +++ b/CommonServer/Utils/Execute.ts @@ -1,24 +1,21 @@ -import { PromiseRejectErrorFunction } from 'Common/Types/FunctionTypes'; -import { ExecException, exec } from 'node:child_process'; +import { PromiseRejectErrorFunction } from "Common/Types/FunctionTypes"; +import { ExecException, exec } from "node:child_process"; export default class Execute { - public static executeCommand(command: string): Promise<string> { - return new Promise( - ( - resolve: (output: string) => void, - reject: PromiseRejectErrorFunction - ) => { - exec( - `${command}`, - (err: ExecException | null, stdout: string) => { - if (err) { - return reject(err); - } + public static executeCommand(command: string): Promise<string> { + return new Promise( + ( + resolve: (output: string) => void, + reject: PromiseRejectErrorFunction, + ) => { + exec(`${command}`, (err: ExecException | null, stdout: string) => { + if (err) { + return reject(err); + } - return resolve(stdout); - } - ); - } - ); - } + return resolve(stdout); + }); + }, + ); + } } diff --git a/CommonServer/Utils/Express.ts b/CommonServer/Utils/Express.ts index 72ec0954d2..18db0f97b5 100644 --- a/CommonServer/Utils/Express.ts +++ b/CommonServer/Utils/Express.ts @@ -1,18 +1,18 @@ -import logger from './Logger'; -import Dictionary from 'Common/Types/Dictionary'; -import GenericFunction from 'Common/Types/GenericFunction'; -import { JSONObject, JSONObjectOrArray } from 'Common/Types/JSON'; -import JSONWebTokenData from 'Common/Types/JsonWebTokenData'; -import ObjectID from 'Common/Types/ObjectID'; +import logger from "./Logger"; +import Dictionary from "Common/Types/Dictionary"; +import GenericFunction from "Common/Types/GenericFunction"; +import { JSONObject, JSONObjectOrArray } from "Common/Types/JSON"; +import JSONWebTokenData from "Common/Types/JsonWebTokenData"; +import ObjectID from "Common/Types/ObjectID"; import { - UserGlobalAccessPermission, - UserTenantAccessPermission, -} from 'Common/Types/Permission'; -import Port from 'Common/Types/Port'; -import UserType from 'Common/Types/UserType'; -import 'ejs'; -import express from 'express'; -import { Server, createServer } from 'http'; + UserGlobalAccessPermission, + UserTenantAccessPermission, +} from "Common/Types/Permission"; +import Port from "Common/Types/Port"; +import UserType from "Common/Types/UserType"; +import "ejs"; +import express from "express"; +import { Server, createServer } from "http"; export type RequestHandler = express.RequestHandler; export type NextFunction = express.NextFunction; @@ -22,7 +22,7 @@ export const ExpressJson: GenericFunction = express.json; export const ExpressUrlEncoded: GenericFunction = express.urlencoded; export type ProbeRequest = { - id: ObjectID; + id: ObjectID; }; export type ExpressRequest = express.Request; @@ -31,68 +31,66 @@ export type ExpressApplication = express.Application; export type ExpressRouter = express.Router; export interface OneUptimeRequest extends express.Request { - bearerTokenData?: JSONObject | string | undefined; // if bearer token is passed then this is populated. - probe?: ProbeRequest; - userType?: UserType; - userAuthorization?: JSONWebTokenData; - tenantId?: ObjectID; - userGlobalAccessPermission?: UserGlobalAccessPermission; - userTenantAccessPermission?: Dictionary<UserTenantAccessPermission>; // tenantId <-> UserTenantAccessPermission + bearerTokenData?: JSONObject | string | undefined; // if bearer token is passed then this is populated. + probe?: ProbeRequest; + userType?: UserType; + userAuthorization?: JSONWebTokenData; + tenantId?: ObjectID; + userGlobalAccessPermission?: UserGlobalAccessPermission; + userTenantAccessPermission?: Dictionary<UserTenantAccessPermission>; // tenantId <-> UserTenantAccessPermission } export interface OneUptimeResponse extends express.Response { - logBody: JSONObjectOrArray; + logBody: JSONObjectOrArray; } class Express { - private static app: express.Application; - private static httpServer: Server; + private static app: express.Application; + private static httpServer: Server; - public static getRouter(): express.Router { - return express.Router(); + public static getRouter(): express.Router { + return express.Router(); + } + + public static setupExpress(): void { + this.app = express(); + } + + public static getHttpServer(): Server { + return this.httpServer; + } + + public static getExpressApp(): express.Application { + if (!this.app) { + this.setupExpress(); } - public static setupExpress(): void { - this.app = express(); + return this.app; + } + + public static async launchApplication( + appName: string, + port?: Port, + ): Promise<express.Application> { + if (!this.app) { + this.setupExpress(); } - public static getHttpServer(): Server { - return this.httpServer; + if (!this.httpServer) { + this.httpServer = createServer(this.app); } - public static getExpressApp(): express.Application { - if (!this.app) { - this.setupExpress(); - } + type ResolveFunction = (app: express.Application) => void; - return this.app; - } - - public static async launchApplication( - appName: string, - port?: Port - ): Promise<express.Application> { - if (!this.app) { - this.setupExpress(); - } - - if (!this.httpServer) { - this.httpServer = createServer(this.app); - } - - type ResolveFunction = (app: express.Application) => void; - - return new Promise<express.Application>((resolve: ResolveFunction) => { - this.httpServer.listen( - port?.toNumber() || this.app.get('port'), - () => { - // eslint-disable-next-line + return new Promise<express.Application>((resolve: ResolveFunction) => { + this.httpServer.listen(port?.toNumber() || this.app.get("port"), () => { + // eslint-disable-next-line logger.debug(`${appName} server started on port: ${port?.toNumber() || this.app.get('port')}`); - return resolve(this.app); - } - ); - }); - } + ); + return resolve(this.app); + }); + }); + } } export default Express; diff --git a/CommonServer/Utils/Greenlock/Greenlock.ts b/CommonServer/Utils/Greenlock/Greenlock.ts index 730399b14c..109650aa0f 100644 --- a/CommonServer/Utils/Greenlock/Greenlock.ts +++ b/CommonServer/Utils/Greenlock/Greenlock.ts @@ -1,262 +1,257 @@ import { - LetsEncryptAccountKey, - LetsEncryptNotificationEmail, -} from '../../EnvironmentConfig'; -import AcmeCertificateService from '../../Services/AcmeCertificateService'; -import AcmeChallengeService from '../../Services/AcmeChallengeService'; -import QueryHelper from '../../Types/Database/QueryHelper'; -import logger from '../Logger'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Exception from 'Common/Types/Exception/Exception'; -import ServerException from 'Common/Types/Exception/ServerException'; -import Text from 'Common/Types/Text'; -import AcmeCertificate from 'Model/Models/AcmeCertificate'; -import AcmeChallenge from 'Model/Models/AcmeChallenge'; -import acme from 'acme-client'; -import { Challenge } from 'acme-client/types/rfc8555'; + LetsEncryptAccountKey, + LetsEncryptNotificationEmail, +} from "../../EnvironmentConfig"; +import AcmeCertificateService from "../../Services/AcmeCertificateService"; +import AcmeChallengeService from "../../Services/AcmeChallengeService"; +import QueryHelper from "../../Types/Database/QueryHelper"; +import logger from "../Logger"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Exception from "Common/Types/Exception/Exception"; +import ServerException from "Common/Types/Exception/ServerException"; +import Text from "Common/Types/Text"; +import AcmeCertificate from "Model/Models/AcmeCertificate"; +import AcmeChallenge from "Model/Models/AcmeChallenge"; +import acme from "acme-client"; +import { Challenge } from "acme-client/types/rfc8555"; export default class GreenlockUtil { - public static async renewAllCertsWhichAreExpiringSoon(data: { - validateCname: (domain: string) => Promise<boolean>; - notifyDomainRemoved: (domain: string) => Promise<void>; - }): Promise<void> { - logger.debug('Renewing all certificates'); + public static async renewAllCertsWhichAreExpiringSoon(data: { + validateCname: (domain: string) => Promise<boolean>; + notifyDomainRemoved: (domain: string) => Promise<void>; + }): Promise<void> { + logger.debug("Renewing all certificates"); - // get all certificates which are expiring soon + // get all certificates which are expiring soon - const certificates: AcmeCertificate[] = - await AcmeCertificateService.findBy({ - query: { - expiresAt: QueryHelper.lessThanEqualTo( - OneUptimeDate.addRemoveDays( - OneUptimeDate.getCurrentDate(), - 40 // 40 days before expiry - ) - ), - }, - limit: LIMIT_MAX, - skip: 0, - select: { - domain: true, - }, - sort: { - expiresAt: SortOrder.Ascending, - }, - props: { - isRoot: true, - }, - }); + const certificates: AcmeCertificate[] = await AcmeCertificateService.findBy( + { + query: { + expiresAt: QueryHelper.lessThanEqualTo( + OneUptimeDate.addRemoveDays( + OneUptimeDate.getCurrentDate(), + 40, // 40 days before expiry + ), + ), + }, + limit: LIMIT_MAX, + skip: 0, + select: { + domain: true, + }, + sort: { + expiresAt: SortOrder.Ascending, + }, + props: { + isRoot: true, + }, + }, + ); - // order certificate for each domain + // order certificate for each domain - for (const certificate of certificates) { - if (!certificate.domain) { - continue; - } + for (const certificate of certificates) { + if (!certificate.domain) { + continue; + } - try { - //validate cname - const isValidCname: boolean = await data.validateCname( - certificate.domain - ); + try { + //validate cname + const isValidCname: boolean = await data.validateCname( + certificate.domain, + ); - if (!isValidCname) { - await GreenlockUtil.orderCert({ - domain: certificate.domain, - validateCname: data.validateCname, - }); - } else { - await GreenlockUtil.removeDomain(certificate.domain); - await data.notifyDomainRemoved(certificate.domain); - } - } catch (e) { - logger.error( - `Error renewing certificate for domain: ${certificate.domain}` - ); - logger.error(e); - } + if (!isValidCname) { + await GreenlockUtil.orderCert({ + domain: certificate.domain, + validateCname: data.validateCname, + }); + } else { + await GreenlockUtil.removeDomain(certificate.domain); + await data.notifyDomainRemoved(certificate.domain); } + } catch (e) { + logger.error( + `Error renewing certificate for domain: ${certificate.domain}`, + ); + logger.error(e); + } } + } - public static async removeDomain(domain: string): Promise<void> { - // remove certificate for this domain. - await AcmeCertificateService.deleteBy({ - query: { - domain: domain, - }, - limit: 1, - skip: 0, - props: { + public static async removeDomain(domain: string): Promise<void> { + // remove certificate for this domain. + await AcmeCertificateService.deleteBy({ + query: { + domain: domain, + }, + limit: 1, + skip: 0, + props: { + isRoot: true, + }, + }); + } + + public static async orderCert(data: { + domain: string; + validateCname: (domain: string) => Promise<boolean>; + }): Promise<void> { + try { + let { domain } = data; + + domain = domain.trim().toLowerCase(); + + const acmeAccountKeyInBase64: string = LetsEncryptAccountKey; + + if (!acmeAccountKeyInBase64) { + throw new ServerException( + "No lets encrypt account key found in environment variables. Please add one.", + ); + } + + let acmeAccountKey: string = Buffer.from( + acmeAccountKeyInBase64, + "base64", + ).toString(); + + acmeAccountKey = Text.replaceAll(acmeAccountKey, "\\n", "\n"); + + //validate cname + + const isValidCname: boolean = await data.validateCname(domain); + + if (!isValidCname) { + await GreenlockUtil.removeDomain(domain); + logger.error(`Cname is not valid for domain: ${domain}`); + throw new BadDataException("Cname is not valid for domain " + domain); + } + + const client: acme.Client = new acme.Client({ + directoryUrl: acme.directory.letsencrypt.production, + accountKey: acmeAccountKey, + }); + + const [certificateKey, certificateRequest] = await acme.crypto.createCsr({ + commonName: domain, + }); + + const certificate: string = await client.auto({ + csr: certificateRequest, + email: LetsEncryptNotificationEmail.toString(), + termsOfServiceAgreed: true, + challengePriority: ["http-01"], // only http-01 challenge is supported by oneuptime + challengeCreateFn: async ( + authz: acme.Authorization, + challenge: Challenge, + keyAuthorization: string, + ) => { + // Satisfy challenge here + /* http-01 */ + if (challenge.type === "http-01") { + const acmeChallenge: AcmeChallenge = new AcmeChallenge(); + acmeChallenge.challenge = keyAuthorization; + acmeChallenge.token = challenge.token; + acmeChallenge.domain = authz.identifier.value; + + await AcmeChallengeService.create({ + data: acmeChallenge, + props: { isRoot: true, - }, + }, + }); + } + }, + challengeRemoveFn: async ( + authz: acme.Authorization, + challenge: Challenge, + ) => { + // Clean up challenge here + + if (challenge.type === "http-01") { + await AcmeChallengeService.deleteBy({ + query: { + domain: authz.identifier.value, + }, + limit: 1, + skip: 0, + props: { + isRoot: true, + }, + }); + } + }, + }); + + // get expires at date from certificate + const cert: acme.CertificateInfo = + acme.crypto.readCertificateInfo(certificate); + const issuedAt: Date = cert.notBefore; + const expiresAt: Date = cert.notAfter; + + // check if the certificate is already in the database. + const existingCertificate: AcmeCertificate | null = + await AcmeCertificateService.findOneBy({ + query: { + domain: domain, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, }); + + if (existingCertificate) { + // update the certificate + await AcmeCertificateService.updateBy({ + query: { + domain: domain, + }, + limit: 1, + skip: 0, + data: { + certificate: certificate.toString(), + certificateKey: certificateKey.toString(), + issuedAt: issuedAt, + expiresAt: expiresAt, + }, + props: { + isRoot: true, + }, + }); + } else { + // create the certificate + const acmeCertificate: AcmeCertificate = new AcmeCertificate(); + + acmeCertificate.domain = domain; + acmeCertificate.certificate = certificate.toString(); + acmeCertificate.certificateKey = certificateKey.toString(); + acmeCertificate.issuedAt = issuedAt; + acmeCertificate.expiresAt = expiresAt; + + await AcmeCertificateService.create({ + data: acmeCertificate, + props: { + isRoot: true, + }, + }); + } + } catch (e) { + logger.error(`Error ordering certificate for domain: ${data.domain}`); + logger.error(e); + + if (e instanceof Exception) { + throw e; + } + + throw new ServerException( + `Unable to order certificate for ${data.domain}. Please contact support at support@oneuptime.com for more information.`, + ); } - - public static async orderCert(data: { - domain: string; - validateCname: (domain: string) => Promise<boolean>; - }): Promise<void> { - try { - let { domain } = data; - - domain = domain.trim().toLowerCase(); - - const acmeAccountKeyInBase64: string = LetsEncryptAccountKey; - - if (!acmeAccountKeyInBase64) { - throw new ServerException( - 'No lets encrypt account key found in environment variables. Please add one.' - ); - } - - let acmeAccountKey: string = Buffer.from( - acmeAccountKeyInBase64, - 'base64' - ).toString(); - - acmeAccountKey = Text.replaceAll(acmeAccountKey, '\\n', '\n'); - - //validate cname - - const isValidCname: boolean = await data.validateCname(domain); - - if (!isValidCname) { - await GreenlockUtil.removeDomain(domain); - logger.error(`Cname is not valid for domain: ${domain}`); - throw new BadDataException( - 'Cname is not valid for domain ' + domain - ); - } - - const client: acme.Client = new acme.Client({ - directoryUrl: acme.directory.letsencrypt.production, - accountKey: acmeAccountKey, - }); - - const [certificateKey, certificateRequest] = - await acme.crypto.createCsr({ - commonName: domain, - }); - - const certificate: string = await client.auto({ - csr: certificateRequest, - email: LetsEncryptNotificationEmail.toString(), - termsOfServiceAgreed: true, - challengePriority: ['http-01'], // only http-01 challenge is supported by oneuptime - challengeCreateFn: async ( - authz: acme.Authorization, - challenge: Challenge, - keyAuthorization: string - ) => { - // Satisfy challenge here - /* http-01 */ - if (challenge.type === 'http-01') { - const acmeChallenge: AcmeChallenge = - new AcmeChallenge(); - acmeChallenge.challenge = keyAuthorization; - acmeChallenge.token = challenge.token; - acmeChallenge.domain = authz.identifier.value; - - await AcmeChallengeService.create({ - data: acmeChallenge, - props: { - isRoot: true, - }, - }); - } - }, - challengeRemoveFn: async ( - authz: acme.Authorization, - challenge: Challenge - ) => { - // Clean up challenge here - - if (challenge.type === 'http-01') { - await AcmeChallengeService.deleteBy({ - query: { - domain: authz.identifier.value, - }, - limit: 1, - skip: 0, - props: { - isRoot: true, - }, - }); - } - }, - }); - - // get expires at date from certificate - const cert: acme.CertificateInfo = - acme.crypto.readCertificateInfo(certificate); - const issuedAt: Date = cert.notBefore; - const expiresAt: Date = cert.notAfter; - - // check if the certificate is already in the database. - const existingCertificate: AcmeCertificate | null = - await AcmeCertificateService.findOneBy({ - query: { - domain: domain, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); - - if (existingCertificate) { - // update the certificate - await AcmeCertificateService.updateBy({ - query: { - domain: domain, - }, - limit: 1, - skip: 0, - data: { - certificate: certificate.toString(), - certificateKey: certificateKey.toString(), - issuedAt: issuedAt, - expiresAt: expiresAt, - }, - props: { - isRoot: true, - }, - }); - } else { - // create the certificate - const acmeCertificate: AcmeCertificate = new AcmeCertificate(); - - acmeCertificate.domain = domain; - acmeCertificate.certificate = certificate.toString(); - acmeCertificate.certificateKey = certificateKey.toString(); - acmeCertificate.issuedAt = issuedAt; - acmeCertificate.expiresAt = expiresAt; - - await AcmeCertificateService.create({ - data: acmeCertificate, - props: { - isRoot: true, - }, - }); - } - } catch (e) { - logger.error( - `Error ordering certificate for domain: ${data.domain}` - ); - logger.error(e); - - if (e instanceof Exception) { - throw e; - } - - throw new ServerException( - `Unable to order certificate for ${data.domain}. Please contact support at support@oneuptime.com for more information.` - ); - } - } + } } diff --git a/CommonServer/Utils/JsonToCsv.ts b/CommonServer/Utils/JsonToCsv.ts index 770e97703a..8852852b60 100644 --- a/CommonServer/Utils/JsonToCsv.ts +++ b/CommonServer/Utils/JsonToCsv.ts @@ -1,16 +1,14 @@ -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import Json2Csv from 'json2csv'; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import Json2Csv from "json2csv"; export default { - ToCsv: (json: JSONArray): string => { - if (json.length === 0) { - throw new Error( - 'Cannot convert to CSV when the object length is 0' - ); - } - const fields: Array<string> = Object.keys(json[0] || {}); - const opts: JSONObject = { fields }; - const parser: Json2Csv.Parser<JSONArray> = new Json2Csv.Parser(opts); - return parser.parse(json); - }, + ToCsv: (json: JSONArray): string => { + if (json.length === 0) { + throw new Error("Cannot convert to CSV when the object length is 0"); + } + const fields: Array<string> = Object.keys(json[0] || {}); + const opts: JSONObject = { fields }; + const parser: Json2Csv.Parser<JSONArray> = new Json2Csv.Parser(opts); + return parser.parse(json); + }, }; diff --git a/CommonServer/Utils/JsonWebToken.ts b/CommonServer/Utils/JsonWebToken.ts index 9a6fef0f80..80fbc2de71 100644 --- a/CommonServer/Utils/JsonWebToken.ts +++ b/CommonServer/Utils/JsonWebToken.ts @@ -1,126 +1,119 @@ -import { EncryptionSecret } from '../EnvironmentConfig'; -import Email from 'Common/Types/Email'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import JSONWebTokenData from 'Common/Types/JsonWebTokenData'; -import Name from 'Common/Types/Name'; -import ObjectID from 'Common/Types/ObjectID'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import User from 'Model/Models/User'; -import jwt from 'jsonwebtoken'; +import { EncryptionSecret } from "../EnvironmentConfig"; +import Email from "Common/Types/Email"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import JSONWebTokenData from "Common/Types/JsonWebTokenData"; +import Name from "Common/Types/Name"; +import ObjectID from "Common/Types/ObjectID"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import User from "Model/Models/User"; +import jwt from "jsonwebtoken"; class JSONWebToken { - public static signUserLoginToken(data: { - tokenData: { - userId: ObjectID; - email: Email; - name: Name; - isMasterAdmin: boolean; - // If this is OneUptime username and password login. This is true, if this is SSO login. Then, this is false. - isGlobalLogin: boolean; + public static signUserLoginToken(data: { + tokenData: { + userId: ObjectID; + email: Email; + name: Name; + isMasterAdmin: boolean; + // If this is OneUptime username and password login. This is true, if this is SSO login. Then, this is false. + isGlobalLogin: boolean; + }; + expiresInSeconds: number; + }): string { + return JSONWebToken.sign({ + data: data.tokenData, + expiresInSeconds: data.expiresInSeconds, + }); + } + + public static sign(props: { + data: JSONWebTokenData | User | StatusPagePrivateUser | string | JSONObject; + expiresInSeconds: number; + }): string { + const { data, expiresInSeconds } = props; + + let jsonObj: JSONObject; + + if (typeof data === "string") { + jsonObj = { + data: data.toString(), + }; + } else if (data instanceof User) { + jsonObj = { + userId: data.id!.toString(), + email: data.email!.toString(), + name: data.name?.toString() || "", + isMasterAdmin: data.isMasterAdmin!, + }; + } else if (data instanceof StatusPagePrivateUser) { + jsonObj = { + userId: data.id!.toString(), + email: data.email!.toString(), + statusPageId: data.statusPageId?.toString(), + }; + } else { + jsonObj = { + ...data, + userId: data.userId?.toString(), + email: data.email?.toString(), + name: data.name?.toString() || "", + projectId: data.projectId?.toString() || "", + isMasterAdmin: data.isMasterAdmin, + }; + } + + return JSONWebToken.signJsonPayload(jsonObj, expiresInSeconds); + } + + public static signJsonPayload( + payload: JSONObject, + expiresInSeconds: number, + ): string { + return jwt.sign(payload, EncryptionSecret.toString(), { + expiresIn: expiresInSeconds, + }); + } + + public static decodeJsonPayload(token: string): JSONObject { + const decodedToken: string = JSON.stringify( + jwt.verify(token, EncryptionSecret.toString()) as string, + ); + const decoded: JSONObject = JSONFunctions.parseJSONObject(decodedToken); + + return decoded; + } + + public static decode(token: string): JSONWebTokenData { + try { + const decoded: JSONObject = JSONWebToken.decodeJsonPayload(token); + + if (decoded["statusPageId"]) { + return { + userId: new ObjectID(decoded["userId"] as string), + email: new Email(decoded["email"] as string), + statusPageId: new ObjectID(decoded["statusPageId"] as string), + isMasterAdmin: false, + name: new Name("User"), + isGlobalLogin: Boolean(decoded["isGlobalLogin"]), }; - expiresInSeconds: number; - }): string { - return JSONWebToken.sign({ - data: data.tokenData, - expiresInSeconds: data.expiresInSeconds, - }); - } - - public static sign(props: { - data: - | JSONWebTokenData - | User - | StatusPagePrivateUser - | string - | JSONObject; - expiresInSeconds: number; - }): string { - const { data, expiresInSeconds } = props; - - let jsonObj: JSONObject; - - if (typeof data === 'string') { - jsonObj = { - data: data.toString(), - }; - } else if (data instanceof User) { - jsonObj = { - userId: data.id!.toString(), - email: data.email!.toString(), - name: data.name?.toString() || '', - isMasterAdmin: data.isMasterAdmin!, - }; - } else if (data instanceof StatusPagePrivateUser) { - jsonObj = { - userId: data.id!.toString(), - email: data.email!.toString(), - statusPageId: data.statusPageId?.toString(), - }; - } else { - jsonObj = { - ...data, - userId: data.userId?.toString(), - email: data.email?.toString(), - name: data.name?.toString() || '', - projectId: data.projectId?.toString() || '', - isMasterAdmin: data.isMasterAdmin, - }; - } - - return JSONWebToken.signJsonPayload(jsonObj, expiresInSeconds); - } - - public static signJsonPayload( - payload: JSONObject, - expiresInSeconds: number - ): string { - return jwt.sign(payload, EncryptionSecret.toString(), { - expiresIn: expiresInSeconds, - }); - } - - public static decodeJsonPayload(token: string): JSONObject { - const decodedToken: string = JSON.stringify( - jwt.verify(token, EncryptionSecret.toString()) as string - ); - const decoded: JSONObject = JSONFunctions.parseJSONObject(decodedToken); - - return decoded; - } - - public static decode(token: string): JSONWebTokenData { - try { - const decoded: JSONObject = JSONWebToken.decodeJsonPayload(token); - - if (decoded['statusPageId']) { - return { - userId: new ObjectID(decoded['userId'] as string), - email: new Email(decoded['email'] as string), - statusPageId: new ObjectID( - decoded['statusPageId'] as string - ), - isMasterAdmin: false, - name: new Name('User'), - isGlobalLogin: Boolean(decoded['isGlobalLogin']), - }; - } - - return { - userId: new ObjectID(decoded['userId'] as string), - email: new Email(decoded['email'] as string), - name: new Name(decoded['name'] as string), - projectId: decoded['projectId'] - ? new ObjectID(decoded['projectId'] as string) - : undefined, - isMasterAdmin: Boolean(decoded['isMasterAdmin']), - isGlobalLogin: Boolean(decoded['isGlobalLogin']), - }; - } catch (e) { - throw new BadDataException('AccessToken is invalid or expired'); - } + } + + return { + userId: new ObjectID(decoded["userId"] as string), + email: new Email(decoded["email"] as string), + name: new Name(decoded["name"] as string), + projectId: decoded["projectId"] + ? new ObjectID(decoded["projectId"] as string) + : undefined, + isMasterAdmin: Boolean(decoded["isMasterAdmin"]), + isGlobalLogin: Boolean(decoded["isGlobalLogin"]), + }; + } catch (e) { + throw new BadDataException("AccessToken is invalid or expired"); } + } } export default JSONWebToken; diff --git a/CommonServer/Utils/LocalFile.ts b/CommonServer/Utils/LocalFile.ts index d0767e9477..e777922d7b 100644 --- a/CommonServer/Utils/LocalFile.ts +++ b/CommonServer/Utils/LocalFile.ts @@ -1,55 +1,52 @@ -import { PromiseRejectErrorFunction } from 'Common/Types/FunctionTypes'; -import fs from 'fs'; +import { PromiseRejectErrorFunction } from "Common/Types/FunctionTypes"; +import fs from "fs"; export default class LocalFile { - public static sanitizeFilePath(filePath: string): string { - // remove double slashes - return filePath.replace(/\/\//g, '/'); - } + public static sanitizeFilePath(filePath: string): string { + // remove double slashes + return filePath.replace(/\/\//g, "/"); + } - public static async makeDirectory(path: string): Promise<void> { - return new Promise( - (resolve: VoidFunction, reject: PromiseRejectErrorFunction) => { - fs.mkdir(path, { recursive: true }, (err: Error | null) => { - if (err) { - return reject(err); - } - resolve(); - }); - } - ); - } + public static async makeDirectory(path: string): Promise<void> { + return new Promise( + (resolve: VoidFunction, reject: PromiseRejectErrorFunction) => { + fs.mkdir(path, { recursive: true }, (err: Error | null) => { + if (err) { + return reject(err); + } + resolve(); + }); + }, + ); + } - public static async write(path: string, data: string): Promise<void> { - return new Promise( - (resolve: VoidFunction, reject: PromiseRejectErrorFunction) => { - fs.writeFile(path, data, (err: Error | null) => { - if (err) { - return reject(err); - } - resolve(); - }); - } - ); - } + public static async write(path: string, data: string): Promise<void> { + return new Promise( + (resolve: VoidFunction, reject: PromiseRejectErrorFunction) => { + fs.writeFile(path, data, (err: Error | null) => { + if (err) { + return reject(err); + } + resolve(); + }); + }, + ); + } - public static async read(path: string): Promise<string> { - return new Promise( - ( - resolve: (data: string) => void, - reject: PromiseRejectErrorFunction - ) => { - fs.readFile( - path, - { encoding: 'utf-8' }, - (err: Error | null, data: string) => { - if (!err) { - return resolve(data); - } - return reject(err); - } - ); + public static async read(path: string): Promise<string> { + return new Promise( + (resolve: (data: string) => void, reject: PromiseRejectErrorFunction) => { + fs.readFile( + path, + { encoding: "utf-8" }, + (err: Error | null, data: string) => { + if (!err) { + return resolve(data); } + return reject(err); + }, ); - } + }, + ); + } } diff --git a/CommonServer/Utils/Logger.ts b/CommonServer/Utils/Logger.ts index bd6f8c69d7..71fe55a8d3 100644 --- a/CommonServer/Utils/Logger.ts +++ b/CommonServer/Utils/Logger.ts @@ -1,104 +1,101 @@ -import { ConfigLogLevel, LogLevel } from '../EnvironmentConfig'; -import OneUptimeTelemetry from './Telemetry'; -import { SeverityNumber } from '@opentelemetry/api-logs'; -import Exception from 'Common/Types/Exception/Exception'; -import { JSONObject } from 'Common/Types/JSON'; +import { ConfigLogLevel, LogLevel } from "../EnvironmentConfig"; +import OneUptimeTelemetry from "./Telemetry"; +import { SeverityNumber } from "@opentelemetry/api-logs"; +import Exception from "Common/Types/Exception/Exception"; +import { JSONObject } from "Common/Types/JSON"; export type LogBody = string | JSONObject | Exception | Error | unknown; export default class logger { - public static getLogLevel(): ConfigLogLevel { - if (!LogLevel) { - return ConfigLogLevel.INFO; - } - - return LogLevel; + public static getLogLevel(): ConfigLogLevel { + if (!LogLevel) { + return ConfigLogLevel.INFO; } - public static serializeLogBody(body: LogBody): string { - if (typeof body === 'string') { - return body; - } else if (body instanceof Exception || body instanceof Error) { - return body.message; - } - return JSON.stringify(body); + return LogLevel; + } + + public static serializeLogBody(body: LogBody): string { + if (typeof body === "string") { + return body; + } else if (body instanceof Exception || body instanceof Error) { + return body.message; } + return JSON.stringify(body); + } - public static info(message: LogBody): void { - const logLevel: ConfigLogLevel = this.getLogLevel(); + public static info(message: LogBody): void { + const logLevel: ConfigLogLevel = this.getLogLevel(); - if ( - logLevel === ConfigLogLevel.DEBUG || - logLevel === ConfigLogLevel.INFO - ) { - // eslint-disable-next-line no-console - console.info(message); + if (logLevel === ConfigLogLevel.DEBUG || logLevel === ConfigLogLevel.INFO) { + // eslint-disable-next-line no-console + console.info(message); - this.emit({ - body: message, - severityNumber: SeverityNumber.INFO, - }); - } + this.emit({ + body: message, + severityNumber: SeverityNumber.INFO, + }); } + } - public static error(message: LogBody): void { - const logLevel: ConfigLogLevel = this.getLogLevel(); + public static error(message: LogBody): void { + const logLevel: ConfigLogLevel = this.getLogLevel(); - if ( - logLevel === ConfigLogLevel.DEBUG || - logLevel === ConfigLogLevel.INFO || - logLevel === ConfigLogLevel.WARN || - logLevel === ConfigLogLevel.ERROR - ) { - // eslint-disable-next-line no-console - console.error(message); + if ( + logLevel === ConfigLogLevel.DEBUG || + logLevel === ConfigLogLevel.INFO || + logLevel === ConfigLogLevel.WARN || + logLevel === ConfigLogLevel.ERROR + ) { + // eslint-disable-next-line no-console + console.error(message); - this.emit({ - body: message, - severityNumber: SeverityNumber.ERROR, - }); - } + this.emit({ + body: message, + severityNumber: SeverityNumber.ERROR, + }); } + } - public static warn(message: LogBody): void { - const logLevel: ConfigLogLevel = this.getLogLevel(); + public static warn(message: LogBody): void { + const logLevel: ConfigLogLevel = this.getLogLevel(); - if ( - logLevel === ConfigLogLevel.DEBUG || - logLevel === ConfigLogLevel.INFO || - logLevel === ConfigLogLevel.WARN - ) { - // eslint-disable-next-line no-console - console.warn(message); + if ( + logLevel === ConfigLogLevel.DEBUG || + logLevel === ConfigLogLevel.INFO || + logLevel === ConfigLogLevel.WARN + ) { + // eslint-disable-next-line no-console + console.warn(message); - this.emit({ - body: message, - severityNumber: SeverityNumber.WARN, - }); - } + this.emit({ + body: message, + severityNumber: SeverityNumber.WARN, + }); } + } - public static debug(message: LogBody): void { - const logLevel: ConfigLogLevel = this.getLogLevel(); + public static debug(message: LogBody): void { + const logLevel: ConfigLogLevel = this.getLogLevel(); - if (logLevel === ConfigLogLevel.DEBUG) { - // eslint-disable-next-line no-console - console.debug(message); + if (logLevel === ConfigLogLevel.DEBUG) { + // eslint-disable-next-line no-console + console.debug(message); - this.emit({ - body: message, - severityNumber: SeverityNumber.DEBUG, - }); - } + this.emit({ + body: message, + severityNumber: SeverityNumber.DEBUG, + }); } + } - public static emit(data: { - body: LogBody; - severityNumber: SeverityNumber; - }): void { - OneUptimeTelemetry.getLogger().emit({ - body: this.serializeLogBody(data.body), - severityNumber: data.severityNumber, - }); - } + public static emit(data: { + body: LogBody; + severityNumber: SeverityNumber; + }): void { + OneUptimeTelemetry.getLogger().emit({ + body: this.serializeLogBody(data.body), + severityNumber: data.severityNumber, + }); + } } diff --git a/CommonServer/Utils/Probe/Criteria/APIRequestCriteria.ts b/CommonServer/Utils/Probe/Criteria/APIRequestCriteria.ts index cf7cfb07d5..489f7f8041 100644 --- a/CommonServer/Utils/Probe/Criteria/APIRequestCriteria.ts +++ b/CommonServer/Utils/Probe/Criteria/APIRequestCriteria.ts @@ -1,180 +1,176 @@ -import DataToProcess from '../DataToProcess'; -import CompareCriteria from './CompareCriteria'; -import EvaluateOverTime from './EvaluateOverTime'; -import { JSONObject } from 'Common/Types/JSON'; +import DataToProcess from "../DataToProcess"; +import CompareCriteria from "./CompareCriteria"; +import EvaluateOverTime from "./EvaluateOverTime"; +import { JSONObject } from "Common/Types/JSON"; import { - CheckOn, - CriteriaFilter, - FilterType, -} from 'Common/Types/Monitor/CriteriaFilter'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import Typeof from 'Common/Types/Typeof'; + CheckOn, + CriteriaFilter, + FilterType, +} from "Common/Types/Monitor/CriteriaFilter"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import Typeof from "Common/Types/Typeof"; export default class APIRequestCriteria { - public static async isMonitorInstanceCriteriaFilterMet(input: { - dataToProcess: DataToProcess; - criteriaFilter: CriteriaFilter; - }): Promise<string | null> { - // Server Monitoring Checks + public static async isMonitorInstanceCriteriaFilterMet(input: { + dataToProcess: DataToProcess; + criteriaFilter: CriteriaFilter; + }): Promise<string | null> { + // Server Monitoring Checks - let threshold: number | string | undefined | null = - input.criteriaFilter.value; + let threshold: number | string | undefined | null = + input.criteriaFilter.value; - let overTimeValue: Array<number> | number | undefined = undefined; + let overTimeValue: Array<number> | number | undefined = undefined; - if ( - input.criteriaFilter.eveluateOverTime && - input.criteriaFilter.evaluateOverTimeOptions - ) { - overTimeValue = await EvaluateOverTime.getValueOverTime({ - monitorId: input.dataToProcess.monitorId!, - evaluateOverTimeOptions: - input.criteriaFilter.evaluateOverTimeOptions, - metricType: input.criteriaFilter.checkOn, - miscData: input.criteriaFilter - .serverMonitorOptions as JSONObject, - }); - - if (Array.isArray(overTimeValue) && overTimeValue.length === 0) { - return null; - } - - if (overTimeValue === undefined) { - return null; - } - } - - // check response time filter - if (input.criteriaFilter.checkOn === CheckOn.ResponseTime) { - threshold = CompareCriteria.convertToNumber(threshold); - - const value: Array<number> | number = - overTimeValue || - (input.dataToProcess as ProbeMonitorResponse).responseTimeInMs!; - - return CompareCriteria.compareCriteriaNumbers({ - value: value, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } - - //check response code - if (input.criteriaFilter.checkOn === CheckOn.ResponseStatusCode) { - threshold = CompareCriteria.convertToNumber(threshold); - - const value: Array<number> | number = - overTimeValue || - (input.dataToProcess as ProbeMonitorResponse).responseCode!; - - return CompareCriteria.compareCriteriaNumbers({ - value: value, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } - - if (input.criteriaFilter.checkOn === CheckOn.ResponseBody) { - let responseBody: string | JSONObject | undefined = ( - input.dataToProcess as ProbeMonitorResponse - ).responseBody; - - if (responseBody && typeof responseBody === Typeof.Object) { - responseBody = JSON.stringify(responseBody); - } - - if (!responseBody) { - return null; - } - - // contains - if (input.criteriaFilter.filterType === FilterType.Contains) { - if ( - threshold && - responseBody && - (responseBody as string).includes(threshold as string) - ) { - return `Response body contains ${threshold}.`; - } - return null; - } - - if (input.criteriaFilter.filterType === FilterType.NotContains) { - if ( - threshold && - responseBody && - !(responseBody as string).includes(threshold as string) - ) { - return `Response body does not contain ${threshold}.`; - } - return null; - } - } - - if (input.criteriaFilter.checkOn === CheckOn.ResponseHeader) { - const headerKeys: Array<string> = Object.keys( - (input.dataToProcess as ProbeMonitorResponse).responseHeaders || - {} - ).map((key: string) => { - return key.toLowerCase(); - }); - - // contains - if (input.criteriaFilter.filterType === FilterType.Contains) { - if ( - threshold && - headerKeys && - headerKeys.includes(threshold as string) - ) { - return `Response header contains ${threshold}.`; - } - return null; - } - - if (input.criteriaFilter.filterType === FilterType.NotContains) { - if ( - threshold && - headerKeys && - !headerKeys.includes(threshold as string) - ) { - return `Response header does not contain ${threshold}.`; - } - return null; - } - } - - if (input.criteriaFilter.checkOn === CheckOn.ResponseHeaderValue) { - const headerValues: Array<string> = Object.values( - (input.dataToProcess as ProbeMonitorResponse).responseHeaders || - {} - ).map((key: string) => { - return key.toLowerCase(); - }); - - // contains - if (input.criteriaFilter.filterType === FilterType.Contains) { - if ( - threshold && - headerValues && - headerValues.includes(threshold as string) - ) { - return `Response header threshold contains ${threshold}.`; - } - return null; - } - - if (input.criteriaFilter.filterType === FilterType.NotContains) { - if ( - threshold && - headerValues && - !headerValues.includes(threshold as string) - ) { - return `Response header threshold does not contain ${threshold}.`; - } - return null; - } - } + if ( + input.criteriaFilter.eveluateOverTime && + input.criteriaFilter.evaluateOverTimeOptions + ) { + overTimeValue = await EvaluateOverTime.getValueOverTime({ + monitorId: input.dataToProcess.monitorId!, + evaluateOverTimeOptions: input.criteriaFilter.evaluateOverTimeOptions, + metricType: input.criteriaFilter.checkOn, + miscData: input.criteriaFilter.serverMonitorOptions as JSONObject, + }); + if (Array.isArray(overTimeValue) && overTimeValue.length === 0) { return null; + } + + if (overTimeValue === undefined) { + return null; + } } + + // check response time filter + if (input.criteriaFilter.checkOn === CheckOn.ResponseTime) { + threshold = CompareCriteria.convertToNumber(threshold); + + const value: Array<number> | number = + overTimeValue || + (input.dataToProcess as ProbeMonitorResponse).responseTimeInMs!; + + return CompareCriteria.compareCriteriaNumbers({ + value: value, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); + } + + //check response code + if (input.criteriaFilter.checkOn === CheckOn.ResponseStatusCode) { + threshold = CompareCriteria.convertToNumber(threshold); + + const value: Array<number> | number = + overTimeValue || + (input.dataToProcess as ProbeMonitorResponse).responseCode!; + + return CompareCriteria.compareCriteriaNumbers({ + value: value, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); + } + + if (input.criteriaFilter.checkOn === CheckOn.ResponseBody) { + let responseBody: string | JSONObject | undefined = ( + input.dataToProcess as ProbeMonitorResponse + ).responseBody; + + if (responseBody && typeof responseBody === Typeof.Object) { + responseBody = JSON.stringify(responseBody); + } + + if (!responseBody) { + return null; + } + + // contains + if (input.criteriaFilter.filterType === FilterType.Contains) { + if ( + threshold && + responseBody && + (responseBody as string).includes(threshold as string) + ) { + return `Response body contains ${threshold}.`; + } + return null; + } + + if (input.criteriaFilter.filterType === FilterType.NotContains) { + if ( + threshold && + responseBody && + !(responseBody as string).includes(threshold as string) + ) { + return `Response body does not contain ${threshold}.`; + } + return null; + } + } + + if (input.criteriaFilter.checkOn === CheckOn.ResponseHeader) { + const headerKeys: Array<string> = Object.keys( + (input.dataToProcess as ProbeMonitorResponse).responseHeaders || {}, + ).map((key: string) => { + return key.toLowerCase(); + }); + + // contains + if (input.criteriaFilter.filterType === FilterType.Contains) { + if ( + threshold && + headerKeys && + headerKeys.includes(threshold as string) + ) { + return `Response header contains ${threshold}.`; + } + return null; + } + + if (input.criteriaFilter.filterType === FilterType.NotContains) { + if ( + threshold && + headerKeys && + !headerKeys.includes(threshold as string) + ) { + return `Response header does not contain ${threshold}.`; + } + return null; + } + } + + if (input.criteriaFilter.checkOn === CheckOn.ResponseHeaderValue) { + const headerValues: Array<string> = Object.values( + (input.dataToProcess as ProbeMonitorResponse).responseHeaders || {}, + ).map((key: string) => { + return key.toLowerCase(); + }); + + // contains + if (input.criteriaFilter.filterType === FilterType.Contains) { + if ( + threshold && + headerValues && + headerValues.includes(threshold as string) + ) { + return `Response header threshold contains ${threshold}.`; + } + return null; + } + + if (input.criteriaFilter.filterType === FilterType.NotContains) { + if ( + threshold && + headerValues && + !headerValues.includes(threshold as string) + ) { + return `Response header threshold does not contain ${threshold}.`; + } + return null; + } + } + + return null; + } } diff --git a/CommonServer/Utils/Probe/Criteria/CompareCriteria.ts b/CommonServer/Utils/Probe/Criteria/CompareCriteria.ts index 8ec368567f..23809ae3a3 100644 --- a/CommonServer/Utils/Probe/Criteria/CompareCriteria.ts +++ b/CommonServer/Utils/Probe/Criteria/CompareCriteria.ts @@ -1,486 +1,478 @@ -import logger from '../../../Utils/Logger'; +import logger from "../../../Utils/Logger"; import { - CheckOn, - CriteriaFilter, - EvaluateOverTimeType, - FilterType, -} from 'Common/Types/Monitor/CriteriaFilter'; -import Typeof from 'Common/Types/Typeof'; + CheckOn, + CriteriaFilter, + EvaluateOverTimeType, + FilterType, +} from "Common/Types/Monitor/CriteriaFilter"; +import Typeof from "Common/Types/Typeof"; export default class CompareCriteria { - public static greaterThan(data: { - value: number | Array<number>; - evaluationType?: EvaluateOverTimeType | undefined; - threshold: number; - }): boolean { - if (Array.isArray(data.value)) { - if (data.evaluationType === EvaluateOverTimeType.AnyValue) { - return data.value.some((value: number) => { - return value > data.threshold; - }); - } - return data.value.every((value: number) => { - return value > data.threshold; - }); - } - - return data.value > data.threshold; + public static greaterThan(data: { + value: number | Array<number>; + evaluationType?: EvaluateOverTimeType | undefined; + threshold: number; + }): boolean { + if (Array.isArray(data.value)) { + if (data.evaluationType === EvaluateOverTimeType.AnyValue) { + return data.value.some((value: number) => { + return value > data.threshold; + }); + } + return data.value.every((value: number) => { + return value > data.threshold; + }); } - public static lessThan(data: { - value: number | Array<number>; - evaluationType?: EvaluateOverTimeType | undefined; - threshold: number; - }): boolean { - if (Array.isArray(data.value)) { - if (data.evaluationType === EvaluateOverTimeType.AnyValue) { - return data.value.some((value: number) => { - return value < data.threshold; - }); - } - return data.value.every((value: number) => { - return value < data.threshold; - }); - } + return data.value > data.threshold; + } - return data.value < data.threshold; + public static lessThan(data: { + value: number | Array<number>; + evaluationType?: EvaluateOverTimeType | undefined; + threshold: number; + }): boolean { + if (Array.isArray(data.value)) { + if (data.evaluationType === EvaluateOverTimeType.AnyValue) { + return data.value.some((value: number) => { + return value < data.threshold; + }); + } + return data.value.every((value: number) => { + return value < data.threshold; + }); } - public static greaterThanOrEqual(data: { - value: number | Array<number>; - evaluationType?: EvaluateOverTimeType | undefined; - threshold: number; - }): boolean { - if (Array.isArray(data.value)) { - if (data.evaluationType === EvaluateOverTimeType.AnyValue) { - return data.value.some((value: number) => { - return value >= data.threshold; - }); - } - return data.value.every((value: number) => { - return value >= data.threshold; - }); - } + return data.value < data.threshold; + } - return data.value >= data.threshold; + public static greaterThanOrEqual(data: { + value: number | Array<number>; + evaluationType?: EvaluateOverTimeType | undefined; + threshold: number; + }): boolean { + if (Array.isArray(data.value)) { + if (data.evaluationType === EvaluateOverTimeType.AnyValue) { + return data.value.some((value: number) => { + return value >= data.threshold; + }); + } + return data.value.every((value: number) => { + return value >= data.threshold; + }); } - public static lessThanOrEqual(data: { - value: number | Array<number>; - evaluationType?: EvaluateOverTimeType | undefined; - threshold: number; - }): boolean { - if (Array.isArray(data.value)) { - if (data.evaluationType === EvaluateOverTimeType.AnyValue) { - return data.value.some((value: number) => { - return value <= data.threshold; - }); - } - return data.value.every((value: number) => { - return value <= data.threshold; - }); - } + return data.value >= data.threshold; + } - return data.value <= data.threshold; + public static lessThanOrEqual(data: { + value: number | Array<number>; + evaluationType?: EvaluateOverTimeType | undefined; + threshold: number; + }): boolean { + if (Array.isArray(data.value)) { + if (data.evaluationType === EvaluateOverTimeType.AnyValue) { + return data.value.some((value: number) => { + return value <= data.threshold; + }); + } + return data.value.every((value: number) => { + return value <= data.threshold; + }); } - public static equalTo(data: { - value: number | Array<number>; - evaluationType?: EvaluateOverTimeType | undefined; - threshold: number; - }): boolean { - if (Array.isArray(data.value)) { - if (data.evaluationType === EvaluateOverTimeType.AnyValue) { - return data.value.some((value: number) => { - return value === data.threshold; - }); - } - return data.value.every((value: number) => { - return value === data.threshold; - }); - } + return data.value <= data.threshold; + } - return data.value === data.threshold; + public static equalTo(data: { + value: number | Array<number>; + evaluationType?: EvaluateOverTimeType | undefined; + threshold: number; + }): boolean { + if (Array.isArray(data.value)) { + if (data.evaluationType === EvaluateOverTimeType.AnyValue) { + return data.value.some((value: number) => { + return value === data.threshold; + }); + } + return data.value.every((value: number) => { + return value === data.threshold; + }); } - public static notEqualTo(data: { - value: number | Array<number>; - evaluationType?: EvaluateOverTimeType | undefined; - threshold: number; - }): boolean { - if (Array.isArray(data.value)) { - if (data.evaluationType === EvaluateOverTimeType.AnyValue) { - return data.value.some((value: number) => { - return value !== data.threshold; - }); - } - return data.value.every((value: number) => { - return value !== data.threshold; - }); - } + return data.value === data.threshold; + } - return data.value !== data.threshold; + public static notEqualTo(data: { + value: number | Array<number>; + evaluationType?: EvaluateOverTimeType | undefined; + threshold: number; + }): boolean { + if (Array.isArray(data.value)) { + if (data.evaluationType === EvaluateOverTimeType.AnyValue) { + return data.value.some((value: number) => { + return value !== data.threshold; + }); + } + return data.value.every((value: number) => { + return value !== data.threshold; + }); } - public static convertToNumber( - threshold: string | number | undefined - ): number | null { - if (!threshold) { - return null; - } + return data.value !== data.threshold; + } - if (typeof threshold === Typeof.String) { - try { - threshold = parseInt(threshold as string); - } catch (err) { - logger.error(err); - return null; - } - } - - if (typeof threshold !== Typeof.Number) { - return null; - } - - return threshold as number; + public static convertToNumber( + threshold: string | number | undefined, + ): number | null { + if (!threshold) { + return null; } - public static checkEqualToOrNotEqualTo(data: { - value: string | number; - threshold: string | number; - criteriaFilter: CriteriaFilter; - }): string | null { - if (data.criteriaFilter.filterType === FilterType.EqualTo) { - if (data.value === data.threshold) { - return `${data.criteriaFilter.checkOn} is equal to ${data.threshold}.`; - } - - return null; - } - - if (data.criteriaFilter.filterType === FilterType.NotEqualTo) { - if (data.value !== data.threshold) { - return `${data.criteriaFilter.checkOn} is not equal to ${data.threshold}.`; - } - - return null; - } - + if (typeof threshold === Typeof.String) { + try { + threshold = parseInt(threshold as string); + } catch (err) { + logger.error(err); return null; + } } - public static compareEmptyAndNotEmpty(data: { - value: any; - criteriaFilter: CriteriaFilter; - }): string | null { - if (data.criteriaFilter.filterType === FilterType.IsEmpty) { - if (data.value === null || data.value === undefined) { - return `${data.criteriaFilter.checkOn} is empty.`; - } - - return null; - } - - if (data.criteriaFilter.filterType === FilterType.IsNotEmpty) { - if (data.value !== null && data.value !== undefined) { - return `${data.criteriaFilter.checkOn} is not empty.`; - } - - return null; - } - - return null; + if (typeof threshold !== Typeof.Number) { + return null; } - public static compareCriteriaStrings(data: { - value: string; - threshold: string; - criteriaFilter: CriteriaFilter; - }): string | null { - if (data.value === null || data.value === undefined) { - return null; - } + return threshold as number; + } - if (data.threshold === null || data.threshold === undefined) { - return null; - } + public static checkEqualToOrNotEqualTo(data: { + value: string | number; + threshold: string | number; + criteriaFilter: CriteriaFilter; + }): string | null { + if (data.criteriaFilter.filterType === FilterType.EqualTo) { + if (data.value === data.threshold) { + return `${data.criteriaFilter.checkOn} is equal to ${data.threshold}.`; + } - if (typeof data.value !== Typeof.String) { - data.value = data.value.toString(); - } - - if (typeof data.threshold !== Typeof.String) { - data.threshold = data.threshold.toString(); - } - - if (data.criteriaFilter.filterType === FilterType.Contains) { - if (data.value.includes(data.threshold)) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - if (data.criteriaFilter.filterType === FilterType.NotContains) { - if (!data.value.includes(data.threshold)) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - if (data.criteriaFilter.filterType === FilterType.StartsWith) { - if (data.value.startsWith(data.threshold)) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - if (data.criteriaFilter.filterType === FilterType.EndsWith) { - if (data.value.endsWith(data.threshold)) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - return null; + return null; } - public static compareCriteriaNumbers(data: { - value: Array<number> | number; - threshold: number; - criteriaFilter: CriteriaFilter; - }): string | null { - if (data.value === null || data.value === undefined) { - return null; - } + if (data.criteriaFilter.filterType === FilterType.NotEqualTo) { + if (data.value !== data.threshold) { + return `${data.criteriaFilter.checkOn} is not equal to ${data.threshold}.`; + } - if (data.threshold === null || data.threshold === undefined) { - return null; - } - - if (data.criteriaFilter.filterType === FilterType.GreaterThan) { - if ( - CompareCriteria.greaterThan({ - threshold: data.threshold as number, - value: data.value, - evaluationType: - data.criteriaFilter.evaluateOverTimeOptions - ?.evaluateOverTimeType, - }) - ) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold as number, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - if (data.criteriaFilter.filterType === FilterType.LessThan) { - if ( - CompareCriteria.lessThan({ - threshold: data.threshold as number, - value: data.value, - evaluationType: - data.criteriaFilter.evaluateOverTimeOptions - ?.evaluateOverTimeType, - }) - ) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold as number, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - if (data.criteriaFilter.filterType === FilterType.EqualTo) { - if ( - CompareCriteria.equalTo({ - threshold: data.threshold as number, - value: data.value, - evaluationType: - data.criteriaFilter.evaluateOverTimeOptions - ?.evaluateOverTimeType, - }) - ) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold as number, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - if (data.criteriaFilter.filterType === FilterType.NotEqualTo) { - if ( - CompareCriteria.notEqualTo({ - threshold: data.threshold as number, - value: data.value, - evaluationType: - data.criteriaFilter.evaluateOverTimeOptions - ?.evaluateOverTimeType, - }) - ) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold as number, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - if ( - data.criteriaFilter.filterType === FilterType.GreaterThanOrEqualTo - ) { - if ( - CompareCriteria.greaterThanOrEqual({ - threshold: data.threshold as number, - value: data.value, - evaluationType: - data.criteriaFilter.evaluateOverTimeOptions - ?.evaluateOverTimeType, - }) - ) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold as number, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - if (data.criteriaFilter.filterType === FilterType.LessThanOrEqualTo) { - if ( - CompareCriteria.lessThanOrEqual({ - threshold: data.threshold as number, - value: data.value, - evaluationType: - data.criteriaFilter.evaluateOverTimeOptions - ?.evaluateOverTimeType, - }) - ) { - return CompareCriteria.getCompareMessage({ - values: data.value, - threshold: data.threshold as number, - criteriaFilter: data.criteriaFilter, - }); - } - - return null; - } - - return null; + return null; } - public static getCompareMessage(data: { - values: Array<number> | number | string; - threshold: number | string; - criteriaFilter: CriteriaFilter; - }): string { - // CPU Percent over the last 5 minutes is 10 which is less than the threshold of 20 - let message: string = ''; + return null; + } - if ( - data.criteriaFilter.evaluateOverTimeOptions - ?.evaluateOverTimeType === EvaluateOverTimeType.AnyValue - ) { - message += 'Any value of'; - } + public static compareEmptyAndNotEmpty(data: { + value: any; + criteriaFilter: CriteriaFilter; + }): string | null { + if (data.criteriaFilter.filterType === FilterType.IsEmpty) { + if (data.value === null || data.value === undefined) { + return `${data.criteriaFilter.checkOn} is empty.`; + } - if ( - data.criteriaFilter.evaluateOverTimeOptions - ?.evaluateOverTimeType === EvaluateOverTimeType.AllValues - ) { - message += 'All values of'; - } - - message += ` ${data.criteriaFilter.checkOn}`; - - if (data.criteriaFilter.checkOn === CheckOn.DiskUsagePercent) { - const diskPath: string = - data.criteriaFilter.serverMonitorOptions?.diskPath || '/'; - - message += ` on disk ${diskPath}`; - } - - if ( - data.criteriaFilter.eveluateOverTime && - data.criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes - ) { - message += ` over the last ${data.criteriaFilter.evaluateOverTimeOptions.timeValueInMinutes} minutes`; - } - - if (Array.isArray(data.values)) { - message += ` is ${data.values.join(', ')}`; - } else { - message += ` is ${data.values}`; - } - - message += ' which is'; - - switch (data.criteriaFilter.filterType) { - case FilterType.EqualTo: - message += ` equal to threshold ${data.threshold}`; - break; - case FilterType.GreaterThan: - message += ` greater than threshold ${data.threshold}`; - break; - case FilterType.GreaterThanOrEqualTo: - message += ` greater than or equal to threshold ${data.threshold}`; - break; - case FilterType.LessThan: - message += ` less than threshold ${data.threshold}`; - break; - case FilterType.LessThanOrEqualTo: - message += ` less than or equal to threshold ${data.threshold}`; - break; - case FilterType.NotEqualTo: - message += ` not equal to threshold ${data.threshold}`; - break; - case FilterType.Contains: - message += ` contains ${data.threshold}`; - break; - case FilterType.NotContains: - message += ` does not contain ${data.threshold}`; - break; - case FilterType.StartsWith: - message += ` starts with ${data.threshold}`; - break; - case FilterType.EndsWith: - message += ` ends with ${data.threshold}`; - break; - } - - return message.trim(); + return null; } + + if (data.criteriaFilter.filterType === FilterType.IsNotEmpty) { + if (data.value !== null && data.value !== undefined) { + return `${data.criteriaFilter.checkOn} is not empty.`; + } + + return null; + } + + return null; + } + + public static compareCriteriaStrings(data: { + value: string; + threshold: string; + criteriaFilter: CriteriaFilter; + }): string | null { + if (data.value === null || data.value === undefined) { + return null; + } + + if (data.threshold === null || data.threshold === undefined) { + return null; + } + + if (typeof data.value !== Typeof.String) { + data.value = data.value.toString(); + } + + if (typeof data.threshold !== Typeof.String) { + data.threshold = data.threshold.toString(); + } + + if (data.criteriaFilter.filterType === FilterType.Contains) { + if (data.value.includes(data.threshold)) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + if (data.criteriaFilter.filterType === FilterType.NotContains) { + if (!data.value.includes(data.threshold)) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + if (data.criteriaFilter.filterType === FilterType.StartsWith) { + if (data.value.startsWith(data.threshold)) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + if (data.criteriaFilter.filterType === FilterType.EndsWith) { + if (data.value.endsWith(data.threshold)) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + return null; + } + + public static compareCriteriaNumbers(data: { + value: Array<number> | number; + threshold: number; + criteriaFilter: CriteriaFilter; + }): string | null { + if (data.value === null || data.value === undefined) { + return null; + } + + if (data.threshold === null || data.threshold === undefined) { + return null; + } + + if (data.criteriaFilter.filterType === FilterType.GreaterThan) { + if ( + CompareCriteria.greaterThan({ + threshold: data.threshold as number, + value: data.value, + evaluationType: + data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType, + }) + ) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold as number, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + if (data.criteriaFilter.filterType === FilterType.LessThan) { + if ( + CompareCriteria.lessThan({ + threshold: data.threshold as number, + value: data.value, + evaluationType: + data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType, + }) + ) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold as number, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + if (data.criteriaFilter.filterType === FilterType.EqualTo) { + if ( + CompareCriteria.equalTo({ + threshold: data.threshold as number, + value: data.value, + evaluationType: + data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType, + }) + ) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold as number, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + if (data.criteriaFilter.filterType === FilterType.NotEqualTo) { + if ( + CompareCriteria.notEqualTo({ + threshold: data.threshold as number, + value: data.value, + evaluationType: + data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType, + }) + ) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold as number, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + if (data.criteriaFilter.filterType === FilterType.GreaterThanOrEqualTo) { + if ( + CompareCriteria.greaterThanOrEqual({ + threshold: data.threshold as number, + value: data.value, + evaluationType: + data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType, + }) + ) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold as number, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + if (data.criteriaFilter.filterType === FilterType.LessThanOrEqualTo) { + if ( + CompareCriteria.lessThanOrEqual({ + threshold: data.threshold as number, + value: data.value, + evaluationType: + data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType, + }) + ) { + return CompareCriteria.getCompareMessage({ + values: data.value, + threshold: data.threshold as number, + criteriaFilter: data.criteriaFilter, + }); + } + + return null; + } + + return null; + } + + public static getCompareMessage(data: { + values: Array<number> | number | string; + threshold: number | string; + criteriaFilter: CriteriaFilter; + }): string { + // CPU Percent over the last 5 minutes is 10 which is less than the threshold of 20 + let message: string = ""; + + if ( + data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === + EvaluateOverTimeType.AnyValue + ) { + message += "Any value of"; + } + + if ( + data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === + EvaluateOverTimeType.AllValues + ) { + message += "All values of"; + } + + message += ` ${data.criteriaFilter.checkOn}`; + + if (data.criteriaFilter.checkOn === CheckOn.DiskUsagePercent) { + const diskPath: string = + data.criteriaFilter.serverMonitorOptions?.diskPath || "/"; + + message += ` on disk ${diskPath}`; + } + + if ( + data.criteriaFilter.eveluateOverTime && + data.criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes + ) { + message += ` over the last ${data.criteriaFilter.evaluateOverTimeOptions.timeValueInMinutes} minutes`; + } + + if (Array.isArray(data.values)) { + message += ` is ${data.values.join(", ")}`; + } else { + message += ` is ${data.values}`; + } + + message += " which is"; + + switch (data.criteriaFilter.filterType) { + case FilterType.EqualTo: + message += ` equal to threshold ${data.threshold}`; + break; + case FilterType.GreaterThan: + message += ` greater than threshold ${data.threshold}`; + break; + case FilterType.GreaterThanOrEqualTo: + message += ` greater than or equal to threshold ${data.threshold}`; + break; + case FilterType.LessThan: + message += ` less than threshold ${data.threshold}`; + break; + case FilterType.LessThanOrEqualTo: + message += ` less than or equal to threshold ${data.threshold}`; + break; + case FilterType.NotEqualTo: + message += ` not equal to threshold ${data.threshold}`; + break; + case FilterType.Contains: + message += ` contains ${data.threshold}`; + break; + case FilterType.NotContains: + message += ` does not contain ${data.threshold}`; + break; + case FilterType.StartsWith: + message += ` starts with ${data.threshold}`; + break; + case FilterType.EndsWith: + message += ` ends with ${data.threshold}`; + break; + } + + return message.trim(); + } } diff --git a/CommonServer/Utils/Probe/Criteria/CustomCodeMonitorCriteria.ts b/CommonServer/Utils/Probe/Criteria/CustomCodeMonitorCriteria.ts index 2fc63b8147..9c6f48fe37 100644 --- a/CommonServer/Utils/Probe/Criteria/CustomCodeMonitorCriteria.ts +++ b/CommonServer/Utils/Probe/Criteria/CustomCodeMonitorCriteria.ts @@ -1,115 +1,109 @@ -import CompareCriteria from './CompareCriteria'; -import { CheckOn, CriteriaFilter } from 'Common/Types/Monitor/CriteriaFilter'; -import CustomCodeMonitorResponse from 'Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse'; +import CompareCriteria from "./CompareCriteria"; +import { CheckOn, CriteriaFilter } from "Common/Types/Monitor/CriteriaFilter"; +import CustomCodeMonitorResponse from "Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse"; export default class CustomCodeMonitoringCriteria { - public static async isMonitorInstanceCriteriaFilterMet(input: { - monitorResponse: CustomCodeMonitorResponse; - criteriaFilter: CriteriaFilter; - }): Promise<string | null> { - // Server Monitoring Checks + public static async isMonitorInstanceCriteriaFilterMet(input: { + monitorResponse: CustomCodeMonitorResponse; + criteriaFilter: CriteriaFilter; + }): Promise<string | null> { + // Server Monitoring Checks - let threshold: number | string | undefined | null = - input.criteriaFilter.value; + let threshold: number | string | undefined | null = + input.criteriaFilter.value; - const syntheticMonitorResponse: CustomCodeMonitorResponse = - input.monitorResponse; + const syntheticMonitorResponse: CustomCodeMonitorResponse = + input.monitorResponse; - if (input.criteriaFilter.checkOn === CheckOn.ExecutionTime) { - threshold = CompareCriteria.convertToNumber(threshold); + if (input.criteriaFilter.checkOn === CheckOn.ExecutionTime) { + threshold = CompareCriteria.convertToNumber(threshold); - const currentExecutionTime: number = - syntheticMonitorResponse.executionTimeInMS || 0; + const currentExecutionTime: number = + syntheticMonitorResponse.executionTimeInMS || 0; - return CompareCriteria.compareCriteriaNumbers({ - value: currentExecutionTime, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } - - if (input.criteriaFilter.checkOn === CheckOn.Error) { - const emptyNotEmptyResult: string | null = - CompareCriteria.compareEmptyAndNotEmpty({ - value: syntheticMonitorResponse.scriptError, - criteriaFilter: input.criteriaFilter, - }); - - if (emptyNotEmptyResult) { - return emptyNotEmptyResult; - } - - if ( - threshold && - typeof syntheticMonitorResponse.scriptError === 'string' - ) { - const result: string | null = - CompareCriteria.compareCriteriaStrings({ - value: syntheticMonitorResponse.scriptError!, - threshold: threshold.toString(), - criteriaFilter: input.criteriaFilter, - }); - - if (result) { - return result; - } - } - } - - if (input.criteriaFilter.checkOn === CheckOn.ResultValue) { - const emptyNotEmptyResult: string | null = - CompareCriteria.compareEmptyAndNotEmpty({ - value: syntheticMonitorResponse.result, - criteriaFilter: input.criteriaFilter, - }); - - if (emptyNotEmptyResult) { - return emptyNotEmptyResult; - } - - let thresholdAsNumber: number | null = null; - - try { - if (threshold) { - thresholdAsNumber = parseFloat(threshold.toString()); - } - } catch (err) { - thresholdAsNumber = null; - } - - if ( - thresholdAsNumber !== null && - typeof syntheticMonitorResponse.result === 'number' - ) { - const result: string | null = - CompareCriteria.compareCriteriaNumbers({ - value: syntheticMonitorResponse.result, - threshold: thresholdAsNumber as number, - criteriaFilter: input.criteriaFilter, - }); - - if (result) { - return result; - } - } - - if ( - threshold && - typeof syntheticMonitorResponse.result === 'string' - ) { - const result: string | null = - CompareCriteria.compareCriteriaStrings({ - value: syntheticMonitorResponse.result, - threshold: threshold.toString(), - criteriaFilter: input.criteriaFilter, - }); - - if (result) { - return result; - } - } - } - - return null; + return CompareCriteria.compareCriteriaNumbers({ + value: currentExecutionTime, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); } + + if (input.criteriaFilter.checkOn === CheckOn.Error) { + const emptyNotEmptyResult: string | null = + CompareCriteria.compareEmptyAndNotEmpty({ + value: syntheticMonitorResponse.scriptError, + criteriaFilter: input.criteriaFilter, + }); + + if (emptyNotEmptyResult) { + return emptyNotEmptyResult; + } + + if ( + threshold && + typeof syntheticMonitorResponse.scriptError === "string" + ) { + const result: string | null = CompareCriteria.compareCriteriaStrings({ + value: syntheticMonitorResponse.scriptError!, + threshold: threshold.toString(), + criteriaFilter: input.criteriaFilter, + }); + + if (result) { + return result; + } + } + } + + if (input.criteriaFilter.checkOn === CheckOn.ResultValue) { + const emptyNotEmptyResult: string | null = + CompareCriteria.compareEmptyAndNotEmpty({ + value: syntheticMonitorResponse.result, + criteriaFilter: input.criteriaFilter, + }); + + if (emptyNotEmptyResult) { + return emptyNotEmptyResult; + } + + let thresholdAsNumber: number | null = null; + + try { + if (threshold) { + thresholdAsNumber = parseFloat(threshold.toString()); + } + } catch (err) { + thresholdAsNumber = null; + } + + if ( + thresholdAsNumber !== null && + typeof syntheticMonitorResponse.result === "number" + ) { + const result: string | null = CompareCriteria.compareCriteriaNumbers({ + value: syntheticMonitorResponse.result, + threshold: thresholdAsNumber as number, + criteriaFilter: input.criteriaFilter, + }); + + if (result) { + return result; + } + } + + if (threshold && typeof syntheticMonitorResponse.result === "string") { + const result: string | null = CompareCriteria.compareCriteriaStrings({ + value: syntheticMonitorResponse.result, + threshold: threshold.toString(), + criteriaFilter: input.criteriaFilter, + }); + + if (result) { + return result; + } + } + } + + return null; + } } diff --git a/CommonServer/Utils/Probe/Criteria/EvaluateOverTime.ts b/CommonServer/Utils/Probe/Criteria/EvaluateOverTime.ts index 38eedacc91..560d7d9b41 100644 --- a/CommonServer/Utils/Probe/Criteria/EvaluateOverTime.ts +++ b/CommonServer/Utils/Probe/Criteria/EvaluateOverTime.ts @@ -1,117 +1,116 @@ -import MonitorMetricsByMinuteService from '../../../Services/MonitorMetricsByMinuteService'; -import Query from '../../../Types/AnalyticsDatabase/Query'; -import GreaterThanOrEqual from 'Common/Types/BaseDatabase/GreaterThanOrEqual'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import { JSONObject } from 'Common/Types/JSON'; +import MonitorMetricsByMinuteService from "../../../Services/MonitorMetricsByMinuteService"; +import Query from "../../../Types/AnalyticsDatabase/Query"; +import GreaterThanOrEqual from "Common/Types/BaseDatabase/GreaterThanOrEqual"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import { JSONObject } from "Common/Types/JSON"; import { - CheckOn, - EvaluateOverTimeOptions, - EvaluateOverTimeType, -} from 'Common/Types/Monitor/CriteriaFilter'; -import ObjectID from 'Common/Types/ObjectID'; -import MonitorMetricsByMinute from 'Model/AnalyticsModels/MonitorMetricsByMinute'; + CheckOn, + EvaluateOverTimeOptions, + EvaluateOverTimeType, +} from "Common/Types/Monitor/CriteriaFilter"; +import ObjectID from "Common/Types/ObjectID"; +import MonitorMetricsByMinute from "Model/AnalyticsModels/MonitorMetricsByMinute"; export default class EvaluateOverTime { - public static async getValueOverTime(data: { - monitorId: ObjectID; - evaluateOverTimeOptions: EvaluateOverTimeOptions; - metricType: CheckOn; - miscData?: JSONObject | undefined; - }): Promise<number | Array<number>> { - // get values over time + public static async getValueOverTime(data: { + monitorId: ObjectID; + evaluateOverTimeOptions: EvaluateOverTimeOptions; + metricType: CheckOn; + miscData?: JSONObject | undefined; + }): Promise<number | Array<number>> { + // get values over time - const lastMinutesDate: Date = OneUptimeDate.getSomeMinutesAgo( - data.evaluateOverTimeOptions.timeValueInMinutes! - ); + const lastMinutesDate: Date = OneUptimeDate.getSomeMinutesAgo( + data.evaluateOverTimeOptions.timeValueInMinutes!, + ); - // TODO: Query over miscData + // TODO: Query over miscData - const query: Query<MonitorMetricsByMinute> = { - createdAt: new GreaterThanOrEqual(lastMinutesDate), - monitorId: data.monitorId, - metricType: data.metricType, - }; + const query: Query<MonitorMetricsByMinute> = { + createdAt: new GreaterThanOrEqual(lastMinutesDate), + monitorId: data.monitorId, + metricType: data.metricType, + }; - if (data.miscData) { - query.miscData = data.miscData; - } - - const monitorMetricsItems: Array<MonitorMetricsByMinute> = - await MonitorMetricsByMinuteService.findBy({ - query: query, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - select: { - metricValue: true, - }, - }); - - const values: Array<number> = monitorMetricsItems - .map((item: MonitorMetricsByMinute) => { - return item.metricValue; - }) - .filter((value: number | undefined) => { - return value !== undefined; - }) as Array<number>; - - if ( - data.evaluateOverTimeOptions.evaluateOverTimeType === - EvaluateOverTimeType.AnyValue || - data.evaluateOverTimeOptions.evaluateOverTimeType === - EvaluateOverTimeType.AllValues - ) { - // if its any or all then return the values. Otherwise compute the value based on the type - return values; - } - - return this.getValueByEvaluationType({ - values: values, - evaluateOverTimeType: - data.evaluateOverTimeOptions.evaluateOverTimeType!, - }); + if (data.miscData) { + query.miscData = data.miscData; } - private static getValueByEvaluationType(data: { - values: Array<number>; - evaluateOverTimeType: EvaluateOverTimeType; - }): number { - switch (data.evaluateOverTimeType) { - case EvaluateOverTimeType.Average: - return this.getAverage(data.values); - case EvaluateOverTimeType.MaximumValue: - return this.getMax(data.values); - case EvaluateOverTimeType.MunimumValue: - return this.getMin(data.values); - case EvaluateOverTimeType.Sum: - return this.getSum(data.values); - default: - return 0; - } + const monitorMetricsItems: Array<MonitorMetricsByMinute> = + await MonitorMetricsByMinuteService.findBy({ + query: query, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + select: { + metricValue: true, + }, + }); + + const values: Array<number> = monitorMetricsItems + .map((item: MonitorMetricsByMinute) => { + return item.metricValue; + }) + .filter((value: number | undefined) => { + return value !== undefined; + }) as Array<number>; + + if ( + data.evaluateOverTimeOptions.evaluateOverTimeType === + EvaluateOverTimeType.AnyValue || + data.evaluateOverTimeOptions.evaluateOverTimeType === + EvaluateOverTimeType.AllValues + ) { + // if its any or all then return the values. Otherwise compute the value based on the type + return values; } - private static getSum(values: number[]): number { - // get sum of all values - return values.reduce((a: number, b: number) => { - return a + b; - }, 0); - } + return this.getValueByEvaluationType({ + values: values, + evaluateOverTimeType: data.evaluateOverTimeOptions.evaluateOverTimeType!, + }); + } - private static getMin(values: number[]): number { - // get min value - return Math.min(...values); + private static getValueByEvaluationType(data: { + values: Array<number>; + evaluateOverTimeType: EvaluateOverTimeType; + }): number { + switch (data.evaluateOverTimeType) { + case EvaluateOverTimeType.Average: + return this.getAverage(data.values); + case EvaluateOverTimeType.MaximumValue: + return this.getMax(data.values); + case EvaluateOverTimeType.MunimumValue: + return this.getMin(data.values); + case EvaluateOverTimeType.Sum: + return this.getSum(data.values); + default: + return 0; } + } - private static getMax(values: number[]): number { - // get max value - return Math.max(...values); - } + private static getSum(values: number[]): number { + // get sum of all values + return values.reduce((a: number, b: number) => { + return a + b; + }, 0); + } - private static getAverage(values: number[]): number { - // get average value - return this.getSum(values) / values.length; - } + private static getMin(values: number[]): number { + // get min value + return Math.min(...values); + } + + private static getMax(values: number[]): number { + // get max value + return Math.max(...values); + } + + private static getAverage(values: number[]): number { + // get average value + return this.getSum(values) / values.length; + } } diff --git a/CommonServer/Utils/Probe/Criteria/IncomingRequestCriteria.ts b/CommonServer/Utils/Probe/Criteria/IncomingRequestCriteria.ts index 22908619fd..67bf7fdfe3 100644 --- a/CommonServer/Utils/Probe/Criteria/IncomingRequestCriteria.ts +++ b/CommonServer/Utils/Probe/Criteria/IncomingRequestCriteria.ts @@ -1,187 +1,163 @@ -import logger from '../../../Utils/Logger'; -import DataToProcess from '../DataToProcess'; -import OneUptimeDate from 'Common/Types/Date'; -import { JSONObject } from 'Common/Types/JSON'; +import logger from "../../../Utils/Logger"; +import DataToProcess from "../DataToProcess"; +import OneUptimeDate from "Common/Types/Date"; +import { JSONObject } from "Common/Types/JSON"; import { - CheckOn, - CriteriaFilter, - FilterType, -} from 'Common/Types/Monitor/CriteriaFilter'; -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; -import Typeof from 'Common/Types/Typeof'; + CheckOn, + CriteriaFilter, + FilterType, +} from "Common/Types/Monitor/CriteriaFilter"; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; +import Typeof from "Common/Types/Typeof"; export default class IncomingRequestCriteria { - public static async isMonitorInstanceCriteriaFilterMet(input: { - dataToProcess: DataToProcess; - criteriaFilter: CriteriaFilter; - }): Promise<string | null> { - // Server Monitoring Checks + public static async isMonitorInstanceCriteriaFilterMet(input: { + dataToProcess: DataToProcess; + criteriaFilter: CriteriaFilter; + }): Promise<string | null> { + // Server Monitoring Checks - let value: number | string | undefined = input.criteriaFilter.value; + let value: number | string | undefined = input.criteriaFilter.value; - // All incoming request related checks + // All incoming request related checks - if (input.criteriaFilter.checkOn === CheckOn.IncomingRequest) { - const lastCheckTime: Date = ( - input.dataToProcess as IncomingMonitorRequest - ).incomingRequestReceivedAt; + if (input.criteriaFilter.checkOn === CheckOn.IncomingRequest) { + const lastCheckTime: Date = ( + input.dataToProcess as IncomingMonitorRequest + ).incomingRequestReceivedAt; - const differenceInMinutes: number = - OneUptimeDate.getDifferenceInMinutes( - lastCheckTime, - OneUptimeDate.getCurrentDate() - ); - - if (!value) { - return null; - } - - if (typeof value === Typeof.String) { - try { - value = parseInt(value as string); - } catch (err) { - logger.error(err); - return null; - } - } - - if (typeof value !== Typeof.Number) { - return null; - } - - if ( - input.criteriaFilter.filterType === FilterType.RecievedInMinutes - ) { - if (value && differenceInMinutes <= (value as number)) { - return `Incoming request / heartbeat received in ${value} minutes.`; - } - return null; - } - - if ( - input.criteriaFilter.filterType === - FilterType.NotRecievedInMinutes - ) { - if (value && differenceInMinutes > (value as number)) { - return `Incoming request / heartbeat not received in ${value} minutes.`; - } - return null; - } - } - - if ( - input.criteriaFilter.checkOn === CheckOn.RequestBody && - !(input.dataToProcess as IncomingMonitorRequest) - .onlyCheckForIncomingRequestReceivedAt - ) { - let responseBody: string | JSONObject | undefined = ( - input.dataToProcess as IncomingMonitorRequest - ).requestBody; - - if (responseBody && typeof responseBody === Typeof.Object) { - responseBody = JSON.stringify(responseBody); - } - - if (!responseBody) { - return null; - } - - // contains - if (input.criteriaFilter.filterType === FilterType.Contains) { - if ( - value && - responseBody && - (responseBody as string).includes(value as string) - ) { - return `Request body contains ${value}.`; - } - return null; - } - - if (input.criteriaFilter.filterType === FilterType.NotContains) { - if ( - value && - responseBody && - !(responseBody as string).includes(value as string) - ) { - return `Request body does not contain ${value}.`; - } - return null; - } - } - - if ( - input.criteriaFilter.checkOn === CheckOn.RequestHeader && - !(input.dataToProcess as IncomingMonitorRequest) - .onlyCheckForIncomingRequestReceivedAt - ) { - const headerKeys: Array<string> = Object.keys( - (input.dataToProcess as IncomingMonitorRequest) - .requestHeaders || {} - ).map((key: string) => { - return key.toLowerCase(); - }); - - // contains - if (input.criteriaFilter.filterType === FilterType.Contains) { - if ( - value && - headerKeys && - headerKeys.includes(value as string) - ) { - return `Request header contains ${value}.`; - } - return null; - } - - if (input.criteriaFilter.filterType === FilterType.NotContains) { - if ( - value && - headerKeys && - !headerKeys.includes(value as string) - ) { - return `Request header does not contain ${value}.`; - } - return null; - } - } - - if ( - input.criteriaFilter.checkOn === CheckOn.RequestHeaderValue && - !(input.dataToProcess as IncomingMonitorRequest) - .onlyCheckForIncomingRequestReceivedAt - ) { - const headerValues: Array<string> = Object.values( - (input.dataToProcess as IncomingMonitorRequest) - .requestHeaders || {} - ).map((key: string) => { - return key.toLowerCase(); - }); - - // contains - if (input.criteriaFilter.filterType === FilterType.Contains) { - if ( - value && - headerValues && - headerValues.includes(value as string) - ) { - return `Request header value contains ${value}.`; - } - return null; - } - - if (input.criteriaFilter.filterType === FilterType.NotContains) { - if ( - value && - headerValues && - !headerValues.includes(value as string) - ) { - return `Request header value does not contain ${value}.`; - } - return null; - } - } + const differenceInMinutes: number = OneUptimeDate.getDifferenceInMinutes( + lastCheckTime, + OneUptimeDate.getCurrentDate(), + ); + if (!value) { return null; + } + + if (typeof value === Typeof.String) { + try { + value = parseInt(value as string); + } catch (err) { + logger.error(err); + return null; + } + } + + if (typeof value !== Typeof.Number) { + return null; + } + + if (input.criteriaFilter.filterType === FilterType.RecievedInMinutes) { + if (value && differenceInMinutes <= (value as number)) { + return `Incoming request / heartbeat received in ${value} minutes.`; + } + return null; + } + + if (input.criteriaFilter.filterType === FilterType.NotRecievedInMinutes) { + if (value && differenceInMinutes > (value as number)) { + return `Incoming request / heartbeat not received in ${value} minutes.`; + } + return null; + } } + + if ( + input.criteriaFilter.checkOn === CheckOn.RequestBody && + !(input.dataToProcess as IncomingMonitorRequest) + .onlyCheckForIncomingRequestReceivedAt + ) { + let responseBody: string | JSONObject | undefined = ( + input.dataToProcess as IncomingMonitorRequest + ).requestBody; + + if (responseBody && typeof responseBody === Typeof.Object) { + responseBody = JSON.stringify(responseBody); + } + + if (!responseBody) { + return null; + } + + // contains + if (input.criteriaFilter.filterType === FilterType.Contains) { + if ( + value && + responseBody && + (responseBody as string).includes(value as string) + ) { + return `Request body contains ${value}.`; + } + return null; + } + + if (input.criteriaFilter.filterType === FilterType.NotContains) { + if ( + value && + responseBody && + !(responseBody as string).includes(value as string) + ) { + return `Request body does not contain ${value}.`; + } + return null; + } + } + + if ( + input.criteriaFilter.checkOn === CheckOn.RequestHeader && + !(input.dataToProcess as IncomingMonitorRequest) + .onlyCheckForIncomingRequestReceivedAt + ) { + const headerKeys: Array<string> = Object.keys( + (input.dataToProcess as IncomingMonitorRequest).requestHeaders || {}, + ).map((key: string) => { + return key.toLowerCase(); + }); + + // contains + if (input.criteriaFilter.filterType === FilterType.Contains) { + if (value && headerKeys && headerKeys.includes(value as string)) { + return `Request header contains ${value}.`; + } + return null; + } + + if (input.criteriaFilter.filterType === FilterType.NotContains) { + if (value && headerKeys && !headerKeys.includes(value as string)) { + return `Request header does not contain ${value}.`; + } + return null; + } + } + + if ( + input.criteriaFilter.checkOn === CheckOn.RequestHeaderValue && + !(input.dataToProcess as IncomingMonitorRequest) + .onlyCheckForIncomingRequestReceivedAt + ) { + const headerValues: Array<string> = Object.values( + (input.dataToProcess as IncomingMonitorRequest).requestHeaders || {}, + ).map((key: string) => { + return key.toLowerCase(); + }); + + // contains + if (input.criteriaFilter.filterType === FilterType.Contains) { + if (value && headerValues && headerValues.includes(value as string)) { + return `Request header value contains ${value}.`; + } + return null; + } + + if (input.criteriaFilter.filterType === FilterType.NotContains) { + if (value && headerValues && !headerValues.includes(value as string)) { + return `Request header value does not contain ${value}.`; + } + return null; + } + } + + return null; + } } diff --git a/CommonServer/Utils/Probe/Criteria/SSLMonitorCriteria.ts b/CommonServer/Utils/Probe/Criteria/SSLMonitorCriteria.ts index dda20771ae..ec8e13bbf1 100644 --- a/CommonServer/Utils/Probe/Criteria/SSLMonitorCriteria.ts +++ b/CommonServer/Utils/Probe/Criteria/SSLMonitorCriteria.ts @@ -1,181 +1,179 @@ -import DataToProcess from '../DataToProcess'; -import CompareCriteria from './CompareCriteria'; -import OneUptimeDate from 'Common/Types/Date'; +import DataToProcess from "../DataToProcess"; +import CompareCriteria from "./CompareCriteria"; +import OneUptimeDate from "Common/Types/Date"; import { - CheckOn, - CriteriaFilter, - FilterType, -} from 'Common/Types/Monitor/CriteriaFilter'; -import SslMonitorResponse from 'Common/Types/Monitor/SSLMonitor/SslMonitorResponse'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; + CheckOn, + CriteriaFilter, + FilterType, +} from "Common/Types/Monitor/CriteriaFilter"; +import SslMonitorResponse from "Common/Types/Monitor/SSLMonitor/SslMonitorResponse"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; export default class ServerMonitorCriteria { - public static async isMonitorInstanceCriteriaFilterMet(input: { - dataToProcess: DataToProcess; - criteriaFilter: CriteriaFilter; - }): Promise<string | null> { - let threshold: number | string | undefined | null = - input.criteriaFilter.value; + public static async isMonitorInstanceCriteriaFilterMet(input: { + dataToProcess: DataToProcess; + criteriaFilter: CriteriaFilter; + }): Promise<string | null> { + let threshold: number | string | undefined | null = + input.criteriaFilter.value; - const dataToProcess: ProbeMonitorResponse = - input.dataToProcess as ProbeMonitorResponse; + const dataToProcess: ProbeMonitorResponse = + input.dataToProcess as ProbeMonitorResponse; - const sslResponse: SslMonitorResponse | undefined = - dataToProcess.sslResponse; + const sslResponse: SslMonitorResponse | undefined = + dataToProcess.sslResponse; - if (input.criteriaFilter.checkOn === CheckOn.IsValidCertificate) { - const isValidCertificate: boolean = Boolean( - sslResponse && - dataToProcess.isOnline && - sslResponse.expiresAt && - !sslResponse.isSelfSigned && - OneUptimeDate.isAfter( - sslResponse.expiresAt, - OneUptimeDate.getCurrentDate() - ) - ); + if (input.criteriaFilter.checkOn === CheckOn.IsValidCertificate) { + const isValidCertificate: boolean = Boolean( + sslResponse && + dataToProcess.isOnline && + sslResponse.expiresAt && + !sslResponse.isSelfSigned && + OneUptimeDate.isAfter( + sslResponse.expiresAt, + OneUptimeDate.getCurrentDate(), + ), + ); - const isTrue: boolean = - input.criteriaFilter.filterType === FilterType.True; + const isTrue: boolean = + input.criteriaFilter.filterType === FilterType.True; - const isFalse: boolean = - input.criteriaFilter.filterType === FilterType.False; + const isFalse: boolean = + input.criteriaFilter.filterType === FilterType.False; - if (isValidCertificate && isTrue) { - return 'SSL certificate is valid.'; - } + if (isValidCertificate && isTrue) { + return "SSL certificate is valid."; + } - if (!isValidCertificate && isFalse) { - return 'SSL certificate is not valid.'; - } - } - - if (input.criteriaFilter.checkOn === CheckOn.IsSelfSignedCertificate) { - const isSelfSigned: boolean = Boolean( - sslResponse && sslResponse.isSelfSigned - ); - const isTrue: boolean = - input.criteriaFilter.filterType === FilterType.True; - - const isFalse: boolean = - input.criteriaFilter.filterType === FilterType.False; - - if (isSelfSigned && isTrue) { - return 'SSL Certificate is self signed.'; - } - - if (!isSelfSigned && isFalse) { - return 'SSL Certificate is not self signed.'; - } - } - - if (input.criteriaFilter.checkOn === CheckOn.IsExpiredCertificate) { - const isExpired: boolean = Boolean( - sslResponse && - sslResponse.expiresAt && - OneUptimeDate.isBefore( - sslResponse.expiresAt, - OneUptimeDate.getCurrentDate() - ) - ); - - const isTrue: boolean = - input.criteriaFilter.filterType === FilterType.True; - - const isFalse: boolean = - input.criteriaFilter.filterType === FilterType.False; - - if (isExpired && isTrue) { - return 'SSL certificate is expired.'; - } - - if (!isExpired && isFalse) { - return 'SSL certificate is not expired.'; - } - } - - if (input.criteriaFilter.checkOn === CheckOn.IsNotAValidCertificate) { - const isNotValid: boolean = - !sslResponse || - !dataToProcess.isOnline || - Boolean( - sslResponse && - sslResponse.expiresAt && - (sslResponse.isSelfSigned || - OneUptimeDate.isBefore( - sslResponse.expiresAt, - OneUptimeDate.getCurrentDate() - )) - ); - const isTrue: boolean = - input.criteriaFilter.filterType === FilterType.True; - - const isFalse: boolean = - input.criteriaFilter.filterType === FilterType.False; - - if (isNotValid && isTrue) { - return 'SSL certificate is not valid.'; - } - - if (!isNotValid && isFalse) { - return 'SSL certificate is valid.'; - } - } - - if (input.criteriaFilter.checkOn === CheckOn.ExpiresInHours) { - threshold = CompareCriteria.convertToNumber(threshold); - - if (!threshold) { - return null; - } - - const expiresAt: Date | undefined = - sslResponse && sslResponse.expiresAt; - const hours: number | undefined = - expiresAt && - OneUptimeDate.getHoursBetweenTwoDates( - OneUptimeDate.getCurrentDate(), - expiresAt - ); - - if (hours === null || hours === undefined) { - return null; - } - - return CompareCriteria.compareCriteriaNumbers({ - value: hours, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } - - if (input.criteriaFilter.checkOn === CheckOn.ExpiresInDays) { - threshold = CompareCriteria.convertToNumber(threshold); - - if (!threshold) { - return null; - } - - const expiresAt: Date | undefined = - sslResponse && sslResponse.expiresAt; - const days: number | undefined = - expiresAt && - OneUptimeDate.getDaysBetweenTwoDates( - OneUptimeDate.getCurrentDate(), - expiresAt - ); - - if (days === null || days === undefined) { - return null; - } - - return CompareCriteria.compareCriteriaNumbers({ - value: days, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } - - return null; + if (!isValidCertificate && isFalse) { + return "SSL certificate is not valid."; + } } + + if (input.criteriaFilter.checkOn === CheckOn.IsSelfSignedCertificate) { + const isSelfSigned: boolean = Boolean( + sslResponse && sslResponse.isSelfSigned, + ); + const isTrue: boolean = + input.criteriaFilter.filterType === FilterType.True; + + const isFalse: boolean = + input.criteriaFilter.filterType === FilterType.False; + + if (isSelfSigned && isTrue) { + return "SSL Certificate is self signed."; + } + + if (!isSelfSigned && isFalse) { + return "SSL Certificate is not self signed."; + } + } + + if (input.criteriaFilter.checkOn === CheckOn.IsExpiredCertificate) { + const isExpired: boolean = Boolean( + sslResponse && + sslResponse.expiresAt && + OneUptimeDate.isBefore( + sslResponse.expiresAt, + OneUptimeDate.getCurrentDate(), + ), + ); + + const isTrue: boolean = + input.criteriaFilter.filterType === FilterType.True; + + const isFalse: boolean = + input.criteriaFilter.filterType === FilterType.False; + + if (isExpired && isTrue) { + return "SSL certificate is expired."; + } + + if (!isExpired && isFalse) { + return "SSL certificate is not expired."; + } + } + + if (input.criteriaFilter.checkOn === CheckOn.IsNotAValidCertificate) { + const isNotValid: boolean = + !sslResponse || + !dataToProcess.isOnline || + Boolean( + sslResponse && + sslResponse.expiresAt && + (sslResponse.isSelfSigned || + OneUptimeDate.isBefore( + sslResponse.expiresAt, + OneUptimeDate.getCurrentDate(), + )), + ); + const isTrue: boolean = + input.criteriaFilter.filterType === FilterType.True; + + const isFalse: boolean = + input.criteriaFilter.filterType === FilterType.False; + + if (isNotValid && isTrue) { + return "SSL certificate is not valid."; + } + + if (!isNotValid && isFalse) { + return "SSL certificate is valid."; + } + } + + if (input.criteriaFilter.checkOn === CheckOn.ExpiresInHours) { + threshold = CompareCriteria.convertToNumber(threshold); + + if (!threshold) { + return null; + } + + const expiresAt: Date | undefined = sslResponse && sslResponse.expiresAt; + const hours: number | undefined = + expiresAt && + OneUptimeDate.getHoursBetweenTwoDates( + OneUptimeDate.getCurrentDate(), + expiresAt, + ); + + if (hours === null || hours === undefined) { + return null; + } + + return CompareCriteria.compareCriteriaNumbers({ + value: hours, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); + } + + if (input.criteriaFilter.checkOn === CheckOn.ExpiresInDays) { + threshold = CompareCriteria.convertToNumber(threshold); + + if (!threshold) { + return null; + } + + const expiresAt: Date | undefined = sslResponse && sslResponse.expiresAt; + const days: number | undefined = + expiresAt && + OneUptimeDate.getDaysBetweenTwoDates( + OneUptimeDate.getCurrentDate(), + expiresAt, + ); + + if (days === null || days === undefined) { + return null; + } + + return CompareCriteria.compareCriteriaNumbers({ + value: days, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); + } + + return null; + } } diff --git a/CommonServer/Utils/Probe/Criteria/ServerMonitorCriteria.ts b/CommonServer/Utils/Probe/Criteria/ServerMonitorCriteria.ts index a580a1a6de..190ac2013b 100644 --- a/CommonServer/Utils/Probe/Criteria/ServerMonitorCriteria.ts +++ b/CommonServer/Utils/Probe/Criteria/ServerMonitorCriteria.ts @@ -1,291 +1,280 @@ -import DataToProcess from '../DataToProcess'; -import CompareCriteria from './CompareCriteria'; -import EvaluateOverTime from './EvaluateOverTime'; -import OneUptimeDate from 'Common/Types/Date'; -import { BasicDiskMetrics } from 'Common/Types/Infrastructure/BasicMetrics'; -import { JSONObject } from 'Common/Types/JSON'; +import DataToProcess from "../DataToProcess"; +import CompareCriteria from "./CompareCriteria"; +import EvaluateOverTime from "./EvaluateOverTime"; +import OneUptimeDate from "Common/Types/Date"; +import { BasicDiskMetrics } from "Common/Types/Infrastructure/BasicMetrics"; +import { JSONObject } from "Common/Types/JSON"; import { - CheckOn, - CriteriaFilter, - FilterType, -} from 'Common/Types/Monitor/CriteriaFilter'; + CheckOn, + CriteriaFilter, + FilterType, +} from "Common/Types/Monitor/CriteriaFilter"; import ServerMonitorResponse, { - ServerProcess, -} from 'Common/Types/Monitor/ServerMonitor/ServerMonitorResponse'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; + ServerProcess, +} from "Common/Types/Monitor/ServerMonitor/ServerMonitorResponse"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; export default class ServerMonitorCriteria { - public static async isMonitorInstanceCriteriaFilterMet(input: { - dataToProcess: DataToProcess; - criteriaFilter: CriteriaFilter; - }): Promise<string | null> { - // Server Monitoring Checks + public static async isMonitorInstanceCriteriaFilterMet(input: { + dataToProcess: DataToProcess; + criteriaFilter: CriteriaFilter; + }): Promise<string | null> { + // Server Monitoring Checks - let threshold: number | string | undefined | null = - input.criteriaFilter.value; - let overTimeValue: Array<number> | number | undefined = undefined; + let threshold: number | string | undefined | null = + input.criteriaFilter.value; + let overTimeValue: Array<number> | number | undefined = undefined; - if ( - input.criteriaFilter.eveluateOverTime && - input.criteriaFilter.evaluateOverTimeOptions - ) { - overTimeValue = await EvaluateOverTime.getValueOverTime({ - monitorId: input.dataToProcess.monitorId!, - evaluateOverTimeOptions: - input.criteriaFilter.evaluateOverTimeOptions, - metricType: input.criteriaFilter.checkOn, - miscData: input.criteriaFilter - .serverMonitorOptions as JSONObject, - }); + if ( + input.criteriaFilter.eveluateOverTime && + input.criteriaFilter.evaluateOverTimeOptions + ) { + overTimeValue = await EvaluateOverTime.getValueOverTime({ + monitorId: input.dataToProcess.monitorId!, + evaluateOverTimeOptions: input.criteriaFilter.evaluateOverTimeOptions, + metricType: input.criteriaFilter.checkOn, + miscData: input.criteriaFilter.serverMonitorOptions as JSONObject, + }); - if (Array.isArray(overTimeValue) && overTimeValue.length === 0) { - return null; - } + if (Array.isArray(overTimeValue) && overTimeValue.length === 0) { + return null; + } - if (overTimeValue === undefined) { - return null; - } - } + if (overTimeValue === undefined) { + return null; + } + } - if ( - (input.dataToProcess as ServerMonitorResponse) - .onlyCheckRequestReceivedAt - ) { - const lastCheckTime: Date = ( - input.dataToProcess as ServerMonitorResponse - ).requestReceivedAt; + if ( + (input.dataToProcess as ServerMonitorResponse).onlyCheckRequestReceivedAt + ) { + const lastCheckTime: Date = (input.dataToProcess as ServerMonitorResponse) + .requestReceivedAt; - const differenceInMinutes: number = - OneUptimeDate.getDifferenceInMinutes( - lastCheckTime, - OneUptimeDate.getCurrentDate() - ); + const differenceInMinutes: number = OneUptimeDate.getDifferenceInMinutes( + lastCheckTime, + OneUptimeDate.getCurrentDate(), + ); - const offlineIfNotCheckedInMinutes: number = 2; + const offlineIfNotCheckedInMinutes: number = 2; - if ( - input.criteriaFilter.checkOn === CheckOn.IsOnline && - input.criteriaFilter.filterType === FilterType.True && - differenceInMinutes <= offlineIfNotCheckedInMinutes - ) { - if ((input.dataToProcess as ProbeMonitorResponse).isOnline) { - return 'Monitor is online.'; - } - - return null; - } - - if ( - input.criteriaFilter.checkOn === CheckOn.IsOnline && - input.criteriaFilter.filterType === FilterType.False && - differenceInMinutes > offlineIfNotCheckedInMinutes - ) { - if (!(input.dataToProcess as ProbeMonitorResponse).isOnline) { - return 'Monitor is offline.'; - } - return null; - } - } - - if ( - input.criteriaFilter.checkOn === CheckOn.CPUUsagePercent && - !(input.dataToProcess as ServerMonitorResponse) - .onlyCheckRequestReceivedAt - ) { - threshold = CompareCriteria.convertToNumber(threshold); - - const currentCpuPercent: number | Array<number> = - overTimeValue || - (input.dataToProcess as ServerMonitorResponse) - .basicInfrastructureMetrics?.cpuMetrics.percentUsed || - 0; - - return CompareCriteria.compareCriteriaNumbers({ - value: currentCpuPercent, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } - - if ( - input.criteriaFilter.checkOn === CheckOn.MemoryUsagePercent && - !(input.dataToProcess as ServerMonitorResponse) - .onlyCheckRequestReceivedAt - ) { - threshold = CompareCriteria.convertToNumber(threshold); - - const memoryPercent: number | Array<number> = - overTimeValue || - (input.dataToProcess as ServerMonitorResponse) - .basicInfrastructureMetrics?.memoryMetrics.percentUsed || - 0; - - return CompareCriteria.compareCriteriaNumbers({ - value: memoryPercent, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } - - if ( - input.criteriaFilter.checkOn === CheckOn.DiskUsagePercent && - !(input.dataToProcess as ServerMonitorResponse) - .onlyCheckRequestReceivedAt - ) { - threshold = CompareCriteria.convertToNumber(threshold); - - const diskPath: string = - input.criteriaFilter.serverMonitorOptions?.diskPath || '/'; - - const diskPercent: number = - ( - input.dataToProcess as ServerMonitorResponse - ).basicInfrastructureMetrics?.diskMetrics.filter( - (item: BasicDiskMetrics) => { - return ( - item.diskPath.trim().toLowerCase() === - diskPath.trim().toLowerCase() - ); - } - )[0]?.percentFree || 0; - - return CompareCriteria.compareCriteriaNumbers({ - value: diskPercent, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } - - if ( - input.criteriaFilter.checkOn === CheckOn.ServerProcessName && - threshold && - !(input.dataToProcess as ServerMonitorResponse) - .onlyCheckRequestReceivedAt - ) { - const thresholdProcessName: string = threshold - .toString() - .trim() - .toLowerCase(); - - if (input.criteriaFilter.filterType === FilterType.IsExecuting) { - const processNames: Array<string> = - ( - input.dataToProcess as ServerMonitorResponse - )?.processes?.map((item: ServerProcess) => { - return item.name.trim().toLowerCase(); - }) || []; - - if (processNames.includes(thresholdProcessName)) { - return `Process ${threshold} is executing.`; - } - - return null; - } - - if (input.criteriaFilter.filterType === FilterType.IsNotExecuting) { - const processNames: Array<string> = - ( - input.dataToProcess as ServerMonitorResponse - )?.processes?.map((item: ServerProcess) => { - return item.name.trim().toLowerCase(); - }) || []; - - if (!processNames.includes(thresholdProcessName)) { - return `Process ${threshold} is not executing.`; - } - - return null; - } - } - - if ( - input.criteriaFilter.checkOn === CheckOn.ServerProcessPID && - threshold && - !(input.dataToProcess as ServerMonitorResponse) - .onlyCheckRequestReceivedAt - ) { - const thresholdProcessPID: string = threshold - .toString() - .trim() - .toLowerCase(); - - if (input.criteriaFilter.filterType === FilterType.IsExecuting) { - const processPIDs: Array<string> = - ( - input.dataToProcess as ServerMonitorResponse - )?.processes?.map((item: ServerProcess) => { - return item.pid.toString().trim().toLowerCase(); - }) || []; - - if (processPIDs.includes(thresholdProcessPID)) { - return `Process with PID ${threshold} is executing.`; - } - - return null; - } - - if (input.criteriaFilter.filterType === FilterType.IsNotExecuting) { - const processPIDs: Array<string> = - ( - input.dataToProcess as ServerMonitorResponse - )?.processes?.map((item: ServerProcess) => { - return item.pid.toString().trim().toLowerCase(); - }) || []; - - if (!processPIDs.includes(thresholdProcessPID)) { - return `Process with PID ${threshold} is not executing.`; - } - - return null; - } - - return null; - } - - if ( - input.criteriaFilter.checkOn === CheckOn.ServerProcessCommand && - threshold && - !(input.dataToProcess as ServerMonitorResponse) - .onlyCheckRequestReceivedAt - ) { - const thresholdProcessCommand: string = threshold - .toString() - .trim() - .toLowerCase(); - - if (input.criteriaFilter.filterType === FilterType.IsExecuting) { - const processCommands: Array<string> = - ( - input.dataToProcess as ServerMonitorResponse - )?.processes?.map((item: ServerProcess) => { - return item.command.trim().toLowerCase(); - }) || []; - - if (processCommands.includes(thresholdProcessCommand)) { - return `Process with command ${threshold} is executing.`; - } - - return null; - } - - if (input.criteriaFilter.filterType === FilterType.IsNotExecuting) { - const processCommands: Array<string> = - ( - input.dataToProcess as ServerMonitorResponse - )?.processes?.map((item: ServerProcess) => { - return item.command.trim().toLowerCase(); - }) || []; - - if (!processCommands.includes(thresholdProcessCommand)) { - return `Process with command ${threshold} is not executing.`; - } - - return null; - } - - return null; + if ( + input.criteriaFilter.checkOn === CheckOn.IsOnline && + input.criteriaFilter.filterType === FilterType.True && + differenceInMinutes <= offlineIfNotCheckedInMinutes + ) { + if ((input.dataToProcess as ProbeMonitorResponse).isOnline) { + return "Monitor is online."; } return null; + } + + if ( + input.criteriaFilter.checkOn === CheckOn.IsOnline && + input.criteriaFilter.filterType === FilterType.False && + differenceInMinutes > offlineIfNotCheckedInMinutes + ) { + if (!(input.dataToProcess as ProbeMonitorResponse).isOnline) { + return "Monitor is offline."; + } + return null; + } } + + if ( + input.criteriaFilter.checkOn === CheckOn.CPUUsagePercent && + !(input.dataToProcess as ServerMonitorResponse).onlyCheckRequestReceivedAt + ) { + threshold = CompareCriteria.convertToNumber(threshold); + + const currentCpuPercent: number | Array<number> = + overTimeValue || + (input.dataToProcess as ServerMonitorResponse) + .basicInfrastructureMetrics?.cpuMetrics.percentUsed || + 0; + + return CompareCriteria.compareCriteriaNumbers({ + value: currentCpuPercent, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); + } + + if ( + input.criteriaFilter.checkOn === CheckOn.MemoryUsagePercent && + !(input.dataToProcess as ServerMonitorResponse).onlyCheckRequestReceivedAt + ) { + threshold = CompareCriteria.convertToNumber(threshold); + + const memoryPercent: number | Array<number> = + overTimeValue || + (input.dataToProcess as ServerMonitorResponse) + .basicInfrastructureMetrics?.memoryMetrics.percentUsed || + 0; + + return CompareCriteria.compareCriteriaNumbers({ + value: memoryPercent, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); + } + + if ( + input.criteriaFilter.checkOn === CheckOn.DiskUsagePercent && + !(input.dataToProcess as ServerMonitorResponse).onlyCheckRequestReceivedAt + ) { + threshold = CompareCriteria.convertToNumber(threshold); + + const diskPath: string = + input.criteriaFilter.serverMonitorOptions?.diskPath || "/"; + + const diskPercent: number = + ( + input.dataToProcess as ServerMonitorResponse + ).basicInfrastructureMetrics?.diskMetrics.filter( + (item: BasicDiskMetrics) => { + return ( + item.diskPath.trim().toLowerCase() === + diskPath.trim().toLowerCase() + ); + }, + )[0]?.percentFree || 0; + + return CompareCriteria.compareCriteriaNumbers({ + value: diskPercent, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); + } + + if ( + input.criteriaFilter.checkOn === CheckOn.ServerProcessName && + threshold && + !(input.dataToProcess as ServerMonitorResponse).onlyCheckRequestReceivedAt + ) { + const thresholdProcessName: string = threshold + .toString() + .trim() + .toLowerCase(); + + if (input.criteriaFilter.filterType === FilterType.IsExecuting) { + const processNames: Array<string> = + (input.dataToProcess as ServerMonitorResponse)?.processes?.map( + (item: ServerProcess) => { + return item.name.trim().toLowerCase(); + }, + ) || []; + + if (processNames.includes(thresholdProcessName)) { + return `Process ${threshold} is executing.`; + } + + return null; + } + + if (input.criteriaFilter.filterType === FilterType.IsNotExecuting) { + const processNames: Array<string> = + (input.dataToProcess as ServerMonitorResponse)?.processes?.map( + (item: ServerProcess) => { + return item.name.trim().toLowerCase(); + }, + ) || []; + + if (!processNames.includes(thresholdProcessName)) { + return `Process ${threshold} is not executing.`; + } + + return null; + } + } + + if ( + input.criteriaFilter.checkOn === CheckOn.ServerProcessPID && + threshold && + !(input.dataToProcess as ServerMonitorResponse).onlyCheckRequestReceivedAt + ) { + const thresholdProcessPID: string = threshold + .toString() + .trim() + .toLowerCase(); + + if (input.criteriaFilter.filterType === FilterType.IsExecuting) { + const processPIDs: Array<string> = + (input.dataToProcess as ServerMonitorResponse)?.processes?.map( + (item: ServerProcess) => { + return item.pid.toString().trim().toLowerCase(); + }, + ) || []; + + if (processPIDs.includes(thresholdProcessPID)) { + return `Process with PID ${threshold} is executing.`; + } + + return null; + } + + if (input.criteriaFilter.filterType === FilterType.IsNotExecuting) { + const processPIDs: Array<string> = + (input.dataToProcess as ServerMonitorResponse)?.processes?.map( + (item: ServerProcess) => { + return item.pid.toString().trim().toLowerCase(); + }, + ) || []; + + if (!processPIDs.includes(thresholdProcessPID)) { + return `Process with PID ${threshold} is not executing.`; + } + + return null; + } + + return null; + } + + if ( + input.criteriaFilter.checkOn === CheckOn.ServerProcessCommand && + threshold && + !(input.dataToProcess as ServerMonitorResponse).onlyCheckRequestReceivedAt + ) { + const thresholdProcessCommand: string = threshold + .toString() + .trim() + .toLowerCase(); + + if (input.criteriaFilter.filterType === FilterType.IsExecuting) { + const processCommands: Array<string> = + (input.dataToProcess as ServerMonitorResponse)?.processes?.map( + (item: ServerProcess) => { + return item.command.trim().toLowerCase(); + }, + ) || []; + + if (processCommands.includes(thresholdProcessCommand)) { + return `Process with command ${threshold} is executing.`; + } + + return null; + } + + if (input.criteriaFilter.filterType === FilterType.IsNotExecuting) { + const processCommands: Array<string> = + (input.dataToProcess as ServerMonitorResponse)?.processes?.map( + (item: ServerProcess) => { + return item.command.trim().toLowerCase(); + }, + ) || []; + + if (!processCommands.includes(thresholdProcessCommand)) { + return `Process with command ${threshold} is not executing.`; + } + + return null; + } + + return null; + } + + return null; + } } diff --git a/CommonServer/Utils/Probe/Criteria/SyntheticMonitor.ts b/CommonServer/Utils/Probe/Criteria/SyntheticMonitor.ts index 2dbbdd4134..e1621f86e9 100644 --- a/CommonServer/Utils/Probe/Criteria/SyntheticMonitor.ts +++ b/CommonServer/Utils/Probe/Criteria/SyntheticMonitor.ts @@ -1,49 +1,47 @@ -import CompareCriteria from './CompareCriteria'; -import CustomCodeMonitoringCriteria from './CustomCodeMonitorCriteria'; -import { CheckOn, CriteriaFilter } from 'Common/Types/Monitor/CriteriaFilter'; -import SyntheticMonitorResponse from 'Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse'; +import CompareCriteria from "./CompareCriteria"; +import CustomCodeMonitoringCriteria from "./CustomCodeMonitorCriteria"; +import { CheckOn, CriteriaFilter } from "Common/Types/Monitor/CriteriaFilter"; +import SyntheticMonitorResponse from "Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse"; export default class SyntheticMonitoringCriteria { - public static async isMonitorInstanceCriteriaFilterMet(input: { - monitorResponse: Array<SyntheticMonitorResponse>; - criteriaFilter: CriteriaFilter; - }): Promise<string | null> { - for (const syntheticMonitorResponse of input.monitorResponse) { - const threshold: number | string | undefined | null = - input.criteriaFilter.value; + public static async isMonitorInstanceCriteriaFilterMet(input: { + monitorResponse: Array<SyntheticMonitorResponse>; + criteriaFilter: CriteriaFilter; + }): Promise<string | null> { + for (const syntheticMonitorResponse of input.monitorResponse) { + const threshold: number | string | undefined | null = + input.criteriaFilter.value; - // check custom code monitoring criteria first - const result: string | null = - await CustomCodeMonitoringCriteria.isMonitorInstanceCriteriaFilterMet( - { - monitorResponse: syntheticMonitorResponse, - criteriaFilter: input.criteriaFilter, - } - ); + // check custom code monitoring criteria first + const result: string | null = + await CustomCodeMonitoringCriteria.isMonitorInstanceCriteriaFilterMet({ + monitorResponse: syntheticMonitorResponse, + criteriaFilter: input.criteriaFilter, + }); - if (result) { - return result; - } + if (result) { + return result; + } - // check browser type and screen type. + // check browser type and screen type. - if (CheckOn.ScreenSizeType === input.criteriaFilter.checkOn) { - return CompareCriteria.checkEqualToOrNotEqualTo({ - value: syntheticMonitorResponse.screenSizeType, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } + if (CheckOn.ScreenSizeType === input.criteriaFilter.checkOn) { + return CompareCriteria.checkEqualToOrNotEqualTo({ + value: syntheticMonitorResponse.screenSizeType, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); + } - if (CheckOn.BrowserType === input.criteriaFilter.checkOn) { - return CompareCriteria.checkEqualToOrNotEqualTo({ - value: syntheticMonitorResponse.browserType, - threshold: threshold as number, - criteriaFilter: input.criteriaFilter, - }); - } - } - - return null; + if (CheckOn.BrowserType === input.criteriaFilter.checkOn) { + return CompareCriteria.checkEqualToOrNotEqualTo({ + value: syntheticMonitorResponse.browserType, + threshold: threshold as number, + criteriaFilter: input.criteriaFilter, + }); + } } + + return null; + } } diff --git a/CommonServer/Utils/Probe/DataToProcess.ts b/CommonServer/Utils/Probe/DataToProcess.ts index 15bfa00128..f808c856f6 100644 --- a/CommonServer/Utils/Probe/DataToProcess.ts +++ b/CommonServer/Utils/Probe/DataToProcess.ts @@ -1,10 +1,10 @@ -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; -import ServerMonitorResponse from 'Common/Types/Monitor/ServerMonitor/ServerMonitorResponse'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; +import ServerMonitorResponse from "Common/Types/Monitor/ServerMonitor/ServerMonitorResponse"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; type DataToProcess = - | ProbeMonitorResponse - | IncomingMonitorRequest - | ServerMonitorResponse; + | ProbeMonitorResponse + | IncomingMonitorRequest + | ServerMonitorResponse; export default DataToProcess; diff --git a/CommonServer/Utils/Probe/ProbeMonitorResponse.ts b/CommonServer/Utils/Probe/ProbeMonitorResponse.ts index 1031a0c372..3edb8121ea 100644 --- a/CommonServer/Utils/Probe/ProbeMonitorResponse.ts +++ b/CommonServer/Utils/Probe/ProbeMonitorResponse.ts @@ -1,1224 +1,1159 @@ -import IncidentService from '../../Services/IncidentService'; -import IncidentSeverityService from '../../Services/IncidentSeverityService'; -import IncidentStateTimelineService from '../../Services/IncidentStateTimelineService'; -import MonitorMetricsByMinuteService from '../../Services/MonitorMetricsByMinuteService'; -import MonitorProbeService from '../../Services/MonitorProbeService'; -import MonitorService from '../../Services/MonitorService'; -import MonitorStatusTimelineService from '../../Services/MonitorStatusTimelineService'; -import logger from '../../Utils/Logger'; -import VMUtil from '../VM/VMAPI'; -import APIRequestCriteria from './Criteria/APIRequestCriteria'; -import CustomCodeMonitoringCriteria from './Criteria/CustomCodeMonitorCriteria'; -import IncomingRequestCriteria from './Criteria/IncomingRequestCriteria'; -import SSLMonitorCriteria from './Criteria/SSLMonitorCriteria'; -import ServerMonitorCriteria from './Criteria/ServerMonitorCriteria'; -import SyntheticMonitoringCriteria from './Criteria/SyntheticMonitor'; -import DataToProcess from './DataToProcess'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import BasicInfrastructureMetrics from 'Common/Types/Infrastructure/BasicMetrics'; -import ReturnResult from 'Common/Types/IsolatedVM/ReturnResult'; -import { JSONObject } from 'Common/Types/JSON'; +import IncidentService from "../../Services/IncidentService"; +import IncidentSeverityService from "../../Services/IncidentSeverityService"; +import IncidentStateTimelineService from "../../Services/IncidentStateTimelineService"; +import MonitorMetricsByMinuteService from "../../Services/MonitorMetricsByMinuteService"; +import MonitorProbeService from "../../Services/MonitorProbeService"; +import MonitorService from "../../Services/MonitorService"; +import MonitorStatusTimelineService from "../../Services/MonitorStatusTimelineService"; +import logger from "../../Utils/Logger"; +import VMUtil from "../VM/VMAPI"; +import APIRequestCriteria from "./Criteria/APIRequestCriteria"; +import CustomCodeMonitoringCriteria from "./Criteria/CustomCodeMonitorCriteria"; +import IncomingRequestCriteria from "./Criteria/IncomingRequestCriteria"; +import SSLMonitorCriteria from "./Criteria/SSLMonitorCriteria"; +import ServerMonitorCriteria from "./Criteria/ServerMonitorCriteria"; +import SyntheticMonitoringCriteria from "./Criteria/SyntheticMonitor"; +import DataToProcess from "./DataToProcess"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import BasicInfrastructureMetrics from "Common/Types/Infrastructure/BasicMetrics"; +import ReturnResult from "Common/Types/IsolatedVM/ReturnResult"; +import { JSONObject } from "Common/Types/JSON"; import { - CheckOn, - CriteriaFilter, - FilterCondition, - FilterType, -} from 'Common/Types/Monitor/CriteriaFilter'; -import CustomCodeMonitorResponse from 'Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse'; -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; -import MonitorCriteria from 'Common/Types/Monitor/MonitorCriteria'; -import MonitorCriteriaInstance from 'Common/Types/Monitor/MonitorCriteriaInstance'; -import MonitorStep from 'Common/Types/Monitor/MonitorStep'; -import MonitorSteps from 'Common/Types/Monitor/MonitorSteps'; + CheckOn, + CriteriaFilter, + FilterCondition, + FilterType, +} from "Common/Types/Monitor/CriteriaFilter"; +import CustomCodeMonitorResponse from "Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse"; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; +import MonitorCriteria from "Common/Types/Monitor/MonitorCriteria"; +import MonitorCriteriaInstance from "Common/Types/Monitor/MonitorCriteriaInstance"; +import MonitorStep from "Common/Types/Monitor/MonitorStep"; +import MonitorSteps from "Common/Types/Monitor/MonitorSteps"; import MonitorType, { - MonitorTypeHelper, -} from 'Common/Types/Monitor/MonitorType'; -import ServerMonitorResponse from 'Common/Types/Monitor/ServerMonitor/ServerMonitorResponse'; -import ObjectID from 'Common/Types/ObjectID'; -import ProbeApiIngestResponse from 'Common/Types/Probe/ProbeApiIngestResponse'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import Typeof from 'Common/Types/Typeof'; -import MonitorMetricsByMinute from 'Model/AnalyticsModels/MonitorMetricsByMinute'; -import Incident from 'Model/Models/Incident'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import Monitor from 'Model/Models/Monitor'; -import MonitorProbe from 'Model/Models/MonitorProbe'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; + MonitorTypeHelper, +} from "Common/Types/Monitor/MonitorType"; +import ServerMonitorResponse from "Common/Types/Monitor/ServerMonitor/ServerMonitorResponse"; +import ObjectID from "Common/Types/ObjectID"; +import ProbeApiIngestResponse from "Common/Types/Probe/ProbeApiIngestResponse"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import Typeof from "Common/Types/Typeof"; +import MonitorMetricsByMinute from "Model/AnalyticsModels/MonitorMetricsByMinute"; +import Incident from "Model/Models/Incident"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import Monitor from "Model/Models/Monitor"; +import MonitorProbe from "Model/Models/MonitorProbe"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; export default class ProbeMonitorResponseService { - public static async processProbeResponse( - dataToProcess: DataToProcess - ): Promise<ProbeApiIngestResponse> { - let response: ProbeApiIngestResponse = { - monitorId: dataToProcess.monitorId, - criteriaMetId: undefined, - rootCause: null, + public static async processProbeResponse( + dataToProcess: DataToProcess, + ): Promise<ProbeApiIngestResponse> { + let response: ProbeApiIngestResponse = { + monitorId: dataToProcess.monitorId, + criteriaMetId: undefined, + rootCause: null, + }; + + logger.debug("Processing probe response"); + logger.debug("Monitor ID: " + dataToProcess.monitorId); + + // fetch monitor + const monitor: Monitor | null = await MonitorService.findOneById({ + id: dataToProcess.monitorId, + select: { + monitorSteps: true, + monitorType: true, + projectId: true, + disableActiveMonitoring: true, + disableActiveMonitoringBecauseOfManualIncident: true, + disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: true, + currentMonitorStatusId: true, + _id: true, + }, + props: { + isRoot: true, + }, + }); + + if (!monitor) { + throw new BadDataException("Monitor not found"); + } + + if (monitor.disableActiveMonitoring) { + logger.debug( + `${dataToProcess.monitorId.toString()} Monitor is disabled. Please enable it to start monitoring again.`, + ); + + throw new BadDataException( + "Monitor is disabled. Please enable it to start monitoring again.", + ); + } + + if (monitor.disableActiveMonitoringBecauseOfManualIncident) { + logger.debug( + `${dataToProcess.monitorId.toString()} Monitor is disabled because an incident which is created manually is not resolved. Please resolve the incident to start monitoring again.`, + ); + + throw new BadDataException( + "Monitor is disabled because an incident which is created manually is not resolved. Please resolve the incident to start monitoring again.", + ); + } + + if (monitor.disableActiveMonitoringBecauseOfScheduledMaintenanceEvent) { + logger.debug( + `${dataToProcess.monitorId.toString()} Monitor is disabled because one of the scheduled maintenance event this monitor is attached to has not ended. Please end the scheduled maintenance event to start monitoring again.`, + ); + + throw new BadDataException( + "Monitor is disabled because one of the scheduled maintenance event this monitor is attached to has not ended. Please end the scheduled maintenance event to start monitoring again.", + ); + } + + // save the last log to MonitorProbe. + + // get last log. We do this because there are many monitoring steps and we need to store those. + logger.debug( + `${dataToProcess.monitorId.toString()} - monitor type ${ + monitor.monitorType + }`, + ); + + if ( + monitor.monitorType && + MonitorTypeHelper.isProbableMonitors(monitor.monitorType) + ) { + dataToProcess = dataToProcess as ProbeMonitorResponse; + if ((dataToProcess as ProbeMonitorResponse).probeId) { + const monitorProbe: MonitorProbe | null = + await MonitorProbeService.findOneBy({ + query: { + monitorId: monitor.id!, + probeId: (dataToProcess as ProbeMonitorResponse).probeId!, + }, + select: { + lastMonitoringLog: true, + }, + props: { + isRoot: true, + }, + }); + + if (!monitorProbe) { + throw new BadDataException("Probe is not assigned to this monitor"); + } + + await MonitorProbeService.updateOneBy({ + query: { + monitorId: monitor.id!, + probeId: (dataToProcess as ProbeMonitorResponse).probeId!, + }, + data: { + lastMonitoringLog: { + ...(monitorProbe.lastMonitoringLog || {}), + [( + dataToProcess as ProbeMonitorResponse + ).monitorStepId.toString()]: { + ...JSON.parse(JSON.stringify(dataToProcess)), + monitoredAt: OneUptimeDate.getCurrentDate(), + }, + } as any, + }, + props: { + isRoot: true, + }, + }); + } + } + + if ( + monitor.monitorType === MonitorType.IncomingRequest && + (dataToProcess as IncomingMonitorRequest).incomingRequestReceivedAt + ) { + await MonitorService.updateOneById({ + id: monitor.id!, + data: { + incomingMonitorRequest: dataToProcess as any, + incomingRequestReceivedAt: (dataToProcess as IncomingMonitorRequest) + .incomingRequestReceivedAt!, // this could be redundant as we are already saving this in the incomingMonitorRequest. we should remove this in the future. + }, + props: { + isRoot: true, + }, + }); + } + + if ( + monitor.monitorType === MonitorType.Server && + (dataToProcess as ServerMonitorResponse).requestReceivedAt + ) { + await MonitorService.updateOneById({ + id: monitor.id!, + data: { + serverMonitorRequestReceivedAt: ( + dataToProcess as ServerMonitorResponse + ).requestReceivedAt!, + serverMonitorResponse: dataToProcess as ServerMonitorResponse, // this could be redundant as we are already saving this in the incomingMonitorRequest. we should remove this in the future. + }, + props: { + isRoot: true, + }, + }); + } + + await this.saveMonitorMetrics({ + monitorId: monitor.id!, + projectId: monitor.projectId!, + dataToProcess: dataToProcess, + }); + + const monitorSteps: MonitorSteps = monitor.monitorSteps!; + + if ( + !monitorSteps.data?.monitorStepsInstanceArray || + monitorSteps.data?.monitorStepsInstanceArray.length === 0 + ) { + // no steps, ignore everything. This happens when the monitor is updated shortly after the probing attempt. + logger.debug( + `${dataToProcess.monitorId.toString()} - No monitoring steps.`, + ); + return response; + } + + // auto resolve criteria Id's. + + const criteriaInstances: Array<MonitorCriteriaInstance> = + monitorSteps.data.monitorStepsInstanceArray + .map((step: MonitorStep) => { + return step.data?.monitorCriteria; + }) + .filter((criteria: MonitorCriteria | undefined) => { + return Boolean(criteria); + }) + .map((criteria: MonitorCriteria | undefined) => { + return [...(criteria?.data?.monitorCriteriaInstanceArray || [])]; + }) + .flat(); + + const autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary< + Array<string> + > = {}; + const criteriaInstanceMap: Dictionary<MonitorCriteriaInstance> = {}; + for (const criteriaInstance of criteriaInstances) { + criteriaInstanceMap[criteriaInstance.data?.id || ""] = criteriaInstance; + + if ( + criteriaInstance.data?.incidents && + criteriaInstance.data?.incidents.length > 0 + ) { + for (const incidentTemplate of criteriaInstance.data!.incidents) { + if (incidentTemplate.autoResolveIncident) { + if ( + !autoResolveCriteriaInstanceIdIncidentIdsDictionary[ + criteriaInstance.data.id.toString() + ] + ) { + autoResolveCriteriaInstanceIdIncidentIdsDictionary[ + criteriaInstance.data.id.toString() + ] = []; + } + + autoResolveCriteriaInstanceIdIncidentIdsDictionary[ + criteriaInstance.data.id.toString() + ]?.push(incidentTemplate.id); + } + } + } + } + + const monitorStep: MonitorStep | undefined = + monitorSteps.data.monitorStepsInstanceArray[0]; + + if ((dataToProcess as ProbeMonitorResponse).monitorStepId) { + monitorSteps.data.monitorStepsInstanceArray.find( + (monitorStep: MonitorStep) => { + return ( + monitorStep.id.toString() === + (dataToProcess as ProbeMonitorResponse).monitorStepId.toString() + ); + }, + ); + } + + if (!monitorStep) { + // no steps, ignore everything. This happens when the monitor is updated shortly after the probing attempt. + return response; + } + + // now process the monitor step + + response.ingestedMonitorStepId = monitorStep.id; + + //find next monitor step after this one. + const nextMonitorStepIndex: number = + monitorSteps.data.monitorStepsInstanceArray.findIndex( + (step: MonitorStep) => { + return step.id.toString() === monitorStep.id.toString(); + }, + ); + + response.nextMonitorStepId = + monitorSteps.data.monitorStepsInstanceArray[nextMonitorStepIndex + 1]?.id; + + // now process probe response monitors + + response = await ProbeMonitorResponseService.processMonitorStep({ + dataToProcess: dataToProcess, + monitorStep: monitorStep, + monitor: monitor, + probeApiIngestResponse: response, + }); + + if (response.criteriaMetId && response.rootCause) { + logger.debug( + `${dataToProcess.monitorId.toString()} - Criteria met: ${ + response.criteriaMetId + }`, + ); + logger.debug( + `${dataToProcess.monitorId.toString()} - Root cause: ${ + response.rootCause + }`, + ); + + await this.criteriaMetCreateIncidentsAndUpdateMonitorStatus({ + monitor: monitor, + rootCause: response.rootCause, + dataToProcess: dataToProcess, + autoResolveCriteriaInstanceIdIncidentIdsDictionary, + criteriaInstance: criteriaInstanceMap[response.criteriaMetId!]!, + }); + } else if ( + !response.criteriaMetId && + monitorSteps.data.defaultMonitorStatusId && + monitor.currentMonitorStatusId?.toString() !== + monitorSteps.data.defaultMonitorStatusId.toString() + ) { + logger.debug( + `${dataToProcess.monitorId.toString()} - No criteria met. Change to default status.`, + ); + + await this.checkOpenIncidentsAndCloseIfResolved({ + monitorId: monitor.id!, + autoResolveCriteriaInstanceIdIncidentIdsDictionary, + rootCause: "No monitoring criteria met. Change to default status.", + criteriaInstance: null, // no criteria met! + dataToProcess: dataToProcess, + }); + + // if no criteria is met then update monitor to default state. + const monitorStatusTimeline: MonitorStatusTimeline = + new MonitorStatusTimeline(); + monitorStatusTimeline.monitorId = monitor.id!; + monitorStatusTimeline.monitorStatusId = + monitorSteps.data.defaultMonitorStatusId!; + monitorStatusTimeline.projectId = monitor.projectId!; + monitorStatusTimeline.statusChangeLog = JSON.parse( + JSON.stringify(dataToProcess), + ); + monitorStatusTimeline.rootCause = + "No monitoring criteria met. Change to default status. "; + await MonitorStatusTimelineService.create({ + data: monitorStatusTimeline, + props: { + isRoot: true, + }, + }); + } + + return response; + } + + public static async saveMonitorMetrics(data: { + monitorId: ObjectID; + projectId: ObjectID; + dataToProcess: DataToProcess; + }): Promise<void> { + if (!data.monitorId) { + return; + } + + if (!data.projectId) { + return; + } + + if (!data.dataToProcess) { + return; + } + + const itemsToSave: Array<MonitorMetricsByMinute> = []; + + if ( + (data.dataToProcess as ServerMonitorResponse).basicInfrastructureMetrics + ) { + // store cpu, memory, disk metrics. + + const basicMetrics: BasicInfrastructureMetrics | undefined = ( + data.dataToProcess as ServerMonitorResponse + ).basicInfrastructureMetrics; + + if (!basicMetrics) { + return; + } + + if (basicMetrics.cpuMetrics) { + const monitorMetricsByMinute: MonitorMetricsByMinute = + new MonitorMetricsByMinute(); + monitorMetricsByMinute.monitorId = data.monitorId; + monitorMetricsByMinute.projectId = data.projectId; + monitorMetricsByMinute.metricType = CheckOn.CPUUsagePercent; + monitorMetricsByMinute.metricValue = + basicMetrics.cpuMetrics.percentUsed; + + itemsToSave.push(monitorMetricsByMinute); + } + + if (basicMetrics.memoryMetrics) { + const monitorMetricsByMinute: MonitorMetricsByMinute = + new MonitorMetricsByMinute(); + monitorMetricsByMinute.monitorId = data.monitorId; + monitorMetricsByMinute.projectId = data.projectId; + monitorMetricsByMinute.metricType = CheckOn.MemoryUsagePercent; + monitorMetricsByMinute.metricValue = + basicMetrics.memoryMetrics.percentUsed; + + itemsToSave.push(monitorMetricsByMinute); + } + + if (basicMetrics.diskMetrics && basicMetrics.diskMetrics.length > 0) { + for (const diskMetric of basicMetrics.diskMetrics) { + const monitorMetricsByMinute: MonitorMetricsByMinute = + new MonitorMetricsByMinute(); + monitorMetricsByMinute.monitorId = data.monitorId; + monitorMetricsByMinute.projectId = data.projectId; + monitorMetricsByMinute.metricType = CheckOn.DiskUsagePercent; + monitorMetricsByMinute.metricValue = diskMetric.percentUsed; + monitorMetricsByMinute.miscData = { + diskPath: diskMetric.diskPath, + }; + + itemsToSave.push(monitorMetricsByMinute); + } + } + } + + if ((data.dataToProcess as ProbeMonitorResponse).responseTimeInMs) { + const monitorMetricsByMinute: MonitorMetricsByMinute = + new MonitorMetricsByMinute(); + monitorMetricsByMinute.monitorId = data.monitorId; + monitorMetricsByMinute.projectId = data.projectId; + monitorMetricsByMinute.metricType = CheckOn.ResponseTime; + monitorMetricsByMinute.metricValue = ( + data.dataToProcess as ProbeMonitorResponse + ).responseTimeInMs; + monitorMetricsByMinute.miscData = { + probeId: ( + data.dataToProcess as ProbeMonitorResponse + ).probeId.toString(), + }; + + itemsToSave.push(monitorMetricsByMinute); + } + + if ((data.dataToProcess as ProbeMonitorResponse).responseCode) { + const monitorMetricsByMinute: MonitorMetricsByMinute = + new MonitorMetricsByMinute(); + monitorMetricsByMinute.monitorId = data.monitorId; + monitorMetricsByMinute.projectId = data.projectId; + monitorMetricsByMinute.metricType = CheckOn.ResponseStatusCode; + monitorMetricsByMinute.metricValue = ( + data.dataToProcess as ProbeMonitorResponse + ).responseCode; + monitorMetricsByMinute.miscData = { + probeId: ( + data.dataToProcess as ProbeMonitorResponse + ).probeId.toString(), + }; + + itemsToSave.push(monitorMetricsByMinute); + } + + if ((data.dataToProcess as ProbeMonitorResponse).syntheticMonitorResponse) { + for (const syntheticMonitorResponse of ( + data.dataToProcess as ProbeMonitorResponse + ).syntheticMonitorResponse || []) { + const monitorMetricsByMinute: MonitorMetricsByMinute = + new MonitorMetricsByMinute(); + monitorMetricsByMinute.monitorId = data.monitorId; + monitorMetricsByMinute.projectId = data.projectId; + monitorMetricsByMinute.metricType = CheckOn.ExecutionTime; + monitorMetricsByMinute.metricValue = + syntheticMonitorResponse.executionTimeInMS; + monitorMetricsByMinute.miscData = { + probeId: ( + data.dataToProcess as ProbeMonitorResponse + ).probeId.toString(), + browserType: syntheticMonitorResponse.browserType, + screenSizeType: syntheticMonitorResponse.screenSizeType, }; - logger.debug('Processing probe response'); - logger.debug('Monitor ID: ' + dataToProcess.monitorId); + itemsToSave.push(monitorMetricsByMinute); + } + } - // fetch monitor - const monitor: Monitor | null = await MonitorService.findOneById({ - id: dataToProcess.monitorId, - select: { - monitorSteps: true, - monitorType: true, - projectId: true, - disableActiveMonitoring: true, - disableActiveMonitoringBecauseOfManualIncident: true, - disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: true, - currentMonitorStatusId: true, - _id: true, - }, - props: { - isRoot: true, - }, + if ( + (data.dataToProcess as ProbeMonitorResponse).customCodeMonitorResponse + ) { + const customCodeMonitorResponse: CustomCodeMonitorResponse = ( + data.dataToProcess as ProbeMonitorResponse + ).customCodeMonitorResponse!; + + const monitorMetricsByMinute: MonitorMetricsByMinute = + new MonitorMetricsByMinute(); + monitorMetricsByMinute.monitorId = data.monitorId; + monitorMetricsByMinute.projectId = data.projectId; + monitorMetricsByMinute.metricType = CheckOn.ExecutionTime; + monitorMetricsByMinute.metricValue = + customCodeMonitorResponse.executionTimeInMS; + monitorMetricsByMinute.miscData = { + probeId: ( + data.dataToProcess as ProbeMonitorResponse + ).probeId.toString(), + }; + + itemsToSave.push(monitorMetricsByMinute); + } + + await MonitorMetricsByMinuteService.createMany({ + items: itemsToSave, + props: { + isRoot: true, + }, + }); + } + + private static async checkOpenIncidentsAndCloseIfResolved(input: { + monitorId: ObjectID; + autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary< + Array<string> + >; + rootCause: string; + criteriaInstance: MonitorCriteriaInstance | null; + dataToProcess: DataToProcess; + }): Promise<Array<Incident>> { + // check active incidents and if there are open incidents, do not cretae anothr incident. + const openIncidents: Array<Incident> = await IncidentService.findBy({ + query: { + monitors: [input.monitorId] as any, + currentIncidentState: { + isResolvedState: false, + }, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + select: { + _id: true, + createdCriteriaId: true, + createdIncidentTemplateId: true, + projectId: true, + }, + props: { + isRoot: true, + }, + }); + + // check if should close the incident. + + for (const openIncident of openIncidents) { + const shouldClose: boolean = + ProbeMonitorResponseService.shouldCloseIncident({ + openIncident, + autoResolveCriteriaInstanceIdIncidentIdsDictionary: + input.autoResolveCriteriaInstanceIdIncidentIdsDictionary, + criteriaInstance: input.criteriaInstance, }); - if (!monitor) { - throw new BadDataException('Monitor not found'); - } + if (shouldClose) { + // then resolve incident. + await ProbeMonitorResponseService.resolveOpenIncident({ + openIncident: openIncident, + rootCause: input.rootCause, + dataToProcess: input.dataToProcess, + }); + } + } - if (monitor.disableActiveMonitoring) { - logger.debug( - `${dataToProcess.monitorId.toString()} Monitor is disabled. Please enable it to start monitoring again.` + return openIncidents; + } + + private static async criteriaMetCreateIncidentsAndUpdateMonitorStatus(input: { + criteriaInstance: MonitorCriteriaInstance; + monitor: Monitor; + dataToProcess: DataToProcess; + rootCause: string; + autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary< + Array<string> + >; + }): Promise<void> { + // criteria filters are met, now process the actions. + + if ( + input.criteriaInstance.data?.changeMonitorStatus && + input.criteriaInstance.data?.monitorStatusId && + input.criteriaInstance.data?.monitorStatusId.toString() !== + input.monitor.currentMonitorStatusId?.toString() + ) { + logger.debug( + `${input.monitor.id?.toString()} - Change monitor status to ${input.criteriaInstance.data?.monitorStatusId.toString()}`, + ); + // change monitor status + + const monitorStatusId: ObjectID | undefined = + input.criteriaInstance.data?.monitorStatusId; + + //change monitor status. + + const monitorStatusTimeline: MonitorStatusTimeline = + new MonitorStatusTimeline(); + monitorStatusTimeline.monitorId = input.monitor.id!; + monitorStatusTimeline.monitorStatusId = monitorStatusId; + monitorStatusTimeline.projectId = input.monitor.projectId!; + monitorStatusTimeline.statusChangeLog = JSON.parse( + JSON.stringify(input.dataToProcess), + ); + monitorStatusTimeline.rootCause = input.rootCause; + + await MonitorStatusTimelineService.create({ + data: monitorStatusTimeline, + props: { + isRoot: true, + }, + }); + } + + // check open incidents + logger.debug(`${input.monitor.id?.toString()} - Check open incidents.`); + // check active incidents and if there are open incidents, do not cretae anothr incident. + const openIncidents: Array<Incident> = + await this.checkOpenIncidentsAndCloseIfResolved({ + monitorId: input.monitor.id!, + autoResolveCriteriaInstanceIdIncidentIdsDictionary: + input.autoResolveCriteriaInstanceIdIncidentIdsDictionary, + rootCause: input.rootCause, + criteriaInstance: input.criteriaInstance, + dataToProcess: input.dataToProcess, + }); + + if (input.criteriaInstance.data?.createIncidents) { + // create incidents + + for (const criteriaIncident of input.criteriaInstance.data?.incidents || + []) { + // should create incident. + + const alreadyOpenIncident: Incident | undefined = openIncidents.find( + (incident: Incident) => { + return ( + incident.createdCriteriaId === + input.criteriaInstance.data?.id.toString() && + incident.createdIncidentTemplateId === + criteriaIncident.id.toString() ); - - throw new BadDataException( - 'Monitor is disabled. Please enable it to start monitoring again.' - ); - } - - if (monitor.disableActiveMonitoringBecauseOfManualIncident) { - logger.debug( - `${dataToProcess.monitorId.toString()} Monitor is disabled because an incident which is created manually is not resolved. Please resolve the incident to start monitoring again.` - ); - - throw new BadDataException( - 'Monitor is disabled because an incident which is created manually is not resolved. Please resolve the incident to start monitoring again.' - ); - } - - if (monitor.disableActiveMonitoringBecauseOfScheduledMaintenanceEvent) { - logger.debug( - `${dataToProcess.monitorId.toString()} Monitor is disabled because one of the scheduled maintenance event this monitor is attached to has not ended. Please end the scheduled maintenance event to start monitoring again.` - ); - - throw new BadDataException( - 'Monitor is disabled because one of the scheduled maintenance event this monitor is attached to has not ended. Please end the scheduled maintenance event to start monitoring again.' - ); - } - - // save the last log to MonitorProbe. - - // get last log. We do this because there are many monitoring steps and we need to store those. - logger.debug( - `${dataToProcess.monitorId.toString()} - monitor type ${ - monitor.monitorType - }` + }, ); - if ( - monitor.monitorType && - MonitorTypeHelper.isProbableMonitors(monitor.monitorType) - ) { - dataToProcess = dataToProcess as ProbeMonitorResponse; - if ((dataToProcess as ProbeMonitorResponse).probeId) { - const monitorProbe: MonitorProbe | null = - await MonitorProbeService.findOneBy({ - query: { - monitorId: monitor.id!, - probeId: (dataToProcess as ProbeMonitorResponse) - .probeId!, - }, - select: { - lastMonitoringLog: true, - }, - props: { - isRoot: true, - }, - }); + const hasAlreadyOpenIncident: boolean = Boolean(alreadyOpenIncident); - if (!monitorProbe) { - throw new BadDataException( - 'Probe is not assigned to this monitor' - ); - } + logger.debug( + `${input.monitor.id?.toString()} - Open Incident ${alreadyOpenIncident?.id?.toString()}`, + ); - await MonitorProbeService.updateOneBy({ - query: { - monitorId: monitor.id!, - probeId: (dataToProcess as ProbeMonitorResponse) - .probeId!, - }, - data: { - lastMonitoringLog: { - ...(monitorProbe.lastMonitoringLog || {}), - [( - dataToProcess as ProbeMonitorResponse - ).monitorStepId.toString()]: { - ...JSON.parse(JSON.stringify(dataToProcess)), - monitoredAt: OneUptimeDate.getCurrentDate(), - }, - } as any, - }, - props: { - isRoot: true, - }, - }); - } + logger.debug( + `${input.monitor.id?.toString()} - Has open incident ${hasAlreadyOpenIncident}`, + ); + + if (hasAlreadyOpenIncident) { + continue; } - if ( - monitor.monitorType === MonitorType.IncomingRequest && - (dataToProcess as IncomingMonitorRequest).incomingRequestReceivedAt - ) { - await MonitorService.updateOneById({ - id: monitor.id!, - data: { - incomingMonitorRequest: dataToProcess as any, - incomingRequestReceivedAt: ( - dataToProcess as IncomingMonitorRequest - ).incomingRequestReceivedAt!, // this could be redundant as we are already saving this in the incomingMonitorRequest. we should remove this in the future. - }, - props: { - isRoot: true, - }, - }); - } + // create incident here. - if ( - monitor.monitorType === MonitorType.Server && - (dataToProcess as ServerMonitorResponse).requestReceivedAt - ) { - await MonitorService.updateOneById({ - id: monitor.id!, - data: { - serverMonitorRequestReceivedAt: ( - dataToProcess as ServerMonitorResponse - ).requestReceivedAt!, - serverMonitorResponse: - dataToProcess as ServerMonitorResponse, // this could be redundant as we are already saving this in the incomingMonitorRequest. we should remove this in the future. - }, - props: { - isRoot: true, - }, - }); - } + logger.debug(`${input.monitor.id?.toString()} - Create incident.`); - await this.saveMonitorMetrics({ - monitorId: monitor.id!, - projectId: monitor.projectId!, - dataToProcess: dataToProcess, - }); + const incident: Incident = new Incident(); - const monitorSteps: MonitorSteps = monitor.monitorSteps!; + incident.title = criteriaIncident.title; + incident.description = criteriaIncident.description; - if ( - !monitorSteps.data?.monitorStepsInstanceArray || - monitorSteps.data?.monitorStepsInstanceArray.length === 0 - ) { - // no steps, ignore everything. This happens when the monitor is updated shortly after the probing attempt. - logger.debug( - `${dataToProcess.monitorId.toString()} - No monitoring steps.` - ); - return response; - } + if (!criteriaIncident.incidentSeverityId) { + // pick the critical criteria. - // auto resolve criteria Id's. - - const criteriaInstances: Array<MonitorCriteriaInstance> = - monitorSteps.data.monitorStepsInstanceArray - .map((step: MonitorStep) => { - return step.data?.monitorCriteria; - }) - .filter((criteria: MonitorCriteria | undefined) => { - return Boolean(criteria); - }) - .map((criteria: MonitorCriteria | undefined) => { - return [ - ...(criteria?.data?.monitorCriteriaInstanceArray || []), - ]; - }) - .flat(); - - const autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary< - Array<string> - > = {}; - const criteriaInstanceMap: Dictionary<MonitorCriteriaInstance> = {}; - for (const criteriaInstance of criteriaInstances) { - criteriaInstanceMap[criteriaInstance.data?.id || ''] = - criteriaInstance; - - if ( - criteriaInstance.data?.incidents && - criteriaInstance.data?.incidents.length > 0 - ) { - for (const incidentTemplate of criteriaInstance.data! - .incidents) { - if (incidentTemplate.autoResolveIncident) { - if ( - !autoResolveCriteriaInstanceIdIncidentIdsDictionary[ - criteriaInstance.data.id.toString() - ] - ) { - autoResolveCriteriaInstanceIdIncidentIdsDictionary[ - criteriaInstance.data.id.toString() - ] = []; - } - - autoResolveCriteriaInstanceIdIncidentIdsDictionary[ - criteriaInstance.data.id.toString() - ]?.push(incidentTemplate.id); - } - } - } - } - - const monitorStep: MonitorStep | undefined = - monitorSteps.data.monitorStepsInstanceArray[0]; - - if ((dataToProcess as ProbeMonitorResponse).monitorStepId) { - monitorSteps.data.monitorStepsInstanceArray.find( - (monitorStep: MonitorStep) => { - return ( - monitorStep.id.toString() === - ( - dataToProcess as ProbeMonitorResponse - ).monitorStepId.toString() - ); - } - ); - } - - if (!monitorStep) { - // no steps, ignore everything. This happens when the monitor is updated shortly after the probing attempt. - return response; - } - - // now process the monitor step - - response.ingestedMonitorStepId = monitorStep.id; - - //find next monitor step after this one. - const nextMonitorStepIndex: number = - monitorSteps.data.monitorStepsInstanceArray.findIndex( - (step: MonitorStep) => { - return step.id.toString() === monitorStep.id.toString(); - } - ); - - response.nextMonitorStepId = - monitorSteps.data.monitorStepsInstanceArray[ - nextMonitorStepIndex + 1 - ]?.id; - - // now process probe response monitors - - response = await ProbeMonitorResponseService.processMonitorStep({ - dataToProcess: dataToProcess, - monitorStep: monitorStep, - monitor: monitor, - probeApiIngestResponse: response, - }); - - if (response.criteriaMetId && response.rootCause) { - logger.debug( - `${dataToProcess.monitorId.toString()} - Criteria met: ${ - response.criteriaMetId - }` - ); - logger.debug( - `${dataToProcess.monitorId.toString()} - Root cause: ${ - response.rootCause - }` - ); - - await this.criteriaMetCreateIncidentsAndUpdateMonitorStatus({ - monitor: monitor, - rootCause: response.rootCause, - dataToProcess: dataToProcess, - autoResolveCriteriaInstanceIdIncidentIdsDictionary, - criteriaInstance: criteriaInstanceMap[response.criteriaMetId!]!, - }); - } else if ( - !response.criteriaMetId && - monitorSteps.data.defaultMonitorStatusId && - monitor.currentMonitorStatusId?.toString() !== - monitorSteps.data.defaultMonitorStatusId.toString() - ) { - logger.debug( - `${dataToProcess.monitorId.toString()} - No criteria met. Change to default status.` - ); - - await this.checkOpenIncidentsAndCloseIfResolved({ - monitorId: monitor.id!, - autoResolveCriteriaInstanceIdIncidentIdsDictionary, - rootCause: - 'No monitoring criteria met. Change to default status.', - criteriaInstance: null, // no criteria met! - dataToProcess: dataToProcess, - }); - - // if no criteria is met then update monitor to default state. - const monitorStatusTimeline: MonitorStatusTimeline = - new MonitorStatusTimeline(); - monitorStatusTimeline.monitorId = monitor.id!; - monitorStatusTimeline.monitorStatusId = - monitorSteps.data.defaultMonitorStatusId!; - monitorStatusTimeline.projectId = monitor.projectId!; - monitorStatusTimeline.statusChangeLog = JSON.parse( - JSON.stringify(dataToProcess) - ); - monitorStatusTimeline.rootCause = - 'No monitoring criteria met. Change to default status. '; - await MonitorStatusTimelineService.create({ - data: monitorStatusTimeline, - props: { - isRoot: true, - }, - }); - } - - return response; - } - - public static async saveMonitorMetrics(data: { - monitorId: ObjectID; - projectId: ObjectID; - dataToProcess: DataToProcess; - }): Promise<void> { - if (!data.monitorId) { - return; - } - - if (!data.projectId) { - return; - } - - if (!data.dataToProcess) { - return; - } - - const itemsToSave: Array<MonitorMetricsByMinute> = []; - - if ( - (data.dataToProcess as ServerMonitorResponse) - .basicInfrastructureMetrics - ) { - // store cpu, memory, disk metrics. - - const basicMetrics: BasicInfrastructureMetrics | undefined = ( - data.dataToProcess as ServerMonitorResponse - ).basicInfrastructureMetrics; - - if (!basicMetrics) { - return; - } - - if (basicMetrics.cpuMetrics) { - const monitorMetricsByMinute: MonitorMetricsByMinute = - new MonitorMetricsByMinute(); - monitorMetricsByMinute.monitorId = data.monitorId; - monitorMetricsByMinute.projectId = data.projectId; - monitorMetricsByMinute.metricType = CheckOn.CPUUsagePercent; - monitorMetricsByMinute.metricValue = - basicMetrics.cpuMetrics.percentUsed; - - itemsToSave.push(monitorMetricsByMinute); - } - - if (basicMetrics.memoryMetrics) { - const monitorMetricsByMinute: MonitorMetricsByMinute = - new MonitorMetricsByMinute(); - monitorMetricsByMinute.monitorId = data.monitorId; - monitorMetricsByMinute.projectId = data.projectId; - monitorMetricsByMinute.metricType = CheckOn.MemoryUsagePercent; - monitorMetricsByMinute.metricValue = - basicMetrics.memoryMetrics.percentUsed; - - itemsToSave.push(monitorMetricsByMinute); - } - - if ( - basicMetrics.diskMetrics && - basicMetrics.diskMetrics.length > 0 - ) { - for (const diskMetric of basicMetrics.diskMetrics) { - const monitorMetricsByMinute: MonitorMetricsByMinute = - new MonitorMetricsByMinute(); - monitorMetricsByMinute.monitorId = data.monitorId; - monitorMetricsByMinute.projectId = data.projectId; - monitorMetricsByMinute.metricType = - CheckOn.DiskUsagePercent; - monitorMetricsByMinute.metricValue = diskMetric.percentUsed; - monitorMetricsByMinute.miscData = { - diskPath: diskMetric.diskPath, - }; - - itemsToSave.push(monitorMetricsByMinute); - } - } - } - - if ((data.dataToProcess as ProbeMonitorResponse).responseTimeInMs) { - const monitorMetricsByMinute: MonitorMetricsByMinute = - new MonitorMetricsByMinute(); - monitorMetricsByMinute.monitorId = data.monitorId; - monitorMetricsByMinute.projectId = data.projectId; - monitorMetricsByMinute.metricType = CheckOn.ResponseTime; - monitorMetricsByMinute.metricValue = ( - data.dataToProcess as ProbeMonitorResponse - ).responseTimeInMs; - monitorMetricsByMinute.miscData = { - probeId: ( - data.dataToProcess as ProbeMonitorResponse - ).probeId.toString(), - }; - - itemsToSave.push(monitorMetricsByMinute); - } - - if ((data.dataToProcess as ProbeMonitorResponse).responseCode) { - const monitorMetricsByMinute: MonitorMetricsByMinute = - new MonitorMetricsByMinute(); - monitorMetricsByMinute.monitorId = data.monitorId; - monitorMetricsByMinute.projectId = data.projectId; - monitorMetricsByMinute.metricType = CheckOn.ResponseStatusCode; - monitorMetricsByMinute.metricValue = ( - data.dataToProcess as ProbeMonitorResponse - ).responseCode; - monitorMetricsByMinute.miscData = { - probeId: ( - data.dataToProcess as ProbeMonitorResponse - ).probeId.toString(), - }; - - itemsToSave.push(monitorMetricsByMinute); - } - - if ( - (data.dataToProcess as ProbeMonitorResponse) - .syntheticMonitorResponse - ) { - for (const syntheticMonitorResponse of ( - data.dataToProcess as ProbeMonitorResponse - ).syntheticMonitorResponse || []) { - const monitorMetricsByMinute: MonitorMetricsByMinute = - new MonitorMetricsByMinute(); - monitorMetricsByMinute.monitorId = data.monitorId; - monitorMetricsByMinute.projectId = data.projectId; - monitorMetricsByMinute.metricType = CheckOn.ExecutionTime; - monitorMetricsByMinute.metricValue = - syntheticMonitorResponse.executionTimeInMS; - monitorMetricsByMinute.miscData = { - probeId: ( - data.dataToProcess as ProbeMonitorResponse - ).probeId.toString(), - browserType: syntheticMonitorResponse.browserType, - screenSizeType: syntheticMonitorResponse.screenSizeType, - }; - - itemsToSave.push(monitorMetricsByMinute); - } - } - - if ( - (data.dataToProcess as ProbeMonitorResponse) - .customCodeMonitorResponse - ) { - const customCodeMonitorResponse: CustomCodeMonitorResponse = ( - data.dataToProcess as ProbeMonitorResponse - ).customCodeMonitorResponse!; - - const monitorMetricsByMinute: MonitorMetricsByMinute = - new MonitorMetricsByMinute(); - monitorMetricsByMinute.monitorId = data.monitorId; - monitorMetricsByMinute.projectId = data.projectId; - monitorMetricsByMinute.metricType = CheckOn.ExecutionTime; - monitorMetricsByMinute.metricValue = - customCodeMonitorResponse.executionTimeInMS; - monitorMetricsByMinute.miscData = { - probeId: ( - data.dataToProcess as ProbeMonitorResponse - ).probeId.toString(), - }; - - itemsToSave.push(monitorMetricsByMinute); - } - - await MonitorMetricsByMinuteService.createMany({ - items: itemsToSave, - props: { + const severity: IncidentSeverity | null = + await IncidentSeverityService.findOneBy({ + query: { + projectId: input.monitor.projectId!, + }, + sort: { + order: SortOrder.Ascending, + }, + props: { isRoot: true, - }, - }); - } - - private static async checkOpenIncidentsAndCloseIfResolved(input: { - monitorId: ObjectID; - autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary< - Array<string> - >; - rootCause: string; - criteriaInstance: MonitorCriteriaInstance | null; - dataToProcess: DataToProcess; - }): Promise<Array<Incident>> { - // check active incidents and if there are open incidents, do not cretae anothr incident. - const openIncidents: Array<Incident> = await IncidentService.findBy({ - query: { - monitors: [input.monitorId] as any, - currentIncidentState: { - isResolvedState: false, - }, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - select: { + }, + select: { _id: true, - createdCriteriaId: true, - createdIncidentTemplateId: true, - projectId: true, - }, - props: { - isRoot: true, - }, - }); - - // check if should close the incident. - - for (const openIncident of openIncidents) { - const shouldClose: boolean = - ProbeMonitorResponseService.shouldCloseIncident({ - openIncident, - autoResolveCriteriaInstanceIdIncidentIdsDictionary: - input.autoResolveCriteriaInstanceIdIncidentIdsDictionary, - criteriaInstance: input.criteriaInstance, - }); - - if (shouldClose) { - // then resolve incident. - await ProbeMonitorResponseService.resolveOpenIncident({ - openIncident: openIncident, - rootCause: input.rootCause, - dataToProcess: input.dataToProcess, - }); - } - } - - return openIncidents; - } - - private static async criteriaMetCreateIncidentsAndUpdateMonitorStatus(input: { - criteriaInstance: MonitorCriteriaInstance; - monitor: Monitor; - dataToProcess: DataToProcess; - rootCause: string; - autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary< - Array<string> - >; - }): Promise<void> { - // criteria filters are met, now process the actions. - - if ( - input.criteriaInstance.data?.changeMonitorStatus && - input.criteriaInstance.data?.monitorStatusId && - input.criteriaInstance.data?.monitorStatusId.toString() !== - input.monitor.currentMonitorStatusId?.toString() - ) { - logger.debug( - `${input.monitor.id?.toString()} - Change monitor status to ${input.criteriaInstance.data?.monitorStatusId.toString()}` - ); - // change monitor status - - const monitorStatusId: ObjectID | undefined = - input.criteriaInstance.data?.monitorStatusId; - - //change monitor status. - - const monitorStatusTimeline: MonitorStatusTimeline = - new MonitorStatusTimeline(); - monitorStatusTimeline.monitorId = input.monitor.id!; - monitorStatusTimeline.monitorStatusId = monitorStatusId; - monitorStatusTimeline.projectId = input.monitor.projectId!; - monitorStatusTimeline.statusChangeLog = JSON.parse( - JSON.stringify(input.dataToProcess) - ); - monitorStatusTimeline.rootCause = input.rootCause; - - await MonitorStatusTimelineService.create({ - data: monitorStatusTimeline, - props: { - isRoot: true, - }, - }); - } - - // check open incidents - logger.debug(`${input.monitor.id?.toString()} - Check open incidents.`); - // check active incidents and if there are open incidents, do not cretae anothr incident. - const openIncidents: Array<Incident> = - await this.checkOpenIncidentsAndCloseIfResolved({ - monitorId: input.monitor.id!, - autoResolveCriteriaInstanceIdIncidentIdsDictionary: - input.autoResolveCriteriaInstanceIdIncidentIdsDictionary, - rootCause: input.rootCause, - criteriaInstance: input.criteriaInstance, - dataToProcess: input.dataToProcess, + }, }); - if (input.criteriaInstance.data?.createIncidents) { - // create incidents - - for (const criteriaIncident of input.criteriaInstance.data - ?.incidents || []) { - // should create incident. - - const alreadyOpenIncident: Incident | undefined = - openIncidents.find((incident: Incident) => { - return ( - incident.createdCriteriaId === - input.criteriaInstance.data?.id.toString() && - incident.createdIncidentTemplateId === - criteriaIncident.id.toString() - ); - }); - - const hasAlreadyOpenIncident: boolean = - Boolean(alreadyOpenIncident); - - logger.debug( - `${input.monitor.id?.toString()} - Open Incident ${alreadyOpenIncident?.id?.toString()}` - ); - - logger.debug( - `${input.monitor.id?.toString()} - Has open incident ${hasAlreadyOpenIncident}` - ); - - if (hasAlreadyOpenIncident) { - continue; - } - - // create incident here. - - logger.debug( - `${input.monitor.id?.toString()} - Create incident.` - ); - - const incident: Incident = new Incident(); - - incident.title = criteriaIncident.title; - incident.description = criteriaIncident.description; - - if (!criteriaIncident.incidentSeverityId) { - // pick the critical criteria. - - const severity: IncidentSeverity | null = - await IncidentSeverityService.findOneBy({ - query: { - projectId: input.monitor.projectId!, - }, - sort: { - order: SortOrder.Ascending, - }, - props: { - isRoot: true, - }, - select: { - _id: true, - }, - }); - - if (!severity) { - throw new BadDataException( - 'Project does not have incident severity' - ); - } else { - incident.incidentSeverityId = severity.id!; - } - } else { - incident.incidentSeverityId = - criteriaIncident.incidentSeverityId!; - } - - incident.monitors = [input.monitor]; - incident.projectId = input.monitor.projectId!; - incident.rootCause = input.rootCause; - incident.createdStateLog = JSON.parse( - JSON.stringify(input.dataToProcess, null, 2) - ); - - incident.createdCriteriaId = - input.criteriaInstance.data.id.toString(); - - incident.createdIncidentTemplateId = - criteriaIncident.id.toString(); - - incident.onCallDutyPolicies = - criteriaIncident.onCallPolicyIds?.map((id: ObjectID) => { - const onCallPolicy: OnCallDutyPolicy = - new OnCallDutyPolicy(); - onCallPolicy._id = id.toString(); - return onCallPolicy; - }) || []; - - incident.isCreatedAutomatically = true; - - if ( - input.dataToProcess && - (input.dataToProcess as ProbeMonitorResponse).probeId - ) { - incident.createdByProbeId = ( - input.dataToProcess as ProbeMonitorResponse - ).probeId; - } - - await IncidentService.create({ - data: incident, - props: { - isRoot: true, - }, - }); - } - } - } - - private static async resolveOpenIncident(input: { - openIncident: Incident; - rootCause: string; - dataToProcess: - | ProbeMonitorResponse - | IncomingMonitorRequest - | DataToProcess; - }): Promise<void> { - const resolvedStateId: ObjectID = - await IncidentStateTimelineService.getResolvedStateIdForProject( - input.openIncident.projectId! + if (!severity) { + throw new BadDataException( + "Project does not have incident severity", ); - - const incidentStateTimeline: IncidentStateTimeline = - new IncidentStateTimeline(); - incidentStateTimeline.incidentId = input.openIncident.id!; - incidentStateTimeline.incidentStateId = resolvedStateId; - incidentStateTimeline.projectId = input.openIncident.projectId!; - - if (input.rootCause) { - incidentStateTimeline.rootCause = - 'Incident autoresolved because autoresolve is set to true in monitor criteria. ' + - input.rootCause; + } else { + incident.incidentSeverityId = severity.id!; + } + } else { + incident.incidentSeverityId = criteriaIncident.incidentSeverityId!; } - if (input.dataToProcess) { - incidentStateTimeline.stateChangeLog = JSON.parse( - JSON.stringify(input.dataToProcess) - ); + incident.monitors = [input.monitor]; + incident.projectId = input.monitor.projectId!; + incident.rootCause = input.rootCause; + incident.createdStateLog = JSON.parse( + JSON.stringify(input.dataToProcess, null, 2), + ); + + incident.createdCriteriaId = input.criteriaInstance.data.id.toString(); + + incident.createdIncidentTemplateId = criteriaIncident.id.toString(); + + incident.onCallDutyPolicies = + criteriaIncident.onCallPolicyIds?.map((id: ObjectID) => { + const onCallPolicy: OnCallDutyPolicy = new OnCallDutyPolicy(); + onCallPolicy._id = id.toString(); + return onCallPolicy; + }) || []; + + incident.isCreatedAutomatically = true; + + if ( + input.dataToProcess && + (input.dataToProcess as ProbeMonitorResponse).probeId + ) { + incident.createdByProbeId = ( + input.dataToProcess as ProbeMonitorResponse + ).probeId; } - await IncidentStateTimelineService.create({ - data: incidentStateTimeline, - props: { - isRoot: true, - }, + await IncidentService.create({ + data: incident, + props: { + isRoot: true, + }, }); + } + } + } + + private static async resolveOpenIncident(input: { + openIncident: Incident; + rootCause: string; + dataToProcess: + | ProbeMonitorResponse + | IncomingMonitorRequest + | DataToProcess; + }): Promise<void> { + const resolvedStateId: ObjectID = + await IncidentStateTimelineService.getResolvedStateIdForProject( + input.openIncident.projectId!, + ); + + const incidentStateTimeline: IncidentStateTimeline = + new IncidentStateTimeline(); + incidentStateTimeline.incidentId = input.openIncident.id!; + incidentStateTimeline.incidentStateId = resolvedStateId; + incidentStateTimeline.projectId = input.openIncident.projectId!; + + if (input.rootCause) { + incidentStateTimeline.rootCause = + "Incident autoresolved because autoresolve is set to true in monitor criteria. " + + input.rootCause; } - private static shouldCloseIncident(input: { - openIncident: Incident; - autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary< - Array<string> - >; - criteriaInstance: MonitorCriteriaInstance | null; // null if no criteia met. - }): boolean { - if ( - input.openIncident.createdCriteriaId?.toString() === - input.criteriaInstance?.data?.id.toString() - ) { - // same incident active. So, do not close. - return false; - } - - // If antoher criteria is active then, check if the incident id is present in the map. - - if (!input.openIncident.createdCriteriaId?.toString()) { - return false; - } - - if (!input.openIncident.createdIncidentTemplateId?.toString()) { - return false; - } - - if ( - input.autoResolveCriteriaInstanceIdIncidentIdsDictionary[ - input.openIncident.createdCriteriaId?.toString() - ] - ) { - if ( - input.autoResolveCriteriaInstanceIdIncidentIdsDictionary[ - input.openIncident.createdCriteriaId?.toString() - ]?.includes( - input.openIncident.createdIncidentTemplateId?.toString() - ) - ) { - return true; - } - } - - return false; + if (input.dataToProcess) { + incidentStateTimeline.stateChangeLog = JSON.parse( + JSON.stringify(input.dataToProcess), + ); } - private static async processMonitorStep(input: { - dataToProcess: DataToProcess; - monitorStep: MonitorStep; - monitor: Monitor; - probeApiIngestResponse: ProbeApiIngestResponse; - }): Promise<ProbeApiIngestResponse> { - // process monitor step here. + await IncidentStateTimelineService.create({ + data: incidentStateTimeline, + props: { + isRoot: true, + }, + }); + } - const criteria: MonitorCriteria | undefined = - input.monitorStep.data?.monitorCriteria; - - if (!criteria || !criteria.data) { - // do nothing as there's no criteria to process. - return input.probeApiIngestResponse; - } - - for (const criteriaInstance of criteria.data - .monitorCriteriaInstanceArray) { - const rootCause: string | null = - await ProbeMonitorResponseService.processMonitorCriteiaInstance( - { - dataToProcess: input.dataToProcess, - monitorStep: input.monitorStep, - monitor: input.monitor, - probeApiIngestResponse: input.probeApiIngestResponse, - criteriaInstance: criteriaInstance, - } - ); - - if (rootCause) { - input.probeApiIngestResponse.criteriaMetId = - criteriaInstance.data?.id; - input.probeApiIngestResponse.rootCause = - rootCause + - ' ' + - ((input.dataToProcess as ProbeMonitorResponse) - .failureCause || ''); - break; - } - } - - return input.probeApiIngestResponse; + private static shouldCloseIncident(input: { + openIncident: Incident; + autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary< + Array<string> + >; + criteriaInstance: MonitorCriteriaInstance | null; // null if no criteia met. + }): boolean { + if ( + input.openIncident.createdCriteriaId?.toString() === + input.criteriaInstance?.data?.id.toString() + ) { + // same incident active. So, do not close. + return false; } - private static async processMonitorCriteiaInstance(input: { - dataToProcess: DataToProcess; - monitorStep: MonitorStep; - monitor: Monitor; - probeApiIngestResponse: ProbeApiIngestResponse; - criteriaInstance: MonitorCriteriaInstance; - }): Promise<string | null> { - // returns root cause if any. Otherwise criteria is not met. - // process monitor criteria instance here. + // If antoher criteria is active then, check if the incident id is present in the map. - const rootCause: string | null = - await ProbeMonitorResponseService.isMonitorInstanceCriteriaFiltersMet( - { - dataToProcess: input.dataToProcess, - monitorStep: input.monitorStep, - monitor: input.monitor, - probeApiIngestResponse: input.probeApiIngestResponse, - criteriaInstance: input.criteriaInstance, - } - ); - - // do nothing as there's no criteria to process. - return rootCause; + if (!input.openIncident.createdCriteriaId?.toString()) { + return false; } - private static async isMonitorInstanceCriteriaFiltersMet(input: { - dataToProcess: DataToProcess; - monitorStep: MonitorStep; - monitor: Monitor; - probeApiIngestResponse: ProbeApiIngestResponse; - criteriaInstance: MonitorCriteriaInstance; - }): Promise<string | null> { - // returns root cause if any. Otherwise criteria is not met. - let finalResult: string | null = 'All Criteria Met.'; - - if ( - FilterCondition.Any === input.criteriaInstance.data?.filterCondition - ) { - finalResult = null; // set to false as we need to check if any of the filters are met. - } - - for (const criteriaFilter of input.criteriaInstance.data?.filters || - []) { - const rootCause: string | null = - await ProbeMonitorResponseService.isMonitorInstanceCriteriaFilterMet( - { - dataToProcess: input.dataToProcess, - monitorStep: input.monitorStep, - monitor: input.monitor, - probeApiIngestResponse: input.probeApiIngestResponse, - criteriaInstance: input.criteriaInstance, - criteriaFilter: criteriaFilter, - } - ); - - const didMeetCriteria: boolean = Boolean(rootCause); - - if ( - FilterCondition.Any === - input.criteriaInstance.data?.filterCondition && - didMeetCriteria === true - ) { - finalResult = rootCause; - } - - if ( - FilterCondition.All === - input.criteriaInstance.data?.filterCondition && - didMeetCriteria === false - ) { - finalResult = null; - break; - } - - if ( - FilterCondition.All === - input.criteriaInstance.data?.filterCondition && - didMeetCriteria && - rootCause - ) { - finalResult += rootCause + ' '; - } - } - - return finalResult; + if (!input.openIncident.createdIncidentTemplateId?.toString()) { + return false; } - private static async isMonitorInstanceCriteriaFilterMet(input: { - dataToProcess: DataToProcess; - monitorStep: MonitorStep; - monitor: Monitor; - probeApiIngestResponse: ProbeApiIngestResponse; - criteriaInstance: MonitorCriteriaInstance; - criteriaFilter: CriteriaFilter; - }): Promise<string | null> { - // returns root cause if any. Otherwise criteria is not met. - // process monitor criteria filter here. + if ( + input.autoResolveCriteriaInstanceIdIncidentIdsDictionary[ + input.openIncident.createdCriteriaId?.toString() + ] + ) { + if ( + input.autoResolveCriteriaInstanceIdIncidentIdsDictionary[ + input.openIncident.createdCriteriaId?.toString() + ]?.includes(input.openIncident.createdIncidentTemplateId?.toString()) + ) { + return true; + } + } - if (input.criteriaFilter.checkOn === CheckOn.JavaScriptExpression) { - let storageMap: JSONObject = {}; + return false; + } - if ( - input.monitor.monitorType === MonitorType.API || - input.monitor.monitorType === MonitorType.Website - ) { - // try to parse json - let responseBody: JSONObject | null = null; - try { - responseBody = JSON.parse( - ((input.dataToProcess as ProbeMonitorResponse) - .responseBody as string) || '{}' - ); - } catch (err) { - responseBody = (input.dataToProcess as ProbeMonitorResponse) - .responseBody as JSONObject; - } + private static async processMonitorStep(input: { + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + monitor: Monitor; + probeApiIngestResponse: ProbeApiIngestResponse; + }): Promise<ProbeApiIngestResponse> { + // process monitor step here. - if ( - typeof responseBody === Typeof.String && - responseBody?.toString() === '' - ) { - // if empty string then set to empty object. - responseBody = {}; - } + const criteria: MonitorCriteria | undefined = + input.monitorStep.data?.monitorCriteria; - storageMap = { - responseBody: responseBody, - responseHeaders: ( - input.dataToProcess as ProbeMonitorResponse - ).responseHeaders, - responseStatusCode: ( - input.dataToProcess as ProbeMonitorResponse - ).responseCode, - responseTimeInMs: ( - input.dataToProcess as ProbeMonitorResponse - ).responseTimeInMs, - isOnline: (input.dataToProcess as ProbeMonitorResponse) - .isOnline, - }; - } + if (!criteria || !criteria.data) { + // do nothing as there's no criteria to process. + return input.probeApiIngestResponse; + } - if (input.monitor.monitorType === MonitorType.IncomingRequest) { - storageMap = { - requestBody: (input.dataToProcess as IncomingMonitorRequest) - .requestBody, - requestHeaders: ( - input.dataToProcess as IncomingMonitorRequest - ).requestHeaders, - }; - } + for (const criteriaInstance of criteria.data.monitorCriteriaInstanceArray) { + const rootCause: string | null = + await ProbeMonitorResponseService.processMonitorCriteiaInstance({ + dataToProcess: input.dataToProcess, + monitorStep: input.monitorStep, + monitor: input.monitor, + probeApiIngestResponse: input.probeApiIngestResponse, + criteriaInstance: criteriaInstance, + }); - // now evaluate the expression. - let expression: string = input.criteriaFilter.value as string; - expression = VMUtil.replaceValueInPlace( - storageMap, - expression, - false - ); // now pass this to the VM. + if (rootCause) { + input.probeApiIngestResponse.criteriaMetId = criteriaInstance.data?.id; + input.probeApiIngestResponse.rootCause = + rootCause + + " " + + ((input.dataToProcess as ProbeMonitorResponse).failureCause || ""); + break; + } + } - const code: string = `return Boolean(${expression});`; - let result: ReturnResult | null = null; + return input.probeApiIngestResponse; + } - try { - result = await VMUtil.runCodeInSandbox({ - code: code, - options: { - args: {}, - }, - }); - } catch (err) { - logger.error(err); - return null; - } + private static async processMonitorCriteiaInstance(input: { + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + monitor: Monitor; + probeApiIngestResponse: ProbeApiIngestResponse; + criteriaInstance: MonitorCriteriaInstance; + }): Promise<string | null> { + // returns root cause if any. Otherwise criteria is not met. + // process monitor criteria instance here. - if (result.returnValue) { - return `JavaScript Expression - ${expression} - evaluated to true.`; - } + const rootCause: string | null = + await ProbeMonitorResponseService.isMonitorInstanceCriteriaFiltersMet({ + dataToProcess: input.dataToProcess, + monitorStep: input.monitorStep, + monitor: input.monitor, + probeApiIngestResponse: input.probeApiIngestResponse, + criteriaInstance: input.criteriaInstance, + }); - return null; // if true then return null. + // do nothing as there's no criteria to process. + return rootCause; + } + + private static async isMonitorInstanceCriteriaFiltersMet(input: { + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + monitor: Monitor; + probeApiIngestResponse: ProbeApiIngestResponse; + criteriaInstance: MonitorCriteriaInstance; + }): Promise<string | null> { + // returns root cause if any. Otherwise criteria is not met. + let finalResult: string | null = "All Criteria Met."; + + if (FilterCondition.Any === input.criteriaInstance.data?.filterCondition) { + finalResult = null; // set to false as we need to check if any of the filters are met. + } + + for (const criteriaFilter of input.criteriaInstance.data?.filters || []) { + const rootCause: string | null = + await ProbeMonitorResponseService.isMonitorInstanceCriteriaFilterMet({ + dataToProcess: input.dataToProcess, + monitorStep: input.monitorStep, + monitor: input.monitor, + probeApiIngestResponse: input.probeApiIngestResponse, + criteriaInstance: input.criteriaInstance, + criteriaFilter: criteriaFilter, + }); + + const didMeetCriteria: boolean = Boolean(rootCause); + + if ( + FilterCondition.Any === input.criteriaInstance.data?.filterCondition && + didMeetCriteria === true + ) { + finalResult = rootCause; + } + + if ( + FilterCondition.All === input.criteriaInstance.data?.filterCondition && + didMeetCriteria === false + ) { + finalResult = null; + break; + } + + if ( + FilterCondition.All === input.criteriaInstance.data?.filterCondition && + didMeetCriteria && + rootCause + ) { + finalResult += rootCause + " "; + } + } + + return finalResult; + } + + private static async isMonitorInstanceCriteriaFilterMet(input: { + dataToProcess: DataToProcess; + monitorStep: MonitorStep; + monitor: Monitor; + probeApiIngestResponse: ProbeApiIngestResponse; + criteriaInstance: MonitorCriteriaInstance; + criteriaFilter: CriteriaFilter; + }): Promise<string | null> { + // returns root cause if any. Otherwise criteria is not met. + // process monitor criteria filter here. + + if (input.criteriaFilter.checkOn === CheckOn.JavaScriptExpression) { + let storageMap: JSONObject = {}; + + if ( + input.monitor.monitorType === MonitorType.API || + input.monitor.monitorType === MonitorType.Website + ) { + // try to parse json + let responseBody: JSONObject | null = null; + try { + responseBody = JSON.parse( + ((input.dataToProcess as ProbeMonitorResponse) + .responseBody as string) || "{}", + ); + } catch (err) { + responseBody = (input.dataToProcess as ProbeMonitorResponse) + .responseBody as JSONObject; } if ( - input.monitor.monitorType === MonitorType.API || - input.monitor.monitorType === MonitorType.Website || - input.monitor.monitorType === MonitorType.IP || - input.monitor.monitorType === MonitorType.Ping || - input.monitor.monitorType === MonitorType.Port + typeof responseBody === Typeof.String && + responseBody?.toString() === "" ) { - const apiRequestCriteriaResult: string | null = - await APIRequestCriteria.isMonitorInstanceCriteriaFilterMet({ - dataToProcess: input.dataToProcess, - criteriaFilter: input.criteriaFilter, - }); - - if (apiRequestCriteriaResult) { - return apiRequestCriteriaResult; - } + // if empty string then set to empty object. + responseBody = {}; } - if ( - input.monitor.monitorType === MonitorType.CustomJavaScriptCode && - (input.dataToProcess as ProbeMonitorResponse) - .customCodeMonitorResponse - ) { - const criteriaResult: string | null = - await CustomCodeMonitoringCriteria.isMonitorInstanceCriteriaFilterMet( - { - monitorResponse: ( - input.dataToProcess as ProbeMonitorResponse - ).customCodeMonitorResponse!, - criteriaFilter: input.criteriaFilter, - } - ); + storageMap = { + responseBody: responseBody, + responseHeaders: (input.dataToProcess as ProbeMonitorResponse) + .responseHeaders, + responseStatusCode: (input.dataToProcess as ProbeMonitorResponse) + .responseCode, + responseTimeInMs: (input.dataToProcess as ProbeMonitorResponse) + .responseTimeInMs, + isOnline: (input.dataToProcess as ProbeMonitorResponse).isOnline, + }; + } - if (criteriaResult) { - return criteriaResult; - } - } + if (input.monitor.monitorType === MonitorType.IncomingRequest) { + storageMap = { + requestBody: (input.dataToProcess as IncomingMonitorRequest) + .requestBody, + requestHeaders: (input.dataToProcess as IncomingMonitorRequest) + .requestHeaders, + }; + } - if ( - input.monitor.monitorType === MonitorType.SyntheticMonitor && - (input.dataToProcess as ProbeMonitorResponse) - .syntheticMonitorResponse - ) { - const criteriaResult: string | null = - await SyntheticMonitoringCriteria.isMonitorInstanceCriteriaFilterMet( - { - monitorResponse: - (input.dataToProcess as ProbeMonitorResponse) - .syntheticMonitorResponse || [], - criteriaFilter: input.criteriaFilter, - } - ); + // now evaluate the expression. + let expression: string = input.criteriaFilter.value as string; + expression = VMUtil.replaceValueInPlace(storageMap, expression, false); // now pass this to the VM. - if (criteriaResult) { - return criteriaResult; - } - } - - if (input.monitor.monitorType === MonitorType.IncomingRequest) { - //check incoming request - const incomingRequestResult: string | null = - await IncomingRequestCriteria.isMonitorInstanceCriteriaFilterMet( - { - dataToProcess: input.dataToProcess, - criteriaFilter: input.criteriaFilter, - } - ); - - if (incomingRequestResult) { - return incomingRequestResult; - } - } - - if (input.monitor.monitorType === MonitorType.SSLCertificate) { - // check server monitor - const sslMonitorResult: string | null = - await SSLMonitorCriteria.isMonitorInstanceCriteriaFilterMet({ - dataToProcess: input.dataToProcess, - criteriaFilter: input.criteriaFilter, - }); - - if (sslMonitorResult) { - return sslMonitorResult; - } - } - - if (input.monitor.monitorType === MonitorType.Server) { - // check server monitor - const serverMonitorResult: string | null = - await ServerMonitorCriteria.isMonitorInstanceCriteriaFilterMet({ - dataToProcess: input.dataToProcess, - criteriaFilter: input.criteriaFilter, - }); - - if (serverMonitorResult) { - return serverMonitorResult; - } - } - - if (input.monitor.monitorType !== MonitorType.Server) { - if ( - input.criteriaFilter.checkOn === CheckOn.IsOnline && - input.criteriaFilter.filterType === FilterType.True - ) { - if ((input.dataToProcess as ProbeMonitorResponse).isOnline) { - return 'Monitor is online.'; - } - return null; - } - - if ( - input.criteriaFilter.checkOn === CheckOn.IsOnline && - input.criteriaFilter.filterType === FilterType.False - ) { - if (!(input.dataToProcess as ProbeMonitorResponse).isOnline) { - return 'Monitor is offline.'; - } - return null; - } - } + const code: string = `return Boolean(${expression});`; + let result: ReturnResult | null = null; + try { + result = await VMUtil.runCodeInSandbox({ + code: code, + options: { + args: {}, + }, + }); + } catch (err) { + logger.error(err); return null; + } + + if (result.returnValue) { + return `JavaScript Expression - ${expression} - evaluated to true.`; + } + + return null; // if true then return null. } + + if ( + input.monitor.monitorType === MonitorType.API || + input.monitor.monitorType === MonitorType.Website || + input.monitor.monitorType === MonitorType.IP || + input.monitor.monitorType === MonitorType.Ping || + input.monitor.monitorType === MonitorType.Port + ) { + const apiRequestCriteriaResult: string | null = + await APIRequestCriteria.isMonitorInstanceCriteriaFilterMet({ + dataToProcess: input.dataToProcess, + criteriaFilter: input.criteriaFilter, + }); + + if (apiRequestCriteriaResult) { + return apiRequestCriteriaResult; + } + } + + if ( + input.monitor.monitorType === MonitorType.CustomJavaScriptCode && + (input.dataToProcess as ProbeMonitorResponse).customCodeMonitorResponse + ) { + const criteriaResult: string | null = + await CustomCodeMonitoringCriteria.isMonitorInstanceCriteriaFilterMet({ + monitorResponse: (input.dataToProcess as ProbeMonitorResponse) + .customCodeMonitorResponse!, + criteriaFilter: input.criteriaFilter, + }); + + if (criteriaResult) { + return criteriaResult; + } + } + + if ( + input.monitor.monitorType === MonitorType.SyntheticMonitor && + (input.dataToProcess as ProbeMonitorResponse).syntheticMonitorResponse + ) { + const criteriaResult: string | null = + await SyntheticMonitoringCriteria.isMonitorInstanceCriteriaFilterMet({ + monitorResponse: + (input.dataToProcess as ProbeMonitorResponse) + .syntheticMonitorResponse || [], + criteriaFilter: input.criteriaFilter, + }); + + if (criteriaResult) { + return criteriaResult; + } + } + + if (input.monitor.monitorType === MonitorType.IncomingRequest) { + //check incoming request + const incomingRequestResult: string | null = + await IncomingRequestCriteria.isMonitorInstanceCriteriaFilterMet({ + dataToProcess: input.dataToProcess, + criteriaFilter: input.criteriaFilter, + }); + + if (incomingRequestResult) { + return incomingRequestResult; + } + } + + if (input.monitor.monitorType === MonitorType.SSLCertificate) { + // check server monitor + const sslMonitorResult: string | null = + await SSLMonitorCriteria.isMonitorInstanceCriteriaFilterMet({ + dataToProcess: input.dataToProcess, + criteriaFilter: input.criteriaFilter, + }); + + if (sslMonitorResult) { + return sslMonitorResult; + } + } + + if (input.monitor.monitorType === MonitorType.Server) { + // check server monitor + const serverMonitorResult: string | null = + await ServerMonitorCriteria.isMonitorInstanceCriteriaFilterMet({ + dataToProcess: input.dataToProcess, + criteriaFilter: input.criteriaFilter, + }); + + if (serverMonitorResult) { + return serverMonitorResult; + } + } + + if (input.monitor.monitorType !== MonitorType.Server) { + if ( + input.criteriaFilter.checkOn === CheckOn.IsOnline && + input.criteriaFilter.filterType === FilterType.True + ) { + if ((input.dataToProcess as ProbeMonitorResponse).isOnline) { + return "Monitor is online."; + } + return null; + } + + if ( + input.criteriaFilter.checkOn === CheckOn.IsOnline && + input.criteriaFilter.filterType === FilterType.False + ) { + if (!(input.dataToProcess as ProbeMonitorResponse).isOnline) { + return "Monitor is offline."; + } + return null; + } + } + + return null; + } } diff --git a/CommonServer/Utils/Process.ts b/CommonServer/Utils/Process.ts index 607bbcb497..2933a657ab 100644 --- a/CommonServer/Utils/Process.ts +++ b/CommonServer/Utils/Process.ts @@ -1,20 +1,20 @@ /* eslint-disable no-console */ -import logger from './Logger'; +import logger from "./Logger"; -process.on('exit', () => { - logger.debug('Server Shutting Shutdown'); +process.on("exit", () => { + logger.debug("Server Shutting Shutdown"); }); process.on( - 'unhandledRejection', - (reason: unknown, promise: Promise<unknown>) => { - logger.error('Unhandled rejection in server process occurred'); - logger.error(reason); - logger.error(promise); - } + "unhandledRejection", + (reason: unknown, promise: Promise<unknown>) => { + logger.error("Unhandled rejection in server process occurred"); + logger.error(reason); + logger.error(promise); + }, ); -process.on('uncaughtException', (err: Error) => { - logger.error('Uncaught exception in server process occurred'); - logger.error(err); +process.on("uncaughtException", (err: Error) => { + logger.error("Uncaught exception in server process occurred"); + logger.error(err); }); diff --git a/CommonServer/Utils/Realtime.ts b/CommonServer/Utils/Realtime.ts index 23bdf2fa33..9581952cb0 100644 --- a/CommonServer/Utils/Realtime.ts +++ b/CommonServer/Utils/Realtime.ts @@ -1,155 +1,144 @@ -import IO, { Socket, SocketServer } from '../Infrastructure/SocketIO'; -import logger from './Logger'; +import IO, { Socket, SocketServer } from "../Infrastructure/SocketIO"; +import logger from "./Logger"; import AnalyticsBaseModel, { - AnalyticsBaseModelType, -} from 'Common/AnalyticsModels/BaseModel'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import DatabaseType from 'Common/Types/BaseDatabase/DatabaseType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; + AnalyticsBaseModelType, +} from "Common/AnalyticsModels/BaseModel"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import DatabaseType from "Common/Types/BaseDatabase/DatabaseType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; import RealtimeUtil, { - EventName, - ListenToModelEventJSON, - ModelEventType, -} from 'Common/Utils/Realtime'; + EventName, + ListenToModelEventJSON, + ModelEventType, +} from "Common/Utils/Realtime"; export default abstract class Realtime { - private static socketServer: SocketServer | null = null; + private static socketServer: SocketServer | null = null; - public static isInitialized(): boolean { - return this.socketServer !== null; + public static isInitialized(): boolean { + return this.socketServer !== null; + } + + public static async init(): Promise<SocketServer | null> { + if (!this.socketServer) { + this.socketServer = IO.getSocketServer(); + logger.debug("Realtime socket server initialized"); } - public static async init(): Promise<SocketServer | null> { - if (!this.socketServer) { - this.socketServer = IO.getSocketServer(); - logger.debug('Realtime socket server initialized'); + this.socketServer!.on("connection", (socket: Socket) => { + socket.on(EventName.ListenToModalEvent, async (data: JSONObject) => { + // TODO: validate if this soocket has access to this tenant + + // TODO: validate if this socket has access to this model + + // TODO: validate if this socket has access to this event type + + // TODO: validate if this socket has access to this query + + // TODO: validate if this socket has access to this select + + // validate data + + if (typeof data["eventType"] !== "string") { + throw new BadDataException("eventType is not a string"); + } + if (typeof data["modelType"] !== "string") { + throw new BadDataException("modelType is not a string"); + } + if (typeof data["modelName"] !== "string") { + throw new BadDataException("modelName is not a string"); + } + if (typeof data["query"] !== "object") { + throw new BadDataException("query is not an object"); + } + if (typeof data["tenantId"] !== "string") { + throw new BadDataException("tenantId is not a string"); + } + if (typeof data["select"] !== "object") { + throw new BadDataException("select is not an object"); } - this.socketServer!.on('connection', (socket: Socket) => { - socket.on( - EventName.ListenToModalEvent, - async (data: JSONObject) => { - // TODO: validate if this soocket has access to this tenant - - // TODO: validate if this socket has access to this model - - // TODO: validate if this socket has access to this event type - - // TODO: validate if this socket has access to this query - - // TODO: validate if this socket has access to this select - - // validate data - - if (typeof data['eventType'] !== 'string') { - throw new BadDataException('eventType is not a string'); - } - if (typeof data['modelType'] !== 'string') { - throw new BadDataException('modelType is not a string'); - } - if (typeof data['modelName'] !== 'string') { - throw new BadDataException('modelName is not a string'); - } - if (typeof data['query'] !== 'object') { - throw new BadDataException('query is not an object'); - } - if (typeof data['tenantId'] !== 'string') { - throw new BadDataException('tenantId is not a string'); - } - if (typeof data['select'] !== 'object') { - throw new BadDataException('select is not an object'); - } - - await Realtime.listenToModelEvent(socket, { - eventType: data['eventType'] as ModelEventType, - modelType: data['modelType'] as DatabaseType, - modelName: data['modelName'] as string, - query: JSONFunctions.deserialize( - data['query'] as JSONObject - ), - tenantId: data['tenantId'] as string, - select: JSONFunctions.deserialize( - data['select'] as JSONObject - ), - }); - } - ); + await Realtime.listenToModelEvent(socket, { + eventType: data["eventType"] as ModelEventType, + modelType: data["modelType"] as DatabaseType, + modelName: data["modelName"] as string, + query: JSONFunctions.deserialize(data["query"] as JSONObject), + tenantId: data["tenantId"] as string, + select: JSONFunctions.deserialize(data["select"] as JSONObject), }); + }); + }); - return this.socketServer; + return this.socketServer; + } + + public static async listenToModelEvent( + socket: Socket, + data: ListenToModelEventJSON, + ): Promise<void> { + if (!this.socketServer) { + await this.init(); } - public static async listenToModelEvent( - socket: Socket, - data: ListenToModelEventJSON - ): Promise<void> { - if (!this.socketServer) { - await this.init(); - } + const roomId: string = RealtimeUtil.getRoomId( + data.tenantId, + data.modelName, + data.eventType, + ); - const roomId: string = RealtimeUtil.getRoomId( - data.tenantId, - data.modelName, - data.eventType - ); + // join the room. + await socket.join(roomId); + } - // join the room. - await socket.join(roomId); + public static async stopListeningToModelEvent( + socket: Socket, + data: ListenToModelEventJSON, + ): Promise<void> { + if (!this.socketServer) { + await this.init(); } - public static async stopListeningToModelEvent( - socket: Socket, - data: ListenToModelEventJSON - ): Promise<void> { - if (!this.socketServer) { - await this.init(); - } + // leave this room. + await socket.leave( + RealtimeUtil.getRoomId(data.tenantId, data.modelName, data.eventType), + ); + } - // leave this room. - await socket.leave( - RealtimeUtil.getRoomId( - data.tenantId, - data.modelName, - data.eventType - ) - ); + public static async emitModelEvent(data: { + tenantId: string | ObjectID; + eventType: ModelEventType; + model: BaseModel | AnalyticsBaseModel; + modelType: { new (): BaseModel | AnalyticsBaseModel }; + }): Promise<void> { + if (!this.socketServer) { + await this.init(); } - public static async emitModelEvent(data: { - tenantId: string | ObjectID; - eventType: ModelEventType; - model: BaseModel | AnalyticsBaseModel; - modelType: { new (): BaseModel | AnalyticsBaseModel }; - }): Promise<void> { - if (!this.socketServer) { - await this.init(); - } + let jsonObject: JSONObject = {}; - let jsonObject: JSONObject = {}; - - if (data.model instanceof BaseModel) { - jsonObject = BaseModel.toJSON( - data.model, - data.modelType as BaseModelType - ); - } - - if (data.model instanceof AnalyticsBaseModel) { - jsonObject = AnalyticsBaseModel.toJSON( - data.model, - data.modelType as AnalyticsBaseModelType - ); - } - - const roomId: string = RealtimeUtil.getRoomId( - data.tenantId, - data.model.tableName!, - data.eventType - ); - - this.socketServer!.to(roomId).emit(roomId, jsonObject); + if (data.model instanceof BaseModel) { + jsonObject = BaseModel.toJSON( + data.model, + data.modelType as BaseModelType, + ); } + + if (data.model instanceof AnalyticsBaseModel) { + jsonObject = AnalyticsBaseModel.toJSON( + data.model, + data.modelType as AnalyticsBaseModelType, + ); + } + + const roomId: string = RealtimeUtil.getRoomId( + data.tenantId, + data.model.tableName!, + data.eventType, + ); + + this.socketServer!.to(roomId).emit(roomId, jsonObject); + } } diff --git a/CommonServer/Utils/Response.ts b/CommonServer/Utils/Response.ts index e617bb041f..e5bfb6cc14 100644 --- a/CommonServer/Utils/Response.ts +++ b/CommonServer/Utils/Response.ts @@ -1,289 +1,289 @@ import { - ExpressRequest, - ExpressResponse, - OneUptimeRequest, - OneUptimeResponse, -} from './Express'; -import JsonToCsv from './JsonToCsv'; -import logger from './Logger'; + ExpressRequest, + ExpressResponse, + OneUptimeRequest, + OneUptimeResponse, +} from "./Express"; +import JsonToCsv from "./JsonToCsv"; +import logger from "./Logger"; import AnalyticsDataModel, { - AnalyticsBaseModelType, -} from 'Common/AnalyticsModels/BaseModel'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import FileModel from 'Common/Models/FileModel'; -import EmptyResponse from 'Common/Types/API/EmptyResponse'; -import StatusCode from 'Common/Types/API/StatusCode'; -import URL from 'Common/Types/API/URL'; -import { DEFAULT_LIMIT } from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import Exception from 'Common/Types/Exception/Exception'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import ListData from 'Common/Types/ListData'; -import PositiveNumber from 'Common/Types/PositiveNumber'; + AnalyticsBaseModelType, +} from "Common/AnalyticsModels/BaseModel"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import FileModel from "Common/Models/FileModel"; +import EmptyResponse from "Common/Types/API/EmptyResponse"; +import StatusCode from "Common/Types/API/StatusCode"; +import URL from "Common/Types/API/URL"; +import { DEFAULT_LIMIT } from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import Exception from "Common/Types/Exception/Exception"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import ListData from "Common/Types/ListData"; +import PositiveNumber from "Common/Types/PositiveNumber"; export default class Response { - public static sendEmptySuccessResponse( - _req: ExpressRequest, - res: ExpressResponse - ): void { - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + public static sendEmptySuccessResponse( + _req: ExpressRequest, + res: ExpressResponse, + ): void { + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; - oneUptimeResponse.status(200).send({} as EmptyResponse); + oneUptimeResponse.status(200).send({} as EmptyResponse); + } + + public static sendCustomResponse( + _req: ExpressRequest, + res: ExpressResponse, + statusCode: number, + body: JSONObject | string, + headers: Dictionary<string>, + ): void { + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + + if (headers) { + for (const key in headers) { + oneUptimeResponse.set(key, headers[key]?.toString() || ""); + } } - public static sendCustomResponse( - _req: ExpressRequest, - res: ExpressResponse, - statusCode: number, - body: JSONObject | string, - headers: Dictionary<string> - ): void { - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + oneUptimeResponse.status(statusCode).send(body); + } - if (headers) { - for (const key in headers) { - oneUptimeResponse.set(key, headers[key]?.toString() || ''); - } - } + public static async sendFileResponse( + _req: ExpressRequest | ExpressRequest, + res: ExpressResponse, + file: FileModel, + ): Promise<void> { + /** Create read stream */ - oneUptimeResponse.status(statusCode).send(body); + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + + /** Set the proper content type */ + oneUptimeResponse.set("Content-Type", file.type); + oneUptimeResponse.status(200); + /** Return response */ + // readstream.pipe(res); + + oneUptimeResponse.send(file.file); + } + + public static render( + _req: ExpressRequest, + res: ExpressResponse, + path: string, + vars: JSONObject, + ): void { + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + + oneUptimeResponse.render(path, vars); + } + + public static sendErrorResponse( + _req: ExpressRequest, + res: ExpressResponse, + error: Exception, + ): void { + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + + oneUptimeResponse.logBody = { message: error.message }; // To be used in 'auditLog' middleware to log response data; + const status: number = error.code || 500; + const message: string = error.message || "Server Error"; + + logger.error(error); + + oneUptimeResponse.status(status).send({ message }); + } + + public static sendEntityArrayResponse( + req: ExpressRequest, + res: ExpressResponse, + list: Array<BaseModel | AnalyticsDataModel>, + count: PositiveNumber | number, + modelType: { new (): BaseModel | AnalyticsDataModel }, + ): void { + if (!(count instanceof PositiveNumber)) { + count = new PositiveNumber(count); } - public static async sendFileResponse( - _req: ExpressRequest | ExpressRequest, - res: ExpressResponse, - file: FileModel - ): Promise<void> { - /** Create read stream */ + let jsonArray: JSONArray = []; - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + const model: BaseModel | AnalyticsDataModel = new modelType(); - /** Set the proper content type */ - oneUptimeResponse.set('Content-Type', file.type); - oneUptimeResponse.status(200); - /** Return response */ - // readstream.pipe(res); - - oneUptimeResponse.send(file.file); + if (model instanceof BaseModel) { + jsonArray = BaseModel.toJSONArray( + list as Array<BaseModel>, + modelType as BaseModelType, + ); } - public static render( - _req: ExpressRequest, - res: ExpressResponse, - path: string, - vars: JSONObject - ): void { - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; - - oneUptimeResponse.render(path, vars); + if (model instanceof AnalyticsDataModel) { + jsonArray = AnalyticsDataModel.toJSONArray( + list as Array<AnalyticsDataModel>, + modelType as AnalyticsBaseModelType, + ); } - public static sendErrorResponse( - _req: ExpressRequest, - res: ExpressResponse, - error: Exception - ): void { - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + return this.sendJsonArrayResponse(req, res, jsonArray, count); + } - oneUptimeResponse.logBody = { message: error.message }; // To be used in 'auditLog' middleware to log response data; - const status: number = error.code || 500; - const message: string = error.message || 'Server Error'; + public static sendEntityResponse( + req: ExpressRequest, + res: ExpressResponse, + item: BaseModel | AnalyticsDataModel | null, + modelType: { new (): BaseModel | AnalyticsDataModel }, + options?: + | { + miscData?: JSONObject; + } + | undefined, + ): void { + let response: JSONObject = {}; - logger.error(error); - - oneUptimeResponse.status(status).send({ message }); + if (item && item instanceof BaseModel) { + response = BaseModel.toJSON(item, modelType as BaseModelType); } - public static sendEntityArrayResponse( - req: ExpressRequest, - res: ExpressResponse, - list: Array<BaseModel | AnalyticsDataModel>, - count: PositiveNumber | number, - modelType: { new (): BaseModel | AnalyticsDataModel } - ): void { - if (!(count instanceof PositiveNumber)) { - count = new PositiveNumber(count); - } - - let jsonArray: JSONArray = []; - - const model: BaseModel | AnalyticsDataModel = new modelType(); - - if (model instanceof BaseModel) { - jsonArray = BaseModel.toJSONArray( - list as Array<BaseModel>, - modelType as BaseModelType - ); - } - - if (model instanceof AnalyticsDataModel) { - jsonArray = AnalyticsDataModel.toJSONArray( - list as Array<AnalyticsDataModel>, - modelType as AnalyticsBaseModelType - ); - } - - return this.sendJsonArrayResponse(req, res, jsonArray, count); + if (item && item instanceof AnalyticsDataModel) { + response = AnalyticsDataModel.toJSON( + item, + modelType as AnalyticsBaseModelType, + ); } - public static sendEntityResponse( - req: ExpressRequest, - res: ExpressResponse, - item: BaseModel | AnalyticsDataModel | null, - modelType: { new (): BaseModel | AnalyticsDataModel }, - options?: - | { - miscData?: JSONObject; - } - | undefined - ): void { - let response: JSONObject = {}; - - if (item && item instanceof BaseModel) { - response = BaseModel.toJSON(item, modelType as BaseModelType); - } - - if (item && item instanceof AnalyticsDataModel) { - response = AnalyticsDataModel.toJSON( - item, - modelType as AnalyticsBaseModelType - ); - } - - if (options?.miscData) { - response['_miscData'] = options.miscData; - } - - return this.sendJsonObjectResponse(req, res, response); + if (options?.miscData) { + response["_miscData"] = options.miscData; } - public static redirect( - _req: ExpressRequest, - res: ExpressResponse, - url: URL - ): void { - return res.redirect(url.toString()); + return this.sendJsonObjectResponse(req, res, response); + } + + public static redirect( + _req: ExpressRequest, + res: ExpressResponse, + url: URL, + ): void { + return res.redirect(url.toString()); + } + + public static sendJsonArrayResponse( + req: ExpressRequest, + res: ExpressResponse, + list: Array<JSONObject>, + count: PositiveNumber, + ): void { + const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest; + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + + const listData: ListData = new ListData({ + data: [], + count: new PositiveNumber(0), + skip: new PositiveNumber(0), + limit: new PositiveNumber(0), + }); + + if (!list) { + list = []; } - public static sendJsonArrayResponse( - req: ExpressRequest, - res: ExpressResponse, - list: Array<JSONObject>, - count: PositiveNumber - ): void { - const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest; - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + listData.data = list as JSONArray; - const listData: ListData = new ListData({ - data: [], - count: new PositiveNumber(0), - skip: new PositiveNumber(0), - limit: new PositiveNumber(0), - }); - - if (!list) { - list = []; - } - - listData.data = list as JSONArray; - - if (count) { - listData.count = count; - } else if (list) { - listData.count = new PositiveNumber(list.length); - } - - if (oneUptimeRequest.query['skip']) { - listData.skip = new PositiveNumber( - parseInt(oneUptimeRequest.query['skip'].toString()) - ); - } - - if (oneUptimeRequest.query['limit']) { - listData.limit = new PositiveNumber( - parseInt(oneUptimeRequest.query['limit'].toString()) - ); - } else { - listData.limit = new PositiveNumber(DEFAULT_LIMIT); - } - - if (oneUptimeRequest.query['output-type'] === 'csv') { - const csv: string = JsonToCsv.ToCsv(listData.data); - oneUptimeResponse.status(200).send(csv); - } else { - oneUptimeResponse.status(200).send(listData); - oneUptimeResponse.logBody = listData.toJSON(); // To be used in 'auditLog' middleware to log response data; - } + if (count) { + listData.count = count; + } else if (list) { + listData.count = new PositiveNumber(list.length); } - public static sendJsonObjectResponse( - req: ExpressRequest, - res: ExpressResponse, - item: JSONObject, - options?: { - statusCode?: StatusCode; - } - ): void { - const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest; - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; - - if (oneUptimeRequest.query['output-type'] === 'csv') { - const csv: string = JsonToCsv.ToCsv([item as JSONObject]); - oneUptimeResponse.status(200).send(csv); - - return; - } - - oneUptimeResponse.logBody = item as JSONObject; - oneUptimeResponse - .status(options?.statusCode ? options?.statusCode.toNumber() : 200) - .send(item); + if (oneUptimeRequest.query["skip"]) { + listData.skip = new PositiveNumber( + parseInt(oneUptimeRequest.query["skip"].toString()), + ); } - public static sendTextResponse( - _req: ExpressRequest, - res: ExpressResponse, - text: string - ): void { - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; - - oneUptimeResponse.logBody = { text: text as string }; - oneUptimeResponse.status(200).send(text); + if (oneUptimeRequest.query["limit"]) { + listData.limit = new PositiveNumber( + parseInt(oneUptimeRequest.query["limit"].toString()), + ); + } else { + listData.limit = new PositiveNumber(DEFAULT_LIMIT); } - public static sendHtmlResponse( - _req: ExpressRequest, - res: ExpressResponse, - html: string - ): void { - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + if (oneUptimeRequest.query["output-type"] === "csv") { + const csv: string = JsonToCsv.ToCsv(listData.data); + oneUptimeResponse.status(200).send(csv); + } else { + oneUptimeResponse.status(200).send(listData); + oneUptimeResponse.logBody = listData.toJSON(); // To be used in 'auditLog' middleware to log response data; + } + } - oneUptimeResponse.logBody = { html: html as string }; - oneUptimeResponse.writeHead(200, { 'Content-Type': 'text/html' }); - oneUptimeResponse.end(html); + public static sendJsonObjectResponse( + req: ExpressRequest, + res: ExpressResponse, + item: JSONObject, + options?: { + statusCode?: StatusCode; + }, + ): void { + const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest; + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + + if (oneUptimeRequest.query["output-type"] === "csv") { + const csv: string = JsonToCsv.ToCsv([item as JSONObject]); + oneUptimeResponse.status(200).send(csv); + + return; } - public static sendXmlResponse( - _req: ExpressRequest, - res: ExpressResponse, - xml: string - ): void { - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + oneUptimeResponse.logBody = item as JSONObject; + oneUptimeResponse + .status(options?.statusCode ? options?.statusCode.toNumber() : 200) + .send(item); + } - oneUptimeResponse.logBody = { xml: xml as string }; - oneUptimeResponse.writeHead(200, { 'Content-Type': 'text/xml' }); - oneUptimeResponse.end(xml); - } + public static sendTextResponse( + _req: ExpressRequest, + res: ExpressResponse, + text: string, + ): void { + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; - public static sendJavaScriptResponse( - _req: ExpressRequest, - res: ExpressResponse, - javascript: string - ): void { - const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + oneUptimeResponse.logBody = { text: text as string }; + oneUptimeResponse.status(200).send(text); + } - oneUptimeResponse.logBody = { javascript: javascript as string }; - oneUptimeResponse.writeHead(200, { 'Content-Type': 'text/javascript' }); - oneUptimeResponse.end(javascript); - } + public static sendHtmlResponse( + _req: ExpressRequest, + res: ExpressResponse, + html: string, + ): void { + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + + oneUptimeResponse.logBody = { html: html as string }; + oneUptimeResponse.writeHead(200, { "Content-Type": "text/html" }); + oneUptimeResponse.end(html); + } + + public static sendXmlResponse( + _req: ExpressRequest, + res: ExpressResponse, + xml: string, + ): void { + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + + oneUptimeResponse.logBody = { xml: xml as string }; + oneUptimeResponse.writeHead(200, { "Content-Type": "text/xml" }); + oneUptimeResponse.end(xml); + } + + public static sendJavaScriptResponse( + _req: ExpressRequest, + res: ExpressResponse, + javascript: string, + ): void { + const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse; + + oneUptimeResponse.logBody = { javascript: javascript as string }; + oneUptimeResponse.writeHead(200, { "Content-Type": "text/javascript" }); + oneUptimeResponse.end(javascript); + } } diff --git a/CommonServer/Utils/StartServer.ts b/CommonServer/Utils/StartServer.ts index 86c4d70213..9a2c35164b 100644 --- a/CommonServer/Utils/StartServer.ts +++ b/CommonServer/Utils/StartServer.ts @@ -1,81 +1,81 @@ // Connect common api's. -import CommonAPI from '../API/Index'; -import { StatusAPIOptions } from '../API/StatusAPI'; -import { AppVersion } from '../EnvironmentConfig'; -import LocalCache from '../Infrastructure/LocalCache'; -import './Environment'; +import CommonAPI from "../API/Index"; +import { StatusAPIOptions } from "../API/StatusAPI"; +import { AppVersion } from "../EnvironmentConfig"; +import LocalCache from "../Infrastructure/LocalCache"; +import "./Environment"; import Express, { - ExpressApplication, - ExpressJson, - ExpressRequest, - ExpressResponse, - ExpressStatic, - ExpressUrlEncoded, - NextFunction, - RequestHandler, -} from './Express'; -import logger from './Logger'; -import './Process'; -import Response from './Response'; -import { api } from '@opentelemetry/sdk-node'; -import StatusCode from 'Common/Types/API/StatusCode'; -import Exception from 'Common/Types/Exception/Exception'; -import NotFoundException from 'Common/Types/Exception/NotFoundException'; -import ServerException from 'Common/Types/Exception/ServerException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Port from 'Common/Types/Port'; -import Typeof from 'Common/Types/Typeof'; -import CookieParser from 'cookie-parser'; -import cors from 'cors'; -import zlib from 'zlib'; + ExpressApplication, + ExpressJson, + ExpressRequest, + ExpressResponse, + ExpressStatic, + ExpressUrlEncoded, + NextFunction, + RequestHandler, +} from "./Express"; +import logger from "./Logger"; +import "./Process"; +import Response from "./Response"; +import { api } from "@opentelemetry/sdk-node"; +import StatusCode from "Common/Types/API/StatusCode"; +import Exception from "Common/Types/Exception/Exception"; +import NotFoundException from "Common/Types/Exception/NotFoundException"; +import ServerException from "Common/Types/Exception/ServerException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Port from "Common/Types/Port"; +import Typeof from "Common/Types/Typeof"; +import CookieParser from "cookie-parser"; +import cors from "cors"; +import zlib from "zlib"; // Make sure we have stack trace for debugging. Error.stackTraceLimit = Infinity; const app: ExpressApplication = Express.getExpressApp(); -app.disable('x-powered-by'); -app.set('port', process.env['PORT']); -app.set('view engine', 'ejs'); +app.disable("x-powered-by"); +app.set("port", process.env["PORT"]); +app.set("view engine", "ejs"); app.use(CookieParser()); const jsonBodyParserMiddleware: RequestHandler = ExpressJson({ - limit: '50mb', - extended: true, + limit: "50mb", + extended: true, }); // 50 MB limit. const urlEncodedMiddleware: RequestHandler = ExpressUrlEncoded({ - limit: '50mb', - extended: true, + limit: "50mb", + extended: true, }); // 50 MB limit. const setDefaultHeaders: RequestHandler = ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, ): void => { - if (typeof req.body === Typeof.String) { - req.body = JSONFunctions.parse(req.body); - } + if (typeof req.body === Typeof.String) { + req.body = JSONFunctions.parse(req.body); + } - res.header('Access-Control-Allow-Credentials', 'true'); - res.header('Access-Control-Allow-Origin', req.headers['origin']); - res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); - res.header( - 'Access-Control-Allow-Headers', - 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept,Authorization' - ); + res.header("Access-Control-Allow-Credentials", "true"); + res.header("Access-Control-Allow-Origin", req.headers["origin"]); + res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS"); + res.header( + "Access-Control-Allow-Headers", + "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept,Authorization", + ); - next(); + next(); }; app.use(cors()); app.use(setDefaultHeaders); // Set the view engine to ejs -app.set('view engine', 'ejs'); +app.set("view engine", "ejs"); /* * Add limit of 10 MB to avoid "Request Entity too large error" @@ -83,92 +83,92 @@ app.set('view engine', 'ejs'); */ app.use((req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { - if (req.headers['content-encoding'] === 'gzip') { - const buffers: any = []; + if (req.headers["content-encoding"] === "gzip") { + const buffers: any = []; - req.on('data', (chunk: any) => { - buffers.push(chunk); - }); + req.on("data", (chunk: any) => { + buffers.push(chunk); + }); - req.on('end', () => { - const buffer: Buffer = Buffer.concat(buffers); - zlib.gunzip(buffer, (err: unknown, decoded: Buffer) => { - if (err) { - logger.error(err); - return Response.sendErrorResponse( - req, - res, - new ServerException('Error decompressing data') - ); - } + req.on("end", () => { + const buffer: Buffer = Buffer.concat(buffers); + zlib.gunzip(buffer, (err: unknown, decoded: Buffer) => { + if (err) { + logger.error(err); + return Response.sendErrorResponse( + req, + res, + new ServerException("Error decompressing data"), + ); + } - req.body = decoded; + req.body = decoded; - next(); - }); - }); - } else { - jsonBodyParserMiddleware(req, res, next); - } + next(); + }); + }); + } else { + jsonBodyParserMiddleware(req, res, next); + } }); app.use((req: ExpressRequest, res: ExpressResponse, next: NextFunction) => { - if (req.headers['content-encoding'] === 'gzip') { - next(); - } else { - urlEncodedMiddleware(req, res, next); - } + if (req.headers["content-encoding"] === "gzip") { + next(); + } else { + urlEncodedMiddleware(req, res, next); + } }); app.use((_req: ExpressRequest, _res: ExpressResponse, next: NextFunction) => { - // set span status code to OK by default. If the error occurs, it will be updated in the error handler. - const span: api.Span | undefined = api.trace.getSpan(api.context.active()); - if (span) { - span.setStatus({ code: api.SpanStatusCode.OK }); - } + // set span status code to OK by default. If the error occurs, it will be updated in the error handler. + const span: api.Span | undefined = api.trace.getSpan(api.context.active()); + if (span) { + span.setStatus({ code: api.SpanStatusCode.OK }); + } - next(); + next(); }); export interface InitFuctionOptions { - appName: string; - port?: Port | undefined; - isFrontendApp?: boolean; - statusOptions: StatusAPIOptions; + appName: string; + port?: Port | undefined; + isFrontendApp?: boolean; + statusOptions: StatusAPIOptions; } type InitFunction = ( - options: InitFuctionOptions + options: InitFuctionOptions, ) => Promise<ExpressApplication>; const init: InitFunction = async ( - data: InitFuctionOptions + data: InitFuctionOptions, ): Promise<ExpressApplication> => { - const { appName, port, isFrontendApp = false } = data; + const { appName, port, isFrontendApp = false } = data; - logger.info(`App Version: ${AppVersion.toString()}`); + logger.info(`App Version: ${AppVersion.toString()}`); - await Express.launchApplication(appName, port); - LocalCache.setString('app', 'name', appName); + await Express.launchApplication(appName, port); + LocalCache.setString("app", "name", appName); - CommonAPI({ - appName, - statusOptions: data.statusOptions, - }); + CommonAPI({ + appName, + statusOptions: data.statusOptions, + }); - if (isFrontendApp) { - app.use(ExpressStatic('/usr/src/app/public')); + if (isFrontendApp) { + app.use(ExpressStatic("/usr/src/app/public")); - app.get( - [`/${appName}/env.js`, '/env.js'], - async (req: ExpressRequest, res: ExpressResponse) => { - // ping api server for database config. + app.get( + [`/${appName}/env.js`, "/env.js"], + async (req: ExpressRequest, res: ExpressResponse) => { + // ping api server for database config. - const env: JSONObject = { - ...process.env, - }; + const env: JSONObject = { + ...process.env, + }; - const script: string = ` + const script: string = ` if(!window.process){ window.process = {} } @@ -180,114 +180,110 @@ const init: InitFunction = async ( window.process.env = JSON.parse(envVars); `; - Response.sendJavaScriptResponse(req, res, script); - } - ); + Response.sendJavaScriptResponse(req, res, script); + }, + ); - app.use(`/${appName}`, ExpressStatic('/usr/src/app/public')); + app.use(`/${appName}`, ExpressStatic("/usr/src/app/public")); - app.get( - `/${appName}/dist/bundle.js`, - (_req: ExpressRequest, res: ExpressResponse) => { - res.sendFile('/usr/src/app/public/dist/bundle.js'); - } - ); + app.get( + `/${appName}/dist/bundle.js`, + (_req: ExpressRequest, res: ExpressResponse) => { + res.sendFile("/usr/src/app/public/dist/bundle.js"); + }, + ); - app.get('/*', (_req: ExpressRequest, res: ExpressResponse) => { - res.sendFile('/usr/src/app/public/index.html'); - }); - } + app.get("/*", (_req: ExpressRequest, res: ExpressResponse) => { + res.sendFile("/usr/src/app/public/index.html"); + }); + } - return app; + return app; }; const addDefaultRoutes: PromiseVoidFunction = async (): Promise<void> => { - app.post('*', (req: ExpressRequest, res: ExpressResponse) => { - return Response.sendErrorResponse( - req, - res, - new NotFoundException(`Page not found - ${req.url}`) - ); - }); - - app.put('*', (req: ExpressRequest, res: ExpressResponse) => { - return Response.sendErrorResponse( - req, - res, - new NotFoundException(`Page not found - ${req.url}`) - ); - }); - - app.delete('*', (req: ExpressRequest, res: ExpressResponse) => { - return Response.sendErrorResponse( - req, - res, - new NotFoundException(`Page not found - ${req.url}`) - ); - }); - - app.get('*', (req: ExpressRequest, res: ExpressResponse) => { - return Response.sendErrorResponse( - req, - res, - new NotFoundException(`Page not found - ${req.url}`) - ); - }); - - // Attach Error Handler. - app.use( - ( - err: Error | Exception, - _req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ) => { - logger.error(err); - - // Mark span as error. - if (err) { - const span: api.Span | undefined = api.trace.getSpan( - api.context.active() - ); - if (span) { - // record exception - span.recordException(err); - - // set span status code to ERROR - span.setStatus({ - code: api.SpanStatusCode.ERROR, - message: err.message, - }); - } - } - - if (res.headersSent) { - return next(err); - } - - if (err instanceof Promise) { - err.catch((exception: Exception) => { - if ( - StatusCode.isValidStatusCode( - (exception as Exception).code - ) - ) { - res.status((exception as Exception).code); - res.send({ error: (exception as Exception).message }); - } else { - res.status(500); - res.send({ error: 'Server Error' }); - } - }); - } else if (err instanceof Exception) { - res.status((err as Exception).code); - res.send({ error: (err as Exception).message }); - } else { - res.status(500); - res.send({ error: 'Server Error' }); - } - } + app.post("*", (req: ExpressRequest, res: ExpressResponse) => { + return Response.sendErrorResponse( + req, + res, + new NotFoundException(`Page not found - ${req.url}`), ); + }); + + app.put("*", (req: ExpressRequest, res: ExpressResponse) => { + return Response.sendErrorResponse( + req, + res, + new NotFoundException(`Page not found - ${req.url}`), + ); + }); + + app.delete("*", (req: ExpressRequest, res: ExpressResponse) => { + return Response.sendErrorResponse( + req, + res, + new NotFoundException(`Page not found - ${req.url}`), + ); + }); + + app.get("*", (req: ExpressRequest, res: ExpressResponse) => { + return Response.sendErrorResponse( + req, + res, + new NotFoundException(`Page not found - ${req.url}`), + ); + }); + + // Attach Error Handler. + app.use( + ( + err: Error | Exception, + _req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ) => { + logger.error(err); + + // Mark span as error. + if (err) { + const span: api.Span | undefined = api.trace.getSpan( + api.context.active(), + ); + if (span) { + // record exception + span.recordException(err); + + // set span status code to ERROR + span.setStatus({ + code: api.SpanStatusCode.ERROR, + message: err.message, + }); + } + } + + if (res.headersSent) { + return next(err); + } + + if (err instanceof Promise) { + err.catch((exception: Exception) => { + if (StatusCode.isValidStatusCode((exception as Exception).code)) { + res.status((exception as Exception).code); + res.send({ error: (exception as Exception).message }); + } else { + res.status(500); + res.send({ error: "Server Error" }); + } + }); + } else if (err instanceof Exception) { + res.status((err as Exception).code); + res.send({ error: (err as Exception).message }); + } else { + res.status(500); + res.send({ error: "Server Error" }); + } + }, + ); }; export default { init, addDefaultRoutes }; diff --git a/CommonServer/Utils/Stream.ts b/CommonServer/Utils/Stream.ts index 781a990716..37c6a540c4 100644 --- a/CommonServer/Utils/Stream.ts +++ b/CommonServer/Utils/Stream.ts @@ -1,30 +1,30 @@ -import { PromiseRejectErrorFunction } from 'Common/Types/FunctionTypes'; -import { Stream } from 'stream'; +import { PromiseRejectErrorFunction } from "Common/Types/FunctionTypes"; +import { Stream } from "stream"; export default class StreamUtil { - public static convertStreamToText(stream: Stream): Promise<string> { - return new Promise<string>( - ( - resolve: (result: string) => void, - reject: PromiseRejectErrorFunction - ) => { - const chunks: Array<any> = []; + public static convertStreamToText(stream: Stream): Promise<string> { + return new Promise<string>( + ( + resolve: (result: string) => void, + reject: PromiseRejectErrorFunction, + ) => { + const chunks: Array<any> = []; - stream.on('data', (chunk: any) => { - chunks.push(Buffer.from(chunk)); - }); - stream.on('end', () => { - resolve(Buffer.concat(chunks).toString('utf8')); - }); - stream.on('error', (err: Error) => { - reject(err); - }); - } - ); - } + stream.on("data", (chunk: any) => { + chunks.push(Buffer.from(chunk)); + }); + stream.on("end", () => { + resolve(Buffer.concat(chunks).toString("utf8")); + }); + stream.on("error", (err: Error) => { + reject(err); + }); + }, + ); + } - public static async toStringArray(stream: Stream): Promise<Array<string>> { - const text: string = await StreamUtil.convertStreamToText(stream); - return text.split('\n'); - } + public static async toStringArray(stream: Stream): Promise<Array<string>> { + const text: string = await StreamUtil.convertStreamToText(stream); + return text.split("\n"); + } } diff --git a/CommonServer/Utils/Telemetry.ts b/CommonServer/Utils/Telemetry.ts index 8dbf5fab63..8e478e31d2 100644 --- a/CommonServer/Utils/Telemetry.ts +++ b/CommonServer/Utils/Telemetry.ts @@ -1,287 +1,284 @@ -import OpenTelemetryAPI, { Meter } from '@opentelemetry/api'; -import { Logger, logs } from '@opentelemetry/api-logs'; +import OpenTelemetryAPI, { Meter } from "@opentelemetry/api"; +import { Logger, logs } from "@opentelemetry/api-logs"; import { - Counter, - Histogram, - MetricOptions, - ObservableGauge, -} from '@opentelemetry/api/build/src/metrics/Metric'; -import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; -import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; -import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto'; -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; -import { AWSXRayIdGenerator } from '@opentelemetry/id-generator-aws-xray'; -import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base'; -import { Resource } from '@opentelemetry/resources'; + Counter, + Histogram, + MetricOptions, + ObservableGauge, +} from "@opentelemetry/api/build/src/metrics/Metric"; +import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; +import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"; +import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"; +import { AWSXRayIdGenerator } from "@opentelemetry/id-generator-aws-xray"; +import { CompressionAlgorithm } from "@opentelemetry/otlp-exporter-base"; +import { Resource } from "@opentelemetry/resources"; import { - BatchLogRecordProcessor, - LoggerProvider, -} from '@opentelemetry/sdk-logs'; + BatchLogRecordProcessor, + LoggerProvider, +} from "@opentelemetry/sdk-logs"; import { - MeterProvider, - PeriodicExportingMetricReader, -} from '@opentelemetry/sdk-metrics'; -import * as opentelemetry from '@opentelemetry/sdk-node'; -import { SpanExporter } from '@opentelemetry/sdk-trace-node'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; + MeterProvider, + PeriodicExportingMetricReader, +} from "@opentelemetry/sdk-metrics"; +import * as opentelemetry from "@opentelemetry/sdk-node"; +import { SpanExporter } from "@opentelemetry/sdk-trace-node"; +import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; // Enable this line to see debug logs // diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); -const serviceName: string = process.env['SERVICE_NAME'] || 'oneuptime'; +const serviceName: string = process.env["SERVICE_NAME"] || "oneuptime"; export default class Telemetry { - public static sdk: opentelemetry.NodeSDK | null = null; - public static logger: Logger | null = null; - public static meter: Meter | null = null; - public static meterProvider: MeterProvider | null = null; + public static sdk: opentelemetry.NodeSDK | null = null; + public static logger: Logger | null = null; + public static meter: Meter | null = null; + public static meterProvider: MeterProvider | null = null; - public static getHeaders(): Dictionary<string> { - if (!process.env['OPENTELEMETRY_EXPORTER_OTLP_HEADERS']) { - return {}; - } - - const headersStrings: Array<string> = - process.env['OPENTELEMETRY_EXPORTER_OTLP_HEADERS'].split(';'); - - const headers: Dictionary<string> = {}; - - for (const headerString of headersStrings) { - const header: Array<string> = headerString.split('='); - if (header.length === 2) { - headers[header[0]!.toString()] = header[1]!.toString(); - } - } - - return headers; + public static getHeaders(): Dictionary<string> { + if (!process.env["OPENTELEMETRY_EXPORTER_OTLP_HEADERS"]) { + return {}; } - public static getOtlpEndpoint(): URL | null { - if (!process.env['OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT']) { - return null; - } + const headersStrings: Array<string> = + process.env["OPENTELEMETRY_EXPORTER_OTLP_HEADERS"].split(";"); - return URL.fromString( - process.env['OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT'] || '' - ); + const headers: Dictionary<string> = {}; + + for (const headerString of headersStrings) { + const header: Array<string> = headerString.split("="); + if (header.length === 2) { + headers[header[0]!.toString()] = header[1]!.toString(); + } } - public static getOltpLogsEndpoint(): URL | null { - const oltpEndpoint: URL | null = this.getOtlpEndpoint(); + return headers; + } - if (!oltpEndpoint) { - return null; - } - - return URL.fromString(oltpEndpoint.toString() + '/v1/logs'); + public static getOtlpEndpoint(): URL | null { + if (!process.env["OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT"]) { + return null; } - public static getOltpMetricsEndpoint(): URL | null { - const oltpEndpoint: URL | null = this.getOtlpEndpoint(); + return URL.fromString( + process.env["OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT"] || "", + ); + } - if (!oltpEndpoint) { - return null; - } + public static getOltpLogsEndpoint(): URL | null { + const oltpEndpoint: URL | null = this.getOtlpEndpoint(); - return URL.fromString(oltpEndpoint.toString() + '/v1/metrics'); + if (!oltpEndpoint) { + return null; } - public static getOltpTracesEndpoint(): URL | null { - const oltpEndpoint: URL | null = this.getOtlpEndpoint(); + return URL.fromString(oltpEndpoint.toString() + "/v1/logs"); + } - if (!oltpEndpoint) { - return null; - } + public static getOltpMetricsEndpoint(): URL | null { + const oltpEndpoint: URL | null = this.getOtlpEndpoint(); - return URL.fromString(oltpEndpoint.toString() + '/v1/traces'); + if (!oltpEndpoint) { + return null; } - public static getResource(data: { serviceName: string }): Resource { - return new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: data.serviceName, + return URL.fromString(oltpEndpoint.toString() + "/v1/metrics"); + } + + public static getOltpTracesEndpoint(): URL | null { + const oltpEndpoint: URL | null = this.getOtlpEndpoint(); + + if (!oltpEndpoint) { + return null; + } + + return URL.fromString(oltpEndpoint.toString() + "/v1/traces"); + } + + public static getResource(data: { serviceName: string }): Resource { + return new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: data.serviceName, + }); + } + + public static init(data: { serviceName: string }): opentelemetry.NodeSDK { + if (!this.sdk) { + const headers: Dictionary<string> = this.getHeaders(); + + const hasHeaders: boolean = Object.keys(headers).length > 0; + + let traceExporter: SpanExporter | undefined = undefined; + + let metricReader: PeriodicExportingMetricReader | undefined = undefined; + + if (this.getOltpTracesEndpoint() && hasHeaders) { + traceExporter = new OTLPTraceExporter({ + url: this.getOltpTracesEndpoint()!.toString(), + headers: headers, + compression: CompressionAlgorithm.GZIP, }); - } + } - public static init(data: { serviceName: string }): opentelemetry.NodeSDK { - if (!this.sdk) { - const headers: Dictionary<string> = this.getHeaders(); + if (this.getOltpMetricsEndpoint() && hasHeaders) { + metricReader = new PeriodicExportingMetricReader({ + exporter: new OTLPMetricExporter({ + url: this.getOltpMetricsEndpoint()!.toString(), + headers: headers, + compression: CompressionAlgorithm.GZIP, + }), + }); + } - const hasHeaders: boolean = Object.keys(headers).length > 0; + const loggerProvider: LoggerProvider = new LoggerProvider(); - let traceExporter: SpanExporter | undefined = undefined; + if (this.getOltpLogsEndpoint() && hasHeaders) { + const logExporter: OTLPLogExporter = new OTLPLogExporter({ + url: this.getOltpLogsEndpoint()!.toString(), + headers: headers, + compression: CompressionAlgorithm.GZIP, + }); - let metricReader: PeriodicExportingMetricReader | undefined = - undefined; + loggerProvider.addLogRecordProcessor( + new BatchLogRecordProcessor(logExporter), + ); + } - if (this.getOltpTracesEndpoint() && hasHeaders) { - traceExporter = new OTLPTraceExporter({ - url: this.getOltpTracesEndpoint()!.toString(), - headers: headers, - compression: CompressionAlgorithm.GZIP, - }); - } + logs.setGlobalLoggerProvider(loggerProvider); - if (this.getOltpMetricsEndpoint() && hasHeaders) { - metricReader = new PeriodicExportingMetricReader({ - exporter: new OTLPMetricExporter({ - url: this.getOltpMetricsEndpoint()!.toString(), - headers: headers, - compression: CompressionAlgorithm.GZIP, - }), - }); - } + this.logger = logs.getLogger("default"); - const loggerProvider: LoggerProvider = new LoggerProvider(); - - if (this.getOltpLogsEndpoint() && hasHeaders) { - const logExporter: OTLPLogExporter = new OTLPLogExporter({ - url: this.getOltpLogsEndpoint()!.toString(), - headers: headers, - compression: CompressionAlgorithm.GZIP, - }); - - loggerProvider.addLogRecordProcessor( - new BatchLogRecordProcessor(logExporter) - ); - } - - logs.setGlobalLoggerProvider(loggerProvider); - - this.logger = logs.getLogger('default'); - - const nodeSdkConfiguration: Partial<opentelemetry.NodeSDKConfiguration> = - { - idGenerator: new AWSXRayIdGenerator(), - instrumentations: hasHeaders - ? [getNodeAutoInstrumentations()] - : [], - resource: this.getResource({ - serviceName: data.serviceName, - }), - logRecordProcessor: loggerProvider as any, - }; - - if (traceExporter) { - nodeSdkConfiguration.traceExporter = traceExporter; - } - - if (metricReader) { - nodeSdkConfiguration.metricReader = metricReader as any; - } - - const sdk: opentelemetry.NodeSDK = new opentelemetry.NodeSDK( - nodeSdkConfiguration - ); - - process.on('SIGTERM', () => { - sdk.shutdown().finally(() => { - return process.exit(0); - }); - }); - - sdk.start(); - - this.sdk = sdk; - } - - return this.sdk; - } - - public static getLogger(): Logger { - if (!this.logger) { - throw new Error('Logger not initialized'); - } - - return this.logger!; - } - - public static getMeterProvider(): MeterProvider { - if (!this.meterProvider) { - this.meterProvider = new MeterProvider(); - OpenTelemetryAPI.metrics.setGlobalMeterProvider(this.meterProvider); - } - - return this.meterProvider; - } - - public static getMeter(): Meter { - if (!this.meter) { - this.meter = OpenTelemetryAPI.metrics.getMeter('default'); - } - - return this.meter; - } - - public static getCounter(data: { - name: string; - description: string; - unit?: string; - }): Counter { - const { name, description } = data; - - const metricOptions: MetricOptions = { - description: description, + const nodeSdkConfiguration: Partial<opentelemetry.NodeSDKConfiguration> = + { + idGenerator: new AWSXRayIdGenerator(), + instrumentations: hasHeaders ? [getNodeAutoInstrumentations()] : [], + resource: this.getResource({ + serviceName: data.serviceName, + }), + logRecordProcessor: loggerProvider as any, }; - if (data.unit) { - metricOptions.unit = data.unit; - } + if (traceExporter) { + nodeSdkConfiguration.traceExporter = traceExporter; + } - const counter: Counter<opentelemetry.api.Attributes> = - this.getMeter().createCounter(name, metricOptions); + if (metricReader) { + nodeSdkConfiguration.metricReader = metricReader as any; + } - return counter; + const sdk: opentelemetry.NodeSDK = new opentelemetry.NodeSDK( + nodeSdkConfiguration, + ); + + process.on("SIGTERM", () => { + sdk.shutdown().finally(() => { + return process.exit(0); + }); + }); + + sdk.start(); + + this.sdk = sdk; } - // guage + return this.sdk; + } - public static getGauge(data: { - name: string; - description: string; - unit?: string; - }): ObservableGauge { - const { name, description } = data; - - const metricOptions: MetricOptions = { - description: description, - }; - - if (data.unit) { - metricOptions.unit = data.unit; - } - - const guage: ObservableGauge<opentelemetry.api.Attributes> = - this.getMeter().createObservableGauge(name, metricOptions); - - return guage; + public static getLogger(): Logger { + if (!this.logger) { + throw new Error("Logger not initialized"); } - // histogram + return this.logger!; + } - public static getHistogram(data: { - name: string; - description: string; - unit?: string; - }): Histogram { - const { name, description } = data; - - const metricOptions: MetricOptions = { - description: description, - }; - - if (data.unit) { - metricOptions.unit = data.unit; - } - - const histogram: Histogram<opentelemetry.api.Attributes> = - this.getMeter().createHistogram(name, metricOptions); - - return histogram; + public static getMeterProvider(): MeterProvider { + if (!this.meterProvider) { + this.meterProvider = new MeterProvider(); + OpenTelemetryAPI.metrics.setGlobalMeterProvider(this.meterProvider); } + + return this.meterProvider; + } + + public static getMeter(): Meter { + if (!this.meter) { + this.meter = OpenTelemetryAPI.metrics.getMeter("default"); + } + + return this.meter; + } + + public static getCounter(data: { + name: string; + description: string; + unit?: string; + }): Counter { + const { name, description } = data; + + const metricOptions: MetricOptions = { + description: description, + }; + + if (data.unit) { + metricOptions.unit = data.unit; + } + + const counter: Counter<opentelemetry.api.Attributes> = + this.getMeter().createCounter(name, metricOptions); + + return counter; + } + + // guage + + public static getGauge(data: { + name: string; + description: string; + unit?: string; + }): ObservableGauge { + const { name, description } = data; + + const metricOptions: MetricOptions = { + description: description, + }; + + if (data.unit) { + metricOptions.unit = data.unit; + } + + const guage: ObservableGauge<opentelemetry.api.Attributes> = + this.getMeter().createObservableGauge(name, metricOptions); + + return guage; + } + + // histogram + + public static getHistogram(data: { + name: string; + description: string; + unit?: string; + }): Histogram { + const { name, description } = data; + + const metricOptions: MetricOptions = { + description: description, + }; + + if (data.unit) { + metricOptions.unit = data.unit; + } + + const histogram: Histogram<opentelemetry.api.Attributes> = + this.getMeter().createHistogram(name, metricOptions); + + return histogram; + } } Telemetry.init({ - serviceName: serviceName, + serviceName: serviceName, }); diff --git a/CommonServer/Utils/VM/VMAPI.ts b/CommonServer/Utils/VM/VMAPI.ts index 065108e666..0453c428fb 100644 --- a/CommonServer/Utils/VM/VMAPI.ts +++ b/CommonServer/Utils/VM/VMAPI.ts @@ -1,188 +1,178 @@ -import { IsolatedVMHostname } from '../../EnvironmentConfig'; -import ClusterKeyAuthorization from '../../Middleware/ClusterKeyAuthorization'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ReturnResult from 'Common/Types/IsolatedVM/ReturnResult'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; -import API from 'Common/Utils/API'; +import { IsolatedVMHostname } from "../../EnvironmentConfig"; +import ClusterKeyAuthorization from "../../Middleware/ClusterKeyAuthorization"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ReturnResult from "Common/Types/IsolatedVM/ReturnResult"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; +import API from "Common/Utils/API"; export default class VMUtil { - public static async runCodeInSandbox(data: { - code: string; - options: { - args?: JSONObject | undefined; - timeout?: number | undefined; - }; - }): Promise<ReturnResult> { - const returnResultHttpResponse: - | HTTPErrorResponse - | HTTPResponse<JSONObject> = await API.post<JSONObject>( - new URL( - Protocol.HTTP, - IsolatedVMHostname, - new Route('/isolated-vm/run-code') - ), - { - ...data, - }, - { - ...ClusterKeyAuthorization.getClusterKeyHeaders(), - } - ); + public static async runCodeInSandbox(data: { + code: string; + options: { + args?: JSONObject | undefined; + timeout?: number | undefined; + }; + }): Promise<ReturnResult> { + const returnResultHttpResponse: + | HTTPErrorResponse + | HTTPResponse<JSONObject> = await API.post<JSONObject>( + new URL( + Protocol.HTTP, + IsolatedVMHostname, + new Route("/isolated-vm/run-code"), + ), + { + ...data, + }, + { + ...ClusterKeyAuthorization.getClusterKeyHeaders(), + }, + ); - if (returnResultHttpResponse instanceof HTTPErrorResponse) { - throw returnResultHttpResponse; - } - - const returnResult: ReturnResult = returnResultHttpResponse.data as any; - - return returnResult; + if (returnResultHttpResponse instanceof HTTPErrorResponse) { + throw returnResultHttpResponse; } - public static replaceValueInPlace( - storageMap: JSONObject, - valueToReplaceInPlace: string, - isJSON: boolean | undefined - ): string { - let didStringify: boolean = false; + const returnResult: ReturnResult = returnResultHttpResponse.data as any; - if (typeof valueToReplaceInPlace === 'object') { - try { - valueToReplaceInPlace = JSON.stringify(valueToReplaceInPlace); - didStringify = true; - } catch (err) { - return valueToReplaceInPlace; - } - } + return returnResult; + } - if ( - typeof valueToReplaceInPlace === 'string' && - valueToReplaceInPlace.toString().includes('{{') && - valueToReplaceInPlace.toString().includes('}}') - ) { - let valueToReplaceInPlaceCopy: string = - valueToReplaceInPlace.toString(); - const variablesInArgument: Array<string> = []; - - const regex: RegExp = /{{(.*?)}}/g; // Find all matches of the regular expression and capture the word between the braces {{x}} => x - - let match: RegExpExecArray | null = null; - - while ((match = regex.exec(valueToReplaceInPlaceCopy)) !== null) { - if (match[1]) { - variablesInArgument.push(match[1]); - } - } - - for (const variable of variablesInArgument) { - const valueToReplaceInPlace: string = VMUtil.deepFind( - storageMap as any, - variable as any - ) as string; - - if ( - valueToReplaceInPlaceCopy.trim() === - '{{' + variable + '}}' - ) { - valueToReplaceInPlaceCopy = valueToReplaceInPlace; - } else { - valueToReplaceInPlaceCopy = - valueToReplaceInPlaceCopy.replace( - '{{' + variable + '}}', - isJSON - ? VMUtil.serializeValueForJSON( - valueToReplaceInPlace - ) - : `${valueToReplaceInPlace}` - ); - } - } - - valueToReplaceInPlace = valueToReplaceInPlaceCopy; - } - - if (didStringify) { - try { - valueToReplaceInPlace = JSON.parse(valueToReplaceInPlace); - } catch (err) { - return valueToReplaceInPlace; - } - } + public static replaceValueInPlace( + storageMap: JSONObject, + valueToReplaceInPlace: string, + isJSON: boolean | undefined, + ): string { + let didStringify: boolean = false; + if (typeof valueToReplaceInPlace === "object") { + try { + valueToReplaceInPlace = JSON.stringify(valueToReplaceInPlace); + didStringify = true; + } catch (err) { return valueToReplaceInPlace; + } } - public static serializeValueForJSON(value: string): string { - if (!value) { - return value; - } + if ( + typeof valueToReplaceInPlace === "string" && + valueToReplaceInPlace.toString().includes("{{") && + valueToReplaceInPlace.toString().includes("}}") + ) { + let valueToReplaceInPlaceCopy: string = valueToReplaceInPlace.toString(); + const variablesInArgument: Array<string> = []; - if (typeof value !== 'string') { - value = JSON.stringify(value); + const regex: RegExp = /{{(.*?)}}/g; // Find all matches of the regular expression and capture the word between the braces {{x}} => x + + let match: RegExpExecArray | null = null; + + while ((match = regex.exec(valueToReplaceInPlaceCopy)) !== null) { + if (match[1]) { + variablesInArgument.push(match[1]); + } + } + + for (const variable of variablesInArgument) { + const valueToReplaceInPlace: string = VMUtil.deepFind( + storageMap as any, + variable as any, + ) as string; + + if (valueToReplaceInPlaceCopy.trim() === "{{" + variable + "}}") { + valueToReplaceInPlaceCopy = valueToReplaceInPlace; } else { - value = value - .split('\t') - .join('\\t') - .split('\n') - .join('\\n') - .split('\r') - .join('\\r') - .split('\b') - .join('\\b') - .split('\f') - .join('\\f') - .split('"') - .join('\\"'); + valueToReplaceInPlaceCopy = valueToReplaceInPlaceCopy.replace( + "{{" + variable + "}}", + isJSON + ? VMUtil.serializeValueForJSON(valueToReplaceInPlace) + : `${valueToReplaceInPlace}`, + ); } + } - return value; + valueToReplaceInPlace = valueToReplaceInPlaceCopy; } - public static deepFind(obj: JSONObject, path: string): JSONValue { - const paths: Array<string> = path.split('.'); - let current: any = JSON.parse(JSON.stringify(obj)); + if (didStringify) { + try { + valueToReplaceInPlace = JSON.parse(valueToReplaceInPlace); + } catch (err) { + return valueToReplaceInPlace; + } + } - for (let i: number = 0; i < paths.length; ++i) { - const key: string | undefined = paths[i]; + return valueToReplaceInPlace; + } - if (!key) { - return undefined; - } - const openBracketIndex: number = key.indexOf('['); - const closeBracketIndex: number = key.indexOf(']'); + public static serializeValueForJSON(value: string): string { + if (!value) { + return value; + } - if (openBracketIndex !== -1 && closeBracketIndex !== -1) { - const arrayKey: string = key.slice(0, openBracketIndex); - const indexString: string = key.slice( - openBracketIndex + 1, - closeBracketIndex - ); - let index: number = 0; + if (typeof value !== "string") { + value = JSON.stringify(value); + } else { + value = value + .split("\t") + .join("\\t") + .split("\n") + .join("\\n") + .split("\r") + .join("\\r") + .split("\b") + .join("\\b") + .split("\f") + .join("\\f") + .split('"') + .join('\\"'); + } - if (indexString !== 'last') { - index = parseInt(indexString); - } else { - index = current[arrayKey].length - 1; - } + return value; + } - if ( - Array.isArray(current[arrayKey]) && - current[arrayKey][index] - ) { - current = current[arrayKey][index]; - } else { - return undefined; - } - } else if (current && current[key] !== undefined) { - current = current[key]; - } else { - return undefined; - } + public static deepFind(obj: JSONObject, path: string): JSONValue { + const paths: Array<string> = path.split("."); + let current: any = JSON.parse(JSON.stringify(obj)); + + for (let i: number = 0; i < paths.length; ++i) { + const key: string | undefined = paths[i]; + + if (!key) { + return undefined; + } + const openBracketIndex: number = key.indexOf("["); + const closeBracketIndex: number = key.indexOf("]"); + + if (openBracketIndex !== -1 && closeBracketIndex !== -1) { + const arrayKey: string = key.slice(0, openBracketIndex); + const indexString: string = key.slice( + openBracketIndex + 1, + closeBracketIndex, + ); + let index: number = 0; + + if (indexString !== "last") { + index = parseInt(indexString); + } else { + index = current[arrayKey].length - 1; } - return current; + if (Array.isArray(current[arrayKey]) && current[arrayKey][index]) { + current = current[arrayKey][index]; + } else { + return undefined; + } + } else if (current && current[key] !== undefined) { + current = current[key]; + } else { + return undefined; + } } + + return current; + } } diff --git a/CommonServer/Utils/VM/VMRunner.ts b/CommonServer/Utils/VM/VMRunner.ts index 19995525f3..613d04573d 100644 --- a/CommonServer/Utils/VM/VMRunner.ts +++ b/CommonServer/Utils/VM/VMRunner.ts @@ -1,58 +1,58 @@ -import Dictionary from 'Common/Types/Dictionary'; -import GenericObject from 'Common/Types/GenericObject'; -import ReturnResult from 'Common/Types/IsolatedVM/ReturnResult'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; -import axios from 'axios'; -import http from 'http'; -import https from 'https'; -import vm, { Context } from 'node:vm'; +import Dictionary from "Common/Types/Dictionary"; +import GenericObject from "Common/Types/GenericObject"; +import ReturnResult from "Common/Types/IsolatedVM/ReturnResult"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; +import axios from "axios"; +import http from "http"; +import https from "https"; +import vm, { Context } from "node:vm"; export default class VMRunner { - public static async runCodeInSandbox(data: { - code: string; - options: { - timeout?: number; - args?: JSONObject | undefined; - context?: Dictionary<GenericObject> | undefined; - }; - }): Promise<ReturnResult> { - const { code, options } = data; + public static async runCodeInSandbox(data: { + code: string; + options: { + timeout?: number; + args?: JSONObject | undefined; + context?: Dictionary<GenericObject> | undefined; + }; + }): Promise<ReturnResult> { + const { code, options } = data; - const logMessages: string[] = []; + const logMessages: string[] = []; - let sandbox: Context = { - console: { - log: (...args: JSONValue[]) => { - logMessages.push(args.join(' ')); - }, - }, - http: http, - https: https, - axios: axios, - ...options.context, - }; + let sandbox: Context = { + console: { + log: (...args: JSONValue[]) => { + logMessages.push(args.join(" ")); + }, + }, + http: http, + https: https, + axios: axios, + ...options.context, + }; - if (options.args) { - sandbox = { - ...sandbox, - args: options.args, - }; - } - - vm.createContext(sandbox); // Contextify the object. - - const script: string = - `(async()=>{ - ${code} - })()` || ''; - - const returnVal: any = await vm.runInContext(script, sandbox, { - timeout: options.timeout || 5000, - }); // run the script - - return { - returnValue: returnVal, - logMessages, - }; + if (options.args) { + sandbox = { + ...sandbox, + args: options.args, + }; } + + vm.createContext(sandbox); // Contextify the object. + + const script: string = + `(async()=>{ + ${code} + })()` || ""; + + const returnVal: any = await vm.runInContext(script, sandbox, { + timeout: options.timeout || 5000, + }); // run the script + + return { + returnValue: returnVal, + logMessages, + }; + } } diff --git a/CommonUI/setupTest.js b/CommonUI/setupTest.js index ae3ec021c4..de4842fe47 100644 --- a/CommonUI/setupTest.js +++ b/CommonUI/setupTest.js @@ -1,3 +1,3 @@ -jest.mock('remark-gfm', () => { - return () => {}; +jest.mock("remark-gfm", () => { + return () => {}; }); diff --git a/CommonUI/src/Components/404.tsx b/CommonUI/src/Components/404.tsx index 7449871411..bccf400721 100644 --- a/CommonUI/src/Components/404.tsx +++ b/CommonUI/src/Components/404.tsx @@ -1,63 +1,59 @@ // Tailwind. -import Navigation from '../Utils/Navigation'; -import Button, { ButtonStyleType } from './Button/Button'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Email from 'Common/Types/Email'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Navigation from "../Utils/Navigation"; +import Button, { ButtonStyleType } from "./Button/Button"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Email from "Common/Types/Email"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - homeRoute: Route; - supportEmail: Email; + homeRoute: Route; + supportEmail: Email; } const NotFound: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mx-auto max-w-full sm:px-6 lg:px-8 rounded-lg"> - <div className="min-h-full py-16 px-6 sm:py-24 md:grid md:place-items-center lg:px-8"> - <div className="mx-auto"> - <main className="sm:flex"> - <p className="text-4xl tracking-tight text-indigo-600 sm:text-5xl"> - 404 - </p> - <div className="sm:ml-6"> - <div className="sm:border-l sm:border-gray-200 sm:pl-6"> - <h1 className="text-4xl tracking-tight text-gray-900 sm:text-5xl"> - Page not found - </h1> - <p className="mt-1 text-base text-gray-500"> - Please check the URL in the address bar and - try again. - </p> - </div> - <div className="mt-10 flex space-x-3 sm:border-l sm:border-transparent sm:pl-6"> - <Button - title="Go Home" - buttonStyle={ButtonStyleType.PRIMARY} - onClick={() => { - Navigation.navigate(props.homeRoute); - }} - /> - <Button - title="Contact Support" - buttonStyle={ButtonStyleType.NORMAL} - onClick={() => { - Navigation.navigate( - URL.fromString( - 'mailto:' + - props.supportEmail.toString() - ) - ); - }} - /> - </div> - </div> - </main> - </div> + return ( + <div className="mx-auto max-w-full sm:px-6 lg:px-8 rounded-lg"> + <div className="min-h-full py-16 px-6 sm:py-24 md:grid md:place-items-center lg:px-8"> + <div className="mx-auto"> + <main className="sm:flex"> + <p className="text-4xl tracking-tight text-indigo-600 sm:text-5xl"> + 404 + </p> + <div className="sm:ml-6"> + <div className="sm:border-l sm:border-gray-200 sm:pl-6"> + <h1 className="text-4xl tracking-tight text-gray-900 sm:text-5xl"> + Page not found + </h1> + <p className="mt-1 text-base text-gray-500"> + Please check the URL in the address bar and try again. + </p> + </div> + <div className="mt-10 flex space-x-3 sm:border-l sm:border-transparent sm:pl-6"> + <Button + title="Go Home" + buttonStyle={ButtonStyleType.PRIMARY} + onClick={() => { + Navigation.navigate(props.homeRoute); + }} + /> + <Button + title="Contact Support" + buttonStyle={ButtonStyleType.NORMAL} + onClick={() => { + Navigation.navigate( + URL.fromString("mailto:" + props.supportEmail.toString()), + ); + }} + /> + </div> </div> + </main> </div> - ); + </div> + </div> + ); }; export default NotFound; diff --git a/CommonUI/src/Components/Accordion/Accordion.tsx b/CommonUI/src/Components/Accordion/Accordion.tsx index 1da7832886..094e9fbdbf 100644 --- a/CommonUI/src/Components/Accordion/Accordion.tsx +++ b/CommonUI/src/Components/Accordion/Accordion.tsx @@ -1,123 +1,119 @@ -import Icon, { ThickProp } from '../Icon/Icon'; -import MarkdownViewer from '../Markdown.tsx/LazyMarkdownViewer'; -import IconProp from 'Common/Types/Icon/IconProp'; +import Icon, { ThickProp } from "../Icon/Icon"; +import MarkdownViewer from "../Markdown.tsx/LazyMarkdownViewer"; +import IconProp from "Common/Types/Icon/IconProp"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - title?: string | ReactElement | undefined; - description?: string | undefined; - onClose?: undefined | (() => void); - onClick?: (() => void) | undefined; - onOpen?: undefined | (() => void); - children: ReactElement | Array<ReactElement>; - rightElement?: ReactElement | undefined; - isLastElement?: boolean | undefined; - isInitiallyExpanded?: boolean | undefined; - titleClassName?: string | undefined; + title?: string | ReactElement | undefined; + description?: string | undefined; + onClose?: undefined | (() => void); + onClick?: (() => void) | undefined; + onOpen?: undefined | (() => void); + children: ReactElement | Array<ReactElement>; + rightElement?: ReactElement | undefined; + isLastElement?: boolean | undefined; + isInitiallyExpanded?: boolean | undefined; + titleClassName?: string | undefined; } const Accordion: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isOpen, setIsOpen] = useState<boolean>(false); + const [isOpen, setIsOpen] = useState<boolean>(false); - useEffect(() => { - if (props.isInitiallyExpanded) { - setIsOpen(true); - } - }, [props.isInitiallyExpanded]); + useEffect(() => { + if (props.isInitiallyExpanded) { + setIsOpen(true); + } + }, [props.isInitiallyExpanded]); - useEffect(() => { - if (!props.title) { - setIsOpen(true); - } else if (!props.isInitiallyExpanded) { - setIsOpen(false); - } - }, [props.title]); + useEffect(() => { + if (!props.title) { + setIsOpen(true); + } else if (!props.isInitiallyExpanded) { + setIsOpen(false); + } + }, [props.title]); - useEffect(() => { - props.onClick && props.onClick(); + useEffect(() => { + props.onClick && props.onClick(); - if (isOpen) { - props.onOpen && props.onOpen(); - } - - if (!isOpen) { - props.onClose && props.onClose(); - } - }, [isOpen]); - - let className: string = 'border-gray-100 border-b-2 -ml-5 -mr-5 p-5 mt-1'; - - if (props.isLastElement) { - className = '-ml-5 -mr-5 p-5 mt-1'; + if (isOpen) { + props.onOpen && props.onOpen(); } - return ( - <div className={className}> - <div> - <div - className={`flex justify-between cursor-pointer`} - role="alert" - onClick={() => { - setIsOpen(!isOpen); - }} - > - <div className="flex"> - {props.title && ( - <div> - {isOpen && ( - <Icon - className="h-4 w-4 text-gray-500" - icon={IconProp.ChevronDown} - thick={ThickProp.Thick} - /> - )} - {!isOpen && ( - <Icon - className="h-4 w-4 text-gray-500" - icon={IconProp.ChevronRight} - thick={ThickProp.Thick} - /> - )} - </div> - )} - {props.title && ( - <div - className={`ml-1 -mt-1 ${ - props.onClick ? 'cursor-pointer' : '' - }`} - > - <div - className={`text-gray-500 ${props.titleClassName}`} - > - {props.title}{' '} - </div> - <div className="mb-2 text-sm"> - {props.description && ( - <MarkdownViewer - text={props.description || ''} - /> - )} - </div> - </div> - )} - </div> - {!isOpen && <div className="">{props.rightElement}</div>} - </div> + if (!isOpen) { + props.onClose && props.onClose(); + } + }, [isOpen]); + + let className: string = "border-gray-100 border-b-2 -ml-5 -mr-5 p-5 mt-1"; + + if (props.isLastElement) { + className = "-ml-5 -mr-5 p-5 mt-1"; + } + + return ( + <div className={className}> + <div> + <div + className={`flex justify-between cursor-pointer`} + role="alert" + onClick={() => { + setIsOpen(!isOpen); + }} + > + <div className="flex"> + {props.title && ( + <div> {isOpen && ( - <div className={`space-y-5 ${props.title ? 'mt-4' : ''}`}> - {props.children} - </div> + <Icon + className="h-4 w-4 text-gray-500" + icon={IconProp.ChevronDown} + thick={ThickProp.Thick} + /> )} - </div> + {!isOpen && ( + <Icon + className="h-4 w-4 text-gray-500" + icon={IconProp.ChevronRight} + thick={ThickProp.Thick} + /> + )} + </div> + )} + {props.title && ( + <div + className={`ml-1 -mt-1 ${ + props.onClick ? "cursor-pointer" : "" + }`} + > + <div className={`text-gray-500 ${props.titleClassName}`}> + {props.title}{" "} + </div> + <div className="mb-2 text-sm"> + {props.description && ( + <MarkdownViewer text={props.description || ""} /> + )} + </div> + </div> + )} + </div> + {!isOpen && <div className="">{props.rightElement}</div>} </div> - ); + {isOpen && ( + <div className={`space-y-5 ${props.title ? "mt-4" : ""}`}> + {props.children} + </div> + )} + </div> + </div> + ); }; export default Accordion; diff --git a/CommonUI/src/Components/Accordion/AccordionGroup.tsx b/CommonUI/src/Components/Accordion/AccordionGroup.tsx index 6e8d629f35..03e2840dbe 100644 --- a/CommonUI/src/Components/Accordion/AccordionGroup.tsx +++ b/CommonUI/src/Components/Accordion/AccordionGroup.tsx @@ -1,13 +1,13 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: Array<ReactElement> | ReactElement; + children: Array<ReactElement> | ReactElement; } const AccordionGroup: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return <div className="row accordian-group">{props.children}</div>; + return <div className="row accordian-group">{props.children}</div>; }; export default AccordionGroup; diff --git a/CommonUI/src/Components/ActionButton/ActionButtonSchema.ts b/CommonUI/src/Components/ActionButton/ActionButtonSchema.ts index 8a2ceae1aa..a2fe42b702 100644 --- a/CommonUI/src/Components/ActionButton/ActionButtonSchema.ts +++ b/CommonUI/src/Components/ActionButton/ActionButtonSchema.ts @@ -1,19 +1,19 @@ -import { ButtonStyleType } from '../Button/Button'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import GenericObject from 'Common/Types/GenericObject'; -import IconProp from 'Common/Types/Icon/IconProp'; +import { ButtonStyleType } from "../Button/Button"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import GenericObject from "Common/Types/GenericObject"; +import IconProp from "Common/Types/Icon/IconProp"; interface ActionButtonSchema<T extends GenericObject> { - title: string; - icon?: undefined | IconProp; - buttonStyleType: ButtonStyleType; - isLoading?: boolean | undefined; - isVisible?: (item: T) => boolean | undefined; - onClick: ( - item: T, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => void; + title: string; + icon?: undefined | IconProp; + buttonStyleType: ButtonStyleType; + isLoading?: boolean | undefined; + isVisible?: (item: T) => boolean | undefined; + onClick: ( + item: T, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => void; } export default ActionButtonSchema; diff --git a/CommonUI/src/Components/Alerts/Alert.tsx b/CommonUI/src/Components/Alerts/Alert.tsx index d5de676874..f55dca69fc 100644 --- a/CommonUI/src/Components/Alerts/Alert.tsx +++ b/CommonUI/src/Components/Alerts/Alert.tsx @@ -1,139 +1,124 @@ -import Icon from '../Icon/Icon'; -import Color from 'Common/Types/Color'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../Icon/Icon"; +import Color from "Common/Types/Color"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export enum AlertType { - INFO, - SUCCESS, - DANGER, - WARNING, + INFO, + SUCCESS, + DANGER, + WARNING, } export enum AlertSize { - Normal, - Large, + Normal, + Large, } export interface ComponentProps { - strongTitle?: undefined | string; - title?: undefined | string; - onClose?: undefined | (() => void); - type?: undefined | AlertType; - onClick?: (() => void) | undefined; - doNotShowIcon?: boolean | undefined; - dataTestId?: string; - textClassName?: string | undefined; - className?: string | undefined; - color?: Color | undefined; - id?: string | undefined; + strongTitle?: undefined | string; + title?: undefined | string; + onClose?: undefined | (() => void); + type?: undefined | AlertType; + onClick?: (() => void) | undefined; + doNotShowIcon?: boolean | undefined; + dataTestId?: string; + textClassName?: string | undefined; + className?: string | undefined; + color?: Color | undefined; + id?: string | undefined; } const Alert: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let type: AlertType = AlertType.INFO; + let type: AlertType = AlertType.INFO; - if (props.type) { - type = props.type; - } + if (props.type) { + type = props.type; + } - let className: string = 'text-blue'; - let bgClassName: string = 'bg-blue'; + let className: string = "text-blue"; + let bgClassName: string = "bg-blue"; - if (AlertType.DANGER === type) { - className = 'text-red'; - bgClassName = 'bg-red'; - } else if (AlertType.INFO === type) { - className = 'text-blue'; - bgClassName = 'bg-blue'; - } else if (AlertType.WARNING === type) { - className = 'text-yellow'; - bgClassName = 'bg-yellow'; - } else if (AlertType.SUCCESS === type) { - className = 'text-green'; - bgClassName = 'bg-green'; - } + if (AlertType.DANGER === type) { + className = "text-red"; + bgClassName = "bg-red"; + } else if (AlertType.INFO === type) { + className = "text-blue"; + bgClassName = "bg-blue"; + } else if (AlertType.WARNING === type) { + className = "text-yellow"; + bgClassName = "bg-yellow"; + } else if (AlertType.SUCCESS === type) { + className = "text-green"; + bgClassName = "bg-green"; + } - return ( - <div - id={props.id} - className={`rounded-md ${bgClassName}-50 p-4`} - data-testid={props.dataTestId} - onClick={() => { - props.onClick && props.onClick(); - }} - role="alert" - style={ - props.color - ? { - backgroundColor: props.color?.toString(), - } - : {} + return ( + <div + id={props.id} + className={`rounded-md ${bgClassName}-50 p-4`} + data-testid={props.dataTestId} + onClick={() => { + props.onClick && props.onClick(); + }} + role="alert" + style={ + props.color + ? { + backgroundColor: props.color?.toString(), } + : {} + } + > + <div className="flex "> + {!props.doNotShowIcon && ( + <div className="flex-shrink-0"> + {AlertType.DANGER === type && ( + <Icon icon={IconProp.Alert} className="h-5 w-5 text-red-400" /> + )} + {AlertType.WARNING === type && ( + <Icon icon={IconProp.Alert} className="h-5 w-5 text-yellow-400" /> + )} + {AlertType.SUCCESS === type && ( + <Icon + icon={IconProp.CheckCircle} + className="h-5 w-5 text-green-400" + /> + )} + {AlertType.INFO === type && ( + <Icon icon={IconProp.Info} className="h-5 w-5 text-blue-400" /> + )} + </div> + )} + <div + className={ + props.className || "ml-3 flex-1 md:flex md:justify-between" + } > - <div className="flex "> - {!props.doNotShowIcon && ( - <div className="flex-shrink-0"> - {AlertType.DANGER === type && ( - <Icon - icon={IconProp.Alert} - className="h-5 w-5 text-red-400" - /> - )} - {AlertType.WARNING === type && ( - <Icon - icon={IconProp.Alert} - className="h-5 w-5 text-yellow-400" - /> - )} - {AlertType.SUCCESS === type && ( - <Icon - icon={IconProp.CheckCircle} - className="h-5 w-5 text-green-400" - /> - )} - {AlertType.INFO === type && ( - <Icon - icon={IconProp.Info} - className="h-5 w-5 text-blue-400" - /> - )} - </div> - )} - <div - className={ - props.className || - 'ml-3 flex-1 md:flex md:justify-between' - } - > - <p - className={ - props.textClassName || `text-sm ${className}-600` - } - > - {props.strongTitle}{' '} - {props.title && props.strongTitle ? '-' : ''}{' '} - {props.title} - </p> - {props.onClose && ( - <p className="mt-3 text-sm md:mt-0 md:ml-6"> - <button - onClick={() => { - props.onClose && props.onClose(); - }} - role={'alert-close-button'} - className={`whitespace-nowrap font-medium ${className}-500 hover:${className}-600`} - > - Close - <span aria-hidden="true"> →</span> - </button> - </p> - )} - </div> - </div> + <p className={props.textClassName || `text-sm ${className}-600`}> + {props.strongTitle} {props.title && props.strongTitle ? "-" : ""}{" "} + {props.title} + </p> + {props.onClose && ( + <p className="mt-3 text-sm md:mt-0 md:ml-6"> + <button + onClick={() => { + props.onClose && props.onClose(); + }} + role={"alert-close-button"} + className={`whitespace-nowrap font-medium ${className}-500 hover:${className}-600`} + > + Close + <span aria-hidden="true"> →</span> + </button> + </p> + )} </div> - ); + </div> + </div> + ); }; export default Alert; diff --git a/CommonUI/src/Components/AuthContainer/AuthContainer.tsx b/CommonUI/src/Components/AuthContainer/AuthContainer.tsx index d636dbc512..1097576bf4 100644 --- a/CommonUI/src/Components/AuthContainer/AuthContainer.tsx +++ b/CommonUI/src/Components/AuthContainer/AuthContainer.tsx @@ -1,13 +1,13 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; + title: string; } const AuthContainer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return <div>{props.title}</div>; + return <div>{props.title}</div>; }; export default AuthContainer; diff --git a/CommonUI/src/Components/Badge/Badge.tsx b/CommonUI/src/Components/Badge/Badge.tsx index c957317c92..28b8140998 100644 --- a/CommonUI/src/Components/Badge/Badge.tsx +++ b/CommonUI/src/Components/Badge/Badge.tsx @@ -1,50 +1,48 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export enum BadgeType { - DANGER, - SUCCESS, - WARNING, + DANGER, + SUCCESS, + WARNING, } export interface ComponentProps { - badgeCount: number; - badgeType?: undefined | BadgeType; - id?: string | undefined; + badgeCount: number; + badgeType?: undefined | BadgeType; + id?: string | undefined; } const Badge: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let className: string = - 'bg-white text-gray-600 ring-1 ring-inset ring-gray-300'; + let className: string = + "bg-white text-gray-600 ring-1 ring-inset ring-gray-300"; - if (props.badgeType === BadgeType.DANGER) { - className = 'bg-white text-red-600 ring-1 ring-inset ring-red-300'; - } + if (props.badgeType === BadgeType.DANGER) { + className = "bg-white text-red-600 ring-1 ring-inset ring-red-300"; + } - if (props.badgeType === BadgeType.WARNING) { - className = - 'bg-white text-yellow-600 ring-1 ring-inset ring-yellow-300'; - } + if (props.badgeType === BadgeType.WARNING) { + className = "bg-white text-yellow-600 ring-1 ring-inset ring-yellow-300"; + } - if (props.badgeType === BadgeType.SUCCESS) { - className = - 'bg-white text-emerald-600 ring-1 ring-inset ring-emerald-300'; - } + if (props.badgeType === BadgeType.SUCCESS) { + className = "bg-white text-emerald-600 ring-1 ring-inset ring-emerald-300"; + } - if (props.badgeCount) { - return ( - <span - id={props.id} - data-testid={props.id} - className={`${className} ml-auto w-11 min-w-max whitespace-nowrap rounded-full px-2.5 py-0.5 text-center text-sm font-medium leading-5 `} - aria-hidden="true" - > - {props.badgeCount} - </span> - ); - } - return <></>; + if (props.badgeCount) { + return ( + <span + id={props.id} + data-testid={props.id} + className={`${className} ml-auto w-11 min-w-max whitespace-nowrap rounded-full px-2.5 py-0.5 text-center text-sm font-medium leading-5 `} + aria-hidden="true" + > + {props.badgeCount} + </span> + ); + } + return <></>; }; export default Badge; diff --git a/CommonUI/src/Components/Banner/Banner.tsx b/CommonUI/src/Components/Banner/Banner.tsx index 2e37dce9fc..a43c25c6de 100644 --- a/CommonUI/src/Components/Banner/Banner.tsx +++ b/CommonUI/src/Components/Banner/Banner.tsx @@ -1,38 +1,38 @@ -import Link from '../Link/Link'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Link from "../Link/Link"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; - description: string; - link: URL | Route; - openInNewTab: boolean; + title: string; + description: string; + link: URL | Route; + openInNewTab: boolean; } const Banner: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="flex border-gray-200 rounded-xl border-2 py-2.5 px-6 sm:px-3.5"> - <p className="text-sm text-gray-400 hover:text-gray-500"> - <Link to={props.link} openInNewTab={props.openInNewTab}> - <> - <strong className="font-semibold">{props.title}</strong> - <svg - viewBox="0 0 2 2" - className="mx-2 inline h-0.5 w-0.5 fill-current" - aria-hidden="true" - > - <circle cx="1" cy="1" r="1" /> - </svg> - {props.description}  - <span aria-hidden="true">→</span> - </> - </Link> - </p> - </div> - ); + return ( + <div className="flex border-gray-200 rounded-xl border-2 py-2.5 px-6 sm:px-3.5"> + <p className="text-sm text-gray-400 hover:text-gray-500"> + <Link to={props.link} openInNewTab={props.openInNewTab}> + <> + <strong className="font-semibold">{props.title}</strong> + <svg + viewBox="0 0 2 2" + className="mx-2 inline h-0.5 w-0.5 fill-current" + aria-hidden="true" + > + <circle cx="1" cy="1" r="1" /> + </svg> + {props.description}  + <span aria-hidden="true">→</span> + </> + </Link> + </p> + </div> + ); }; export default Banner; diff --git a/CommonUI/src/Components/Breadcrumbs/Breadcrumbs.tsx b/CommonUI/src/Components/Breadcrumbs/Breadcrumbs.tsx index ea5e2ec1e1..129990b744 100644 --- a/CommonUI/src/Components/Breadcrumbs/Breadcrumbs.tsx +++ b/CommonUI/src/Components/Breadcrumbs/Breadcrumbs.tsx @@ -1,67 +1,67 @@ -import Icon from '../Icon/Icon'; -import UILink from '../Link/Link'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Link from 'Common/Types/Link'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../Icon/Icon"; +import UILink from "../Link/Link"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import IconProp from "Common/Types/Icon/IconProp"; +import Link from "Common/Types/Link"; +import React, { FunctionComponent, ReactElement } from "react"; interface ComponentProps { - links: Array<Link>; + links: Array<Link>; } const Breadcrumbs: FunctionComponent<ComponentProps> = ({ - links, + links, }: ComponentProps): ReactElement => { - return ( - <nav className="flex" aria-label="Breadcrumb"> - <ol role="list" className="flex items-center space-x-1"> - {links && - links.length > 0 && - links.map((link: Link, i: number) => { - return ( - <li className="breadcrumb-item" key={i}> - {i === 0 && ( - <div className="-mt-1"> - <UILink - to={link.to} - className="text-gray-400 hover:text-gray-500 -mt-1" - > - <span className="text-sm font-medium text-gray-500 hover:text-gray-700 -mt-1"> - {link.title} - </span> - </UILink> - </div> - )} + return ( + <nav className="flex" aria-label="Breadcrumb"> + <ol role="list" className="flex items-center space-x-1"> + {links && + links.length > 0 && + links.map((link: Link, i: number) => { + return ( + <li className="breadcrumb-item" key={i}> + {i === 0 && ( + <div className="-mt-1"> + <UILink + to={link.to} + className="text-gray-400 hover:text-gray-500 -mt-1" + > + <span className="text-sm font-medium text-gray-500 hover:text-gray-700 -mt-1"> + {link.title} + </span> + </UILink> + </div> + )} - {i !== 0 && ( - <div className="flex items-center"> - <Icon - className="h-5 w-5 flex-shrink-0 text-gray-400" - icon={IconProp.ChevronRight} - /> - <UILink - to={ - isCurrentPage(link.to) - ? null // Avoid linking to current page - : link.to - } - className="ml-1 text-sm font-medium text-gray-500 hover:text-gray-700 -mt-1" - > - {link.title} - </UILink> - </div> - )} - </li> - ); - })} - </ol> - </nav> - ); + {i !== 0 && ( + <div className="flex items-center"> + <Icon + className="h-5 w-5 flex-shrink-0 text-gray-400" + icon={IconProp.ChevronRight} + /> + <UILink + to={ + isCurrentPage(link.to) + ? null // Avoid linking to current page + : link.to + } + className="ml-1 text-sm font-medium text-gray-500 hover:text-gray-700 -mt-1" + > + {link.title} + </UILink> + </div> + )} + </li> + ); + })} + </ol> + </nav> + ); }; function isCurrentPage(linkTo: Route | URL): boolean { - return linkTo.toString() === window.location.pathname; + return linkTo.toString() === window.location.pathname; } export default Breadcrumbs; diff --git a/CommonUI/src/Components/BulkUpdate/BulkUpdateForm.tsx b/CommonUI/src/Components/BulkUpdate/BulkUpdateForm.tsx index d28f0a2c76..1d6449a9af 100644 --- a/CommonUI/src/Components/BulkUpdate/BulkUpdateForm.tsx +++ b/CommonUI/src/Components/BulkUpdate/BulkUpdateForm.tsx @@ -1,352 +1,301 @@ -import { GetReactElementFunction } from '../../Types/FunctionTypes'; -import Button, { ButtonSize, ButtonStyleType } from '../Button/Button'; -import Icon, { SizeProp } from '../Icon/Icon'; +import { GetReactElementFunction } from "../../Types/FunctionTypes"; +import Button, { ButtonSize, ButtonStyleType } from "../Button/Button"; +import Icon, { SizeProp } from "../Icon/Icon"; import ConfirmModal, { - ComponentProps as ConfirmModalProps, -} from '../Modal/ConfirmModal'; -import ProgressBar, { ProgressBarSize } from '../ProgressBar/ProgressBar'; -import ShortcutKey from '../ShortcutKey/ShortcutKey'; -import SimpleLogViewer from '../SimpleLogViewer/SimpleLogViewer'; -import { Green, Red } from 'Common/Types/BrandColors'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import GenericObject from 'Common/Types/GenericObject'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { ReactElement } from 'react'; + ComponentProps as ConfirmModalProps, +} from "../Modal/ConfirmModal"; +import ProgressBar, { ProgressBarSize } from "../ProgressBar/ProgressBar"; +import ShortcutKey from "../ShortcutKey/ShortcutKey"; +import SimpleLogViewer from "../SimpleLogViewer/SimpleLogViewer"; +import { Green, Red } from "Common/Types/BrandColors"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import GenericObject from "Common/Types/GenericObject"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { ReactElement } from "react"; export interface BulkActionFailed<T extends GenericObject> { - failedMessage: string | ReactElement; - item: T; + failedMessage: string | ReactElement; + item: T; } export interface ProgressInfo<T extends GenericObject> { - inProgressItems: Array<T>; - successItems: Array<T>; - failed: Array<BulkActionFailed<T>>; - totalItems: Array<T>; + inProgressItems: Array<T>; + successItems: Array<T>; + failed: Array<BulkActionFailed<T>>; + totalItems: Array<T>; } export type OnProgressInfoFunction<T extends GenericObject> = ( - progressInfo: ProgressInfo<T> + progressInfo: ProgressInfo<T>, ) => void; export type OnBulkActionStart = () => void; export type OnBulkActionEnd = () => void; export interface BulkActionOnClickProps<T extends GenericObject> { - items: Array<T>; - onProgressInfo: OnProgressInfoFunction<T>; - onBulkActionStart: OnBulkActionStart; - onBulkActionEnd: OnBulkActionEnd; + items: Array<T>; + onProgressInfo: OnProgressInfoFunction<T>; + onBulkActionStart: OnBulkActionStart; + onBulkActionEnd: OnBulkActionEnd; } export interface BulkActionButtonSchema<T extends GenericObject> { - title: string; - icon?: undefined | IconProp; - buttonStyleType: ButtonStyleType; - isLoading?: boolean | undefined; - isVisible?: (items: Array<T>) => boolean | undefined; - className?: string | undefined; - onClick: (props: BulkActionOnClickProps<T>) => Promise<void>; - disabled?: boolean | undefined; - shortcutKey?: undefined | ShortcutKey; - confirmMessage?: ((items: Array<T>) => string) | undefined; - confirmTitle?: ((items: Array<T>) => string) | undefined; - confirmButtonStyleType?: ButtonStyleType; + title: string; + icon?: undefined | IconProp; + buttonStyleType: ButtonStyleType; + isLoading?: boolean | undefined; + isVisible?: (items: Array<T>) => boolean | undefined; + className?: string | undefined; + onClick: (props: BulkActionOnClickProps<T>) => Promise<void>; + disabled?: boolean | undefined; + shortcutKey?: undefined | ShortcutKey; + confirmMessage?: ((items: Array<T>) => string) | undefined; + confirmTitle?: ((items: Array<T>) => string) | undefined; + confirmButtonStyleType?: ButtonStyleType; } export interface ComponentProps<T extends GenericObject> { - selectedItems: Array<T>; - isAllItemsSelected: boolean; - onSelectAllClick: () => void; - singularLabel: string; - pluralLabel: string; - onClearSelectionClick: () => void; - buttons: Array<BulkActionButtonSchema<T>>; - onActionStart?: () => void; - onActionEnd?: () => void; - itemToString?: (item: T) => string; + selectedItems: Array<T>; + isAllItemsSelected: boolean; + onSelectAllClick: () => void; + singularLabel: string; + pluralLabel: string; + onClearSelectionClick: () => void; + buttons: Array<BulkActionButtonSchema<T>>; + onActionStart?: () => void; + onActionEnd?: () => void; + itemToString?: (item: T) => string; } const BulkUpdateForm: <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const [confirmModalProps, setConfirmModalProps] = - React.useState<ConfirmModalProps | null>(null); + const [confirmModalProps, setConfirmModalProps] = + React.useState<ConfirmModalProps | null>(null); - const [progressInfo, setProgressInfo] = - React.useState<ProgressInfo<T> | null>(null); - const [showProgressInfoModal, setShowProgressInfoModal] = - React.useState<boolean>(false); + const [progressInfo, setProgressInfo] = + React.useState<ProgressInfo<T> | null>(null); + const [showProgressInfoModal, setShowProgressInfoModal] = + React.useState<boolean>(false); - const [actionInProgress, setActionInProgress] = - React.useState<boolean>(false); + const [actionInProgress, setActionInProgress] = + React.useState<boolean>(false); - const [actionName, setActionName] = React.useState<string>(''); + const [actionName, setActionName] = React.useState<string>(""); - if (props.selectedItems.length === 0) { - return <></>; + if (props.selectedItems.length === 0) { + return <></>; + } + + const showProgressInfo: GetReactElementFunction = (): ReactElement => { + if (actionInProgress && progressInfo) { + return ( + <ProgressBar + count={progressInfo.successItems.length + progressInfo.failed.length} + totalCount={progressInfo.totalItems.length} + suffix={props.pluralLabel} + size={ProgressBarSize.Small} + /> + ); } - const showProgressInfo: GetReactElementFunction = (): ReactElement => { - if (actionInProgress && progressInfo) { - return ( - <ProgressBar - count={ - progressInfo.successItems.length + - progressInfo.failed.length - } - totalCount={progressInfo.totalItems.length} - suffix={props.pluralLabel} - size={ProgressBarSize.Small} - /> - ); - } - - if (!actionInProgress && progressInfo) { - return ( - <div className="mt-1 mb-1 space-y-1"> - <div className="flex"> - <Icon - className="h-5 w-5" - icon={IconProp.CheckCircle} - color={Green} - /> - <div className="ml-1 font-medium"> - {progressInfo.successItems.length}{' '} - {props.pluralLabel} Succeeded - </div> - </div> - {progressInfo.failed.length > 0 && ( - <div> - <div className="flex mt-3"> - <Icon - className="h-5 w-5" - icon={IconProp.Close} - color={Red} - /> - <div className="ml-1 font-medium"> - {progressInfo.failed.length}{' '} - {props.pluralLabel} Failed. Here is more - information: - </div> - </div> - <div> - <SimpleLogViewer> - {progressInfo.failed.map( - ( - failedItem: BulkActionFailed<T>, - i: number - ) => { - return ( - <div - className="flex mb-2" - key={i} - > - {actionName}{' '} - {props.itemToString - ? props.itemToString( - failedItem.item - ) - : ''}{' '} - {'Failed: '} - {failedItem.failedMessage} - </div> - ); - } - )} - </SimpleLogViewer> - </div> - </div> - )} - </div> - ); - } - - return <></>; - }; - - return ( - <div> - <div> - <div className="flex mt-5 mb-5 bg-gray-50 rounded rounded-xl p-5 border border-2 border-gray-100 justify-between"> - <div className="w-full -mt-1"> - <div className="flex mt-1"> - <div className="flex-auto py-0.5 text-sm leading-5"> - <span className="font-semibold"> - {props.selectedItems.length}{' '} - {props.pluralLabel + ' ' || ''} - Selected - </span>{' '} - {props.isAllItemsSelected && - props.selectedItems.length === - LIMIT_PER_PROJECT && ( - <span className="text-gray-500"> - (You can only select{' '} - {LIMIT_PER_PROJECT}{' '} - {props.pluralLabel} at a time. This - is for performance reasons.) - </span> - )} - </div> - </div> - <div className="flex -ml-3 mt-1"> - {/** Edit Filter Button */} - {!props.isAllItemsSelected && ( - <Button - className="font-medium text-gray-900" - icon={IconProp.CheckCircle} - onClick={() => { - props.onSelectAllClick(); - }} - title={`Select All ${props.pluralLabel}`} - iconSize={SizeProp.Smaller} - buttonStyle={ButtonStyleType.SECONDARY_LINK} - /> - )} - - {/** Clear Filter Button */} - <Button - onClick={() => { - props.onClearSelectionClick(); - }} - className="font-medium text-gray-900 -ml-2" - icon={IconProp.Close} - title="Clear Selection" - buttonStyle={ButtonStyleType.SECONDARY_LINK} - /> - </div> - </div> - - <div className="flex w-full h-full -mt-1 justify-end mt-auto mb-auto"> - {props.buttons?.map( - (button: BulkActionButtonSchema<T>, i: number) => { - return ( - <div key={i}> - <Button - key={i} - title={button.title} - buttonStyle={button.buttonStyleType} - className={button.className} - onClick={async () => { - const buttonClickObject: BulkActionOnClickProps<T> = - { - items: props.selectedItems, - onProgressInfo: ( - progressInfo: ProgressInfo<T> - ) => { - setProgressInfo( - progressInfo - ); - }, - onBulkActionStart: - () => { - setShowProgressInfoModal( - true - ); - setActionName( - button.title - ); - setProgressInfo( - { - inProgressItems: - props.selectedItems, - successItems: - [], - failed: [], - totalItems: - props.selectedItems, - } - ); - setActionInProgress( - true - ); - props.onActionStart && - props.onActionStart(); - }, - onBulkActionEnd: () => { - setActionInProgress( - false - ); - setActionName(''); - }, - }; - - if (button.confirmMessage) { - setConfirmModalProps({ - title: button.confirmTitle - ? button.confirmTitle( - props.selectedItems - ) - : 'Confirm', - description: - button.confirmMessage( - props.selectedItems - ), - submitButtonType: - button.confirmButtonStyleType, - submitButtonText: - button.title, - onClose: () => { - setConfirmModalProps( - null - ); - }, - onSubmit: async () => { - await button.onClick( - buttonClickObject - ); - }, - }); - return; - } - - if (button.onClick) { - await button.onClick( - buttonClickObject - ); - } - }} - disabled={button.disabled} - icon={button.icon} - shortcutKey={button.shortcutKey} - buttonSize={ButtonSize.Small} - dataTestId="card-button" - /> - </div> - ); - } - )} - </div> - </div> + if (!actionInProgress && progressInfo) { + return ( + <div className="mt-1 mb-1 space-y-1"> + <div className="flex"> + <Icon + className="h-5 w-5" + icon={IconProp.CheckCircle} + color={Green} + /> + <div className="ml-1 font-medium"> + {progressInfo.successItems.length} {props.pluralLabel} Succeeded </div> - - {confirmModalProps && ( - <ConfirmModal - {...confirmModalProps} - onSubmit={() => { - confirmModalProps.onSubmit(); - setConfirmModalProps(null); - }} - /> - )} - - {showProgressInfoModal && progressInfo && ( - <ConfirmModal - title={actionInProgress ? 'In Progress' : 'Completed'} - description={<div>{showProgressInfo()}</div>} - submitButtonType={ButtonStyleType.NORMAL} - disableSubmitButton={actionInProgress} - submitButtonText="Close" - onSubmit={() => { - setShowProgressInfoModal(false); - props.onActionEnd && props.onActionEnd(); - }} - /> - )} + </div> + {progressInfo.failed.length > 0 && ( + <div> + <div className="flex mt-3"> + <Icon className="h-5 w-5" icon={IconProp.Close} color={Red} /> + <div className="ml-1 font-medium"> + {progressInfo.failed.length} {props.pluralLabel} Failed. Here + is more information: + </div> + </div> + <div> + <SimpleLogViewer> + {progressInfo.failed.map( + (failedItem: BulkActionFailed<T>, i: number) => { + return ( + <div className="flex mb-2" key={i}> + {actionName}{" "} + {props.itemToString + ? props.itemToString(failedItem.item) + : ""}{" "} + {"Failed: "} + {failedItem.failedMessage} + </div> + ); + }, + )} + </SimpleLogViewer> + </div> + </div> + )} </div> - ); + ); + } + + return <></>; + }; + + return ( + <div> + <div> + <div className="flex mt-5 mb-5 bg-gray-50 rounded rounded-xl p-5 border border-2 border-gray-100 justify-between"> + <div className="w-full -mt-1"> + <div className="flex mt-1"> + <div className="flex-auto py-0.5 text-sm leading-5"> + <span className="font-semibold"> + {props.selectedItems.length} {props.pluralLabel + " " || ""} + Selected + </span>{" "} + {props.isAllItemsSelected && + props.selectedItems.length === LIMIT_PER_PROJECT && ( + <span className="text-gray-500"> + (You can only select {LIMIT_PER_PROJECT}{" "} + {props.pluralLabel} at a time. This is for performance + reasons.) + </span> + )} + </div> + </div> + <div className="flex -ml-3 mt-1"> + {/** Edit Filter Button */} + {!props.isAllItemsSelected && ( + <Button + className="font-medium text-gray-900" + icon={IconProp.CheckCircle} + onClick={() => { + props.onSelectAllClick(); + }} + title={`Select All ${props.pluralLabel}`} + iconSize={SizeProp.Smaller} + buttonStyle={ButtonStyleType.SECONDARY_LINK} + /> + )} + + {/** Clear Filter Button */} + <Button + onClick={() => { + props.onClearSelectionClick(); + }} + className="font-medium text-gray-900 -ml-2" + icon={IconProp.Close} + title="Clear Selection" + buttonStyle={ButtonStyleType.SECONDARY_LINK} + /> + </div> + </div> + + <div className="flex w-full h-full -mt-1 justify-end mt-auto mb-auto"> + {props.buttons?.map( + (button: BulkActionButtonSchema<T>, i: number) => { + return ( + <div key={i}> + <Button + key={i} + title={button.title} + buttonStyle={button.buttonStyleType} + className={button.className} + onClick={async () => { + const buttonClickObject: BulkActionOnClickProps<T> = { + items: props.selectedItems, + onProgressInfo: (progressInfo: ProgressInfo<T>) => { + setProgressInfo(progressInfo); + }, + onBulkActionStart: () => { + setShowProgressInfoModal(true); + setActionName(button.title); + setProgressInfo({ + inProgressItems: props.selectedItems, + successItems: [], + failed: [], + totalItems: props.selectedItems, + }); + setActionInProgress(true); + props.onActionStart && props.onActionStart(); + }, + onBulkActionEnd: () => { + setActionInProgress(false); + setActionName(""); + }, + }; + + if (button.confirmMessage) { + setConfirmModalProps({ + title: button.confirmTitle + ? button.confirmTitle(props.selectedItems) + : "Confirm", + description: button.confirmMessage( + props.selectedItems, + ), + submitButtonType: button.confirmButtonStyleType, + submitButtonText: button.title, + onClose: () => { + setConfirmModalProps(null); + }, + onSubmit: async () => { + await button.onClick(buttonClickObject); + }, + }); + return; + } + + if (button.onClick) { + await button.onClick(buttonClickObject); + } + }} + disabled={button.disabled} + icon={button.icon} + shortcutKey={button.shortcutKey} + buttonSize={ButtonSize.Small} + dataTestId="card-button" + /> + </div> + ); + }, + )} + </div> + </div> + </div> + + {confirmModalProps && ( + <ConfirmModal + {...confirmModalProps} + onSubmit={() => { + confirmModalProps.onSubmit(); + setConfirmModalProps(null); + }} + /> + )} + + {showProgressInfoModal && progressInfo && ( + <ConfirmModal + title={actionInProgress ? "In Progress" : "Completed"} + description={<div>{showProgressInfo()}</div>} + submitButtonType={ButtonStyleType.NORMAL} + disableSubmitButton={actionInProgress} + submitButtonText="Close" + onSubmit={() => { + setShowProgressInfoModal(false); + props.onActionEnd && props.onActionEnd(); + }} + /> + )} + </div> + ); }; export default BulkUpdateForm; diff --git a/CommonUI/src/Components/Button/Button.tsx b/CommonUI/src/Components/Button/Button.tsx index d8028e3afa..cc9637d952 100644 --- a/CommonUI/src/Components/Button/Button.tsx +++ b/CommonUI/src/Components/Button/Button.tsx @@ -1,259 +1,249 @@ -import { KeyboardEventProp } from '../../Types/HtmlEvents'; -import Icon, { SizeProp } from '../Icon/Icon'; -import ShortcutKey from '../ShortcutKey/ShortcutKey'; -import ButtonType from './ButtonTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import { KeyboardEventProp } from "../../Types/HtmlEvents"; +import Icon, { SizeProp } from "../Icon/Icon"; +import ShortcutKey from "../ShortcutKey/ShortcutKey"; +import ButtonType from "./ButtonTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export enum ButtonStyleType { - PRIMARY, - SECONDARY, - OUTLINE, - NORMAL, - DANGER, - DANGER_OUTLINE, - SUCCESS, - SUCCESS_OUTLINE, - WARNING, - WARNING_OUTLINE, - ICON_LIGHT, - LINK, - SECONDARY_LINK, - ICON, + PRIMARY, + SECONDARY, + OUTLINE, + NORMAL, + DANGER, + DANGER_OUTLINE, + SUCCESS, + SUCCESS_OUTLINE, + WARNING, + WARNING_OUTLINE, + ICON_LIGHT, + LINK, + SECONDARY_LINK, + ICON, } export enum ButtonSize { - Normal = 'px-3 py-2', - Small = 'px-2 py-1', - Large = 'px-4 py-2', - ExtraSmall = 'px-0 py-0', + Normal = "px-3 py-2", + Small = "px-2 py-1", + Large = "px-4 py-2", + ExtraSmall = "px-0 py-0", } /* Defining the props that the component will take. */ export interface ComponentProps { - title?: undefined | string; - onClick?: undefined | (() => void); - disabled?: undefined | boolean; - id?: undefined | string; - shortcutKey?: undefined | ShortcutKey; - type?: undefined | ButtonType; - isLoading?: undefined | boolean; - style?: undefined | React.CSSProperties; - icon?: undefined | IconProp; - iconSize?: undefined | SizeProp; - buttonStyle?: undefined | ButtonStyleType; - buttonSize?: ButtonSize | undefined; - dataTestId?: string; - className?: string | undefined; + title?: undefined | string; + onClick?: undefined | (() => void); + disabled?: undefined | boolean; + id?: undefined | string; + shortcutKey?: undefined | ShortcutKey; + type?: undefined | ButtonType; + isLoading?: undefined | boolean; + style?: undefined | React.CSSProperties; + icon?: undefined | IconProp; + iconSize?: undefined | SizeProp; + buttonStyle?: undefined | ButtonStyleType; + buttonSize?: ButtonSize | undefined; + dataTestId?: string; + className?: string | undefined; } const Button: FunctionComponent<ComponentProps> = ({ - title, - onClick, - disabled, - id, - shortcutKey, - type = ButtonType.Button, - isLoading = false, - style, - icon, - iconSize, - buttonStyle = ButtonStyleType.NORMAL, - buttonSize = ButtonSize.Normal, - dataTestId, - className, + title, + onClick, + disabled, + id, + shortcutKey, + type = ButtonType.Button, + isLoading = false, + style, + icon, + iconSize, + buttonStyle = ButtonStyleType.NORMAL, + buttonSize = ButtonSize.Normal, + dataTestId, + className, }: ComponentProps): ReactElement => { - useEffect(() => { - // componentDidMount - if (shortcutKey) { - window.addEventListener(`keydown`, (e: KeyboardEventProp) => { - return handleKeyboard(e); - }); - } + useEffect(() => { + // componentDidMount + if (shortcutKey) { + window.addEventListener(`keydown`, (e: KeyboardEventProp) => { + return handleKeyboard(e); + }); + } - // componentDidUnmount - return () => { - if (shortcutKey) { - window.removeEventListener( - `keydown`, - (e: KeyboardEventProp) => { - return handleKeyboard(e); - } - ); - } - }; - }); - - type HandleKeyboardFunction = (event: KeyboardEventProp) => void; - - const handleKeyboard: HandleKeyboardFunction = ( - event: KeyboardEventProp - ): void => { - if ( - event.target instanceof HTMLBodyElement && - event.key && - shortcutKey - ) { - switch (event.key) { - case shortcutKey.toUpperCase(): - case shortcutKey.toLowerCase(): - onClick && onClick(); - return; - default: - return; - } - } + // componentDidUnmount + return () => { + if (shortcutKey) { + window.removeEventListener(`keydown`, (e: KeyboardEventProp) => { + return handleKeyboard(e); + }); + } }; + }); - let buttonStyleCssClass: string = `inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm`; - let loadingIconClassName: string = `w-5 h-5 mr-3 -ml-1 mr-1 animate-spin`; - let iconClassName: string = `w-5 h-5`; + type HandleKeyboardFunction = (event: KeyboardEventProp) => void; - if ( - buttonStyle !== ButtonStyleType.ICON && - buttonStyle !== ButtonStyleType.ICON_LIGHT - ) { - iconClassName += ` mr-1`; - } else { - iconClassName += ` m-1`; + const handleKeyboard: HandleKeyboardFunction = ( + event: KeyboardEventProp, + ): void => { + if (event.target instanceof HTMLBodyElement && event.key && shortcutKey) { + switch (event.key) { + case shortcutKey.toUpperCase(): + case shortcutKey.toLowerCase(): + onClick && onClick(); + return; + default: + return; + } } + }; - if (buttonStyle === ButtonStyleType.LINK) { - buttonStyleCssClass = `text-indigo-600 hover:text-indigo-900 space-x-2`; + let buttonStyleCssClass: string = `inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm`; + let loadingIconClassName: string = `w-5 h-5 mr-3 -ml-1 mr-1 animate-spin`; + let iconClassName: string = `w-5 h-5`; - if (icon) { - buttonStyleCssClass += ` flex`; + if ( + buttonStyle !== ButtonStyleType.ICON && + buttonStyle !== ButtonStyleType.ICON_LIGHT + ) { + iconClassName += ` mr-1`; + } else { + iconClassName += ` m-1`; + } + + if (buttonStyle === ButtonStyleType.LINK) { + buttonStyleCssClass = `text-indigo-600 hover:text-indigo-900 space-x-2`; + + if (icon) { + buttonStyleCssClass += ` flex`; + } + } + + if (buttonStyle === ButtonStyleType.SECONDARY_LINK) { + buttonStyleCssClass = `text-sm text-gray-400 hover:text-gray-500 space-x-2`; + + if (icon) { + buttonStyleCssClass += ` flex`; + } + } + + if (buttonStyle === ButtonStyleType.DANGER) { + buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-transparent bg-red-600 text-base font-medium text-white shadow-sm ${ + disabled ? "hover:bg-red-700" : "" + } focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm`; + } + + if (buttonStyle === ButtonStyleType.DANGER_OUTLINE) { + buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-red-700 bg-white text-base font-medium text-red-700 shadow-sm ${ + disabled ? "hover:bg-red-50" : "" + } focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm`; + } + + if (buttonStyle === ButtonStyleType.PRIMARY) { + loadingIconClassName += ` text-indigo-100`; + buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 text-base font-medium text-white shadow-sm ${ + disabled ? "hover:bg-indigo-700" : "" + } focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm`; + + if (disabled) { + buttonStyleCssClass += ` bg-indigo-300`; + } + } + + if (buttonStyle === ButtonStyleType.SECONDARY) { + loadingIconClassName += ` text-indigo-500`; + buttonStyleCssClass = `inline-flex items-center rounded-md border border-transparent bg-indigo-100 text-sm font-medium text-indigo-700 ${ + disabled ? "hover:bg-indigo-200" : "" + } focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2`; + + if (disabled) { + buttonStyleCssClass += ` bg-indigo-300`; + } + } + + if (buttonStyle === ButtonStyleType.ICON_LIGHT) { + buttonStyleCssClass = `rounded-md bg-white text-gray-400 ${ + disabled ? "hover:text-gray-500" : "" + } focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2`; + } + + if (buttonStyle === ButtonStyleType.ICON) { + buttonStyleCssClass = `rounded-md bg-white text-gray-600 ${ + disabled ? "hover:text-gray-900" : "" + } focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2`; + } + + if (buttonStyle === ButtonStyleType.OUTLINE) { + buttonStyleCssClass = `flex btn-outline-secondary background-very-light-Gray500-on-hover sm:text-sm ml-1`; + } + + if (buttonStyle === ButtonStyleType.SUCCESS) { + buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-transparent bg-green-600 text-base font-medium text-white shadow-sm ${ + disabled ? "hover:bg-green-700" : "" + } focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm`; + } + + if (buttonStyle === ButtonStyleType.SUCCESS_OUTLINE) { + buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-green-700 bg-white text-base font-medium text-green-700 shadow-sm ${ + disabled ? "hover:bg-green-50" : "" + } focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm`; + } + + if (buttonStyle === ButtonStyleType.WARNING) { + buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-transparent bg-yellow-600 text-base font-medium text-white shadow-sm ${ + disabled ? "hover:bg-yellow-700" : "" + } focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm`; + } + + if (buttonStyle === ButtonStyleType.WARNING_OUTLINE) { + buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-yellow-700 bg-white text-base font-medium text-yellow-700 shadow-sm ${ + disabled ? "hover:bg-yellow-50" : "" + } focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm`; + } + + buttonStyleCssClass += ` ` + buttonSize; + + if (className) { + buttonStyleCssClass += ` ` + className; + } + + return ( + <button + style={style} + id={id} + onClick={() => { + if (onClick) { + onClick(); } - } + }} + data-testid={dataTestId} + type={type} + disabled={disabled || isLoading} + className={buttonStyleCssClass} + > + {isLoading && buttonStyle !== ButtonStyleType.ICON && ( + <Icon icon={IconProp.Spinner} className={loadingIconClassName} /> + )} - if (buttonStyle === ButtonStyleType.SECONDARY_LINK) { - buttonStyleCssClass = `text-sm text-gray-400 hover:text-gray-500 space-x-2`; + {!isLoading && icon && ( + <Icon + icon={icon} + className={iconClassName} + size={iconSize || undefined} + /> + )} - if (icon) { - buttonStyleCssClass += ` flex`; - } - } + {title && buttonStyle !== ButtonStyleType.ICON ? title : ``} - if (buttonStyle === ButtonStyleType.DANGER) { - buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-transparent bg-red-600 text-base font-medium text-white shadow-sm ${ - disabled ? 'hover:bg-red-700' : '' - } focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm`; - } - - if (buttonStyle === ButtonStyleType.DANGER_OUTLINE) { - buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-red-700 bg-white text-base font-medium text-red-700 shadow-sm ${ - disabled ? 'hover:bg-red-50' : '' - } focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm`; - } - - if (buttonStyle === ButtonStyleType.PRIMARY) { - loadingIconClassName += ` text-indigo-100`; - buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 text-base font-medium text-white shadow-sm ${ - disabled ? 'hover:bg-indigo-700' : '' - } focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm`; - - if (disabled) { - buttonStyleCssClass += ` bg-indigo-300`; - } - } - - if (buttonStyle === ButtonStyleType.SECONDARY) { - loadingIconClassName += ` text-indigo-500`; - buttonStyleCssClass = `inline-flex items-center rounded-md border border-transparent bg-indigo-100 text-sm font-medium text-indigo-700 ${ - disabled ? 'hover:bg-indigo-200' : '' - } focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2`; - - if (disabled) { - buttonStyleCssClass += ` bg-indigo-300`; - } - } - - if (buttonStyle === ButtonStyleType.ICON_LIGHT) { - buttonStyleCssClass = `rounded-md bg-white text-gray-400 ${ - disabled ? 'hover:text-gray-500' : '' - } focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2`; - } - - if (buttonStyle === ButtonStyleType.ICON) { - buttonStyleCssClass = `rounded-md bg-white text-gray-600 ${ - disabled ? 'hover:text-gray-900' : '' - } focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2`; - } - - if (buttonStyle === ButtonStyleType.OUTLINE) { - buttonStyleCssClass = `flex btn-outline-secondary background-very-light-Gray500-on-hover sm:text-sm ml-1`; - } - - if (buttonStyle === ButtonStyleType.SUCCESS) { - buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-transparent bg-green-600 text-base font-medium text-white shadow-sm ${ - disabled ? 'hover:bg-green-700' : '' - } focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm`; - } - - if (buttonStyle === ButtonStyleType.SUCCESS_OUTLINE) { - buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-green-700 bg-white text-base font-medium text-green-700 shadow-sm ${ - disabled ? 'hover:bg-green-50' : '' - } focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm`; - } - - if (buttonStyle === ButtonStyleType.WARNING) { - buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-transparent bg-yellow-600 text-base font-medium text-white shadow-sm ${ - disabled ? 'hover:bg-yellow-700' : '' - } focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm`; - } - - if (buttonStyle === ButtonStyleType.WARNING_OUTLINE) { - buttonStyleCssClass = `inline-flex w-full justify-center rounded-md border border-yellow-700 bg-white text-base font-medium text-yellow-700 shadow-sm ${ - disabled ? 'hover:bg-yellow-50' : '' - } focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm`; - } - - buttonStyleCssClass += ` ` + buttonSize; - - if (className) { - buttonStyleCssClass += ` ` + className; - } - - return ( - <button - style={style} - id={id} - onClick={() => { - if (onClick) { - onClick(); - } - }} - data-testid={dataTestId} - type={type} - disabled={disabled || isLoading} - className={buttonStyleCssClass} - > - {isLoading && buttonStyle !== ButtonStyleType.ICON && ( - <Icon - icon={IconProp.Spinner} - className={loadingIconClassName} - /> - )} - - {!isLoading && icon && ( - <Icon - icon={icon} - className={iconClassName} - size={iconSize || undefined} - /> - )} - - {title && buttonStyle !== ButtonStyleType.ICON ? title : ``} - - {shortcutKey && ( - <div className="ml-2"> - <kbd className="inline-flex items-center rounded border border-gray-200 px-2 font-sans text-sm font-medium text-gray-400"> - {shortcutKey} - </kbd> - </div> - )} - </button> - ); + {shortcutKey && ( + <div className="ml-2"> + <kbd className="inline-flex items-center rounded border border-gray-200 px-2 font-sans text-sm font-medium text-gray-400"> + {shortcutKey} + </kbd> + </div> + )} + </button> + ); }; export default Button; diff --git a/CommonUI/src/Components/Button/ButtonTypes.ts b/CommonUI/src/Components/Button/ButtonTypes.ts index 9413d4e71d..136ef3a00f 100644 --- a/CommonUI/src/Components/Button/ButtonTypes.ts +++ b/CommonUI/src/Components/Button/ButtonTypes.ts @@ -1,7 +1,7 @@ enum ButtonType { - Submit = 'submit', - Reset = 'reset', - Button = 'button', + Submit = "submit", + Reset = "reset", + Button = "button", } export default ButtonType; diff --git a/CommonUI/src/Components/Calendar/Calendar.tsx b/CommonUI/src/Components/Calendar/Calendar.tsx index a140c01448..ff8af66df3 100644 --- a/CommonUI/src/Components/Calendar/Calendar.tsx +++ b/CommonUI/src/Components/Calendar/Calendar.tsx @@ -1,93 +1,93 @@ -import { Blue500 } from 'Common/Types/BrandColors'; -import CalendarEvent from 'Common/Types/Calendar/CalendarEvent'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import StartAndEndTime from 'Common/Types/Time/StartAndEndTime'; -import moment from 'moment-timezone'; -import React, { FunctionComponent, ReactElement, useMemo } from 'react'; +import { Blue500 } from "Common/Types/BrandColors"; +import CalendarEvent from "Common/Types/Calendar/CalendarEvent"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import StartAndEndTime from "Common/Types/Time/StartAndEndTime"; +import moment from "moment-timezone"; +import React, { FunctionComponent, ReactElement, useMemo } from "react"; import { - Calendar, - DateLocalizer, - EventPropGetter, - momentLocalizer, -} from 'react-big-calendar'; -import 'react-big-calendar/lib/css/react-big-calendar.css'; + Calendar, + DateLocalizer, + EventPropGetter, + momentLocalizer, +} from "react-big-calendar"; +import "react-big-calendar/lib/css/react-big-calendar.css"; const localizer: DateLocalizer = momentLocalizer(moment); export interface ComponentProps { - id?: string | undefined; - events: Array<CalendarEvent>; - defaultCalendarView?: DefaultCalendarView; - onRangeChange: (startAndEndTime: StartAndEndTime) => void; + id?: string | undefined; + events: Array<CalendarEvent>; + defaultCalendarView?: DefaultCalendarView; + onRangeChange: (startAndEndTime: StartAndEndTime) => void; } export enum DefaultCalendarView { - Month = 'month', - Week = 'week', - Day = 'day', - Agenda = 'agenda', + Month = "month", + Week = "week", + Day = "day", + Agenda = "agenda", } const CalendarElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const { defaultDate } = useMemo(() => { - return { - defaultDate: OneUptimeDate.getCurrentDate(), - }; - }, []); + const { defaultDate } = useMemo(() => { + return { + defaultDate: OneUptimeDate.getCurrentDate(), + }; + }, []); - const eventStyleGetter: EventPropGetter<any> = ( - event: CalendarEvent - ): { className?: string | undefined; style?: React.CSSProperties } => { - const backgroundColor: string = - event.color?.toString() || Blue500.toString(); - const style: React.CSSProperties = { - backgroundColor: backgroundColor, - borderRadius: '0px', - opacity: 0.8, - color: - event.textColor?.toString() || - Color.shouldUseDarkText(new Color(backgroundColor)) - ? '#000000' - : '#ffffff', - border: '0px', - display: 'block', - }; - - return { - style: style, - }; + const eventStyleGetter: EventPropGetter<any> = ( + event: CalendarEvent, + ): { className?: string | undefined; style?: React.CSSProperties } => { + const backgroundColor: string = + event.color?.toString() || Blue500.toString(); + const style: React.CSSProperties = { + backgroundColor: backgroundColor, + borderRadius: "0px", + opacity: 0.8, + color: + event.textColor?.toString() || + Color.shouldUseDarkText(new Color(backgroundColor)) + ? "#000000" + : "#ffffff", + border: "0px", + display: "block", }; - return ( - <div id={props.id} className="mt-5 h-[42rem]"> - <Calendar - defaultDate={defaultDate} - events={props.events} - localizer={localizer} - showMultiDayTimes - defaultView={props.defaultCalendarView || 'day'} - eventPropGetter={eventStyleGetter} - onRangeChange={(range: Date[] | { start: Date; end: Date }) => { - if (Array.isArray(range)) { - return props.onRangeChange({ - startTime: range[0] as Date, - endTime: OneUptimeDate.getEndOfDay( - range[range.length - 1] as Date - ), - }); - } + return { + style: style, + }; + }; - props.onRangeChange({ - startTime: range.start, - endTime: range.end, - }); - }} - /> - </div> - ); + return ( + <div id={props.id} className="mt-5 h-[42rem]"> + <Calendar + defaultDate={defaultDate} + events={props.events} + localizer={localizer} + showMultiDayTimes + defaultView={props.defaultCalendarView || "day"} + eventPropGetter={eventStyleGetter} + onRangeChange={(range: Date[] | { start: Date; end: Date }) => { + if (Array.isArray(range)) { + return props.onRangeChange({ + startTime: range[0] as Date, + endTime: OneUptimeDate.getEndOfDay( + range[range.length - 1] as Date, + ), + }); + } + + props.onRangeChange({ + startTime: range.start, + endTime: range.end, + }); + }} + /> + </div> + ); }; export default CalendarElement; diff --git a/CommonUI/src/Components/Card/Card.tsx b/CommonUI/src/Components/Card/Card.tsx index d957833b12..9047f4a906 100644 --- a/CommonUI/src/Components/Card/Card.tsx +++ b/CommonUI/src/Components/Card/Card.tsx @@ -1,112 +1,101 @@ -import Button, { ButtonStyleType } from '../Button/Button'; -import ShortcutKey from '../ShortcutKey/ShortcutKey'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Button, { ButtonStyleType } from "../Button/Button"; +import ShortcutKey from "../ShortcutKey/ShortcutKey"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface CardButtonSchema { - title: string; - buttonStyle?: ButtonStyleType | undefined; - onClick: () => void; - disabled?: boolean | undefined; - icon: IconProp; - isLoading?: undefined | boolean; - className?: string | undefined; - shortcutKey?: undefined | ShortcutKey; + title: string; + buttonStyle?: ButtonStyleType | undefined; + onClick: () => void; + disabled?: boolean | undefined; + icon: IconProp; + isLoading?: undefined | boolean; + className?: string | undefined; + shortcutKey?: undefined | ShortcutKey; } export interface ComponentProps { - title: string | ReactElement; - description: string | ReactElement; - buttons?: undefined | Array<CardButtonSchema>; - children?: undefined | Array<ReactElement> | ReactElement; - className?: string | undefined; - bodyClassName?: string | undefined; - rightElement?: ReactElement | undefined; + title: string | ReactElement; + description: string | ReactElement; + buttons?: undefined | Array<CardButtonSchema>; + children?: undefined | Array<ReactElement> | ReactElement; + className?: string | undefined; + bodyClassName?: string | undefined; + rightElement?: ReactElement | undefined; } const Card: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const noRightElementsOrButtons: boolean = - !props.rightElement && (!props.buttons || props.buttons.length === 0); + const noRightElementsOrButtons: boolean = + !props.rightElement && (!props.buttons || props.buttons.length === 0); - return ( - <React.Fragment> - <div className={props.className}> - <div className="shadow sm:rounded-md"> - <div className="bg-white py-6 px-4 sm:p-6"> - <div className="flex justify-between"> - <div - className={`${ - noRightElementsOrButtons ? 'w-full' : '' - }`} - > - <h2 - data-testid="card-details-heading" - id="card-details-heading" - className="text-lg font-medium leading-6 text-gray-900" - > - {props.title} - </h2> - <p - data-testid="card-description" - className="mt-1 text-sm text-gray-500 w-full" - > - {props.description} - </p> - </div> - <div className="flex w-fit"> - {props.rightElement} - {props.buttons?.map( - (button: CardButtonSchema, i: number) => { - return ( - <div - style={ - i > 0 - ? { - marginLeft: - '10px', - } - : {} - } - key={i} - > - <Button - key={i} - title={button.title} - buttonStyle={ - button.buttonStyle - } - className={button.className} - onClick={() => { - if (button.onClick) { - button.onClick(); - } - }} - disabled={button.disabled} - icon={button.icon} - shortcutKey={ - button.shortcutKey - } - dataTestId="card-button" - /> - </div> - ); - } - )} - </div> - </div> - - {props.children && ( - <div className={props.bodyClassName || 'mt-6'}> - {props.children} - </div> - )} + return ( + <React.Fragment> + <div className={props.className}> + <div className="shadow sm:rounded-md"> + <div className="bg-white py-6 px-4 sm:p-6"> + <div className="flex justify-between"> + <div className={`${noRightElementsOrButtons ? "w-full" : ""}`}> + <h2 + data-testid="card-details-heading" + id="card-details-heading" + className="text-lg font-medium leading-6 text-gray-900" + > + {props.title} + </h2> + <p + data-testid="card-description" + className="mt-1 text-sm text-gray-500 w-full" + > + {props.description} + </p> + </div> + <div className="flex w-fit"> + {props.rightElement} + {props.buttons?.map((button: CardButtonSchema, i: number) => { + return ( + <div + style={ + i > 0 + ? { + marginLeft: "10px", + } + : {} + } + key={i} + > + <Button + key={i} + title={button.title} + buttonStyle={button.buttonStyle} + className={button.className} + onClick={() => { + if (button.onClick) { + button.onClick(); + } + }} + disabled={button.disabled} + icon={button.icon} + shortcutKey={button.shortcutKey} + dataTestId="card-button" + /> </div> - </div> + ); + })} + </div> </div> - </React.Fragment> - ); + + {props.children && ( + <div className={props.bodyClassName || "mt-6"}> + {props.children} + </div> + )} + </div> + </div> + </div> + </React.Fragment> + ); }; export default Card; diff --git a/CommonUI/src/Components/Card/CardButtons/Refresh.ts b/CommonUI/src/Components/Card/CardButtons/Refresh.ts index 5bf3f1afd6..6582359a69 100644 --- a/CommonUI/src/Components/Card/CardButtons/Refresh.ts +++ b/CommonUI/src/Components/Card/CardButtons/Refresh.ts @@ -1,15 +1,15 @@ -import { ButtonStyleType } from '../../Button/Button'; -import { CardButtonSchema } from '../Card'; -import IconProp from 'Common/Types/Icon/IconProp'; +import { ButtonStyleType } from "../../Button/Button"; +import { CardButtonSchema } from "../Card"; +import IconProp from "Common/Types/Icon/IconProp"; type GetButtonFunctionType = () => CardButtonSchema; export const getRefreshButton: GetButtonFunctionType = (): CardButtonSchema => { - return { - title: '', - buttonStyle: ButtonStyleType.ICON, - onClick: () => {}, - disabled: false, - icon: IconProp.Refresh, - }; + return { + title: "", + buttonStyle: ButtonStyleType.ICON, + onClick: () => {}, + disabled: false, + icon: IconProp.Refresh, + }; }; diff --git a/CommonUI/src/Components/CategoryCheckbox/Category.tsx b/CommonUI/src/Components/CategoryCheckbox/Category.tsx index 7360b49410..e0f6a26a13 100644 --- a/CommonUI/src/Components/CategoryCheckbox/Category.tsx +++ b/CommonUI/src/Components/CategoryCheckbox/Category.tsx @@ -1,152 +1,138 @@ -import CheckboxElement from '../Checkbox/Checkbox'; +import CheckboxElement from "../Checkbox/Checkbox"; import { - CategoryCheckboxOption, - CheckboxCategory, -} from './CategoryCheckboxTypes'; -import CheckBoxList from './CheckboxList'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; + CategoryCheckboxOption, + CheckboxCategory, +} from "./CategoryCheckboxTypes"; +import CheckBoxList from "./CheckboxList"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export type CategoryCheckboxValue = string | number | boolean; export interface CategoryProps { - category?: CheckboxCategory | undefined; - options: Array<CategoryCheckboxOption>; - onChange: (value: Array<CategoryCheckboxValue>) => void; - initialValue?: undefined | Array<CategoryCheckboxValue>; - isLastCategory: boolean; - dataTestId?: string | undefined; + category?: CheckboxCategory | undefined; + options: Array<CategoryCheckboxOption>; + onChange: (value: Array<CategoryCheckboxValue>) => void; + initialValue?: undefined | Array<CategoryCheckboxValue>; + isLastCategory: boolean; + dataTestId?: string | undefined; } enum CategoryCheckboxValueState { - Checked, - Unchecked, - Indeterminate, + Checked, + Unchecked, + Indeterminate, } const Category: FunctionComponent<CategoryProps> = ( - props: CategoryProps + props: CategoryProps, ): ReactElement => { - const [currentValues, setCurrentValues] = React.useState< - Array<CategoryCheckboxValue> - >(props.initialValue || []); + const [currentValues, setCurrentValues] = React.useState< + Array<CategoryCheckboxValue> + >(props.initialValue || []); - const [categoryCheckboxState, setCategoryCheckboxState] = - React.useState<CategoryCheckboxValueState>( - CategoryCheckboxValueState.Unchecked - ); - - useEffect(() => { - // check if all of the options are checked. and if so, check the category - - const noOptionsChecked: boolean = currentValues.length === 0; - - if (noOptionsChecked) { - setCategoryCheckboxState(CategoryCheckboxValueState.Unchecked); - return; - } - - const allOptionsChecked: boolean = props.options.every( - (option: CategoryCheckboxOption) => { - return currentValues.includes(option.value); - } - ); - - if (allOptionsChecked) { - setCategoryCheckboxState(CategoryCheckboxValueState.Checked); - } else { - setCategoryCheckboxState(CategoryCheckboxValueState.Indeterminate); - } - }, [currentValues]); - - useEffect(() => { - setCurrentValues(props.initialValue || []); - }, [props.initialValue]); - - return ( - <div> - {props.category && ( - <div> - <CheckboxElement - title={props.category.title} - dataTestId={props.dataTestId} - value={ - categoryCheckboxState === - CategoryCheckboxValueState.Checked - } - isIndeterminate={ - categoryCheckboxState === - CategoryCheckboxValueState.Indeterminate - } - onChange={( - isChecked: boolean, - isIndeterminate?: boolean | undefined - ) => { - if (isIndeterminate && !isChecked) { - return; - } - - if (isChecked) { - // add all of the options to the currentValues array - const newValues: Array<CategoryCheckboxValue> = - [...currentValues]; - props.options.forEach( - (option: CategoryCheckboxOption) => { - // please make sure that the option.value is not already in the array - - if (newValues.includes(option.value)) { - return; - } - - newValues.push(option.value); - } - ); - setCurrentValues(newValues); - props.onChange(newValues); - } else { - // remove all of the options from the currentValues array - const newValues: Array<CategoryCheckboxValue> = - [...currentValues]; - props.options.forEach( - (option: CategoryCheckboxOption) => { - while ( - newValues.includes(option.value) - ) { - const index: number = - newValues.findIndex( - ( - value: CategoryCheckboxValue - ) => { - return ( - value === - option.value - ); - } - ); - newValues.splice(index, 1); - } - } - ); - setCurrentValues(newValues); - props.onChange(newValues); - } - }} - /> - </div> - )} - <div className={`${props.category ? 'ml-7' : ''}`}> - <CheckBoxList - options={props.options} - initialValue={currentValues} - onChange={(newValues: Array<CategoryCheckboxValue>) => { - setCurrentValues(newValues); - props.onChange(newValues); - }} - /> - </div> - - {!props.isLastCategory ? <div className="mt-3 mb-3"></div> : <></>} - </div> + const [categoryCheckboxState, setCategoryCheckboxState] = + React.useState<CategoryCheckboxValueState>( + CategoryCheckboxValueState.Unchecked, ); + + useEffect(() => { + // check if all of the options are checked. and if so, check the category + + const noOptionsChecked: boolean = currentValues.length === 0; + + if (noOptionsChecked) { + setCategoryCheckboxState(CategoryCheckboxValueState.Unchecked); + return; + } + + const allOptionsChecked: boolean = props.options.every( + (option: CategoryCheckboxOption) => { + return currentValues.includes(option.value); + }, + ); + + if (allOptionsChecked) { + setCategoryCheckboxState(CategoryCheckboxValueState.Checked); + } else { + setCategoryCheckboxState(CategoryCheckboxValueState.Indeterminate); + } + }, [currentValues]); + + useEffect(() => { + setCurrentValues(props.initialValue || []); + }, [props.initialValue]); + + return ( + <div> + {props.category && ( + <div> + <CheckboxElement + title={props.category.title} + dataTestId={props.dataTestId} + value={categoryCheckboxState === CategoryCheckboxValueState.Checked} + isIndeterminate={ + categoryCheckboxState === CategoryCheckboxValueState.Indeterminate + } + onChange={( + isChecked: boolean, + isIndeterminate?: boolean | undefined, + ) => { + if (isIndeterminate && !isChecked) { + return; + } + + if (isChecked) { + // add all of the options to the currentValues array + const newValues: Array<CategoryCheckboxValue> = [ + ...currentValues, + ]; + props.options.forEach((option: CategoryCheckboxOption) => { + // please make sure that the option.value is not already in the array + + if (newValues.includes(option.value)) { + return; + } + + newValues.push(option.value); + }); + setCurrentValues(newValues); + props.onChange(newValues); + } else { + // remove all of the options from the currentValues array + const newValues: Array<CategoryCheckboxValue> = [ + ...currentValues, + ]; + props.options.forEach((option: CategoryCheckboxOption) => { + while (newValues.includes(option.value)) { + const index: number = newValues.findIndex( + (value: CategoryCheckboxValue) => { + return value === option.value; + }, + ); + newValues.splice(index, 1); + } + }); + setCurrentValues(newValues); + props.onChange(newValues); + } + }} + /> + </div> + )} + <div className={`${props.category ? "ml-7" : ""}`}> + <CheckBoxList + options={props.options} + initialValue={currentValues} + onChange={(newValues: Array<CategoryCheckboxValue>) => { + setCurrentValues(newValues); + props.onChange(newValues); + }} + /> + </div> + + {!props.isLastCategory ? <div className="mt-3 mb-3"></div> : <></>} + </div> + ); }; export default Category; diff --git a/CommonUI/src/Components/CategoryCheckbox/CategoryCheckboxTypes.ts b/CommonUI/src/Components/CategoryCheckbox/CategoryCheckboxTypes.ts index 68f5d39e52..9ea41b3b7f 100644 --- a/CommonUI/src/Components/CategoryCheckbox/CategoryCheckboxTypes.ts +++ b/CommonUI/src/Components/CategoryCheckbox/CategoryCheckboxTypes.ts @@ -1,14 +1,14 @@ -import { ReactElement } from 'react-markdown/lib/react-markdown'; +import { ReactElement } from "react-markdown/lib/react-markdown"; export type CategoryCheckboxValue = string | number | boolean; export interface CheckboxCategory { - title: string | ReactElement; - id: string; + title: string | ReactElement; + id: string; } export interface CategoryCheckboxOption { - value: CategoryCheckboxValue; - label: string | ReactElement; - categoryId?: string | undefined; + value: CategoryCheckboxValue; + label: string | ReactElement; + categoryId?: string | undefined; } diff --git a/CommonUI/src/Components/CategoryCheckbox/CheckboxList.tsx b/CommonUI/src/Components/CategoryCheckbox/CheckboxList.tsx index 07ea0a074e..78c7b5a22a 100644 --- a/CommonUI/src/Components/CategoryCheckbox/CheckboxList.tsx +++ b/CommonUI/src/Components/CategoryCheckbox/CheckboxList.tsx @@ -1,105 +1,103 @@ -import CheckboxElement from '../Checkbox/Checkbox'; -import { CategoryCheckboxOption } from './CategoryCheckboxTypes'; -import GenericObject from 'Common/Types/GenericObject'; -import { JSONObject } from 'Common/Types/JSON'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import CheckboxElement from "../Checkbox/Checkbox"; +import { CategoryCheckboxOption } from "./CategoryCheckboxTypes"; +import GenericObject from "Common/Types/GenericObject"; +import { JSONObject } from "Common/Types/JSON"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export type CategoryCheckboxValue = string | number | boolean; type EnumToCategoryCheckboxOptionFunction = ( - obj: GenericObject + obj: GenericObject, ) => Array<CategoryCheckboxOption>; export const enumToCategoryCheckboxOption: EnumToCategoryCheckboxOptionFunction = - (obj: GenericObject): Array<CategoryCheckboxOption> => { - const options: Array<CategoryCheckboxOption> = []; + (obj: GenericObject): Array<CategoryCheckboxOption> => { + const options: Array<CategoryCheckboxOption> = []; - for (const key in obj) { - options.push({ - label: ((obj as JSONObject)[key] as string).toString(), - value: ((obj as JSONObject)[key] as string).toString(), - }); - } + for (const key in obj) { + options.push({ + label: ((obj as JSONObject)[key] as string).toString(), + value: ((obj as JSONObject)[key] as string).toString(), + }); + } - return options; - }; + return options; + }; export interface CategoryProps { - options: Array<CategoryCheckboxOption>; - onChecked?: (value: CategoryCheckboxValue) => void; - onUnchecked?: (value: CategoryCheckboxValue) => void; - onChange: (checked: Array<CategoryCheckboxValue>) => void; - initialValue?: undefined | Array<CategoryCheckboxValue>; + options: Array<CategoryCheckboxOption>; + onChecked?: (value: CategoryCheckboxValue) => void; + onUnchecked?: (value: CategoryCheckboxValue) => void; + onChange: (checked: Array<CategoryCheckboxValue>) => void; + initialValue?: undefined | Array<CategoryCheckboxValue>; } const CheckBoxList: FunctionComponent<CategoryProps> = ( - props: CategoryProps + props: CategoryProps, ): ReactElement => { - const [currentValues, setCurrentValues] = React.useState< - Array<CategoryCheckboxValue> - >(props.initialValue || []); + const [currentValues, setCurrentValues] = React.useState< + Array<CategoryCheckboxValue> + >(props.initialValue || []); - useEffect(() => { - setCurrentValues(props.initialValue || []); - }, [props.initialValue]); + useEffect(() => { + setCurrentValues(props.initialValue || []); + }, [props.initialValue]); - return ( - <div> - {props.options.map((option: CategoryCheckboxOption, i: number) => { - return ( - <CheckboxElement - key={i} - title={option.label} - value={Boolean( - currentValues?.find( - (value: CategoryCheckboxValue) => { - return value === option.value; - } - ) || false - )} - onChange={(changedValue: boolean) => { - if (changedValue) { - props.onChecked && - props.onChecked(option.value); + return ( + <div> + {props.options.map((option: CategoryCheckboxOption, i: number) => { + return ( + <CheckboxElement + key={i} + title={option.label} + value={Boolean( + currentValues?.find((value: CategoryCheckboxValue) => { + return value === option.value; + }) || false, + )} + onChange={(changedValue: boolean) => { + if (changedValue) { + props.onChecked && props.onChecked(option.value); - // add the option.value to the currentValues array - const newValues: Array<CategoryCheckboxValue> = - [...currentValues]; + // add the option.value to the currentValues array + const newValues: Array<CategoryCheckboxValue> = [ + ...currentValues, + ]; - if (newValues.includes(option.value)) { - return; - } + if (newValues.includes(option.value)) { + return; + } - newValues.push(option.value); - setCurrentValues(newValues); - props.onChange(newValues); - } else { - props.onUnchecked && - props.onUnchecked(option.value); + newValues.push(option.value); + setCurrentValues(newValues); + props.onChange(newValues); + } else { + props.onUnchecked && props.onUnchecked(option.value); - // remove the option.value from the currentValues array + // remove the option.value from the currentValues array - const newValues: Array<CategoryCheckboxValue> = - [...currentValues]; + const newValues: Array<CategoryCheckboxValue> = [ + ...currentValues, + ]; - while (newValues.includes(option.value)) { - const index: number = newValues.findIndex( - (value: CategoryCheckboxValue) => { - return value === option.value; - } - ); - newValues.splice(index, 1); - } + while (newValues.includes(option.value)) { + const index: number = newValues.findIndex( + (value: CategoryCheckboxValue) => { + return value === option.value; + }, + ); + newValues.splice(index, 1); + } - setCurrentValues(newValues); - props.onChange(newValues); - } - }} - /> - ); - })} - </div> - ); + setCurrentValues(newValues); + props.onChange(newValues); + } + }} + /> + ); + })} + </div> + ); }; export default CheckBoxList; diff --git a/CommonUI/src/Components/CategoryCheckbox/Index.tsx b/CommonUI/src/Components/CategoryCheckbox/Index.tsx index b3b82f4da2..dc54f909f8 100644 --- a/CommonUI/src/Components/CategoryCheckbox/Index.tsx +++ b/CommonUI/src/Components/CategoryCheckbox/Index.tsx @@ -1,179 +1,173 @@ -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import Category from './Category'; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import Category from "./Category"; import { - CategoryCheckboxOption, - CategoryCheckboxValue, - CheckboxCategory, -} from './CategoryCheckboxTypes'; -import BaseModel from 'Common/Models/BaseModel'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; + CategoryCheckboxOption, + CategoryCheckboxValue, + CheckboxCategory, +} from "./CategoryCheckboxTypes"; +import BaseModel from "Common/Models/BaseModel"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface CategoryCheckboxOptionsAndCategories { - categories: Array<CheckboxCategory>; - options: Array<CategoryCheckboxOption>; + categories: Array<CheckboxCategory>; + options: Array<CategoryCheckboxOption>; } export interface CategoryCheckboxProps - extends CategoryCheckboxOptionsAndCategories { - onChange: (value: Array<CategoryCheckboxValue>) => void; - initialValue?: undefined | Array<CategoryCheckboxValue | BaseModel>; - error?: string | undefined; - dataTestId?: string | undefined; + extends CategoryCheckboxOptionsAndCategories { + onChange: (value: Array<CategoryCheckboxValue>) => void; + initialValue?: undefined | Array<CategoryCheckboxValue | BaseModel>; + error?: string | undefined; + dataTestId?: string | undefined; } const CategoryCheckbox: FunctionComponent<CategoryCheckboxProps> = ( - props: CategoryCheckboxProps + props: CategoryCheckboxProps, ): ReactElement => { - type SanitizeInitialValuesFunction = ( - value?: Array<CategoryCheckboxValue | BaseModel> - ) => Array<CategoryCheckboxValue>; + type SanitizeInitialValuesFunction = ( + value?: Array<CategoryCheckboxValue | BaseModel>, + ) => Array<CategoryCheckboxValue>; - const sanitizeInitialValues: SanitizeInitialValuesFunction = ( - value?: Array<CategoryCheckboxValue | BaseModel> - ): Array<CategoryCheckboxValue> => { - if (!value) { - return []; - } - - return value.map((value: CategoryCheckboxValue | BaseModel) => { - if (value instanceof BaseModel) { - return value._id as string; - } - return value; - }); - }; - - const [currentValues, setCurrentValues] = React.useState< - Array<CategoryCheckboxValue> - >(sanitizeInitialValues(props.initialValue)); - - const [categories] = React.useState<Array<CheckboxCategory>>( - [...props.categories] || [] - ); - const [options] = React.useState<Array<CategoryCheckboxOption>>( - [...props.options] || [] - ); - - useEffect(() => { - // whenevent currentValue changes, make sure all the values are unique. - - const doesHaveDuplicates: boolean = currentValues.some( - (value: CategoryCheckboxValue, index: number) => { - return currentValues.indexOf(value) !== index; - } - ); - - if (!doesHaveDuplicates) { - return; - } - - const tempCurrentValues: Array<CategoryCheckboxValue> = []; - - currentValues.forEach((value: CategoryCheckboxValue) => { - if (!tempCurrentValues.includes(value)) { - tempCurrentValues.push(value); - } - }); - - setCurrentValues(tempCurrentValues); - }, [currentValues]); - - if (options.length === 0) { - return ( - <div> - <ErrorMessage error="No options found." /> - </div> - ); + const sanitizeInitialValues: SanitizeInitialValuesFunction = ( + value?: Array<CategoryCheckboxValue | BaseModel>, + ): Array<CategoryCheckboxValue> => { + if (!value) { + return []; } - type GetCategoryFunction = ( - category?: CheckboxCategory, - isLastCategory?: boolean - ) => ReactElement; + return value.map((value: CategoryCheckboxValue | BaseModel) => { + if (value instanceof BaseModel) { + return value._id as string; + } + return value; + }); + }; - const getCategory: GetCategoryFunction = ( - category?: CheckboxCategory, - isLastCategory: boolean = false - ): ReactElement => { - return ( - <Category - initialValue={currentValues.filter( - (value: CategoryCheckboxValue) => { - // only return this option if it belongs to this category + const [currentValues, setCurrentValues] = React.useState< + Array<CategoryCheckboxValue> + >(sanitizeInitialValues(props.initialValue)); - const option: CategoryCheckboxOption | undefined = - options.find((option: CategoryCheckboxOption) => { - return ( - option.value === value && - (option.categoryId || '') === - (category?.id || '') - ); - }); + const [categories] = React.useState<Array<CheckboxCategory>>( + [...props.categories] || [], + ); + const [options] = React.useState<Array<CategoryCheckboxOption>>( + [...props.options] || [], + ); - return Boolean(option); - } - )} - dataTestId={props.dataTestId} - category={category} - options={options.filter((option: CategoryCheckboxOption) => { - return (option.categoryId || '') === (category?.id || ''); - })} - isLastCategory={isLastCategory} - onChange={(value: Array<CategoryCheckboxValue>) => { - // remove any value from currentValue that belongs to this category. + useEffect(() => { + // whenevent currentValue changes, make sure all the values are unique. - const tempCurrentValues: Array<CategoryCheckboxValue> = [ - ...currentValues, - ]; - - options.forEach((option: CategoryCheckboxOption) => { - const index: number = tempCurrentValues.findIndex( - (value: CategoryCheckboxValue) => { - return ( - value === option.value && - (option.categoryId || '') === - (category?.id || '') - ); - } - ); - if (index > -1) { - tempCurrentValues.splice(index, 1); - } - }); - - // add the new values to currentValue - value.forEach((value: CategoryCheckboxValue) => { - tempCurrentValues.push(value); - }); - - setCurrentValues(tempCurrentValues); - - props.onChange(tempCurrentValues); - }} - /> - ); - }; - - return ( - <div> - {getCategory(undefined, categories.length === 0)} - {categories.map((category: CheckboxCategory, i: number) => { - return ( - <div key={i}> - {getCategory(category, i === categories.length - 1)} - </div> - ); - })} - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} - </div> + const doesHaveDuplicates: boolean = currentValues.some( + (value: CategoryCheckboxValue, index: number) => { + return currentValues.indexOf(value) !== index; + }, ); + + if (!doesHaveDuplicates) { + return; + } + + const tempCurrentValues: Array<CategoryCheckboxValue> = []; + + currentValues.forEach((value: CategoryCheckboxValue) => { + if (!tempCurrentValues.includes(value)) { + tempCurrentValues.push(value); + } + }); + + setCurrentValues(tempCurrentValues); + }, [currentValues]); + + if (options.length === 0) { + return ( + <div> + <ErrorMessage error="No options found." /> + </div> + ); + } + + type GetCategoryFunction = ( + category?: CheckboxCategory, + isLastCategory?: boolean, + ) => ReactElement; + + const getCategory: GetCategoryFunction = ( + category?: CheckboxCategory, + isLastCategory: boolean = false, + ): ReactElement => { + return ( + <Category + initialValue={currentValues.filter((value: CategoryCheckboxValue) => { + // only return this option if it belongs to this category + + const option: CategoryCheckboxOption | undefined = options.find( + (option: CategoryCheckboxOption) => { + return ( + option.value === value && + (option.categoryId || "") === (category?.id || "") + ); + }, + ); + + return Boolean(option); + })} + dataTestId={props.dataTestId} + category={category} + options={options.filter((option: CategoryCheckboxOption) => { + return (option.categoryId || "") === (category?.id || ""); + })} + isLastCategory={isLastCategory} + onChange={(value: Array<CategoryCheckboxValue>) => { + // remove any value from currentValue that belongs to this category. + + const tempCurrentValues: Array<CategoryCheckboxValue> = [ + ...currentValues, + ]; + + options.forEach((option: CategoryCheckboxOption) => { + const index: number = tempCurrentValues.findIndex( + (value: CategoryCheckboxValue) => { + return ( + value === option.value && + (option.categoryId || "") === (category?.id || "") + ); + }, + ); + if (index > -1) { + tempCurrentValues.splice(index, 1); + } + }); + + // add the new values to currentValue + value.forEach((value: CategoryCheckboxValue) => { + tempCurrentValues.push(value); + }); + + setCurrentValues(tempCurrentValues); + + props.onChange(tempCurrentValues); + }} + /> + ); + }; + + return ( + <div> + {getCategory(undefined, categories.length === 0)} + {categories.map((category: CheckboxCategory, i: number) => { + return ( + <div key={i}> + {getCategory(category, i === categories.length - 1)} + </div> + ); + })} + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default CategoryCheckbox; diff --git a/CommonUI/src/Components/Charts/ChartGroup/ChartGroup.tsx b/CommonUI/src/Components/Charts/ChartGroup/ChartGroup.tsx index 141aec0f57..26d4ea07ac 100644 --- a/CommonUI/src/Components/Charts/ChartGroup/ChartGroup.tsx +++ b/CommonUI/src/Components/Charts/ChartGroup/ChartGroup.tsx @@ -1,82 +1,75 @@ -import LineChart, { ComponentProps as LineChartProps } from '../Line/LineChart'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import LineChart, { ComponentProps as LineChartProps } from "../Line/LineChart"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export enum ChartGroupInterval { - ONE_HOUR = '1 hour', + ONE_HOUR = "1 hour", } export enum ChartType { - LINE = 'line', + LINE = "line", } export interface Chart { - id: string; - title: string; - description: string; - type: ChartType; - props: LineChartProps; - sync: boolean; + id: string; + title: string; + description: string; + type: ChartType; + props: LineChartProps; + sync: boolean; } export interface ComponentProps { - charts: Array<Chart>; - interval: ChartGroupInterval; + charts: Array<Chart>; + interval: ChartGroupInterval; } const ChartGroup: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [syncValue, setSyncValue] = useState< - number | string | Date | undefined - >(undefined); + const [syncValue, setSyncValue] = useState< + number | string | Date | undefined + >(undefined); - return ( - <div className="lg:grid grid-cols-2 gap-5"> - {props.charts.map((chart: Chart, index: number) => { - switch (chart.type) { - case ChartType.LINE: - return ( - <div - key={index} - className="p-6 rounded-md bg-white shadow" - > - <h2 - data-testid="card-details-heading" - id="card-details-heading" - className="text-lg font-medium leading-6 text-gray-900" - > - {chart.title} - </h2> - <p - data-testid="card-description" - className="mt-1 text-sm text-gray-500 w-full" - > - {chart.description} - </p> - <LineChart - key={index} - {...chart.props} - xAxisMarker={{ - value: chart.sync - ? syncValue - : undefined, - }} - onHoverXAxis={( - value: string | number | Date - ) => { - if (chart.sync) { - setSyncValue(value); - } - }} - /> - </div> - ); - default: - return <></>; - } - })} - </div> - ); + return ( + <div className="lg:grid grid-cols-2 gap-5"> + {props.charts.map((chart: Chart, index: number) => { + switch (chart.type) { + case ChartType.LINE: + return ( + <div key={index} className="p-6 rounded-md bg-white shadow"> + <h2 + data-testid="card-details-heading" + id="card-details-heading" + className="text-lg font-medium leading-6 text-gray-900" + > + {chart.title} + </h2> + <p + data-testid="card-description" + className="mt-1 text-sm text-gray-500 w-full" + > + {chart.description} + </p> + <LineChart + key={index} + {...chart.props} + xAxisMarker={{ + value: chart.sync ? syncValue : undefined, + }} + onHoverXAxis={(value: string | number | Date) => { + if (chart.sync) { + setSyncValue(value); + } + }} + /> + </div> + ); + default: + return <></>; + } + })} + </div> + ); }; export default ChartGroup; diff --git a/CommonUI/src/Components/Charts/Line/LineChart.tsx b/CommonUI/src/Components/Charts/Line/LineChart.tsx index 1d03bf1e47..053cec5339 100644 --- a/CommonUI/src/Components/Charts/Line/LineChart.tsx +++ b/CommonUI/src/Components/Charts/Line/LineChart.tsx @@ -1,239 +1,234 @@ -import { Box, CartesianMarkerProps } from '@nivo/core'; -import { LegendProps } from '@nivo/legends'; -import { Point, ResponsiveLine } from '@nivo/line'; -import { BrightColors } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { Box, CartesianMarkerProps } from "@nivo/core"; +import { LegendProps } from "@nivo/legends"; +import { Point, ResponsiveLine } from "@nivo/line"; +import { BrightColors } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import React, { FunctionComponent, ReactElement } from "react"; export type XValue = string | number | Date; export type YValue = number; export interface LineChartDataItem { - x: XValue; - y: YValue; + x: XValue; + y: YValue; } export enum ChartCurve { - LINEAR = 'linear', - MONOTONE_X = 'monotoneX', - STEP = 'step', - STEP_BEFORE = 'stepBefore', - STEP_AFTER = 'stepAfter', + LINEAR = "linear", + MONOTONE_X = "monotoneX", + STEP = "step", + STEP_BEFORE = "stepBefore", + STEP_AFTER = "stepAfter", } export enum XScaleType { - TIME = 'time', + TIME = "time", } export enum XScalePrecision { - SECOND = 'second', - MINUTE = 'minute', - HOUR = 'hour', - DAY = 'day', - MONTH = 'month', - YEAR = 'year', + SECOND = "second", + MINUTE = "minute", + HOUR = "hour", + DAY = "day", + MONTH = "month", + YEAR = "year", } -export type XScaleMaxMin = 'auto' | number | Date; -export type YScaleMaxMin = 'auto' | number; +export type XScaleMaxMin = "auto" | number | Date; +export type YScaleMaxMin = "auto" | number; export interface XScale { - type: XScaleType; - min: XScaleMaxMin; - max: XScaleMaxMin; - precision: XScalePrecision; + type: XScaleType; + min: XScaleMaxMin; + max: XScaleMaxMin; + precision: XScalePrecision; } export interface LineChartData { - seriesName: string; - data: Array<LineChartDataItem>; + seriesName: string; + data: Array<LineChartDataItem>; } export enum YScaleType { - LINEAR = 'linear', + LINEAR = "linear", } export interface YScale { - type: YScaleType; - min: YScaleMaxMin; - max: YScaleMaxMin; + type: YScaleType; + min: YScaleMaxMin; + max: YScaleMaxMin; } export enum AxisType { - Date = 'date', - Time = 'time', - Number = 'number', + Date = "date", + Time = "time", + Number = "number", } export interface AxisBottom { - legend: string; - type: AxisType; + legend: string; + type: AxisType; } export interface AxisLeft { - legend: string; - type: AxisType; + legend: string; + type: AxisType; } export interface LineChartPoint { - x: XValue; - y: YValue; - seriesName: string; - seriesColor: Color; + x: XValue; + y: YValue; + seriesName: string; + seriesColor: Color; } export interface ComponentProps { - data: Array<LineChartData>; - curve?: ChartCurve; - xScale: XScale; - yScale: YScale; - axisBottom: AxisBottom; - axisLeft: AxisLeft; - onHoverXAxis?: (x: XValue) => void; - xAxisMarker?: { - value: XValue | undefined; - }; - getHoverTooltip?: (data: { points: Array<LineChartPoint> }) => ReactElement; + data: Array<LineChartData>; + curve?: ChartCurve; + xScale: XScale; + yScale: YScale; + axisBottom: AxisBottom; + axisLeft: AxisLeft; + onHoverXAxis?: (x: XValue) => void; + xAxisMarker?: { + value: XValue | undefined; + }; + getHoverTooltip?: (data: { points: Array<LineChartPoint> }) => ReactElement; } const LineChart: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const markers: Array<CartesianMarkerProps> = []; + const markers: Array<CartesianMarkerProps> = []; - if (props.xAxisMarker && props.xAxisMarker.value) { - markers.push({ - axis: 'x', - legend: '', - lineStyle: { - stroke: '#cbd5e1', - strokeWidth: 2, + if (props.xAxisMarker && props.xAxisMarker.value) { + markers.push({ + axis: "x", + legend: "", + lineStyle: { + stroke: "#cbd5e1", + strokeWidth: 2, + }, + value: props.xAxisMarker.value, + }); + } + + let legends: Array<LegendProps> = []; + + const showLegends: boolean = props.data.length > 1; + + const margin: Box = { bottom: 60, left: 60, top: 20 }; + + if (showLegends) { + margin.right = 200; + + legends = [ + { + anchor: "bottom-right", + direction: "column", + justify: false, + translateX: 100, + translateY: 0, + itemsSpacing: 0, + itemDirection: "left-to-right", + itemWidth: 80, + itemHeight: 20, + itemOpacity: 0.75, + symbolSize: 12, + symbolShape: "circle", + symbolBorderColor: "rgba(0, 0, 0, .5)", + effects: [ + { + on: "hover", + style: { + itemBackground: "rgba(0, 0, 0, .03)", + itemOpacity: 1, }, - value: props.xAxisMarker.value, - }); - } + }, + ], + }, + ]; + } - let legends: Array<LegendProps> = []; + return ( + <div className="h-96 w-full"> + <ResponsiveLine + data={props.data.map((series: LineChartData) => { + return { + id: series.seriesName, + data: series.data, + }; + })} + onMouseMove={(data: Point) => { + if (props.onHoverXAxis) { + const xValue: XValue = ((data as any).points as Array<any>)?.[0] + ?.data?.x; + props.onHoverXAxis(xValue); + } + }} + margin={margin} + curve={props.curve || ChartCurve.LINEAR} + markers={markers} + xScale={{ + type: props.xScale.type, + max: props.xScale.max as any, + min: props.xScale.min as any, + precision: props.xScale.precision, + }} + yScale={{ + type: props.yScale.type, + min: props.yScale.min, + max: props.yScale.max, + }} + axisTop={null} + axisRight={null} + axisBottom={{ + legend: props.axisBottom.legend, + format: (value: XValue) => { + if (props.axisBottom.type === AxisType.Date) { + return OneUptimeDate.getDateAsLocalFormattedString(value as Date); + } - const showLegends: boolean = props.data.length > 1; + if (props.axisBottom.type === AxisType.Time) { + return OneUptimeDate.getLocalHourAndMinuteFromDate(value as Date); + } - const margin: Box = { bottom: 60, left: 60, top: 20 }; + return value.toString(); + }, + }} + useMesh={true} + axisLeft={{ + legend: props.axisLeft.legend, + }} + enableSlices="x" + sliceTooltip={(data: { + slice: { + points: readonly Point[]; + }; + }) => { + if (!props.getHoverTooltip) { + return <></>; + } - if (showLegends) { - margin.right = 200; - - legends = [ - { - anchor: 'bottom-right', - direction: 'column', - justify: false, - translateX: 100, - translateY: 0, - itemsSpacing: 0, - itemDirection: 'left-to-right', - itemWidth: 80, - itemHeight: 20, - itemOpacity: 0.75, - symbolSize: 12, - symbolShape: 'circle', - symbolBorderColor: 'rgba(0, 0, 0, .5)', - effects: [ - { - on: 'hover', - style: { - itemBackground: 'rgba(0, 0, 0, .03)', - itemOpacity: 1, - }, - }, - ], - }, - ]; - } - - return ( - <div className="h-96 w-full"> - <ResponsiveLine - data={props.data.map((series: LineChartData) => { - return { - id: series.seriesName, - data: series.data, - }; - })} - onMouseMove={(data: Point) => { - if (props.onHoverXAxis) { - const xValue: XValue = ( - (data as any).points as Array<any> - )?.[0]?.data?.x; - props.onHoverXAxis(xValue); - } - }} - margin={margin} - curve={props.curve || ChartCurve.LINEAR} - markers={markers} - xScale={{ - type: props.xScale.type, - max: props.xScale.max as any, - min: props.xScale.min as any, - precision: props.xScale.precision, - }} - yScale={{ - type: props.yScale.type, - min: props.yScale.min, - max: props.yScale.max, - }} - axisTop={null} - axisRight={null} - axisBottom={{ - legend: props.axisBottom.legend, - format: (value: XValue) => { - if (props.axisBottom.type === AxisType.Date) { - return OneUptimeDate.getDateAsLocalFormattedString( - value as Date - ); - } - - if (props.axisBottom.type === AxisType.Time) { - return OneUptimeDate.getLocalHourAndMinuteFromDate( - value as Date - ); - } - - return value.toString(); - }, - }} - useMesh={true} - axisLeft={{ - legend: props.axisLeft.legend, - }} - enableSlices="x" - sliceTooltip={(data: { - slice: { - points: readonly Point[]; - }; - }) => { - if (!props.getHoverTooltip) { - return <></>; - } - - return props.getHoverTooltip({ - points: data.slice.points.map((point: Point) => { - return { - x: point.data.x as XValue, - y: point.data.y as YValue, - seriesName: point.serieId.toString(), - seriesColor: new Color(point.color), - }; - }), - }); - }} - colors={BrightColors.map((item: Color) => { - return item.toString(); - })} - legends={legends} - /> - </div> - ); + return props.getHoverTooltip({ + points: data.slice.points.map((point: Point) => { + return { + x: point.data.x as XValue, + y: point.data.y as YValue, + seriesName: point.serieId.toString(), + seriesColor: new Color(point.color), + }; + }), + }); + }} + colors={BrightColors.map((item: Color) => { + return item.toString(); + })} + legends={legends} + /> + </div> + ); }; export default LineChart; diff --git a/CommonUI/src/Components/Checkbox/Checkbox.tsx b/CommonUI/src/Components/Checkbox/Checkbox.tsx index d9311466c3..71524ab3d9 100644 --- a/CommonUI/src/Components/Checkbox/Checkbox.tsx +++ b/CommonUI/src/Components/Checkbox/Checkbox.tsx @@ -1,106 +1,94 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export type CategoryCheckboxValue = string | number | boolean; export interface CategoryProps { - title?: string | ReactElement | undefined; - description?: string | undefined; - initialValue?: undefined | boolean; - onChange?: undefined | ((value: boolean, interminate?: boolean) => void); - value?: boolean | undefined; - readOnly?: boolean | undefined; - disabled?: boolean | undefined; - onFocus?: (() => void) | undefined; - onBlur?: (() => void) | undefined; - dataTestId?: string | undefined; - tabIndex?: number | undefined; - error?: string | undefined; - outerDivClassName?: string | undefined; - autoFocus?: boolean | undefined; - className?: string | undefined; - isIndeterminate?: boolean | undefined; + title?: string | ReactElement | undefined; + description?: string | undefined; + initialValue?: undefined | boolean; + onChange?: undefined | ((value: boolean, interminate?: boolean) => void); + value?: boolean | undefined; + readOnly?: boolean | undefined; + disabled?: boolean | undefined; + onFocus?: (() => void) | undefined; + onBlur?: (() => void) | undefined; + dataTestId?: string | undefined; + tabIndex?: number | undefined; + error?: string | undefined; + outerDivClassName?: string | undefined; + autoFocus?: boolean | undefined; + className?: string | undefined; + isIndeterminate?: boolean | undefined; } const CheckboxElement: FunctionComponent<CategoryProps> = ( - props: CategoryProps + props: CategoryProps, ): ReactElement => { - const [value, setValue] = React.useState<boolean>( - props.initialValue || false - ); + const [value, setValue] = React.useState<boolean>( + props.initialValue || false, + ); - // ref this checkbox. - const checkboxRef: React.RefObject<HTMLInputElement> = - React.useRef<HTMLInputElement>(null); + // ref this checkbox. + const checkboxRef: React.RefObject<HTMLInputElement> = + React.useRef<HTMLInputElement>(null); - React.useEffect(() => { - checkboxRef.current!.indeterminate = props.isIndeterminate || false; - }, [props.isIndeterminate]); + React.useEffect(() => { + checkboxRef.current!.indeterminate = props.isIndeterminate || false; + }, [props.isIndeterminate]); - React.useEffect(() => { - if (props.value === undefined) { - return; - } + React.useEffect(() => { + if (props.value === undefined) { + return; + } - setValue(props.value || false); - }, [props.value]); + setValue(props.value || false); + }, [props.value]); - return ( - <div> - <div - className={`relative flex items-start ${ - props.outerDivClassName || '' - }`} - > - <div className="flex h-6 items-center"> - <input - checked={value} - onChange={( - event: React.ChangeEvent<HTMLInputElement> - ) => { - setValue(event.target.checked); + return ( + <div> + <div + className={`relative flex items-start ${props.outerDivClassName || ""}`} + > + <div className="flex h-6 items-center"> + <input + checked={value} + onChange={(event: React.ChangeEvent<HTMLInputElement>) => { + setValue(event.target.checked); - if (props.onChange) { - props.onChange( - event.target.checked, - props.isIndeterminate - ); - } - }} - ref={checkboxRef} - autoFocus={props.autoFocus} - tabIndex={props.tabIndex} - readOnly={props.readOnly} - disabled={props.disabled} - onFocus={props.onFocus} - onBlur={props.onBlur} - data-testid={props.dataTestId} - aria-describedby="comments-description" - name="comments" - type="checkbox" - className={`accent-indigo-600 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600 ${ - props.className || '' - }`} - /> - </div> - <div className="ml-3 text-sm leading-6"> - <label className="font-medium text-gray-900"> - {props.title} - </label> - {props.description && ( - <div className="text-gray-500">{props.description}</div> - )} - </div> - </div> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} + if (props.onChange) { + props.onChange(event.target.checked, props.isIndeterminate); + } + }} + ref={checkboxRef} + autoFocus={props.autoFocus} + tabIndex={props.tabIndex} + readOnly={props.readOnly} + disabled={props.disabled} + onFocus={props.onFocus} + onBlur={props.onBlur} + data-testid={props.dataTestId} + aria-describedby="comments-description" + name="comments" + type="checkbox" + className={`accent-indigo-600 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600 ${ + props.className || "" + }`} + /> </div> - ); + <div className="ml-3 text-sm leading-6"> + <label className="font-medium text-gray-900">{props.title}</label> + {props.description && ( + <div className="text-gray-500">{props.description}</div> + )} + </div> + </div> + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default CheckboxElement; diff --git a/CommonUI/src/Components/Checkbox/CheckboxViewer.tsx b/CommonUI/src/Components/Checkbox/CheckboxViewer.tsx index 6e8fbc1dbc..748de40844 100644 --- a/CommonUI/src/Components/Checkbox/CheckboxViewer.tsx +++ b/CommonUI/src/Components/Checkbox/CheckboxViewer.tsx @@ -1,40 +1,36 @@ -import Icon from '../Icon/Icon'; -import { Green, Red } from 'Common/Types/BrandColors'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../Icon/Icon"; +import { Green, Red } from "Common/Types/BrandColors"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - text: string; - isChecked: boolean; + text: string; + isChecked: boolean; } const CheckboxViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div> - <div className="flex"> - <div className="h-6 w-6"> - {props.isChecked ? ( - <Icon - className="h-5 w-5" - icon={IconProp.CheckCircle} - color={Green} - /> - ) : ( - <Icon - className="h-5 w-5" - icon={IconProp.CircleClose} - color={Red} - /> - )} - </div> - <div className="text-sm text-gray-900 flex justify-left"> - {props.text} - </div> - </div> + return ( + <div> + <div className="flex"> + <div className="h-6 w-6"> + {props.isChecked ? ( + <Icon + className="h-5 w-5" + icon={IconProp.CheckCircle} + color={Green} + /> + ) : ( + <Icon className="h-5 w-5" icon={IconProp.CircleClose} color={Red} /> + )} </div> - ); + <div className="text-sm text-gray-900 flex justify-left"> + {props.text} + </div> + </div> + </div> + ); }; export default CheckboxViewer; diff --git a/CommonUI/src/Components/CodeBlock/CodeBlock.tsx b/CommonUI/src/Components/CodeBlock/CodeBlock.tsx index 2290cb49f1..44350994b6 100644 --- a/CommonUI/src/Components/CodeBlock/CodeBlock.tsx +++ b/CommonUI/src/Components/CodeBlock/CodeBlock.tsx @@ -1,22 +1,20 @@ -import 'highlight.js/styles/a11y-dark.css'; -import React, { FunctionComponent, ReactElement } from 'react'; -import Highlight from 'react-highlight'; +import "highlight.js/styles/a11y-dark.css"; +import React, { FunctionComponent, ReactElement } from "react"; +import Highlight from "react-highlight"; export interface ComponentProps { - code: string | ReactElement; - language: string; + code: string | ReactElement; + language: string; } const CodeBlock: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Highlight - className={`p-3 language-${props.language} rounded-md shadow`} - > - {props.code} - </Highlight> - ); + return ( + <Highlight className={`p-3 language-${props.language} rounded-md shadow`}> + {props.code} + </Highlight> + ); }; export default CodeBlock; diff --git a/CommonUI/src/Components/CodeEditor/CodeEditor.tsx b/CommonUI/src/Components/CodeEditor/CodeEditor.tsx index 1f5d4b312e..8b706028c0 100644 --- a/CommonUI/src/Components/CodeEditor/CodeEditor.tsx +++ b/CommonUI/src/Components/CodeEditor/CodeEditor.tsx @@ -1,192 +1,192 @@ -import Editor from '@monaco-editor/react'; -import CodeType from 'Common/Types/Code/CodeType'; +import Editor from "@monaco-editor/react"; +import CodeType from "Common/Types/Code/CodeType"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - initialValue?: undefined | string; - onClick?: undefined | (() => void); - placeholder?: undefined | string; - className?: undefined | string; - onChange?: undefined | ((value: string) => void); - readOnly?: boolean | undefined; - type: CodeType; - onFocus?: (() => void) | undefined; - onBlur?: (() => void) | undefined; - dataTestId?: string | undefined; - tabIndex?: number | undefined; - error?: string | undefined; - value?: string | undefined; - showLineNumbers?: boolean | undefined; + initialValue?: undefined | string; + onClick?: undefined | (() => void); + placeholder?: undefined | string; + className?: undefined | string; + onChange?: undefined | ((value: string) => void); + readOnly?: boolean | undefined; + type: CodeType; + onFocus?: (() => void) | undefined; + onBlur?: (() => void) | undefined; + dataTestId?: string | undefined; + tabIndex?: number | undefined; + error?: string | undefined; + value?: string | undefined; + showLineNumbers?: boolean | undefined; } const CodeEditor: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let className: string = ''; + let className: string = ""; - const [placeholder, setPlaceholder] = useState<string>(''); - const [helpText, setHelpText] = useState<string>(''); + const [placeholder, setPlaceholder] = useState<string>(""); + const [helpText, setHelpText] = useState<string>(""); - useEffect(() => { - let value: string | undefined = props.value; + useEffect(() => { + let value: string | undefined = props.value; - if (value && typeof value !== 'string') { - value = JSON.stringify(value, null, 4); - } + if (value && typeof value !== "string") { + value = JSON.stringify(value, null, 4); + } - setValue(value || ''); - }, [props.value]); + setValue(value || ""); + }, [props.value]); - useEffect(() => { - if (props.placeholder) { - if (props.type === CodeType.Markdown) { - setHelpText(`${props.placeholder}. This is in Markdown`); - } + useEffect(() => { + if (props.placeholder) { + if (props.type === CodeType.Markdown) { + setHelpText(`${props.placeholder}. This is in Markdown`); + } - if (props.type === CodeType.HTML) { - setHelpText(`${props.placeholder}. This is in HTML`); - } + if (props.type === CodeType.HTML) { + setHelpText(`${props.placeholder}. This is in HTML`); + } - if (props.type === CodeType.JavaScript) { - setPlaceholder( - `/* ${props.placeholder} + if (props.type === CodeType.JavaScript) { + setPlaceholder( + `/* ${props.placeholder} This is in JavaScript. - */` - ); - } + */`, + ); + } - if (props.type === CodeType.JSON) { - setHelpText(`${props.placeholder}`); - } + if (props.type === CodeType.JSON) { + setHelpText(`${props.placeholder}`); + } - if (props.type === CodeType.CSS) { - setPlaceholder(`/* ${props.placeholder}. This is in CSS. */`); - } - } - }, [props.placeholder, props.type]); + if (props.type === CodeType.CSS) { + setPlaceholder(`/* ${props.placeholder}. This is in CSS. */`); + } + } + }, [props.placeholder, props.type]); - if (!props.className) { - className = - 'block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:text-sm'; - } else { - className = props.className; + if (!props.className) { + className = + "block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:text-sm"; + } else { + className = props.className; + } + + if (props.error) { + className = + "block w-full rounded-md border bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-red-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-red-500 sm:text-sm border-red-300 pr-10 text-red-900 placeholder-red-300 focus:border-red-500 focus:outline-none focus:ring-red-500"; + } + + const [value, setValue] = useState<string>(""); + + useEffect(() => { + let initialValue: string | undefined = props.initialValue; + + if (initialValue && typeof initialValue !== "string") { + initialValue = JSON.stringify(initialValue, null, 4); } - if (props.error) { - className = - 'block w-full rounded-md border bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-red-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-red-500 sm:text-sm border-red-300 pr-10 text-red-900 placeholder-red-300 focus:border-red-500 focus:outline-none focus:ring-red-500'; - } + setValue(initialValue || ""); + }, [props.initialValue]); - const [value, setValue] = useState<string>(''); + return ( + <div + data-testid={props.dataTestId} + onClick={() => { + props.onClick && props.onClick(); + props.onFocus && props.onFocus(); + }} + > + {helpText && ( + <p className="bg-gray-50 text-gray-500 p-3 mt-2 mb-2 rounded text-base text-sm"> + {" "} + {helpText}{" "} + </p> + )} - useEffect(() => { - let initialValue: string | undefined = props.initialValue; + <Editor + defaultLanguage={props.type} + height="30vh" + value={value} + onChange={(code: string | undefined) => { + if (code === undefined) { + code = ""; + } - if (initialValue && typeof initialValue !== 'string') { - initialValue = JSON.stringify(initialValue, null, 4); - } + setValue(code); + props.onBlur && props.onBlur(); + props.onChange && props.onChange(code); + }} + defaultValue={value || placeholder || ""} + className={className} + options={{ + acceptSuggestionOnCommitCharacter: true, + acceptSuggestionOnEnter: "on", + accessibilitySupport: "auto", + fontSize: 14, + automaticLayout: true, + codeLens: false, + colorDecorators: true, + contextmenu: false, + cursorBlinking: "blink", + tabIndex: props.tabIndex || 0, + minimap: { enabled: false }, + cursorStyle: "line", + disableLayerHinting: false, + disableMonospaceOptimizations: false, + dragAndDrop: false, + fixedOverflowWidgets: false, + folding: true, + foldingStrategy: "auto", + fontLigatures: false, + formatOnPaste: false, + formatOnType: false, - setValue(initialValue || ''); - }, [props.initialValue]); - - return ( - <div - data-testid={props.dataTestId} - onClick={() => { - props.onClick && props.onClick(); - props.onFocus && props.onFocus(); - }} - > - {helpText && ( - <p className="bg-gray-50 text-gray-500 p-3 mt-2 mb-2 rounded text-base text-sm"> - {' '} - {helpText}{' '} - </p> - )} - - <Editor - defaultLanguage={props.type} - height="30vh" - value={value} - onChange={(code: string | undefined) => { - if (code === undefined) { - code = ''; - } - - setValue(code); - props.onBlur && props.onBlur(); - props.onChange && props.onChange(code); - }} - defaultValue={value || placeholder || ''} - className={className} - options={{ - acceptSuggestionOnCommitCharacter: true, - acceptSuggestionOnEnter: 'on', - accessibilitySupport: 'auto', - fontSize: 14, - automaticLayout: true, - codeLens: false, - colorDecorators: true, - contextmenu: false, - cursorBlinking: 'blink', - tabIndex: props.tabIndex || 0, - minimap: { enabled: false }, - cursorStyle: 'line', - disableLayerHinting: false, - disableMonospaceOptimizations: false, - dragAndDrop: false, - fixedOverflowWidgets: false, - folding: true, - foldingStrategy: 'auto', - fontLigatures: false, - formatOnPaste: false, - formatOnType: false, - - hideCursorInOverviewRuler: false, - links: true, - mouseWheelZoom: false, - multiCursorMergeOverlapping: true, - multiCursorModifier: 'alt', - overviewRulerBorder: true, - overviewRulerLanes: 2, - quickSuggestions: true, - quickSuggestionsDelay: 100, - readOnly: props.readOnly || false, - renderControlCharacters: false, - scrollbar: { - horizontal: 'hidden', - }, - renderLineHighlight: 'all', - renderWhitespace: 'none', - revealHorizontalRightPadding: 30, - roundedSelection: true, - rulers: [], - scrollBeyondLastColumn: 5, - scrollBeyondLastLine: true, - selectOnLineNumbers: true, - lineNumbers: props.showLineNumbers ? 'on' : 'off', - selectionClipboard: true, - selectionHighlight: true, - showFoldingControls: 'mouseover', - smoothScrolling: false, - suggestOnTriggerCharacters: true, - wordBasedSuggestions: 'off', - wordWrap: props.type === CodeType.Markdown ? 'on' : 'off', - }} - /> - {props.error && ( - <p className="mt-1 text-sm text-red-400">{props.error}</p> - )} - </div> - ); + hideCursorInOverviewRuler: false, + links: true, + mouseWheelZoom: false, + multiCursorMergeOverlapping: true, + multiCursorModifier: "alt", + overviewRulerBorder: true, + overviewRulerLanes: 2, + quickSuggestions: true, + quickSuggestionsDelay: 100, + readOnly: props.readOnly || false, + renderControlCharacters: false, + scrollbar: { + horizontal: "hidden", + }, + renderLineHighlight: "all", + renderWhitespace: "none", + revealHorizontalRightPadding: 30, + roundedSelection: true, + rulers: [], + scrollBeyondLastColumn: 5, + scrollBeyondLastLine: true, + selectOnLineNumbers: true, + lineNumbers: props.showLineNumbers ? "on" : "off", + selectionClipboard: true, + selectionHighlight: true, + showFoldingControls: "mouseover", + smoothScrolling: false, + suggestOnTriggerCharacters: true, + wordBasedSuggestions: "off", + wordWrap: props.type === CodeType.Markdown ? "on" : "off", + }} + /> + {props.error && ( + <p className="mt-1 text-sm text-red-400">{props.error}</p> + )} + </div> + ); }; export default CodeEditor; diff --git a/CommonUI/src/Components/ColorCircle/ColorCircle.tsx b/CommonUI/src/Components/ColorCircle/ColorCircle.tsx index 6add578fb9..2f683e18da 100644 --- a/CommonUI/src/Components/ColorCircle/ColorCircle.tsx +++ b/CommonUI/src/Components/ColorCircle/ColorCircle.tsx @@ -1,25 +1,25 @@ -import Tooltip from '../Tooltip/Tooltip'; -import Color from 'Common/Types/Color'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Tooltip from "../Tooltip/Tooltip"; +import Color from "Common/Types/Color"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - color: Color; - tooltip: string; + color: Color; + tooltip: string; } const ColorCircle: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Tooltip text={props.tooltip}> - <div - className="rounded-full h-3 w-3" - style={{ - backgroundColor: props.color.toString(), - }} - ></div> - </Tooltip> - ); + return ( + <Tooltip text={props.tooltip}> + <div + className="rounded-full h-3 w-3" + style={{ + backgroundColor: props.color.toString(), + }} + ></div> + </Tooltip> + ); }; export default ColorCircle; diff --git a/CommonUI/src/Components/ColorSquareCube/ColorSquareCube.tsx b/CommonUI/src/Components/ColorSquareCube/ColorSquareCube.tsx index d066b1f897..713e32c189 100644 --- a/CommonUI/src/Components/ColorSquareCube/ColorSquareCube.tsx +++ b/CommonUI/src/Components/ColorSquareCube/ColorSquareCube.tsx @@ -1,25 +1,25 @@ -import Tooltip from '../Tooltip/Tooltip'; -import Color from 'Common/Types/Color'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Tooltip from "../Tooltip/Tooltip"; +import Color from "Common/Types/Color"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - color: Color; - tooltip: string; + color: Color; + tooltip: string; } const ColorSquareCube: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Tooltip text={props.tooltip}> - <div - className="rounded h-3 w-3" - style={{ - backgroundColor: props.color.toString(), - }} - ></div> - </Tooltip> - ); + return ( + <Tooltip text={props.tooltip}> + <div + className="rounded h-3 w-3" + style={{ + backgroundColor: props.color.toString(), + }} + ></div> + </Tooltip> + ); }; export default ColorSquareCube; diff --git a/CommonUI/src/Components/ColorViewer/ColorViewer.tsx b/CommonUI/src/Components/ColorViewer/ColorViewer.tsx index 82bde33bab..9248bc4e30 100644 --- a/CommonUI/src/Components/ColorViewer/ColorViewer.tsx +++ b/CommonUI/src/Components/ColorViewer/ColorViewer.tsx @@ -1,48 +1,46 @@ -import { Gray500 } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { Gray500 } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - placeholder?: undefined | string; - className?: undefined | string; - value?: Color | undefined; - dataTestId?: string; - onClick?: (() => void) | undefined; + placeholder?: undefined | string; + className?: undefined | string; + value?: Color | undefined; + dataTestId?: string; + onClick?: (() => void) | undefined; } const ColorInput: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( + return ( + <div + className={`flex ${props.className}`} + onClick={() => { + props.onClick && props.onClick(); + }} + data-testid={props.dataTestId} + > + {props.value && ( <div - className={`flex ${props.className}`} - onClick={() => { - props.onClick && props.onClick(); - }} - data-testid={props.dataTestId} - > - {props.value && ( - <div - style={{ - backgroundColor: props.value.toString(), - height: '20px', - borderWidth: '1px', - borderColor: Gray500.toString(), - width: '20px', - borderRadius: '300px', - boxShadow: 'rgb(149 157 165 / 20%) 0px 8px 24px', - marginRight: '7px', - borderStyle: 'solid', - }} - ></div> - )} - <div> - {props.value?.toString() || - props.placeholder || - 'No Color Selected'} - </div> - </div> - ); + style={{ + backgroundColor: props.value.toString(), + height: "20px", + borderWidth: "1px", + borderColor: Gray500.toString(), + width: "20px", + borderRadius: "300px", + boxShadow: "rgb(149 157 165 / 20%) 0px 8px 24px", + marginRight: "7px", + borderStyle: "solid", + }} + ></div> + )} + <div> + {props.value?.toString() || props.placeholder || "No Color Selected"} + </div> + </div> + ); }; export default ColorInput; diff --git a/CommonUI/src/Components/ComingSoon/ComingSoon.tsx b/CommonUI/src/Components/ComingSoon/ComingSoon.tsx index 0eb8614545..8cc822dca5 100644 --- a/CommonUI/src/Components/ComingSoon/ComingSoon.tsx +++ b/CommonUI/src/Components/ComingSoon/ComingSoon.tsx @@ -1,16 +1,16 @@ -import EmptyState from '../EmptyState/EmptyState'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import EmptyState from "../EmptyState/EmptyState"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; const ComingSoon: FunctionComponent = (): ReactElement => { - return ( - <EmptyState - id="coming-soon" - icon={IconProp.CursorArrowRays} - title="Coming soon!" - description="We will be launching this feature very soon. Stay Tuned!" - /> - ); + return ( + <EmptyState + id="coming-soon" + icon={IconProp.CursorArrowRays} + title="Coming soon!" + description="We will be launching this feature very soon. Stay Tuned!" + /> + ); }; export default ComingSoon; diff --git a/CommonUI/src/Components/ComponentLoader/CompactLoader.tsx b/CommonUI/src/Components/ComponentLoader/CompactLoader.tsx index b1bc908204..5a4098832a 100644 --- a/CommonUI/src/Components/ComponentLoader/CompactLoader.tsx +++ b/CommonUI/src/Components/ComponentLoader/CompactLoader.tsx @@ -1,17 +1,13 @@ -import Loader, { LoaderType } from '../Loader/Loader'; -import { VeryLightGray } from 'Common/Types/BrandColors'; -import React, { ReactElement } from 'react'; +import Loader, { LoaderType } from "../Loader/Loader"; +import { VeryLightGray } from "Common/Types/BrandColors"; +import React, { ReactElement } from "react"; const CompactLoader: () => JSX.Element = (): ReactElement => { - return ( - <div className="my-5 w-full flex justify-center"> - <Loader - loaderType={LoaderType.Bar} - color={VeryLightGray} - size={200} - /> - </div> - ); + return ( + <div className="my-5 w-full flex justify-center"> + <Loader loaderType={LoaderType.Bar} color={VeryLightGray} size={200} /> + </div> + ); }; export default CompactLoader; diff --git a/CommonUI/src/Components/ComponentLoader/ComponentLoader.tsx b/CommonUI/src/Components/ComponentLoader/ComponentLoader.tsx index 4a96a54aef..28ea7a20f3 100644 --- a/CommonUI/src/Components/ComponentLoader/ComponentLoader.tsx +++ b/CommonUI/src/Components/ComponentLoader/ComponentLoader.tsx @@ -1,12 +1,12 @@ -import CompactLoader from './CompactLoader'; -import React, { ReactElement } from 'react'; +import CompactLoader from "./CompactLoader"; +import React, { ReactElement } from "react"; const ComponentLoader: () => JSX.Element = (): ReactElement => { - return ( - <div className="my-16" data-testid="component-loader"> - <CompactLoader /> - </div> - ); + return ( + <div className="my-16" data-testid="component-loader"> + <CompactLoader /> + </div> + ); }; export default ComponentLoader; diff --git a/CommonUI/src/Components/CopyTextButton/CopyTextButton.tsx b/CommonUI/src/Components/CopyTextButton/CopyTextButton.tsx index bdf9752c31..26bab53909 100644 --- a/CommonUI/src/Components/CopyTextButton/CopyTextButton.tsx +++ b/CommonUI/src/Components/CopyTextButton/CopyTextButton.tsx @@ -1,40 +1,37 @@ -import Clipboard from '../../Utils/Clipboard'; +import Clipboard from "../../Utils/Clipboard"; import React, { - FunctionComponent, - MouseEventHandler, - ReactElement, -} from 'react'; + FunctionComponent, + MouseEventHandler, + ReactElement, +} from "react"; export interface ComponentProps { - textToBeCopied: string; - className?: string | undefined; + textToBeCopied: string; + className?: string | undefined; } const CopyTextButton: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [copied, setCopied] = React.useState(false); + const [copied, setCopied] = React.useState(false); - const handleCopy: MouseEventHandler<HTMLDivElement> = async ( - event: React.MouseEvent<HTMLDivElement, MouseEvent> - ) => { - event.preventDefault(); - event.stopPropagation(); - await Clipboard.copyToClipboard(props.textToBeCopied); - setCopied(true); - setTimeout(() => { - setCopied(false); - }, 1000); - }; + const handleCopy: MouseEventHandler<HTMLDivElement> = async ( + event: React.MouseEvent<HTMLDivElement, MouseEvent>, + ) => { + event.preventDefault(); + event.stopPropagation(); + await Clipboard.copyToClipboard(props.textToBeCopied); + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 1000); + }; - return ( - <div - className={`cursor-pointer ${props.className}`} - onClick={handleCopy} - > - <div>{copied ? 'Copied!' : 'Copy'}</div> - </div> - ); + return ( + <div className={`cursor-pointer ${props.className}`} onClick={handleCopy}> + <div>{copied ? "Copied!" : "Copy"}</div> + </div> + ); }; export default CopyTextButton; diff --git a/CommonUI/src/Components/CopyableButton/CopyableButton.tsx b/CommonUI/src/Components/CopyableButton/CopyableButton.tsx index c540b66f30..8ab5e67ae4 100644 --- a/CommonUI/src/Components/CopyableButton/CopyableButton.tsx +++ b/CommonUI/src/Components/CopyableButton/CopyableButton.tsx @@ -1,51 +1,51 @@ -import Icon, { SizeProp, ThickProp } from '../Icon/Icon'; -import Tooltip from '../Tooltip/Tooltip'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import Icon, { SizeProp, ThickProp } from "../Icon/Icon"; +import Tooltip from "../Tooltip/Tooltip"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - textToBeCopied: string; + textToBeCopied: string; } const CopyableButton: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [copiedToClipboard, setCopyToClipboard] = useState<boolean>(false); + const [copiedToClipboard, setCopyToClipboard] = useState<boolean>(false); - const refreshCopyToClipboardState: VoidFunction = (): void => { - setCopyToClipboard(true); - setTimeout(() => { - setCopyToClipboard(false); - }, 2000); - }; + const refreshCopyToClipboardState: VoidFunction = (): void => { + setCopyToClipboard(true); + setTimeout(() => { + setCopyToClipboard(false); + }, 2000); + }; - return ( - <div - className={`${ - copiedToClipboard ? '' : 'cursor-pointer mt-0.5' - } flex ml-1 text-gray-500`} - onClick={async () => { - refreshCopyToClipboardState(); - await navigator.clipboard?.writeText(props.textToBeCopied); - }} - role="copy-to-clipboard" - > - {' '} - {copiedToClipboard ? ( - 'Copied to Clipboard' - ) : ( - <Tooltip text="Copy to Clipboard"> - <Icon - className="h-4 w-4" - data-testid="copy-to-clipboard-icon" - icon={IconProp.Copy} - size={SizeProp.Small} - thick={ThickProp.Thick} - /> - </Tooltip> - )}{' '} - </div> - ); + return ( + <div + className={`${ + copiedToClipboard ? "" : "cursor-pointer mt-0.5" + } flex ml-1 text-gray-500`} + onClick={async () => { + refreshCopyToClipboardState(); + await navigator.clipboard?.writeText(props.textToBeCopied); + }} + role="copy-to-clipboard" + > + {" "} + {copiedToClipboard ? ( + "Copied to Clipboard" + ) : ( + <Tooltip text="Copy to Clipboard"> + <Icon + className="h-4 w-4" + data-testid="copy-to-clipboard-icon" + icon={IconProp.Copy} + size={SizeProp.Small} + thick={ThickProp.Thick} + /> + </Tooltip> + )}{" "} + </div> + ); }; export default CopyableButton; diff --git a/CommonUI/src/Components/CustomFields/CustomFieldsDetail.tsx b/CommonUI/src/Components/CustomFields/CustomFieldsDetail.tsx index f0b79a3454..299855e75e 100644 --- a/CommonUI/src/Components/CustomFields/CustomFieldsDetail.tsx +++ b/CommonUI/src/Components/CustomFields/CustomFieldsDetail.tsx @@ -1,175 +1,174 @@ -import API from '../../Utils/API/API'; -import ModelAPI, { ListResult } from '../../Utils/ModelAPI/ModelAPI'; -import { ButtonStyleType } from '../Button/Button'; -import Card from '../Card/Card'; -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import Detail from '../Detail/Detail'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import BasicFormModal from '../FormModal/BasicFormModal'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import API from "../../Utils/API/API"; +import ModelAPI, { ListResult } from "../../Utils/ModelAPI/ModelAPI"; +import { ButtonStyleType } from "../Button/Button"; +import Card from "../Card/Card"; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import Detail from "../Detail/Detail"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import BasicFormModal from "../FormModal/BasicFormModal"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import React, { FunctionComponent, ReactElement, useState } from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps { - title: string; - description: string; - modelId: ObjectID; - modelType: BaseModelType; - customFieldType: BaseModelType; - projectId: ObjectID; - name: string; + title: string; + description: string; + modelId: ObjectID; + modelType: BaseModelType; + customFieldType: BaseModelType; + projectId: ObjectID; + name: string; } const CustomFieldsDetail: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [schemaList, setSchemaList] = useState<Array<BaseModel>>([]); - const [error, setError] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(false); - const [model, setModel] = useState<BaseModel | null>(null); - const [showModelForm, setShowModelForm] = useState<boolean>(false); + const [schemaList, setSchemaList] = useState<Array<BaseModel>>([]); + const [error, setError] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(false); + const [model, setModel] = useState<BaseModel | null>(null); + const [showModelForm, setShowModelForm] = useState<boolean>(false); - const onLoad: PromiseVoidFunction = async (): Promise<void> => { - try { - // load schema. - setIsLoading(true); + const onLoad: PromiseVoidFunction = async (): Promise<void> => { + try { + // load schema. + setIsLoading(true); - const schemaList: ListResult<BaseModel> = - await ModelAPI.getList<BaseModel>({ - modelType: props.customFieldType, - query: { - projectId: props.projectId, - } as any, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - type: true, - description: true, - } as any, - sort: {}, - }); + const schemaList: ListResult<BaseModel> = + await ModelAPI.getList<BaseModel>({ + modelType: props.customFieldType, + query: { + projectId: props.projectId, + } as any, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + type: true, + description: true, + } as any, + sort: {}, + }); - const item: BaseModel | null = await ModelAPI.getItem<BaseModel>({ - modelType: props.modelType, - id: props.modelId, - select: { - customFields: true, - } as any, - }); + const item: BaseModel | null = await ModelAPI.getItem<BaseModel>({ + modelType: props.modelType, + id: props.modelId, + select: { + customFields: true, + } as any, + }); - setSchemaList(schemaList.data); - setModel(item); + setSchemaList(schemaList.data); + setModel(item); - setIsLoading(false); - } catch (err) { - setIsLoading(false); - setError(API.getFriendlyMessage(err)); - } - }; + setIsLoading(false); + } catch (err) { + setIsLoading(false); + setError(API.getFriendlyMessage(err)); + } + }; - type OnSaveFunction = (data: JSONObject) => Promise<void>; + type OnSaveFunction = (data: JSONObject) => Promise<void>; - const onSave: OnSaveFunction = async (data: JSONObject): Promise<void> => { - try { - // load schema. - setIsLoading(true); - setShowModelForm(false); + const onSave: OnSaveFunction = async (data: JSONObject): Promise<void> => { + try { + // load schema. + setIsLoading(true); + setShowModelForm(false); - await ModelAPI.updateById({ - modelType: props.modelType, - id: props.modelId, - data: { - customFields: data, - }, - }); + await ModelAPI.updateById({ + modelType: props.modelType, + id: props.modelId, + data: { + customFields: data, + }, + }); - await onLoad(); - } catch (err) { - setIsLoading(false); - setError(API.getFriendlyMessage(err)); - } - }; + await onLoad(); + } catch (err) { + setIsLoading(false); + setError(API.getFriendlyMessage(err)); + } + }; - useAsyncEffect(async () => { - await onLoad(); - }, []); + useAsyncEffect(async () => { + await onLoad(); + }, []); - return ( - <Card - title={props.title} - description={props.description} - buttons={[ - { - title: 'Edit Fields', - buttonStyle: ButtonStyleType.NORMAL, - onClick: () => { - setShowModelForm(true); - }, - icon: IconProp.Edit, - }, - ]} - > - <div className="border-t border-gray-200 px-4 py-5 sm:px-6 -m-6 -mt-2"> - {isLoading && !error && <ComponentLoader />} - {!isLoading && !error && schemaList.length === 0 && ( - <ErrorMessage error="No custom fields have been added for this resource. You may add custom fields in Project Settings." /> - )} - {error && <ErrorMessage error={error} />} - {!model && <ErrorMessage error={'Item not found'} />} + return ( + <Card + title={props.title} + description={props.description} + buttons={[ + { + title: "Edit Fields", + buttonStyle: ButtonStyleType.NORMAL, + onClick: () => { + setShowModelForm(true); + }, + icon: IconProp.Edit, + }, + ]} + > + <div className="border-t border-gray-200 px-4 py-5 sm:px-6 -m-6 -mt-2"> + {isLoading && !error && <ComponentLoader />} + {!isLoading && !error && schemaList.length === 0 && ( + <ErrorMessage error="No custom fields have been added for this resource. You may add custom fields in Project Settings." /> + )} + {error && <ErrorMessage error={error} />} + {!model && <ErrorMessage error={"Item not found"} />} - {!isLoading && !error && model && ( - <Detail - id={props.name} - item={(model as any)['customFields'] || {}} - fields={schemaList.map((schemaItem: BaseModel) => { - return { - key: (schemaItem as any).name, - title: (schemaItem as any).name, - description: (schemaItem as any).description, - fieldType: (schemaItem as any).type, - placeholder: 'No data entered', - }; - })} - showDetailsInNumberOfColumns={1} - /> - )} + {!isLoading && !error && model && ( + <Detail + id={props.name} + item={(model as any)["customFields"] || {}} + fields={schemaList.map((schemaItem: BaseModel) => { + return { + key: (schemaItem as any).name, + title: (schemaItem as any).name, + description: (schemaItem as any).description, + fieldType: (schemaItem as any).type, + placeholder: "No data entered", + }; + })} + showDetailsInNumberOfColumns={1} + /> + )} - {showModelForm && ( - <BasicFormModal - title={'Edit ' + new props.modelType().singularName} - onClose={() => { - return setShowModelForm(false); - }} - onSubmit={async (data: JSONObject) => { - await onSave(data).catch(); - }} - formProps={{ - initialValues: (model as any)['customFields'] || {}, - fields: schemaList.map((schemaItem: BaseModel) => { - return { - field: { - [(schemaItem as any).name]: true, - }, - title: (schemaItem as any).name, - description: (schemaItem as any) - .description, - fieldType: (schemaItem as any).type, - required: false, - placeholder: '', - }; - }), - }} - /> - )} - </div> - </Card> - ); + {showModelForm && ( + <BasicFormModal + title={"Edit " + new props.modelType().singularName} + onClose={() => { + return setShowModelForm(false); + }} + onSubmit={async (data: JSONObject) => { + await onSave(data).catch(); + }} + formProps={{ + initialValues: (model as any)["customFields"] || {}, + fields: schemaList.map((schemaItem: BaseModel) => { + return { + field: { + [(schemaItem as any).name]: true, + }, + title: (schemaItem as any).name, + description: (schemaItem as any).description, + fieldType: (schemaItem as any).type, + required: false, + placeholder: "", + }; + }), + }} + /> + )} + </div> + </Card> + ); }; export default CustomFieldsDetail; diff --git a/CommonUI/src/Components/Detail/Detail.tsx b/CommonUI/src/Components/Detail/Detail.tsx index 14c56a94bb..38025c6907 100644 --- a/CommonUI/src/Components/Detail/Detail.tsx +++ b/CommonUI/src/Components/Detail/Detail.tsx @@ -1,409 +1,396 @@ -import AlignItem from '../../Types/AlignItem'; -import { Logger } from '../../Utils/Logger'; -import CodeEditor from '../CodeEditor/CodeEditor'; -import ColorViewer from '../ColorViewer/ColorViewer'; -import CopyableButton from '../CopyableButton/CopyableButton'; -import DictionaryOfStringsViewer from '../Dictionary/DictionaryOfStingsViewer'; -import { DropdownOption } from '../Dropdown/Dropdown'; -import HiddenText from '../HiddenText/HiddenText'; -import MarkdownViewer from '../Markdown.tsx/LazyMarkdownViewer'; -import FieldType from '../Types/FieldType'; -import Field from './Field'; -import FieldLabelElement from './FieldLabel'; -import PlaceholderText from './PlaceholderText'; -import FileModel from 'Common/Models/FileModel'; -import CodeType from 'Common/Types/Code/CodeType'; -import Color from 'Common/Types/Color'; -import DatabaseProperty from 'Common/Types/Database/DatabaseProperty'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import GenericObject from 'Common/Types/GenericObject'; -import get from 'lodash/get'; -import React, { ReactElement } from 'react'; +import AlignItem from "../../Types/AlignItem"; +import { Logger } from "../../Utils/Logger"; +import CodeEditor from "../CodeEditor/CodeEditor"; +import ColorViewer from "../ColorViewer/ColorViewer"; +import CopyableButton from "../CopyableButton/CopyableButton"; +import DictionaryOfStringsViewer from "../Dictionary/DictionaryOfStingsViewer"; +import { DropdownOption } from "../Dropdown/Dropdown"; +import HiddenText from "../HiddenText/HiddenText"; +import MarkdownViewer from "../Markdown.tsx/LazyMarkdownViewer"; +import FieldType from "../Types/FieldType"; +import Field from "./Field"; +import FieldLabelElement from "./FieldLabel"; +import PlaceholderText from "./PlaceholderText"; +import FileModel from "Common/Models/FileModel"; +import CodeType from "Common/Types/Code/CodeType"; +import Color from "Common/Types/Color"; +import DatabaseProperty from "Common/Types/Database/DatabaseProperty"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import GenericObject from "Common/Types/GenericObject"; +import get from "lodash/get"; +import React, { ReactElement } from "react"; export interface ComponentProps<T extends GenericObject> { - item: T; - fields: Array<Field<T>>; - id?: string | undefined; - showDetailsInNumberOfColumns?: number | undefined; + item: T; + fields: Array<Field<T>>; + id?: string | undefined; + showDetailsInNumberOfColumns?: number | undefined; } type DetailFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const Detail: DetailFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - type GetMarkdownViewerFunction = (text: string) => ReactElement; + type GetMarkdownViewerFunction = (text: string) => ReactElement; - const getMarkdownViewer: GetMarkdownViewerFunction = ( - text: string - ): ReactElement => { - if (!text) { - return <></>; - } + const getMarkdownViewer: GetMarkdownViewerFunction = ( + text: string, + ): ReactElement => { + if (!text) { + return <></>; + } - return <MarkdownViewer text={text} />; - }; + return <MarkdownViewer text={text} />; + }; - type GetDropdownViewerFunction = ( - data: string, - options: Array<DropdownOption>, - placeholder: string - ) => ReactElement; + type GetDropdownViewerFunction = ( + data: string, + options: Array<DropdownOption>, + placeholder: string, + ) => ReactElement; - const getDropdownViewer: GetDropdownViewerFunction = ( - data: string, - options: Array<DropdownOption>, - placeholder: string - ): ReactElement => { - if (!options) { - return <div>No options found</div>; - } + const getDropdownViewer: GetDropdownViewerFunction = ( + data: string, + options: Array<DropdownOption>, + placeholder: string, + ): ReactElement => { + if (!options) { + return <div>No options found</div>; + } - if ( - !options.find((i: DropdownOption) => { - return i.value === data; - }) - ) { - return <div>{placeholder}</div>; - } - - return ( - <div> - { - options.find((i: DropdownOption) => { - return i.value === data; - })?.label as string - } - </div> - ); - }; - - type GetDictionaryOfStringsViewerFunction = ( - data: Dictionary<string> - ) => ReactElement; - - const getDictionaryOfStringsViewer: GetDictionaryOfStringsViewerFunction = ( - data: Dictionary<string> - ): ReactElement => { - return <DictionaryOfStringsViewer value={data} />; - }; - - type GetColorFieldFunction = (color: Color) => ReactElement; - - const getColorField: GetColorFieldFunction = ( - color: Color - ): ReactElement => { - return <ColorViewer value={color} />; - }; - - type GetUSDCentsFieldFunction = (usdCents: number | null) => ReactElement; - - const getUSDCentsField: GetUSDCentsFieldFunction = ( - usdCents: number | null - ): ReactElement => { - if (usdCents === null) { - return <></>; - } - - return <div className="text-gray-900">{usdCents / 100} USD</div>; - }; - - type GetMinutesFieldFunction = (minutes: number | null) => ReactElement; - - const getMinutesField: GetMinutesFieldFunction = ( - minutes: number | null - ): ReactElement => { - if (minutes === null) { - return <></>; - } - - return ( - <div className="text-gray-900"> - {minutes} {minutes > 1 ? 'minutes' : 'minute'} - </div> - ); - }; - - type GetFieldFunction = (field: Field<T>, index: number) => ReactElement; - - const getField: GetFieldFunction = ( - field: Field<T>, - index: number - ): ReactElement => { - const fieldKey: keyof T | null = field.key; - - if (!fieldKey) { - return <></>; - } - - if (!props.item) { - throw new BadDataException('Item not found'); - } - - let data: string | ReactElement = ''; - - if (get(props.item, fieldKey)) { - data = (get(props.item, fieldKey, '') as any) || ''; - } - - if (field.fieldType === FieldType.Date) { - data = OneUptimeDate.getDateAsLocalFormattedString( - data as string, - true - ); - } - - if (field.fieldType === FieldType.Boolean) { - if (data) { - data = 'Yes'; - } else { - data = 'No'; - } - } - - if (field.fieldType === FieldType.DateTime) { - data = OneUptimeDate.getDateAsLocalFormattedString( - data as string, - false - ); - } - - if (data && field.fieldType === FieldType.Color) { - if (data instanceof Color) { - data = getColorField(data); - } - } - - if (data && field.fieldType === FieldType.USDCents) { - let usdCents: number | null = null; - - if (typeof data === 'string') { - usdCents = parseInt(data); - } - - if (typeof data === 'number') { - usdCents = data; - } - - data = getUSDCentsField(usdCents); - } - - if (data && field.fieldType === FieldType.Minutes) { - let minutes: number | null = null; - - if (typeof data === 'string') { - minutes = parseInt(data); - } - - if (typeof data === 'number') { - minutes = data; - } - - data = getMinutesField(minutes); - } - - if (data && field.fieldType === FieldType.DictionaryOfStrings) { - data = getDictionaryOfStringsViewer( - props.item[fieldKey] as Dictionary<string> - ); - } - - if (data && field.fieldType === FieldType.ArrayOfText) { - data = (data as any).join(', '); - } - - if (!data && field.fieldType === FieldType.Color && field.placeholder) { - data = getColorField(new Color(field.placeholder)); - } - - if (field.fieldType === FieldType.ImageFile) { - if ( - props.item[fieldKey] && - (props.item[fieldKey] as FileModel).file && - (props.item[fieldKey] as FileModel).type - ) { - const blob: Blob = new Blob( - [(props.item[fieldKey] as FileModel).file as Uint8Array], - { - type: (props.item[fieldKey] as FileModel) - .type as string, - } - ); - - const url: string = URL.createObjectURL(blob); - - data = ( - <img - src={url} - className={'rounded'} - style={{ - height: '100px', - }} - /> - ); - } else { - data = ''; - } - } - - if (field.fieldType === FieldType.Markdown) { - if (data) { - data = getMarkdownViewer(data as string); - } - } - - if (field.fieldType === FieldType.Dropdown) { - data = getDropdownViewer( - data as string, - field.dropdownOptions || [], - field.placeholder as string - ); - } - - if (data && field.fieldType === FieldType.HiddenText) { - data = ( - <HiddenText - isCopyable={field.opts?.isCopyable || false} - text={data.toString()} - /> - ); - } - - if ( - data && - (field.fieldType === FieldType.HTML || - field.fieldType === FieldType.CSS || - field.fieldType === FieldType.JSON || - field.fieldType === FieldType.JavaScript) - ) { - let codeType: CodeType = CodeType.HTML; - - if (field.fieldType === FieldType.CSS) { - codeType = CodeType.CSS; - } - - if (field.fieldType === FieldType.JSON) { - codeType = CodeType.JSON; - - //make sure json is well formatted. - - if (typeof data === 'string') { - try { - data = JSON.stringify(JSON.parse(data), null, 2); - } catch (e) { - // cant format json for some reason. ignore. - Logger.error( - 'Cant format json for field: ' + - field.title + - ' with value: ' + - data - ); - } - } - } - - if (field.fieldType === FieldType.JavaScript) { - codeType = CodeType.JavaScript; - } - - data = ( - <CodeEditor - type={codeType} - readOnly={true} - initialValue={data as string} - /> - ); - } - - if (field.getElement) { - data = field.getElement(props.item); - } - - let className: string = 'sm:col-span-1'; - - if (field.colSpan) { - className = 'sm:col-span-' + field.colSpan; - } - - let alignClassName: string = 'flex justify-left'; - - if (field.alignItem === AlignItem.Right) { - alignClassName = 'flex justify-end'; - } else if (field.alignItem === AlignItem.Center) { - alignClassName = 'flex justify-center'; - } else if (field.alignItem === AlignItem.Left) { - alignClassName = 'flex justify-start'; - } - - if (data instanceof DatabaseProperty) { - data = data.toString(); - } - - return ( - <div - className={className} - key={index} - id={props.id} - style={ - props.showDetailsInNumberOfColumns - ? { - width: - 100 / props.showDetailsInNumberOfColumns + - '%', - } - : { width: '100%' } - } - > - <FieldLabelElement - size={field.fieldTitleSize} - title={field.title} - description={field.description} - sideLink={field.sideLink} - alignClassName={alignClassName} - /> - - <div className={`mt-1 text-sm text-gray-900 ${alignClassName}`}> - {data && ( - <div - className={`${field.contentClassName} w-full ${ - field.opts?.isCopyable ? 'flex' : '' - }`} - > - <div>{data}</div> - - {field.opts?.isCopyable && - field.fieldType !== FieldType.HiddenText && ( - <CopyableButton - textToBeCopied={data.toString()} - /> - )} - </div> - )} - {!data && field.placeholder && ( - <PlaceholderText text={field.placeholder} /> - )} - </div> - </div> - ); - }; + if ( + !options.find((i: DropdownOption) => { + return i.value === data; + }) + ) { + return <div>{placeholder}</div>; + } return ( - <div - className={`grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-${ - props.showDetailsInNumberOfColumns || 1 - } w-full`} - > - {props.fields && - props.fields.length > 0 && - props.fields.map((field: Field<T>, i: number) => { - return getField(field, i); - })} - </div> + <div> + { + options.find((i: DropdownOption) => { + return i.value === data; + })?.label as string + } + </div> ); + }; + + type GetDictionaryOfStringsViewerFunction = ( + data: Dictionary<string>, + ) => ReactElement; + + const getDictionaryOfStringsViewer: GetDictionaryOfStringsViewerFunction = ( + data: Dictionary<string>, + ): ReactElement => { + return <DictionaryOfStringsViewer value={data} />; + }; + + type GetColorFieldFunction = (color: Color) => ReactElement; + + const getColorField: GetColorFieldFunction = (color: Color): ReactElement => { + return <ColorViewer value={color} />; + }; + + type GetUSDCentsFieldFunction = (usdCents: number | null) => ReactElement; + + const getUSDCentsField: GetUSDCentsFieldFunction = ( + usdCents: number | null, + ): ReactElement => { + if (usdCents === null) { + return <></>; + } + + return <div className="text-gray-900">{usdCents / 100} USD</div>; + }; + + type GetMinutesFieldFunction = (minutes: number | null) => ReactElement; + + const getMinutesField: GetMinutesFieldFunction = ( + minutes: number | null, + ): ReactElement => { + if (minutes === null) { + return <></>; + } + + return ( + <div className="text-gray-900"> + {minutes} {minutes > 1 ? "minutes" : "minute"} + </div> + ); + }; + + type GetFieldFunction = (field: Field<T>, index: number) => ReactElement; + + const getField: GetFieldFunction = ( + field: Field<T>, + index: number, + ): ReactElement => { + const fieldKey: keyof T | null = field.key; + + if (!fieldKey) { + return <></>; + } + + if (!props.item) { + throw new BadDataException("Item not found"); + } + + let data: string | ReactElement = ""; + + if (get(props.item, fieldKey)) { + data = (get(props.item, fieldKey, "") as any) || ""; + } + + if (field.fieldType === FieldType.Date) { + data = OneUptimeDate.getDateAsLocalFormattedString(data as string, true); + } + + if (field.fieldType === FieldType.Boolean) { + if (data) { + data = "Yes"; + } else { + data = "No"; + } + } + + if (field.fieldType === FieldType.DateTime) { + data = OneUptimeDate.getDateAsLocalFormattedString(data as string, false); + } + + if (data && field.fieldType === FieldType.Color) { + if (data instanceof Color) { + data = getColorField(data); + } + } + + if (data && field.fieldType === FieldType.USDCents) { + let usdCents: number | null = null; + + if (typeof data === "string") { + usdCents = parseInt(data); + } + + if (typeof data === "number") { + usdCents = data; + } + + data = getUSDCentsField(usdCents); + } + + if (data && field.fieldType === FieldType.Minutes) { + let minutes: number | null = null; + + if (typeof data === "string") { + minutes = parseInt(data); + } + + if (typeof data === "number") { + minutes = data; + } + + data = getMinutesField(minutes); + } + + if (data && field.fieldType === FieldType.DictionaryOfStrings) { + data = getDictionaryOfStringsViewer( + props.item[fieldKey] as Dictionary<string>, + ); + } + + if (data && field.fieldType === FieldType.ArrayOfText) { + data = (data as any).join(", "); + } + + if (!data && field.fieldType === FieldType.Color && field.placeholder) { + data = getColorField(new Color(field.placeholder)); + } + + if (field.fieldType === FieldType.ImageFile) { + if ( + props.item[fieldKey] && + (props.item[fieldKey] as FileModel).file && + (props.item[fieldKey] as FileModel).type + ) { + const blob: Blob = new Blob( + [(props.item[fieldKey] as FileModel).file as Uint8Array], + { + type: (props.item[fieldKey] as FileModel).type as string, + }, + ); + + const url: string = URL.createObjectURL(blob); + + data = ( + <img + src={url} + className={"rounded"} + style={{ + height: "100px", + }} + /> + ); + } else { + data = ""; + } + } + + if (field.fieldType === FieldType.Markdown) { + if (data) { + data = getMarkdownViewer(data as string); + } + } + + if (field.fieldType === FieldType.Dropdown) { + data = getDropdownViewer( + data as string, + field.dropdownOptions || [], + field.placeholder as string, + ); + } + + if (data && field.fieldType === FieldType.HiddenText) { + data = ( + <HiddenText + isCopyable={field.opts?.isCopyable || false} + text={data.toString()} + /> + ); + } + + if ( + data && + (field.fieldType === FieldType.HTML || + field.fieldType === FieldType.CSS || + field.fieldType === FieldType.JSON || + field.fieldType === FieldType.JavaScript) + ) { + let codeType: CodeType = CodeType.HTML; + + if (field.fieldType === FieldType.CSS) { + codeType = CodeType.CSS; + } + + if (field.fieldType === FieldType.JSON) { + codeType = CodeType.JSON; + + //make sure json is well formatted. + + if (typeof data === "string") { + try { + data = JSON.stringify(JSON.parse(data), null, 2); + } catch (e) { + // cant format json for some reason. ignore. + Logger.error( + "Cant format json for field: " + + field.title + + " with value: " + + data, + ); + } + } + } + + if (field.fieldType === FieldType.JavaScript) { + codeType = CodeType.JavaScript; + } + + data = ( + <CodeEditor + type={codeType} + readOnly={true} + initialValue={data as string} + /> + ); + } + + if (field.getElement) { + data = field.getElement(props.item); + } + + let className: string = "sm:col-span-1"; + + if (field.colSpan) { + className = "sm:col-span-" + field.colSpan; + } + + let alignClassName: string = "flex justify-left"; + + if (field.alignItem === AlignItem.Right) { + alignClassName = "flex justify-end"; + } else if (field.alignItem === AlignItem.Center) { + alignClassName = "flex justify-center"; + } else if (field.alignItem === AlignItem.Left) { + alignClassName = "flex justify-start"; + } + + if (data instanceof DatabaseProperty) { + data = data.toString(); + } + + return ( + <div + className={className} + key={index} + id={props.id} + style={ + props.showDetailsInNumberOfColumns + ? { + width: 100 / props.showDetailsInNumberOfColumns + "%", + } + : { width: "100%" } + } + > + <FieldLabelElement + size={field.fieldTitleSize} + title={field.title} + description={field.description} + sideLink={field.sideLink} + alignClassName={alignClassName} + /> + + <div className={`mt-1 text-sm text-gray-900 ${alignClassName}`}> + {data && ( + <div + className={`${field.contentClassName} w-full ${ + field.opts?.isCopyable ? "flex" : "" + }`} + > + <div>{data}</div> + + {field.opts?.isCopyable && + field.fieldType !== FieldType.HiddenText && ( + <CopyableButton textToBeCopied={data.toString()} /> + )} + </div> + )} + {!data && field.placeholder && ( + <PlaceholderText text={field.placeholder} /> + )} + </div> + </div> + ); + }; + + return ( + <div + className={`grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-${ + props.showDetailsInNumberOfColumns || 1 + } w-full`} + > + {props.fields && + props.fields.length > 0 && + props.fields.map((field: Field<T>, i: number) => { + return getField(field, i); + })} + </div> + ); }; export default Detail; diff --git a/CommonUI/src/Components/Detail/Field.ts b/CommonUI/src/Components/Detail/Field.ts index 4a1b9b370a..244eac5d2e 100644 --- a/CommonUI/src/Components/Detail/Field.ts +++ b/CommonUI/src/Components/Detail/Field.ts @@ -1,44 +1,44 @@ -import AlignItem from '../../Types/AlignItem'; -import { DropdownOption } from '../Dropdown/Dropdown'; -import FieldType from '../Types/FieldType'; -import { Size } from './FieldLabel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { JSONObject } from 'Common/Types/JSON'; -import { ReactElement } from 'react'; +import AlignItem from "../../Types/AlignItem"; +import { DropdownOption } from "../Dropdown/Dropdown"; +import FieldType from "../Types/FieldType"; +import { Size } from "./FieldLabel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { JSONObject } from "Common/Types/JSON"; +import { ReactElement } from "react"; export interface DetailSideLink { - text: string; - url: Route | URL; - openLinkInNewTab?: boolean; + text: string; + url: Route | URL; + openLinkInNewTab?: boolean; } export interface FieldBase<T> { - title?: string; - description?: string; - fieldTitleSize?: Size | undefined; + title?: string; + description?: string; + fieldTitleSize?: Size | undefined; - fieldType?: FieldType; - dropdownOptions?: Array<DropdownOption> | undefined; - colSpan?: number | undefined; - alignItem?: AlignItem | undefined; - contentClassName?: string | undefined; - getElement?: - | (( - item: T, - onBeforeFetchData?: JSONObject | undefined, - fetchItems?: VoidFunction - ) => ReactElement) - | undefined; - sideLink?: DetailSideLink | undefined; - placeholder?: string; - opts?: - | { - isCopyable?: boolean | undefined; - } - | undefined; + fieldType?: FieldType; + dropdownOptions?: Array<DropdownOption> | undefined; + colSpan?: number | undefined; + alignItem?: AlignItem | undefined; + contentClassName?: string | undefined; + getElement?: + | (( + item: T, + onBeforeFetchData?: JSONObject | undefined, + fetchItems?: VoidFunction, + ) => ReactElement) + | undefined; + sideLink?: DetailSideLink | undefined; + placeholder?: string; + opts?: + | { + isCopyable?: boolean | undefined; + } + | undefined; } export default interface Field<T> extends FieldBase<T> { - key: keyof T | null; // null because some fields are not directly from the model. It could be from getElements + key: keyof T | null; // null because some fields are not directly from the model. It could be from getElements } diff --git a/CommonUI/src/Components/Detail/FieldLabel.tsx b/CommonUI/src/Components/Detail/FieldLabel.tsx index 09c2c928d4..6eabcab01c 100644 --- a/CommonUI/src/Components/Detail/FieldLabel.tsx +++ b/CommonUI/src/Components/Detail/FieldLabel.tsx @@ -1,54 +1,47 @@ -import Link from '../Link/Link'; -import { DetailSideLink } from './Field'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Link from "../Link/Link"; +import { DetailSideLink } from "./Field"; +import React, { FunctionComponent, ReactElement } from "react"; export enum Size { - Normal = 'text-sm', - Medium = 'text-base', - Large = 'text-=lg', + Normal = "text-sm", + Medium = "text-base", + Large = "text-=lg", } export interface ComponentProps { - title?: string | undefined; - description?: string | undefined; - alignClassName?: string | undefined; - sideLink?: DetailSideLink | undefined; - size?: Size | undefined; + title?: string | undefined; + description?: string | undefined; + alignClassName?: string | undefined; + sideLink?: DetailSideLink | undefined; + size?: Size | undefined; } const FieldLabelElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <> - {props.title && ( - <label - className={`${ - props.size || 'text-sm' - } font-medium text-gray-500`} - > - <span className={props.alignClassName}>{props.title}</span> - {props.sideLink && - props.sideLink?.text && - props.sideLink?.url && ( - <span> - <Link - to={props.sideLink?.url} - className="hover:underline" - > - {props.sideLink?.text} - </Link> - </span> - )} - </label> - )} - {props.description && ( - <p className={`${props.alignClassName} text-sm text-gray-400`}> - {props.description} - </p> - )} - </> - ); + return ( + <> + {props.title && ( + <label + className={`${props.size || "text-sm"} font-medium text-gray-500`} + > + <span className={props.alignClassName}>{props.title}</span> + {props.sideLink && props.sideLink?.text && props.sideLink?.url && ( + <span> + <Link to={props.sideLink?.url} className="hover:underline"> + {props.sideLink?.text} + </Link> + </span> + )} + </label> + )} + {props.description && ( + <p className={`${props.alignClassName} text-sm text-gray-400`}> + {props.description} + </p> + )} + </> + ); }; export default FieldLabelElement; diff --git a/CommonUI/src/Components/Detail/PlaceholderText.tsx b/CommonUI/src/Components/Detail/PlaceholderText.tsx index c3d1faa77c..8ee9fd2f12 100644 --- a/CommonUI/src/Components/Detail/PlaceholderText.tsx +++ b/CommonUI/src/Components/Detail/PlaceholderText.tsx @@ -1,13 +1,13 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - text: string; + text: string; } const PlaceholderText: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return <div className="text-gray-500">{props.text}</div>; + return <div className="text-gray-500">{props.text}</div>; }; export default PlaceholderText; diff --git a/CommonUI/src/Components/Dictionary/Dictionary.tsx b/CommonUI/src/Components/Dictionary/Dictionary.tsx index 92e280018c..b1c0a1c6b4 100644 --- a/CommonUI/src/Components/Dictionary/Dictionary.tsx +++ b/CommonUI/src/Components/Dictionary/Dictionary.tsx @@ -1,301 +1,278 @@ -import Button, { ButtonSize, ButtonStyleType } from '../Button/Button'; -import Dropdown, { DropdownOption, DropdownValue } from '../Dropdown/Dropdown'; -import Icon, { SizeProp } from '../Icon/Icon'; -import Input, { InputType } from '../Input/Input'; -import Dictionary from 'Common/Types/Dictionary'; -import IconProp from 'Common/Types/Icon/IconProp'; +import Button, { ButtonSize, ButtonStyleType } from "../Button/Button"; +import Dropdown, { DropdownOption, DropdownValue } from "../Dropdown/Dropdown"; +import Icon, { SizeProp } from "../Icon/Icon"; +import Input, { InputType } from "../Input/Input"; +import Dictionary from "Common/Types/Dictionary"; +import IconProp from "Common/Types/Icon/IconProp"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export enum ValueType { - Text = 'Text', - Number = 'Number', - Boolean = 'Boolean', + Text = "Text", + Number = "Number", + Boolean = "Boolean", } export interface ComponentProps { - onChange?: - | undefined - | ((value: Dictionary<string | boolean | number>) => void); - initialValue?: Dictionary<string | boolean | number>; - keyPlaceholder?: string; - valuePlaceholder?: string; - addButtonSuffix?: string; - valueTypes?: Array<ValueType>; // by default it'll be Text + onChange?: + | undefined + | ((value: Dictionary<string | boolean | number>) => void); + initialValue?: Dictionary<string | boolean | number>; + keyPlaceholder?: string; + valuePlaceholder?: string; + addButtonSuffix?: string; + valueTypes?: Array<ValueType>; // by default it'll be Text } interface Item { - key: string; - value: string | number | boolean; - type: ValueType; + key: string; + value: string | number | boolean; + type: ValueType; } const DictionaryForm: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const valueTypes: Array<ValueType> = - props.valueTypes && props.valueTypes.length > 0 - ? props.valueTypes - : [ValueType.Text]; - const dropdownOptionsForValueTypes: Array<DropdownOption> = valueTypes.map( - (valueType: ValueType) => { - return { - label: valueType, - value: valueType, - }; + const valueTypes: Array<ValueType> = + props.valueTypes && props.valueTypes.length > 0 + ? props.valueTypes + : [ValueType.Text]; + const dropdownOptionsForValueTypes: Array<DropdownOption> = valueTypes.map( + (valueType: ValueType) => { + return { + label: valueType, + value: valueType, + }; + }, + ); + + const [data, setData] = useState<Array<Item>>([]); + const [isInitialValueSet, setIsInitialValueSet] = useState<boolean>(false); + + type UpdateDataFunction = ( + json: Dictionary<string | number | boolean>, + ) => void; + + const updateData: UpdateDataFunction = ( + json: Dictionary<string | number | boolean>, + ): void => { + const newData: Array<Item> = Object.keys(json).map((key: string) => { + // check if the value type is in data + + const valueTypeInData: ValueType | undefined = data.find((item: Item) => { + return item.key === key; + })?.type; + + let valueType: ValueType = valueTypeInData || ValueType.Text; + + if (!valueTypeInData) { + if (typeof json[key] === "number") { + valueType = ValueType.Number; } - ); - const [data, setData] = useState<Array<Item>>([]); - const [isInitialValueSet, setIsInitialValueSet] = useState<boolean>(false); - - type UpdateDataFunction = ( - json: Dictionary<string | number | boolean> - ) => void; - - const updateData: UpdateDataFunction = ( - json: Dictionary<string | number | boolean> - ): void => { - const newData: Array<Item> = Object.keys(json).map((key: string) => { - // check if the value type is in data - - const valueTypeInData: ValueType | undefined = data.find( - (item: Item) => { - return item.key === key; - } - )?.type; - - let valueType: ValueType = valueTypeInData || ValueType.Text; - - if (!valueTypeInData) { - if (typeof json[key] === 'number') { - valueType = ValueType.Number; - } - - if (typeof json[key] === 'boolean') { - valueType = ValueType.Boolean; - } - } - - return { - key: key, - value: json[key] || '', - type: valueType, - }; - }); - - setData(newData); - }; - - useEffect(() => { - if ( - props.initialValue && - !isInitialValueSet && - Object.keys(props.initialValue).length > 0 - ) { - setIsInitialValueSet(true); - updateData(props.initialValue); + if (typeof json[key] === "boolean") { + valueType = ValueType.Boolean; } - }, [props.initialValue]); + } - type OnDataChangeFunction = (data: Array<Item>) => void; + return { + key: key, + value: json[key] || "", + type: valueType, + }; + }); - const onDataChange: OnDataChangeFunction = (data: Array<Item>): void => { - const result: Dictionary<string | number | boolean> = {}; - data.forEach((item: Item) => { - result[item.key] = item.value; - }); - if (props.onChange) { - props.onChange(result); - } - }; + setData(newData); + }; - const trueDropdownOption: DropdownOption = { - label: 'True', - value: 'True', - }; + useEffect(() => { + if ( + props.initialValue && + !isInitialValueSet && + Object.keys(props.initialValue).length > 0 + ) { + setIsInitialValueSet(true); + updateData(props.initialValue); + } + }, [props.initialValue]); - const falseDropdownOption: DropdownOption = { - label: 'False', - value: 'False', - }; + type OnDataChangeFunction = (data: Array<Item>) => void; - return ( - <div> - <div> - {data.map((item: Item, index: number) => { - return ( - <div key={index} className="flex"> - <div className="mr-1"> - <Input - value={item.key} - placeholder={props.keyPlaceholder} - onChange={(value: string) => { - const newData: Array<Item> = [...data]; - newData[index]!.key = value; - setData(newData); - onDataChange(newData); - }} - /> - </div> - <div className="mr-1 ml-1 mt-auto mb-auto"> - <Icon - className="h-3 w-3" - icon={IconProp.Equals} - size={SizeProp.Small} - /> - </div> - {valueTypes.length > 1 && ( - <div className="ml-1"> - <Dropdown - value={dropdownOptionsForValueTypes.find( - ( - dropdownOption: DropdownOption - ) => { - return ( - dropdownOption.value === - item.type - ); - } - )} - options={dropdownOptionsForValueTypes} - isMultiSelect={false} - onChange={( - selectedOption: - | DropdownValue - | Array<DropdownValue> - | null - ) => { - const newData: Array<Item> = [ - ...data, - ]; - newData[index]!.type = - (selectedOption as ValueType) || - valueTypes[0]; - setData(newData); - onDataChange(newData); - }} - /> - </div> - )} - <div className="ml-1"> - {item.type === ValueType.Text && ( - <Input - value={item.value.toString()} - placeholder={props.valuePlaceholder} - onChange={(value: string) => { - const newData: Array<Item> = [ - ...data, - ]; - newData[index]!.value = value; - setData(newData); - onDataChange(newData); - }} - /> - )} + const onDataChange: OnDataChangeFunction = (data: Array<Item>): void => { + const result: Dictionary<string | number | boolean> = {}; + data.forEach((item: Item) => { + result[item.key] = item.value; + }); + if (props.onChange) { + props.onChange(result); + } + }; - {item.type === ValueType.Number && ( - <Input - value={item.value.toString()} - placeholder={props.valuePlaceholder} - onChange={(value: string) => { - const newData: Array<Item> = [ - ...data, - ]; + const trueDropdownOption: DropdownOption = { + label: "True", + value: "True", + }; - if ( - typeof value === 'string' && - value.length > 0 - ) { - newData[index]!.value = - parseInt(value); - } else { - delete newData[index]; - } + const falseDropdownOption: DropdownOption = { + label: "False", + value: "False", + }; - setData(newData); - onDataChange(newData); - }} - type={InputType.NUMBER} - /> - )} - - {item.type === ValueType.Boolean && ( - <Dropdown - value={ - item.value === true - ? trueDropdownOption - : falseDropdownOption - } - options={[ - trueDropdownOption, - falseDropdownOption, - ]} - isMultiSelect={false} - onChange={( - selectedOption: - | DropdownValue - | Array<DropdownValue> - | null - ) => { - const newData: Array<Item> = [ - ...data, - ]; - if (selectedOption === 'True') { - newData[index]!.value = true; - } - - if (selectedOption === 'False') { - newData[index]!.value = false; - } - - setData(newData); - onDataChange(newData); - }} - /> - )} - </div> - <div className="ml-1 mt-1"> - <Button - dataTestId={`delete-${item.key}`} - title="Delete" - buttonStyle={ButtonStyleType.ICON} - icon={IconProp.Trash} - onClick={() => { - const newData: Array<Item> = [...data]; - newData.splice(index, 1); - setData(newData); - onDataChange(newData); - }} - /> - </div> - </div> - ); - })} - <div className="-ml-3 mt-4"> - <Button - title={`Add ${props.addButtonSuffix || 'Item'}`} - icon={IconProp.Add} - buttonSize={ButtonSize.Small} - onClick={() => { - setData([ - ...data, - { - key: '', - value: '', - type: valueTypes[0] as ValueType, - }, - ]); - }} - /> + return ( + <div> + <div> + {data.map((item: Item, index: number) => { + return ( + <div key={index} className="flex"> + <div className="mr-1"> + <Input + value={item.key} + placeholder={props.keyPlaceholder} + onChange={(value: string) => { + const newData: Array<Item> = [...data]; + newData[index]!.key = value; + setData(newData); + onDataChange(newData); + }} + /> + </div> + <div className="mr-1 ml-1 mt-auto mb-auto"> + <Icon + className="h-3 w-3" + icon={IconProp.Equals} + size={SizeProp.Small} + /> + </div> + {valueTypes.length > 1 && ( + <div className="ml-1"> + <Dropdown + value={dropdownOptionsForValueTypes.find( + (dropdownOption: DropdownOption) => { + return dropdownOption.value === item.type; + }, + )} + options={dropdownOptionsForValueTypes} + isMultiSelect={false} + onChange={( + selectedOption: + | DropdownValue + | Array<DropdownValue> + | null, + ) => { + const newData: Array<Item> = [...data]; + newData[index]!.type = + (selectedOption as ValueType) || valueTypes[0]; + setData(newData); + onDataChange(newData); + }} + /> </div> + )} + <div className="ml-1"> + {item.type === ValueType.Text && ( + <Input + value={item.value.toString()} + placeholder={props.valuePlaceholder} + onChange={(value: string) => { + const newData: Array<Item> = [...data]; + newData[index]!.value = value; + setData(newData); + onDataChange(newData); + }} + /> + )} + + {item.type === ValueType.Number && ( + <Input + value={item.value.toString()} + placeholder={props.valuePlaceholder} + onChange={(value: string) => { + const newData: Array<Item> = [...data]; + + if (typeof value === "string" && value.length > 0) { + newData[index]!.value = parseInt(value); + } else { + delete newData[index]; + } + + setData(newData); + onDataChange(newData); + }} + type={InputType.NUMBER} + /> + )} + + {item.type === ValueType.Boolean && ( + <Dropdown + value={ + item.value === true + ? trueDropdownOption + : falseDropdownOption + } + options={[trueDropdownOption, falseDropdownOption]} + isMultiSelect={false} + onChange={( + selectedOption: + | DropdownValue + | Array<DropdownValue> + | null, + ) => { + const newData: Array<Item> = [...data]; + if (selectedOption === "True") { + newData[index]!.value = true; + } + + if (selectedOption === "False") { + newData[index]!.value = false; + } + + setData(newData); + onDataChange(newData); + }} + /> + )} + </div> + <div className="ml-1 mt-1"> + <Button + dataTestId={`delete-${item.key}`} + title="Delete" + buttonStyle={ButtonStyleType.ICON} + icon={IconProp.Trash} + onClick={() => { + const newData: Array<Item> = [...data]; + newData.splice(index, 1); + setData(newData); + onDataChange(newData); + }} + /> + </div> </div> + ); + })} + <div className="-ml-3 mt-4"> + <Button + title={`Add ${props.addButtonSuffix || "Item"}`} + icon={IconProp.Add} + buttonSize={ButtonSize.Small} + onClick={() => { + setData([ + ...data, + { + key: "", + value: "", + type: valueTypes[0] as ValueType, + }, + ]); + }} + /> </div> - ); + </div> + </div> + ); }; export default DictionaryForm; diff --git a/CommonUI/src/Components/Dictionary/DictionaryOfStingsViewer.tsx b/CommonUI/src/Components/Dictionary/DictionaryOfStingsViewer.tsx index 632aabd79a..ecbed475c0 100644 --- a/CommonUI/src/Components/Dictionary/DictionaryOfStingsViewer.tsx +++ b/CommonUI/src/Components/Dictionary/DictionaryOfStingsViewer.tsx @@ -1,63 +1,63 @@ -import Dictionary from 'Common/Types/Dictionary'; +import Dictionary from "Common/Types/Dictionary"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - value?: Dictionary<string>; + value?: Dictionary<string>; } interface Item { - key: string; - value: string; + key: string; + value: string; } const DictionaryOfStringsViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [data, setData] = useState<Array<Item>>([]); + const [data, setData] = useState<Array<Item>>([]); - useEffect(() => { - setData( - Object.keys( - props.value || { - '': '', - } - ).map((key: string) => { - return { - key: key!, - value: props.value![key] || '', - }; - }) || [ - { - key: '', - value: '', - }, - ] - ); - }, [props.value]); - - return ( - <div> - <div> - {data.map((item: Item, index: number) => { - return ( - <div key={index} className="flex"> - <div className="mr-1"> - <div>{item.key}</div> - </div> - <div className="ml-1"> - <div>{item.value}</div> - </div> - </div> - ); - })} - </div> - </div> + useEffect(() => { + setData( + Object.keys( + props.value || { + "": "", + }, + ).map((key: string) => { + return { + key: key!, + value: props.value![key] || "", + }; + }) || [ + { + key: "", + value: "", + }, + ], ); + }, [props.value]); + + return ( + <div> + <div> + {data.map((item: Item, index: number) => { + return ( + <div key={index} className="flex"> + <div className="mr-1"> + <div>{item.key}</div> + </div> + <div className="ml-1"> + <div>{item.value}</div> + </div> + </div> + ); + })} + </div> + </div> + ); }; export default DictionaryOfStringsViewer; diff --git a/CommonUI/src/Components/Dictionary/DictionaryOfStrings.tsx b/CommonUI/src/Components/Dictionary/DictionaryOfStrings.tsx index d74ed1f3c9..ac992e0da6 100644 --- a/CommonUI/src/Components/Dictionary/DictionaryOfStrings.tsx +++ b/CommonUI/src/Components/Dictionary/DictionaryOfStrings.tsx @@ -1,41 +1,40 @@ -import DictionaryForm, { ValueType } from './Dictionary'; -import Dictionary from 'Common/Types/Dictionary'; -import React, { FunctionComponent, ReactElement } from 'react'; +import DictionaryForm, { ValueType } from "./Dictionary"; +import Dictionary from "Common/Types/Dictionary"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onChange?: undefined | ((value: Dictionary<string>) => void); - initialValue?: Dictionary<string>; - keyPlaceholder?: string; - valuePlaceholder?: string; - addButtonSuffix?: string; + onChange?: undefined | ((value: Dictionary<string>) => void); + initialValue?: Dictionary<string>; + keyPlaceholder?: string; + valuePlaceholder?: string; + addButtonSuffix?: string; } const DictionaryOfStrings: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - // only allow text values - <DictionaryForm - {...props} - valueTypes={[ValueType.Text]} - onChange={(value: Dictionary<string | number | boolean>) => { - const stringDict: Dictionary<string> = - value as Dictionary<string>; + return ( + // only allow text values + <DictionaryForm + {...props} + valueTypes={[ValueType.Text]} + onChange={(value: Dictionary<string | number | boolean>) => { + const stringDict: Dictionary<string> = value as Dictionary<string>; - // convert all values to strings + // convert all values to strings - for (const key in stringDict) { - if (stringDict[key]) { - stringDict[key] = stringDict[key]?.toString() || ''; - } - } + for (const key in stringDict) { + if (stringDict[key]) { + stringDict[key] = stringDict[key]?.toString() || ""; + } + } - if (props.onChange) { - props.onChange(stringDict); - } - }} - /> - ); + if (props.onChange) { + props.onChange(stringDict); + } + }} + /> + ); }; export default DictionaryOfStrings; diff --git a/CommonUI/src/Components/Divider/Divider.tsx b/CommonUI/src/Components/Divider/Divider.tsx index a1c0991362..7f08805fa6 100644 --- a/CommonUI/src/Components/Divider/Divider.tsx +++ b/CommonUI/src/Components/Divider/Divider.tsx @@ -1,12 +1,12 @@ -import React, { ReactElement } from 'react'; +import React, { ReactElement } from "react"; const Divider: () => JSX.Element = (): ReactElement => { - return ( - <div - className="w-full border-t border-gray-100" - style={{ borderColor: '#f9fafb' }} - ></div> - ); + return ( + <div + className="w-full border-t border-gray-100" + style={{ borderColor: "#f9fafb" }} + ></div> + ); }; export default Divider; diff --git a/CommonUI/src/Components/Dropdown/Dropdown.tsx b/CommonUI/src/Components/Dropdown/Dropdown.tsx index f75798da49..42cdec92ef 100644 --- a/CommonUI/src/Components/Dropdown/Dropdown.tsx +++ b/CommonUI/src/Components/Dropdown/Dropdown.tsx @@ -1,226 +1,218 @@ -import ObjectID from 'Common/Types/ObjectID'; +import ObjectID from "Common/Types/ObjectID"; import React, { - FunctionComponent, - ReactElement, - useLayoutEffect, - useRef, - useState, -} from 'react'; -import Select, { ControlProps, GroupBase, OptionProps } from 'react-select'; + FunctionComponent, + ReactElement, + useLayoutEffect, + useRef, + useState, +} from "react"; +import Select, { ControlProps, GroupBase, OptionProps } from "react-select"; export type DropdownValue = string | number | boolean; export interface DropdownOption { - value: DropdownValue; - label: string; + value: DropdownValue; + label: string; } export interface ComponentProps { - options: Array<DropdownOption>; - initialValue?: undefined | DropdownOption | Array<DropdownOption>; - onClick?: undefined | (() => void); - placeholder?: undefined | string; - className?: undefined | string; - onChange?: - | undefined - | ((value: DropdownValue | Array<DropdownValue> | null) => void); - value?: DropdownOption | Array<DropdownOption> | undefined; - onFocus?: (() => void) | undefined; - onBlur?: (() => void) | undefined; - isMultiSelect?: boolean; - tabIndex?: number | undefined; - error?: string | undefined; - id?: string | undefined; - dataTestId?: string | undefined; + options: Array<DropdownOption>; + initialValue?: undefined | DropdownOption | Array<DropdownOption>; + onClick?: undefined | (() => void); + placeholder?: undefined | string; + className?: undefined | string; + onChange?: + | undefined + | ((value: DropdownValue | Array<DropdownValue> | null) => void); + value?: DropdownOption | Array<DropdownOption> | undefined; + onFocus?: (() => void) | undefined; + onBlur?: (() => void) | undefined; + isMultiSelect?: boolean; + tabIndex?: number | undefined; + error?: string | undefined; + id?: string | undefined; + dataTestId?: string | undefined; } const Dropdown: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - type GetDropdownOptionFromValueFunctionProps = - | undefined - | DropdownValue - | DropdownOption - | Array<DropdownOption> - | Array<DropdownValue>; + type GetDropdownOptionFromValueFunctionProps = + | undefined + | DropdownValue + | DropdownOption + | Array<DropdownOption> + | Array<DropdownValue>; - type GetDropdownOptionFromValueFunction = ( - value: GetDropdownOptionFromValueFunctionProps - ) => DropdownOption | Array<DropdownOption> | undefined; + type GetDropdownOptionFromValueFunction = ( + value: GetDropdownOptionFromValueFunctionProps, + ) => DropdownOption | Array<DropdownOption> | undefined; - const getDropdownOptionFromValue: GetDropdownOptionFromValueFunction = ( - value: GetDropdownOptionFromValueFunctionProps - ): DropdownOption | Array<DropdownOption> | undefined => { - if (value === undefined) { - return undefined; - } + const getDropdownOptionFromValue: GetDropdownOptionFromValueFunction = ( + value: GetDropdownOptionFromValueFunctionProps, + ): DropdownOption | Array<DropdownOption> | undefined => { + if (value === undefined) { + return undefined; + } - if (value instanceof ObjectID) { - value = value.toString(); - } + if (value instanceof ObjectID) { + value = value.toString(); + } + if ( + Array.isArray(value) && + value.length > 0 && + Object.keys(value[0]!).includes("value") + ) { + return value as Array<DropdownOption>; + } + + if ( + Array.isArray(value) && + value.length > 0 && + typeof value[0] === "string" + ) { + const options: Array<DropdownOption> = []; + + for (const item of value as Array<DropdownValue>) { if ( - Array.isArray(value) && - value.length > 0 && - Object.keys(value[0]!).includes('value') + (!Array.isArray(item) && typeof item === "string") || + typeof item === "number" ) { - return value as Array<DropdownOption>; - } - - if ( - Array.isArray(value) && - value.length > 0 && - typeof value[0] === 'string' - ) { - const options: Array<DropdownOption> = []; - - for (const item of value as Array<DropdownValue>) { - if ( - (!Array.isArray(item) && typeof item === 'string') || - typeof item === 'number' - ) { - const option: - | DropdownOption - | undefined - | Array<DropdownOption> = props.options.find( - (option: DropdownOption) => { - return option.value === item; - } - ) as DropdownOption | Array<DropdownOption>; - - if (option) { - options.push(option as DropdownOption); - } - } - } - - return options; - } - - if ( - (!Array.isArray(value) && typeof value === 'string') || - typeof value === 'number' - ) { - return props.options.find((option: DropdownOption) => { - return option.value === value; + const option: DropdownOption | undefined | Array<DropdownOption> = + props.options.find((option: DropdownOption) => { + return option.value === item; }) as DropdownOption | Array<DropdownOption>; + + if (option) { + options.push(option as DropdownOption); + } } + } - return value as DropdownOption | Array<DropdownOption>; - }; + return options; + } - const [value, setValue] = useState< - DropdownOption | Array<DropdownOption> | undefined - >(getDropdownOptionFromValue(props.initialValue)); + if ( + (!Array.isArray(value) && typeof value === "string") || + typeof value === "number" + ) { + return props.options.find((option: DropdownOption) => { + return option.value === value; + }) as DropdownOption | Array<DropdownOption>; + } - const firstUpdate: React.MutableRefObject<boolean> = useRef(true); + return value as DropdownOption | Array<DropdownOption>; + }; - useLayoutEffect(() => { - if (firstUpdate.current && props.initialValue) { - firstUpdate.current = false; - return; - } + const [value, setValue] = useState< + DropdownOption | Array<DropdownOption> | undefined + >(getDropdownOptionFromValue(props.initialValue)); - const value: DropdownOption | Array<DropdownOption> | undefined = - getDropdownOptionFromValue( - props.value === null ? undefined : props.value - ); + const firstUpdate: React.MutableRefObject<boolean> = useRef(true); - setValue(value); - }, [props.value]); + useLayoutEffect(() => { + if (firstUpdate.current && props.initialValue) { + firstUpdate.current = false; + return; + } - return ( - <div - id={props.id} - className={`${ - props.className || - 'relative mt-2 mb-1 rounded-md w-full overflow-visible' - }`} - onClick={() => { - props.onClick && props.onClick(); - props.onFocus && props.onFocus(); - }} - > - <Select - onBlur={() => { - props.onBlur && props.onBlur(); - }} - data-testid={props.dataTestId} - tabIndex={props.tabIndex} - isMulti={props.isMultiSelect} - value={value || null} - onFocus={() => { - props.onFocus && props.onFocus(); - }} - classNames={{ - control: ( - state: ControlProps<any, boolean, GroupBase<any>> - ): string => { - return state.isFocused - ? '!border-indigo-500' - : 'border-Gray500-300'; - }, - option: ( - state: OptionProps<any, boolean, GroupBase<any>> - ): string => { - if (state.isDisabled) { - return 'bg-gray-100'; - } - if (state.isSelected) { - return '!bg-indigo-500'; - } - if (state.isFocused) { - return '!bg-indigo-100'; - } - return ''; - }, - }} - isClearable={true} - isSearchable={true} - placeholder={props.placeholder} - options={props.options as any} - onChange={(option: any | null) => { - if (option) { - if (props.isMultiSelect) { - const value: Array<DropdownOption> = - option as Array<DropdownOption>; - setValue(value); + const value: DropdownOption | Array<DropdownOption> | undefined = + getDropdownOptionFromValue( + props.value === null ? undefined : props.value, + ); - props.onChange && - props.onChange( - value.map((i: DropdownOption) => { - return i.value; - }) - ); - } else { - const value: DropdownOption = - option as DropdownOption; - setValue(value); - props.onChange && props.onChange(value.value); - } - } + setValue(value); + }, [props.value]); - if (option === null && props.isMultiSelect) { - setValue([]); - props.onChange && props.onChange([]); - } + return ( + <div + id={props.id} + className={`${ + props.className || + "relative mt-2 mb-1 rounded-md w-full overflow-visible" + }`} + onClick={() => { + props.onClick && props.onClick(); + props.onFocus && props.onFocus(); + }} + > + <Select + onBlur={() => { + props.onBlur && props.onBlur(); + }} + data-testid={props.dataTestId} + tabIndex={props.tabIndex} + isMulti={props.isMultiSelect} + value={value || null} + onFocus={() => { + props.onFocus && props.onFocus(); + }} + classNames={{ + control: ( + state: ControlProps<any, boolean, GroupBase<any>>, + ): string => { + return state.isFocused + ? "!border-indigo-500" + : "border-Gray500-300"; + }, + option: ( + state: OptionProps<any, boolean, GroupBase<any>>, + ): string => { + if (state.isDisabled) { + return "bg-gray-100"; + } + if (state.isSelected) { + return "!bg-indigo-500"; + } + if (state.isFocused) { + return "!bg-indigo-100"; + } + return ""; + }, + }} + isClearable={true} + isSearchable={true} + placeholder={props.placeholder} + options={props.options as any} + onChange={(option: any | null) => { + if (option) { + if (props.isMultiSelect) { + const value: Array<DropdownOption> = + option as Array<DropdownOption>; + setValue(value); - if (option === null && !props.isMultiSelect) { - setValue(undefined); - props.onChange && props.onChange(null); - } - }} - /> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} - </div> - ); + props.onChange && + props.onChange( + value.map((i: DropdownOption) => { + return i.value; + }), + ); + } else { + const value: DropdownOption = option as DropdownOption; + setValue(value); + props.onChange && props.onChange(value.value); + } + } + + if (option === null && props.isMultiSelect) { + setValue([]); + props.onChange && props.onChange([]); + } + + if (option === null && !props.isMultiSelect) { + setValue(undefined); + props.onChange && props.onChange(null); + } + }} + /> + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default Dropdown; diff --git a/CommonUI/src/Components/DuplicateModel/DuplicateModel.tsx b/CommonUI/src/Components/DuplicateModel/DuplicateModel.tsx index 6b2a963e56..8e4d7dfe93 100644 --- a/CommonUI/src/Components/DuplicateModel/DuplicateModel.tsx +++ b/CommonUI/src/Components/DuplicateModel/DuplicateModel.tsx @@ -1,164 +1,159 @@ -import API from '../../Utils/API/API'; -import Select from '../../Utils/BaseDatabase/Select'; -import ModelAPI from '../../Utils/ModelAPI/ModelAPI'; -import Navigation from '../../Utils/Navigation'; -import { ButtonStyleType } from '../Button/Button'; -import Card from '../Card/Card'; -import BasicFormModal from '../FormModal/BasicFormModal'; -import { ModelField } from '../Forms/ModelForm'; -import ConfirmModal from '../Modal/ConfirmModal'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { ReactElement, useState } from 'react'; +import API from "../../Utils/API/API"; +import Select from "../../Utils/BaseDatabase/Select"; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import Navigation from "../../Utils/Navigation"; +import { ButtonStyleType } from "../Button/Button"; +import Card from "../Card/Card"; +import BasicFormModal from "../FormModal/BasicFormModal"; +import { ModelField } from "../Forms/ModelForm"; +import ConfirmModal from "../Modal/ConfirmModal"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import React, { ReactElement, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - modelType: { new (): TBaseModel }; - modelId: ObjectID; - onDuplicateSuccess?: (item: TBaseModel) => void | undefined; - fieldsToDuplicate: Select<TBaseModel>; - fieldsToChange: Array<ModelField<TBaseModel>>; - navigateToOnSuccess?: Route | undefined; + modelType: { new (): TBaseModel }; + modelId: ObjectID; + onDuplicateSuccess?: (item: TBaseModel) => void | undefined; + fieldsToDuplicate: Select<TBaseModel>; + fieldsToChange: Array<ModelField<TBaseModel>>; + navigateToOnSuccess?: Route | undefined; } const DuplicateModel: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const model: TBaseModel = new props.modelType(); - const [showModal, setShowModal] = useState<boolean>(false); - const [isLoading, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [showErrorModal, setShowErrorModal] = useState<boolean>(false); + const model: TBaseModel = new props.modelType(); + const [showModal, setShowModal] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [showErrorModal, setShowErrorModal] = useState<boolean>(false); - type DuplicateItemFunction = (partialModel: TBaseModel) => void; + type DuplicateItemFunction = (partialModel: TBaseModel) => void; - const duplicateItem: DuplicateItemFunction = async ( - partialModel: TBaseModel - ) => { - setIsLoading(true); - try { - const item: TBaseModel | null = await ModelAPI.getItem<TBaseModel>({ - modelType: props.modelType, - id: props.modelId, - select: props.fieldsToDuplicate, - }); + const duplicateItem: DuplicateItemFunction = async ( + partialModel: TBaseModel, + ) => { + setIsLoading(true); + try { + const item: TBaseModel | null = await ModelAPI.getItem<TBaseModel>({ + modelType: props.modelType, + id: props.modelId, + select: props.fieldsToDuplicate, + }); - if (!item) { - throw new Error( - `Could not find ${model.singularName} with id ${props.modelId}` - ); - } + if (!item) { + throw new Error( + `Could not find ${model.singularName} with id ${props.modelId}`, + ); + } - for (const field of props.fieldsToChange) { - const key: string | undefined = Object.keys( - field.field || {} - )[0]; + for (const field of props.fieldsToChange) { + const key: string | undefined = Object.keys(field.field || {})[0]; - if (!key) { - continue; - } - - const value: string = partialModel.getValue(key); - item.setValue(key, value); - } - - item.removeValue('_id'); - - // now we have the item, we need to remove the id and then save it - - const newItem: HTTPResponse<TBaseModel> = - (await ModelAPI.create<TBaseModel>({ - model: item, - modelType: props.modelType, - })) as HTTPResponse<TBaseModel>; - - if (!newItem) { - throw new Error(`Could not create ${model.singularName}`); - } - - props.onDuplicateSuccess && props.onDuplicateSuccess(newItem.data); - - if (props.navigateToOnSuccess) { - Navigation.navigate( - new Route(props.navigateToOnSuccess.toString()).addRoute( - `/${newItem.data.id!.toString()}` - ), - { - forceNavigate: true, - } - ); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - setShowErrorModal(true); + if (!key) { + continue; } - setIsLoading(false); - }; + const value: string = partialModel.getValue(key); + item.setValue(key, value); + } - return ( - <> - <Card - title={`Duplicate ${model.singularName}`} - description={`Duplicating this ${model.singularName?.toLowerCase()} will create another ${model.singularName?.toLowerCase()} exactly like this one.`} - buttons={[ - { - title: `Duplicate ${model.singularName}`, - buttonStyle: ButtonStyleType.NORMAL, - onClick: () => { - setShowModal(true); - }, - isLoading: isLoading, - icon: IconProp.Copy, - }, - ]} - /> + item.removeValue("_id"); - {showModal ? ( - <BasicFormModal<TBaseModel> - description={`Are you sure you want to duplicate this ${model.singularName?.toLowerCase()}?`} - title={`Duplicate ${model.singularName}`} - onSubmit={(item: TBaseModel) => { - setShowModal(false); - duplicateItem( - BaseModel.fromJSONObject( - item, - props.modelType - ) as TBaseModel - ); - }} - onClose={() => { - setShowModal(false); - }} - submitButtonText={`Duplicate ${model.singularName}`} - formProps={{ - fields: props.fieldsToChange, - }} - /> - ) : ( - <></> - )} + // now we have the item, we need to remove the id and then save it - {showErrorModal ? ( - <ConfirmModal - description={error} - title={`Duplicate Error`} - onSubmit={() => { - setShowErrorModal(false); - setError(''); - }} - submitButtonText={`Close`} - submitButtonType={ButtonStyleType.NORMAL} - /> - ) : ( - <></> - )} - </> - ); + const newItem: HTTPResponse<TBaseModel> = + (await ModelAPI.create<TBaseModel>({ + model: item, + modelType: props.modelType, + })) as HTTPResponse<TBaseModel>; + + if (!newItem) { + throw new Error(`Could not create ${model.singularName}`); + } + + props.onDuplicateSuccess && props.onDuplicateSuccess(newItem.data); + + if (props.navigateToOnSuccess) { + Navigation.navigate( + new Route(props.navigateToOnSuccess.toString()).addRoute( + `/${newItem.data.id!.toString()}`, + ), + { + forceNavigate: true, + }, + ); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + setShowErrorModal(true); + } + + setIsLoading(false); + }; + + return ( + <> + <Card + title={`Duplicate ${model.singularName}`} + description={`Duplicating this ${model.singularName?.toLowerCase()} will create another ${model.singularName?.toLowerCase()} exactly like this one.`} + buttons={[ + { + title: `Duplicate ${model.singularName}`, + buttonStyle: ButtonStyleType.NORMAL, + onClick: () => { + setShowModal(true); + }, + isLoading: isLoading, + icon: IconProp.Copy, + }, + ]} + /> + + {showModal ? ( + <BasicFormModal<TBaseModel> + description={`Are you sure you want to duplicate this ${model.singularName?.toLowerCase()}?`} + title={`Duplicate ${model.singularName}`} + onSubmit={(item: TBaseModel) => { + setShowModal(false); + duplicateItem( + BaseModel.fromJSONObject(item, props.modelType) as TBaseModel, + ); + }} + onClose={() => { + setShowModal(false); + }} + submitButtonText={`Duplicate ${model.singularName}`} + formProps={{ + fields: props.fieldsToChange, + }} + /> + ) : ( + <></> + )} + + {showErrorModal ? ( + <ConfirmModal + description={error} + title={`Duplicate Error`} + onSubmit={() => { + setShowErrorModal(false); + setError(""); + }} + submitButtonText={`Close`} + submitButtonType={ButtonStyleType.NORMAL} + /> + ) : ( + <></> + )} + </> + ); }; export default DuplicateModel; diff --git a/CommonUI/src/Components/EmptyState/EmptyState.tsx b/CommonUI/src/Components/EmptyState/EmptyState.tsx index 709b52ad59..a03e677b38 100644 --- a/CommonUI/src/Components/EmptyState/EmptyState.tsx +++ b/CommonUI/src/Components/EmptyState/EmptyState.tsx @@ -1,50 +1,47 @@ -import Icon from '../Icon/Icon'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string | ReactElement; - description: string | ReactElement; - icon: IconProp | undefined; - footer?: ReactElement | undefined; - id: string; - iconClassName?: string; - showSolidBackground?: boolean | undefined; + title: string | ReactElement; + description: string | ReactElement; + icon: IconProp | undefined; + footer?: ReactElement | undefined; + id: string; + iconClassName?: string; + showSolidBackground?: boolean | undefined; } const EmptyState: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <React.Fragment> - <div - id={props.id} - className={`flex pt-52 pb-52 ${ - props.showSolidBackground ? 'bg-white rounded shadow' : '' - }`} - > - <div className="m-auto text-center"> - {props.icon && ( - <Icon - icon={props.icon} - className={ - props.iconClassName || - `mx-auto h-12 w-12 text-gray-400` - } - /> - )} + return ( + <React.Fragment> + <div + id={props.id} + className={`flex pt-52 pb-52 ${ + props.showSolidBackground ? "bg-white rounded shadow" : "" + }`} + > + <div className="m-auto text-center"> + {props.icon && ( + <Icon + icon={props.icon} + className={ + props.iconClassName || `mx-auto h-12 w-12 text-gray-400` + } + /> + )} - <h3 className="mt-2 text-sm font-medium text-gray-900"> - {props.title} - </h3> - <p className="mt-1 text-sm text-gray-500"> - {props.description} - </p> - {props.footer && <div className="mt-6">{props.footer}</div>} - </div> - </div> - </React.Fragment> - ); + <h3 className="mt-2 text-sm font-medium text-gray-900"> + {props.title} + </h3> + <p className="mt-1 text-sm text-gray-500">{props.description}</p> + {props.footer && <div className="mt-6">{props.footer}</div>} + </div> + </div> + </React.Fragment> + ); }; export default EmptyState; diff --git a/CommonUI/src/Components/Error/PageError.tsx b/CommonUI/src/Components/Error/PageError.tsx index 9d2dc684f6..434a2a8d17 100644 --- a/CommonUI/src/Components/Error/PageError.tsx +++ b/CommonUI/src/Components/Error/PageError.tsx @@ -1,38 +1,38 @@ -import Navigation from '../../Utils/Navigation'; -import { ButtonStyleType } from '../Button/Button'; -import { IconType } from '../Icon/Icon'; -import Modal from '../Modal/Modal'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Navigation from "../../Utils/Navigation"; +import { ButtonStyleType } from "../Button/Button"; +import { IconType } from "../Icon/Icon"; +import Modal from "../Modal/Modal"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title?: string; - message: string; + title?: string; + message: string; } const PageError: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let message: string = props.message; + let message: string = props.message; - if (props.message === 'Server Error') { - message = 'Network Error: Please reload the page and try again.'; - } + if (props.message === "Server Error") { + message = "Network Error: Please reload the page and try again."; + } - return ( - <Modal - title={props.title || 'Oops, something went wrong.'} - icon={IconProp.Alert} - iconType={IconType.Danger} - onSubmit={() => { - Navigation.reload(); - }} - submitButtonStyleType={ButtonStyleType.NORMAL} - submitButtonText="Reload Page" - > - <p className="text-sm text-gray-500">{message}</p> - </Modal> - ); + return ( + <Modal + title={props.title || "Oops, something went wrong."} + icon={IconProp.Alert} + iconType={IconType.Danger} + onSubmit={() => { + Navigation.reload(); + }} + submitButtonStyleType={ButtonStyleType.NORMAL} + submitButtonText="Reload Page" + > + <p className="text-sm text-gray-500">{message}</p> + </Modal> + ); }; export default PageError; diff --git a/CommonUI/src/Components/ErrorBoundary.tsx b/CommonUI/src/Components/ErrorBoundary.tsx index 0b5e2762e8..2bac11b58f 100644 --- a/CommonUI/src/Components/ErrorBoundary.tsx +++ b/CommonUI/src/Components/ErrorBoundary.tsx @@ -1,43 +1,42 @@ -import React, { FunctionComponent, ReactElement } from 'react'; -import { ErrorBoundary as NativeErrorBoundary } from 'react-error-boundary'; +import React, { FunctionComponent, ReactElement } from "react"; +import { ErrorBoundary as NativeErrorBoundary } from "react-error-boundary"; export interface ComponentProps { - children?: ReactElement; + children?: ReactElement; } const Fallback: FunctionComponent = () => { - return ( - <div - id="app-loading" - style={{ - position: 'fixed', - top: '0', - bottom: '0', - left: '0', - right: '0', - backgroundColor: '#fdfdfd', - zIndex: '999', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }} - > - <div> - An unexpected error has occurred. Please reload the page to - continue - </div> - </div> - ); + return ( + <div + id="app-loading" + style={{ + position: "fixed", + top: "0", + bottom: "0", + left: "0", + right: "0", + backgroundColor: "#fdfdfd", + zIndex: "999", + display: "flex", + justifyContent: "center", + alignItems: "center", + }} + > + <div> + An unexpected error has occurred. Please reload the page to continue + </div> + </div> + ); }; const ErrorBoundary: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ) => { - return ( - <NativeErrorBoundary FallbackComponent={Fallback}> - {props.children} - </NativeErrorBoundary> - ); + return ( + <NativeErrorBoundary FallbackComponent={Fallback}> + {props.children} + </NativeErrorBoundary> + ); }; export default ErrorBoundary; diff --git a/CommonUI/src/Components/ErrorMessage/ErrorMessage.tsx b/CommonUI/src/Components/ErrorMessage/ErrorMessage.tsx index 6ddeda4f42..b6a34a217e 100644 --- a/CommonUI/src/Components/ErrorMessage/ErrorMessage.tsx +++ b/CommonUI/src/Components/ErrorMessage/ErrorMessage.tsx @@ -1,33 +1,33 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - error: string | ReactElement; - onRefreshClick?: undefined | (() => void); + error: string | ReactElement; + onRefreshClick?: undefined | (() => void); } const ErrorMessage: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="text-center my-10 text-gray-500 text-sm"> - {props.error} - {props.onRefreshClick ? ( - <div - role={'refresh-button'} - onClick={() => { - if (props.onRefreshClick) { - props.onRefreshClick(); - } - }} - className="underline cursor-pointer hover:text-gray-700 mt-3" - > - Refresh? - </div> - ) : ( - <></> - )} + return ( + <div className="text-center my-10 text-gray-500 text-sm"> + {props.error} + {props.onRefreshClick ? ( + <div + role={"refresh-button"} + onClick={() => { + if (props.onRefreshClick) { + props.onRefreshClick(); + } + }} + className="underline cursor-pointer hover:text-gray-700 mt-3" + > + Refresh? </div> - ); + ) : ( + <></> + )} + </div> + ); }; export default ErrorMessage; diff --git a/CommonUI/src/Components/EventHistoryList/EventHistoryDayList.tsx b/CommonUI/src/Components/EventHistoryList/EventHistoryDayList.tsx index 6edb599d5b..da8194dfb1 100644 --- a/CommonUI/src/Components/EventHistoryList/EventHistoryDayList.tsx +++ b/CommonUI/src/Components/EventHistoryList/EventHistoryDayList.tsx @@ -1,46 +1,46 @@ import EventHistoryItem, { - ComponentProps as ItemComponentProps, -} from '../EventItem/EventItem'; -import OneUptimeDate from 'Common/Types/Date'; -import React, { FunctionComponent, ReactElement } from 'react'; + ComponentProps as ItemComponentProps, +} from "../EventItem/EventItem"; +import OneUptimeDate from "Common/Types/Date"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - date: Date; - items: Array<ItemComponentProps>; - isLastItem?: boolean | undefined; + date: Date; + items: Array<ItemComponentProps>; + isLastItem?: boolean | undefined; } const EventHistoryDayList: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - className="flex bottom-Gray500-border" - style={{ - marginLeft: '-10px', - marginRight: '-10px', - marginBottom: props.isLastItem ? '0px' : '20px', - borderBottomWidth: props.isLastItem ? '0px' : '1px', - }} - > - <div - className="text-gray-400 mt-2 text-sm" - style={{ - padding: '20px', - paddingLeft: '10px', - paddingRight: '0px', - width: '15%', - }} - > - {OneUptimeDate.getDateAsLocalFormattedString(props.date, true)} - </div> - <div style={{ padding: '10px', paddingTop: '0px', width: '85%' }}> - {props.items.map((item: ItemComponentProps, i: number) => { - return <EventHistoryItem key={i} {...item} />; - })} - </div> - </div> - ); + return ( + <div + className="flex bottom-Gray500-border" + style={{ + marginLeft: "-10px", + marginRight: "-10px", + marginBottom: props.isLastItem ? "0px" : "20px", + borderBottomWidth: props.isLastItem ? "0px" : "1px", + }} + > + <div + className="text-gray-400 mt-2 text-sm" + style={{ + padding: "20px", + paddingLeft: "10px", + paddingRight: "0px", + width: "15%", + }} + > + {OneUptimeDate.getDateAsLocalFormattedString(props.date, true)} + </div> + <div style={{ padding: "10px", paddingTop: "0px", width: "85%" }}> + {props.items.map((item: ItemComponentProps, i: number) => { + return <EventHistoryItem key={i} {...item} />; + })} + </div> + </div> + ); }; export default EventHistoryDayList; diff --git a/CommonUI/src/Components/EventHistoryList/EventHistoryList.tsx b/CommonUI/src/Components/EventHistoryList/EventHistoryList.tsx index a598d3d3e7..67cdbe8c80 100644 --- a/CommonUI/src/Components/EventHistoryList/EventHistoryList.tsx +++ b/CommonUI/src/Components/EventHistoryList/EventHistoryList.tsx @@ -1,30 +1,28 @@ import EventHistoryDayList, { - ComponentProps as EventHistoryDayListComponentProps, -} from './EventHistoryDayList'; -import React, { FunctionComponent, ReactElement } from 'react'; + ComponentProps as EventHistoryDayListComponentProps, +} from "./EventHistoryDayList"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - items: Array<EventHistoryDayListComponentProps>; + items: Array<EventHistoryDayListComponentProps>; } const ActiveEvent: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="event-history-box"> - {props.items.map( - (item: EventHistoryDayListComponentProps, i: number) => { - return ( - <EventHistoryDayList - key={i} - isLastItem={props.items.length - 1 === i} - {...item} - /> - ); - } - )} - </div> - ); + return ( + <div className="event-history-box"> + {props.items.map((item: EventHistoryDayListComponentProps, i: number) => { + return ( + <EventHistoryDayList + key={i} + isLastItem={props.items.length - 1 === i} + {...item} + /> + ); + })} + </div> + ); }; export default ActiveEvent; diff --git a/CommonUI/src/Components/EventHistoryList/NoEventDay.tsx b/CommonUI/src/Components/EventHistoryList/NoEventDay.tsx index 2409074821..37b356dee9 100644 --- a/CommonUI/src/Components/EventHistoryList/NoEventDay.tsx +++ b/CommonUI/src/Components/EventHistoryList/NoEventDay.tsx @@ -1,29 +1,27 @@ -import React, { ReactElement } from 'react'; +import React, { ReactElement } from "react"; const NoEventDay: () => JSX.Element = (): ReactElement => { - return ( - <div - className="flex bottom-Gray500-border" - style={{ - marginLeft: '-10px', - marginRight: '-10px', - marginBottom: '20px', - }} - > - <div style={{ padding: '20px', paddingRight: '0px' }}> - Oct 20, 2022 - </div> - <div - style={{ - padding: '20px', - paddingTop: '20px', - marginLeft: '10px', - }} - > - No incidents on this day. - </div> - </div> - ); + return ( + <div + className="flex bottom-Gray500-border" + style={{ + marginLeft: "-10px", + marginRight: "-10px", + marginBottom: "20px", + }} + > + <div style={{ padding: "20px", paddingRight: "0px" }}>Oct 20, 2022</div> + <div + style={{ + padding: "20px", + paddingTop: "20px", + marginLeft: "10px", + }} + > + No incidents on this day. + </div> + </div> + ); }; export default NoEventDay; diff --git a/CommonUI/src/Components/EventItem/EventItem.tsx b/CommonUI/src/Components/EventItem/EventItem.tsx index 11d526eef7..61d5db6639 100644 --- a/CommonUI/src/Components/EventItem/EventItem.tsx +++ b/CommonUI/src/Components/EventItem/EventItem.tsx @@ -1,361 +1,320 @@ -import Icon from '../Icon/Icon'; -import Link from '../Link/Link'; -import MarkdownViewer from '../Markdown.tsx/LazyMarkdownViewer'; -import Pill from '../Pill/Pill'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { VeryLightGray } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../Icon/Icon"; +import Link from "../Link/Link"; +import MarkdownViewer from "../Markdown.tsx/LazyMarkdownViewer"; +import Pill from "../Pill/Pill"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { VeryLightGray } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export enum TimelineItemType { - StateChange = 'StateChange', - Note = 'Note', + StateChange = "StateChange", + Note = "Note", } export interface TimelineItem { - date: Date; - note?: string; - type: TimelineItemType; - state?: BaseModel; - icon: IconProp; - iconColor: Color; + date: Date; + note?: string; + type: TimelineItemType; + state?: BaseModel; + icon: IconProp; + iconColor: Color; } export interface EventItemLabel { - name: string; - color: Color; + name: string; + color: Color; } export interface ComponentProps { - eventTitle: string; - eventResourcesAffected?: Array<string> | undefined; - eventDescription?: string | undefined; - eventTimeline: Array<TimelineItem>; - eventMiniDescription?: string | undefined; - eventTypeColor: Color; - eventType: string; - eventViewRoute?: Route | URL | undefined; - isDetailItem: boolean; - currentStatus?: string; - currentStatusColor?: Color; - anotherStatus?: string | undefined; - anotherStatusColor?: Color | undefined; - eventSecondDescription: string; - labels?: Array<EventItemLabel> | undefined; + eventTitle: string; + eventResourcesAffected?: Array<string> | undefined; + eventDescription?: string | undefined; + eventTimeline: Array<TimelineItem>; + eventMiniDescription?: string | undefined; + eventTypeColor: Color; + eventType: string; + eventViewRoute?: Route | URL | undefined; + isDetailItem: boolean; + currentStatus?: string; + currentStatusColor?: Color; + anotherStatus?: string | undefined; + anotherStatusColor?: Color | undefined; + eventSecondDescription: string; + labels?: Array<EventItemLabel> | undefined; } const EventItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mt-5 mb-5 bg-white shadow rounded-xl border-gray-100 p-5"> + return ( + <div className="mt-5 mb-5 bg-white shadow rounded-xl border-gray-100 p-5"> + <div> + <div className="flex space-x-1"> + <div> + <Pill + key={1} + text={props.eventType} + color={props.eventTypeColor} + isMinimal={true} + /> + </div> + {props.currentStatus && props.currentStatusColor && ( <div> - <div className="flex space-x-1"> - <div> - <Pill - key={1} - text={props.eventType} - color={props.eventTypeColor} - isMinimal={true} - /> - </div> - {props.currentStatus && props.currentStatusColor && ( - <div> - <Pill - key={2} - text={props.currentStatus} - color={props.currentStatusColor} - isMinimal={true} - /> - </div> - )} - {props.anotherStatus && props.anotherStatusColor && ( - <div> - <Pill - key={3} - text={props.anotherStatus} - color={props.anotherStatusColor} - isMinimal={true} - /> - </div> - )} - </div> - <div className="mt-5"> - <h2 - className={`active-event-box-body-title event-${props.eventType.toLowerCase()}-box-body-title`} - style={{ - fontSize: props.isDetailItem ? '20px' : '16px', - }} - > - {props.eventTitle} - </h2> - </div> - {props.eventDescription && ( - <div - className={`mt-2 text-sm active-event-box-body-description event-${props.eventType.toLowerCase()}-box-body-description`} - > - <MarkdownViewer text={props.eventDescription || ''} /> - </div> - )} - - {props.eventSecondDescription && ( - <div className="mt-3 text-gray-500 text-sm active-event-box-body-second-description"> - {props.eventSecondDescription} - </div> - )} - - {props.eventMiniDescription && ( - <div className="mt-3 text-gray-400 text-sm active-event-box-body-mini-description"> - {props.eventMiniDescription} - </div> - )} - - {props.labels && props.labels.length > 0 ? ( - <div className="flex space-x-1 mt-3 active-event-box-body-labels"> - {props.labels.map( - (label: EventItemLabel, i: number) => { - return ( - <div key={i}> - <Pill - text={label.name} - color={label.color} - /> - </div> - ); - } - )} - </div> - ) : ( - <></> - )} + <Pill + key={2} + text={props.currentStatus} + color={props.currentStatusColor} + isMinimal={true} + /> </div> + )} + {props.anotherStatus && props.anotherStatusColor && ( <div> - {props.eventResourcesAffected && - props.eventResourcesAffected?.length > 0 && ( - <div - className="w-full border-t border-gray-200 mt-5 mb-5 -ml-5 -mr-5 -pr-5" - style={{ width: 'calc(100% + 2.5em)' }} - ></div> - )} - - {props.eventResourcesAffected && - props.eventResourcesAffected?.length > 0 ? ( - <div key={0}> - <div className="flex flex-wrap gap-y-4 space-x-1 active-event-box-body-reesources"> - <div className="text-sm text-gray-400 mr-3 mt-1"> - Affected resources - </div> - {props.eventResourcesAffected?.map( - (item: string, i: number) => { - return ( - <Pill - key={i} - text={item} - color={VeryLightGray} - style={{ - backgroundColor: '#f3f4f6', - color: '#9ca3af', - }} - /> - ); - } - )} - </div> - </div> - ) : ( - <></> - )} - - {props.eventTimeline && props.eventTimeline.length > 0 && ( - <div - className={`w-full border-t border-gray-200 mt-5 -ml-5 ${ - props.eventTimeline && - props.eventTimeline.length > 0 - ? 'mb-5' - : 'mb-0' - } -mr-5 -pr-5`} - style={{ width: 'calc(100% + 2.5em)' }} - ></div> - )} - - <div className="flow-root"> - <ul role="list" className="-mb-8"> - {props.eventTimeline && - props.eventTimeline.map( - (item: TimelineItem, i: number) => { - if ( - item.type === - TimelineItemType.StateChange - ) { - return ( - <li key={i}> - <div className="relative pb-8"> - {i !== - props.eventTimeline - .length - - 1 && ( - <span - className="absolute top-5 left-5 -ml-px h-full w-0.5 bg-gray-200" - aria-hidden="true" - ></span> - )} - <div className="relative flex items-start space-x-3"> - <div> - <div className="relative px-1"> - <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 ring-8 ring-white"> - <Icon - icon={ - item.icon - } - className="h-5 w-5 text-gray-500" - style={{ - color: item.iconColor.toString(), - }} - /> - </div> - </div> - </div> - <div className="min-w-0 flex-1 py-0"> - <div className="text-sm leading-8 text-gray-500"> - <span className="mr-2"> - <span className="font-medium text-gray-900 mr-1"> - { - props.eventType - } - </span> - state - changed to - </span> - <span className="mr-1"> - <Pill - text={ - item.state?.getColumnValue( - 'name' - ) as string - } - color={ - item.state?.getColumnValue( - 'color' - ) as Color - } - isMinimal={ - true - } - /> - </span> - </div> - <div> - <span className="text-sm leading-8 text-gray-500 whitespace-nowrap"> - {OneUptimeDate.getDateAsLocalFormattedString( - item.date - )} - </span> - </div> - </div> - </div> - </div> - </li> - ); - } - - if (item.type === TimelineItemType.Note) { - return ( - <li key={i}> - <div className="relative pb-8"> - {i !== - props.eventTimeline - .length - - 1 && ( - <span - className="absolute top-5 left-5 -ml-px h-full w-0.5 bg-gray-200" - aria-hidden="true" - ></span> - )} - <div className="relative flex items-start space-x-3"> - <div> - <div className="relative px-1"> - <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 ring-8 ring-white"> - <Icon - icon={ - item.icon - } - className="h-5 w-5 text-gray-500" - style={{ - color: item.iconColor.toString(), - }} - /> - </div> - </div> - </div> - <div className="min-w-0 flex-1"> - <div> - <div className="text-sm"> - <span className="font-medium text-gray-900"> - Update - to this{' '} - { - props.eventType - } - </span> - </div> - <p className="mt-0.5 text-sm text-gray-500"> - posted on{' '} - {OneUptimeDate.getDateAsLocalFormattedString( - item.date - )} - </p> - </div> - <div className="mt-2 text-sm text-gray-700"> - <p> - <MarkdownViewer - text={ - item.note || - '' - } - /> - </p> - </div> - </div> - </div> - </div> - </li> - ); - } - - return <></>; - } - )} - </ul> - </div> - - {props.eventViewRoute && ( - <div - className="w-full border-t border-gray-200 mt-5 mb-5 -ml-5 -mr-5 -pr-5" - style={{ width: 'calc(100% + 2.5em)' }} - ></div> - )} - - <div className="active-event-box-body-timestamp mt-5 flex justify-end"> - {props.eventViewRoute ? ( - <span> - <Link - className="cursor-pointer text-gray-400 hover:text-gray-500 text-sm" - to={props.eventViewRoute} - > - <>View {props.eventType}</> - </Link> - </span> - ) : ( - <></> - )} - </div> + <Pill + key={3} + text={props.anotherStatus} + color={props.anotherStatusColor} + isMinimal={true} + /> </div> + )} </div> - ); + <div className="mt-5"> + <h2 + className={`active-event-box-body-title event-${props.eventType.toLowerCase()}-box-body-title`} + style={{ + fontSize: props.isDetailItem ? "20px" : "16px", + }} + > + {props.eventTitle} + </h2> + </div> + {props.eventDescription && ( + <div + className={`mt-2 text-sm active-event-box-body-description event-${props.eventType.toLowerCase()}-box-body-description`} + > + <MarkdownViewer text={props.eventDescription || ""} /> + </div> + )} + + {props.eventSecondDescription && ( + <div className="mt-3 text-gray-500 text-sm active-event-box-body-second-description"> + {props.eventSecondDescription} + </div> + )} + + {props.eventMiniDescription && ( + <div className="mt-3 text-gray-400 text-sm active-event-box-body-mini-description"> + {props.eventMiniDescription} + </div> + )} + + {props.labels && props.labels.length > 0 ? ( + <div className="flex space-x-1 mt-3 active-event-box-body-labels"> + {props.labels.map((label: EventItemLabel, i: number) => { + return ( + <div key={i}> + <Pill text={label.name} color={label.color} /> + </div> + ); + })} + </div> + ) : ( + <></> + )} + </div> + <div> + {props.eventResourcesAffected && + props.eventResourcesAffected?.length > 0 && ( + <div + className="w-full border-t border-gray-200 mt-5 mb-5 -ml-5 -mr-5 -pr-5" + style={{ width: "calc(100% + 2.5em)" }} + ></div> + )} + + {props.eventResourcesAffected && + props.eventResourcesAffected?.length > 0 ? ( + <div key={0}> + <div className="flex flex-wrap gap-y-4 space-x-1 active-event-box-body-reesources"> + <div className="text-sm text-gray-400 mr-3 mt-1"> + Affected resources + </div> + {props.eventResourcesAffected?.map((item: string, i: number) => { + return ( + <Pill + key={i} + text={item} + color={VeryLightGray} + style={{ + backgroundColor: "#f3f4f6", + color: "#9ca3af", + }} + /> + ); + })} + </div> + </div> + ) : ( + <></> + )} + + {props.eventTimeline && props.eventTimeline.length > 0 && ( + <div + className={`w-full border-t border-gray-200 mt-5 -ml-5 ${ + props.eventTimeline && props.eventTimeline.length > 0 + ? "mb-5" + : "mb-0" + } -mr-5 -pr-5`} + style={{ width: "calc(100% + 2.5em)" }} + ></div> + )} + + <div className="flow-root"> + <ul role="list" className="-mb-8"> + {props.eventTimeline && + props.eventTimeline.map((item: TimelineItem, i: number) => { + if (item.type === TimelineItemType.StateChange) { + return ( + <li key={i}> + <div className="relative pb-8"> + {i !== props.eventTimeline.length - 1 && ( + <span + className="absolute top-5 left-5 -ml-px h-full w-0.5 bg-gray-200" + aria-hidden="true" + ></span> + )} + <div className="relative flex items-start space-x-3"> + <div> + <div className="relative px-1"> + <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 ring-8 ring-white"> + <Icon + icon={item.icon} + className="h-5 w-5 text-gray-500" + style={{ + color: item.iconColor.toString(), + }} + /> + </div> + </div> + </div> + <div className="min-w-0 flex-1 py-0"> + <div className="text-sm leading-8 text-gray-500"> + <span className="mr-2"> + <span className="font-medium text-gray-900 mr-1"> + {props.eventType} + </span> + state changed to + </span> + <span className="mr-1"> + <Pill + text={ + item.state?.getColumnValue("name") as string + } + color={ + item.state?.getColumnValue("color") as Color + } + isMinimal={true} + /> + </span> + </div> + <div> + <span className="text-sm leading-8 text-gray-500 whitespace-nowrap"> + {OneUptimeDate.getDateAsLocalFormattedString( + item.date, + )} + </span> + </div> + </div> + </div> + </div> + </li> + ); + } + + if (item.type === TimelineItemType.Note) { + return ( + <li key={i}> + <div className="relative pb-8"> + {i !== props.eventTimeline.length - 1 && ( + <span + className="absolute top-5 left-5 -ml-px h-full w-0.5 bg-gray-200" + aria-hidden="true" + ></span> + )} + <div className="relative flex items-start space-x-3"> + <div> + <div className="relative px-1"> + <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 ring-8 ring-white"> + <Icon + icon={item.icon} + className="h-5 w-5 text-gray-500" + style={{ + color: item.iconColor.toString(), + }} + /> + </div> + </div> + </div> + <div className="min-w-0 flex-1"> + <div> + <div className="text-sm"> + <span className="font-medium text-gray-900"> + Update to this {props.eventType} + </span> + </div> + <p className="mt-0.5 text-sm text-gray-500"> + posted on{" "} + {OneUptimeDate.getDateAsLocalFormattedString( + item.date, + )} + </p> + </div> + <div className="mt-2 text-sm text-gray-700"> + <p> + <MarkdownViewer text={item.note || ""} /> + </p> + </div> + </div> + </div> + </div> + </li> + ); + } + + return <></>; + })} + </ul> + </div> + + {props.eventViewRoute && ( + <div + className="w-full border-t border-gray-200 mt-5 mb-5 -ml-5 -mr-5 -pr-5" + style={{ width: "calc(100% + 2.5em)" }} + ></div> + )} + + <div className="active-event-box-body-timestamp mt-5 flex justify-end"> + {props.eventViewRoute ? ( + <span> + <Link + className="cursor-pointer text-gray-400 hover:text-gray-500 text-sm" + to={props.eventViewRoute} + > + <>View {props.eventType}</> + </Link> + </span> + ) : ( + <></> + )} + </div> + </div> + </div> + ); }; export default EventItem; diff --git a/CommonUI/src/Components/Events/RecurringFieldElement.tsx b/CommonUI/src/Components/Events/RecurringFieldElement.tsx index 5563f455fd..eadfe7c724 100644 --- a/CommonUI/src/Components/Events/RecurringFieldElement.tsx +++ b/CommonUI/src/Components/Events/RecurringFieldElement.tsx @@ -1,97 +1,88 @@ -import DropdownUtil from '../../Utils/Dropdown'; -import Dropdown, { DropdownValue } from '../Dropdown/Dropdown'; -import Input, { InputType } from '../Input/Input'; -import EventInterval from 'Common/Types/Events/EventInterval'; -import Recurring from 'Common/Types/Events/Recurring'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Typeof from 'Common/Types/Typeof'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import DropdownUtil from "../../Utils/Dropdown"; +import Dropdown, { DropdownValue } from "../Dropdown/Dropdown"; +import Input, { InputType } from "../Input/Input"; +import EventInterval from "Common/Types/Events/EventInterval"; +import Recurring from "Common/Types/Events/Recurring"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Typeof from "Common/Types/Typeof"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - error?: string | undefined; - onChange?: ((value: Recurring) => void) | undefined; - initialValue?: Recurring | undefined; + error?: string | undefined; + onChange?: ((value: Recurring) => void) | undefined; + initialValue?: Recurring | undefined; } const RecurringFieldElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [recurring, setRecurring] = useState<Recurring | undefined>( - props.initialValue ? Recurring.fromJSON(props.initialValue) : undefined - ); + const [recurring, setRecurring] = useState<Recurring | undefined>( + props.initialValue ? Recurring.fromJSON(props.initialValue) : undefined, + ); - type UpdateRecurringFunction = (restrictionTimes: Recurring) => void; + type UpdateRecurringFunction = (restrictionTimes: Recurring) => void; - const updateRecurring: UpdateRecurringFunction = ( - restrictionTimes: Recurring - ): void => { - setRecurring(Recurring.fromJSON(restrictionTimes.toJSON())); - if (props.onChange) { - props.onChange(restrictionTimes); - } - }; + const updateRecurring: UpdateRecurringFunction = ( + restrictionTimes: Recurring, + ): void => { + setRecurring(Recurring.fromJSON(restrictionTimes.toJSON())); + if (props.onChange) { + props.onChange(restrictionTimes); + } + }; - return ( - <div> - <div className="flex space-x-3"> - <Input - value={recurring?.intervalCount.toString()} - type={InputType.NUMBER} - placeholder="1" - onChange={(value: any) => { - let valueNumber: number = value as number; + return ( + <div> + <div className="flex space-x-3"> + <Input + value={recurring?.intervalCount.toString()} + type={InputType.NUMBER} + placeholder="1" + onChange={(value: any) => { + let valueNumber: number = value as number; - if (typeof valueNumber === Typeof.String) { - valueNumber = parseInt(valueNumber.toString()); - } + if (typeof valueNumber === Typeof.String) { + valueNumber = parseInt(valueNumber.toString()); + } - let tempRecurring: Recurring | undefined = recurring; + let tempRecurring: Recurring | undefined = recurring; - if (!tempRecurring) { - tempRecurring = new Recurring(); - } + if (!tempRecurring) { + tempRecurring = new Recurring(); + } - tempRecurring.intervalCount = new PositiveNumber( - valueNumber - ); + tempRecurring.intervalCount = new PositiveNumber(valueNumber); - updateRecurring(tempRecurring); - }} - /> - <Dropdown - value={DropdownUtil.getDropdownOptionFromEnumForValue( - EventInterval, - recurring?.intervalType || EventInterval.Day - )} - options={DropdownUtil.getDropdownOptionsFromEnum( - EventInterval - )} - onChange={( - value: DropdownValue | Array<DropdownValue> | null - ) => { - let tempRecurring: Recurring | undefined = recurring; + updateRecurring(tempRecurring); + }} + /> + <Dropdown + value={DropdownUtil.getDropdownOptionFromEnumForValue( + EventInterval, + recurring?.intervalType || EventInterval.Day, + )} + options={DropdownUtil.getDropdownOptionsFromEnum(EventInterval)} + onChange={(value: DropdownValue | Array<DropdownValue> | null) => { + let tempRecurring: Recurring | undefined = recurring; - if (!tempRecurring) { - tempRecurring = new Recurring(); - } + if (!tempRecurring) { + tempRecurring = new Recurring(); + } - tempRecurring.intervalType = value as EventInterval; + tempRecurring.intervalType = value as EventInterval; - updateRecurring(tempRecurring); - }} - /> - </div> + updateRecurring(tempRecurring); + }} + /> + </div> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} - </div> - ); + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default RecurringFieldElement; diff --git a/CommonUI/src/Components/FilePicker/FilePicker.tsx b/CommonUI/src/Components/FilePicker/FilePicker.tsx index fd72f7365e..ac412e0cd6 100644 --- a/CommonUI/src/Components/FilePicker/FilePicker.tsx +++ b/CommonUI/src/Components/FilePicker/FilePicker.tsx @@ -1,285 +1,269 @@ -import { FILE_URL } from '../../Config'; -import API from '../../Utils/API/API'; -import ModelAPI from '../../Utils/ModelAPI/ModelAPI'; -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import Icon, { SizeProp } from '../Icon/Icon'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import CommonURL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import MimeType from 'Common/Types/File/MimeType'; -import IconProp from 'Common/Types/Icon/IconProp'; -import FileModel from 'Model/Models/File'; +import { FILE_URL } from "../../Config"; +import API from "../../Utils/API/API"; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import Icon, { SizeProp } from "../Icon/Icon"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import CommonURL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import MimeType from "Common/Types/File/MimeType"; +import IconProp from "Common/Types/Icon/IconProp"; +import FileModel from "Model/Models/File"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import { useDropzone } from 'react-dropzone'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import { useDropzone } from "react-dropzone"; export interface ComponentProps { - initialValue?: undefined | Array<FileModel> | FileModel; - onClick?: undefined | (() => void); - placeholder?: undefined | string; - className?: undefined | string; - onChange?: undefined | ((value: Array<FileModel>) => void); - value?: Array<FileModel> | undefined; - readOnly?: boolean | undefined; - mimeTypes?: Array<MimeType> | undefined; - onFocus?: (() => void) | undefined; - onBlur?: (() => void) | undefined; - dataTestId?: string | undefined; - isMultiFilePicker?: boolean | undefined; - tabIndex?: number | undefined; - error?: string | undefined; + initialValue?: undefined | Array<FileModel> | FileModel; + onClick?: undefined | (() => void); + placeholder?: undefined | string; + className?: undefined | string; + onChange?: undefined | ((value: Array<FileModel>) => void); + value?: Array<FileModel> | undefined; + readOnly?: boolean | undefined; + mimeTypes?: Array<MimeType> | undefined; + onFocus?: (() => void) | undefined; + onBlur?: (() => void) | undefined; + dataTestId?: string | undefined; + isMultiFilePicker?: boolean | undefined; + tabIndex?: number | undefined; + error?: string | undefined; } const FilePicker: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [filesModel, setFilesModel] = useState<Array<FileModel>>([]); + const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [filesModel, setFilesModel] = useState<Array<FileModel>>([]); - const [acceptTypes, setAcceptTypes] = useState<Dictionary<Array<string>>>( - {} - ); + const [acceptTypes, setAcceptTypes] = useState<Dictionary<Array<string>>>({}); - useEffect(() => { - const _acceptTypes: Dictionary<Array<string>> = {}; - if (props.mimeTypes) { - for (const key of props.mimeTypes) { - _acceptTypes[key] = []; - } - } - setAcceptTypes(_acceptTypes); - }, [props.mimeTypes]); - - useEffect(() => { - setInitialValue(); - }, [props.initialValue]); - - const setInitialValue: VoidFunction = () => { - if ( - Array.isArray(props.initialValue) && - props.initialValue && - props.initialValue.length > 0 - ) { - setFilesModel(props.initialValue); - } else if (props.initialValue instanceof FileModel) { - setFilesModel([props.initialValue as FileModel]); - } - }; - - useEffect(() => { - if (props.value && props.value.length > 0) { - setFilesModel( - props.value && props.value.length > 0 ? props.value : [] - ); - } else { - setInitialValue(); - } - }, [props.value]); - - const { getRootProps, getInputProps } = useDropzone({ - accept: acceptTypes, - multiple: props.isMultiFilePicker, - noClick: true, - onDrop: async (acceptedFiles: Array<File>) => { - setIsLoading(true); - try { - if (props.readOnly) { - return; - } - - // Upload these files. - const filesResult: Array<FileModel> = []; - for (const acceptedFile of acceptedFiles) { - const fileModel: FileModel = new FileModel(); - fileModel.name = acceptedFile.name; - - const arrayBuffer: ArrayBuffer = - await acceptedFile.arrayBuffer(); - - const fileBuffer: Uint8Array = new Uint8Array(arrayBuffer); - fileModel.file = Buffer.from(fileBuffer); - fileModel.isPublic = false; - fileModel.type = acceptedFile.type as MimeType; - - const result: HTTPResponse<FileModel> = - (await ModelAPI.create<FileModel>({ - model: fileModel, - modelType: FileModel, - requestOptions: { - overrideRequestUrl: CommonURL.fromURL(FILE_URL), - }, - })) as HTTPResponse<FileModel>; - filesResult.push(result.data as FileModel); - } - - setFilesModel(filesResult); - - props.onBlur && props.onBlur(); - props.onChange && props.onChange(filesResult); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - setIsLoading(false); - }, - }); - - type GetThumbsFunction = () => Array<ReactElement>; - - const getThumbs: GetThumbsFunction = (): Array<ReactElement> => { - return filesModel.map((file: FileModel, i: number) => { - if (!file.file) { - return <></>; - } - - const blob: Blob = new Blob([file.file as Uint8Array], { - type: file.type as string, - }); - const url: string = URL.createObjectURL(blob); - - return ( - <div key={file.name}> - <div className="text-right flex justify-end"> - <Icon - icon={IconProp.Close} - className="bg-gray-400 rounded text-white h-7 w-7 align-right items-right p-1 absolute hover:bg-gray-500 cursor-pointer -ml-7" - size={SizeProp.Regular} - onClick={() => { - const tempFileModel: Array<FileModel> = [ - ...filesModel, - ]; - tempFileModel.splice(i, 1); - setFilesModel(tempFileModel); - props.onChange && props.onChange(tempFileModel); - }} - /> - </div> - <div> - <img src={url} className="rounded" /> - </div> - </div> - ); - }); - }; - - if (isLoading) { - return ( - <div className="flex justify-center w-full"> - <ComponentLoader /> - </div> - ); + useEffect(() => { + const _acceptTypes: Dictionary<Array<string>> = {}; + if (props.mimeTypes) { + for (const key of props.mimeTypes) { + _acceptTypes[key] = []; + } } + setAcceptTypes(_acceptTypes); + }, [props.mimeTypes]); - return ( - <div> - <div - onClick={() => { - props.onClick && props.onClick(); - props.onFocus && props.onFocus(); - }} - data-testid={props.dataTestId} - className="flex max-w-lg justify-center rounded-md border-2 border-dashed border-gray-300 px-6 pt-5 pb-6" - > - {props.isMultiFilePicker || - (filesModel.length === 0 && ( - <div - {...getRootProps({ - className: 'space-y-1 text-center', - })} - > - <svg - className="mx-auto h-12 w-12 text-gray-400" - stroke="currentColor" - fill="none" - viewBox="0 0 48 48" - aria-hidden="true" - > - <path - d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - ></path> - </svg> - <div className="flex text-sm text-gray-600"> - <label className="relative cursor-pointer rounded-md bg-white font-medium text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2 hover:text-indigo-500"> - {!props.placeholder && !error && ( - <span>{'Upload a file'}</span> - )} + useEffect(() => { + setInitialValue(); + }, [props.initialValue]); - {error && ( - <span> - <span>{error}</span> - </span> - )} + const setInitialValue: VoidFunction = () => { + if ( + Array.isArray(props.initialValue) && + props.initialValue && + props.initialValue.length > 0 + ) { + setFilesModel(props.initialValue); + } else if (props.initialValue instanceof FileModel) { + setFilesModel([props.initialValue as FileModel]); + } + }; - {props.placeholder && !error && ( - <span>{props.placeholder}</span> - )} + useEffect(() => { + if (props.value && props.value.length > 0) { + setFilesModel(props.value && props.value.length > 0 ? props.value : []); + } else { + setInitialValue(); + } + }, [props.value]); - <input - tabIndex={props.tabIndex} - {...(getInputProps() as any)} - id="file-upload" - name="file-upload" - type="file" - className="sr-only" - /> - </label> - <p className="pl-1">or drag and drop</p> - </div> - <p className="text-xs text-gray-500"> - {props.mimeTypes && - props.mimeTypes?.length > 0 && ( - <span>File types: </span> - )} - {props.mimeTypes && - props.mimeTypes - .map((type: MimeType) => { - const enumKey: string | undefined = - Object.keys(MimeType)[ - Object.values( - MimeType - ).indexOf(type) - ]; - return enumKey?.toUpperCase() || ''; - }) - .filter( - ( - item: string | undefined, - pos: number, - array: Array<string | undefined> - ) => { - return ( - array.indexOf(item) === pos - ); - } - ) - .join(', ')} - {props.mimeTypes && - props.mimeTypes?.length > 0 && ( - <span>.</span> - )} -  10 MB or less. - </p> - </div> - ))} - <aside>{getThumbs()}</aside> - </div> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} + const { getRootProps, getInputProps } = useDropzone({ + accept: acceptTypes, + multiple: props.isMultiFilePicker, + noClick: true, + onDrop: async (acceptedFiles: Array<File>) => { + setIsLoading(true); + try { + if (props.readOnly) { + return; + } + + // Upload these files. + const filesResult: Array<FileModel> = []; + for (const acceptedFile of acceptedFiles) { + const fileModel: FileModel = new FileModel(); + fileModel.name = acceptedFile.name; + + const arrayBuffer: ArrayBuffer = await acceptedFile.arrayBuffer(); + + const fileBuffer: Uint8Array = new Uint8Array(arrayBuffer); + fileModel.file = Buffer.from(fileBuffer); + fileModel.isPublic = false; + fileModel.type = acceptedFile.type as MimeType; + + const result: HTTPResponse<FileModel> = + (await ModelAPI.create<FileModel>({ + model: fileModel, + modelType: FileModel, + requestOptions: { + overrideRequestUrl: CommonURL.fromURL(FILE_URL), + }, + })) as HTTPResponse<FileModel>; + filesResult.push(result.data as FileModel); + } + + setFilesModel(filesResult); + + props.onBlur && props.onBlur(); + props.onChange && props.onChange(filesResult); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + setIsLoading(false); + }, + }); + + type GetThumbsFunction = () => Array<ReactElement>; + + const getThumbs: GetThumbsFunction = (): Array<ReactElement> => { + return filesModel.map((file: FileModel, i: number) => { + if (!file.file) { + return <></>; + } + + const blob: Blob = new Blob([file.file as Uint8Array], { + type: file.type as string, + }); + const url: string = URL.createObjectURL(blob); + + return ( + <div key={file.name}> + <div className="text-right flex justify-end"> + <Icon + icon={IconProp.Close} + className="bg-gray-400 rounded text-white h-7 w-7 align-right items-right p-1 absolute hover:bg-gray-500 cursor-pointer -ml-7" + size={SizeProp.Regular} + onClick={() => { + const tempFileModel: Array<FileModel> = [...filesModel]; + tempFileModel.splice(i, 1); + setFilesModel(tempFileModel); + props.onChange && props.onChange(tempFileModel); + }} + /> + </div> + <div> + <img src={url} className="rounded" /> + </div> </div> + ); + }); + }; + + if (isLoading) { + return ( + <div className="flex justify-center w-full"> + <ComponentLoader /> + </div> ); + } + + return ( + <div> + <div + onClick={() => { + props.onClick && props.onClick(); + props.onFocus && props.onFocus(); + }} + data-testid={props.dataTestId} + className="flex max-w-lg justify-center rounded-md border-2 border-dashed border-gray-300 px-6 pt-5 pb-6" + > + {props.isMultiFilePicker || + (filesModel.length === 0 && ( + <div + {...getRootProps({ + className: "space-y-1 text-center", + })} + > + <svg + className="mx-auto h-12 w-12 text-gray-400" + stroke="currentColor" + fill="none" + viewBox="0 0 48 48" + aria-hidden="true" + > + <path + d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + ></path> + </svg> + <div className="flex text-sm text-gray-600"> + <label className="relative cursor-pointer rounded-md bg-white font-medium text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2 hover:text-indigo-500"> + {!props.placeholder && !error && ( + <span>{"Upload a file"}</span> + )} + + {error && ( + <span> + <span>{error}</span> + </span> + )} + + {props.placeholder && !error && ( + <span>{props.placeholder}</span> + )} + + <input + tabIndex={props.tabIndex} + {...(getInputProps() as any)} + id="file-upload" + name="file-upload" + type="file" + className="sr-only" + /> + </label> + <p className="pl-1">or drag and drop</p> + </div> + <p className="text-xs text-gray-500"> + {props.mimeTypes && props.mimeTypes?.length > 0 && ( + <span>File types: </span> + )} + {props.mimeTypes && + props.mimeTypes + .map((type: MimeType) => { + const enumKey: string | undefined = + Object.keys(MimeType)[ + Object.values(MimeType).indexOf(type) + ]; + return enumKey?.toUpperCase() || ""; + }) + .filter( + ( + item: string | undefined, + pos: number, + array: Array<string | undefined>, + ) => { + return array.indexOf(item) === pos; + }, + ) + .join(", ")} + {props.mimeTypes && props.mimeTypes?.length > 0 && ( + <span>.</span> + )} +  10 MB or less. + </p> + </div> + ))} + <aside>{getThumbs()}</aside> + </div> + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default FilePicker; diff --git a/CommonUI/src/Components/Filters/BooleanFilter.tsx b/CommonUI/src/Components/Filters/BooleanFilter.tsx index 0d3cba1486..bfe6837e2b 100644 --- a/CommonUI/src/Components/Filters/BooleanFilter.tsx +++ b/CommonUI/src/Components/Filters/BooleanFilter.tsx @@ -1,62 +1,60 @@ -import Dropdown, { DropdownValue } from '../Dropdown/Dropdown'; -import FieldType from '../Types/FieldType'; -import Filter from './Types/Filter'; -import FilterData from './Types/FilterData'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; +import Dropdown, { DropdownValue } from "../Dropdown/Dropdown"; +import FieldType from "../Types/FieldType"; +import Filter from "./Types/Filter"; +import FilterData from "./Types/FilterData"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; export interface ComponentProps<T extends GenericObject> { - filter: Filter<T>; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - filterData: FilterData<T>; + filter: Filter<T>; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + filterData: FilterData<T>; } type BooleanFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const BooleanFilter: BooleanFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const filter: Filter<T> = props.filter; - const filterData: FilterData<T> = { ...props.filterData }; + const filter: Filter<T> = props.filter; + const filterData: FilterData<T> = { ...props.filterData }; - if (filter.type === FieldType.Boolean) { - return ( - <Dropdown - options={[ - { - value: true, - label: 'Yes', - }, - { - value: false, - label: 'No', - }, - ]} - onChange={( - value: DropdownValue | Array<DropdownValue> | null - ) => { - if (!filter.key) { - return; - } + if (filter.type === FieldType.Boolean) { + return ( + <Dropdown + options={[ + { + value: true, + label: "Yes", + }, + { + value: false, + label: "No", + }, + ]} + onChange={(value: DropdownValue | Array<DropdownValue> | null) => { + if (!filter.key) { + return; + } - if (value === null) { - delete filterData[filter.key]; - } else { - filterData[filter.key] = value; - } + if (value === null) { + delete filterData[filter.key]; + } else { + filterData[filter.key] = value; + } - if (props.onFilterChanged) { - props.onFilterChanged(filterData); - } - }} - placeholder={`Filter by ${filter.title}`} - /> - ); - } + if (props.onFilterChanged) { + props.onFilterChanged(filterData); + } + }} + placeholder={`Filter by ${filter.title}`} + /> + ); + } - return <></>; + return <></>; }; export default BooleanFilter; diff --git a/CommonUI/src/Components/Filters/DateFilter.tsx b/CommonUI/src/Components/Filters/DateFilter.tsx index 237c334506..5c013bb253 100644 --- a/CommonUI/src/Components/Filters/DateFilter.tsx +++ b/CommonUI/src/Components/Filters/DateFilter.tsx @@ -1,324 +1,304 @@ -import Button, { ButtonSize, ButtonStyleType } from '../Button/Button'; -import Input, { InputType } from '../Input/Input'; -import FieldType from '../Types/FieldType'; -import Filter from './Types/Filter'; -import FilterData from './Types/FilterData'; -import InBetween from 'Common/Types/BaseDatabase/InBetween'; -import OneUptimeDate from 'Common/Types/Date'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement, useEffect } from 'react'; +import Button, { ButtonSize, ButtonStyleType } from "../Button/Button"; +import Input, { InputType } from "../Input/Input"; +import FieldType from "../Types/FieldType"; +import Filter from "./Types/Filter"; +import FilterData from "./Types/FilterData"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import OneUptimeDate from "Common/Types/Date"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement, useEffect } from "react"; export interface ComponentProps<T extends GenericObject> { - filter: Filter<T>; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - filterData: FilterData<T>; + filter: Filter<T>; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + filterData: FilterData<T>; } type DateFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const DateFilter: DateFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const filter: Filter<T> = props.filter; - const filterData: FilterData<T> = { ...props.filterData }; + const filter: Filter<T> = props.filter; + const filterData: FilterData<T> = { ...props.filterData }; - const [startDateTime, setStartDateTime] = React.useState<Date | null>(null); - const [endDateTime, setEndDateTime] = React.useState<Date | null>(null); + const [startDateTime, setStartDateTime] = React.useState<Date | null>(null); + const [endDateTime, setEndDateTime] = React.useState<Date | null>(null); - const [startDateError, setStartDateError] = React.useState<string>(''); - const [endDateError, setEndDateError] = React.useState<string>(''); + const [startDateError, setStartDateError] = React.useState<string>(""); + const [endDateError, setEndDateError] = React.useState<string>(""); - const [didSetInitialValue, setDidSetInitialValue] = - React.useState<boolean>(false); + const [didSetInitialValue, setDidSetInitialValue] = + React.useState<boolean>(false); - let inputType: InputType = InputType.TEXT; + let inputType: InputType = InputType.TEXT; - if (filter.type === FieldType.Date) { - inputType = InputType.DATE; - } else if (filter.type === FieldType.DateTime) { - inputType = InputType.DATETIME_LOCAL; - } + if (filter.type === FieldType.Date) { + inputType = InputType.DATE; + } else if (filter.type === FieldType.DateTime) { + inputType = InputType.DATETIME_LOCAL; + } - useEffect(() => { - // prefill the date filter if it is already set - - if ( - !didSetInitialValue && - filterData[filter.key] && - filterData[filter.key] instanceof InBetween - ) { - const inBetween: InBetween = filterData[filter.key] as InBetween; - - if (inBetween.startValue) { - setStartDateTime( - OneUptimeDate.fromString(inBetween.startValue as string) - ); - } - - if (inBetween.endValue) { - setEndDateTime( - OneUptimeDate.fromString(inBetween.endValue as string) - ); - } - - setDidSetInitialValue(true); - } - }, [props.filterData]); - - useEffect(() => { - if (startDateTime && endDateTime) { - // check if start date is after end date - - if (!OneUptimeDate.isAfter(endDateTime, startDateTime)) { - setStartDateError('Start date should be before end date'); - setEndDateError('End date should be after start date'); - delete filterData[filter.key]; - - props.onFilterChanged && props.onFilterChanged(filterData); - - return; - } - - filterData[filter.key] = new InBetween(startDateTime, endDateTime); - } - - if (!startDateTime || !endDateTime) { - delete filterData[filter.key]; - } - - if (startDateTime && !endDateTime) { - setStartDateError(''); - setEndDateError('End date is required'); - } else if (!startDateTime && endDateTime) { - setEndDateError(''); - setStartDateError('Start date is required'); - } else { - setStartDateError(''); - setEndDateError(''); - } - - props.onFilterChanged && props.onFilterChanged(filterData); - }, [startDateTime, endDateTime]); + useEffect(() => { + // prefill the date filter if it is already set if ( - !filter.filterDropdownOptions && - (filter.type === FieldType.Date || filter.type === FieldType.DateTime) + !didSetInitialValue && + filterData[filter.key] && + filterData[filter.key] instanceof InBetween ) { - return ( - <div> - <div className="flex space-x-3 mt-1"> - <div className="w-1/2"> - <div className="text-xs text-gray-500">From:</div> - <div> - <Input - error={startDateError} - onChange={(changedValue: string | Date) => { - if (filter.key) { - if (!changedValue) { - setStartDateTime(null); - } + const inBetween: InBetween = filterData[filter.key] as InBetween; - if ( - changedValue && - (filter.type === FieldType.Date || - filter.type === - FieldType.DateTime) - ) { - setStartDateTime( - OneUptimeDate.fromString( - changedValue as string - ) - ); - } - } - }} - value={startDateTime || ''} - placeholder={`Filter by ${filter.title}`} - type={inputType} - /> - </div> - </div> - <div className="w-1/2"> - <div className="text-xs text-gray-500">To:</div> - <div> - <Input - error={endDateError} - onChange={(changedValue: string | Date) => { - if (filter.key) { - if (!changedValue) { - setEndDateTime(null); - } - - if ( - changedValue && - (filter.type === FieldType.Date || - filter.type === - FieldType.DateTime) - ) { - setEndDateTime( - OneUptimeDate.fromString( - changedValue as string - ) - ); - } - } - }} - value={endDateTime || ''} - placeholder={`Filter by ${filter.title}`} - type={inputType} - /> - </div> - </div> - </div> - - <div className="mt-1 flex space-x-2 -ml-3"> - {filter.type === FieldType.DateTime && ( - <Button - buttonStyle={ButtonStyleType.NORMAL} - buttonSize={ButtonSize.Small} - onClick={() => { - // set it to past 1 hour - const endDate: Date = - OneUptimeDate.getCurrentDate(); - const startDate: Date = - OneUptimeDate.addRemoveHours(endDate, -1); - - setStartDateTime(startDate); - setEndDateTime(endDate); - }} - title="1 hour" - /> - )} - - {filter.type === FieldType.DateTime && ( - <Button - buttonStyle={ButtonStyleType.NORMAL} - buttonSize={ButtonSize.Small} - onClick={() => { - // set it to past 3 hour - const endDate: Date = - OneUptimeDate.getCurrentDate(); - const startDate: Date = - OneUptimeDate.addRemoveHours(endDate, -3); - - setStartDateTime(startDate); - setEndDateTime(endDate); - }} - title="3 hours" - /> - )} - - <Button - buttonStyle={ButtonStyleType.NORMAL} - buttonSize={ButtonSize.Small} - onClick={() => { - // set it to past 1 day - const endDate: Date = - OneUptimeDate.getCurrentDate(); - const startDate: Date = OneUptimeDate.addRemoveDays( - endDate, - -1 - ); - - setStartDateTime(startDate); - setEndDateTime(endDate); - }} - title="1 day" - /> - - <Button - buttonStyle={ButtonStyleType.NORMAL} - buttonSize={ButtonSize.Small} - onClick={() => { - // set it to past 1 week - const endDate: Date = - OneUptimeDate.getCurrentDate(); - const startDate: Date = OneUptimeDate.addRemoveDays( - endDate, - -7 - ); - - setStartDateTime(startDate); - setEndDateTime(endDate); - }} - title="1 week" - /> - - <Button - buttonStyle={ButtonStyleType.NORMAL} - buttonSize={ButtonSize.Small} - onClick={() => { - // set it to past 1 week - const endDate: Date = - OneUptimeDate.getCurrentDate(); - const startDate: Date = OneUptimeDate.addRemoveDays( - endDate, - -14 - ); - - setStartDateTime(startDate); - setEndDateTime(endDate); - }} - title="2 weeks" - /> - - <Button - buttonStyle={ButtonStyleType.NORMAL} - buttonSize={ButtonSize.Small} - onClick={() => { - // set it to past 1 week - const endDate: Date = - OneUptimeDate.getCurrentDate(); - const startDate: Date = OneUptimeDate.addRemoveDays( - endDate, - -21 - ); - - setStartDateTime(startDate); - setEndDateTime(endDate); - }} - title="3 weeks" - /> - - <Button - buttonStyle={ButtonStyleType.NORMAL} - buttonSize={ButtonSize.Small} - onClick={() => { - // set it to past 1 month - const endDate: Date = - OneUptimeDate.getCurrentDate(); - const startDate: Date = - OneUptimeDate.addRemoveMonths(endDate, -1); - - setStartDateTime(startDate); - setEndDateTime(endDate); - }} - title="1 month" - /> - - <Button - buttonStyle={ButtonStyleType.NORMAL} - buttonSize={ButtonSize.Small} - onClick={() => { - // set it to past 1 month - const endDate: Date = - OneUptimeDate.getCurrentDate(); - const startDate: Date = - OneUptimeDate.addRemoveMonths(endDate, -3); - - setStartDateTime(startDate); - setEndDateTime(endDate); - }} - title="3 months" - /> - </div> - </div> + if (inBetween.startValue) { + setStartDateTime( + OneUptimeDate.fromString(inBetween.startValue as string), ); + } + + if (inBetween.endValue) { + setEndDateTime(OneUptimeDate.fromString(inBetween.endValue as string)); + } + + setDidSetInitialValue(true); + } + }, [props.filterData]); + + useEffect(() => { + if (startDateTime && endDateTime) { + // check if start date is after end date + + if (!OneUptimeDate.isAfter(endDateTime, startDateTime)) { + setStartDateError("Start date should be before end date"); + setEndDateError("End date should be after start date"); + delete filterData[filter.key]; + + props.onFilterChanged && props.onFilterChanged(filterData); + + return; + } + + filterData[filter.key] = new InBetween(startDateTime, endDateTime); } - return <></>; + if (!startDateTime || !endDateTime) { + delete filterData[filter.key]; + } + + if (startDateTime && !endDateTime) { + setStartDateError(""); + setEndDateError("End date is required"); + } else if (!startDateTime && endDateTime) { + setEndDateError(""); + setStartDateError("Start date is required"); + } else { + setStartDateError(""); + setEndDateError(""); + } + + props.onFilterChanged && props.onFilterChanged(filterData); + }, [startDateTime, endDateTime]); + + if ( + !filter.filterDropdownOptions && + (filter.type === FieldType.Date || filter.type === FieldType.DateTime) + ) { + return ( + <div> + <div className="flex space-x-3 mt-1"> + <div className="w-1/2"> + <div className="text-xs text-gray-500">From:</div> + <div> + <Input + error={startDateError} + onChange={(changedValue: string | Date) => { + if (filter.key) { + if (!changedValue) { + setStartDateTime(null); + } + + if ( + changedValue && + (filter.type === FieldType.Date || + filter.type === FieldType.DateTime) + ) { + setStartDateTime( + OneUptimeDate.fromString(changedValue as string), + ); + } + } + }} + value={startDateTime || ""} + placeholder={`Filter by ${filter.title}`} + type={inputType} + /> + </div> + </div> + <div className="w-1/2"> + <div className="text-xs text-gray-500">To:</div> + <div> + <Input + error={endDateError} + onChange={(changedValue: string | Date) => { + if (filter.key) { + if (!changedValue) { + setEndDateTime(null); + } + + if ( + changedValue && + (filter.type === FieldType.Date || + filter.type === FieldType.DateTime) + ) { + setEndDateTime( + OneUptimeDate.fromString(changedValue as string), + ); + } + } + }} + value={endDateTime || ""} + placeholder={`Filter by ${filter.title}`} + type={inputType} + /> + </div> + </div> + </div> + + <div className="mt-1 flex space-x-2 -ml-3"> + {filter.type === FieldType.DateTime && ( + <Button + buttonStyle={ButtonStyleType.NORMAL} + buttonSize={ButtonSize.Small} + onClick={() => { + // set it to past 1 hour + const endDate: Date = OneUptimeDate.getCurrentDate(); + const startDate: Date = OneUptimeDate.addRemoveHours( + endDate, + -1, + ); + + setStartDateTime(startDate); + setEndDateTime(endDate); + }} + title="1 hour" + /> + )} + + {filter.type === FieldType.DateTime && ( + <Button + buttonStyle={ButtonStyleType.NORMAL} + buttonSize={ButtonSize.Small} + onClick={() => { + // set it to past 3 hour + const endDate: Date = OneUptimeDate.getCurrentDate(); + const startDate: Date = OneUptimeDate.addRemoveHours( + endDate, + -3, + ); + + setStartDateTime(startDate); + setEndDateTime(endDate); + }} + title="3 hours" + /> + )} + + <Button + buttonStyle={ButtonStyleType.NORMAL} + buttonSize={ButtonSize.Small} + onClick={() => { + // set it to past 1 day + const endDate: Date = OneUptimeDate.getCurrentDate(); + const startDate: Date = OneUptimeDate.addRemoveDays(endDate, -1); + + setStartDateTime(startDate); + setEndDateTime(endDate); + }} + title="1 day" + /> + + <Button + buttonStyle={ButtonStyleType.NORMAL} + buttonSize={ButtonSize.Small} + onClick={() => { + // set it to past 1 week + const endDate: Date = OneUptimeDate.getCurrentDate(); + const startDate: Date = OneUptimeDate.addRemoveDays(endDate, -7); + + setStartDateTime(startDate); + setEndDateTime(endDate); + }} + title="1 week" + /> + + <Button + buttonStyle={ButtonStyleType.NORMAL} + buttonSize={ButtonSize.Small} + onClick={() => { + // set it to past 1 week + const endDate: Date = OneUptimeDate.getCurrentDate(); + const startDate: Date = OneUptimeDate.addRemoveDays(endDate, -14); + + setStartDateTime(startDate); + setEndDateTime(endDate); + }} + title="2 weeks" + /> + + <Button + buttonStyle={ButtonStyleType.NORMAL} + buttonSize={ButtonSize.Small} + onClick={() => { + // set it to past 1 week + const endDate: Date = OneUptimeDate.getCurrentDate(); + const startDate: Date = OneUptimeDate.addRemoveDays(endDate, -21); + + setStartDateTime(startDate); + setEndDateTime(endDate); + }} + title="3 weeks" + /> + + <Button + buttonStyle={ButtonStyleType.NORMAL} + buttonSize={ButtonSize.Small} + onClick={() => { + // set it to past 1 month + const endDate: Date = OneUptimeDate.getCurrentDate(); + const startDate: Date = OneUptimeDate.addRemoveMonths( + endDate, + -1, + ); + + setStartDateTime(startDate); + setEndDateTime(endDate); + }} + title="1 month" + /> + + <Button + buttonStyle={ButtonStyleType.NORMAL} + buttonSize={ButtonSize.Small} + onClick={() => { + // set it to past 1 month + const endDate: Date = OneUptimeDate.getCurrentDate(); + const startDate: Date = OneUptimeDate.addRemoveMonths( + endDate, + -3, + ); + + setStartDateTime(startDate); + setEndDateTime(endDate); + }} + title="3 months" + /> + </div> + </div> + ); + } + + return <></>; }; export default DateFilter; diff --git a/CommonUI/src/Components/Filters/DropdownFilter.tsx b/CommonUI/src/Components/Filters/DropdownFilter.tsx index 2e7a1e66ec..da1fe59f07 100644 --- a/CommonUI/src/Components/Filters/DropdownFilter.tsx +++ b/CommonUI/src/Components/Filters/DropdownFilter.tsx @@ -1,69 +1,62 @@ -import Dropdown, { DropdownOption, DropdownValue } from '../Dropdown/Dropdown'; -import FieldType from '../Types/FieldType'; -import Filter from './Types/Filter'; -import FilterData from './Types/FilterData'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; +import Dropdown, { DropdownOption, DropdownValue } from "../Dropdown/Dropdown"; +import FieldType from "../Types/FieldType"; +import Filter from "./Types/Filter"; +import FilterData from "./Types/FilterData"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; export interface ComponentProps<T extends GenericObject> { - filter: Filter<T>; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - filterData: FilterData<T>; + filter: Filter<T>; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + filterData: FilterData<T>; } type DropdownFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const DropdownFilter: DropdownFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const filter: Filter<T> = props.filter; - const filterData: FilterData<T> = { ...props.filterData }; + const filter: Filter<T> = props.filter; + const filterData: FilterData<T> = { ...props.filterData }; - const dropdownValue: DropdownOption | undefined = - props.filter.filterDropdownOptions?.find((option: DropdownOption) => { - return ( - option.value.toString() === filterData[filter.key]?.toString() - ); - }); + const dropdownValue: DropdownOption | undefined = + props.filter.filterDropdownOptions?.find((option: DropdownOption) => { + return option.value.toString() === filterData[filter.key]?.toString(); + }); - if ( - filter.type !== FieldType.Entity && - filter.type !== FieldType.EntityArray && - filter.filterDropdownOptions - ) { - return ( - <Dropdown - options={filter.filterDropdownOptions} - onChange={( - value: DropdownValue | Array<DropdownValue> | null - ) => { - if (!filter.key) { - return; - } + if ( + filter.type !== FieldType.Entity && + filter.type !== FieldType.EntityArray && + filter.filterDropdownOptions + ) { + return ( + <Dropdown + options={filter.filterDropdownOptions} + onChange={(value: DropdownValue | Array<DropdownValue> | null) => { + if (!filter.key) { + return; + } - if ( - !value || - (Array.isArray(value) && value.length === 0) - ) { - delete filterData[filter.key]; - } else { - filterData[filter.key] = value; - } + if (!value || (Array.isArray(value) && value.length === 0)) { + delete filterData[filter.key]; + } else { + filterData[filter.key] = value; + } - if (props.onFilterChanged) { - props.onFilterChanged(filterData); - } - }} - value={dropdownValue} - isMultiSelect={false} - placeholder={`Filter by ${filter.title}`} - /> - ); - } + if (props.onFilterChanged) { + props.onFilterChanged(filterData); + } + }} + value={dropdownValue} + isMultiSelect={false} + placeholder={`Filter by ${filter.title}`} + /> + ); + } - return <></>; + return <></>; }; export default DropdownFilter; diff --git a/CommonUI/src/Components/Filters/EntityFilter.tsx b/CommonUI/src/Components/Filters/EntityFilter.tsx index 4ad18f55a5..7ac5ce826f 100644 --- a/CommonUI/src/Components/Filters/EntityFilter.tsx +++ b/CommonUI/src/Components/Filters/EntityFilter.tsx @@ -1,77 +1,70 @@ -import Dropdown, { DropdownOption, DropdownValue } from '../Dropdown/Dropdown'; -import FieldType from '../Types/FieldType'; -import Filter from './Types/Filter'; -import FilterData from './Types/FilterData'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; +import Dropdown, { DropdownOption, DropdownValue } from "../Dropdown/Dropdown"; +import FieldType from "../Types/FieldType"; +import Filter from "./Types/Filter"; +import FilterData from "./Types/FilterData"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; export interface ComponentProps<T extends GenericObject> { - filter: Filter<T>; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - filterData: FilterData<T>; + filter: Filter<T>; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + filterData: FilterData<T>; } type EntityFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const EntityFilter: EntityFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const filter: Filter<T> = props.filter; - const filterData: FilterData<T> = { ...props.filterData }; + const filter: Filter<T> = props.filter; + const filterData: FilterData<T> = { ...props.filterData }; - const dropdownValues: Array<DropdownOption> = - props.filter.filterDropdownOptions?.filter((option: DropdownOption) => { - if (filterData[filter.key] instanceof Array) { - return (filterData[filter.key] as Array<string>) - .map((value: string) => { - return value.toString(); - }) - .includes(option.value.toString()); - } + const dropdownValues: Array<DropdownOption> = + props.filter.filterDropdownOptions?.filter((option: DropdownOption) => { + if (filterData[filter.key] instanceof Array) { + return (filterData[filter.key] as Array<string>) + .map((value: string) => { + return value.toString(); + }) + .includes(option.value.toString()); + } - return ( - option.value.toString() === filterData[filter.key]?.toString() - ); - }) || []; + return option.value.toString() === filterData[filter.key]?.toString(); + }) || []; - if ( - (filter.type === FieldType.Entity || - filter.type === FieldType.EntityArray) && - filter.filterDropdownOptions - ) { - return ( - <Dropdown - options={filter.filterDropdownOptions} - onChange={( - value: DropdownValue | Array<DropdownValue> | null - ) => { - if (!filter.key) { - return; - } + if ( + (filter.type === FieldType.Entity || + filter.type === FieldType.EntityArray) && + filter.filterDropdownOptions + ) { + return ( + <Dropdown + options={filter.filterDropdownOptions} + onChange={(value: DropdownValue | Array<DropdownValue> | null) => { + if (!filter.key) { + return; + } - if ( - !value || - (Array.isArray(value) && value.length === 0) - ) { - delete filterData[filter.key]; - } else { - filterData[filter.key] = value; - } + if (!value || (Array.isArray(value) && value.length === 0)) { + delete filterData[filter.key]; + } else { + filterData[filter.key] = value; + } - if (props.onFilterChanged) { - props.onFilterChanged(filterData); - } - }} - value={dropdownValues} - isMultiSelect={filter.type === FieldType.EntityArray} - placeholder={`Filter by ${filter.title}`} - /> - ); - } + if (props.onFilterChanged) { + props.onFilterChanged(filterData); + } + }} + value={dropdownValues} + isMultiSelect={filter.type === FieldType.EntityArray} + placeholder={`Filter by ${filter.title}`} + /> + ); + } - return <></>; + return <></>; }; export default EntityFilter; diff --git a/CommonUI/src/Components/Filters/FilterViewer.tsx b/CommonUI/src/Components/Filters/FilterViewer.tsx index 1e85966d2b..5b694112db 100644 --- a/CommonUI/src/Components/Filters/FilterViewer.tsx +++ b/CommonUI/src/Components/Filters/FilterViewer.tsx @@ -1,470 +1,442 @@ -import Button, { ButtonStyleType } from '../Button/Button'; -import { DropdownOption } from '../Dropdown/Dropdown'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import { SizeProp } from '../Icon/Icon'; -import Modal, { ModalWidth } from '../Modal/Modal'; -import FieldType from '../Types/FieldType'; -import FilterViewerItem from './FilterViewerItem'; -import FiltersForm from './FiltersForm'; -import Filter from './Types/Filter'; -import FilterData from './Types/FilterData'; -import InBetween from 'Common/Types/BaseDatabase/InBetween'; -import Search from 'Common/Types/BaseDatabase/Search'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import GenericObject from 'Common/Types/GenericObject'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { ReactElement, useEffect, useState } from 'react'; +import Button, { ButtonStyleType } from "../Button/Button"; +import { DropdownOption } from "../Dropdown/Dropdown"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import { SizeProp } from "../Icon/Icon"; +import Modal, { ModalWidth } from "../Modal/Modal"; +import FieldType from "../Types/FieldType"; +import FilterViewerItem from "./FilterViewerItem"; +import FiltersForm from "./FiltersForm"; +import Filter from "./Types/Filter"; +import FilterData from "./Types/FilterData"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import Search from "Common/Types/BaseDatabase/Search"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import GenericObject from "Common/Types/GenericObject"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { ReactElement, useEffect, useState } from "react"; export interface ComponentProps<T extends GenericObject> { - filters: Array<Filter<T>>; - singularLabel?: string; - pluralLabel?: string; - id: string; - showFilterModal: boolean; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - filterError?: string | undefined; - onFilterModalClose: () => void; - onFilterModalOpen: () => void; - isModalLoading?: boolean; - onFilterRefreshClick?: undefined | (() => void); + filters: Array<Filter<T>>; + singularLabel?: string; + pluralLabel?: string; + id: string; + showFilterModal: boolean; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + filterError?: string | undefined; + onFilterModalClose: () => void; + onFilterModalOpen: () => void; + isModalLoading?: boolean; + onFilterRefreshClick?: undefined | (() => void); } type FilterComponentFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const FilterComponent: FilterComponentFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const [filterData, setFilterData] = useState<FilterData<T>>({}); - const [tempFilterDataForModal, setTempFilterDataForModal] = useState< - FilterData<T> - >({}); + const [filterData, setFilterData] = useState<FilterData<T>>({}); + const [tempFilterDataForModal, setTempFilterDataForModal] = useState< + FilterData<T> + >({}); - type ChangeFilterDataFunction = (filterData: FilterData<T>) => void; + type ChangeFilterDataFunction = (filterData: FilterData<T>) => void; - const changeFilterData: ChangeFilterDataFunction = ( - filterData: FilterData<T> - ) => { - setFilterData(filterData); - setTempFilterDataForModal(filterData); - props.onFilterChanged?.(filterData); - }; + const changeFilterData: ChangeFilterDataFunction = ( + filterData: FilterData<T>, + ) => { + setFilterData(filterData); + setTempFilterDataForModal(filterData); + props.onFilterChanged?.(filterData); + }; - useEffect(() => { - if (props.showFilterModal) { - setTempFilterDataForModal({ ...filterData }); - } - }, [props.showFilterModal]); + useEffect(() => { + if (props.showFilterModal) { + setTempFilterDataForModal({ ...filterData }); + } + }, [props.showFilterModal]); - type FormatJsonFunction = ( - json: Dictionary<string | number | boolean> - ) => ReactElement; + type FormatJsonFunction = ( + json: Dictionary<string | number | boolean>, + ) => ReactElement; - const formatJson: FormatJsonFunction = ( - json: Dictionary<string | number | boolean> - ): ReactElement => { - return ( - <div className="flex space-x-2 -mt-1"> - {Object.keys(json).map((key: string, i: number) => { - let jsonText: string | number | boolean = json[key] as - | string - | number - | boolean; + const formatJson: FormatJsonFunction = ( + json: Dictionary<string | number | boolean>, + ): ReactElement => { + return ( + <div className="flex space-x-2 -mt-1"> + {Object.keys(json).map((key: string, i: number) => { + let jsonText: string | number | boolean = json[key] as + | string + | number + | boolean; - if (typeof jsonText === 'boolean' && jsonText === true) { - jsonText = 'True'; - } + if (typeof jsonText === "boolean" && jsonText === true) { + jsonText = "True"; + } - if (typeof jsonText === 'boolean' && jsonText === false) { - jsonText = 'False'; - } + if (typeof jsonText === "boolean" && jsonText === false) { + jsonText = "False"; + } - return ( - <div - key={i} - className="rounded-full h-7 bg-gray-100 text-gray-500 border-2 border-gray-200 p-1 pr-2 pl-2 text-xs" - > - <span className="font-medium">{key}</span> ={' '} - <span className="font-medium">{jsonText}</span> - </div> - ); - })} + return ( + <div + key={i} + className="rounded-full h-7 bg-gray-100 text-gray-500 border-2 border-gray-200 p-1 pr-2 pl-2 text-xs" + > + <span className="font-medium">{key}</span> ={" "} + <span className="font-medium">{jsonText}</span> </div> - ); - }; + ); + })} + </div> + ); + }; - type TranslateFilterToTextFunction = <T extends GenericObject>(data: { - filters: Array<Filter<T>>; - filterData: FilterData<T>; - }) => Array<ReactElement>; + type TranslateFilterToTextFunction = <T extends GenericObject>(data: { + filters: Array<Filter<T>>; + filterData: FilterData<T>; + }) => Array<ReactElement>; - const translateFilterToText: TranslateFilterToTextFunction = < - T extends GenericObject - >(data: { - filters: Array<Filter<T>>; - filterData: FilterData<T>; - }): Array<ReactElement> => { - const filterTexts: Array<ReactElement | null> = []; + const translateFilterToText: TranslateFilterToTextFunction = < + T extends GenericObject, + >(data: { + filters: Array<Filter<T>>; + filterData: FilterData<T>; + }): Array<ReactElement> => { + const filterTexts: Array<ReactElement | null> = []; - for (const filter of data.filters) { - filterTexts.push( - translateFilterItemToText({ - filter: filter, - filterData: data.filterData, - }) - ); - } - - return filterTexts.filter((filterText: ReactElement | null) => { - return filterText !== null; - }) as Array<ReactElement>; - }; - - type TranslateFilterItemToTextFunction = <T extends GenericObject>(data: { - filter: Filter<T>; - filterData: FilterData<T>; - }) => ReactElement | null; - - const translateFilterItemToText: TranslateFilterItemToTextFunction = < - T extends GenericObject - >(data: { - filter: Filter<T>; - filterData: FilterData<T>; - }): ReactElement | null => { - let filterText: ReactElement = <></>; - - if (!data.filter.key) { - return null; - } - - if ( - data.filterData[data.filter.key] === undefined || - data.filterData[data.filter.key] === null - ) { - return null; - } - - if (data.filter.type === FieldType.Boolean) { - filterText = ( - <div> - {' '} - <span className="font-medium"> - {data.filter.title} - </span> is{' '} - <span className="font-medium"> - {data.filterData[data.filter.key] ? 'Yes' : 'No'} - </span>{' '} - </div> - ); - return filterText; - } - - if ( - data.filter.type === FieldType.Text || - data.filter.type === FieldType.Number || - data.filter.type === FieldType.Email || - data.filter.type === FieldType.Phone || - data.filter.type === FieldType.URL || - data.filter.type === FieldType.Hostname - ) { - const key: keyof T = data.filter.key; - - if ( - data.filterData[key] && - data.filterData[key] instanceof Search - ) { - filterText = ( - <div> - {' '} - <span className="font-medium"> - {data.filter.title} - </span>{' '} - contains{' '} - <span className="font-medium"> - {data.filterData[data.filter.key]?.toString()} - </span>{' '} - </div> - ); - } else if (data.filterData[key]) { - filterText = ( - <div> - {' '} - <span className="font-medium"> - {data.filter.title} - </span>{' '} - is{' '} - <span className="font-medium"> - {data.filterData[data.filter.key]?.toString()} - </span>{' '} - </div> - ); - } - return filterText; - } - - if ( - data.filter.type === FieldType.Date || - data.filter.type === FieldType.DateTime - ) { - const key: keyof T = data.filter.key; - - const startAndEndDates: InBetween = data.filterData[ - key - ] as InBetween; - - const shouldOnlyShowDate: boolean = - data.filter.type === FieldType.Date; - - if ( - OneUptimeDate.getDateAsLocalFormattedString( - startAndEndDates.startValue as Date, - shouldOnlyShowDate - ) === - OneUptimeDate.getDateAsLocalFormattedString( - startAndEndDates.endValue as Date, - shouldOnlyShowDate - ) - ) { - return ( - <div> - {' '} - <span className="font-medium"> - {data.filter.title} - </span>{' '} - at{' '} - <span className="font-medium"> - {OneUptimeDate.getDateAsLocalFormattedString( - startAndEndDates.startValue as Date, - data.filter.type === FieldType.Date - )} - </span>{' '} - </div> - ); - } - return ( - <div> - {' '} - <span className="font-medium">{data.filter.title}</span> is - in between{' '} - <span className="font-medium"> - {OneUptimeDate.getDateAsLocalFormattedString( - startAndEndDates.startValue as Date, - shouldOnlyShowDate - )} - </span>{' '} - and{' '} - <span className="font-medium"> - {OneUptimeDate.getDateAsLocalFormattedString( - startAndEndDates.endValue as Date, - shouldOnlyShowDate - )} - </span>{' '} - </div> - ); - } - - if (data.filter.type === FieldType.JSON) { - const key: keyof T = data.filter.key; - - const json: Dictionary<string | number | boolean> = data.filterData[ - key - ] as Dictionary<string | number | boolean>; - - // if json is empty, return null - - if (Object.keys(json).length === 0) { - return null; - } - - const isPlural: boolean = Object.keys(json).length > 1; - - return ( - <div className="flex space-x-1"> - {' '} - <div className="font-medium">{data.filter.title}</div>{' '} - <div> - {isPlural ? 'are' : 'is'} {''} - </div> - <div className="font-medium">{formatJson(json)}</div>{' '} - </div> - ); - } - - if ( - data.filter.type === FieldType.Dropdown || - data.filter.type === FieldType.Entity || - data.filter.type === FieldType.EntityArray - ) { - const key: keyof T = data.filter.key; - - let items: Array<string> = data.filterData[key] as Array<string>; - - if (typeof items === 'string') { - items = [items]; - } - - const isMoreItems: boolean = items.length > 1; - - if (items && items instanceof Array) { - const entityNames: string = (items as Array<string>) - .map((item: string) => { - // item is the id of the entity. We need to find the name of the entity from the list of entities. - - const entity: DropdownOption | undefined = - data.filter.filterDropdownOptions?.find( - (entity: DropdownOption | undefined) => { - return ( - entity?.value.toString() === - item.toString() - ); - } - ); - - if (entity) { - return entity.label.toString(); - } - - return null; - }) - .filter((item: string | null) => { - return item !== null; - }) - .join(', '); - - return ( - <div> - <span className="font-medium">{data.filter.title}</span> - {isMoreItems ? ' is any of these values: ' : ' is '} - <span className="font-medium">{entityNames}</span> - </div> - ); - } - - return filterText; - } - - return filterText; - }; - - const filterTexts: Array<ReactElement> = translateFilterToText({ - filters: props.filters, - filterData: filterData, - }); - - if (props.filterError) { - return <ErrorMessage error={props.filterError} />; + for (const filter of data.filters) { + filterTexts.push( + translateFilterItemToText({ + filter: filter, + filterData: data.filterData, + }), + ); } - const showViewer: boolean = filterTexts.length > 0; + return filterTexts.filter((filterText: ReactElement | null) => { + return filterText !== null; + }) as Array<ReactElement>; + }; - return ( + type TranslateFilterItemToTextFunction = <T extends GenericObject>(data: { + filter: Filter<T>; + filterData: FilterData<T>; + }) => ReactElement | null; + + const translateFilterItemToText: TranslateFilterItemToTextFunction = < + T extends GenericObject, + >(data: { + filter: Filter<T>; + filterData: FilterData<T>; + }): ReactElement | null => { + let filterText: ReactElement = <></>; + + if (!data.filter.key) { + return null; + } + + if ( + data.filterData[data.filter.key] === undefined || + data.filterData[data.filter.key] === null + ) { + return null; + } + + if (data.filter.type === FieldType.Boolean) { + filterText = ( <div> - {showViewer && ( - <div> - <div className="mt-5 mb-5 bg-gray-50 rounded rounded-xl p-5 border border-2 border-gray-100"> - <div className="flex mt-1 mb-2"> - <div className="flex-auto py-0.5 text-sm leading-5"> - <span className="font-semibold"> - Filter {props.pluralLabel + ' ' || ''} - by the following criteria: - </span>{' '} - </div> - </div> - - <ul role="list" className="space-y-3"> - {filterTexts.map( - (filterText: ReactElement, index: number) => { - const isLastItem: boolean = - index === filterTexts.length - 1; - return ( - <li - className="relative flex gap-x-2" - key={index} - > - {!isLastItem && ( - <div className="absolute left-0 top-0 flex w-6 justify-center -bottom-6"> - <div className="w-px bg-gray-200"></div> - </div> - )} - <div className="relative flex h-6 w-6 flex-none items-center justify-center bg-gray-50"> - <div className="h-1.5 w-1.5 rounded-full bg-gray-100 ring-1 ring-gray-300"></div> - </div> - <FilterViewerItem - key={index} - text={filterText} - />{' '} - </li> - ); - } - )} - </ul> - - <div className="flex -ml-3 mt-3 -mb-2"> - {/** Edit Filter Button */} - <Button - className="font-medium text-gray-900" - icon={IconProp.Filter} - onClick={props.onFilterModalOpen} - title="Edit Filters" - iconSize={SizeProp.Smaller} - buttonStyle={ButtonStyleType.SECONDARY_LINK} - /> - - {/** Clear Filter Button */} - <Button - onClick={() => { - changeFilterData({}); - props.onFilterModalClose(); - }} - className="font-medium text-gray-900" - icon={IconProp.Close} - title="Clear Filters" - buttonStyle={ButtonStyleType.SECONDARY_LINK} - /> - </div> - </div> - </div> - )} - - {props.showFilterModal && ( - <Modal - modalWidth={ModalWidth.Large} - isLoading={props.isModalLoading} - title={`${props.singularLabel + ' ' || ''}Filters`} - description={`Filter ${ - props.pluralLabel || '' - } by the following criteria:`} - submitButtonText={`Apply Filters`} - onClose={() => { - props.onFilterModalClose(); - }} - onSubmit={() => { - setFilterData({ ...tempFilterDataForModal }); - setTempFilterDataForModal({}); - if (props.onFilterChanged) { - props.onFilterChanged({ - ...tempFilterDataForModal, - }); - } - props.onFilterModalClose(); - }} - > - <FiltersForm - onFilterRefreshClick={props.onFilterRefreshClick} - filterData={tempFilterDataForModal} - filters={props.filters} - id={props.id + '-form'} - showFilter={true} - onFilterChanged={(filterData: FilterData<T>) => { - setTempFilterDataForModal(filterData); - }} - /> - </Modal> - )} + {" "} + <span className="font-medium">{data.filter.title}</span> is{" "} + <span className="font-medium"> + {data.filterData[data.filter.key] ? "Yes" : "No"} + </span>{" "} </div> - ); + ); + return filterText; + } + + if ( + data.filter.type === FieldType.Text || + data.filter.type === FieldType.Number || + data.filter.type === FieldType.Email || + data.filter.type === FieldType.Phone || + data.filter.type === FieldType.URL || + data.filter.type === FieldType.Hostname + ) { + const key: keyof T = data.filter.key; + + if (data.filterData[key] && data.filterData[key] instanceof Search) { + filterText = ( + <div> + {" "} + <span className="font-medium"> + {data.filter.title} + </span> contains{" "} + <span className="font-medium"> + {data.filterData[data.filter.key]?.toString()} + </span>{" "} + </div> + ); + } else if (data.filterData[key]) { + filterText = ( + <div> + {" "} + <span className="font-medium">{data.filter.title}</span> is{" "} + <span className="font-medium"> + {data.filterData[data.filter.key]?.toString()} + </span>{" "} + </div> + ); + } + return filterText; + } + + if ( + data.filter.type === FieldType.Date || + data.filter.type === FieldType.DateTime + ) { + const key: keyof T = data.filter.key; + + const startAndEndDates: InBetween = data.filterData[key] as InBetween; + + const shouldOnlyShowDate: boolean = data.filter.type === FieldType.Date; + + if ( + OneUptimeDate.getDateAsLocalFormattedString( + startAndEndDates.startValue as Date, + shouldOnlyShowDate, + ) === + OneUptimeDate.getDateAsLocalFormattedString( + startAndEndDates.endValue as Date, + shouldOnlyShowDate, + ) + ) { + return ( + <div> + {" "} + <span className="font-medium">{data.filter.title}</span> at{" "} + <span className="font-medium"> + {OneUptimeDate.getDateAsLocalFormattedString( + startAndEndDates.startValue as Date, + data.filter.type === FieldType.Date, + )} + </span>{" "} + </div> + ); + } + return ( + <div> + {" "} + <span className="font-medium">{data.filter.title}</span> is in between{" "} + <span className="font-medium"> + {OneUptimeDate.getDateAsLocalFormattedString( + startAndEndDates.startValue as Date, + shouldOnlyShowDate, + )} + </span>{" "} + and{" "} + <span className="font-medium"> + {OneUptimeDate.getDateAsLocalFormattedString( + startAndEndDates.endValue as Date, + shouldOnlyShowDate, + )} + </span>{" "} + </div> + ); + } + + if (data.filter.type === FieldType.JSON) { + const key: keyof T = data.filter.key; + + const json: Dictionary<string | number | boolean> = data.filterData[ + key + ] as Dictionary<string | number | boolean>; + + // if json is empty, return null + + if (Object.keys(json).length === 0) { + return null; + } + + const isPlural: boolean = Object.keys(json).length > 1; + + return ( + <div className="flex space-x-1"> + {" "} + <div className="font-medium">{data.filter.title}</div>{" "} + <div> + {isPlural ? "are" : "is"} {""} + </div> + <div className="font-medium">{formatJson(json)}</div>{" "} + </div> + ); + } + + if ( + data.filter.type === FieldType.Dropdown || + data.filter.type === FieldType.Entity || + data.filter.type === FieldType.EntityArray + ) { + const key: keyof T = data.filter.key; + + let items: Array<string> = data.filterData[key] as Array<string>; + + if (typeof items === "string") { + items = [items]; + } + + const isMoreItems: boolean = items.length > 1; + + if (items && items instanceof Array) { + const entityNames: string = (items as Array<string>) + .map((item: string) => { + // item is the id of the entity. We need to find the name of the entity from the list of entities. + + const entity: DropdownOption | undefined = + data.filter.filterDropdownOptions?.find( + (entity: DropdownOption | undefined) => { + return entity?.value.toString() === item.toString(); + }, + ); + + if (entity) { + return entity.label.toString(); + } + + return null; + }) + .filter((item: string | null) => { + return item !== null; + }) + .join(", "); + + return ( + <div> + <span className="font-medium">{data.filter.title}</span> + {isMoreItems ? " is any of these values: " : " is "} + <span className="font-medium">{entityNames}</span> + </div> + ); + } + + return filterText; + } + + return filterText; + }; + + const filterTexts: Array<ReactElement> = translateFilterToText({ + filters: props.filters, + filterData: filterData, + }); + + if (props.filterError) { + return <ErrorMessage error={props.filterError} />; + } + + const showViewer: boolean = filterTexts.length > 0; + + return ( + <div> + {showViewer && ( + <div> + <div className="mt-5 mb-5 bg-gray-50 rounded rounded-xl p-5 border border-2 border-gray-100"> + <div className="flex mt-1 mb-2"> + <div className="flex-auto py-0.5 text-sm leading-5"> + <span className="font-semibold"> + Filter {props.pluralLabel + " " || ""} + by the following criteria: + </span>{" "} + </div> + </div> + + <ul role="list" className="space-y-3"> + {filterTexts.map((filterText: ReactElement, index: number) => { + const isLastItem: boolean = index === filterTexts.length - 1; + return ( + <li className="relative flex gap-x-2" key={index}> + {!isLastItem && ( + <div className="absolute left-0 top-0 flex w-6 justify-center -bottom-6"> + <div className="w-px bg-gray-200"></div> + </div> + )} + <div className="relative flex h-6 w-6 flex-none items-center justify-center bg-gray-50"> + <div className="h-1.5 w-1.5 rounded-full bg-gray-100 ring-1 ring-gray-300"></div> + </div> + <FilterViewerItem key={index} text={filterText} />{" "} + </li> + ); + })} + </ul> + + <div className="flex -ml-3 mt-3 -mb-2"> + {/** Edit Filter Button */} + <Button + className="font-medium text-gray-900" + icon={IconProp.Filter} + onClick={props.onFilterModalOpen} + title="Edit Filters" + iconSize={SizeProp.Smaller} + buttonStyle={ButtonStyleType.SECONDARY_LINK} + /> + + {/** Clear Filter Button */} + <Button + onClick={() => { + changeFilterData({}); + props.onFilterModalClose(); + }} + className="font-medium text-gray-900" + icon={IconProp.Close} + title="Clear Filters" + buttonStyle={ButtonStyleType.SECONDARY_LINK} + /> + </div> + </div> + </div> + )} + + {props.showFilterModal && ( + <Modal + modalWidth={ModalWidth.Large} + isLoading={props.isModalLoading} + title={`${props.singularLabel + " " || ""}Filters`} + description={`Filter ${ + props.pluralLabel || "" + } by the following criteria:`} + submitButtonText={`Apply Filters`} + onClose={() => { + props.onFilterModalClose(); + }} + onSubmit={() => { + setFilterData({ ...tempFilterDataForModal }); + setTempFilterDataForModal({}); + if (props.onFilterChanged) { + props.onFilterChanged({ + ...tempFilterDataForModal, + }); + } + props.onFilterModalClose(); + }} + > + <FiltersForm + onFilterRefreshClick={props.onFilterRefreshClick} + filterData={tempFilterDataForModal} + filters={props.filters} + id={props.id + "-form"} + showFilter={true} + onFilterChanged={(filterData: FilterData<T>) => { + setTempFilterDataForModal(filterData); + }} + /> + </Modal> + )} + </div> + ); }; export default FilterComponent; diff --git a/CommonUI/src/Components/Filters/FilterViewerItem.tsx b/CommonUI/src/Components/Filters/FilterViewerItem.tsx index a9bd1c8213..23e44920f5 100644 --- a/CommonUI/src/Components/Filters/FilterViewerItem.tsx +++ b/CommonUI/src/Components/Filters/FilterViewerItem.tsx @@ -1,27 +1,27 @@ -import React, { ReactElement } from 'react'; +import React, { ReactElement } from "react"; export interface ComponentProps { - text: string | ReactElement; + text: string | ReactElement; } type FilterViewerItemComponentFunction = ( - props: ComponentProps + props: ComponentProps, ) => ReactElement; const FilterViewerItem: FilterViewerItemComponentFunction = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const { text } = props; + const { text } = props; - return ( - <div className="flex w-full -ml-3"> - <div className="flex"> - <div className="ml-1 flex-auto py-0.5 text-sm leading-5 text-gray-500"> - <span className="text-gray-900">{text}</span>{' '} - </div> - </div> + return ( + <div className="flex w-full -ml-3"> + <div className="flex"> + <div className="ml-1 flex-auto py-0.5 text-sm leading-5 text-gray-500"> + <span className="text-gray-900">{text}</span>{" "} </div> - ); + </div> + </div> + ); }; export default FilterViewerItem; diff --git a/CommonUI/src/Components/Filters/FiltersForm.tsx b/CommonUI/src/Components/Filters/FiltersForm.tsx index 93046d518d..18734b308b 100644 --- a/CommonUI/src/Components/Filters/FiltersForm.tsx +++ b/CommonUI/src/Components/Filters/FiltersForm.tsx @@ -1,118 +1,115 @@ -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import BooleanFilter from './BooleanFilter'; -import DateFilter from './DateFilter'; -import DropdownFilter from './DropdownFilter'; -import EntityFilter from './EntityFilter'; -import JSONFilter from './JSONFilter'; -import TextFilter from './TextFilter'; -import Filter from './Types/Filter'; -import FilterData from './Types/FilterData'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import BooleanFilter from "./BooleanFilter"; +import DateFilter from "./DateFilter"; +import DropdownFilter from "./DropdownFilter"; +import EntityFilter from "./EntityFilter"; +import JSONFilter from "./JSONFilter"; +import TextFilter from "./TextFilter"; +import Filter from "./Types/Filter"; +import FilterData from "./Types/FilterData"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; export interface ComponentProps<T extends GenericObject> { - filters: Array<Filter<T>>; - id: string; - showFilter: boolean; - filterData: FilterData<T>; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - isFilterLoading?: undefined | boolean; - filterError?: string | undefined; - onFilterRefreshClick?: undefined | (() => void); + filters: Array<Filter<T>>; + id: string; + showFilter: boolean; + filterData: FilterData<T>; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + isFilterLoading?: undefined | boolean; + filterError?: string | undefined; + onFilterRefreshClick?: undefined | (() => void); } type FiltersFormFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const FiltersForm: FiltersFormFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - if (!props.showFilter) { - return <></>; + if (!props.showFilter) { + return <></>; + } + + type ChangeFilterDataFunction = (filterData: FilterData<T>) => void; + + const changeFilterData: ChangeFilterDataFunction = ( + filterData: FilterData<T>, + ): void => { + if (props.onFilterChanged) { + props.onFilterChanged(filterData); } + }; - type ChangeFilterDataFunction = (filterData: FilterData<T>) => void; + return ( + <div id={props.id}> + <div className="pt-3 pb-5"> + <div className="space-y-5"> + {props.showFilter && + !props.isFilterLoading && + !props.filterError && + props.filters && + props.filters.map((filter: Filter<T>, i: number) => { + return ( + <div key={i} className="col-span-3 sm:col-span-3 "> + <label className="block text-sm font-medium text-gray-700"> + {filter.title} + </label> - const changeFilterData: ChangeFilterDataFunction = ( - filterData: FilterData<T> - ): void => { - if (props.onFilterChanged) { - props.onFilterChanged(filterData); - } - }; + <DropdownFilter + filter={filter} + filterData={props.filterData} + onFilterChanged={changeFilterData} + /> - return ( - <div id={props.id}> - <div className="pt-3 pb-5"> - <div className="space-y-5"> - {props.showFilter && - !props.isFilterLoading && - !props.filterError && - props.filters && - props.filters.map((filter: Filter<T>, i: number) => { - return ( - <div - key={i} - className="col-span-3 sm:col-span-3 " - > - <label className="block text-sm font-medium text-gray-700"> - {filter.title} - </label> + <EntityFilter + filter={filter} + filterData={props.filterData} + onFilterChanged={changeFilterData} + /> - <DropdownFilter - filter={filter} - filterData={props.filterData} - onFilterChanged={changeFilterData} - /> + <BooleanFilter + filter={filter} + filterData={props.filterData} + onFilterChanged={changeFilterData} + /> - <EntityFilter - filter={filter} - filterData={props.filterData} - onFilterChanged={changeFilterData} - /> + <DateFilter + filter={filter} + filterData={props.filterData} + onFilterChanged={changeFilterData} + /> - <BooleanFilter - filter={filter} - filterData={props.filterData} - onFilterChanged={changeFilterData} - /> + <TextFilter + filter={filter} + filterData={props.filterData} + onFilterChanged={changeFilterData} + /> - <DateFilter - filter={filter} - filterData={props.filterData} - onFilterChanged={changeFilterData} - /> - - <TextFilter - filter={filter} - filterData={props.filterData} - onFilterChanged={changeFilterData} - /> - - <JSONFilter - filter={filter} - filterData={props.filterData} - onFilterChanged={changeFilterData} - /> - </div> - ); - })} + <JSONFilter + filter={filter} + filterData={props.filterData} + onFilterChanged={changeFilterData} + /> </div> - </div> - {props.showFilter && - props.isFilterLoading && - !props.filterError && <ComponentLoader />} - - {props.showFilter && props.filterError && ( - <ErrorMessage - error={props.filterError} - onRefreshClick={props.onFilterRefreshClick} - /> - )} + ); + })} </div> - ); + </div> + {props.showFilter && props.isFilterLoading && !props.filterError && ( + <ComponentLoader /> + )} + + {props.showFilter && props.filterError && ( + <ErrorMessage + error={props.filterError} + onRefreshClick={props.onFilterRefreshClick} + /> + )} + </div> + ); }; export default FiltersForm; diff --git a/CommonUI/src/Components/Filters/JSONFilter.tsx b/CommonUI/src/Components/Filters/JSONFilter.tsx index f59c44c1f7..464cfe3164 100644 --- a/CommonUI/src/Components/Filters/JSONFilter.tsx +++ b/CommonUI/src/Components/Filters/JSONFilter.tsx @@ -1,61 +1,55 @@ -import DictionaryForm, { ValueType } from '../Dictionary/Dictionary'; -import FieldType from '../Types/FieldType'; -import Filter from './Types/Filter'; -import FilterData from './Types/FilterData'; -import Dictionary from 'Common/Types/Dictionary'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; +import DictionaryForm, { ValueType } from "../Dictionary/Dictionary"; +import FieldType from "../Types/FieldType"; +import Filter from "./Types/Filter"; +import FilterData from "./Types/FilterData"; +import Dictionary from "Common/Types/Dictionary"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; export interface ComponentProps<T extends GenericObject> { - filter: Filter<T>; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - filterData: FilterData<T>; + filter: Filter<T>; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + filterData: FilterData<T>; } type JSONFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const JSONFilter: JSONFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const filter: Filter<T> = props.filter; - const filterData: FilterData<T> = { ...props.filterData }; + const filter: Filter<T> = props.filter; + const filterData: FilterData<T> = { ...props.filterData }; - if (filter.type === FieldType.JSON) { - return ( - <DictionaryForm - valueTypes={[ - ValueType.Text, - ValueType.Number, - ValueType.Boolean, - ]} - addButtonSuffix={filter.title} - keyPlaceholder={'Key'} - valuePlaceholder={'Value'} - initialValue={ - (filterData[filter.key] as Dictionary<string>) || {} - } - onChange={(value: Dictionary<string | number | boolean>) => { - if (!value) { - delete filterData[filter.key]; - } + if (filter.type === FieldType.JSON) { + return ( + <DictionaryForm + valueTypes={[ValueType.Text, ValueType.Number, ValueType.Boolean]} + addButtonSuffix={filter.title} + keyPlaceholder={"Key"} + valuePlaceholder={"Value"} + initialValue={(filterData[filter.key] as Dictionary<string>) || {}} + onChange={(value: Dictionary<string | number | boolean>) => { + if (!value) { + delete filterData[filter.key]; + } - // if no keys in the dictionary, remove the filter + // if no keys in the dictionary, remove the filter - if (Object.keys(value).length > 0) { - filterData[filter.key] = value; - } + if (Object.keys(value).length > 0) { + filterData[filter.key] = value; + } - if (props.onFilterChanged) { - props.onFilterChanged(filterData); - } - }} - /> - ); - } + if (props.onFilterChanged) { + props.onFilterChanged(filterData); + } + }} + /> + ); + } - return <></>; + return <></>; }; export default JSONFilter; diff --git a/CommonUI/src/Components/Filters/TextFilter.tsx b/CommonUI/src/Components/Filters/TextFilter.tsx index c7dec19151..3ce09ab577 100644 --- a/CommonUI/src/Components/Filters/TextFilter.tsx +++ b/CommonUI/src/Components/Filters/TextFilter.tsx @@ -1,104 +1,99 @@ -import Input, { InputType } from '../Input/Input'; -import FieldType from '../Types/FieldType'; -import Filter from './Types/Filter'; -import FilterData from './Types/FilterData'; -import Search from 'Common/Types/BaseDatabase/Search'; -import DatabaseDate from 'Common/Types/Database/Date'; -import OneUptimeDate from 'Common/Types/Date'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; +import Input, { InputType } from "../Input/Input"; +import FieldType from "../Types/FieldType"; +import Filter from "./Types/Filter"; +import FilterData from "./Types/FilterData"; +import Search from "Common/Types/BaseDatabase/Search"; +import DatabaseDate from "Common/Types/Database/Date"; +import OneUptimeDate from "Common/Types/Date"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; export interface ComponentProps<T extends GenericObject> { - filter: Filter<T>; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - filterData: FilterData<T>; + filter: Filter<T>; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + filterData: FilterData<T>; } type TextFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const TextFilter: TextFilterFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const filter: Filter<T> = props.filter; - const filterData: FilterData<T> = { ...props.filterData }; + const filter: Filter<T> = props.filter; + const filterData: FilterData<T> = { ...props.filterData }; - let inputType: InputType = InputType.TEXT; + let inputType: InputType = InputType.TEXT; - if (filter.type === FieldType.Date) { - inputType = InputType.DATE; - } else if (filter.type === FieldType.DateTime) { - inputType = InputType.DATETIME_LOCAL; - } + if (filter.type === FieldType.Date) { + inputType = InputType.DATE; + } else if (filter.type === FieldType.DateTime) { + inputType = InputType.DATETIME_LOCAL; + } - if ( - !filter.filterDropdownOptions && - (filter.type === FieldType.Email || - filter.type === FieldType.Phone || - filter.type === FieldType.Name || - filter.type === FieldType.Port || - filter.type === FieldType.URL || - filter.type === FieldType.ObjectID || - filter.type === FieldType.Text) - ) { - return ( - <Input - onChange={(changedValue: string | Date) => { - if (filter.key) { - if (!changedValue) { - delete filterData[filter.key]; - } + if ( + !filter.filterDropdownOptions && + (filter.type === FieldType.Email || + filter.type === FieldType.Phone || + filter.type === FieldType.Name || + filter.type === FieldType.Port || + filter.type === FieldType.URL || + filter.type === FieldType.ObjectID || + filter.type === FieldType.Text) + ) { + return ( + <Input + onChange={(changedValue: string | Date) => { + if (filter.key) { + if (!changedValue) { + delete filterData[filter.key]; + } - if ( - changedValue && - (filter.type === FieldType.Date || - filter.type === FieldType.DateTime) - ) { - filterData[filter.key] = - OneUptimeDate.asFilterDateForDatabaseQuery( - changedValue as string - ); - } + if ( + changedValue && + (filter.type === FieldType.Date || + filter.type === FieldType.DateTime) + ) { + filterData[filter.key] = + OneUptimeDate.asFilterDateForDatabaseQuery( + changedValue as string, + ); + } - if ( - changedValue && - filter.type === FieldType.DateTime - ) { - filterData[filter.key] = - DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( - changedValue - ); - } + if (changedValue && filter.type === FieldType.DateTime) { + filterData[filter.key] = + DatabaseDate.asDateStartOfTheDayEndOfTheDayForDatabaseQuery( + changedValue, + ); + } - if ( - changedValue && - (filter.type === FieldType.Text || - filter.type === FieldType.Email || - filter.type === FieldType.Phone || - filter.type === FieldType.Name || - filter.type === FieldType.Port || - filter.type === FieldType.URL || - filter.type === FieldType.ObjectID) - ) { - filterData[filter.key] = new Search( - changedValue.toString() - ); - } + if ( + changedValue && + (filter.type === FieldType.Text || + filter.type === FieldType.Email || + filter.type === FieldType.Phone || + filter.type === FieldType.Name || + filter.type === FieldType.Port || + filter.type === FieldType.URL || + filter.type === FieldType.ObjectID) + ) { + filterData[filter.key] = new Search(changedValue.toString()); + } - if (props.onFilterChanged) { - props.onFilterChanged(filterData); - } - } - }} - initialValue={(filterData[filter.key] || '').toString()} - placeholder={`Filter by ${filter.title}`} - type={inputType} - /> - ); - } + if (props.onFilterChanged) { + props.onFilterChanged(filterData); + } + } + }} + initialValue={(filterData[filter.key] || "").toString()} + placeholder={`Filter by ${filter.title}`} + type={inputType} + /> + ); + } - return <></>; + return <></>; }; export default TextFilter; diff --git a/CommonUI/src/Components/Filters/Types/Filter.ts b/CommonUI/src/Components/Filters/Types/Filter.ts index 27aedd9c5a..c3f8b73ae7 100644 --- a/CommonUI/src/Components/Filters/Types/Filter.ts +++ b/CommonUI/src/Components/Filters/Types/Filter.ts @@ -1,10 +1,10 @@ -import { DropdownOption } from '../../Dropdown/Dropdown'; -import FieldType from '../../Types/FieldType'; -import GenericObject from 'Common/Types/GenericObject'; +import { DropdownOption } from "../../Dropdown/Dropdown"; +import FieldType from "../../Types/FieldType"; +import GenericObject from "Common/Types/GenericObject"; export default interface Filter<T extends GenericObject> { - title: string; - filterDropdownOptions?: Array<DropdownOption> | undefined; - key: keyof T; - type: FieldType; + title: string; + filterDropdownOptions?: Array<DropdownOption> | undefined; + key: keyof T; + type: FieldType; } diff --git a/CommonUI/src/Components/Filters/Types/FilterData.ts b/CommonUI/src/Components/Filters/Types/FilterData.ts index 4785476a40..9c25a89044 100644 --- a/CommonUI/src/Components/Filters/Types/FilterData.ts +++ b/CommonUI/src/Components/Filters/Types/FilterData.ts @@ -1,5 +1,5 @@ -import Query from '../../../Utils/BaseDatabase/Query'; -import GenericObject from 'Common/Types/GenericObject'; +import Query from "../../../Utils/BaseDatabase/Query"; +import GenericObject from "Common/Types/GenericObject"; type FillterData<T extends GenericObject> = Query<T>; // this is bascially a Query object diff --git a/CommonUI/src/Components/Footer/Footer.tsx b/CommonUI/src/Components/Footer/Footer.tsx index feb456434c..7e9c4eaf2b 100644 --- a/CommonUI/src/Components/Footer/Footer.tsx +++ b/CommonUI/src/Components/Footer/Footer.tsx @@ -1,62 +1,60 @@ -import UILink from '../Link/Link'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import React, { FunctionComponent, ReactElement } from 'react'; +import UILink from "../Link/Link"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import React, { FunctionComponent, ReactElement } from "react"; export interface FooterLink { - onClick?: (() => void) | undefined; - openInNewTab?: boolean | undefined; - to?: Route | URL | undefined; - title: string; + onClick?: (() => void) | undefined; + openInNewTab?: boolean | undefined; + to?: Route | URL | undefined; + title: string; } export interface ComponentProps { - copyright?: string | undefined; - links: Array<FooterLink>; - style?: React.CSSProperties | undefined; - className?: string | undefined; + copyright?: string | undefined; + links: Array<FooterLink>; + style?: React.CSSProperties | undefined; + className?: string | undefined; } const Footer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <React.Fragment> - <footer - className={ - props.className || 'bg-white h-16 inset-x-0 bottom-0' - } - style={props.style} - > - <div className="mx-auto w-full py-5 px-6 md:flex md:items-center md:justify-between lg:px-0"> - <div className="flex justify-center space-x-6 md:order-2"> - {props.links && - props.links.length > 0 && - props.links.map((link: FooterLink, i: number) => { - return ( - <UILink - key={i} - className="text-gray-400 hover:text-gray-500" - to={link.to} - openInNewTab={link.openInNewTab} - onClick={link.onClick} - > - {link.title} - </UILink> - ); - })} - </div> - <div className="mt-8 md:order-1 md:mt-0"> - {props.copyright && ( - <p className="text-center text-base text-gray-400"> - © {props.copyright} - </p> - )} - </div> - </div> - </footer> - </React.Fragment> - ); + return ( + <React.Fragment> + <footer + className={props.className || "bg-white h-16 inset-x-0 bottom-0"} + style={props.style} + > + <div className="mx-auto w-full py-5 px-6 md:flex md:items-center md:justify-between lg:px-0"> + <div className="flex justify-center space-x-6 md:order-2"> + {props.links && + props.links.length > 0 && + props.links.map((link: FooterLink, i: number) => { + return ( + <UILink + key={i} + className="text-gray-400 hover:text-gray-500" + to={link.to} + openInNewTab={link.openInNewTab} + onClick={link.onClick} + > + {link.title} + </UILink> + ); + })} + </div> + <div className="mt-8 md:order-1 md:mt-0"> + {props.copyright && ( + <p className="text-center text-base text-gray-400"> + © {props.copyright} + </p> + )} + </div> + </div> + </footer> + </React.Fragment> + ); }; export default Footer; diff --git a/CommonUI/src/Components/FormModal/BasicFormModal.tsx b/CommonUI/src/Components/FormModal/BasicFormModal.tsx index 10bf1da278..581f68cc34 100644 --- a/CommonUI/src/Components/FormModal/BasicFormModal.tsx +++ b/CommonUI/src/Components/FormModal/BasicFormModal.tsx @@ -1,70 +1,68 @@ -import { ButtonStyleType } from '../Button/Button'; -import ButtonType from '../Button/ButtonTypes'; -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; +import { ButtonStyleType } from "../Button/Button"; +import ButtonType from "../Button/ButtonTypes"; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; import BasicForm, { - BaseComponentProps as BasicFormComponentProps, -} from '../Forms/BasicForm'; -import Modal from '../Modal/Modal'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement, useEffect, useRef, useState } from 'react'; + BaseComponentProps as BasicFormComponentProps, +} from "../Forms/BasicForm"; +import Modal from "../Modal/Modal"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement, useEffect, useRef, useState } from "react"; export interface ComponentProps<T extends GenericObject> { - title: string; - isLoading?: boolean | undefined; - error?: string | undefined; - onClose?: undefined | (() => void); - submitButtonText?: undefined | string; - onSubmit?: undefined | ((data: T) => void); - submitButtonStyleType?: undefined | ButtonStyleType; - formProps: BasicFormComponentProps<T>; - description?: string | undefined; + title: string; + isLoading?: boolean | undefined; + error?: string | undefined; + onClose?: undefined | (() => void); + submitButtonText?: undefined | string; + onSubmit?: undefined | ((data: T) => void); + submitButtonStyleType?: undefined | ButtonStyleType; + formProps: BasicFormComponentProps<T>; + description?: string | undefined; } const BasicFormModal: <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>( - Boolean(props.isLoading) - ); - const formRef: any = useRef<any>(null); + const [isLoading, setIsLoading] = useState<boolean>(Boolean(props.isLoading)); + const formRef: any = useRef<any>(null); - useEffect(() => { - setIsLoading(Boolean(props.isLoading)); - }, [props.isLoading]); + useEffect(() => { + setIsLoading(Boolean(props.isLoading)); + }, [props.isLoading]); - return ( - <Modal - {...props} - submitButtonType={ButtonType.Submit} - isLoading={isLoading} - onSubmit={() => { - formRef.current.submitForm(); + return ( + <Modal + {...props} + submitButtonType={ButtonType.Submit} + isLoading={isLoading} + onSubmit={() => { + formRef.current.submitForm(); + }} + > + <> + {isLoading && <ComponentLoader />} + + {props.error && <ErrorMessage error={props.error} />} + + {!isLoading && ( + <BasicForm + {...props.formProps} + hideSubmitButton={true} + ref={formRef} + onLoadingChange={(isFormLoading: boolean) => { + setIsLoading(isFormLoading); }} - > - <> - {isLoading && <ComponentLoader />} - - {props.error && <ErrorMessage error={props.error} />} - - {!isLoading && ( - <BasicForm - {...props.formProps} - hideSubmitButton={true} - ref={formRef} - onLoadingChange={(isFormLoading: boolean) => { - setIsLoading(isFormLoading); - }} - onSubmit={(data: T) => { - props.onSubmit && props.onSubmit(data); - }} - /> - )} - </> - </Modal> - ); + onSubmit={(data: T) => { + props.onSubmit && props.onSubmit(data); + }} + /> + )} + </> + </Modal> + ); }; export default BasicFormModal; diff --git a/CommonUI/src/Components/Forms/BasicForm.tsx b/CommonUI/src/Components/Forms/BasicForm.tsx index ecf05af169..366e3f5595 100644 --- a/CommonUI/src/Components/Forms/BasicForm.tsx +++ b/CommonUI/src/Components/Forms/BasicForm.tsx @@ -1,760 +1,658 @@ -import API from '../../Utils/API/API'; -import UiAnalytics from '../../Utils/Analytics'; -import Alert, { AlertType } from '../Alerts/Alert'; -import Button, { ButtonStyleType } from '../Button/Button'; -import ButtonTypes from '../Button/ButtonTypes'; -import { DropdownOption, DropdownValue } from '../Dropdown/Dropdown'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import FormField from './Fields/FormField'; -import Steps from './Steps/Steps'; -import Field from './Types/Field'; -import Fields from './Types/Fields'; -import FormFieldSchemaType from './Types/FormFieldSchemaType'; -import { FormStep } from './Types/FormStep'; -import FormValues from './Types/FormValues'; -import Validation from './Validation'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import { VoidFunction } from 'Common/Types/FunctionTypes'; -import GenericObject from 'Common/Types/GenericObject'; -import HashedString from 'Common/Types/HashedString'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Typeof from 'Common/Types/Typeof'; -import { FormikErrors, FormikProps } from 'formik'; +import API from "../../Utils/API/API"; +import UiAnalytics from "../../Utils/Analytics"; +import Alert, { AlertType } from "../Alerts/Alert"; +import Button, { ButtonStyleType } from "../Button/Button"; +import ButtonTypes from "../Button/ButtonTypes"; +import { DropdownOption, DropdownValue } from "../Dropdown/Dropdown"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import FormField from "./Fields/FormField"; +import Steps from "./Steps/Steps"; +import Field from "./Types/Field"; +import Fields from "./Types/Fields"; +import FormFieldSchemaType from "./Types/FormFieldSchemaType"; +import { FormStep } from "./Types/FormStep"; +import FormValues from "./Types/FormValues"; +import Validation from "./Validation"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import { VoidFunction } from "Common/Types/FunctionTypes"; +import GenericObject from "Common/Types/GenericObject"; +import HashedString from "Common/Types/HashedString"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Typeof from "Common/Types/Typeof"; +import { FormikErrors, FormikProps } from "formik"; import React, { - ForwardRefExoticComponent, - MutableRefObject, - ReactElement, - Ref, - forwardRef, - useEffect, - useImperativeHandle, - useRef, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + ForwardRefExoticComponent, + MutableRefObject, + ReactElement, + Ref, + forwardRef, + useEffect, + useImperativeHandle, + useRef, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; export type FormProps<T> = FormikProps<T>; export type FormErrors<T> = FormikErrors<T>; type DefaultValidateFunctionType = ( - values: FormValues<JSONObject> + values: FormValues<JSONObject>, ) => JSONObject; export const DefaultValidateFunction: DefaultValidateFunctionType = ( - _values: FormValues<JSONObject> + _values: FormValues<JSONObject>, ): JSONObject => { - return {}; + return {}; }; export interface BaseComponentProps<T extends GenericObject> { - submitButtonStyleType?: ButtonStyleType | undefined; - initialValues?: FormValues<T> | undefined; - onValidate?: undefined | ((values: FormValues<T>) => JSONObject); - onChange?: undefined | ((values: FormValues<T>) => void); - fields: Fields<T>; - steps?: undefined | Array<FormStep<T>>; - submitButtonText?: undefined | string; - title?: undefined | string; - description?: undefined | string; - showAsColumns?: undefined | number; - isLoading?: undefined | boolean; - id?: string | undefined; - name?: string | undefined; - onCancel?: undefined | (() => void) | null; - cancelButtonText?: undefined | string | null; - maxPrimaryButtonWidth?: undefined | boolean; - disableAutofocus?: undefined | boolean; - hideSubmitButton?: undefined | boolean; - error?: string | undefined; - onFormStepChange?: undefined | ((stepId: string) => void); - onIsLastFormStep?: undefined | ((isLastFormStep: boolean) => void); - onFormValidationErrorChanged?: ((hasError: boolean) => void) | undefined; - showSubmitButtonOnlyIfSomethingChanged?: boolean | undefined; + submitButtonStyleType?: ButtonStyleType | undefined; + initialValues?: FormValues<T> | undefined; + onValidate?: undefined | ((values: FormValues<T>) => JSONObject); + onChange?: undefined | ((values: FormValues<T>) => void); + fields: Fields<T>; + steps?: undefined | Array<FormStep<T>>; + submitButtonText?: undefined | string; + title?: undefined | string; + description?: undefined | string; + showAsColumns?: undefined | number; + isLoading?: undefined | boolean; + id?: string | undefined; + name?: string | undefined; + onCancel?: undefined | (() => void) | null; + cancelButtonText?: undefined | string | null; + maxPrimaryButtonWidth?: undefined | boolean; + disableAutofocus?: undefined | boolean; + hideSubmitButton?: undefined | boolean; + error?: string | undefined; + onFormStepChange?: undefined | ((stepId: string) => void); + onIsLastFormStep?: undefined | ((isLastFormStep: boolean) => void); + onFormValidationErrorChanged?: ((hasError: boolean) => void) | undefined; + showSubmitButtonOnlyIfSomethingChanged?: boolean | undefined; } export interface ComponentProps<T extends GenericObject> - extends BaseComponentProps<T> { - onSubmit: (values: FormValues<T>, onSubmitSuccessful?: () => void) => void; - footer: ReactElement; + extends BaseComponentProps<T> { + onSubmit: (values: FormValues<T>, onSubmitSuccessful?: () => void) => void; + footer: ReactElement; } const BasicForm: ForwardRefExoticComponent<any> = forwardRef( - <T extends GenericObject>( - props: ComponentProps<T>, - ref: Ref<any> - ): ReactElement => { - const isSubmitting: MutableRefObject<boolean> = useRef(false); + <T extends GenericObject>( + props: ComponentProps<T>, + ref: Ref<any>, + ): ReactElement => { + const isSubmitting: MutableRefObject<boolean> = useRef(false); - const [didSomethingChange, setDidSomethingChange] = - useState<boolean>(false); + const [didSomethingChange, setDidSomethingChange] = + useState<boolean>(false); - const [isLoading, setIsLoading] = useState<boolean | undefined>( - props.isLoading + const [isLoading, setIsLoading] = useState<boolean | undefined>( + props.isLoading, + ); + + const [formError, setFormError] = useState<string | null>(null); + + const [isDropdownOptionsLoading, setIsDropdownOptionsLoading] = + useState<boolean>(false); + + useEffect(() => { + setIsLoading(props.isLoading); + }, [props.isLoading]); + + const [submitButtonText, setSubmitButtonText] = useState<string>( + props.submitButtonText || "Submit", + ); + + const [formSteps, setFormSteps] = useState<Array<FormStep<T>> | undefined>( + props.steps, + ); + + const isInitialValuesSet: MutableRefObject<boolean> = useRef(false); + + const refCurrentValue: React.MutableRefObject<FormValues<T>> = useRef( + props.initialValues || {}, + ); + + const [currentFormStepId, setCurrentFormStepId] = useState<string | null>( + null, + ); + + useEffect(() => { + if (formSteps && formSteps.length > 0 && formSteps[0]) { + setCurrentFormStepId(formSteps[0].id); + } + }, []); + + useEffect(() => { + // if last step, + + if ( + formSteps && + formSteps.length > 0 && + ((formSteps as Array<FormStep<T>>)[formSteps.length - 1] as FormStep<T>) + .id === currentFormStepId + ) { + setSubmitButtonText(props.submitButtonText || "Submit"); + if (props.onIsLastFormStep) { + props.onIsLastFormStep(true); + } + } else { + setSubmitButtonText("Next"); + if (props.onIsLastFormStep) { + props.onIsLastFormStep(false); + } + } + + if (props.onFormStepChange && currentFormStepId) { + props.onFormStepChange(currentFormStepId); + } + + if (!currentFormStepId) { + setSubmitButtonText(props.submitButtonText || "Submit"); + if (props.onIsLastFormStep) { + props.onIsLastFormStep(true); + } + } + }, [currentFormStepId, formSteps]); + + const [currentValue, setCurrentValue] = useState<FormValues<T>>( + props.initialValues || {}, + ); + + const [errors, setErrors] = useState<Dictionary<string>>({}); + const [touched, setTouched] = useState<Dictionary<boolean>>({}); + + useEffect(() => { + setFormSteps( + props.steps?.filter((step: FormStep<T>) => { + if (!step.showIf) { + return true; + } + + return step.showIf(refCurrentValue.current); + }), + ); + }, [refCurrentValue.current]); + + const [formFields, setFormFields] = useState<Fields<T>>([]); + + const setFieldTouched: (fieldName: string, value: boolean) => void = ( + fieldName: string, + value: boolean, + ): void => { + setTouched({ ...touched, [fieldName]: value }); + }; + + const validate: (values: FormValues<T>) => Dictionary<string> = ( + values: FormValues<T>, + ): Dictionary<string> => { + const totalValidationErrors: Dictionary<string> = Validation.validate({ + values, + formFields, + currentFormStepId, + onValidate: props.onValidate || undefined, + }); + + if (props.onFormValidationErrorChanged) { + props.onFormValidationErrorChanged( + Object.keys(totalValidationErrors).length !== 0, ); - - const [formError, setFormError] = useState<string | null>(null); - - const [isDropdownOptionsLoading, setIsDropdownOptionsLoading] = - useState<boolean>(false); - - useEffect(() => { - setIsLoading(props.isLoading); - }, [props.isLoading]); - - const [submitButtonText, setSubmitButtonText] = useState<string>( - props.submitButtonText || 'Submit' - ); - - const [formSteps, setFormSteps] = useState< - Array<FormStep<T>> | undefined - >(props.steps); - - const isInitialValuesSet: MutableRefObject<boolean> = useRef(false); - - const refCurrentValue: React.MutableRefObject<FormValues<T>> = useRef( - props.initialValues || {} - ); - - const [currentFormStepId, setCurrentFormStepId] = useState< - string | null - >(null); - - useEffect(() => { - if (formSteps && formSteps.length > 0 && formSteps[0]) { - setCurrentFormStepId(formSteps[0].id); - } - }, []); - - useEffect(() => { - // if last step, - - if ( - formSteps && - formSteps.length > 0 && - ( - (formSteps as Array<FormStep<T>>)[ - formSteps.length - 1 - ] as FormStep<T> - ).id === currentFormStepId - ) { - setSubmitButtonText(props.submitButtonText || 'Submit'); - if (props.onIsLastFormStep) { - props.onIsLastFormStep(true); - } - } else { - setSubmitButtonText('Next'); - if (props.onIsLastFormStep) { - props.onIsLastFormStep(false); - } - } - - if (props.onFormStepChange && currentFormStepId) { - props.onFormStepChange(currentFormStepId); - } - - if (!currentFormStepId) { - setSubmitButtonText(props.submitButtonText || 'Submit'); - if (props.onIsLastFormStep) { - props.onIsLastFormStep(true); - } - } - }, [currentFormStepId, formSteps]); - - const [currentValue, setCurrentValue] = useState<FormValues<T>>( - props.initialValues || {} - ); - - const [errors, setErrors] = useState<Dictionary<string>>({}); - const [touched, setTouched] = useState<Dictionary<boolean>>({}); - - useEffect(() => { - setFormSteps( - props.steps?.filter((step: FormStep<T>) => { - if (!step.showIf) { - return true; - } - - return step.showIf(refCurrentValue.current); - }) - ); - }, [refCurrentValue.current]); - - const [formFields, setFormFields] = useState<Fields<T>>([]); - - const setFieldTouched: (fieldName: string, value: boolean) => void = ( - fieldName: string, - value: boolean - ): void => { - setTouched({ ...touched, [fieldName]: value }); - }; - - const validate: (values: FormValues<T>) => Dictionary<string> = ( - values: FormValues<T> - ): Dictionary<string> => { - const totalValidationErrors: Dictionary<string> = - Validation.validate({ - values, - formFields, - currentFormStepId, - onValidate: props.onValidate || undefined, - }); - - if (props.onFormValidationErrorChanged) { - props.onFormValidationErrorChanged( - Object.keys(totalValidationErrors).length !== 0 - ); - } - - setErrors(totalValidationErrors); - - return totalValidationErrors; - }; - - useEffect(() => { - setDidSomethingChange(true); - validate(currentValue); - }, [currentValue]); - - useImperativeHandle( - ref, - () => { - return { - setFieldTouched, - setFieldValue, - submitForm, - }; - }, - [currentValue, errors, touched, formFields] - ); - - useAsyncEffect(async () => { - const fields: Fields<T> = [ - ...props.fields.map((field: Field<T>) => { - return { - name: getFieldName(field), - ...field, - }; - }), - ]; - - for (const item of fields) { - // if this field is not the current step. - if ( - currentFormStepId && - item.stepId && - item.stepId !== currentFormStepId - ) { - continue; - } - - if (item.fetchDropdownOptions) { - setIsDropdownOptionsLoading(true); - // If a dropdown has fetch optiosn then we need to fetch them - try { - const options: Array<DropdownOption> = - await item.fetchDropdownOptions( - refCurrentValue.current - ); - item.dropdownOptions = options; - } catch (err) { - setFormError(API.getFriendlyMessage(err)); - } - } - } - - setIsDropdownOptionsLoading(false); - - setFormFields(fields); - }, [props.fields, currentFormStepId]); - - type GetFieldNameFunction = (field: Field<T>) => string; - - const getFieldName: GetFieldNameFunction = ( - field: Field<T> - ): string => { - const fieldName: string = field.overrideFieldKey - ? field.overrideFieldKey - : (Object.keys(field.field || {})[0] as string); - - return fieldName; - }; - - const setAllTouched: VoidFunction = (): void => { - const touchedObj: Dictionary<boolean> = {}; - - for (const field of formFields) { - if ( - currentFormStepId && - field.stepId && - field.stepId !== currentFormStepId - ) { - continue; - } - - touchedObj[field.name!] = true; - } - - setTouched({ ...touched, ...touchedObj }); - }; - - const setFieldValue: (fieldName: string, value: JSONValue) => void = ( - fieldName: string, - value: JSONValue - ): void => { - const updatedValue: FormValues<T> = { - ...refCurrentValue.current, - [fieldName]: value as any, - }; - - refCurrentValue.current = updatedValue; - - setCurrentValue(refCurrentValue.current); - - if (props.onChange && isInitialValuesSet.current) { - props.onChange(refCurrentValue.current); - } - }; - - const submitForm: () => void = (): void => { - // check for any boolean values and if they don't exist in values - mark them as false. - - setAllTouched(); - - const validationErrors: Dictionary<string> = validate( - refCurrentValue.current - ); - - isSubmitting.current = true; - - if (Object.keys(validationErrors).length > 0) { - // errors on form, do not submit. - return; - } - - // if last step then submit. - - if ( - (formSteps && - formSteps.length > 0 && - ( - (formSteps as Array<FormStep<T>>)[ - formSteps.length - 1 - ] as FormStep<T> - ).id === currentFormStepId) || - currentFormStepId === null - ) { - const values: FormValues<T> = refCurrentValue.current; - - for (const field of formFields) { - if (field.fieldType === FormFieldSchemaType.Toggle) { - const fieldName: string = field.name!; - if (!(values as any)[fieldName]) { - (values as any)[fieldName] = false; - } - } - - if (field.fieldType === FormFieldSchemaType.Email) { - const fieldName: string = field.name!; - if ((values as any)[fieldName]) { - (values as any)[fieldName] = ( - (values as any)[fieldName] as string - ) - .toString() - .toLowerCase(); - } - } - - if ( - field.fieldType === - FormFieldSchemaType.MultiSelectDropdown - ) { - const fieldName: string = field.name!; - - if ( - (values as any)[fieldName] && - (values as any)[fieldName].length > 0 && - (values as any)[fieldName][0]['value'] - ) { - (values as any)[fieldName] = ( - (values as any)[ - fieldName - ] as Array<DropdownOption> - ).map((item: DropdownOption) => { - return item.value; - }); - } - } - - if (field.fieldType === FormFieldSchemaType.Dropdown) { - const fieldName: string = field.name!; - if ( - (values as any)[fieldName] && - (values as any)[fieldName]['value'] - ) { - (values as any)[fieldName] = (values as any)[ - fieldName - ]['value']; - } - } - - if (field.fieldType === FormFieldSchemaType.Password) { - const fieldName: string = field.name!; - if ( - (values as any)[fieldName] && - typeof (values as any)[fieldName] === Typeof.String - ) { - (values as any)[fieldName] = new HashedString( - (values as any)[fieldName], - false - ); - } - } - } - - UiAnalytics.capture('FORM SUBMIT: ' + props.name); - - props.onSubmit(values, () => { - setDidSomethingChange(false); - }); - } else if (formSteps && formSteps.length > 0) { - const steps: Array<FormStep<T>> = formSteps; - - const currentStepIndex: number = steps.findIndex( - (step: FormStep<T>) => { - return step.id === currentFormStepId; - } - ); - - if (currentStepIndex > -1) { - setCurrentFormStepId( - (steps[currentStepIndex + 1] as FormStep<T>).id - ); - } - } - }; - - useEffect(() => { - if (isSubmitting.current) { - return; - } - - if (isInitialValuesSet.current) { - return; - } - - const values: FormValues<T> = { - ...props.initialValues, - } as FormValues<T>; - for (const field of formFields) { - const fieldName: string = field.name!; - - if ( - field.fieldType === FormFieldSchemaType.Date && - (values as any)[fieldName] - ) { - (values as any)[fieldName] = - OneUptimeDate.asDateForDatabaseQuery( - (values as any)[fieldName] - ); - } - - if ( - field.fieldType === FormFieldSchemaType.Dropdown && - (values as any)[fieldName] - ) { - const dropdownOption: DropdownOption | undefined = - field.dropdownOptions?.find( - (option: DropdownOption) => { - let valueToCompare: DropdownValue = ( - values as any - )[fieldName]; - - if ( - (valueToCompare as any) instanceof ObjectID - ) { - valueToCompare = valueToCompare.toString(); - } - - return option.value === valueToCompare; - } - ); - - (values as any)[fieldName] = dropdownOption?.value || null; - } - - if ( - field.fieldType === - FormFieldSchemaType.MultiSelectDropdown && - (values as any)[fieldName] - ) { - const dropdownOptions: Array<DropdownOption> = - field.dropdownOptions?.filter( - (option: DropdownOption) => { - let valueToCompare: Array<DropdownValue> = [ - ...(values as any)[fieldName], - ]; - - valueToCompare = valueToCompare.map( - (item: DropdownValue) => { - if ((item as any) instanceof ObjectID) { - return item.toString(); - } - - return item; - } - ); - - return valueToCompare.includes(option.value); - } - ) || []; - - (values as any)[fieldName] = dropdownOptions.map( - (option: DropdownOption) => { - return option.value; - } - ); - } - - // if the field is still null but has a default value then... have the default initial value - if ( - field.defaultValue && - (values as any)[fieldName] === undefined - ) { - (values as any)[fieldName] = field.defaultValue; - } - - isInitialValuesSet.current = true; - } - - refCurrentValue.current = values; - setCurrentValue(refCurrentValue.current); - }, [props.initialValues, formFields]); - - const primaryButtonStyle: React.CSSProperties = {}; - - if (props.maxPrimaryButtonWidth) { - primaryButtonStyle.marginLeft = '0px'; - primaryButtonStyle.width = '100%'; + } + + setErrors(totalValidationErrors); + + return totalValidationErrors; + }; + + useEffect(() => { + setDidSomethingChange(true); + validate(currentValue); + }, [currentValue]); + + useImperativeHandle(ref, () => { + return { + setFieldTouched, + setFieldValue, + submitForm, + }; + }, [currentValue, errors, touched, formFields]); + + useAsyncEffect(async () => { + const fields: Fields<T> = [ + ...props.fields.map((field: Field<T>) => { + return { + name: getFieldName(field), + ...field, + }; + }), + ]; + + for (const item of fields) { + // if this field is not the current step. + if ( + currentFormStepId && + item.stepId && + item.stepId !== currentFormStepId + ) { + continue; } - if (formError) { - return <ErrorMessage error={formError} />; + if (item.fetchDropdownOptions) { + setIsDropdownOptionsLoading(true); + // If a dropdown has fetch optiosn then we need to fetch them + try { + const options: Array<DropdownOption> = + await item.fetchDropdownOptions(refCurrentValue.current); + item.dropdownOptions = options; + } catch (err) { + setFormError(API.getFriendlyMessage(err)); + } + } + } + + setIsDropdownOptionsLoading(false); + + setFormFields(fields); + }, [props.fields, currentFormStepId]); + + type GetFieldNameFunction = (field: Field<T>) => string; + + const getFieldName: GetFieldNameFunction = (field: Field<T>): string => { + const fieldName: string = field.overrideFieldKey + ? field.overrideFieldKey + : (Object.keys(field.field || {})[0] as string); + + return fieldName; + }; + + const setAllTouched: VoidFunction = (): void => { + const touchedObj: Dictionary<boolean> = {}; + + for (const field of formFields) { + if ( + currentFormStepId && + field.stepId && + field.stepId !== currentFormStepId + ) { + continue; } - let showSubmitButton: boolean = !props.hideSubmitButton; + touchedObj[field.name!] = true; + } + + setTouched({ ...touched, ...touchedObj }); + }; + + const setFieldValue: (fieldName: string, value: JSONValue) => void = ( + fieldName: string, + value: JSONValue, + ): void => { + const updatedValue: FormValues<T> = { + ...refCurrentValue.current, + [fieldName]: value as any, + }; + + refCurrentValue.current = updatedValue; + + setCurrentValue(refCurrentValue.current); + + if (props.onChange && isInitialValuesSet.current) { + props.onChange(refCurrentValue.current); + } + }; + + const submitForm: () => void = (): void => { + // check for any boolean values and if they don't exist in values - mark them as false. + + setAllTouched(); + + const validationErrors: Dictionary<string> = validate( + refCurrentValue.current, + ); + + isSubmitting.current = true; + + if (Object.keys(validationErrors).length > 0) { + // errors on form, do not submit. + return; + } + + // if last step then submit. + + if ( + (formSteps && + formSteps.length > 0 && + ( + (formSteps as Array<FormStep<T>>)[ + formSteps.length - 1 + ] as FormStep<T> + ).id === currentFormStepId) || + currentFormStepId === null + ) { + const values: FormValues<T> = refCurrentValue.current; + + for (const field of formFields) { + if (field.fieldType === FormFieldSchemaType.Toggle) { + const fieldName: string = field.name!; + if (!(values as any)[fieldName]) { + (values as any)[fieldName] = false; + } + } + + if (field.fieldType === FormFieldSchemaType.Email) { + const fieldName: string = field.name!; + if ((values as any)[fieldName]) { + (values as any)[fieldName] = ( + (values as any)[fieldName] as string + ) + .toString() + .toLowerCase(); + } + } + + if (field.fieldType === FormFieldSchemaType.MultiSelectDropdown) { + const fieldName: string = field.name!; + + if ( + (values as any)[fieldName] && + (values as any)[fieldName].length > 0 && + (values as any)[fieldName][0]["value"] + ) { + (values as any)[fieldName] = ( + (values as any)[fieldName] as Array<DropdownOption> + ).map((item: DropdownOption) => { + return item.value; + }); + } + } + + if (field.fieldType === FormFieldSchemaType.Dropdown) { + const fieldName: string = field.name!; + if ( + (values as any)[fieldName] && + (values as any)[fieldName]["value"] + ) { + (values as any)[fieldName] = (values as any)[fieldName]["value"]; + } + } + + if (field.fieldType === FormFieldSchemaType.Password) { + const fieldName: string = field.name!; + if ( + (values as any)[fieldName] && + typeof (values as any)[fieldName] === Typeof.String + ) { + (values as any)[fieldName] = new HashedString( + (values as any)[fieldName], + false, + ); + } + } + } + + UiAnalytics.capture("FORM SUBMIT: " + props.name); + + props.onSubmit(values, () => { + setDidSomethingChange(false); + }); + } else if (formSteps && formSteps.length > 0) { + const steps: Array<FormStep<T>> = formSteps; + + const currentStepIndex: number = steps.findIndex( + (step: FormStep<T>) => { + return step.id === currentFormStepId; + }, + ); + + if (currentStepIndex > -1) { + setCurrentFormStepId((steps[currentStepIndex + 1] as FormStep<T>).id); + } + } + }; + + useEffect(() => { + if (isSubmitting.current) { + return; + } + + if (isInitialValuesSet.current) { + return; + } + + const values: FormValues<T> = { + ...props.initialValues, + } as FormValues<T>; + for (const field of formFields) { + const fieldName: string = field.name!; if ( - props.showSubmitButtonOnlyIfSomethingChanged && - didSomethingChange + field.fieldType === FormFieldSchemaType.Date && + (values as any)[fieldName] ) { - showSubmitButton = true; + (values as any)[fieldName] = OneUptimeDate.asDateForDatabaseQuery( + (values as any)[fieldName], + ); } - return ( - <div className="row"> - <div className="col-lg-1"> - <div> - {props.title && ( - <h1 className="text-lg text-gray-700 mt-5"> - {props.title} - </h1> - )} + if ( + field.fieldType === FormFieldSchemaType.Dropdown && + (values as any)[fieldName] + ) { + const dropdownOption: DropdownOption | undefined = + field.dropdownOptions?.find((option: DropdownOption) => { + let valueToCompare: DropdownValue = (values as any)[fieldName]; - {Boolean(props.description) && ( - <div className="text-sm text-gray-500 mb-5"> - {props.description} - </div> - )} + if ((valueToCompare as any) instanceof ObjectID) { + valueToCompare = valueToCompare.toString(); + } - <div className="flex"> - {formSteps && currentFormStepId && ( - <div className="w-1/3"> - {/* Form Steps */} + return option.value === valueToCompare; + }); - <Steps - currentFormStepId={currentFormStepId} - steps={formSteps} - formValues={refCurrentValue.current} - onClick={(step: FormStep<T>) => { - setCurrentFormStepId(step.id); - }} - /> - </div> - )} - <div - className={`${ - formSteps && currentFormStepId - ? 'w-2/3 pt-6' - : 'w-full pt-1' - }`} - > - {props.error && ( - <div className="mb-3"> - <Alert - title={props.error} - type={AlertType.DANGER} - /> - </div> - )} + (values as any)[fieldName] = dropdownOption?.value || null; + } - <div> - <div - className={`grid md:grid-cols-${ - props.showAsColumns || 1 - } grid-cols-1 gap-4`} - > - {formFields && - formFields - .filter((field: Field<T>) => { - if (currentFormStepId) { - return ( - field.stepId === - currentFormStepId - ); - } + if ( + field.fieldType === FormFieldSchemaType.MultiSelectDropdown && + (values as any)[fieldName] + ) { + const dropdownOptions: Array<DropdownOption> = + field.dropdownOptions?.filter((option: DropdownOption) => { + let valueToCompare: Array<DropdownValue> = [ + ...(values as any)[fieldName], + ]; - return true; - }) - .map( - ( - field: Field<T>, - i: number - ) => { - return ( - <div - key={getFieldName( - field - )} - > - { - <FormField<T> - field={ - field - } - fieldName={getFieldName( - field - )} - index={ - i - } - error={ - errors[ - getFieldName( - field - ) - ] || - '' - } - touched={ - touched[ - getFieldName( - field - ) - ] || - false - } - isDisabled={ - isLoading || - isDropdownOptionsLoading || - false - } - currentValues={ - refCurrentValue.current - } - setFieldValue={ - setFieldValue - } - setFieldTouched={ - setFieldTouched - } - submitForm={ - submitForm - } - disableAutofocus={ - props.disableAutofocus || - false - } - /> - } - { - field.footerElement - } - </div> - ); - } - )} - </div> - </div> + valueToCompare = valueToCompare.map((item: DropdownValue) => { + if ((item as any) instanceof ObjectID) { + return item.toString(); + } - <div className="flex w-full justify-end"> - {showSubmitButton && ( - <div - className="mt-3" - style={{ - width: props.maxPrimaryButtonWidth - ? '100%' - : ' auto', - }} - > - <Button - title={submitButtonText} - dataTestId={ - props.submitButtonText! - } - onClick={() => { - submitForm(); - }} - id={`${props.id}-submit-button`} - isLoading={ - isLoading || - isDropdownOptionsLoading || - false - } - buttonStyle={ - props.submitButtonStyleType || - ButtonStyleType.PRIMARY - } - style={primaryButtonStyle} - /> - </div> - )} - {props.onCancel && ( - <div> - <Button - title={ - props.cancelButtonText || - 'Cancel' - } - type={ButtonTypes.Button} - id={`${props.id}-cancel-button`} - disabled={ - isLoading || - isDropdownOptionsLoading || - false - } - buttonStyle={ - ButtonStyleType.NORMAL - } - onClick={() => { - props.onCancel && - props.onCancel(); - }} - /> - </div> - )} - </div> - </div> - </div> - {props.footer} - </div> - </div> - </div> - ); + return item; + }); + + return valueToCompare.includes(option.value); + }) || []; + + (values as any)[fieldName] = dropdownOptions.map( + (option: DropdownOption) => { + return option.value; + }, + ); + } + + // if the field is still null but has a default value then... have the default initial value + if (field.defaultValue && (values as any)[fieldName] === undefined) { + (values as any)[fieldName] = field.defaultValue; + } + + isInitialValuesSet.current = true; + } + + refCurrentValue.current = values; + setCurrentValue(refCurrentValue.current); + }, [props.initialValues, formFields]); + + const primaryButtonStyle: React.CSSProperties = {}; + + if (props.maxPrimaryButtonWidth) { + primaryButtonStyle.marginLeft = "0px"; + primaryButtonStyle.width = "100%"; } + + if (formError) { + return <ErrorMessage error={formError} />; + } + + let showSubmitButton: boolean = !props.hideSubmitButton; + + if (props.showSubmitButtonOnlyIfSomethingChanged && didSomethingChange) { + showSubmitButton = true; + } + + return ( + <div className="row"> + <div className="col-lg-1"> + <div> + {props.title && ( + <h1 className="text-lg text-gray-700 mt-5">{props.title}</h1> + )} + + {Boolean(props.description) && ( + <div className="text-sm text-gray-500 mb-5"> + {props.description} + </div> + )} + + <div className="flex"> + {formSteps && currentFormStepId && ( + <div className="w-1/3"> + {/* Form Steps */} + + <Steps + currentFormStepId={currentFormStepId} + steps={formSteps} + formValues={refCurrentValue.current} + onClick={(step: FormStep<T>) => { + setCurrentFormStepId(step.id); + }} + /> + </div> + )} + <div + className={`${ + formSteps && currentFormStepId ? "w-2/3 pt-6" : "w-full pt-1" + }`} + > + {props.error && ( + <div className="mb-3"> + <Alert title={props.error} type={AlertType.DANGER} /> + </div> + )} + + <div> + <div + className={`grid md:grid-cols-${ + props.showAsColumns || 1 + } grid-cols-1 gap-4`} + > + {formFields && + formFields + .filter((field: Field<T>) => { + if (currentFormStepId) { + return field.stepId === currentFormStepId; + } + + return true; + }) + .map((field: Field<T>, i: number) => { + return ( + <div key={getFieldName(field)}> + { + <FormField<T> + field={field} + fieldName={getFieldName(field)} + index={i} + error={errors[getFieldName(field)] || ""} + touched={ + touched[getFieldName(field)] || false + } + isDisabled={ + isLoading || + isDropdownOptionsLoading || + false + } + currentValues={refCurrentValue.current} + setFieldValue={setFieldValue} + setFieldTouched={setFieldTouched} + submitForm={submitForm} + disableAutofocus={ + props.disableAutofocus || false + } + /> + } + {field.footerElement} + </div> + ); + })} + </div> + </div> + + <div className="flex w-full justify-end"> + {showSubmitButton && ( + <div + className="mt-3" + style={{ + width: props.maxPrimaryButtonWidth ? "100%" : " auto", + }} + > + <Button + title={submitButtonText} + dataTestId={props.submitButtonText!} + onClick={() => { + submitForm(); + }} + id={`${props.id}-submit-button`} + isLoading={ + isLoading || isDropdownOptionsLoading || false + } + buttonStyle={ + props.submitButtonStyleType || ButtonStyleType.PRIMARY + } + style={primaryButtonStyle} + /> + </div> + )} + {props.onCancel && ( + <div> + <Button + title={props.cancelButtonText || "Cancel"} + type={ButtonTypes.Button} + id={`${props.id}-cancel-button`} + disabled={ + isLoading || isDropdownOptionsLoading || false + } + buttonStyle={ButtonStyleType.NORMAL} + onClick={() => { + props.onCancel && props.onCancel(); + }} + /> + </div> + )} + </div> + </div> + </div> + {props.footer} + </div> + </div> + </div> + ); + }, ); -BasicForm.displayName = 'BasicForm'; +BasicForm.displayName = "BasicForm"; export default BasicForm; diff --git a/CommonUI/src/Components/Forms/BasicModelForm.tsx b/CommonUI/src/Components/Forms/BasicModelForm.tsx index a86fc6947e..5f465b12a5 100644 --- a/CommonUI/src/Components/Forms/BasicModelForm.tsx +++ b/CommonUI/src/Components/Forms/BasicModelForm.tsx @@ -1,133 +1,128 @@ -import { ButtonStyleType } from '../Button/Button'; +import { ButtonStyleType } from "../Button/Button"; import BasicForm, { - DefaultValidateFunction, - FormErrors, - FormProps, -} from './BasicForm'; -import Fields from './Types/Fields'; -import { FormStep } from './Types/FormStep'; -import FormValues from './Types/FormValues'; -import BaseModel from 'Common/Models/BaseModel'; + DefaultValidateFunction, + FormErrors, + FormProps, +} from "./BasicForm"; +import Fields from "./Types/Fields"; +import { FormStep } from "./Types/FormStep"; +import FormValues from "./Types/FormValues"; +import BaseModel from "Common/Models/BaseModel"; import React, { - MutableRefObject, - ReactElement, - useEffect, - useState, -} from 'react'; + MutableRefObject, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - model: TBaseModel; - id: string; - onSubmit: ( - values: FormValues<TBaseModel>, - onSubmitSuccessful: () => void - ) => void; - onChange?: undefined | ((values: FormValues<TBaseModel>) => void); - onValidate?: - | undefined - | (( - values: FormValues<TBaseModel> - ) => FormErrors<FormValues<TBaseModel>>); - fields: Fields<TBaseModel>; - submitButtonText?: undefined | string; - submitButtonStyleType?: ButtonStyleType | undefined; - name?: string | undefined; - steps?: undefined | Array<FormStep<TBaseModel>>; - onIsLastFormStep?: undefined | ((isLastFormStep: boolean) => void); - onFormStepChange?: undefined | ((stepId: string) => void); - title?: undefined | string; - description?: undefined | string; - showAsColumns?: undefined | number; - disableAutofocus?: undefined | boolean; - footer?: ReactElement | undefined; - isLoading?: undefined | boolean; - onCancel?: undefined | (() => void); - cancelButtonText?: undefined | string; - maxPrimaryButtonWidth?: undefined | boolean; - error: string | null; - hideSubmitButton?: undefined | boolean; - formRef?: undefined | MutableRefObject<FormProps<FormValues<TBaseModel>>>; - initialValues?: FormValues<TBaseModel> | undefined; + model: TBaseModel; + id: string; + onSubmit: ( + values: FormValues<TBaseModel>, + onSubmitSuccessful: () => void, + ) => void; + onChange?: undefined | ((values: FormValues<TBaseModel>) => void); + onValidate?: + | undefined + | ((values: FormValues<TBaseModel>) => FormErrors<FormValues<TBaseModel>>); + fields: Fields<TBaseModel>; + submitButtonText?: undefined | string; + submitButtonStyleType?: ButtonStyleType | undefined; + name?: string | undefined; + steps?: undefined | Array<FormStep<TBaseModel>>; + onIsLastFormStep?: undefined | ((isLastFormStep: boolean) => void); + onFormStepChange?: undefined | ((stepId: string) => void); + title?: undefined | string; + description?: undefined | string; + showAsColumns?: undefined | number; + disableAutofocus?: undefined | boolean; + footer?: ReactElement | undefined; + isLoading?: undefined | boolean; + onCancel?: undefined | (() => void); + cancelButtonText?: undefined | string; + maxPrimaryButtonWidth?: undefined | boolean; + error: string | null; + hideSubmitButton?: undefined | boolean; + formRef?: undefined | MutableRefObject<FormProps<FormValues<TBaseModel>>>; + initialValues?: FormValues<TBaseModel> | undefined; } const BasicModelForm: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [formFields, setFormFields] = useState<Fields<TBaseModel>>([]); + const [formFields, setFormFields] = useState<Fields<TBaseModel>>([]); - let initialValues: FormValues<TBaseModel> = {}; + let initialValues: FormValues<TBaseModel> = {}; - if (props.initialValues) { - initialValues = { ...props.initialValues }; - } + if (props.initialValues) { + initialValues = { ...props.initialValues }; + } - useEffect(() => { - const fields: Fields<TBaseModel> = []; - // Prep - for (const field of props.fields) { - if (Object.keys(field.field || {}).length > 0) { - if ( - props.model.getDisplayColumnTitleAs( - Object.keys(field.field || {})[0] as string - ) && - !field.title - ) { - field.title = props.model.getDisplayColumnTitleAs( - Object.keys(field.field || {})[0] as string - ) as string; - } - - if ( - props.model.getDisplayColumnDescriptionAs( - Object.keys(field.field || {})[0] as string - ) && - !field.description - ) { - field.description = - props.model.getDisplayColumnDescriptionAs( - Object.keys(field.field || {})[0] as string - ) as string; - } - } - - fields.push(field); + useEffect(() => { + const fields: Fields<TBaseModel> = []; + // Prep + for (const field of props.fields) { + if (Object.keys(field.field || {}).length > 0) { + if ( + props.model.getDisplayColumnTitleAs( + Object.keys(field.field || {})[0] as string, + ) && + !field.title + ) { + field.title = props.model.getDisplayColumnTitleAs( + Object.keys(field.field || {})[0] as string, + ) as string; } - setFormFields(fields); - }, [props.fields]); + if ( + props.model.getDisplayColumnDescriptionAs( + Object.keys(field.field || {})[0] as string, + ) && + !field.description + ) { + field.description = props.model.getDisplayColumnDescriptionAs( + Object.keys(field.field || {})[0] as string, + ) as string; + } + } - return ( - <BasicForm - isLoading={props.isLoading || false} - fields={formFields} - id={props.id} - onChange={props.onChange} - onValidate={ - props.onValidate ? props.onValidate : DefaultValidateFunction - } - disableAutofocus={props.disableAutofocus} - steps={props.steps} - name={props.name} - onFormStepChange={props.onFormStepChange} - submitButtonStyleType={props.submitButtonStyleType} - onSubmit={props.onSubmit} - initialValues={initialValues} - submitButtonText={props.submitButtonText || 'Save'} - title={props.title || ''} - description={props.description || ''} - footer={props.footer} - showAsColumns={props.showAsColumns || 1} - onCancel={props.onCancel} - cancelButtonText={props.cancelButtonText} - maxPrimaryButtonWidth={props.maxPrimaryButtonWidth || false} - error={props.error} - onIsLastFormStep={props.onIsLastFormStep} - hideSubmitButton={props.hideSubmitButton} - ref={props.formRef} - ></BasicForm> - ); + fields.push(field); + } + + setFormFields(fields); + }, [props.fields]); + + return ( + <BasicForm + isLoading={props.isLoading || false} + fields={formFields} + id={props.id} + onChange={props.onChange} + onValidate={props.onValidate ? props.onValidate : DefaultValidateFunction} + disableAutofocus={props.disableAutofocus} + steps={props.steps} + name={props.name} + onFormStepChange={props.onFormStepChange} + submitButtonStyleType={props.submitButtonStyleType} + onSubmit={props.onSubmit} + initialValues={initialValues} + submitButtonText={props.submitButtonText || "Save"} + title={props.title || ""} + description={props.description || ""} + footer={props.footer} + showAsColumns={props.showAsColumns || 1} + onCancel={props.onCancel} + cancelButtonText={props.cancelButtonText} + maxPrimaryButtonWidth={props.maxPrimaryButtonWidth || false} + error={props.error} + onIsLastFormStep={props.onIsLastFormStep} + hideSubmitButton={props.hideSubmitButton} + ref={props.formRef} + ></BasicForm> + ); }; export default BasicModelForm; diff --git a/CommonUI/src/Components/Forms/Fields/ColorPicker.tsx b/CommonUI/src/Components/Forms/Fields/ColorPicker.tsx index 78be41d8e2..798ceeb4ef 100644 --- a/CommonUI/src/Components/Forms/Fields/ColorPicker.tsx +++ b/CommonUI/src/Components/Forms/Fields/ColorPicker.tsx @@ -1,138 +1,135 @@ -import useComponentOutsideClick from '../../../Types/UseComponentOutsideClick'; -import Icon from '../../Icon/Icon'; -import Input, { InputType } from '../../Input/Input'; -import Color from 'Common/Types/Color'; -import IconProp from 'Common/Types/Icon/IconProp'; +import useComponentOutsideClick from "../../../Types/UseComponentOutsideClick"; +import Icon from "../../Icon/Icon"; +import Input, { InputType } from "../../Input/Input"; +import Color from "Common/Types/Color"; +import IconProp from "Common/Types/Icon/IconProp"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import { ChromePicker, ColorResult } from 'react-color'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import { ChromePicker, ColorResult } from "react-color"; export interface ComponentProps { - onChange: (value: Color | null) => void; - initialValue?: undefined | Color; - placeholder: string; - onFocus?: (() => void) | undefined; - tabIndex?: number | undefined; - value?: string | undefined; - readOnly?: boolean | undefined; - disabled?: boolean | undefined; - onBlur?: (() => void) | undefined; - dataTestId?: string | undefined; - onEnterPress?: (() => void) | undefined; - error?: string | undefined; + onChange: (value: Color | null) => void; + initialValue?: undefined | Color; + placeholder: string; + onFocus?: (() => void) | undefined; + tabIndex?: number | undefined; + value?: string | undefined; + readOnly?: boolean | undefined; + disabled?: boolean | undefined; + onBlur?: (() => void) | undefined; + dataTestId?: string | undefined; + onEnterPress?: (() => void) | undefined; + error?: string | undefined; } const ColorPicker: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [color, setColor] = useState<string>(''); - const { ref, isComponentVisible, setIsComponentVisible } = - useComponentOutsideClick(false); + const [color, setColor] = useState<string>(""); + const { ref, isComponentVisible, setIsComponentVisible } = + useComponentOutsideClick(false); - const [isInitialValuesInitialized, setIsInitialValuesInitialized] = - useState<boolean>(false); + const [isInitialValuesInitialized, setIsInitialValuesInitialized] = + useState<boolean>(false); - useEffect(() => { - if (props.initialValue && !isInitialValuesInitialized) { - setColor(props.initialValue.toString()); - setIsInitialValuesInitialized(true); - } - }, [props.initialValue]); + useEffect(() => { + if (props.initialValue && !isInitialValuesInitialized) { + setColor(props.initialValue.toString()); + setIsInitialValuesInitialized(true); + } + }, [props.initialValue]); - type HandleChangeFunction = (color: string) => void; + type HandleChangeFunction = (color: string) => void; - const handleChange: HandleChangeFunction = (color: string): void => { - setColor(color); - if (!color) { - return props.onChange(null); - } - props.onChange(new Color(color)); - }; + const handleChange: HandleChangeFunction = (color: string): void => { + setColor(color); + if (!color) { + return props.onChange(null); + } + props.onChange(new Color(color)); + }; - return ( - <div> - <div className="flex block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"> - <div - onClick={() => { - if (!props.readOnly) { - setIsComponentVisible(!isComponentVisible); - } - }} - className="rounded h-5 w-5 border border-gray-200 cursor-pointer" - style={{ backgroundColor: color.toString() }} - ></div> + return ( + <div> + <div className="flex block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"> + <div + onClick={() => { + if (!props.readOnly) { + setIsComponentVisible(!isComponentVisible); + } + }} + className="rounded h-5 w-5 border border-gray-200 cursor-pointer" + style={{ backgroundColor: color.toString() }} + ></div> - <Input - onClick={() => { - if (!props.readOnly) { - setIsComponentVisible(!isComponentVisible); - } - }} - disabled={props.disabled} - dataTestId={props.dataTestId} - onBlur={props.onBlur} - onEnterPress={props.onEnterPress} - className="border-none focus:outline-none w-full pl-2 text-gray-500 cursor-pointer" - outerDivClassName='className="border-none focus:outline-none w-full pl-2 text-gray-500 cursor-pointer"' - placeholder={props.placeholder} - value={color || props.value} - readOnly={true} - type={InputType.TEXT} - tabIndex={props.tabIndex} - onChange={(value: string) => { - if (!value) { - return handleChange(''); - } - }} - onFocus={props.onFocus || undefined} - /> - {color && !props.disabled && ( - <Icon - icon={IconProp.Close} - className="text-gray-400 h-5 w-5 cursor-pointer hover:text-gray-600" - onClick={() => { - setColor('#FFFFFF'); - if (props.onChange) { - props.onChange(null); - } - }} - /> - )} - {isComponentVisible ? ( - <div - ref={ref} - style={{ - position: 'absolute', - }} - > - <ChromePicker - color={color} - onChange={(color: ColorResult) => { - setColor(color.hex); - }} - onChangeComplete={(color: ColorResult) => { - return handleChange(color.hex); - }} - /> - </div> - ) : ( - <></> - )} - </div> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} - </div> - ); + <Input + onClick={() => { + if (!props.readOnly) { + setIsComponentVisible(!isComponentVisible); + } + }} + disabled={props.disabled} + dataTestId={props.dataTestId} + onBlur={props.onBlur} + onEnterPress={props.onEnterPress} + className="border-none focus:outline-none w-full pl-2 text-gray-500 cursor-pointer" + outerDivClassName='className="border-none focus:outline-none w-full pl-2 text-gray-500 cursor-pointer"' + placeholder={props.placeholder} + value={color || props.value} + readOnly={true} + type={InputType.TEXT} + tabIndex={props.tabIndex} + onChange={(value: string) => { + if (!value) { + return handleChange(""); + } + }} + onFocus={props.onFocus || undefined} + /> + {color && !props.disabled && ( + <Icon + icon={IconProp.Close} + className="text-gray-400 h-5 w-5 cursor-pointer hover:text-gray-600" + onClick={() => { + setColor("#FFFFFF"); + if (props.onChange) { + props.onChange(null); + } + }} + /> + )} + {isComponentVisible ? ( + <div + ref={ref} + style={{ + position: "absolute", + }} + > + <ChromePicker + color={color} + onChange={(color: ColorResult) => { + setColor(color.hex); + }} + onChangeComplete={(color: ColorResult) => { + return handleChange(color.hex); + }} + /> + </div> + ) : ( + <></> + )} + </div> + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default ColorPicker; diff --git a/CommonUI/src/Components/Forms/Fields/FieldLabel.tsx b/CommonUI/src/Components/Forms/Fields/FieldLabel.tsx index dcb6787858..b0b18c4e96 100644 --- a/CommonUI/src/Components/Forms/Fields/FieldLabel.tsx +++ b/CommonUI/src/Components/Forms/Fields/FieldLabel.tsx @@ -1,51 +1,49 @@ -import Link from '../../Link/Link'; -import { FormFieldSideLink } from '../Types/Field'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Link from "../../Link/Link"; +import { FormFieldSideLink } from "../Types/Field"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; - required?: boolean | undefined; - sideLink?: FormFieldSideLink | undefined; - description?: string | ReactElement | undefined; - isHeading?: boolean | undefined; + title: string; + required?: boolean | undefined; + sideLink?: FormFieldSideLink | undefined; + description?: string | ReactElement | undefined; + isHeading?: boolean | undefined; } const FieldLabelElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <> - <label - className={`block ${ - props.isHeading ? 'text-lg' : 'text-sm' - } font-medium text-gray-700 flex justify-between`} + return ( + <> + <label + className={`block ${ + props.isHeading ? "text-lg" : "text-sm" + } font-medium text-gray-700 flex justify-between`} + > + <span> + {props.title}{" "} + <span className="text-gray-400 text-xs"> + {props.required ? "" : "(Optional)"} + </span> + </span> + {props.sideLink && props.sideLink?.text && props.sideLink?.url && ( + <span data-testid="login-forgot-password"> + <Link + to={props.sideLink?.url} + openInNewTab={props.sideLink?.openLinkInNewTab} + className="text-indigo-500 hover:text-indigo-900 cursor-pointer" > - <span> - {props.title}{' '} - <span className="text-gray-400 text-xs"> - {props.required ? '' : '(Optional)'} - </span> - </span> - {props.sideLink && props.sideLink?.text && props.sideLink?.url && ( - <span data-testid="login-forgot-password"> - <Link - to={props.sideLink?.url} - openInNewTab={props.sideLink?.openLinkInNewTab} - className="text-indigo-500 hover:text-indigo-900 cursor-pointer" - > - {props.sideLink?.text} - </Link> - </span> - )} - </label> + {props.sideLink?.text} + </Link> + </span> + )} + </label> - {props.description && ( - <div className="mt-1 text-sm text-gray-500"> - {props.description} - </div> - )} - </> - ); + {props.description && ( + <div className="mt-1 text-sm text-gray-500">{props.description}</div> + )} + </> + ); }; export default FieldLabelElement; diff --git a/CommonUI/src/Components/Forms/Fields/FormField.tsx b/CommonUI/src/Components/Forms/Fields/FormField.tsx index 8783d7e6b2..b945f6044e 100644 --- a/CommonUI/src/Components/Forms/Fields/FormField.tsx +++ b/CommonUI/src/Components/Forms/Fields/FormField.tsx @@ -1,778 +1,628 @@ -import { GetReactElementFunction } from '../../../Types/FunctionTypes'; -import CategoryCheckbox from '../../CategoryCheckbox/Index'; +import { GetReactElementFunction } from "../../../Types/FunctionTypes"; +import CategoryCheckbox from "../../CategoryCheckbox/Index"; import CheckboxElement, { - CategoryCheckboxValue, -} from '../../Checkbox/Checkbox'; -import CodeEditor from '../../CodeEditor/CodeEditor'; -import Dropdown, { DropdownValue } from '../../Dropdown/Dropdown'; -import FilePicker from '../../FilePicker/FilePicker'; -import Input, { InputType } from '../../Input/Input'; -import Link from '../../Link/Link'; -import Modal from '../../Modal/Modal'; -import IDGenerator from '../../ObjectID/IDGenerator'; -import RadioButtons from '../../RadioButtons/GroupRadioButtons'; -import TextArea from '../../TextArea/TextArea'; -import Toggle from '../../Toggle/Toggle'; -import ColorPicker from '../Fields/ColorPicker'; -import FieldLabelElement from '../Fields/FieldLabel'; -import Field, { FormFieldStyleType } from '../Types/Field'; -import FormFieldSchemaType from '../Types/FormFieldSchemaType'; -import FormValues from '../Types/FormValues'; -import FileModel from 'Common/Models/FileModel'; -import CodeType from 'Common/Types/Code/CodeType'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import MimeType from 'Common/Types/File/MimeType'; -import GenericObject from 'Common/Types/GenericObject'; -import { JSONValue } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Typeof from 'Common/Types/Typeof'; -import React, { ReactElement, useEffect } from 'react'; + CategoryCheckboxValue, +} from "../../Checkbox/Checkbox"; +import CodeEditor from "../../CodeEditor/CodeEditor"; +import Dropdown, { DropdownValue } from "../../Dropdown/Dropdown"; +import FilePicker from "../../FilePicker/FilePicker"; +import Input, { InputType } from "../../Input/Input"; +import Link from "../../Link/Link"; +import Modal from "../../Modal/Modal"; +import IDGenerator from "../../ObjectID/IDGenerator"; +import RadioButtons from "../../RadioButtons/GroupRadioButtons"; +import TextArea from "../../TextArea/TextArea"; +import Toggle from "../../Toggle/Toggle"; +import ColorPicker from "../Fields/ColorPicker"; +import FieldLabelElement from "../Fields/FieldLabel"; +import Field, { FormFieldStyleType } from "../Types/Field"; +import FormFieldSchemaType from "../Types/FormFieldSchemaType"; +import FormValues from "../Types/FormValues"; +import FileModel from "Common/Models/FileModel"; +import CodeType from "Common/Types/Code/CodeType"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import MimeType from "Common/Types/File/MimeType"; +import GenericObject from "Common/Types/GenericObject"; +import { JSONValue } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Typeof from "Common/Types/Typeof"; +import React, { ReactElement, useEffect } from "react"; export interface ComponentProps<T extends GenericObject> { - field: Field<T>; - fieldName: string; - index: number; - isDisabled: boolean; - error: string; - touched: boolean; - currentValues: FormValues<T>; - setFieldTouched: (fieldName: string, value: boolean) => void; - setFieldValue: (fieldName: string, value: JSONValue) => void; - disableAutofocus?: boolean; - submitForm?: (() => void) | undefined; + field: Field<T>; + fieldName: string; + index: number; + isDisabled: boolean; + error: string; + touched: boolean; + currentValues: FormValues<T>; + setFieldTouched: (fieldName: string, value: boolean) => void; + setFieldValue: (fieldName: string, value: JSONValue) => void; + disableAutofocus?: boolean; + submitForm?: (() => void) | undefined; } const FormField: <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - type GetFieldTypeFunction = (fieldType: FormFieldSchemaType) => string; + type GetFieldTypeFunction = (fieldType: FormFieldSchemaType) => string; - const getFieldType: GetFieldTypeFunction = ( - fieldType: FormFieldSchemaType - ): string => { - switch (fieldType) { - case FormFieldSchemaType.Email: - return 'email'; - case FormFieldSchemaType.Password: - return 'password'; - case FormFieldSchemaType.EncryptedText: - return 'password'; - case FormFieldSchemaType.Number: - return 'number'; - case FormFieldSchemaType.Date: - return 'date'; - case FormFieldSchemaType.DateTime: - return 'datetime-local'; - case FormFieldSchemaType.Time: - return 'time'; - case FormFieldSchemaType.LongText: - return 'textarea'; - case FormFieldSchemaType.Color: - return 'color'; - case FormFieldSchemaType.URL: - return 'url'; - case FormFieldSchemaType.PositiveNumber: - return 'number'; - default: - return 'text'; - } - }; + const getFieldType: GetFieldTypeFunction = ( + fieldType: FormFieldSchemaType, + ): string => { + switch (fieldType) { + case FormFieldSchemaType.Email: + return "email"; + case FormFieldSchemaType.Password: + return "password"; + case FormFieldSchemaType.EncryptedText: + return "password"; + case FormFieldSchemaType.Number: + return "number"; + case FormFieldSchemaType.Date: + return "date"; + case FormFieldSchemaType.DateTime: + return "datetime-local"; + case FormFieldSchemaType.Time: + return "time"; + case FormFieldSchemaType.LongText: + return "textarea"; + case FormFieldSchemaType.Color: + return "color"; + case FormFieldSchemaType.URL: + return "url"; + case FormFieldSchemaType.PositiveNumber: + return "number"; + default: + return "text"; + } + }; - const getFormField: GetReactElementFunction = (): ReactElement => { - const [ - showMultiSelectCheckboxCategoryModal, - setShowMultiSelectCheckboxCategoryModal, - ] = React.useState<boolean>(false); - const [checkboxCategoryValues, setCheckboxCategoryValues] = - React.useState<Array<CategoryCheckboxValue>>( - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[props.fieldName] - : [] - ); + const getFormField: GetReactElementFunction = (): ReactElement => { + const [ + showMultiSelectCheckboxCategoryModal, + setShowMultiSelectCheckboxCategoryModal, + ] = React.useState<boolean>(false); + const [checkboxCategoryValues, setCheckboxCategoryValues] = React.useState< + Array<CategoryCheckboxValue> + >( + props.currentValues && (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : [], + ); - useEffect(() => { - setCheckboxCategoryValues( - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[props.fieldName] - : [] - ); - }, [props.currentValues]); - - const getMultiSelectCheckboxCategoryModal: GetReactElementFunction = - (): ReactElement => { - return ( - <Modal - title={`${props.field.title}`} - description={`${props.field.description}`} - onSubmit={() => { - setShowMultiSelectCheckboxCategoryModal(false); - props.field.onChange && - props.field.onChange(checkboxCategoryValues); - props.setFieldValue( - props.fieldName, - checkboxCategoryValues - ); - }} - onClose={() => { - setShowMultiSelectCheckboxCategoryModal(false); - }} - > - <div className="max-h-96 overflow-y-auto"> - <CategoryCheckbox - categories={ - props.field.selectByAccessControlProps - ?.categoryCheckboxProps.categories || [] - } - options={ - props.field.selectByAccessControlProps - ?.categoryCheckboxProps.options || [] - } - onChange={( - value: Array<CategoryCheckboxValue> - ) => { - setCheckboxCategoryValues(value); - }} - initialValue={checkboxCategoryValues} - /> - </div> - </Modal> - ); - }; - - const index: number = props.index + 1; - - const fieldType: string = props.field.fieldType - ? getFieldType(props.field.fieldType) - : 'text'; - - if (Object.keys(props.field.field || {}).length === 0) { - throw new BadDataException('Object cannot be without Field'); - } - - if (props.field.showIf && !props.field.showIf(props.currentValues)) { - return <></>; - } - - let required: boolean = false; - - if ( - props.field.required && - typeof props.field.required === Typeof.Boolean - ) { - required = true; - } else if ( - props.field.required && - typeof props.field.required === 'function' && - props.field.required(props.currentValues) - ) { - required = true; - } - - let codeType: CodeType = CodeType.HTML; - - if (props.field.fieldType === FormFieldSchemaType.CSS) { - codeType = CodeType.CSS; - } - - if (props.field.fieldType === FormFieldSchemaType.JavaScript) { - codeType = CodeType.JavaScript; - } - - let fieldDescription: string | undefined = props.field.description; - - if ( - props.field.fieldType === FormFieldSchemaType.DateTime || - props.field.fieldType === FormFieldSchemaType.Time - ) { - if (!fieldDescription) { - fieldDescription = ''; - } - - fieldDescription += - ' This is in your local timezone - ' + - OneUptimeDate.getCurrentTimezoneString(); - } - - type GetFieldDescriptionFunction = () => ReactElement | string; - - const getFieldDescription: GetFieldDescriptionFunction = (): - | ReactElement - | string => { - if ( - props.field.fieldType === - FormFieldSchemaType.MultiSelectDropdown && - props.field.selectByAccessControlProps - ) { - return ( - <span> - {fieldDescription} - <Link - onClick={() => { - setShowMultiSelectCheckboxCategoryModal(true); - }} - className="ml-1 underline text-blue-500 cursor-pointer" - > - <span> - Select items by{' '} - {props.field.selectByAccessControlProps - .accessControlColumnTitle || ''} - </span> - </Link> - </span> - ); - } - - if (fieldDescription) { - return fieldDescription; - } - - return <></>; - }; + useEffect(() => { + setCheckboxCategoryValues( + props.currentValues && (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : [], + ); + }, [props.currentValues]); + const getMultiSelectCheckboxCategoryModal: GetReactElementFunction = + (): ReactElement => { return ( - <div className="sm:col-span-4 mt-0 mb-2" key={props.fieldName}> - {/*** Do not display label on checkbox because checkbox can display its own label */} - - {props.field.fieldType !== FormFieldSchemaType.Checkbox && ( - <FieldLabelElement - title={props.field.title || ''} - description={getFieldDescription()} - sideLink={props.field.sideLink} - required={required} - isHeading={ - props.field.styleType === FormFieldStyleType.Heading - } - /> - )} - - <div className="mt-2"> - {props.field.fieldType === FormFieldSchemaType.Color && ( - <ColorPicker - error={ - props.touched && props.error - ? props.error - : undefined - } - dataTestId={props.field.dataTestId} - onChange={async (value: Color | null) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - props.setFieldTouched(props.fieldName, true); - }} - tabIndex={index} - placeholder={props.field.placeholder || ''} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : '' - } - /> - )} - - {(props.field.fieldType === FormFieldSchemaType.Dropdown || - props.field.fieldType === - FormFieldSchemaType.MultiSelectDropdown) && ( - <Dropdown - error={ - props.touched && props.error - ? props.error - : undefined - } - id={props.field.id} - tabIndex={index} - dataTestId={props.field.dataTestId} - onChange={async ( - value: - | DropdownValue - | Array<DropdownValue> - | null - ) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - onBlur={async () => { - props.setFieldTouched(props.fieldName, true); - }} - isMultiSelect={ - props.field.fieldType === - FormFieldSchemaType.MultiSelectDropdown - } - options={props.field.dropdownOptions || []} - placeholder={props.field.placeholder || ''} - value={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : '' - } - /> - )} - - {props.field.fieldType === FormFieldSchemaType.ObjectID && ( - <IDGenerator - tabIndex={index} - dataTestId={props.field.dataTestId} - disabled={props.isDisabled || props.field.disabled} - error={ - props.touched && props.error - ? props.error - : undefined - } - onChange={(value: ObjectID) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - onEnterPress={() => { - props.submitForm && props.submitForm(); - }} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : props.field.defaultValue || null - } - /> - )} - - {props.field.fieldType === - FormFieldSchemaType.RadioButton && ( - <RadioButtons - error={ - props.touched && props.error - ? props.error - : undefined - } - dataTestId={props.field.dataTestId} - onChange={async (value: string) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - options={props.field.radioButtonOptions || []} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : '' - } - /> - )} - - {props.field.fieldType === FormFieldSchemaType.LongText && ( - <TextArea - autoFocus={!props.disableAutofocus && index === 1} - error={ - props.touched && props.error - ? props.error - : undefined - } - tabIndex={index} - dataTestId={props.field.dataTestId} - onChange={async (value: string) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - onBlur={async () => { - props.setFieldTouched(props.fieldName, true); - }} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : '' - } - placeholder={props.field.placeholder || ''} - /> - )} - - {props.field.fieldType === FormFieldSchemaType.JSON && ( - <CodeEditor - error={ - props.touched && props.error - ? props.error - : undefined - } - type={CodeType.JSON} - tabIndex={index} - dataTestId={props.field.dataTestId} - onChange={async (value: string) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - onBlur={async () => { - props.setFieldTouched(props.fieldName, true); - }} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : '' - } - value={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : '' - } - placeholder={props.field.placeholder || ''} - /> - )} - - {props.field.fieldType === FormFieldSchemaType.Markdown && ( - <CodeEditor - error={ - props.touched && props.error - ? props.error - : undefined - } - dataTestId={props.field.dataTestId} - tabIndex={index} - type={CodeType.Markdown} - onChange={async (value: string) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - onBlur={async () => { - props.setFieldTouched(props.fieldName, true); - }} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : '' - } - placeholder={props.field.placeholder || ''} - /> - )} - - {props.field.fieldType === - FormFieldSchemaType.CustomComponent && - props.field.getCustomElement && - props.field.getCustomElement(props.currentValues, { - error: - props.touched && props.error - ? props.error - : undefined, - tabIndex: index, - onChange: async (value: string) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }, - onBlur: async () => { - props.setFieldTouched(props.fieldName, true); - }, - - initialValue: - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : '', - - placeholder: props.field.placeholder || '', - })} - - {(props.field.fieldType === FormFieldSchemaType.HTML || - props.field.fieldType === FormFieldSchemaType.CSS || - props.field.fieldType === - FormFieldSchemaType.JavaScript) && ( - <CodeEditor - error={ - props.touched && props.error - ? props.error - : undefined - } - tabIndex={index} - onChange={async (value: string) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - onBlur={async () => { - props.setFieldTouched(props.fieldName, true); - }} - dataTestId={props.field.dataTestId} - type={codeType} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : '' - } - placeholder={props.field.placeholder || ''} - /> - )} - - {(props.field.fieldType === FormFieldSchemaType.File || - props.field.fieldType === - FormFieldSchemaType.ImageFile) && ( - <FilePicker - error={ - props.touched && props.error - ? props.error - : undefined - } - tabIndex={index} - onChange={async (files: Array<FileModel>) => { - let fileResult: - | FileModel - | Array<FileModel> - | null = files.map((i: FileModel) => { - const strippedModel: FileModel = - new FileModel(); - strippedModel._id = i._id!; - return strippedModel; - }); - - if ( - (props.field.fieldType === - FormFieldSchemaType.File || - props.field.fieldType === - FormFieldSchemaType.ImageFile) && - Array.isArray(fileResult) - ) { - if (fileResult.length > 0) { - fileResult = fileResult[0] as FileModel; - } else { - fileResult = null; - } - } - - props.field.onChange && - props.field.onChange(fileResult); - props.setFieldValue( - props.fieldName, - fileResult - ); - }} - onBlur={async () => { - props.setFieldTouched(props.fieldName, true); - }} - mimeTypes={ - props.field.fieldType === - FormFieldSchemaType.ImageFile - ? [ - MimeType.png, - MimeType.jpeg, - MimeType.jpg, - ] - : [] - } - dataTestId={props.field.dataTestId} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : [] - } - placeholder={props.field.placeholder || ''} - /> - )} - - {props.field.fieldType === FormFieldSchemaType.Toggle && ( - <Toggle - error={ - props.touched && props.error - ? props.error - : undefined - } - onChange={async (value: boolean) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - onBlur={async () => { - props.setFieldTouched(props.fieldName, true); - }} - dataTestId={props.field.dataTestId} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] && - ((props.currentValues as any)[ - props.fieldName - ] === true || - (props.currentValues as any)[ - props.fieldName - ] === false) - ? (props.currentValues as any)[ - props.fieldName - ] - : (props.field.defaultValue as boolean) || - false - } - /> - )} - - {props.field.fieldType === FormFieldSchemaType.Checkbox && ( - <CheckboxElement - error={ - props.touched && props.error - ? props.error - : undefined - } - title={props.field.title || ''} - description={props.field.description || ''} - onChange={async (value: boolean) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - dataTestId={props.field.dataTestId} - onBlur={async () => { - props.setFieldTouched(props.fieldName, true); - }} - onFocus={async () => { - props.setFieldTouched(props.fieldName, true); - }} - initialValue={ - props.currentValues && - ((props.currentValues as any)[ - props.fieldName - ] === true || - (props.currentValues as any)[ - props.fieldName - ] === false) - ? (props.currentValues as any)[ - props.fieldName - ] - : (props.field.defaultValue as boolean) || - false - } - /> - )} - - {props.field.fieldType === - FormFieldSchemaType.CategoryCheckbox && ( - <CategoryCheckbox - categories={ - props.field.categoryCheckboxProps?.categories || - [] - } - options={ - props.field.categoryCheckboxProps?.options || [] - } - error={ - props.touched && props.error - ? props.error - : undefined - } - dataTestId={props.field.dataTestId} - onChange={async ( - value: Array<CategoryCheckboxValue> - ) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : [] - } - /> - )} - - {/* Default Field */} - {(props.field.fieldType === FormFieldSchemaType.Name || - props.field.fieldType === FormFieldSchemaType.Email || - props.field.fieldType === - FormFieldSchemaType.Hostname || - props.field.fieldType === FormFieldSchemaType.URL || - props.field.fieldType === FormFieldSchemaType.Route || - props.field.fieldType === FormFieldSchemaType.Text || - props.field.fieldType === FormFieldSchemaType.Number || - props.field.fieldType === - FormFieldSchemaType.Password || - props.field.fieldType === - FormFieldSchemaType.EncryptedText || - props.field.fieldType === FormFieldSchemaType.Date || - props.field.fieldType === - FormFieldSchemaType.DateTime || - props.field.fieldType === FormFieldSchemaType.Port || - props.field.fieldType === FormFieldSchemaType.Phone || - props.field.fieldType === FormFieldSchemaType.Domain || - props.field.fieldType === - FormFieldSchemaType.PositiveNumber) && ( - <Input - autoFocus={!props.disableAutofocus && index === 1} - tabIndex={index} - disabled={props.isDisabled || props.field.disabled} - error={ - props.touched && props.error - ? props.error - : undefined - } - dataTestId={props.field.dataTestId} - type={fieldType as InputType} - onChange={(value: string) => { - props.field.onChange && - props.field.onChange(value); - props.setFieldValue(props.fieldName, value); - }} - onEnterPress={() => { - props.submitForm && props.submitForm(); - }} - onBlur={() => { - props.setFieldTouched(props.fieldName, true); - }} - initialValue={ - props.currentValues && - (props.currentValues as any)[props.fieldName] - ? (props.currentValues as any)[ - props.fieldName - ] - : props.field.defaultValue || '' - } - placeholder={props.field.placeholder || ''} - /> - )} - </div> - - {showMultiSelectCheckboxCategoryModal && - getMultiSelectCheckboxCategoryModal()} + <Modal + title={`${props.field.title}`} + description={`${props.field.description}`} + onSubmit={() => { + setShowMultiSelectCheckboxCategoryModal(false); + props.field.onChange && + props.field.onChange(checkboxCategoryValues); + props.setFieldValue(props.fieldName, checkboxCategoryValues); + }} + onClose={() => { + setShowMultiSelectCheckboxCategoryModal(false); + }} + > + <div className="max-h-96 overflow-y-auto"> + <CategoryCheckbox + categories={ + props.field.selectByAccessControlProps?.categoryCheckboxProps + .categories || [] + } + options={ + props.field.selectByAccessControlProps?.categoryCheckboxProps + .options || [] + } + onChange={(value: Array<CategoryCheckboxValue>) => { + setCheckboxCategoryValues(value); + }} + initialValue={checkboxCategoryValues} + /> </div> + </Modal> ); + }; + + const index: number = props.index + 1; + + const fieldType: string = props.field.fieldType + ? getFieldType(props.field.fieldType) + : "text"; + + if (Object.keys(props.field.field || {}).length === 0) { + throw new BadDataException("Object cannot be without Field"); + } + + if (props.field.showIf && !props.field.showIf(props.currentValues)) { + return <></>; + } + + let required: boolean = false; + + if ( + props.field.required && + typeof props.field.required === Typeof.Boolean + ) { + required = true; + } else if ( + props.field.required && + typeof props.field.required === "function" && + props.field.required(props.currentValues) + ) { + required = true; + } + + let codeType: CodeType = CodeType.HTML; + + if (props.field.fieldType === FormFieldSchemaType.CSS) { + codeType = CodeType.CSS; + } + + if (props.field.fieldType === FormFieldSchemaType.JavaScript) { + codeType = CodeType.JavaScript; + } + + let fieldDescription: string | undefined = props.field.description; + + if ( + props.field.fieldType === FormFieldSchemaType.DateTime || + props.field.fieldType === FormFieldSchemaType.Time + ) { + if (!fieldDescription) { + fieldDescription = ""; + } + + fieldDescription += + " This is in your local timezone - " + + OneUptimeDate.getCurrentTimezoneString(); + } + + type GetFieldDescriptionFunction = () => ReactElement | string; + + const getFieldDescription: GetFieldDescriptionFunction = (): + | ReactElement + | string => { + if ( + props.field.fieldType === FormFieldSchemaType.MultiSelectDropdown && + props.field.selectByAccessControlProps + ) { + return ( + <span> + {fieldDescription} + <Link + onClick={() => { + setShowMultiSelectCheckboxCategoryModal(true); + }} + className="ml-1 underline text-blue-500 cursor-pointer" + > + <span> + Select items by{" "} + {props.field.selectByAccessControlProps + .accessControlColumnTitle || ""} + </span> + </Link> + </span> + ); + } + + if (fieldDescription) { + return fieldDescription; + } + + return <></>; }; - return <>{getFormField()}</>; + return ( + <div className="sm:col-span-4 mt-0 mb-2" key={props.fieldName}> + {/*** Do not display label on checkbox because checkbox can display its own label */} + + {props.field.fieldType !== FormFieldSchemaType.Checkbox && ( + <FieldLabelElement + title={props.field.title || ""} + description={getFieldDescription()} + sideLink={props.field.sideLink} + required={required} + isHeading={props.field.styleType === FormFieldStyleType.Heading} + /> + )} + + <div className="mt-2"> + {props.field.fieldType === FormFieldSchemaType.Color && ( + <ColorPicker + error={props.touched && props.error ? props.error : undefined} + dataTestId={props.field.dataTestId} + onChange={async (value: Color | null) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + props.setFieldTouched(props.fieldName, true); + }} + tabIndex={index} + placeholder={props.field.placeholder || ""} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : "" + } + /> + )} + + {(props.field.fieldType === FormFieldSchemaType.Dropdown || + props.field.fieldType === + FormFieldSchemaType.MultiSelectDropdown) && ( + <Dropdown + error={props.touched && props.error ? props.error : undefined} + id={props.field.id} + tabIndex={index} + dataTestId={props.field.dataTestId} + onChange={async ( + value: DropdownValue | Array<DropdownValue> | null, + ) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + onBlur={async () => { + props.setFieldTouched(props.fieldName, true); + }} + isMultiSelect={ + props.field.fieldType === + FormFieldSchemaType.MultiSelectDropdown + } + options={props.field.dropdownOptions || []} + placeholder={props.field.placeholder || ""} + value={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : "" + } + /> + )} + + {props.field.fieldType === FormFieldSchemaType.ObjectID && ( + <IDGenerator + tabIndex={index} + dataTestId={props.field.dataTestId} + disabled={props.isDisabled || props.field.disabled} + error={props.touched && props.error ? props.error : undefined} + onChange={(value: ObjectID) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + onEnterPress={() => { + props.submitForm && props.submitForm(); + }} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : props.field.defaultValue || null + } + /> + )} + + {props.field.fieldType === FormFieldSchemaType.RadioButton && ( + <RadioButtons + error={props.touched && props.error ? props.error : undefined} + dataTestId={props.field.dataTestId} + onChange={async (value: string) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + options={props.field.radioButtonOptions || []} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : "" + } + /> + )} + + {props.field.fieldType === FormFieldSchemaType.LongText && ( + <TextArea + autoFocus={!props.disableAutofocus && index === 1} + error={props.touched && props.error ? props.error : undefined} + tabIndex={index} + dataTestId={props.field.dataTestId} + onChange={async (value: string) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + onBlur={async () => { + props.setFieldTouched(props.fieldName, true); + }} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : "" + } + placeholder={props.field.placeholder || ""} + /> + )} + + {props.field.fieldType === FormFieldSchemaType.JSON && ( + <CodeEditor + error={props.touched && props.error ? props.error : undefined} + type={CodeType.JSON} + tabIndex={index} + dataTestId={props.field.dataTestId} + onChange={async (value: string) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + onBlur={async () => { + props.setFieldTouched(props.fieldName, true); + }} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : "" + } + value={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : "" + } + placeholder={props.field.placeholder || ""} + /> + )} + + {props.field.fieldType === FormFieldSchemaType.Markdown && ( + <CodeEditor + error={props.touched && props.error ? props.error : undefined} + dataTestId={props.field.dataTestId} + tabIndex={index} + type={CodeType.Markdown} + onChange={async (value: string) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + onBlur={async () => { + props.setFieldTouched(props.fieldName, true); + }} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : "" + } + placeholder={props.field.placeholder || ""} + /> + )} + + {props.field.fieldType === FormFieldSchemaType.CustomComponent && + props.field.getCustomElement && + props.field.getCustomElement(props.currentValues, { + error: props.touched && props.error ? props.error : undefined, + tabIndex: index, + onChange: async (value: string) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }, + onBlur: async () => { + props.setFieldTouched(props.fieldName, true); + }, + + initialValue: + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : "", + + placeholder: props.field.placeholder || "", + })} + + {(props.field.fieldType === FormFieldSchemaType.HTML || + props.field.fieldType === FormFieldSchemaType.CSS || + props.field.fieldType === FormFieldSchemaType.JavaScript) && ( + <CodeEditor + error={props.touched && props.error ? props.error : undefined} + tabIndex={index} + onChange={async (value: string) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + onBlur={async () => { + props.setFieldTouched(props.fieldName, true); + }} + dataTestId={props.field.dataTestId} + type={codeType} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : "" + } + placeholder={props.field.placeholder || ""} + /> + )} + + {(props.field.fieldType === FormFieldSchemaType.File || + props.field.fieldType === FormFieldSchemaType.ImageFile) && ( + <FilePicker + error={props.touched && props.error ? props.error : undefined} + tabIndex={index} + onChange={async (files: Array<FileModel>) => { + let fileResult: FileModel | Array<FileModel> | null = files.map( + (i: FileModel) => { + const strippedModel: FileModel = new FileModel(); + strippedModel._id = i._id!; + return strippedModel; + }, + ); + + if ( + (props.field.fieldType === FormFieldSchemaType.File || + props.field.fieldType === FormFieldSchemaType.ImageFile) && + Array.isArray(fileResult) + ) { + if (fileResult.length > 0) { + fileResult = fileResult[0] as FileModel; + } else { + fileResult = null; + } + } + + props.field.onChange && props.field.onChange(fileResult); + props.setFieldValue(props.fieldName, fileResult); + }} + onBlur={async () => { + props.setFieldTouched(props.fieldName, true); + }} + mimeTypes={ + props.field.fieldType === FormFieldSchemaType.ImageFile + ? [MimeType.png, MimeType.jpeg, MimeType.jpg] + : [] + } + dataTestId={props.field.dataTestId} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : [] + } + placeholder={props.field.placeholder || ""} + /> + )} + + {props.field.fieldType === FormFieldSchemaType.Toggle && ( + <Toggle + error={props.touched && props.error ? props.error : undefined} + onChange={async (value: boolean) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + onBlur={async () => { + props.setFieldTouched(props.fieldName, true); + }} + dataTestId={props.field.dataTestId} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] && + ((props.currentValues as any)[props.fieldName] === true || + (props.currentValues as any)[props.fieldName] === false) + ? (props.currentValues as any)[props.fieldName] + : (props.field.defaultValue as boolean) || false + } + /> + )} + + {props.field.fieldType === FormFieldSchemaType.Checkbox && ( + <CheckboxElement + error={props.touched && props.error ? props.error : undefined} + title={props.field.title || ""} + description={props.field.description || ""} + onChange={async (value: boolean) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + dataTestId={props.field.dataTestId} + onBlur={async () => { + props.setFieldTouched(props.fieldName, true); + }} + onFocus={async () => { + props.setFieldTouched(props.fieldName, true); + }} + initialValue={ + props.currentValues && + ((props.currentValues as any)[props.fieldName] === true || + (props.currentValues as any)[props.fieldName] === false) + ? (props.currentValues as any)[props.fieldName] + : (props.field.defaultValue as boolean) || false + } + /> + )} + + {props.field.fieldType === FormFieldSchemaType.CategoryCheckbox && ( + <CategoryCheckbox + categories={props.field.categoryCheckboxProps?.categories || []} + options={props.field.categoryCheckboxProps?.options || []} + error={props.touched && props.error ? props.error : undefined} + dataTestId={props.field.dataTestId} + onChange={async (value: Array<CategoryCheckboxValue>) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : [] + } + /> + )} + + {/* Default Field */} + {(props.field.fieldType === FormFieldSchemaType.Name || + props.field.fieldType === FormFieldSchemaType.Email || + props.field.fieldType === FormFieldSchemaType.Hostname || + props.field.fieldType === FormFieldSchemaType.URL || + props.field.fieldType === FormFieldSchemaType.Route || + props.field.fieldType === FormFieldSchemaType.Text || + props.field.fieldType === FormFieldSchemaType.Number || + props.field.fieldType === FormFieldSchemaType.Password || + props.field.fieldType === FormFieldSchemaType.EncryptedText || + props.field.fieldType === FormFieldSchemaType.Date || + props.field.fieldType === FormFieldSchemaType.DateTime || + props.field.fieldType === FormFieldSchemaType.Port || + props.field.fieldType === FormFieldSchemaType.Phone || + props.field.fieldType === FormFieldSchemaType.Domain || + props.field.fieldType === FormFieldSchemaType.PositiveNumber) && ( + <Input + autoFocus={!props.disableAutofocus && index === 1} + tabIndex={index} + disabled={props.isDisabled || props.field.disabled} + error={props.touched && props.error ? props.error : undefined} + dataTestId={props.field.dataTestId} + type={fieldType as InputType} + onChange={(value: string) => { + props.field.onChange && props.field.onChange(value); + props.setFieldValue(props.fieldName, value); + }} + onEnterPress={() => { + props.submitForm && props.submitForm(); + }} + onBlur={() => { + props.setFieldTouched(props.fieldName, true); + }} + initialValue={ + props.currentValues && + (props.currentValues as any)[props.fieldName] + ? (props.currentValues as any)[props.fieldName] + : props.field.defaultValue || "" + } + placeholder={props.field.placeholder || ""} + /> + )} + </div> + + {showMultiSelectCheckboxCategoryModal && + getMultiSelectCheckboxCategoryModal()} + </div> + ); + }; + + return <>{getFormField()}</>; }; export default FormField; diff --git a/CommonUI/src/Components/Forms/ModelForm.tsx b/CommonUI/src/Components/Forms/ModelForm.tsx index c1ad71e54c..cf099c7043 100644 --- a/CommonUI/src/Components/Forms/ModelForm.tsx +++ b/CommonUI/src/Components/Forms/ModelForm.tsx @@ -1,811 +1,760 @@ -import SelectFormFields from '../../Types/SelectEntityField'; -import API from '../../Utils/API/API'; -import Select from '../../Utils/BaseDatabase/Select'; +import SelectFormFields from "../../Types/SelectEntityField"; +import API from "../../Utils/API/API"; +import Select from "../../Utils/BaseDatabase/Select"; import ModelAPI, { - ListResult, - ModelAPIHttpResponse, - RequestOptions, -} from '../../Utils/ModelAPI/ModelAPI'; -import PermissionUtil from '../../Utils/Permission'; -import User from '../../Utils/User'; -import { ButtonStyleType } from '../Button/Button'; + ListResult, + ModelAPIHttpResponse, + RequestOptions, +} from "../../Utils/ModelAPI/ModelAPI"; +import PermissionUtil from "../../Utils/Permission"; +import User from "../../Utils/User"; +import { ButtonStyleType } from "../Button/Button"; import { - CategoryCheckboxOption, - CheckboxCategory, -} from '../CategoryCheckbox/CategoryCheckboxTypes'; -import Loader, { LoaderType } from '../Loader/Loader'; -import Pill, { PillSize } from '../Pill/Pill'; -import { FormErrors, FormProps } from './BasicForm'; -import BasicModelForm from './BasicModelForm'; -import Field from './Types/Field'; -import Fields from './Types/Fields'; -import { FormStep } from './Types/FormStep'; -import FormValues from './Types/FormValues'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import AccessControlModel from 'Common/Models/AccessControlModel'; -import BaseModel from 'Common/Models/BaseModel'; -import FileModel from 'Common/Models/FileModel'; -import URL from 'Common/Types/API/URL'; -import { ColumnAccessControl } from 'Common/Types/BaseDatabase/AccessControl'; -import { Black, VeryLightGray } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import { getMaxLengthFromTableColumnType } from 'Common/Types/Database/ColumnLength'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import { TableColumnMetadata } from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import GenericObject from 'Common/Types/GenericObject'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; + CategoryCheckboxOption, + CheckboxCategory, +} from "../CategoryCheckbox/CategoryCheckboxTypes"; +import Loader, { LoaderType } from "../Loader/Loader"; +import Pill, { PillSize } from "../Pill/Pill"; +import { FormErrors, FormProps } from "./BasicForm"; +import BasicModelForm from "./BasicModelForm"; +import Field from "./Types/Field"; +import Fields from "./Types/Fields"; +import { FormStep } from "./Types/FormStep"; +import FormValues from "./Types/FormValues"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import AccessControlModel from "Common/Models/AccessControlModel"; +import BaseModel from "Common/Models/BaseModel"; +import FileModel from "Common/Models/FileModel"; +import URL from "Common/Types/API/URL"; +import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl"; +import { Black, VeryLightGray } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import { getMaxLengthFromTableColumnType } from "Common/Types/Database/ColumnLength"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import { TableColumnMetadata } from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import GenericObject from "Common/Types/GenericObject"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; import Permission, { - PermissionHelper, - UserPermission, -} from 'Common/Types/Permission'; -import Typeof from 'Common/Types/Typeof'; -import React, { MutableRefObject, ReactElement, useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; + PermissionHelper, + UserPermission, +} from "Common/Types/Permission"; +import Typeof from "Common/Types/Typeof"; +import React, { MutableRefObject, ReactElement, useState } from "react"; +import useAsyncEffect from "use-async-effect"; export enum FormType { - Create, - Update, + Create, + Update, } export interface ModelField<TBaseModel extends BaseModel | AnalyticsBaseModel> - extends Field<TBaseModel> { - overrideField?: - | { - // This is used to override the field type in the form. - [field: string]: true; - } - | undefined; + extends Field<TBaseModel> { + overrideField?: + | { + // This is used to override the field type in the form. + [field: string]: true; + } + | undefined; } export interface ComponentProps<TBaseModel extends BaseModel> { - modelType: { new (): TBaseModel }; - id: string; - onValidate?: - | undefined - | (( - values: FormValues<TBaseModel> - ) => FormErrors<FormValues<TBaseModel>>); - fields: Array<ModelField<TBaseModel>>; - onFormStepChange?: undefined | ((stepId: string) => void); - steps?: undefined | Array<FormStep<TBaseModel>>; - submitButtonText?: undefined | string; - requestHeaders?: undefined | Dictionary<string>; - title?: undefined | string; - description?: undefined | string; - showAsColumns?: undefined | number; - disableAutofocus?: undefined | boolean; - footer?: ReactElement | undefined; - onCancel?: undefined | (() => void); - name?: string | undefined; - onChange?: undefined | ((values: FormValues<TBaseModel>) => void); - onSuccess?: undefined | ((data: TBaseModel, miscData?: JSONObject) => void); - cancelButtonText?: undefined | string; - maxPrimaryButtonWidth?: undefined | boolean; - createOrUpdateApiUrl?: undefined | URL; - fetchItemApiUrl?: undefined | URL; - formType: FormType; - hideSubmitButton?: undefined | boolean; - submitButtonStyleType?: ButtonStyleType | undefined; - formRef?: undefined | MutableRefObject<FormProps<FormValues<TBaseModel>>>; - onIsLastFormStep?: undefined | ((isLastFormStep: boolean) => void); - onLoadingChange?: undefined | ((isLoading: boolean) => void); - initialValues?: FormValues<TBaseModel> | undefined; - modelIdToEdit?: ObjectID | undefined; - onError?: ((error: string) => void) | undefined; - onBeforeCreate?: - | ((item: TBaseModel, miscDataProps: JSONObject) => Promise<TBaseModel>) - | undefined; - saveRequestOptions?: RequestOptions | undefined; - doNotFetchExistingModel?: boolean | undefined; - modelAPI?: typeof ModelAPI | undefined; + modelType: { new (): TBaseModel }; + id: string; + onValidate?: + | undefined + | ((values: FormValues<TBaseModel>) => FormErrors<FormValues<TBaseModel>>); + fields: Array<ModelField<TBaseModel>>; + onFormStepChange?: undefined | ((stepId: string) => void); + steps?: undefined | Array<FormStep<TBaseModel>>; + submitButtonText?: undefined | string; + requestHeaders?: undefined | Dictionary<string>; + title?: undefined | string; + description?: undefined | string; + showAsColumns?: undefined | number; + disableAutofocus?: undefined | boolean; + footer?: ReactElement | undefined; + onCancel?: undefined | (() => void); + name?: string | undefined; + onChange?: undefined | ((values: FormValues<TBaseModel>) => void); + onSuccess?: undefined | ((data: TBaseModel, miscData?: JSONObject) => void); + cancelButtonText?: undefined | string; + maxPrimaryButtonWidth?: undefined | boolean; + createOrUpdateApiUrl?: undefined | URL; + fetchItemApiUrl?: undefined | URL; + formType: FormType; + hideSubmitButton?: undefined | boolean; + submitButtonStyleType?: ButtonStyleType | undefined; + formRef?: undefined | MutableRefObject<FormProps<FormValues<TBaseModel>>>; + onIsLastFormStep?: undefined | ((isLastFormStep: boolean) => void); + onLoadingChange?: undefined | ((isLoading: boolean) => void); + initialValues?: FormValues<TBaseModel> | undefined; + modelIdToEdit?: ObjectID | undefined; + onError?: ((error: string) => void) | undefined; + onBeforeCreate?: + | ((item: TBaseModel, miscDataProps: JSONObject) => Promise<TBaseModel>) + | undefined; + saveRequestOptions?: RequestOptions | undefined; + doNotFetchExistingModel?: boolean | undefined; + modelAPI?: typeof ModelAPI | undefined; } const ModelForm: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [fields, setFields] = useState<Fields<TBaseModel>>([]); - const [isLoading, setLoading] = useState<boolean>(false); - const [isFetching, setIsFetching] = useState<boolean>(false); - const [isFetchingDropdownOptions, setIsFetchingDropdownOptions] = - useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [itemToEdit, setItemToEdit] = useState<TBaseModel | null>(null); - const model: TBaseModel = new props.modelType(); - - const modelAPI: typeof ModelAPI = props.modelAPI || ModelAPI; - - type GetSelectFieldsFunction = () => Select<TBaseModel>; - - const getSelectFields: GetSelectFieldsFunction = (): Select<TBaseModel> => { - const select: Select<TBaseModel> = {}; - for (const field of props.fields) { - const key: string | null = field.field - ? (Object.keys(field.field)[0] as string) - : null; - - if ( - key && - (hasPermissionOnField(key) || - field.showEvenIfPermissionDoesNotExist) - ) { - (select as Dictionary<boolean>)[key] = true; - } - } - - return select; - }; - - const getRelationSelect: () => Select<TBaseModel> = - (): Select<TBaseModel> => { - const relationSelect: Select<TBaseModel> = {}; - - for (const field of props.fields) { - const key: string | null = field.field - ? (Object.keys(field.field)[0] as string) - : null; - - if (key && model.isFileColumn(key)) { - (relationSelect as JSONObject)[key] = { - file: true, - _id: true, - type: true, - name: true, - }; - } else if (key && model.isEntityColumn(key)) { - (relationSelect as JSONObject)[key] = (field.field as any)[ - key - ]; - } - } - - return relationSelect; - }; - - const hasPermissionOnField: (fieldName: string) => boolean = ( - fieldName: string - ): boolean => { - if (User.isMasterAdmin()) { - return true; // master admin can do anything. - } - - let userPermissions: Array<Permission> = - PermissionUtil.getGlobalPermissions()?.globalPermissions || []; - if ( - PermissionUtil.getProjectPermissions() && - PermissionUtil.getProjectPermissions()?.permissions && - PermissionUtil.getProjectPermissions()!.permissions.length > 0 - ) { - userPermissions = userPermissions.concat( - PermissionUtil.getProjectPermissions()!.permissions.map( - (i: UserPermission) => { - return i.permission; - } - ) - ); - } - - userPermissions.push(Permission.Public); - - const accessControl: Dictionary<ColumnAccessControl> = - model.getColumnAccessControlForAllColumns(); - - let fieldPermissions: Array<Permission> = []; - - if (FormType.Create === props.formType) { - fieldPermissions = accessControl[fieldName]?.create || []; - } else { - fieldPermissions = accessControl[fieldName]?.update || []; - } - - if ( - fieldPermissions && - PermissionHelper.doesPermissionsIntersect( - userPermissions, - fieldPermissions - ) - ) { - return true; - } - - return false; - }; - - const setFormFields: PromiseVoidFunction = async (): Promise<void> => { - let fieldsToSet: Fields<TBaseModel> = []; - - for (const field of props.fields) { - const fieldObj: - | { - [field: string]: true; - } - | SelectFormFields<TBaseModel> - | undefined = field.field || field.overrideField; - - if (!fieldObj) { - continue; - } - - const keys: Array<string> = Object.keys(fieldObj); - - if (keys.length > 0) { - const key: string = keys[0] as string; - - const hasPermission: boolean = hasPermissionOnField(key); - - if ( - (field.showEvenIfPermissionDoesNotExist || hasPermission) && - fieldsToSet.filter((i: ModelField<TBaseModel>) => { - const fieldObj: - | { - [field: string]: true; - } - | SelectFormFields<TBaseModel> - | undefined = i.field || i.overrideField; - - if (!fieldObj) { - return false; - } - // check if field already exists. If it does, don't add it. - const iKeys: Array<string> = Object.keys(fieldObj); - const iFieldKey: string = iKeys[0] as string; - return iFieldKey === key; - }).length === 0 - ) { - // check if has maxLength - if ( - !field.validation?.maxLength && - model.getTableColumnMetadata(key)?.type - ) { - field.validation = { - ...field.validation, - maxLength: getMaxLengthFromTableColumnType( - model.getTableColumnMetadata(key).type - ), - }; - } - - fieldsToSet.push({ - ...field, - field: { - [key]: true, - } as SelectFormFields<TBaseModel>, - }); - } - } - } - - fieldsToSet = await fetchDropdownOptions(fieldsToSet); - - setFields(fieldsToSet); - }; - - useAsyncEffect(async () => { - // set fields. - await setFormFields(); - }, [props.fields]); - - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - if (!props.modelIdToEdit || props.formType !== FormType.Update) { - throw new BadDataException('Model ID to update not found.'); - } - - let item: BaseModel | null = await modelAPI.getItem({ - modelType: props.modelType, - id: props.modelIdToEdit, - select: { ...getSelectFields(), ...getRelationSelect() }, - requestOptions: { - overrideRequestUrl: props.fetchItemApiUrl, - }, - }); - - if (!(item instanceof BaseModel) && item) { - item = BaseModel.fromJSON( - item as JSONObject, - props.modelType - ) as BaseModel; - } - - if (!item) { - setError( - `Cannot edit ${( - model.singularName || 'item' - ).toLowerCase()}. It could be because you don't have enough permissions to read or edit this ${( - model.singularName || 'item' - ).toLowerCase()}.` - ); - } - - const relationSelect: Select<TBaseModel> = getRelationSelect(); - - for (const key in relationSelect) { - if (item) { - if (Array.isArray((item as any)[key])) { - const idArray: Array<string> = []; - let isModelArray: boolean = false; - for (const itemInArray of (item as any)[key] as any) { - if (typeof (itemInArray as any) === 'object') { - if ((itemInArray as any as JSONObject)['_id']) { - isModelArray = true; - idArray.push( - (itemInArray as any as JSONObject)[ - '_id' - ] as string - ); - } - } - } - - if (isModelArray) { - (item as any)[key] = idArray; - } - } - if ( - (item as any)[key] && - typeof (item as any)[key] === 'object' && - !((item as any)[key] instanceof FileModel) - ) { - if (((item as any)[key] as JSONObject)['_id']) { - (item as any)[key] = ((item as any)[key] as JSONObject)[ - '_id' - ] as string; - } - } - } - } - - setItemToEdit(item as TBaseModel); - }; - - type FetchDropdownOptionsFunction = ( - fields: Fields<TBaseModel> - ) => Promise<Fields<TBaseModel>>; - - const fetchDropdownOptions: FetchDropdownOptionsFunction = async ( - fields: Fields<TBaseModel> - ): Promise<Fields<TBaseModel>> => { - setIsFetchingDropdownOptions(true); - - try { - for (const field of fields) { - if (field.dropdownModal && field.dropdownModal.type) { - const tempModel: BaseModel = new field.dropdownModal.type(); - const select: any = { - [field.dropdownModal.labelField]: true, - [field.dropdownModal.valueField]: true, - } as any; - - let hasAccessControlColumn: boolean = false; - - // also select labels, so they can select resources by labels. This is useful for resources like monitors, etc. - if (tempModel.getAccessControlColumn()) { - select[tempModel.getAccessControlColumn()!] = { - _id: true, - name: true, - color: true, - } as any; - - hasAccessControlColumn = true; - } - - const listResult: ListResult<BaseModel> = - await modelAPI.getList<BaseModel>({ - modelType: field.dropdownModal.type, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: select, - sort: {}, - }); - - if (listResult.data && listResult.data.length > 0) { - field.dropdownOptions = listResult.data.map( - (item: BaseModel) => { - if (!field.dropdownModal) { - throw new BadDataException( - 'Dropdown Modal value mot found' - ); - } - - return { - label: (item as any)[ - field.dropdownModal?.labelField - ].toString(), - value: (item as any)[ - field.dropdownModal?.valueField - ].toString(), - }; - } - ); - - if (hasAccessControlColumn) { - const categories: Array<CheckboxCategory> = []; - - // populate categories. - - let localLabels: Array<AccessControlModel> = []; - - for (const item of listResult.data) { - const accessControlColumn: string | null = - tempModel.getAccessControlColumn()!; - const labels: Array<AccessControlModel> = - ((item as any)[ - accessControlColumn - ] as Array<AccessControlModel>) || []; - - for (const label of labels) { - if ( - label && - label._id && - label.getColumnValue('name') - ) { - // check if this category already exists. - - const existingLabel: - | AccessControlModel - | undefined = localLabels.find( - (i: AccessControlModel) => { - return ( - i._id?.toString() === - label._id?.toString() - ); - } - ); - - if (!existingLabel) { - localLabels.push(label); - } - } - } - } - - // sort category by name. - - localLabels = localLabels.sort( - ( - a: AccessControlModel, - b: AccessControlModel - ) => { - return a - .getColumnValue('name')! - .toString() - .localeCompare( - b - .getColumnValue('name') - ?.toString() || '' - ); - } - ); - - // for each of these labels add category. - - for (const label of localLabels) { - categories.push({ - id: label._id?.toString() || '', - title: ( - <span className="mb-1"> - <Pill - size={PillSize.Small} - color={ - (label.getColumnValue( - 'color' - ) as Color) || Black - } - text={ - (label.getColumnValue( - 'name' - ) as string) || '' - } - /> - </span> - ), - }); - } - - // now populate options. - const options: Array<CategoryCheckboxOption> = []; - - for (const item of listResult.data) { - const accessControlColumn: string = - tempModel.getAccessControlColumn()!; - const labels: Array<AccessControlModel> = - ((item as any)[ - accessControlColumn - ] as Array<AccessControlModel>) || []; - - if (labels.length > 0) { - for (const label of labels) { - options.push({ - value: item.getColumnValue( - field.dropdownModal.valueField - ) as string, - label: item.getColumnValue( - field.dropdownModal.labelField - ) as string, - categoryId: - label._id?.toString() || '', - }); - } - } else { - options.push({ - value: item.getColumnValue( - field.dropdownModal.valueField - ) as string, - label: item.getColumnValue( - field.dropdownModal.labelField - ) as string, - categoryId: '', - }); - } - } - - field.selectByAccessControlProps = { - categoryCheckboxProps: { - categories: categories, - options: options, - }, - accessControlColumnTitle: - tempModel.getTableColumnMetadata( - tempModel.getAccessControlColumn()! - ).title || '', - }; - } - } else { - field.dropdownOptions = []; - } - } - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsFetchingDropdownOptions(false); - - return fields; - }; - - useAsyncEffect(async () => { - if ( - props.modelIdToEdit && - props.formType === FormType.Update && - !props.doNotFetchExistingModel - ) { - // get item. - setLoading(true); - setIsFetching(true); - setError(''); - try { - await fetchItem(); - } catch (err) { - setError(API.getFriendlyMessage(err)); - props.onError && props.onError(API.getFriendlyMessage(err)); - } - - setLoading(false); - setIsFetching(false); - } - }, []); - - type GetMiscDataPropsFunction = ( - values: FormValues<JSONObject> - ) => JSONObject; - - const getMiscDataProps: GetMiscDataPropsFunction = ( - values: FormValues<JSONObject> - ): JSONObject => { - const result: JSONObject = {}; - - for (const field of fields) { - if (field.overrideFieldKey && values[field.overrideFieldKey]) { - result[field.overrideFieldKey] = - (values[field.overrideFieldKey] as JSONObject) || null; - } - } - - return result; - }; - - const onSubmit: ( - values: FormValues<JSONObject>, - onSubmitSuccessful: () => void - ) => Promise<void> = async ( - values: FormValues<JSONObject>, - onSubmitSuccessful: () => void - ): Promise<void> => { - // Ping an API here. - - setError(''); - setLoading(true); - if (props.onLoadingChange) { - props.onLoadingChange(true); - } - - let result: ModelAPIHttpResponse<TBaseModel>; - - try { - // strip data. - const valuesToSend: JSONObject = {}; - - for (const key in getSelectFields()) { - (valuesToSend as any)[key] = values[key]; - } - - if (props.formType === FormType.Update && props.modelIdToEdit) { - (valuesToSend as any)['_id'] = props.modelIdToEdit.toString(); - } - - const miscDataProps: JSONObject = getMiscDataProps(values); - - // remove those props from valuesToSend - for (const key in miscDataProps) { - delete valuesToSend[key]; - } - - for (const key of model.getTableColumns().columns) { - const tableColumnMetadata: TableColumnMetadata = - model.getTableColumnMetadata(key); - - if ( - tableColumnMetadata && - tableColumnMetadata.modelType && - tableColumnMetadata.type === TableColumnType.Entity && - valuesToSend[key] && - typeof valuesToSend[key] === Typeof.String - ) { - const baseModel: BaseModel = - new tableColumnMetadata.modelType(); - baseModel._id = valuesToSend[key] as string; - valuesToSend[key] = baseModel; - } - - if ( - tableColumnMetadata && - tableColumnMetadata.modelType && - tableColumnMetadata.type === TableColumnType.EntityArray && - Array.isArray(valuesToSend[key]) && - (valuesToSend[key] as Array<any>).length > 0 && - typeof (valuesToSend[key] as Array<any>)[0] === - Typeof.Object && - typeof (valuesToSend[key] as Array<any>)[0].value === - Typeof.String - ) { - const arr: Array<string> = []; - for (const id of valuesToSend[ - key - ] as Array<GenericObject>) { - arr.push((id as any).value as string); - } - valuesToSend[key] = arr; - } - - if ( - tableColumnMetadata && - tableColumnMetadata.modelType && - tableColumnMetadata.type === TableColumnType.EntityArray && - Array.isArray(valuesToSend[key]) && - (valuesToSend[key] as Array<any>).length > 0 && - typeof (valuesToSend[key] as Array<any>)[0] === - Typeof.String - ) { - const arr: Array<BaseModel> = []; - for (const id of valuesToSend[key] as Array<string>) { - const baseModel: BaseModel = - new tableColumnMetadata.modelType(); - baseModel._id = id as string; - arr.push(baseModel); - } - valuesToSend[key] = arr; - } - } - - let tBaseModel: TBaseModel = BaseModel.fromJSON( - valuesToSend, - props.modelType - ) as TBaseModel; - - if (props.onBeforeCreate && props.formType === FormType.Create) { - tBaseModel = await props.onBeforeCreate( - tBaseModel, - miscDataProps - ); - } - - result = await modelAPI.createOrUpdate<TBaseModel>({ - model: tBaseModel as TBaseModel, - modelType: props.modelType, - formType: props.formType, - miscDataProps: miscDataProps, - requestOptions: { - ...props.saveRequestOptions, - requestHeaders: props.requestHeaders, - overrideRequestUrl: props.createOrUpdateApiUrl, - }, - }); - - const miscData: JSONObject | undefined = result.miscData; - - if (props.onSuccess) { - // we do props.formType === FormType.Create ? result.data: tBaseModel because update API does not return the updated model. - props.onSuccess( - BaseModel.fromJSONObject( - props.formType === FormType.Create - ? result.data - : tBaseModel, - props.modelType - ), - miscData - ); - } - - onSubmitSuccessful(); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setLoading(false); - - if (props.onLoadingChange) { - props.onLoadingChange(false); - } - }; - - if (isFetching || isFetchingDropdownOptions) { - return ( - <div className="row flex justify-center mt-20 mb-20"> - <Loader - loaderType={LoaderType.Bar} - color={VeryLightGray} - size={200} - /> - </div> - ); + const [fields, setFields] = useState<Fields<TBaseModel>>([]); + const [isLoading, setLoading] = useState<boolean>(false); + const [isFetching, setIsFetching] = useState<boolean>(false); + const [isFetchingDropdownOptions, setIsFetchingDropdownOptions] = + useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [itemToEdit, setItemToEdit] = useState<TBaseModel | null>(null); + const model: TBaseModel = new props.modelType(); + + const modelAPI: typeof ModelAPI = props.modelAPI || ModelAPI; + + type GetSelectFieldsFunction = () => Select<TBaseModel>; + + const getSelectFields: GetSelectFieldsFunction = (): Select<TBaseModel> => { + const select: Select<TBaseModel> = {}; + for (const field of props.fields) { + const key: string | null = field.field + ? (Object.keys(field.field)[0] as string) + : null; + + if ( + key && + (hasPermissionOnField(key) || field.showEvenIfPermissionDoesNotExist) + ) { + (select as Dictionary<boolean>)[key] = true; + } } - return ( - <div> - <BasicModelForm<TBaseModel> - title={props.title} - description={props.description} - disableAutofocus={props.disableAutofocus} - model={model} - id={props.id} - name={props.name} - onFormStepChange={props.onFormStepChange} - onIsLastFormStep={props.onIsLastFormStep} - fields={fields} - steps={props.steps} - onChange={(values: FormValues<TBaseModel>) => { - if (!isLoading) { - props.onChange && props.onChange(values); - } - }} - showAsColumns={props.showAsColumns} - footer={props.footer} - isLoading={isLoading} - submitButtonText={props.submitButtonText} - cancelButtonText={props.cancelButtonText} - onSubmit={onSubmit} - submitButtonStyleType={props.submitButtonStyleType} - onValidate={props.onValidate} - onCancel={props.onCancel} - maxPrimaryButtonWidth={props.maxPrimaryButtonWidth} - error={error} - hideSubmitButton={props.hideSubmitButton} - formRef={props.formRef} - initialValues={ - (itemToEdit || props.initialValues) as - | FormValues<TBaseModel> - | undefined + return select; + }; + + const getRelationSelect: () => Select<TBaseModel> = + (): Select<TBaseModel> => { + const relationSelect: Select<TBaseModel> = {}; + + for (const field of props.fields) { + const key: string | null = field.field + ? (Object.keys(field.field)[0] as string) + : null; + + if (key && model.isFileColumn(key)) { + (relationSelect as JSONObject)[key] = { + file: true, + _id: true, + type: true, + name: true, + }; + } else if (key && model.isEntityColumn(key)) { + (relationSelect as JSONObject)[key] = (field.field as any)[key]; + } + } + + return relationSelect; + }; + + const hasPermissionOnField: (fieldName: string) => boolean = ( + fieldName: string, + ): boolean => { + if (User.isMasterAdmin()) { + return true; // master admin can do anything. + } + + let userPermissions: Array<Permission> = + PermissionUtil.getGlobalPermissions()?.globalPermissions || []; + if ( + PermissionUtil.getProjectPermissions() && + PermissionUtil.getProjectPermissions()?.permissions && + PermissionUtil.getProjectPermissions()!.permissions.length > 0 + ) { + userPermissions = userPermissions.concat( + PermissionUtil.getProjectPermissions()!.permissions.map( + (i: UserPermission) => { + return i.permission; + }, + ), + ); + } + + userPermissions.push(Permission.Public); + + const accessControl: Dictionary<ColumnAccessControl> = + model.getColumnAccessControlForAllColumns(); + + let fieldPermissions: Array<Permission> = []; + + if (FormType.Create === props.formType) { + fieldPermissions = accessControl[fieldName]?.create || []; + } else { + fieldPermissions = accessControl[fieldName]?.update || []; + } + + if ( + fieldPermissions && + PermissionHelper.doesPermissionsIntersect( + userPermissions, + fieldPermissions, + ) + ) { + return true; + } + + return false; + }; + + const setFormFields: PromiseVoidFunction = async (): Promise<void> => { + let fieldsToSet: Fields<TBaseModel> = []; + + for (const field of props.fields) { + const fieldObj: + | { + [field: string]: true; + } + | SelectFormFields<TBaseModel> + | undefined = field.field || field.overrideField; + + if (!fieldObj) { + continue; + } + + const keys: Array<string> = Object.keys(fieldObj); + + if (keys.length > 0) { + const key: string = keys[0] as string; + + const hasPermission: boolean = hasPermissionOnField(key); + + if ( + (field.showEvenIfPermissionDoesNotExist || hasPermission) && + fieldsToSet.filter((i: ModelField<TBaseModel>) => { + const fieldObj: + | { + [field: string]: true; } - ></BasicModelForm> - </div> + | SelectFormFields<TBaseModel> + | undefined = i.field || i.overrideField; + + if (!fieldObj) { + return false; + } + // check if field already exists. If it does, don't add it. + const iKeys: Array<string> = Object.keys(fieldObj); + const iFieldKey: string = iKeys[0] as string; + return iFieldKey === key; + }).length === 0 + ) { + // check if has maxLength + if ( + !field.validation?.maxLength && + model.getTableColumnMetadata(key)?.type + ) { + field.validation = { + ...field.validation, + maxLength: getMaxLengthFromTableColumnType( + model.getTableColumnMetadata(key).type, + ), + }; + } + + fieldsToSet.push({ + ...field, + field: { + [key]: true, + } as SelectFormFields<TBaseModel>, + }); + } + } + } + + fieldsToSet = await fetchDropdownOptions(fieldsToSet); + + setFields(fieldsToSet); + }; + + useAsyncEffect(async () => { + // set fields. + await setFormFields(); + }, [props.fields]); + + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + if (!props.modelIdToEdit || props.formType !== FormType.Update) { + throw new BadDataException("Model ID to update not found."); + } + + let item: BaseModel | null = await modelAPI.getItem({ + modelType: props.modelType, + id: props.modelIdToEdit, + select: { ...getSelectFields(), ...getRelationSelect() }, + requestOptions: { + overrideRequestUrl: props.fetchItemApiUrl, + }, + }); + + if (!(item instanceof BaseModel) && item) { + item = BaseModel.fromJSON( + item as JSONObject, + props.modelType, + ) as BaseModel; + } + + if (!item) { + setError( + `Cannot edit ${( + model.singularName || "item" + ).toLowerCase()}. It could be because you don't have enough permissions to read or edit this ${( + model.singularName || "item" + ).toLowerCase()}.`, + ); + } + + const relationSelect: Select<TBaseModel> = getRelationSelect(); + + for (const key in relationSelect) { + if (item) { + if (Array.isArray((item as any)[key])) { + const idArray: Array<string> = []; + let isModelArray: boolean = false; + for (const itemInArray of (item as any)[key] as any) { + if (typeof (itemInArray as any) === "object") { + if ((itemInArray as any as JSONObject)["_id"]) { + isModelArray = true; + idArray.push( + (itemInArray as any as JSONObject)["_id"] as string, + ); + } + } + } + + if (isModelArray) { + (item as any)[key] = idArray; + } + } + if ( + (item as any)[key] && + typeof (item as any)[key] === "object" && + !((item as any)[key] instanceof FileModel) + ) { + if (((item as any)[key] as JSONObject)["_id"]) { + (item as any)[key] = ((item as any)[key] as JSONObject)[ + "_id" + ] as string; + } + } + } + } + + setItemToEdit(item as TBaseModel); + }; + + type FetchDropdownOptionsFunction = ( + fields: Fields<TBaseModel>, + ) => Promise<Fields<TBaseModel>>; + + const fetchDropdownOptions: FetchDropdownOptionsFunction = async ( + fields: Fields<TBaseModel>, + ): Promise<Fields<TBaseModel>> => { + setIsFetchingDropdownOptions(true); + + try { + for (const field of fields) { + if (field.dropdownModal && field.dropdownModal.type) { + const tempModel: BaseModel = new field.dropdownModal.type(); + const select: any = { + [field.dropdownModal.labelField]: true, + [field.dropdownModal.valueField]: true, + } as any; + + let hasAccessControlColumn: boolean = false; + + // also select labels, so they can select resources by labels. This is useful for resources like monitors, etc. + if (tempModel.getAccessControlColumn()) { + select[tempModel.getAccessControlColumn()!] = { + _id: true, + name: true, + color: true, + } as any; + + hasAccessControlColumn = true; + } + + const listResult: ListResult<BaseModel> = + await modelAPI.getList<BaseModel>({ + modelType: field.dropdownModal.type, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: select, + sort: {}, + }); + + if (listResult.data && listResult.data.length > 0) { + field.dropdownOptions = listResult.data.map((item: BaseModel) => { + if (!field.dropdownModal) { + throw new BadDataException("Dropdown Modal value mot found"); + } + + return { + label: (item as any)[ + field.dropdownModal?.labelField + ].toString(), + value: (item as any)[ + field.dropdownModal?.valueField + ].toString(), + }; + }); + + if (hasAccessControlColumn) { + const categories: Array<CheckboxCategory> = []; + + // populate categories. + + let localLabels: Array<AccessControlModel> = []; + + for (const item of listResult.data) { + const accessControlColumn: string | null = + tempModel.getAccessControlColumn()!; + const labels: Array<AccessControlModel> = + ((item as any)[ + accessControlColumn + ] as Array<AccessControlModel>) || []; + + for (const label of labels) { + if (label && label._id && label.getColumnValue("name")) { + // check if this category already exists. + + const existingLabel: AccessControlModel | undefined = + localLabels.find((i: AccessControlModel) => { + return i._id?.toString() === label._id?.toString(); + }); + + if (!existingLabel) { + localLabels.push(label); + } + } + } + } + + // sort category by name. + + localLabels = localLabels.sort( + (a: AccessControlModel, b: AccessControlModel) => { + return a + .getColumnValue("name")! + .toString() + .localeCompare(b.getColumnValue("name")?.toString() || ""); + }, + ); + + // for each of these labels add category. + + for (const label of localLabels) { + categories.push({ + id: label._id?.toString() || "", + title: ( + <span className="mb-1"> + <Pill + size={PillSize.Small} + color={ + (label.getColumnValue("color") as Color) || Black + } + text={(label.getColumnValue("name") as string) || ""} + /> + </span> + ), + }); + } + + // now populate options. + const options: Array<CategoryCheckboxOption> = []; + + for (const item of listResult.data) { + const accessControlColumn: string = + tempModel.getAccessControlColumn()!; + const labels: Array<AccessControlModel> = + ((item as any)[ + accessControlColumn + ] as Array<AccessControlModel>) || []; + + if (labels.length > 0) { + for (const label of labels) { + options.push({ + value: item.getColumnValue( + field.dropdownModal.valueField, + ) as string, + label: item.getColumnValue( + field.dropdownModal.labelField, + ) as string, + categoryId: label._id?.toString() || "", + }); + } + } else { + options.push({ + value: item.getColumnValue( + field.dropdownModal.valueField, + ) as string, + label: item.getColumnValue( + field.dropdownModal.labelField, + ) as string, + categoryId: "", + }); + } + } + + field.selectByAccessControlProps = { + categoryCheckboxProps: { + categories: categories, + options: options, + }, + accessControlColumnTitle: + tempModel.getTableColumnMetadata( + tempModel.getAccessControlColumn()!, + ).title || "", + }; + } + } else { + field.dropdownOptions = []; + } + } + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsFetchingDropdownOptions(false); + + return fields; + }; + + useAsyncEffect(async () => { + if ( + props.modelIdToEdit && + props.formType === FormType.Update && + !props.doNotFetchExistingModel + ) { + // get item. + setLoading(true); + setIsFetching(true); + setError(""); + try { + await fetchItem(); + } catch (err) { + setError(API.getFriendlyMessage(err)); + props.onError && props.onError(API.getFriendlyMessage(err)); + } + + setLoading(false); + setIsFetching(false); + } + }, []); + + type GetMiscDataPropsFunction = ( + values: FormValues<JSONObject>, + ) => JSONObject; + + const getMiscDataProps: GetMiscDataPropsFunction = ( + values: FormValues<JSONObject>, + ): JSONObject => { + const result: JSONObject = {}; + + for (const field of fields) { + if (field.overrideFieldKey && values[field.overrideFieldKey]) { + result[field.overrideFieldKey] = + (values[field.overrideFieldKey] as JSONObject) || null; + } + } + + return result; + }; + + const onSubmit: ( + values: FormValues<JSONObject>, + onSubmitSuccessful: () => void, + ) => Promise<void> = async ( + values: FormValues<JSONObject>, + onSubmitSuccessful: () => void, + ): Promise<void> => { + // Ping an API here. + + setError(""); + setLoading(true); + if (props.onLoadingChange) { + props.onLoadingChange(true); + } + + let result: ModelAPIHttpResponse<TBaseModel>; + + try { + // strip data. + const valuesToSend: JSONObject = {}; + + for (const key in getSelectFields()) { + (valuesToSend as any)[key] = values[key]; + } + + if (props.formType === FormType.Update && props.modelIdToEdit) { + (valuesToSend as any)["_id"] = props.modelIdToEdit.toString(); + } + + const miscDataProps: JSONObject = getMiscDataProps(values); + + // remove those props from valuesToSend + for (const key in miscDataProps) { + delete valuesToSend[key]; + } + + for (const key of model.getTableColumns().columns) { + const tableColumnMetadata: TableColumnMetadata = + model.getTableColumnMetadata(key); + + if ( + tableColumnMetadata && + tableColumnMetadata.modelType && + tableColumnMetadata.type === TableColumnType.Entity && + valuesToSend[key] && + typeof valuesToSend[key] === Typeof.String + ) { + const baseModel: BaseModel = new tableColumnMetadata.modelType(); + baseModel._id = valuesToSend[key] as string; + valuesToSend[key] = baseModel; + } + + if ( + tableColumnMetadata && + tableColumnMetadata.modelType && + tableColumnMetadata.type === TableColumnType.EntityArray && + Array.isArray(valuesToSend[key]) && + (valuesToSend[key] as Array<any>).length > 0 && + typeof (valuesToSend[key] as Array<any>)[0] === Typeof.Object && + typeof (valuesToSend[key] as Array<any>)[0].value === Typeof.String + ) { + const arr: Array<string> = []; + for (const id of valuesToSend[key] as Array<GenericObject>) { + arr.push((id as any).value as string); + } + valuesToSend[key] = arr; + } + + if ( + tableColumnMetadata && + tableColumnMetadata.modelType && + tableColumnMetadata.type === TableColumnType.EntityArray && + Array.isArray(valuesToSend[key]) && + (valuesToSend[key] as Array<any>).length > 0 && + typeof (valuesToSend[key] as Array<any>)[0] === Typeof.String + ) { + const arr: Array<BaseModel> = []; + for (const id of valuesToSend[key] as Array<string>) { + const baseModel: BaseModel = new tableColumnMetadata.modelType(); + baseModel._id = id as string; + arr.push(baseModel); + } + valuesToSend[key] = arr; + } + } + + let tBaseModel: TBaseModel = BaseModel.fromJSON( + valuesToSend, + props.modelType, + ) as TBaseModel; + + if (props.onBeforeCreate && props.formType === FormType.Create) { + tBaseModel = await props.onBeforeCreate(tBaseModel, miscDataProps); + } + + result = await modelAPI.createOrUpdate<TBaseModel>({ + model: tBaseModel as TBaseModel, + modelType: props.modelType, + formType: props.formType, + miscDataProps: miscDataProps, + requestOptions: { + ...props.saveRequestOptions, + requestHeaders: props.requestHeaders, + overrideRequestUrl: props.createOrUpdateApiUrl, + }, + }); + + const miscData: JSONObject | undefined = result.miscData; + + if (props.onSuccess) { + // we do props.formType === FormType.Create ? result.data: tBaseModel because update API does not return the updated model. + props.onSuccess( + BaseModel.fromJSONObject( + props.formType === FormType.Create ? result.data : tBaseModel, + props.modelType, + ), + miscData, + ); + } + + onSubmitSuccessful(); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setLoading(false); + + if (props.onLoadingChange) { + props.onLoadingChange(false); + } + }; + + if (isFetching || isFetchingDropdownOptions) { + return ( + <div className="row flex justify-center mt-20 mb-20"> + <Loader loaderType={LoaderType.Bar} color={VeryLightGray} size={200} /> + </div> ); + } + + return ( + <div> + <BasicModelForm<TBaseModel> + title={props.title} + description={props.description} + disableAutofocus={props.disableAutofocus} + model={model} + id={props.id} + name={props.name} + onFormStepChange={props.onFormStepChange} + onIsLastFormStep={props.onIsLastFormStep} + fields={fields} + steps={props.steps} + onChange={(values: FormValues<TBaseModel>) => { + if (!isLoading) { + props.onChange && props.onChange(values); + } + }} + showAsColumns={props.showAsColumns} + footer={props.footer} + isLoading={isLoading} + submitButtonText={props.submitButtonText} + cancelButtonText={props.cancelButtonText} + onSubmit={onSubmit} + submitButtonStyleType={props.submitButtonStyleType} + onValidate={props.onValidate} + onCancel={props.onCancel} + maxPrimaryButtonWidth={props.maxPrimaryButtonWidth} + error={error} + hideSubmitButton={props.hideSubmitButton} + formRef={props.formRef} + initialValues={ + (itemToEdit || props.initialValues) as + | FormValues<TBaseModel> + | undefined + } + ></BasicModelForm> + </div> + ); }; export default ModelForm; diff --git a/CommonUI/src/Components/Forms/Steps/Step.tsx b/CommonUI/src/Components/Forms/Steps/Step.tsx index 3aec5d9cc6..add9062930 100644 --- a/CommonUI/src/Components/Forms/Steps/Step.tsx +++ b/CommonUI/src/Components/Forms/Steps/Step.tsx @@ -1,85 +1,85 @@ -import { FormStep, FormStepState } from '../Types/FormStep'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; +import { FormStep, FormStepState } from "../Types/FormStep"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; export interface ComponentProps<T> { - step: FormStep<T>; - onClick: (step: FormStep<T>) => void; - state: FormStepState; + step: FormStep<T>; + onClick: (step: FormStep<T>) => void; + state: FormStepState; } const Step: <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - return ( - <li - onClick={() => { - if (props.state === FormStepState.COMPLETED) { - props.onClick(props.step); - } - }} - className={`${ - props.state === FormStepState.COMPLETED ? 'cursor-pointer' : '' - }`} - > - {props.state === FormStepState.COMPLETED && ( - <div className="group"> - <span className="flex items-start"> - <span className="relative flex h-5 w-5 flex-shrink-0 items-center justify-center"> - <svg - className="h-full w-full text-indigo-600 group-hover:text-indigo-800" - viewBox="0 0 20 20" - fill="currentColor" - aria-hidden="true" - > - <path - fillRule="evenodd" - d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" - clipRule="evenodd" - /> - </svg> - </span> - <span className="ml-3 text-sm font-medium text-gray-500 group-hover:text-gray-900"> - {props.step.title} - </span> - </span> - </div> - )} + return ( + <li + onClick={() => { + if (props.state === FormStepState.COMPLETED) { + props.onClick(props.step); + } + }} + className={`${ + props.state === FormStepState.COMPLETED ? "cursor-pointer" : "" + }`} + > + {props.state === FormStepState.COMPLETED && ( + <div className="group"> + <span className="flex items-start"> + <span className="relative flex h-5 w-5 flex-shrink-0 items-center justify-center"> + <svg + className="h-full w-full text-indigo-600 group-hover:text-indigo-800" + viewBox="0 0 20 20" + fill="currentColor" + aria-hidden="true" + > + <path + fillRule="evenodd" + d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" + clipRule="evenodd" + /> + </svg> + </span> + <span className="ml-3 text-sm font-medium text-gray-500 group-hover:text-gray-900"> + {props.step.title} + </span> + </span> + </div> + )} - {props.state === FormStepState.ACTIVE && ( - <div className="flex items-start" aria-current="step"> - <span - className="relative flex h-5 w-5 flex-shrink-0 items-center justify-center" - aria-hidden="true" - > - <span className="absolute h-4 w-4 rounded-full bg-indigo-200"></span> - <span className="relative block h-2 w-2 rounded-full bg-indigo-600"></span> - </span> - <span className="ml-3 text-sm font-medium text-indigo-600"> - {props.step.title} - </span> - </div> - )} + {props.state === FormStepState.ACTIVE && ( + <div className="flex items-start" aria-current="step"> + <span + className="relative flex h-5 w-5 flex-shrink-0 items-center justify-center" + aria-hidden="true" + > + <span className="absolute h-4 w-4 rounded-full bg-indigo-200"></span> + <span className="relative block h-2 w-2 rounded-full bg-indigo-600"></span> + </span> + <span className="ml-3 text-sm font-medium text-indigo-600"> + {props.step.title} + </span> + </div> + )} - {props.state === FormStepState.INACTIVE && ( - <div className="group"> - <div className="flex items-start"> - <div - className="relative flex h-5 w-5 flex-shrink-0 items-center justify-center" - aria-hidden="true" - > - <div className="h-2 w-2 rounded-full bg-gray-300"></div> - </div> - <p className="ml-3 text-sm font-medium text-gray-500 w-max"> - {props.step.title} - </p> - </div> - </div> - )} - </li> - ); + {props.state === FormStepState.INACTIVE && ( + <div className="group"> + <div className="flex items-start"> + <div + className="relative flex h-5 w-5 flex-shrink-0 items-center justify-center" + aria-hidden="true" + > + <div className="h-2 w-2 rounded-full bg-gray-300"></div> + </div> + <p className="ml-3 text-sm font-medium text-gray-500 w-max"> + {props.step.title} + </p> + </div> + </div> + )} + </li> + ); }; export default Step; diff --git a/CommonUI/src/Components/Forms/Steps/Steps.tsx b/CommonUI/src/Components/Forms/Steps/Steps.tsx index 7f51f84581..72b2a92683 100644 --- a/CommonUI/src/Components/Forms/Steps/Steps.tsx +++ b/CommonUI/src/Components/Forms/Steps/Steps.tsx @@ -1,67 +1,65 @@ -import { FormStep, FormStepState } from '../Types/FormStep'; -import FormValues from '../Types/FormValues'; -import Step from './Step'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; +import { FormStep, FormStepState } from "../Types/FormStep"; +import FormValues from "../Types/FormValues"; +import Step from "./Step"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; export interface ComponentProps<T> { - steps: Array<FormStep<T>>; - onClick: (step: FormStep<T>) => void; - currentFormStepId: string; - formValues: FormValues<T>; + steps: Array<FormStep<T>>; + onClick: (step: FormStep<T>) => void; + currentFormStepId: string; + formValues: FormValues<T>; } const Steps: <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const steps: Array<FormStep<T>> = props.steps.filter( - (step: FormStep<T>) => { - if (!step.showIf) { - return true; + const steps: Array<FormStep<T>> = props.steps.filter((step: FormStep<T>) => { + if (!step.showIf) { + return true; + } + + return step.showIf(props.formValues); + }); + + return ( + <div className="pr-4 py-6 sm:pr-6 lg:pr-8"> + <nav className="flex" aria-label="Progress"> + <ol role="list" className="space-y-6"> + {steps.map((step: FormStep<T>, index: number) => { + const indexOfCurrentState: number = steps.findIndex( + (step: FormStep<T>) => { + return step.id === props.currentFormStepId; + }, + ); + + let state: FormStepState = FormStepState.INACTIVE; + + if (indexOfCurrentState > index) { + state = FormStepState.COMPLETED; + } else if (indexOfCurrentState === index) { + state = FormStepState.ACTIVE; + } else { + state = FormStepState.INACTIVE; } - return step.showIf(props.formValues); - } - ); - - return ( - <div className="pr-4 py-6 sm:pr-6 lg:pr-8"> - <nav className="flex" aria-label="Progress"> - <ol role="list" className="space-y-6"> - {steps.map((step: FormStep<T>, index: number) => { - const indexOfCurrentState: number = steps.findIndex( - (step: FormStep<T>) => { - return step.id === props.currentFormStepId; - } - ); - - let state: FormStepState = FormStepState.INACTIVE; - - if (indexOfCurrentState > index) { - state = FormStepState.COMPLETED; - } else if (indexOfCurrentState === index) { - state = FormStepState.ACTIVE; - } else { - state = FormStepState.INACTIVE; - } - - return ( - <Step - state={state} - step={step} - key={index} - onClick={(step: FormStep<T>) => { - props.onClick(step); - }} - /> - ); - })} - </ol> - </nav> - </div> - ); + return ( + <Step + state={state} + step={step} + key={index} + onClick={(step: FormStep<T>) => { + props.onClick(step); + }} + /> + ); + })} + </ol> + </nav> + </div> + ); }; export default Steps; diff --git a/CommonUI/src/Components/Forms/Types/Field.ts b/CommonUI/src/Components/Forms/Types/Field.ts index e651a389e7..0b41c71002 100644 --- a/CommonUI/src/Components/Forms/Types/Field.ts +++ b/CommonUI/src/Components/Forms/Types/Field.ts @@ -1,100 +1,100 @@ -import SelectFormFields from '../../../Types/SelectEntityField'; +import SelectFormFields from "../../../Types/SelectEntityField"; import { - CategoryCheckboxOption, - CheckboxCategory, -} from '../../CategoryCheckbox/CategoryCheckboxTypes'; -import { DropdownOption } from '../../Dropdown/Dropdown'; -import { RadioButton } from '../../RadioButtons/GroupRadioButtons'; -import FormFieldSchemaType from './FormFieldSchemaType'; -import FormValues from './FormValues'; -import { BaseModelType } from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import MimeType from 'Common/Types/File/MimeType'; -import { ReactElement } from 'react'; + CategoryCheckboxOption, + CheckboxCategory, +} from "../../CategoryCheckbox/CategoryCheckboxTypes"; +import { DropdownOption } from "../../Dropdown/Dropdown"; +import { RadioButton } from "../../RadioButtons/GroupRadioButtons"; +import FormFieldSchemaType from "./FormFieldSchemaType"; +import FormValues from "./FormValues"; +import { BaseModelType } from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import MimeType from "Common/Types/File/MimeType"; +import { ReactElement } from "react"; export enum FormFieldStyleType { - Default = 'Default', - Heading = 'Heading', - DividerBelow = 'DividerBelow', + Default = "Default", + Heading = "Heading", + DividerBelow = "DividerBelow", } export interface FormFieldSideLink { - text: string; - url: Route | URL; - openLinkInNewTab?: boolean; + text: string; + url: Route | URL; + openLinkInNewTab?: boolean; } export interface CustomElementProps { - error?: string | undefined; - tabIndex?: number | undefined; - onChange?: ((value: any) => void) | undefined; - onBlur?: () => void; - initialValue?: any; - placeholder?: string | undefined; + error?: string | undefined; + tabIndex?: number | undefined; + onChange?: ((value: any) => void) | undefined; + onBlur?: () => void; + initialValue?: any; + placeholder?: string | undefined; } export interface CategoryCheckboxProps { - categories: Array<CheckboxCategory>; - options: Array<CategoryCheckboxOption>; + categories: Array<CheckboxCategory>; + options: Array<CategoryCheckboxOption>; } export default interface Field<TEntity> { - name?: string; // form field name, should be unique in thr form. If not provided, the field will be auto generated. - title?: string; - description?: string; - field?: SelectFormFields<TEntity> | undefined; - placeholder?: string; - showEvenIfPermissionDoesNotExist?: boolean; // show this field even if user does not have permissions to view. - disabled?: boolean; - stepId?: string | undefined; - required?: boolean | ((item: FormValues<TEntity>) => boolean) | undefined; - dropdownOptions?: Array<DropdownOption> | undefined; - fetchDropdownOptions?: - | ((item: FormValues<TEntity>) => Promise<Array<DropdownOption>>) - | undefined; - dropdownModal?: { - type: BaseModelType; - labelField: string; - valueField: string; - }; - selectByAccessControlProps?: { - categoryCheckboxProps: CategoryCheckboxProps; - accessControlColumnTitle: string; - }; - fileTypes?: Array<MimeType> | undefined; - sideLink?: FormFieldSideLink | undefined; - validation?: { - minLength?: number | undefined; - maxLength?: number | undefined; - toMatchField?: keyof TEntity | undefined; - noSpaces?: boolean | undefined; - noSpecialCharacters?: boolean; - noNumbers?: boolean | undefined; - minValue?: number | undefined; - maxValue?: number | undefined; - dateShouldBeInTheFuture?: boolean | undefined; - }; - customValidation?: - | ((values: FormValues<TEntity>) => string | null) - | undefined; - styleType?: FormFieldStyleType | undefined; - showIf?: ((item: FormValues<TEntity>) => boolean) | undefined; - onChange?: ((value: any) => void) | undefined; - fieldType?: FormFieldSchemaType; - overrideFieldKey?: string; - defaultValue?: boolean | string | Date | number | undefined; - radioButtonOptions?: Array<RadioButton>; - footerElement?: ReactElement | undefined; - id?: string | undefined; - getCustomElement?: ( - values: FormValues<TEntity>, - props: CustomElementProps - ) => ReactElement | undefined; // custom element to render instead of the elements in the form. - categoryCheckboxProps?: CategoryCheckboxProps | undefined; // props for the category checkbox component. If fieldType is CategoryCheckbox, this prop is required. - dataTestId?: string | undefined; + name?: string; // form field name, should be unique in thr form. If not provided, the field will be auto generated. + title?: string; + description?: string; + field?: SelectFormFields<TEntity> | undefined; + placeholder?: string; + showEvenIfPermissionDoesNotExist?: boolean; // show this field even if user does not have permissions to view. + disabled?: boolean; + stepId?: string | undefined; + required?: boolean | ((item: FormValues<TEntity>) => boolean) | undefined; + dropdownOptions?: Array<DropdownOption> | undefined; + fetchDropdownOptions?: + | ((item: FormValues<TEntity>) => Promise<Array<DropdownOption>>) + | undefined; + dropdownModal?: { + type: BaseModelType; + labelField: string; + valueField: string; + }; + selectByAccessControlProps?: { + categoryCheckboxProps: CategoryCheckboxProps; + accessControlColumnTitle: string; + }; + fileTypes?: Array<MimeType> | undefined; + sideLink?: FormFieldSideLink | undefined; + validation?: { + minLength?: number | undefined; + maxLength?: number | undefined; + toMatchField?: keyof TEntity | undefined; + noSpaces?: boolean | undefined; + noSpecialCharacters?: boolean; + noNumbers?: boolean | undefined; + minValue?: number | undefined; + maxValue?: number | undefined; + dateShouldBeInTheFuture?: boolean | undefined; + }; + customValidation?: + | ((values: FormValues<TEntity>) => string | null) + | undefined; + styleType?: FormFieldStyleType | undefined; + showIf?: ((item: FormValues<TEntity>) => boolean) | undefined; + onChange?: ((value: any) => void) | undefined; + fieldType?: FormFieldSchemaType; + overrideFieldKey?: string; + defaultValue?: boolean | string | Date | number | undefined; + radioButtonOptions?: Array<RadioButton>; + footerElement?: ReactElement | undefined; + id?: string | undefined; + getCustomElement?: ( + values: FormValues<TEntity>, + props: CustomElementProps, + ) => ReactElement | undefined; // custom element to render instead of the elements in the form. + categoryCheckboxProps?: CategoryCheckboxProps | undefined; // props for the category checkbox component. If fieldType is CategoryCheckbox, this prop is required. + dataTestId?: string | undefined; - // set this to true if you want to show this field in the form even when the form is in edit mode. - doNotShowWhenEditing?: boolean | undefined; - doNotShowWhenCreating?: boolean | undefined; + // set this to true if you want to show this field in the form even when the form is in edit mode. + doNotShowWhenEditing?: boolean | undefined; + doNotShowWhenCreating?: boolean | undefined; } diff --git a/CommonUI/src/Components/Forms/Types/Fields.ts b/CommonUI/src/Components/Forms/Types/Fields.ts index fb3d4ee77c..1744cee26f 100644 --- a/CommonUI/src/Components/Forms/Types/Fields.ts +++ b/CommonUI/src/Components/Forms/Types/Fields.ts @@ -1,4 +1,4 @@ -import Field from './Field'; +import Field from "./Field"; type Fields<T> = Array<Field<T>>; diff --git a/CommonUI/src/Components/Forms/Types/FormFieldSchemaType.ts b/CommonUI/src/Components/Forms/Types/FormFieldSchemaType.ts index 38c0ce3d37..057d5afbfc 100644 --- a/CommonUI/src/Components/Forms/Types/FormFieldSchemaType.ts +++ b/CommonUI/src/Components/Forms/Types/FormFieldSchemaType.ts @@ -1,39 +1,39 @@ enum FormFieldSchemaType { - ObjectID = 'ObjectID', - Name = 'Name', - Hostname = 'Hostname', - ImageFile = 'ImageFile', - URL = 'URL', - Route = 'Route', - Number = 'Number', - Password = 'Password', - Text = 'Text', - Time = 'Time', - Email = 'Email', - PositiveNumber = 'PositiveNumber', - Date = 'Date', - Phone = 'Phone', - DateTime = 'DateTime', - Domain = 'Domain', - LongText = 'LongText', - Color = 'Color', - Dropdown = 'Dropdown', - Radio = 'Radio', - File = 'File', - MultiSelectDropdown = 'MultiSelectDropdown', - Toggle = 'Boolean', - Port = 'Port', - EncryptedText = 'EncryptedText', - Markdown = 'Markdown', - JavaScript = 'JavaScript', - CSS = 'CSS', - HTML = 'HTML', - RadioButton = 'RadioButton', - JSON = 'JSON', - Query = 'Query', - CustomComponent = 'CustomComponent', - Checkbox = 'Checkbox', - CategoryCheckbox = 'CategoryCheckbox', + ObjectID = "ObjectID", + Name = "Name", + Hostname = "Hostname", + ImageFile = "ImageFile", + URL = "URL", + Route = "Route", + Number = "Number", + Password = "Password", + Text = "Text", + Time = "Time", + Email = "Email", + PositiveNumber = "PositiveNumber", + Date = "Date", + Phone = "Phone", + DateTime = "DateTime", + Domain = "Domain", + LongText = "LongText", + Color = "Color", + Dropdown = "Dropdown", + Radio = "Radio", + File = "File", + MultiSelectDropdown = "MultiSelectDropdown", + Toggle = "Boolean", + Port = "Port", + EncryptedText = "EncryptedText", + Markdown = "Markdown", + JavaScript = "JavaScript", + CSS = "CSS", + HTML = "HTML", + RadioButton = "RadioButton", + JSON = "JSON", + Query = "Query", + CustomComponent = "CustomComponent", + Checkbox = "Checkbox", + CategoryCheckbox = "CategoryCheckbox", } export default FormFieldSchemaType; diff --git a/CommonUI/src/Components/Forms/Types/FormFieldsSchema.ts b/CommonUI/src/Components/Forms/Types/FormFieldsSchema.ts index 0dd88fd08c..d9b757fd0f 100644 --- a/CommonUI/src/Components/Forms/Types/FormFieldsSchema.ts +++ b/CommonUI/src/Components/Forms/Types/FormFieldsSchema.ts @@ -1,13 +1,13 @@ -import FormType from './FormFieldSchemaType'; -import Hostname from 'Common/Types/API/Hostname'; -import ObjectID from 'Common/Types/ObjectID'; +import FormType from "./FormFieldSchemaType"; +import Hostname from "Common/Types/API/Hostname"; +import ObjectID from "Common/Types/ObjectID"; type FormField<Property> = Property extends ObjectID - ? FormType.ObjectID - : Property extends Hostname + ? FormType.ObjectID + : Property extends Hostname ? FormType.Hostname : unknown; export declare type FormFields<Entity> = { - [P in keyof Entity]?: FormField<NonNullable<Entity[P]>>; + [P in keyof Entity]?: FormField<NonNullable<Entity[P]>>; }; diff --git a/CommonUI/src/Components/Forms/Types/FormStep.ts b/CommonUI/src/Components/Forms/Types/FormStep.ts index feebd3a5ca..8542b8f04e 100644 --- a/CommonUI/src/Components/Forms/Types/FormStep.ts +++ b/CommonUI/src/Components/Forms/Types/FormStep.ts @@ -1,13 +1,13 @@ -import FormValues from './FormValues'; +import FormValues from "./FormValues"; export enum FormStepState { - ACTIVE, - INACTIVE, - COMPLETED, + ACTIVE, + INACTIVE, + COMPLETED, } export interface FormStep<TEntity> { - id: string; - title: string; - showIf?: ((item: FormValues<TEntity>) => boolean) | undefined; + id: string; + title: string; + showIf?: ((item: FormValues<TEntity>) => boolean) | undefined; } diff --git a/CommonUI/src/Components/Forms/Types/FormValues.ts b/CommonUI/src/Components/Forms/Types/FormValues.ts index 273e875ab1..f39f074729 100644 --- a/CommonUI/src/Components/Forms/Types/FormValues.ts +++ b/CommonUI/src/Components/Forms/Types/FormValues.ts @@ -1,11 +1,11 @@ -import FormFieldSchemaTypes from '../../../Types/EntityFieldType'; +import FormFieldSchemaTypes from "../../../Types/EntityFieldType"; export type FormField<Property> = Property extends FormFieldSchemaTypes - ? Property - : unknown; + ? Property + : unknown; declare type FormValues<Entity> = { - [P in keyof Entity]?: FormField<NonNullable<Entity[P]>> | undefined; + [P in keyof Entity]?: FormField<NonNullable<Entity[P]>> | undefined; }; export default FormValues; diff --git a/CommonUI/src/Components/Forms/Validation.ts b/CommonUI/src/Components/Forms/Validation.ts index bffdc56d06..dc726122f2 100644 --- a/CommonUI/src/Components/Forms/Validation.ts +++ b/CommonUI/src/Components/Forms/Validation.ts @@ -1,348 +1,337 @@ -import Field from './Types/Field'; -import FormFieldSchemaType from './Types/FormFieldSchemaType'; -import FormValues from './Types/FormValues'; -import Hostname from 'Common/Types/API/Hostname'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import Domain from 'Common/Types/Domain'; -import Email from 'Common/Types/Email'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import Exception from 'Common/Types/Exception/Exception'; -import GenericObject from 'Common/Types/GenericObject'; -import { JSONObject } from 'Common/Types/JSON'; -import Phone from 'Common/Types/Phone'; -import Port from 'Common/Types/Port'; -import Typeof from 'Common/Types/Typeof'; +import Field from "./Types/Field"; +import FormFieldSchemaType from "./Types/FormFieldSchemaType"; +import FormValues from "./Types/FormValues"; +import Hostname from "Common/Types/API/Hostname"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import Domain from "Common/Types/Domain"; +import Email from "Common/Types/Email"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import Exception from "Common/Types/Exception/Exception"; +import GenericObject from "Common/Types/GenericObject"; +import { JSONObject } from "Common/Types/JSON"; +import Phone from "Common/Types/Phone"; +import Port from "Common/Types/Port"; +import Typeof from "Common/Types/Typeof"; export default class Validation { - public static validateLength<T extends GenericObject>( - content: string | undefined, - field: Field<T> - ): string | null { - if (content && field.validation) { - if (field.validation.minLength) { - if (content.trim().length < field.validation?.minLength) { - return `${field.title || name} cannot be less than ${ - field.validation.minLength - } characters.`; - } - } - - if (field.validation.maxLength) { - if (content.trim().length > field.validation?.maxLength) { - return `${field.title || name} cannot be more than ${ - field.validation.maxLength - } characters.`; - } - } - - if (field.validation.noSpaces) { - if (content.trim().includes(' ')) { - return `${field.title || name} should not have spaces.`; - } - } - - if (field.validation.noSpecialCharacters) { - if (!content.match(/^[A-Za-z0-9]*$/)) { - return `${ - field.title || name - } should not have special characters.`; - } - } - - if (field.validation.noNumbers) { - if (!content.match(/^[A-Za-z]*$/)) { - return `${field.title || name} should not have numbers.`; - } - } + public static validateLength<T extends GenericObject>( + content: string | undefined, + field: Field<T>, + ): string | null { + if (content && field.validation) { + if (field.validation.minLength) { + if (content.trim().length < field.validation?.minLength) { + return `${field.title || name} cannot be less than ${ + field.validation.minLength + } characters.`; } - return null; + } + + if (field.validation.maxLength) { + if (content.trim().length > field.validation?.maxLength) { + return `${field.title || name} cannot be more than ${ + field.validation.maxLength + } characters.`; + } + } + + if (field.validation.noSpaces) { + if (content.trim().includes(" ")) { + return `${field.title || name} should not have spaces.`; + } + } + + if (field.validation.noSpecialCharacters) { + if (!content.match(/^[A-Za-z0-9]*$/)) { + return `${field.title || name} should not have special characters.`; + } + } + + if (field.validation.noNumbers) { + if (!content.match(/^[A-Za-z]*$/)) { + return `${field.title || name} should not have numbers.`; + } + } + } + return null; + } + + public static validateDate<T extends GenericObject>( + content: string | undefined, + field: Field<T>, + ): string | null { + if (content && field.validation) { + if (field.validation.dateShouldBeInTheFuture) { + if (OneUptimeDate.isInThePast(content.trim())) { + return `${field.title || name} should be a future date.`; + } + } + } + return null; + } + + public static validateMaxValueAndMinValue<T extends GenericObject>( + content: string | number | undefined, + field: Field<T>, + ): string | null { + if (content && field.validation) { + if (typeof content === "string") { + try { + content = parseInt(content); + } catch (e) { + return `${field.title || name} should be a number.`; + } + } + + if (field.validation.maxValue) { + if (content > field.validation?.maxValue) { + return `${field.title || name} should not be more than ${ + field.validation?.maxValue + }.`; + } + } + + if (field.validation.minValue) { + if (content < field.validation?.minValue) { + return `${field.title || name} should not be less than ${ + field.validation?.minValue + }.`; + } + } + } + return null; + } + + public static validateRequired<T extends GenericObject>( + currentValues: FormValues<T>, + content: string | undefined, + field: Field<T>, + ): string | null { + let required: boolean = false; + + if (field.required && typeof field.required === Typeof.Boolean) { + required = true; + } else if ( + field.required && + typeof field.required === "function" && + field.required(currentValues) + ) { + required = true; } - public static validateDate<T extends GenericObject>( - content: string | undefined, - field: Field<T> - ): string | null { - if (content && field.validation) { - if (field.validation.dateShouldBeInTheFuture) { - if (OneUptimeDate.isInThePast(content.trim())) { - return `${field.title || name} should be a future date.`; - } - } - } - return null; + if (required && (!content || content.length === 0)) { + return `${field.title} is required.`; + } + return null; + } + + public static validateMatchField<T extends GenericObject>( + content: string | undefined, + field: Field<T>, + entity: FormValues<T>, + ): string | null { + if ( + content && + field.validation?.toMatchField && + entity[field.validation?.toMatchField] && + (entity[field.validation?.toMatchField] as string).toString().trim() !== + content.trim() + ) { + return `${field.title} should match ${ + field.validation?.toMatchField as string + }`; + } + return null; + } + + public static validateData<T extends GenericObject>( + content: string | undefined, + field: Field<T>, + ): string | null { + if (content && field.fieldType === FormFieldSchemaType.Email) { + if (!Email.isValid(content!)) { + return "Email is not valid."; + } } - public static validateMaxValueAndMinValue<T extends GenericObject>( - content: string | number | undefined, - field: Field<T> - ): string | null { - if (content && field.validation) { - if (typeof content === 'string') { - try { - content = parseInt(content); - } catch (e) { - return `${field.title || name} should be a number.`; - } - } - - if (field.validation.maxValue) { - if (content > field.validation?.maxValue) { - return `${field.title || name} should not be more than ${ - field.validation?.maxValue - }.`; - } - } - - if (field.validation.minValue) { - if (content < field.validation?.minValue) { - return `${field.title || name} should not be less than ${ - field.validation?.minValue - }.`; - } - } + if (content && field.fieldType === FormFieldSchemaType.Port) { + try { + new Port(content); + } catch (e: unknown) { + if (e instanceof Exception) { + return e.getMessage(); } - return null; + } } - public static validateRequired<T extends GenericObject>( - currentValues: FormValues<T>, - content: string | undefined, - field: Field<T> - ): string | null { - let required: boolean = false; - - if (field.required && typeof field.required === Typeof.Boolean) { - required = true; - } else if ( - field.required && - typeof field.required === 'function' && - field.required(currentValues) - ) { - required = true; + if (content && field.fieldType === FormFieldSchemaType.URL) { + try { + URL.fromString(content); + } catch (e: unknown) { + if (e instanceof Exception) { + return e.getMessage(); } - - if (required && (!content || content.length === 0)) { - return `${field.title} is required.`; - } - return null; + } } - public static validateMatchField<T extends GenericObject>( - content: string | undefined, - field: Field<T>, - entity: FormValues<T> - ): string | null { - if ( - content && - field.validation?.toMatchField && - entity[field.validation?.toMatchField] && - (entity[field.validation?.toMatchField] as string) - .toString() - .trim() !== content.trim() - ) { - return `${field.title} should match ${ - field.validation?.toMatchField as string - }`; + if (content && field.fieldType === FormFieldSchemaType.Hostname) { + try { + new Hostname(content.toString()); + } catch (e: unknown) { + if (e instanceof Exception) { + return e.getMessage(); } - return null; + } } - public static validateData<T extends GenericObject>( - content: string | undefined, - field: Field<T> - ): string | null { - if (content && field.fieldType === FormFieldSchemaType.Email) { - if (!Email.isValid(content!)) { - return 'Email is not valid.'; - } + if (content && field.fieldType === FormFieldSchemaType.Route) { + try { + new Route(content.toString()); + } catch (e: unknown) { + if (e instanceof Exception) { + return e.getMessage(); } - - if (content && field.fieldType === FormFieldSchemaType.Port) { - try { - new Port(content); - } catch (e: unknown) { - if (e instanceof Exception) { - return e.getMessage(); - } - } - } - - if (content && field.fieldType === FormFieldSchemaType.URL) { - try { - URL.fromString(content); - } catch (e: unknown) { - if (e instanceof Exception) { - return e.getMessage(); - } - } - } - - if (content && field.fieldType === FormFieldSchemaType.Hostname) { - try { - new Hostname(content.toString()); - } catch (e: unknown) { - if (e instanceof Exception) { - return e.getMessage(); - } - } - } - - if (content && field.fieldType === FormFieldSchemaType.Route) { - try { - new Route(content.toString()); - } catch (e: unknown) { - if (e instanceof Exception) { - return e.getMessage(); - } - } - } - - if (content && field.fieldType === FormFieldSchemaType.Phone) { - try { - new Phone(content.toString()); - } catch (e: unknown) { - if (e instanceof Exception) { - return e.getMessage(); - } - } - } - - if (content && field.fieldType === FormFieldSchemaType.Color) { - try { - new Color(content.toString()); - } catch (e: unknown) { - if (e instanceof Exception) { - return e.getMessage(); - } - } - } - - if (content && field.fieldType === FormFieldSchemaType.Domain) { - try { - new Domain(content.toString()); - } catch (e: unknown) { - if (e instanceof Exception) { - return e.getMessage(); - } - } - } - - return null; + } } - public static validate<T extends GenericObject>(args: { - formFields: Array<Field<T>>; - values: FormValues<T>; - onValidate: ((values: FormValues<T>) => JSONObject) | undefined; - currentFormStepId?: string | null | undefined; - }): Dictionary<string> { - const errors: JSONObject = {}; - const entries: FormValues<T> = { ...args.values }; - - for (const field of args.formFields) { - if ( - args.currentFormStepId && - field.stepId !== args.currentFormStepId - ) { - continue; - } - - if (!field.name) { - throw new BadDataException('Field name is required.'); - } - - const name: string = field.name; - - if (name in entries) { - const content: string | undefined = (entries as JSONObject)[ - name - ]?.toString(); - - // Check Required fields. - const resultRequired: string | null = this.validateRequired( - args.values, - content, - field - ); - - if (resultRequired) { - errors[name] = resultRequired; - } - - // Check for valid email data. - const resultValidateData: string | null = this.validateData( - content, - field - ); - if (resultValidateData) { - errors[name] = resultValidateData; - } - - const resultMatch: string | null = this.validateMatchField( - content, - field, - entries - ); - - if (resultMatch) { - errors[name] = resultMatch; - } - - // check for length of content - const result: string | null = this.validateLength( - content, - field - ); - if (result) { - errors[name] = result; - } - - // check for date - const resultDate: string | null = this.validateDate( - content, - field - ); - if (resultDate) { - errors[name] = resultDate; - } - - // check for length of content - const resultMaxMinValue: string | null = - this.validateMaxValueAndMinValue(content, field); - - if (resultMaxMinValue) { - errors[name] = resultMaxMinValue; - } - - if (field.customValidation) { - // check for length of content - const resultCustomValidation: string | null = - field.customValidation({ ...args.values }); - - if (resultCustomValidation) { - errors[name] = resultCustomValidation; - } - } - } else if (field.required) { - errors[name] = `${field.title || name} is required.`; - } + if (content && field.fieldType === FormFieldSchemaType.Phone) { + try { + new Phone(content.toString()); + } catch (e: unknown) { + if (e instanceof Exception) { + return e.getMessage(); } - - let customValidateResult: JSONObject = {}; - - if (args.onValidate) { - customValidateResult = args.onValidate(args.values); - } - - const totalValidationErrors: Dictionary<string> = { - ...errors, - ...customValidateResult, - } as Dictionary<string>; - - return totalValidationErrors; + } } + + if (content && field.fieldType === FormFieldSchemaType.Color) { + try { + new Color(content.toString()); + } catch (e: unknown) { + if (e instanceof Exception) { + return e.getMessage(); + } + } + } + + if (content && field.fieldType === FormFieldSchemaType.Domain) { + try { + new Domain(content.toString()); + } catch (e: unknown) { + if (e instanceof Exception) { + return e.getMessage(); + } + } + } + + return null; + } + + public static validate<T extends GenericObject>(args: { + formFields: Array<Field<T>>; + values: FormValues<T>; + onValidate: ((values: FormValues<T>) => JSONObject) | undefined; + currentFormStepId?: string | null | undefined; + }): Dictionary<string> { + const errors: JSONObject = {}; + const entries: FormValues<T> = { ...args.values }; + + for (const field of args.formFields) { + if (args.currentFormStepId && field.stepId !== args.currentFormStepId) { + continue; + } + + if (!field.name) { + throw new BadDataException("Field name is required."); + } + + const name: string = field.name; + + if (name in entries) { + const content: string | undefined = (entries as JSONObject)[ + name + ]?.toString(); + + // Check Required fields. + const resultRequired: string | null = this.validateRequired( + args.values, + content, + field, + ); + + if (resultRequired) { + errors[name] = resultRequired; + } + + // Check for valid email data. + const resultValidateData: string | null = this.validateData( + content, + field, + ); + if (resultValidateData) { + errors[name] = resultValidateData; + } + + const resultMatch: string | null = this.validateMatchField( + content, + field, + entries, + ); + + if (resultMatch) { + errors[name] = resultMatch; + } + + // check for length of content + const result: string | null = this.validateLength(content, field); + if (result) { + errors[name] = result; + } + + // check for date + const resultDate: string | null = this.validateDate(content, field); + if (resultDate) { + errors[name] = resultDate; + } + + // check for length of content + const resultMaxMinValue: string | null = + this.validateMaxValueAndMinValue(content, field); + + if (resultMaxMinValue) { + errors[name] = resultMaxMinValue; + } + + if (field.customValidation) { + // check for length of content + const resultCustomValidation: string | null = field.customValidation({ + ...args.values, + }); + + if (resultCustomValidation) { + errors[name] = resultCustomValidation; + } + } + } else if (field.required) { + errors[name] = `${field.title || name} is required.`; + } + } + + let customValidateResult: JSONObject = {}; + + if (args.onValidate) { + customValidateResult = args.onValidate(args.values); + } + + const totalValidationErrors: Dictionary<string> = { + ...errors, + ...customValidateResult, + } as Dictionary<string>; + + return totalValidationErrors; + } } diff --git a/CommonUI/src/Components/FullPageModal/FullPageModal.tsx b/CommonUI/src/Components/FullPageModal/FullPageModal.tsx index 064ed8e42c..b8dbe5e57e 100644 --- a/CommonUI/src/Components/FullPageModal/FullPageModal.tsx +++ b/CommonUI/src/Components/FullPageModal/FullPageModal.tsx @@ -1,32 +1,32 @@ -import Icon, { SizeProp, ThickProp } from '../Icon/Icon'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon, { SizeProp, ThickProp } from "../Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onClose: () => void; - children: ReactElement | Array<ReactElement>; + onClose: () => void; + children: ReactElement | Array<ReactElement>; } const FullPageModal: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="full-page-modal"> - <div - className="margin-50 align-right" - onClick={() => { - props.onClose && props.onClose(); - }} - > - <Icon - icon={IconProp.Close} - size={SizeProp.ExtraLarge} - thick={ThickProp.Thick} - /> - </div> - <div className="margin-50">{props.children}</div> - </div> - ); + return ( + <div className="full-page-modal"> + <div + className="margin-50 align-right" + onClick={() => { + props.onClose && props.onClose(); + }} + > + <Icon + icon={IconProp.Close} + size={SizeProp.ExtraLarge} + thick={ThickProp.Thick} + /> + </div> + <div className="margin-50">{props.children}</div> + </div> + ); }; export default FullPageModal; diff --git a/CommonUI/src/Components/GanttChart/Bar/BarLabel.tsx b/CommonUI/src/Components/GanttChart/Bar/BarLabel.tsx index cb73367dd8..a6556a5917 100644 --- a/CommonUI/src/Components/GanttChart/Bar/BarLabel.tsx +++ b/CommonUI/src/Components/GanttChart/Bar/BarLabel.tsx @@ -1,17 +1,17 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - label: string | ReactElement; + label: string | ReactElement; } const BarLabel: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - // rectangle div with curved corners and text inside in tailwindcss + return ( + // rectangle div with curved corners and text inside in tailwindcss - <div className="text-center text-sm font-medium">{props.label}</div> - ); + <div className="text-center text-sm font-medium">{props.label}</div> + ); }; export default BarLabel; diff --git a/CommonUI/src/Components/GanttChart/Bar/Index.tsx b/CommonUI/src/Components/GanttChart/Bar/Index.tsx index 82bbe21014..b3445e945b 100644 --- a/CommonUI/src/Components/GanttChart/Bar/Index.tsx +++ b/CommonUI/src/Components/GanttChart/Bar/Index.tsx @@ -1,118 +1,118 @@ -import BarLabel from './BarLabel'; -import Color from 'Common/Types/Color'; +import BarLabel from "./BarLabel"; +import Color from "Common/Types/Color"; import React, { - FunctionComponent, - MouseEventHandler, - ReactElement, - useState, -} from 'react'; + FunctionComponent, + MouseEventHandler, + ReactElement, + useState, +} from "react"; export interface GanttChartBar { - id: string; - label: string | ReactElement; - barColor: Color; - barTimelineStart: number; - barTimelineEnd: number; - rowId: string; - tooltip?: ReactElement; + id: string; + label: string | ReactElement; + barColor: Color; + barTimelineStart: number; + barTimelineEnd: number; + rowId: string; + tooltip?: ReactElement; } export interface ComponentProps { - bar: GanttChartBar; - chartTimelineStart: number; - chartTimelineEnd: number; - timelineWidth: number; - areOtherBarsSelected: boolean; - onSelect: (barId: string) => void; - onDeselect: (barId: string) => void; - isSelected?: boolean; + bar: GanttChartBar; + chartTimelineStart: number; + chartTimelineEnd: number; + timelineWidth: number; + areOtherBarsSelected: boolean; + onSelect: (barId: string) => void; + onDeselect: (barId: string) => void; + isSelected?: boolean; } const Bar: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isHovered, setIsHovered] = useState(false); + const [isHovered, setIsHovered] = useState(false); - // calculate bar width. - let barWidth: number = - ((props.bar.barTimelineEnd - props.bar.barTimelineStart) / - (props.chartTimelineEnd - props.chartTimelineStart)) * - props.timelineWidth; + // calculate bar width. + let barWidth: number = + ((props.bar.barTimelineEnd - props.bar.barTimelineStart) / + (props.chartTimelineEnd - props.chartTimelineStart)) * + props.timelineWidth; - const barLeftPosition: number = - ((props.bar.barTimelineStart - props.chartTimelineStart) / - (props.chartTimelineEnd - props.chartTimelineStart)) * - props.timelineWidth; + const barLeftPosition: number = + ((props.bar.barTimelineStart - props.chartTimelineStart) / + (props.chartTimelineEnd - props.chartTimelineStart)) * + props.timelineWidth; - if (barWidth > 1) { - barWidth -= 1; // we do this because the bar width is calculated based on the timeline width, and we want to remove 1px from the width to account for the 1px margin on the left and right of the bar. - } + if (barWidth > 1) { + barWidth -= 1; // we do this because the bar width is calculated based on the timeline width, and we want to remove 1px from the width to account for the 1px margin on the left and right of the bar. + } - if (barWidth < 5) { - barWidth = 5; - } + if (barWidth < 5) { + barWidth = 5; + } - const handleMouseEnter: MouseEventHandler = (): void => { - setIsHovered(true); - }; + const handleMouseEnter: MouseEventHandler = (): void => { + setIsHovered(true); + }; - const handleMouseLeave: MouseEventHandler = (): void => { - setIsHovered(false); - }; + const handleMouseLeave: MouseEventHandler = (): void => { + setIsHovered(false); + }; - let barOpacity: number = 0.9; + let barOpacity: number = 0.9; - if (props.areOtherBarsSelected && !props.isSelected) { - barOpacity = 0.5; - } + if (props.areOtherBarsSelected && !props.isSelected) { + barOpacity = 0.5; + } - if (isHovered) { - barOpacity = 1; - } + if (isHovered) { + barOpacity = 1; + } - return ( - // rectangle div with curved corners and text inside in tailwindcss - <div - className="flex absolute" - style={{ - marginLeft: `${barLeftPosition}px`, - }} - > - <div - className="chart-bar h-8 pt-1 pb-1 mt-2.5 mb-2.5 rounded absolute cursor-pointer ml-1 mr-1" - style={{ - width: `${barWidth}px`, - backgroundColor: `${props.bar.barColor.toString()}`, - opacity: barOpacity, - }} - onMouseEnter={handleMouseEnter} - onMouseLeave={handleMouseLeave} - onClick={() => { - if (props.isSelected) { - props.onDeselect(props.bar.id); - } else { - props.onSelect(props.bar.id); - } - }} - > - {isHovered && props.bar.tooltip && ( - <div className="bar-tooltip cursor-pointer bg-white shadow rounded p-2 w-fit z-40 absolute"> - {props.bar.tooltip} - </div> - )} - </div> + return ( + // rectangle div with curved corners and text inside in tailwindcss + <div + className="flex absolute" + style={{ + marginLeft: `${barLeftPosition}px`, + }} + > + <div + className="chart-bar h-8 pt-1 pb-1 mt-2.5 mb-2.5 rounded absolute cursor-pointer ml-1 mr-1" + style={{ + width: `${barWidth}px`, + backgroundColor: `${props.bar.barColor.toString()}`, + opacity: barOpacity, + }} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + onClick={() => { + if (props.isSelected) { + props.onDeselect(props.bar.id); + } else { + props.onSelect(props.bar.id); + } + }} + > + {isHovered && props.bar.tooltip && ( + <div className="bar-tooltip cursor-pointer bg-white shadow rounded p-2 w-fit z-40 absolute"> + {props.bar.tooltip} + </div> + )} + </div> - <div - className="h-8 pt-1 pb-1 mt-2.5 mb-2.5" - style={{ - marginLeft: `${barWidth + 15}px`, - opacity: barOpacity, - }} - > - <BarLabel label={props.bar.label} /> - </div> - </div> - ); + <div + className="h-8 pt-1 pb-1 mt-2.5 mb-2.5" + style={{ + marginLeft: `${barWidth + 15}px`, + opacity: barOpacity, + }} + > + <BarLabel label={props.bar.label} /> + </div> + </div> + ); }; export default Bar; diff --git a/CommonUI/src/Components/GanttChart/ChartContainer.tsx b/CommonUI/src/Components/GanttChart/ChartContainer.tsx index a1a84740a6..be88223276 100644 --- a/CommonUI/src/Components/GanttChart/ChartContainer.tsx +++ b/CommonUI/src/Components/GanttChart/ChartContainer.tsx @@ -1,42 +1,42 @@ -import React, { FunctionComponent, ReactElement, Ref } from 'react'; +import React, { FunctionComponent, ReactElement, Ref } from "react"; export interface ComponentProps { - onWidthChange: (width: number) => void; - children?: undefined | Array<ReactElement> | ReactElement; + onWidthChange: (width: number) => void; + children?: undefined | Array<ReactElement> | ReactElement; } const ChartContainer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const divRef: Ref<HTMLDivElement> = React.useRef<HTMLDivElement>(null); + const divRef: Ref<HTMLDivElement> = React.useRef<HTMLDivElement>(null); - React.useEffect(() => { - if (!props.onWidthChange) { - return; - } + React.useEffect(() => { + if (!props.onWidthChange) { + return; + } - if (!divRef.current) { - return; - } + if (!divRef.current) { + return; + } - const resizeObserver: ResizeObserver = new ResizeObserver(() => { - if (divRef.current && props.onWidthChange) { - props.onWidthChange(divRef.current.offsetWidth); - } - }); + const resizeObserver: ResizeObserver = new ResizeObserver(() => { + if (divRef.current && props.onWidthChange) { + props.onWidthChange(divRef.current.offsetWidth); + } + }); - resizeObserver.observe(divRef.current); + resizeObserver.observe(divRef.current); - return () => { - return resizeObserver.disconnect(); - }; // clean up - }, []); + return () => { + return resizeObserver.disconnect(); + }; // clean up + }, []); - return ( - <div ref={divRef} className={'w-full'}> - {props.children} - </div> - ); + return ( + <div ref={divRef} className={"w-full"}> + {props.children} + </div> + ); }; export default ChartContainer; diff --git a/CommonUI/src/Components/GanttChart/Index.tsx b/CommonUI/src/Components/GanttChart/Index.tsx index 410d9b88f3..7d8e3a1fc1 100644 --- a/CommonUI/src/Components/GanttChart/Index.tsx +++ b/CommonUI/src/Components/GanttChart/Index.tsx @@ -1,86 +1,84 @@ -import ChartContainer from './ChartContainer'; -import { GanttChartRow } from './Row/Row'; -import Rows from './Rows'; -import Timeline, { GanttChartTimeline } from './Timeline/Index'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import ChartContainer from "./ChartContainer"; +import { GanttChartRow } from "./Row/Row"; +import Rows from "./Rows"; +import Timeline, { GanttChartTimeline } from "./Timeline/Index"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface GanttChartProps { - id: string; - rows: GanttChartRow[]; - timeline: GanttChartTimeline; - onBarSelectChange: (barIds: string[]) => void; - multiSelect?: boolean | undefined; - selectedBarIds: string[]; + id: string; + rows: GanttChartRow[]; + timeline: GanttChartTimeline; + onBarSelectChange: (barIds: string[]) => void; + multiSelect?: boolean | undefined; + selectedBarIds: string[]; } export interface ComponentProps { - chart: GanttChartProps; + chart: GanttChartProps; } const GanttChart: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const eachIntervalDefaultWidth: number = 100; // in pixels + const eachIntervalDefaultWidth: number = 100; // in pixels - const [chartWidth, setChartWidth] = React.useState<number>(0); - const [timelineWidth, setTimelineWidth] = React.useState<number>(2000); + const [chartWidth, setChartWidth] = React.useState<number>(0); + const [timelineWidth, setTimelineWidth] = React.useState<number>(2000); - const [eachIntervalWidth, setEachIntervalWidth] = React.useState<number>( - eachIntervalDefaultWidth - ); + const [eachIntervalWidth, setEachIntervalWidth] = React.useState<number>( + eachIntervalDefaultWidth, + ); - const numberOfInterval: number = - (props.chart.timeline.end - props.chart.timeline.start) / - props.chart.timeline.interval; + const numberOfInterval: number = + (props.chart.timeline.end - props.chart.timeline.start) / + props.chart.timeline.interval; - const totalIntervalWidth: number = - eachIntervalDefaultWidth * numberOfInterval; + const totalIntervalWidth: number = + eachIntervalDefaultWidth * numberOfInterval; - useEffect(() => { - if (chartWidth < totalIntervalWidth) { - setChartWidth(totalIntervalWidth); - } else { - setEachIntervalWidth(chartWidth / numberOfInterval); - } + useEffect(() => { + if (chartWidth < totalIntervalWidth) { + setChartWidth(totalIntervalWidth); + } else { + setEachIntervalWidth(chartWidth / numberOfInterval); + } - const timelineWidth: number = chartWidth * 0.75; // 75 % of chart width, 25% for category spacer + const timelineWidth: number = chartWidth * 0.75; // 75 % of chart width, 25% for category spacer - setTimelineWidth(timelineWidth); // 75 % of chart width, 25% for category spacer - }, [chartWidth]); + setTimelineWidth(timelineWidth); // 75 % of chart width, 25% for category spacer + }, [chartWidth]); - return ( - <ChartContainer - onWidthChange={(width: number) => { - setChartWidth(width); - }} - > - <div - style={{ - width: `${chartWidth}px`, - }} - > - {/** Remve 25% because of category spacer */} - <Timeline - timeline={props.chart.timeline} - eachIntervalWidth={ - eachIntervalWidth - 0.25 * eachIntervalWidth - } - /> + return ( + <ChartContainer + onWidthChange={(width: number) => { + setChartWidth(width); + }} + > + <div + style={{ + width: `${chartWidth}px`, + }} + > + {/** Remve 25% because of category spacer */} + <Timeline + timeline={props.chart.timeline} + eachIntervalWidth={eachIntervalWidth - 0.25 * eachIntervalWidth} + /> - <Rows - timelineWidth={timelineWidth} - chartTimelineEnd={props.chart.timeline.end} - chartTimelineStart={props.chart.timeline.start} - rows={props.chart.rows} - selectedBarIds={props.chart.selectedBarIds} - multiSelect={props.chart.multiSelect} - onBarSelectChange={(barIds: string[]) => { - props.chart.onBarSelectChange(barIds); - }} - /> - </div> - </ChartContainer> - ); + <Rows + timelineWidth={timelineWidth} + chartTimelineEnd={props.chart.timeline.end} + chartTimelineStart={props.chart.timeline.start} + rows={props.chart.rows} + selectedBarIds={props.chart.selectedBarIds} + multiSelect={props.chart.multiSelect} + onBarSelectChange={(barIds: string[]) => { + props.chart.onBarSelectChange(barIds); + }} + /> + </div> + </ChartContainer> + ); }; export default GanttChart; diff --git a/CommonUI/src/Components/GanttChart/Row/Index.tsx b/CommonUI/src/Components/GanttChart/Row/Index.tsx index 1c0d2935e1..ad609cd147 100644 --- a/CommonUI/src/Components/GanttChart/Row/Index.tsx +++ b/CommonUI/src/Components/GanttChart/Row/Index.tsx @@ -1,21 +1,21 @@ -import Row, { GanttChartRow } from './Row'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Row, { GanttChartRow } from "./Row"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - row: GanttChartRow; - chartTimelineStart: number; - chartTimelineEnd: number; - timelineWidth: number; - selectedBarIds: string[]; - onBarSelectChange: (barIds: string[]) => void; - children?: ReactElement; - multiSelect?: boolean | undefined; + row: GanttChartRow; + chartTimelineStart: number; + chartTimelineEnd: number; + timelineWidth: number; + selectedBarIds: string[]; + onBarSelectChange: (barIds: string[]) => void; + children?: ReactElement; + multiSelect?: boolean | undefined; } const RowIndex: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return <Row {...props} level={0} />; + return <Row {...props} level={0} />; }; export default RowIndex; diff --git a/CommonUI/src/Components/GanttChart/Row/Row.tsx b/CommonUI/src/Components/GanttChart/Row/Row.tsx index 4746df0812..499d366ee1 100644 --- a/CommonUI/src/Components/GanttChart/Row/Row.tsx +++ b/CommonUI/src/Components/GanttChart/Row/Row.tsx @@ -1,151 +1,138 @@ -import Icon from '../../Icon/Icon'; -import Bar, { GanttChartBar } from '../Bar/Index'; -import RowLabel from './RowLabel'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import Icon from "../../Icon/Icon"; +import Bar, { GanttChartBar } from "../Bar/Index"; +import RowLabel from "./RowLabel"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface GanttChartRow { - rowInfo: { - id: string; - title: string | ReactElement; - description: string | ReactElement; - }; - childRows: GanttChartRow[]; - bars: Array<GanttChartBar>; // usually will have only one bar, this is for future proofing + rowInfo: { + id: string; + title: string | ReactElement; + description: string | ReactElement; + }; + childRows: GanttChartRow[]; + bars: Array<GanttChartBar>; // usually will have only one bar, this is for future proofing } export interface ComponentProps { - row: GanttChartRow; - chartTimelineStart: number; - chartTimelineEnd: number; - timelineWidth: number; - selectedBarIds: string[]; - onBarSelectChange: (barIds: string[]) => void; - level: number; - multiSelect?: boolean | undefined; + row: GanttChartRow; + chartTimelineStart: number; + chartTimelineEnd: number; + timelineWidth: number; + selectedBarIds: string[]; + onBarSelectChange: (barIds: string[]) => void; + level: number; + multiSelect?: boolean | undefined; } const Row: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const { row } = props; + const { row } = props; - let { level } = props; + let { level } = props; - if (!level) { - level = 0; - } + if (!level) { + level = 0; + } - const hasChildRows: boolean = row.childRows.length > 0; + const hasChildRows: boolean = row.childRows.length > 0; - const shouldShowChildRows: boolean = level < 2; + const shouldShowChildRows: boolean = level < 2; - const [showChildRows, setShowChildRows] = useState(shouldShowChildRows); + const [showChildRows, setShowChildRows] = useState(shouldShowChildRows); - const paddingCount: number = level * 4; + const paddingCount: number = level * 4; - return ( - // rectangle div with curved corners and text inside in tailwindcss - <div> - <div - className={`flex w-full border-b-2 border-gray-200 border-l-2 border-l-gray-400 border-r-2 border-r-gray-400`} - > - <div className="flex w-1/4 border-r-2 border-gray-300"> - <div className={`pl-${paddingCount} pt-2 pb-2 pr-2 flex`}> - <div className="w-5 h-5 ml-3 mt-1"> - {hasChildRows && ( - <Icon - icon={ - showChildRows - ? IconProp.ChevronDown - : IconProp.ChevronRight - } - className="cursor-pointer h-4 w-4 text-gray-500 hover:text-gray-800 font-semibold" - onClick={() => { - return setShowChildRows(!showChildRows); - }} - /> - )} - </div> - <RowLabel - title={row.rowInfo.title} - description={row.rowInfo.description} - /> - </div> - </div> - <div className="flex w-3/4"> - {row.bars.map((bar: GanttChartBar, i: number) => { - return ( - <Bar - key={i} - bar={bar} - chartTimelineEnd={props.chartTimelineEnd} - chartTimelineStart={props.chartTimelineStart} - timelineWidth={props.timelineWidth} - areOtherBarsSelected={ - props.selectedBarIds.length > 0 - } - isSelected={props.selectedBarIds.includes( - bar.id - )} - onSelect={(barId: string) => { - // check if the bar is already selected - if (props.selectedBarIds.includes(barId)) { - return; - } - - if (!props.multiSelect) { - props.onBarSelectChange([barId]); - return; - } - - props.onBarSelectChange([ - ...props.selectedBarIds, - barId, - ]); - }} - onDeselect={(barId: string) => { - // check if the bar is already selected - if (!props.selectedBarIds.includes(barId)) { - return; - } - - props.onBarSelectChange( - props.selectedBarIds.filter( - (id: string) => { - return id !== barId; - } - ) - ); - }} - /> - ); - })} - </div> + return ( + // rectangle div with curved corners and text inside in tailwindcss + <div> + <div + className={`flex w-full border-b-2 border-gray-200 border-l-2 border-l-gray-400 border-r-2 border-r-gray-400`} + > + <div className="flex w-1/4 border-r-2 border-gray-300"> + <div className={`pl-${paddingCount} pt-2 pb-2 pr-2 flex`}> + <div className="w-5 h-5 ml-3 mt-1"> + {hasChildRows && ( + <Icon + icon={ + showChildRows ? IconProp.ChevronDown : IconProp.ChevronRight + } + className="cursor-pointer h-4 w-4 text-gray-500 hover:text-gray-800 font-semibold" + onClick={() => { + return setShowChildRows(!showChildRows); + }} + /> + )} </div> - {showChildRows && ( - <div> - {row.childRows.map((childRow: GanttChartRow, i: number) => { - return ( - <div key={i}> - <Row - level={level + 1} - row={childRow} - chartTimelineEnd={props.chartTimelineEnd} - chartTimelineStart={ - props.chartTimelineStart - } - timelineWidth={props.timelineWidth} - selectedBarIds={props.selectedBarIds} - onBarSelectChange={props.onBarSelectChange} - /> - </div> - ); - })} - </div> - )} + <RowLabel + title={row.rowInfo.title} + description={row.rowInfo.description} + /> + </div> </div> - ); + <div className="flex w-3/4"> + {row.bars.map((bar: GanttChartBar, i: number) => { + return ( + <Bar + key={i} + bar={bar} + chartTimelineEnd={props.chartTimelineEnd} + chartTimelineStart={props.chartTimelineStart} + timelineWidth={props.timelineWidth} + areOtherBarsSelected={props.selectedBarIds.length > 0} + isSelected={props.selectedBarIds.includes(bar.id)} + onSelect={(barId: string) => { + // check if the bar is already selected + if (props.selectedBarIds.includes(barId)) { + return; + } + + if (!props.multiSelect) { + props.onBarSelectChange([barId]); + return; + } + + props.onBarSelectChange([...props.selectedBarIds, barId]); + }} + onDeselect={(barId: string) => { + // check if the bar is already selected + if (!props.selectedBarIds.includes(barId)) { + return; + } + + props.onBarSelectChange( + props.selectedBarIds.filter((id: string) => { + return id !== barId; + }), + ); + }} + /> + ); + })} + </div> + </div> + {showChildRows && ( + <div> + {row.childRows.map((childRow: GanttChartRow, i: number) => { + return ( + <div key={i}> + <Row + level={level + 1} + row={childRow} + chartTimelineEnd={props.chartTimelineEnd} + chartTimelineStart={props.chartTimelineStart} + timelineWidth={props.timelineWidth} + selectedBarIds={props.selectedBarIds} + onBarSelectChange={props.onBarSelectChange} + /> + </div> + ); + })} + </div> + )} + </div> + ); }; export default Row; diff --git a/CommonUI/src/Components/GanttChart/Row/RowLabel.tsx b/CommonUI/src/Components/GanttChart/Row/RowLabel.tsx index a430889298..0b762a541e 100644 --- a/CommonUI/src/Components/GanttChart/Row/RowLabel.tsx +++ b/CommonUI/src/Components/GanttChart/Row/RowLabel.tsx @@ -1,21 +1,21 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string | ReactElement; - description: string | ReactElement; + title: string | ReactElement; + description: string | ReactElement; } const RowLabel: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - // rectangle div with curved corners and text inside in tailwindcss + return ( + // rectangle div with curved corners and text inside in tailwindcss - <div> - <div className="text-sm text-gray-600">{props.title}</div> - <div className="text-xs text-gray-500">{props.description}</div> - </div> - ); + <div> + <div className="text-sm text-gray-600">{props.title}</div> + <div className="text-xs text-gray-500">{props.description}</div> + </div> + ); }; export default RowLabel; diff --git a/CommonUI/src/Components/GanttChart/Rows.tsx b/CommonUI/src/Components/GanttChart/Rows.tsx index a0a963e02d..925def4e82 100644 --- a/CommonUI/src/Components/GanttChart/Rows.tsx +++ b/CommonUI/src/Components/GanttChart/Rows.tsx @@ -1,53 +1,53 @@ -import Row from './Row/Index'; -import { GanttChartRow } from './Row/Row'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Row from "./Row/Index"; +import { GanttChartRow } from "./Row/Row"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - rows: Array<GanttChartRow>; - chartTimelineStart: number; - chartTimelineEnd: number; - timelineWidth: number; - selectedBarIds: string[]; - onBarSelectChange: (barIds: string[]) => void; - multiSelect?: boolean | undefined; + rows: Array<GanttChartRow>; + chartTimelineStart: number; + chartTimelineEnd: number; + timelineWidth: number; + selectedBarIds: string[]; + onBarSelectChange: (barIds: string[]) => void; + multiSelect?: boolean | undefined; } const Rows: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - type GetRowFunction = (data: { row: GanttChartRow }) => ReactElement; + type GetRowFunction = (data: { row: GanttChartRow }) => ReactElement; - const getRow: GetRowFunction = (data: { - row: GanttChartRow; - }): ReactElement => { - const { row } = data; - - return ( - <Row - chartTimelineStart={props.chartTimelineStart} - chartTimelineEnd={props.chartTimelineEnd} - timelineWidth={props.timelineWidth} - row={row} - multiSelect={props.multiSelect} - selectedBarIds={props.selectedBarIds} - onBarSelectChange={props.onBarSelectChange} - /> - ); - }; + const getRow: GetRowFunction = (data: { + row: GanttChartRow; + }): ReactElement => { + const { row } = data; return ( - <div className="w-full border-b-2 border-gray-400"> - {props.rows.map((row: GanttChartRow, i: number) => { - return ( - <div key={i}> - {getRow({ - row: row, - })} - </div> - ); - })} - </div> + <Row + chartTimelineStart={props.chartTimelineStart} + chartTimelineEnd={props.chartTimelineEnd} + timelineWidth={props.timelineWidth} + row={row} + multiSelect={props.multiSelect} + selectedBarIds={props.selectedBarIds} + onBarSelectChange={props.onBarSelectChange} + /> ); + }; + + return ( + <div className="w-full border-b-2 border-gray-400"> + {props.rows.map((row: GanttChartRow, i: number) => { + return ( + <div key={i}> + {getRow({ + row: row, + })} + </div> + ); + })} + </div> + ); }; export default Rows; diff --git a/CommonUI/src/Components/GanttChart/Timeline/Index.tsx b/CommonUI/src/Components/GanttChart/Timeline/Index.tsx index e74000febe..16503d8819 100644 --- a/CommonUI/src/Components/GanttChart/Timeline/Index.tsx +++ b/CommonUI/src/Components/GanttChart/Timeline/Index.tsx @@ -1,50 +1,46 @@ -import TimelineInterval from './TimelineInterval'; -import React, { FunctionComponent, ReactElement } from 'react'; +import TimelineInterval from "./TimelineInterval"; +import React, { FunctionComponent, ReactElement } from "react"; export interface GanttChartTimeline { - start: number; - end: number; - interval: number; - intervalUnit: string; + start: number; + end: number; + interval: number; + intervalUnit: string; } export interface ComponentProps { - timeline: GanttChartTimeline; - eachIntervalWidth: number; + timeline: GanttChartTimeline; + eachIntervalWidth: number; } const Timeline: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const numberOfInterval: number = - (props.timeline.end - props.timeline.start) / props.timeline.interval; + const numberOfInterval: number = + (props.timeline.end - props.timeline.start) / props.timeline.interval; - return ( - <div className="timeline flex h-5 border-b-2 border-l-2 border-t-2 border-gray-400 w-full"> - <div className="w-1/4 border-r-2 border-gray-400"> - {/** Row Category Spacer */} - </div> + return ( + <div className="timeline flex h-5 border-b-2 border-l-2 border-t-2 border-gray-400 w-full"> + <div className="w-1/4 border-r-2 border-gray-400"> + {/** Row Category Spacer */} + </div> - {/** Render Timeline Intervals */} - {Array.from( - { length: numberOfInterval }, - (_: number, i: number) => { - return ( - <TimelineInterval - key={i} - timelineInterval={{ - width: props.eachIntervalWidth, - intervalUnit: props.timeline.intervalUnit, - intervalCount: - props.timeline.start + - (i + 1) * props.timeline.interval, - }} - /> - ); - } - )} - </div> - ); + {/** Render Timeline Intervals */} + {Array.from({ length: numberOfInterval }, (_: number, i: number) => { + return ( + <TimelineInterval + key={i} + timelineInterval={{ + width: props.eachIntervalWidth, + intervalUnit: props.timeline.intervalUnit, + intervalCount: + props.timeline.start + (i + 1) * props.timeline.interval, + }} + /> + ); + })} + </div> + ); }; export default Timeline; diff --git a/CommonUI/src/Components/GanttChart/Timeline/TimelineInterval.tsx b/CommonUI/src/Components/GanttChart/Timeline/TimelineInterval.tsx index 62fceb5645..2228b8c0e6 100644 --- a/CommonUI/src/Components/GanttChart/Timeline/TimelineInterval.tsx +++ b/CommonUI/src/Components/GanttChart/Timeline/TimelineInterval.tsx @@ -1,33 +1,33 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface GanttChartTimelineInterval { - width: number; - intervalUnit: string; - intervalCount: number; + width: number; + intervalUnit: string; + intervalCount: number; } export interface ComponentProps { - timelineInterval: GanttChartTimelineInterval; + timelineInterval: GanttChartTimelineInterval; } const TimelineInterval: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - className="timeline-interval h-full" - style={{ - width: `${props.timelineInterval.width}px`, - }} - > - <div className="timeline-interval h-full flex justify-end border-r-2 border-gray-400 border-solid text-xs text-gray-400 font-bold"> - <div className="mr-1"> - {props.timelineInterval.intervalCount}{' '} - {props.timelineInterval.intervalUnit} - </div> - </div> + return ( + <div + className="timeline-interval h-full" + style={{ + width: `${props.timelineInterval.width}px`, + }} + > + <div className="timeline-interval h-full flex justify-end border-r-2 border-gray-400 border-solid text-xs text-gray-400 font-bold"> + <div className="mr-1"> + {props.timelineInterval.intervalCount}{" "} + {props.timelineInterval.intervalUnit} </div> - ); + </div> + </div> + ); }; export default TimelineInterval; diff --git a/CommonUI/src/Components/GanttChart/Timeline/TimelineIntervalMarks.tsx b/CommonUI/src/Components/GanttChart/Timeline/TimelineIntervalMarks.tsx index f1c11fe6d7..e2816d59c0 100644 --- a/CommonUI/src/Components/GanttChart/Timeline/TimelineIntervalMarks.tsx +++ b/CommonUI/src/Components/GanttChart/Timeline/TimelineIntervalMarks.tsx @@ -1,43 +1,43 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface GanttChartTimelineIntervalMarks { - numberOfMarks: number; - widthOfEachMark: number; + numberOfMarks: number; + widthOfEachMark: number; } export interface ComponentProps { - timelineIntervalMarks: GanttChartTimelineIntervalMarks; + timelineIntervalMarks: GanttChartTimelineIntervalMarks; } const TimelineInterval: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - className="timeline-interval-marks h-10 flex" - style={{ - width: `${ - props.timelineIntervalMarks.widthOfEachMark * - props.timelineIntervalMarks.numberOfMarks - }`, - }} - > - {Array.from( - { length: props.timelineIntervalMarks.numberOfMarks }, - (_: number, i: number) => { - return ( - <div - key={i} - className="h-10 border-right" - style={{ - width: `${props.timelineIntervalMarks.widthOfEachMark}`, - }} - ></div> - ); - } - )} - </div> - ); + return ( + <div + className="timeline-interval-marks h-10 flex" + style={{ + width: `${ + props.timelineIntervalMarks.widthOfEachMark * + props.timelineIntervalMarks.numberOfMarks + }`, + }} + > + {Array.from( + { length: props.timelineIntervalMarks.numberOfMarks }, + (_: number, i: number) => { + return ( + <div + key={i} + className="h-10 border-right" + style={{ + width: `${props.timelineIntervalMarks.widthOfEachMark}`, + }} + ></div> + ); + }, + )} + </div> + ); }; export default TimelineInterval; diff --git a/CommonUI/src/Components/Graphs/DayUptimeGraph.tsx b/CommonUI/src/Components/Graphs/DayUptimeGraph.tsx index 6987da6e24..de3ee57fea 100644 --- a/CommonUI/src/Components/Graphs/DayUptimeGraph.tsx +++ b/CommonUI/src/Components/Graphs/DayUptimeGraph.tsx @@ -1,269 +1,245 @@ -import Tooltip from '../Tooltip/Tooltip'; -import { Green } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; +import Tooltip from "../Tooltip/Tooltip"; +import { Green } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface Event { - startDate: Date; - endDate: Date; - label: string; - priority: number; - color: Color; - eventStatusId: ObjectID; // this is the id of the event status. for example, monitor status id. + startDate: Date; + endDate: Date; + label: string; + priority: number; + color: Color; + eventStatusId: ObjectID; // this is the id of the event status. for example, monitor status id. } export interface BarChartRule { - barColor: Color; - uptimePercentGreaterThanOrEqualTo: number; + barColor: Color; + uptimePercentGreaterThanOrEqualTo: number; } export interface ComponentProps { - startDate: Date; - endDate: Date; - events: Array<Event>; - height?: number | undefined; - barColorRules?: Array<BarChartRule> | undefined; - downtimeEventStatusIds?: Array<ObjectID> | undefined; - defaultBarColor: Color; + startDate: Date; + endDate: Date; + events: Array<Event>; + height?: number | undefined; + barColorRules?: Array<BarChartRule> | undefined; + downtimeEventStatusIds?: Array<ObjectID> | undefined; + defaultBarColor: Color; } const DayUptimeGraph: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [days, setDays] = useState<number>(0); + const [days, setDays] = useState<number>(0); - useEffect(() => { - setDays( - OneUptimeDate.getNumberOfDaysBetweenDatesInclusive( - props.startDate, - props.endDate - ) - ); - }, [props.startDate, props.endDate]); - - type GetUptimeBarFunction = (dayNumber: number) => ReactElement; - - const getUptimeBar: GetUptimeBarFunction = ( - dayNumber: number - ): ReactElement => { - let color: Color = props.defaultBarColor || Green; - - const todaysDay: Date = OneUptimeDate.getSomeDaysAfterDate( - props.startDate, - dayNumber - ); - - let toolTipText: string = `${OneUptimeDate.getDateAsLocalFormattedString( - todaysDay, - true - )}`; - - const startOfTheDay: Date = OneUptimeDate.getStartOfDay(todaysDay); - const endOfTheDay: Date = OneUptimeDate.getEndOfDay(todaysDay); - - const todaysEvents: Array<Event> = props.events.filter( - (event: Event) => { - let doesEventBelongsToToday: boolean = false; - - /// if the event starts or end today. - if ( - OneUptimeDate.isBetween( - event.startDate, - startOfTheDay, - endOfTheDay - ) - ) { - doesEventBelongsToToday = true; - } - - if ( - OneUptimeDate.isBetween( - event.endDate, - startOfTheDay, - endOfTheDay - ) - ) { - doesEventBelongsToToday = true; - } - - // if the event is outside start or end day but overlaps the day completely. - - if ( - OneUptimeDate.isBetween( - startOfTheDay, - event.startDate, - endOfTheDay - ) && - OneUptimeDate.isBetween( - endOfTheDay, - startOfTheDay, - event.endDate - ) - ) { - doesEventBelongsToToday = true; - } - - return doesEventBelongsToToday; - } - ); - - const secondsOfEvent: Dictionary<number> = {}; - - let currentPriority: number = 1; - - const eventLabels: Dictionary<string> = {}; - - for (const event of todaysEvents) { - const startDate: Date = OneUptimeDate.getGreaterDate( - event.startDate, - startOfTheDay - ); - - const endDate: Date = OneUptimeDate.getLesserDate( - event.endDate, - OneUptimeDate.getLesserDate( - OneUptimeDate.getCurrentDate(), - endOfTheDay - ) - ); - - const seconds: number = OneUptimeDate.getSecondsBetweenDates( - startDate, - endDate - ); - - if (!secondsOfEvent[event.eventStatusId.toString()]) { - secondsOfEvent[event.eventStatusId.toString()] = 0; - } - - secondsOfEvent[event.eventStatusId.toString()] += seconds; - - eventLabels[event.eventStatusId.toString()] = event.label; - - // set bar color. - if (currentPriority <= event.priority) { - currentPriority = event.priority; - - // if there are no rules then use the color of the event. - - if (!props.barColorRules || props.barColorRules.length === 0) { - color = event.color; - } - } - } - - let hasEvents: boolean = false; - - let totalDowntimeInSeconds: number = 0; - - let totalUptimeInSeconds: number = 0; - - for (const key in secondsOfEvent) { - hasEvents = true; - - const eventStatusId: string = key; - - // if this is downtime state then, include tooltip. - - if ( - (props.downtimeEventStatusIds?.filter((id: ObjectID) => { - return id.toString() === eventStatusId.toString(); - }).length || 0) > 0 - ) { - toolTipText += `, ${ - eventLabels[key] - } for ${OneUptimeDate.secondsToFormattedFriendlyTimeString( - secondsOfEvent[key] || 0 - )}`; - } - - const isDowntimeEvent: boolean = Boolean( - props.downtimeEventStatusIds?.find((id: ObjectID) => { - return id.toString() === eventStatusId; - }) - ); - - if (isDowntimeEvent) { - // remove the seconds from total uptime. - const secondsOfDowntime: number = secondsOfEvent[key] || 0; - totalDowntimeInSeconds += secondsOfDowntime; - } else { - totalUptimeInSeconds += secondsOfEvent[key] || 0; - } - } - - // now check bar rules and finalize the color of the bar - - const uptimePercentForTheDay: number = - (totalUptimeInSeconds / - (totalDowntimeInSeconds + totalUptimeInSeconds)) * - 100; - - for (const rules of props.barColorRules || []) { - if ( - uptimePercentForTheDay >= - rules.uptimePercentGreaterThanOrEqualTo - ) { - color = rules.barColor; - break; - } - } - - if (todaysEvents.length === 1) { - hasEvents = true; - toolTipText = `${OneUptimeDate.getDateAsLocalFormattedString( - todaysDay, - true - )} - 100% ${todaysEvents[0]?.label || 'Operational'}.`; - } - - if (!hasEvents) { - toolTipText += ` - No data for this day.`; - color = props.defaultBarColor || Green; - } - - let className: string = 'h-20 w-20'; - - if (props.height) { - className = 'w-20 h-' + props.height; - } - return ( - <Tooltip key={dayNumber} text={toolTipText || '100% Operational'}> - <div - className={className} - style={{ - backgroundColor: color.toString(), - }} - ></div> - </Tooltip> - ); - }; - - type GetUptimeGraphFunction = () => Array<ReactElement>; - - const getUptimeGraph: GetUptimeGraphFunction = (): Array<ReactElement> => { - const elements: Array<ReactElement> = []; - - for (let i: number = 0; i < days; i++) { - elements.push(getUptimeBar(i)); - } - - return elements; - }; - - return ( - <div className="flex space-x-0.5 rounded overflow-hidden"> - {getUptimeGraph()} - </div> + useEffect(() => { + setDays( + OneUptimeDate.getNumberOfDaysBetweenDatesInclusive( + props.startDate, + props.endDate, + ), ); + }, [props.startDate, props.endDate]); + + type GetUptimeBarFunction = (dayNumber: number) => ReactElement; + + const getUptimeBar: GetUptimeBarFunction = ( + dayNumber: number, + ): ReactElement => { + let color: Color = props.defaultBarColor || Green; + + const todaysDay: Date = OneUptimeDate.getSomeDaysAfterDate( + props.startDate, + dayNumber, + ); + + let toolTipText: string = `${OneUptimeDate.getDateAsLocalFormattedString( + todaysDay, + true, + )}`; + + const startOfTheDay: Date = OneUptimeDate.getStartOfDay(todaysDay); + const endOfTheDay: Date = OneUptimeDate.getEndOfDay(todaysDay); + + const todaysEvents: Array<Event> = props.events.filter((event: Event) => { + let doesEventBelongsToToday: boolean = false; + + /// if the event starts or end today. + if ( + OneUptimeDate.isBetween(event.startDate, startOfTheDay, endOfTheDay) + ) { + doesEventBelongsToToday = true; + } + + if (OneUptimeDate.isBetween(event.endDate, startOfTheDay, endOfTheDay)) { + doesEventBelongsToToday = true; + } + + // if the event is outside start or end day but overlaps the day completely. + + if ( + OneUptimeDate.isBetween(startOfTheDay, event.startDate, endOfTheDay) && + OneUptimeDate.isBetween(endOfTheDay, startOfTheDay, event.endDate) + ) { + doesEventBelongsToToday = true; + } + + return doesEventBelongsToToday; + }); + + const secondsOfEvent: Dictionary<number> = {}; + + let currentPriority: number = 1; + + const eventLabels: Dictionary<string> = {}; + + for (const event of todaysEvents) { + const startDate: Date = OneUptimeDate.getGreaterDate( + event.startDate, + startOfTheDay, + ); + + const endDate: Date = OneUptimeDate.getLesserDate( + event.endDate, + OneUptimeDate.getLesserDate( + OneUptimeDate.getCurrentDate(), + endOfTheDay, + ), + ); + + const seconds: number = OneUptimeDate.getSecondsBetweenDates( + startDate, + endDate, + ); + + if (!secondsOfEvent[event.eventStatusId.toString()]) { + secondsOfEvent[event.eventStatusId.toString()] = 0; + } + + secondsOfEvent[event.eventStatusId.toString()] += seconds; + + eventLabels[event.eventStatusId.toString()] = event.label; + + // set bar color. + if (currentPriority <= event.priority) { + currentPriority = event.priority; + + // if there are no rules then use the color of the event. + + if (!props.barColorRules || props.barColorRules.length === 0) { + color = event.color; + } + } + } + + let hasEvents: boolean = false; + + let totalDowntimeInSeconds: number = 0; + + let totalUptimeInSeconds: number = 0; + + for (const key in secondsOfEvent) { + hasEvents = true; + + const eventStatusId: string = key; + + // if this is downtime state then, include tooltip. + + if ( + (props.downtimeEventStatusIds?.filter((id: ObjectID) => { + return id.toString() === eventStatusId.toString(); + }).length || 0) > 0 + ) { + toolTipText += `, ${ + eventLabels[key] + } for ${OneUptimeDate.secondsToFormattedFriendlyTimeString( + secondsOfEvent[key] || 0, + )}`; + } + + const isDowntimeEvent: boolean = Boolean( + props.downtimeEventStatusIds?.find((id: ObjectID) => { + return id.toString() === eventStatusId; + }), + ); + + if (isDowntimeEvent) { + // remove the seconds from total uptime. + const secondsOfDowntime: number = secondsOfEvent[key] || 0; + totalDowntimeInSeconds += secondsOfDowntime; + } else { + totalUptimeInSeconds += secondsOfEvent[key] || 0; + } + } + + // now check bar rules and finalize the color of the bar + + const uptimePercentForTheDay: number = + (totalUptimeInSeconds / (totalDowntimeInSeconds + totalUptimeInSeconds)) * + 100; + + for (const rules of props.barColorRules || []) { + if (uptimePercentForTheDay >= rules.uptimePercentGreaterThanOrEqualTo) { + color = rules.barColor; + break; + } + } + + if (todaysEvents.length === 1) { + hasEvents = true; + toolTipText = `${OneUptimeDate.getDateAsLocalFormattedString( + todaysDay, + true, + )} - 100% ${todaysEvents[0]?.label || "Operational"}.`; + } + + if (!hasEvents) { + toolTipText += ` - No data for this day.`; + color = props.defaultBarColor || Green; + } + + let className: string = "h-20 w-20"; + + if (props.height) { + className = "w-20 h-" + props.height; + } + return ( + <Tooltip key={dayNumber} text={toolTipText || "100% Operational"}> + <div + className={className} + style={{ + backgroundColor: color.toString(), + }} + ></div> + </Tooltip> + ); + }; + + type GetUptimeGraphFunction = () => Array<ReactElement>; + + const getUptimeGraph: GetUptimeGraphFunction = (): Array<ReactElement> => { + const elements: Array<ReactElement> = []; + + for (let i: number = 0; i < days; i++) { + elements.push(getUptimeBar(i)); + } + + return elements; + }; + + return ( + <div className="flex space-x-0.5 rounded overflow-hidden"> + {getUptimeGraph()} + </div> + ); }; export default DayUptimeGraph; diff --git a/CommonUI/src/Components/Header/Header.tsx b/CommonUI/src/Components/Header/Header.tsx index 009aff780f..d02b04bf77 100644 --- a/CommonUI/src/Components/Header/Header.tsx +++ b/CommonUI/src/Components/Header/Header.tsx @@ -1,39 +1,38 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - leftComponents?: undefined | Array<ReactElement> | ReactElement; - rightComponents?: undefined | Array<ReactElement> | ReactElement; - centerComponents?: undefined | Array<ReactElement> | ReactElement; - className?: string | undefined; + leftComponents?: undefined | Array<ReactElement> | ReactElement; + rightComponents?: undefined | Array<ReactElement> | ReactElement; + centerComponents?: undefined | Array<ReactElement> | ReactElement; + className?: string | undefined; } const Header: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <React.Fragment> - <div - className={ - props.className || - 'relative flex h-16 justify-between bg-white' - } - > - <div className="relative z-20 flex px-2 lg:px-0"> - {props.leftComponents} - </div> + return ( + <React.Fragment> + <div + className={ + props.className || "relative flex h-16 justify-between bg-white" + } + > + <div className="relative z-20 flex px-2 lg:px-0"> + {props.leftComponents} + </div> - {props.centerComponents && ( - <div className="relative z-0 flex flex-1 items-center justify-center px-2 sm:absolute sm:inset-0"> - {props.centerComponents} - </div> - )} + {props.centerComponents && ( + <div className="relative z-0 flex flex-1 items-center justify-center px-2 sm:absolute sm:inset-0"> + {props.centerComponents} + </div> + )} - <div className="hidden lg:relative lg:z-10 lg:ml-4 lg:flex lg:items-center"> - {props.rightComponents} - </div> - </div> - </React.Fragment> - ); + <div className="hidden lg:relative lg:z-10 lg:ml-4 lg:flex lg:items-center"> + {props.rightComponents} + </div> + </div> + </React.Fragment> + ); }; export default Header; diff --git a/CommonUI/src/Components/Header/HeaderIconDropdownButton.tsx b/CommonUI/src/Components/Header/HeaderIconDropdownButton.tsx index 41deddc2d4..2f43c82e66 100644 --- a/CommonUI/src/Components/Header/HeaderIconDropdownButton.tsx +++ b/CommonUI/src/Components/Header/HeaderIconDropdownButton.tsx @@ -1,86 +1,84 @@ -import useComponentOutsideClick from '../../Types/UseComponentOutsideClick'; -import Icon, { SizeProp } from '../Icon/Icon'; -import Image from '../Image/Image'; -import Route from 'Common/Types/API/Route'; -import IconProp from 'Common/Types/Icon/IconProp'; +import useComponentOutsideClick from "../../Types/UseComponentOutsideClick"; +import Icon, { SizeProp } from "../Icon/Icon"; +import Image from "../Image/Image"; +import Route from "Common/Types/API/Route"; +import IconProp from "Common/Types/Icon/IconProp"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - icon?: IconProp; - iconImageUrl?: string; - badge?: undefined | number; - children?: undefined | ReactElement | Array<ReactElement>; - title?: string | undefined; - onClick?: (() => void) | undefined; - name: string; - showDropdown: boolean; + icon?: IconProp; + iconImageUrl?: string; + badge?: undefined | number; + children?: undefined | ReactElement | Array<ReactElement>; + title?: string | undefined; + onClick?: (() => void) | undefined; + name: string; + showDropdown: boolean; } const HeaderIconDropdownButton: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ) => { - const { ref, isComponentVisible, setIsComponentVisible } = - useComponentOutsideClick(false); - const [isDropdownVisible, setDropdownVisible] = useState<boolean>(false); + const { ref, isComponentVisible, setIsComponentVisible } = + useComponentOutsideClick(false); + const [isDropdownVisible, setDropdownVisible] = useState<boolean>(false); - useEffect(() => { - setDropdownVisible(isComponentVisible); - }, [isComponentVisible]); + useEffect(() => { + setDropdownVisible(isComponentVisible); + }, [isComponentVisible]); - useEffect(() => { - setDropdownVisible(Boolean(props.showDropdown)); - setIsComponentVisible(Boolean(props.showDropdown)); - }, [props.showDropdown]); + useEffect(() => { + setDropdownVisible(Boolean(props.showDropdown)); + setIsComponentVisible(Boolean(props.showDropdown)); + }, [props.showDropdown]); - return ( - <div className="relative ml-4 flex-shrink-0"> - <div> - <button - type="button" - className="flex rounded-full bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" - id="user-menu-button" - aria-expanded="false" - aria-haspopup="true" - onClick={() => { - props.onClick && props.onClick(); - setIsComponentVisible(!isDropdownVisible); - }} - > - <span className="sr-only">{props.name}</span> - {props.iconImageUrl && ( - <Image - className="h-8 w-8 rounded-full" - onClick={() => { - props.onClick && props.onClick(); - }} - imageUrl={Route.fromString(`${props.iconImageUrl}`)} - alt={props.name} - /> - )} - {props.icon && ( - <Icon - className="text-gray-400 hover:text-indigo-500" - icon={props.icon} - size={SizeProp.Large} - /> - )} - </button> - {props.title} - {props.badge && props.badge > 0 && ( - <span className="badge bg-danger rounded-pill"> - {props.badge} - </span> - )} - </div> + return ( + <div className="relative ml-4 flex-shrink-0"> + <div> + <button + type="button" + className="flex rounded-full bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" + id="user-menu-button" + aria-expanded="false" + aria-haspopup="true" + onClick={() => { + props.onClick && props.onClick(); + setIsComponentVisible(!isDropdownVisible); + }} + > + <span className="sr-only">{props.name}</span> + {props.iconImageUrl && ( + <Image + className="h-8 w-8 rounded-full" + onClick={() => { + props.onClick && props.onClick(); + }} + imageUrl={Route.fromString(`${props.iconImageUrl}`)} + alt={props.name} + /> + )} + {props.icon && ( + <Icon + className="text-gray-400 hover:text-indigo-500" + icon={props.icon} + size={SizeProp.Large} + /> + )} + </button> + {props.title} + {props.badge && props.badge > 0 && ( + <span className="badge bg-danger rounded-pill">{props.badge}</span> + )} + </div> - <div ref={ref}>{isComponentVisible && props.children}</div> - </div> - ); + <div ref={ref}>{isComponentVisible && props.children}</div> + </div> + ); }; export default HeaderIconDropdownButton; diff --git a/CommonUI/src/Components/Header/IconDropdown/IconDropdownItem.tsx b/CommonUI/src/Components/Header/IconDropdown/IconDropdownItem.tsx index 97bf752d81..842bddb7c5 100644 --- a/CommonUI/src/Components/Header/IconDropdown/IconDropdownItem.tsx +++ b/CommonUI/src/Components/Header/IconDropdown/IconDropdownItem.tsx @@ -1,34 +1,34 @@ -import Icon from '../../Icon/Icon'; -import Link from '../../Link/Link'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../../Icon/Icon"; +import Link from "../../Link/Link"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - url?: URL | Route; - icon?: IconProp; - title: string; - openInNewTab?: boolean; - onClick?: (() => void) | undefined; + url?: URL | Route; + icon?: IconProp; + title: string; + openInNewTab?: boolean; + onClick?: (() => void) | undefined; } const IconDropdown: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Link - className="block py-2 px-4 text-sm text-gray-700 flex hover:bg-gray-100" - to={props.url} - openInNewTab={props.openInNewTab} - onClick={props.onClick} - > - <div className="mr-1 h-5 w-5"> - {props.icon ? <Icon icon={props.icon} /> : <></>} - </div> - <span className="">{props.title}</span> - </Link> - ); + return ( + <Link + className="block py-2 px-4 text-sm text-gray-700 flex hover:bg-gray-100" + to={props.url} + openInNewTab={props.openInNewTab} + onClick={props.onClick} + > + <div className="mr-1 h-5 w-5"> + {props.icon ? <Icon icon={props.icon} /> : <></>} + </div> + <span className="">{props.title}</span> + </Link> + ); }; export default IconDropdown; diff --git a/CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu.tsx b/CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu.tsx index 7b7eb2606d..2eb11eda93 100644 --- a/CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu.tsx +++ b/CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu.tsx @@ -1,22 +1,22 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; + children: ReactElement | Array<ReactElement>; } const IconDropdown: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none transform opacity-100 scale-100" - role="menu" - aria-orientation="vertical" - aria-labelledby="user-menu-button" - > - {props.children} - </div> - ); + return ( + <div + className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none transform opacity-100 scale-100" + role="menu" + aria-orientation="vertical" + aria-labelledby="user-menu-button" + > + {props.children} + </div> + ); }; export default IconDropdown; diff --git a/CommonUI/src/Components/Header/IconDropdown/IconDropdownRow.tsx b/CommonUI/src/Components/Header/IconDropdown/IconDropdownRow.tsx index c169fdc611..b76305d73d 100644 --- a/CommonUI/src/Components/Header/IconDropdown/IconDropdownRow.tsx +++ b/CommonUI/src/Components/Header/IconDropdown/IconDropdownRow.tsx @@ -1,13 +1,13 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; + children: ReactElement | Array<ReactElement>; } const IconDropdownRow: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return <div>{props.children}</div>; + return <div>{props.children}</div>; }; export default IconDropdownRow; diff --git a/CommonUI/src/Components/Header/Notifications/NotificationItem.tsx b/CommonUI/src/Components/Header/Notifications/NotificationItem.tsx index c617537cd4..1154d22839 100644 --- a/CommonUI/src/Components/Header/Notifications/NotificationItem.tsx +++ b/CommonUI/src/Components/Header/Notifications/NotificationItem.tsx @@ -1,48 +1,48 @@ -import CircularIconImage from '../../Icon/CircularIconImage'; -import Icon from '../../Icon/Icon'; -import OneUptimeDate from 'Common/Types/Date'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import CircularIconImage from "../../Icon/CircularIconImage"; +import Icon from "../../Icon/Icon"; +import OneUptimeDate from "Common/Types/Date"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - icon: IconProp; - title: string; - description: string; - createdAt: Date; - onClick?: (() => void) | undefined; + icon: IconProp; + title: string; + description: string; + createdAt: Date; + onClick?: (() => void) | undefined; } const NotificationItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <a - className="text-reset notification-item" - href="/" - onClick={props.onClick} - > - <div className="d-flex"> - <CircularIconImage icon={props.icon} /> - <div className="flex-grow-1"> - <h6 className="mt-0 mb-1">{props.title}</h6> - <div className="font-size-12 text-muted"> - <p className="mb-1">{props.description}</p> - <p className="mb-0 flex"> - <Icon icon={IconProp.Time} /> - <div - style={{ - marginTop: '1px', - marginRight: '3px', - }} - > - {OneUptimeDate.fromNow(props.createdAt)}{' '} - </div> - </p> - </div> - </div> - </div> - </a> - ); + return ( + <a + className="text-reset notification-item" + href="/" + onClick={props.onClick} + > + <div className="d-flex"> + <CircularIconImage icon={props.icon} /> + <div className="flex-grow-1"> + <h6 className="mt-0 mb-1">{props.title}</h6> + <div className="font-size-12 text-muted"> + <p className="mb-1">{props.description}</p> + <p className="mb-0 flex"> + <Icon icon={IconProp.Time} /> + <div + style={{ + marginTop: "1px", + marginRight: "3px", + }} + > + {OneUptimeDate.fromNow(props.createdAt)}{" "} + </div> + </p> + </div> + </div> + </div> + </a> + ); }; export default NotificationItem; diff --git a/CommonUI/src/Components/Header/Notifications/Notifications.tsx b/CommonUI/src/Components/Header/Notifications/Notifications.tsx index a567933760..af17e01a94 100644 --- a/CommonUI/src/Components/Header/Notifications/Notifications.tsx +++ b/CommonUI/src/Components/Header/Notifications/Notifications.tsx @@ -1,101 +1,98 @@ -import Icon from '../../Icon/Icon'; -import Link from '../../Link/Link'; -import Route from 'Common/Types/API/Route'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../../Icon/Icon"; +import Link from "../../Link/Link"; +import Route from "Common/Types/API/Route"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; + children: ReactElement | Array<ReactElement>; } const Notifications: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - tabIndex={-1} - role="menu" - aria-hidden="true" - style={{ - position: 'absolute', - willChange: 'transform', - top: '0px', - left: '0px', - transform: 'tranblue3d(0px, 5px, 0px)', - }} - className="dropdown-menu-lg dropdown-menu-end p-0 dropdown-menu show" - > - <div className="p-3"> - <div className="align-items-center row"> - <div className="col"> - <h6 className="m-0"> Notifications </h6> - </div> - <div className="col-auto"> - <a className="small" href="/dashboard"> - {' '} - Mark all as read - </a> - </div> - </div> - </div> - <div data-simplebar="init" style={{ height: '230px' }}> - <div className="simplebar-wrapper" style={{ margin: '0px' }}> - <div className="simplebar-height-auto-observer-wrapper"> - <div className="simplebar-height-auto-observer" /> - </div> - <div className="simplebar-mask"> - <div - className="simplebar-offset" - style={{ right: '0px', bottom: '0px' }} - > - <div - className="simplebar-content-wrapper" - style={{ height: 'auto', overflow: 'hidden' }} - > - <div - className="simplebar-content" - style={{ padding: '0px' }} - > - {props.children} - </div> - </div> - </div> - </div> - <div - className="simplebar-placeholder" - style={{ width: '0px', height: '0px' }} - /> - </div> - <div - className="simplebar-track simplebar-horizontal" - style={{ visibility: 'hidden' }} - > - <div - className="simplebar-scrollbar" - style={{ width: '0px', display: 'none' }} - /> - </div> - <div - className="simplebar-track simplebar-vertical" - style={{ visibility: 'hidden' }} - > - <div - className="simplebar-scrollbar" - style={{ height: '0px', display: 'none' }} - /> - </div> - </div> - <div className="p-2 border-top d-grid"> - <Link - className="btn btn-sm btn-link font-size-14 btn-block text-center flex" - to={new Route('/notifications')} - > - <span>View all</span> - <Icon icon={IconProp.ChevronRight} /> - </Link> - </div> + return ( + <div + tabIndex={-1} + role="menu" + aria-hidden="true" + style={{ + position: "absolute", + willChange: "transform", + top: "0px", + left: "0px", + transform: "tranblue3d(0px, 5px, 0px)", + }} + className="dropdown-menu-lg dropdown-menu-end p-0 dropdown-menu show" + > + <div className="p-3"> + <div className="align-items-center row"> + <div className="col"> + <h6 className="m-0"> Notifications </h6> + </div> + <div className="col-auto"> + <a className="small" href="/dashboard"> + {" "} + Mark all as read + </a> + </div> </div> - ); + </div> + <div data-simplebar="init" style={{ height: "230px" }}> + <div className="simplebar-wrapper" style={{ margin: "0px" }}> + <div className="simplebar-height-auto-observer-wrapper"> + <div className="simplebar-height-auto-observer" /> + </div> + <div className="simplebar-mask"> + <div + className="simplebar-offset" + style={{ right: "0px", bottom: "0px" }} + > + <div + className="simplebar-content-wrapper" + style={{ height: "auto", overflow: "hidden" }} + > + <div className="simplebar-content" style={{ padding: "0px" }}> + {props.children} + </div> + </div> + </div> + </div> + <div + className="simplebar-placeholder" + style={{ width: "0px", height: "0px" }} + /> + </div> + <div + className="simplebar-track simplebar-horizontal" + style={{ visibility: "hidden" }} + > + <div + className="simplebar-scrollbar" + style={{ width: "0px", display: "none" }} + /> + </div> + <div + className="simplebar-track simplebar-vertical" + style={{ visibility: "hidden" }} + > + <div + className="simplebar-scrollbar" + style={{ height: "0px", display: "none" }} + /> + </div> + </div> + <div className="p-2 border-top d-grid"> + <Link + className="btn btn-sm btn-link font-size-14 btn-block text-center flex" + to={new Route("/notifications")} + > + <span>View all</span> + <Icon icon={IconProp.ChevronRight} /> + </Link> + </div> + </div> + ); }; export default Notifications; diff --git a/CommonUI/src/Components/Header/ProjectPicker/CreateNewProjectButton.tsx b/CommonUI/src/Components/Header/ProjectPicker/CreateNewProjectButton.tsx index 9c84d17ae8..7c170fc051 100644 --- a/CommonUI/src/Components/Header/ProjectPicker/CreateNewProjectButton.tsx +++ b/CommonUI/src/Components/Header/ProjectPicker/CreateNewProjectButton.tsx @@ -1,34 +1,34 @@ -import Icon from '../../Icon/Icon'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../../Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onCreateButtonClicked: () => void; + onCreateButtonClicked: () => void; } const CreateNewProjectButton: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <li - className="text-gray-900 relative cursor-default select-none py-2 pl-3 pr-9 bg-gray-100 cursor-pointer hover:bg-gray-200 hover:text-gray-900 text-gray-500 -mb-1" - id="listbox-option-0" - role="option" - onClick={() => { - props.onCreateButtonClicked(); - }} - > - <div className="flex items-center"> - <Icon - icon={IconProp.Add} - className="h-6 w-6 flex-shrink-0 rounded-full" - /> - <span className="cursor-pointer rounded-md py-2 px-3 inline-flex items-center text-sm font-medium block truncate"> - Create New Project - </span> - </div> - </li> - ); + return ( + <li + className="text-gray-900 relative cursor-default select-none py-2 pl-3 pr-9 bg-gray-100 cursor-pointer hover:bg-gray-200 hover:text-gray-900 text-gray-500 -mb-1" + id="listbox-option-0" + role="option" + onClick={() => { + props.onCreateButtonClicked(); + }} + > + <div className="flex items-center"> + <Icon + icon={IconProp.Add} + className="h-6 w-6 flex-shrink-0 rounded-full" + /> + <span className="cursor-pointer rounded-md py-2 px-3 inline-flex items-center text-sm font-medium block truncate"> + Create New Project + </span> + </div> + </li> + ); }; export default CreateNewProjectButton; diff --git a/CommonUI/src/Components/Header/ProjectPicker/ProjectPicker.tsx b/CommonUI/src/Components/Header/ProjectPicker/ProjectPicker.tsx index 508117690b..2d1df7ba68 100644 --- a/CommonUI/src/Components/Header/ProjectPicker/ProjectPicker.tsx +++ b/CommonUI/src/Components/Header/ProjectPicker/ProjectPicker.tsx @@ -1,126 +1,118 @@ -import useComponentOutsideClick from '../../../Types/UseComponentOutsideClick'; -import Icon from '../../Icon/Icon'; -import CreateNewProjectButton from './CreateNewProjectButton'; -import ProjectPickerMenu from './ProjectPickerMenu'; -import ProjectPickerMenuItem from './ProjectPickerMenuItem'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Project from 'Model/Models/Project'; +import useComponentOutsideClick from "../../../Types/UseComponentOutsideClick"; +import Icon from "../../Icon/Icon"; +import CreateNewProjectButton from "./CreateNewProjectButton"; +import ProjectPickerMenu from "./ProjectPickerMenu"; +import ProjectPickerMenuItem from "./ProjectPickerMenuItem"; +import IconProp from "Common/Types/Icon/IconProp"; +import Project from "Model/Models/Project"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - projects: Array<Project>; - selectedProjectIcon: IconProp; - selectedProjectName: string; - onCreateProjectButtonClicked: () => void; - onProjectSelected: (project: Project) => void; + projects: Array<Project>; + selectedProjectIcon: IconProp; + selectedProjectName: string; + onCreateProjectButtonClicked: () => void; + onProjectSelected: (project: Project) => void; } const ProjectPicker: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const { ref, isComponentVisible, setIsComponentVisible } = - useComponentOutsideClick(false); + const { ref, isComponentVisible, setIsComponentVisible } = + useComponentOutsideClick(false); - const [isDropdownVisible, setDropdownVisible] = useState<boolean>(false); + const [isDropdownVisible, setDropdownVisible] = useState<boolean>(false); - const [filterValue, setFilterValue] = useState<string>(''); + const [filterValue, setFilterValue] = useState<string>(""); - useEffect(() => { - setDropdownVisible(isComponentVisible); - setFilterValue(''); - }, [isComponentVisible]); + useEffect(() => { + setDropdownVisible(isComponentVisible); + setFilterValue(""); + }, [isComponentVisible]); - return ( - <div className="w-64 mr-3"> - <div className="relative mt-3 w-full"> - <button - onClick={() => { - setIsComponentVisible(!isDropdownVisible); - }} - type="button" - className="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm" - aria-haspopup="listbox" - aria-expanded="true" - aria-labelledby="listbox-label" - > - <span className="flex items-center"> - <Icon - icon={props.selectedProjectIcon} - className="h-6 w-6 flex-shrink-0 rounded-full" + return ( + <div className="w-64 mr-3"> + <div className="relative mt-3 w-full"> + <button + onClick={() => { + setIsComponentVisible(!isDropdownVisible); + }} + type="button" + className="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm" + aria-haspopup="listbox" + aria-expanded="true" + aria-labelledby="listbox-label" + > + <span className="flex items-center"> + <Icon + icon={props.selectedProjectIcon} + className="h-6 w-6 flex-shrink-0 rounded-full" + /> + + <span className="ml-3 block truncate"> + {props.selectedProjectName} + </span> + </span> + <span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2"> + <Icon + icon={IconProp.UpDownArrow} + className="h-5 w-5 text-gray-400" + /> + </span> + </button> + <div ref={ref}> + {isComponentVisible && ( + <ProjectPickerMenu + onFilter={(value: string) => { + setFilterValue(value.toLowerCase().trim()); + }} + > + <> + {props.projects && props.projects.length > 0 ? ( + props.projects + .filter((project: Project) => { + if (!filterValue) { + return true; + } + return ( + project.name && + project.name.toLowerCase().startsWith(filterValue) + ); + }) + .map((project: Project, i: number) => { + return ( + <ProjectPickerMenuItem + key={i} + project={project} + onProjectSelected={(project: Project) => { + setIsComponentVisible(false); + props.onProjectSelected(project); + }} + icon={IconProp.Folder} /> - - <span className="ml-3 block truncate"> - {props.selectedProjectName} - </span> - </span> - <span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2"> - <Icon - icon={IconProp.UpDownArrow} - className="h-5 w-5 text-gray-400" - /> - </span> - </button> - <div ref={ref}> - {isComponentVisible && ( - <ProjectPickerMenu - onFilter={(value: string) => { - setFilterValue(value.toLowerCase().trim()); - }} - > - <> - {props.projects && props.projects.length > 0 ? ( - props.projects - .filter((project: Project) => { - if (!filterValue) { - return true; - } - return ( - project.name && - project.name - .toLowerCase() - .startsWith(filterValue) - ); - }) - .map((project: Project, i: number) => { - return ( - <ProjectPickerMenuItem - key={i} - project={project} - onProjectSelected={( - project: Project - ) => { - setIsComponentVisible( - false - ); - props.onProjectSelected( - project - ); - }} - icon={IconProp.Folder} - /> - ); - }) - ) : ( - <></> - )} - </> - <CreateNewProjectButton - onCreateButtonClicked={() => { - setIsComponentVisible(false); - props.onCreateProjectButtonClicked(); - }} - /> - </ProjectPickerMenu> - )} - </div> - </div> + ); + }) + ) : ( + <></> + )} + </> + <CreateNewProjectButton + onCreateButtonClicked={() => { + setIsComponentVisible(false); + props.onCreateProjectButtonClicked(); + }} + /> + </ProjectPickerMenu> + )} </div> - ); + </div> + </div> + ); }; export default ProjectPicker; diff --git a/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerFilterBox.tsx b/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerFilterBox.tsx index ab6744ee45..ae66860d54 100644 --- a/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerFilterBox.tsx +++ b/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerFilterBox.tsx @@ -1,27 +1,27 @@ -import Input from '../../Input/Input'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Input from "../../Input/Input"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onChange: (search: string) => void; + onChange: (search: string) => void; } const ProjectPickerFilterBox: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="sm:max-w-xs m-2"> - <label className="sr-only">Search Projects</label> - <div className="relative"> - <Input - onChange={(value: string) => { - props.onChange(value); - }} - className="block w-full rounded-md border border-gray-300 bg-white py-2 pl-5 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm" - placeholder="Search Projects" - /> - </div> - </div> - ); + return ( + <div className="sm:max-w-xs m-2"> + <label className="sr-only">Search Projects</label> + <div className="relative"> + <Input + onChange={(value: string) => { + props.onChange(value); + }} + className="block w-full rounded-md border border-gray-300 bg-white py-2 pl-5 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm" + placeholder="Search Projects" + /> + </div> + </div> + ); }; export default ProjectPickerFilterBox; diff --git a/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerMenu.tsx b/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerMenu.tsx index 0a8d7146b5..cdd80133c8 100644 --- a/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerMenu.tsx +++ b/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerMenu.tsx @@ -1,30 +1,30 @@ -import ProjectPickerFilterBox from './ProjectPickerFilterBox'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ProjectPickerFilterBox from "./ProjectPickerFilterBox"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; - onFilter: (value: string) => void; + children: ReactElement | Array<ReactElement>; + onFilter: (value: string) => void; } const ProjectPickerMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <ul - className="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" - role="listbox" - aria-labelledby="listbox-label" - aria-activedescendant="listbox-option-3" - > - <ProjectPickerFilterBox - key={2} - onChange={(value: string) => { - props.onFilter(value); - }} - /> - {props.children} - </ul> - ); + return ( + <ul + className="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" + role="listbox" + aria-labelledby="listbox-label" + aria-activedescendant="listbox-option-3" + > + <ProjectPickerFilterBox + key={2} + onChange={(value: string) => { + props.onFilter(value); + }} + /> + {props.children} + </ul> + ); }; export default ProjectPickerMenu; diff --git a/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerMenuItem.tsx b/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerMenuItem.tsx index 1406e1a2f1..a2cae1f1bc 100644 --- a/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerMenuItem.tsx +++ b/CommonUI/src/Components/Header/ProjectPicker/ProjectPickerMenuItem.tsx @@ -1,45 +1,43 @@ -import Navigation from '../../../Utils/Navigation'; -import Icon from '../../Icon/Icon'; -import Route from 'Common/Types/API/Route'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Project from 'Model/Models/Project'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Navigation from "../../../Utils/Navigation"; +import Icon from "../../Icon/Icon"; +import Route from "Common/Types/API/Route"; +import IconProp from "Common/Types/Icon/IconProp"; +import Project from "Model/Models/Project"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - icon: IconProp; - onProjectSelected: (project: Project) => void; - project: Project; + icon: IconProp; + onProjectSelected: (project: Project) => void; + project: Project; } const ProjectPickerMenuItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const title: string = props.project.name!; - const route: Route = new Route( - '/dashboard/' + props.project.id?.toString() - ); + const title: string = props.project.name!; + const route: Route = new Route("/dashboard/" + props.project.id?.toString()); - return ( - <li - className="text-gray-900 relative cursor-default select-none py-2 pl-3 pr-9 cursor-pointer hover:bg-gray-50" - id="listbox-option-0" - role="option" - onClick={() => { - props.onProjectSelected(props.project); - Navigation.navigate(route); - }} - > - <div className="flex items-center"> - <Icon - icon={props.icon} - className="h-6 w-6 flex-shrink-0 rounded-full" - /> - <span className="cursor-pointer text-gray-500 hover:bg-gray-50 hover:text-gray-900 rounded-md py-2 px-3 inline-flex items-center text-sm font-medium block truncate"> - {title} - </span> - </div> - </li> - ); + return ( + <li + className="text-gray-900 relative cursor-default select-none py-2 pl-3 pr-9 cursor-pointer hover:bg-gray-50" + id="listbox-option-0" + role="option" + onClick={() => { + props.onProjectSelected(props.project); + Navigation.navigate(route); + }} + > + <div className="flex items-center"> + <Icon + icon={props.icon} + className="h-6 w-6 flex-shrink-0 rounded-full" + /> + <span className="cursor-pointer text-gray-500 hover:bg-gray-50 hover:text-gray-900 rounded-md py-2 px-3 inline-flex items-center text-sm font-medium block truncate"> + {title} + </span> + </div> + </li> + ); }; export default ProjectPickerMenuItem; diff --git a/CommonUI/src/Components/Header/SearchBox.tsx b/CommonUI/src/Components/Header/SearchBox.tsx index 32f95e8e5b..8b5264803f 100644 --- a/CommonUI/src/Components/Header/SearchBox.tsx +++ b/CommonUI/src/Components/Header/SearchBox.tsx @@ -1,30 +1,30 @@ // Tailwind -import Input from '../Input/Input'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Input from "../Input/Input"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onChange: (search: string) => void; + onChange: (search: string) => void; } const SearchBox: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="relative z-0 flex flex-1 items-center justify-center px-2 sm:absolute sm:inset-0"> - <div className="w-full sm:max-w-xs"> - <label className="sr-only">Search</label> - <div className="relative"> - <Input - onChange={(value: string) => { - props.onChange(value); - }} - className="block w-full rounded-md border border-gray-300 bg-white py-2 pl-5 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm" - placeholder="Search" - /> - </div> - </div> + return ( + <div className="relative z-0 flex flex-1 items-center justify-center px-2 sm:absolute sm:inset-0"> + <div className="w-full sm:max-w-xs"> + <label className="sr-only">Search</label> + <div className="relative"> + <Input + onChange={(value: string) => { + props.onChange(value); + }} + className="block w-full rounded-md border border-gray-300 bg-white py-2 pl-5 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm" + placeholder="Search" + /> </div> - ); + </div> + </div> + ); }; export default SearchBox; diff --git a/CommonUI/src/Components/Header/UserProfile/UserProfile.tsx b/CommonUI/src/Components/Header/UserProfile/UserProfile.tsx index 67449fd97f..68ee659856 100644 --- a/CommonUI/src/Components/Header/UserProfile/UserProfile.tsx +++ b/CommonUI/src/Components/Header/UserProfile/UserProfile.tsx @@ -1,46 +1,46 @@ -import Icon from '../../Icon/Icon'; -import Image from '../../Image/Image'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Name from 'Common/Types/Name'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../../Icon/Icon"; +import Image from "../../Image/Image"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import IconProp from "Common/Types/Icon/IconProp"; +import Name from "Common/Types/Name"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - userFullName: Name; - userProfilePicture: URL | Route; - onClick: () => void; + userFullName: Name; + userProfilePicture: URL | Route; + onClick: () => void; } const UserProfile: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="d-inline-block dropdown"> - <button - onClick={() => { - props.onClick(); - }} - id="page-header-user-dropdown" - aria-haspopup="true" - className="btn header-item bg-soft-light border-start border-end flex" - aria-expanded="false" - style={{ - alignItems: 'center', - }} - > - <Image - className="rounded-circle header-profile-user" - imageUrl={props.userProfilePicture} - /> + return ( + <div className="d-inline-block dropdown"> + <button + onClick={() => { + props.onClick(); + }} + id="page-header-user-dropdown" + aria-haspopup="true" + className="btn header-item bg-soft-light border-start border-end flex" + aria-expanded="false" + style={{ + alignItems: "center", + }} + > + <Image + className="rounded-circle header-profile-user" + imageUrl={props.userProfilePicture} + /> - <span className="d-none d-xl-inline-block ms-2 me-1"> - {props.userFullName.toString()} - </span> - <Icon icon={IconProp.ChevronDown} /> - </button> - </div> - ); + <span className="d-none d-xl-inline-block ms-2 me-1"> + {props.userFullName.toString()} + </span> + <Icon icon={IconProp.ChevronDown} /> + </button> + </div> + ); }; export default UserProfile; diff --git a/CommonUI/src/Components/Header/UserProfile/UserProfileDropdownDivider.tsx b/CommonUI/src/Components/Header/UserProfile/UserProfileDropdownDivider.tsx index bcda699748..98e6df0f1f 100644 --- a/CommonUI/src/Components/Header/UserProfile/UserProfileDropdownDivider.tsx +++ b/CommonUI/src/Components/Header/UserProfile/UserProfileDropdownDivider.tsx @@ -1,7 +1,7 @@ -import React, { ReactElement } from 'react'; +import React, { ReactElement } from "react"; const UserProfileDropdownDivider: () => JSX.Element = (): ReactElement => { - return <div className="dropdown-divider"></div>; + return <div className="dropdown-divider"></div>; }; export default UserProfileDropdownDivider; diff --git a/CommonUI/src/Components/Header/UserProfile/UserProfileMenu.tsx b/CommonUI/src/Components/Header/UserProfile/UserProfileMenu.tsx index 8147b422a6..8604ebe443 100644 --- a/CommonUI/src/Components/Header/UserProfile/UserProfileMenu.tsx +++ b/CommonUI/src/Components/Header/UserProfile/UserProfileMenu.tsx @@ -1,29 +1,29 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; + children: ReactElement | Array<ReactElement>; } const UserProfile: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - tabIndex={-1} - role="menu" - aria-hidden="true" - className="dropdown-menu-end dropdown-menu show" - style={{ - position: 'absolute', - willChange: 'transform', - top: '0px', - left: '0px', - transform: 'tranblue3d(0px, 70px, 0px)', - }} - > - {props.children} - </div> - ); + return ( + <div + tabIndex={-1} + role="menu" + aria-hidden="true" + className="dropdown-menu-end dropdown-menu show" + style={{ + position: "absolute", + willChange: "transform", + top: "0px", + left: "0px", + transform: "tranblue3d(0px, 70px, 0px)", + }} + > + {props.children} + </div> + ); }; export default UserProfile; diff --git a/CommonUI/src/Components/Header/UserProfile/UserProfileMenuItem.tsx b/CommonUI/src/Components/Header/UserProfile/UserProfileMenuItem.tsx index e74987c10d..be8af32855 100644 --- a/CommonUI/src/Components/Header/UserProfile/UserProfileMenuItem.tsx +++ b/CommonUI/src/Components/Header/UserProfile/UserProfileMenuItem.tsx @@ -1,50 +1,48 @@ -import Icon from '../../Icon/Icon'; -import Link from '../../Link/Link'; -import Route from 'Common/Types/API/Route'; -import Color from 'Common/Types/Color'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../../Icon/Icon"; +import Link from "../../Link/Link"; +import Route from "Common/Types/API/Route"; +import Color from "Common/Types/Color"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; - badge?: undefined | number; - route?: Route | undefined; - onClick?: (() => void) | undefined; - icon: IconProp; - iconColor?: undefined | Color; + title: string; + badge?: undefined | number; + route?: Route | undefined; + onClick?: (() => void) | undefined; + icon: IconProp; + iconColor?: undefined | Color; } const UserProfile: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Link - to={props.route} - onClick={props.onClick} - className="dropdown-item flex" + return ( + <Link + to={props.route} + onClick={props.onClick} + className="dropdown-item flex" + > + {props.badge ? ( + <span className="badge bg-success float-end">{props.badge}</span> + ) : ( + <></> + )} + <Icon + icon={props.icon} + color={props.iconColor ? props.iconColor : null} + /> + { + <div + style={{ + marginTop: "1px", + }} > - {props.badge ? ( - <span className="badge bg-success float-end"> - {props.badge} - </span> - ) : ( - <></> - )} - <Icon - icon={props.icon} - color={props.iconColor ? props.iconColor : null} - /> - { - <div - style={{ - marginTop: '1px', - }} - > -   {props.title}     - </div> - } - </Link> - ); +   {props.title}     + </div> + } + </Link> + ); }; export default UserProfile; diff --git a/CommonUI/src/Components/HeaderAlert/HeaderAlert.tsx b/CommonUI/src/Components/HeaderAlert/HeaderAlert.tsx index d6138a38aa..2fda1cd2de 100644 --- a/CommonUI/src/Components/HeaderAlert/HeaderAlert.tsx +++ b/CommonUI/src/Components/HeaderAlert/HeaderAlert.tsx @@ -1,34 +1,34 @@ -import Icon from '../Icon/Icon'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { ReactElement } from 'react'; +import Icon from "../Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { ReactElement } from "react"; export interface ComponentProps { - icon: IconProp; - onClick?: (() => void) | undefined; - title: string; - className?: string | undefined; + icon: IconProp; + onClick?: (() => void) | undefined; + title: string; + className?: string | undefined; } const HeaderAlert: (props: ComponentProps) => ReactElement = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - className={`rounded-md ${props.className} p-3 pr-4`} - onClick={() => { - props.onClick && props.onClick(); - }} - > - <div className="flex "> - <div className="flex-shrink-0"> - <Icon icon={props.icon} className="h-5 w-5 text-white" /> - </div> - <div className="ml-1 flex-1 md:flex md:justify-between"> - <p className={`text-sm text-white`}>{props.title}</p> - </div> - </div> + return ( + <div + className={`rounded-md ${props.className} p-3 pr-4`} + onClick={() => { + props.onClick && props.onClick(); + }} + > + <div className="flex "> + <div className="flex-shrink-0"> + <Icon icon={props.icon} className="h-5 w-5 text-white" /> </div> - ); + <div className="ml-1 flex-1 md:flex md:justify-between"> + <p className={`text-sm text-white`}>{props.title}</p> + </div> + </div> + </div> + ); }; export default HeaderAlert; diff --git a/CommonUI/src/Components/HeaderAlert/HeaderModelAlert.tsx b/CommonUI/src/Components/HeaderAlert/HeaderModelAlert.tsx index 70e9a33e71..b604698dac 100644 --- a/CommonUI/src/Components/HeaderAlert/HeaderModelAlert.tsx +++ b/CommonUI/src/Components/HeaderAlert/HeaderModelAlert.tsx @@ -1,93 +1,91 @@ -import API from '../../Utils/API/API'; -import Query from '../../Utils/BaseDatabase/Query'; -import ModelAPI, { RequestOptions } from '../../Utils/ModelAPI/ModelAPI'; -import HeaderAlert from './HeaderAlert'; -import BaseModel from 'Common/Models/BaseModel'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { ReactElement, useEffect, useState } from 'react'; +import API from "../../Utils/API/API"; +import Query from "../../Utils/BaseDatabase/Query"; +import ModelAPI, { RequestOptions } from "../../Utils/ModelAPI/ModelAPI"; +import HeaderAlert from "./HeaderAlert"; +import BaseModel from "Common/Models/BaseModel"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { ReactElement, useEffect, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - icon: IconProp; - modelType: { new (): TBaseModel }; - singularName: string; - pluralName: string; - query: Query<TBaseModel>; - requestOptions?: RequestOptions | undefined; - onCountFetchInit?: (() => void) | undefined; - onClick?: (() => void) | undefined; - refreshToggle?: boolean | undefined; - className?: string | undefined; + icon: IconProp; + modelType: { new (): TBaseModel }; + singularName: string; + pluralName: string; + query: Query<TBaseModel>; + requestOptions?: RequestOptions | undefined; + onCountFetchInit?: (() => void) | undefined; + onClick?: (() => void) | undefined; + refreshToggle?: boolean | undefined; + className?: string | undefined; } const HeaderModelAlert: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); - const [count, setCount] = useState<number>(0); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [count, setCount] = useState<number>(0); - useEffect(() => { - fetchCount().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); - }); - }, [props.refreshToggle]); + useEffect(() => { + fetchCount().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, [props.refreshToggle]); - const fetchCount: PromiseVoidFunction = async (): Promise<void> => { - setError(''); - setIsLoading(true); + const fetchCount: PromiseVoidFunction = async (): Promise<void> => { + setError(""); + setIsLoading(true); - if (props.onCountFetchInit) { - props.onCountFetchInit(); - } - - try { - const count: number = await ModelAPI.count<TBaseModel>({ - modelType: props.modelType, - query: props.query, - requestOptions: props.requestOptions, - }); - - setCount(count); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - useEffect(() => { - setIsLoading(true); - fetchCount().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); - }); - setIsLoading(false); - }, []); - - if (error) { - return <></>; + if (props.onCountFetchInit) { + props.onCountFetchInit(); } - if (isLoading) { - return <></>; + try { + const count: number = await ModelAPI.count<TBaseModel>({ + modelType: props.modelType, + query: props.query, + requestOptions: props.requestOptions, + }); + + setCount(count); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (count === 0) { - return <></>; - } + setIsLoading(false); + }; - return ( - <HeaderAlert - title={`${count} ${ - count > 1 ? props.pluralName : props.singularName - }`} - icon={props.icon} - onClick={props.onClick} - className={props.className} - /> - ); + useEffect(() => { + setIsLoading(true); + fetchCount().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + setIsLoading(false); + }, []); + + if (error) { + return <></>; + } + + if (isLoading) { + return <></>; + } + + if (count === 0) { + return <></>; + } + + return ( + <HeaderAlert + title={`${count} ${count > 1 ? props.pluralName : props.singularName}`} + icon={props.icon} + onClick={props.onClick} + className={props.className} + /> + ); }; export default HeaderModelAlert; diff --git a/CommonUI/src/Components/HiddenText/HiddenText.tsx b/CommonUI/src/Components/HiddenText/HiddenText.tsx index d087d4f9df..248f31aa8c 100644 --- a/CommonUI/src/Components/HiddenText/HiddenText.tsx +++ b/CommonUI/src/Components/HiddenText/HiddenText.tsx @@ -1,57 +1,57 @@ -import CopyableButton from '../CopyableButton/CopyableButton'; -import Icon from '../Icon/Icon'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import CopyableButton from "../CopyableButton/CopyableButton"; +import Icon from "../Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - text: string; - isCopyable?: boolean; + text: string; + isCopyable?: boolean; } const HiddenText: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [showText, setShowText] = useState<boolean>(false); - - if (!showText) { - return ( - <p - role="hidden-text" - className="cursor-pointer underline" - onClick={() => { - setShowText(true); - }} - > - Click here to reveal - </p> - ); - } + const [showText, setShowText] = useState<boolean>(false); + if (!showText) { return ( - <div className="flex"> - <div className="flex"> - <div - style={{ - marginRight: '5px', - }} - role="revealed-text" - > - {props.text} - </div>{' '} - <Icon - icon={IconProp.Hide} - className="cursor-pointer text-gray-400 h-4 w-4" - data-testid="hide-text-icon" - onClick={() => { - setShowText(false); - }} - /> - </div> - {props.isCopyable && showText && ( - <CopyableButton textToBeCopied={props.text} /> - )} - </div> + <p + role="hidden-text" + className="cursor-pointer underline" + onClick={() => { + setShowText(true); + }} + > + Click here to reveal + </p> ); + } + + return ( + <div className="flex"> + <div className="flex"> + <div + style={{ + marginRight: "5px", + }} + role="revealed-text" + > + {props.text} + </div>{" "} + <Icon + icon={IconProp.Hide} + className="cursor-pointer text-gray-400 h-4 w-4" + data-testid="hide-text-icon" + onClick={() => { + setShowText(false); + }} + /> + </div> + {props.isCopyable && showText && ( + <CopyableButton textToBeCopied={props.text} /> + )} + </div> + ); }; export default HiddenText; diff --git a/CommonUI/src/Components/HorizontalRule/HorizontalRule.tsx b/CommonUI/src/Components/HorizontalRule/HorizontalRule.tsx index 24531f4435..f636d7d14e 100644 --- a/CommonUI/src/Components/HorizontalRule/HorizontalRule.tsx +++ b/CommonUI/src/Components/HorizontalRule/HorizontalRule.tsx @@ -1,17 +1,17 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - className?: string | undefined; + className?: string | undefined; } const HorizontalRule: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - className={`border-b border-gray-700/10 mb-8 mt-8 ${props.className}`} - ></div> - ); + return ( + <div + className={`border-b border-gray-700/10 mb-8 mt-8 ${props.className}`} + ></div> + ); }; export default HorizontalRule; diff --git a/CommonUI/src/Components/Icon/CircularIconImage.tsx b/CommonUI/src/Components/Icon/CircularIconImage.tsx index a09af645d7..86008b5161 100644 --- a/CommonUI/src/Components/Icon/CircularIconImage.tsx +++ b/CommonUI/src/Components/Icon/CircularIconImage.tsx @@ -1,35 +1,35 @@ -import Icon, { SizeProp } from './Icon'; -import Color from 'Common/Types/Color'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon, { SizeProp } from "./Icon"; +import Color from "Common/Types/Color"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - backgroundColor?: undefined | Color; - icon: IconProp; - iconColor?: undefined | Color; + backgroundColor?: undefined | Color; + icon: IconProp; + iconColor?: undefined | Color; } const CircularIconImage: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - className="me-3 rounded-circle avatar-sm shadow-md" - style={{ - textAlign: 'center', - paddingTop: '3px', - backgroundColor: props.backgroundColor - ? props.backgroundColor.toString() - : 'black', - }} - > - <Icon - icon={props.icon} - size={SizeProp.Large} - color={props.iconColor ? props.iconColor : new Color('#ffffff')} - /> - </div> - ); + return ( + <div + className="me-3 rounded-circle avatar-sm shadow-md" + style={{ + textAlign: "center", + paddingTop: "3px", + backgroundColor: props.backgroundColor + ? props.backgroundColor.toString() + : "black", + }} + > + <Icon + icon={props.icon} + size={SizeProp.Large} + color={props.iconColor ? props.iconColor : new Color("#ffffff")} + /> + </div> + ); }; export default CircularIconImage; diff --git a/CommonUI/src/Components/Icon/Icon.tsx b/CommonUI/src/Components/Icon/Icon.tsx index bb55615160..efe0dba18b 100644 --- a/CommonUI/src/Components/Icon/Icon.tsx +++ b/CommonUI/src/Components/Icon/Icon.tsx @@ -1,1103 +1,1092 @@ -import Color from 'Common/Types/Color'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Color from "Common/Types/Color"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export enum SizeProp { - ExtraSmall, - Smaller, - Small, - Regular, - Large, - Larger, - ExtraLarge, - Five, + ExtraSmall, + Smaller, + Small, + Regular, + Large, + Larger, + ExtraLarge, + Five, } export enum ThickProp { - Normal = '0.5px', - LessThick = '1px', - Thick = '2px', + Normal = "0.5px", + LessThick = "1px", + Thick = "2px", } export enum IconType { - Danger = 'Danger', - Success = 'Success', - Info = 'Info', - Warning = 'Warning', + Danger = "Danger", + Success = "Success", + Info = "Info", + Warning = "Warning", } export interface ComponentProps { - icon: IconProp; - size?: SizeProp | undefined; - className?: string | undefined; - color?: Color | null; - thick?: ThickProp; - onClick?: (() => void) | undefined; - type?: IconType | undefined; - style?: React.CSSProperties | undefined; - 'data-testid'?: string; + icon: IconProp; + size?: SizeProp | undefined; + className?: string | undefined; + color?: Color | null; + thick?: ThickProp; + onClick?: (() => void) | undefined; + type?: IconType | undefined; + style?: React.CSSProperties | undefined; + "data-testid"?: string; } const Icon: FunctionComponent<ComponentProps> = ({ - size = SizeProp.Regular, - icon, - className, - color, - thick, - onClick, - type, - style, - 'data-testid': dataTestId, + size = SizeProp.Regular, + icon, + className, + color, + thick, + onClick, + type, + style, + "data-testid": dataTestId, }: ComponentProps): ReactElement => { - let sizeClassName: string = ''; - if ( - className && - !(className?.includes('h-') && className?.includes('w-')) - ) { - if (size === SizeProp.Large) { - sizeClassName = 'h-6 w-6'; - } else if (size === SizeProp.Larger) { - sizeClassName = 'h-8 w-8'; - } else if (size === SizeProp.ExtraLarge) { - sizeClassName = 'h-10 w-10'; - } else if (size === SizeProp.ExtraSmall) { - sizeClassName = 'h-1 w-1'; - } else if (size === SizeProp.Small) { - sizeClassName = 'h-3 w-3'; - } else if (size === SizeProp.Smaller) { - sizeClassName = 'h-2 w-2'; - } else if (size === SizeProp.Five) { - sizeClassName = 'h-5 w-5'; - } else if (size === SizeProp.Regular) { - sizeClassName = 'h4 w4'; - } + let sizeClassName: string = ""; + if (className && !(className?.includes("h-") && className?.includes("w-"))) { + if (size === SizeProp.Large) { + sizeClassName = "h-6 w-6"; + } else if (size === SizeProp.Larger) { + sizeClassName = "h-8 w-8"; + } else if (size === SizeProp.ExtraLarge) { + sizeClassName = "h-10 w-10"; + } else if (size === SizeProp.ExtraSmall) { + sizeClassName = "h-1 w-1"; + } else if (size === SizeProp.Small) { + sizeClassName = "h-3 w-3"; + } else if (size === SizeProp.Smaller) { + sizeClassName = "h-2 w-2"; + } else if (size === SizeProp.Five) { + sizeClassName = "h-5 w-5"; + } else if (size === SizeProp.Regular) { + sizeClassName = "h4 w4"; } + } - let strokeWidth: string = ''; + let strokeWidth: string = ""; - if (thick) { - if (thick === ThickProp.LessThick) { - strokeWidth = 'stroke-0'; - } else if (thick === ThickProp.Thick) { - strokeWidth = 'stroke-2'; - } else if (thick === ThickProp.Normal) { - strokeWidth = 'stroke-1'; - } + if (thick) { + if (thick === ThickProp.LessThick) { + strokeWidth = "stroke-0"; + } else if (thick === ThickProp.Thick) { + strokeWidth = "stroke-2"; + } else if (thick === ThickProp.Normal) { + strokeWidth = "stroke-1"; } + } - let textColor: string = ''; + let textColor: string = ""; - if (type === IconType.Info) { - textColor = 'text-indigo-600'; - } else if (type === IconType.Warning) { - textColor = 'text-yellow-600'; - } else if (type === IconType.Success) { - textColor = 'text-green-600'; - } else if (type === IconType.Danger) { - textColor = 'text-red-600'; - } + if (type === IconType.Info) { + textColor = "text-indigo-600"; + } else if (type === IconType.Warning) { + textColor = "text-yellow-600"; + } else if (type === IconType.Success) { + textColor = "text-green-600"; + } else if (type === IconType.Danger) { + textColor = "text-red-600"; + } - type GetSvgWrapperFunction = ( - children: ReactElement | Array<ReactElement> - ) => ReactElement; + type GetSvgWrapperFunction = ( + children: ReactElement | Array<ReactElement>, + ) => ReactElement; - const getSvgWrapper: GetSvgWrapperFunction = ( - children: ReactElement | Array<ReactElement> - ): ReactElement => { - return ( - <div role="icon"> - <svg - onClick={() => { - onClick && onClick(); - }} - className={`${textColor} ${sizeClassName} ${strokeWidth} ${className}`} - style={ - color - ? { color: color.toString(), ...style } - : { ...style } - } - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth="1.5" - stroke="currentColor" - aria-hidden="true" - data-testid={dataTestId} - > - {children} - </svg> - </div> - ); - }; + const getSvgWrapper: GetSvgWrapperFunction = ( + children: ReactElement | Array<ReactElement>, + ): ReactElement => { + return ( + <div role="icon"> + <svg + onClick={() => { + onClick && onClick(); + }} + className={`${textColor} ${sizeClassName} ${strokeWidth} ${className}`} + style={color ? { color: color.toString(), ...style } : { ...style }} + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + strokeWidth="1.5" + stroke="currentColor" + aria-hidden="true" + data-testid={dataTestId} + > + {children} + </svg> + </div> + ); + }; - if (icon === IconProp.Close) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6 18L18 6M6 6l12 12" - /> - ); - } else if (icon === IconProp.Logs) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z" - /> - ); - } else if (icon === IconProp.CircleClose) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" - /> - ); - } else if (icon === IconProp.Swatch) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M4.098 19.902a3.75 3.75 0 0 0 5.304 0l6.401-6.402M6.75 21A3.75 3.75 0 0 1 3 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 0 0 3.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008Z" - /> - ); - } else if (icon === IconProp.Reload) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" - /> - ); - } else if (icon === IconProp.ArrowUpDown) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5" - /> - ); - } else if (icon === IconProp.Calendar) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" - /> - ); - } else if (icon === IconProp.Stop) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M5.25 7.5A2.25 2.25 0 017.5 5.25h9a2.25 2.25 0 012.25 2.25v9a2.25 2.25 0 01-2.25 2.25h-9a2.25 2.25 0 01-2.25-2.25v-9z" - /> - ); - } else if (icon === IconProp.Copy) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" - /> - ); - } else if (icon === IconProp.Squares) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" - /> - ); - } else if (icon === IconProp.RectangleStack) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6 6.878V6a2.25 2.25 0 012.25-2.25h7.5A2.25 2.25 0 0118 6v.878m-12 0c.235-.083.487-.128.75-.128h10.5c.263 0 .515.045.75.128m-12 0A2.25 2.25 0 004.5 9v.878m13.5-3A2.25 2.25 0 0119.5 9v.878m0 0a2.246 2.246 0 00-.75-.128H5.25c-.263 0-.515.045-.75.128m15 0A2.25 2.25 0 0121 12v6a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18v-6c0-.98.626-1.813 1.5-2.122" - /> - ); - } else if (icon === IconProp.CursorArrowRays) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M15.042 21.672L13.684 16.6m0 0l-2.51 2.225.569-9.47 5.227 7.917-3.286-.672zM12 2.25V4.5m5.834.166l-1.591 1.591M20.25 10.5H18M7.757 14.743l-1.59 1.59M6 10.5H3.75m4.007-4.243l-1.59-1.59" - /> - ); - } else if (icon === IconProp.ChartBar) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" - /> - ); - } else if (icon === IconProp.SquareStack) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6" - /> - ); - } else if (icon === IconProp.Cube) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" - /> - ); - } else if (icon === IconProp.Pencil) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" - /> - ); - } else if (icon === IconProp.Equals) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3.75 9h16.5m-16.5 6.75h16.5" - /> - ); - } else if (icon === IconProp.Flag) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3 3v1.5M3 21v-6m0 0l2.77-.693a9 9 0 016.208.682l.108.054a9 9 0 006.086.71l3.114-.732a48.524 48.524 0 01-.005-10.499l-3.11.732a9 9 0 01-6.085-.711l-.108-.054a9 9 0 00-6.208-.682L3 4.5M3 15V4.5" - /> - ); - } else if (icon === IconProp.Bolt) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" - /> - ); - } else if (icon === IconProp.ListBullet) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" - /> - ); - } else if (icon === IconProp.Template) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5A3.375 3.375 0 006.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0015 2.25h-1.5a2.251 2.251 0 00-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 00-9-9z" - /> - ); - } else if (icon === IconProp.TableCells) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 01-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0112 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 1.5v-1.5m0 0c0-.621.504-1.125 1.125-1.125m0 0h7.5" - /> - ); - } else if (icon === IconProp.MinusSmall) { - return getSvgWrapper( - <path strokeLinecap="round" strokeLinejoin="round" d="M18 12H6" /> - ); - } else if (icon === IconProp.Minus) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M19.5 12h-15" - /> - ); - } else if (icon === IconProp.Database) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" - /> - ); - } else if (icon === IconProp.Book) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25" - /> - ); - } else if (icon === IconProp.Chat) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 20.25c4.97 0 9-3.694 9-8.25s-4.03-8.25-9-8.25S3 7.444 3 12c0 2.104.859 4.023 2.273 5.48.432.447.74 1.04.586 1.641a4.483 4.483 0 01-.923 1.785A5.969 5.969 0 006 21c1.282 0 2.47-.402 3.445-1.087.81.22 1.668.337 2.555.337z" - /> - ); - } else if (icon === IconProp.Condition) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" - /> - ); - } else if (icon === IconProp.JSON) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z" - /> - ); - } else if (icon === IconProp.Variable) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25" - /> - ); - } else if (icon === IconProp.Workflow) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z" - /> - ); - } else if (icon === IconProp.TransparentCube) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25" - /> - ); - } else if (icon === IconProp.Wrench) { - return getSvgWrapper( - <> - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M21.75 6.75a4.5 4.5 0 01-4.884 4.484c-1.076-.091-2.264.071-2.95.904l-7.152 8.684a2.548 2.548 0 11-3.586-3.586l8.684-7.152c.833-.686.995-1.874.904-2.95a4.5 4.5 0 016.336-4.486l-3.276 3.276a3.004 3.004 0 002.25 2.25l3.276-3.276c.256.565.398 1.192.398 1.852z" - /> - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M4.867 19.125h.008v.008h-.008v-.008z" - /> - </> - ); - } else if (icon === IconProp.ArrowCircleDown) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M9 12.75l3 3m0 0l3-3m-3 3v-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.ArrowCircleUp) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M15 11.25l-3-3m0 0l-3 3m3-3v7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.ArrowCircleLeft) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M11.25 9l-3 3m0 0l3 3m-3-3h7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.AddImage) { - return getSvgWrapper( - <path - d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - ></path> - ); - } else if (icon === IconProp.Alert) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 10.5v3.75m-9.303 3.376C1.83 19.126 2.914 21 4.645 21h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 4.88c-.866-1.501-3.032-1.501-3.898 0L2.697 17.626zM12 17.25h.007v.008H12v-.008z" - /> - ); - } else if (icon === IconProp.Notification) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" - /> - ); - } else if (icon === IconProp.Play) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" - /> - ); - } else if (icon === IconProp.Signal) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M9.348 14.651a3.75 3.75 0 010-5.303m5.304 0a3.75 3.75 0 010 5.303m-7.425 2.122a6.75 6.75 0 010-9.546m9.546 0a6.75 6.75 0 010 9.546M5.106 18.894c-3.808-3.808-3.808-9.98 0-13.789m13.788 0c3.808 3.808 3.808 9.981 0 13.79M12 12h.008v.007H12V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" - /> - ); - } else if (icon === IconProp.Criteria) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" - /> - ); - } else if (icon === IconProp.AddFolder) { - return getSvgWrapper( - <path - vectorEffect="non-scaling-stroke" - strokeLinecap="round" - strokeLinejoin="round" - strokeWidth="2" - d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" - /> - ); - } else if (icon === IconProp.Help) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" - /> - ); - } else if (icon === IconProp.Email) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" - /> - ); - } else if (icon === IconProp.Search) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" - /> - ); - } else if (icon === IconProp.User) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" - /> - ); - } else if (icon === IconProp.UpDownArrow) { - return getSvgWrapper( - <path - fillRule="evenodd" - d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" - clipRule="evenodd" - /> - ); - } else if (icon === IconProp.ErrorSolid) { - return getSvgWrapper( - <path - fillRule="evenodd" - d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" - clipRule="evenodd" - /> - ); - } else if (icon === IconProp.Spinner) { - return getSvgWrapper( - <> - <circle - className="opacity-25" - cx="12" - cy="12" - r="10" - stroke="currentColor" - strokeWidth="4" - ></circle> - <path - className="opacity-75" - fill="currentColor" - d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" - ></path> - </> - ); - } else if (icon === IconProp.Home) { - return getSvgWrapper( - <path - fillRule="evenodd" - d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z" - clipRule="evenodd" - /> - ); - } else if (icon === IconProp.ChevronRight) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M8.25 4.5l7.5 7.5-7.5 7.5" - /> - ); - } else if (icon === IconProp.Clock) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.Settings) { - return getSvgWrapper( - <> - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" - /> - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" - /> - </> - ); - } else if (icon === IconProp.AltGlobe) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" - /> - ); - } else if (icon === IconProp.BarsArrowDown) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0l-3.75-3.75M17.25 21L21 17.25" - /> - ); - } else if (icon === IconProp.Bell) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" - /> - ); - } else if (icon === IconProp.AdjustmentHorizontal) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" - /> - ); - } else if (icon === IconProp.AdjustmentVertical) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6 13.5V3.75m0 9.75a1.5 1.5 0 010 3m0-3a1.5 1.5 0 000 3m0 3.75V16.5m12-3V3.75m0 9.75a1.5 1.5 0 010 3m0-3a1.5 1.5 0 000 3m0 3.75V16.5m-6-9V3.75m0 3.75a1.5 1.5 0 010 3m0-3a1.5 1.5 0 000 3m0 9.75V10.5" - /> - ); - } else if (icon === IconProp.BellRinging) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0M3.124 7.5A8.969 8.969 0 015.292 3m13.416 0a8.969 8.969 0 012.168 4.5" - /> - ); - } else if (icon === IconProp.BarsArrowUp) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0l-3.75-3.75M17.25 21L21 17.25" - /> - ); - } else if (icon === IconProp.CheckCircle) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.Refresh) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" - /> - ); - } else if (icon === IconProp.Filter) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" - /> - ); - } else if (icon === IconProp.Add) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 4.5v15m7.5-7.5h-15" - /> - ); - } else if (icon === IconProp.Check) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M4.5 12.75l6 6 9-13.5" - /> - ); - } else if (icon === IconProp.Folder) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" - /> - ); - } else if (icon === IconProp.Label) { - return getSvgWrapper( - <> - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z" - /> - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6 6h.008v.008H6V6z" - /> - </> - ); - } else if (icon === IconProp.ArrowCircleRight) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12.75 15l3-3m0 0l-3-3m3 3h-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.Team) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" - /> - ); - } else if (icon === IconProp.Globe) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6.115 5.19l.319 1.913A6 6 0 008.11 10.36L9.75 12l-.387.775c-.217.433-.132.956.21 1.298l1.348 1.348c.21.21.329.497.329.795v1.089c0 .426.24.815.622 1.006l.153.076c.433.217.956.132 1.298-.21l.723-.723a8.7 8.7 0 002.288-4.042 1.087 1.087 0 00-.358-1.099l-1.33-1.108c-.251-.21-.582-.299-.905-.245l-1.17.195a1.125 1.125 0 01-.98-.314l-.295-.295a1.125 1.125 0 010-1.591l.13-.132a1.125 1.125 0 011.3-.21l.603.302a.809.809 0 001.086-1.086L14.25 7.5l1.256-.837a4.5 4.5 0 001.528-1.732l.146-.292M6.115 5.19A9 9 0 1017.18 4.64M6.115 5.19A8.965 8.965 0 0112 3c1.929 0 3.716.607 5.18 1.64" - /> - ); - } else if (icon === IconProp.Terminal) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z" - /> - ); - } else if (icon === IconProp.Error) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" - /> - ); - } else if (icon === IconProp.List) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" - /> - ); - } else if (icon === IconProp.ChevronDown) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M19.5 8.25l-7.5 7.5-7.5-7.5" - /> - ); - } else if (icon === IconProp.ChevronLeft) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" - /> - ); - } else if (icon === IconProp.Window) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3 8.25V18a2.25 2.25 0 002.25 2.25h13.5A2.25 2.25 0 0021 18V8.25m-18 0V6a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 6v2.25m-18 0h18M5.25 6h.008v.008H5.25V6zM7.5 6h.008v.008H7.5V6zm2.25 0h.008v.008H9.75V6z" - /> - ); - } else if (icon === IconProp.Trash) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" - /> - ); - } else if (icon === IconProp.ChevronUp) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M4.5 15.75l7.5-7.5 7.5 7.5" - /> - ); - } else if (icon === IconProp.Logout) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M5.636 5.636a9 9 0 1012.728 0M12 3v9" - /> - ); - } else if (icon === IconProp.Report) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" - /> - ); - } else if (icon === IconProp.Lock) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" - /> - ); - } else if (icon === IconProp.Time) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.Call) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z" - /> - ); - } else if (icon === IconProp.More) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.Integrations) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 002.25-2.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v2.25A2.25 2.25 0 006 10.5zm0 9.75h2.25A2.25 2.25 0 0010.5 18v-2.25a2.25 2.25 0 00-2.25-2.25H6a2.25 2.25 0 00-2.25 2.25V18A2.25 2.25 0 006 20.25zm9.75-9.75H18a2.25 2.25 0 002.25-2.25V6A2.25 2.25 0 0018 3.75h-2.25A2.25 2.25 0 0013.5 6v2.25a2.25 2.25 0 002.25 2.25z" - /> - ); - } else if (icon === IconProp.Activity) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 01-.825-.242m9.345-8.334a2.126 2.126 0 00-.476-.095 48.64 48.64 0 00-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0011.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" - /> - ); - } else if (icon === IconProp.Billing) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z" - /> - ); - } else if (icon === IconProp.Code) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" - /> - ); - } else if (icon === IconProp.Key) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" - /> - ); - } else if (icon === IconProp.Success) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.Info) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" - /> - ); - } else if (icon === IconProp.SMS) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" - /> - ); - } else if (icon === IconProp.Hide) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" - /> - ); - } else if (icon === IconProp.Edit) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" - /> - ); - } else if (icon === IconProp.True) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M4.5 12.75l6 6 9-13.5" - /> - ); - } else if (icon === IconProp.False) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6 18L18 6M6 6l12 12" - /> - ); - } else if (icon === IconProp.Star) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" - /> - ); - } else if (icon === IconProp.Invoice) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" - /> - ); - } else if (icon === IconProp.Circle) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.Public) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" - /> - ); - } else if (icon === IconProp.Image) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" - /> - ); - } else if (icon === IconProp.TextFile) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" - /> - ); - } else if (icon === IconProp.SendMessage) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" - /> - ); - } else if (icon === IconProp.RSS) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12.75 19.5v-.75a7.5 7.5 0 00-7.5-7.5H4.5m0-6.75h.75c7.87 0 14.25 6.38 14.25 14.25v.75M6 18.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" - /> - ); - } else if (icon === IconProp.Slack) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 01-.825-.242m9.345-8.334a2.126 2.126 0 00-.476-.095 48.64 48.64 0 00-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0011.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" - /> - ); - } else if (icon === IconProp.Drag) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 12.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 18.75a.75.75 0 110-1.5.75.75 0 010 1.5z" - /> - ); - } else if (icon === IconProp.Graph) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" - /> - ); - } else if (icon === IconProp.Link) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" - /> - ); - } else if (icon === IconProp.ExternalLink) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" - /> - ); - } else if (icon === IconProp.Download) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" - /> - ); - } else if (icon === IconProp.Layers) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" - /> - ); - } else if (icon === IconProp.Layout) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z" - /> - ); - } else if (icon === IconProp.Upgrade) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z" - /> - ); - } else if (icon === IconProp.Automation) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 002.25-2.25V6.75a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 6.75v10.5a2.25 2.25 0 002.25 2.25zm.75-12h9v9h-9v-9z" - /> - ); - } else if (icon === IconProp.Webhook) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" - /> - ); - } else if (icon === IconProp.Announcement) { - return getSvgWrapper( - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46" - /> - ); - } + if (icon === IconProp.Close) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6 18L18 6M6 6l12 12" + />, + ); + } else if (icon === IconProp.Logs) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z" + />, + ); + } else if (icon === IconProp.CircleClose) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" + />, + ); + } else if (icon === IconProp.Swatch) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M4.098 19.902a3.75 3.75 0 0 0 5.304 0l6.401-6.402M6.75 21A3.75 3.75 0 0 1 3 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 0 0 3.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008Z" + />, + ); + } else if (icon === IconProp.Reload) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" + />, + ); + } else if (icon === IconProp.ArrowUpDown) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5" + />, + ); + } else if (icon === IconProp.Calendar) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" + />, + ); + } else if (icon === IconProp.Stop) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M5.25 7.5A2.25 2.25 0 017.5 5.25h9a2.25 2.25 0 012.25 2.25v9a2.25 2.25 0 01-2.25 2.25h-9a2.25 2.25 0 01-2.25-2.25v-9z" + />, + ); + } else if (icon === IconProp.Copy) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" + />, + ); + } else if (icon === IconProp.Squares) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" + />, + ); + } else if (icon === IconProp.RectangleStack) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6 6.878V6a2.25 2.25 0 012.25-2.25h7.5A2.25 2.25 0 0118 6v.878m-12 0c.235-.083.487-.128.75-.128h10.5c.263 0 .515.045.75.128m-12 0A2.25 2.25 0 004.5 9v.878m13.5-3A2.25 2.25 0 0119.5 9v.878m0 0a2.246 2.246 0 00-.75-.128H5.25c-.263 0-.515.045-.75.128m15 0A2.25 2.25 0 0121 12v6a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18v-6c0-.98.626-1.813 1.5-2.122" + />, + ); + } else if (icon === IconProp.CursorArrowRays) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M15.042 21.672L13.684 16.6m0 0l-2.51 2.225.569-9.47 5.227 7.917-3.286-.672zM12 2.25V4.5m5.834.166l-1.591 1.591M20.25 10.5H18M7.757 14.743l-1.59 1.59M6 10.5H3.75m4.007-4.243l-1.59-1.59" + />, + ); + } else if (icon === IconProp.ChartBar) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" + />, + ); + } else if (icon === IconProp.SquareStack) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6" + />, + ); + } else if (icon === IconProp.Cube) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" + />, + ); + } else if (icon === IconProp.Pencil) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" + />, + ); + } else if (icon === IconProp.Equals) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3.75 9h16.5m-16.5 6.75h16.5" + />, + ); + } else if (icon === IconProp.Flag) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3 3v1.5M3 21v-6m0 0l2.77-.693a9 9 0 016.208.682l.108.054a9 9 0 006.086.71l3.114-.732a48.524 48.524 0 01-.005-10.499l-3.11.732a9 9 0 01-6.085-.711l-.108-.054a9 9 0 00-6.208-.682L3 4.5M3 15V4.5" + />, + ); + } else if (icon === IconProp.Bolt) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" + />, + ); + } else if (icon === IconProp.ListBullet) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" + />, + ); + } else if (icon === IconProp.Template) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5A3.375 3.375 0 006.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0015 2.25h-1.5a2.251 2.251 0 00-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 00-9-9z" + />, + ); + } else if (icon === IconProp.TableCells) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 01-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0112 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 1.5v-1.5m0 0c0-.621.504-1.125 1.125-1.125m0 0h7.5" + />, + ); + } else if (icon === IconProp.MinusSmall) { + return getSvgWrapper( + <path strokeLinecap="round" strokeLinejoin="round" d="M18 12H6" />, + ); + } else if (icon === IconProp.Minus) { + return getSvgWrapper( + <path strokeLinecap="round" strokeLinejoin="round" d="M19.5 12h-15" />, + ); + } else if (icon === IconProp.Database) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" + />, + ); + } else if (icon === IconProp.Book) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25" + />, + ); + } else if (icon === IconProp.Chat) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 20.25c4.97 0 9-3.694 9-8.25s-4.03-8.25-9-8.25S3 7.444 3 12c0 2.104.859 4.023 2.273 5.48.432.447.74 1.04.586 1.641a4.483 4.483 0 01-.923 1.785A5.969 5.969 0 006 21c1.282 0 2.47-.402 3.445-1.087.81.22 1.668.337 2.555.337z" + />, + ); + } else if (icon === IconProp.Condition) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" + />, + ); + } else if (icon === IconProp.JSON) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M14.25 9.75L16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0020.25 18V6A2.25 2.25 0 0018 3.75H6A2.25 2.25 0 003.75 6v12A2.25 2.25 0 006 20.25z" + />, + ); + } else if (icon === IconProp.Variable) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25" + />, + ); + } else if (icon === IconProp.Workflow) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z" + />, + ); + } else if (icon === IconProp.TransparentCube) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25" + />, + ); + } else if (icon === IconProp.Wrench) { + return getSvgWrapper( + <> + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M21.75 6.75a4.5 4.5 0 01-4.884 4.484c-1.076-.091-2.264.071-2.95.904l-7.152 8.684a2.548 2.548 0 11-3.586-3.586l8.684-7.152c.833-.686.995-1.874.904-2.95a4.5 4.5 0 016.336-4.486l-3.276 3.276a3.004 3.004 0 002.25 2.25l3.276-3.276c.256.565.398 1.192.398 1.852z" + /> + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M4.867 19.125h.008v.008h-.008v-.008z" + /> + </>, + ); + } else if (icon === IconProp.ArrowCircleDown) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M9 12.75l3 3m0 0l3-3m-3 3v-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.ArrowCircleUp) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M15 11.25l-3-3m0 0l-3 3m3-3v7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.ArrowCircleLeft) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M11.25 9l-3 3m0 0l3 3m-3-3h7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.AddImage) { + return getSvgWrapper( + <path + d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + ></path>, + ); + } else if (icon === IconProp.Alert) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 10.5v3.75m-9.303 3.376C1.83 19.126 2.914 21 4.645 21h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 4.88c-.866-1.501-3.032-1.501-3.898 0L2.697 17.626zM12 17.25h.007v.008H12v-.008z" + />, + ); + } else if (icon === IconProp.Notification) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" + />, + ); + } else if (icon === IconProp.Play) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" + />, + ); + } else if (icon === IconProp.Signal) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M9.348 14.651a3.75 3.75 0 010-5.303m5.304 0a3.75 3.75 0 010 5.303m-7.425 2.122a6.75 6.75 0 010-9.546m9.546 0a6.75 6.75 0 010 9.546M5.106 18.894c-3.808-3.808-3.808-9.98 0-13.789m13.788 0c3.808 3.808 3.808 9.981 0 13.79M12 12h.008v.007H12V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" + />, + ); + } else if (icon === IconProp.Criteria) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" + />, + ); + } else if (icon === IconProp.AddFolder) { + return getSvgWrapper( + <path + vectorEffect="non-scaling-stroke" + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth="2" + d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" + />, + ); + } else if (icon === IconProp.Help) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" + />, + ); + } else if (icon === IconProp.Email) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" + />, + ); + } else if (icon === IconProp.Search) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" + />, + ); + } else if (icon === IconProp.User) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" + />, + ); + } else if (icon === IconProp.UpDownArrow) { + return getSvgWrapper( + <path + fillRule="evenodd" + d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" + clipRule="evenodd" + />, + ); + } else if (icon === IconProp.ErrorSolid) { + return getSvgWrapper( + <path + fillRule="evenodd" + d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" + clipRule="evenodd" + />, + ); + } else if (icon === IconProp.Spinner) { + return getSvgWrapper( + <> + <circle + className="opacity-25" + cx="12" + cy="12" + r="10" + stroke="currentColor" + strokeWidth="4" + ></circle> + <path + className="opacity-75" + fill="currentColor" + d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" + ></path> + </>, + ); + } else if (icon === IconProp.Home) { + return getSvgWrapper( + <path + fillRule="evenodd" + d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z" + clipRule="evenodd" + />, + ); + } else if (icon === IconProp.ChevronRight) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M8.25 4.5l7.5 7.5-7.5 7.5" + />, + ); + } else if (icon === IconProp.Clock) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.Settings) { + return getSvgWrapper( + <> + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" + /> + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" + /> + </>, + ); + } else if (icon === IconProp.AltGlobe) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" + />, + ); + } else if (icon === IconProp.BarsArrowDown) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0l-3.75-3.75M17.25 21L21 17.25" + />, + ); + } else if (icon === IconProp.Bell) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" + />, + ); + } else if (icon === IconProp.AdjustmentHorizontal) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" + />, + ); + } else if (icon === IconProp.AdjustmentVertical) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6 13.5V3.75m0 9.75a1.5 1.5 0 010 3m0-3a1.5 1.5 0 000 3m0 3.75V16.5m12-3V3.75m0 9.75a1.5 1.5 0 010 3m0-3a1.5 1.5 0 000 3m0 3.75V16.5m-6-9V3.75m0 3.75a1.5 1.5 0 010 3m0-3a1.5 1.5 0 000 3m0 9.75V10.5" + />, + ); + } else if (icon === IconProp.BellRinging) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0M3.124 7.5A8.969 8.969 0 015.292 3m13.416 0a8.969 8.969 0 012.168 4.5" + />, + ); + } else if (icon === IconProp.BarsArrowUp) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0l-3.75-3.75M17.25 21L21 17.25" + />, + ); + } else if (icon === IconProp.CheckCircle) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.Refresh) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" + />, + ); + } else if (icon === IconProp.Filter) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" + />, + ); + } else if (icon === IconProp.Add) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 4.5v15m7.5-7.5h-15" + />, + ); + } else if (icon === IconProp.Check) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M4.5 12.75l6 6 9-13.5" + />, + ); + } else if (icon === IconProp.Folder) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" + />, + ); + } else if (icon === IconProp.Label) { + return getSvgWrapper( + <> + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z" + /> + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6 6h.008v.008H6V6z" + /> + </>, + ); + } else if (icon === IconProp.ArrowCircleRight) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12.75 15l3-3m0 0l-3-3m3 3h-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.Team) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" + />, + ); + } else if (icon === IconProp.Globe) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6.115 5.19l.319 1.913A6 6 0 008.11 10.36L9.75 12l-.387.775c-.217.433-.132.956.21 1.298l1.348 1.348c.21.21.329.497.329.795v1.089c0 .426.24.815.622 1.006l.153.076c.433.217.956.132 1.298-.21l.723-.723a8.7 8.7 0 002.288-4.042 1.087 1.087 0 00-.358-1.099l-1.33-1.108c-.251-.21-.582-.299-.905-.245l-1.17.195a1.125 1.125 0 01-.98-.314l-.295-.295a1.125 1.125 0 010-1.591l.13-.132a1.125 1.125 0 011.3-.21l.603.302a.809.809 0 001.086-1.086L14.25 7.5l1.256-.837a4.5 4.5 0 001.528-1.732l.146-.292M6.115 5.19A9 9 0 1017.18 4.64M6.115 5.19A8.965 8.965 0 0112 3c1.929 0 3.716.607 5.18 1.64" + />, + ); + } else if (icon === IconProp.Terminal) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z" + />, + ); + } else if (icon === IconProp.Error) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" + />, + ); + } else if (icon === IconProp.List) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" + />, + ); + } else if (icon === IconProp.ChevronDown) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M19.5 8.25l-7.5 7.5-7.5-7.5" + />, + ); + } else if (icon === IconProp.ChevronLeft) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" + />, + ); + } else if (icon === IconProp.Window) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3 8.25V18a2.25 2.25 0 002.25 2.25h13.5A2.25 2.25 0 0021 18V8.25m-18 0V6a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 6v2.25m-18 0h18M5.25 6h.008v.008H5.25V6zM7.5 6h.008v.008H7.5V6zm2.25 0h.008v.008H9.75V6z" + />, + ); + } else if (icon === IconProp.Trash) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" + />, + ); + } else if (icon === IconProp.ChevronUp) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M4.5 15.75l7.5-7.5 7.5 7.5" + />, + ); + } else if (icon === IconProp.Logout) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M5.636 5.636a9 9 0 1012.728 0M12 3v9" + />, + ); + } else if (icon === IconProp.Report) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" + />, + ); + } else if (icon === IconProp.Lock) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" + />, + ); + } else if (icon === IconProp.Time) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.Call) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z" + />, + ); + } else if (icon === IconProp.More) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.Integrations) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 002.25-2.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v2.25A2.25 2.25 0 006 10.5zm0 9.75h2.25A2.25 2.25 0 0010.5 18v-2.25a2.25 2.25 0 00-2.25-2.25H6a2.25 2.25 0 00-2.25 2.25V18A2.25 2.25 0 006 20.25zm9.75-9.75H18a2.25 2.25 0 002.25-2.25V6A2.25 2.25 0 0018 3.75h-2.25A2.25 2.25 0 0013.5 6v2.25a2.25 2.25 0 002.25 2.25z" + />, + ); + } else if (icon === IconProp.Activity) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 01-.825-.242m9.345-8.334a2.126 2.126 0 00-.476-.095 48.64 48.64 0 00-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0011.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" + />, + ); + } else if (icon === IconProp.Billing) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z" + />, + ); + } else if (icon === IconProp.Code) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" + />, + ); + } else if (icon === IconProp.Key) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" + />, + ); + } else if (icon === IconProp.Success) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.Info) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" + />, + ); + } else if (icon === IconProp.SMS) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" + />, + ); + } else if (icon === IconProp.Hide) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" + />, + ); + } else if (icon === IconProp.Edit) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" + />, + ); + } else if (icon === IconProp.True) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M4.5 12.75l6 6 9-13.5" + />, + ); + } else if (icon === IconProp.False) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6 18L18 6M6 6l12 12" + />, + ); + } else if (icon === IconProp.Star) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" + />, + ); + } else if (icon === IconProp.Invoice) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" + />, + ); + } else if (icon === IconProp.Circle) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.Public) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" + />, + ); + } else if (icon === IconProp.Image) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" + />, + ); + } else if (icon === IconProp.TextFile) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" + />, + ); + } else if (icon === IconProp.SendMessage) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" + />, + ); + } else if (icon === IconProp.RSS) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12.75 19.5v-.75a7.5 7.5 0 00-7.5-7.5H4.5m0-6.75h.75c7.87 0 14.25 6.38 14.25 14.25v.75M6 18.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" + />, + ); + } else if (icon === IconProp.Slack) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 01-.825-.242m9.345-8.334a2.126 2.126 0 00-.476-.095 48.64 48.64 0 00-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0011.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" + />, + ); + } else if (icon === IconProp.Drag) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 12.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 18.75a.75.75 0 110-1.5.75.75 0 010 1.5z" + />, + ); + } else if (icon === IconProp.Graph) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" + />, + ); + } else if (icon === IconProp.Link) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" + />, + ); + } else if (icon === IconProp.ExternalLink) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" + />, + ); + } else if (icon === IconProp.Download) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" + />, + ); + } else if (icon === IconProp.Layers) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" + />, + ); + } else if (icon === IconProp.Layout) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z" + />, + ); + } else if (icon === IconProp.Upgrade) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z" + />, + ); + } else if (icon === IconProp.Automation) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 002.25-2.25V6.75a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 6.75v10.5a2.25 2.25 0 002.25 2.25zm.75-12h9v9h-9v-9z" + />, + ); + } else if (icon === IconProp.Webhook) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" + />, + ); + } else if (icon === IconProp.Announcement) { + return getSvgWrapper( + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46" + />, + ); + } - return <></>; + return <></>; }; export default Icon; diff --git a/CommonUI/src/Components/Image/Image.tsx b/CommonUI/src/Components/Image/Image.tsx index 3bfce16e2c..c2eb0bf360 100644 --- a/CommonUI/src/Components/Image/Image.tsx +++ b/CommonUI/src/Components/Image/Image.tsx @@ -1,65 +1,65 @@ // Taiwind -import Route from 'Common/Types/API/Route'; -import URLFromProject from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import File from 'Model/Models/File'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Route from "Common/Types/API/Route"; +import URLFromProject from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import File from "Model/Models/File"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onClick?: () => void | undefined; - imageUrl?: URLFromProject | Route | ReactElement | undefined; - height?: number | undefined; - file?: File | undefined; - className?: string | undefined; - alt?: string | undefined; - style?: React.CSSProperties | undefined; - 'data-testid'?: string; + onClick?: () => void | undefined; + imageUrl?: URLFromProject | Route | ReactElement | undefined; + height?: number | undefined; + file?: File | undefined; + className?: string | undefined; + alt?: string | undefined; + style?: React.CSSProperties | undefined; + "data-testid"?: string; } export class ImageFunctions { - public static getImageURL(file: File): string { - const blob: Blob = new Blob([file.file as Uint8Array], { - type: (file as File).type as string, - }); + public static getImageURL(file: File): string { + const blob: Blob = new Blob([file.file as Uint8Array], { + type: (file as File).type as string, + }); - const url: string = URL.createObjectURL(blob); - return url; - } + const url: string = URL.createObjectURL(blob); + return url; + } } const Image: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - type GetImageElementFunction = (url: string) => ReactElement; + type GetImageElementFunction = (url: string) => ReactElement; - const getImageElement: GetImageElementFunction = ( - url: string - ): ReactElement => { - return ( - <img - onClick={() => { - props.onClick && props.onClick(); - }} - data-testid={props['data-testid']} - alt={props.alt} - src={url} - height={props.height} - className={props.className} - style={props.style} - /> - ); - }; + const getImageElement: GetImageElementFunction = ( + url: string, + ): ReactElement => { + return ( + <img + onClick={() => { + props.onClick && props.onClick(); + }} + data-testid={props["data-testid"]} + alt={props.alt} + src={url} + height={props.height} + className={props.className} + style={props.style} + /> + ); + }; - if (props.imageUrl) { - return getImageElement(props.imageUrl.toString()); - } + if (props.imageUrl) { + return getImageElement(props.imageUrl.toString()); + } - if (props.file && props.file.file && props.file.type) { - const url: string = ImageFunctions.getImageURL(props.file); - return getImageElement(url); - } + if (props.file && props.file.file && props.file.type) { + const url: string = ImageFunctions.getImageURL(props.file); + return getImageElement(url); + } - throw new BadDataException('file or imageUrl required for <Image>'); + throw new BadDataException("file or imageUrl required for <Image>"); }; export default Image; diff --git a/CommonUI/src/Components/ImageTiles/ImageTiles.tsx b/CommonUI/src/Components/ImageTiles/ImageTiles.tsx index 2fbebea1f5..8885b7df8d 100644 --- a/CommonUI/src/Components/ImageTiles/ImageTiles.tsx +++ b/CommonUI/src/Components/ImageTiles/ImageTiles.tsx @@ -1,61 +1,58 @@ -import Navigation from '../../Utils/Navigation'; -import FieldLabelElement from '../Detail/FieldLabel'; -import Image from '../Image/Image'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Navigation from "../../Utils/Navigation"; +import FieldLabelElement from "../Detail/FieldLabel"; +import Image from "../Image/Image"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ImageTile { - image: ReactElement; - navigateToUrl: URL | Route; - title: string; + image: ReactElement; + navigateToUrl: URL | Route; + title: string; } export interface ComponentProps { - tiles: Array<ImageTile>; - title: string; - description: string; + tiles: Array<ImageTile>; + title: string; + description: string; } const ImageTilesElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div> - <div> - <FieldLabelElement - title={props.title} - description={props.description} - /> + return ( + <div> + <div> + <FieldLabelElement + title={props.title} + description={props.description} + /> + </div> + <div className="flex mt-5 mb-5 space-x-5"> + {/** Generate a squares in a grid in tailwind. One square for each tile */} + {props.tiles.map((tile: ImageTile, i: number) => { + return ( + <div + key={i} + className="p-3 cursor-pointer pb-5" + onClick={() => { + Navigation.navigate(tile.navigateToUrl, { + openInNewTab: true, + }); + }} + > + <div> + <Image className="h-20 w-20" imageUrl={tile.image} /> + </div> + <div className="text-sm text-gray-400 w-full text-center mt-2"> + {tile.title} + </div> </div> - <div className="flex mt-5 mb-5 space-x-5"> - {/** Generate a squares in a grid in tailwind. One square for each tile */} - {props.tiles.map((tile: ImageTile, i: number) => { - return ( - <div - key={i} - className="p-3 cursor-pointer pb-5" - onClick={() => { - Navigation.navigate(tile.navigateToUrl, { - openInNewTab: true, - }); - }} - > - <div> - <Image - className="h-20 w-20" - imageUrl={tile.image} - /> - </div> - <div className="text-sm text-gray-400 w-full text-center mt-2"> - {tile.title} - </div> - </div> - ); - })} - </div> - </div> - ); + ); + })} + </div> + </div> + ); }; export default ImageTilesElement; diff --git a/CommonUI/src/Components/InfoCard/InfoCard.tsx b/CommonUI/src/Components/InfoCard/InfoCard.tsx index b6b93ac578..569d07fddd 100644 --- a/CommonUI/src/Components/InfoCard/InfoCard.tsx +++ b/CommonUI/src/Components/InfoCard/InfoCard.tsx @@ -1,28 +1,26 @@ -import FieldLabelElement from '../Detail/FieldLabel'; -import React, { FunctionComponent, ReactElement } from 'react'; +import FieldLabelElement from "../Detail/FieldLabel"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; - value: string | ReactElement; - className?: string; - textClassName?: string; + title: string; + value: string | ReactElement; + className?: string; + textClassName?: string; } const InfoCard: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div - className={`rounded-md bg-white shadow-md p-5 ${ - props.className || '' - }`} - > - <div> - <FieldLabelElement title={props.title} /> - </div> - <div className={props.textClassName || ''}>{props.value}</div> - </div> - ); + return ( + <div + className={`rounded-md bg-white shadow-md p-5 ${props.className || ""}`} + > + <div> + <FieldLabelElement title={props.title} /> + </div> + <div className={props.textClassName || ""}>{props.value}</div> + </div> + ); }; export default InfoCard; diff --git a/CommonUI/src/Components/InlineCode/InlineCode.tsx b/CommonUI/src/Components/InlineCode/InlineCode.tsx index 7a847bca69..52db21f4da 100644 --- a/CommonUI/src/Components/InlineCode/InlineCode.tsx +++ b/CommonUI/src/Components/InlineCode/InlineCode.tsx @@ -1,17 +1,17 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - text: string | ReactElement; + text: string | ReactElement; } const InlineCode: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <span className="font-medium text-xs text-slate-200 bg-slate-700 p-1 pl-2 pr-2 rounded"> - {props.text} - </span> - ); + return ( + <span className="font-medium text-xs text-slate-200 bg-slate-700 p-1 pl-2 pr-2 rounded"> + {props.text} + </span> + ); }; export default InlineCode; diff --git a/CommonUI/src/Components/Input/Input.tsx b/CommonUI/src/Components/Input/Input.tsx index 4616df84c9..e5a5e50877 100644 --- a/CommonUI/src/Components/Input/Input.tsx +++ b/CommonUI/src/Components/Input/Input.tsx @@ -1,245 +1,230 @@ // Tailwind -import { Logger } from '../../Utils/Logger'; -import Icon from '../Icon/Icon'; -import OneUptimeDate from 'Common/Types/Date'; -import IconProp from 'Common/Types/Icon/IconProp'; +import { Logger } from "../../Utils/Logger"; +import Icon from "../Icon/Icon"; +import OneUptimeDate from "Common/Types/Date"; +import IconProp from "Common/Types/Icon/IconProp"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useRef, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useRef, + useState, +} from "react"; export enum InputType { - TEXT = 'text', - NUMBER = 'number', - DATE = 'date', - DATETIME_LOCAL = 'datetime-local', - URL = 'url', - TIME = 'time', + TEXT = "text", + NUMBER = "number", + DATE = "date", + DATETIME_LOCAL = "datetime-local", + URL = "url", + TIME = "time", } export interface ComponentProps { - initialValue?: undefined | string | Date; - onClick?: undefined | (() => void); - placeholder?: undefined | string; - className?: undefined | string; - onChange?: undefined | ((value: string) => void); - value?: string | Date | undefined; - readOnly?: boolean | undefined; - disabled?: boolean | undefined; - type?: InputType; - onFocus?: (() => void) | undefined; - onBlur?: (() => void) | undefined; - dataTestId?: string | undefined; - tabIndex?: number | undefined; - onEnterPress?: (() => void) | undefined; - error?: string | undefined; - outerDivClassName?: string | undefined; - autoFocus?: boolean | undefined; + initialValue?: undefined | string | Date; + onClick?: undefined | (() => void); + placeholder?: undefined | string; + className?: undefined | string; + onChange?: undefined | ((value: string) => void); + value?: string | Date | undefined; + readOnly?: boolean | undefined; + disabled?: boolean | undefined; + type?: InputType; + onFocus?: (() => void) | undefined; + onBlur?: (() => void) | undefined; + dataTestId?: string | undefined; + tabIndex?: number | undefined; + onEnterPress?: (() => void) | undefined; + error?: string | undefined; + outerDivClassName?: string | undefined; + autoFocus?: boolean | undefined; } const Input: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let className: string = ''; + let className: string = ""; - if (!props.className) { - className = - 'block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm'; + if (!props.className) { + className = + "block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"; + } else { + className = props.className; + } + + if (props.error) { + className += + " border-red-300 pr-10 text-red-900 placeholder-red-300 focus:border-red-500 focus:outline-none focus:ring-red-500"; + } + + if (props.disabled) { + className += " bg-gray-100 text-gray-500 cursor-not-allowed"; + } + + const [value, setValue] = useState<string | Date>(""); + const [displayValue, setDisplayValue] = useState<string>(""); + const ref: any = useRef<any>(null); + + useEffect(() => { + if ( + props.type === InputType.DATE || + props.type === InputType.DATETIME_LOCAL || + props.type === InputType.TIME + ) { + if (value && (value as unknown) instanceof Date) { + let dateString: string = ""; + try { + if (props.type === InputType.DATETIME_LOCAL) { + dateString = OneUptimeDate.toDateTimeLocalString(value as any); + } else if (props.type === InputType.TIME) { + // get time from date + dateString = OneUptimeDate.toTimeString(value as any); + } else { + dateString = OneUptimeDate.asDateForDatabaseQuery(value); + } + } catch (e: any) { + Logger.error(e); + } + setDisplayValue(dateString); + } else if ( + value && + (value as any).includes && + !(value as any).includes(" - ") + ) { + // " - " is for InBetween dates. + const date: Date = OneUptimeDate.fromString(value); + let dateString: string = ""; + try { + if (props.type === InputType.DATETIME_LOCAL) { + dateString = OneUptimeDate.toDateTimeLocalString(date); + } else if (props.type === InputType.TIME) { + // get time from date + dateString = OneUptimeDate.toTimeString(value as any); + } else { + dateString = OneUptimeDate.asDateForDatabaseQuery(date); + } + } catch (err: any) { + Logger.error(err); + } + setDisplayValue(dateString); + } else if ( + !value || + ((value as any).includes && !(value as any).includes(" - ")) + ) { + setDisplayValue(""); + } } else { - className = props.className; + setDisplayValue(value as string); + } + }, [value]); + + useEffect(() => { + const input: any = ref.current; + if (input) { + (input as any).value = displayValue; + } + }, [ref, displayValue]); + + useEffect(() => { + if (props.initialValue) { + setValue(props.initialValue); } - if (props.error) { - className += - ' border-red-300 pr-10 text-red-900 placeholder-red-300 focus:border-red-500 focus:outline-none focus:ring-red-500'; + if (props.value) { + setValue(props.value); } + }, []); - if (props.disabled) { - className += ' bg-gray-100 text-gray-500 cursor-not-allowed'; + useEffect(() => { + if (props.initialValue) { + setValue(props.initialValue); } + }, [props.initialValue]); - const [value, setValue] = useState<string | Date>(''); - const [displayValue, setDisplayValue] = useState<string>(''); - const ref: any = useRef<any>(null); + useEffect(() => { + setValue(props.value ? props.value : props.initialValue || ""); + }, [props.value]); - useEffect(() => { - if ( - props.type === InputType.DATE || - props.type === InputType.DATETIME_LOCAL || - props.type === InputType.TIME - ) { - if (value && (value as unknown) instanceof Date) { - let dateString: string = ''; - try { - if (props.type === InputType.DATETIME_LOCAL) { - dateString = OneUptimeDate.toDateTimeLocalString( - value as any - ); - } else if (props.type === InputType.TIME) { - // get time from date - dateString = OneUptimeDate.toTimeString(value as any); - } else { - dateString = - OneUptimeDate.asDateForDatabaseQuery(value); - } - } catch (e: any) { - Logger.error(e); - } - setDisplayValue(dateString); - } else if ( - value && - (value as any).includes && - !(value as any).includes(' - ') + return ( + <> + <div + className={ + props.outerDivClassName || + `relative mt-2 mb-1 rounded-md shadow-sm w-full` + } + > + <input + autoFocus={props.autoFocus} + ref={ref} + onFocus={props.onFocus} + onClick={props.onClick} + data-testid={props.dataTestId} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + let value: string | Date = e.target.value; + + if ( + (props.type === InputType.DATE || + props.type === InputType.DATETIME_LOCAL || + props.type === InputType.TIME) && + value ) { - // " - " is for InBetween dates. - const date: Date = OneUptimeDate.fromString(value); - let dateString: string = ''; - try { - if (props.type === InputType.DATETIME_LOCAL) { - dateString = OneUptimeDate.toDateTimeLocalString(date); - } else if (props.type === InputType.TIME) { - // get time from date - dateString = OneUptimeDate.toTimeString(value as any); - } else { - dateString = OneUptimeDate.asDateForDatabaseQuery(date); - } - } catch (err: any) { - Logger.error(err); - } - setDisplayValue(dateString); - } else if ( - !value || - ((value as any).includes && !(value as any).includes(' - ')) - ) { - setDisplayValue(''); + if (props.type === InputType.TIME) { + // conver value like "16:00" to date with local timezone + value = OneUptimeDate.getDateWithCustomTime({ + hours: parseInt(value.split(":")[0]?.toString() || "0"), + minutes: parseInt(value.split(":")[1]?.toString() || "0"), + seconds: 0, + }); + } + + const date: Date = OneUptimeDate.fromString(value); + const dateString: string = OneUptimeDate.toString(date); + setValue(dateString); + if (props.onChange) { + props.onChange(dateString); + } + } else { + setValue(value); + if (props.onChange) { + props.onChange(value); + } } - } else { - setDisplayValue(value as string); - } - }, [value]); - - useEffect(() => { - const input: any = ref.current; - if (input) { - (input as any).value = displayValue; - } - }, [ref, displayValue]); - - useEffect(() => { - if (props.initialValue) { - setValue(props.initialValue); - } - - if (props.value) { - setValue(props.value); - } - }, []); - - useEffect(() => { - if (props.initialValue) { - setValue(props.initialValue); - } - }, [props.initialValue]); - - useEffect(() => { - setValue(props.value ? props.value : props.initialValue || ''); - }, [props.value]); - - return ( - <> - <div - className={ - props.outerDivClassName || - `relative mt-2 mb-1 rounded-md shadow-sm w-full` + }} + tabIndex={props.tabIndex} + onKeyDown={ + props.onEnterPress + ? (event: any) => { + if (event.key === "Enter") { + props.onEnterPress && props.onEnterPress(); + } } - > - <input - autoFocus={props.autoFocus} - ref={ref} - onFocus={props.onFocus} - onClick={props.onClick} - data-testid={props.dataTestId} - onChange={(e: React.ChangeEvent<HTMLInputElement>) => { - let value: string | Date = e.target.value; + : undefined + } + readOnly={props.readOnly || props.disabled || false} + type={props.type || "text"} + placeholder={props.placeholder} + className={className} + onBlur={() => { + if (props.onBlur) { + props.onBlur(); + } + }} + /> - if ( - (props.type === InputType.DATE || - props.type === InputType.DATETIME_LOCAL || - props.type === InputType.TIME) && - value - ) { - if (props.type === InputType.TIME) { - // conver value like "16:00" to date with local timezone - value = OneUptimeDate.getDateWithCustomTime({ - hours: parseInt( - value.split(':')[0]?.toString() || '0' - ), - minutes: parseInt( - value.split(':')[1]?.toString() || '0' - ), - seconds: 0, - }); - } + {props.error && ( + <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> + <Icon icon={IconProp.ErrorSolid} className="h-5 w-5 text-red-500" /> + </div> + )} + </div> - const date: Date = OneUptimeDate.fromString(value); - const dateString: string = - OneUptimeDate.toString(date); - setValue(dateString); - if (props.onChange) { - props.onChange(dateString); - } - } else { - setValue(value); - if (props.onChange) { - props.onChange(value); - } - } - }} - tabIndex={props.tabIndex} - onKeyDown={ - props.onEnterPress - ? (event: any) => { - if (event.key === 'Enter') { - props.onEnterPress && - props.onEnterPress(); - } - } - : undefined - } - readOnly={props.readOnly || props.disabled || false} - type={props.type || 'text'} - placeholder={props.placeholder} - className={className} - onBlur={() => { - if (props.onBlur) { - props.onBlur(); - } - }} - /> - - {props.error && ( - <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> - <Icon - icon={IconProp.ErrorSolid} - className="h-5 w-5 text-red-500" - /> - </div> - )} - </div> - - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} - </> - ); + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </> + ); }; export default Input; diff --git a/CommonUI/src/Components/Link/Link.tsx b/CommonUI/src/Components/Link/Link.tsx index bbaaa396d9..630ed6cec4 100644 --- a/CommonUI/src/Components/Link/Link.tsx +++ b/CommonUI/src/Components/Link/Link.tsx @@ -1,76 +1,76 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -import Navigation from '../../Utils/Navigation'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { JSONObject } from 'Common/Types/JSON'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Navigation from "../../Utils/Navigation"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { JSONObject } from "Common/Types/JSON"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement> | string; - className?: undefined | string; - to?: Route | URL | null | undefined; - onClick?: undefined | (() => void); - onNavigateComplete?: (() => void) | undefined; - openInNewTab?: boolean | undefined; - style?: React.CSSProperties | undefined; - onMouseOver?: (() => void) | undefined; - onMouseOut?: (() => void) | undefined; - onMouseLeave?: (() => void) | undefined; - id?: string | undefined; + children: ReactElement | Array<ReactElement> | string; + className?: undefined | string; + to?: Route | URL | null | undefined; + onClick?: undefined | (() => void); + onNavigateComplete?: (() => void) | undefined; + openInNewTab?: boolean | undefined; + style?: React.CSSProperties | undefined; + onMouseOver?: (() => void) | undefined; + onMouseOut?: (() => void) | undefined; + onMouseLeave?: (() => void) | undefined; + id?: string | undefined; } const Link: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let children: ReactElement | Array<ReactElement>; + let children: ReactElement | Array<ReactElement>; - if (typeof props.children === 'string') { - children = <span>{props.children}</span>; - } else { - children = props.children; - } + if (typeof props.children === "string") { + children = <span>{props.children}</span>; + } else { + children = props.children; + } - const linkProps: JSONObject = {}; + const linkProps: JSONObject = {}; - if (props.openInNewTab) { - linkProps['target'] = '_blank'; - linkProps['href'] = props.to?.toString(); - } + if (props.openInNewTab) { + linkProps["target"] = "_blank"; + linkProps["href"] = props.to?.toString(); + } - const cursorClassName: string = props.to - ? 'cursor-pointer' - : 'cursor-default'; + const cursorClassName: string = props.to + ? "cursor-pointer" + : "cursor-default"; - return ( - <a - id={props.id} - className={`${cursorClassName} ${props.className || ''}`} - onMouseOver={props.onMouseOver} - onMouseOut={props.onMouseOut} - onMouseLeave={props.onMouseLeave} - style={props.style} - onClick={() => { - if (props.onClick) { - props.onClick(); - } + return ( + <a + id={props.id} + className={`${cursorClassName} ${props.className || ""}`} + onMouseOver={props.onMouseOver} + onMouseOut={props.onMouseOut} + onMouseLeave={props.onMouseLeave} + style={props.style} + onClick={() => { + if (props.onClick) { + props.onClick(); + } - if (props.openInNewTab) { - return; - } + if (props.openInNewTab) { + return; + } - if (props.to) { - Navigation.navigate(props.to); - } + if (props.to) { + Navigation.navigate(props.to); + } - if (props.onNavigateComplete) { - props.onNavigateComplete(); - } - }} - {...linkProps} - > - {children} - </a> - ); + if (props.onNavigateComplete) { + props.onNavigateComplete(); + } + }} + {...linkProps} + > + {children} + </a> + ); }; export default Link; diff --git a/CommonUI/src/Components/List/List.tsx b/CommonUI/src/Components/List/List.tsx index ecefb118ba..b2733fc3e7 100644 --- a/CommonUI/src/Components/List/List.tsx +++ b/CommonUI/src/Components/List/List.tsx @@ -1,159 +1,154 @@ -import { GetReactElementFunction } from '../../Types/FunctionTypes'; -import ActionButtonSchema from '../ActionButton/ActionButtonSchema'; -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import Field from '../Detail/Field'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import FilterViewer from '../Filters/FilterViewer'; -import FilterType from '../Filters/Types/Filter'; -import FilterData from '../Filters/Types/FilterData'; -import Pagination from '../Pagination/Pagination'; -import ListBody from './ListBody'; -import { ListDetailProps } from './ListRow'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; -import { DragDropContext, DropResult } from 'react-beautiful-dnd'; +import { GetReactElementFunction } from "../../Types/FunctionTypes"; +import ActionButtonSchema from "../ActionButton/ActionButtonSchema"; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import Field from "../Detail/Field"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import FilterViewer from "../Filters/FilterViewer"; +import FilterType from "../Filters/Types/Filter"; +import FilterData from "../Filters/Types/FilterData"; +import Pagination from "../Pagination/Pagination"; +import ListBody from "./ListBody"; +import { ListDetailProps } from "./ListRow"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; +import { DragDropContext, DropResult } from "react-beautiful-dnd"; export interface ComponentProps<T extends GenericObject> { - data: Array<T>; - id: string; - fields: Array<Field<T>>; - disablePagination?: undefined | boolean; - onNavigateToPage: (pageNumber: number, itemsOnPage: number) => void; - currentPageNumber: number; - totalItemsCount: number; - itemsOnPage: number; - enableDragAndDrop?: boolean | undefined; - dragDropIndexField?: keyof T | undefined; - dragDropIdField?: keyof T | undefined; - onDragDrop?: ((id: string, newIndex: number) => void) | undefined; - error: string; - isLoading: boolean; - singularLabel: string; - pluralLabel: string; - actionButtons?: undefined | Array<ActionButtonSchema<T>>; - onRefreshClick?: undefined | (() => void); - noItemsMessage?: undefined | string | ReactElement; - listDetailOptions?: undefined | ListDetailProps; + data: Array<T>; + id: string; + fields: Array<Field<T>>; + disablePagination?: undefined | boolean; + onNavigateToPage: (pageNumber: number, itemsOnPage: number) => void; + currentPageNumber: number; + totalItemsCount: number; + itemsOnPage: number; + enableDragAndDrop?: boolean | undefined; + dragDropIndexField?: keyof T | undefined; + dragDropIdField?: keyof T | undefined; + onDragDrop?: ((id: string, newIndex: number) => void) | undefined; + error: string; + isLoading: boolean; + singularLabel: string; + pluralLabel: string; + actionButtons?: undefined | Array<ActionButtonSchema<T>>; + onRefreshClick?: undefined | (() => void); + noItemsMessage?: undefined | string | ReactElement; + listDetailOptions?: undefined | ListDetailProps; - isFilterLoading?: undefined | boolean; - filters?: Array<FilterType<T>>; - showFilterModal?: undefined | boolean; - filterError?: string | undefined; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - onFilterRefreshClick?: undefined | (() => void); - onFilterModalClose?: (() => void) | undefined; - onFilterModalOpen?: (() => void) | undefined; + isFilterLoading?: undefined | boolean; + filters?: Array<FilterType<T>>; + showFilterModal?: undefined | boolean; + filterError?: string | undefined; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + onFilterRefreshClick?: undefined | (() => void); + onFilterModalClose?: (() => void) | undefined; + onFilterModalOpen?: (() => void) | undefined; } type ListFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const List: ListFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const getListbody: GetReactElementFunction = (): ReactElement => { - if (props.isLoading) { - return <ComponentLoader />; - } + const getListbody: GetReactElementFunction = (): ReactElement => { + if (props.isLoading) { + return <ComponentLoader />; + } - if (props.error) { - return ( - <div className="p-6"> - <ErrorMessage - error={props.error} - onRefreshClick={props.onRefreshClick} - /> - </div> - ); - } + if (props.error) { + return ( + <div className="p-6"> + <ErrorMessage + error={props.error} + onRefreshClick={props.onRefreshClick} + /> + </div> + ); + } - if (props.data.length === 0) { - return ( - <div className="p-6"> - <ErrorMessage - error={ - props.noItemsMessage - ? props.noItemsMessage - : `No ${props.singularLabel.toLocaleLowerCase()}` - } - onRefreshClick={props.onRefreshClick} - /> - </div> - ); - } - - return ( - <ListBody - id={`${props.id}-body`} - data={props.data} - fields={props.fields} - actionButtons={props.actionButtons} - enableDragAndDrop={props.enableDragAndDrop} - dragAndDropScope={`${props.id}-dnd`} - dragDropIdField={props.dragDropIdField} - dragDropIndexField={props.dragDropIndexField} - listDetailOptions={props.listDetailOptions} - /> - ); - }; + if (props.data.length === 0) { + return ( + <div className="p-6"> + <ErrorMessage + error={ + props.noItemsMessage + ? props.noItemsMessage + : `No ${props.singularLabel.toLocaleLowerCase()}` + } + onRefreshClick={props.onRefreshClick} + /> + </div> + ); + } return ( - <div data-testid="list-container"> - <div className="mt-6"> - <div className="bg-white pr-6 pl-6"> - <FilterViewer - id={`${props.id}-filter`} - showFilterModal={props.showFilterModal || false} - onFilterChanged={props.onFilterChanged || undefined} - isModalLoading={props.isFilterLoading || false} - filterError={props.filterError} - onFilterRefreshClick={props.onFilterRefreshClick} - filters={props.filters || []} - onFilterModalClose={() => { - props.onFilterModalClose && - props.onFilterModalClose(); - }} - onFilterModalOpen={() => { - props.onFilterModalOpen && - props.onFilterModalOpen(); - }} - singularLabel={props.singularLabel} - pluralLabel={props.pluralLabel} - /> - </div> - <div className=""> - <DragDropContext - onDragEnd={(result: DropResult) => { - result.destination?.index && - props.onDragDrop && - props.onDragDrop( - result.draggableId, - result.destination.index - ); - }} - > - {getListbody()} - </DragDropContext> - {!props.disablePagination && ( - <div className="mt-5 -mb-6"> - <Pagination - singularLabel={props.singularLabel} - pluralLabel={props.pluralLabel} - currentPageNumber={props.currentPageNumber} - totalItemsCount={props.totalItemsCount} - itemsOnPage={props.itemsOnPage} - onNavigateToPage={props.onNavigateToPage} - isLoading={props.isLoading} - isError={Boolean(props.error)} - dataTestId="list-pagination" - /> - </div> - )} - </div> - </div> - </div> + <ListBody + id={`${props.id}-body`} + data={props.data} + fields={props.fields} + actionButtons={props.actionButtons} + enableDragAndDrop={props.enableDragAndDrop} + dragAndDropScope={`${props.id}-dnd`} + dragDropIdField={props.dragDropIdField} + dragDropIndexField={props.dragDropIndexField} + listDetailOptions={props.listDetailOptions} + /> ); + }; + + return ( + <div data-testid="list-container"> + <div className="mt-6"> + <div className="bg-white pr-6 pl-6"> + <FilterViewer + id={`${props.id}-filter`} + showFilterModal={props.showFilterModal || false} + onFilterChanged={props.onFilterChanged || undefined} + isModalLoading={props.isFilterLoading || false} + filterError={props.filterError} + onFilterRefreshClick={props.onFilterRefreshClick} + filters={props.filters || []} + onFilterModalClose={() => { + props.onFilterModalClose && props.onFilterModalClose(); + }} + onFilterModalOpen={() => { + props.onFilterModalOpen && props.onFilterModalOpen(); + }} + singularLabel={props.singularLabel} + pluralLabel={props.pluralLabel} + /> + </div> + <div className=""> + <DragDropContext + onDragEnd={(result: DropResult) => { + result.destination?.index && + props.onDragDrop && + props.onDragDrop(result.draggableId, result.destination.index); + }} + > + {getListbody()} + </DragDropContext> + {!props.disablePagination && ( + <div className="mt-5 -mb-6"> + <Pagination + singularLabel={props.singularLabel} + pluralLabel={props.pluralLabel} + currentPageNumber={props.currentPageNumber} + totalItemsCount={props.totalItemsCount} + itemsOnPage={props.itemsOnPage} + onNavigateToPage={props.onNavigateToPage} + isLoading={props.isLoading} + isError={Boolean(props.error)} + dataTestId="list-pagination" + /> + </div> + )} + </div> + </div> + </div> + ); }; export default List; diff --git a/CommonUI/src/Components/List/ListBody.tsx b/CommonUI/src/Components/List/ListBody.tsx index 65e8191b89..105b8bef21 100644 --- a/CommonUI/src/Components/List/ListBody.tsx +++ b/CommonUI/src/Components/List/ListBody.tsx @@ -1,72 +1,72 @@ -import ActionButtonSchema from '../ActionButton/ActionButtonSchema'; -import Field from '../Detail/Field'; -import ListRow, { ListDetailProps } from './ListRow'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; -import { Droppable, DroppableProvided } from 'react-beautiful-dnd'; +import ActionButtonSchema from "../ActionButton/ActionButtonSchema"; +import Field from "../Detail/Field"; +import ListRow, { ListDetailProps } from "./ListRow"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; +import { Droppable, DroppableProvided } from "react-beautiful-dnd"; export interface ComponentProps<T extends GenericObject> { - data: Array<T>; - id: string; - fields: Array<Field<T>>; - actionButtons?: undefined | Array<ActionButtonSchema<T>> | undefined; - enableDragAndDrop?: undefined | boolean; - dragAndDropScope?: string | undefined; - dragDropIdField?: keyof T | undefined; - dragDropIndexField?: keyof T | undefined; - listDetailOptions?: undefined | ListDetailProps; + data: Array<T>; + id: string; + fields: Array<Field<T>>; + actionButtons?: undefined | Array<ActionButtonSchema<T>> | undefined; + enableDragAndDrop?: undefined | boolean; + dragAndDropScope?: string | undefined; + dragDropIdField?: keyof T | undefined; + dragDropIndexField?: keyof T | undefined; + listDetailOptions?: undefined | ListDetailProps; } type ListBodyFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const ListBody: ListBodyFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - type GetBodyFunction = (provided?: DroppableProvided) => ReactElement; + type GetBodyFunction = (provided?: DroppableProvided) => ReactElement; - const getBody: GetBodyFunction = ( - provided?: DroppableProvided - ): ReactElement => { - return ( - <div - ref={provided?.innerRef} - {...provided?.droppableProps} - id={props.id} - className="space-y-6 p-6 border-t border-gray-200" - > - {props.data && - props.data.map((item: T, i: number) => { - return ( - <ListRow - key={i} - item={item} - fields={props.fields} - actionButtons={props.actionButtons} - dragAndDropScope={props.dragAndDropScope} - enableDragAndDrop={props.enableDragAndDrop} - dragDropIdField={props.dragDropIdField} - dragDropIndexField={props.dragDropIndexField} - listDetailOptions={props.listDetailOptions} - /> - ); - })} - </div> - ); - }; + const getBody: GetBodyFunction = ( + provided?: DroppableProvided, + ): ReactElement => { + return ( + <div + ref={provided?.innerRef} + {...provided?.droppableProps} + id={props.id} + className="space-y-6 p-6 border-t border-gray-200" + > + {props.data && + props.data.map((item: T, i: number) => { + return ( + <ListRow + key={i} + item={item} + fields={props.fields} + actionButtons={props.actionButtons} + dragAndDropScope={props.dragAndDropScope} + enableDragAndDrop={props.enableDragAndDrop} + dragDropIdField={props.dragDropIdField} + dragDropIndexField={props.dragDropIndexField} + listDetailOptions={props.listDetailOptions} + /> + ); + })} + </div> + ); + }; - if (props.enableDragAndDrop) { - return ( - <Droppable droppableId={props.dragAndDropScope || ''}> - {(provided: DroppableProvided) => { - return getBody(provided); - }} - </Droppable> - ); - } + if (props.enableDragAndDrop) { + return ( + <Droppable droppableId={props.dragAndDropScope || ""}> + {(provided: DroppableProvided) => { + return getBody(provided); + }} + </Droppable> + ); + } - return getBody(); + return getBody(); }; export default ListBody; diff --git a/CommonUI/src/Components/List/ListRow.tsx b/CommonUI/src/Components/List/ListRow.tsx index be9556cbb4..563b8af699 100644 --- a/CommonUI/src/Components/List/ListRow.tsx +++ b/CommonUI/src/Components/List/ListRow.tsx @@ -1,184 +1,161 @@ -import ActionButtonSchema from '../ActionButton/ActionButtonSchema'; -import Button, { ButtonSize } from '../Button/Button'; -import Detail from '../Detail/Detail'; -import Field from '../Detail/Field'; -import Icon, { ThickProp } from '../Icon/Icon'; -import ConfirmModal from '../Modal/ConfirmModal'; -import GenericObject from 'Common/Types/GenericObject'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { ReactElement, useState } from 'react'; -import { Draggable, DraggableProvided } from 'react-beautiful-dnd'; +import ActionButtonSchema from "../ActionButton/ActionButtonSchema"; +import Button, { ButtonSize } from "../Button/Button"; +import Detail from "../Detail/Detail"; +import Field from "../Detail/Field"; +import Icon, { ThickProp } from "../Icon/Icon"; +import ConfirmModal from "../Modal/ConfirmModal"; +import GenericObject from "Common/Types/GenericObject"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { ReactElement, useState } from "react"; +import { Draggable, DraggableProvided } from "react-beautiful-dnd"; export interface ListDetailProps { - showDetailsInNumberOfColumns?: number | undefined; + showDetailsInNumberOfColumns?: number | undefined; } export interface ComponentProps<T extends GenericObject> { - item: T; - fields: Array<Field<T>>; - actionButtons?: Array<ActionButtonSchema<T>> | undefined; - enableDragAndDrop?: boolean | undefined; - dragAndDropScope?: string | undefined; - dragDropIdField?: keyof T | undefined; - dragDropIndexField?: keyof T | undefined; - listDetailOptions?: ListDetailProps | undefined; + item: T; + fields: Array<Field<T>>; + actionButtons?: Array<ActionButtonSchema<T>> | undefined; + enableDragAndDrop?: boolean | undefined; + dragAndDropScope?: string | undefined; + dragDropIdField?: keyof T | undefined; + dragDropIndexField?: keyof T | undefined; + listDetailOptions?: ListDetailProps | undefined; } type ListRowFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const ListRow: ListRowFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const [isButtonLoading, setIsButtonLoading] = useState<Array<boolean>>( - props.actionButtons?.map(() => { - return false; - }) || [] - ); + const [isButtonLoading, setIsButtonLoading] = useState<Array<boolean>>( + props.actionButtons?.map(() => { + return false; + }) || [], + ); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - type GetRowFunction = (provided?: DraggableProvided) => ReactElement; + type GetRowFunction = (provided?: DraggableProvided) => ReactElement; - const getRow: GetRowFunction = ( - provided?: DraggableProvided - ): ReactElement => { - return ( - <div - {...provided?.draggableProps} - ref={provided?.innerRef} - className="bg-white px-4 py-6 shadow sm:rounded-lg sm:px-6" - > - <div> - {props.enableDragAndDrop && ( - <div className="flex"> - <div - className="ml-0 -ml-2 w-10" - {...provided?.dragHandleProps} - > - <Icon - icon={IconProp.Drag} - thick={ThickProp.Thick} - className=" h-6 w-6 text-gray-500 hover:text-gray-700 m-auto cursor-ns-resize" - /> - </div> - <Detail - item={props.item} - fields={props.fields} - showDetailsInNumberOfColumns={ - props.listDetailOptions - ?.showDetailsInNumberOfColumns || 1 - } - /> - </div> - )} - {!props.enableDragAndDrop && ( - <Detail - item={props.item} - fields={props.fields} - showDetailsInNumberOfColumns={ - props.listDetailOptions - ?.showDetailsInNumberOfColumns || 1 - } - /> - )} - </div> - - <div - className={ - props.enableDragAndDrop - ? `flex mt-5 ml-5` - : `flex mt-5 -ml-3` - } - > - {props.actionButtons?.map( - (button: ActionButtonSchema<T>, i: number) => { - if ( - button.isVisible && - !button.isVisible(props.item) - ) { - return <></>; - } - - return ( - <div key={i}> - <Button - buttonSize={ButtonSize.Small} - title={button.title} - icon={button.icon} - buttonStyle={button.buttonStyleType} - isLoading={isButtonLoading[i]} - onClick={() => { - if (button.onClick) { - isButtonLoading[i] = true; - setIsButtonLoading( - isButtonLoading - ); - button.onClick( - props.item, - () => { - // on action complete - isButtonLoading[i] = - false; - setIsButtonLoading( - isButtonLoading - ); - }, - (err: Error) => { - isButtonLoading[i] = - false; - setIsButtonLoading( - isButtonLoading - ); - setError( - (err as Error) - .message - ); - } - ); - } - }} - /> - </div> - ); - } - )} - </div> - {error && ( - <ConfirmModal - title={`Error`} - description={error} - submitButtonText={'Close'} - onSubmit={() => { - return setError(''); - }} - /> - )} - </div> - ); - }; - - if ( - props.enableDragAndDrop && - props.dragDropIdField && - props.dragDropIndexField - ) { - return ( - <Draggable - draggableId={ - (props.item[props.dragDropIdField] as string) || '' + const getRow: GetRowFunction = ( + provided?: DraggableProvided, + ): ReactElement => { + return ( + <div + {...provided?.draggableProps} + ref={provided?.innerRef} + className="bg-white px-4 py-6 shadow sm:rounded-lg sm:px-6" + > + <div> + {props.enableDragAndDrop && ( + <div className="flex"> + <div className="ml-0 -ml-2 w-10" {...provided?.dragHandleProps}> + <Icon + icon={IconProp.Drag} + thick={ThickProp.Thick} + className=" h-6 w-6 text-gray-500 hover:text-gray-700 m-auto cursor-ns-resize" + /> + </div> + <Detail + item={props.item} + fields={props.fields} + showDetailsInNumberOfColumns={ + props.listDetailOptions?.showDetailsInNumberOfColumns || 1 } - index={(props.item[props.dragDropIndexField] as number) || 0} - > - {(provided: DraggableProvided) => { - return getRow(provided); - }} - </Draggable> - ); - } + /> + </div> + )} + {!props.enableDragAndDrop && ( + <Detail + item={props.item} + fields={props.fields} + showDetailsInNumberOfColumns={ + props.listDetailOptions?.showDetailsInNumberOfColumns || 1 + } + /> + )} + </div> - return getRow(); + <div + className={ + props.enableDragAndDrop ? `flex mt-5 ml-5` : `flex mt-5 -ml-3` + } + > + {props.actionButtons?.map( + (button: ActionButtonSchema<T>, i: number) => { + if (button.isVisible && !button.isVisible(props.item)) { + return <></>; + } + + return ( + <div key={i}> + <Button + buttonSize={ButtonSize.Small} + title={button.title} + icon={button.icon} + buttonStyle={button.buttonStyleType} + isLoading={isButtonLoading[i]} + onClick={() => { + if (button.onClick) { + isButtonLoading[i] = true; + setIsButtonLoading(isButtonLoading); + button.onClick( + props.item, + () => { + // on action complete + isButtonLoading[i] = false; + setIsButtonLoading(isButtonLoading); + }, + (err: Error) => { + isButtonLoading[i] = false; + setIsButtonLoading(isButtonLoading); + setError((err as Error).message); + }, + ); + } + }} + /> + </div> + ); + }, + )} + </div> + {error && ( + <ConfirmModal + title={`Error`} + description={error} + submitButtonText={"Close"} + onSubmit={() => { + return setError(""); + }} + /> + )} + </div> + ); + }; + + if ( + props.enableDragAndDrop && + props.dragDropIdField && + props.dragDropIndexField + ) { + return ( + <Draggable + draggableId={(props.item[props.dragDropIdField] as string) || ""} + index={(props.item[props.dragDropIndexField] as number) || 0} + > + {(provided: DraggableProvided) => { + return getRow(provided); + }} + </Draggable> + ); + } + + return getRow(); }; export default ListRow; diff --git a/CommonUI/src/Components/Loader/Loader.tsx b/CommonUI/src/Components/Loader/Loader.tsx index 81c509bf91..7a25b7c832 100644 --- a/CommonUI/src/Components/Loader/Loader.tsx +++ b/CommonUI/src/Components/Loader/Loader.tsx @@ -1,50 +1,50 @@ -import { VeryLightGray } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import React, { FunctionComponent } from 'react'; -import BarLoader from 'react-spinners/BarLoader'; -import BeatLoader from 'react-spinners/BeatLoader'; +import { VeryLightGray } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import React, { FunctionComponent } from "react"; +import BarLoader from "react-spinners/BarLoader"; +import BeatLoader from "react-spinners/BeatLoader"; export enum LoaderType { - Bar, - Beats, + Bar, + Beats, } export interface ComponentProps { - size?: undefined | number; - color?: undefined | Color; - loaderType?: undefined | LoaderType; + size?: undefined | number; + color?: undefined | Color; + loaderType?: undefined | LoaderType; } const Loader: FunctionComponent<ComponentProps> = ({ - size = 50, - color = VeryLightGray, - loaderType = LoaderType.Bar, + size = 50, + color = VeryLightGray, + loaderType = LoaderType.Bar, }: ComponentProps) => { - if (loaderType === LoaderType.Bar) { - return ( - <div - role="bar-loader mt-1" - className="justify-center" - data-testid="loader" - > - <BarLoader height={4} width={size} color={color.toString()} /> - </div> - ); - } + if (loaderType === LoaderType.Bar) { + return ( + <div + role="bar-loader mt-1" + className="justify-center" + data-testid="loader" + > + <BarLoader height={4} width={size} color={color.toString()} /> + </div> + ); + } - if (loaderType === LoaderType.Beats) { - return ( - <div - role="beat-loader mt-1" - className="justify-center" - data-testid="loader" - > - <BeatLoader size={size} color={color.toString()} /> - </div> - ); - } + if (loaderType === LoaderType.Beats) { + return ( + <div + role="beat-loader mt-1" + className="justify-center" + data-testid="loader" + > + <BeatLoader size={size} color={color.toString()} /> + </div> + ); + } - return <></>; + return <></>; }; export default Loader; diff --git a/CommonUI/src/Components/Loader/PageLoader.tsx b/CommonUI/src/Components/Loader/PageLoader.tsx index 01b6a1ab13..a5079990ae 100644 --- a/CommonUI/src/Components/Loader/PageLoader.tsx +++ b/CommonUI/src/Components/Loader/PageLoader.tsx @@ -1,26 +1,22 @@ -import Loader, { LoaderType } from './Loader'; -import { VeryLightGray } from 'Common/Types/BrandColors'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Loader, { LoaderType } from "./Loader"; +import { VeryLightGray } from "Common/Types/BrandColors"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - isVisible: boolean; + isVisible: boolean; } const PageLoader: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.isVisible) { - return ( - <div className="m-auto w-full text-center w-max mt-52 align-middle flex items-center"> - <Loader - loaderType={LoaderType.Bar} - color={VeryLightGray} - size={200} - /> - </div> - ); - } - return <></>; + if (props.isVisible) { + return ( + <div className="m-auto w-full text-center w-max mt-52 align-middle flex items-center"> + <Loader loaderType={LoaderType.Bar} color={VeryLightGray} size={200} /> + </div> + ); + } + return <></>; }; export default PageLoader; diff --git a/CommonUI/src/Components/LogsViewer/LogItem.tsx b/CommonUI/src/Components/LogsViewer/LogItem.tsx index 52427ca4d2..36bbaffdc8 100644 --- a/CommonUI/src/Components/LogsViewer/LogItem.tsx +++ b/CommonUI/src/Components/LogsViewer/LogItem.tsx @@ -1,256 +1,236 @@ -import CopyTextButton from '../CopyTextButton/CopyTextButton'; -import OneUptimeDate from 'Common/Types/Date'; -import Log, { LogSeverity } from 'Model/AnalyticsModels/Log'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import CopyTextButton from "../CopyTextButton/CopyTextButton"; +import OneUptimeDate from "Common/Types/Date"; +import Log, { LogSeverity } from "Model/AnalyticsModels/Log"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - log: Log; + log: Log; } const LogItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isCollapsed, setIsCollapsed] = React.useState<boolean>(true); + const [isCollapsed, setIsCollapsed] = React.useState<boolean>(true); - useEffect(() => { - setIsCollapsed(true); - }, []); + useEffect(() => { + setIsCollapsed(true); + }, []); - let bodyColor: string = 'text-slate-200'; + let bodyColor: string = "text-slate-200"; - type GetCopyButtonFunction = (textToBeCopied: string) => ReactElement; - - const getCopyButton: GetCopyButtonFunction = (textToBeCopied: string) => { - return ( - <CopyTextButton - textToBeCopied={textToBeCopied} - className="ml-5 font-medium px-3 my-0.5 py-0.5 text-xs bg-slate-900 text-slate-300 rounded hover:bg-slate-600 border-slate-700 border-solid border-0" - /> - ); - }; - - if (props.log.severityText === LogSeverity.Warning) { - bodyColor = 'text-amber-400'; - } else if (props.log.severityText === LogSeverity.Error) { - bodyColor = 'text-rose-400'; - } else if ( - props.log.severityText === LogSeverity.Trace || - props.log.severityText === LogSeverity.Debug - ) { - bodyColor = 'text-slate-400'; - } - - let logBody: string = props.log.body?.toString() || ''; - - let isBodyInJSON: boolean = false; - - try { - logBody = JSON.stringify(JSON.parse(logBody), null, 2); - isBodyInJSON = true; - } catch (e) { - isBodyInJSON = false; - } - - if (isCollapsed) { - return ( - <div - className="text-slate-200 flex cursor-pointer hover:border-slate-700 px-2 border-transparent border-2 rounded-md" - onClick={() => { - setIsCollapsed(false); - }} - > - {props.log.time && ( - <div - className="text-slate-500 courier-prime flex-none" - style={{ - width: '230px !important', - }} - > - {OneUptimeDate.getDateAsLocalFormattedString( - props.log.time - )}{' '} -  {' '} - </div> - )} - {props.log.severityText === LogSeverity.Information && ( - <div className="text-sky-400 courier-prime flex-none"> - [INFO]   - </div> - )} - {props.log.severityText === LogSeverity.Warning && ( - <div className="text-amber-400 courier-prime flex-none"> - [WARN]   - </div> - )} - {props.log.severityText === LogSeverity.Trace && ( - <div className="text-slate-400 courier-prime flex-none"> - [TRACE]   - </div> - )} - {props.log.severityText === LogSeverity.Debug && ( - <div className="text-slate-400 courier-prime flex-none"> - [DEBUG]   - </div> - )} - {props.log.severityText === LogSeverity.Error && ( - <div className="text-rose-400 courier-prime flex-none"> - [ERROR]   - </div> - )} - {props.log.severityText === LogSeverity.Fatal && ( - <div className="text-rose-400 courier-prime flex-none"> - [FATAL]   - </div> - )} - - <div className={`${bodyColor} courier-prime`}> - {isBodyInJSON && <pre>{logBody}</pre>} - {!isBodyInJSON && props.log.body?.toString()} - </div> - </div> - ); - } + type GetCopyButtonFunction = (textToBeCopied: string) => ReactElement; + const getCopyButton: GetCopyButtonFunction = (textToBeCopied: string) => { return ( - <div - className="text-slate-200 cursor-pointer hover:border-slate-700 px-2 border-transparent border-2 rounded-md mb-1" - onClick={() => { - setIsCollapsed(true); - }} - > - {props.log.time && ( - <div className="text-slate-500 courier-prime"> - {OneUptimeDate.getDateAsFormattedString(props.log.time)}{' '} -  {' '} - </div> - )} - {props.log.severityText === LogSeverity.Information && ( - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - SEVERITY: - </div> - <div className="text-sky-400 courier-prime"> - [INFO]   - </div> - </div> - )} - {props.log.severityText === LogSeverity.Unspecified && ( - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - SEVERITY: - </div> - <div className="text-sky-400 courier-prime"> - [UNKNOWN]   - </div> - </div> - )} - {props.log.severityText === LogSeverity.Warning && ( - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - SEVERITY: - </div> - <div className="text-amber-400 courier-prime"> - [WARN]   - </div> - </div> - )} - {props.log.severityText === LogSeverity.Trace && ( - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - SEVERITY: - </div> - <div className="text-slate-400 courier-prime"> - [TRACE]   - </div> - </div> - )} - {props.log.severityText === LogSeverity.Debug && ( - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - SEVERITY: - </div> - <div className="text-slate-400 courier-prime"> - [DEBUG]   - </div> - </div> - )} - {props.log.severityText === LogSeverity.Error && ( - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - SEVERITY: - </div> - <div className="text-rose-400 courier-prime"> - [ERROR]   - </div> - </div> - )} - {props.log.severityText === LogSeverity.Fatal && ( - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - SEVERITY: - </div> - <div className="text-rose-400 courier-prime"> - [FATAL]   - </div> - </div> - )} - - <div> - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - MESSAGE:  - </div> - {!isBodyInJSON && ( - <div className={`${bodyColor} courier-prime`}> - {props.log.body?.toString()} - </div> - )} - </div> - {isBodyInJSON && ( - <pre className={`${bodyColor} courier-prime`}> - {logBody} - </pre> - )} - </div> - - {props.log.traceId && ( - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - TRACE:    - </div> - <div className={`${bodyColor} courier-prime`}> - {props.log.traceId?.toString()} - </div> - {getCopyButton(props.log.traceId?.toString() || '')} - </div> - )} - - {props.log.spanId && ( - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - SPAN:     - </div> - <div className={`${bodyColor} courier-prime`}> - {props.log.spanId?.toString()} - </div> - {getCopyButton(props.log.spanId?.toString() || '')} - </div> - )} - - {props.log.attributes && ( - <div> - <div className="flex"> - <div className="font-medium text-slate-200 courier-prime mr-2"> - ATTRIBUTES: - </div> - </div> - <pre className={`${bodyColor} courier-prime`}> - {JSON.stringify(props.log.attributes, null, 2)} - </pre> - </div> - )} - </div> + <CopyTextButton + textToBeCopied={textToBeCopied} + className="ml-5 font-medium px-3 my-0.5 py-0.5 text-xs bg-slate-900 text-slate-300 rounded hover:bg-slate-600 border-slate-700 border-solid border-0" + /> ); + }; + + if (props.log.severityText === LogSeverity.Warning) { + bodyColor = "text-amber-400"; + } else if (props.log.severityText === LogSeverity.Error) { + bodyColor = "text-rose-400"; + } else if ( + props.log.severityText === LogSeverity.Trace || + props.log.severityText === LogSeverity.Debug + ) { + bodyColor = "text-slate-400"; + } + + let logBody: string = props.log.body?.toString() || ""; + + let isBodyInJSON: boolean = false; + + try { + logBody = JSON.stringify(JSON.parse(logBody), null, 2); + isBodyInJSON = true; + } catch (e) { + isBodyInJSON = false; + } + + if (isCollapsed) { + return ( + <div + className="text-slate-200 flex cursor-pointer hover:border-slate-700 px-2 border-transparent border-2 rounded-md" + onClick={() => { + setIsCollapsed(false); + }} + > + {props.log.time && ( + <div + className="text-slate-500 courier-prime flex-none" + style={{ + width: "230px !important", + }} + > + {OneUptimeDate.getDateAsLocalFormattedString(props.log.time)}  {" "} + </div> + )} + {props.log.severityText === LogSeverity.Information && ( + <div className="text-sky-400 courier-prime flex-none"> + [INFO]   + </div> + )} + {props.log.severityText === LogSeverity.Warning && ( + <div className="text-amber-400 courier-prime flex-none"> + [WARN]   + </div> + )} + {props.log.severityText === LogSeverity.Trace && ( + <div className="text-slate-400 courier-prime flex-none"> + [TRACE]   + </div> + )} + {props.log.severityText === LogSeverity.Debug && ( + <div className="text-slate-400 courier-prime flex-none"> + [DEBUG]   + </div> + )} + {props.log.severityText === LogSeverity.Error && ( + <div className="text-rose-400 courier-prime flex-none"> + [ERROR]   + </div> + )} + {props.log.severityText === LogSeverity.Fatal && ( + <div className="text-rose-400 courier-prime flex-none"> + [FATAL]   + </div> + )} + + <div className={`${bodyColor} courier-prime`}> + {isBodyInJSON && <pre>{logBody}</pre>} + {!isBodyInJSON && props.log.body?.toString()} + </div> + </div> + ); + } + + return ( + <div + className="text-slate-200 cursor-pointer hover:border-slate-700 px-2 border-transparent border-2 rounded-md mb-1" + onClick={() => { + setIsCollapsed(true); + }} + > + {props.log.time && ( + <div className="text-slate-500 courier-prime"> + {OneUptimeDate.getDateAsFormattedString(props.log.time)}  {" "} + </div> + )} + {props.log.severityText === LogSeverity.Information && ( + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + SEVERITY: + </div> + <div className="text-sky-400 courier-prime">[INFO]  </div> + </div> + )} + {props.log.severityText === LogSeverity.Unspecified && ( + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + SEVERITY: + </div> + <div className="text-sky-400 courier-prime">[UNKNOWN]  </div> + </div> + )} + {props.log.severityText === LogSeverity.Warning && ( + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + SEVERITY: + </div> + <div className="text-amber-400 courier-prime">[WARN]  </div> + </div> + )} + {props.log.severityText === LogSeverity.Trace && ( + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + SEVERITY: + </div> + <div className="text-slate-400 courier-prime">[TRACE]  </div> + </div> + )} + {props.log.severityText === LogSeverity.Debug && ( + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + SEVERITY: + </div> + <div className="text-slate-400 courier-prime">[DEBUG]  </div> + </div> + )} + {props.log.severityText === LogSeverity.Error && ( + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + SEVERITY: + </div> + <div className="text-rose-400 courier-prime">[ERROR]  </div> + </div> + )} + {props.log.severityText === LogSeverity.Fatal && ( + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + SEVERITY: + </div> + <div className="text-rose-400 courier-prime">[FATAL]  </div> + </div> + )} + + <div> + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + MESSAGE:  + </div> + {!isBodyInJSON && ( + <div className={`${bodyColor} courier-prime`}> + {props.log.body?.toString()} + </div> + )} + </div> + {isBodyInJSON && ( + <pre className={`${bodyColor} courier-prime`}>{logBody}</pre> + )} + </div> + + {props.log.traceId && ( + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + TRACE:    + </div> + <div className={`${bodyColor} courier-prime`}> + {props.log.traceId?.toString()} + </div> + {getCopyButton(props.log.traceId?.toString() || "")} + </div> + )} + + {props.log.spanId && ( + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + SPAN:     + </div> + <div className={`${bodyColor} courier-prime`}> + {props.log.spanId?.toString()} + </div> + {getCopyButton(props.log.spanId?.toString() || "")} + </div> + )} + + {props.log.attributes && ( + <div> + <div className="flex"> + <div className="font-medium text-slate-200 courier-prime mr-2"> + ATTRIBUTES: + </div> + </div> + <pre className={`${bodyColor} courier-prime`}> + {JSON.stringify(props.log.attributes, null, 2)} + </pre> + </div> + )} + </div> + ); }; export default LogItem; diff --git a/CommonUI/src/Components/LogsViewer/LogsFilters.tsx b/CommonUI/src/Components/LogsViewer/LogsFilters.tsx index 6feec76c70..c21997fa6a 100644 --- a/CommonUI/src/Components/LogsViewer/LogsFilters.tsx +++ b/CommonUI/src/Components/LogsViewer/LogsFilters.tsx @@ -1,276 +1,232 @@ -import DropdownUtil from '../../Utils/Dropdown'; -import Button, { ButtonStyleType } from '../Button/Button'; +import DropdownUtil from "../../Utils/Dropdown"; +import Button, { ButtonStyleType } from "../Button/Button"; // import TelemetryService from 'Model/Models/TelemetryService'; -import CodeEditor from '../CodeEditor/CodeEditor'; -import Dropdown, { DropdownValue } from '../Dropdown/Dropdown'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import FieldLabelElement from '../Forms/Fields/FieldLabel'; -import Input, { InputType } from '../Input/Input'; -import CodeType from 'Common/Types/Code/CodeType'; -import OneUptimeDate from 'Common/Types/Date'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { LogSeverity } from 'Model/AnalyticsModels/Log'; -import React, { FunctionComponent, ReactElement } from 'react'; +import CodeEditor from "../CodeEditor/CodeEditor"; +import Dropdown, { DropdownValue } from "../Dropdown/Dropdown"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import FieldLabelElement from "../Forms/Fields/FieldLabel"; +import Input, { InputType } from "../Input/Input"; +import CodeType from "Common/Types/Code/CodeType"; +import OneUptimeDate from "Common/Types/Date"; +import IconProp from "Common/Types/Icon/IconProp"; +import { LogSeverity } from "Model/AnalyticsModels/Log"; +import React, { FunctionComponent, ReactElement } from "react"; export interface FilterOption { - searchText?: string | undefined; - logSeverity?: LogSeverity | undefined; - startTime?: Date | undefined; - endTime?: Date | undefined; + searchText?: string | undefined; + logSeverity?: LogSeverity | undefined; + startTime?: Date | undefined; + endTime?: Date | undefined; } export interface ComponentProps { - onFilterChanged: (filterOptions: FilterOption) => void; - onAutoScrollChanged: (turnOnAutoScroll: boolean) => void; - // telemetryServices?: Array<TelemetryService>; + onFilterChanged: (filterOptions: FilterOption) => void; + onAutoScrollChanged: (turnOnAutoScroll: boolean) => void; + // telemetryServices?: Array<TelemetryService>; } const LogsFilters: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [filterOptions, setFilterOptions] = React.useState<FilterOption>({}); + const [filterOptions, setFilterOptions] = React.useState<FilterOption>({}); - const [turnOnAutoScroll, setTurnOnAutoScroll] = - React.useState<boolean>(true); - const [showMoreFilters, setShowMoreFilters] = - React.useState<boolean>(false); - const [isSqlQuery] = React.useState<boolean>(false); + const [turnOnAutoScroll, setTurnOnAutoScroll] = React.useState<boolean>(true); + const [showMoreFilters, setShowMoreFilters] = React.useState<boolean>(false); + const [isSqlQuery] = React.useState<boolean>(false); - const showAutoScrollButton: boolean = - !isSqlQuery && !showMoreFilters && !filterOptions.searchText; - const showSearchButton: boolean = Boolean( - showMoreFilters || filterOptions.searchText - ); + const showAutoScrollButton: boolean = + !isSqlQuery && !showMoreFilters && !filterOptions.searchText; + const showSearchButton: boolean = Boolean( + showMoreFilters || filterOptions.searchText, + ); - return ( - <div className="shadow sm:rounded-md"> - <div className="bg-white py-6 px-4 sm:p-6"> - <div> - <div className="flex space-x-2 justify-between"> - <div className="w-full mr-2"> - {!isSqlQuery && ( - <div className="space-y-4"> - <div> - <FieldLabelElement - title="Search Logs" - required={true} - /> - <Input - placeholder="Search" - onChange={(value: string) => { - setFilterOptions({ - ...filterOptions, - searchText: value, - }); - }} - /> - </div> + return ( + <div className="shadow sm:rounded-md"> + <div className="bg-white py-6 px-4 sm:p-6"> + <div> + <div className="flex space-x-2 justify-between"> + <div className="w-full mr-2"> + {!isSqlQuery && ( + <div className="space-y-4"> + <div> + <FieldLabelElement title="Search Logs" required={true} /> + <Input + placeholder="Search" + onChange={(value: string) => { + setFilterOptions({ + ...filterOptions, + searchText: value, + }); + }} + /> + </div> - {showMoreFilters && ( - <div> - <FieldLabelElement - title="Log Severity" - required={true} - /> - <Dropdown - onChange={( - value: - | DropdownValue - | Array<DropdownValue> - | null - ) => { - if (value === null) { - setFilterOptions({ - ...filterOptions, - logSeverity: - undefined, - }); - } else { - setFilterOptions({ - ...filterOptions, - logSeverity: - value.toString() as LogSeverity, - }); - } - }} - options={DropdownUtil.getDropdownOptionsFromEnum( - LogSeverity - )} - /> - </div> - )} - - {showMoreFilters && ( - <div className="flex space-x-2 w-full"> - <div className="w-1/2"> - <FieldLabelElement - title="Start Time" - required={true} - /> - <Input - placeholder="Start Time" - onChange={( - value: string - ) => { - setFilterOptions({ - ...filterOptions, - startTime: value - ? OneUptimeDate.fromString( - value - ) - : undefined, - }); - }} - type={ - InputType.DATETIME_LOCAL - } - /> - {filterOptions.endTime && - !filterOptions.startTime && ( - <ErrorMessage error="Start Time is required." /> - )} - </div> - <div className="w-1/2"> - <FieldLabelElement - title="End Time" - required={true} - /> - <Input - placeholder="End Time" - onChange={( - value: string - ) => { - setFilterOptions({ - ...filterOptions, - endTime: value - ? OneUptimeDate.fromString( - value - ) - : undefined, - }); - }} - type={ - InputType.DATETIME_LOCAL - } - /> - {filterOptions.startTime && - !filterOptions.endTime && ( - <ErrorMessage error="End Time is required." /> - )} - </div> - </div> - )} - </div> - )} - {isSqlQuery && ( - <div> - <div className="space-y-1"> - <FieldLabelElement - title="SQL Query" - required={true} - description="Enter a SQL query to filter logs." - /> - <CodeEditor - type={CodeType.SQL} - placeholder="SQL Query" - onChange={(value: string) => { - setFilterOptions({ - searchText: value, - }); - }} - showLineNumbers={true} - /> - </div> - </div> - )} - </div> - {showAutoScrollButton && ( - <div> - <div className="mt-7 -ml-5 justify-end flex w-44"> - {!turnOnAutoScroll && ( - <Button - title="Start Autoscroll" - icon={IconProp.Play} - onClick={() => { - setTurnOnAutoScroll(true); - props.onAutoScrollChanged(true); - }} - /> - )} - {turnOnAutoScroll && ( - <Button - title="Stop Autoscroll" - icon={IconProp.Stop} - onClick={() => { - setTurnOnAutoScroll(false); - props.onAutoScrollChanged( - false - ); - }} - /> - )} - </div> - </div> - )} - {isSqlQuery && ( - <div className=""> - <div className="mt-12 -ml-8 justify-end flex w-44"> - <Button - title="Search with SQL" - onClick={() => {}} - /> - </div> - </div> - )} - {showSearchButton && ( - <div className=""> - <div className="mt-7 -ml-20 justify-end flex w-44"> - <Button - title="Search" - onClick={() => { - props.onFilterChanged( - filterOptions - ); - }} - icon={IconProp.Search} - /> - </div> - </div> + {showMoreFilters && ( + <div> + <FieldLabelElement title="Log Severity" required={true} /> + <Dropdown + onChange={( + value: DropdownValue | Array<DropdownValue> | null, + ) => { + if (value === null) { + setFilterOptions({ + ...filterOptions, + logSeverity: undefined, + }); + } else { + setFilterOptions({ + ...filterOptions, + logSeverity: value.toString() as LogSeverity, + }); + } + }} + options={DropdownUtil.getDropdownOptionsFromEnum( + LogSeverity, )} + /> </div> - </div> + )} + {showMoreFilters && ( + <div className="flex space-x-2 w-full"> + <div className="w-1/2"> + <FieldLabelElement title="Start Time" required={true} /> + <Input + placeholder="Start Time" + onChange={(value: string) => { + setFilterOptions({ + ...filterOptions, + startTime: value + ? OneUptimeDate.fromString(value) + : undefined, + }); + }} + type={InputType.DATETIME_LOCAL} + /> + {filterOptions.endTime && !filterOptions.startTime && ( + <ErrorMessage error="Start Time is required." /> + )} + </div> + <div className="w-1/2"> + <FieldLabelElement title="End Time" required={true} /> + <Input + placeholder="End Time" + onChange={(value: string) => { + setFilterOptions({ + ...filterOptions, + endTime: value + ? OneUptimeDate.fromString(value) + : undefined, + }); + }} + type={InputType.DATETIME_LOCAL} + /> + {filterOptions.startTime && !filterOptions.endTime && ( + <ErrorMessage error="End Time is required." /> + )} + </div> + </div> + )} + </div> + )} + {isSqlQuery && ( <div> - <div className="flex justify-between -ml-2 -mr-2"> - <div className="flex"> - {!isSqlQuery && ( - <div> - {!showMoreFilters && ( - <Button - buttonStyle={ - ButtonStyleType.SECONDARY_LINK - } - title="Show More Options" - onClick={() => { - setShowMoreFilters(true); - }} - /> - )} - {showMoreFilters && ( - <Button - buttonStyle={ - ButtonStyleType.SECONDARY_LINK - } - title="Hide More Options" - onClick={() => { - setShowMoreFilters(false); - }} - /> - )} - </div> - )} - <div> - {/* {!isSqlQuery && ( + <div className="space-y-1"> + <FieldLabelElement + title="SQL Query" + required={true} + description="Enter a SQL query to filter logs." + /> + <CodeEditor + type={CodeType.SQL} + placeholder="SQL Query" + onChange={(value: string) => { + setFilterOptions({ + searchText: value, + }); + }} + showLineNumbers={true} + /> + </div> + </div> + )} + </div> + {showAutoScrollButton && ( + <div> + <div className="mt-7 -ml-5 justify-end flex w-44"> + {!turnOnAutoScroll && ( + <Button + title="Start Autoscroll" + icon={IconProp.Play} + onClick={() => { + setTurnOnAutoScroll(true); + props.onAutoScrollChanged(true); + }} + /> + )} + {turnOnAutoScroll && ( + <Button + title="Stop Autoscroll" + icon={IconProp.Stop} + onClick={() => { + setTurnOnAutoScroll(false); + props.onAutoScrollChanged(false); + }} + /> + )} + </div> + </div> + )} + {isSqlQuery && ( + <div className=""> + <div className="mt-12 -ml-8 justify-end flex w-44"> + <Button title="Search with SQL" onClick={() => {}} /> + </div> + </div> + )} + {showSearchButton && ( + <div className=""> + <div className="mt-7 -ml-20 justify-end flex w-44"> + <Button + title="Search" + onClick={() => { + props.onFilterChanged(filterOptions); + }} + icon={IconProp.Search} + /> + </div> + </div> + )} + </div> + </div> + + <div> + <div className="flex justify-between -ml-2 -mr-2"> + <div className="flex"> + {!isSqlQuery && ( + <div> + {!showMoreFilters && ( + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Show More Options" + onClick={() => { + setShowMoreFilters(true); + }} + /> + )} + {showMoreFilters && ( + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Hide More Options" + onClick={() => { + setShowMoreFilters(false); + }} + /> + )} + </div> + )} + <div> + {/* {!isSqlQuery && ( <Button buttonStyle={ ButtonStyleType.SECONDARY_LINK @@ -292,10 +248,10 @@ const LogsFilters: FunctionComponent<ComponentProps> = ( }} /> )} */} - </div> - </div> - <div className="flex"> - {/* <div> + </div> + </div> + <div className="flex"> + {/* <div> <Button buttonStyle={ButtonStyleType.SECONDARY_LINK} title="Save as Preset" @@ -313,12 +269,12 @@ const LogsFilters: FunctionComponent<ComponentProps> = ( }} /> </div> */} - </div> - </div> - </div> </div> + </div> </div> - ); + </div> + </div> + ); }; export default LogsFilters; diff --git a/CommonUI/src/Components/LogsViewer/LogsViewer.tsx b/CommonUI/src/Components/LogsViewer/LogsViewer.tsx index c257d2e8aa..c54dbcabca 100644 --- a/CommonUI/src/Components/LogsViewer/LogsViewer.tsx +++ b/CommonUI/src/Components/LogsViewer/LogsViewer.tsx @@ -1,99 +1,97 @@ -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import LogItem from './LogItem'; -import LogsFilters, { FilterOption } from './LogsFilters'; -import { VoidFunction } from 'Common/Types/FunctionTypes'; -import Log from 'Model/AnalyticsModels/Log'; -import React, { FunctionComponent, ReactElement, Ref } from 'react'; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import LogItem from "./LogItem"; +import LogsFilters, { FilterOption } from "./LogsFilters"; +import { VoidFunction } from "Common/Types/FunctionTypes"; +import Log from "Model/AnalyticsModels/Log"; +import React, { FunctionComponent, ReactElement, Ref } from "react"; export interface ComponentProps { - logs: Array<Log>; - onFilterChanged: (filterOptions: FilterOption) => void; - isLoading: boolean; - showFilters?: boolean | undefined; - noLogsMessage?: string | undefined; + logs: Array<Log>; + onFilterChanged: (filterOptions: FilterOption) => void; + isLoading: boolean; + showFilters?: boolean | undefined; + noLogsMessage?: string | undefined; } const LogsViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [screenHeight, setScreenHeight] = React.useState<number>( - window.innerHeight - ); - const [autoScroll, setAutoScroll] = React.useState<boolean>(true); - const logsViewerRef: Ref<HTMLDivElement> = - React.useRef<HTMLDivElement>(null); + const [screenHeight, setScreenHeight] = React.useState<number>( + window.innerHeight, + ); + const [autoScroll, setAutoScroll] = React.useState<boolean>(true); + const logsViewerRef: Ref<HTMLDivElement> = React.useRef<HTMLDivElement>(null); - // Update the screen height when the window is resized + // Update the screen height when the window is resized - React.useEffect(() => { - const handleResize: any = (): void => { - setScreenHeight(window.innerHeight); - }; - - window.addEventListener('resize', handleResize); - - return () => { - window.removeEventListener('resize', handleResize); - }; - }, []); - - // Keep scroll to the bottom of the log - - const scrollToBottom: VoidFunction = (): void => { - const logsViewer: HTMLDivElement | null = logsViewerRef.current; - - if (logsViewer) { - logsViewer.scrollTop = logsViewer.scrollHeight; - } + React.useEffect(() => { + const handleResize: any = (): void => { + setScreenHeight(window.innerHeight); }; - React.useEffect(() => { - if (!autoScroll) { - return; - } + window.addEventListener("resize", handleResize); - scrollToBottom(); - }, [props.logs]); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); - return ( - <div> - {props.showFilters && ( - <div className="mb-5"> - <LogsFilters - onAutoScrollChanged={(autoscroll: boolean) => { - setAutoScroll(autoscroll); + // Keep scroll to the bottom of the log - if (autoScroll) { - scrollToBottom(); - } - }} - onFilterChanged={props.onFilterChanged} - /> - </div> - )} - {!props.isLoading && ( - <div - ref={logsViewerRef} - className="shadow-xl rounded-xl bg-slate-800 p-5 overflow-hidden hover:overflow-y-auto dark-scrollbar" - style={{ - height: screenHeight - 520, - }} - > - {props.logs.map((log: Log, i: number) => { - return <LogItem key={i} log={log} />; - })} + const scrollToBottom: VoidFunction = (): void => { + const logsViewer: HTMLDivElement | null = logsViewerRef.current; - {props.logs.length === 0 && ( - <div className={`text-slate-200 courier-prime`}> - {props.noLogsMessage || - 'No logs found for this service.'} - </div> - )} - </div> - )} - {props.isLoading && <ComponentLoader />} + if (logsViewer) { + logsViewer.scrollTop = logsViewer.scrollHeight; + } + }; + + React.useEffect(() => { + if (!autoScroll) { + return; + } + + scrollToBottom(); + }, [props.logs]); + + return ( + <div> + {props.showFilters && ( + <div className="mb-5"> + <LogsFilters + onAutoScrollChanged={(autoscroll: boolean) => { + setAutoScroll(autoscroll); + + if (autoScroll) { + scrollToBottom(); + } + }} + onFilterChanged={props.onFilterChanged} + /> </div> - ); + )} + {!props.isLoading && ( + <div + ref={logsViewerRef} + className="shadow-xl rounded-xl bg-slate-800 p-5 overflow-hidden hover:overflow-y-auto dark-scrollbar" + style={{ + height: screenHeight - 520, + }} + > + {props.logs.map((log: Log, i: number) => { + return <LogItem key={i} log={log} />; + })} + + {props.logs.length === 0 && ( + <div className={`text-slate-200 courier-prime`}> + {props.noLogsMessage || "No logs found for this service."} + </div> + )} + </div> + )} + {props.isLoading && <ComponentLoader />} + </div> + ); }; export default LogsViewer; diff --git a/CommonUI/src/Components/Markdown.tsx/LazyMarkdownViewer.tsx b/CommonUI/src/Components/Markdown.tsx/LazyMarkdownViewer.tsx index 0b3b78fabd..5da379e44b 100644 --- a/CommonUI/src/Components/Markdown.tsx/LazyMarkdownViewer.tsx +++ b/CommonUI/src/Components/Markdown.tsx/LazyMarkdownViewer.tsx @@ -1,24 +1,24 @@ -import { ComponentProps } from './MarkdownViewer'; +import { ComponentProps } from "./MarkdownViewer"; import React, { - FunctionComponent, - LazyExoticComponent, - Suspense, - lazy, -} from 'react'; + FunctionComponent, + LazyExoticComponent, + Suspense, + lazy, +} from "react"; const MarkdownViewer: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('./MarkdownViewer'); - }); + lazy(() => { + return import("./MarkdownViewer"); + }); const LazyMarkdownViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): JSX.Element => { - return ( - <Suspense fallback={<div>Loading...</div>}> - <MarkdownViewer {...props} /> - </Suspense> - ); + return ( + <Suspense fallback={<div>Loading...</div>}> + <MarkdownViewer {...props} /> + </Suspense> + ); }; export default LazyMarkdownViewer; diff --git a/CommonUI/src/Components/Markdown.tsx/MarkdownEditor.tsx b/CommonUI/src/Components/Markdown.tsx/MarkdownEditor.tsx index 4531bbf80d..774c89d2f5 100644 --- a/CommonUI/src/Components/Markdown.tsx/MarkdownEditor.tsx +++ b/CommonUI/src/Components/Markdown.tsx/MarkdownEditor.tsx @@ -1,32 +1,32 @@ -import TextArea from '../TextArea/TextArea'; -import React, { FunctionComponent, ReactElement } from 'react'; +import TextArea from "../TextArea/TextArea"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - initialValue?: undefined | string; - placeholder?: undefined | string; - className?: undefined | string; - onChange?: undefined | ((value: string) => void); - onFocus?: (() => void) | undefined; - onBlur?: (() => void) | undefined; - tabIndex?: number | undefined; - error?: string | undefined; + initialValue?: undefined | string; + placeholder?: undefined | string; + className?: undefined | string; + onChange?: undefined | ((value: string) => void); + onFocus?: (() => void) | undefined; + onBlur?: (() => void) | undefined; + tabIndex?: number | undefined; + error?: string | undefined; } const MarkdownEditor: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <TextArea - tabIndex={props.tabIndex} - className={props.className} - initialValue={props.initialValue || ''} - placeholder={props.placeholder} - onChange={props.onChange ? props.onChange : () => {}} - onFocus={props.onFocus ? props.onFocus : () => {}} - onBlur={props.onBlur ? props.onBlur : () => {}} - error={props.error} - /> - ); + return ( + <TextArea + tabIndex={props.tabIndex} + className={props.className} + initialValue={props.initialValue || ""} + placeholder={props.placeholder} + onChange={props.onChange ? props.onChange : () => {}} + onFocus={props.onFocus ? props.onFocus : () => {}} + onBlur={props.onBlur ? props.onBlur : () => {}} + error={props.error} + /> + ); }; export default MarkdownEditor; diff --git a/CommonUI/src/Components/Markdown.tsx/MarkdownViewer.tsx b/CommonUI/src/Components/Markdown.tsx/MarkdownViewer.tsx index 086c389687..606071cb39 100644 --- a/CommonUI/src/Components/Markdown.tsx/MarkdownViewer.tsx +++ b/CommonUI/src/Components/Markdown.tsx/MarkdownViewer.tsx @@ -1,105 +1,95 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; // https://github.com/remarkjs/react-markdown -import ReactMarkdown from 'react-markdown'; +import ReactMarkdown from "react-markdown"; // https://github.com/remarkjs/remark-gfm -import remarkGfm from 'remark-gfm'; +import remarkGfm from "remark-gfm"; export interface ComponentProps { - text: string; + text: string; } const MarkdownViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div> - <ReactMarkdown - components={{ - // because tailwind does not supply <h1 ... /> styles https://tailwindcss.com/docs/preflight#headings-are-unstyled - h1: ({ ...props }: any) => { - return ( - <h1 - className="text-3xl mt-5 border border-gray-200 border-r-0 border-l-0 border-t-0 pb-1 mb-5" - {...props} - /> - ); - }, - h2: ({ ...props }: any) => { - return ( - <h2 - className="text-2xl mt-4 border border-gray-200 border-r-0 border-l-0 border-t-0 pb-1 mb-4" - {...props} - /> - ); - }, - h3: ({ ...props }: any) => { - return <h3 className="text-xl mt-3" {...props} />; - }, - h4: ({ ...props }: any) => { - return <h4 className="text-lg mt-2" {...props} />; - }, - h5: ({ ...props }: any) => { - return <h5 className="text-lg mt-1" {...props} />; - }, - h6: ({ ...props }: any) => { - return <h6 className="text-base " {...props} />; - }, - p: ({ ...props }: any) => { - return ( - <p - className="text-sm mt-1 mb-3 text-gray-500" - {...props} - /> - ); - }, - a: ({ ...props }: any) => { - return ( - <a className="underline text-blue-500" {...props} /> - ); - }, + return ( + <div> + <ReactMarkdown + components={{ + // because tailwind does not supply <h1 ... /> styles https://tailwindcss.com/docs/preflight#headings-are-unstyled + h1: ({ ...props }: any) => { + return ( + <h1 + className="text-3xl mt-5 border border-gray-200 border-r-0 border-l-0 border-t-0 pb-1 mb-5" + {...props} + /> + ); + }, + h2: ({ ...props }: any) => { + return ( + <h2 + className="text-2xl mt-4 border border-gray-200 border-r-0 border-l-0 border-t-0 pb-1 mb-4" + {...props} + /> + ); + }, + h3: ({ ...props }: any) => { + return <h3 className="text-xl mt-3" {...props} />; + }, + h4: ({ ...props }: any) => { + return <h4 className="text-lg mt-2" {...props} />; + }, + h5: ({ ...props }: any) => { + return <h5 className="text-lg mt-1" {...props} />; + }, + h6: ({ ...props }: any) => { + return <h6 className="text-base " {...props} />; + }, + p: ({ ...props }: any) => { + return <p className="text-sm mt-1 mb-3 text-gray-500" {...props} />; + }, + a: ({ ...props }: any) => { + return <a className="underline text-blue-500" {...props} />; + }, - pre: ({ ...props }: any) => { - return ( - <pre - className="bg-gray-50 text-gray-600 p-3 mt-4 mb-2 rounded text-sm text-sm overflow-x-auto" - {...props} - /> - ); - }, - strong: ({ ...props }: any) => { - return ( - <strong - className="text-sm mt-2 text-gray-900 font-medium" - {...props} - /> - ); - }, - li: ({ ...props }: any) => { - return ( - <li - className="text-sm mt-2 text-gray-500 list-disc" - {...props} - /> - ); - }, - ul: ({ ...props }: any) => { - return <ul className="list-disc px-6 m-1" {...props} />; - }, - code: ({ ...props }: any) => { - return ( - <pre - className="bg-gray-50 text-gray-600 p-3 pt-0 pb-0 mt-4 mb-2 rounded text-sm text-sm overflow-x-auto" - {...props} - /> - ); - }, - }} - remarkPlugins={[remarkGfm]} - > - {props.text} - </ReactMarkdown> - </div> - ); + pre: ({ ...props }: any) => { + return ( + <pre + className="bg-gray-50 text-gray-600 p-3 mt-4 mb-2 rounded text-sm text-sm overflow-x-auto" + {...props} + /> + ); + }, + strong: ({ ...props }: any) => { + return ( + <strong + className="text-sm mt-2 text-gray-900 font-medium" + {...props} + /> + ); + }, + li: ({ ...props }: any) => { + return ( + <li className="text-sm mt-2 text-gray-500 list-disc" {...props} /> + ); + }, + ul: ({ ...props }: any) => { + return <ul className="list-disc px-6 m-1" {...props} />; + }, + code: ({ ...props }: any) => { + return ( + <pre + className="bg-gray-50 text-gray-600 p-3 pt-0 pb-0 mt-4 mb-2 rounded text-sm text-sm overflow-x-auto" + {...props} + /> + ); + }, + }} + remarkPlugins={[remarkGfm]} + > + {props.text} + </ReactMarkdown> + </div> + ); }; export default MarkdownViewer; diff --git a/CommonUI/src/Components/MasterPage/MasterPage.tsx b/CommonUI/src/Components/MasterPage/MasterPage.tsx index b23f78da4a..26e00ccc6b 100644 --- a/CommonUI/src/Components/MasterPage/MasterPage.tsx +++ b/CommonUI/src/Components/MasterPage/MasterPage.tsx @@ -1,62 +1,58 @@ -import PageError from '../Error/PageError'; -import PageLoader from '../Loader/PageLoader'; -import TopSection from '../TopSection/TopSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageError from "../Error/PageError"; +import PageLoader from "../Loader/PageLoader"; +import TopSection from "../TopSection/TopSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - header?: undefined | ReactElement; - footer?: undefined | ReactElement; - navBar?: undefined | ReactElement; - children: ReactElement | Array<ReactElement>; - isLoading: boolean; - error: string; - topSectionClassName?: string | undefined; - className?: string | undefined; - hideHeader?: boolean | undefined; - makeTopSectionUnstick?: boolean | undefined; + header?: undefined | ReactElement; + footer?: undefined | ReactElement; + navBar?: undefined | ReactElement; + children: ReactElement | Array<ReactElement>; + isLoading: boolean; + error: string; + topSectionClassName?: string | undefined; + className?: string | undefined; + hideHeader?: boolean | undefined; + makeTopSectionUnstick?: boolean | undefined; } const MasterPage: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.isLoading) { - return ( - <React.Fragment> - <PageLoader isVisible={true} /> - </React.Fragment> - ); - } - - if (props.error) { - return ( - <React.Fragment> - <PageError message={props.error} /> - </React.Fragment> - ); - } - + if (props.isLoading) { return ( - <React.Fragment> - <div className={props.className}> - <div - className={ - props.makeTopSectionUnstick ? '' : 'sticky top-0 z-10' - } - > - <TopSection - hideHeader={props.hideHeader} - className={props.topSectionClassName} - header={props.header} - navbar={props.navBar} - /> - </div> - - {props.children} - - {props.footer && props.footer} - </div> - </React.Fragment> + <React.Fragment> + <PageLoader isVisible={true} /> + </React.Fragment> ); + } + + if (props.error) { + return ( + <React.Fragment> + <PageError message={props.error} /> + </React.Fragment> + ); + } + + return ( + <React.Fragment> + <div className={props.className}> + <div className={props.makeTopSectionUnstick ? "" : "sticky top-0 z-10"}> + <TopSection + hideHeader={props.hideHeader} + className={props.topSectionClassName} + header={props.header} + navbar={props.navBar} + /> + </div> + + {props.children} + + {props.footer && props.footer} + </div> + </React.Fragment> + ); }; export default MasterPage; diff --git a/CommonUI/src/Components/Modal/ConfirmModal.tsx b/CommonUI/src/Components/Modal/ConfirmModal.tsx index 6d59757c34..b0d48f71cd 100644 --- a/CommonUI/src/Components/Modal/ConfirmModal.tsx +++ b/CommonUI/src/Components/Modal/ConfirmModal.tsx @@ -1,55 +1,53 @@ -import { ButtonStyleType } from '../Button/Button'; -import Modal from './Modal'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { ButtonStyleType } from "../Button/Button"; +import Modal from "./Modal"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; - description: string | ReactElement; - onClose?: undefined | (() => void); - submitButtonText?: undefined | string; - onSubmit: () => void; - submitButtonType?: undefined | ButtonStyleType; - closeButtonType?: undefined | ButtonStyleType; - isLoading?: boolean; - error?: string | undefined; - disableSubmitButton?: boolean | undefined; + title: string; + description: string | ReactElement; + onClose?: undefined | (() => void); + submitButtonText?: undefined | string; + onSubmit: () => void; + submitButtonType?: undefined | ButtonStyleType; + closeButtonType?: undefined | ButtonStyleType; + isLoading?: boolean; + error?: string | undefined; + disableSubmitButton?: boolean | undefined; } const ConfirmModal: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Modal - title={props.title} - isLoading={props.isLoading} - onSubmit={props.onSubmit} - onClose={props.onClose ? props.onClose : undefined} - submitButtonText={ - props.submitButtonText ? props.submitButtonText : 'Confirm' - } - closeButtonStyleType={ - props.closeButtonType - ? props.closeButtonType - : ButtonStyleType.NORMAL - } - disableSubmitButton={ - props.disableSubmitButton ? props.disableSubmitButton : false - } - submitButtonStyleType={ - props.submitButtonType - ? props.submitButtonType - : ButtonStyleType.PRIMARY - } - error={props.error} - > - <div - data-testid="confirm-modal-description" - className="text-gray-500 mt-5 text-sm" - > - {props.description} - </div> - </Modal> - ); + return ( + <Modal + title={props.title} + isLoading={props.isLoading} + onSubmit={props.onSubmit} + onClose={props.onClose ? props.onClose : undefined} + submitButtonText={ + props.submitButtonText ? props.submitButtonText : "Confirm" + } + closeButtonStyleType={ + props.closeButtonType ? props.closeButtonType : ButtonStyleType.NORMAL + } + disableSubmitButton={ + props.disableSubmitButton ? props.disableSubmitButton : false + } + submitButtonStyleType={ + props.submitButtonType + ? props.submitButtonType + : ButtonStyleType.PRIMARY + } + error={props.error} + > + <div + data-testid="confirm-modal-description" + className="text-gray-500 mt-5 text-sm" + > + {props.description} + </div> + </Modal> + ); }; export default ConfirmModal; diff --git a/CommonUI/src/Components/Modal/Modal.tsx b/CommonUI/src/Components/Modal/Modal.tsx index 9f5863972c..757d87151f 100644 --- a/CommonUI/src/Components/Modal/Modal.tsx +++ b/CommonUI/src/Components/Modal/Modal.tsx @@ -1,191 +1,181 @@ -import Button, { ButtonStyleType } from '../Button/Button'; -import ButtonType from '../Button/ButtonTypes'; -import Icon, { IconType, SizeProp, ThickProp } from '../Icon/Icon'; -import Loader, { LoaderType } from '../Loader/Loader'; -import ModalBody from './ModalBody'; -import ModalFooter from './ModalFooter'; -import { VeryLightGray } from 'Common/Types/BrandColors'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Button, { ButtonStyleType } from "../Button/Button"; +import ButtonType from "../Button/ButtonTypes"; +import Icon, { IconType, SizeProp, ThickProp } from "../Icon/Icon"; +import Loader, { LoaderType } from "../Loader/Loader"; +import ModalBody from "./ModalBody"; +import ModalFooter from "./ModalFooter"; +import { VeryLightGray } from "Common/Types/BrandColors"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export enum ModalWidth { - Normal, - Medium, - Large, + Normal, + Medium, + Large, } export interface ComponentProps { - title: string; - description?: string | undefined; - children: Array<ReactElement> | ReactElement; - onClose?: undefined | (() => void); - submitButtonText?: undefined | string; - onSubmit: () => void; - submitButtonStyleType?: undefined | ButtonStyleType; - submitButtonType?: undefined | ButtonType; - closeButtonStyleType?: undefined | ButtonStyleType; - isLoading?: undefined | boolean; - disableSubmitButton?: undefined | boolean; - error?: string | undefined; - isBodyLoading?: boolean | undefined; - icon?: IconProp | undefined; - iconType?: IconType | undefined; - modalWidth?: ModalWidth | undefined; - rightElement?: ReactElement | undefined; + title: string; + description?: string | undefined; + children: Array<ReactElement> | ReactElement; + onClose?: undefined | (() => void); + submitButtonText?: undefined | string; + onSubmit: () => void; + submitButtonStyleType?: undefined | ButtonStyleType; + submitButtonType?: undefined | ButtonType; + closeButtonStyleType?: undefined | ButtonStyleType; + isLoading?: undefined | boolean; + disableSubmitButton?: undefined | boolean; + error?: string | undefined; + isBodyLoading?: boolean | undefined; + icon?: IconProp | undefined; + iconType?: IconType | undefined; + modalWidth?: ModalWidth | undefined; + rightElement?: ReactElement | undefined; } const Modal: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let iconBgColor: string = 'bg-indigo-100'; + let iconBgColor: string = "bg-indigo-100"; - if (props.iconType === IconType.Info) { - iconBgColor = 'bg-indigo-100'; - } else if (props.iconType === IconType.Warning) { - iconBgColor = 'bg-yellow-100'; - } else if (props.iconType === IconType.Success) { - iconBgColor = 'bg-green-100'; - } else if (props.iconType === IconType.Danger) { - iconBgColor = 'bg-red-100'; - } + if (props.iconType === IconType.Info) { + iconBgColor = "bg-indigo-100"; + } else if (props.iconType === IconType.Warning) { + iconBgColor = "bg-yellow-100"; + } else if (props.iconType === IconType.Success) { + iconBgColor = "bg-green-100"; + } else if (props.iconType === IconType.Danger) { + iconBgColor = "bg-red-100"; + } - return ( - <div - className="relative z-20" - aria-labelledby="modal-title" - role="dialog" - aria-modal="true" - > - <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div> + return ( + <div + className="relative z-20" + aria-labelledby="modal-title" + role="dialog" + aria-modal="true" + > + <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div> - <div className="fixed inset-0 z-20 overflow-y-auto"> - <div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> - <div - className={`relative transform rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full ${ - props.modalWidth && - props.modalWidth === ModalWidth.Large - ? 'sm:max-w-7xl' - : '' - } ${ - props.modalWidth && - props.modalWidth === ModalWidth.Medium - ? 'sm:max-w-3xl' - : '' - } ${!props.modalWidth ? 'sm:max-w-lg' : ''} `} - data-testid="modal" - > - {props.onClose && ( - <div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block"> - <Button - buttonStyle={ButtonStyleType.ICON} - icon={IconProp.Close} - iconSize={SizeProp.Large} - title="Close" - dataTestId="close-button" - onClick={props.onClose} - /> - </div> - )} - <div className="sm:p-6"> - {props.icon && ( - <div - className={`mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full ${iconBgColor} sm:mx-0 sm:h-10 sm:w-10`} - data-testid="icon" - > - <Icon - thick={ThickProp.Thick} - type={ - props.iconType === undefined - ? IconType.Info - : props.iconType - } - className={ - 'text-red-600 h-6 w-6 stroke-2' - } - icon={props.icon} - size={SizeProp.Large} - /> - </div> - )} - <div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:mr-4 sm:text-left"> - <div className="flex justify-between"> - <div> - <h3 - data-testid="modal-title" - className={`text-lg font-medium leading-6 text-gray-900 ${ - props.icon - ? 'ml-10 -mt-8 mb-5' - : '' - }`} - id="modal-title" - > - {props.title} - </h3> - {props.description && ( - <h3 - data-testid="modal-description" - className="text-sm leading-6 text-gray-500" - > - {props.description} - </h3> - )} - </div> - {props.rightElement && ( - <div data-testid="right-element"> - {props.rightElement} - </div> - )} - </div> - <div className="mt-2"> - <ModalBody error={props.error}> - {!props.isBodyLoading ? ( - props.children - ) : ( - <div className="modal-body mt-20 mb-20 flex justify-center"> - <Loader - loaderType={LoaderType.Bar} - color={VeryLightGray} - size={200} - /> - </div> - )} - </ModalBody> - </div> - </div> - </div> - <ModalFooter - submitButtonType={ - props.submitButtonType - ? props.submitButtonType - : ButtonType.Button - } - submitButtonStyleType={ - props.submitButtonStyleType - ? props.submitButtonStyleType - : ButtonStyleType.PRIMARY - } - closeButtonStyleType={ - props.closeButtonStyleType - ? props.closeButtonStyleType - : ButtonStyleType.NORMAL - } - submitButtonText={ - props.submitButtonText - ? props.submitButtonText - : 'Save' - } - onSubmit={props.onSubmit} - onClose={props.onClose ? props.onClose : undefined} - isLoading={props.isLoading || false} - disableSubmitButton={ - props.isBodyLoading || props.disableSubmitButton - } - /> - </div> + <div className="fixed inset-0 z-20 overflow-y-auto"> + <div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"> + <div + className={`relative transform rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full ${ + props.modalWidth && props.modalWidth === ModalWidth.Large + ? "sm:max-w-7xl" + : "" + } ${ + props.modalWidth && props.modalWidth === ModalWidth.Medium + ? "sm:max-w-3xl" + : "" + } ${!props.modalWidth ? "sm:max-w-lg" : ""} `} + data-testid="modal" + > + {props.onClose && ( + <div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block"> + <Button + buttonStyle={ButtonStyleType.ICON} + icon={IconProp.Close} + iconSize={SizeProp.Large} + title="Close" + dataTestId="close-button" + onClick={props.onClose} + /> + </div> + )} + <div className="sm:p-6"> + {props.icon && ( + <div + className={`mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full ${iconBgColor} sm:mx-0 sm:h-10 sm:w-10`} + data-testid="icon" + > + <Icon + thick={ThickProp.Thick} + type={ + props.iconType === undefined + ? IconType.Info + : props.iconType + } + className={"text-red-600 h-6 w-6 stroke-2"} + icon={props.icon} + size={SizeProp.Large} + /> </div> + )} + <div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:mr-4 sm:text-left"> + <div className="flex justify-between"> + <div> + <h3 + data-testid="modal-title" + className={`text-lg font-medium leading-6 text-gray-900 ${ + props.icon ? "ml-10 -mt-8 mb-5" : "" + }`} + id="modal-title" + > + {props.title} + </h3> + {props.description && ( + <h3 + data-testid="modal-description" + className="text-sm leading-6 text-gray-500" + > + {props.description} + </h3> + )} + </div> + {props.rightElement && ( + <div data-testid="right-element">{props.rightElement}</div> + )} + </div> + <div className="mt-2"> + <ModalBody error={props.error}> + {!props.isBodyLoading ? ( + props.children + ) : ( + <div className="modal-body mt-20 mb-20 flex justify-center"> + <Loader + loaderType={LoaderType.Bar} + color={VeryLightGray} + size={200} + /> + </div> + )} + </ModalBody> + </div> + </div> </div> + <ModalFooter + submitButtonType={ + props.submitButtonType + ? props.submitButtonType + : ButtonType.Button + } + submitButtonStyleType={ + props.submitButtonStyleType + ? props.submitButtonStyleType + : ButtonStyleType.PRIMARY + } + closeButtonStyleType={ + props.closeButtonStyleType + ? props.closeButtonStyleType + : ButtonStyleType.NORMAL + } + submitButtonText={ + props.submitButtonText ? props.submitButtonText : "Save" + } + onSubmit={props.onSubmit} + onClose={props.onClose ? props.onClose : undefined} + isLoading={props.isLoading || false} + disableSubmitButton={ + props.isBodyLoading || props.disableSubmitButton + } + /> + </div> </div> - ); + </div> + </div> + ); }; export default Modal; diff --git a/CommonUI/src/Components/Modal/ModalBody.tsx b/CommonUI/src/Components/Modal/ModalBody.tsx index a9cf9a173e..393ce423cb 100644 --- a/CommonUI/src/Components/Modal/ModalBody.tsx +++ b/CommonUI/src/Components/Modal/ModalBody.tsx @@ -1,22 +1,20 @@ -import Alert, { AlertType } from '../Alerts/Alert'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Alert, { AlertType } from "../Alerts/Alert"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: Array<ReactElement> | ReactElement; - error?: string | undefined; + children: Array<ReactElement> | ReactElement; + error?: string | undefined; } const ModalBody: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="modal-body"> - {props.error && ( - <Alert title={props.error} type={AlertType.DANGER} /> - )} - {props.children} - </div> - ); + return ( + <div className="modal-body"> + {props.error && <Alert title={props.error} type={AlertType.DANGER} />} + {props.children} + </div> + ); }; export default ModalBody; diff --git a/CommonUI/src/Components/Modal/ModalFooter.tsx b/CommonUI/src/Components/Modal/ModalFooter.tsx index c08a12e96b..ac560d3f36 100644 --- a/CommonUI/src/Components/Modal/ModalFooter.tsx +++ b/CommonUI/src/Components/Modal/ModalFooter.tsx @@ -1,70 +1,66 @@ -import Button, { ButtonStyleType } from '../Button/Button'; -import ButtonType from '../Button/ButtonTypes'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Button, { ButtonStyleType } from "../Button/Button"; +import ButtonType from "../Button/ButtonTypes"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onClose?: undefined | (() => void) | undefined; - submitButtonText?: undefined | string; - onSubmit: () => void; - submitButtonStyleType?: undefined | ButtonStyleType; - closeButtonStyleType?: undefined | ButtonStyleType; - submitButtonType?: undefined | ButtonType; - isLoading?: undefined | boolean; - disableSubmitButton?: undefined | boolean; + onClose?: undefined | (() => void) | undefined; + submitButtonText?: undefined | string; + onSubmit: () => void; + submitButtonStyleType?: undefined | ButtonStyleType; + closeButtonStyleType?: undefined | ButtonStyleType; + submitButtonType?: undefined | ButtonType; + isLoading?: undefined | boolean; + disableSubmitButton?: undefined | boolean; } const ModalFooter: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"> - {props.onSubmit ? ( - <Button - buttonStyle={ - props.submitButtonStyleType - ? props.submitButtonStyleType - : ButtonStyleType.PRIMARY - } - title={ - props.submitButtonText - ? props.submitButtonText - : 'Save Changes' - } - onClick={() => { - props.onSubmit(); - }} - disabled={props.disableSubmitButton || false} - isLoading={props.isLoading || false} - type={ - props.submitButtonType - ? props.submitButtonType - : ButtonType.Button - } - dataTestId="modal-footer-submit-button" - /> - ) : ( - <></> - )} + return ( + <div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"> + {props.onSubmit ? ( + <Button + buttonStyle={ + props.submitButtonStyleType + ? props.submitButtonStyleType + : ButtonStyleType.PRIMARY + } + title={ + props.submitButtonText ? props.submitButtonText : "Save Changes" + } + onClick={() => { + props.onSubmit(); + }} + disabled={props.disableSubmitButton || false} + isLoading={props.isLoading || false} + type={ + props.submitButtonType ? props.submitButtonType : ButtonType.Button + } + dataTestId="modal-footer-submit-button" + /> + ) : ( + <></> + )} - {props.onClose ? ( - <Button - buttonStyle={ - props.closeButtonStyleType - ? props.closeButtonStyleType - : ButtonStyleType.NORMAL - } - title={'Close'} - data-dismiss="modal" - onClick={() => { - props.onClose && props.onClose(); - }} - dataTestId="modal-footer-close-button" - /> - ) : ( - <></> - )} - </div> - ); + {props.onClose ? ( + <Button + buttonStyle={ + props.closeButtonStyleType + ? props.closeButtonStyleType + : ButtonStyleType.NORMAL + } + title={"Close"} + data-dismiss="modal" + onClick={() => { + props.onClose && props.onClose(); + }} + dataTestId="modal-footer-close-button" + /> + ) : ( + <></> + )} + </div> + ); }; export default ModalFooter; diff --git a/CommonUI/src/Components/ModelDelete/ModelDelete.tsx b/CommonUI/src/Components/ModelDelete/ModelDelete.tsx index ec1a76bc24..3855dd7f7e 100644 --- a/CommonUI/src/Components/ModelDelete/ModelDelete.tsx +++ b/CommonUI/src/Components/ModelDelete/ModelDelete.tsx @@ -1,99 +1,99 @@ -import API from '../../Utils/API/API'; -import ModelAPI from '../../Utils/ModelAPI/ModelAPI'; -import { ButtonStyleType } from '../Button/Button'; -import Card from '../Card/Card'; -import ConfirmModal from '../Modal/ConfirmModal'; -import BaseModel from 'Common/Models/BaseModel'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { ReactElement, useState } from 'react'; +import API from "../../Utils/API/API"; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import { ButtonStyleType } from "../Button/Button"; +import Card from "../Card/Card"; +import ConfirmModal from "../Modal/ConfirmModal"; +import BaseModel from "Common/Models/BaseModel"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import React, { ReactElement, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - modelType: { new (): TBaseModel }; - modelId: ObjectID; - onDeleteSuccess: () => void; + modelType: { new (): TBaseModel }; + modelId: ObjectID; + onDeleteSuccess: () => void; } const ModelDelete: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const model: TBaseModel = new props.modelType(); - const [showModal, setShowModal] = useState<boolean>(false); - const [isLoading, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [showErrorModal, setShowErrorModal] = useState<boolean>(false); + const model: TBaseModel = new props.modelType(); + const [showModal, setShowModal] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [showErrorModal, setShowErrorModal] = useState<boolean>(false); - const deleteItem: PromiseVoidFunction = async (): Promise<void> => { - setIsLoading(true); - try { - await ModelAPI.deleteItem<TBaseModel>({ - modelType: props.modelType, - id: props.modelId, - }); - props.onDeleteSuccess && props.onDeleteSuccess(); - } catch (err) { - setError(API.getFriendlyMessage(err)); - setShowErrorModal(true); - } + const deleteItem: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); + try { + await ModelAPI.deleteItem<TBaseModel>({ + modelType: props.modelType, + id: props.modelId, + }); + props.onDeleteSuccess && props.onDeleteSuccess(); + } catch (err) { + setError(API.getFriendlyMessage(err)); + setShowErrorModal(true); + } - setIsLoading(false); - }; + setIsLoading(false); + }; - return ( - <> - <Card - title={`Delete ${model.singularName}`} - description={`Are you sure you want to delete this ${model.singularName?.toLowerCase()}?`} - buttons={[ - { - title: `Delete ${model.singularName}`, - buttonStyle: ButtonStyleType.DANGER, - onClick: () => { - setShowModal(true); - }, - isLoading: isLoading, - icon: IconProp.Trash, - }, - ]} - /> + return ( + <> + <Card + title={`Delete ${model.singularName}`} + description={`Are you sure you want to delete this ${model.singularName?.toLowerCase()}?`} + buttons={[ + { + title: `Delete ${model.singularName}`, + buttonStyle: ButtonStyleType.DANGER, + onClick: () => { + setShowModal(true); + }, + isLoading: isLoading, + icon: IconProp.Trash, + }, + ]} + /> - {showModal ? ( - <ConfirmModal - description={`Are you sure you want to delete this ${model.singularName?.toLowerCase()}?`} - title={`Delete ${model.singularName}`} - onSubmit={async () => { - setShowModal(false); - await deleteItem(); - }} - onClose={() => { - setShowModal(false); - }} - submitButtonText={`Delete ${model.singularName}`} - submitButtonType={ButtonStyleType.DANGER} - /> - ) : ( - <></> - )} + {showModal ? ( + <ConfirmModal + description={`Are you sure you want to delete this ${model.singularName?.toLowerCase()}?`} + title={`Delete ${model.singularName}`} + onSubmit={async () => { + setShowModal(false); + await deleteItem(); + }} + onClose={() => { + setShowModal(false); + }} + submitButtonText={`Delete ${model.singularName}`} + submitButtonType={ButtonStyleType.DANGER} + /> + ) : ( + <></> + )} - {showErrorModal ? ( - <ConfirmModal - description={error} - title={`Delete Error`} - onSubmit={() => { - setShowErrorModal(false); - setError(''); - }} - submitButtonText={`Close`} - submitButtonType={ButtonStyleType.NORMAL} - /> - ) : ( - <></> - )} - </> - ); + {showErrorModal ? ( + <ConfirmModal + description={error} + title={`Delete Error`} + onSubmit={() => { + setShowErrorModal(false); + setError(""); + }} + submitButtonText={`Close`} + submitButtonType={ButtonStyleType.NORMAL} + /> + ) : ( + <></> + )} + </> + ); }; export default ModelDelete; diff --git a/CommonUI/src/Components/ModelDetail/CardModelDetail.tsx b/CommonUI/src/Components/ModelDetail/CardModelDetail.tsx index 2bd65bb82d..3e86f9cc77 100644 --- a/CommonUI/src/Components/ModelDetail/CardModelDetail.tsx +++ b/CommonUI/src/Components/ModelDetail/CardModelDetail.tsx @@ -1,141 +1,139 @@ -import PermissionUtil from '../../Utils/Permission'; -import User from '../../Utils/User'; -import { ButtonStyleType } from '../Button/Button'; +import PermissionUtil from "../../Utils/Permission"; +import User from "../../Utils/User"; +import { ButtonStyleType } from "../Button/Button"; import Card, { - CardButtonSchema, - ComponentProps as CardProps, -} from '../Card/Card'; -import { FormType } from '../Forms/ModelForm'; -import Fields from '../Forms/Types/Fields'; -import { FormStep } from '../Forms/Types/FormStep'; -import { ModalWidth } from '../Modal/Modal'; -import ModelFormModal from '../ModelFormModal/ModelFormModal'; -import ModelDetail, { ComponentProps as ModeDetailProps } from './ModelDetail'; -import BaseModel from 'Common/Models/BaseModel'; -import IconProp from 'Common/Types/Icon/IconProp'; + CardButtonSchema, + ComponentProps as CardProps, +} from "../Card/Card"; +import { FormType } from "../Forms/ModelForm"; +import Fields from "../Forms/Types/Fields"; +import { FormStep } from "../Forms/Types/FormStep"; +import { ModalWidth } from "../Modal/Modal"; +import ModelFormModal from "../ModelFormModal/ModelFormModal"; +import ModelDetail, { ComponentProps as ModeDetailProps } from "./ModelDetail"; +import BaseModel from "Common/Models/BaseModel"; +import IconProp from "Common/Types/Icon/IconProp"; import { - PermissionHelper, - UserPermission, - UserTenantAccessPermission, -} from 'Common/Types/Permission'; -import React, { ReactElement, useEffect, useState } from 'react'; + PermissionHelper, + UserPermission, + UserTenantAccessPermission, +} from "Common/Types/Permission"; +import React, { ReactElement, useEffect, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - cardProps: CardProps; - modelDetailProps: ModeDetailProps<TBaseModel>; - isEditable?: undefined | boolean; - onSaveSuccess?: undefined | ((item: TBaseModel) => void); - editButtonText?: undefined | string; - formSteps?: undefined | Array<FormStep<TBaseModel>>; - formFields?: undefined | Fields<TBaseModel>; - className?: string | undefined; - name: string; - createEditModalWidth?: ModalWidth | undefined; - refresher?: boolean; + cardProps: CardProps; + modelDetailProps: ModeDetailProps<TBaseModel>; + isEditable?: undefined | boolean; + onSaveSuccess?: undefined | ((item: TBaseModel) => void); + editButtonText?: undefined | string; + formSteps?: undefined | Array<FormStep<TBaseModel>>; + formFields?: undefined | Fields<TBaseModel>; + className?: string | undefined; + name: string; + createEditModalWidth?: ModalWidth | undefined; + refresher?: boolean; } const CardModelDetail: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [cardButtons, setCardButtons] = useState<Array<CardButtonSchema>>([]); - const [showModel, setShowModal] = useState<boolean>(false); - const [item, setItem] = useState<TBaseModel | null>(null); - const [refresher, setRefresher] = useState<boolean>(false); - const model: TBaseModel = new props.modelDetailProps.modelType(); + const [cardButtons, setCardButtons] = useState<Array<CardButtonSchema>>([]); + const [showModel, setShowModal] = useState<boolean>(false); + const [item, setItem] = useState<TBaseModel | null>(null); + const [refresher, setRefresher] = useState<boolean>(false); + const model: TBaseModel = new props.modelDetailProps.modelType(); - useEffect(() => { - setRefresher(!refresher); - }, [props.refresher]); + useEffect(() => { + setRefresher(!refresher); + }, [props.refresher]); - useEffect(() => { - const userProjectPermissions: UserTenantAccessPermission | null = - PermissionUtil.getProjectPermissions(); + useEffect(() => { + const userProjectPermissions: UserTenantAccessPermission | null = + PermissionUtil.getProjectPermissions(); - const hasPermissionToEdit: boolean = - Boolean( - userProjectPermissions && - userProjectPermissions.permissions && - PermissionHelper.doesPermissionsIntersect( - model.updateRecordPermissions, - userProjectPermissions.permissions.map( - (item: UserPermission) => { - return item.permission; - } - ) - ) - ) || User.isMasterAdmin(); + const hasPermissionToEdit: boolean = + Boolean( + userProjectPermissions && + userProjectPermissions.permissions && + PermissionHelper.doesPermissionsIntersect( + model.updateRecordPermissions, + userProjectPermissions.permissions.map((item: UserPermission) => { + return item.permission; + }), + ), + ) || User.isMasterAdmin(); - let cardButtons: Array<CardButtonSchema> = []; + let cardButtons: Array<CardButtonSchema> = []; - if (props.isEditable && hasPermissionToEdit) { - cardButtons.push({ - title: props.editButtonText || `Edit ${model.singularName}`, - buttonStyle: ButtonStyleType.NORMAL, - onClick: () => { - setShowModal(true); - }, - icon: IconProp.Edit, - }); - } + if (props.isEditable && hasPermissionToEdit) { + cardButtons.push({ + title: props.editButtonText || `Edit ${model.singularName}`, + buttonStyle: ButtonStyleType.NORMAL, + onClick: () => { + setShowModal(true); + }, + icon: IconProp.Edit, + }); + } - if (props.cardProps.buttons) { - cardButtons = cardButtons.concat(...props.cardProps.buttons); - } + if (props.cardProps.buttons) { + cardButtons = cardButtons.concat(...props.cardProps.buttons); + } - setCardButtons(cardButtons); - }, []); + setCardButtons(cardButtons); + }, []); - return ( - <> - <Card {...props.cardProps} buttons={cardButtons}> - <div className="border-t border-gray-200 px-4 py-5 sm:px-6 -m-6 -mt-2"> - <ModelDetail - refresher={refresher} - {...props.modelDetailProps} - onItemLoaded={(item: TBaseModel) => { - setItem(item); - if (props.modelDetailProps.onItemLoaded) { - props.modelDetailProps.onItemLoaded(item); - } - }} - /> - </div> - </Card> + return ( + <> + <Card {...props.cardProps} buttons={cardButtons}> + <div className="border-t border-gray-200 px-4 py-5 sm:px-6 -m-6 -mt-2"> + <ModelDetail + refresher={refresher} + {...props.modelDetailProps} + onItemLoaded={(item: TBaseModel) => { + setItem(item); + if (props.modelDetailProps.onItemLoaded) { + props.modelDetailProps.onItemLoaded(item); + } + }} + /> + </div> + </Card> - {showModel ? ( - <ModelFormModal<TBaseModel> - title={`Edit ${model.singularName}`} - modalWidth={props.createEditModalWidth} - onClose={() => { - setShowModal(false); - }} - submitButtonText={`Save Changes`} - onSuccess={(item: TBaseModel) => { - setShowModal(false); - setRefresher(!refresher); - if (props.onSaveSuccess) { - props.onSaveSuccess(item); - } - }} - name={props.name} - modelType={props.modelDetailProps.modelType} - formProps={{ - id: `edit-${model.singularName?.toLowerCase()}-from`, - fields: props.formFields || [], - name: props.name, - formType: FormType.Update, - modelType: props.modelDetailProps.modelType, - steps: props.formSteps || [], - }} - modelIdToEdit={item?.id || undefined} - /> - ) : ( - <></> - )} - </> - ); + {showModel ? ( + <ModelFormModal<TBaseModel> + title={`Edit ${model.singularName}`} + modalWidth={props.createEditModalWidth} + onClose={() => { + setShowModal(false); + }} + submitButtonText={`Save Changes`} + onSuccess={(item: TBaseModel) => { + setShowModal(false); + setRefresher(!refresher); + if (props.onSaveSuccess) { + props.onSaveSuccess(item); + } + }} + name={props.name} + modelType={props.modelDetailProps.modelType} + formProps={{ + id: `edit-${model.singularName?.toLowerCase()}-from`, + fields: props.formFields || [], + name: props.name, + formType: FormType.Update, + modelType: props.modelDetailProps.modelType, + steps: props.formSteps || [], + }} + modelIdToEdit={item?.id || undefined} + /> + ) : ( + <></> + )} + </> + ); }; export default CardModelDetail; diff --git a/CommonUI/src/Components/ModelDetail/Field.ts b/CommonUI/src/Components/ModelDetail/Field.ts index 36b7270b1c..03c8cc06f2 100644 --- a/CommonUI/src/Components/ModelDetail/Field.ts +++ b/CommonUI/src/Components/ModelDetail/Field.ts @@ -1,8 +1,8 @@ -import Select from '../../Utils/BaseDatabase/Select'; -import { FieldBase } from '../Detail/Field'; -import BaseModel from 'Common/Models/BaseModel'; +import Select from "../../Utils/BaseDatabase/Select"; +import { FieldBase } from "../Detail/Field"; +import BaseModel from "Common/Models/BaseModel"; export default interface Field<TBaseModel extends BaseModel> - extends FieldBase<TBaseModel> { - field?: Select<TBaseModel> | undefined; + extends FieldBase<TBaseModel> { + field?: Select<TBaseModel> | undefined; } diff --git a/CommonUI/src/Components/ModelDetail/ModelDetail.tsx b/CommonUI/src/Components/ModelDetail/ModelDetail.tsx index 45ae3ecb1c..812ed0c499 100644 --- a/CommonUI/src/Components/ModelDetail/ModelDetail.tsx +++ b/CommonUI/src/Components/ModelDetail/ModelDetail.tsx @@ -1,275 +1,259 @@ -import API from '../../Utils/API/API'; -import Select from '../../Utils/BaseDatabase/Select'; -import ModelAPI from '../../Utils/ModelAPI/ModelAPI'; -import PermissionUtil from '../../Utils/Permission'; -import User from '../../Utils/User'; -import Detail from '../Detail/Detail'; -import DetailField from '../Detail/Field'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import Loader, { LoaderType } from '../Loader/Loader'; -import Field from './Field'; -import BaseModel from 'Common/Models/BaseModel'; -import { ColumnAccessControl } from 'Common/Types/BaseDatabase/AccessControl'; -import { VeryLightGray } from 'Common/Types/BrandColors'; -import Dictionary from 'Common/Types/Dictionary'; -import { PromiseVoidFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission, { PermissionHelper } from 'Common/Types/Permission'; -import React, { ReactElement, useEffect, useState } from 'react'; -import { useAsyncEffect } from 'use-async-effect'; +import API from "../../Utils/API/API"; +import Select from "../../Utils/BaseDatabase/Select"; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import PermissionUtil from "../../Utils/Permission"; +import User from "../../Utils/User"; +import Detail from "../Detail/Detail"; +import DetailField from "../Detail/Field"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import Loader, { LoaderType } from "../Loader/Loader"; +import Field from "./Field"; +import BaseModel from "Common/Models/BaseModel"; +import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl"; +import { VeryLightGray } from "Common/Types/BrandColors"; +import Dictionary from "Common/Types/Dictionary"; +import { PromiseVoidFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission, { PermissionHelper } from "Common/Types/Permission"; +import React, { ReactElement, useEffect, useState } from "react"; +import { useAsyncEffect } from "use-async-effect"; export interface ComponentProps<TBaseModel extends BaseModel> { - modelType: { new (): TBaseModel }; - id: string; - fields: Array<Field<TBaseModel>>; - onLoadingChange?: undefined | ((isLoading: boolean) => void); - modelId: ObjectID; - onError?: ((error: string) => void) | undefined; - onItemLoaded?: (item: TBaseModel) => void | undefined; - refresher?: undefined | boolean; - showDetailsInNumberOfColumns?: number | undefined; - onBeforeFetch?: (() => Promise<JSONObject>) | undefined; - selectMoreFields?: Select<TBaseModel>; + modelType: { new (): TBaseModel }; + id: string; + fields: Array<Field<TBaseModel>>; + onLoadingChange?: undefined | ((isLoading: boolean) => void); + modelId: ObjectID; + onError?: ((error: string) => void) | undefined; + onItemLoaded?: (item: TBaseModel) => void | undefined; + refresher?: undefined | boolean; + showDetailsInNumberOfColumns?: number | undefined; + onBeforeFetch?: (() => Promise<JSONObject>) | undefined; + selectMoreFields?: Select<TBaseModel>; } const ModelDetail: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [fields, setFields] = useState<Array<DetailField<TBaseModel>>>([]); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); - const [item, setItem] = useState<TBaseModel | null>(null); + const [fields, setFields] = useState<Array<DetailField<TBaseModel>>>([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [item, setItem] = useState<TBaseModel | null>(null); - const [onBeforeFetchData, setOnBeforeFetchData] = useState< - JSONObject | undefined - >(undefined); + const [onBeforeFetchData, setOnBeforeFetchData] = useState< + JSONObject | undefined + >(undefined); - type GetSelectFields = () => Select<TBaseModel>; + type GetSelectFields = () => Select<TBaseModel>; - const getSelectFields: GetSelectFields = (): Select<TBaseModel> => { - const select: Select<TBaseModel> = {}; - for (const field of props.fields) { - const key: string | null = field.field - ? (Object.keys(field.field)[0] as string) - : null; + const getSelectFields: GetSelectFields = (): Select<TBaseModel> => { + const select: Select<TBaseModel> = {}; + for (const field of props.fields) { + const key: string | null = field.field + ? (Object.keys(field.field)[0] as string) + : null; - if (key) { - (select as Dictionary<boolean>)[key] = true; - } + if (key) { + (select as Dictionary<boolean>)[key] = true; + } + } + + for (const field of Object.keys(props.selectMoreFields || {})) { + (select as Dictionary<boolean>)[field] = true; + } + + return select; + }; + + type GetRelationSelectFunction = () => Select<TBaseModel>; + + const getRelationSelect: GetRelationSelectFunction = + (): Select<TBaseModel> => { + const relationSelect: Select<TBaseModel> = {}; + + for (const field of props.fields || []) { + const key: string | null = field.field + ? (Object.keys(field.field)[0] as string) + : null; + + if (key && new props.modelType()?.isFileColumn(key)) { + (relationSelect as JSONObject)[key] = { + file: true, + _id: true, + type: true, + name: true, + }; + } else if (key && new props.modelType()?.isEntityColumn(key)) { + (relationSelect as JSONObject)[key] = (field.field as any)[key]; } + } - for (const field of Object.keys(props.selectMoreFields || {})) { - (select as Dictionary<boolean>)[field] = true; - } - - return select; + return relationSelect; }; - type GetRelationSelectFunction = () => Select<TBaseModel>; + const setDetailFields: VoidFunction = (): void => { + // set fields. - const getRelationSelect: GetRelationSelectFunction = - (): Select<TBaseModel> => { - const relationSelect: Select<TBaseModel> = {}; + const userPermissions: Array<Permission> = + PermissionUtil.getAllPermissions(); - for (const field of props.fields || []) { - const key: string | null = field.field - ? (Object.keys(field.field)[0] as string) - : null; + const model: BaseModel = new props.modelType(); - if (key && new props.modelType()?.isFileColumn(key)) { - (relationSelect as JSONObject)[key] = { - file: true, - _id: true, - type: true, - name: true, - }; - } else if (key && new props.modelType()?.isEntityColumn(key)) { - (relationSelect as JSONObject)[key] = (field.field as any)[ - key - ]; + const accessControl: Dictionary<ColumnAccessControl> = + model.getColumnAccessControlForAllColumns() || {}; + + const fieldsToSet: Array<DetailField<TBaseModel>> = []; + + for (const field of props.fields) { + const keys: Array<string> = Object.keys(field.field ? field.field : {}); + + if (keys.length > 0) { + const key: keyof TBaseModel = keys[0] as keyof TBaseModel; + + let fieldPermissions: Array<Permission> = []; + + fieldPermissions = accessControl[key]?.read || []; + + const hasPermissions: boolean = + fieldPermissions && + PermissionHelper.doesPermissionsIntersect( + userPermissions, + fieldPermissions, + ); + + if (hasPermissions || User.isMasterAdmin()) { + fieldsToSet.push({ + ...field, + key: key, + getElement: field.getElement + ? (item: TBaseModel): ReactElement => { + return field.getElement!(item, onBeforeFetchData, fetchItem); } - } - - return relationSelect; - }; - - const setDetailFields: VoidFunction = (): void => { - // set fields. - - const userPermissions: Array<Permission> = - PermissionUtil.getAllPermissions(); - - const model: BaseModel = new props.modelType(); - - const accessControl: Dictionary<ColumnAccessControl> = - model.getColumnAccessControlForAllColumns() || {}; - - const fieldsToSet: Array<DetailField<TBaseModel>> = []; - - for (const field of props.fields) { - const keys: Array<string> = Object.keys( - field.field ? field.field : {} - ); - - if (keys.length > 0) { - const key: keyof TBaseModel = keys[0] as keyof TBaseModel; - - let fieldPermissions: Array<Permission> = []; - - fieldPermissions = accessControl[key]?.read || []; - - const hasPermissions: boolean = - fieldPermissions && - PermissionHelper.doesPermissionsIntersect( - userPermissions, - fieldPermissions - ); - - if (hasPermissions || User.isMasterAdmin()) { - fieldsToSet.push({ - ...field, - key: key, - getElement: field.getElement - ? (item: TBaseModel): ReactElement => { - return field.getElement!( - item, - onBeforeFetchData, - fetchItem - ); - } - : undefined, - }); - } - } else { - fieldsToSet.push({ - ...field, - key: null, - getElement: field.getElement - ? (item: TBaseModel): ReactElement => { - return field.getElement!( - item, - onBeforeFetchData, - fetchItem - ); - } - : undefined, - }); - } + : undefined, + }); } + } else { + fieldsToSet.push({ + ...field, + key: null, + getElement: field.getElement + ? (item: TBaseModel): ReactElement => { + return field.getElement!(item, onBeforeFetchData, fetchItem); + } + : undefined, + }); + } + } - setFields(fieldsToSet); - }; + setFields(fieldsToSet); + }; - useEffect(() => { - if (props.modelType) { - setDetailFields(); - } - }, [onBeforeFetchData, props.modelType]); + useEffect(() => { + if (props.modelType) { + setDetailFields(); + } + }, [onBeforeFetchData, props.modelType]); - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - // get item. - setIsLoading(true); - props.onLoadingChange && props.onLoadingChange(true); - setError(''); - try { - if (props.onBeforeFetch) { - const model: JSONObject = await props.onBeforeFetch(); - setOnBeforeFetchData(model); - } + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + // get item. + setIsLoading(true); + props.onLoadingChange && props.onLoadingChange(true); + setError(""); + try { + if (props.onBeforeFetch) { + const model: JSONObject = await props.onBeforeFetch(); + setOnBeforeFetchData(model); + } - const item: TBaseModel | null = await ModelAPI.getItem({ - modelType: props.modelType, - id: props.modelId, - select: { - ...getSelectFields(), - ...getRelationSelect(), - }, - }); + const item: TBaseModel | null = await ModelAPI.getItem({ + modelType: props.modelType, + id: props.modelId, + select: { + ...getSelectFields(), + ...getRelationSelect(), + }, + }); - if (!item) { - setError( - `Cannot load ${( - new props.modelType()?.singularName || 'item' - ).toLowerCase()}. It could be because you don't have enough permissions to read this ${( - new props.modelType()?.singularName || 'item' - ).toLowerCase()}.` - ); - } - - if (props.onItemLoaded && item) { - props.onItemLoaded(item); - } - - setItem(item); - } catch (err) { - setError(API.getFriendlyMessage(err)); - props.onError && props.onError(API.getFriendlyMessage(err)); - } - setIsLoading(false); - props.onLoadingChange && props.onLoadingChange(false); - }; - - useAsyncEffect(async () => { - if (props.modelId && props.modelType) { - await fetchItem(); - } - }, [props.modelId, props.refresher, props.modelType]); - - if (isLoading) { - return ( - <div - className="row text-center flex justify-center" - style={{ - marginTop: '50px', - marginBottom: '50px', - }} - > - <Loader - loaderType={LoaderType.Bar} - color={VeryLightGray} - size={200} - /> - </div> + if (!item) { + setError( + `Cannot load ${( + new props.modelType()?.singularName || "item" + ).toLowerCase()}. It could be because you don't have enough permissions to read this ${( + new props.modelType()?.singularName || "item" + ).toLowerCase()}.`, ); - } + } - if (error) { - return ( - <p - className="text-center color-light-Gray500" - style={{ - marginTop: '50px', - marginBottom: '50px', - }} - > - {error} <br />{' '} - <span - onClick={async () => { - await fetchItem(); - }} - className="underline primary-on-hover" - > - Refresh? - </span> - </p> - ); - } + if (props.onItemLoaded && item) { + props.onItemLoaded(item); + } - if (!item) { - return <ErrorMessage error="Item not found" />; + setItem(item); + } catch (err) { + setError(API.getFriendlyMessage(err)); + props.onError && props.onError(API.getFriendlyMessage(err)); } + setIsLoading(false); + props.onLoadingChange && props.onLoadingChange(false); + }; + useAsyncEffect(async () => { + if (props.modelId && props.modelType) { + await fetchItem(); + } + }, [props.modelId, props.refresher, props.modelType]); + + if (isLoading) { return ( - <Detail - id={props.id} - item={item} - fields={fields} - showDetailsInNumberOfColumns={props.showDetailsInNumberOfColumns} - /> + <div + className="row text-center flex justify-center" + style={{ + marginTop: "50px", + marginBottom: "50px", + }} + > + <Loader loaderType={LoaderType.Bar} color={VeryLightGray} size={200} /> + </div> ); + } + + if (error) { + return ( + <p + className="text-center color-light-Gray500" + style={{ + marginTop: "50px", + marginBottom: "50px", + }} + > + {error} <br />{" "} + <span + onClick={async () => { + await fetchItem(); + }} + className="underline primary-on-hover" + > + Refresh? + </span> + </p> + ); + } + + if (!item) { + return <ErrorMessage error="Item not found" />; + } + + return ( + <Detail + id={props.id} + item={item} + fields={fields} + showDetailsInNumberOfColumns={props.showDetailsInNumberOfColumns} + /> + ); }; export default ModelDetail; diff --git a/CommonUI/src/Components/ModelFilter/Filter.ts b/CommonUI/src/Components/ModelFilter/Filter.ts index f2f6466071..e82af3c283 100644 --- a/CommonUI/src/Components/ModelFilter/Filter.ts +++ b/CommonUI/src/Components/ModelFilter/Filter.ts @@ -1,28 +1,26 @@ -import SelectEntityField from '../../Types/SelectEntityField'; -import Query from '../../Utils/BaseDatabase/Query'; -import { DropdownOption } from '../Dropdown/Dropdown'; -import FieldType from '../Types/FieldType'; +import SelectEntityField from "../../Types/SelectEntityField"; +import Query from "../../Utils/BaseDatabase/Query"; +import { DropdownOption } from "../Dropdown/Dropdown"; +import FieldType from "../Types/FieldType"; import AnalyticsBaseModel, { - AnalyticsBaseModelType, -} from 'Common/AnalyticsModels/BaseModel'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; + AnalyticsBaseModelType, +} from "Common/AnalyticsModels/BaseModel"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; export default interface Filter< - TEntity extends BaseModel | AnalyticsBaseModel + TEntity extends BaseModel | AnalyticsBaseModel, > { - title: string; - type: FieldType; - field: SelectEntityField<TEntity>; - filterEntityType?: BaseModelType | AnalyticsBaseModelType | undefined; - filterQuery?: Query<TEntity> | undefined; - filterDropdownField?: - | { - label: string; - value: string; - } - | undefined; - filterDropdownOptions?: Array<DropdownOption> | undefined; - fetchFilterDropdownOptions?: () => - | Promise<Array<DropdownOption>> - | undefined; + title: string; + type: FieldType; + field: SelectEntityField<TEntity>; + filterEntityType?: BaseModelType | AnalyticsBaseModelType | undefined; + filterQuery?: Query<TEntity> | undefined; + filterDropdownField?: + | { + label: string; + value: string; + } + | undefined; + filterDropdownOptions?: Array<DropdownOption> | undefined; + fetchFilterDropdownOptions?: () => Promise<Array<DropdownOption>> | undefined; } diff --git a/CommonUI/src/Components/ModelFormModal/ModelFormModal.tsx b/CommonUI/src/Components/ModelFormModal/ModelFormModal.tsx index 830f42e784..d00767a6ab 100644 --- a/CommonUI/src/Components/ModelFormModal/ModelFormModal.tsx +++ b/CommonUI/src/Components/ModelFormModal/ModelFormModal.tsx @@ -1,125 +1,120 @@ -import ModelAPI from '../../Utils/ModelAPI/ModelAPI'; -import Alert, { AlertType } from '../Alerts/Alert'; -import { ButtonStyleType } from '../Button/Button'; -import ButtonType from '../Button/ButtonTypes'; -import { FormProps } from '../Forms/BasicForm'; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import Alert, { AlertType } from "../Alerts/Alert"; +import { ButtonStyleType } from "../Button/Button"; +import ButtonType from "../Button/ButtonTypes"; +import { FormProps } from "../Forms/BasicForm"; import ModelForm, { - ComponentProps as ModelFormComponentProps, -} from '../Forms/ModelForm'; -import FormValues from '../Forms/Types/FormValues'; -import Modal, { ModalWidth } from '../Modal/Modal'; -import BaseModel from 'Common/Models/BaseModel'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { MutableRefObject, ReactElement, useRef, useState } from 'react'; + ComponentProps as ModelFormComponentProps, +} from "../Forms/ModelForm"; +import FormValues from "../Forms/Types/FormValues"; +import Modal, { ModalWidth } from "../Modal/Modal"; +import BaseModel from "Common/Models/BaseModel"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import React, { MutableRefObject, ReactElement, useRef, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - title: string; - description?: string | undefined; - modelAPI?: typeof ModelAPI | undefined; - name?: string | undefined; - modelType: { new (): TBaseModel }; - initialValues?: FormValues<TBaseModel> | undefined; - onClose?: undefined | (() => void); - submitButtonText?: undefined | string; - modalWidth?: ModalWidth | undefined; - onSuccess?: undefined | ((data: TBaseModel) => void); - submitButtonStyleType?: undefined | ButtonStyleType; - formProps: ModelFormComponentProps<TBaseModel>; - modelIdToEdit?: ObjectID | undefined; - onBeforeCreate?: - | ((item: TBaseModel, miscDataProps: JSONObject) => Promise<TBaseModel>) - | undefined; - footer?: ReactElement | undefined; - formRef?: undefined | MutableRefObject<FormProps<FormValues<TBaseModel>>>; + title: string; + description?: string | undefined; + modelAPI?: typeof ModelAPI | undefined; + name?: string | undefined; + modelType: { new (): TBaseModel }; + initialValues?: FormValues<TBaseModel> | undefined; + onClose?: undefined | (() => void); + submitButtonText?: undefined | string; + modalWidth?: ModalWidth | undefined; + onSuccess?: undefined | ((data: TBaseModel) => void); + submitButtonStyleType?: undefined | ButtonStyleType; + formProps: ModelFormComponentProps<TBaseModel>; + modelIdToEdit?: ObjectID | undefined; + onBeforeCreate?: + | ((item: TBaseModel, miscDataProps: JSONObject) => Promise<TBaseModel>) + | undefined; + footer?: ReactElement | undefined; + formRef?: undefined | MutableRefObject<FormProps<FormValues<TBaseModel>>>; } const ModelFormModal: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [isFormLoading, setIsFormLoading] = useState<boolean>(false); + const [isFormLoading, setIsFormLoading] = useState<boolean>(false); - const [submitButtonText, setSubmitButtonText] = useState<string>( - props.submitButtonText || 'Save' - ); + const [submitButtonText, setSubmitButtonText] = useState<string>( + props.submitButtonText || "Save", + ); - const formRef: MutableRefObject<FormProps<FormValues<TBaseModel>>> = - props.formRef || - (useRef<FormProps<FormValues<TBaseModel>>>(null) as MutableRefObject< - FormProps<FormValues<TBaseModel>> - >); + const formRef: MutableRefObject<FormProps<FormValues<TBaseModel>>> = + props.formRef || + (useRef<FormProps<FormValues<TBaseModel>>>(null) as MutableRefObject< + FormProps<FormValues<TBaseModel>> + >); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - let modalWidth: ModalWidth = props.modalWidth || ModalWidth.Normal; + let modalWidth: ModalWidth = props.modalWidth || ModalWidth.Normal; - if (props.formProps.steps && props.formProps.steps.length > 0) { - modalWidth = props.modalWidth || ModalWidth.Medium; - } + if (props.formProps.steps && props.formProps.steps.length > 0) { + modalWidth = props.modalWidth || ModalWidth.Medium; + } - return ( - <Modal - {...props} - submitButtonText={submitButtonText} - modalWidth={modalWidth} - submitButtonType={ButtonType.Submit} - isLoading={isFormLoading} - description={props.description} - disableSubmitButton={isFormLoading} - onSubmit={async () => { - await formRef.current?.submitForm(); + return ( + <Modal + {...props} + submitButtonText={submitButtonText} + modalWidth={modalWidth} + submitButtonType={ButtonType.Submit} + isLoading={isFormLoading} + description={props.description} + disableSubmitButton={isFormLoading} + onSubmit={async () => { + await formRef.current?.submitForm(); + }} + error={error} + > + {!error ? ( + <> + <ModelForm<TBaseModel> + {...props.formProps} + name={props.name} + modelAPI={props.modelAPI} + modelType={props.modelType} + onIsLastFormStep={(isLastFormStep: boolean) => { + if (isLastFormStep) { + setSubmitButtonText(props.submitButtonText || "Save"); + } else { + setSubmitButtonText("Next"); + } }} - error={error} - > - {!error ? ( - <> - <ModelForm<TBaseModel> - {...props.formProps} - name={props.name} - modelAPI={props.modelAPI} - modelType={props.modelType} - onIsLastFormStep={(isLastFormStep: boolean) => { - if (isLastFormStep) { - setSubmitButtonText( - props.submitButtonText || 'Save' - ); - } else { - setSubmitButtonText('Next'); - } - }} - modelIdToEdit={props.modelIdToEdit} - hideSubmitButton={true} - formRef={formRef} - onLoadingChange={(isFormLoading: boolean) => { - setIsFormLoading(isFormLoading); - }} - initialValues={props.initialValues} - onSuccess={(data: TBaseModel) => { - props.onSuccess && - props.onSuccess( - BaseModel.fromJSONObject( - data as TBaseModel, - props.modelType - ) - ); - }} - onError={(error: string) => { - setError(error); - }} - onBeforeCreate={props.onBeforeCreate} - /> + modelIdToEdit={props.modelIdToEdit} + hideSubmitButton={true} + formRef={formRef} + onLoadingChange={(isFormLoading: boolean) => { + setIsFormLoading(isFormLoading); + }} + initialValues={props.initialValues} + onSuccess={(data: TBaseModel) => { + props.onSuccess && + props.onSuccess( + BaseModel.fromJSONObject(data as TBaseModel, props.modelType), + ); + }} + onError={(error: string) => { + setError(error); + }} + onBeforeCreate={props.onBeforeCreate} + /> - {props.footer} - </> - ) : ( - <></> - )} + {props.footer} + </> + ) : ( + <></> + )} - {error ? <Alert title={error} type={AlertType.DANGER} /> : <></>} - </Modal> - ); + {error ? <Alert title={error} type={AlertType.DANGER} /> : <></>} + </Modal> + ); }; export default ModelFormModal; diff --git a/CommonUI/src/Components/ModelList/ModelList.tsx b/CommonUI/src/Components/ModelList/ModelList.tsx index f54a75f49b..34421e9137 100644 --- a/CommonUI/src/Components/ModelList/ModelList.tsx +++ b/CommonUI/src/Components/ModelList/ModelList.tsx @@ -1,316 +1,303 @@ -import API from '../../Utils/API/API'; -import Query from '../../Utils/BaseDatabase/Query'; -import Select from '../../Utils/BaseDatabase/Select'; +import API from "../../Utils/API/API"; +import Query from "../../Utils/BaseDatabase/Query"; +import Select from "../../Utils/BaseDatabase/Select"; import ModelAPI, { - ListResult, - RequestOptions, -} from '../../Utils/ModelAPI/ModelAPI'; -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import Input from '../Input/Input'; -import StaticModelList from '../ModelList/StaticModelList'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { JSONArray } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { ReactElement, useEffect, useState } from 'react'; + ListResult, + RequestOptions, +} from "../../Utils/ModelAPI/ModelAPI"; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import Input from "../Input/Input"; +import StaticModelList from "../ModelList/StaticModelList"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { JSONArray } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import React, { ReactElement, useEffect, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - id: string; - query?: Query<TBaseModel>; - modelType: { new (): TBaseModel }; - titleField: string; - isSearchEnabled?: boolean | undefined; - descriptionField?: string | undefined; - selectMultiple?: boolean | undefined; - overrideFetchApiUrl?: URL | undefined; - select: Select<TBaseModel>; - fetchRequestOptions?: RequestOptions | undefined; - customElement?: ((item: TBaseModel) => ReactElement) | undefined; - noItemsMessage: string; - headerField?: string | ((item: TBaseModel) => ReactElement) | undefined; - onSelectChange?: ((list: Array<TBaseModel>) => void) | undefined; - refreshToggle?: boolean | undefined; - footer?: ReactElement | undefined; - isDeleteable?: boolean | undefined; - enableDragAndDrop?: boolean | undefined; - dragDropIdField?: keyof TBaseModel | undefined; - dragDropIndexField?: keyof TBaseModel | undefined; - sortBy?: keyof TBaseModel | undefined; - sortOrder?: SortOrder | undefined; - onListLoaded?: ((list: Array<TBaseModel>) => void) | undefined; + id: string; + query?: Query<TBaseModel>; + modelType: { new (): TBaseModel }; + titleField: string; + isSearchEnabled?: boolean | undefined; + descriptionField?: string | undefined; + selectMultiple?: boolean | undefined; + overrideFetchApiUrl?: URL | undefined; + select: Select<TBaseModel>; + fetchRequestOptions?: RequestOptions | undefined; + customElement?: ((item: TBaseModel) => ReactElement) | undefined; + noItemsMessage: string; + headerField?: string | ((item: TBaseModel) => ReactElement) | undefined; + onSelectChange?: ((list: Array<TBaseModel>) => void) | undefined; + refreshToggle?: boolean | undefined; + footer?: ReactElement | undefined; + isDeleteable?: boolean | undefined; + enableDragAndDrop?: boolean | undefined; + dragDropIdField?: keyof TBaseModel | undefined; + dragDropIndexField?: keyof TBaseModel | undefined; + sortBy?: keyof TBaseModel | undefined; + sortOrder?: SortOrder | undefined; + onListLoaded?: ((list: Array<TBaseModel>) => void) | undefined; } const ModelList: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [selectedList, setSelectedList] = useState<Array<TBaseModel>>([]); - const [modelList, setModalList] = useState<Array<TBaseModel>>([]); - const [error, setError] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(false); - const [searchedList, setSearchedList] = useState<Array<TBaseModel>>([]); - const [searchText, setSearchText] = useState<string>(''); + const [selectedList, setSelectedList] = useState<Array<TBaseModel>>([]); + const [modelList, setModalList] = useState<Array<TBaseModel>>([]); + const [error, setError] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(false); + const [searchedList, setSearchedList] = useState<Array<TBaseModel>>([]); + const [searchText, setSearchText] = useState<string>(""); - useEffect(() => { - props.onSelectChange && props.onSelectChange(selectedList); - }, [selectedList]); + useEffect(() => { + props.onSelectChange && props.onSelectChange(selectedList); + }, [selectedList]); - useEffect(() => { - fetchItems().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); + useEffect(() => { + fetchItems().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, [props.refreshToggle]); + + useEffect(() => { + fetchItems().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, []); + + useEffect(() => { + if (!props.isSearchEnabled) { + setSearchedList([...modelList]); + } + }, [props.isSearchEnabled, modelList]); + + const fetchItems: PromiseVoidFunction = async (): Promise<void> => { + setError(""); + setIsLoading(true); + + try { + const select: Select<TBaseModel> = { + ...props.select, + }; + + if ( + props.dragDropIdField && + !Object.keys(select).includes(props.dragDropIdField as string) + ) { + select[props.dragDropIdField] = true; + } + + if ( + props.dragDropIndexField && + !Object.keys(select).includes(props.dragDropIndexField as string) + ) { + select[props.dragDropIndexField] = true; + } + + let listResult: ListResult<TBaseModel> = { + data: [], + count: 0, + skip: 0, + limit: 0, + }; + + if (props.overrideFetchApiUrl) { + const result: HTTPResponse<JSONArray> = (await API.post( + props.overrideFetchApiUrl, + {}, + {}, + )) as HTTPResponse<JSONArray>; + + listResult = { + data: BaseModel.fromJSONArray( + result.data as JSONArray, + props.modelType, + ), + count: (result.data as JSONArray).length as number, + skip: 0, + limit: LIMIT_PER_PROJECT, + }; + } else { + listResult = await ModelAPI.getList<TBaseModel>({ + modelType: props.modelType, + query: { + ...props.query, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: select, + sort: props.sortBy + ? { + [props.sortBy as any]: props.sortOrder, + } + : {}, + + requestOptions: props.fetchRequestOptions, }); - }, [props.refreshToggle]); + } - useEffect(() => { - fetchItems().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); - }); - }, []); + props.onListLoaded && props.onListLoaded(listResult.data); + setModalList(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - useEffect(() => { - if (!props.isSearchEnabled) { - setSearchedList([...modelList]); - } - }, [props.isSearchEnabled, modelList]); + setIsLoading(false); + }; - const fetchItems: PromiseVoidFunction = async (): Promise<void> => { - setError(''); - setIsLoading(true); + useEffect(() => { + if (!searchText) { + setSearchedList([...modelList]); + } else { + // search - try { - const select: Select<TBaseModel> = { - ...props.select, - }; + setSearchedList( + [...modelList].filter((model: TBaseModel): boolean => { + const includedInSearch: boolean = ( + model.getValue(props.titleField) as string + ) + .toLowerCase() + .includes(searchText); - if ( - props.dragDropIdField && - !Object.keys(select).includes(props.dragDropIdField as string) - ) { - select[props.dragDropIdField] = true; + if (!includedInSearch && props.descriptionField) { + return (model.getValue(props.descriptionField) as string) + .toLowerCase() + .includes(searchText); + } + + return includedInSearch; + }), + ); + } + }, [modelList, searchText]); + + type DeleteItemFunction = (item: TBaseModel) => Promise<void>; + + const deleteItem: DeleteItemFunction = async (item: TBaseModel) => { + if (!item.id) { + throw new BadDataException("item.id cannot be null"); + } + + setIsLoading(true); + + try { + await ModelAPI.deleteItem<TBaseModel>({ + modelType: props.modelType, + id: item.id, + }); + + await fetchItems(); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + return ( + <div> + <div> + {!isLoading && !error && props.isSearchEnabled && ( + <div className="p-2"> + <Input + placeholder="Search..." + onChange={(value: string) => { + setSearchText(value); + }} + /> + </div> + )} + </div> + <div className="max-h-96 mb-5 overflow-y-auto p-2"> + {error ? <ErrorMessage error={error} /> : <></>} + {isLoading ? <ComponentLoader /> : <></>} + + {!isLoading && !error && searchedList.length === 0 ? ( + <ErrorMessage + error={ + searchText + ? "No items match your search" + : props.noItemsMessage || "No items found." } + /> + ) : ( + <></> + )} - if ( - props.dragDropIndexField && - !Object.keys(select).includes( - props.dragDropIndexField as string - ) - ) { - select[props.dragDropIndexField] = true; - } + {!error && !isLoading && ( + <StaticModelList<TBaseModel> + enableDragAndDrop={props.enableDragAndDrop} + dragDropIdField={props.dragDropIdField} + dragDropIndexField={props.dragDropIndexField} + list={searchedList} + headerField={props.headerField} + descriptionField={props.descriptionField} + dragAndDropScope={`${props.id}-dnd`} + customElement={props.customElement} + onDragDrop={async (id: string, newOrder: number) => { + if (!props.dragDropIndexField) { + return; + } - let listResult: ListResult<TBaseModel> = { - data: [], - count: 0, - skip: 0, - limit: 0, - }; + setIsLoading(true); - if (props.overrideFetchApiUrl) { - const result: HTTPResponse<JSONArray> = (await API.post( - props.overrideFetchApiUrl, - {}, - {} - )) as HTTPResponse<JSONArray>; - - listResult = { - data: BaseModel.fromJSONArray( - result.data as JSONArray, - props.modelType - ), - count: (result.data as JSONArray).length as number, - skip: 0, - limit: LIMIT_PER_PROJECT, - }; - } else { - listResult = await ModelAPI.getList<TBaseModel>({ - modelType: props.modelType, - query: { - ...props.query, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: select, - sort: props.sortBy - ? { - [props.sortBy as any]: props.sortOrder, - } - : {}, - - requestOptions: props.fetchRequestOptions, - }); - } - - props.onListLoaded && props.onListLoaded(listResult.data); - setModalList(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - useEffect(() => { - if (!searchText) { - setSearchedList([...modelList]); - } else { - // search - - setSearchedList( - [...modelList].filter((model: TBaseModel): boolean => { - const includedInSearch: boolean = ( - model.getValue(props.titleField) as string - ) - .toLowerCase() - .includes(searchText); - - if (!includedInSearch && props.descriptionField) { - return ( - model.getValue(props.descriptionField) as string - ) - .toLowerCase() - .includes(searchText); - } - - return includedInSearch; - }) - ); - } - }, [modelList, searchText]); - - type DeleteItemFunction = (item: TBaseModel) => Promise<void>; - - const deleteItem: DeleteItemFunction = async (item: TBaseModel) => { - if (!item.id) { - throw new BadDataException('item.id cannot be null'); - } - - setIsLoading(true); - - try { - await ModelAPI.deleteItem<TBaseModel>({ + await ModelAPI.updateById({ modelType: props.modelType, - id: item.id, - }); + id: new ObjectID(id), + data: { + [props.dragDropIndexField]: newOrder, + }, + }); - await fetchItems(); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - return ( - <div> - <div> - {!isLoading && !error && props.isSearchEnabled && ( - <div className="p-2"> - <Input - placeholder="Search..." - onChange={(value: string) => { - setSearchText(value); - }} - /> - </div> - )} - </div> - <div className="max-h-96 mb-5 overflow-y-auto p-2"> - {error ? <ErrorMessage error={error} /> : <></>} - {isLoading ? <ComponentLoader /> : <></>} - - {!isLoading && !error && searchedList.length === 0 ? ( - <ErrorMessage - error={ - searchText - ? 'No items match your search' - : props.noItemsMessage || 'No items found.' - } - /> - ) : ( - <></> - )} - - {!error && !isLoading && ( - <StaticModelList<TBaseModel> - enableDragAndDrop={props.enableDragAndDrop} - dragDropIdField={props.dragDropIdField} - dragDropIndexField={props.dragDropIndexField} - list={searchedList} - headerField={props.headerField} - descriptionField={props.descriptionField} - dragAndDropScope={`${props.id}-dnd`} - customElement={props.customElement} - onDragDrop={async (id: string, newOrder: number) => { - if (!props.dragDropIndexField) { - return; - } - - setIsLoading(true); - - await ModelAPI.updateById({ - modelType: props.modelType, - id: new ObjectID(id), - data: { - [props.dragDropIndexField]: newOrder, - }, - }); - - await fetchItems(); - }} - titleField={props.titleField} - onDelete={ - props.isDeleteable - ? async (item: TBaseModel) => { - await deleteItem(item); - } - : undefined - } - selectedItems={selectedList} - onClick={(model: TBaseModel) => { - if (props.selectMultiple) { - // if added to the list, then remove or add to list - const isSelected: boolean = - selectedList.filter( - (selectedItem: TBaseModel) => { - return ( - selectedItem._id?.toString() === - model._id?.toString() - ); - } - ).length > 0; - if (isSelected) { - // remove the item. - setSelectedList( - selectedList.filter((i: TBaseModel) => { - return ( - i._id?.toString() !== - model._id?.toString() - ); - }) - ); - } else { - setSelectedList([ - ...selectedList, - { ...model }, - ]); - } - } else { - setSelectedList([{ ...model }]); - } - }} - /> - )} - {props.footer} - </div> - </div> - ); + await fetchItems(); + }} + titleField={props.titleField} + onDelete={ + props.isDeleteable + ? async (item: TBaseModel) => { + await deleteItem(item); + } + : undefined + } + selectedItems={selectedList} + onClick={(model: TBaseModel) => { + if (props.selectMultiple) { + // if added to the list, then remove or add to list + const isSelected: boolean = + selectedList.filter((selectedItem: TBaseModel) => { + return ( + selectedItem._id?.toString() === model._id?.toString() + ); + }).length > 0; + if (isSelected) { + // remove the item. + setSelectedList( + selectedList.filter((i: TBaseModel) => { + return i._id?.toString() !== model._id?.toString(); + }), + ); + } else { + setSelectedList([...selectedList, { ...model }]); + } + } else { + setSelectedList([{ ...model }]); + } + }} + /> + )} + {props.footer} + </div> + </div> + ); }; export default ModelList; diff --git a/CommonUI/src/Components/ModelList/StaticModelList.tsx b/CommonUI/src/Components/ModelList/StaticModelList.tsx index d2ccb23f89..cdaf929cbb 100644 --- a/CommonUI/src/Components/ModelList/StaticModelList.tsx +++ b/CommonUI/src/Components/ModelList/StaticModelList.tsx @@ -1,218 +1,190 @@ -import { GetReactElementFunction } from '../../Types/FunctionTypes'; -import Button, { ButtonStyleType } from '../Button/Button'; -import Icon from '../Icon/Icon'; -import BaseModel from 'Common/Models/BaseModel'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Typeof from 'Common/Types/Typeof'; -import React, { ReactElement } from 'react'; +import { GetReactElementFunction } from "../../Types/FunctionTypes"; +import Button, { ButtonStyleType } from "../Button/Button"; +import Icon from "../Icon/Icon"; +import BaseModel from "Common/Models/BaseModel"; +import IconProp from "Common/Types/Icon/IconProp"; +import Typeof from "Common/Types/Typeof"; +import React, { ReactElement } from "react"; import { - DragDropContext, - Draggable, - DraggableProvided, - DropResult, - Droppable, - DroppableProvided, -} from 'react-beautiful-dnd'; + DragDropContext, + Draggable, + DraggableProvided, + DropResult, + Droppable, + DroppableProvided, +} from "react-beautiful-dnd"; export interface ComponentProps<TBaseModel extends BaseModel> { - list: Array<TBaseModel>; - headerField?: string | ((item: TBaseModel) => ReactElement) | undefined; - descriptionField?: string | undefined; - selectedItems: Array<TBaseModel>; - onClick: (item: TBaseModel) => void; - titleField: string; - onDelete?: ((item: TBaseModel) => void) | undefined; - customElement?: ((item: TBaseModel) => ReactElement) | undefined; - enableDragAndDrop?: boolean | undefined; - dragAndDropScope?: string | undefined; - dragDropIdField?: keyof TBaseModel | undefined; - dragDropIndexField?: keyof TBaseModel | undefined; - onDragDrop?: ((id: string, newIndex: number) => void) | undefined; + list: Array<TBaseModel>; + headerField?: string | ((item: TBaseModel) => ReactElement) | undefined; + descriptionField?: string | undefined; + selectedItems: Array<TBaseModel>; + onClick: (item: TBaseModel) => void; + titleField: string; + onDelete?: ((item: TBaseModel) => void) | undefined; + customElement?: ((item: TBaseModel) => ReactElement) | undefined; + enableDragAndDrop?: boolean | undefined; + dragAndDropScope?: string | undefined; + dragDropIdField?: keyof TBaseModel | undefined; + dragDropIndexField?: keyof TBaseModel | undefined; + onDragDrop?: ((id: string, newIndex: number) => void) | undefined; } const StaticModelList: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - type GetRowFunction = ( - model: TBaseModel, - isSelected: boolean, - provided?: DraggableProvided | undefined - ) => ReactElement; - - const getRow: GetRowFunction = ( - model: TBaseModel, - isSelected: boolean, - provided?: DraggableProvided | undefined - ): ReactElement => { - return ( - <div - onClick={() => { - props.onClick(model); - }} - {...provided?.draggableProps} - ref={provided?.innerRef} - className={`cursor-pointer mt-2 mb-2 flex justify-between items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2 hover:border-gray-400 ${ - isSelected ? 'ring ring-indigo-500' : '' - }`} - > - <div className="flex"> - {props.enableDragAndDrop && ( - <div - className="mr-5 mt-2 -ml-5 w-10" - {...provided?.dragHandleProps} - > - <Icon - icon={IconProp.ArrowUpDown} - className="ml-6 h-5 w-5 text-gray-500 hover:text-indigo-800 m-auto cursor-ns-resize" - /> - </div> - )} - {!props.customElement && ( - <div className="min-w-0 flex-1"> - <div className="focus:outline-none"> - {props.headerField && - typeof props.headerField === - Typeof.String && ( - <p className="text-sm font-medium text-gray-300"> - { - model.getValue( - props.headerField as string - ) as string - } - </p> - )} - - {props.headerField && - typeof props.headerField === 'function' && - props.headerField(model)} - <p className="text-sm font-medium text-gray-900"> - {model.getValue(props.titleField) as string} - </p> - {props.descriptionField && ( - <p className="truncate text-sm text-gray-500"> - { - model.getValue( - props.descriptionField - ) as string - } - </p> - )} - </div> - </div> - )} - - {props.customElement && props.customElement(model)} - </div> - - {props.onDelete && ( - <div> - <Button - icon={IconProp.Trash} - buttonStyle={ButtonStyleType.OUTLINE} - title="Delete" - onClick={() => { - props.onDelete && props.onDelete(model); - }} - /> - </div> - )} - </div> - ); - }; - - type GetBodyFunction = ( - provided?: DroppableProvided | undefined - ) => ReactElement; - - const getBody: GetBodyFunction = ( - provided?: DroppableProvided - ): ReactElement => { - return ( - <div ref={provided?.innerRef} {...provided?.droppableProps}> - {props.list && - props.list.length > 0 && - props.list.map( - (model: TBaseModel, i: number): ReactElement => { - const isSelected: boolean = - props.selectedItems.filter( - (selectedItem: TBaseModel) => { - return ( - selectedItem._id?.toString() === - model._id?.toString() - ); - } - ).length > 0; - - if (props.enableDragAndDrop) { - return ( - <Draggable - draggableId={ - ((model as any)[ - props.dragDropIdField || '' - ] as string) || '' - } - index={ - ((model as any)[ - props.dragDropIndexField || 0 - ] as number) || 0 - } - key={ - ((model as any)[ - props.dragDropIndexField || 0 - ] as number) || 0 - } - > - {(provided: DraggableProvided) => { - return getRow( - model, - isSelected, - provided - ); - }} - </Draggable> - ); - } - - return ( - <div key={i}>{getRow(model, isSelected)}</div> - ); - } - )} - {provided?.placeholder} - </div> - ); - }; - - const getComponent: GetReactElementFunction = (): ReactElement => { - if (props.enableDragAndDrop) { - return ( - <Droppable droppableId={props.dragAndDropScope || ''}> - {(provided: DroppableProvided) => { - return getBody(provided); - }} - </Droppable> - ); - } - - return getBody(); - }; + type GetRowFunction = ( + model: TBaseModel, + isSelected: boolean, + provided?: DraggableProvided | undefined, + ) => ReactElement; + const getRow: GetRowFunction = ( + model: TBaseModel, + isSelected: boolean, + provided?: DraggableProvided | undefined, + ): ReactElement => { return ( - <DragDropContext - onDragEnd={(result: DropResult) => { - result.destination?.index && - props.onDragDrop && - props.onDragDrop( - result.draggableId, - result.destination.index - ); - }} - > - {getComponent()} - </DragDropContext> + <div + onClick={() => { + props.onClick(model); + }} + {...provided?.draggableProps} + ref={provided?.innerRef} + className={`cursor-pointer mt-2 mb-2 flex justify-between items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2 hover:border-gray-400 ${ + isSelected ? "ring ring-indigo-500" : "" + }`} + > + <div className="flex"> + {props.enableDragAndDrop && ( + <div + className="mr-5 mt-2 -ml-5 w-10" + {...provided?.dragHandleProps} + > + <Icon + icon={IconProp.ArrowUpDown} + className="ml-6 h-5 w-5 text-gray-500 hover:text-indigo-800 m-auto cursor-ns-resize" + /> + </div> + )} + {!props.customElement && ( + <div className="min-w-0 flex-1"> + <div className="focus:outline-none"> + {props.headerField && + typeof props.headerField === Typeof.String && ( + <p className="text-sm font-medium text-gray-300"> + {model.getValue(props.headerField as string) as string} + </p> + )} + + {props.headerField && + typeof props.headerField === "function" && + props.headerField(model)} + <p className="text-sm font-medium text-gray-900"> + {model.getValue(props.titleField) as string} + </p> + {props.descriptionField && ( + <p className="truncate text-sm text-gray-500"> + {model.getValue(props.descriptionField) as string} + </p> + )} + </div> + </div> + )} + + {props.customElement && props.customElement(model)} + </div> + + {props.onDelete && ( + <div> + <Button + icon={IconProp.Trash} + buttonStyle={ButtonStyleType.OUTLINE} + title="Delete" + onClick={() => { + props.onDelete && props.onDelete(model); + }} + /> + </div> + )} + </div> ); + }; + + type GetBodyFunction = ( + provided?: DroppableProvided | undefined, + ) => ReactElement; + + const getBody: GetBodyFunction = ( + provided?: DroppableProvided, + ): ReactElement => { + return ( + <div ref={provided?.innerRef} {...provided?.droppableProps}> + {props.list && + props.list.length > 0 && + props.list.map((model: TBaseModel, i: number): ReactElement => { + const isSelected: boolean = + props.selectedItems.filter((selectedItem: TBaseModel) => { + return selectedItem._id?.toString() === model._id?.toString(); + }).length > 0; + + if (props.enableDragAndDrop) { + return ( + <Draggable + draggableId={ + ((model as any)[props.dragDropIdField || ""] as string) || + "" + } + index={ + ((model as any)[props.dragDropIndexField || 0] as number) || + 0 + } + key={ + ((model as any)[props.dragDropIndexField || 0] as number) || + 0 + } + > + {(provided: DraggableProvided) => { + return getRow(model, isSelected, provided); + }} + </Draggable> + ); + } + + return <div key={i}>{getRow(model, isSelected)}</div>; + })} + {provided?.placeholder} + </div> + ); + }; + + const getComponent: GetReactElementFunction = (): ReactElement => { + if (props.enableDragAndDrop) { + return ( + <Droppable droppableId={props.dragAndDropScope || ""}> + {(provided: DroppableProvided) => { + return getBody(provided); + }} + </Droppable> + ); + } + + return getBody(); + }; + + return ( + <DragDropContext + onDragEnd={(result: DropResult) => { + result.destination?.index && + props.onDragDrop && + props.onDragDrop(result.draggableId, result.destination.index); + }} + > + {getComponent()} + </DragDropContext> + ); }; export default StaticModelList; diff --git a/CommonUI/src/Components/ModelListModal/ModelListModal.tsx b/CommonUI/src/Components/ModelListModal/ModelListModal.tsx index cd59c02508..5aaf95ba9f 100644 --- a/CommonUI/src/Components/ModelListModal/ModelListModal.tsx +++ b/CommonUI/src/Components/ModelListModal/ModelListModal.tsx @@ -1,56 +1,56 @@ -import Query from '../../Utils/BaseDatabase/Query'; -import Select from '../../Utils/BaseDatabase/Select'; -import Modal from '../Modal/Modal'; -import ModelList from '../ModelList/ModelList'; -import BaseModel from 'Common/Models/BaseModel'; -import React, { ReactElement, useState } from 'react'; +import Query from "../../Utils/BaseDatabase/Query"; +import Select from "../../Utils/BaseDatabase/Select"; +import Modal from "../Modal/Modal"; +import ModelList from "../ModelList/ModelList"; +import BaseModel from "Common/Models/BaseModel"; +import React, { ReactElement, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - query?: Query<TBaseModel>; - onClose: () => void; - onSave: (modals: Array<TBaseModel>) => void; - modelType: { new (): TBaseModel }; - titleField: string; - isSearchEnabled?: boolean | undefined; - descriptionField?: string | undefined; - selectMultiple?: boolean | undefined; - select: Select<TBaseModel>; - modalTitle: string; - modalDescription: string; - noItemsMessage: string; - headerField?: string | ((item: TBaseModel) => ReactElement) | undefined; + query?: Query<TBaseModel>; + onClose: () => void; + onSave: (modals: Array<TBaseModel>) => void; + modelType: { new (): TBaseModel }; + titleField: string; + isSearchEnabled?: boolean | undefined; + descriptionField?: string | undefined; + selectMultiple?: boolean | undefined; + select: Select<TBaseModel>; + modalTitle: string; + modalDescription: string; + noItemsMessage: string; + headerField?: string | ((item: TBaseModel) => ReactElement) | undefined; } const ModelListModal: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [selectedList, setSelectedList] = useState<Array<TBaseModel>>([]); + const [selectedList, setSelectedList] = useState<Array<TBaseModel>>([]); - return ( - <Modal - title={props.modalTitle} - description={props.modalDescription} - onClose={props.onClose} - disableSubmitButton={selectedList.length === 0} - onSubmit={() => { - if (selectedList && selectedList.length === 0) { - props.onClose(); - } + return ( + <Modal + title={props.modalTitle} + description={props.modalDescription} + onClose={props.onClose} + disableSubmitButton={selectedList.length === 0} + onSubmit={() => { + if (selectedList && selectedList.length === 0) { + props.onClose(); + } - props.onSave(selectedList); - }} - > - <ModelList<TBaseModel> - {...props} - id="model-list-modal" - onSelectChange={(list: Array<TBaseModel>) => { - setSelectedList([...list]); - }} - /> - </Modal> - ); + props.onSave(selectedList); + }} + > + <ModelList<TBaseModel> + {...props} + id="model-list-modal" + onSelectChange={(list: Array<TBaseModel>) => { + setSelectedList([...list]); + }} + /> + </Modal> + ); }; export default ModelListModal; diff --git a/CommonUI/src/Components/ModelProgress/ModelProgress.tsx b/CommonUI/src/Components/ModelProgress/ModelProgress.tsx index 000fe614f6..49f5172c6b 100644 --- a/CommonUI/src/Components/ModelProgress/ModelProgress.tsx +++ b/CommonUI/src/Components/ModelProgress/ModelProgress.tsx @@ -1,74 +1,74 @@ -import API from '../../Utils/API/API'; -import Query from '../../Utils/BaseDatabase/Query'; -import ModelAPI from '../../Utils/ModelAPI/ModelAPI'; -import Card from '../Card/Card'; -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import ProgressBar from '../ProgressBar/ProgressBar'; -import BaseModel from 'Common/Models/BaseModel'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import React, { ReactElement, useEffect, useState } from 'react'; +import API from "../../Utils/API/API"; +import Query from "../../Utils/BaseDatabase/Query"; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import Card from "../Card/Card"; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import ProgressBar from "../ProgressBar/ProgressBar"; +import BaseModel from "Common/Models/BaseModel"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import React, { ReactElement, useEffect, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - title: string; - description: string; - totalCount: number; - countQuery: Query<TBaseModel>; - modelType: { new (): TBaseModel }; + title: string; + description: string; + totalCount: number; + countQuery: Query<TBaseModel>; + modelType: { new (): TBaseModel }; } const ModelProgress: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); - const [count, setCount] = useState<number>(0); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [count, setCount] = useState<number>(0); - const fetchCount: PromiseVoidFunction = async (): Promise<void> => { - setError(''); - setIsLoading(true); + const fetchCount: PromiseVoidFunction = async (): Promise<void> => { + setError(""); + setIsLoading(true); - try { - const count: number = await ModelAPI.count<TBaseModel>({ - modelType: props.modelType, - query: props.countQuery, - }); + try { + const count: number = await ModelAPI.count<TBaseModel>({ + modelType: props.modelType, + query: props.countQuery, + }); - setCount(count); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + setCount(count); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - setIsLoading(false); - }; + setIsLoading(false); + }; - useEffect(() => { - fetchCount().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); - }); - }, []); + useEffect(() => { + fetchCount().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, []); - return ( - <Card title={props.title} description={props.description}> - <div className="w-full -mt-6"> - {!error && ( - <div> - <ErrorMessage error={error} /> - </div> - )} - {isLoading && <ComponentLoader />} - {!error && !isLoading && ( - <ProgressBar - totalCount={props.totalCount} - count={count} - suffix={props.title} - /> - )} - </div> - </Card> - ); + return ( + <Card title={props.title} description={props.description}> + <div className="w-full -mt-6"> + {!error && ( + <div> + <ErrorMessage error={error} /> + </div> + )} + {isLoading && <ComponentLoader />} + {!error && !isLoading && ( + <ProgressBar + totalCount={props.totalCount} + count={count} + suffix={props.title} + /> + )} + </div> + </Card> + ); }; export default ModelProgress; diff --git a/CommonUI/src/Components/ModelTable/AnalyticsModelTable.tsx b/CommonUI/src/Components/ModelTable/AnalyticsModelTable.tsx index 8fe5037c02..90fe78864f 100644 --- a/CommonUI/src/Components/ModelTable/AnalyticsModelTable.tsx +++ b/CommonUI/src/Components/ModelTable/AnalyticsModelTable.tsx @@ -1,120 +1,112 @@ -import ModelAPI from '../../Utils/AnalyticsModelAPI/AnalyticsModelAPI'; -import GroupBy from '../../Utils/BaseDatabase/GroupBy'; -import Query from '../../Utils/BaseDatabase/Query'; -import RequestOptions from '../../Utils/BaseDatabase/RequestOptions'; -import Select from '../../Utils/BaseDatabase/Select'; -import Sort from '../../Utils/BaseDatabase/Sort'; -import BaseModelTable, { BaseTableProps, ModalType } from './BaseModelTable'; +import ModelAPI from "../../Utils/AnalyticsModelAPI/AnalyticsModelAPI"; +import GroupBy from "../../Utils/BaseDatabase/GroupBy"; +import Query from "../../Utils/BaseDatabase/Query"; +import RequestOptions from "../../Utils/BaseDatabase/RequestOptions"; +import Select from "../../Utils/BaseDatabase/Select"; +import Sort from "../../Utils/BaseDatabase/Sort"; +import BaseModelTable, { BaseTableProps, ModalType } from "./BaseModelTable"; import AnalyticsBaseModel, { - AnalyticsBaseModelType, -} from 'Common/AnalyticsModels/BaseModel'; -import { BaseModelType } from 'Common/Models/BaseModel'; -import NotImplementedException from 'Common/Types/Exception/NotImplementedException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { ReactElement } from 'react'; + AnalyticsBaseModelType, +} from "Common/AnalyticsModels/BaseModel"; +import { BaseModelType } from "Common/Models/BaseModel"; +import NotImplementedException from "Common/Types/Exception/NotImplementedException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import React, { ReactElement } from "react"; export interface ComponentProps<TBaseModel extends AnalyticsBaseModel> - extends BaseTableProps<TBaseModel> { - modelAPI?: typeof ModelAPI | undefined; + extends BaseTableProps<TBaseModel> { + modelAPI?: typeof ModelAPI | undefined; } const AnalyticsModelTable: <TBaseModel extends AnalyticsBaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends AnalyticsBaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const modelAPI: typeof ModelAPI = props.modelAPI || ModelAPI; + const modelAPI: typeof ModelAPI = props.modelAPI || ModelAPI; - return ( - <BaseModelTable - {...props} - callbacks={{ - getJSONFromModel: (item: TBaseModel): JSONObject => { - return AnalyticsBaseModel.toJSON(item, props.modelType); - }, + return ( + <BaseModelTable + {...props} + callbacks={{ + getJSONFromModel: (item: TBaseModel): JSONObject => { + return AnalyticsBaseModel.toJSON(item, props.modelType); + }, - updateById: async (args: { - id: ObjectID; - data: JSONObject; - }) => { - const { id, data } = args; + updateById: async (args: { id: ObjectID; data: JSONObject }) => { + const { id, data } = args; - await modelAPI.updateById({ - modelType: props.modelType, - id: new ObjectID(id), - data: data, - }); - }, + await modelAPI.updateById({ + modelType: props.modelType, + id: new ObjectID(id), + data: data, + }); + }, - showCreateEditModal: (_data: { - modalType: ModalType; - modelIdToEdit?: ObjectID | undefined; - onBeforeCreate?: - | (( - item: TBaseModel, - miscDataProps: JSONObject - ) => Promise<TBaseModel>) - | undefined; - onSuccess?: ((item: TBaseModel) => void) | undefined; - onClose?: (() => void) | undefined; - }): ReactElement => { - // Analytics database like clickhuse dont support edit operations - throw new NotImplementedException(); - }, + showCreateEditModal: (_data: { + modalType: ModalType; + modelIdToEdit?: ObjectID | undefined; + onBeforeCreate?: + | (( + item: TBaseModel, + miscDataProps: JSONObject, + ) => Promise<TBaseModel>) + | undefined; + onSuccess?: ((item: TBaseModel) => void) | undefined; + onClose?: (() => void) | undefined; + }): ReactElement => { + // Analytics database like clickhuse dont support edit operations + throw new NotImplementedException(); + }, - toJSONArray: (items: TBaseModel[]): JSONObject[] => { - return AnalyticsBaseModel.toJSONArray( - items, - props.modelType - ); - }, + toJSONArray: (items: TBaseModel[]): JSONObject[] => { + return AnalyticsBaseModel.toJSONArray(items, props.modelType); + }, - getList: async (data: { - modelType: BaseModelType | AnalyticsBaseModelType; - query: Query<TBaseModel>; - groupBy?: GroupBy<TBaseModel> | undefined; - limit: number; - skip: number; - sort: Sort<TBaseModel>; - select: Select<TBaseModel>; - requestOptions?: RequestOptions | undefined; - }) => { - return await modelAPI.getList<TBaseModel>({ - modelType: data.modelType as { new (): TBaseModel }, - query: data.query, - groupBy: data.groupBy, - limit: data.limit, - skip: data.skip, - sort: data.sort, - select: data.select, - requestOptions: data.requestOptions, - }); - }, + getList: async (data: { + modelType: BaseModelType | AnalyticsBaseModelType; + query: Query<TBaseModel>; + groupBy?: GroupBy<TBaseModel> | undefined; + limit: number; + skip: number; + sort: Sort<TBaseModel>; + select: Select<TBaseModel>; + requestOptions?: RequestOptions | undefined; + }) => { + return await modelAPI.getList<TBaseModel>({ + modelType: data.modelType as { new (): TBaseModel }, + query: data.query, + groupBy: data.groupBy, + limit: data.limit, + skip: data.skip, + sort: data.sort, + select: data.select, + requestOptions: data.requestOptions, + }); + }, - addSlugToSelect: ( - select: Select<TBaseModel> - ): Select<TBaseModel> => { - return select; - }, + addSlugToSelect: (select: Select<TBaseModel>): Select<TBaseModel> => { + return select; + }, - getModelFromJSON: (item: JSONObject): TBaseModel => { - return AnalyticsBaseModel.fromJSON( - item, - props.modelType - ) as TBaseModel; - }, + getModelFromJSON: (item: JSONObject): TBaseModel => { + return AnalyticsBaseModel.fromJSON( + item, + props.modelType, + ) as TBaseModel; + }, - deleteItem: async (item: TBaseModel) => { - await modelAPI.deleteItem({ - modelType: props.modelType, - id: item.id as ObjectID, - requestOptions: props.deleteRequestOptions, - }); - }, - }} - /> - ); + deleteItem: async (item: TBaseModel) => { + await modelAPI.deleteItem({ + modelType: props.modelType, + id: item.id as ObjectID, + requestOptions: props.deleteRequestOptions, + }); + }, + }} + /> + ); }; export default AnalyticsModelTable; diff --git a/CommonUI/src/Components/ModelTable/BaseModelTable.tsx b/CommonUI/src/Components/ModelTable/BaseModelTable.tsx index a78f64970b..a9be679262 100644 --- a/CommonUI/src/Components/ModelTable/BaseModelTable.tsx +++ b/CommonUI/src/Components/ModelTable/BaseModelTable.tsx @@ -1,1851 +1,1760 @@ -import { API_DOCS_URL, BILLING_ENABLED, getAllEnvVars } from '../../Config'; -import { GetReactElementFunction } from '../../Types/FunctionTypes'; -import SelectEntityField from '../../Types/SelectEntityField'; -import API from '../../Utils/API/API'; -import GroupBy from '../../Utils/BaseDatabase/GroupBy'; -import ListResult from '../../Utils/BaseDatabase/ListResult'; -import Query from '../../Utils/BaseDatabase/Query'; -import RequestOptions from '../../Utils/BaseDatabase/RequestOptions'; -import Select from '../../Utils/BaseDatabase/Select'; -import Sort from '../../Utils/BaseDatabase/Sort'; -import { Logger } from '../../Utils/Logger'; -import Navigation from '../../Utils/Navigation'; -import PermissionUtil from '../../Utils/Permission'; -import ProjectUtil from '../../Utils/Project'; -import User from '../../Utils/User'; -import ActionButtonSchema from '../ActionButton/ActionButtonSchema'; +import { API_DOCS_URL, BILLING_ENABLED, getAllEnvVars } from "../../Config"; +import { GetReactElementFunction } from "../../Types/FunctionTypes"; +import SelectEntityField from "../../Types/SelectEntityField"; +import API from "../../Utils/API/API"; +import GroupBy from "../../Utils/BaseDatabase/GroupBy"; +import ListResult from "../../Utils/BaseDatabase/ListResult"; +import Query from "../../Utils/BaseDatabase/Query"; +import RequestOptions from "../../Utils/BaseDatabase/RequestOptions"; +import Select from "../../Utils/BaseDatabase/Select"; +import Sort from "../../Utils/BaseDatabase/Sort"; +import { Logger } from "../../Utils/Logger"; +import Navigation from "../../Utils/Navigation"; +import PermissionUtil from "../../Utils/Permission"; +import ProjectUtil from "../../Utils/Project"; +import User from "../../Utils/User"; +import ActionButtonSchema from "../ActionButton/ActionButtonSchema"; import { - BulkActionButtonSchema, - BulkActionFailed, - BulkActionOnClickProps, -} from '../BulkUpdate/BulkUpdateForm'; -import { ButtonStyleType } from '../Button/Button'; + BulkActionButtonSchema, + BulkActionFailed, + BulkActionOnClickProps, +} from "../BulkUpdate/BulkUpdateForm"; +import { ButtonStyleType } from "../Button/Button"; import Card, { - CardButtonSchema, - ComponentProps as CardComponentProps, -} from '../Card/Card'; -import { getRefreshButton } from '../Card/CardButtons/Refresh'; -import Field from '../Detail/Field'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import ClassicFilterType from '../Filters/Types/Filter'; -import FilterData from '../Filters/Types/FilterData'; -import { FormProps } from '../Forms/BasicForm'; -import { ModelField } from '../Forms/ModelForm'; -import { FormStep } from '../Forms/Types/FormStep'; -import FormValues from '../Forms/Types/FormValues'; -import List from '../List/List'; -import { ListDetailProps } from '../List/ListRow'; -import ConfirmModal from '../Modal/ConfirmModal'; -import { ModalWidth } from '../Modal/Modal'; -import Filter from '../ModelFilter/Filter'; -import OrderedStatesList from '../OrderedStatesList/OrderedStatesList'; -import Pill from '../Pill/Pill'; -import Table from '../Table/Table'; -import TableColumn from '../Table/Types/Column'; -import FieldType from '../Types/FieldType'; -import ModelTableColumn from './Column'; -import Columns from './Columns'; + CardButtonSchema, + ComponentProps as CardComponentProps, +} from "../Card/Card"; +import { getRefreshButton } from "../Card/CardButtons/Refresh"; +import Field from "../Detail/Field"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import ClassicFilterType from "../Filters/Types/Filter"; +import FilterData from "../Filters/Types/FilterData"; +import { FormProps } from "../Forms/BasicForm"; +import { ModelField } from "../Forms/ModelForm"; +import { FormStep } from "../Forms/Types/FormStep"; +import FormValues from "../Forms/Types/FormValues"; +import List from "../List/List"; +import { ListDetailProps } from "../List/ListRow"; +import ConfirmModal from "../Modal/ConfirmModal"; +import { ModalWidth } from "../Modal/Modal"; +import Filter from "../ModelFilter/Filter"; +import OrderedStatesList from "../OrderedStatesList/OrderedStatesList"; +import Pill from "../Pill/Pill"; +import Table from "../Table/Table"; +import TableColumn from "../Table/Types/Column"; +import FieldType from "../Types/FieldType"; +import ModelTableColumn from "./Column"; +import Columns from "./Columns"; import AnalyticsBaseModel, { - AnalyticsBaseModelType, -} from 'Common/AnalyticsModels/BaseModel'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { ColumnAccessControl } from 'Common/Types/BaseDatabase/AccessControl'; -import InBetween from 'Common/Types/BaseDatabase/InBetween'; -import Search from 'Common/Types/BaseDatabase/Search'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; + AnalyticsBaseModelType, +} from "Common/AnalyticsModels/BaseModel"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import Search from "Common/Types/BaseDatabase/Search"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; import SubscriptionPlan, { - PlanSelect, -} from 'Common/Types/Billing/SubscriptionPlan'; -import { Yellow } from 'Common/Types/BrandColors'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; + PlanSelect, +} from "Common/Types/Billing/SubscriptionPlan"; +import { Yellow } from "Common/Types/BrandColors"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; import { - ErrorFunction, - PromiseVoidFunction, - VoidFunction, -} from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; + ErrorFunction, + PromiseVoidFunction, + VoidFunction, +} from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; import Permission, { - PermissionHelper, - UserPermission, -} from 'Common/Types/Permission'; -import Typeof from 'Common/Types/Typeof'; + PermissionHelper, + UserPermission, +} from "Common/Types/Permission"; +import Typeof from "Common/Types/Typeof"; import React, { - MutableRefObject, - ReactElement, - useEffect, - useState, -} from 'react'; + MutableRefObject, + ReactElement, + useEffect, + useState, +} from "react"; export enum ShowAs { - Table, - List, - OrderedStatesList, + Table, + List, + OrderedStatesList, } export interface BaseTableCallbacks< - TBaseModel extends BaseModel | AnalyticsBaseModel + TBaseModel extends BaseModel | AnalyticsBaseModel, > { - deleteItem: (item: TBaseModel) => Promise<void>; - getModelFromJSON: (item: JSONObject) => TBaseModel; - getJSONFromModel: (item: TBaseModel) => JSONObject; - addSlugToSelect: (select: Select<TBaseModel>) => Select<TBaseModel>; - getList: (data: { - modelType: BaseModelType | AnalyticsBaseModelType; - query: Query<TBaseModel>; - groupBy?: GroupBy<TBaseModel> | undefined; - limit: number; - skip: number; - sort: Sort<TBaseModel>; - select: Select<TBaseModel>; - requestOptions?: RequestOptions | undefined; - }) => Promise<ListResult<TBaseModel>>; - toJSONArray: (data: Array<TBaseModel>) => Array<JSONObject>; - updateById: (data: { id: ObjectID; data: JSONObject }) => Promise<void>; - showCreateEditModal: (data: { - modalType: ModalType; - modelIdToEdit?: ObjectID | undefined; - onBeforeCreate?: - | (( - item: TBaseModel, - miscDataProps: JSONObject - ) => Promise<TBaseModel>) - | undefined; - onSuccess?: ((item: TBaseModel) => void) | undefined; - onClose?: (() => void) | undefined; - }) => ReactElement; + deleteItem: (item: TBaseModel) => Promise<void>; + getModelFromJSON: (item: JSONObject) => TBaseModel; + getJSONFromModel: (item: TBaseModel) => JSONObject; + addSlugToSelect: (select: Select<TBaseModel>) => Select<TBaseModel>; + getList: (data: { + modelType: BaseModelType | AnalyticsBaseModelType; + query: Query<TBaseModel>; + groupBy?: GroupBy<TBaseModel> | undefined; + limit: number; + skip: number; + sort: Sort<TBaseModel>; + select: Select<TBaseModel>; + requestOptions?: RequestOptions | undefined; + }) => Promise<ListResult<TBaseModel>>; + toJSONArray: (data: Array<TBaseModel>) => Array<JSONObject>; + updateById: (data: { id: ObjectID; data: JSONObject }) => Promise<void>; + showCreateEditModal: (data: { + modalType: ModalType; + modelIdToEdit?: ObjectID | undefined; + onBeforeCreate?: + | ((item: TBaseModel, miscDataProps: JSONObject) => Promise<TBaseModel>) + | undefined; + onSuccess?: ((item: TBaseModel) => void) | undefined; + onClose?: (() => void) | undefined; + }) => ReactElement; } export enum ModalTableBulkDefaultActions { - Delete = 'Delete', + Delete = "Delete", } export interface BulkActionProps<T extends BaseModel | AnalyticsBaseModel> { - buttons: Array<BulkActionButtonSchema<T> | ModalTableBulkDefaultActions>; - matchBulkSelectedItemByField?: keyof T | undefined; // which field to use to match selected items. For exmaple this could be '_id' + buttons: Array<BulkActionButtonSchema<T> | ModalTableBulkDefaultActions>; + matchBulkSelectedItemByField?: keyof T | undefined; // which field to use to match selected items. For exmaple this could be '_id' } export interface BaseTableProps< - TBaseModel extends BaseModel | AnalyticsBaseModel + TBaseModel extends BaseModel | AnalyticsBaseModel, > { - modelType: { new (): TBaseModel }; - id: string; - onFetchInit?: - | undefined - | ((pageNumber: number, itemsOnPage: number) => void); - onFetchSuccess?: - | undefined - | ((data: Array<TBaseModel>, totalCount: number) => void); - cardProps?: CardComponentProps | undefined; - showCreateForm?: undefined | boolean; - columns: Columns<TBaseModel>; - filters: Array<Filter<TBaseModel>>; - listDetailOptions?: undefined | ListDetailProps; - selectMoreFields?: Select<TBaseModel>; - initialItemsOnPage?: number; - isDeleteable: boolean; - isEditable?: boolean | undefined; - isCreateable: boolean; - disablePagination?: undefined | boolean; - formFields?: undefined | Array<ModelField<TBaseModel>>; - formSteps?: undefined | Array<FormStep<TBaseModel>>; - noItemsMessage?: undefined | string | ReactElement; - showRefreshButton?: undefined | boolean; - isViewable?: undefined | boolean; - showViewIdButton?: undefined | boolean; - enableDragAndDrop?: boolean | undefined; - viewPageRoute?: undefined | Route; - onViewPage?: (item: TBaseModel) => Promise<Route>; - query?: Query<TBaseModel>; - groupBy?: GroupBy<TBaseModel> | undefined; - onBeforeFetch?: (() => Promise<TBaseModel>) | undefined; - createInitialValues?: FormValues<TBaseModel> | undefined; - onBeforeCreate?: - | ((item: TBaseModel, miscDataProps: JSONObject) => Promise<TBaseModel>) - | undefined; - onCreateSuccess?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined; - createVerb?: string; - showAs?: ShowAs | undefined; - singularName?: string | undefined; - pluralName?: string | undefined; - actionButtons?: Array<ActionButtonSchema<TBaseModel>> | undefined; - deleteButtonText?: string | undefined; - onCreateEditModalClose?: (() => void) | undefined; - editButtonText?: string | undefined; - viewButtonText?: string | undefined; - refreshToggle?: boolean | undefined; - fetchRequestOptions?: RequestOptions | undefined; - deleteRequestOptions?: RequestOptions | undefined; - onItemDeleted?: ((item: TBaseModel) => void) | undefined; - onBeforeEdit?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined; - onBeforeDelete?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined; - onBeforeView?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined; - sortBy?: keyof TBaseModel | undefined; - sortOrder?: SortOrder | undefined; - dragDropIdField?: keyof TBaseModel | undefined; - dragDropIndexField?: keyof TBaseModel | undefined; - createEditModalWidth?: ModalWidth | undefined; - orderedStatesListProps?: { - titleField: keyof TBaseModel; - descriptionField?: keyof TBaseModel | undefined; - orderField: keyof TBaseModel; - shouldAddItemInTheEnd?: boolean; - shouldAddItemInTheBeginning?: boolean; - }; - onViewComplete?: ((item: TBaseModel) => void) | undefined; - createEditFromRef?: - | undefined - | MutableRefObject<FormProps<FormValues<TBaseModel>>>; - name: string; + modelType: { new (): TBaseModel }; + id: string; + onFetchInit?: undefined | ((pageNumber: number, itemsOnPage: number) => void); + onFetchSuccess?: + | undefined + | ((data: Array<TBaseModel>, totalCount: number) => void); + cardProps?: CardComponentProps | undefined; + showCreateForm?: undefined | boolean; + columns: Columns<TBaseModel>; + filters: Array<Filter<TBaseModel>>; + listDetailOptions?: undefined | ListDetailProps; + selectMoreFields?: Select<TBaseModel>; + initialItemsOnPage?: number; + isDeleteable: boolean; + isEditable?: boolean | undefined; + isCreateable: boolean; + disablePagination?: undefined | boolean; + formFields?: undefined | Array<ModelField<TBaseModel>>; + formSteps?: undefined | Array<FormStep<TBaseModel>>; + noItemsMessage?: undefined | string | ReactElement; + showRefreshButton?: undefined | boolean; + isViewable?: undefined | boolean; + showViewIdButton?: undefined | boolean; + enableDragAndDrop?: boolean | undefined; + viewPageRoute?: undefined | Route; + onViewPage?: (item: TBaseModel) => Promise<Route>; + query?: Query<TBaseModel>; + groupBy?: GroupBy<TBaseModel> | undefined; + onBeforeFetch?: (() => Promise<TBaseModel>) | undefined; + createInitialValues?: FormValues<TBaseModel> | undefined; + onBeforeCreate?: + | ((item: TBaseModel, miscDataProps: JSONObject) => Promise<TBaseModel>) + | undefined; + onCreateSuccess?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined; + createVerb?: string; + showAs?: ShowAs | undefined; + singularName?: string | undefined; + pluralName?: string | undefined; + actionButtons?: Array<ActionButtonSchema<TBaseModel>> | undefined; + deleteButtonText?: string | undefined; + onCreateEditModalClose?: (() => void) | undefined; + editButtonText?: string | undefined; + viewButtonText?: string | undefined; + refreshToggle?: boolean | undefined; + fetchRequestOptions?: RequestOptions | undefined; + deleteRequestOptions?: RequestOptions | undefined; + onItemDeleted?: ((item: TBaseModel) => void) | undefined; + onBeforeEdit?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined; + onBeforeDelete?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined; + onBeforeView?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined; + sortBy?: keyof TBaseModel | undefined; + sortOrder?: SortOrder | undefined; + dragDropIdField?: keyof TBaseModel | undefined; + dragDropIndexField?: keyof TBaseModel | undefined; + createEditModalWidth?: ModalWidth | undefined; + orderedStatesListProps?: { + titleField: keyof TBaseModel; + descriptionField?: keyof TBaseModel | undefined; + orderField: keyof TBaseModel; + shouldAddItemInTheEnd?: boolean; + shouldAddItemInTheBeginning?: boolean; + }; + onViewComplete?: ((item: TBaseModel) => void) | undefined; + createEditFromRef?: + | undefined + | MutableRefObject<FormProps<FormValues<TBaseModel>>>; + name: string; - // bulk actions - bulkActions?: BulkActionProps<TBaseModel> | undefined; + // bulk actions + bulkActions?: BulkActionProps<TBaseModel> | undefined; - onShowFormType?: (formType: ModalType) => void; + onShowFormType?: (formType: ModalType) => void; } export interface ComponentProps< - TBaseModel extends BaseModel | AnalyticsBaseModel + TBaseModel extends BaseModel | AnalyticsBaseModel, > extends BaseTableProps<TBaseModel> { - callbacks: BaseTableCallbacks<TBaseModel>; + callbacks: BaseTableCallbacks<TBaseModel>; } export enum ModalType { - Create, - Edit, + Create, + Edit, } const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel | AnalyticsBaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const matchBulkSelectedItemByField: keyof TBaseModel = - props.bulkActions?.matchBulkSelectedItemByField || '_id'; + const matchBulkSelectedItemByField: keyof TBaseModel = + props.bulkActions?.matchBulkSelectedItemByField || "_id"; - const model: TBaseModel = new props.modelType(); + const model: TBaseModel = new props.modelType(); - const [bulkSelectedItems, setBulkSelectedItems] = useState< - Array<TBaseModel> - >([]); + const [bulkSelectedItems, setBulkSelectedItems] = useState<Array<TBaseModel>>( + [], + ); - let showAs: ShowAs | undefined = props.showAs; + let showAs: ShowAs | undefined = props.showAs; - if (!showAs) { - showAs = ShowAs.Table; + if (!showAs) { + showAs = ShowAs.Table; + } + + const [showViewIdModal, setShowViewIdModal] = useState<boolean>(false); + const [viewId, setViewId] = useState<string | null>(null); + const [tableColumns, setColumns] = useState<Array<TableColumn<TBaseModel>>>( + [], + ); + + const [classicTableFilters, setClassicTableFilters] = useState< + Array<ClassicFilterType<TBaseModel>> + >([]); + + const [cardButtons, setCardButtons] = useState<Array<CardButtonSchema>>([]); + + const [actionButtonSchema, setActionButtonSchema] = useState< + Array<ActionButtonSchema<TBaseModel>> + >([]); + + useEffect(() => { + if (props.showCreateForm) { + setShowModal(true); + setModalType(ModalType.Create); + } + }, [props.showCreateForm]); + + useEffect(() => { + if (props.onShowFormType) { + props.onShowFormType(modalType); + } + }, [props.modelType]); + + const [orderedStatesListNewItemOrder, setOrderedStatesListNewItemOrder] = + useState<number | null>(null); + + const [onBeforeFetchData, setOnBeforeFetchData] = useState< + TBaseModel | undefined + >(undefined); + const [data, setData] = useState<Array<TBaseModel>>([]); + const [query, setQuery] = useState<Query<TBaseModel>>({}); + const [currentPageNumber, setCurrentPageNumber] = useState<number>(1); + const [totalItemsCount, setTotalItemsCount] = useState<number>(0); + const [isLoading, setIsLoading] = useState<boolean>(true); + + const [error, setError] = useState<string>(""); + const [tableFilterError, setTableFilterError] = useState<string>(""); + + const [showModel, setShowModal] = useState<boolean>(false); + const [showFilterModal, setShowFilterModal] = useState<boolean>(false); + const [modalType, setModalType] = useState<ModalType>(ModalType.Create); + const [sortBy, setSortBy] = useState<keyof TBaseModel | null>( + props.sortBy || null, + ); + const [sortOrder, setSortOrder] = useState<SortOrder>( + props.sortOrder || SortOrder.Ascending, + ); + const [showDeleteConfirmModal, setShowDeleteConfirmModal] = + useState<boolean>(false); + const [currentEditableItem, setCurrentEditableItem] = + useState<TBaseModel | null>(null); + const [currentDeleteableItem, setCurrentDeleteableItem] = + useState<TBaseModel | null>(null); + + const [itemsOnPage, setItemsOnPage] = useState<number>( + props.initialItemsOnPage || 10, + ); + + const [fields, setFields] = useState<Array<Field<TBaseModel>>>([]); + + const [isFilterFetchLoading, setIsFilterFetchLoading] = useState(false); + + const [errorModalText, setErrorModalText] = useState<string>(""); + + useEffect(() => { + if (!showModel) { + props.onCreateEditModalClose && props.onCreateEditModalClose(); + } + }, [showModel]); + + useEffect(() => { + const detailFields: Array<Field<TBaseModel>> = []; + for (const column of tableColumns) { + if (!column.key) { + // if its an action column, ignore. + continue; + } + + detailFields.push({ + title: column.title, + description: column.description || "", + key: column.key as keyof TBaseModel, + fieldType: column.type, + colSpan: column.colSpan, + contentClassName: column.contentClassName, + alignItem: column.alignItem, + getElement: column.getElement + ? (item: TBaseModel): ReactElement => { + return column.getElement!(item, onBeforeFetchData); + } + : undefined, + }); + + setFields(detailFields); + } + }, [tableColumns]); + + type GetRelationSelectFunction = () => Select<TBaseModel>; + + const getRelationSelect: GetRelationSelectFunction = + (): Select<TBaseModel> => { + const relationSelect: Select<TBaseModel> = {}; + + for (const column of props.columns || []) { + const key: string | null = column.field + ? (Object.keys(column.field)[0] as string) + : null; + + if (key && model.isFileColumn(key)) { + (relationSelect as JSONObject)[key] = { + file: true, + _id: true, + type: true, + name: true, + }; + } else if (key && model.isEntityColumn(key)) { + if (!(relationSelect as JSONObject)[key]) { + (relationSelect as JSONObject)[key] = {}; + } + + (relationSelect as JSONObject)[key] = { + ...((relationSelect as JSONObject)[key] as JSONObject), + ...(column.field as any)[key], + }; + } + } + + return relationSelect; + }; + + type DeleteItemFunction = (item: TBaseModel) => Promise<void>; + + const deleteItem: DeleteItemFunction = async (item: TBaseModel) => { + if (!item.id) { + throw new BadDataException("item.id cannot be null"); } - const [showViewIdModal, setShowViewIdModal] = useState<boolean>(false); - const [viewId, setViewId] = useState<string | null>(null); - const [tableColumns, setColumns] = useState<Array<TableColumn<TBaseModel>>>( - [] - ); + setIsLoading(true); - const [classicTableFilters, setClassicTableFilters] = useState< - Array<ClassicFilterType<TBaseModel>> - >([]); + try { + await props.callbacks.deleteItem(item); - const [cardButtons, setCardButtons] = useState<Array<CardButtonSchema>>([]); + props.onItemDeleted && props.onItemDeleted(item); - const [actionButtonSchema, setActionButtonSchema] = useState< - Array<ActionButtonSchema<TBaseModel>> - >([]); + if (data.length === 1 && currentPageNumber > 1) { + setCurrentPageNumber(currentPageNumber - 1); + } + await fetchItems(); + } catch (err) { + setErrorModalText(API.getFriendlyMessage(err)); + } - useEffect(() => { - if (props.showCreateForm) { - setShowModal(true); - setModalType(ModalType.Create); - } - }, [props.showCreateForm]); + setIsLoading(false); + }; - useEffect(() => { - if (props.onShowFormType) { - props.onShowFormType(modalType); - } - }, [props.modelType]); + const serializeToTableColumns: VoidFunction = (): void => { + // Convert ModelColumns to TableColumns. - const [orderedStatesListNewItemOrder, setOrderedStatesListNewItemOrder] = - useState<number | null>(null); + const columns: Array<TableColumn<TBaseModel>> = []; - const [onBeforeFetchData, setOnBeforeFetchData] = useState< - TBaseModel | undefined - >(undefined); - const [data, setData] = useState<Array<TBaseModel>>([]); - const [query, setQuery] = useState<Query<TBaseModel>>({}); - const [currentPageNumber, setCurrentPageNumber] = useState<number>(1); - const [totalItemsCount, setTotalItemsCount] = useState<number>(0); - const [isLoading, setIsLoading] = useState<boolean>(true); - - const [error, setError] = useState<string>(''); - const [tableFilterError, setTableFilterError] = useState<string>(''); - - const [showModel, setShowModal] = useState<boolean>(false); - const [showFilterModal, setShowFilterModal] = useState<boolean>(false); - const [modalType, setModalType] = useState<ModalType>(ModalType.Create); - const [sortBy, setSortBy] = useState<keyof TBaseModel | null>( - props.sortBy || null - ); - const [sortOrder, setSortOrder] = useState<SortOrder>( - props.sortOrder || SortOrder.Ascending - ); - const [showDeleteConfirmModal, setShowDeleteConfirmModal] = - useState<boolean>(false); - const [currentEditableItem, setCurrentEditableItem] = - useState<TBaseModel | null>(null); - const [currentDeleteableItem, setCurrentDeleteableItem] = - useState<TBaseModel | null>(null); - - const [itemsOnPage, setItemsOnPage] = useState<number>( - props.initialItemsOnPage || 10 - ); - - const [fields, setFields] = useState<Array<Field<TBaseModel>>>([]); - - const [isFilterFetchLoading, setIsFilterFetchLoading] = useState(false); - - const [errorModalText, setErrorModalText] = useState<string>(''); - - useEffect(() => { - if (!showModel) { - props.onCreateEditModalClose && props.onCreateEditModalClose(); - } - }, [showModel]); - - useEffect(() => { - const detailFields: Array<Field<TBaseModel>> = []; - for (const column of tableColumns) { - if (!column.key) { - // if its an action column, ignore. - continue; - } - - detailFields.push({ - title: column.title, - description: column.description || '', - key: column.key as keyof TBaseModel, - fieldType: column.type, - colSpan: column.colSpan, - contentClassName: column.contentClassName, - alignItem: column.alignItem, - getElement: column.getElement - ? (item: TBaseModel): ReactElement => { - return column.getElement!(item, onBeforeFetchData); - } - : undefined, - }); - - setFields(detailFields); - } - }, [tableColumns]); - - type GetRelationSelectFunction = () => Select<TBaseModel>; - - const getRelationSelect: GetRelationSelectFunction = - (): Select<TBaseModel> => { - const relationSelect: Select<TBaseModel> = {}; - - for (const column of props.columns || []) { - const key: string | null = column.field - ? (Object.keys(column.field)[0] as string) - : null; - - if (key && model.isFileColumn(key)) { - (relationSelect as JSONObject)[key] = { - file: true, - _id: true, - type: true, - name: true, - }; - } else if (key && model.isEntityColumn(key)) { - if (!(relationSelect as JSONObject)[key]) { - (relationSelect as JSONObject)[key] = {}; - } - - (relationSelect as JSONObject)[key] = { - ...((relationSelect as JSONObject)[key] as JSONObject), - ...(column.field as any)[key], - }; - } - } - - return relationSelect; - }; - - type DeleteItemFunction = (item: TBaseModel) => Promise<void>; - - const deleteItem: DeleteItemFunction = async (item: TBaseModel) => { - if (!item.id) { - throw new BadDataException('item.id cannot be null'); - } - - setIsLoading(true); - - try { - await props.callbacks.deleteItem(item); - - props.onItemDeleted && props.onItemDeleted(item); - - if (data.length === 1 && currentPageNumber > 1) { - setCurrentPageNumber(currentPageNumber - 1); - } - await fetchItems(); - } catch (err) { - setErrorModalText(API.getFriendlyMessage(err)); - } - - setIsLoading(false); + let selectFields: Select<TBaseModel> = { + _id: true, }; - const serializeToTableColumns: VoidFunction = (): void => { - // Convert ModelColumns to TableColumns. + selectFields = props.callbacks.addSlugToSelect(selectFields); - const columns: Array<TableColumn<TBaseModel>> = []; + const userPermissions: Array<Permission> = getUserPermissions(); - let selectFields: Select<TBaseModel> = { - _id: true, - }; + const accessControl: Dictionary<ColumnAccessControl> = + model.getColumnAccessControlForAllColumns(); - selectFields = props.callbacks.addSlugToSelect(selectFields); + for (const column of props.columns || []) { + const hasPermission: boolean = + hasPermissionToReadColumn(column) || User.isMasterAdmin(); + const key: keyof TBaseModel | null = getColumnKey(column); - const userPermissions: Array<Permission> = getUserPermissions(); - - const accessControl: Dictionary<ColumnAccessControl> = - model.getColumnAccessControlForAllColumns(); - - for (const column of props.columns || []) { - const hasPermission: boolean = - hasPermissionToReadColumn(column) || User.isMasterAdmin(); - const key: keyof TBaseModel | null = getColumnKey(column); - - if (hasPermission) { - let tooltipText: ((item: TBaseModel) => string) | undefined = - undefined; - - if (column.tooltipText) { - tooltipText = (item: TBaseModel): string => { - if ( - item instanceof BaseModel || - item instanceof AnalyticsBaseModel - ) { - return column.tooltipText!(item); - } - - return column.tooltipText!( - props.callbacks.getModelFromJSON(item as JSONObject) - ); - }; - } - - // get filter options if they were already loaded - - const columnKey: keyof TBaseModel | null = - column.selectedProperty - ? (((key as string) + - '.' + - column.selectedProperty) as keyof TBaseModel) - : key; - - columns.push({ - ...column, - disableSort: column.disableSort || shouldDisableSort(key), - key: columnKey, - tooltipText, - getElement: column.getElement, - }); - - if (key) { - selectFields[key] = true; - } - } - } - - const selectMoreFields: Array<string> = props.selectMoreFields - ? Object.keys(props.selectMoreFields) - : []; - - for (const moreField of selectMoreFields) { - let hasPermissionToSelectField: boolean = true; - let fieldPermissions: Array<Permission> = []; - fieldPermissions = accessControl[moreField as string]?.read || []; + if (hasPermission) { + let tooltipText: ((item: TBaseModel) => string) | undefined = undefined; + if (column.tooltipText) { + tooltipText = (item: TBaseModel): string => { if ( - accessControl[moreField]?.read && - !PermissionHelper.doesPermissionsIntersect( - userPermissions, - fieldPermissions - ) + item instanceof BaseModel || + item instanceof AnalyticsBaseModel ) { - hasPermissionToSelectField = false; + return column.tooltipText!(item); } - if (hasPermissionToSelectField) { - (selectFields as Dictionary<boolean>)[moreField] = true; - } else { - Logger.warn( - 'User does not have read permissions to read - ' + moreField - ); - } + return column.tooltipText!( + props.callbacks.getModelFromJSON(item as JSONObject), + ); + }; } - const permissions: Array<Permission> | null = - PermissionUtil.getAllPermissions(); + // get filter options if they were already loaded - let showActionsColumn: boolean = Boolean( - (permissions && - ((props.isDeleteable && - model.hasDeletePermissions(permissions)) || - (props.isEditable && - model.hasUpdatePermissions(permissions)) || - (props.isViewable && - model.hasReadPermissions(permissions)))) || - (props.actionButtons && props.actionButtons.length > 0) || - props.showViewIdButton - ); + const columnKey: keyof TBaseModel | null = column.selectedProperty + ? (((key as string) + + "." + + column.selectedProperty) as keyof TBaseModel) + : key; - if (User.isMasterAdmin()) { - if ( - (props.actionButtons && props.actionButtons.length > 0) || - props.showViewIdButton - ) { - showActionsColumn = true; - } - } - - if (showActionsColumn) { - columns.push({ - title: 'Actions', - type: FieldType.Actions, - }); - } - - setActionSchema(); - setHeaderButtons(); - - setColumns(columns); - }; - - const getFilterDropdownItems: PromiseVoidFunction = - async (): Promise<void> => { - setTableFilterError(''); - setIsFilterFetchLoading(true); - - const filters: Array<Filter<TBaseModel>> = [...props.filters]; - - try { - for (const filter of filters) { - const key: keyof TBaseModel | null = getFilterKey(filter); - - if (!key) { - continue; - } - - if (!filter.filterEntityType) { - continue; - } - - if (!filter.filterDropdownField) { - Logger.warn( - `Cannot filter on ${key.toString()} because filter.dropdownField is not set.` - ); - continue; - } - - const hasPermission: boolean = - hasPermissionToReadFilter(filter); - - if (!hasPermission) { - continue; - } - - if (filter.fetchFilterDropdownOptions) { - // fetch filter dropdown options. - filter.filterDropdownOptions = - await filter.fetchFilterDropdownOptions(); - continue; - } - - const query: Query<TBaseModel> = filter.filterQuery || {}; - - const listResult: ListResult<TBaseModel> = - await props.callbacks.getList({ - modelType: filter.filterEntityType, - query: query, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - [filter.filterDropdownField.label]: true, - [filter.filterDropdownField.value]: true, - } as any, - sort: {}, - }); - - filter.filterDropdownOptions = []; - - for (const item of listResult.data) { - filter.filterDropdownOptions.push({ - value: item.getColumnValue( - filter.filterDropdownField.value - ) as string, - label: item.getColumnValue( - filter.filterDropdownField.label - ) as string, - }); - } - } - - const classicFilters: Array<ClassicFilterType<TBaseModel>> = - filters - .map((filter: Filter<TBaseModel>) => { - const key: keyof TBaseModel | null = - getFilterKey(filter); - - if (!key) { - return null; - } - - return { - title: filter.title, - filterDropdownOptions: - filter.filterDropdownOptions, - key: key, - type: filter.type, - }; - }) - .filter( - (filter: ClassicFilterType<TBaseModel> | null) => { - return filter !== null; - } - ) as Array<ClassicFilterType<TBaseModel>>; - - setClassicTableFilters(classicFilters); - - setHeaderButtons(); - } catch (err) { - setTableFilterError(API.getFriendlyMessage(err)); - } - - setIsFilterFetchLoading(false); - }; - - const fetchAllBulkItems: PromiseVoidFunction = async (): Promise<void> => { - setError(''); - setIsLoading(true); - - try { - const listResult: ListResult<TBaseModel> = - await props.callbacks.getList({ - modelType: props.modelType as - | BaseModelType - | AnalyticsBaseModelType, - query: { - ...query, - ...props.query, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - }, - sort: {}, - requestOptions: props.fetchRequestOptions, - }); - - setBulkSelectedItems(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - const fetchItems: PromiseVoidFunction = async (): Promise<void> => { - setError(''); - setIsLoading(true); - - if (props.onFetchInit) { - props.onFetchInit(currentPageNumber, itemsOnPage); - } - - if (props.onBeforeFetch) { - const model: TBaseModel = await props.onBeforeFetch(); - setOnBeforeFetchData(model); - } - - try { - const listResult: ListResult<TBaseModel> = - await props.callbacks.getList({ - modelType: props.modelType as - | BaseModelType - | AnalyticsBaseModelType, - query: { - ...query, - ...props.query, - }, - groupBy: { - ...props.groupBy, - }, - limit: itemsOnPage, - skip: (currentPageNumber - 1) * itemsOnPage, - select: { - ...getSelect(), - ...getRelationSelect(), - }, - sort: sortBy - ? { - [sortBy as any]: sortOrder, - } - : {}, - requestOptions: props.fetchRequestOptions, - }); - - setTotalItemsCount(listResult.count); - setData(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - useEffect(() => { - if (showFilterModal) { - getFilterDropdownItems().catch((err: Error) => { - setTableFilterError(API.getFriendlyMessage(err)); - }); - } - }, [showFilterModal]); - - type GetSelectFunction = () => Select<TBaseModel>; - - const getSelect: GetSelectFunction = (): Select<TBaseModel> => { - const selectFields: Select<TBaseModel> = { - _id: true, - }; - - for (const column of props.columns || []) { - const key: string | null = column.field - ? (Object.keys(column.field)[0] as string) - : null; - - if (key) { - if (model.hasColumn(key)) { - (selectFields as Dictionary<boolean>)[key] = true; - } else { - throw new BadDataException( - `${key} column not found on ${model.singularName}` - ); - } - } - } - - const selectMoreFields: Array<keyof TBaseModel> = props.selectMoreFields - ? (Object.keys(props.selectMoreFields) as Array<keyof TBaseModel>) - : []; - - if (props.dragDropIndexField) { - selectMoreFields.push(props.dragDropIndexField); - } - - if ( - props.dragDropIdField && - !Object.keys(selectFields).includes( - props.dragDropIdField as string - ) && - !selectMoreFields.includes(props.dragDropIdField) - ) { - selectMoreFields.push(props.dragDropIdField); - } - - for (const moreField of selectMoreFields) { - if ( - model.hasColumn(moreField as string) && - model.isEntityColumn(moreField as string) - ) { - (selectFields as Dictionary<boolean>)[moreField as string] = ( - props.selectMoreFields as any - )[moreField]; - } else if (model.hasColumn(moreField as string)) { - (selectFields as Dictionary<boolean>)[moreField as string] = - true; - } else { - throw new BadDataException( - `${moreField as string} column not found on ${ - model.singularName - }` - ); - } - } - - return selectFields; - }; - - const setHeaderButtons: VoidFunction = (): void => { - // add header buttons. - let headerbuttons: Array<CardButtonSchema> = []; - - if (props.cardProps?.buttons && props.cardProps?.buttons.length > 0) { - headerbuttons = [...props.cardProps.buttons]; - } - - const permissions: Array<Permission> | null = - PermissionUtil.getAllPermissions(); - - let hasPermissionToCreate: boolean = false; - - if (permissions) { - hasPermissionToCreate = - model.hasCreatePermissions(permissions) || User.isMasterAdmin(); - } - - const showFilterButton: boolean = props.filters.length > 0; - - // because ordered list add button is inside the table and not on the card header. - if ( - props.isCreateable && - hasPermissionToCreate && - showAs !== ShowAs.OrderedStatesList - ) { - headerbuttons.push({ - title: `${props.createVerb || 'Create'} ${ - props.singularName || model.singularName - }`, - buttonStyle: ButtonStyleType.NORMAL, - className: - showFilterButton || props.showRefreshButton ? 'mr-1' : '', - onClick: () => { - setModalType(ModalType.Create); - setShowModal(true); - }, - icon: IconProp.Add, - }); - } - - if (props.showRefreshButton) { - headerbuttons.push({ - ...getRefreshButton(), - className: showFilterButton - ? 'p-1 px-1 pr-0 pl-0 py-0 mt-1' - : 'py-0 pr-0 pl-1 mt-1', - - onClick: async () => { - await fetchItems(); - }, - disabled: isFilterFetchLoading, - }); - } - - if (showFilterButton) { - headerbuttons.push({ - title: '', - buttonStyle: ButtonStyleType.ICON, - className: props.showRefreshButton - ? 'p-1 px-1 pr-0 pl-0 py-0 mt-1' - : 'py-0 pr-0 pl-1 mt-1', - onClick: () => { - setQuery({}); - setShowFilterModal(true); - }, - disabled: isFilterFetchLoading, - icon: IconProp.Filter, - }); - } - - setCardButtons(headerbuttons); - }; - - useEffect(() => { - fetchItems().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); + columns.push({ + ...column, + disableSort: column.disableSort || shouldDisableSort(key), + key: columnKey, + tooltipText, + getElement: column.getElement, }); - }, [ - currentPageNumber, - sortBy, - sortOrder, - itemsOnPage, - query, - props.refreshToggle, - ]); - - type ShouldDisableSortFunction = ( - columnName: keyof TBaseModel | null - ) => boolean; - - const shouldDisableSort: ShouldDisableSortFunction = ( - columnName: keyof TBaseModel | null - ): boolean => { - if (!columnName) { - return true; - } - - return model.isEntityColumn(columnName as string); - }; - - type GetColumnKeyFunction = ( - column: ModelTableColumn<TBaseModel> - ) => keyof TBaseModel | null; - - const getColumnKey: GetColumnKeyFunction = ( - column: ModelTableColumn<TBaseModel> - ): keyof TBaseModel | null => { - const key: keyof TBaseModel | null = column.field - ? (Object.keys(column.field)[0] as keyof TBaseModel) - : null; - - return key; - }; - - type GetFilterKeyFunction = ( - filter: Filter<TBaseModel> - ) => keyof TBaseModel | null; - - const getFilterKey: GetFilterKeyFunction = ( - filter: Filter<TBaseModel> - ): keyof TBaseModel | null => { - const key: keyof TBaseModel | null = filter.field - ? (Object.keys(filter.field)[0] as keyof TBaseModel) - : null; - - return key; - }; - - type HasPermissionToReadColumnFunction = ( - column: ModelTableColumn<TBaseModel> - ) => boolean; - - const hasPermissionToReadColumn: HasPermissionToReadColumnFunction = ( - column: ModelTableColumn<TBaseModel> - ): boolean => { - const key: keyof TBaseModel | null = getColumnKey(column); - - if (!key) { - return true; - } - - return hasPermissionToReadField(key); - }; - - type HasPermissionToReadFilterColumn = ( - filter: Filter<TBaseModel> - ) => boolean; - - const hasPermissionToReadFilter: HasPermissionToReadFilterColumn = ( - filter: Filter<TBaseModel> - ): boolean => { - const key: keyof TBaseModel | null = getFilterKey(filter); - - if (!key) { - return true; - } - - return hasPermissionToReadField(key); - }; - - type HasPermissionToReadFieldFunction = ( - field: keyof TBaseModel - ) => boolean; - - const hasPermissionToReadField: HasPermissionToReadFieldFunction = ( - field: keyof TBaseModel - ): boolean => { - const accessControl: Dictionary<ColumnAccessControl> = - model.getColumnAccessControlForAllColumns(); - - const userPermissions: Array<Permission> = getUserPermissions(); - - const key: keyof TBaseModel = field; - // check permissions. - let hasPermission: boolean = false; - - if (!key) { - hasPermission = true; - } if (key) { - hasPermission = true; - let fieldPermissions: Array<Permission> = []; - fieldPermissions = accessControl[key as string]?.read || []; - - if ( - accessControl[key]?.read && - !PermissionHelper.doesPermissionsIntersect( - userPermissions, - fieldPermissions - ) - ) { - hasPermission = false; - } + selectFields[key] = true; } + } + } - return hasPermission; - }; + const selectMoreFields: Array<string> = props.selectMoreFields + ? Object.keys(props.selectMoreFields) + : []; - type GetUserPermissionsFunction = () => Array<Permission>; + for (const moreField of selectMoreFields) { + let hasPermissionToSelectField: boolean = true; + let fieldPermissions: Array<Permission> = []; + fieldPermissions = accessControl[moreField as string]?.read || []; - const getUserPermissions: GetUserPermissionsFunction = - (): Array<Permission> => { - let userPermissions: Array<Permission> = - PermissionUtil.getGlobalPermissions()?.globalPermissions || []; - if ( - PermissionUtil.getProjectPermissions() && - PermissionUtil.getProjectPermissions()?.permissions && - PermissionUtil.getProjectPermissions()!.permissions.length > 0 - ) { - userPermissions = userPermissions.concat( - PermissionUtil.getProjectPermissions()!.permissions.map( - (i: UserPermission) => { - return i.permission; - } - ) - ); - } + if ( + accessControl[moreField]?.read && + !PermissionHelper.doesPermissionsIntersect( + userPermissions, + fieldPermissions, + ) + ) { + hasPermissionToSelectField = false; + } - userPermissions.push(Permission.Public); + if (hasPermissionToSelectField) { + (selectFields as Dictionary<boolean>)[moreField] = true; + } else { + Logger.warn( + "User does not have read permissions to read - " + moreField, + ); + } + } - return userPermissions; - }; + const permissions: Array<Permission> | null = + PermissionUtil.getAllPermissions(); - useEffect(() => { - serializeToTableColumns(); - }, []); + let showActionsColumn: boolean = Boolean( + (permissions && + ((props.isDeleteable && model.hasDeletePermissions(permissions)) || + (props.isEditable && model.hasUpdatePermissions(permissions)) || + (props.isViewable && model.hasReadPermissions(permissions)))) || + (props.actionButtons && props.actionButtons.length > 0) || + props.showViewIdButton, + ); - useEffect(() => { - serializeToTableColumns(); - }, [data]); + if (User.isMasterAdmin()) { + if ( + (props.actionButtons && props.actionButtons.length > 0) || + props.showViewIdButton + ) { + showActionsColumn = true; + } + } - const setActionSchema: VoidFunction = () => { - const permissions: Array<Permission> = - PermissionUtil.getAllPermissions(); + if (showActionsColumn) { + columns.push({ + title: "Actions", + type: FieldType.Actions, + }); + } - const actionsSchema: Array<ActionButtonSchema<TBaseModel>> = []; + setActionSchema(); + setHeaderButtons(); - if (props.showViewIdButton) { - actionsSchema.push({ - title: 'Show ID', - buttonStyleType: ButtonStyleType.OUTLINE, - onClick: async ( - item: TBaseModel, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setViewId(item['_id'] as string); - setShowViewIdModal(true); - onCompleteAction(); - } catch (err) { - onError(err as Error); - } - }, + setColumns(columns); + }; + + const getFilterDropdownItems: PromiseVoidFunction = + async (): Promise<void> => { + setTableFilterError(""); + setIsFilterFetchLoading(true); + + const filters: Array<Filter<TBaseModel>> = [...props.filters]; + + try { + for (const filter of filters) { + const key: keyof TBaseModel | null = getFilterKey(filter); + + if (!key) { + continue; + } + + if (!filter.filterEntityType) { + continue; + } + + if (!filter.filterDropdownField) { + Logger.warn( + `Cannot filter on ${key.toString()} because filter.dropdownField is not set.`, + ); + continue; + } + + const hasPermission: boolean = hasPermissionToReadFilter(filter); + + if (!hasPermission) { + continue; + } + + if (filter.fetchFilterDropdownOptions) { + // fetch filter dropdown options. + filter.filterDropdownOptions = + await filter.fetchFilterDropdownOptions(); + continue; + } + + const query: Query<TBaseModel> = filter.filterQuery || {}; + + const listResult: ListResult<TBaseModel> = + await props.callbacks.getList({ + modelType: filter.filterEntityType, + query: query, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + [filter.filterDropdownField.label]: true, + [filter.filterDropdownField.value]: true, + } as any, + sort: {}, }); + + filter.filterDropdownOptions = []; + + for (const item of listResult.data) { + filter.filterDropdownOptions.push({ + value: item.getColumnValue( + filter.filterDropdownField.value, + ) as string, + label: item.getColumnValue( + filter.filterDropdownField.label, + ) as string, + }); + } } - // add actions buttons from props. - if (props.actionButtons) { - for (const moreSchema of props.actionButtons) { - actionsSchema.push(moreSchema); - } - } + const classicFilters: Array<ClassicFilterType<TBaseModel>> = filters + .map((filter: Filter<TBaseModel>) => { + const key: keyof TBaseModel | null = getFilterKey(filter); - if (permissions) { - if ( - props.isViewable && - (model.hasReadPermissions(permissions) || User.isMasterAdmin()) - ) { - actionsSchema.push({ - title: - props.viewButtonText || - `View ${props.singularName || model.singularName}`, - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - item: TBaseModel, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - let baseModel: TBaseModel = item; - if ( - !(item instanceof BaseModel) && - !(item instanceof AnalyticsBaseModel) - ) { - baseModel = props.callbacks.getModelFromJSON( - item as JSONObject - ); - } - - if (props.onBeforeView) { - item = await props.onBeforeView(baseModel); - } - - if (props.onViewPage) { - const route: Route = await props.onViewPage( - baseModel - ); - - onCompleteAction(); - - if (props.onViewComplete) { - props.onViewComplete(baseModel); - } - - return Navigation.navigate(route); - } - - if (!props.viewPageRoute) { - throw new BadDataException( - 'props.viewPageRoute not found' - ); - } - - onCompleteAction(); - if (props.onViewComplete) { - props.onViewComplete(baseModel); - } - - const id: string = baseModel.id?.toString() || ''; - - return Navigation.navigate( - new Route( - props.viewPageRoute.toString() - ).addRoute('/' + id) - ); - } catch (err) { - onError(err as Error); - } - }, - }); + if (!key) { + return null; } - if ( - props.isEditable && - (model.hasUpdatePermissions(permissions) || - User.isMasterAdmin()) - ) { - actionsSchema.push({ - title: props.editButtonText || 'Edit', - buttonStyleType: ButtonStyleType.OUTLINE, - onClick: async ( - item: TBaseModel, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - if (props.onBeforeEdit) { - item = await props.onBeforeEdit(item); - } - - setModalType(ModalType.Edit); - setShowModal(true); - setCurrentEditableItem(item); - - onCompleteAction(); - } catch (err) { - onError(err as Error); - } - }, - }); - } - - if ( - props.isDeleteable && - (model.hasDeletePermissions(permissions) || - User.isMasterAdmin()) - ) { - actionsSchema.push({ - title: props.deleteButtonText || 'Delete', - icon: IconProp.Trash, - buttonStyleType: ButtonStyleType.DANGER_OUTLINE, - onClick: async ( - item: TBaseModel, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - if (props.onBeforeDelete) { - item = await props.onBeforeDelete(item); - } - - setShowDeleteConfirmModal(true); - setCurrentDeleteableItem(item); - onCompleteAction(); - } catch (err) { - onError(err as Error); - } - }, - }); - } - } - - setActionButtonSchema(actionsSchema); - }; - - type OnFilterChangedFunction = (filterData: FilterData<TBaseModel>) => void; - - const onFilterChanged: OnFilterChangedFunction = ( - filterData: FilterData<TBaseModel> - ): void => { - const newQuery: Query<TBaseModel> = {}; - - for (const key in filterData) { - if (filterData[key] && typeof filterData[key] === Typeof.String) { - newQuery[key as keyof TBaseModel] = ( - filterData[key] || '' - ).toString(); - } - - if (typeof filterData[key] === Typeof.Boolean) { - newQuery[key as keyof TBaseModel] = Boolean(filterData[key]); - } - - if (filterData[key] instanceof Date) { - newQuery[key as keyof TBaseModel] = filterData[key]; - } - - if (filterData[key] instanceof Search) { - newQuery[key as keyof TBaseModel] = filterData[key]; - } - - if (filterData[key] instanceof InBetween) { - newQuery[key as keyof TBaseModel] = filterData[key]; - } - - if ( - props.filters.find((f: Filter<TBaseModel>) => { - return f.field && f.field[key]; - })?.type === FieldType.JSON && - typeof filterData[key] === Typeof.Object - ) { - newQuery[key as keyof TBaseModel] = filterData[key]; - } - - if (Array.isArray(filterData[key])) { - newQuery[key as keyof TBaseModel] = filterData[key]; - } - } - - setQuery(newQuery); - }; - - type GetDeleteBulkActionFunction = () => BulkActionButtonSchema<TBaseModel>; - - const getDeleteBulkAction: GetDeleteBulkActionFunction = - (): BulkActionButtonSchema<TBaseModel> => { return { - title: 'Delete', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.Trash, - confirmMessage: (items: Array<TBaseModel>) => { - return `Are you sure you want to delete ${items.length} ${ - props.pluralName || model.pluralName || 'items' - }?`; - }, - confirmTitle: (items: Array<TBaseModel>) => { - return `Delete ${items.length} ${ - props.pluralName || model.pluralName || 'items' - }`; - }, - confirmButtonStyleType: ButtonStyleType.DANGER, - onClick: async ({ - items, - onProgressInfo, - onBulkActionStart, - onBulkActionEnd, - }: BulkActionOnClickProps<TBaseModel>) => { - onBulkActionStart(); - - const inProgressItems: Array<TBaseModel> = [...items]; - const successItems: Array<TBaseModel> = []; - const failedItems: Array<BulkActionFailed<TBaseModel>> = []; - - for (let i: number = 0; i < items.length; i++) { - try { - const item: TBaseModel = items[i]!; - // remove items from inProgressItems - inProgressItems.splice( - inProgressItems.indexOf(item), - 1 - ); - - await props.callbacks.deleteItem(item); - successItems.push(item); - - onProgressInfo({ - inProgressItems: inProgressItems, - successItems: successItems, - failed: failedItems, - totalItems: items, - }); - } catch (err) { - failedItems.push({ - item: items[i]!, - failedMessage: API.getFriendlyMessage(err), - }); - } - } - - onBulkActionEnd(); - }, + title: filter.title, + filterDropdownOptions: filter.filterDropdownOptions, + key: key, + type: filter.type, }; - }; + }) + .filter((filter: ClassicFilterType<TBaseModel> | null) => { + return filter !== null; + }) as Array<ClassicFilterType<TBaseModel>>; - const getTable: GetReactElementFunction = (): ReactElement => { - return ( - <Table - onFilterChanged={(filterData: FilterData<TBaseModel>) => { - onFilterChanged(filterData); - }} - onFilterRefreshClick={async () => { - await getFilterDropdownItems(); - }} - bulkActions={{ - buttons: props.bulkActions?.buttons.map( - ( - action: - | BulkActionButtonSchema<TBaseModel> - | ModalTableBulkDefaultActions - ) => { - const permissions: Array<Permission> = - PermissionUtil.getAllPermissions(); - if ( - action === - ModalTableBulkDefaultActions.Delete && - model.hasDeletePermissions(permissions) - ) { - return getDeleteBulkAction(); - } - return action; - } - ) as Array<BulkActionButtonSchema<TBaseModel>>, - }} - onBulkActionEnd={async () => { - setBulkSelectedItems([]); - await fetchItems(); - }} - onBulkActionStart={() => {}} - bulkSelectedItems={bulkSelectedItems} - onBulkSelectedItemAdded={(item: TBaseModel) => { - setBulkSelectedItems([...bulkSelectedItems, item]); - }} - onBulkSelectedItemRemoved={(item: TBaseModel) => { - setBulkSelectedItems( - bulkSelectedItems.filter((i: TBaseModel) => { - return ( - i[matchBulkSelectedItemByField]?.toString() !== - item[matchBulkSelectedItemByField]?.toString() - ); - }) - ); - }} - onBulkSelectItemsOnCurrentPage={() => { - const items: TBaseModel[] = [...bulkSelectedItems, ...data]; + setClassicTableFilters(classicFilters); - // remove duplicates + setHeaderButtons(); + } catch (err) { + setTableFilterError(API.getFriendlyMessage(err)); + } - const uniqueItems: TBaseModel[] = items.filter( - ( - item: TBaseModel, - index: number, - self: Array<TBaseModel> - ) => { - return ( - index === - self.findIndex((t: TBaseModel) => { - return ( - t[ - matchBulkSelectedItemByField - ]?.toString() === - item[ - matchBulkSelectedItemByField - ]?.toString() - ); - }) - ); - } - ); - - setBulkSelectedItems(uniqueItems); - }} - onBulkClearAllItems={() => { - setBulkSelectedItems([]); - }} - onBulkSelectAllItems={async () => { - await fetchAllBulkItems(); - }} - matchBulkSelectedItemByField={ - matchBulkSelectedItemByField || '_id' - } - bulkItemToString={(item: TBaseModel) => { - return ( - (props.singularName || item.singularName || '') + - ' ' + - item[matchBulkSelectedItemByField]?.toString() + - ' ' || '' - ); - }} - filters={classicTableFilters} - filterError={tableFilterError} - isFilterLoading={isFilterFetchLoading} - showFilterModal={showFilterModal} - onFilterModalClose={() => { - setShowFilterModal(false); - }} - onFilterModalOpen={() => { - setShowFilterModal(true); - }} - onSortChanged={( - sortBy: keyof TBaseModel | null, - sortOrder: SortOrder - ) => { - setSortBy(sortBy); - setSortOrder(sortOrder); - }} - singularLabel={ - props.singularName || model.singularName || 'Item' - } - pluralLabel={props.pluralName || model.pluralName || 'Items'} - error={error} - currentPageNumber={currentPageNumber} - isLoading={isLoading} - enableDragAndDrop={props.enableDragAndDrop} - dragDropIdField={'_id'} - dragDropIndexField={props.dragDropIndexField} - totalItemsCount={totalItemsCount} - data={data} - id={props.id} - columns={tableColumns} - itemsOnPage={itemsOnPage} - onDragDrop={async (id: string, newOrder: number) => { - if (!props.dragDropIndexField) { - return; - } - - setIsLoading(true); - - await props.callbacks.updateById({ - id: new ObjectID(id), - data: { - [props.dragDropIndexField]: newOrder, - }, - }); - - await fetchItems(); - }} - disablePagination={props.disablePagination || false} - onNavigateToPage={async ( - pageNumber: number, - itemsOnPage: number - ) => { - setCurrentPageNumber(pageNumber); - setItemsOnPage(itemsOnPage); - }} - noItemsMessage={props.noItemsMessage || ''} - onRefreshClick={async () => { - await fetchItems(); - }} - actionButtons={actionButtonSchema} - /> - ); + setIsFilterFetchLoading(false); }; - const getOrderedStatesList: GetReactElementFunction = (): ReactElement => { - if (!props.orderedStatesListProps) { - throw new BadDataException( - 'props.orderedStatesListProps required when showAs === ShowAs.OrderedStatesList' - ); - } + const fetchAllBulkItems: PromiseVoidFunction = async (): Promise<void> => { + setError(""); + setIsLoading(true); - let getTitleElement: - | (( - item: TBaseModel, - onBeforeFetchData?: TBaseModel | undefined - ) => ReactElement) - | undefined = undefined; + try { + const listResult: ListResult<TBaseModel> = await props.callbacks.getList({ + modelType: props.modelType as BaseModelType | AnalyticsBaseModelType, + query: { + ...query, + ...props.query, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + }, + sort: {}, + requestOptions: props.fetchRequestOptions, + }); - let getDescriptionElement: - | ((item: TBaseModel) => ReactElement) - | undefined = undefined; + setBulkSelectedItems(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - for (const column of props.columns) { - const key: string | undefined = Object.keys( - column.field as SelectEntityField<TBaseModel> - )[0]; + setIsLoading(false); + }; - if (key === props.orderedStatesListProps.titleField) { - getTitleElement = column.getElement; + const fetchItems: PromiseVoidFunction = async (): Promise<void> => { + setError(""); + setIsLoading(true); + + if (props.onFetchInit) { + props.onFetchInit(currentPageNumber, itemsOnPage); + } + + if (props.onBeforeFetch) { + const model: TBaseModel = await props.onBeforeFetch(); + setOnBeforeFetchData(model); + } + + try { + const listResult: ListResult<TBaseModel> = await props.callbacks.getList({ + modelType: props.modelType as BaseModelType | AnalyticsBaseModelType, + query: { + ...query, + ...props.query, + }, + groupBy: { + ...props.groupBy, + }, + limit: itemsOnPage, + skip: (currentPageNumber - 1) * itemsOnPage, + select: { + ...getSelect(), + ...getRelationSelect(), + }, + sort: sortBy + ? { + [sortBy as any]: sortOrder, } + : {}, + requestOptions: props.fetchRequestOptions, + }); - if (key === props.orderedStatesListProps.descriptionField) { - getDescriptionElement = column.getElement; + setTotalItemsCount(listResult.count); + setData(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + if (showFilterModal) { + getFilterDropdownItems().catch((err: Error) => { + setTableFilterError(API.getFriendlyMessage(err)); + }); + } + }, [showFilterModal]); + + type GetSelectFunction = () => Select<TBaseModel>; + + const getSelect: GetSelectFunction = (): Select<TBaseModel> => { + const selectFields: Select<TBaseModel> = { + _id: true, + }; + + for (const column of props.columns || []) { + const key: string | null = column.field + ? (Object.keys(column.field)[0] as string) + : null; + + if (key) { + if (model.hasColumn(key)) { + (selectFields as Dictionary<boolean>)[key] = true; + } else { + throw new BadDataException( + `${key} column not found on ${model.singularName}`, + ); + } + } + } + + const selectMoreFields: Array<keyof TBaseModel> = props.selectMoreFields + ? (Object.keys(props.selectMoreFields) as Array<keyof TBaseModel>) + : []; + + if (props.dragDropIndexField) { + selectMoreFields.push(props.dragDropIndexField); + } + + if ( + props.dragDropIdField && + !Object.keys(selectFields).includes(props.dragDropIdField as string) && + !selectMoreFields.includes(props.dragDropIdField) + ) { + selectMoreFields.push(props.dragDropIdField); + } + + for (const moreField of selectMoreFields) { + if ( + model.hasColumn(moreField as string) && + model.isEntityColumn(moreField as string) + ) { + (selectFields as Dictionary<boolean>)[moreField as string] = ( + props.selectMoreFields as any + )[moreField]; + } else if (model.hasColumn(moreField as string)) { + (selectFields as Dictionary<boolean>)[moreField as string] = true; + } else { + throw new BadDataException( + `${moreField as string} column not found on ${model.singularName}`, + ); + } + } + + return selectFields; + }; + + const setHeaderButtons: VoidFunction = (): void => { + // add header buttons. + let headerbuttons: Array<CardButtonSchema> = []; + + if (props.cardProps?.buttons && props.cardProps?.buttons.length > 0) { + headerbuttons = [...props.cardProps.buttons]; + } + + const permissions: Array<Permission> | null = + PermissionUtil.getAllPermissions(); + + let hasPermissionToCreate: boolean = false; + + if (permissions) { + hasPermissionToCreate = + model.hasCreatePermissions(permissions) || User.isMasterAdmin(); + } + + const showFilterButton: boolean = props.filters.length > 0; + + // because ordered list add button is inside the table and not on the card header. + if ( + props.isCreateable && + hasPermissionToCreate && + showAs !== ShowAs.OrderedStatesList + ) { + headerbuttons.push({ + title: `${props.createVerb || "Create"} ${ + props.singularName || model.singularName + }`, + buttonStyle: ButtonStyleType.NORMAL, + className: showFilterButton || props.showRefreshButton ? "mr-1" : "", + onClick: () => { + setModalType(ModalType.Create); + setShowModal(true); + }, + icon: IconProp.Add, + }); + } + + if (props.showRefreshButton) { + headerbuttons.push({ + ...getRefreshButton(), + className: showFilterButton + ? "p-1 px-1 pr-0 pl-0 py-0 mt-1" + : "py-0 pr-0 pl-1 mt-1", + + onClick: async () => { + await fetchItems(); + }, + disabled: isFilterFetchLoading, + }); + } + + if (showFilterButton) { + headerbuttons.push({ + title: "", + buttonStyle: ButtonStyleType.ICON, + className: props.showRefreshButton + ? "p-1 px-1 pr-0 pl-0 py-0 mt-1" + : "py-0 pr-0 pl-1 mt-1", + onClick: () => { + setQuery({}); + setShowFilterModal(true); + }, + disabled: isFilterFetchLoading, + icon: IconProp.Filter, + }); + } + + setCardButtons(headerbuttons); + }; + + useEffect(() => { + fetchItems().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, [ + currentPageNumber, + sortBy, + sortOrder, + itemsOnPage, + query, + props.refreshToggle, + ]); + + type ShouldDisableSortFunction = ( + columnName: keyof TBaseModel | null, + ) => boolean; + + const shouldDisableSort: ShouldDisableSortFunction = ( + columnName: keyof TBaseModel | null, + ): boolean => { + if (!columnName) { + return true; + } + + return model.isEntityColumn(columnName as string); + }; + + type GetColumnKeyFunction = ( + column: ModelTableColumn<TBaseModel>, + ) => keyof TBaseModel | null; + + const getColumnKey: GetColumnKeyFunction = ( + column: ModelTableColumn<TBaseModel>, + ): keyof TBaseModel | null => { + const key: keyof TBaseModel | null = column.field + ? (Object.keys(column.field)[0] as keyof TBaseModel) + : null; + + return key; + }; + + type GetFilterKeyFunction = ( + filter: Filter<TBaseModel>, + ) => keyof TBaseModel | null; + + const getFilterKey: GetFilterKeyFunction = ( + filter: Filter<TBaseModel>, + ): keyof TBaseModel | null => { + const key: keyof TBaseModel | null = filter.field + ? (Object.keys(filter.field)[0] as keyof TBaseModel) + : null; + + return key; + }; + + type HasPermissionToReadColumnFunction = ( + column: ModelTableColumn<TBaseModel>, + ) => boolean; + + const hasPermissionToReadColumn: HasPermissionToReadColumnFunction = ( + column: ModelTableColumn<TBaseModel>, + ): boolean => { + const key: keyof TBaseModel | null = getColumnKey(column); + + if (!key) { + return true; + } + + return hasPermissionToReadField(key); + }; + + type HasPermissionToReadFilterColumn = ( + filter: Filter<TBaseModel>, + ) => boolean; + + const hasPermissionToReadFilter: HasPermissionToReadFilterColumn = ( + filter: Filter<TBaseModel>, + ): boolean => { + const key: keyof TBaseModel | null = getFilterKey(filter); + + if (!key) { + return true; + } + + return hasPermissionToReadField(key); + }; + + type HasPermissionToReadFieldFunction = (field: keyof TBaseModel) => boolean; + + const hasPermissionToReadField: HasPermissionToReadFieldFunction = ( + field: keyof TBaseModel, + ): boolean => { + const accessControl: Dictionary<ColumnAccessControl> = + model.getColumnAccessControlForAllColumns(); + + const userPermissions: Array<Permission> = getUserPermissions(); + + const key: keyof TBaseModel = field; + // check permissions. + let hasPermission: boolean = false; + + if (!key) { + hasPermission = true; + } + + if (key) { + hasPermission = true; + let fieldPermissions: Array<Permission> = []; + fieldPermissions = accessControl[key as string]?.read || []; + + if ( + accessControl[key]?.read && + !PermissionHelper.doesPermissionsIntersect( + userPermissions, + fieldPermissions, + ) + ) { + hasPermission = false; + } + } + + return hasPermission; + }; + + type GetUserPermissionsFunction = () => Array<Permission>; + + const getUserPermissions: GetUserPermissionsFunction = + (): Array<Permission> => { + let userPermissions: Array<Permission> = + PermissionUtil.getGlobalPermissions()?.globalPermissions || []; + if ( + PermissionUtil.getProjectPermissions() && + PermissionUtil.getProjectPermissions()?.permissions && + PermissionUtil.getProjectPermissions()!.permissions.length > 0 + ) { + userPermissions = userPermissions.concat( + PermissionUtil.getProjectPermissions()!.permissions.map( + (i: UserPermission) => { + return i.permission; + }, + ), + ); + } + + userPermissions.push(Permission.Public); + + return userPermissions; + }; + + useEffect(() => { + serializeToTableColumns(); + }, []); + + useEffect(() => { + serializeToTableColumns(); + }, [data]); + + const setActionSchema: VoidFunction = () => { + const permissions: Array<Permission> = PermissionUtil.getAllPermissions(); + + const actionsSchema: Array<ActionButtonSchema<TBaseModel>> = []; + + if (props.showViewIdButton) { + actionsSchema.push({ + title: "Show ID", + buttonStyleType: ButtonStyleType.OUTLINE, + onClick: async ( + item: TBaseModel, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setViewId(item["_id"] as string); + setShowViewIdModal(true); + onCompleteAction(); + } catch (err) { + onError(err as Error); + } + }, + }); + } + + // add actions buttons from props. + if (props.actionButtons) { + for (const moreSchema of props.actionButtons) { + actionsSchema.push(moreSchema); + } + } + + if (permissions) { + if ( + props.isViewable && + (model.hasReadPermissions(permissions) || User.isMasterAdmin()) + ) { + actionsSchema.push({ + title: + props.viewButtonText || + `View ${props.singularName || model.singularName}`, + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async ( + item: TBaseModel, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + let baseModel: TBaseModel = item; + if ( + !(item instanceof BaseModel) && + !(item instanceof AnalyticsBaseModel) + ) { + baseModel = props.callbacks.getModelFromJSON( + item as JSONObject, + ); + } + + if (props.onBeforeView) { + item = await props.onBeforeView(baseModel); + } + + if (props.onViewPage) { + const route: Route = await props.onViewPage(baseModel); + + onCompleteAction(); + + if (props.onViewComplete) { + props.onViewComplete(baseModel); + } + + return Navigation.navigate(route); + } + + if (!props.viewPageRoute) { + throw new BadDataException("props.viewPageRoute not found"); + } + + onCompleteAction(); + if (props.onViewComplete) { + props.onViewComplete(baseModel); + } + + const id: string = baseModel.id?.toString() || ""; + + return Navigation.navigate( + new Route(props.viewPageRoute.toString()).addRoute("/" + id), + ); + } catch (err) { + onError(err as Error); } - } + }, + }); + } - return ( - <OrderedStatesList<TBaseModel> - error={error} - isLoading={isLoading} - data={data} - id={props.id} - titleField={props.orderedStatesListProps?.titleField} - descriptionField={ - props.orderedStatesListProps?.descriptionField - } - orderField={props.orderedStatesListProps?.orderField} - shouldAddItemInTheBeginning={ - props.orderedStatesListProps.shouldAddItemInTheBeginning - } - shouldAddItemInTheEnd={ - props.orderedStatesListProps.shouldAddItemInTheEnd - } - noItemsMessage={props.noItemsMessage || ''} - onRefreshClick={async () => { - await fetchItems(); - }} - onCreateNewItem={ - props.isCreateable - ? (order: number) => { - setOrderedStatesListNewItemOrder(order); - setModalType(ModalType.Create); - setShowModal(true); - } - : undefined - } - singularLabel={ - props.singularName || model.singularName || 'Item' - } - actionButtons={actionButtonSchema} - getTitleElement={getTitleElement} - getDescriptionElement={getDescriptionElement} - /> - ); + if ( + props.isEditable && + (model.hasUpdatePermissions(permissions) || User.isMasterAdmin()) + ) { + actionsSchema.push({ + title: props.editButtonText || "Edit", + buttonStyleType: ButtonStyleType.OUTLINE, + onClick: async ( + item: TBaseModel, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + if (props.onBeforeEdit) { + item = await props.onBeforeEdit(item); + } + + setModalType(ModalType.Edit); + setShowModal(true); + setCurrentEditableItem(item); + + onCompleteAction(); + } catch (err) { + onError(err as Error); + } + }, + }); + } + + if ( + props.isDeleteable && + (model.hasDeletePermissions(permissions) || User.isMasterAdmin()) + ) { + actionsSchema.push({ + title: props.deleteButtonText || "Delete", + icon: IconProp.Trash, + buttonStyleType: ButtonStyleType.DANGER_OUTLINE, + onClick: async ( + item: TBaseModel, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + if (props.onBeforeDelete) { + item = await props.onBeforeDelete(item); + } + + setShowDeleteConfirmModal(true); + setCurrentDeleteableItem(item); + onCompleteAction(); + } catch (err) { + onError(err as Error); + } + }, + }); + } + } + + setActionButtonSchema(actionsSchema); + }; + + type OnFilterChangedFunction = (filterData: FilterData<TBaseModel>) => void; + + const onFilterChanged: OnFilterChangedFunction = ( + filterData: FilterData<TBaseModel>, + ): void => { + const newQuery: Query<TBaseModel> = {}; + + for (const key in filterData) { + if (filterData[key] && typeof filterData[key] === Typeof.String) { + newQuery[key as keyof TBaseModel] = (filterData[key] || "").toString(); + } + + if (typeof filterData[key] === Typeof.Boolean) { + newQuery[key as keyof TBaseModel] = Boolean(filterData[key]); + } + + if (filterData[key] instanceof Date) { + newQuery[key as keyof TBaseModel] = filterData[key]; + } + + if (filterData[key] instanceof Search) { + newQuery[key as keyof TBaseModel] = filterData[key]; + } + + if (filterData[key] instanceof InBetween) { + newQuery[key as keyof TBaseModel] = filterData[key]; + } + + if ( + props.filters.find((f: Filter<TBaseModel>) => { + return f.field && f.field[key]; + })?.type === FieldType.JSON && + typeof filterData[key] === Typeof.Object + ) { + newQuery[key as keyof TBaseModel] = filterData[key]; + } + + if (Array.isArray(filterData[key])) { + newQuery[key as keyof TBaseModel] = filterData[key]; + } + } + + setQuery(newQuery); + }; + + type GetDeleteBulkActionFunction = () => BulkActionButtonSchema<TBaseModel>; + + const getDeleteBulkAction: GetDeleteBulkActionFunction = + (): BulkActionButtonSchema<TBaseModel> => { + return { + title: "Delete", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.Trash, + confirmMessage: (items: Array<TBaseModel>) => { + return `Are you sure you want to delete ${items.length} ${ + props.pluralName || model.pluralName || "items" + }?`; + }, + confirmTitle: (items: Array<TBaseModel>) => { + return `Delete ${items.length} ${ + props.pluralName || model.pluralName || "items" + }`; + }, + confirmButtonStyleType: ButtonStyleType.DANGER, + onClick: async ({ + items, + onProgressInfo, + onBulkActionStart, + onBulkActionEnd, + }: BulkActionOnClickProps<TBaseModel>) => { + onBulkActionStart(); + + const inProgressItems: Array<TBaseModel> = [...items]; + const successItems: Array<TBaseModel> = []; + const failedItems: Array<BulkActionFailed<TBaseModel>> = []; + + for (let i: number = 0; i < items.length; i++) { + try { + const item: TBaseModel = items[i]!; + // remove items from inProgressItems + inProgressItems.splice(inProgressItems.indexOf(item), 1); + + await props.callbacks.deleteItem(item); + successItems.push(item); + + onProgressInfo({ + inProgressItems: inProgressItems, + successItems: successItems, + failed: failedItems, + totalItems: items, + }); + } catch (err) { + failedItems.push({ + item: items[i]!, + failedMessage: API.getFriendlyMessage(err), + }); + } + } + + onBulkActionEnd(); + }, + }; }; - const getList: GetReactElementFunction = (): ReactElement => { - return ( - <List - onFilterChanged={(filterData: FilterData<TBaseModel>) => { - onFilterChanged(filterData); - }} - onFilterRefreshClick={async () => { - await getFilterDropdownItems(); - }} - filters={classicTableFilters} - filterError={tableFilterError} - isFilterLoading={isFilterFetchLoading} - showFilterModal={showFilterModal} - onFilterModalClose={() => { - setShowFilterModal(false); - }} - onFilterModalOpen={() => { - setShowFilterModal(true); - }} - singularLabel={ - props.singularName || model.singularName || 'Item' - } - pluralLabel={props.pluralName || model.pluralName || 'Items'} - error={error} - currentPageNumber={currentPageNumber} - listDetailOptions={props.listDetailOptions} - enableDragAndDrop={props.enableDragAndDrop} - onDragDrop={async (id: string, newOrder: number) => { - if (!props.dragDropIndexField) { - return; - } + const getTable: GetReactElementFunction = (): ReactElement => { + return ( + <Table + onFilterChanged={(filterData: FilterData<TBaseModel>) => { + onFilterChanged(filterData); + }} + onFilterRefreshClick={async () => { + await getFilterDropdownItems(); + }} + bulkActions={{ + buttons: props.bulkActions?.buttons.map( + ( + action: + | BulkActionButtonSchema<TBaseModel> + | ModalTableBulkDefaultActions, + ) => { + const permissions: Array<Permission> = + PermissionUtil.getAllPermissions(); + if ( + action === ModalTableBulkDefaultActions.Delete && + model.hasDeletePermissions(permissions) + ) { + return getDeleteBulkAction(); + } + return action; + }, + ) as Array<BulkActionButtonSchema<TBaseModel>>, + }} + onBulkActionEnd={async () => { + setBulkSelectedItems([]); + await fetchItems(); + }} + onBulkActionStart={() => {}} + bulkSelectedItems={bulkSelectedItems} + onBulkSelectedItemAdded={(item: TBaseModel) => { + setBulkSelectedItems([...bulkSelectedItems, item]); + }} + onBulkSelectedItemRemoved={(item: TBaseModel) => { + setBulkSelectedItems( + bulkSelectedItems.filter((i: TBaseModel) => { + return ( + i[matchBulkSelectedItemByField]?.toString() !== + item[matchBulkSelectedItemByField]?.toString() + ); + }), + ); + }} + onBulkSelectItemsOnCurrentPage={() => { + const items: TBaseModel[] = [...bulkSelectedItems, ...data]; - setIsLoading(true); + // remove duplicates - await props.callbacks.updateById({ - id: new ObjectID(id), - data: { - [props.dragDropIndexField]: newOrder, - }, - }); + const uniqueItems: TBaseModel[] = items.filter( + (item: TBaseModel, index: number, self: Array<TBaseModel>) => { + return ( + index === + self.findIndex((t: TBaseModel) => { + return ( + t[matchBulkSelectedItemByField]?.toString() === + item[matchBulkSelectedItemByField]?.toString() + ); + }) + ); + }, + ); - await fetchItems(); - }} - dragDropIdField={'_id'} - dragDropIndexField={props.dragDropIndexField} - isLoading={isLoading} - totalItemsCount={totalItemsCount} - data={data} - id={props.id} - fields={fields} - itemsOnPage={itemsOnPage} - disablePagination={props.disablePagination || false} - onNavigateToPage={async ( - pageNumber: number, - itemsOnPage: number - ) => { - setCurrentPageNumber(pageNumber); - setItemsOnPage(itemsOnPage); - }} - noItemsMessage={props.noItemsMessage || ''} - onRefreshClick={async () => { - await fetchItems(); - }} - actionButtons={actionButtonSchema} - /> - ); - }; + setBulkSelectedItems(uniqueItems); + }} + onBulkClearAllItems={() => { + setBulkSelectedItems([]); + }} + onBulkSelectAllItems={async () => { + await fetchAllBulkItems(); + }} + matchBulkSelectedItemByField={matchBulkSelectedItemByField || "_id"} + bulkItemToString={(item: TBaseModel) => { + return ( + (props.singularName || item.singularName || "") + + " " + + item[matchBulkSelectedItemByField]?.toString() + + " " || "" + ); + }} + filters={classicTableFilters} + filterError={tableFilterError} + isFilterLoading={isFilterFetchLoading} + showFilterModal={showFilterModal} + onFilterModalClose={() => { + setShowFilterModal(false); + }} + onFilterModalOpen={() => { + setShowFilterModal(true); + }} + onSortChanged={( + sortBy: keyof TBaseModel | null, + sortOrder: SortOrder, + ) => { + setSortBy(sortBy); + setSortOrder(sortOrder); + }} + singularLabel={props.singularName || model.singularName || "Item"} + pluralLabel={props.pluralName || model.pluralName || "Items"} + error={error} + currentPageNumber={currentPageNumber} + isLoading={isLoading} + enableDragAndDrop={props.enableDragAndDrop} + dragDropIdField={"_id"} + dragDropIndexField={props.dragDropIndexField} + totalItemsCount={totalItemsCount} + data={data} + id={props.id} + columns={tableColumns} + itemsOnPage={itemsOnPage} + onDragDrop={async (id: string, newOrder: number) => { + if (!props.dragDropIndexField) { + return; + } - type GetCardTitleFunction = (title: ReactElement | string) => ReactElement; + setIsLoading(true); - const getCardTitle: GetCardTitleFunction = ( - title: ReactElement | string - ): ReactElement => { - const plan: PlanSelect | null = ProjectUtil.getCurrentPlan(); + await props.callbacks.updateById({ + id: new ObjectID(id), + data: { + [props.dragDropIndexField]: newOrder, + }, + }); - let showPlan: boolean = Boolean( - BILLING_ENABLED && - plan && - new props.modelType().getReadBillingPlan() && - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - new props.modelType().getReadBillingPlan()!, - plan, - getAllEnvVars() - ) - ); + await fetchItems(); + }} + disablePagination={props.disablePagination || false} + onNavigateToPage={async (pageNumber: number, itemsOnPage: number) => { + setCurrentPageNumber(pageNumber); + setItemsOnPage(itemsOnPage); + }} + noItemsMessage={props.noItemsMessage || ""} + onRefreshClick={async () => { + await fetchItems(); + }} + actionButtons={actionButtonSchema} + /> + ); + }; - let planName: string = new props.modelType().getReadBillingPlan()!; + const getOrderedStatesList: GetReactElementFunction = (): ReactElement => { + if (!props.orderedStatesListProps) { + throw new BadDataException( + "props.orderedStatesListProps required when showAs === ShowAs.OrderedStatesList", + ); + } - if (props.isCreateable && !showPlan) { - // if createable then read create billing permissions. - showPlan = Boolean( - BILLING_ENABLED && - plan && - new props.modelType().getCreateBillingPlan() && - !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( - new props.modelType().getCreateBillingPlan()!, - plan, - getAllEnvVars() - ) - ); + let getTitleElement: + | (( + item: TBaseModel, + onBeforeFetchData?: TBaseModel | undefined, + ) => ReactElement) + | undefined = undefined; - planName = new props.modelType().getCreateBillingPlan()!; - } + let getDescriptionElement: + | ((item: TBaseModel) => ReactElement) + | undefined = undefined; - return ( - <span> - {title} - {showPlan && ( - <span - style={{ - marginLeft: '5px', - }} - > - <Pill text={`${planName} Plan`} color={Yellow} /> - </span> - )} - </span> - ); - }; + for (const column of props.columns) { + const key: string | undefined = Object.keys( + column.field as SelectEntityField<TBaseModel>, + )[0]; - const getCardComponent: GetReactElementFunction = (): ReactElement => { - if (showAs === ShowAs.Table || showAs === ShowAs.List) { - return ( - <div> - {props.cardProps && ( - <Card - {...props.cardProps} - buttons={cardButtons} - bodyClassName={ - showAs === ShowAs.List - ? '-ml-6 -mr-6 bg-gray-50 border-top' - : '' - } - title={getCardTitle(props.cardProps.title)} - > - {tableColumns.length === 0 && - props.columns.length > 0 ? ( - <ErrorMessage - error={`You are not authorized to view this table. You need any one of these permissions: ${PermissionHelper.getPermissionTitles( - model.getReadPermissions() - ).join(', ')}`} - /> - ) : ( - <></> - )} - {tableColumns.length > 0 && - showAs === ShowAs.Table ? ( - getTable() - ) : ( - <></> - )} + if (key === props.orderedStatesListProps.titleField) { + getTitleElement = column.getElement; + } - {tableColumns.length > 0 && - showAs === ShowAs.List ? ( - getList() - ) : ( - <></> - )} - </Card> - )} - - {!props.cardProps && showAs === ShowAs.Table ? ( - getTable() - ) : ( - <></> - )} - {!props.cardProps && showAs === ShowAs.List ? ( - getList() - ) : ( - <></> - )} - </div> - ); - } - - return ( - <div> - {props.cardProps && ( - <Card - {...props.cardProps} - buttons={cardButtons} - title={getCardTitle(props.cardProps.title)} - > - {getOrderedStatesList()} - </Card> - )} - - {!props.cardProps && getOrderedStatesList()} - </div> - ); - }; + if (key === props.orderedStatesListProps.descriptionField) { + getDescriptionElement = column.getElement; + } + } return ( - <> - <div className="mb-5 mt-5">{getCardComponent()}</div> - - {showModel ? ( - props.callbacks.showCreateEditModal({ - onClose: () => { - setShowModal(false); - }, - modalType: modalType, - onBeforeCreate: async ( - item: TBaseModel, - miscDataProps: JSONObject - ) => { - if ( - showAs === ShowAs.OrderedStatesList && - props.orderedStatesListProps?.orderField && - orderedStatesListNewItemOrder - ) { - item.setColumnValue( - props.orderedStatesListProps - .orderField as string, - orderedStatesListNewItemOrder - ); - } - - if (props.onBeforeCreate) { - item = await props.onBeforeCreate( - item, - miscDataProps - ); - } - - return item; - }, - onSuccess: async (item: TBaseModel): Promise<void> => { - setShowModal(false); - setCurrentPageNumber(1); - await fetchItems(); - if (props.onCreateSuccess) { - await props.onCreateSuccess(item); - } - - return Promise.resolve(); - }, - modelIdToEdit: currentEditableItem - ? new ObjectID(currentEditableItem['_id'] as string) - : undefined, - }) - ) : ( - <></> - )} - - {showDeleteConfirmModal && ( - <ConfirmModal - title={`Delete ${props.singularName || model.singularName}`} - description={`Are you sure you want to delete this ${( - props.singularName || - model.singularName || - 'item' - )?.toLowerCase()}?`} - onClose={() => { - setShowDeleteConfirmModal(false); - }} - submitButtonText={'Delete'} - onSubmit={async () => { - if ( - currentDeleteableItem && - currentDeleteableItem['_id'] - ) { - await deleteItem(currentDeleteableItem); - setShowDeleteConfirmModal(false); - } - }} - submitButtonType={ButtonStyleType.DANGER} - /> - )} - - {errorModalText && ( - <ConfirmModal - title={`Error`} - description={`${errorModalText}`} - submitButtonText={'Close'} - onSubmit={() => { - setErrorModalText(''); - }} - submitButtonType={ButtonStyleType.NORMAL} - /> - )} - - {showViewIdModal && ( - <ConfirmModal - title={`${ - props.singularName || model.singularName || '' - } ID`} - description={ - <div> - <span> - ID of this{' '} - {props.singularName || model.singularName || ''} - : {viewId} - </span> - <br /> - <br /> - - <span> - You can use this ID to interact with{' '} - {props.singularName || model.singularName || ''}{' '} - via the OneUptime API. Click the button below to - go to API Reference. - </span> - </div> - } - onClose={() => { - setShowViewIdModal(false); - }} - submitButtonText={'Go to API Docs'} - onSubmit={() => { - setShowViewIdModal(false); - Navigation.navigate( - URL.fromString(API_DOCS_URL.toString()).addRoute( - '/' + model.getAPIDocumentationPath() - ), - { openInNewTab: true } - ); - }} - submitButtonType={ButtonStyleType.NORMAL} - closeButtonType={ButtonStyleType.OUTLINE} - /> - )} - </> + <OrderedStatesList<TBaseModel> + error={error} + isLoading={isLoading} + data={data} + id={props.id} + titleField={props.orderedStatesListProps?.titleField} + descriptionField={props.orderedStatesListProps?.descriptionField} + orderField={props.orderedStatesListProps?.orderField} + shouldAddItemInTheBeginning={ + props.orderedStatesListProps.shouldAddItemInTheBeginning + } + shouldAddItemInTheEnd={ + props.orderedStatesListProps.shouldAddItemInTheEnd + } + noItemsMessage={props.noItemsMessage || ""} + onRefreshClick={async () => { + await fetchItems(); + }} + onCreateNewItem={ + props.isCreateable + ? (order: number) => { + setOrderedStatesListNewItemOrder(order); + setModalType(ModalType.Create); + setShowModal(true); + } + : undefined + } + singularLabel={props.singularName || model.singularName || "Item"} + actionButtons={actionButtonSchema} + getTitleElement={getTitleElement} + getDescriptionElement={getDescriptionElement} + /> ); + }; + + const getList: GetReactElementFunction = (): ReactElement => { + return ( + <List + onFilterChanged={(filterData: FilterData<TBaseModel>) => { + onFilterChanged(filterData); + }} + onFilterRefreshClick={async () => { + await getFilterDropdownItems(); + }} + filters={classicTableFilters} + filterError={tableFilterError} + isFilterLoading={isFilterFetchLoading} + showFilterModal={showFilterModal} + onFilterModalClose={() => { + setShowFilterModal(false); + }} + onFilterModalOpen={() => { + setShowFilterModal(true); + }} + singularLabel={props.singularName || model.singularName || "Item"} + pluralLabel={props.pluralName || model.pluralName || "Items"} + error={error} + currentPageNumber={currentPageNumber} + listDetailOptions={props.listDetailOptions} + enableDragAndDrop={props.enableDragAndDrop} + onDragDrop={async (id: string, newOrder: number) => { + if (!props.dragDropIndexField) { + return; + } + + setIsLoading(true); + + await props.callbacks.updateById({ + id: new ObjectID(id), + data: { + [props.dragDropIndexField]: newOrder, + }, + }); + + await fetchItems(); + }} + dragDropIdField={"_id"} + dragDropIndexField={props.dragDropIndexField} + isLoading={isLoading} + totalItemsCount={totalItemsCount} + data={data} + id={props.id} + fields={fields} + itemsOnPage={itemsOnPage} + disablePagination={props.disablePagination || false} + onNavigateToPage={async (pageNumber: number, itemsOnPage: number) => { + setCurrentPageNumber(pageNumber); + setItemsOnPage(itemsOnPage); + }} + noItemsMessage={props.noItemsMessage || ""} + onRefreshClick={async () => { + await fetchItems(); + }} + actionButtons={actionButtonSchema} + /> + ); + }; + + type GetCardTitleFunction = (title: ReactElement | string) => ReactElement; + + const getCardTitle: GetCardTitleFunction = ( + title: ReactElement | string, + ): ReactElement => { + const plan: PlanSelect | null = ProjectUtil.getCurrentPlan(); + + let showPlan: boolean = Boolean( + BILLING_ENABLED && + plan && + new props.modelType().getReadBillingPlan() && + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + new props.modelType().getReadBillingPlan()!, + plan, + getAllEnvVars(), + ), + ); + + let planName: string = new props.modelType().getReadBillingPlan()!; + + if (props.isCreateable && !showPlan) { + // if createable then read create billing permissions. + showPlan = Boolean( + BILLING_ENABLED && + plan && + new props.modelType().getCreateBillingPlan() && + !SubscriptionPlan.isFeatureAccessibleOnCurrentPlan( + new props.modelType().getCreateBillingPlan()!, + plan, + getAllEnvVars(), + ), + ); + + planName = new props.modelType().getCreateBillingPlan()!; + } + + return ( + <span> + {title} + {showPlan && ( + <span + style={{ + marginLeft: "5px", + }} + > + <Pill text={`${planName} Plan`} color={Yellow} /> + </span> + )} + </span> + ); + }; + + const getCardComponent: GetReactElementFunction = (): ReactElement => { + if (showAs === ShowAs.Table || showAs === ShowAs.List) { + return ( + <div> + {props.cardProps && ( + <Card + {...props.cardProps} + buttons={cardButtons} + bodyClassName={ + showAs === ShowAs.List + ? "-ml-6 -mr-6 bg-gray-50 border-top" + : "" + } + title={getCardTitle(props.cardProps.title)} + > + {tableColumns.length === 0 && props.columns.length > 0 ? ( + <ErrorMessage + error={`You are not authorized to view this table. You need any one of these permissions: ${PermissionHelper.getPermissionTitles( + model.getReadPermissions(), + ).join(", ")}`} + /> + ) : ( + <></> + )} + {tableColumns.length > 0 && showAs === ShowAs.Table ? ( + getTable() + ) : ( + <></> + )} + + {tableColumns.length > 0 && showAs === ShowAs.List ? ( + getList() + ) : ( + <></> + )} + </Card> + )} + + {!props.cardProps && showAs === ShowAs.Table ? getTable() : <></>} + {!props.cardProps && showAs === ShowAs.List ? getList() : <></>} + </div> + ); + } + + return ( + <div> + {props.cardProps && ( + <Card + {...props.cardProps} + buttons={cardButtons} + title={getCardTitle(props.cardProps.title)} + > + {getOrderedStatesList()} + </Card> + )} + + {!props.cardProps && getOrderedStatesList()} + </div> + ); + }; + + return ( + <> + <div className="mb-5 mt-5">{getCardComponent()}</div> + + {showModel ? ( + props.callbacks.showCreateEditModal({ + onClose: () => { + setShowModal(false); + }, + modalType: modalType, + onBeforeCreate: async ( + item: TBaseModel, + miscDataProps: JSONObject, + ) => { + if ( + showAs === ShowAs.OrderedStatesList && + props.orderedStatesListProps?.orderField && + orderedStatesListNewItemOrder + ) { + item.setColumnValue( + props.orderedStatesListProps.orderField as string, + orderedStatesListNewItemOrder, + ); + } + + if (props.onBeforeCreate) { + item = await props.onBeforeCreate(item, miscDataProps); + } + + return item; + }, + onSuccess: async (item: TBaseModel): Promise<void> => { + setShowModal(false); + setCurrentPageNumber(1); + await fetchItems(); + if (props.onCreateSuccess) { + await props.onCreateSuccess(item); + } + + return Promise.resolve(); + }, + modelIdToEdit: currentEditableItem + ? new ObjectID(currentEditableItem["_id"] as string) + : undefined, + }) + ) : ( + <></> + )} + + {showDeleteConfirmModal && ( + <ConfirmModal + title={`Delete ${props.singularName || model.singularName}`} + description={`Are you sure you want to delete this ${( + props.singularName || + model.singularName || + "item" + )?.toLowerCase()}?`} + onClose={() => { + setShowDeleteConfirmModal(false); + }} + submitButtonText={"Delete"} + onSubmit={async () => { + if (currentDeleteableItem && currentDeleteableItem["_id"]) { + await deleteItem(currentDeleteableItem); + setShowDeleteConfirmModal(false); + } + }} + submitButtonType={ButtonStyleType.DANGER} + /> + )} + + {errorModalText && ( + <ConfirmModal + title={`Error`} + description={`${errorModalText}`} + submitButtonText={"Close"} + onSubmit={() => { + setErrorModalText(""); + }} + submitButtonType={ButtonStyleType.NORMAL} + /> + )} + + {showViewIdModal && ( + <ConfirmModal + title={`${props.singularName || model.singularName || ""} ID`} + description={ + <div> + <span> + ID of this {props.singularName || model.singularName || ""}:{" "} + {viewId} + </span> + <br /> + <br /> + + <span> + You can use this ID to interact with{" "} + {props.singularName || model.singularName || ""} via the + OneUptime API. Click the button below to go to API Reference. + </span> + </div> + } + onClose={() => { + setShowViewIdModal(false); + }} + submitButtonText={"Go to API Docs"} + onSubmit={() => { + setShowViewIdModal(false); + Navigation.navigate( + URL.fromString(API_DOCS_URL.toString()).addRoute( + "/" + model.getAPIDocumentationPath(), + ), + { openInNewTab: true }, + ); + }} + submitButtonType={ButtonStyleType.NORMAL} + closeButtonType={ButtonStyleType.OUTLINE} + /> + )} + </> + ); }; export default BaseModelTable; diff --git a/CommonUI/src/Components/ModelTable/Column.ts b/CommonUI/src/Components/ModelTable/Column.ts index bffc8179f8..002fba265b 100644 --- a/CommonUI/src/Components/ModelTable/Column.ts +++ b/CommonUI/src/Components/ModelTable/Column.ts @@ -1,37 +1,34 @@ -import AlignItem from '../../Types/AlignItem'; -import SelectEntityField from '../../Types/SelectEntityField'; -import FieldType from '../Types/FieldType'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import BaseModel from 'Common/Models/BaseModel'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { ReactElement } from 'react'; +import AlignItem from "../../Types/AlignItem"; +import SelectEntityField from "../../Types/SelectEntityField"; +import FieldType from "../Types/FieldType"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import BaseModel from "Common/Models/BaseModel"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { ReactElement } from "react"; export interface ActionButton { - buttonText: string; - icon: IconProp; - onClick: (id: ObjectID) => void; + buttonText: string; + icon: IconProp; + onClick: (id: ObjectID) => void; } export default interface Columns< - TEntity extends BaseModel | AnalyticsBaseModel + TEntity extends BaseModel | AnalyticsBaseModel, > { - field: SelectEntityField<TEntity>; - selectedProperty?: string | undefined; - title: string; - contentClassName?: string | undefined; - colSpan?: number | undefined; - disableSort?: boolean; - description?: string | undefined; - type: FieldType; - tooltipText?: ((item: TEntity) => string) | undefined; - actionButtons?: Array<ActionButton>; - alignItem?: AlignItem | undefined; - noValueMessage?: string | undefined; - getElement?: - | (( - item: TEntity, - onBeforeFetchData?: TEntity | undefined - ) => ReactElement) - | undefined; + field: SelectEntityField<TEntity>; + selectedProperty?: string | undefined; + title: string; + contentClassName?: string | undefined; + colSpan?: number | undefined; + disableSort?: boolean; + description?: string | undefined; + type: FieldType; + tooltipText?: ((item: TEntity) => string) | undefined; + actionButtons?: Array<ActionButton>; + alignItem?: AlignItem | undefined; + noValueMessage?: string | undefined; + getElement?: + | ((item: TEntity, onBeforeFetchData?: TEntity | undefined) => ReactElement) + | undefined; } diff --git a/CommonUI/src/Components/ModelTable/Columns.ts b/CommonUI/src/Components/ModelTable/Columns.ts index 2ea8a7fe2e..df2cbc946d 100644 --- a/CommonUI/src/Components/ModelTable/Columns.ts +++ b/CommonUI/src/Components/ModelTable/Columns.ts @@ -1,6 +1,6 @@ -import Column from './Column'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import BaseModel from 'Common/Models/BaseModel'; +import Column from "./Column"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import BaseModel from "Common/Models/BaseModel"; type Columns<T extends BaseModel | AnalyticsBaseModel> = Array<Column<T>>; diff --git a/CommonUI/src/Components/ModelTable/ModelTable.tsx b/CommonUI/src/Components/ModelTable/ModelTable.tsx index a28b0b0858..f31a46400a 100644 --- a/CommonUI/src/Components/ModelTable/ModelTable.tsx +++ b/CommonUI/src/Components/ModelTable/ModelTable.tsx @@ -1,204 +1,185 @@ -import GroupBy from '../../Utils/BaseDatabase/GroupBy'; -import Query from '../../Utils/BaseDatabase/Query'; -import Select from '../../Utils/BaseDatabase/Select'; -import Sort from '../../Utils/BaseDatabase/Sort'; -import ModelAPI, { RequestOptions } from '../../Utils/ModelAPI/ModelAPI'; -import { FormType, ModelField } from '../Forms/ModelForm'; -import ModelFormModal from '../ModelFormModal/ModelFormModal'; -import BaseModelTable, { BaseTableProps, ModalType } from './BaseModelTable'; -import { AnalyticsBaseModelType } from 'Common/AnalyticsModels/BaseModel'; -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import Dictionary from 'Common/Types/Dictionary'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { ReactElement } from 'react'; +import GroupBy from "../../Utils/BaseDatabase/GroupBy"; +import Query from "../../Utils/BaseDatabase/Query"; +import Select from "../../Utils/BaseDatabase/Select"; +import Sort from "../../Utils/BaseDatabase/Sort"; +import ModelAPI, { RequestOptions } from "../../Utils/ModelAPI/ModelAPI"; +import { FormType, ModelField } from "../Forms/ModelForm"; +import ModelFormModal from "../ModelFormModal/ModelFormModal"; +import BaseModelTable, { BaseTableProps, ModalType } from "./BaseModelTable"; +import { AnalyticsBaseModelType } from "Common/AnalyticsModels/BaseModel"; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import Dictionary from "Common/Types/Dictionary"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import React, { ReactElement } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> - extends BaseTableProps<TBaseModel> { - modelAPI?: typeof ModelAPI | undefined; + extends BaseTableProps<TBaseModel> { + modelAPI?: typeof ModelAPI | undefined; } const ModelTable: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const modelAPI: typeof ModelAPI = props.modelAPI || ModelAPI; - const model: TBaseModel = new props.modelType(); + const modelAPI: typeof ModelAPI = props.modelAPI || ModelAPI; + const model: TBaseModel = new props.modelType(); - return ( - <BaseModelTable - {...props} - callbacks={{ - getJSONFromModel: (item: TBaseModel): JSONObject => { - return BaseModel.toJSONObject(item, props.modelType); - }, + return ( + <BaseModelTable + {...props} + callbacks={{ + getJSONFromModel: (item: TBaseModel): JSONObject => { + return BaseModel.toJSONObject(item, props.modelType); + }, - updateById: async (args: { - id: ObjectID; - data: JSONObject; - }) => { - const { id, data } = args; + updateById: async (args: { id: ObjectID; data: JSONObject }) => { + const { id, data } = args; - await modelAPI.updateById({ - modelType: props.modelType, - id: new ObjectID(id), - data: data, - }); - }, + await modelAPI.updateById({ + modelType: props.modelType, + id: new ObjectID(id), + data: data, + }); + }, - toJSONArray: (items: TBaseModel[]): JSONObject[] => { - return BaseModel.toJSONObjectArray(items, props.modelType); - }, + toJSONArray: (items: TBaseModel[]): JSONObject[] => { + return BaseModel.toJSONObjectArray(items, props.modelType); + }, - getList: async (data: { - modelType: BaseModelType | AnalyticsBaseModelType; - query: Query<TBaseModel>; - groupBy?: GroupBy<TBaseModel> | undefined; - limit: number; - skip: number; - sort: Sort<TBaseModel>; - select: Select<TBaseModel>; - requestOptions?: RequestOptions | undefined; - }) => { - return await modelAPI.getList<TBaseModel>({ - modelType: data.modelType as { new (): TBaseModel }, - query: data.query, - limit: data.limit, - groupBy: data.groupBy, - skip: data.skip, - sort: data.sort, - select: data.select, - requestOptions: data.requestOptions, - }); - }, + getList: async (data: { + modelType: BaseModelType | AnalyticsBaseModelType; + query: Query<TBaseModel>; + groupBy?: GroupBy<TBaseModel> | undefined; + limit: number; + skip: number; + sort: Sort<TBaseModel>; + select: Select<TBaseModel>; + requestOptions?: RequestOptions | undefined; + }) => { + return await modelAPI.getList<TBaseModel>({ + modelType: data.modelType as { new (): TBaseModel }, + query: data.query, + limit: data.limit, + groupBy: data.groupBy, + skip: data.skip, + sort: data.sort, + select: data.select, + requestOptions: data.requestOptions, + }); + }, - addSlugToSelect: ( - select: Select<TBaseModel> - ): Select<TBaseModel> => { - const slugifyColumn: string | null = ( - model as BaseModel - ).getSlugifyColumn(); + addSlugToSelect: (select: Select<TBaseModel>): Select<TBaseModel> => { + const slugifyColumn: string | null = ( + model as BaseModel + ).getSlugifyColumn(); - if (slugifyColumn) { - (select as Dictionary<boolean>)[slugifyColumn] = true; + if (slugifyColumn) { + (select as Dictionary<boolean>)[slugifyColumn] = true; + } + + return select; + }, + + showCreateEditModal: (data: { + modalType: ModalType; + modelIdToEdit?: ObjectID | undefined; + onBeforeCreate?: + | (( + item: TBaseModel, + miscDataProps: JSONObject, + ) => Promise<TBaseModel>) + | undefined; + onSuccess?: ((item: TBaseModel) => void) | undefined; + onClose?: (() => void) | undefined; + }): ReactElement => { + const { + modalType, + modelIdToEdit, + onBeforeCreate, + onSuccess, + onClose, + } = data; + + return ( + <ModelFormModal<TBaseModel> + modelAPI={props.modelAPI} + title={ + modalType === ModalType.Create + ? `${props.createVerb || "Create"} New ${ + props.singularName || model.singularName + }` + : `Edit ${props.singularName || model.singularName}` + } + formRef={props.createEditFromRef} + modalWidth={props.createEditModalWidth} + name={ + modalType === ModalType.Create + ? `${props.name} > ${props.createVerb || "Create"} New ${ + props.singularName || model.singularName + }` + : `${props.name} > Edit ${ + props.singularName || model.singularName + }` + } + initialValues={ + modalType === ModalType.Create + ? props.createInitialValues + : undefined + } + onClose={onClose} + submitButtonText={ + modalType === ModalType.Create + ? `${props.createVerb || "Create"} ${ + props.singularName || model.singularName + }` + : `Save Changes` + } + onSuccess={onSuccess} + onBeforeCreate={onBeforeCreate} + modelType={props.modelType} + formProps={{ + name: `create-${props.modelType.name}-from`, + modelType: props.modelType, + id: `create-${props.modelType.name}-from`, + fields: + props.formFields?.filter((field: ModelField<TBaseModel>) => { + // If the field has doNotShowWhenEditing set to true, then don't show it when editing + + if (modelIdToEdit) { + return !field.doNotShowWhenEditing; } - return select; - }, + // If the field has doNotShowWhenCreating set to true, then don't show it when creating - showCreateEditModal: (data: { - modalType: ModalType; - modelIdToEdit?: ObjectID | undefined; - onBeforeCreate?: - | (( - item: TBaseModel, - miscDataProps: JSONObject - ) => Promise<TBaseModel>) - | undefined; - onSuccess?: ((item: TBaseModel) => void) | undefined; - onClose?: (() => void) | undefined; - }): ReactElement => { - const { - modalType, - modelIdToEdit, - onBeforeCreate, - onSuccess, - onClose, - } = data; + return !field.doNotShowWhenCreating; + }) || [], + steps: props.formSteps || [], + formType: + modalType === ModalType.Create + ? FormType.Create + : FormType.Update, + }} + modelIdToEdit={modelIdToEdit} + /> + ); + }, - return ( - <ModelFormModal<TBaseModel> - modelAPI={props.modelAPI} - title={ - modalType === ModalType.Create - ? `${props.createVerb || 'Create'} New ${ - props.singularName || - model.singularName - }` - : `Edit ${ - props.singularName || - model.singularName - }` - } - formRef={props.createEditFromRef} - modalWidth={props.createEditModalWidth} - name={ - modalType === ModalType.Create - ? `${props.name} > ${ - props.createVerb || 'Create' - } New ${ - props.singularName || - model.singularName - }` - : `${props.name} > Edit ${ - props.singularName || - model.singularName - }` - } - initialValues={ - modalType === ModalType.Create - ? props.createInitialValues - : undefined - } - onClose={onClose} - submitButtonText={ - modalType === ModalType.Create - ? `${props.createVerb || 'Create'} ${ - props.singularName || - model.singularName - }` - : `Save Changes` - } - onSuccess={onSuccess} - onBeforeCreate={onBeforeCreate} - modelType={props.modelType} - formProps={{ - name: `create-${props.modelType.name}-from`, - modelType: props.modelType, - id: `create-${props.modelType.name}-from`, - fields: - props.formFields?.filter( - (field: ModelField<TBaseModel>) => { - // If the field has doNotShowWhenEditing set to true, then don't show it when editing + getModelFromJSON: (item: JSONObject): TBaseModel => { + return BaseModel.fromJSON(item, props.modelType) as TBaseModel; + }, - if (modelIdToEdit) { - return !field.doNotShowWhenEditing; - } - - // If the field has doNotShowWhenCreating set to true, then don't show it when creating - - return !field.doNotShowWhenCreating; - } - ) || [], - steps: props.formSteps || [], - formType: - modalType === ModalType.Create - ? FormType.Create - : FormType.Update, - }} - modelIdToEdit={modelIdToEdit} - /> - ); - }, - - getModelFromJSON: (item: JSONObject): TBaseModel => { - return BaseModel.fromJSON( - item, - props.modelType - ) as TBaseModel; - }, - - deleteItem: async (item: TBaseModel) => { - await modelAPI.deleteItem({ - modelType: props.modelType, - id: item.id as ObjectID, - requestOptions: props.deleteRequestOptions, - }); - }, - }} - /> - ); + deleteItem: async (item: TBaseModel) => { + await modelAPI.deleteItem({ + modelType: props.modelType, + id: item.id as ObjectID, + requestOptions: props.deleteRequestOptions, + }); + }, + }} + /> + ); }; export default ModelTable; diff --git a/CommonUI/src/Components/MonitorGraphs/Uptime.tsx b/CommonUI/src/Components/MonitorGraphs/Uptime.tsx index daa648a9e7..7bc1303535 100644 --- a/CommonUI/src/Components/MonitorGraphs/Uptime.tsx +++ b/CommonUI/src/Components/MonitorGraphs/Uptime.tsx @@ -1,95 +1,92 @@ -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import DayUptimeGraph, { BarChartRule, Event } from '../Graphs/DayUptimeGraph'; -import UptimeUtil from './UptimeUtil'; -import Color from 'Common/Types/Color'; -import ObjectID from 'Common/Types/ObjectID'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import StatusPageHistoryChartBarColorRule from 'Model/Models/StatusPageHistoryChartBarColorRule'; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import DayUptimeGraph, { BarChartRule, Event } from "../Graphs/DayUptimeGraph"; +import UptimeUtil from "./UptimeUtil"; +import Color from "Common/Types/Color"; +import ObjectID from "Common/Types/ObjectID"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import StatusPageHistoryChartBarColorRule from "Model/Models/StatusPageHistoryChartBarColorRule"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface MonitorEvent extends Event { - monitorId: ObjectID; + monitorId: ObjectID; } export interface ComponentProps { - startDate: Date; - endDate: Date; - items: Array<MonitorStatusTimeline>; - isLoading?: boolean | undefined; - onRefreshClick?: (() => void) | undefined; - error?: string | undefined; - height?: number | undefined; - barColorRules?: Array<StatusPageHistoryChartBarColorRule> | undefined; - downtimeMonitorStatuses: Array<MonitorStatus> | undefined; - defaultBarColor: Color; + startDate: Date; + endDate: Date; + items: Array<MonitorStatusTimeline>; + isLoading?: boolean | undefined; + onRefreshClick?: (() => void) | undefined; + error?: string | undefined; + height?: number | undefined; + barColorRules?: Array<StatusPageHistoryChartBarColorRule> | undefined; + downtimeMonitorStatuses: Array<MonitorStatus> | undefined; + defaultBarColor: Color; } const MonitorUptimeGraph: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [events, setEvents] = useState<Array<Event>>([]); + const [events, setEvents] = useState<Array<Event>>([]); - const [barColorRules, setBarColorRules] = useState<BarChartRule[]>([]); + const [barColorRules, setBarColorRules] = useState<BarChartRule[]>([]); - useEffect(() => { - const eventList: Array<Event> = - UptimeUtil.getNonOverlappingMonitorEvents(props.items); - setEvents(eventList); - }, [props.items]); - - useEffect(() => { - if (props.barColorRules) { - setBarColorRules( - props.barColorRules.map( - (rule: StatusPageHistoryChartBarColorRule) => { - return { - barColor: rule.barColor!, - uptimePercentGreaterThanOrEqualTo: - rule.uptimePercentGreaterThanOrEqualTo!, - }; - } - ) - ); - } - }, [props.barColorRules]); - - if (props.isLoading) { - return <ComponentLoader />; - } - - if (props.error) { - return ( - <ErrorMessage - error={props.error} - onRefreshClick={ - props.onRefreshClick ? props.onRefreshClick : undefined - } - /> - ); - } - - return ( - <DayUptimeGraph - startDate={props.startDate} - endDate={props.endDate} - events={events} - defaultBarColor={props.defaultBarColor} - height={props.height} - barColorRules={barColorRules} - downtimeEventStatusIds={ - props.downtimeMonitorStatuses?.map((status: MonitorStatus) => { - return status.id!; - }) || [] - } - /> + useEffect(() => { + const eventList: Array<Event> = UptimeUtil.getNonOverlappingMonitorEvents( + props.items, ); + setEvents(eventList); + }, [props.items]); + + useEffect(() => { + if (props.barColorRules) { + setBarColorRules( + props.barColorRules.map((rule: StatusPageHistoryChartBarColorRule) => { + return { + barColor: rule.barColor!, + uptimePercentGreaterThanOrEqualTo: + rule.uptimePercentGreaterThanOrEqualTo!, + }; + }), + ); + } + }, [props.barColorRules]); + + if (props.isLoading) { + return <ComponentLoader />; + } + + if (props.error) { + return ( + <ErrorMessage + error={props.error} + onRefreshClick={props.onRefreshClick ? props.onRefreshClick : undefined} + /> + ); + } + + return ( + <DayUptimeGraph + startDate={props.startDate} + endDate={props.endDate} + events={events} + defaultBarColor={props.defaultBarColor} + height={props.height} + barColorRules={barColorRules} + downtimeEventStatusIds={ + props.downtimeMonitorStatuses?.map((status: MonitorStatus) => { + return status.id!; + }) || [] + } + /> + ); }; export default MonitorUptimeGraph; diff --git a/CommonUI/src/Components/MonitorGraphs/UptimeUtil.ts b/CommonUI/src/Components/MonitorGraphs/UptimeUtil.ts index 3ad472d817..3d89114d4e 100644 --- a/CommonUI/src/Components/MonitorGraphs/UptimeUtil.ts +++ b/CommonUI/src/Components/MonitorGraphs/UptimeUtil.ts @@ -1,345 +1,331 @@ -import { Event } from '../Graphs/DayUptimeGraph'; -import { MonitorEvent } from './Uptime'; -import { Green } from 'Common/Types/BrandColors'; -import OneUptimeDate from 'Common/Types/Date'; -import ObjectID from 'Common/Types/ObjectID'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import { UptimePrecision } from 'Model/Models/StatusPageResource'; +import { Event } from "../Graphs/DayUptimeGraph"; +import { MonitorEvent } from "./Uptime"; +import { Green } from "Common/Types/BrandColors"; +import OneUptimeDate from "Common/Types/Date"; +import ObjectID from "Common/Types/ObjectID"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import { UptimePrecision } from "Model/Models/StatusPageResource"; export default class UptimeUtil { - /** - * This function, `getMonitorEventsForId`, takes a `monitorId` as an argument and returns an array of `MonitorEvent` objects. - * @param {ObjectID} monitorId - The ID of the monitor for which events are to be fetched. - * @returns {Array<MonitorEvent>} - An array of `MonitorEvent` objects. - */ - public static getMonitorEventsForId( - monitorId: ObjectID, - statusTimelineItems: Array<MonitorStatusTimeline> - ): Array<MonitorEvent> { - // Initialize an empty array to store the monitor events. + /** + * This function, `getMonitorEventsForId`, takes a `monitorId` as an argument and returns an array of `MonitorEvent` objects. + * @param {ObjectID} monitorId - The ID of the monitor for which events are to be fetched. + * @returns {Array<MonitorEvent>} - An array of `MonitorEvent` objects. + */ + public static getMonitorEventsForId( + monitorId: ObjectID, + statusTimelineItems: Array<MonitorStatusTimeline>, + ): Array<MonitorEvent> { + // Initialize an empty array to store the monitor events. - // make sure items are sorted by start date. + // make sure items are sorted by start date. - let items: Array<MonitorStatusTimeline> = [...statusTimelineItems]; + let items: Array<MonitorStatusTimeline> = [...statusTimelineItems]; - items = items.sort( - (a: MonitorStatusTimeline, b: MonitorStatusTimeline) => { - if (!a.startsAt || !b.startsAt) { - return 0; - } + items = items.sort((a: MonitorStatusTimeline, b: MonitorStatusTimeline) => { + if (!a.startsAt || !b.startsAt) { + return 0; + } - if (OneUptimeDate.isAfter(a.startsAt!, b.startsAt!)) { - return 1; - } + if (OneUptimeDate.isAfter(a.startsAt!, b.startsAt!)) { + return 1; + } - if (OneUptimeDate.isAfter(b.startsAt!, a.startsAt!)) { - return -1; - } + if (OneUptimeDate.isAfter(b.startsAt!, a.startsAt!)) { + return -1; + } - return 0; - } - ); + return 0; + }); - const eventList: Array<MonitorEvent> = []; + const eventList: Array<MonitorEvent> = []; - const monitorEvents: Array<MonitorStatusTimeline> = items.filter( - (item: MonitorStatusTimeline) => { - return item.monitorId?.toString() === monitorId.toString(); - } - ); + const monitorEvents: Array<MonitorStatusTimeline> = items.filter( + (item: MonitorStatusTimeline) => { + return item.monitorId?.toString() === monitorId.toString(); + }, + ); - // Loop through the items in the props object. - for (let i: number = 0; i < monitorEvents.length; i++) { - // If the current item is null or undefined, skip to the next iteration. - if (!monitorEvents[i]) { - continue; - } + // Loop through the items in the props object. + for (let i: number = 0; i < monitorEvents.length; i++) { + // If the current item is null or undefined, skip to the next iteration. + if (!monitorEvents[i]) { + continue; + } - // Set the start date of the event to the creation date of the current item. If it doesn't exist, use the current date. - const startDate: Date = - monitorEvents[i]!.startsAt || OneUptimeDate.getCurrentDate(); + // Set the start date of the event to the creation date of the current item. If it doesn't exist, use the current date. + const startDate: Date = + monitorEvents[i]!.startsAt || OneUptimeDate.getCurrentDate(); - // Initialize the end date as the current date. - let endDate: Date | undefined = monitorEvents[i]!.endsAt; + // Initialize the end date as the current date. + let endDate: Date | undefined = monitorEvents[i]!.endsAt; - if (!endDate) { - // check if there's next event, if there is, set the end date to the start date of the next event. - if (i < monitorEvents.length - 1) { - endDate = monitorEvents[i + 1]!.startsAt; - } else { - endDate = OneUptimeDate.getCurrentDate(); - } - } + if (!endDate) { + // check if there's next event, if there is, set the end date to the start date of the next event. + if (i < monitorEvents.length - 1) { + endDate = monitorEvents[i + 1]!.startsAt; + } else { + endDate = OneUptimeDate.getCurrentDate(); + } + } - // Push a new MonitorEvent object to the eventList array with properties from the current item and calculated dates. + // Push a new MonitorEvent object to the eventList array with properties from the current item and calculated dates. + eventList.push({ + startDate: startDate, + endDate: endDate!, + label: monitorEvents[i]?.monitorStatus?.name || "Operational", + priority: monitorEvents[i]?.monitorStatus?.priority || 0, + color: monitorEvents[i]?.monitorStatus?.color || Green, + monitorId: monitorEvents[i]!.monitorId!, + eventStatusId: monitorEvents[i]!.monitorStatus!.id!, + }); + } + + // Return the populated eventList array. + return eventList; + } + + public static getNonOverlappingMonitorEvents( + items: Array<MonitorStatusTimeline>, + ): Array<Event> { + const monitorEventList: Array<MonitorEvent> = this.getMonitorEvents(items); + + const eventList: Array<Event> = []; + + for (let i: number = 0; i < monitorEventList.length; i++) { + // if this event starts after the last event, then add it to the list directly. + + const monitorEvent: MonitorEvent = monitorEventList[i]!; + + if (!monitorEvent.endDate) { + // if this is the last event then set endDate to current date. + + // otherwise set it to start date of next event. + + if (i === monitorEventList.length - 1) { + monitorEvent.endDate = OneUptimeDate.getCurrentDate(); + } else { + monitorEvent.endDate = + monitorEventList[i + 1]!.startDate || + OneUptimeDate.getCurrentDate(); + } + } + + if ( + eventList.length === 0 || + OneUptimeDate.isAfter( + monitorEvent.startDate, + eventList[eventList.length - 1]!.endDate, + ) || + OneUptimeDate.isEqualBySeconds( + monitorEvent.startDate, + eventList[eventList.length - 1]!.endDate, + ) + ) { + eventList.push(monitorEvent); + continue; + } + + // if this event starts before the last event, then we need to check if it ends before the last event. If it does, then we can skip this event if the monitrEvent is of lower priority than the last event. If it is of higher priority, then we need to add it to the list and remove the last event from the list. + if ( + OneUptimeDate.isBefore( + monitorEvent.startDate, + eventList[eventList.length - 1]!.endDate, + ) + ) { + if (monitorEvent.priority > eventList[eventList.length - 1]!.priority) { + // end the last event at the start of this event. + + const tempLastEvent: Event = { + ...eventList[eventList.length - 1], + } as Event; + + eventList[eventList.length - 1]!.endDate = monitorEvent.startDate; + eventList.push(monitorEvent); + + // if the monitorEvent endDate is before the end of the last event, then we need to add the end of the last event to the list. + + if ( + OneUptimeDate.isBefore(monitorEvent.endDate, tempLastEvent.endDate) + ) { eventList.push({ - startDate: startDate, - endDate: endDate!, - label: monitorEvents[i]?.monitorStatus?.name || 'Operational', - priority: monitorEvents[i]?.monitorStatus?.priority || 0, - color: monitorEvents[i]?.monitorStatus?.color || Green, - monitorId: monitorEvents[i]!.monitorId!, - eventStatusId: monitorEvents[i]!.monitorStatus!.id!, + startDate: monitorEvent.endDate, + endDate: tempLastEvent.endDate, + label: tempLastEvent.label, + priority: tempLastEvent.priority, + color: tempLastEvent.color, + eventStatusId: tempLastEvent.eventStatusId, }); + } } - // Return the populated eventList array. - return eventList; + continue; + } } - public static getNonOverlappingMonitorEvents( - items: Array<MonitorStatusTimeline> - ): Array<Event> { - const monitorEventList: Array<MonitorEvent> = - this.getMonitorEvents(items); + return eventList; + } - const eventList: Array<Event> = []; + public static getMonitorEvents( + items: Array<MonitorStatusTimeline>, + ): Array<MonitorEvent> { + // get all distinct monitor ids. + const monitorIds: Array<ObjectID> = []; - for (let i: number = 0; i < monitorEventList.length; i++) { - // if this event starts after the last event, then add it to the list directly. + for (let i: number = 0; i < items.length; i++) { + if (!items[i]) { + continue; + } - const monitorEvent: MonitorEvent = monitorEventList[i]!; + const monitorId: string | undefined = items[i]!.monitorId?.toString(); - if (!monitorEvent.endDate) { - // if this is the last event then set endDate to current date. + if (!monitorId) { + continue; + } - // otherwise set it to start date of next event. - - if (i === monitorEventList.length - 1) { - monitorEvent.endDate = OneUptimeDate.getCurrentDate(); - } else { - monitorEvent.endDate = - monitorEventList[i + 1]!.startDate || - OneUptimeDate.getCurrentDate(); - } - } - - if ( - eventList.length === 0 || - OneUptimeDate.isAfter( - monitorEvent.startDate, - eventList[eventList.length - 1]!.endDate - ) || - OneUptimeDate.isEqualBySeconds( - monitorEvent.startDate, - eventList[eventList.length - 1]!.endDate - ) - ) { - eventList.push(monitorEvent); - continue; - } - - // if this event starts before the last event, then we need to check if it ends before the last event. If it does, then we can skip this event if the monitrEvent is of lower priority than the last event. If it is of higher priority, then we need to add it to the list and remove the last event from the list. - if ( - OneUptimeDate.isBefore( - monitorEvent.startDate, - eventList[eventList.length - 1]!.endDate - ) - ) { - if ( - monitorEvent.priority > - eventList[eventList.length - 1]!.priority - ) { - // end the last event at the start of this event. - - const tempLastEvent: Event = { - ...eventList[eventList.length - 1], - } as Event; - - eventList[eventList.length - 1]!.endDate = - monitorEvent.startDate; - eventList.push(monitorEvent); - - // if the monitorEvent endDate is before the end of the last event, then we need to add the end of the last event to the list. - - if ( - OneUptimeDate.isBefore( - monitorEvent.endDate, - tempLastEvent.endDate - ) - ) { - eventList.push({ - startDate: monitorEvent.endDate, - endDate: tempLastEvent.endDate, - label: tempLastEvent.label, - priority: tempLastEvent.priority, - color: tempLastEvent.color, - eventStatusId: tempLastEvent.eventStatusId, - }); - } - } - - continue; - } - } - - return eventList; + if ( + !monitorIds.find((item: ObjectID) => { + return item.toString() === monitorId; + }) + ) { + monitorIds.push(new ObjectID(monitorId)); + } } - public static getMonitorEvents( - items: Array<MonitorStatusTimeline> - ): Array<MonitorEvent> { - // get all distinct monitor ids. - const monitorIds: Array<ObjectID> = []; + const eventList: Array<MonitorEvent> = []; + // convert data to events. - for (let i: number = 0; i < items.length; i++) { - if (!items[i]) { - continue; - } - - const monitorId: string | undefined = - items[i]!.monitorId?.toString(); - - if (!monitorId) { - continue; - } - - if ( - !monitorIds.find((item: ObjectID) => { - return item.toString() === monitorId; - }) - ) { - monitorIds.push(new ObjectID(monitorId)); - } - } - - const eventList: Array<MonitorEvent> = []; - // convert data to events. - - for (const monitorId of monitorIds) { - const monitorEvents: Array<MonitorEvent> = - this.getMonitorEventsForId(monitorId, items); - eventList.push(...monitorEvents); - } - - // sort event list by start date. - eventList.sort((a: MonitorEvent, b: MonitorEvent) => { - if (OneUptimeDate.isAfter(a.startDate, b.startDate)) { - return 1; - } - - if (OneUptimeDate.isAfter(b.startDate, a.startDate)) { - return -1; - } - - return 0; - }); - - return [...eventList]; + for (const monitorId of monitorIds) { + const monitorEvents: Array<MonitorEvent> = this.getMonitorEventsForId( + monitorId, + items, + ); + eventList.push(...monitorEvents); } - public static calculateUptimePercentage( - items: Array<MonitorStatusTimeline>, - precision: UptimePrecision, - downtimeMonitorStatuses: Array<MonitorStatus> - ): number { - const monitorEvents: Array<Event> = - this.getNonOverlappingMonitorEvents(items); + // sort event list by start date. + eventList.sort((a: MonitorEvent, b: MonitorEvent) => { + if (OneUptimeDate.isAfter(a.startDate, b.startDate)) { + return 1; + } - // sort these by start date, - monitorEvents.sort((a: Event, b: Event) => { - if (OneUptimeDate.isAfter(a.startDate, b.startDate)) { - return 1; - } + if (OneUptimeDate.isAfter(b.startDate, a.startDate)) { + return -1; + } - if (OneUptimeDate.isAfter(b.startDate, a.startDate)) { - return -1; - } + return 0; + }); - return 0; - }); + return [...eventList]; + } - // calculate number of seconds between start of first event to date time now. - let totalSecondsInTimePeriod: number = 0; + public static calculateUptimePercentage( + items: Array<MonitorStatusTimeline>, + precision: UptimePrecision, + downtimeMonitorStatuses: Array<MonitorStatus>, + ): number { + const monitorEvents: Array<Event> = + this.getNonOverlappingMonitorEvents(items); - if (monitorEvents.length === 0) { - return 100; - } + // sort these by start date, + monitorEvents.sort((a: Event, b: Event) => { + if (OneUptimeDate.isAfter(a.startDate, b.startDate)) { + return 1; + } - if ( - OneUptimeDate.isAfter( - monitorEvents[0]!.startDate, - OneUptimeDate.getCurrentDate() - ) - ) { - return 100; - } + if (OneUptimeDate.isAfter(b.startDate, a.startDate)) { + return -1; + } - totalSecondsInTimePeriod = - OneUptimeDate.getSecondsBetweenDates( - monitorEvents[0]!.startDate, - OneUptimeDate.getCurrentDate() - ) || 1; + return 0; + }); - // get order of operational state. + // calculate number of seconds between start of first event to date time now. + let totalSecondsInTimePeriod: number = 0; - // if the event belongs to less than operationalStatePriority, then add the seconds to the total seconds. - - let totalDowntime: number = 0; - - for (const monitorEvent of monitorEvents) { - const isDowntimeEvent: boolean = Boolean( - downtimeMonitorStatuses.find((item: MonitorStatus) => { - return ( - item.id?.toString() === - monitorEvent.eventStatusId.toString() - ); - }) - ); - - if (isDowntimeEvent) { - totalDowntime += OneUptimeDate.getSecondsBetweenDates( - monitorEvent.startDate, - monitorEvent.endDate - ); - } - } - - // calculate percentage. - - const percentage: number = - ((totalSecondsInTimePeriod - totalDowntime) / - totalSecondsInTimePeriod) * - 100; - - if (precision === UptimePrecision.NO_DECIMAL) { - const noDecimalPercent: number = Math.round(percentage); - if (noDecimalPercent === 100 && totalDowntime > 0) { - return 99; - } - - return noDecimalPercent; - } - - if (precision === UptimePrecision.ONE_DECIMAL) { - const noDecimalPercent: number = Math.round(percentage * 10) / 10; - if (noDecimalPercent === 100 && totalDowntime > 0) { - return 99.9; - } - - return noDecimalPercent; - } - - if (precision === UptimePrecision.TWO_DECIMAL) { - const noDecimalPercent: number = Math.round(percentage * 100) / 100; - if (noDecimalPercent === 100 && totalDowntime > 0) { - return 99.99; - } - - return noDecimalPercent; - } - - if (precision === UptimePrecision.THREE_DECIMAL) { - const noDecimalPercent: number = - Math.round(percentage * 1000) / 1000; - if (noDecimalPercent === 100 && totalDowntime > 0) { - return 99.999; - } - - return noDecimalPercent; - } - - return percentage; + if (monitorEvents.length === 0) { + return 100; } + + if ( + OneUptimeDate.isAfter( + monitorEvents[0]!.startDate, + OneUptimeDate.getCurrentDate(), + ) + ) { + return 100; + } + + totalSecondsInTimePeriod = + OneUptimeDate.getSecondsBetweenDates( + monitorEvents[0]!.startDate, + OneUptimeDate.getCurrentDate(), + ) || 1; + + // get order of operational state. + + // if the event belongs to less than operationalStatePriority, then add the seconds to the total seconds. + + let totalDowntime: number = 0; + + for (const monitorEvent of monitorEvents) { + const isDowntimeEvent: boolean = Boolean( + downtimeMonitorStatuses.find((item: MonitorStatus) => { + return item.id?.toString() === monitorEvent.eventStatusId.toString(); + }), + ); + + if (isDowntimeEvent) { + totalDowntime += OneUptimeDate.getSecondsBetweenDates( + monitorEvent.startDate, + monitorEvent.endDate, + ); + } + } + + // calculate percentage. + + const percentage: number = + ((totalSecondsInTimePeriod - totalDowntime) / totalSecondsInTimePeriod) * + 100; + + if (precision === UptimePrecision.NO_DECIMAL) { + const noDecimalPercent: number = Math.round(percentage); + if (noDecimalPercent === 100 && totalDowntime > 0) { + return 99; + } + + return noDecimalPercent; + } + + if (precision === UptimePrecision.ONE_DECIMAL) { + const noDecimalPercent: number = Math.round(percentage * 10) / 10; + if (noDecimalPercent === 100 && totalDowntime > 0) { + return 99.9; + } + + return noDecimalPercent; + } + + if (precision === UptimePrecision.TWO_DECIMAL) { + const noDecimalPercent: number = Math.round(percentage * 100) / 100; + if (noDecimalPercent === 100 && totalDowntime > 0) { + return 99.99; + } + + return noDecimalPercent; + } + + if (precision === UptimePrecision.THREE_DECIMAL) { + const noDecimalPercent: number = Math.round(percentage * 1000) / 1000; + if (noDecimalPercent === 100 && totalDowntime > 0) { + return 99.999; + } + + return noDecimalPercent; + } + + return percentage; + } } diff --git a/CommonUI/src/Components/Navbar/NavBar.tsx b/CommonUI/src/Components/Navbar/NavBar.tsx index 75cc0eaedc..5f454ca456 100644 --- a/CommonUI/src/Components/Navbar/NavBar.tsx +++ b/CommonUI/src/Components/Navbar/NavBar.tsx @@ -1,27 +1,27 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; - className?: string | undefined; - rightElement?: ReactElement | undefined; + children: ReactElement | Array<ReactElement>; + className?: string | undefined; + rightElement?: ReactElement | undefined; } const Navbar: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const className: string = - props.className || 'flex text-center lg:space-x-8 lg:py-2 bg-white '; + const className: string = + props.className || "flex text-center lg:space-x-8 lg:py-2 bg-white "; - return ( - <nav className={props.rightElement ? `flex justify-between` : ''}> - <div data-testid="nav-children" className={className}> - {props.children} - </div> - {props.rightElement && ( - <div className={className}>{props.rightElement}</div> - )} - </nav> - ); + return ( + <nav className={props.rightElement ? `flex justify-between` : ""}> + <div data-testid="nav-children" className={className}> + {props.children} + </div> + {props.rightElement && ( + <div className={className}>{props.rightElement}</div> + )} + </nav> + ); }; export default Navbar; diff --git a/CommonUI/src/Components/Navbar/NavBarItem.tsx b/CommonUI/src/Components/Navbar/NavBarItem.tsx index 1520a289a0..a5f3d8a633 100644 --- a/CommonUI/src/Components/Navbar/NavBarItem.tsx +++ b/CommonUI/src/Components/Navbar/NavBarItem.tsx @@ -1,78 +1,78 @@ // Tailwind -import Navigation from '../../Utils/Navigation'; -import Icon, { ThickProp } from '../Icon/Icon'; -import Link from '../Link/Link'; -import Route from 'Common/Types/API/Route'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Navigation from "../../Utils/Navigation"; +import Icon, { ThickProp } from "../Icon/Icon"; +import Link from "../Link/Link"; +import Route from "Common/Types/API/Route"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; - icon?: undefined | IconProp; - route?: undefined | Route; - activeRoute?: undefined | Route; - exact?: boolean; - children?: undefined | ReactElement | Array<ReactElement>; - isRenderedOnMobile?: boolean; - onMouseOver?: (() => void) | undefined; - onClick?: (() => void) | undefined; - onMouseLeave?: (() => void) | undefined; - id?: string | undefined; + title: string; + icon?: undefined | IconProp; + route?: undefined | Route; + activeRoute?: undefined | Route; + exact?: boolean; + children?: undefined | ReactElement | Array<ReactElement>; + isRenderedOnMobile?: boolean; + onMouseOver?: (() => void) | undefined; + onClick?: (() => void) | undefined; + onMouseLeave?: (() => void) | undefined; + id?: string | undefined; } const NavBarItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const activeRoute: Route | undefined = props.activeRoute || props.route; - const isActive: boolean = Boolean( - activeRoute && - (props.exact - ? Navigation.isOnThisPage(activeRoute) - : Navigation.isStartWith(activeRoute)) - ); + const activeRoute: Route | undefined = props.activeRoute || props.route; + const isActive: boolean = Boolean( + activeRoute && + (props.exact + ? Navigation.isOnThisPage(activeRoute) + : Navigation.isStartWith(activeRoute)), + ); - let classNames: string = - 'text-gray-500 hover:bg-gray-50 hover:text-gray-900 rounded-md py-2 px-3 inline-flex items-center text-sm font-medium'; + let classNames: string = + "text-gray-500 hover:bg-gray-50 hover:text-gray-900 rounded-md py-2 px-3 inline-flex items-center text-sm font-medium"; + if (isActive) { + classNames = + "bg-gray-100 text-gray-900 rounded-md py-2 px-3 inline-flex items-center text-sm font-medium"; + } + + if (props.isRenderedOnMobile) { + classNames = + "text-gray-900 hover:bg-gray-50 hover:text-gray-900 block rounded-md py-2 px-3 text-base font-medium"; if (isActive) { - classNames = - 'bg-gray-100 text-gray-900 rounded-md py-2 px-3 inline-flex items-center text-sm font-medium'; + classNames = + "bg-gray-100 text-gray-900 block rounded-md py-2 px-3 text-base font-medium"; } + } - if (props.isRenderedOnMobile) { - classNames = - 'text-gray-900 hover:bg-gray-50 hover:text-gray-900 block rounded-md py-2 px-3 text-base font-medium'; - if (isActive) { - classNames = - 'bg-gray-100 text-gray-900 block rounded-md py-2 px-3 text-base font-medium'; - } - } - - return ( - <> - <Link - id={props.id} - className={classNames} - to={props.route ? props.route : null} - onMouseOver={props.onMouseOver} - onClick={props.onClick} - onMouseLeave={props.onMouseLeave} - > - {props.icon ? ( - <Icon - icon={props.icon} - className="mr-1 h-4 w-4" - thick={ThickProp.Thick} - /> - ) : ( - <></> - )} - <span>{props.title}</span> - {props.children ? <div className="arrow-down"></div> : <></>} - </Link> - {props.children} - </> - ); + return ( + <> + <Link + id={props.id} + className={classNames} + to={props.route ? props.route : null} + onMouseOver={props.onMouseOver} + onClick={props.onClick} + onMouseLeave={props.onMouseLeave} + > + {props.icon ? ( + <Icon + icon={props.icon} + className="mr-1 h-4 w-4" + thick={ThickProp.Thick} + /> + ) : ( + <></> + )} + <span>{props.title}</span> + {props.children ? <div className="arrow-down"></div> : <></>} + </Link> + {props.children} + </> + ); }; export default NavBarItem; diff --git a/CommonUI/src/Components/Navbar/NavBarMenu.tsx b/CommonUI/src/Components/Navbar/NavBarMenu.tsx index 46707d7b03..b4bbb69b8a 100644 --- a/CommonUI/src/Components/Navbar/NavBarMenu.tsx +++ b/CommonUI/src/Components/Navbar/NavBarMenu.tsx @@ -1,52 +1,52 @@ -import Link from '../Link/Link'; -import URL from 'Common/Types/API/URL'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Link from "../Link/Link"; +import URL from "Common/Types/API/URL"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; - footer?: { - title: string; - description: string; - link: URL; - }; + children: ReactElement | Array<ReactElement>; + footer?: { + title: string; + description: string; + link: URL; + }; } const NavBarItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let children: Array<ReactElement>; - if (!Array.isArray(props.children) && props.children) { - children = [props.children]; - } else { - children = props.children; - } - return ( - <div className="absolute left-1/3 z-10 mt-10 w-screen max-w-md -translate-x-1/2 transform px-2 sm:px-0 lg:max-w-3xl"> - <div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"> - <div className="relative grid gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8 lg:grid-cols-2"> - {children} - </div> - {props.footer && ( - <div className="bg-gray-50 p-5 sm:p-8"> - <Link - to={props.footer.link} - openInNewTab={true} - className="-m-3 flow-root rounded-md p-3 transition duration-150 ease-in-out hover:bg-gray-100" - > - <span className="flex items-center"> - <span className="text-base font-medium text-gray-900"> - {props.footer.title} - </span> - </span> - <span className="mt-1 block text-sm text-gray-500 text-left"> - {props.footer.description} - </span> - </Link> - </div> - )} - </div> + let children: Array<ReactElement>; + if (!Array.isArray(props.children) && props.children) { + children = [props.children]; + } else { + children = props.children; + } + return ( + <div className="absolute left-1/3 z-10 mt-10 w-screen max-w-md -translate-x-1/2 transform px-2 sm:px-0 lg:max-w-3xl"> + <div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"> + <div className="relative grid gap-6 bg-white px-5 py-6 sm:gap-8 sm:p-8 lg:grid-cols-2"> + {children} </div> - ); + {props.footer && ( + <div className="bg-gray-50 p-5 sm:p-8"> + <Link + to={props.footer.link} + openInNewTab={true} + className="-m-3 flow-root rounded-md p-3 transition duration-150 ease-in-out hover:bg-gray-100" + > + <span className="flex items-center"> + <span className="text-base font-medium text-gray-900"> + {props.footer.title} + </span> + </span> + <span className="mt-1 block text-sm text-gray-500 text-left"> + {props.footer.description} + </span> + </Link> + </div> + )} + </div> + </div> + ); }; export default NavBarItem; diff --git a/CommonUI/src/Components/Navbar/NavBarMenuItem.tsx b/CommonUI/src/Components/Navbar/NavBarMenuItem.tsx index 3ac0fc2f28..f86f55b189 100644 --- a/CommonUI/src/Components/Navbar/NavBarMenuItem.tsx +++ b/CommonUI/src/Components/Navbar/NavBarMenuItem.tsx @@ -1,39 +1,37 @@ -import Icon from '../Icon/Icon'; -import Link from '../Link/Link'; -import Route from 'Common/Types/API/Route'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Icon from "../Icon/Icon"; +import Link from "../Link/Link"; +import Route from "Common/Types/API/Route"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; - route: Route; - icon: IconProp; - description: string; - onClick: () => void; + title: string; + route: Route; + icon: IconProp; + description: string; + onClick: () => void; } const NavBarMenuItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="dropdown"> - <Link - onClick={props.onClick} - to={props.route} - className="-m-3 text-left flex items-start rounded-lg p-3 transition duration-150 ease-in-out hover:bg-gray-50" - > - <div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md bg-indigo-500 text-white sm:h-12 sm:w-12"> - <Icon icon={props.icon} className="h-6 w-6" /> - </div> - <div className="ml-4"> - <p className="text-base font-medium text-gray-900"> - {props.title} - </p> - <p className="text-sm text-gray-500">{props.description}</p> - </div> - </Link> + return ( + <div className="dropdown"> + <Link + onClick={props.onClick} + to={props.route} + className="-m-3 text-left flex items-start rounded-lg p-3 transition duration-150 ease-in-out hover:bg-gray-50" + > + <div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md bg-indigo-500 text-white sm:h-12 sm:w-12"> + <Icon icon={props.icon} className="h-6 w-6" /> </div> - ); + <div className="ml-4"> + <p className="text-base font-medium text-gray-900">{props.title}</p> + <p className="text-sm text-gray-500">{props.description}</p> + </div> + </Link> + </div> + ); }; export default NavBarMenuItem; diff --git a/CommonUI/src/Components/Navbar/NavBarMenuSubItem.tsx b/CommonUI/src/Components/Navbar/NavBarMenuSubItem.tsx index bdb83c1cca..fe64780f53 100644 --- a/CommonUI/src/Components/Navbar/NavBarMenuSubItem.tsx +++ b/CommonUI/src/Components/Navbar/NavBarMenuSubItem.tsx @@ -1,20 +1,20 @@ -import Link from '../Link/Link'; -import Route from 'Common/Types/API/Route'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Link from "../Link/Link"; +import Route from "Common/Types/API/Route"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; - route: Route; + title: string; + route: Route; } const NavBarMenuItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Link className="dropdown-item" to={props.route}> - {props.title} - </Link> - ); + return ( + <Link className="dropdown-item" to={props.route}> + {props.title} + </Link> + ); }; export default NavBarMenuItem; diff --git a/CommonUI/src/Components/ObjectID/IDGenerator.tsx b/CommonUI/src/Components/ObjectID/IDGenerator.tsx index 72799da878..f35c2cdeea 100644 --- a/CommonUI/src/Components/ObjectID/IDGenerator.tsx +++ b/CommonUI/src/Components/ObjectID/IDGenerator.tsx @@ -1,89 +1,85 @@ -import Button, { ButtonStyleType } from '../Button/Button'; -import Input from '../Input/Input'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; +import Button, { ButtonStyleType } from "../Button/Button"; +import Input from "../Input/Input"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - readonly?: boolean | undefined; - initialValue?: undefined | ObjectID; - onChange?: undefined | ((value: ObjectID) => void); - value?: ObjectID | undefined; - disabled?: boolean | undefined; - dataTestId?: string | undefined; - tabIndex?: number | undefined; - onEnterPress?: (() => void) | undefined; - error?: string | undefined; + readonly?: boolean | undefined; + initialValue?: undefined | ObjectID; + onChange?: undefined | ((value: ObjectID) => void); + value?: ObjectID | undefined; + disabled?: boolean | undefined; + dataTestId?: string | undefined; + tabIndex?: number | undefined; + onEnterPress?: (() => void) | undefined; + error?: string | undefined; } const IDGenerator: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [value, setValue] = useState<ObjectID | null>(null); + const [value, setValue] = useState<ObjectID | null>(null); - useEffect(() => { - if (props.initialValue) { - setValue(props.initialValue); - } + useEffect(() => { + if (props.initialValue) { + setValue(props.initialValue); + } - if (props.value) { - setValue(props.value); - } - }, []); + if (props.value) { + setValue(props.value); + } + }, []); - useEffect(() => { - if (props.initialValue) { - setValue(props.initialValue); - } - }, [props.initialValue]); + useEffect(() => { + if (props.initialValue) { + setValue(props.initialValue); + } + }, [props.initialValue]); - useEffect(() => { - setValue(props.value ? props.value : props.initialValue || null); - }, [props.value]); + useEffect(() => { + setValue(props.value ? props.value : props.initialValue || null); + }, [props.value]); - return ( - <> - <> - <div className="flex" data-testid={props.dataTestId}> - {value && ( - <Input - readOnly={props.readonly} - tabIndex={props.tabIndex} - onEnterPress={props.onEnterPress} - value={value.toString()} - /> - )} - <div className="mt-2"> - <Button - icon={IconProp.Refresh} - buttonStyle={ButtonStyleType.NORMAL} - disabled={props.disabled || props.readonly} - title={value ? 'Regenerate' : 'Generate'} - onClick={() => { - const generatedID: ObjectID = - ObjectID.generate(); - setValue(generatedID); - props.onChange && props.onChange(generatedID); - }} - /> - </div> - </div> - </> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} - </> - ); + return ( + <> + <> + <div className="flex" data-testid={props.dataTestId}> + {value && ( + <Input + readOnly={props.readonly} + tabIndex={props.tabIndex} + onEnterPress={props.onEnterPress} + value={value.toString()} + /> + )} + <div className="mt-2"> + <Button + icon={IconProp.Refresh} + buttonStyle={ButtonStyleType.NORMAL} + disabled={props.disabled || props.readonly} + title={value ? "Regenerate" : "Generate"} + onClick={() => { + const generatedID: ObjectID = ObjectID.generate(); + setValue(generatedID); + props.onChange && props.onChange(generatedID); + }} + /> + </div> + </div> + </> + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </> + ); }; export default IDGenerator; diff --git a/CommonUI/src/Components/OrderedStatesList/Item.tsx b/CommonUI/src/Components/OrderedStatesList/Item.tsx index d9e24b05fd..3d7aabd6db 100644 --- a/CommonUI/src/Components/OrderedStatesList/Item.tsx +++ b/CommonUI/src/Components/OrderedStatesList/Item.tsx @@ -1,121 +1,114 @@ -import ActionButtonSchema from '../ActionButton/ActionButtonSchema'; -import Button, { ButtonSize } from '../Button/Button'; -import ConfirmModal from '../Modal/ConfirmModal'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement, useState } from 'react'; +import ActionButtonSchema from "../ActionButton/ActionButtonSchema"; +import Button, { ButtonSize } from "../Button/Button"; +import ConfirmModal from "../Modal/ConfirmModal"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement, useState } from "react"; export interface ComponentProps<T extends GenericObject> { - item: T; - actionButtons?: undefined | Array<ActionButtonSchema<T>>; - titleField: keyof T; - descriptionField?: keyof T | undefined; - getTitleElement?: ((item: T) => ReactElement) | undefined; - getDescriptionElement?: ((item: T) => ReactElement) | undefined; + item: T; + actionButtons?: undefined | Array<ActionButtonSchema<T>>; + titleField: keyof T; + descriptionField?: keyof T | undefined; + getTitleElement?: ((item: T) => ReactElement) | undefined; + getDescriptionElement?: ((item: T) => ReactElement) | undefined; } type ItemFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const Item: ItemFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const [isButtonLoading, setIsButtonLoading] = useState<Array<boolean>>( - props.actionButtons?.map(() => { - return false; - }) || [] - ); + const [isButtonLoading, setIsButtonLoading] = useState<Array<boolean>>( + props.actionButtons?.map(() => { + return false; + }) || [], + ); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - return ( - <div className="text-center border border-gray-300 rounded p-10 space-y-4 w-fit"> - {error && ( - <ConfirmModal - title={`Error`} - description={error} - submitButtonText={'Close'} - onSubmit={() => { - return setError(''); - }} - /> - )} + return ( + <div className="text-center border border-gray-300 rounded p-10 space-y-4 w-fit"> + {error && ( + <ConfirmModal + title={`Error`} + description={error} + submitButtonText={"Close"} + onSubmit={() => { + return setError(""); + }} + /> + )} - {!props.getTitleElement && ( - <div> - {props.item[props.titleField] - ? (props.item[props.titleField] as string) - : ''} - </div> - )} - {props.getTitleElement && ( - <div className="justify-center flex"> - {props.getTitleElement(props.item)} - </div> - )} - <div className="text-gray-500"> - {props.getDescriptionElement && ( - <div className="justify-center flex"> - {props.getDescriptionElement(props.item)} - </div> - )} - {!props.getDescriptionElement && ( - <div> - {props.descriptionField && - props.item[props.descriptionField] - ? (props.item[props.descriptionField] as string) - : ''} - </div> - )} - </div> - <div className="flex justify-center"> - {props.actionButtons?.map( - (button: ActionButtonSchema<T>, i: number) => { - if (button.isVisible && !button.isVisible(props.item)) { - return <></>; - } - - return ( - <div key={i} className=""> - <Button - buttonSize={ButtonSize.Small} - title={button.title} - icon={button.icon} - buttonStyle={button.buttonStyleType} - isLoading={isButtonLoading[i]} - onClick={() => { - if (button.onClick) { - isButtonLoading[i] = true; - setIsButtonLoading(isButtonLoading); - button.onClick( - props.item, - () => { - // on action complete - isButtonLoading[i] = false; - setIsButtonLoading( - isButtonLoading - ); - }, - (err: Error) => { - isButtonLoading[i] = false; - setIsButtonLoading( - isButtonLoading - ); - setError( - (err as Error).message - ); - } - ); - } - }} - /> - </div> - ); - } - )} - </div> + {!props.getTitleElement && ( + <div> + {props.item[props.titleField] + ? (props.item[props.titleField] as string) + : ""} </div> - ); + )} + {props.getTitleElement && ( + <div className="justify-center flex"> + {props.getTitleElement(props.item)} + </div> + )} + <div className="text-gray-500"> + {props.getDescriptionElement && ( + <div className="justify-center flex"> + {props.getDescriptionElement(props.item)} + </div> + )} + {!props.getDescriptionElement && ( + <div> + {props.descriptionField && props.item[props.descriptionField] + ? (props.item[props.descriptionField] as string) + : ""} + </div> + )} + </div> + <div className="flex justify-center"> + {props.actionButtons?.map( + (button: ActionButtonSchema<T>, i: number) => { + if (button.isVisible && !button.isVisible(props.item)) { + return <></>; + } + + return ( + <div key={i} className=""> + <Button + buttonSize={ButtonSize.Small} + title={button.title} + icon={button.icon} + buttonStyle={button.buttonStyleType} + isLoading={isButtonLoading[i]} + onClick={() => { + if (button.onClick) { + isButtonLoading[i] = true; + setIsButtonLoading(isButtonLoading); + button.onClick( + props.item, + () => { + // on action complete + isButtonLoading[i] = false; + setIsButtonLoading(isButtonLoading); + }, + (err: Error) => { + isButtonLoading[i] = false; + setIsButtonLoading(isButtonLoading); + setError((err as Error).message); + }, + ); + } + }} + /> + </div> + ); + }, + )} + </div> + </div> + ); }; export default Item; diff --git a/CommonUI/src/Components/OrderedStatesList/OrderedStatesList.tsx b/CommonUI/src/Components/OrderedStatesList/OrderedStatesList.tsx index a0d1f2c720..c1d0fb40dd 100644 --- a/CommonUI/src/Components/OrderedStatesList/OrderedStatesList.tsx +++ b/CommonUI/src/Components/OrderedStatesList/OrderedStatesList.tsx @@ -1,182 +1,155 @@ -import ActionButtonSchema from '../ActionButton/ActionButtonSchema'; -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import Icon, { SizeProp } from '../Icon/Icon'; -import Item from './Item'; -import GenericObject from 'Common/Types/GenericObject'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { ReactElement } from 'react'; +import ActionButtonSchema from "../ActionButton/ActionButtonSchema"; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import Icon, { SizeProp } from "../Icon/Icon"; +import Item from "./Item"; +import GenericObject from "Common/Types/GenericObject"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { ReactElement } from "react"; export interface ComponentProps<T extends GenericObject> { - data: Array<T>; - onCreateNewItem?: ((order: number) => void) | undefined; - noItemsMessage?: string | ReactElement | undefined; - error?: string | undefined; - isLoading?: boolean | undefined; - onRefreshClick?: (() => void) | undefined; - singularLabel: string; - id?: string; - actionButtons?: undefined | Array<ActionButtonSchema<T>>; - titleField: keyof T; - descriptionField?: keyof T | undefined; - orderField: keyof T; - getTitleElement?: ((item: T) => ReactElement) | undefined; - getDescriptionElement?: ((item: T) => ReactElement) | undefined; - shouldAddItemInTheEnd?: boolean | undefined; - shouldAddItemInTheBeginning?: boolean | undefined; + data: Array<T>; + onCreateNewItem?: ((order: number) => void) | undefined; + noItemsMessage?: string | ReactElement | undefined; + error?: string | undefined; + isLoading?: boolean | undefined; + onRefreshClick?: (() => void) | undefined; + singularLabel: string; + id?: string; + actionButtons?: undefined | Array<ActionButtonSchema<T>>; + titleField: keyof T; + descriptionField?: keyof T | undefined; + orderField: keyof T; + getTitleElement?: ((item: T) => ReactElement) | undefined; + getDescriptionElement?: ((item: T) => ReactElement) | undefined; + shouldAddItemInTheEnd?: boolean | undefined; + shouldAddItemInTheBeginning?: boolean | undefined; } type OrderedStatesListFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const OrderedStatesList: OrderedStatesListFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - if (props.isLoading) { - return <ComponentLoader />; - } - - if (props.error) { - return ( - <ErrorMessage - error={props.error} - onRefreshClick={props.onRefreshClick} - /> - ); - } - - if (props.data.length === 0) { - return ( - <ErrorMessage - error={ - props.noItemsMessage - ? props.noItemsMessage - : `No ${props.singularLabel.toLocaleLowerCase()}` - } - onRefreshClick={props.onRefreshClick} - /> - ); - } + if (props.isLoading) { + return <ComponentLoader />; + } + if (props.error) { return ( - <div className="m-32 text-center"> - {props.error && <p>{props.error}</p>} - {!props.error && - props.data && - props.data.length > 0 && - props.data.map((item: T, i: number) => { - const isEnd: boolean = !(i + 1 < props.data.length); - const isBeginning: boolean = !(i === 0); - - return ( - <div - key={i} - id={props.id} - className="items-center w-fit m-auto" - > - {props.onCreateNewItem && - isBeginning && - props.shouldAddItemInTheBeginning && ( - <div className="text-center"> - <div - className="m-auto rounded-full items-center cursor-pointer text-gray-400 hover:bg-gray-50 hover:text-gray-600 items-center border border-gray-300 p-5 w-fit" - onClick={() => { - props.onCreateNewItem && - props.onCreateNewItem( - item[props.orderField] - ? (item[ - props - .orderField - ] as number) + 1 - : 0 - ); - }} - > - <div className="flex text-center"> - <Icon - icon={IconProp.Add} - className="m-auto h-5 w-5" - />{' '} - <span className="text-sm ml-2"> - Add New Item - </span> - </div> - </div> - - <div className="items-center m-10 "> - <Icon - icon={IconProp.ChevronDown} - size={SizeProp.Regular} - className="m-auto h-5 w-5 text-gray-500" - /> - </div> - </div> - )} - <Item - item={item} - titleField={props.titleField} - descriptionField={props.descriptionField} - actionButtons={props.actionButtons} - getTitleElement={props.getTitleElement} - getDescriptionElement={ - props.getDescriptionElement - } - /> - {((isEnd && props.shouldAddItemInTheEnd) || - !isEnd) && ( - <div className="vertical-list items-center m-10 "> - <Icon - icon={IconProp.ChevronDown} - size={SizeProp.Regular} - className="m-auto h-5 w-5 text-gray-500" - /> - </div> - )} - {props.onCreateNewItem && - ((isEnd && props.shouldAddItemInTheEnd) || - !isEnd) && ( - <div className="text-center"> - <div - className="m-auto items-center cursor-pointer text-gray-400 hover:bg-gray-50 border hover:text-gray-600 rounded-full border-gray-300 p-5 w-fit" - onClick={() => { - props.onCreateNewItem && - props.onCreateNewItem( - item[props.orderField] - ? (item[ - props - .orderField - ] as number) + 1 - : 0 - ); - }} - > - <div className="flex items-center "> - <Icon - icon={IconProp.Add} - className="m-auto h-5 w-5" - />{' '} - <span className="text-sm ml-2"> - Add New Item - </span> - </div> - </div> - {!isEnd && ( - <div className="items-center m-10"> - <Icon - icon={IconProp.ChevronDown} - size={SizeProp.Larger} - className="m-auto h-5 w-5 text-gray-500" - /> - </div> - )} - </div> - )} - </div> - ); - })} - </div> + <ErrorMessage error={props.error} onRefreshClick={props.onRefreshClick} /> ); + } + + if (props.data.length === 0) { + return ( + <ErrorMessage + error={ + props.noItemsMessage + ? props.noItemsMessage + : `No ${props.singularLabel.toLocaleLowerCase()}` + } + onRefreshClick={props.onRefreshClick} + /> + ); + } + + return ( + <div className="m-32 text-center"> + {props.error && <p>{props.error}</p>} + {!props.error && + props.data && + props.data.length > 0 && + props.data.map((item: T, i: number) => { + const isEnd: boolean = !(i + 1 < props.data.length); + const isBeginning: boolean = !(i === 0); + + return ( + <div key={i} id={props.id} className="items-center w-fit m-auto"> + {props.onCreateNewItem && + isBeginning && + props.shouldAddItemInTheBeginning && ( + <div className="text-center"> + <div + className="m-auto rounded-full items-center cursor-pointer text-gray-400 hover:bg-gray-50 hover:text-gray-600 items-center border border-gray-300 p-5 w-fit" + onClick={() => { + props.onCreateNewItem && + props.onCreateNewItem( + item[props.orderField] + ? (item[props.orderField] as number) + 1 + : 0, + ); + }} + > + <div className="flex text-center"> + <Icon icon={IconProp.Add} className="m-auto h-5 w-5" />{" "} + <span className="text-sm ml-2">Add New Item</span> + </div> + </div> + + <div className="items-center m-10 "> + <Icon + icon={IconProp.ChevronDown} + size={SizeProp.Regular} + className="m-auto h-5 w-5 text-gray-500" + /> + </div> + </div> + )} + <Item + item={item} + titleField={props.titleField} + descriptionField={props.descriptionField} + actionButtons={props.actionButtons} + getTitleElement={props.getTitleElement} + getDescriptionElement={props.getDescriptionElement} + /> + {((isEnd && props.shouldAddItemInTheEnd) || !isEnd) && ( + <div className="vertical-list items-center m-10 "> + <Icon + icon={IconProp.ChevronDown} + size={SizeProp.Regular} + className="m-auto h-5 w-5 text-gray-500" + /> + </div> + )} + {props.onCreateNewItem && + ((isEnd && props.shouldAddItemInTheEnd) || !isEnd) && ( + <div className="text-center"> + <div + className="m-auto items-center cursor-pointer text-gray-400 hover:bg-gray-50 border hover:text-gray-600 rounded-full border-gray-300 p-5 w-fit" + onClick={() => { + props.onCreateNewItem && + props.onCreateNewItem( + item[props.orderField] + ? (item[props.orderField] as number) + 1 + : 0, + ); + }} + > + <div className="flex items-center "> + <Icon icon={IconProp.Add} className="m-auto h-5 w-5" />{" "} + <span className="text-sm ml-2">Add New Item</span> + </div> + </div> + {!isEnd && ( + <div className="items-center m-10"> + <Icon + icon={IconProp.ChevronDown} + size={SizeProp.Larger} + className="m-auto h-5 w-5 text-gray-500" + /> + </div> + )} + </div> + )} + </div> + ); + })} + </div> + ); }; export default OrderedStatesList; diff --git a/CommonUI/src/Components/Page/ModelPage.tsx b/CommonUI/src/Components/Page/ModelPage.tsx index e8009a28b1..58e31f3fc1 100644 --- a/CommonUI/src/Components/Page/ModelPage.tsx +++ b/CommonUI/src/Components/Page/ModelPage.tsx @@ -1,81 +1,79 @@ -import API from '../../Utils/API/API'; -import ModelAPI from '../../Utils/ModelAPI/ModelAPI'; -import Page from './Page'; -import BaseModel from 'Common/Models/BaseModel'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Link from 'Common/Types/Link'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { ReactElement, useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import API from "../../Utils/API/API"; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import Page from "./Page"; +import BaseModel from "Common/Models/BaseModel"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Link from "Common/Types/Link"; +import ObjectID from "Common/Types/ObjectID"; +import React, { ReactElement, useState } from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps<TBaseModel extends BaseModel> { - title?: string | undefined; - breadcrumbLinks?: Array<Link> | undefined; - children: Array<ReactElement> | ReactElement; - sideMenu?: undefined | ReactElement; - className?: string | undefined; - modelType: { new (): TBaseModel }; - modelId: ObjectID; - modelNameField: string; + title?: string | undefined; + breadcrumbLinks?: Array<Link> | undefined; + children: Array<ReactElement> | ReactElement; + sideMenu?: undefined | ReactElement; + className?: string | undefined; + modelType: { new (): TBaseModel }; + modelId: ObjectID; + modelNameField: string; } const ModelPage: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); + const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - // get item. - setIsLoading(true); + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + // get item. + setIsLoading(true); - setError(''); - try { - const item: TBaseModel | null = await ModelAPI.getItem({ - modelType: props.modelType, - id: props.modelId, - select: { - [props.modelNameField]: true, - } as any, - requestOptions: {}, - }); + setError(""); + try { + const item: TBaseModel | null = await ModelAPI.getItem({ + modelType: props.modelType, + id: props.modelId, + select: { + [props.modelNameField]: true, + } as any, + requestOptions: {}, + }); - if (!item) { - setError( - `Cannot load ${( - new props.modelType()?.singularName || 'item' - ).toLowerCase()}. It could be because you don't have enough permissions to read this ${( - new props.modelType()?.singularName || 'item' - ).toLowerCase()}.` - ); + if (!item) { + setError( + `Cannot load ${( + new props.modelType()?.singularName || "item" + ).toLowerCase()}. It could be because you don't have enough permissions to read this ${( + new props.modelType()?.singularName || "item" + ).toLowerCase()}.`, + ); - return; - } + return; + } - setTitle( - `${props.title || ''} - ${ - (item as any)[props.modelNameField] as string - }` - ); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - setIsLoading(false); - }; + setTitle( + `${props.title || ""} - ${ + (item as any)[props.modelNameField] as string + }`, + ); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + setIsLoading(false); + }; - const [title, setTitle] = useState<string | undefined>(props.title); + const [title, setTitle] = useState<string | undefined>(props.title); - useAsyncEffect(async () => { - // fetch the model - await fetchItem(); - }, []); + useAsyncEffect(async () => { + // fetch the model + await fetchItem(); + }, []); - return ( - <Page {...props} isLoading={isLoading} error={error} title={title} /> - ); + return <Page {...props} isLoading={isLoading} error={error} title={title} />; }; export default ModelPage; diff --git a/CommonUI/src/Components/Page/Page.tsx b/CommonUI/src/Components/Page/Page.tsx index a80b6669c6..f4017cd4bb 100644 --- a/CommonUI/src/Components/Page/Page.tsx +++ b/CommonUI/src/Components/Page/Page.tsx @@ -1,93 +1,91 @@ -import Analytics from '../../Utils/Analytics'; -import Breadcrumbs from '../Breadcrumbs/Breadcrumbs'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import PageLoader from '../Loader/PageLoader'; -import Link from 'Common/Types/Link'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import Analytics from "../../Utils/Analytics"; +import Breadcrumbs from "../Breadcrumbs/Breadcrumbs"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import PageLoader from "../Loader/PageLoader"; +import Link from "Common/Types/Link"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - title?: string | undefined; - breadcrumbLinks?: Array<Link> | undefined; - children: Array<ReactElement> | ReactElement; - sideMenu?: undefined | ReactElement; - className?: string | undefined; - isLoading?: boolean | undefined; - error?: string | undefined; + title?: string | undefined; + breadcrumbLinks?: Array<Link> | undefined; + children: Array<ReactElement> | ReactElement; + sideMenu?: undefined | ReactElement; + className?: string | undefined; + isLoading?: boolean | undefined; + error?: string | undefined; } const Page: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - useEffect(() => { - if (props.breadcrumbLinks && props.breadcrumbLinks.length > 0) { - Analytics.capture( - 'Page View: ' + - props.breadcrumbLinks - .map((link: Link) => { - return link.title; - }) - .join(' > ') - .toString() || '' - ); - } - }, [props.breadcrumbLinks]); - - if (props.error) { - return <ErrorMessage error={props.error} />; + useEffect(() => { + if (props.breadcrumbLinks && props.breadcrumbLinks.length > 0) { + Analytics.capture( + "Page View: " + + props.breadcrumbLinks + .map((link: Link) => { + return link.title; + }) + .join(" > ") + .toString() || "", + ); } + }, [props.breadcrumbLinks]); - return ( - <div - className={ - props.className || - 'mb-auto max-w-full px-4 sm:px-6 lg:px-8 mt-5 mb-20 h-max' - } - > - {((props.breadcrumbLinks && props.breadcrumbLinks.length > 0) || - props.title) && ( - <div className="mb-5"> - {props.breadcrumbLinks && props.breadcrumbLinks.length > 0 && ( - <div className="mt-2"> - <Breadcrumbs links={props.breadcrumbLinks} /> - </div> - )} - {props.title && ( - <div className="mt-2 md:flex md:items-center md:justify-between"> - <div className="min-w-0"> - <h2 className="text-xl leading-7 text-gray-900 sm:truncate sm:text-2xl sm:tracking-tight"> - {props.title} - </h2> - </div> - </div> - )} - </div> - )} + if (props.error) { + return <ErrorMessage error={props.error} />; + } - {props.sideMenu && ( - <main className="mx-auto max-w-full pb-10 mr-5"> - <div className="lg:grid lg:grid-cols-12 lg:gap-x-5"> - {props.sideMenu} - - {!props.isLoading && ( - <div className="space-y-6 sm:px-6 lg:col-span-10 md:col-span-9 lg:px-0"> - {props.children} - </div> - )} - {props.isLoading && ( - <div className="col-span-10"> - <PageLoader isVisible={true} /> - </div> - )} - </div> - </main> - )} - - {!props.sideMenu && !props.isLoading && props.children} - {!props.sideMenu && props.isLoading && ( - <PageLoader isVisible={true} /> - )} + return ( + <div + className={ + props.className || + "mb-auto max-w-full px-4 sm:px-6 lg:px-8 mt-5 mb-20 h-max" + } + > + {((props.breadcrumbLinks && props.breadcrumbLinks.length > 0) || + props.title) && ( + <div className="mb-5"> + {props.breadcrumbLinks && props.breadcrumbLinks.length > 0 && ( + <div className="mt-2"> + <Breadcrumbs links={props.breadcrumbLinks} /> + </div> + )} + {props.title && ( + <div className="mt-2 md:flex md:items-center md:justify-between"> + <div className="min-w-0"> + <h2 className="text-xl leading-7 text-gray-900 sm:truncate sm:text-2xl sm:tracking-tight"> + {props.title} + </h2> + </div> + </div> + )} </div> - ); + )} + + {props.sideMenu && ( + <main className="mx-auto max-w-full pb-10 mr-5"> + <div className="lg:grid lg:grid-cols-12 lg:gap-x-5"> + {props.sideMenu} + + {!props.isLoading && ( + <div className="space-y-6 sm:px-6 lg:col-span-10 md:col-span-9 lg:px-0"> + {props.children} + </div> + )} + {props.isLoading && ( + <div className="col-span-10"> + <PageLoader isVisible={true} /> + </div> + )} + </div> + </main> + )} + + {!props.sideMenu && !props.isLoading && props.children} + {!props.sideMenu && props.isLoading && <PageLoader isVisible={true} />} + </div> + ); }; export default Page; diff --git a/CommonUI/src/Components/Pagination/Pagination.tsx b/CommonUI/src/Components/Pagination/Pagination.tsx index 79ac0981f5..ce620a897d 100644 --- a/CommonUI/src/Components/Pagination/Pagination.tsx +++ b/CommonUI/src/Components/Pagination/Pagination.tsx @@ -1,255 +1,243 @@ -import Button, { ButtonSize, ButtonStyleType } from '../Button/Button'; -import BasicFormModal from '../FormModal/BasicFormModal'; -import FormFieldSchemaType from '../Forms/Types/FormFieldSchemaType'; -import IconProp from 'Common/Types/Icon/IconProp'; +import Button, { ButtonSize, ButtonStyleType } from "../Button/Button"; +import BasicFormModal from "../FormModal/BasicFormModal"; +import FormFieldSchemaType from "../Forms/Types/FormFieldSchemaType"; +import IconProp from "Common/Types/Icon/IconProp"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface PaginationNavigationItem { - pageNumber: number; - itemsOnPage: number; + pageNumber: number; + itemsOnPage: number; } export interface ComponentProps { - currentPageNumber: number; - totalItemsCount: number; - itemsOnPage: number; - onNavigateToPage: (pageNumber: number, itemsOnPage: number) => void; - isLoading: boolean; - isError: boolean; - singularLabel: string; - pluralLabel: string; - dataTestId?: string; + currentPageNumber: number; + totalItemsCount: number; + itemsOnPage: number; + onNavigateToPage: (pageNumber: number, itemsOnPage: number) => void; + isLoading: boolean; + isError: boolean; + singularLabel: string; + pluralLabel: string; + dataTestId?: string; } const Pagination: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [minPageNumber, setMinPageNumber] = useState<number>(1); - const [maxPageNumber, setMaxPageNumber] = useState<number>(1); + const [minPageNumber, setMinPageNumber] = useState<number>(1); + const [maxPageNumber, setMaxPageNumber] = useState<number>(1); - const setMinAndMaxPageNumber: () => void = (): void => { - setMinPageNumber(1); - let maxPageNo: number = - props.totalItemsCount % props.itemsOnPage === 0 - ? props.totalItemsCount / props.itemsOnPage + 1 - : props.totalItemsCount / props.itemsOnPage; + const setMinAndMaxPageNumber: () => void = (): void => { + setMinPageNumber(1); + let maxPageNo: number = + props.totalItemsCount % props.itemsOnPage === 0 + ? props.totalItemsCount / props.itemsOnPage + 1 + : props.totalItemsCount / props.itemsOnPage; - if (maxPageNo < 1) { - maxPageNo = 1; - } + if (maxPageNo < 1) { + maxPageNo = 1; + } - setMaxPageNumber(Math.ceil(maxPageNo)); - }; + setMaxPageNumber(Math.ceil(maxPageNo)); + }; - useEffect(() => { - setMinAndMaxPageNumber(); - }, []); + useEffect(() => { + setMinAndMaxPageNumber(); + }, []); - useEffect(() => { - setMinAndMaxPageNumber(); - }, [props.totalItemsCount, props.itemsOnPage]); + useEffect(() => { + setMinAndMaxPageNumber(); + }, [props.totalItemsCount, props.itemsOnPage]); - const isPreviousDisabled: boolean = - props.currentPageNumber === 1 || props.isLoading || props.isError; - const isNextDisabled: boolean = - props.currentPageNumber * props.itemsOnPage >= props.totalItemsCount || - props.isLoading || - props.isError; - const isCurrentPageButtonDisabled: boolean = - props.totalItemsCount === 0 || props.isLoading || props.isError; + const isPreviousDisabled: boolean = + props.currentPageNumber === 1 || props.isLoading || props.isError; + const isNextDisabled: boolean = + props.currentPageNumber * props.itemsOnPage >= props.totalItemsCount || + props.isLoading || + props.isError; + const isCurrentPageButtonDisabled: boolean = + props.totalItemsCount === 0 || props.isLoading || props.isError; - const [showPaginationModel, setShowPaginationModel] = - useState<boolean>(false); + const [showPaginationModel, setShowPaginationModel] = + useState<boolean>(false); - return ( - <div - className="flex items-center justify-between border-t border-gray-200 bg-white px-4" - data-testid={props.dataTestId} - > - <div> - <p className="text-sm text-gray-500"> - {!props.isLoading && ( - <span> - {props.totalItemsCount.toLocaleString()}{' '} - {props.totalItemsCount > 1 - ? props.pluralLabel - : props.singularLabel}{' '} - {`in total. Showing ${ - props.itemsOnPage * - (props.currentPageNumber - 1) + - 1 - } to ${ - props.itemsOnPage * props.currentPageNumber - } on this page.`} - </span> - )} - </p> - </div> - <div> - <nav className="inline-flex -space-x-px rounded-md shadow-sm"> - <div className="my-2"> - <Button - dataTestId="show-pagination-modal-button" - className="mx-2 my-2" - buttonSize={ButtonSize.ExtraSmall} - icon={IconProp.AdjustmentHorizontal} - buttonStyle={ButtonStyleType.ICON_LIGHT} - onClick={() => { - setShowPaginationModel(true); - }} - /> - </div> + return ( + <div + className="flex items-center justify-between border-t border-gray-200 bg-white px-4" + data-testid={props.dataTestId} + > + <div> + <p className="text-sm text-gray-500"> + {!props.isLoading && ( + <span> + {props.totalItemsCount.toLocaleString()}{" "} + {props.totalItemsCount > 1 + ? props.pluralLabel + : props.singularLabel}{" "} + {`in total. Showing ${ + props.itemsOnPage * (props.currentPageNumber - 1) + 1 + } to ${ + props.itemsOnPage * props.currentPageNumber + } on this page.`} + </span> + )} + </p> + </div> + <div> + <nav className="inline-flex -space-x-px rounded-md shadow-sm"> + <div className="my-2"> + <Button + dataTestId="show-pagination-modal-button" + className="mx-2 my-2" + buttonSize={ButtonSize.ExtraSmall} + icon={IconProp.AdjustmentHorizontal} + buttonStyle={ButtonStyleType.ICON_LIGHT} + onClick={() => { + setShowPaginationModel(true); + }} + /> + </div> - <ul className="py-3"> - <li - onClick={() => { - let currentPageNumber: number = - props.currentPageNumber; + <ul className="py-3"> + <li + onClick={() => { + let currentPageNumber: number = props.currentPageNumber; - if (typeof currentPageNumber === 'string') { - currentPageNumber = - parseInt(currentPageNumber); - } + if (typeof currentPageNumber === "string") { + currentPageNumber = parseInt(currentPageNumber); + } - if ( - props.onNavigateToPage && - !isPreviousDisabled - ) { - props.onNavigateToPage( - currentPageNumber - 1, - props.itemsOnPage - ); - } - }} - className={` inline-flex items-center rounded-l-md border border-gray-300 bg-white px-2 py-2 text-sm font-medium text-gray-500 ${ - isPreviousDisabled - ? 'bg-gray-100' - : 'hover:bg-gray-50 cursor-pointer' - }`} - > - <span className="page-link">Previous</span> - </li> - <li - data-testid="current-page-link" - className={` z-10 inline-flex items-center border border-x-0 border-gray-300 hover:bg-gray-50 px-4 py-2 text-sm font-medium text-text-600 cursor-pointer ${ - isCurrentPageButtonDisabled ? 'bg-gray-100' : '' - }`} - onClick={() => { - setShowPaginationModel(true); - }} - > - <span>{props.currentPageNumber}</span> - </li> - <li - onClick={() => { - let currentPageNumber: number = - props.currentPageNumber; + if (props.onNavigateToPage && !isPreviousDisabled) { + props.onNavigateToPage( + currentPageNumber - 1, + props.itemsOnPage, + ); + } + }} + className={` inline-flex items-center rounded-l-md border border-gray-300 bg-white px-2 py-2 text-sm font-medium text-gray-500 ${ + isPreviousDisabled + ? "bg-gray-100" + : "hover:bg-gray-50 cursor-pointer" + }`} + > + <span className="page-link">Previous</span> + </li> + <li + data-testid="current-page-link" + className={` z-10 inline-flex items-center border border-x-0 border-gray-300 hover:bg-gray-50 px-4 py-2 text-sm font-medium text-text-600 cursor-pointer ${ + isCurrentPageButtonDisabled ? "bg-gray-100" : "" + }`} + onClick={() => { + setShowPaginationModel(true); + }} + > + <span>{props.currentPageNumber}</span> + </li> + <li + onClick={() => { + let currentPageNumber: number = props.currentPageNumber; - if (typeof currentPageNumber === 'string') { - currentPageNumber = - parseInt(currentPageNumber); - } + if (typeof currentPageNumber === "string") { + currentPageNumber = parseInt(currentPageNumber); + } - if (props.onNavigateToPage && !isNextDisabled) { - props.onNavigateToPage( - currentPageNumber + 1, - props.itemsOnPage - ); - } - }} - className={` inline-flex items-center rounded-r-md border border-gray-300 bg-white px-2 py-2 text-sm font-medium text-gray-500 ${ - isNextDisabled - ? 'bg-gray-100' - : ' hover:bg-gray-50 cursor-pointer' - }`} - > - <span>Next</span> - </li> - </ul> - </nav> - </div> + if (props.onNavigateToPage && !isNextDisabled) { + props.onNavigateToPage( + currentPageNumber + 1, + props.itemsOnPage, + ); + } + }} + className={` inline-flex items-center rounded-r-md border border-gray-300 bg-white px-2 py-2 text-sm font-medium text-gray-500 ${ + isNextDisabled + ? "bg-gray-100" + : " hover:bg-gray-50 cursor-pointer" + }`} + > + <span>Next</span> + </li> + </ul> + </nav> + </div> - {showPaginationModel && ( - <BasicFormModal<PaginationNavigationItem> - data-testid="pagination-modal" - title={'Navigate to Page'} - onClose={() => { - setShowPaginationModel(false); - }} - submitButtonText={'Go to Page'} - onSubmit={(item: PaginationNavigationItem) => { - if (props.onNavigateToPage) { - props.onNavigateToPage( - item.pageNumber, - item.itemsOnPage - ); - } - setShowPaginationModel(false); - }} - formProps={{ - initialValues: { - pageNumber: props.currentPageNumber, - itemsOnPage: props.itemsOnPage, - }, - fields: [ - { - title: 'Page Number', - description: `You can enter page numbers from ${ - minPageNumber !== maxPageNumber - ? minPageNumber + ' to ' + maxPageNumber - : minPageNumber - }. Please enter it here:`, - field: { - pageNumber: true, - }, - disabled: minPageNumber === maxPageNumber, - placeholder: '1', - required: true, - validation: { - minValue: minPageNumber, - maxValue: maxPageNumber, - }, - fieldType: FormFieldSchemaType.PositiveNumber, - }, - { - title: `${props.pluralLabel} on Page `, - description: `Enter the number of ${props.pluralLabel.toLowerCase()} you would like to see on the page:`, - field: { - itemsOnPage: true, - }, - placeholder: '10', - required: true, - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: [ - { - value: 10, - label: '10', - }, - { - value: 20, - label: '20', - }, - { - value: 25, - label: '25', - }, - { - value: 50, - label: '50', - }, - ], - }, - ], - }} - /> - )} - </div> - ); + {showPaginationModel && ( + <BasicFormModal<PaginationNavigationItem> + data-testid="pagination-modal" + title={"Navigate to Page"} + onClose={() => { + setShowPaginationModel(false); + }} + submitButtonText={"Go to Page"} + onSubmit={(item: PaginationNavigationItem) => { + if (props.onNavigateToPage) { + props.onNavigateToPage(item.pageNumber, item.itemsOnPage); + } + setShowPaginationModel(false); + }} + formProps={{ + initialValues: { + pageNumber: props.currentPageNumber, + itemsOnPage: props.itemsOnPage, + }, + fields: [ + { + title: "Page Number", + description: `You can enter page numbers from ${ + minPageNumber !== maxPageNumber + ? minPageNumber + " to " + maxPageNumber + : minPageNumber + }. Please enter it here:`, + field: { + pageNumber: true, + }, + disabled: minPageNumber === maxPageNumber, + placeholder: "1", + required: true, + validation: { + minValue: minPageNumber, + maxValue: maxPageNumber, + }, + fieldType: FormFieldSchemaType.PositiveNumber, + }, + { + title: `${props.pluralLabel} on Page `, + description: `Enter the number of ${props.pluralLabel.toLowerCase()} you would like to see on the page:`, + field: { + itemsOnPage: true, + }, + placeholder: "10", + required: true, + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: [ + { + value: 10, + label: "10", + }, + { + value: 20, + label: "20", + }, + { + value: 25, + label: "25", + }, + { + value: 50, + label: "50", + }, + ], + }, + ], + }} + /> + )} + </div> + ); }; export default Pagination; diff --git a/CommonUI/src/Components/Pill/Pill.tsx b/CommonUI/src/Components/Pill/Pill.tsx index de01422c85..51940b1cdc 100644 --- a/CommonUI/src/Components/Pill/Pill.tsx +++ b/CommonUI/src/Components/Pill/Pill.tsx @@ -1,70 +1,67 @@ -import { Black } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import React, { CSSProperties, FunctionComponent, ReactElement } from 'react'; +import { Black } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import React, { CSSProperties, FunctionComponent, ReactElement } from "react"; export enum PillSize { - Small = '10px', - Normal = '13px', - Large = '15px', - ExtraLarge = '18px', + Small = "10px", + Normal = "13px", + Large = "15px", + ExtraLarge = "18px", } export interface ComponentProps { - text: string; - color: Color; - size?: PillSize | undefined; - style?: CSSProperties; - isMinimal?: boolean | undefined; + text: string; + color: Color; + size?: PillSize | undefined; + style?: CSSProperties; + isMinimal?: boolean | undefined; } const Pill: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.isMinimal) { - return ( - <span className="relative inline-flex items-center rounded-full border border-gray-300 px-3 py-0.5 text-sm"> - <span className="absolute flex flex-shrink-0 items-center justify-center"> - <span - className="h-1.5 w-1.5 rounded-full bg-rose-500" - style={{ - backgroundColor: - props.style?.backgroundColor || props.color - ? props.color.toString() - : Black.toString(), - }} - aria-hidden="true" - ></span> - </span> - <span className="ml-3.5 font-medium text-gray-900"> - {props.text} - </span> - </span> - ); - } + if (props.isMinimal) { return ( - <span - data-testid="pill" - className="rounded-full p-1 pl-3 pr-3" + <span className="relative inline-flex items-center rounded-full border border-gray-300 px-3 py-0.5 text-sm"> + <span className="absolute flex flex-shrink-0 items-center justify-center"> + <span + className="h-1.5 w-1.5 rounded-full bg-rose-500" style={{ - // https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color - - color: - props.style?.color || - Color.shouldUseDarkText(props.color || Black) - ? '#000000' - : '#ffffff', - backgroundColor: - props.style?.backgroundColor || props.color - ? props.color.toString() - : Black.toString(), - fontSize: props.size ? props.size.toString() : PillSize.Normal, - ...props.style, + backgroundColor: + props.style?.backgroundColor || props.color + ? props.color.toString() + : Black.toString(), }} - > - {' '} - {props.text}{' '} + aria-hidden="true" + ></span> </span> + <span className="ml-3.5 font-medium text-gray-900">{props.text}</span> + </span> ); + } + return ( + <span + data-testid="pill" + className="rounded-full p-1 pl-3 pr-3" + style={{ + // https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color + + color: + props.style?.color || Color.shouldUseDarkText(props.color || Black) + ? "#000000" + : "#ffffff", + backgroundColor: + props.style?.backgroundColor || props.color + ? props.color.toString() + : Black.toString(), + fontSize: props.size ? props.size.toString() : PillSize.Normal, + ...props.style, + }} + > + {" "} + {props.text}{" "} + </span> + ); }; export default Pill; diff --git a/CommonUI/src/Components/Probe/Probe.tsx b/CommonUI/src/Components/Probe/Probe.tsx index 4265a5f5d6..4d2e9965fd 100644 --- a/CommonUI/src/Components/Probe/Probe.tsx +++ b/CommonUI/src/Components/Probe/Probe.tsx @@ -1,68 +1,68 @@ -import { FILE_URL } from '../../Config'; -import Icon from '../Icon/Icon'; -import Image from '../Image/Image'; -import BaseModel from 'Common/Models/BaseModel'; -import URL from 'Common/Types/API/URL'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import Probe from 'Model/Models/Probe'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { FILE_URL } from "../../Config"; +import Icon from "../Icon/Icon"; +import Image from "../Image/Image"; +import BaseModel from "Common/Models/BaseModel"; +import URL from "Common/Types/API/URL"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import Probe from "Model/Models/Probe"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - probe?: Probe | JSONObject | undefined | null; + probe?: Probe | JSONObject | undefined | null; } const ProbeElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let probe: JSONObject | null | undefined = null; + let probe: JSONObject | null | undefined = null; - if (props.probe instanceof Probe) { - probe = BaseModel.toJSONObject(props.probe, Probe); - } else { - probe = props.probe; - } - - if (!probe) { - return ( - <div className="flex"> - <div className="bold" data-testid="probe-not-found"> - No probe found. - </div> - </div> - ); - } + if (props.probe instanceof Probe) { + probe = BaseModel.toJSONObject(props.probe, Probe); + } else { + probe = props.probe; + } + if (!probe) { return ( - <div className="flex"> - <div> - {props.probe?.iconFileId && ( - <Image - className="h-6 w-6 rounded-full" - data-testid="probe-image" - imageUrl={URL.fromString(FILE_URL.toString()).addRoute( - '/image/' + props.probe?.iconFileId.toString() - )} - alt={probe['name']?.toString() || 'Probe'} - /> - )} - {!props.probe?.iconFileId && ( - <Icon - data-testid="probe-icon" - icon={IconProp.Signal} - className="text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-0.5 mt-0.5 h-6 w-6" - /> - )} - </div> - <div className="mt-1 mr-1 ml-3"> - <div> - <span data-testid="probe-name">{`${ - (probe['name']?.toString() as string) || '' - }`}</span>{' '} - </div> - </div> + <div className="flex"> + <div className="bold" data-testid="probe-not-found"> + No probe found. </div> + </div> ); + } + + return ( + <div className="flex"> + <div> + {props.probe?.iconFileId && ( + <Image + className="h-6 w-6 rounded-full" + data-testid="probe-image" + imageUrl={URL.fromString(FILE_URL.toString()).addRoute( + "/image/" + props.probe?.iconFileId.toString(), + )} + alt={probe["name"]?.toString() || "Probe"} + /> + )} + {!props.probe?.iconFileId && ( + <Icon + data-testid="probe-icon" + icon={IconProp.Signal} + className="text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-0.5 mt-0.5 h-6 w-6" + /> + )} + </div> + <div className="mt-1 mr-1 ml-3"> + <div> + <span data-testid="probe-name">{`${ + (probe["name"]?.toString() as string) || "" + }`}</span>{" "} + </div> + </div> + </div> + ); }; export default ProbeElement; diff --git a/CommonUI/src/Components/ProgressBar/ProgressBar.tsx b/CommonUI/src/Components/ProgressBar/ProgressBar.tsx index 0f9f0a008f..32b69dc09d 100644 --- a/CommonUI/src/Components/ProgressBar/ProgressBar.tsx +++ b/CommonUI/src/Components/ProgressBar/ProgressBar.tsx @@ -1,71 +1,69 @@ import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export enum ProgressBarSize { - Small = 'small', - Medium = 'medium', - Large = 'large', + Small = "small", + Medium = "medium", + Large = "large", } export interface ComponentProps { - count: number; - totalCount: number; - suffix: string; - size?: ProgressBarSize; + count: number; + totalCount: number; + suffix: string; + size?: ProgressBarSize; } const ProgressBar: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [percent, setPercent] = useState<number>(0); + const [percent, setPercent] = useState<number>(0); - useEffect(() => { - let percent: number = 0; + useEffect(() => { + let percent: number = 0; - try { - percent = (props.count * 100) / props.totalCount; - } catch (err) { - // do nothing. - } - - if (percent > 100) { - percent = 100; - } - - setPercent(Math.ceil(percent)); - }, [props.count, props.totalCount]); - - let progressBarSize: string = 'h-4'; - - if (props.size === ProgressBarSize.Small) { - progressBarSize = 'h-2'; - } else if (props.size === ProgressBarSize.Large) { - progressBarSize = 'h-6'; + try { + percent = (props.count * 100) / props.totalCount; + } catch (err) { + // do nothing. } - return ( - <div - className={`w-full ${progressBarSize} mb-4 bg-gray-200 rounded-full`} - > - <div - data-testid="progress-bar" - className={`${progressBarSize} bg-indigo-600 rounded-full `} - style={{ width: percent + '%' }} - ></div> - <div className="text-sm text-gray-400 mt-1 flex justify-between"> - <div data-testid="progress-bar-count"> - {props.count} {props.suffix} - </div> - <div data-testid="progress-bar-total-count"> - {props.totalCount} {props.suffix} - </div> - </div> + if (percent > 100) { + percent = 100; + } + + setPercent(Math.ceil(percent)); + }, [props.count, props.totalCount]); + + let progressBarSize: string = "h-4"; + + if (props.size === ProgressBarSize.Small) { + progressBarSize = "h-2"; + } else if (props.size === ProgressBarSize.Large) { + progressBarSize = "h-6"; + } + + return ( + <div className={`w-full ${progressBarSize} mb-4 bg-gray-200 rounded-full`}> + <div + data-testid="progress-bar" + className={`${progressBarSize} bg-indigo-600 rounded-full `} + style={{ width: percent + "%" }} + ></div> + <div className="text-sm text-gray-400 mt-1 flex justify-between"> + <div data-testid="progress-bar-count"> + {props.count} {props.suffix} </div> - ); + <div data-testid="progress-bar-total-count"> + {props.totalCount} {props.suffix} + </div> + </div> + </div> + ); }; export default ProgressBar; diff --git a/CommonUI/src/Components/Radio/Radio.tsx b/CommonUI/src/Components/Radio/Radio.tsx index e522c8e225..3b287d6695 100644 --- a/CommonUI/src/Components/Radio/Radio.tsx +++ b/CommonUI/src/Components/Radio/Radio.tsx @@ -1,69 +1,66 @@ -import { DropdownOption, DropdownValue } from '../Dropdown/Dropdown'; -import Text from 'Common/Types/Text'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import { DropdownOption, DropdownValue } from "../Dropdown/Dropdown"; +import Text from "Common/Types/Text"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface RadioOption extends DropdownOption {} export type RadioValue = DropdownValue; export interface ComponentProps { - options: Array<RadioOption>; - initialValue?: undefined | RadioOption; - className?: undefined | string; - onChange?: - | undefined - | ((value: RadioValue | Array<RadioValue> | null) => void); - value?: RadioOption | undefined; - onFocus?: (() => void) | undefined; - onBlur?: (() => void) | undefined; - tabIndex?: number | undefined; - error?: string | undefined; + options: Array<RadioOption>; + initialValue?: undefined | RadioOption; + className?: undefined | string; + onChange?: + | undefined + | ((value: RadioValue | Array<RadioValue> | null) => void); + value?: RadioOption | undefined; + onFocus?: (() => void) | undefined; + onBlur?: (() => void) | undefined; + tabIndex?: number | undefined; + error?: string | undefined; } const Radio: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [value, setValue] = useState<RadioOption | undefined>( - props.initialValue || props.value - ); + const [value, setValue] = useState<RadioOption | undefined>( + props.initialValue || props.value, + ); - const groupName: string = Text.generateRandomText(); + const groupName: string = Text.generateRandomText(); - return ( - <div className={`mt-2 space-y-2 ${props.className}`}> - {props.options.map((option: RadioOption, index: number) => { - return ( - <div key={index} className="flex items-center gap-x-3"> - <input - tabIndex={props.tabIndex} - defaultChecked={value?.value === option.value} - onClick={() => { - setValue(option); - props.onChange && props.onChange(option.value); - props.onBlur && props.onBlur(); - props.onFocus && props.onFocus(); - }} - name={groupName} - type="radio" - className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600" - /> - <label className="block text-sm font-medium leading-6 text-gray-900"> - {option.label} - </label> - </div> - ); - })} + return ( + <div className={`mt-2 space-y-2 ${props.className}`}> + {props.options.map((option: RadioOption, index: number) => { + return ( + <div key={index} className="flex items-center gap-x-3"> + <input + tabIndex={props.tabIndex} + defaultChecked={value?.value === option.value} + onClick={() => { + setValue(option); + props.onChange && props.onChange(option.value); + props.onBlur && props.onBlur(); + props.onFocus && props.onFocus(); + }} + name={groupName} + type="radio" + className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600" + /> + <label className="block text-sm font-medium leading-6 text-gray-900"> + {option.label} + </label> + </div> + ); + })} - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} - </div> - ); + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default Radio; diff --git a/CommonUI/src/Components/RadioButtons/BasicRadioButtons.tsx b/CommonUI/src/Components/RadioButtons/BasicRadioButtons.tsx index 0c11e47862..d8fef8ca5f 100644 --- a/CommonUI/src/Components/RadioButtons/BasicRadioButtons.tsx +++ b/CommonUI/src/Components/RadioButtons/BasicRadioButtons.tsx @@ -1,97 +1,93 @@ -import ObjectID from 'Common/Types/ObjectID'; +import ObjectID from "Common/Types/ObjectID"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface BasicRadioButtonOption { - title: string; - description?: string | undefined; - value: string; - children?: ReactElement | undefined; + title: string; + description?: string | undefined; + value: string; + children?: ReactElement | undefined; } export interface ComponentProps { - onChange: (value: string) => void; - initialValue?: string | undefined; - options: Array<BasicRadioButtonOption>; - error?: string | undefined; - id?: string | undefined; + onChange: (value: string) => void; + initialValue?: string | undefined; + options: Array<BasicRadioButtonOption>; + error?: string | undefined; + id?: string | undefined; } const BasicRadioButton: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [id] = useState<string>(props.id || ObjectID.generate().toString()); + const [id] = useState<string>(props.id || ObjectID.generate().toString()); - const [value, setValue] = useState<string>(''); + const [value, setValue] = useState<string>(""); - useEffect(() => { - if (props.initialValue) { - setValue(props.initialValue); - props.onChange(props.initialValue); - } else { - setValue(''); - props.onChange(''); - } - }, [props.initialValue]); + useEffect(() => { + if (props.initialValue) { + setValue(props.initialValue); + props.onChange(props.initialValue); + } else { + setValue(""); + props.onChange(""); + } + }, [props.initialValue]); - type HandleChangeFunction = (content: string) => void; + type HandleChangeFunction = (content: string) => void; - const handleChange: HandleChangeFunction = (content: string): void => { - setValue(content); - props.onChange(content); - }; + const handleChange: HandleChangeFunction = (content: string): void => { + setValue(content); + props.onChange(content); + }; - return ( - <div> - <fieldset id={id} className="mt-4"> - <div className="space-y-4"> - {props.options.map( - (radioButton: BasicRadioButtonOption, i: number) => { - const checked: boolean = - value === radioButton.value; + return ( + <div> + <fieldset id={id} className="mt-4"> + <div className="space-y-4"> + {props.options.map( + (radioButton: BasicRadioButtonOption, i: number) => { + const checked: boolean = value === radioButton.value; - return ( - <div key={i}> - <div className="flex items-center"> - <input - type="radio" - name={id} - checked={checked} - onClick={() => { - handleChange(radioButton.value); - }} - className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600" - /> - <label className="ml-3 block text-sm font-medium leading-6 text-gray-900"> - <span className="font-medium text-gray-900"> - {radioButton.title} - </span> - <span className="ml-1 text-gray-500 sm:ml-0"> - {radioButton.description} - </span> - </label> - </div> - {checked && radioButton.children} - </div> - ); - } - )} + return ( + <div key={i}> + <div className="flex items-center"> + <input + type="radio" + name={id} + checked={checked} + onClick={() => { + handleChange(radioButton.value); + }} + className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600" + /> + <label className="ml-3 block text-sm font-medium leading-6 text-gray-900"> + <span className="font-medium text-gray-900"> + {radioButton.title} + </span> + <span className="ml-1 text-gray-500 sm:ml-0"> + {radioButton.description} + </span> + </label> + </div> + {checked && radioButton.children} </div> - </fieldset> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} + ); + }, + )} </div> - ); + </fieldset> + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default BasicRadioButton; diff --git a/CommonUI/src/Components/RadioButtons/GroupRadioButtons.tsx b/CommonUI/src/Components/RadioButtons/GroupRadioButtons.tsx index bb70a50fe0..4c2020bd2e 100644 --- a/CommonUI/src/Components/RadioButtons/GroupRadioButtons.tsx +++ b/CommonUI/src/Components/RadioButtons/GroupRadioButtons.tsx @@ -1,135 +1,125 @@ import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import 'react-toggle/style.css'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import "react-toggle/style.css"; export interface RadioButton { - title: string; - description?: string | undefined; - value: string; - sideTitle?: string | undefined; - sideDescription?: string | undefined; + title: string; + description?: string | undefined; + value: string; + sideTitle?: string | undefined; + sideDescription?: string | undefined; } export interface ComponentProps { - onChange: (value: string) => void; - initialValue?: string | undefined; - options: Array<RadioButton>; - error?: string | undefined; - dataTestId?: string | undefined; + onChange: (value: string) => void; + initialValue?: string | undefined; + options: Array<RadioButton>; + error?: string | undefined; + dataTestId?: string | undefined; } const RadioButtons: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [value, setValue] = useState<string>(''); + const [value, setValue] = useState<string>(""); - useEffect(() => { - if (props.initialValue) { - setValue(props.initialValue); - props.onChange(props.initialValue); - } else { - setValue(''); - props.onChange(''); - } - }, [props.initialValue]); + useEffect(() => { + if (props.initialValue) { + setValue(props.initialValue); + props.onChange(props.initialValue); + } else { + setValue(""); + props.onChange(""); + } + }, [props.initialValue]); - type HandleChangeFunction = (content: string) => void; + type HandleChangeFunction = (content: string) => void; - const handleChange: HandleChangeFunction = (content: string): void => { - setValue(content); - props.onChange(content); - }; + const handleChange: HandleChangeFunction = (content: string): void => { + setValue(content); + props.onChange(content); + }; - return ( - <div> - <fieldset role="radiogroup" data-testid={props.dataTestId}> - <div className="space-y-2 mt-2"> - {props.options && - props.options.map( - (radioButton: RadioButton, i: number) => { - let className: string = - 'relative block cursor-pointer rounded-lg border bg-white px-6 py-4 shadow-sm focus:outline-none sm:flex sm:justify-between'; + return ( + <div> + <fieldset role="radiogroup" data-testid={props.dataTestId}> + <div className="space-y-2 mt-2"> + {props.options && + props.options.map((radioButton: RadioButton, i: number) => { + let className: string = + "relative block cursor-pointer rounded-lg border bg-white px-6 py-4 shadow-sm focus:outline-none sm:flex sm:justify-between"; - if (value === radioButton.value) { - className = - 'relative block cursor-pointer rounded-lg border bg-white px-6 py-4 shadow-sm focus:outline-none sm:flex sm:justify-between border-indigo-500 ring-2 ring-indigo-500'; - } + if (value === radioButton.value) { + className = + "relative block cursor-pointer rounded-lg border bg-white px-6 py-4 shadow-sm focus:outline-none sm:flex sm:justify-between border-indigo-500 ring-2 ring-indigo-500"; + } - return ( - <label - key={i} - className={className} - onClick={() => { - handleChange(radioButton.value); - }} - > - <input - type="radio" - className="sr-only" - aria-labelledby={`server-size-${i}-label`} - aria-describedby={`server-size-${i}-description-0 server-size-${i}-description-1`} - defaultChecked={ - props.initialValue === - radioButton.value - } - /> - <span className="flex items-center"> - <span className="flex flex-col text-sm"> - <span - id={`server-size-${i}-label`} - className="font-medium text-gray-900" - > - {radioButton.title} - </span> - <span - id={`server-size-${i}-description-0`} - className="text-gray-500" - > - <span className="block sm:inline"> - {' '} - { - radioButton.description - } - </span> - </span> - </span> - </span> - <span - id={`server-size-${i}-description-1`} - className="mt-2 flex text-sm sm:mt-0 sm:ml-4 sm:flex-col sm:text-right" - > - <span className="font-medium text-gray-900"> - {radioButton.sideTitle} - </span> - <span className="ml-1 text-gray-500 sm:ml-0"> - {radioButton.sideDescription} - </span> - </span> - - <span - className="pointer-events-none absolute -inset-px rounded-lg border-2" - aria-hidden="true" - ></span> - </label> - ); - } - )} - </div> - </fieldset> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" + return ( + <label + key={i} + className={className} + onClick={() => { + handleChange(radioButton.value); + }} > - {props.error} - </p> - )} + <input + type="radio" + className="sr-only" + aria-labelledby={`server-size-${i}-label`} + aria-describedby={`server-size-${i}-description-0 server-size-${i}-description-1`} + defaultChecked={props.initialValue === radioButton.value} + /> + <span className="flex items-center"> + <span className="flex flex-col text-sm"> + <span + id={`server-size-${i}-label`} + className="font-medium text-gray-900" + > + {radioButton.title} + </span> + <span + id={`server-size-${i}-description-0`} + className="text-gray-500" + > + <span className="block sm:inline"> + {" "} + {radioButton.description} + </span> + </span> + </span> + </span> + <span + id={`server-size-${i}-description-1`} + className="mt-2 flex text-sm sm:mt-0 sm:ml-4 sm:flex-col sm:text-right" + > + <span className="font-medium text-gray-900"> + {radioButton.sideTitle} + </span> + <span className="ml-1 text-gray-500 sm:ml-0"> + {radioButton.sideDescription} + </span> + </span> + + <span + className="pointer-events-none absolute -inset-px rounded-lg border-2" + aria-hidden="true" + ></span> + </label> + ); + })} </div> - ); + </fieldset> + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default RadioButtons; diff --git a/CommonUI/src/Components/ResetObjectID/ResetObjectID.tsx b/CommonUI/src/Components/ResetObjectID/ResetObjectID.tsx index 4b7b7b0ac5..fda59b4bce 100644 --- a/CommonUI/src/Components/ResetObjectID/ResetObjectID.tsx +++ b/CommonUI/src/Components/ResetObjectID/ResetObjectID.tsx @@ -1,139 +1,139 @@ -import API from '../../Utils/API/API'; -import ModelAPI from '../../Utils/ModelAPI/ModelAPI'; -import { ButtonStyleType } from '../Button/Button'; -import Card from '../Card/Card'; -import ConfirmModal from '../Modal/ConfirmModal'; -import BaseModel from 'Common/Models/BaseModel'; -import { TableColumnMetadata } from 'Common/Types/Database/TableColumn'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { ReactElement, useState } from 'react'; +import API from "../../Utils/API/API"; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import { ButtonStyleType } from "../Button/Button"; +import Card from "../Card/Card"; +import ConfirmModal from "../Modal/ConfirmModal"; +import BaseModel from "Common/Models/BaseModel"; +import { TableColumnMetadata } from "Common/Types/Database/TableColumn"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import React, { ReactElement, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - modelType: { new (): TBaseModel }; - fieldName: keyof TBaseModel; - title: string; - description: string | ReactElement; - modelId: ObjectID; - onUpdateComplete?: undefined | ((updatedValue: ObjectID) => void); + modelType: { new (): TBaseModel }; + fieldName: keyof TBaseModel; + title: string; + description: string | ReactElement; + modelId: ObjectID; + onUpdateComplete?: undefined | ((updatedValue: ObjectID) => void); } const ResetObjectID: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const model: TBaseModel = new props.modelType(); - const [showModal, setShowModal] = useState<boolean>(false); - const [isLoading, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [showErrorModal, setShowErrorModal] = useState<boolean>(false); - const [showResultModal, setShowResultModal] = useState<boolean>(false); + const model: TBaseModel = new props.modelType(); + const [showModal, setShowModal] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [showErrorModal, setShowErrorModal] = useState<boolean>(false); + const [showResultModal, setShowResultModal] = useState<boolean>(false); - const [newId, setNewId] = useState<ObjectID | null>(null); + const [newId, setNewId] = useState<ObjectID | null>(null); - const resetKey: PromiseVoidFunction = async (): Promise<void> => { - setIsLoading(true); - try { - const resetIdTo: ObjectID = ObjectID.generate(); - setNewId(resetIdTo); - await ModelAPI.updateById<TBaseModel>({ - modelType: props.modelType, - id: props.modelId, - data: { - [props.fieldName]: resetIdTo, - }, - }); - setNewId(resetIdTo); + const resetKey: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); + try { + const resetIdTo: ObjectID = ObjectID.generate(); + setNewId(resetIdTo); + await ModelAPI.updateById<TBaseModel>({ + modelType: props.modelType, + id: props.modelId, + data: { + [props.fieldName]: resetIdTo, + }, + }); + setNewId(resetIdTo); + setShowModal(false); + setShowResultModal(true); + } catch (err) { + setError(API.getFriendlyMessage(err)); + setShowErrorModal(true); + } + + setIsLoading(false); + }; + + const tableColumn: TableColumnMetadata | undefined = props.fieldName + ? model.getTableColumnMetadata(props.fieldName as string) + : undefined; + const tableColumnName: string = + tableColumn?.title || (props.fieldName as string); + + return ( + <> + <Card + title={`${props.title}`} + description={props.description} + buttons={[ + { + title: `Reset ${tableColumnName}`, + buttonStyle: ButtonStyleType.NORMAL, + onClick: () => { + setShowModal(true); + }, + isLoading: isLoading, + icon: IconProp.Reload, + }, + ]} + /> + + {showModal ? ( + <ConfirmModal + description={`Are you sure you want to reset ${tableColumnName}?`} + title={`Reset ${tableColumnName}`} + onSubmit={async () => { + await resetKey(); + }} + isLoading={isLoading} + onClose={() => { setShowModal(false); - setShowResultModal(true); - } catch (err) { - setError(API.getFriendlyMessage(err)); - setShowErrorModal(true); - } + }} + submitButtonText={`Reset`} + submitButtonType={ButtonStyleType.DANGER} + /> + ) : ( + <></> + )} - setIsLoading(false); - }; + {showErrorModal ? ( + <ConfirmModal + description={error} + title={`Reset Error`} + onSubmit={() => { + setShowErrorModal(false); + setError(""); + }} + submitButtonText={`Close`} + submitButtonType={ButtonStyleType.NORMAL} + /> + ) : ( + <></> + )} - const tableColumn: TableColumnMetadata | undefined = props.fieldName - ? model.getTableColumnMetadata(props.fieldName as string) - : undefined; - const tableColumnName: string = - tableColumn?.title || (props.fieldName as string); - - return ( - <> - <Card - title={`${props.title}`} - description={props.description} - buttons={[ - { - title: `Reset ${tableColumnName}`, - buttonStyle: ButtonStyleType.NORMAL, - onClick: () => { - setShowModal(true); - }, - isLoading: isLoading, - icon: IconProp.Reload, - }, - ]} - /> - - {showModal ? ( - <ConfirmModal - description={`Are you sure you want to reset ${tableColumnName}?`} - title={`Reset ${tableColumnName}`} - onSubmit={async () => { - await resetKey(); - }} - isLoading={isLoading} - onClose={() => { - setShowModal(false); - }} - submitButtonText={`Reset`} - submitButtonType={ButtonStyleType.DANGER} - /> - ) : ( - <></> - )} - - {showErrorModal ? ( - <ConfirmModal - description={error} - title={`Reset Error`} - onSubmit={() => { - setShowErrorModal(false); - setError(''); - }} - submitButtonText={`Close`} - submitButtonType={ButtonStyleType.NORMAL} - /> - ) : ( - <></> - )} - - {showResultModal ? ( - <ConfirmModal - description={`Your new ${tableColumnName} is ${ - newId?.toString() || '' - }`} - title={`New ${tableColumnName}`} - onSubmit={() => { - if (props.onUpdateComplete && newId) { - props.onUpdateComplete(newId); - } - setShowResultModal(false); - setError(''); - }} - submitButtonText={`Close`} - submitButtonType={ButtonStyleType.NORMAL} - /> - ) : ( - <></> - )} - </> - ); + {showResultModal ? ( + <ConfirmModal + description={`Your new ${tableColumnName} is ${ + newId?.toString() || "" + }`} + title={`New ${tableColumnName}`} + onSubmit={() => { + if (props.onUpdateComplete && newId) { + props.onUpdateComplete(newId); + } + setShowResultModal(false); + setError(""); + }} + submitButtonText={`Close`} + submitButtonType={ButtonStyleType.NORMAL} + /> + ) : ( + <></> + )} + </> + ); }; export default ResetObjectID; diff --git a/CommonUI/src/Components/ShortcutKey/Shortcut.tsx b/CommonUI/src/Components/ShortcutKey/Shortcut.tsx index 188f1ce8e2..0e4fbe817a 100644 --- a/CommonUI/src/Components/ShortcutKey/Shortcut.tsx +++ b/CommonUI/src/Components/ShortcutKey/Shortcut.tsx @@ -1,20 +1,20 @@ -import Char from 'Common/Types/Char'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Char from "Common/Types/Char"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - shortcuts: Array<Char>; + shortcuts: Array<Char>; } const Shortcut: FunctionComponent<ComponentProps> = ({ - shortcuts, + shortcuts, }: ComponentProps): ReactElement => { - return ( - <div className="shortcut"> - {shortcuts.map((shortcut: Char, index: number) => { - return <code key={index}>{shortcut}</code>; - })} - </div> - ); + return ( + <div className="shortcut"> + {shortcuts.map((shortcut: Char, index: number) => { + return <code key={index}>{shortcut}</code>; + })} + </div> + ); }; export default Shortcut; diff --git a/CommonUI/src/Components/ShortcutKey/ShortcutKey.ts b/CommonUI/src/Components/ShortcutKey/ShortcutKey.ts index 461aa6195d..86149709bf 100644 --- a/CommonUI/src/Components/ShortcutKey/ShortcutKey.ts +++ b/CommonUI/src/Components/ShortcutKey/ShortcutKey.ts @@ -1,8 +1,8 @@ enum ShortcutKey { - Enter = 'Enter', - Esc = 'Esc', - New = 'N', - Settings = 'S', + Enter = "Enter", + Esc = "Esc", + New = "N", + Settings = "S", } export default ShortcutKey; diff --git a/CommonUI/src/Components/SideMenu/CountModelSideMenuItem.tsx b/CommonUI/src/Components/SideMenu/CountModelSideMenuItem.tsx index 3947a85582..c4f0182e7a 100644 --- a/CommonUI/src/Components/SideMenu/CountModelSideMenuItem.tsx +++ b/CommonUI/src/Components/SideMenu/CountModelSideMenuItem.tsx @@ -1,81 +1,81 @@ -import API from '../../Utils/API/API'; -import Query from '../../Utils/BaseDatabase/Query'; -import ModelAPI, { RequestOptions } from '../../Utils/ModelAPI/ModelAPI'; -import { BadgeType } from '../Badge/Badge'; -import SideMenuItem from './SideMenuItem'; -import BaseModel from 'Common/Models/BaseModel'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Link from 'Common/Types/Link'; -import React, { ReactElement, useEffect, useState } from 'react'; +import API from "../../Utils/API/API"; +import Query from "../../Utils/BaseDatabase/Query"; +import ModelAPI, { RequestOptions } from "../../Utils/ModelAPI/ModelAPI"; +import { BadgeType } from "../Badge/Badge"; +import SideMenuItem from "./SideMenuItem"; +import BaseModel from "Common/Models/BaseModel"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import Link from "Common/Types/Link"; +import React, { ReactElement, useEffect, useState } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - link: Link; - modelType?: { new (): TBaseModel } | undefined; - badgeType?: BadgeType | undefined; - countQuery?: Query<TBaseModel> | undefined; - requestOptions?: RequestOptions | undefined; - icon?: undefined | IconProp; - className?: undefined | string; - onCountFetchInit?: (() => void) | undefined; + link: Link; + modelType?: { new (): TBaseModel } | undefined; + badgeType?: BadgeType | undefined; + countQuery?: Query<TBaseModel> | undefined; + requestOptions?: RequestOptions | undefined; + icon?: undefined | IconProp; + className?: undefined | string; + onCountFetchInit?: (() => void) | undefined; } const CountModelSideMenuItem: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); - const [count, setCount] = useState<number>(0); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [count, setCount] = useState<number>(0); - const fetchCount: PromiseVoidFunction = async (): Promise<void> => { - if (!props.modelType) { - return; - } + const fetchCount: PromiseVoidFunction = async (): Promise<void> => { + if (!props.modelType) { + return; + } - if (!props.countQuery) { - return; - } + if (!props.countQuery) { + return; + } - setError(''); - setIsLoading(true); + setError(""); + setIsLoading(true); - if (props.onCountFetchInit) { - props.onCountFetchInit(); - } + if (props.onCountFetchInit) { + props.onCountFetchInit(); + } - try { - const count: number = await ModelAPI.count<BaseModel>({ - modelType: props.modelType, - query: props.countQuery, - requestOptions: props.requestOptions, - }); + try { + const count: number = await ModelAPI.count<BaseModel>({ + modelType: props.modelType, + query: props.countQuery, + requestOptions: props.requestOptions, + }); - setCount(count); - setError(''); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + setCount(count); + setError(""); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - setIsLoading(false); - }; + setIsLoading(false); + }; - useEffect(() => { - fetchCount().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); - }); - }, [props.countQuery]); + useEffect(() => { + fetchCount().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, [props.countQuery]); - return ( - <SideMenuItem - link={props.link} - badge={!isLoading && !error ? count : undefined} - badgeType={props.badgeType} - icon={props.icon} - className={props.className} - /> - ); + return ( + <SideMenuItem + link={props.link} + badge={!isLoading && !error ? count : undefined} + badgeType={props.badgeType} + icon={props.icon} + className={props.className} + /> + ); }; export default CountModelSideMenuItem; diff --git a/CommonUI/src/Components/SideMenu/SideMenu.tsx b/CommonUI/src/Components/SideMenu/SideMenu.tsx index 994cb236e9..6316cfa171 100644 --- a/CommonUI/src/Components/SideMenu/SideMenu.tsx +++ b/CommonUI/src/Components/SideMenu/SideMenu.tsx @@ -1,26 +1,26 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; + children: ReactElement | Array<ReactElement>; } const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => { - let children: Array<ReactElement> = []; - if (!Array.isArray(props.children)) { - children = [props.children]; - } else { - children = props.children; - } + let children: Array<ReactElement> = []; + if (!Array.isArray(props.children)) { + children = [props.children]; + } else { + children = props.children; + } - return ( - <aside className="py-6 px-2 sm:px-6 lg:col-span-2 md:col-span-3 lg:py-0 lg:px-0 mb-10"> - <nav className="space-y-3"> - {children.map((child: ReactElement) => { - return child; - })} - </nav> - </aside> - ); + return ( + <aside className="py-6 px-2 sm:px-6 lg:col-span-2 md:col-span-3 lg:py-0 lg:px-0 mb-10"> + <nav className="space-y-3"> + {children.map((child: ReactElement) => { + return child; + })} + </nav> + </aside> + ); }; export default SideMenu; diff --git a/CommonUI/src/Components/SideMenu/SideMenuItem.tsx b/CommonUI/src/Components/SideMenu/SideMenuItem.tsx index 194a421a5a..a6310f9263 100644 --- a/CommonUI/src/Components/SideMenu/SideMenuItem.tsx +++ b/CommonUI/src/Components/SideMenu/SideMenuItem.tsx @@ -1,202 +1,194 @@ -import Navigation from '../../Utils/Navigation'; -import Badge, { BadgeType } from '../Badge/Badge'; -import Icon from '../Icon/Icon'; -import UILink from '../Link/Link'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Link from 'Common/Types/Link'; -import React, { FunctionComponent } from 'react'; +import Navigation from "../../Utils/Navigation"; +import Badge, { BadgeType } from "../Badge/Badge"; +import Icon from "../Icon/Icon"; +import UILink from "../Link/Link"; +import IconProp from "Common/Types/Icon/IconProp"; +import Link from "Common/Types/Link"; +import React, { FunctionComponent } from "react"; export interface ComponentProps { - link: Link; - showAlert?: undefined | boolean; - showWarning?: undefined | boolean; - badge?: undefined | number; - badgeType?: BadgeType | undefined; - icon?: undefined | IconProp; - className?: undefined | string; - subItemLink?: undefined | Link; - subItemIcon?: undefined | IconProp; + link: Link; + showAlert?: undefined | boolean; + showWarning?: undefined | boolean; + badge?: undefined | number; + badgeType?: BadgeType | undefined; + icon?: undefined | IconProp; + className?: undefined | string; + subItemLink?: undefined | Link; + subItemIcon?: undefined | IconProp; } const SideMenuItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ) => { - const badgeClasName: string = `p-1 rounded-full pr-3 pl-3 text-white text-sm`; + const badgeClasName: string = `p-1 rounded-full pr-3 pl-3 text-white text-sm`; - let linkClassName: string = `text-gray-500 hover:text-gray-900 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + let linkClassName: string = `text-gray-500 hover:text-gray-900 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - if (Navigation.isOnThisPage(props.link.to)) { - linkClassName = `bg-gray-100 text-indigo-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - } + if (Navigation.isOnThisPage(props.link.to)) { + linkClassName = `bg-gray-100 text-indigo-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + } - let subItemLinkClassName: string = `text-gray-500 hover:text-gray-900 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + let subItemLinkClassName: string = `text-gray-500 hover:text-gray-900 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - if (props.subItemLink && Navigation.isOnThisPage(props.subItemLink.to)) { - subItemLinkClassName = `bg-gray-100 text-indigo-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - } + if (props.subItemLink && Navigation.isOnThisPage(props.subItemLink.to)) { + subItemLinkClassName = `bg-gray-100 text-indigo-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + } - // if(props.badge && props.badge > 0){ - // if(props.badgeType === BadgeType.DANGER){ - // linkClassName = `text-red-400 hover:text-red-600 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + // if(props.badge && props.badge > 0){ + // if(props.badgeType === BadgeType.DANGER){ + // linkClassName = `text-red-400 hover:text-red-600 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - // if(Navigation.isOnThisPage(props.link.to)){ - // linkClassName = `bg-gray-100 text-red-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - // } + // if(Navigation.isOnThisPage(props.link.to)){ + // linkClassName = `bg-gray-100 text-red-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + // } - // } + // } - // if(props.badgeType === BadgeType.WARNING){ - // linkClassName = `text-yellow-400 hover:text-yellow-600 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + // if(props.badgeType === BadgeType.WARNING){ + // linkClassName = `text-yellow-400 hover:text-yellow-600 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - // if(Navigation.isOnThisPage(props.link.to)){ - // linkClassName = `bg-gray-100 text-yellow-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - // } - // } + // if(Navigation.isOnThisPage(props.link.to)){ + // linkClassName = `bg-gray-100 text-yellow-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + // } + // } - // if(props.badgeType === BadgeType.SUCCESS){ - // linkClassName = `text-emerald-400 hover:text-emerald-600 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + // if(props.badgeType === BadgeType.SUCCESS){ + // linkClassName = `text-emerald-400 hover:text-emerald-600 hover:bg-gray-100 group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - // if(Navigation.isOnThisPage(props.link.to)){ - // linkClassName = `bg-gray-100 text-emerald-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; - // } - // } - // } + // if(Navigation.isOnThisPage(props.link.to)){ + // linkClassName = `bg-gray-100 text-emerald-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium`; + // } + // } + // } - let iconClassName: string = - 'text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6'; + let iconClassName: string = + "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"; - if (Navigation.isOnThisPage(props.link.to)) { - iconClassName = 'text-indigo-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6'; - } + if (Navigation.isOnThisPage(props.link.to)) { + iconClassName = "text-indigo-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"; + } - let subItemIconClassName: string = - 'text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6'; + let subItemIconClassName: string = + "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"; - if (props.subItemLink && Navigation.isOnThisPage(props.subItemLink.to)) { - subItemIconClassName = - 'text-indigo-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6'; - } + if (props.subItemLink && Navigation.isOnThisPage(props.subItemLink.to)) { + subItemIconClassName = "text-indigo-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"; + } - // if(props.badge && props.badge > 0){ - // if(props.badgeType === BadgeType.DANGER){ - // iconClassName = `text-red-400 group-hover:text-red-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; + // if(props.badge && props.badge > 0){ + // if(props.badgeType === BadgeType.DANGER){ + // iconClassName = `text-red-400 group-hover:text-red-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; - // if(Navigation.isOnThisPage(props.link.to)){ - // iconClassName = `text-red-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; - // } + // if(Navigation.isOnThisPage(props.link.to)){ + // iconClassName = `text-red-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; + // } - // } + // } - // if(props.badgeType === BadgeType.WARNING){ - // iconClassName = `text-yellow-400 group-hover:text-yellow-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; + // if(props.badgeType === BadgeType.WARNING){ + // iconClassName = `text-yellow-400 group-hover:text-yellow-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; - // if(Navigation.isOnThisPage(props.link.to)){ - // iconClassName = `text-yellow-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; - // } - // } + // if(Navigation.isOnThisPage(props.link.to)){ + // iconClassName = `text-yellow-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; + // } + // } - // if(props.badgeType === BadgeType.SUCCESS){ - // iconClassName = `text-emerald-400 group-hover:text-emerald-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; + // if(props.badgeType === BadgeType.SUCCESS){ + // iconClassName = `text-emerald-400 group-hover:text-emerald-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; - // if(Navigation.isOnThisPage(props.link.to)){ - // iconClassName = `text-emerald-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; - // } - // } - // } + // if(Navigation.isOnThisPage(props.link.to)){ + // iconClassName = `text-emerald-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6`; + // } + // } + // } - return ( - <> - <UILink - className={`${ - props.className ? props.className : '' - } ${linkClassName} flex justify-between`} - to={props.link.to} - onClick={() => { - window.scrollTo({ top: 0, behavior: 'smooth' }); - }} - > - <div className="flex"> - {props.icon ? ( - <> - <Icon className={iconClassName} icon={props.icon} /> - </> - ) : ( - <></> - )} + return ( + <> + <UILink + className={`${ + props.className ? props.className : "" + } ${linkClassName} flex justify-between`} + to={props.link.to} + onClick={() => { + window.scrollTo({ top: 0, behavior: "smooth" }); + }} + > + <div className="flex"> + {props.icon ? ( + <> + <Icon className={iconClassName} icon={props.icon} /> + </> + ) : ( + <></> + )} - <span className="truncate mt-1">{props.link.title}</span> - </div> + <span className="truncate mt-1">{props.link.title}</span> + </div> - {props.badge || props.showAlert || props.showWarning ? ( - <div className={badgeClasName}> - {props.badge ? ( - <Badge - badgeCount={props.badge} - badgeType={props.badgeType} - /> - ) : ( - <></> - )} - - {props.showAlert ? ( - <> - <Icon - className="float-end text-red-900" - icon={IconProp.Error} - /> - </> - ) : ( - <></> - )} - {props.showWarning ? ( - <> - <Icon - className="float-end text-yellow-900" - icon={IconProp.Alert} - /> - </> - ) : ( - <></> - )} - </div> - ) : ( - <></> - )} - </UILink> - {props.subItemLink ? ( - <UILink - className={`${ - props.className ? props.className : '' - } ${subItemLinkClassName} flex justify-between`} - to={props.subItemLink.to} - onClick={() => { - window.scrollTo({ top: 0, behavior: 'smooth' }); - }} - > - <div className="ml-8 flex"> - {props.icon ? ( - <> - <Icon - className={subItemIconClassName} - icon={ - props.subItemIcon || IconProp.MinusSmall - } - /> - </> - ) : ( - <></> - )} - - <span className="truncate mt-1"> - {props.subItemLink.title} - </span> - </div> - </UILink> + {props.badge || props.showAlert || props.showWarning ? ( + <div className={badgeClasName}> + {props.badge ? ( + <Badge badgeCount={props.badge} badgeType={props.badgeType} /> ) : ( - <> </> + <></> )} - </> - ); + + {props.showAlert ? ( + <> + <Icon + className="float-end text-red-900" + icon={IconProp.Error} + /> + </> + ) : ( + <></> + )} + {props.showWarning ? ( + <> + <Icon + className="float-end text-yellow-900" + icon={IconProp.Alert} + /> + </> + ) : ( + <></> + )} + </div> + ) : ( + <></> + )} + </UILink> + {props.subItemLink ? ( + <UILink + className={`${ + props.className ? props.className : "" + } ${subItemLinkClassName} flex justify-between`} + to={props.subItemLink.to} + onClick={() => { + window.scrollTo({ top: 0, behavior: "smooth" }); + }} + > + <div className="ml-8 flex"> + {props.icon ? ( + <> + <Icon + className={subItemIconClassName} + icon={props.subItemIcon || IconProp.MinusSmall} + /> + </> + ) : ( + <></> + )} + + <span className="truncate mt-1">{props.subItemLink.title}</span> + </div> + </UILink> + ) : ( + <> </> + )} + </> + ); }; export default SideMenuItem; diff --git a/CommonUI/src/Components/SideMenu/SideMenuSection.tsx b/CommonUI/src/Components/SideMenu/SideMenuSection.tsx index b7f34c10cf..94714363c0 100644 --- a/CommonUI/src/Components/SideMenu/SideMenuSection.tsx +++ b/CommonUI/src/Components/SideMenu/SideMenuSection.tsx @@ -1,19 +1,19 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; - children: ReactElement | Array<ReactElement>; + title: string; + children: ReactElement | Array<ReactElement>; } const SideMenuItem: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ) => { - return ( - <div> - <h6 className="text-sm text-gray-500">{props.title}</h6> - <div>{props.children}</div> - </div> - ); + return ( + <div> + <h6 className="text-sm text-gray-500">{props.title}</h6> + <div>{props.children}</div> + </div> + ); }; export default SideMenuItem; diff --git a/CommonUI/src/Components/SideOver/SideOver.tsx b/CommonUI/src/Components/SideOver/SideOver.tsx index 5d6efd0fc5..d5e7df3607 100644 --- a/CommonUI/src/Components/SideOver/SideOver.tsx +++ b/CommonUI/src/Components/SideOver/SideOver.tsx @@ -1,132 +1,118 @@ -import Button, { ButtonStyleType } from '../Button/Button'; -import Icon from '../Icon/Icon'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Button, { ButtonStyleType } from "../Button/Button"; +import Icon from "../Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { FunctionComponent, ReactElement } from "react"; export enum SideOverSize { - Small = 'Small', - Medium = 'Medium', - Large = 'Large', + Small = "Small", + Medium = "Medium", + Large = "Large", } export interface ComponentProps { - title: string; - description: string; - onClose: () => void; - onSubmit?: (() => void) | undefined; - children: ReactElement | Array<ReactElement>; - submitButtonDisabled?: boolean | undefined; - submitButtonText?: string | undefined; - leftFooterElement?: ReactElement | undefined; - size?: SideOverSize | undefined; + title: string; + description: string; + onClose: () => void; + onSubmit?: (() => void) | undefined; + children: ReactElement | Array<ReactElement>; + submitButtonDisabled?: boolean | undefined; + submitButtonText?: string | undefined; + leftFooterElement?: ReactElement | undefined; + size?: SideOverSize | undefined; } const SideOver: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let widthClass: string = 'max-w-2xl'; + let widthClass: string = "max-w-2xl"; - if (props.size === SideOverSize.Small) { - widthClass = 'max-w-2xl'; - } else if (props.size === SideOverSize.Medium) { - widthClass = 'max-w-5xl'; - } else if (props.size === SideOverSize.Large) { - widthClass = 'max-w-7xl'; - } + if (props.size === SideOverSize.Small) { + widthClass = "max-w-2xl"; + } else if (props.size === SideOverSize.Medium) { + widthClass = "max-w-5xl"; + } else if (props.size === SideOverSize.Large) { + widthClass = "max-w-7xl"; + } - return ( - <div - className="relative z-10" - aria-labelledby="slide-over-title" - role="dialog" - aria-modal="true" - > - <div className="fixed inset-0"></div> + return ( + <div + className="relative z-10" + aria-labelledby="slide-over-title" + role="dialog" + aria-modal="true" + > + <div className="fixed inset-0"></div> - <div className="fixed inset-0 overflow-hidden"> - <div className="absolute inset-0 overflow-hidden"> - <div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16"> - <div - className={`pointer-events-auto w-screen ${widthClass}`} - > - <div className="flex h-full flex-col bg-white shadow-xl"> - <div className="flex-shrink-0 flex flex-col bg-gray-50 px-4 py-6 sm:px-6"> - <div className="flex items-start justify-between space-x-3"> - <div className="space-y-1"> - <h2 - className="text-lg font-medium text-gray-900" - id="slide-over-title" - > - {props.title} - </h2> - <p className="text-sm text-gray-500"> - {props.description} - </p> - </div> - <div className="flex h-7 items-center"> - <button - onClick={() => { - props.onClose(); - }} - type="button" - className="text-gray-400 hover:text-gray-500" - > - <span className="sr-only"> - Close panel - </span> - - <Icon - className="h-6 w-6" - icon={IconProp.Close} - /> - </button> - </div> - </div> - </div> - <div className="h-full overflow-y-scroll"> - <div className="space-y-6 py-6 sm:space-y-0 sm:divide-y sm:divide-gray-200 sm:py-0 p-5"> - {props.children} - </div> - </div> - <div className="flex-shrink-0 border-t border-gray-200 px-4 py-5 sm:px-6 flex justify-between"> - <div className="flex justify-start space-x-3"> - {props.leftFooterElement} - </div> - <div className="flex justify-end space-x-3"> - <Button - title="Close" - onClick={() => { - props.onClose(); - }} - buttonStyle={ButtonStyleType.NORMAL} - /> - - {props.onSubmit && ( - <Button - title={ - props.submitButtonText || - 'Save' - } - disabled={ - props.submitButtonDisabled - } - onClick={() => { - props.onSubmit!(); - }} - buttonStyle={ - ButtonStyleType.PRIMARY - } - /> - )} - </div> - </div> - </div> - </div> + <div className="fixed inset-0 overflow-hidden"> + <div className="absolute inset-0 overflow-hidden"> + <div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16"> + <div className={`pointer-events-auto w-screen ${widthClass}`}> + <div className="flex h-full flex-col bg-white shadow-xl"> + <div className="flex-shrink-0 flex flex-col bg-gray-50 px-4 py-6 sm:px-6"> + <div className="flex items-start justify-between space-x-3"> + <div className="space-y-1"> + <h2 + className="text-lg font-medium text-gray-900" + id="slide-over-title" + > + {props.title} + </h2> + <p className="text-sm text-gray-500"> + {props.description} + </p> </div> + <div className="flex h-7 items-center"> + <button + onClick={() => { + props.onClose(); + }} + type="button" + className="text-gray-400 hover:text-gray-500" + > + <span className="sr-only">Close panel</span> + + <Icon className="h-6 w-6" icon={IconProp.Close} /> + </button> + </div> + </div> </div> + <div className="h-full overflow-y-scroll"> + <div className="space-y-6 py-6 sm:space-y-0 sm:divide-y sm:divide-gray-200 sm:py-0 p-5"> + {props.children} + </div> + </div> + <div className="flex-shrink-0 border-t border-gray-200 px-4 py-5 sm:px-6 flex justify-between"> + <div className="flex justify-start space-x-3"> + {props.leftFooterElement} + </div> + <div className="flex justify-end space-x-3"> + <Button + title="Close" + onClick={() => { + props.onClose(); + }} + buttonStyle={ButtonStyleType.NORMAL} + /> + + {props.onSubmit && ( + <Button + title={props.submitButtonText || "Save"} + disabled={props.submitButtonDisabled} + onClick={() => { + props.onSubmit!(); + }} + buttonStyle={ButtonStyleType.PRIMARY} + /> + )} + </div> + </div> + </div> </div> + </div> </div> - ); + </div> + </div> + ); }; export default SideOver; diff --git a/CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer.tsx b/CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer.tsx index 1ee1a3d082..8ad16caff8 100644 --- a/CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer.tsx +++ b/CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer.tsx @@ -1,17 +1,17 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | string | Array<ReactElement>; + children: ReactElement | string | Array<ReactElement>; } const SimpleLogViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="text-gray-500 mt-5 text-sm h-96 overflow-y-auto overflow-x-hidden p-5 border-gray-50 border border-2 bg-gray-100 rounded"> - {props.children} - </div> - ); + return ( + <div className="text-gray-500 mt-5 text-sm h-96 overflow-y-auto overflow-x-hidden p-5 border-gray-50 border border-2 bg-gray-100 rounded"> + {props.children} + </div> + ); }; export default SimpleLogViewer; diff --git a/CommonUI/src/Components/StatusBubble/StatusBubble.tsx b/CommonUI/src/Components/StatusBubble/StatusBubble.tsx index 9a610bff8f..dbe1c40db5 100644 --- a/CommonUI/src/Components/StatusBubble/StatusBubble.tsx +++ b/CommonUI/src/Components/StatusBubble/StatusBubble.tsx @@ -1,55 +1,51 @@ -import { Black } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import React, { CSSProperties, FunctionComponent, ReactElement } from 'react'; +import { Black } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import React, { CSSProperties, FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - text: string; - color: Color; - style?: CSSProperties; - shouldAnimate: boolean; + text: string; + color: Color; + style?: CSSProperties; + shouldAnimate: boolean; } const Statusbubble: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const backgroundColor: string = props.color - ? props.color.toString() - : Black.toString(); + const backgroundColor: string = props.color + ? props.color.toString() + : Black.toString(); - return ( - <div className="flex" style={props.style}> - <div className="-mr-2 ml-5"> - <span className="relative -left-1 -translate-x-full top-1/2 -translate-y-1/2 flex h-3.5 w-3.5"> - <span - className={`${ - props.shouldAnimate - ? 'motion-safe:animate-ping' - : '' - } absolute inline-flex h-full w-full rounded-full`} - style={{ - backgroundColor: backgroundColor, - }} - ></span> - <span - className="relative inline-flex rounded-full h-3.5 w-3.5" - style={{ - backgroundColor: backgroundColor, - }} - ></span> - </span> - </div> - <div - className="text-sm font-medium" - style={{ - color: props.color - ? props.color.toString() - : Black.toString(), - }} - > - {props.text} - </div> - </div> - ); + return ( + <div className="flex" style={props.style}> + <div className="-mr-2 ml-5"> + <span className="relative -left-1 -translate-x-full top-1/2 -translate-y-1/2 flex h-3.5 w-3.5"> + <span + className={`${ + props.shouldAnimate ? "motion-safe:animate-ping" : "" + } absolute inline-flex h-full w-full rounded-full`} + style={{ + backgroundColor: backgroundColor, + }} + ></span> + <span + className="relative inline-flex rounded-full h-3.5 w-3.5" + style={{ + backgroundColor: backgroundColor, + }} + ></span> + </span> + </div> + <div + className="text-sm font-medium" + style={{ + color: props.color ? props.color.toString() : Black.toString(), + }} + > + {props.text} + </div> + </div> + ); }; export default Statusbubble; diff --git a/CommonUI/src/Components/Table/Table.tsx b/CommonUI/src/Components/Table/Table.tsx index 7fc5b03c4c..ac97d9af2e 100644 --- a/CommonUI/src/Components/Table/Table.tsx +++ b/CommonUI/src/Components/Table/Table.tsx @@ -1,306 +1,292 @@ -import { GetReactElementFunction } from '../../Types/FunctionTypes'; -import ActionButtonSchema from '../ActionButton/ActionButtonSchema'; +import { GetReactElementFunction } from "../../Types/FunctionTypes"; +import ActionButtonSchema from "../ActionButton/ActionButtonSchema"; import BulkUpdateForm, { - BulkActionButtonSchema, -} from '../BulkUpdate/BulkUpdateForm'; -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import FilterViewer from '../Filters/FilterViewer'; -import Filter from '../Filters/Types/Filter'; -import FilterData from '../Filters/Types/FilterData'; -import Pagination from '../Pagination/Pagination'; -import TableBody from './TableBody'; -import TableHeader from './TableHeader'; -import Columns from './Types/Columns'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement, useEffect, useState } from 'react'; -import { DragDropContext, DropResult } from 'react-beautiful-dnd'; + BulkActionButtonSchema, +} from "../BulkUpdate/BulkUpdateForm"; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import FilterViewer from "../Filters/FilterViewer"; +import Filter from "../Filters/Types/Filter"; +import FilterData from "../Filters/Types/FilterData"; +import Pagination from "../Pagination/Pagination"; +import TableBody from "./TableBody"; +import TableHeader from "./TableHeader"; +import Columns from "./Types/Columns"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement, useEffect, useState } from "react"; +import { DragDropContext, DropResult } from "react-beautiful-dnd"; export interface BulkActionProps<T extends GenericObject> { - buttons: Array<BulkActionButtonSchema<T>>; + buttons: Array<BulkActionButtonSchema<T>>; } export interface ComponentProps<T extends GenericObject> { - data: Array<T>; - id: string; - columns: Columns<T>; + data: Array<T>; + id: string; + columns: Columns<T>; - disablePagination?: undefined | boolean; - onNavigateToPage: (pageNumber: number, itemsOnPage: number) => void; - currentPageNumber: number; - totalItemsCount: number; - itemsOnPage: number; - error: string; - isLoading: boolean; - singularLabel: string; - pluralLabel: string; - actionButtons?: undefined | Array<ActionButtonSchema<T>>; - onRefreshClick?: undefined | (() => void); + disablePagination?: undefined | boolean; + onNavigateToPage: (pageNumber: number, itemsOnPage: number) => void; + currentPageNumber: number; + totalItemsCount: number; + itemsOnPage: number; + error: string; + isLoading: boolean; + singularLabel: string; + pluralLabel: string; + actionButtons?: undefined | Array<ActionButtonSchema<T>>; + onRefreshClick?: undefined | (() => void); - noItemsMessage?: undefined | string | ReactElement; - onSortChanged: (sortBy: keyof T | null, sortOrder: SortOrder) => void; + noItemsMessage?: undefined | string | ReactElement; + onSortChanged: (sortBy: keyof T | null, sortOrder: SortOrder) => void; - isFilterLoading?: undefined | boolean; - filters?: Array<Filter<T>>; - showFilterModal?: undefined | boolean; - filterError?: string | undefined; - onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); - onFilterRefreshClick?: undefined | (() => void); - onFilterModalClose: () => void; - onFilterModalOpen: () => void; + isFilterLoading?: undefined | boolean; + filters?: Array<Filter<T>>; + showFilterModal?: undefined | boolean; + filterError?: string | undefined; + onFilterChanged?: undefined | ((filterData: FilterData<T>) => void); + onFilterRefreshClick?: undefined | (() => void); + onFilterModalClose: () => void; + onFilterModalOpen: () => void; - enableDragAndDrop?: boolean | undefined; - dragDropIndexField?: keyof T | undefined; - dragDropIdField?: keyof T | undefined; - onDragDrop?: ((id: string, newIndex: number) => void) | undefined; + enableDragAndDrop?: boolean | undefined; + dragDropIndexField?: keyof T | undefined; + dragDropIdField?: keyof T | undefined; + onDragDrop?: ((id: string, newIndex: number) => void) | undefined; - // bulk actions - bulkActions: BulkActionProps<T>; - bulkSelectedItems: Array<T>; - onBulkSelectedItemAdded: (item: T) => void; - onBulkSelectedItemRemoved: (item: T) => void; - onBulkSelectAllItems: () => void; - onBulkSelectItemsOnCurrentPage: () => void; - onBulkClearAllItems: () => void; - matchBulkSelectedItemByField: keyof T; // which field to use to match selected items. For exmaple this could be '_id' - onBulkActionEnd: () => void; - onBulkActionStart: () => void; - bulkItemToString: (item: T) => string; + // bulk actions + bulkActions: BulkActionProps<T>; + bulkSelectedItems: Array<T>; + onBulkSelectedItemAdded: (item: T) => void; + onBulkSelectedItemRemoved: (item: T) => void; + onBulkSelectAllItems: () => void; + onBulkSelectItemsOnCurrentPage: () => void; + onBulkClearAllItems: () => void; + matchBulkSelectedItemByField: keyof T; // which field to use to match selected items. For exmaple this could be '_id' + onBulkActionEnd: () => void; + onBulkActionStart: () => void; + bulkItemToString: (item: T) => string; } type TableFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const Table: TableFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const isBulkActionsEnabled: boolean = - props.bulkActions && - props.bulkActions.buttons && - props.bulkActions.buttons.length > 0; + const isBulkActionsEnabled: boolean = + props.bulkActions && + props.bulkActions.buttons && + props.bulkActions.buttons.length > 0; - const [isAllItemsSelected, setIsAllItemsSelected] = - useState<boolean>(false); - const [bulkSelectedItems, setBulkSelectedItems] = useState<Array<T>>([]); + const [isAllItemsSelected, setIsAllItemsSelected] = useState<boolean>(false); + const [bulkSelectedItems, setBulkSelectedItems] = useState<Array<T>>([]); - useEffect(() => { - setBulkSelectedItems(props.bulkSelectedItems); - }, [props.bulkSelectedItems]); + useEffect(() => { + setBulkSelectedItems(props.bulkSelectedItems); + }, [props.bulkSelectedItems]); - let colspan: number = props.columns.length || 0; - if (props.actionButtons && props.actionButtons?.length > 0) { - colspan++; + let colspan: number = props.columns.length || 0; + if (props.actionButtons && props.actionButtons?.length > 0) { + colspan++; + } + + const getTablebody: GetReactElementFunction = (): ReactElement => { + if (props.isLoading) { + return ( + <tbody> + <tr> + <td colSpan={colspan}> + <div className="flex justify-center w-full"> + <ComponentLoader /> + </div> + </td> + </tr> + </tbody> + ); } - const getTablebody: GetReactElementFunction = (): ReactElement => { - if (props.isLoading) { - return ( - <tbody> - <tr> - <td colSpan={colspan}> - <div className="flex justify-center w-full"> - <ComponentLoader /> - </div> - </td> - </tr> - </tbody> - ); - } + if (props.error) { + return ( + <tbody> + <tr> + <td colSpan={colspan} className="pl-10 pr-10"> + <ErrorMessage + error={props.error} + onRefreshClick={props.onRefreshClick} + /> + </td> + </tr> + </tbody> + ); + } - if (props.error) { - return ( - <tbody> - <tr> - <td colSpan={colspan} className="pl-10 pr-10"> - <ErrorMessage - error={props.error} - onRefreshClick={props.onRefreshClick} - /> - </td> - </tr> - </tbody> - ); - } - - if (props.data.length === 0) { - return ( - <tbody> - <tr> - <td colSpan={colspan}> - <ErrorMessage - error={ - props.noItemsMessage - ? props.noItemsMessage - : `No ${props.singularLabel.toLocaleLowerCase()}` - } - onRefreshClick={props.onRefreshClick} - /> - </td> - </tr> - </tbody> - ); - } - - if (props.filterError) { - return <></>; - } - - return ( - <TableBody - id={`${props.id}-body`} - data={props.data} - columns={props.columns} - actionButtons={props.actionButtons} - enableDragAndDrop={props.enableDragAndDrop} - dragAndDropScope={`${props.id}-dnd`} - dragDropIdField={props.dragDropIdField} - dragDropIndexField={props.dragDropIndexField} - isBulkActionsEnabled={isBulkActionsEnabled} - onItemSelected={(item: T) => { - // set bulk selected items. - setBulkSelectedItems([...bulkSelectedItems, item]); - props.onBulkSelectedItemAdded(item); - }} - onItemDeselected={(item: T) => { - // set bulk selected items. - const index: number = bulkSelectedItems.findIndex( - (x: T) => { - return ( - x[ - props.matchBulkSelectedItemByField - ]?.toString() === - item[ - props.matchBulkSelectedItemByField - ]?.toString() - ); - } - ); - - if (index > -1) { - bulkSelectedItems.splice(index, 1); - } - - props.onBulkSelectedItemRemoved(item); - }} - selectedItems={bulkSelectedItems} - matchBulkSelectedItemByField={ - props.matchBulkSelectedItemByField + if (props.data.length === 0) { + return ( + <tbody> + <tr> + <td colSpan={colspan}> + <ErrorMessage + error={ + props.noItemsMessage + ? props.noItemsMessage + : `No ${props.singularLabel.toLocaleLowerCase()}` } - /> - ); - }; + onRefreshClick={props.onRefreshClick} + /> + </td> + </tr> + </tbody> + ); + } - // check if all items on the page are selected. - let isAllItemsOnThePageSelected: boolean = true; - - props.data.forEach((item: T) => { - const index: number = bulkSelectedItems.findIndex((x: T) => { - return ( - x[props.matchBulkSelectedItemByField]?.toString() === - item[props.matchBulkSelectedItemByField]?.toString() - ); - }); - - if (index === -1) { - isAllItemsOnThePageSelected = false; - } - }); + if (props.filterError) { + return <></>; + } return ( - <div> - <FilterViewer - id={`${props.id}-filter`} - showFilterModal={props.showFilterModal || false} - onFilterChanged={props.onFilterChanged || undefined} - isModalLoading={props.isFilterLoading || false} - filterError={props.filterError} - onFilterRefreshClick={props.onFilterRefreshClick} - filters={props.filters || []} - onFilterModalClose={props.onFilterModalClose} - onFilterModalOpen={props.onFilterModalOpen} - singularLabel={props.singularLabel} - pluralLabel={props.pluralLabel} - /> - {props.bulkActions?.buttons && ( - <BulkUpdateForm - buttons={props.bulkActions.buttons} - onClearSelectionClick={() => { - props.onBulkClearAllItems(); - setIsAllItemsSelected(false); - }} - onSelectAllClick={() => { - props.onBulkSelectAllItems(); - setIsAllItemsSelected(true); - }} - selectedItems={bulkSelectedItems} - singularLabel={props.singularLabel} - pluralLabel={props.pluralLabel} - isAllItemsSelected={isAllItemsSelected} - onActionStart={props.onBulkActionStart} - onActionEnd={() => { - setIsAllItemsSelected(false); - setBulkSelectedItems([]); - props.onBulkActionEnd(); - }} - itemToString={props.bulkItemToString} - /> - )} - <DragDropContext - onDragEnd={(result: DropResult) => { - result.destination?.index && - props.onDragDrop && - props.onDragDrop( - result.draggableId, - result.destination.index - ); - }} - > - <div className="-my-2 overflow-x-auto -mx-6"> - <div className="inline-block min-w-full py-2 align-middle"> - <div className="overflow-hidden border-t border-gray-200"> - <table className="min-w-full divide-y divide-gray-200"> - <TableHeader - id={`${props.id}-header`} - columns={props.columns} - onSortChanged={props.onSortChanged} - enableDragAndDrop={props.enableDragAndDrop} - isBulkActionsEnabled={isBulkActionsEnabled} - onAllItemsDeselected={() => { - setIsAllItemsSelected(false); - props.onBulkClearAllItems(); - }} - onAllItemsOnThePageSelected={() => { - props.onBulkSelectItemsOnCurrentPage(); - }} - isAllItemsOnThePageSelected={ - isAllItemsOnThePageSelected - } - hasTableItems={props.data.length > 0} - /> - {getTablebody()} - </table> - </div> - </div> - </div> - <div className="bg-gray-50 text-right -mr-6 -ml-6 -mb-6"> - {!props.disablePagination && ( - <Pagination - singularLabel={props.singularLabel} - pluralLabel={props.pluralLabel} - currentPageNumber={props.currentPageNumber} - totalItemsCount={props.totalItemsCount} - itemsOnPage={props.itemsOnPage} - onNavigateToPage={props.onNavigateToPage} - isLoading={props.isLoading} - isError={Boolean(props.error)} - /> - )} - </div> - </DragDropContext> - </div> + <TableBody + id={`${props.id}-body`} + data={props.data} + columns={props.columns} + actionButtons={props.actionButtons} + enableDragAndDrop={props.enableDragAndDrop} + dragAndDropScope={`${props.id}-dnd`} + dragDropIdField={props.dragDropIdField} + dragDropIndexField={props.dragDropIndexField} + isBulkActionsEnabled={isBulkActionsEnabled} + onItemSelected={(item: T) => { + // set bulk selected items. + setBulkSelectedItems([...bulkSelectedItems, item]); + props.onBulkSelectedItemAdded(item); + }} + onItemDeselected={(item: T) => { + // set bulk selected items. + const index: number = bulkSelectedItems.findIndex((x: T) => { + return ( + x[props.matchBulkSelectedItemByField]?.toString() === + item[props.matchBulkSelectedItemByField]?.toString() + ); + }); + + if (index > -1) { + bulkSelectedItems.splice(index, 1); + } + + props.onBulkSelectedItemRemoved(item); + }} + selectedItems={bulkSelectedItems} + matchBulkSelectedItemByField={props.matchBulkSelectedItemByField} + /> ); + }; + + // check if all items on the page are selected. + let isAllItemsOnThePageSelected: boolean = true; + + props.data.forEach((item: T) => { + const index: number = bulkSelectedItems.findIndex((x: T) => { + return ( + x[props.matchBulkSelectedItemByField]?.toString() === + item[props.matchBulkSelectedItemByField]?.toString() + ); + }); + + if (index === -1) { + isAllItemsOnThePageSelected = false; + } + }); + + return ( + <div> + <FilterViewer + id={`${props.id}-filter`} + showFilterModal={props.showFilterModal || false} + onFilterChanged={props.onFilterChanged || undefined} + isModalLoading={props.isFilterLoading || false} + filterError={props.filterError} + onFilterRefreshClick={props.onFilterRefreshClick} + filters={props.filters || []} + onFilterModalClose={props.onFilterModalClose} + onFilterModalOpen={props.onFilterModalOpen} + singularLabel={props.singularLabel} + pluralLabel={props.pluralLabel} + /> + {props.bulkActions?.buttons && ( + <BulkUpdateForm + buttons={props.bulkActions.buttons} + onClearSelectionClick={() => { + props.onBulkClearAllItems(); + setIsAllItemsSelected(false); + }} + onSelectAllClick={() => { + props.onBulkSelectAllItems(); + setIsAllItemsSelected(true); + }} + selectedItems={bulkSelectedItems} + singularLabel={props.singularLabel} + pluralLabel={props.pluralLabel} + isAllItemsSelected={isAllItemsSelected} + onActionStart={props.onBulkActionStart} + onActionEnd={() => { + setIsAllItemsSelected(false); + setBulkSelectedItems([]); + props.onBulkActionEnd(); + }} + itemToString={props.bulkItemToString} + /> + )} + <DragDropContext + onDragEnd={(result: DropResult) => { + result.destination?.index && + props.onDragDrop && + props.onDragDrop(result.draggableId, result.destination.index); + }} + > + <div className="-my-2 overflow-x-auto -mx-6"> + <div className="inline-block min-w-full py-2 align-middle"> + <div className="overflow-hidden border-t border-gray-200"> + <table className="min-w-full divide-y divide-gray-200"> + <TableHeader + id={`${props.id}-header`} + columns={props.columns} + onSortChanged={props.onSortChanged} + enableDragAndDrop={props.enableDragAndDrop} + isBulkActionsEnabled={isBulkActionsEnabled} + onAllItemsDeselected={() => { + setIsAllItemsSelected(false); + props.onBulkClearAllItems(); + }} + onAllItemsOnThePageSelected={() => { + props.onBulkSelectItemsOnCurrentPage(); + }} + isAllItemsOnThePageSelected={isAllItemsOnThePageSelected} + hasTableItems={props.data.length > 0} + /> + {getTablebody()} + </table> + </div> + </div> + </div> + <div className="bg-gray-50 text-right -mr-6 -ml-6 -mb-6"> + {!props.disablePagination && ( + <Pagination + singularLabel={props.singularLabel} + pluralLabel={props.pluralLabel} + currentPageNumber={props.currentPageNumber} + totalItemsCount={props.totalItemsCount} + itemsOnPage={props.itemsOnPage} + onNavigateToPage={props.onNavigateToPage} + isLoading={props.isLoading} + isError={Boolean(props.error)} + /> + )} + </div> + </DragDropContext> + </div> + ); }; export default Table; diff --git a/CommonUI/src/Components/Table/TableBody.tsx b/CommonUI/src/Components/Table/TableBody.tsx index 41b79c7cfe..36e5f85329 100644 --- a/CommonUI/src/Components/Table/TableBody.tsx +++ b/CommonUI/src/Components/Table/TableBody.tsx @@ -1,98 +1,90 @@ -import ActionButtonSchema from '../ActionButton/ActionButtonSchema'; -import TableRow from './TableRow'; -import Columns from './Types/Columns'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; -import { Droppable, DroppableProvided } from 'react-beautiful-dnd'; +import ActionButtonSchema from "../ActionButton/ActionButtonSchema"; +import TableRow from "./TableRow"; +import Columns from "./Types/Columns"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; +import { Droppable, DroppableProvided } from "react-beautiful-dnd"; export interface ComponentProps<T extends GenericObject> { - data: Array<T>; - id: string; - columns: Columns<T>; - actionButtons?: undefined | Array<ActionButtonSchema<T>> | undefined; - enableDragAndDrop?: undefined | boolean; - dragAndDropScope?: string | undefined; - dragDropIdField?: keyof T | undefined; - dragDropIndexField?: keyof T | undefined; + data: Array<T>; + id: string; + columns: Columns<T>; + actionButtons?: undefined | Array<ActionButtonSchema<T>> | undefined; + enableDragAndDrop?: undefined | boolean; + dragAndDropScope?: string | undefined; + dragDropIdField?: keyof T | undefined; + dragDropIndexField?: keyof T | undefined; - // bulk actions - isBulkActionsEnabled?: undefined | boolean; - onItemSelected?: undefined | ((item: T) => void); - onItemDeselected?: undefined | ((item: T) => void); - selectedItems: Array<T>; - matchBulkSelectedItemByField: keyof T; // which field to use to match selected items. For exmaple this could be '_id' + // bulk actions + isBulkActionsEnabled?: undefined | boolean; + onItemSelected?: undefined | ((item: T) => void); + onItemDeselected?: undefined | ((item: T) => void); + selectedItems: Array<T>; + matchBulkSelectedItemByField: keyof T; // which field to use to match selected items. For exmaple this could be '_id' } type TableBodyFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const TableBody: TableBodyFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - type GetBodyFunction = (provided?: DroppableProvided) => ReactElement; + type GetBodyFunction = (provided?: DroppableProvided) => ReactElement; - const getBody: GetBodyFunction = ( - provided?: DroppableProvided - ): ReactElement => { - return ( - <tbody - id={props.id} - ref={provided?.innerRef} - {...provided?.droppableProps} - className="divide-y divide-gray-200 bg-white" - > - {props.data && - props.data.map((item: T, i: number) => { - return ( - <TableRow - isBulkActionsEnabled={ - props.isBulkActionsEnabled - } - onItemSelected={props.onItemSelected} - onItemDeselected={props.onItemDeselected} - isItemSelected={ - props.selectedItems?.filter( - (selectedItem: T) => { - return ( - selectedItem[ - props - .matchBulkSelectedItemByField - ]?.toString() === - item[ - props - .matchBulkSelectedItemByField - ]?.toString() - ); - } - ).length > 0 || false - } - dragAndDropScope={props.dragAndDropScope} - enableDragAndDrop={props.enableDragAndDrop} - key={i} - item={item} - columns={props.columns} - actionButtons={props.actionButtons} - dragDropIdField={props.dragDropIdField} - dragDropIndexField={props.dragDropIndexField} - /> - ); - })} - {provided?.placeholder} - </tbody> - ); - }; + const getBody: GetBodyFunction = ( + provided?: DroppableProvided, + ): ReactElement => { + return ( + <tbody + id={props.id} + ref={provided?.innerRef} + {...provided?.droppableProps} + className="divide-y divide-gray-200 bg-white" + > + {props.data && + props.data.map((item: T, i: number) => { + return ( + <TableRow + isBulkActionsEnabled={props.isBulkActionsEnabled} + onItemSelected={props.onItemSelected} + onItemDeselected={props.onItemDeselected} + isItemSelected={ + props.selectedItems?.filter((selectedItem: T) => { + return ( + selectedItem[ + props.matchBulkSelectedItemByField + ]?.toString() === + item[props.matchBulkSelectedItemByField]?.toString() + ); + }).length > 0 || false + } + dragAndDropScope={props.dragAndDropScope} + enableDragAndDrop={props.enableDragAndDrop} + key={i} + item={item} + columns={props.columns} + actionButtons={props.actionButtons} + dragDropIdField={props.dragDropIdField} + dragDropIndexField={props.dragDropIndexField} + /> + ); + })} + {provided?.placeholder} + </tbody> + ); + }; - if (props.enableDragAndDrop) { - return ( - <Droppable droppableId={props.dragAndDropScope || ''}> - {(provided: DroppableProvided) => { - return getBody(provided); - }} - </Droppable> - ); - } - return getBody(); + if (props.enableDragAndDrop) { + return ( + <Droppable droppableId={props.dragAndDropScope || ""}> + {(provided: DroppableProvided) => { + return getBody(provided); + }} + </Droppable> + ); + } + return getBody(); }; export default TableBody; diff --git a/CommonUI/src/Components/Table/TableCard.tsx b/CommonUI/src/Components/Table/TableCard.tsx index 0c8ae4f537..51ee626b61 100644 --- a/CommonUI/src/Components/Table/TableCard.tsx +++ b/CommonUI/src/Components/Table/TableCard.tsx @@ -1,31 +1,31 @@ -import Card, { CardButtonSchema } from '../Card/Card'; -import Table, { ComponentProps as TableComponentProps } from './Table'; -import GenericObject from 'Common/Types/GenericObject'; -import React, { ReactElement } from 'react'; +import Card, { CardButtonSchema } from "../Card/Card"; +import Table, { ComponentProps as TableComponentProps } from "./Table"; +import GenericObject from "Common/Types/GenericObject"; +import React, { ReactElement } from "react"; export interface ComponentProps<T extends GenericObject> { - title: string; - description: string; - headerButtons: Array<CardButtonSchema>; - tableProps: TableComponentProps<T>; + title: string; + description: string; + headerButtons: Array<CardButtonSchema>; + tableProps: TableComponentProps<T>; } type TableRowFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const TableRow: TableRowFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - return ( - <Card - title={props.title} - description={props.description} - buttons={props.headerButtons} - > - <Table {...props.tableProps} /> - </Card> - ); + return ( + <Card + title={props.title} + description={props.description} + buttons={props.headerButtons} + > + <Table {...props.tableProps} /> + </Card> + ); }; export default TableRow; diff --git a/CommonUI/src/Components/Table/TableHeader.tsx b/CommonUI/src/Components/Table/TableHeader.tsx index 504e91c9af..ca0df5890d 100644 --- a/CommonUI/src/Components/Table/TableHeader.tsx +++ b/CommonUI/src/Components/Table/TableHeader.tsx @@ -1,133 +1,129 @@ -import CheckboxElement from '../Checkbox/Checkbox'; -import Icon, { ThickProp } from '../Icon/Icon'; -import FieldType from '../Types/FieldType'; -import Column from './Types/Column'; -import Columns from './Types/Columns'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import GenericObject from 'Common/Types/GenericObject'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { ReactElement, useState } from 'react'; +import CheckboxElement from "../Checkbox/Checkbox"; +import Icon, { ThickProp } from "../Icon/Icon"; +import FieldType from "../Types/FieldType"; +import Column from "./Types/Column"; +import Columns from "./Types/Columns"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import GenericObject from "Common/Types/GenericObject"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { ReactElement, useState } from "react"; export interface ComponentProps<T extends GenericObject> { - columns: Columns<T>; - id: string; - onSortChanged: (sortBy: keyof T | null, sortOrder: SortOrder) => void; - enableDragAndDrop?: undefined | boolean; - isBulkActionsEnabled: undefined | boolean; - onAllItemsOnThePageSelected: undefined | (() => void); - onAllItemsDeselected: undefined | (() => void); - hasTableItems: undefined | boolean; - isAllItemsOnThePageSelected: undefined | boolean; + columns: Columns<T>; + id: string; + onSortChanged: (sortBy: keyof T | null, sortOrder: SortOrder) => void; + enableDragAndDrop?: undefined | boolean; + isBulkActionsEnabled: undefined | boolean; + onAllItemsOnThePageSelected: undefined | (() => void); + onAllItemsDeselected: undefined | (() => void); + hasTableItems: undefined | boolean; + isAllItemsOnThePageSelected: undefined | boolean; } type TableHeaderFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const TableHeader: TableHeaderFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const [currentSortColumn, setCurrentSortColumn] = useState<keyof T | null>( - null - ); - const [sortOrder, setSortOrder] = useState<SortOrder>(SortOrder.Ascending); + const [currentSortColumn, setCurrentSortColumn] = useState<keyof T | null>( + null, + ); + const [sortOrder, setSortOrder] = useState<SortOrder>(SortOrder.Ascending); - const selectBulkSelectCheckbox: boolean = Boolean( - props.isAllItemsOnThePageSelected && props.hasTableItems - ); + const selectBulkSelectCheckbox: boolean = Boolean( + props.isAllItemsOnThePageSelected && props.hasTableItems, + ); - return ( - <thead className="bg-gray-50" id={props.id}> - <tr> - {props.enableDragAndDrop && <th></th>} - {props.isBulkActionsEnabled && ( - <th> - <div className="ml-5"> - <CheckboxElement - disabled={!props.hasTableItems} - value={selectBulkSelectCheckbox} - onChange={(value: boolean) => { - if (value) { - if (props.onAllItemsOnThePageSelected) { - props.onAllItemsOnThePageSelected(); - } - } else if (props.onAllItemsDeselected) { - props.onAllItemsDeselected(); - } - }} - /> - </div> - </th> - )} - {props.columns.map((column: Column<T>, i: number) => { - const canSort: boolean = - !column.disableSort && Boolean(column.key); + return ( + <thead className="bg-gray-50" id={props.id}> + <tr> + {props.enableDragAndDrop && <th></th>} + {props.isBulkActionsEnabled && ( + <th> + <div className="ml-5"> + <CheckboxElement + disabled={!props.hasTableItems} + value={selectBulkSelectCheckbox} + onChange={(value: boolean) => { + if (value) { + if (props.onAllItemsOnThePageSelected) { + props.onAllItemsOnThePageSelected(); + } + } else if (props.onAllItemsDeselected) { + props.onAllItemsDeselected(); + } + }} + /> + </div> + </th> + )} + {props.columns.map((column: Column<T>, i: number) => { + const canSort: boolean = !column.disableSort && Boolean(column.key); - return ( - <th - key={i} - className={`px-6 py-3 text-left text-sm font-semibold text-gray-900 ${ - canSort ? 'cursor-pointer' : '' - }`} - onClick={() => { - if (!column.key) { - return; - } + return ( + <th + key={i} + className={`px-6 py-3 text-left text-sm font-semibold text-gray-900 ${ + canSort ? "cursor-pointer" : "" + }`} + onClick={() => { + if (!column.key) { + return; + } - if (!canSort) { - return; - } + if (!canSort) { + return; + } - if (currentSortColumn === column.key) { - setSortOrder( - sortOrder === SortOrder.Ascending - ? SortOrder.Descending - : SortOrder.Ascending - ); - } else { - setCurrentSortColumn(column.key); - setSortOrder(SortOrder.Ascending); - } + if (currentSortColumn === column.key) { + setSortOrder( + sortOrder === SortOrder.Ascending + ? SortOrder.Descending + : SortOrder.Ascending, + ); + } else { + setCurrentSortColumn(column.key); + setSortOrder(SortOrder.Ascending); + } - props.onSortChanged( - currentSortColumn, - sortOrder - ); - }} - > - <div - className={`flex ${ - column.type === FieldType.Actions - ? 'justify-end' - : 'justify-start' - }`} - > - {column.title} - {canSort && - currentSortColumn === column.key && - sortOrder === SortOrder.Ascending && ( - <Icon - icon={IconProp.ChevronUp} - thick={ThickProp.Thick} - className="ml-2 p-1 flex-none rounded bg-gray-200 text-gray-500 group-hover:bg-gray-300 h-4 w-4" - /> - )} - {canSort && - currentSortColumn === column.key && - sortOrder === SortOrder.Descending && ( - <Icon - icon={IconProp.ChevronDown} - thick={ThickProp.Thick} - className="ml-2 p-1 flex-none rounded bg-gray-200 text-gray-500 group-hover:bg-gray-300 h-4 w-4" - /> - )} - </div> - </th> - ); - })} - </tr> - </thead> - ); + props.onSortChanged(currentSortColumn, sortOrder); + }} + > + <div + className={`flex ${ + column.type === FieldType.Actions + ? "justify-end" + : "justify-start" + }`} + > + {column.title} + {canSort && + currentSortColumn === column.key && + sortOrder === SortOrder.Ascending && ( + <Icon + icon={IconProp.ChevronUp} + thick={ThickProp.Thick} + className="ml-2 p-1 flex-none rounded bg-gray-200 text-gray-500 group-hover:bg-gray-300 h-4 w-4" + /> + )} + {canSort && + currentSortColumn === column.key && + sortOrder === SortOrder.Descending && ( + <Icon + icon={IconProp.ChevronDown} + thick={ThickProp.Thick} + className="ml-2 p-1 flex-none rounded bg-gray-200 text-gray-500 group-hover:bg-gray-300 h-4 w-4" + /> + )} + </div> + </th> + ); + })} + </tr> + </thead> + ); }; export default TableHeader; diff --git a/CommonUI/src/Components/Table/TableRow.tsx b/CommonUI/src/Components/Table/TableRow.tsx index 99feef2af3..7d20784efd 100644 --- a/CommonUI/src/Components/Table/TableRow.tsx +++ b/CommonUI/src/Components/Table/TableRow.tsx @@ -1,354 +1,276 @@ -import ActionButtonSchema from '../ActionButton/ActionButtonSchema'; -import Button, { ButtonSize, ButtonStyleType } from '../Button/Button'; -import CheckboxElement from '../Checkbox/Checkbox'; -import ColorInput from '../ColorViewer/ColorViewer'; -import Icon, { ThickProp } from '../Icon/Icon'; -import ConfirmModal from '../Modal/ConfirmModal'; -import FieldType from '../Types/FieldType'; -import Column from './Types/Column'; -import Columns from './Types/Columns'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import GenericObject from 'Common/Types/GenericObject'; -import IconProp from 'Common/Types/Icon/IconProp'; -import get from 'lodash/get'; -import React, { ReactElement, useState } from 'react'; -import { Draggable, DraggableProvided } from 'react-beautiful-dnd'; +import ActionButtonSchema from "../ActionButton/ActionButtonSchema"; +import Button, { ButtonSize, ButtonStyleType } from "../Button/Button"; +import CheckboxElement from "../Checkbox/Checkbox"; +import ColorInput from "../ColorViewer/ColorViewer"; +import Icon, { ThickProp } from "../Icon/Icon"; +import ConfirmModal from "../Modal/ConfirmModal"; +import FieldType from "../Types/FieldType"; +import Column from "./Types/Column"; +import Columns from "./Types/Columns"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import GenericObject from "Common/Types/GenericObject"; +import IconProp from "Common/Types/Icon/IconProp"; +import get from "lodash/get"; +import React, { ReactElement, useState } from "react"; +import { Draggable, DraggableProvided } from "react-beautiful-dnd"; export interface ComponentProps<T extends GenericObject> { - item: T; - columns: Columns<T>; - actionButtons?: Array<ActionButtonSchema<T>> | undefined; - enableDragAndDrop?: boolean | undefined; - dragAndDropScope?: string | undefined; - dragDropIdField?: keyof T | undefined; - dragDropIndexField?: keyof T | undefined; + item: T; + columns: Columns<T>; + actionButtons?: Array<ActionButtonSchema<T>> | undefined; + enableDragAndDrop?: boolean | undefined; + dragAndDropScope?: string | undefined; + dragDropIdField?: keyof T | undefined; + dragDropIndexField?: keyof T | undefined; - // bulk actions - isBulkActionsEnabled?: undefined | boolean; - onItemSelected?: undefined | ((item: T) => void); - onItemDeselected?: undefined | ((item: T) => void); - isItemSelected?: boolean | undefined; + // bulk actions + isBulkActionsEnabled?: undefined | boolean; + onItemSelected?: undefined | ((item: T) => void); + onItemDeselected?: undefined | ((item: T) => void); + isItemSelected?: boolean | undefined; } type TableRowFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ) => ReactElement; const TableRow: TableRowFunction = <T extends GenericObject>( - props: ComponentProps<T> + props: ComponentProps<T>, ): ReactElement => { - const [isButtonLoading, setIsButtonLoading] = useState<Array<boolean>>( - props.actionButtons?.map(() => { - return false; - }) || [] - ); + const [isButtonLoading, setIsButtonLoading] = useState<Array<boolean>>( + props.actionButtons?.map(() => { + return false; + }) || [], + ); - const [tooltipModalText, setTooltipModalText] = useState<string>(''); + const [tooltipModalText, setTooltipModalText] = useState<string>(""); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - type GetRowFunction = (provided?: DraggableProvided) => ReactElement; + type GetRowFunction = (provided?: DraggableProvided) => ReactElement; - const getRow: GetRowFunction = ( - provided?: DraggableProvided - ): ReactElement => { - return ( - <> - <tr {...provided?.draggableProps} ref={provided?.innerRef}> - {props.enableDragAndDrop && ( - <td - className="ml-5 py-4 w-10 align-top" - {...provided?.dragHandleProps} - > - <Icon - icon={IconProp.ArrowUpDown} - className="ml-6 h-5 w-5 text-gray-500 hover:text-indigo-800 m-auto cursor-ns-resize" - /> - </td> - )} - {props.isBulkActionsEnabled && ( - <td - className="w-10 py-3.5 align-top" - {...provided?.dragHandleProps} - > - <div className="ml-5"> - <CheckboxElement - value={props.isItemSelected} - onChange={(value: boolean) => { - if (value) { - if (props.onItemSelected) { - props.onItemSelected( - props.item - ); - } - } else if (props.onItemDeselected) { - props.onItemDeselected(props.item); - } - }} - /> - </div> - </td> - )} - {props.columns && - props.columns.map((column: Column<T>, i: number) => { - let className: string = - 'whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-500 sm:pl-6 align-top'; - if (i === props.columns.length - 1) { - className = - 'whitespace-nowrap py-4 pl-4 pr-6 text-sm font-medium text-gray-500 sm:pl-6 align-top'; - } - return ( - <td - key={i} - className={className} - style={{ - textAlign: - column.type === FieldType.Actions - ? 'right' - : 'left', - }} - onClick={() => { - if (column.tooltipText) { - setTooltipModalText( - column.tooltipText(props.item) - ); - } - }} - > - {column.key && !column.getElement ? ( - column.type === FieldType.Date ? ( - props.item[column.key] ? ( - OneUptimeDate.getDateAsLocalFormattedString( - props.item[ - column.key - ] as string, - true - ) - ) : ( - column.noValueMessage || '' - ) - ) : column.type === - FieldType.DateTime ? ( - props.item[column.key] ? ( - OneUptimeDate.getDateAsLocalFormattedString( - props.item[ - column.key - ] as string, - false - ) - ) : ( - column.noValueMessage || '' - ) - ) : column.type === - FieldType.USDCents ? ( - props.item[column.key] ? ( - ((props.item[ - column.key - ] as number) || 0) / - 100 + - ' USD' - ) : ( - column.noValueMessage || '0 USD' - ) - ) : column.type === - FieldType.Percent ? ( - props.item[column.key] ? ( - props.item[column.key] + '%' - ) : ( - column.noValueMessage || '0%' - ) - ) : column.type === FieldType.Color ? ( - props.item[column.key] ? ( - <ColorInput - value={ - props.item[ - column.key - ] as Color - } - /> - ) : ( - column.noValueMessage || '0%' - ) - ) : column.type === - FieldType.Boolean ? ( - props.item[column.key] ? ( - <Icon - icon={IconProp.Check} - className={ - 'h-5 w-5 text-gray-500' - } - thick={ThickProp.Thick} - /> - ) : ( - <Icon - icon={IconProp.False} - className={ - 'h-5 w-5 text-gray-500' - } - thick={ThickProp.Thick} - /> - ) - ) : ( - get( - props.item, - column.key, - '' - )?.toString() || - column.noValueMessage || - '' - ) - ) : ( - <></> - )} - - {column.key && column.getElement ? ( - column.getElement(props.item) - ) : ( - <></> - )} - {column.type === FieldType.Actions && ( - <div className="flex justify-end"> - {error && ( - <div className="text-align-left"> - <ConfirmModal - title={`Error`} - description={error} - submitButtonText={ - 'Close' - } - onSubmit={() => { - return setError(''); - }} - /> - </div> - )} - {props.actionButtons?.map( - ( - button: ActionButtonSchema<T>, - i: number - ) => { - if ( - button.isVisible && - !button.isVisible( - props.item - ) - ) { - return ( - <div key={i}></div> - ); - } - - return ( - <div key={i}> - <Button - buttonSize={ - ButtonSize.Small - } - title={ - button.title - } - icon={ - button.icon - } - buttonStyle={ - button.buttonStyleType - } - isLoading={ - isButtonLoading[ - i - ] - } - onClick={() => { - if ( - button.onClick - ) { - isButtonLoading[ - i - ] = true; - setIsButtonLoading( - isButtonLoading - ); - - button.onClick( - props.item, - () => { - // on action complete - isButtonLoading[ - i - ] = - false; - setIsButtonLoading( - isButtonLoading - ); - }, - ( - err: Error - ) => { - isButtonLoading[ - i - ] = - false; - setIsButtonLoading( - isButtonLoading - ); - setError( - ( - err as Error - ) - .message - ); - } - ); - } - }} - /> - </div> - ); - } - )} - </div> - )} - </td> - ); - })} - </tr> - {tooltipModalText && ( - <ConfirmModal - title={`Help`} - description={`${tooltipModalText}`} - submitButtonText={'Close'} - onSubmit={() => { - setTooltipModalText(''); - }} - submitButtonType={ButtonStyleType.NORMAL} - /> - )} - </> - ); - }; - - if ( - props.enableDragAndDrop && - props.dragDropIdField && - props.dragDropIndexField - ) { - return ( - <Draggable - draggableId={ - (props.item[props.dragDropIdField] as string) || '' - } - index={(props.item[props.dragDropIndexField] as number) || 0} - key={(props.item[props.dragDropIndexField] as number) || 0} + const getRow: GetRowFunction = ( + provided?: DraggableProvided, + ): ReactElement => { + return ( + <> + <tr {...provided?.draggableProps} ref={provided?.innerRef}> + {props.enableDragAndDrop && ( + <td + className="ml-5 py-4 w-10 align-top" + {...provided?.dragHandleProps} > - {(provided: DraggableProvided) => { - return getRow(provided); - }} - </Draggable> - ); - } + <Icon + icon={IconProp.ArrowUpDown} + className="ml-6 h-5 w-5 text-gray-500 hover:text-indigo-800 m-auto cursor-ns-resize" + /> + </td> + )} + {props.isBulkActionsEnabled && ( + <td + className="w-10 py-3.5 align-top" + {...provided?.dragHandleProps} + > + <div className="ml-5"> + <CheckboxElement + value={props.isItemSelected} + onChange={(value: boolean) => { + if (value) { + if (props.onItemSelected) { + props.onItemSelected(props.item); + } + } else if (props.onItemDeselected) { + props.onItemDeselected(props.item); + } + }} + /> + </div> + </td> + )} + {props.columns && + props.columns.map((column: Column<T>, i: number) => { + let className: string = + "whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-500 sm:pl-6 align-top"; + if (i === props.columns.length - 1) { + className = + "whitespace-nowrap py-4 pl-4 pr-6 text-sm font-medium text-gray-500 sm:pl-6 align-top"; + } + return ( + <td + key={i} + className={className} + style={{ + textAlign: + column.type === FieldType.Actions ? "right" : "left", + }} + onClick={() => { + if (column.tooltipText) { + setTooltipModalText(column.tooltipText(props.item)); + } + }} + > + {column.key && !column.getElement ? ( + column.type === FieldType.Date ? ( + props.item[column.key] ? ( + OneUptimeDate.getDateAsLocalFormattedString( + props.item[column.key] as string, + true, + ) + ) : ( + column.noValueMessage || "" + ) + ) : column.type === FieldType.DateTime ? ( + props.item[column.key] ? ( + OneUptimeDate.getDateAsLocalFormattedString( + props.item[column.key] as string, + false, + ) + ) : ( + column.noValueMessage || "" + ) + ) : column.type === FieldType.USDCents ? ( + props.item[column.key] ? ( + ((props.item[column.key] as number) || 0) / 100 + " USD" + ) : ( + column.noValueMessage || "0 USD" + ) + ) : column.type === FieldType.Percent ? ( + props.item[column.key] ? ( + props.item[column.key] + "%" + ) : ( + column.noValueMessage || "0%" + ) + ) : column.type === FieldType.Color ? ( + props.item[column.key] ? ( + <ColorInput value={props.item[column.key] as Color} /> + ) : ( + column.noValueMessage || "0%" + ) + ) : column.type === FieldType.Boolean ? ( + props.item[column.key] ? ( + <Icon + icon={IconProp.Check} + className={"h-5 w-5 text-gray-500"} + thick={ThickProp.Thick} + /> + ) : ( + <Icon + icon={IconProp.False} + className={"h-5 w-5 text-gray-500"} + thick={ThickProp.Thick} + /> + ) + ) : ( + get(props.item, column.key, "")?.toString() || + column.noValueMessage || + "" + ) + ) : ( + <></> + )} - return getRow(); + {column.key && column.getElement ? ( + column.getElement(props.item) + ) : ( + <></> + )} + {column.type === FieldType.Actions && ( + <div className="flex justify-end"> + {error && ( + <div className="text-align-left"> + <ConfirmModal + title={`Error`} + description={error} + submitButtonText={"Close"} + onSubmit={() => { + return setError(""); + }} + /> + </div> + )} + {props.actionButtons?.map( + (button: ActionButtonSchema<T>, i: number) => { + if ( + button.isVisible && + !button.isVisible(props.item) + ) { + return <div key={i}></div>; + } + + return ( + <div key={i}> + <Button + buttonSize={ButtonSize.Small} + title={button.title} + icon={button.icon} + buttonStyle={button.buttonStyleType} + isLoading={isButtonLoading[i]} + onClick={() => { + if (button.onClick) { + isButtonLoading[i] = true; + setIsButtonLoading(isButtonLoading); + + button.onClick( + props.item, + () => { + // on action complete + isButtonLoading[i] = false; + setIsButtonLoading(isButtonLoading); + }, + (err: Error) => { + isButtonLoading[i] = false; + setIsButtonLoading(isButtonLoading); + setError((err as Error).message); + }, + ); + } + }} + /> + </div> + ); + }, + )} + </div> + )} + </td> + ); + })} + </tr> + {tooltipModalText && ( + <ConfirmModal + title={`Help`} + description={`${tooltipModalText}`} + submitButtonText={"Close"} + onSubmit={() => { + setTooltipModalText(""); + }} + submitButtonType={ButtonStyleType.NORMAL} + /> + )} + </> + ); + }; + + if ( + props.enableDragAndDrop && + props.dragDropIdField && + props.dragDropIndexField + ) { + return ( + <Draggable + draggableId={(props.item[props.dragDropIdField] as string) || ""} + index={(props.item[props.dragDropIndexField] as number) || 0} + key={(props.item[props.dragDropIndexField] as number) || 0} + > + {(provided: DraggableProvided) => { + return getRow(provided); + }} + </Draggable> + ); + } + + return getRow(); }; export default TableRow; diff --git a/CommonUI/src/Components/Table/Types/Column.ts b/CommonUI/src/Components/Table/Types/Column.ts index eca2814ba0..4533b7eea0 100644 --- a/CommonUI/src/Components/Table/Types/Column.ts +++ b/CommonUI/src/Components/Table/Types/Column.ts @@ -1,20 +1,20 @@ -import AlignItem from '../../../Types/AlignItem'; -import FieldType from '../../Types/FieldType'; -import GenericObject from 'Common/Types/GenericObject'; -import { ReactElement } from 'react'; +import AlignItem from "../../../Types/AlignItem"; +import FieldType from "../../Types/FieldType"; +import GenericObject from "Common/Types/GenericObject"; +import { ReactElement } from "react"; export default interface Column<T extends GenericObject> { - title: string; - description?: string | undefined; - disableSort?: boolean | undefined; - tooltipText?: ((item: T) => string) | undefined; - type: FieldType; - colSpan?: number | undefined; - noValueMessage?: string | undefined; - contentClassName?: string | undefined; - alignItem?: AlignItem | undefined; - key?: keyof T | null; //can be null because actions column does not have a key. - getElement?: - | ((item: T, onBeforeFetchData?: T | undefined) => ReactElement) - | undefined; + title: string; + description?: string | undefined; + disableSort?: boolean | undefined; + tooltipText?: ((item: T) => string) | undefined; + type: FieldType; + colSpan?: number | undefined; + noValueMessage?: string | undefined; + contentClassName?: string | undefined; + alignItem?: AlignItem | undefined; + key?: keyof T | null; //can be null because actions column does not have a key. + getElement?: + | ((item: T, onBeforeFetchData?: T | undefined) => ReactElement) + | undefined; } diff --git a/CommonUI/src/Components/Table/Types/Columns.ts b/CommonUI/src/Components/Table/Types/Columns.ts index 955fe8c44a..5b7952a7a0 100644 --- a/CommonUI/src/Components/Table/Types/Columns.ts +++ b/CommonUI/src/Components/Table/Types/Columns.ts @@ -1,5 +1,5 @@ -import Column from './Column'; -import GenericObject from 'Common/Types/GenericObject'; +import Column from "./Column"; +import GenericObject from "Common/Types/GenericObject"; type Columns<T extends GenericObject> = Array<Column<T>>; diff --git a/CommonUI/src/Components/TableColumnList/TableColumnListComponent.tsx b/CommonUI/src/Components/TableColumnList/TableColumnListComponent.tsx index b0b97a0dea..501e8ae89e 100644 --- a/CommonUI/src/Components/TableColumnList/TableColumnListComponent.tsx +++ b/CommonUI/src/Components/TableColumnList/TableColumnListComponent.tsx @@ -1,85 +1,83 @@ -import Button, { ButtonStyleType } from '../Button/Button'; -import BaseModel from 'Common/Models/BaseModel'; -import React, { ReactElement } from 'react'; +import Button, { ButtonStyleType } from "../Button/Button"; +import BaseModel from "Common/Models/BaseModel"; +import React, { ReactElement } from "react"; export interface ComponentProps<TBaseModel extends BaseModel> { - items: Array<TBaseModel>; - getEachElement: (element: TBaseModel) => ReactElement; - noItemsMessage: string | undefined; - className?: string | undefined; - moreText?: string | undefined; + items: Array<TBaseModel>; + getEachElement: (element: TBaseModel) => ReactElement; + noItemsMessage: string | undefined; + className?: string | undefined; + moreText?: string | undefined; } const TableColumnListComponent: <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ) => ReactElement = <TBaseModel extends BaseModel>( - props: ComponentProps<TBaseModel> + props: ComponentProps<TBaseModel>, ): ReactElement => { - if (!props.items || props.items.length === 0) { - return <p>{props.noItemsMessage}</p>; + if (!props.items || props.items.length === 0) { + return <p>{props.noItemsMessage}</p>; + } + + const firstThreeItems: Array<TBaseModel> = []; + + for (let i: number = 0; i < 3; i++) { + if (props.items[i]) { + firstThreeItems.push(props.items[i]!); } + } - const firstThreeItems: Array<TBaseModel> = []; + // remaining items + const remainingItems: Array<TBaseModel> = []; - for (let i: number = 0; i < 3; i++) { - if (props.items[i]) { - firstThreeItems.push(props.items[i]!); - } + for (let i: number = 3; i < props.items.length; i++) { + if (props.items[i]) { + remainingItems.push(props.items[i]!); } + } - // remaining items - const remainingItems: Array<TBaseModel> = []; + const [showMoreItems, setShowMoreItems] = React.useState<boolean>(false); - for (let i: number = 3; i < props.items.length; i++) { - if (props.items[i]) { - remainingItems.push(props.items[i]!); - } - } + return ( + <div className={props.className}> + {firstThreeItems.map((item: TBaseModel, i: number) => { + return <div key={i}>{props.getEachElement(item)}</div>; + })} + {showMoreItems ? ( + remainingItems.map((item: TBaseModel, i: number) => { + return <div key={i}>{props.getEachElement(item)}</div>; + }) + ) : ( + <></> + )} - const [showMoreItems, setShowMoreItems] = React.useState<boolean>(false); + {remainingItems.length > 0 && !showMoreItems ? ( + <Button + className="-ml-3" + onClick={() => { + return setShowMoreItems(true); + }} + title={`${remainingItems.length} ${props.moreText || "more"}`} + buttonStyle={ButtonStyleType.SECONDARY_LINK} + /> + ) : ( + <></> + )} - return ( - <div className={props.className}> - {firstThreeItems.map((item: TBaseModel, i: number) => { - return <div key={i}>{props.getEachElement(item)}</div>; - })} - {showMoreItems ? ( - remainingItems.map((item: TBaseModel, i: number) => { - return <div key={i}>{props.getEachElement(item)}</div>; - }) - ) : ( - <></> - )} - - {remainingItems.length > 0 && !showMoreItems ? ( - <Button - className="-ml-3" - onClick={() => { - return setShowMoreItems(true); - }} - title={`${remainingItems.length} ${ - props.moreText || 'more' - }`} - buttonStyle={ButtonStyleType.SECONDARY_LINK} - /> - ) : ( - <></> - )} - - {showMoreItems ? ( - <Button - className="-ml-3" - onClick={() => { - return setShowMoreItems(false); - }} - title="Show less" - buttonStyle={ButtonStyleType.SECONDARY_LINK} - /> - ) : ( - <></> - )} - </div> - ); + {showMoreItems ? ( + <Button + className="-ml-3" + onClick={() => { + return setShowMoreItems(false); + }} + title="Show less" + buttonStyle={ButtonStyleType.SECONDARY_LINK} + /> + ) : ( + <></> + )} + </div> + ); }; export default TableColumnListComponent; diff --git a/CommonUI/src/Components/Tabs/Tab.tsx b/CommonUI/src/Components/Tabs/Tab.tsx index a4042764a1..1027fefd69 100644 --- a/CommonUI/src/Components/Tabs/Tab.tsx +++ b/CommonUI/src/Components/Tabs/Tab.tsx @@ -1,68 +1,68 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export enum TabType { - Error = 'error', - Warning = 'warning', - Info = 'info', - Success = 'success', + Error = "error", + Warning = "warning", + Info = "info", + Success = "success", } export interface Tab { - name: string; - countBadge?: number | undefined; - tabType?: TabType | undefined; - children: ReactElement; + name: string; + countBadge?: number | undefined; + tabType?: TabType | undefined; + children: ReactElement; } export interface ComponentProps { - tab: Tab; - onClick?: () => void; - isSelected?: boolean; + tab: Tab; + onClick?: () => void; + isSelected?: boolean; } const TabElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const backgroundColor: string = 'bg-gray-100'; + const backgroundColor: string = "bg-gray-100"; - return ( - <div className="mt-3 mb-3"> - <div - data-testid={`tab-${props.tab.name}`} - key={props.tab.name} - onClick={props.onClick} - className={`${ - (props.isSelected - ? backgroundColor + ' text-gray-700' - : 'text-gray-500 hover:text-gray-700') + - ' rounded-md px-3 py-2 text-sm font-medium cursor-pointer flex' - }`} - aria-current={props.isSelected ? 'page' : undefined} - > - <div>{props.tab.name}</div> + return ( + <div className="mt-3 mb-3"> + <div + data-testid={`tab-${props.tab.name}`} + key={props.tab.name} + onClick={props.onClick} + className={`${ + (props.isSelected + ? backgroundColor + " text-gray-700" + : "text-gray-500 hover:text-gray-700") + + " rounded-md px-3 py-2 text-sm font-medium cursor-pointer flex" + }`} + aria-current={props.isSelected ? "page" : undefined} + > + <div>{props.tab.name}</div> - {props.tab.countBadge && props.tab.countBadge > 0 ? ( - <span - className={`${ - props.tab.tabType === TabType.Error - ? 'bg-red-500' - : props.tab.tabType === TabType.Warning - ? 'bg-yellow-500' - : props.tab.tabType === TabType.Info - ? 'bg-indigo-500' - : props.tab.tabType === TabType.Success - ? 'bg-green-500' - : 'bg-gray-500' - } text-white rounded-full px-2 py-1 text-xs font-semibold ml-2 -mt-0.5`} - > - {props.tab.countBadge} - </span> - ) : ( - <></> - )} - </div> - </div> - ); + {props.tab.countBadge && props.tab.countBadge > 0 ? ( + <span + className={`${ + props.tab.tabType === TabType.Error + ? "bg-red-500" + : props.tab.tabType === TabType.Warning + ? "bg-yellow-500" + : props.tab.tabType === TabType.Info + ? "bg-indigo-500" + : props.tab.tabType === TabType.Success + ? "bg-green-500" + : "bg-gray-500" + } text-white rounded-full px-2 py-1 text-xs font-semibold ml-2 -mt-0.5`} + > + {props.tab.countBadge} + </span> + ) : ( + <></> + )} + </div> + </div> + ); }; export default TabElement; diff --git a/CommonUI/src/Components/Tabs/Tabs.tsx b/CommonUI/src/Components/Tabs/Tabs.tsx index c9db95d661..84d756443a 100644 --- a/CommonUI/src/Components/Tabs/Tabs.tsx +++ b/CommonUI/src/Components/Tabs/Tabs.tsx @@ -1,52 +1,52 @@ -import TabElement, { Tab } from './Tab'; +import TabElement, { Tab } from "./Tab"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - tabs: Array<Tab>; - onTabChange: (tab: Tab) => void; + tabs: Array<Tab>; + onTabChange: (tab: Tab) => void; } const Tabs: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [currentTab, setCurrentTab] = useState<Tab | null>(null); + const [currentTab, setCurrentTab] = useState<Tab | null>(null); - useEffect(() => { - setCurrentTab(props.tabs.length > 0 ? (props.tabs[0] as Tab) : null); - }, [props.tabs]); + useEffect(() => { + setCurrentTab(props.tabs.length > 0 ? (props.tabs[0] as Tab) : null); + }, [props.tabs]); - useEffect(() => { - if (currentTab) { - props.onTabChange && props.onTabChange(currentTab); - } - }, [currentTab]); + useEffect(() => { + if (currentTab) { + props.onTabChange && props.onTabChange(currentTab); + } + }, [currentTab]); - return ( - <div> - <div className="hidden sm:block"> - <nav className="flex space-x-4" aria-label="Tabs"> - {props.tabs.map((tab: Tab) => { - return ( - <TabElement - key={tab.name} - tab={tab} - onClick={() => { - setCurrentTab(tab); - }} - isSelected={tab === currentTab} - /> - ); - })} - </nav> - </div> - <div className="mt-3 ml-1">{currentTab && currentTab.children}</div> - </div> - ); + return ( + <div> + <div className="hidden sm:block"> + <nav className="flex space-x-4" aria-label="Tabs"> + {props.tabs.map((tab: Tab) => { + return ( + <TabElement + key={tab.name} + tab={tab} + onClick={() => { + setCurrentTab(tab); + }} + isSelected={tab === currentTab} + /> + ); + })} + </nav> + </div> + <div className="mt-3 ml-1">{currentTab && currentTab.children}</div> + </div> + ); }; export default Tabs; diff --git a/CommonUI/src/Components/Template/Template.tsx b/CommonUI/src/Components/Template/Template.tsx index 4f1069725d..f6615132b2 100644 --- a/CommonUI/src/Components/Template/Template.tsx +++ b/CommonUI/src/Components/Template/Template.tsx @@ -1,13 +1,13 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; + title: string; } const Component: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return <div>{props.title}</div>; + return <div>{props.title}</div>; }; export default Component; diff --git a/CommonUI/src/Components/TextArea/TextArea.tsx b/CommonUI/src/Components/TextArea/TextArea.tsx index d98588ca60..bf2e20489c 100644 --- a/CommonUI/src/Components/TextArea/TextArea.tsx +++ b/CommonUI/src/Components/TextArea/TextArea.tsx @@ -1,107 +1,101 @@ -import Icon from '../Icon/Icon'; -import IconProp from 'Common/Types/Icon/IconProp'; +import Icon from "../Icon/Icon"; +import IconProp from "Common/Types/Icon/IconProp"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - onChange?: undefined | ((value: string) => void); - initialValue?: string | undefined; - value?: string | undefined; - placeholder?: undefined | string; - onFocus?: () => void; - onBlur?: () => void; - className?: undefined | string; - tabIndex?: number | undefined; - error?: string | undefined; - autoFocus?: boolean | undefined; - dataTestId?: string | undefined; + onChange?: undefined | ((value: string) => void); + initialValue?: string | undefined; + value?: string | undefined; + placeholder?: undefined | string; + onFocus?: () => void; + onBlur?: () => void; + className?: undefined | string; + tabIndex?: number | undefined; + error?: string | undefined; + autoFocus?: boolean | undefined; + dataTestId?: string | undefined; } const TextArea: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [text, setText] = useState<string>(props.initialValue || ''); + const [text, setText] = useState<string>(props.initialValue || ""); - let className: string = ''; + let className: string = ""; - if (!props.className) { - className = - 'block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm'; - } else { - className = props.className; + if (!props.className) { + className = + "block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:text-gray-900 focus:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"; + } else { + className = props.className; + } + + if (props.error) { + className += + " border-red-300 pr-10 text-red-900 placeholder-red-300 focus:border-red-500 focus:outline-none focus:ring-red-500"; + } + + useEffect(() => { + if (props.value) { + setText(props.value.toString()); } + }, [props.value]); - if (props.error) { - className += - ' border-red-300 pr-10 text-red-900 placeholder-red-300 focus:border-red-500 focus:outline-none focus:ring-red-500'; - } + type HandleChangeFunction = (content: string) => void; - useEffect(() => { - if (props.value) { - setText(props.value.toString()); - } - }, [props.value]); + const handleChange: HandleChangeFunction = (content: string): void => { + setText(content); + props.onChange && props.onChange(content); + }; - type HandleChangeFunction = (content: string) => void; + return ( + <> + <div className="relative mt-2 mb-1 rounded-md shadow-sm"> + <textarea + autoFocus={props.autoFocus} + placeholder={props.placeholder} + data-testid={props.dataTestId} + className={`${className || ""}`} + value={text} + onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => { + const value: string = e.target.value; - const handleChange: HandleChangeFunction = (content: string): void => { - setText(content); - props.onChange && props.onChange(content); - }; + if (value === "\n") { + handleChange(""); + } - return ( - <> - <div className="relative mt-2 mb-1 rounded-md shadow-sm"> - <textarea - autoFocus={props.autoFocus} - placeholder={props.placeholder} - data-testid={props.dataTestId} - className={`${className || ''}`} - value={text} - onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => { - const value: string = e.target.value; - - if (value === '\n') { - handleChange(''); - } - - handleChange(e.target.value); - }} - onFocus={() => { - if (props.onFocus) { - props.onFocus(); - } - }} - onBlur={() => { - if (props.onBlur) { - props.onBlur(); - } - }} - tabIndex={props.tabIndex} - /> - {props.error && ( - <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> - <Icon - icon={IconProp.ErrorSolid} - className="h-5 w-5 text-red-500" - /> - </div> - )} - </div> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} - </> - ); + handleChange(e.target.value); + }} + onFocus={() => { + if (props.onFocus) { + props.onFocus(); + } + }} + onBlur={() => { + if (props.onBlur) { + props.onBlur(); + } + }} + tabIndex={props.tabIndex} + /> + {props.error && ( + <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> + <Icon icon={IconProp.ErrorSolid} className="h-5 w-5 text-red-500" /> + </div> + )} + </div> + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </> + ); }; export default TextArea; diff --git a/CommonUI/src/Components/Toast/Toast.tsx b/CommonUI/src/Components/Toast/Toast.tsx index ab599a36cc..079569c8ee 100644 --- a/CommonUI/src/Components/Toast/Toast.tsx +++ b/CommonUI/src/Components/Toast/Toast.tsx @@ -1,95 +1,90 @@ -import OneUptimeDate from 'Common/Types/Date'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import OneUptimeDate from "Common/Types/Date"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export enum ToastType { - DANGER, - SUCCESS, - INFO, - WARNING, - NORMAL, + DANGER, + SUCCESS, + INFO, + WARNING, + NORMAL, } export interface ComponentProps { - title: string; - description: string; - onClose?: undefined | (() => void); - type?: undefined | ToastType; - createdAt?: undefined | Date; + title: string; + description: string; + onClose?: undefined | (() => void); + type?: undefined | ToastType; + createdAt?: undefined | Date; } const Component: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [show, setShow] = useState<boolean>(true); - let typeCssClass: string = 'text-info'; + const [show, setShow] = useState<boolean>(true); + let typeCssClass: string = "text-info"; - if (props.type === ToastType.NORMAL) { - typeCssClass = 'text-normal'; - } - if (props.type === ToastType.DANGER) { - typeCssClass = 'text-danger'; - } - if (props.type === ToastType.WARNING) { - typeCssClass = 'text-warning'; - } - if (props.type === ToastType.SUCCESS) { - typeCssClass = 'text-success'; - } - if (props.type === ToastType.INFO) { - typeCssClass = 'text-info'; - } + if (props.type === ToastType.NORMAL) { + typeCssClass = "text-normal"; + } + if (props.type === ToastType.DANGER) { + typeCssClass = "text-danger"; + } + if (props.type === ToastType.WARNING) { + typeCssClass = "text-warning"; + } + if (props.type === ToastType.SUCCESS) { + typeCssClass = "text-success"; + } + if (props.type === ToastType.INFO) { + typeCssClass = "text-info"; + } - if (show) { - return ( - <div - data-testid="toast-main" - className="position-fixed top-0 end-0 p-3" - style={{ zIndex: '1005' }} - > - <div className="toast fade show" role="alert"> - <div className="toast-header"> - {props.type && ( - <div - data-testid="toast-status" - role="status" - className={`spinner-grow-sm spinner-grow ${typeCssClass}`} - > - <span className="visually-hidden"> - Loading... - </span> - </div> - )} - <strong - data-testid="toast-strong" - className="me-auto ms-2" - > - {props.title} - </strong> - {props.createdAt && ( - <small data-testid="toast-time"> - {OneUptimeDate.fromNow(props.createdAt)} - </small> - )} + if (show) { + return ( + <div + data-testid="toast-main" + className="position-fixed top-0 end-0 p-3" + style={{ zIndex: "1005" }} + > + <div className="toast fade show" role="alert"> + <div className="toast-header"> + {props.type && ( + <div + data-testid="toast-status" + role="status" + className={`spinner-grow-sm spinner-grow ${typeCssClass}`} + > + <span className="visually-hidden">Loading...</span> + </div> + )} + <strong data-testid="toast-strong" className="me-auto ms-2"> + {props.title} + </strong> + {props.createdAt && ( + <small data-testid="toast-time"> + {OneUptimeDate.fromNow(props.createdAt)} + </small> + )} - <button - data-testid="toast-button" - onClick={() => { - setShow(false); - props.onClose && props.onClose(); - }} - type="button" - className="btn-close" - aria-label="Close" - ></button> - </div> - <div data-testid="toast-desc" className="toast-body"> - {props.description} - </div> - </div> - </div> - ); - } - return <></>; + <button + data-testid="toast-button" + onClick={() => { + setShow(false); + props.onClose && props.onClose(); + }} + type="button" + className="btn-close" + aria-label="Close" + ></button> + </div> + <div data-testid="toast-desc" className="toast-body"> + {props.description} + </div> + </div> + </div> + ); + } + return <></>; }; export default Component; diff --git a/CommonUI/src/Components/Toggle/Toggle.tsx b/CommonUI/src/Components/Toggle/Toggle.tsx index b0d5cf6fba..c5a0c60138 100644 --- a/CommonUI/src/Components/Toggle/Toggle.tsx +++ b/CommonUI/src/Components/Toggle/Toggle.tsx @@ -1,116 +1,113 @@ import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - onChange: (value: boolean) => void; - initialValue?: boolean | undefined; - value?: boolean | undefined; - onFocus?: () => void; - onBlur?: () => void; - tabIndex?: number | undefined; - title?: string | undefined; - description?: string | undefined; - error?: string | undefined; - dataTestId?: string | undefined; + onChange: (value: boolean) => void; + initialValue?: boolean | undefined; + value?: boolean | undefined; + onFocus?: () => void; + onBlur?: () => void; + tabIndex?: number | undefined; + title?: string | undefined; + description?: string | undefined; + error?: string | undefined; + dataTestId?: string | undefined; } const Toggle: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isChecked, setIsChecked] = useState<boolean>( - props.initialValue || false - ); + const [isChecked, setIsChecked] = useState<boolean>( + props.initialValue || false, + ); - useEffect(() => { - if (props.value !== undefined) { - if (props.value) { - setIsChecked(true); - } else { - setIsChecked(false); + useEffect(() => { + if (props.value !== undefined) { + if (props.value) { + setIsChecked(true); + } else { + setIsChecked(false); + } + } + }, [props.value]); + + type HandleChangeFunction = (content: boolean) => void; + + const handleChange: HandleChangeFunction = (content: boolean): void => { + setIsChecked(content); + props.onChange(content); + }; + + let buttonClassName: string = + "bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"; + + if (isChecked) { + buttonClassName = + "bg-indigo-600 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"; + } + + let toggleClassName: string = + "translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"; + + if (isChecked) { + toggleClassName = + "translate-x-5 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"; + } + + return ( + <div> + <div className="flex items-center"> + <button + onClick={() => { + if (props.onFocus) { + props.onFocus(); } - } - }, [props.value]); - - type HandleChangeFunction = (content: boolean) => void; - - const handleChange: HandleChangeFunction = (content: boolean): void => { - setIsChecked(content); - props.onChange(content); - }; - - let buttonClassName: string = - 'bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2'; - - if (isChecked) { - buttonClassName = - 'bg-indigo-600 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2'; - } - - let toggleClassName: string = - 'translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'; - - if (isChecked) { - toggleClassName = - 'translate-x-5 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'; - } - - return ( - <div> - <div className="flex items-center"> - <button - onClick={() => { - if (props.onFocus) { - props.onFocus(); - } - if (props.onBlur) { - props.onBlur(); - } - handleChange(!isChecked); - props.onChange(!isChecked); - }} - onFocus={() => { - if (props.onFocus) { - props.onFocus(); - } - }} - data-testid={props.dataTestId} - onBlur={() => { - if (props.onBlur) { - props.onBlur(); - } - }} - tabIndex={props.tabIndex} - type="button" - className={buttonClassName} - role="switch" - aria-checked={isChecked ? 'true' : 'false'} - aria-labelledby="annual-billing-label" - > - <span aria-hidden="true" className={toggleClassName}></span> - </button> - <span className="ml-3" id="annual-billing-label"> - <span className="text-sm font-medium text-gray-900"> - {props.title} - </span> - <span className="text-sm text-gray-500 ml-1"> - {props.description} - </span> - </span> - </div> - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> - )} - </div> - ); + if (props.onBlur) { + props.onBlur(); + } + handleChange(!isChecked); + props.onChange(!isChecked); + }} + onFocus={() => { + if (props.onFocus) { + props.onFocus(); + } + }} + data-testid={props.dataTestId} + onBlur={() => { + if (props.onBlur) { + props.onBlur(); + } + }} + tabIndex={props.tabIndex} + type="button" + className={buttonClassName} + role="switch" + aria-checked={isChecked ? "true" : "false"} + aria-labelledby="annual-billing-label" + > + <span aria-hidden="true" className={toggleClassName}></span> + </button> + <span className="ml-3" id="annual-billing-label"> + <span className="text-sm font-medium text-gray-900"> + {props.title} + </span> + <span className="text-sm text-gray-500 ml-1"> + {props.description} + </span> + </span> + </div> + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default Toggle; diff --git a/CommonUI/src/Components/Tooltip/Tooltip.tsx b/CommonUI/src/Components/Tooltip/Tooltip.tsx index 6592eaac76..b1e7166a1e 100644 --- a/CommonUI/src/Components/Tooltip/Tooltip.tsx +++ b/CommonUI/src/Components/Tooltip/Tooltip.tsx @@ -1,24 +1,24 @@ -import Tippy from '@tippyjs/react'; -import React, { FunctionComponent, ReactElement } from 'react'; -import 'tippy.js/dist/tippy.css'; +import Tippy from "@tippyjs/react"; +import React, { FunctionComponent, ReactElement } from "react"; +import "tippy.js/dist/tippy.css"; export interface ComponentProps { - text: string; - children: ReactElement; + text: string; + children: ReactElement; } const Tooltip: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.text) { - return props.children; - } + if (!props.text) { + return props.children; + } - return ( - <Tippy key={Math.random()} content={<span>{props.text}</span>}> - {props.children} - </Tippy> - ); + return ( + <Tippy key={Math.random()} content={<span>{props.text}</span>}> + {props.children} + </Tippy> + ); }; export default Tooltip; diff --git a/CommonUI/src/Components/TopAlert/TopAlert.tsx b/CommonUI/src/Components/TopAlert/TopAlert.tsx index 72e6c709ba..a8a907ceda 100644 --- a/CommonUI/src/Components/TopAlert/TopAlert.tsx +++ b/CommonUI/src/Components/TopAlert/TopAlert.tsx @@ -1,43 +1,43 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export enum TopAlertType { - INFO = 'bg-indigo-700', - WARNING = 'bg-yellow-700', - DANGER = 'bg-red-700', - SUCCESS = 'bg-green-700', + INFO = "bg-indigo-700", + WARNING = "bg-yellow-700", + DANGER = "bg-red-700", + SUCCESS = "bg-green-700", } export interface ComponentProps { - title: string; - description: ReactElement | string; - alertType?: TopAlertType | undefined; + title: string; + description: ReactElement | string; + alertType?: TopAlertType | undefined; } const TopAlert: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const alertType: TopAlertType = props.alertType || TopAlertType.INFO; + const alertType: TopAlertType = props.alertType || TopAlertType.INFO; - return ( - <div - className={`flex items-center text-center gap-x-6 ${alertType.toString()} px-6 py-2.5 sm:px-3.5`} - > - <div className="text-sm leading-6 text-white w-full"> - <div className="w-full"> - <strong className="font-semibold">{props.title}</strong> -  -  - {props.description}    - {/** Uncomment the follwing line if you need a button on top alert */} - {/* <a + return ( + <div + className={`flex items-center text-center gap-x-6 ${alertType.toString()} px-6 py-2.5 sm:px-3.5`} + > + <div className="text-sm leading-6 text-white w-full"> + <div className="w-full"> + <strong className="font-semibold">{props.title}</strong> +  -  + {props.description}    + {/** Uncomment the follwing line if you need a button on top alert */} + {/* <a href="#" className="flex-none rounded-full bg-gray-200 px-3.5 py-1 text-sm font-semibold text-gray-900 shadow-sm hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900" > Go to User Dashboard <span aria-hidden="true">→</span> </a> */} - </div> - </div> </div> - ); + </div> + </div> + ); }; export default TopAlert; diff --git a/CommonUI/src/Components/TopSection/TopSection.tsx b/CommonUI/src/Components/TopSection/TopSection.tsx index 13a3aa74a4..a5a16facad 100644 --- a/CommonUI/src/Components/TopSection/TopSection.tsx +++ b/CommonUI/src/Components/TopSection/TopSection.tsx @@ -1,23 +1,23 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - header: ReactElement | undefined; - navbar: ReactElement | undefined; - className?: string | undefined; - hideHeader?: boolean | undefined; + header: ReactElement | undefined; + navbar: ReactElement | undefined; + className?: string | undefined; + hideHeader?: boolean | undefined; } const TopSection: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <header className={props.className || 'bg-white shadow'}> - <div className="w-full px-2 sm:px-4 lg:divide-y lg:divide-gray-200 lg:px-8"> - {!props.hideHeader && props.header} - {props.navbar} - </div> - </header> - ); + return ( + <header className={props.className || "bg-white shadow"}> + <div className="w-full px-2 sm:px-4 lg:divide-y lg:divide-gray-200 lg:px-8"> + {!props.hideHeader && props.header} + {props.navbar} + </div> + </header> + ); }; export default TopSection; diff --git a/CommonUI/src/Components/Types/FieldType.ts b/CommonUI/src/Components/Types/FieldType.ts index c0cb226566..e717cf8932 100644 --- a/CommonUI/src/Components/Types/FieldType.ts +++ b/CommonUI/src/Components/Types/FieldType.ts @@ -1,38 +1,38 @@ enum FieldType { - ObjectID = 'ObjectID', - Name = 'Name', - File = 'File', - Percent = 'Percent', - ImageFile = 'ImageFile', - Hostname = 'Hostname', - URL = 'URL', - Phone = 'Phone', - Route = 'Route', - Number = 'Number', - Password = 'Password', - Dropdown = 'Dropdown', - Text = 'Text', - Email = 'Email', - Date = 'Date', - DateTime = 'DateTime', - LongText = 'LongText', - Color = 'Color', - Port = 'Port', - HiddenText = 'HiddenText', - Actions = 'Actions', - Boolean = 'Boolean', - Entity = 'Entity', - EntityArray = 'EntityArray', - Markdown = 'Markdown', - HTML = 'HTML', - CSS = 'CSS', - JavaScript = 'JavaScript', - DictionaryOfStrings = 'DictionaryOfStrings', - JSON = 'JSON', - USDCents = 'USDCents', - Element = 'Element', - Minutes = 'Minutes', - ArrayOfText = 'ArrayOfText', + ObjectID = "ObjectID", + Name = "Name", + File = "File", + Percent = "Percent", + ImageFile = "ImageFile", + Hostname = "Hostname", + URL = "URL", + Phone = "Phone", + Route = "Route", + Number = "Number", + Password = "Password", + Dropdown = "Dropdown", + Text = "Text", + Email = "Email", + Date = "Date", + DateTime = "DateTime", + LongText = "LongText", + Color = "Color", + Port = "Port", + HiddenText = "HiddenText", + Actions = "Actions", + Boolean = "Boolean", + Entity = "Entity", + EntityArray = "EntityArray", + Markdown = "Markdown", + HTML = "HTML", + CSS = "CSS", + JavaScript = "JavaScript", + DictionaryOfStrings = "DictionaryOfStrings", + JSON = "JSON", + USDCents = "USDCents", + Element = "Element", + Minutes = "Minutes", + ArrayOfText = "ArrayOfText", } export default FieldType; diff --git a/CommonUI/src/Components/Workflow/ArgumentsForm.tsx b/CommonUI/src/Components/Workflow/ArgumentsForm.tsx index 870bbb96b8..b02b2fd0ba 100644 --- a/CommonUI/src/Components/Workflow/ArgumentsForm.tsx +++ b/CommonUI/src/Components/Workflow/ArgumentsForm.tsx @@ -1,204 +1,177 @@ -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import BasicForm, { FormProps } from '../Forms/BasicForm'; -import FormValues from '../Forms/Types/FormValues'; -import ComponentValuePickerModal from './ComponentValuePickerModal'; -import { componentInputTypeToFormFieldType } from './Utils'; -import VariableModal from './VariableModal'; -import Dictionary from 'Common/Types/Dictionary'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { Argument, NodeDataProp } from 'Common/Types/Workflow/Component'; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import BasicForm, { FormProps } from "../Forms/BasicForm"; +import FormValues from "../Forms/Types/FormValues"; +import ComponentValuePickerModal from "./ComponentValuePickerModal"; +import { componentInputTypeToFormFieldType } from "./Utils"; +import VariableModal from "./VariableModal"; +import Dictionary from "Common/Types/Dictionary"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { Argument, NodeDataProp } from "Common/Types/Workflow/Component"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useRef, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useRef, + useState, +} from "react"; export interface ComponentProps { - component: NodeDataProp; - onHasFormValidationErrors: (values: Dictionary<boolean>) => void; - workflowId: ObjectID; - graphComponents: Array<NodeDataProp>; - onFormChange: (value: NodeDataProp) => void; + component: NodeDataProp; + onHasFormValidationErrors: (values: Dictionary<boolean>) => void; + workflowId: ObjectID; + graphComponents: Array<NodeDataProp>; + onFormChange: (value: NodeDataProp) => void; } const ArgumentsForm: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const formRef: any = useRef<FormProps<FormValues<JSONObject>>>(null); - const [component, setComponent] = useState<NodeDataProp>(props.component); - const [showVariableModal, setShowVariableModal] = useState<boolean>(false); - const [showComponentPickerModal, setShowComponentPickerModal] = - useState<boolean>(false); - const [hasFormValidationErrors, setHasFormValidationErrors] = useState< - Dictionary<boolean> - >({}); + const formRef: any = useRef<FormProps<FormValues<JSONObject>>>(null); + const [component, setComponent] = useState<NodeDataProp>(props.component); + const [showVariableModal, setShowVariableModal] = useState<boolean>(false); + const [showComponentPickerModal, setShowComponentPickerModal] = + useState<boolean>(false); + const [hasFormValidationErrors, setHasFormValidationErrors] = useState< + Dictionary<boolean> + >({}); - const [selectedArgId, setSelectedArgId] = useState<string>(''); + const [selectedArgId, setSelectedArgId] = useState<string>(""); - useEffect(() => { - props.onHasFormValidationErrors(hasFormValidationErrors); - }, [hasFormValidationErrors]); + useEffect(() => { + props.onHasFormValidationErrors(hasFormValidationErrors); + }, [hasFormValidationErrors]); - useEffect(() => { - props.onFormChange(component); - }, [component]); + useEffect(() => { + props.onFormChange(component); + }, [component]); - return ( - <div className="mb-3 mt-3"> - <div className="mt-5 mb-5"> - <h2 className="text-base font-medium text-gray-500"> - Arguments - </h2> - <p className="text-sm font-medium text-gray-400 mb-5"> - Arguments for this component - </p> - {component.metadata.arguments && - component.metadata.arguments.length === 0 && ( - <ErrorMessage - error={ - 'This component does not take any arguments.' - } - /> - )} - {component.metadata.arguments && - component.metadata.arguments.length > 0 && ( - <BasicForm - hideSubmitButton={true} - ref={formRef} - initialValues={{ - ...(component.arguments || {}), + return ( + <div className="mb-3 mt-3"> + <div className="mt-5 mb-5"> + <h2 className="text-base font-medium text-gray-500">Arguments</h2> + <p className="text-sm font-medium text-gray-400 mb-5"> + Arguments for this component + </p> + {component.metadata.arguments && + component.metadata.arguments.length === 0 && ( + <ErrorMessage + error={"This component does not take any arguments."} + /> + )} + {component.metadata.arguments && + component.metadata.arguments.length > 0 && ( + <BasicForm + hideSubmitButton={true} + ref={formRef} + initialValues={{ + ...(component.arguments || {}), + }} + onChange={(values: FormValues<JSONObject>) => { + setComponent({ + ...component, + arguments: { + ...((component.arguments as JSONObject) || {}), + ...((values as JSONObject) || {}), + }, + }); + }} + onFormValidationErrorChanged={(hasError: boolean) => { + if (hasFormValidationErrors["id"] !== hasError) { + setHasFormValidationErrors({ + ...hasFormValidationErrors, + id: hasError, + }); + } + }} + fields={ + component.metadata.arguments && + component.metadata.arguments.map((arg: Argument) => { + return { + title: `${arg.name}`, + footerElement: ( + <div className="text-gray-500"> + <p className="text-sm"> + Pick this value from other{" "} + <button + className="underline text-blue-500 hover:text-blue-600 cursor-pointer" + onClick={() => { + setSelectedArgId(arg.id); + setShowComponentPickerModal(true); }} - onChange={(values: FormValues<JSONObject>) => { - setComponent({ - ...component, - arguments: { - ...((component.arguments as JSONObject) || - {}), - ...((values as JSONObject) || {}), - }, - }); + > + component + </button>{" "} + or from{" "} + <button + className="underline text-blue-500 hover:text-blue-600 cursor-pointer" + onClick={() => { + setSelectedArgId(arg.id); + setShowVariableModal(true); }} - onFormValidationErrorChanged={( - hasError: boolean - ) => { - if ( - hasFormValidationErrors['id'] !== hasError - ) { - setHasFormValidationErrors({ - ...hasFormValidationErrors, - id: hasError, - }); - } - }} - fields={ - component.metadata.arguments && - component.metadata.arguments.map( - (arg: Argument) => { - return { - title: `${arg.name}`, - footerElement: ( - <div className="text-gray-500"> - <p className="text-sm"> - Pick this value from - other{' '} - <button - className="underline text-blue-500 hover:text-blue-600 cursor-pointer" - onClick={() => { - setSelectedArgId( - arg.id - ); - setShowComponentPickerModal( - true - ); - }} - > - component - </button>{' '} - or from{' '} - <button - className="underline text-blue-500 hover:text-blue-600 cursor-pointer" - onClick={() => { - setSelectedArgId( - arg.id - ); - setShowVariableModal( - true - ); - }} - > - variable. - </button> - </p> - </div> - ), - description: `${ - arg.required - ? 'Required' - : 'Optional' - }. ${arg.description}`, - field: { - [arg.id]: true, - }, - required: arg.required, - placeholder: arg.placeholder, - ...componentInputTypeToFormFieldType( - arg.type, - component.arguments && - component.arguments[arg.id] - ? component.arguments[ - arg.id - ] - : null - ), - }; - } - ) - } - /> - )} - </div> - {showVariableModal && ( - <VariableModal - workflowId={props.workflowId} - onClose={() => { - setShowVariableModal(false); - }} - onSave={(variableId: string) => { - setShowVariableModal(false); - formRef.current.setFieldValue( - selectedArgId, - (component.arguments && - component.arguments[selectedArgId] - ? component.arguments[selectedArgId] - : '') + variableId - ); - }} - /> - )} + > + variable. + </button> + </p> + </div> + ), + description: `${ + arg.required ? "Required" : "Optional" + }. ${arg.description}`, + field: { + [arg.id]: true, + }, + required: arg.required, + placeholder: arg.placeholder, + ...componentInputTypeToFormFieldType( + arg.type, + component.arguments && component.arguments[arg.id] + ? component.arguments[arg.id] + : null, + ), + }; + }) + } + /> + )} + </div> + {showVariableModal && ( + <VariableModal + workflowId={props.workflowId} + onClose={() => { + setShowVariableModal(false); + }} + onSave={(variableId: string) => { + setShowVariableModal(false); + formRef.current.setFieldValue( + selectedArgId, + (component.arguments && component.arguments[selectedArgId] + ? component.arguments[selectedArgId] + : "") + variableId, + ); + }} + /> + )} - {showComponentPickerModal && ( - <ComponentValuePickerModal - components={props.graphComponents} - onClose={() => { - setShowComponentPickerModal(false); - }} - onSave={(returnValuePath: string) => { - setShowComponentPickerModal(false); - formRef.current.setFieldValue( - selectedArgId, - (component.arguments && - component.arguments[selectedArgId] - ? component.arguments[selectedArgId] - : '') + returnValuePath - ); - }} - /> - )} - </div> - ); + {showComponentPickerModal && ( + <ComponentValuePickerModal + components={props.graphComponents} + onClose={() => { + setShowComponentPickerModal(false); + }} + onSave={(returnValuePath: string) => { + setShowComponentPickerModal(false); + formRef.current.setFieldValue( + selectedArgId, + (component.arguments && component.arguments[selectedArgId] + ? component.arguments[selectedArgId] + : "") + returnValuePath, + ); + }} + /> + )} + </div> + ); }; export default ArgumentsForm; diff --git a/CommonUI/src/Components/Workflow/Component.tsx b/CommonUI/src/Components/Workflow/Component.tsx index e48a22a038..a65e4d0b80 100644 --- a/CommonUI/src/Components/Workflow/Component.tsx +++ b/CommonUI/src/Components/Workflow/Component.tsx @@ -1,343 +1,321 @@ -import Icon, { ThickProp } from '../Icon/Icon'; -import Pill from '../Pill/Pill'; -import Tooltip from '../Tooltip/Tooltip'; -import { Green } from 'Common/Types/BrandColors'; -import IconProp from 'Common/Types/Icon/IconProp'; +import Icon, { ThickProp } from "../Icon/Icon"; +import Pill from "../Pill/Pill"; +import Tooltip from "../Tooltip/Tooltip"; +import { Green } from "Common/Types/BrandColors"; +import IconProp from "Common/Types/Icon/IconProp"; import { - ComponentType, - NodeDataProp, - NodeType, - Port, -} from 'Common/Types/Workflow/Component'; -import React, { FunctionComponent, useState } from 'react'; -import { Connection, Handle, Position } from 'reactflow'; + ComponentType, + NodeDataProp, + NodeType, + Port, +} from "Common/Types/Workflow/Component"; +import React, { FunctionComponent, useState } from "react"; +import { Connection, Handle, Position } from "reactflow"; export interface ComponentProps { - data: NodeDataProp; - selected: boolean; + data: NodeDataProp; + selected: boolean; } const Node: FunctionComponent<ComponentProps> = (props: ComponentProps) => { - const [isHovering, setIsHovering] = useState<boolean>(false); + const [isHovering, setIsHovering] = useState<boolean>(false); - let textColor: string = '#6b7280'; - let descriptionColor: string = '#6b7280'; + let textColor: string = "#6b7280"; + let descriptionColor: string = "#6b7280"; + + if (isHovering) { + textColor = "#111827"; + descriptionColor = "#111827"; + } + + let componentStyle: React.CSSProperties = { + width: "15rem", + height: "10rem", + padding: "1rem", + borderColor: props.selected ? "#6366f1" : textColor, + alignItems: "center", + borderRadius: "0.25rem", + borderWidth: "2px", + backgroundColor: "white", + display: "inline-block", + verticalAlign: "middle", + boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", + }; + + let handleStyle: React.CSSProperties = { + background: "#6b7280", + height: "0.75rem", + width: "0.75rem", + }; + + type GetPortPositionFunction = ( + portCount: number, + totalPorts: number, + isLabel: boolean, + ) => React.CSSProperties; + + const getPortPosition: GetPortPositionFunction = ( + portCount: number, + totalPorts: number, + isLabel: boolean, + ): React.CSSProperties => { + if (portCount === 1 && totalPorts === 1) { + return isLabel ? { left: 100 } : {}; + } + + if (portCount === 1 && totalPorts === 2) { + return { left: isLabel ? 70 : 80 }; + } + + if (portCount === 2 && totalPorts === 2) { + return { left: isLabel ? 150 : 160 }; + } + + if (portCount === 1 && totalPorts === 3) { + return { left: isLabel ? 70 : 80 }; + } + + if (portCount === 2 && totalPorts === 3) { + return isLabel ? { left: 100 } : {}; + } + + if (portCount === 3 && totalPorts === 3) { + return { left: isLabel ? 150 : 160 }; + } + + // default + return {}; + }; + + if (props.data.nodeType === NodeType.PlaceholderNode) { + handleStyle = { + background: "#cbd5e1", + height: "0.75rem", + width: "0.75rem", + }; + + componentStyle = { + borderStyle: "dashed", + width: "15rem", + height: "8rem", + padding: "1rem", + display: "inline-block", + alignItems: "center", + verticalAlign: "middle", + borderColor: isHovering ? "#94a3b8" : "#cbd5e1", + borderRadius: "0.25rem", + borderWidth: "2px", + backgroundColor: "white", + }; + + textColor = "#cbd5e1"; + descriptionColor = "#cbd5e1"; if (isHovering) { - textColor = '#111827'; - descriptionColor = '#111827'; + textColor = "#94a3b8"; + descriptionColor = "#94a3b8"; } + } - let componentStyle: React.CSSProperties = { - width: '15rem', - height: '10rem', - padding: '1rem', - borderColor: props.selected ? '#6366f1' : textColor, - alignItems: 'center', - borderRadius: '0.25rem', - borderWidth: '2px', - backgroundColor: 'white', - display: 'inline-block', - verticalAlign: 'middle', - boxShadow: - '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', - }; - - let handleStyle: React.CSSProperties = { - background: '#6b7280', - height: '0.75rem', - width: '0.75rem', - }; - - type GetPortPositionFunction = ( - portCount: number, - totalPorts: number, - isLabel: boolean - ) => React.CSSProperties; - - const getPortPosition: GetPortPositionFunction = ( - portCount: number, - totalPorts: number, - isLabel: boolean - ): React.CSSProperties => { - if (portCount === 1 && totalPorts === 1) { - return isLabel ? { left: 100 } : {}; + return ( + <div + className="cursor-pointer" + onMouseOver={() => { + setIsHovering(true); + }} + onMouseOut={() => { + setIsHovering(false); + }} + style={{ + ...componentStyle, + height: props.data.id ? "12rem" : "10rem", + }} + onClick={() => { + if (props.data.onClick) { + props.data.onClick(props.data); } - - if (portCount === 1 && totalPorts === 2) { - return { left: isLabel ? 70 : 80 }; - } - - if (portCount === 2 && totalPorts === 2) { - return { left: isLabel ? 150 : 160 }; - } - - if (portCount === 1 && totalPorts === 3) { - return { left: isLabel ? 70 : 80 }; - } - - if (portCount === 2 && totalPorts === 3) { - return isLabel ? { left: 100 } : {}; - } - - if (portCount === 3 && totalPorts === 3) { - return { left: isLabel ? 150 : 160 }; - } - - // default - return {}; - }; - - if (props.data.nodeType === NodeType.PlaceholderNode) { - handleStyle = { - background: '#cbd5e1', - height: '0.75rem', - width: '0.75rem', - }; - - componentStyle = { - borderStyle: 'dashed', - width: '15rem', - height: '8rem', - padding: '1rem', - display: 'inline-block', - alignItems: 'center', - verticalAlign: 'middle', - borderColor: isHovering ? '#94a3b8' : '#cbd5e1', - borderRadius: '0.25rem', - borderWidth: '2px', - backgroundColor: 'white', - }; - - textColor = '#cbd5e1'; - descriptionColor = '#cbd5e1'; - - if (isHovering) { - textColor = '#94a3b8'; - descriptionColor = '#94a3b8'; - } - } - - return ( - <div - className="cursor-pointer" - onMouseOver={() => { - setIsHovering(true); - }} - onMouseOut={() => { - setIsHovering(false); - }} + }} + > + <div className="flex justify-center"> + {props.data.metadata.componentType === ComponentType.Trigger && + props.data.nodeType !== NodeType.PlaceholderNode && + !props.data.isPreview && <Pill text="Trigger" color={Green} />} + </div> + {!props.data.isPreview && + props.data.error && + props.data.nodeType !== NodeType.PlaceholderNode && ( + <div style={{ - ...componentStyle, - height: props.data.id ? '12rem' : '10rem', + width: "20px", + height: "20px", + borderRadius: "100px", + color: "#ef4444", + position: "absolute", + top: "0px", + left: "220px", + cursor: "pointer", }} - onClick={() => { - if (props.data.onClick) { - props.data.onClick(props.data); - } - }} - > - <div className="flex justify-center"> - {props.data.metadata.componentType === ComponentType.Trigger && - props.data.nodeType !== NodeType.PlaceholderNode && - !props.data.isPreview && ( - <Pill text="Trigger" color={Green} /> - )} - </div> - {!props.data.isPreview && - props.data.error && - props.data.nodeType !== NodeType.PlaceholderNode && ( - <div - style={{ - width: '20px', - height: '20px', - borderRadius: '100px', - color: '#ef4444', - position: 'absolute', - top: '0px', - left: '220px', - cursor: 'pointer', - }} - onClick={() => {}} - > - <Icon - icon={IconProp.Alert} - style={{ - color: '#ef4444', - width: '1rem', - height: '1rem', - textAlign: 'center', - margin: 'auto', - marginTop: '2px', - }} - thick={ThickProp.Thick} - /> - </div> - )} + onClick={() => {}} + > + <Icon + icon={IconProp.Alert} + style={{ + color: "#ef4444", + width: "1rem", + height: "1rem", + textAlign: "center", + margin: "auto", + marginTop: "2px", + }} + thick={ThickProp.Thick} + /> + </div> + )} - {!props.data.isPreview && - props.data.metadata.componentType !== ComponentType.Trigger && ( - <div> - {props.data.metadata.inPorts && - props.data.metadata.inPorts.length > 0 && - props.data.metadata.inPorts.map( - (port: Port, i: number) => { - return ( - <Handle - key={i} - type="target" - id={port.id} - onConnect={( - _params: Connection - ) => {}} - isConnectable={true} - position={Position.Top} - style={{ - ...handleStyle, - ...getPortPosition( - i + 1, - props.data.metadata.inPorts - .length, - false - ), - }} - /> - ); - } - )} - </div> - )} - - <div - style={{ - width: '100%', - display: 'flex', - justifyContent: 'center', - }} - > - <div + {!props.data.isPreview && + props.data.metadata.componentType !== ComponentType.Trigger && ( + <div> + {props.data.metadata.inPorts && + props.data.metadata.inPorts.length > 0 && + props.data.metadata.inPorts.map((port: Port, i: number) => { + return ( + <Handle + key={i} + type="target" + id={port.id} + onConnect={(_params: Connection) => {}} + isConnectable={true} + position={Position.Top} style={{ - margin: 'auto', - marginTop: props.data.metadata.iconProp - ? '0.5rem' - : '1rem', + ...handleStyle, + ...getPortPosition( + i + 1, + props.data.metadata.inPorts.length, + false, + ), }} - > - {props.data.metadata.iconProp && ( - <Icon - icon={props.data.metadata.iconProp} - style={{ - color: textColor, - width: '1.5rem', - height: '1.5rem', - textAlign: 'center', - margin: 'auto', - }} - /> - )} - <p - style={{ - color: textColor, - fontSize: '0.875rem', - lineHeight: '1.25rem', - textAlign: 'center', - marginTop: '6px', - }} - > - {props.data.metadata.title} - </p> - {!props.data.isPreview && props.data.id && ( - <p - style={{ - color: descriptionColor, - fontSize: '0.875rem', - textAlign: 'center', - }} - > - ({props.data.id.trim()}) - </p> - )} - <p - style={{ - color: descriptionColor, - fontSize: '0.775rem', - lineHeight: '1.0rem', - textAlign: 'center', - marginTop: '6px', - }} - > - {props.data.metadata.description} - </p> - </div> - </div> + /> + ); + })} + </div> + )} - {!props.data.isPreview && - props.data.nodeType !== NodeType.PlaceholderNode && ( - <> - <div> - {props.data.metadata.outPorts && - props.data.metadata.outPorts.length > 0 && - props.data.metadata.outPorts.map( - (port: Port, i: number) => { - return ( - <Handle - key={i} - type="source" - id={port.id} - onConnect={( - _params: Connection - ) => {}} - isConnectable={true} - position={Position.Bottom} - style={{ - ...handleStyle, - ...getPortPosition( - i + 1, - props.data.metadata - .outPorts.length, - false - ), - }} - /> - ); - } - )} - </div> - <div> - {props.data.metadata.outPorts && - props.data.metadata.outPorts.length > 0 && - props.data.metadata.outPorts.map( - (port: Port, i: number) => { - return ( - <Tooltip - key={i} - text={port.description || ''} - > - <div - key={i} - className="text-sm text-gray-400 absolute" - style={{ - bottom: '10px', - ...getPortPosition( - i + 1, - props.data.metadata - .outPorts - .length, - true - ), - }} - > - {port.title} - </div> - </Tooltip> - ); - } - )} - </div> - </> - )} + <div + style={{ + width: "100%", + display: "flex", + justifyContent: "center", + }} + > + <div + style={{ + margin: "auto", + marginTop: props.data.metadata.iconProp ? "0.5rem" : "1rem", + }} + > + {props.data.metadata.iconProp && ( + <Icon + icon={props.data.metadata.iconProp} + style={{ + color: textColor, + width: "1.5rem", + height: "1.5rem", + textAlign: "center", + margin: "auto", + }} + /> + )} + <p + style={{ + color: textColor, + fontSize: "0.875rem", + lineHeight: "1.25rem", + textAlign: "center", + marginTop: "6px", + }} + > + {props.data.metadata.title} + </p> + {!props.data.isPreview && props.data.id && ( + <p + style={{ + color: descriptionColor, + fontSize: "0.875rem", + textAlign: "center", + }} + > + ({props.data.id.trim()}) + </p> + )} + <p + style={{ + color: descriptionColor, + fontSize: "0.775rem", + lineHeight: "1.0rem", + textAlign: "center", + marginTop: "6px", + }} + > + {props.data.metadata.description} + </p> </div> - ); + </div> + + {!props.data.isPreview && + props.data.nodeType !== NodeType.PlaceholderNode && ( + <> + <div> + {props.data.metadata.outPorts && + props.data.metadata.outPorts.length > 0 && + props.data.metadata.outPorts.map((port: Port, i: number) => { + return ( + <Handle + key={i} + type="source" + id={port.id} + onConnect={(_params: Connection) => {}} + isConnectable={true} + position={Position.Bottom} + style={{ + ...handleStyle, + ...getPortPosition( + i + 1, + props.data.metadata.outPorts.length, + false, + ), + }} + /> + ); + })} + </div> + <div> + {props.data.metadata.outPorts && + props.data.metadata.outPorts.length > 0 && + props.data.metadata.outPorts.map((port: Port, i: number) => { + return ( + <Tooltip key={i} text={port.description || ""}> + <div + key={i} + className="text-sm text-gray-400 absolute" + style={{ + bottom: "10px", + ...getPortPosition( + i + 1, + props.data.metadata.outPorts.length, + true, + ), + }} + > + {port.title} + </div> + </Tooltip> + ); + })} + </div> + </> + )} + </div> + ); }; export default Node; diff --git a/CommonUI/src/Components/Workflow/ComponentArgumentsViewer.tsx b/CommonUI/src/Components/Workflow/ComponentArgumentsViewer.tsx index 0c958a3a35..3301aa39b2 100644 --- a/CommonUI/src/Components/Workflow/ComponentArgumentsViewer.tsx +++ b/CommonUI/src/Components/Workflow/ComponentArgumentsViewer.tsx @@ -1,72 +1,61 @@ -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import Pill from '../Pill/Pill'; -import { Black } from 'Common/Types/BrandColors'; -import { Argument } from 'Common/Types/Workflow/Component'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import Pill from "../Pill/Pill"; +import { Black } from "Common/Types/BrandColors"; +import { Argument } from "Common/Types/Workflow/Component"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - arguments: Array<Argument>; - name: string; - description: string; + arguments: Array<Argument>; + name: string; + description: string; } const ComponentArgumentViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mt-5 mb-5"> - <h2 className="text-base font-medium text-gray-500"> - {props.name} - </h2> - <p className="text-sm font-medium text-gray-400"> - {props.description} - </p> - {props.arguments && props.arguments.length === 0 && ( - <ErrorMessage - error={'This component does not take any arguments.'} - /> - )} - <div className="mt-3"> - {props.arguments && - props.arguments.length > 0 && - props.arguments.map((argument: Argument, i: number) => { - return ( - <div - key={i} - className="mt-2 mb-2 relative flex items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2" - > - <div className="min-w-0 flex-1 flex justify-between"> - <div className="focus:outline-none"> - <span - className="absolute inset-0" - aria-hidden="true" - ></span> - <p className="text-sm font-medium text-gray-900"> - {argument.name}{' '} - <span className="text-gray-500 font-normal"> - (ID: {argument.id}) - </span> - </p> - <p className="truncate text-sm text-gray-500"> - {argument.required - ? 'Required. ' - : 'Optional. '} - {argument.description} - </p> - </div> - <div> - <Pill - color={Black} - text={argument.type} - /> - </div> - </div> - </div> - ); - })} - </div> - </div> - ); + return ( + <div className="mt-5 mb-5"> + <h2 className="text-base font-medium text-gray-500">{props.name}</h2> + <p className="text-sm font-medium text-gray-400">{props.description}</p> + {props.arguments && props.arguments.length === 0 && ( + <ErrorMessage error={"This component does not take any arguments."} /> + )} + <div className="mt-3"> + {props.arguments && + props.arguments.length > 0 && + props.arguments.map((argument: Argument, i: number) => { + return ( + <div + key={i} + className="mt-2 mb-2 relative flex items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2" + > + <div className="min-w-0 flex-1 flex justify-between"> + <div className="focus:outline-none"> + <span + className="absolute inset-0" + aria-hidden="true" + ></span> + <p className="text-sm font-medium text-gray-900"> + {argument.name}{" "} + <span className="text-gray-500 font-normal"> + (ID: {argument.id}) + </span> + </p> + <p className="truncate text-sm text-gray-500"> + {argument.required ? "Required. " : "Optional. "} + {argument.description} + </p> + </div> + <div> + <Pill color={Black} text={argument.type} /> + </div> + </div> + </div> + ); + })} + </div> + </div> + ); }; export default ComponentArgumentViewer; diff --git a/CommonUI/src/Components/Workflow/ComponentPortViewer.tsx b/CommonUI/src/Components/Workflow/ComponentPortViewer.tsx index 8b6f570e62..b16585b767 100644 --- a/CommonUI/src/Components/Workflow/ComponentPortViewer.tsx +++ b/CommonUI/src/Components/Workflow/ComponentPortViewer.tsx @@ -1,61 +1,55 @@ -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import { Port } from 'Common/Types/Workflow/Component'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import { Port } from "Common/Types/Workflow/Component"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - ports: Array<Port>; - name: string; - description: string; + ports: Array<Port>; + name: string; + description: string; } const ComponentPortViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mt-5 mb-5"> - <h2 className="text-base font-medium text-gray-500"> - {props.name} - </h2> - <p className="text-sm font-medium text-gray-400"> - {props.description} - </p> - {props.ports && props.ports.length === 0 && ( - <ErrorMessage - error={'This component does not have any ports.'} - /> - )} - <div className="mt-3"> - {props.ports && - props.ports.length > 0 && - props.ports.map((port: Port, i: number) => { - return ( - <div - key={i} - className="mt-2 mb-2 relative flex items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2" - > - <div className="min-w-0 flex-1"> - <div className="focus:outline-none"> - <span - className="absolute inset-0" - aria-hidden="true" - ></span> - <p className="text-sm font-medium text-gray-900"> - {port.title}{' '} - <span className="text-gray-500 font-normal"> - (ID: {port.id}) - </span> - </p> - <p className="truncate text-sm text-gray-500"> - {port.description} - </p> - </div> - </div> - </div> - ); - })} - </div> - </div> - ); + return ( + <div className="mt-5 mb-5"> + <h2 className="text-base font-medium text-gray-500">{props.name}</h2> + <p className="text-sm font-medium text-gray-400">{props.description}</p> + {props.ports && props.ports.length === 0 && ( + <ErrorMessage error={"This component does not have any ports."} /> + )} + <div className="mt-3"> + {props.ports && + props.ports.length > 0 && + props.ports.map((port: Port, i: number) => { + return ( + <div + key={i} + className="mt-2 mb-2 relative flex items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2" + > + <div className="min-w-0 flex-1"> + <div className="focus:outline-none"> + <span + className="absolute inset-0" + aria-hidden="true" + ></span> + <p className="text-sm font-medium text-gray-900"> + {port.title}{" "} + <span className="text-gray-500 font-normal"> + (ID: {port.id}) + </span> + </p> + <p className="truncate text-sm text-gray-500"> + {port.description} + </p> + </div> + </div> + </div> + ); + })} + </div> + </div> + ); }; export default ComponentPortViewer; diff --git a/CommonUI/src/Components/Workflow/ComponentReturnValueViewer.tsx b/CommonUI/src/Components/Workflow/ComponentReturnValueViewer.tsx index 5d0a2d8318..e4425afa9f 100644 --- a/CommonUI/src/Components/Workflow/ComponentReturnValueViewer.tsx +++ b/CommonUI/src/Components/Workflow/ComponentReturnValueViewer.tsx @@ -1,71 +1,60 @@ -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import Pill from '../Pill/Pill'; -import { Black } from 'Common/Types/BrandColors'; -import { ReturnValue } from 'Common/Types/Workflow/Component'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import Pill from "../Pill/Pill"; +import { Black } from "Common/Types/BrandColors"; +import { ReturnValue } from "Common/Types/Workflow/Component"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - returnValues: Array<ReturnValue>; - name: string; - description: string; + returnValues: Array<ReturnValue>; + name: string; + description: string; } const ComponentReturnValueViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mt-5 mb-5"> - <h2 className="text-base font-medium text-gray-500"> - {props.name} - </h2> - <p className="text-sm font-medium text-gray-400"> - {props.description} - </p> - {props.returnValues && props.returnValues.length === 0 && ( - <ErrorMessage - error={'This component does not return any value.'} - /> - )} - <div className="mt-3"> - {props.returnValues && - props.returnValues.length > 0 && - props.returnValues.map( - (returnValue: ReturnValue, i: number) => { - return ( - <div - key={i} - className="mt-2 mb-2 relative flex items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2" - > - <div className="min-w-0 flex-1 flex justify-between"> - <div className="focus:outline-none"> - <span - className="absolute inset-0" - aria-hidden="true" - ></span> - <p className="text-sm font-medium text-gray-900"> - {returnValue.name}{' '} - <span className="text-gray-500 font-normal"> - (ID: {returnValue.id}) - </span> - </p> - <p className="truncate text-sm text-gray-500"> - {returnValue.description} - </p> - </div> - <div> - <Pill - color={Black} - text={returnValue.type} - /> - </div> - </div> - </div> - ); - } - )} - </div> - </div> - ); + return ( + <div className="mt-5 mb-5"> + <h2 className="text-base font-medium text-gray-500">{props.name}</h2> + <p className="text-sm font-medium text-gray-400">{props.description}</p> + {props.returnValues && props.returnValues.length === 0 && ( + <ErrorMessage error={"This component does not return any value."} /> + )} + <div className="mt-3"> + {props.returnValues && + props.returnValues.length > 0 && + props.returnValues.map((returnValue: ReturnValue, i: number) => { + return ( + <div + key={i} + className="mt-2 mb-2 relative flex items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2" + > + <div className="min-w-0 flex-1 flex justify-between"> + <div className="focus:outline-none"> + <span + className="absolute inset-0" + aria-hidden="true" + ></span> + <p className="text-sm font-medium text-gray-900"> + {returnValue.name}{" "} + <span className="text-gray-500 font-normal"> + (ID: {returnValue.id}) + </span> + </p> + <p className="truncate text-sm text-gray-500"> + {returnValue.description} + </p> + </div> + <div> + <Pill color={Black} text={returnValue.type} /> + </div> + </div> + </div> + ); + })} + </div> + </div> + ); }; export default ComponentReturnValueViewer; diff --git a/CommonUI/src/Components/Workflow/ComponentSettingsModal.tsx b/CommonUI/src/Components/Workflow/ComponentSettingsModal.tsx index 02b74fbc5f..57b6ab68b3 100644 --- a/CommonUI/src/Components/Workflow/ComponentSettingsModal.tsx +++ b/CommonUI/src/Components/Workflow/ComponentSettingsModal.tsx @@ -1,170 +1,168 @@ -import Button, { ButtonStyleType } from '../Button/Button'; -import Divider from '../Divider/Divider'; -import BasicForm from '../Forms/BasicForm'; -import FormFieldSchemaType from '../Forms/Types/FormFieldSchemaType'; -import FormValues from '../Forms/Types/FormValues'; -import ConfirmModal from '../Modal/ConfirmModal'; -import SideOver from '../SideOver/SideOver'; -import ArgumentsForm from './ArgumentsForm'; -import ComponentPortViewer from './ComponentPortViewer'; -import ComponentReturnValueViewer from './ComponentReturnValueViewer'; -import DocumentationViewer from './DocumentationViewer'; -import Dictionary from 'Common/Types/Dictionary'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { NodeDataProp } from 'Common/Types/Workflow/Component'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import Button, { ButtonStyleType } from "../Button/Button"; +import Divider from "../Divider/Divider"; +import BasicForm from "../Forms/BasicForm"; +import FormFieldSchemaType from "../Forms/Types/FormFieldSchemaType"; +import FormValues from "../Forms/Types/FormValues"; +import ConfirmModal from "../Modal/ConfirmModal"; +import SideOver from "../SideOver/SideOver"; +import ArgumentsForm from "./ArgumentsForm"; +import ComponentPortViewer from "./ComponentPortViewer"; +import ComponentReturnValueViewer from "./ComponentReturnValueViewer"; +import DocumentationViewer from "./DocumentationViewer"; +import Dictionary from "Common/Types/Dictionary"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { NodeDataProp } from "Common/Types/Workflow/Component"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - title: string; - description: string; - onClose: () => void; - onSave: (component: NodeDataProp) => void; - onDelete: (component: NodeDataProp) => void; - component: NodeDataProp; - graphComponents: Array<NodeDataProp>; - workflowId: ObjectID; + title: string; + description: string; + onClose: () => void; + onSave: (component: NodeDataProp) => void; + onDelete: (component: NodeDataProp) => void; + component: NodeDataProp; + graphComponents: Array<NodeDataProp>; + workflowId: ObjectID; } const ComponentSettingsModal: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [component, setComponent] = useState<NodeDataProp>(props.component); - const [hasFormValidationErrors, setHasFormValidationErrors] = useState< - Dictionary<boolean> - >({}); - const [showDeleteConfirmation, setShowDeleteConfirmation] = - useState<boolean>(false); + const [component, setComponent] = useState<NodeDataProp>(props.component); + const [hasFormValidationErrors, setHasFormValidationErrors] = useState< + Dictionary<boolean> + >({}); + const [showDeleteConfirmation, setShowDeleteConfirmation] = + useState<boolean>(false); - return ( - <SideOver - title={props.title} - description={props.description} - onClose={props.onClose} - onSubmit={() => { - return component && props.onSave(component); + return ( + <SideOver + title={props.title} + description={props.description} + onClose={props.onClose} + onSubmit={() => { + return component && props.onSave(component); + }} + leftFooterElement={ + <Button + title={`Delete ${component.metadata.componentType}`} + icon={IconProp.Trash} + buttonStyle={ButtonStyleType.DANGER_OUTLINE} + onClick={() => { + setShowDeleteConfirmation(true); + }} + /> + } + > + <> + {showDeleteConfirmation && ( + <ConfirmModal + title={`Delete ${component.metadata.componentType}`} + description={`Are you sure you want to delete this ${component.metadata.componentType.toLowerCase()}? This action is not recoverable.`} + onClose={() => { + setShowDeleteConfirmation(false); }} - leftFooterElement={ - <Button - title={`Delete ${component.metadata.componentType}`} - icon={IconProp.Trash} - buttonStyle={ButtonStyleType.DANGER_OUTLINE} - onClick={() => { - setShowDeleteConfirmation(true); - }} - /> - } - > - <> - {showDeleteConfirmation && ( - <ConfirmModal - title={`Delete ${component.metadata.componentType}`} - description={`Are you sure you want to delete this ${component.metadata.componentType.toLowerCase()}? This action is not recoverable.`} - onClose={() => { - setShowDeleteConfirmation(false); - }} - submitButtonText={'Delete'} - onSubmit={() => { - props.onDelete(component); - setShowDeleteConfirmation(false); - props.onClose(); - }} - submitButtonType={ButtonStyleType.DANGER} - /> - )} - <div className="mb-3 mt-3"> - <BasicForm - hideSubmitButton={true} - initialValues={{ - id: component?.id, - }} - onChange={(values: FormValues<JSONObject>) => { - setComponent({ ...component, ...values }); - }} - onFormValidationErrorChanged={(hasError: boolean) => { - setHasFormValidationErrors({ - ...hasFormValidationErrors, - id: hasError, - }); - }} - fields={[ - { - title: `${component.metadata.componentType} ID`, - description: `${component.metadata.componentType} ID will make it easier for you to connect to other components.`, - field: { - id: true, - }, + submitButtonText={"Delete"} + onSubmit={() => { + props.onDelete(component); + setShowDeleteConfirmation(false); + props.onClose(); + }} + submitButtonType={ButtonStyleType.DANGER} + /> + )} + <div className="mb-3 mt-3"> + <BasicForm + hideSubmitButton={true} + initialValues={{ + id: component?.id, + }} + onChange={(values: FormValues<JSONObject>) => { + setComponent({ ...component, ...values }); + }} + onFormValidationErrorChanged={(hasError: boolean) => { + setHasFormValidationErrors({ + ...hasFormValidationErrors, + id: hasError, + }); + }} + fields={[ + { + title: `${component.metadata.componentType} ID`, + description: `${component.metadata.componentType} ID will make it easier for you to connect to other components.`, + field: { + id: true, + }, - required: true, + required: true, - fieldType: FormFieldSchemaType.Text, - }, - ]} - /> - </div> + fieldType: FormFieldSchemaType.Text, + }, + ]} + /> + </div> - {component.metadata.documentationLink && ( - <div> - <Divider /> + {component.metadata.documentationLink && ( + <div> + <Divider /> - <DocumentationViewer - documentationLink={ - component.metadata.documentationLink - } - workflowId={props.workflowId} - /> - </div> - )} + <DocumentationViewer + documentationLink={component.metadata.documentationLink} + workflowId={props.workflowId} + /> + </div> + )} - <Divider /> + <Divider /> - <ArgumentsForm - graphComponents={props.graphComponents} - workflowId={props.workflowId} - component={component} - onFormChange={(component: NodeDataProp) => { - setComponent({ ...component }); - }} - onHasFormValidationErrors={(value: Dictionary<boolean>) => { - setHasFormValidationErrors({ - ...hasFormValidationErrors, - ...value, - }); - }} - /> + <ArgumentsForm + graphComponents={props.graphComponents} + workflowId={props.workflowId} + component={component} + onFormChange={(component: NodeDataProp) => { + setComponent({ ...component }); + }} + onHasFormValidationErrors={(value: Dictionary<boolean>) => { + setHasFormValidationErrors({ + ...hasFormValidationErrors, + ...value, + }); + }} + /> - <Divider /> + <Divider /> - <div className="mb-3 mt-3"> - <ComponentPortViewer - name="In Ports" - description="Here is a list of inports for this component" - ports={component.metadata.inPorts} - /> - </div> + <div className="mb-3 mt-3"> + <ComponentPortViewer + name="In Ports" + description="Here is a list of inports for this component" + ports={component.metadata.inPorts} + /> + </div> - <Divider /> + <Divider /> - <div className="mb-3 mt-3"> - <ComponentPortViewer - name="Out Ports" - description="Here is a list of outports for this component" - ports={component.metadata.outPorts} - /> - </div> + <div className="mb-3 mt-3"> + <ComponentPortViewer + name="Out Ports" + description="Here is a list of outports for this component" + ports={component.metadata.outPorts} + /> + </div> - <Divider /> - <div className="mb-3 mt-3"> - <ComponentReturnValueViewer - name="Return Values" - description="Here is a list of values that this component returns" - returnValues={component.metadata.returnValues} - /> - </div> - </> - </SideOver> - ); + <Divider /> + <div className="mb-3 mt-3"> + <ComponentReturnValueViewer + name="Return Values" + description="Here is a list of values that this component returns" + returnValues={component.metadata.returnValues} + /> + </div> + </> + </SideOver> + ); }; export default ComponentSettingsModal; diff --git a/CommonUI/src/Components/Workflow/ComponentValuePickerModal.tsx b/CommonUI/src/Components/Workflow/ComponentValuePickerModal.tsx index b08c7db07c..08b3d11dbf 100644 --- a/CommonUI/src/Components/Workflow/ComponentValuePickerModal.tsx +++ b/CommonUI/src/Components/Workflow/ComponentValuePickerModal.tsx @@ -1,236 +1,197 @@ -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import Input from '../Input/Input'; -import Modal, { ModalWidth } from '../Modal/Modal'; -import Pill from '../Pill/Pill'; -import { Black } from 'Common/Types/BrandColors'; -import { NodeDataProp, ReturnValue } from 'Common/Types/Workflow/Component'; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import Input from "../Input/Input"; +import Modal, { ModalWidth } from "../Modal/Modal"; +import Pill from "../Pill/Pill"; +import { Black } from "Common/Types/BrandColors"; +import { NodeDataProp, ReturnValue } from "Common/Types/Workflow/Component"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - onClose: () => void; - onSave: (componentValueId: string) => void; - components: Array<NodeDataProp>; + onClose: () => void; + onSave: (componentValueId: string) => void; + components: Array<NodeDataProp>; } const ComponentValuePickerModal: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [selectedReturnValue, setSelectedReturnValue] = - useState<ReturnValue | null>(null); - const [selectedComponent, setSelectedComponent] = - useState<NodeDataProp | null>(null); - const [searchedComponents, setSearchedComponents] = useState< - Array<NodeDataProp> - >([]); + const [selectedReturnValue, setSelectedReturnValue] = + useState<ReturnValue | null>(null); + const [selectedComponent, setSelectedComponent] = + useState<NodeDataProp | null>(null); + const [searchedComponents, setSearchedComponents] = useState< + Array<NodeDataProp> + >([]); - const [searchText, setSearchText] = useState<string>(''); + const [searchText, setSearchText] = useState<string>(""); - useEffect(() => { - setSearchedComponents(searchReturnValues(props.components, searchText)); - }, [props.components, searchText]); + useEffect(() => { + setSearchedComponents(searchReturnValues(props.components, searchText)); + }, [props.components, searchText]); - type SearchReturnValuesFunction = ( - components: Array<NodeDataProp>, - searchText: string - ) => Array<NodeDataProp>; + type SearchReturnValuesFunction = ( + components: Array<NodeDataProp>, + searchText: string, + ) => Array<NodeDataProp>; - const searchReturnValues: SearchReturnValuesFunction = ( - components: Array<NodeDataProp>, - searchText: string - ): Array<NodeDataProp> => { - if (!searchText) { - return components; + const searchReturnValues: SearchReturnValuesFunction = ( + components: Array<NodeDataProp>, + searchText: string, + ): Array<NodeDataProp> => { + if (!searchText) { + return components; + } + + const searched: Array<NodeDataProp> = []; + + for (const component of components) { + if ( + component.metadata.title.includes(searchText) || + component.metadata.description.includes(searchText) + ) { + searched.push(component); + continue; + } + + for (const returnVal of component.metadata.returnValues) { + if ( + returnVal.name.includes(searchText) || + returnVal.description.includes(searchText) + ) { + searched.push(component); + break; + } + } + } + + return searched; + }; + + return ( + <Modal + modalWidth={ModalWidth.Large} + title={"Select return value from another component"} + description={ + "Select a return value from the component this component is connected to." + } + onClose={props.onClose} + disableSubmitButton={!selectedReturnValue} + onSubmit={() => { + if (!selectedReturnValue) { + return props.onClose(); } - const searched: Array<NodeDataProp> = []; - - for (const component of components) { - if ( - component.metadata.title.includes(searchText) || - component.metadata.description.includes(searchText) - ) { - searched.push(component); - continue; - } - - for (const returnVal of component.metadata.returnValues) { - if ( - returnVal.name.includes(searchText) || - returnVal.description.includes(searchText) - ) { - searched.push(component); - break; - } - } + if (!selectedComponent) { + return props.onClose(); } - return searched; - }; + props.onSave( + `{{local.components.${selectedComponent.id}.returnValues.${selectedReturnValue.id}}}`, + ); + }} + > + <div> + {props.components && props.components.length > 0 && ( + <div className="p-2"> + <Input + placeholder="Search..." + onChange={(value: string) => { + setSearchText(value); + }} + /> + </div> + )} - return ( - <Modal - modalWidth={ModalWidth.Large} - title={'Select return value from another component'} - description={ - 'Select a return value from the component this component is connected to.' - } - onClose={props.onClose} - disableSubmitButton={!selectedReturnValue} - onSubmit={() => { - if (!selectedReturnValue) { - return props.onClose(); - } + <div className="max-h-96 mt-5 mb-5 overflow-y-auto"> + {props.components.length === 0 ? ( + <ErrorMessage error={"No components in this workflow."} /> + ) : ( + <></> + )} - if (!selectedComponent) { - return props.onClose(); - } + {props.components.length > 0 && + searchText && + searchedComponents.length === 0 ? ( + <ErrorMessage error={"No components match your search"} /> + ) : ( + <></> + )} - props.onSave( - `{{local.components.${selectedComponent.id}.returnValues.${selectedReturnValue.id}}}` + {searchedComponents && + searchedComponents.length > 0 && + searchedComponents.map( + (component: NodeDataProp, i: number): ReactElement => { + return ( + <div className="p-3 pl-1" key={`component-${i}`}> + <h2 className="text-base font-medium text-gray-500"> + {component.metadata.title} ({component.id}) + </h2> + <p className="text-sm font-medium text-gray-400"> + {component.metadata.description} + </p> + + {component.metadata.returnValues && + component.metadata.returnValues.length === 0 && ( + <ErrorMessage error="This component does not have any return values." /> + )} + {component.metadata.returnValues && + component.metadata.returnValues.map( + (returnValue: ReturnValue, i: number) => { + const isSelected: boolean = Boolean( + selectedComponent && + component.id === selectedComponent.id && + selectedReturnValue && + selectedReturnValue.id === returnValue.id, + ); + + return ( + <div + key={i} + onClick={() => { + setSelectedComponent(component); + setSelectedReturnValue(returnValue); + }} + className={`cursor-pointer mt-2 mb-2 relative flex items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2 hover:border-gray-400 ${ + isSelected ? "ring ring-indigo-500" : "" + }`} + > + <div className="min-w-0 flex-1 flex justify-between"> + <div className="focus:outline-none"> + <span + className="absolute inset-0" + aria-hidden="true" + ></span> + <p className="text-sm font-medium text-gray-900"> + {returnValue.name}{" "} + <span className="text-gray-500 font-normal"> + (ID: {returnValue.id}) + </span> + </p> + <p className="truncate text-sm text-gray-500"> + {returnValue.description} + </p> + </div> + <div> + <Pill color={Black} text={returnValue.type} /> + </div> + </div> + </div> + ); + }, + )} + </div> ); - }} - > - <div> - {props.components && props.components.length > 0 && ( - <div className="p-2"> - <Input - placeholder="Search..." - onChange={(value: string) => { - setSearchText(value); - }} - /> - </div> - )} - - <div className="max-h-96 mt-5 mb-5 overflow-y-auto"> - {props.components.length === 0 ? ( - <ErrorMessage - error={'No components in this workflow.'} - /> - ) : ( - <></> - )} - - {props.components.length > 0 && - searchText && - searchedComponents.length === 0 ? ( - <ErrorMessage - error={'No components match your search'} - /> - ) : ( - <></> - )} - - {searchedComponents && - searchedComponents.length > 0 && - searchedComponents.map( - ( - component: NodeDataProp, - i: number - ): ReactElement => { - return ( - <div - className="p-3 pl-1" - key={`component-${i}`} - > - <h2 className="text-base font-medium text-gray-500"> - {component.metadata.title} ( - {component.id}) - </h2> - <p className="text-sm font-medium text-gray-400"> - {component.metadata.description} - </p> - - {component.metadata.returnValues && - component.metadata.returnValues - .length === 0 && ( - <ErrorMessage error="This component does not have any return values." /> - )} - {component.metadata.returnValues && - component.metadata.returnValues.map( - ( - returnValue: ReturnValue, - i: number - ) => { - const isSelected: boolean = - Boolean( - selectedComponent && - component.id === - selectedComponent.id && - selectedReturnValue && - selectedReturnValue.id === - returnValue.id - ); - - return ( - <div - key={i} - onClick={() => { - setSelectedComponent( - component - ); - setSelectedReturnValue( - returnValue - ); - }} - className={`cursor-pointer mt-2 mb-2 relative flex items-center space-x-3 rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm focus-within:ring-2 focus-within:ring-pink-500 focus-within:ring-offset-2 hover:border-gray-400 ${ - isSelected - ? 'ring ring-indigo-500' - : '' - }`} - > - <div className="min-w-0 flex-1 flex justify-between"> - <div className="focus:outline-none"> - <span - className="absolute inset-0" - aria-hidden="true" - ></span> - <p className="text-sm font-medium text-gray-900"> - { - returnValue.name - }{' '} - <span className="text-gray-500 font-normal"> - (ID:{' '} - { - returnValue.id - } - ) - </span> - </p> - <p className="truncate text-sm text-gray-500"> - { - returnValue.description - } - </p> - </div> - <div> - <Pill - color={ - Black - } - text={ - returnValue.type - } - /> - </div> - </div> - </div> - ); - } - )} - </div> - ); - } - )} - </div> - </div> - </Modal> - ); + }, + )} + </div> + </div> + </Modal> + ); }; export default ComponentValuePickerModal; diff --git a/CommonUI/src/Components/Workflow/ComponentsModal.tsx b/CommonUI/src/Components/Workflow/ComponentsModal.tsx index a1064cd6de..6223d79636 100644 --- a/CommonUI/src/Components/Workflow/ComponentsModal.tsx +++ b/CommonUI/src/Components/Workflow/ComponentsModal.tsx @@ -1,236 +1,207 @@ // Show a large modal full of components. -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import Icon from '../Icon/Icon'; -import Input from '../Input/Input'; -import SideOver from '../SideOver/SideOver'; -import ComponentElement from './Component'; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import Icon from "../Icon/Icon"; +import Input from "../Input/Input"; +import SideOver from "../SideOver/SideOver"; +import ComponentElement from "./Component"; import ComponentMetadata, { - ComponentCategory, - ComponentType, - NodeType, -} from 'Common/Types/Workflow/Component'; + ComponentCategory, + ComponentType, + NodeType, +} from "Common/Types/Workflow/Component"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - componentsType: ComponentType; - onCloseModal: () => void; - onComponentClick: (componentMetadata: ComponentMetadata) => void; - components: Array<ComponentMetadata>; - categories: Array<ComponentCategory>; + componentsType: ComponentType; + onCloseModal: () => void; + onComponentClick: (componentMetadata: ComponentMetadata) => void; + components: Array<ComponentMetadata>; + categories: Array<ComponentCategory>; } const ComponentsModal: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [search, setSearch] = useState<string>(''); + const [search, setSearch] = useState<string>(""); - const [components, setComponents] = useState<Array<ComponentMetadata>>([]); - const [categories, setCategories] = useState<Array<ComponentCategory>>([]); + const [components, setComponents] = useState<Array<ComponentMetadata>>([]); + const [categories, setCategories] = useState<Array<ComponentCategory>>([]); - const [componentsToShow, setComponentsToShow] = useState< - Array<ComponentMetadata> - >([]); + const [componentsToShow, setComponentsToShow] = useState< + Array<ComponentMetadata> + >([]); - const [isSearching, setIsSearching] = useState<boolean>(false); + const [isSearching, setIsSearching] = useState<boolean>(false); - useEffect(() => { - setComponents(props.components); + useEffect(() => { + setComponents(props.components); - setComponentsToShow([...props.components]); + setComponentsToShow([...props.components]); - setCategories(props.categories); - }, []); + setCategories(props.categories); + }, []); - useEffect(() => { - if (!isSearching) { - return; - } - if (!search) { - setComponentsToShow([ - ...components.filter((componentMetadata: ComponentMetadata) => { - return ( - componentMetadata.componentType === props.componentsType - ); - }), - ]); - } + useEffect(() => { + if (!isSearching) { + return; + } + if (!search) { + setComponentsToShow([ + ...components.filter((componentMetadata: ComponentMetadata) => { + return componentMetadata.componentType === props.componentsType; + }), + ]); + } - setComponentsToShow([ - ...components.filter((componentMetadata: ComponentMetadata) => { - return ( - componentMetadata.componentType === props.componentsType && - (componentMetadata.title - .toLowerCase() - .includes(search.trim().toLowerCase()) || - componentMetadata.description - .toLowerCase() - .includes(search.trim().toLowerCase()) || - componentMetadata.category - .toLowerCase() - .includes(search.trim().toLowerCase())) - ); - }), - ]); - }, [search]); + setComponentsToShow([ + ...components.filter((componentMetadata: ComponentMetadata) => { + return ( + componentMetadata.componentType === props.componentsType && + (componentMetadata.title + .toLowerCase() + .includes(search.trim().toLowerCase()) || + componentMetadata.description + .toLowerCase() + .includes(search.trim().toLowerCase()) || + componentMetadata.category + .toLowerCase() + .includes(search.trim().toLowerCase())) + ); + }), + ]); + }, [search]); - const [selectedComponentMetadata, setSelectedComponentMetadata] = - useState<ComponentMetadata | null>(null); + const [selectedComponentMetadata, setSelectedComponentMetadata] = + useState<ComponentMetadata | null>(null); - return ( - <SideOver - submitButtonText="Create" - title={`Select a ${props.componentsType}`} - description={`Please select a component to add to your workflow.`} - onClose={props.onCloseModal} - submitButtonDisabled={!selectedComponentMetadata} - onSubmit={() => { - return ( - selectedComponentMetadata && - props.onComponentClick(selectedComponentMetadata) - ); - }} - > - <> - <div className="flex flex-col h-full"> - {/** Search box here */} + return ( + <SideOver + submitButtonText="Create" + title={`Select a ${props.componentsType}`} + description={`Please select a component to add to your workflow.`} + onClose={props.onCloseModal} + submitButtonDisabled={!selectedComponentMetadata} + onSubmit={() => { + return ( + selectedComponentMetadata && + props.onComponentClick(selectedComponentMetadata) + ); + }} + > + <> + <div className="flex flex-col h-full"> + {/** Search box here */} - <div className="mt-5"> - <Input - placeholder="Search..." - onChange={(text: string) => { - setIsSearching(true); - setSearch(text); - }} - /> - </div> + <div className="mt-5"> + <Input + placeholder="Search..." + onChange={(text: string) => { + setIsSearching(true); + setSearch(text); + }} + /> + </div> - <div className="overflow-y-auto overflow-x-hidden my-5"> - {!componentsToShow || - (componentsToShow.length === 0 && ( - <div className="w-full flex justify-center mt-20"> - <ErrorMessage error="No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you." /> - </div> - ))} - - {categories && - categories.length > 0 && - categories.map( - (category: ComponentCategory, i: number) => { - if ( - componentsToShow && - componentsToShow.length > 0 && - componentsToShow.filter( - ( - componentMetadata: ComponentMetadata - ) => { - return ( - componentMetadata.category === - category.name - ); - } - ).length > 0 - ) { - return ( - <div key={i}> - <h4 className="text-gray-500 text-base mt-5 flex"> - {' '} - <Icon - icon={category.icon} - className="h-5 w-5 text-gray-500" - />{' '} - <span className="ml-2"> - {category.name} - </span> - </h4> - <p className="text-gray-400 text-sm mb-5"> - {category.description} - </p> - <div className="flex flex-wrap ml-2"> - {components && - components.length > 0 && - components - .filter( - ( - componentMetadata: ComponentMetadata - ) => { - return ( - componentMetadata.category === - category.name - ); - } - ) - .map( - ( - componentMetadata: ComponentMetadata, - i: number - ) => { - return ( - <div - key={ - i - } - onClick={() => { - setSelectedComponentMetadata( - componentMetadata - ); - }} - className={`m-5 ml-0 mt-0 ${ - selectedComponentMetadata && - selectedComponentMetadata.id === - componentMetadata.id - ? 'rounded ring-offset-2 ring ring-indigo-500' - : '' - }`} - > - <ComponentElement - key={ - i - } - selected={ - false - } - data={{ - metadata: - componentMetadata, - metadataId: - componentMetadata.id, - internalId: - '', - nodeType: - NodeType.Node, - componentType: - componentMetadata.componentType, - returnValues: - {}, - isPreview: - true, - id: '', - error: '', - arguments: - {}, - }} - /> - </div> - ); - } - )} - </div> - </div> - ); - } - return <div key={i}></div>; - } - )} - </div> + <div className="overflow-y-auto overflow-x-hidden my-5"> + {!componentsToShow || + (componentsToShow.length === 0 && ( + <div className="w-full flex justify-center mt-20"> + <ErrorMessage error="No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you." /> </div> - </> - </SideOver> - ); + ))} + + {categories && + categories.length > 0 && + categories.map((category: ComponentCategory, i: number) => { + if ( + componentsToShow && + componentsToShow.length > 0 && + componentsToShow.filter( + (componentMetadata: ComponentMetadata) => { + return componentMetadata.category === category.name; + }, + ).length > 0 + ) { + return ( + <div key={i}> + <h4 className="text-gray-500 text-base mt-5 flex"> + {" "} + <Icon + icon={category.icon} + className="h-5 w-5 text-gray-500" + />{" "} + <span className="ml-2">{category.name}</span> + </h4> + <p className="text-gray-400 text-sm mb-5"> + {category.description} + </p> + <div className="flex flex-wrap ml-2"> + {components && + components.length > 0 && + components + .filter((componentMetadata: ComponentMetadata) => { + return ( + componentMetadata.category === category.name + ); + }) + .map( + ( + componentMetadata: ComponentMetadata, + i: number, + ) => { + return ( + <div + key={i} + onClick={() => { + setSelectedComponentMetadata( + componentMetadata, + ); + }} + className={`m-5 ml-0 mt-0 ${ + selectedComponentMetadata && + selectedComponentMetadata.id === + componentMetadata.id + ? "rounded ring-offset-2 ring ring-indigo-500" + : "" + }`} + > + <ComponentElement + key={i} + selected={false} + data={{ + metadata: componentMetadata, + metadataId: componentMetadata.id, + internalId: "", + nodeType: NodeType.Node, + componentType: + componentMetadata.componentType, + returnValues: {}, + isPreview: true, + id: "", + error: "", + arguments: {}, + }} + /> + </div> + ); + }, + )} + </div> + </div> + ); + } + return <div key={i}></div>; + })} + </div> + </div> + </> + </SideOver> + ); }; export default ComponentsModal; diff --git a/CommonUI/src/Components/Workflow/DocumentationViewer.tsx b/CommonUI/src/Components/Workflow/DocumentationViewer.tsx index 172a680804..795fe0fbf8 100644 --- a/CommonUI/src/Components/Workflow/DocumentationViewer.tsx +++ b/CommonUI/src/Components/Workflow/DocumentationViewer.tsx @@ -1,83 +1,79 @@ -import { HOME_URL, HOST, HTTP_PROTOCOL } from '../../Config'; -import API from '../../Utils/API/API'; -import ComponentLoader from '../ComponentLoader/ComponentLoader'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import MarkdownViewer from '../Markdown.tsx/LazyMarkdownViewer'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import { HOME_URL, HOST, HTTP_PROTOCOL } from "../../Config"; +import API from "../../Utils/API/API"; +import ComponentLoader from "../ComponentLoader/ComponentLoader"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import MarkdownViewer from "../Markdown.tsx/LazyMarkdownViewer"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import React, { FunctionComponent, ReactElement, useState } from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps { - documentationLink: Route; - workflowId: ObjectID; + documentationLink: Route; + workflowId: ObjectID; } const DocumentationViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [markdown, setMarkdown] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string>(''); + const [markdown, setMarkdown] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string>(""); - type PopulateWithEnvVarsFunction = (text: string) => string; + type PopulateWithEnvVarsFunction = (text: string) => string; - const populateWithEnvVars: PopulateWithEnvVarsFunction = ( - text: string - ): string => { - text = text.replace('{{serverUrl}}', HOME_URL.toString()); - text = text.replace('{{workflowId}}', props.workflowId.toString()); + const populateWithEnvVars: PopulateWithEnvVarsFunction = ( + text: string, + ): string => { + text = text.replace("{{serverUrl}}", HOME_URL.toString()); + text = text.replace("{{workflowId}}", props.workflowId.toString()); - return text; - }; + return text; + }; - const loadDocs: PromiseVoidFunction = async (): Promise<void> => { - if (props.documentationLink) { - try { - setIsLoading(true); - const body: HTTPResponse<any> = await API.get( - new URL(HTTP_PROTOCOL, HOST, props.documentationLink), - {}, - { - Accept: 'text/plain', - 'Content-Type': 'text/plain', - } - ); - setMarkdown( - populateWithEnvVars((body.data as any).data.toString()) - ); - setIsLoading(false); - } catch (err) { - setIsLoading(false); - setError(API.getFriendlyMessage(err)); - } - } - }; + const loadDocs: PromiseVoidFunction = async (): Promise<void> => { + if (props.documentationLink) { + try { + setIsLoading(true); + const body: HTTPResponse<any> = await API.get( + new URL(HTTP_PROTOCOL, HOST, props.documentationLink), + {}, + { + Accept: "text/plain", + "Content-Type": "text/plain", + }, + ); + setMarkdown(populateWithEnvVars((body.data as any).data.toString())); + setIsLoading(false); + } catch (err) { + setIsLoading(false); + setError(API.getFriendlyMessage(err)); + } + } + }; - useAsyncEffect(async () => { - await loadDocs(); - }, [props.documentationLink]); + useAsyncEffect(async () => { + await loadDocs(); + }, [props.documentationLink]); - return ( - <div className="mt-5 mb-5"> - <h2 className="text-base font-medium text-gray-500"> - Documentation - </h2> - <p className="text-sm font-medium text-gray-400"> - Here is some documentation for this component. - </p> + return ( + <div className="mt-5 mb-5"> + <h2 className="text-base font-medium text-gray-500">Documentation</h2> + <p className="text-sm font-medium text-gray-400"> + Here is some documentation for this component. + </p> - {error ? <ErrorMessage error={error} /> : <></>} - {isLoading ? <ComponentLoader /> : <></>} + {error ? <ErrorMessage error={error} /> : <></>} + {isLoading ? <ComponentLoader /> : <></>} - <div className="mt-3"> - <MarkdownViewer text={markdown} /> - </div> - </div> - ); + <div className="mt-3"> + <MarkdownViewer text={markdown} /> + </div> + </div> + ); }; export default DocumentationViewer; diff --git a/CommonUI/src/Components/Workflow/RunForm.tsx b/CommonUI/src/Components/Workflow/RunForm.tsx index 210e987a95..973e2a06ab 100644 --- a/CommonUI/src/Components/Workflow/RunForm.tsx +++ b/CommonUI/src/Components/Workflow/RunForm.tsx @@ -1,119 +1,109 @@ -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import BasicForm, { FormProps } from '../Forms/BasicForm'; -import FormValues from '../Forms/Types/FormValues'; -import { componentInputTypeToFormFieldType } from './Utils'; -import { JSONObject } from 'Common/Types/JSON'; -import { NodeDataProp, ReturnValue } from 'Common/Types/Workflow/Component'; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import BasicForm, { FormProps } from "../Forms/BasicForm"; +import FormValues from "../Forms/Types/FormValues"; +import { componentInputTypeToFormFieldType } from "./Utils"; +import { JSONObject } from "Common/Types/JSON"; +import { NodeDataProp, ReturnValue } from "Common/Types/Workflow/Component"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useRef, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useRef, + useState, +} from "react"; export interface ComponentProps { - component: NodeDataProp; - onHasFormValidationErrors: (values: boolean) => void; - onFormChange: (value: NodeDataProp) => void; + component: NodeDataProp; + onHasFormValidationErrors: (values: boolean) => void; + onFormChange: (value: NodeDataProp) => void; } const RunForm: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const formRef: any = useRef<FormProps<FormValues<JSONObject>>>(null); - const [component, setComponent] = useState<NodeDataProp>(props.component); - const [hasFormValidationErrors, setHasFormValidationErrors] = - useState<boolean>(false); + const formRef: any = useRef<FormProps<FormValues<JSONObject>>>(null); + const [component, setComponent] = useState<NodeDataProp>(props.component); + const [hasFormValidationErrors, setHasFormValidationErrors] = + useState<boolean>(false); - useEffect(() => { - props.onHasFormValidationErrors(hasFormValidationErrors); - }, [hasFormValidationErrors]); + useEffect(() => { + props.onHasFormValidationErrors(hasFormValidationErrors); + }, [hasFormValidationErrors]); - useEffect(() => { - props.onFormChange(component); - }, [component]); + useEffect(() => { + props.onFormChange(component); + }, [component]); - return ( - <div className="mb-3 mt-3"> - <div className="mt-5 mb-5"> - <h2 className="text-base font-medium text-gray-500"> - Return Values from Trigger - </h2> - <p className="text-sm font-medium text-gray-400 mb-5"> - This workflow has a trigger to get it to run. Since this - trigger returns some values to work. You can pass these - return values from trigger manually and test this workflow. - </p> - {component.metadata.returnValues && - component.metadata.returnValues.length === 0 && ( - <ErrorMessage - error={ - 'This workflow trigger does not take any return values. You can run it by clicking the "Run" button below.' - } - /> - )} - {component.metadata.returnValues && - component.metadata.returnValues.length > 0 && ( - <BasicForm - hideSubmitButton={true} - ref={formRef} - initialValues={{ - ...(component.returnValues || {}), - }} - onChange={(values: FormValues<JSONObject>) => { - setComponent({ - ...component, - returnValues: { - ...((component.returnValues as JSONObject) || - {}), - ...((values as JSONObject) || {}), - }, - }); - }} - onFormValidationErrorChanged={( - hasError: boolean - ) => { - setHasFormValidationErrors(hasError); - }} - fields={ - component.metadata.returnValues && - component.metadata.returnValues.map( - (returnValue: ReturnValue) => { - return { - title: `${returnValue.name}`, + return ( + <div className="mb-3 mt-3"> + <div className="mt-5 mb-5"> + <h2 className="text-base font-medium text-gray-500"> + Return Values from Trigger + </h2> + <p className="text-sm font-medium text-gray-400 mb-5"> + This workflow has a trigger to get it to run. Since this trigger + returns some values to work. You can pass these return values from + trigger manually and test this workflow. + </p> + {component.metadata.returnValues && + component.metadata.returnValues.length === 0 && ( + <ErrorMessage + error={ + 'This workflow trigger does not take any return values. You can run it by clicking the "Run" button below.' + } + /> + )} + {component.metadata.returnValues && + component.metadata.returnValues.length > 0 && ( + <BasicForm + hideSubmitButton={true} + ref={formRef} + initialValues={{ + ...(component.returnValues || {}), + }} + onChange={(values: FormValues<JSONObject>) => { + setComponent({ + ...component, + returnValues: { + ...((component.returnValues as JSONObject) || {}), + ...((values as JSONObject) || {}), + }, + }); + }} + onFormValidationErrorChanged={(hasError: boolean) => { + setHasFormValidationErrors(hasError); + }} + fields={ + component.metadata.returnValues && + component.metadata.returnValues.map( + (returnValue: ReturnValue) => { + return { + title: `${returnValue.name}`, - description: `${ - returnValue.required - ? 'Required' - : 'Optional' - }. ${returnValue.description}`, - field: { - [returnValue.id]: true, - }, - required: returnValue.required, - placeholder: - returnValue.placeholder, - ...componentInputTypeToFormFieldType( - returnValue.type, - component.returnValues && - component.returnValues[ - returnValue.id - ] - ? component.returnValues[ - returnValue.id - ] - : null - ), - }; - } - ) - } - /> - )} - </div> - </div> - ); + description: `${ + returnValue.required ? "Required" : "Optional" + }. ${returnValue.description}`, + field: { + [returnValue.id]: true, + }, + required: returnValue.required, + placeholder: returnValue.placeholder, + ...componentInputTypeToFormFieldType( + returnValue.type, + component.returnValues && + component.returnValues[returnValue.id] + ? component.returnValues[returnValue.id] + : null, + ), + }; + }, + ) + } + /> + )} + </div> + </div> + ); }; export default RunForm; diff --git a/CommonUI/src/Components/Workflow/RunModal.tsx b/CommonUI/src/Components/Workflow/RunModal.tsx index f16b4ac46f..a439952504 100644 --- a/CommonUI/src/Components/Workflow/RunModal.tsx +++ b/CommonUI/src/Components/Workflow/RunModal.tsx @@ -1,134 +1,121 @@ -import { ButtonStyleType } from '../Button/Button'; -import ErrorMessage from '../ErrorMessage/ErrorMessage'; -import ConfirmModal from '../Modal/ConfirmModal'; -import SideOver from '../SideOver/SideOver'; -import RunForm from './RunForm'; -import JSONFunctions from 'Common/Types/JSONFunctions'; +import { ButtonStyleType } from "../Button/Button"; +import ErrorMessage from "../ErrorMessage/ErrorMessage"; +import ConfirmModal from "../Modal/ConfirmModal"; +import SideOver from "../SideOver/SideOver"; +import RunForm from "./RunForm"; +import JSONFunctions from "Common/Types/JSONFunctions"; import { - ComponentInputType, - NodeDataProp, - NodeType, -} from 'Common/Types/Workflow/Component'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; + ComponentInputType, + NodeDataProp, + NodeType, +} from "Common/Types/Workflow/Component"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - onClose: () => void; - onRun: (trigger: NodeDataProp) => void; - trigger: NodeDataProp; + onClose: () => void; + onRun: (trigger: NodeDataProp) => void; + trigger: NodeDataProp; } const RunModal: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [component, setComponent] = useState<NodeDataProp>(props.trigger); - const [hasFormValidationErrors, setHasFormValidationErrors] = - useState<boolean>(false); - const [showRunConfirmation, setShowRunConfirmation] = - useState<boolean>(false); + const [component, setComponent] = useState<NodeDataProp>(props.trigger); + const [hasFormValidationErrors, setHasFormValidationErrors] = + useState<boolean>(false); + const [showRunConfirmation, setShowRunConfirmation] = + useState<boolean>(false); - const [showFormValidationErrors, setShowFormValidationErrors] = - useState<boolean>(false); + const [showFormValidationErrors, setShowFormValidationErrors] = + useState<boolean>(false); - return ( - <SideOver - title={'Run Workflow'} - description={ - 'You can run this workflow manually. This can be helpful to test the workflow.' - } - onClose={props.onClose} - submitButtonDisabled={ - component.nodeType === NodeType.PlaceholderNode - } - submitButtonText={'Run Workflow Manually'} - onSubmit={() => { - if (hasFormValidationErrors) { - setShowFormValidationErrors(true); - } else { - setShowRunConfirmation(true); - } + return ( + <SideOver + title={"Run Workflow"} + description={ + "You can run this workflow manually. This can be helpful to test the workflow." + } + onClose={props.onClose} + submitButtonDisabled={component.nodeType === NodeType.PlaceholderNode} + submitButtonText={"Run Workflow Manually"} + onSubmit={() => { + if (hasFormValidationErrors) { + setShowFormValidationErrors(true); + } else { + setShowRunConfirmation(true); + } + }} + > + <> + {showRunConfirmation && ( + <ConfirmModal + title={`Run Workflow Manually`} + description={`Are you sure you want to run this workflow manually?`} + onClose={() => { + setShowRunConfirmation(false); }} - > - <> - {showRunConfirmation && ( - <ConfirmModal - title={`Run Workflow Manually`} - description={`Are you sure you want to run this workflow manually?`} - onClose={() => { - setShowRunConfirmation(false); - }} - submitButtonText={'Run'} - onSubmit={() => { - setShowRunConfirmation(false); + submitButtonText={"Run"} + onSubmit={() => { + setShowRunConfirmation(false); - // parse things as JSON if args in JSON + // parse things as JSON if args in JSON - for (const args of component.metadata - .returnValues) { - if ( - (args.type === ComponentInputType.JSON || - args.type === - ComponentInputType.JSONArray || - args.type === - ComponentInputType.BaseModel || - args.type === - ComponentInputType.BaseModelArray || - args.type === - ComponentInputType.Query || - args.type === - ComponentInputType.Select || - args.type === - ComponentInputType.StringDictionary) && - component.returnValues && - component.returnValues[args.id] && - typeof component.returnValues[args.id] === - 'string' - ) { - component.returnValues[args.id] = - JSONFunctions.parse( - component.returnValues[ - args.id - ] as string - ); - } - } + for (const args of component.metadata.returnValues) { + if ( + (args.type === ComponentInputType.JSON || + args.type === ComponentInputType.JSONArray || + args.type === ComponentInputType.BaseModel || + args.type === ComponentInputType.BaseModelArray || + args.type === ComponentInputType.Query || + args.type === ComponentInputType.Select || + args.type === ComponentInputType.StringDictionary) && + component.returnValues && + component.returnValues[args.id] && + typeof component.returnValues[args.id] === "string" + ) { + component.returnValues[args.id] = JSONFunctions.parse( + component.returnValues[args.id] as string, + ); + } + } - props.onRun(component); - props.onClose(); - }} - submitButtonType={ButtonStyleType.SUCCESS} - /> - )} + props.onRun(component); + props.onClose(); + }} + submitButtonType={ButtonStyleType.SUCCESS} + /> + )} - {showFormValidationErrors && ( - <ConfirmModal - title={`Please fix errors`} - description={`There are some validation errors on the form. Please fix them before you run the workflow.`} - submitButtonText={'Close'} - onSubmit={() => { - setShowFormValidationErrors(false); - }} - submitButtonType={ButtonStyleType.NORMAL} - /> - )} + {showFormValidationErrors && ( + <ConfirmModal + title={`Please fix errors`} + description={`There are some validation errors on the form. Please fix them before you run the workflow.`} + submitButtonText={"Close"} + onSubmit={() => { + setShowFormValidationErrors(false); + }} + submitButtonType={ButtonStyleType.NORMAL} + /> + )} - {component.nodeType === NodeType.Node && ( - <RunForm - component={component} - onFormChange={(component: NodeDataProp) => { - setComponent({ ...component }); - }} - onHasFormValidationErrors={(value: boolean) => { - setHasFormValidationErrors(value); - }} - /> - )} + {component.nodeType === NodeType.Node && ( + <RunForm + component={component} + onFormChange={(component: NodeDataProp) => { + setComponent({ ...component }); + }} + onHasFormValidationErrors={(value: boolean) => { + setHasFormValidationErrors(value); + }} + /> + )} - {component.nodeType === NodeType.PlaceholderNode && ( - <ErrorMessage error="No trigger added. Please add a trigger in order to run this workflow" /> - )} - </> - </SideOver> - ); + {component.nodeType === NodeType.PlaceholderNode && ( + <ErrorMessage error="No trigger added. Please add a trigger in order to run this workflow" /> + )} + </> + </SideOver> + ); }; export default RunModal; diff --git a/CommonUI/src/Components/Workflow/Utils.ts b/CommonUI/src/Components/Workflow/Utils.ts index e0a0db40e9..0852f14351 100644 --- a/CommonUI/src/Components/Workflow/Utils.ts +++ b/CommonUI/src/Components/Workflow/Utils.ts @@ -1,267 +1,267 @@ -import { DropdownOption } from '../Dropdown/Dropdown'; -import FormFieldSchemaType from '../Forms/Types/FormFieldSchemaType'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Typeof from 'Common/Types/Typeof'; +import { DropdownOption } from "../Dropdown/Dropdown"; +import FormFieldSchemaType from "../Forms/Types/FormFieldSchemaType"; +import IconProp from "Common/Types/Icon/IconProp"; +import Typeof from "Common/Types/Typeof"; import ComponentMetadata, { - ComponentCategory, - ComponentInputType, -} from 'Common/Types/Workflow/Component'; -import Components, { Categories } from 'Common/Types/Workflow/Components'; -import BaseModelComponentFactory from 'Common/Types/Workflow/Components/BaseModel'; -import Entities from 'Model/Models/Index'; + ComponentCategory, + ComponentInputType, +} from "Common/Types/Workflow/Component"; +import Components, { Categories } from "Common/Types/Workflow/Components"; +import BaseModelComponentFactory from "Common/Types/Workflow/Components/BaseModel"; +import Entities from "Model/Models/Index"; type LoadComponentsAndCategoriesFunction = () => { - components: Array<ComponentMetadata>; - categories: Array<ComponentCategory>; + components: Array<ComponentMetadata>; + categories: Array<ComponentCategory>; }; export const loadComponentsAndCategories: LoadComponentsAndCategoriesFunction = - (): { - components: Array<ComponentMetadata>; - categories: Array<ComponentCategory>; - } => { - let initComponents: Array<ComponentMetadata> = []; - const initCategories: Array<ComponentCategory> = [...Categories]; + (): { + components: Array<ComponentMetadata>; + categories: Array<ComponentCategory>; + } => { + let initComponents: Array<ComponentMetadata> = []; + const initCategories: Array<ComponentCategory> = [...Categories]; - initComponents = initComponents.concat(Components); + initComponents = initComponents.concat(Components); - for (const model of Entities) { - initComponents = initComponents.concat( - BaseModelComponentFactory.getComponents(new model()) - ); - initCategories.push({ - name: new model().singularName || 'Model', - description: `Interact with ${ - new model().singularName - } in your workflow.`, - icon: new model().icon || IconProp.Database, - }); - } + for (const model of Entities) { + initComponents = initComponents.concat( + BaseModelComponentFactory.getComponents(new model()), + ); + initCategories.push({ + name: new model().singularName || "Model", + description: `Interact with ${ + new model().singularName + } in your workflow.`, + icon: new model().icon || IconProp.Database, + }); + } - return { components: initComponents, categories: initCategories }; - }; + return { components: initComponents, categories: initCategories }; + }; type ComponentInputTypeToFormFieldTypeFunction = ( - componentInputType: ComponentInputType, - argValue: any + componentInputType: ComponentInputType, + argValue: any, ) => { - fieldType: FormFieldSchemaType; - dropdownOptions?: Array<DropdownOption> | undefined; + fieldType: FormFieldSchemaType; + dropdownOptions?: Array<DropdownOption> | undefined; }; export const componentInputTypeToFormFieldType: ComponentInputTypeToFormFieldTypeFunction = - ( - componentInputType: ComponentInputType, - argValue: any - ): { - fieldType: FormFieldSchemaType; - dropdownOptions?: Array<DropdownOption> | undefined; - } => { - // first priority. + ( + componentInputType: ComponentInputType, + argValue: any, + ): { + fieldType: FormFieldSchemaType; + dropdownOptions?: Array<DropdownOption> | undefined; + } => { + // first priority. - if (componentInputType === ComponentInputType.BaseModel) { - return { - fieldType: FormFieldSchemaType.JSON, - }; - } + if (componentInputType === ComponentInputType.BaseModel) { + return { + fieldType: FormFieldSchemaType.JSON, + }; + } - if (componentInputType === ComponentInputType.BaseModelArray) { - return { - fieldType: FormFieldSchemaType.JSON, - }; - } + if (componentInputType === ComponentInputType.BaseModelArray) { + return { + fieldType: FormFieldSchemaType.JSON, + }; + } - if (componentInputType === ComponentInputType.JSON) { - return { - fieldType: FormFieldSchemaType.JSON, - }; - } + if (componentInputType === ComponentInputType.JSON) { + return { + fieldType: FormFieldSchemaType.JSON, + }; + } - if (componentInputType === ComponentInputType.JSONArray) { - return { - fieldType: FormFieldSchemaType.JSON, - }; - } + if (componentInputType === ComponentInputType.JSONArray) { + return { + fieldType: FormFieldSchemaType.JSON, + }; + } - if (componentInputType === ComponentInputType.Markdown) { - return { - fieldType: FormFieldSchemaType.Markdown, - }; - } + if (componentInputType === ComponentInputType.Markdown) { + return { + fieldType: FormFieldSchemaType.Markdown, + }; + } - if (componentInputType === ComponentInputType.JavaScript) { - return { - fieldType: FormFieldSchemaType.JavaScript, - }; - } + if (componentInputType === ComponentInputType.JavaScript) { + return { + fieldType: FormFieldSchemaType.JavaScript, + }; + } - if (componentInputType === ComponentInputType.Query) { - return { - fieldType: FormFieldSchemaType.JSON, - }; - } + if (componentInputType === ComponentInputType.Query) { + return { + fieldType: FormFieldSchemaType.JSON, + }; + } - if (componentInputType === ComponentInputType.Select) { - return { - fieldType: FormFieldSchemaType.JSON, - }; - } + if (componentInputType === ComponentInputType.Select) { + return { + fieldType: FormFieldSchemaType.JSON, + }; + } - if (componentInputType === ComponentInputType.StringDictionary) { - return { - fieldType: FormFieldSchemaType.JSON, - }; - } + if (componentInputType === ComponentInputType.StringDictionary) { + return { + fieldType: FormFieldSchemaType.JSON, + }; + } - if (componentInputType === ComponentInputType.LongText) { - return { - fieldType: FormFieldSchemaType.LongText, - }; - } + if (componentInputType === ComponentInputType.LongText) { + return { + fieldType: FormFieldSchemaType.LongText, + }; + } - // Second priority. + // Second priority. - if ( - argValue && - typeof argValue === Typeof.String && - argValue.toString().includes('{{') - ) { - return { - fieldType: FormFieldSchemaType.Text, - dropdownOptions: [], - }; - } + if ( + argValue && + typeof argValue === Typeof.String && + argValue.toString().includes("{{") + ) { + return { + fieldType: FormFieldSchemaType.Text, + dropdownOptions: [], + }; + } - if (componentInputType === ComponentInputType.Boolean) { - return { - fieldType: FormFieldSchemaType.Toggle, - dropdownOptions: [], - }; - } + if (componentInputType === ComponentInputType.Boolean) { + return { + fieldType: FormFieldSchemaType.Toggle, + dropdownOptions: [], + }; + } - if (componentInputType === ComponentInputType.HTML) { - return { - fieldType: FormFieldSchemaType.HTML, - dropdownOptions: [], - }; - } + if (componentInputType === ComponentInputType.HTML) { + return { + fieldType: FormFieldSchemaType.HTML, + dropdownOptions: [], + }; + } - if (componentInputType === ComponentInputType.CronTab) { - return { - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: [ - { - label: 'Every Minute', - value: '* * * * *', - }, - { - label: 'Every 30 minutes', - value: '*/30 * * * *', - }, - { - label: 'Every Hour', - value: '0 * * * *', - }, - { - label: 'Every Day', - value: '0 0 * * *', - }, - { - label: 'Every Week', - value: '0 0 * * 0', - }, - { - label: 'Every Month', - value: '0 0 1 * *', - }, - { - label: 'Every Three Months', - value: '0 0 1 */3 *', - }, - { - label: 'Every Six Months', - value: '0 0 1 */6 *', - }, - ], - }; - } + if (componentInputType === ComponentInputType.CronTab) { + return { + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: [ + { + label: "Every Minute", + value: "* * * * *", + }, + { + label: "Every 30 minutes", + value: "*/30 * * * *", + }, + { + label: "Every Hour", + value: "0 * * * *", + }, + { + label: "Every Day", + value: "0 0 * * *", + }, + { + label: "Every Week", + value: "0 0 * * 0", + }, + { + label: "Every Month", + value: "0 0 1 * *", + }, + { + label: "Every Three Months", + value: "0 0 1 */3 *", + }, + { + label: "Every Six Months", + value: "0 0 1 */6 *", + }, + ], + }; + } - if (componentInputType === ComponentInputType.Operator) { - return { - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: [ - { - label: 'Equal To', - value: '==', - }, - { - label: 'Not Equal To', - value: '!=', - }, - { - label: 'Greater Than', - value: '>', - }, - { - label: 'Less Than', - value: '<', - }, - { - label: 'Greater Than or Equal', - value: '>=', - }, - { - label: 'Less Than or Equal', - value: '<=', - }, - ], - }; - } + if (componentInputType === ComponentInputType.Operator) { + return { + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: [ + { + label: "Equal To", + value: "==", + }, + { + label: "Not Equal To", + value: "!=", + }, + { + label: "Greater Than", + value: ">", + }, + { + label: "Less Than", + value: "<", + }, + { + label: "Greater Than or Equal", + value: ">=", + }, + { + label: "Less Than or Equal", + value: "<=", + }, + ], + }; + } - if (componentInputType === ComponentInputType.Date) { - return { - fieldType: FormFieldSchemaType.Date, - }; - } + if (componentInputType === ComponentInputType.Date) { + return { + fieldType: FormFieldSchemaType.Date, + }; + } - if (componentInputType === ComponentInputType.DateTime) { - return { - fieldType: FormFieldSchemaType.DateTime, - }; - } + if (componentInputType === ComponentInputType.DateTime) { + return { + fieldType: FormFieldSchemaType.DateTime, + }; + } - if (componentInputType === ComponentInputType.Decimal) { - return { - fieldType: FormFieldSchemaType.Number, - }; - } + if (componentInputType === ComponentInputType.Decimal) { + return { + fieldType: FormFieldSchemaType.Number, + }; + } - if (componentInputType === ComponentInputType.Email) { - return { - fieldType: FormFieldSchemaType.Email, - }; - } + if (componentInputType === ComponentInputType.Email) { + return { + fieldType: FormFieldSchemaType.Email, + }; + } - if (componentInputType === ComponentInputType.Number) { - return { - fieldType: FormFieldSchemaType.Number, - }; - } + if (componentInputType === ComponentInputType.Number) { + return { + fieldType: FormFieldSchemaType.Number, + }; + } - if (componentInputType === ComponentInputType.Password) { - return { - fieldType: FormFieldSchemaType.Password, - }; - } + if (componentInputType === ComponentInputType.Password) { + return { + fieldType: FormFieldSchemaType.Password, + }; + } - if (componentInputType === ComponentInputType.URL) { - return { - fieldType: FormFieldSchemaType.URL, - }; - } + if (componentInputType === ComponentInputType.URL) { + return { + fieldType: FormFieldSchemaType.URL, + }; + } - return { - fieldType: FormFieldSchemaType.Text, - dropdownOptions: [], - }; + return { + fieldType: FormFieldSchemaType.Text, + dropdownOptions: [], }; + }; diff --git a/CommonUI/src/Components/Workflow/VariableModal.tsx b/CommonUI/src/Components/Workflow/VariableModal.tsx index 696a24f6da..664b241b89 100644 --- a/CommonUI/src/Components/Workflow/VariableModal.tsx +++ b/CommonUI/src/Components/Workflow/VariableModal.tsx @@ -1,57 +1,55 @@ -import ModelListModal from '../ModelListModal/ModelListModal'; -import EqualToOrNull from 'Common/Types/BaseDatabase/EqualToOrNull'; -import ObjectID from 'Common/Types/ObjectID'; -import WorkflowVariable from 'Model/Models/WorkflowVariable'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ModelListModal from "../ModelListModal/ModelListModal"; +import EqualToOrNull from "Common/Types/BaseDatabase/EqualToOrNull"; +import ObjectID from "Common/Types/ObjectID"; +import WorkflowVariable from "Model/Models/WorkflowVariable"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - workflowId: ObjectID; - onClose: () => void; - onSave: (variableId: string) => void; + workflowId: ObjectID; + onClose: () => void; + onSave: (variableId: string) => void; } const VariableModal: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <ModelListModal - modalTitle="Select a variable" - query={{ - workflowId: new EqualToOrNull(props.workflowId.toString()), - }} - isSearchEnabled={true} - noItemsMessage="You do have any variables. Please add global or workflow variables." - modalDescription="This list contains both Global and Workflow variables." - titleField="name" - descriptionField="description" - headerField={(item: WorkflowVariable) => { - let variableType: string = 'GLOBAL VARIABLE'; + return ( + <ModelListModal + modalTitle="Select a variable" + query={{ + workflowId: new EqualToOrNull(props.workflowId.toString()), + }} + isSearchEnabled={true} + noItemsMessage="You do have any variables. Please add global or workflow variables." + modalDescription="This list contains both Global and Workflow variables." + titleField="name" + descriptionField="description" + headerField={(item: WorkflowVariable) => { + let variableType: string = "GLOBAL VARIABLE"; - if (item.workflowId) { - variableType = 'LOCAL WORKFLOW VARIABLE'; - } + if (item.workflowId) { + variableType = "LOCAL WORKFLOW VARIABLE"; + } - return ( - <p className="text-xs text-gray-400 mb-2">{variableType}</p> - ); - }} - modelType={WorkflowVariable} - select={{ - _id: true, - name: true, - description: true, - workflowId: true, - }} - onClose={props.onClose} - onSave={(variables: Array<WorkflowVariable>) => { - if (variables[0]?.workflowId) { - props.onSave(`{{local.variables.${variables[0]?.name}}}`); - } else { - props.onSave(`{{global.variables.${variables[0]?.name}}}`); - } - }} - /> - ); + return <p className="text-xs text-gray-400 mb-2">{variableType}</p>; + }} + modelType={WorkflowVariable} + select={{ + _id: true, + name: true, + description: true, + workflowId: true, + }} + onClose={props.onClose} + onSave={(variables: Array<WorkflowVariable>) => { + if (variables[0]?.workflowId) { + props.onSave(`{{local.variables.${variables[0]?.name}}}`); + } else { + props.onSave(`{{global.variables.${variables[0]?.name}}}`); + } + }} + /> + ); }; export default VariableModal; diff --git a/CommonUI/src/Components/Workflow/Workflow.tsx b/CommonUI/src/Components/Workflow/Workflow.tsx index 37b1dee051..237b147acf 100644 --- a/CommonUI/src/Components/Workflow/Workflow.tsx +++ b/CommonUI/src/Components/Workflow/Workflow.tsx @@ -1,545 +1,525 @@ -import WorkflowComponent from './Component'; -import ComponentSettingsModal from './ComponentSettingsModal'; -import ComponentsModal from './ComponentsModal'; -import RunModal from './RunModal'; -import { loadComponentsAndCategories } from './Utils'; -import { VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; +import WorkflowComponent from "./Component"; +import ComponentSettingsModal from "./ComponentSettingsModal"; +import ComponentsModal from "./ComponentsModal"; +import RunModal from "./RunModal"; +import { loadComponentsAndCategories } from "./Utils"; +import { VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; import ComponentMetadata, { - ComponentCategory, - ComponentType, - NodeDataProp, - NodeType, -} from 'Common/Types/Workflow/Component'; + ComponentCategory, + ComponentType, + NodeDataProp, + NodeType, +} from "Common/Types/Workflow/Component"; import React, { - FunctionComponent, - useCallback, - useEffect, - useRef, - useState, -} from 'react'; + FunctionComponent, + useCallback, + useEffect, + useRef, + useState, +} from "react"; import ReactFlow, { - Background, - Connection, - Controls, - Edge, - MarkerType, - MiniMap, - Node, - NodeTypes, - OnConnect, - ProOptions, - addEdge, - getConnectedEdges, - updateEdge, - useEdgesState, - useNodesState, -} from 'reactflow'; + Background, + Connection, + Controls, + Edge, + MarkerType, + MiniMap, + Node, + NodeTypes, + OnConnect, + ProOptions, + addEdge, + getConnectedEdges, + updateEdge, + useEdgesState, + useNodesState, +} from "reactflow"; // 👇 you need to import the reactflow styles -import 'reactflow/dist/style.css'; +import "reactflow/dist/style.css"; type GetPlaceholderTriggerNodeFunction = () => Node; export const getPlaceholderTriggerNode: GetPlaceholderTriggerNodeFunction = - (): Node => { - return { - id: ObjectID.generate().toString(), - type: 'node', - position: { x: 100, y: 100 }, - data: { - metadata: { - iconProp: IconProp.Bolt, - componentType: ComponentType.Trigger, - title: 'Trigger', - description: 'Please click here to add trigger', - }, - metadataId: '', - internalId: '', - nodeType: NodeType.PlaceholderNode, - id: '', - error: '', - }, - }; + (): Node => { + return { + id: ObjectID.generate().toString(), + type: "node", + position: { x: 100, y: 100 }, + data: { + metadata: { + iconProp: IconProp.Bolt, + componentType: ComponentType.Trigger, + title: "Trigger", + description: "Please click here to add trigger", + }, + metadataId: "", + internalId: "", + nodeType: NodeType.PlaceholderNode, + id: "", + error: "", + }, }; + }; const nodeTypes: NodeTypes = { - node: WorkflowComponent, + node: WorkflowComponent, }; const edgeStyle: React.CSSProperties = { - strokeWidth: '2px', - stroke: '#94a3b8', - color: '#94a3b8', + strokeWidth: "2px", + stroke: "#94a3b8", + color: "#94a3b8", }; const selectedEdgeStyle: React.CSSProperties = { - strokeWidth: '2px', - stroke: '#818cf8', - color: '#818cf8', + strokeWidth: "2px", + stroke: "#818cf8", + color: "#818cf8", }; type GetEdgeDefaultPropsFunction = (selected: boolean) => JSONObject; export const getEdgeDefaultProps: GetEdgeDefaultPropsFunction = ( - selected: boolean + selected: boolean, ): JSONObject => { - return { - type: 'smoothstep', - markerEnd: { - type: MarkerType.Arrow, - color: edgeStyle.color?.toString() || '', - }, - style: selected ? { ...selectedEdgeStyle } : { ...edgeStyle }, - }; + return { + type: "smoothstep", + markerEnd: { + type: MarkerType.Arrow, + color: edgeStyle.color?.toString() || "", + }, + style: selected ? { ...selectedEdgeStyle } : { ...edgeStyle }, + }; }; export interface ComponentProps { - initialNodes: Array<Node>; - initialEdges: Array<Edge>; - onWorkflowUpdated: (nodes: Array<Node>, edges: Array<Edge>) => void; - showComponentsPickerModal: boolean; - showRunModal: boolean; - onComponentPickerModalUpdate: (isModalShown: boolean) => void; - workflowId: ObjectID; - onRunModalUpdate: (isModalShown: boolean) => void; - onRun: (trigger: NodeDataProp) => void; + initialNodes: Array<Node>; + initialEdges: Array<Edge>; + onWorkflowUpdated: (nodes: Array<Node>, edges: Array<Edge>) => void; + showComponentsPickerModal: boolean; + showRunModal: boolean; + onComponentPickerModalUpdate: (isModalShown: boolean) => void; + workflowId: ObjectID; + onRunModalUpdate: (isModalShown: boolean) => void; + onRun: (trigger: NodeDataProp) => void; } const Workflow: FunctionComponent<ComponentProps> = (props: ComponentProps) => { - const [allComponentMetadata, setAllComponentMetadata] = useState< - Array<ComponentMetadata> - >([]); - const [allComponentCategories, setAllComponentCategories] = useState< - Array<ComponentCategory> - >([]); + const [allComponentMetadata, setAllComponentMetadata] = useState< + Array<ComponentMetadata> + >([]); + const [allComponentCategories, setAllComponentCategories] = useState< + Array<ComponentCategory> + >([]); - useEffect(() => { - const value: { - components: Array<ComponentMetadata>; - categories: Array<ComponentCategory>; - } = loadComponentsAndCategories(); + useEffect(() => { + const value: { + components: Array<ComponentMetadata>; + categories: Array<ComponentCategory>; + } = loadComponentsAndCategories(); - setAllComponentCategories(value.categories); - setAllComponentMetadata(value.components); - }, []); + setAllComponentCategories(value.categories); + setAllComponentMetadata(value.components); + }, []); - const edgeUpdateSuccessful: any = useRef(true); - const [showComponentSettingsModal, setShowComponentSettingsModal] = - useState<boolean>(false); - const [selectedNodeData, setSelectedNodeData] = - useState<NodeDataProp | null>(null); + const edgeUpdateSuccessful: any = useRef(true); + const [showComponentSettingsModal, setShowComponentSettingsModal] = + useState<boolean>(false); + const [selectedNodeData, setSelectedNodeData] = useState<NodeDataProp | null>( + null, + ); - type OnNodeClickFunction = (data: NodeDataProp) => void; + type OnNodeClickFunction = (data: NodeDataProp) => void; - const onNodeClick: OnNodeClickFunction = (data: NodeDataProp) => { - // if placeholder node is clicked then show modal. + const onNodeClick: OnNodeClickFunction = (data: NodeDataProp) => { + // if placeholder node is clicked then show modal. - if (data.nodeType === NodeType.PlaceholderNode) { - if (data.componentType === ComponentType.Component) { - setShowComponentsModal(true); - } else { - setShowTriggersModal(true); - } - } else { - setShowComponentSettingsModal(true); - setSelectedNodeData(data); - } - }; + if (data.nodeType === NodeType.PlaceholderNode) { + if (data.componentType === ComponentType.Component) { + setShowComponentsModal(true); + } else { + setShowTriggersModal(true); + } + } else { + setShowComponentSettingsModal(true); + setSelectedNodeData(data); + } + }; - useEffect(() => { - if (props.showComponentsPickerModal) { - setShowComponentsModal(true); - } else { - setShowComponentsModal(false); - } - }, [props.showComponentsPickerModal]); + useEffect(() => { + if (props.showComponentsPickerModal) { + setShowComponentsModal(true); + } else { + setShowComponentsModal(false); + } + }, [props.showComponentsPickerModal]); - useEffect(() => { - if (props.showRunModal) { - setShowRunModal(true); - } else { - setShowComponentsModal(false); - } - }, [props.showRunModal]); + useEffect(() => { + if (props.showRunModal) { + setShowRunModal(true); + } else { + setShowComponentsModal(false); + } + }, [props.showRunModal]); - type DeleteNodeFunction = (id: string) => void; + type DeleteNodeFunction = (id: string) => void; - const deleteNode: DeleteNodeFunction = (id: string): void => { - // remove the node. + const deleteNode: DeleteNodeFunction = (id: string): void => { + // remove the node. - const nodesToDelete: Array<Node> = [...nodes].filter((node: Node) => { - return node.data.id === id; - }); - const edgeToDelete: Array<Edge> = getConnectedEdges( - nodesToDelete, - edges - ); + const nodesToDelete: Array<Node> = [...nodes].filter((node: Node) => { + return node.data.id === id; + }); + const edgeToDelete: Array<Edge> = getConnectedEdges(nodesToDelete, edges); - setNodes((nds: Array<Node>) => { - let nodeToUpdate: Array<Node> = nds.filter((node: Node) => { - return node.data.id !== id; - }); + setNodes((nds: Array<Node>) => { + let nodeToUpdate: Array<Node> = nds.filter((node: Node) => { + return node.data.id !== id; + }); - if ( - nodeToUpdate.filter((n: Node) => { - return ( - (n.data as NodeDataProp).componentType === - ComponentType.Trigger && - (n.data as NodeDataProp).nodeType === NodeType.Node - ); - }).length === 0 - ) { - nodeToUpdate = nodeToUpdate.concat(getPlaceholderTriggerNode()); - } + if ( + nodeToUpdate.filter((n: Node) => { + return ( + (n.data as NodeDataProp).componentType === ComponentType.Trigger && + (n.data as NodeDataProp).nodeType === NodeType.Node + ); + }).length === 0 + ) { + nodeToUpdate = nodeToUpdate.concat(getPlaceholderTriggerNode()); + } - return nodeToUpdate; - }); + return nodeToUpdate; + }); - setEdges((eds: Array<Edge>) => { - return eds - .filter((edge: Edge) => { - const idsToDelete: Array<string> = edgeToDelete.map( - (e: Edge) => { - return e.id; - } - ); - return !idsToDelete.includes(edge.id); - }) - .map((edge: Edge) => { - return { - ...edge, - ...getEdgeDefaultProps(edge.selected || false), - }; - }); - }); - }; - - const [nodes, setNodes, onNodesChange] = useNodesState( - props.initialNodes.map((node: Node) => { - node.data.onClick = onNodeClick; - return node; + setEdges((eds: Array<Edge>) => { + return eds + .filter((edge: Edge) => { + const idsToDelete: Array<string> = edgeToDelete.map((e: Edge) => { + return e.id; + }); + return !idsToDelete.includes(edge.id); }) - ); + .map((edge: Edge) => { + return { + ...edge, + ...getEdgeDefaultProps(edge.selected || false), + }; + }); + }); + }; - const [edges, setEdges, onEdgesChange] = useEdgesState( - props.initialEdges.map((edge: Edge) => { - // add style. + const [nodes, setNodes, onNodesChange] = useNodesState( + props.initialNodes.map((node: Node) => { + node.data.onClick = onNodeClick; + return node; + }), + ); - edge = { - ...edge, - ...getEdgeDefaultProps(edge.selected || false), + const [edges, setEdges, onEdgesChange] = useEdgesState( + props.initialEdges.map((edge: Edge) => { + // add style. + + edge = { + ...edge, + ...getEdgeDefaultProps(edge.selected || false), + }; + + return edge; + }), + ); + + useEffect(() => { + if (props.onWorkflowUpdated) { + props.onWorkflowUpdated(nodes, edges); + } + }, [nodes, edges]); + + const proOptions: ProOptions = { hideAttribution: true }; + + const onConnect: OnConnect = useCallback( + (params: any) => { + return setEdges((eds: Array<Edge>) => { + return addEdge( + { + ...params, + ...getEdgeDefaultProps(params.selected), + }, + eds.map((edge: Edge) => { + return { + ...edge, + ...getEdgeDefaultProps(edge.selected || false), }; + }), + ); + }); + }, + [setEdges], + ); - return edge; - }) - ); + const onEdgeUpdateStart: any = useCallback(() => { + edgeUpdateSuccessful.current = false; + }, []); - useEffect(() => { - if (props.onWorkflowUpdated) { - props.onWorkflowUpdated(nodes, edges); - } - }, [nodes, edges]); + const onEdgeUpdate: any = useCallback( + (oldEdge: Edge, newConnection: Connection) => { + edgeUpdateSuccessful.current = true; + setEdges((eds: Array<Edge>) => { + return updateEdge( + { + ...oldEdge, + markerEnd: { + type: MarkerType.Arrow, + color: edgeStyle.color?.toString() || "", + }, + style: edgeStyle, + }, + newConnection, + eds.map((edge: Edge) => { + return { + ...edge, + ...getEdgeDefaultProps(edge.selected || false), + }; + }), + ); + }); + }, + [], + ); - const proOptions: ProOptions = { hideAttribution: true }; + const onEdgeUpdateEnd: any = useCallback((_props: any, edge: Edge) => { + if (!edgeUpdateSuccessful.current) { + setEdges((eds: Array<Edge>) => { + return eds + .filter((e: Edge) => { + return e.id !== edge.id; + }) + .map((edge: Edge) => { + return { + ...edge, + ...getEdgeDefaultProps(edge.selected || false), + }; + }); + }); + } - const onConnect: OnConnect = useCallback( - (params: any) => { - return setEdges((eds: Array<Edge>) => { - return addEdge( - { - ...params, - ...getEdgeDefaultProps(params.selected), - }, - eds.map((edge: Edge) => { - return { - ...edge, - ...getEdgeDefaultProps(edge.selected || false), - }; - }) - ); - }); - }, - [setEdges] - ); + edgeUpdateSuccessful.current = true; + }, []); - const onEdgeUpdateStart: any = useCallback(() => { - edgeUpdateSuccessful.current = false; - }, []); + const [showComponentsModal, setShowComponentsModal] = + useState<boolean>(false); - const onEdgeUpdate: any = useCallback( - (oldEdge: Edge, newConnection: Connection) => { - edgeUpdateSuccessful.current = true; - setEdges((eds: Array<Edge>) => { - return updateEdge( - { - ...oldEdge, - markerEnd: { - type: MarkerType.Arrow, - color: edgeStyle.color?.toString() || '', - }, - style: edgeStyle, - }, - newConnection, - eds.map((edge: Edge) => { - return { - ...edge, - ...getEdgeDefaultProps(edge.selected || false), - }; - }) - ); - }); - }, - [] - ); + const [showTriggersModal, setShowTriggersModal] = useState<boolean>(false); - const onEdgeUpdateEnd: any = useCallback((_props: any, edge: Edge) => { - if (!edgeUpdateSuccessful.current) { - setEdges((eds: Array<Edge>) => { - return eds - .filter((e: Edge) => { - return e.id !== edge.id; - }) - .map((edge: Edge) => { - return { - ...edge, - ...getEdgeDefaultProps(edge.selected || false), - }; - }); - }); - } + const [showRunModal, setShowRunModal] = useState<boolean>(false); - edgeUpdateSuccessful.current = true; - }, []); + useEffect(() => { + props.onComponentPickerModalUpdate(showComponentsModal); + }, [showComponentsModal]); - const [showComponentsModal, setShowComponentsModal] = - useState<boolean>(false); - - const [showTriggersModal, setShowTriggersModal] = useState<boolean>(false); - - const [showRunModal, setShowRunModal] = useState<boolean>(false); - - useEffect(() => { - props.onComponentPickerModalUpdate(showComponentsModal); - }, [showComponentsModal]); - - const refreshEdges: VoidFunction = (): void => { - setEdges((eds: Array<Edge>) => { - return eds.map((edge: Edge) => { - return { - ...edge, - ...getEdgeDefaultProps(edge.selected || false), - }; - }); - }); - }; - - useEffect(() => { - props.onRunModalUpdate(showRunModal); - }, [showRunModal]); - - type AddToGraphFunction = (componentMetadata: ComponentMetadata) => void; - - const addToGraph: AddToGraphFunction = ( - componentMetadata: ComponentMetadata - ) => { - const metaDataId: string = componentMetadata.id; - - let hasFoundExistingId: boolean = true; - let idCounter: number = 1; - while (hasFoundExistingId) { - const id: string = `${metaDataId}-${idCounter}`; - - const exitingNode: Node | undefined = nodes.find((i: Node) => { - return i.data.id === id; - }); - - if (!exitingNode) { - hasFoundExistingId = false; - break; - } - - idCounter++; - } - - const compToAdd: Node = { - id: ObjectID.generate().toString(), // react-flow id - type: 'node', - position: { x: 200, y: 200 }, - selected: true, - data: { - nodeType: NodeType.Node, - id: `${metaDataId}-${idCounter}`, - error: '', - metadata: { ...componentMetadata }, - metadataId: componentMetadata.id, - internalId: ObjectID.generate().toString(), // runner id - componentType: componentMetadata.componentType, - } as NodeDataProp, + const refreshEdges: VoidFunction = (): void => { + setEdges((eds: Array<Edge>) => { + return eds.map((edge: Edge) => { + return { + ...edge, + ...getEdgeDefaultProps(edge.selected || false), }; + }); + }); + }; - if (componentMetadata.componentType === ComponentType.Trigger) { - // remove the placeholder trigger element from graph. - setNodes((nds: Array<Node>) => { - return nds - .filter((node: Node) => { - return ( - node.data.componentType === ComponentType.Component - ); - }) - .map((n: Node) => { - return { ...n, selected: false }; - }) - .concat({ ...compToAdd } as any); - }); - } else { - setNodes((nds: Array<Node>) => { - return nds - .map((n: Node) => { - return { ...n, selected: false }; - }) - .concat({ ...compToAdd } as any); - }); - } + useEffect(() => { + props.onRunModalUpdate(showRunModal); + }, [showRunModal]); + + type AddToGraphFunction = (componentMetadata: ComponentMetadata) => void; + + const addToGraph: AddToGraphFunction = ( + componentMetadata: ComponentMetadata, + ) => { + const metaDataId: string = componentMetadata.id; + + let hasFoundExistingId: boolean = true; + let idCounter: number = 1; + while (hasFoundExistingId) { + const id: string = `${metaDataId}-${idCounter}`; + + const exitingNode: Node | undefined = nodes.find((i: Node) => { + return i.data.id === id; + }); + + if (!exitingNode) { + hasFoundExistingId = false; + break; + } + + idCounter++; + } + + const compToAdd: Node = { + id: ObjectID.generate().toString(), // react-flow id + type: "node", + position: { x: 200, y: 200 }, + selected: true, + data: { + nodeType: NodeType.Node, + id: `${metaDataId}-${idCounter}`, + error: "", + metadata: { ...componentMetadata }, + metadataId: componentMetadata.id, + internalId: ObjectID.generate().toString(), // runner id + componentType: componentMetadata.componentType, + } as NodeDataProp, }; - return ( - <div className="h-[48rem]"> - <ReactFlow - nodes={nodes} - edges={edges} - fitView={true} - onEdgeClick={() => { - refreshEdges(); - }} - onNodeClick={() => { - refreshEdges(); - }} - proOptions={proOptions} - onNodesChange={onNodesChange} - onEdgesChange={onEdgesChange} - onConnect={onConnect} - multiSelectionKeyCode={null} - onEdgeUpdate={onEdgeUpdate} - nodeTypes={nodeTypes} - onEdgeUpdateStart={onEdgeUpdateStart} - onEdgeUpdateEnd={onEdgeUpdateEnd} - > - <MiniMap /> - <Controls /> - <Background color="#111827" /> - </ReactFlow> + if (componentMetadata.componentType === ComponentType.Trigger) { + // remove the placeholder trigger element from graph. + setNodes((nds: Array<Node>) => { + return nds + .filter((node: Node) => { + return node.data.componentType === ComponentType.Component; + }) + .map((n: Node) => { + return { ...n, selected: false }; + }) + .concat({ ...compToAdd } as any); + }); + } else { + setNodes((nds: Array<Node>) => { + return nds + .map((n: Node) => { + return { ...n, selected: false }; + }) + .concat({ ...compToAdd } as any); + }); + } + }; - {showComponentsModal && ( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={() => { - setShowComponentsModal(false); - }} - categories={allComponentCategories} - components={allComponentMetadata.filter( - (comp: ComponentMetadata) => { - return ( - comp.componentType === ComponentType.Component - ); - } - )} - onComponentClick={(component: ComponentMetadata) => { - setShowComponentsModal(false); + return ( + <div className="h-[48rem]"> + <ReactFlow + nodes={nodes} + edges={edges} + fitView={true} + onEdgeClick={() => { + refreshEdges(); + }} + onNodeClick={() => { + refreshEdges(); + }} + proOptions={proOptions} + onNodesChange={onNodesChange} + onEdgesChange={onEdgesChange} + onConnect={onConnect} + multiSelectionKeyCode={null} + onEdgeUpdate={onEdgeUpdate} + nodeTypes={nodeTypes} + onEdgeUpdateStart={onEdgeUpdateStart} + onEdgeUpdateEnd={onEdgeUpdateEnd} + > + <MiniMap /> + <Controls /> + <Background color="#111827" /> + </ReactFlow> - addToGraph(component); - }} - /> - )} + {showComponentsModal && ( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={() => { + setShowComponentsModal(false); + }} + categories={allComponentCategories} + components={allComponentMetadata.filter((comp: ComponentMetadata) => { + return comp.componentType === ComponentType.Component; + })} + onComponentClick={(component: ComponentMetadata) => { + setShowComponentsModal(false); - {showTriggersModal && ( - <ComponentsModal - componentsType={ComponentType.Trigger} - onCloseModal={() => { - setShowTriggersModal(false); - }} - categories={allComponentCategories} - components={allComponentMetadata.filter( - (comp: ComponentMetadata) => { - return comp.componentType === ComponentType.Trigger; - } - )} - onComponentClick={(component: ComponentMetadata) => { - setShowTriggersModal(false); + addToGraph(component); + }} + /> + )} - addToGraph(component); - }} - /> - )} + {showTriggersModal && ( + <ComponentsModal + componentsType={ComponentType.Trigger} + onCloseModal={() => { + setShowTriggersModal(false); + }} + categories={allComponentCategories} + components={allComponentMetadata.filter((comp: ComponentMetadata) => { + return comp.componentType === ComponentType.Trigger; + })} + onComponentClick={(component: ComponentMetadata) => { + setShowTriggersModal(false); - {showComponentSettingsModal && selectedNodeData && ( - <ComponentSettingsModal - graphComponents={nodes.map((node: Node) => { - return node.data as NodeDataProp; - })} - workflowId={props.workflowId} - component={selectedNodeData} - title={ - selectedNodeData && selectedNodeData.metadata.title - ? selectedNodeData.metadata.title - : 'Component Properties' - } - onDelete={(component: NodeDataProp) => { - deleteNode(component.id); - }} - description={ - selectedNodeData && - selectedNodeData.metadata.description - ? selectedNodeData.metadata.description - : 'Edit Component Properties and variables here.' - } - onClose={() => { - setShowComponentSettingsModal(false); - }} - onSave={(componentData: NodeDataProp) => { - // Update the node. + addToGraph(component); + }} + /> + )} - setNodes((nds: Array<Node>) => { - return nds.map((n: Node) => { - if ( - n.data.internalId === - componentData.internalId - ) { - n.data = componentData; - } + {showComponentSettingsModal && selectedNodeData && ( + <ComponentSettingsModal + graphComponents={nodes.map((node: Node) => { + return node.data as NodeDataProp; + })} + workflowId={props.workflowId} + component={selectedNodeData} + title={ + selectedNodeData && selectedNodeData.metadata.title + ? selectedNodeData.metadata.title + : "Component Properties" + } + onDelete={(component: NodeDataProp) => { + deleteNode(component.id); + }} + description={ + selectedNodeData && selectedNodeData.metadata.description + ? selectedNodeData.metadata.description + : "Edit Component Properties and variables here." + } + onClose={() => { + setShowComponentSettingsModal(false); + }} + onSave={(componentData: NodeDataProp) => { + // Update the node. - return n; - }); - }); + setNodes((nds: Array<Node>) => { + return nds.map((n: Node) => { + if (n.data.internalId === componentData.internalId) { + n.data = componentData; + } - setShowComponentSettingsModal(false); - }} - /> - )} + return n; + }); + }); - {showRunModal && ( - <RunModal - trigger={ - ( - nodes.find((i: Node) => { - return ( - i.data.metadata.componentType === - ComponentType.Trigger - ); - }) || getPlaceholderTriggerNode() - ).data - } - onClose={() => { - setShowRunModal(false); - }} - onRun={(trigger: NodeDataProp) => { - props.onRun(trigger); - }} - /> - )} - </div> - ); + setShowComponentSettingsModal(false); + }} + /> + )} + + {showRunModal && ( + <RunModal + trigger={ + ( + nodes.find((i: Node) => { + return i.data.metadata.componentType === ComponentType.Trigger; + }) || getPlaceholderTriggerNode() + ).data + } + onClose={() => { + setShowRunModal(false); + }} + onRun={(trigger: NodeDataProp) => { + props.onRun(trigger); + }} + /> + )} + </div> + ); }; export default Workflow; diff --git a/CommonUI/src/Components/Workflow/WorkflowStatus.tsx b/CommonUI/src/Components/Workflow/WorkflowStatus.tsx index e7cf3a400d..8fdf6fbc6e 100644 --- a/CommonUI/src/Components/Workflow/WorkflowStatus.tsx +++ b/CommonUI/src/Components/Workflow/WorkflowStatus.tsx @@ -1,37 +1,37 @@ -import Pill from '../Pill/Pill'; -import { Green, Red, Yellow } from 'Common/Types/BrandColors'; -import WorkflowStatus from 'Common/Types/Workflow/WorkflowStatus'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Pill from "../Pill/Pill"; +import { Green, Red, Yellow } from "Common/Types/BrandColors"; +import WorkflowStatus from "Common/Types/Workflow/WorkflowStatus"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - status: WorkflowStatus; + status: WorkflowStatus; } const WorkflowStatusElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.status === WorkflowStatus.Success) { - return <Pill color={Green} text="Success" />; - } - if (props.status === WorkflowStatus.Running) { - return <Pill color={Yellow} text="Running" />; - } - if (props.status === WorkflowStatus.Scheduled) { - return <Pill color={Yellow} text="Scheduled" />; - } - if (props.status === WorkflowStatus.Error) { - return <Pill color={Red} text="Error" />; - } + if (props.status === WorkflowStatus.Success) { + return <Pill color={Green} text="Success" />; + } + if (props.status === WorkflowStatus.Running) { + return <Pill color={Yellow} text="Running" />; + } + if (props.status === WorkflowStatus.Scheduled) { + return <Pill color={Yellow} text="Scheduled" />; + } + if (props.status === WorkflowStatus.Error) { + return <Pill color={Red} text="Error" />; + } - if (props.status === WorkflowStatus.Timeout) { - return <Pill color={Red} text="Timeout" />; - } + if (props.status === WorkflowStatus.Timeout) { + return <Pill color={Red} text="Timeout" />; + } - if (props.status === WorkflowStatus.WorkflowCountExceeded) { - return <Pill color={Red} text="Execution Exceeded Current Plan" />; - } + if (props.status === WorkflowStatus.WorkflowCountExceeded) { + return <Pill color={Red} text="Execution Exceeded Current Plan" />; + } - return <Pill color={Yellow} text="Unknown" />; + return <Pill color={Yellow} text="Unknown" />; }; export default WorkflowStatusElement; diff --git a/CommonUI/src/Config.ts b/CommonUI/src/Config.ts index 794ed487b8..b589da4284 100644 --- a/CommonUI/src/Config.ts +++ b/CommonUI/src/Config.ts @@ -1,51 +1,51 @@ import { - AccountsRoute, - AdminDashboardRoute, - ApiReferenceRoute, - AppApiRoute, - DashboardRoute, - DocsRoute, - FileRoute, - HomeRoute, - IdentityRoute, - IngestorRoute, - IntegrationRoute, - NotificationRoute, - RealtimeRoute, - StatusPageRoute, - WorkflowRoute, -} from 'Common/ServiceRoute'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import Dictionary from 'Common/Types/Dictionary'; -import { JSONObject } from 'Common/Types/JSON'; -import Version from 'Common/Types/Version'; + AccountsRoute, + AdminDashboardRoute, + ApiReferenceRoute, + AppApiRoute, + DashboardRoute, + DocsRoute, + FileRoute, + HomeRoute, + IdentityRoute, + IngestorRoute, + IntegrationRoute, + NotificationRoute, + RealtimeRoute, + StatusPageRoute, + WorkflowRoute, +} from "Common/ServiceRoute"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import Dictionary from "Common/Types/Dictionary"; +import { JSONObject } from "Common/Types/JSON"; +import Version from "Common/Types/Version"; type GetAllEnvVarsFunction = () => JSONObject; export const getAllEnvVars: GetAllEnvVarsFunction = (): JSONObject => { - const envVars: JSONObject = window?.process?.env || process?.env || {}; - return envVars; + const envVars: JSONObject = window?.process?.env || process?.env || {}; + return envVars; }; type GetEnvFunction = (key: string) => string; export const env: GetEnvFunction = (key: string): string => { - const allEnv: JSONObject = getAllEnvVars(); - return (allEnv[key] as string) || ''; + const allEnv: JSONObject = getAllEnvVars(); + return (allEnv[key] as string) || ""; }; export const HTTP_PROTOCOL: Protocol = - env('HTTP_PROTOCOL') === 'https' ? Protocol.HTTPS : Protocol.HTTP; + env("HTTP_PROTOCOL") === "https" ? Protocol.HTTPS : Protocol.HTTP; -export const HOST: string = env('HOST') || ''; +export const HOST: string = env("HOST") || ""; -export const BILLING_ENABLED: boolean = env('BILLING_ENABLED') === 'true'; -export const BILLING_PUBLIC_KEY: string = env('BILLING_PUBLIC_KEY') || ''; +export const BILLING_ENABLED: boolean = env("BILLING_ENABLED") === "true"; +export const BILLING_PUBLIC_KEY: string = env("BILLING_PUBLIC_KEY") || ""; -export const VERSION: Version = new Version(env('VERSION') || '1.0.0'); +export const VERSION: Version = new Version(env("VERSION") || "1.0.0"); export const APP_HOSTNAME: Hostname = Hostname.fromString(HOST); @@ -77,128 +77,128 @@ export const HOME_HOSTNAME: Hostname = Hostname.fromString(HOST); export const FILE_HOSTNAME: Hostname = Hostname.fromString(HOST); export const APP_API_URL: URL = new URL( - HTTP_PROTOCOL, - APP_HOSTNAME, - AppApiRoute + HTTP_PROTOCOL, + APP_HOSTNAME, + AppApiRoute, ); export const REALTIME_URL: URL = new URL( - HTTP_PROTOCOL, - REALTIME_HOSTNAME, - RealtimeRoute + HTTP_PROTOCOL, + REALTIME_HOSTNAME, + RealtimeRoute, ); export const DOCS_URL: URL = new URL( - HTTP_PROTOCOL, - REALTIME_HOSTNAME, - DocsRoute + HTTP_PROTOCOL, + REALTIME_HOSTNAME, + DocsRoute, ); export const IDENTITY_URL: URL = new URL( - HTTP_PROTOCOL, - IDENTITY_HOSTNAME, - IdentityRoute + HTTP_PROTOCOL, + IDENTITY_HOSTNAME, + IdentityRoute, ); export const NOTIFICATION_URL: URL = new URL( - HTTP_PROTOCOL, - NOTIFICATION_HOSTNAME, - NotificationRoute + HTTP_PROTOCOL, + NOTIFICATION_HOSTNAME, + NotificationRoute, ); export const WORKFLOW_URL: URL = new URL( - HTTP_PROTOCOL, - WORKFLOW_HOSTNAME, - WorkflowRoute + HTTP_PROTOCOL, + WORKFLOW_HOSTNAME, + WorkflowRoute, ); export const INGESTOR_URL: URL = new URL( - HTTP_PROTOCOL, - INGESTOR_HOSTNAME, - IngestorRoute + HTTP_PROTOCOL, + INGESTOR_HOSTNAME, + IngestorRoute, ); export const STATUS_PAGE_URL: URL = new URL( - HTTP_PROTOCOL, - STATUS_PAGE_HOSTNAME, - StatusPageRoute + HTTP_PROTOCOL, + STATUS_PAGE_HOSTNAME, + StatusPageRoute, ); export const FILE_URL: URL = new URL(HTTP_PROTOCOL, FILE_HOSTNAME, FileRoute); export const DASHBOARD_URL: URL = new URL( - HTTP_PROTOCOL, - DASHBOARD_HOSTNAME, - DashboardRoute + HTTP_PROTOCOL, + DASHBOARD_HOSTNAME, + DashboardRoute, ); export const INTEGRATION_URL: URL = new URL( - HTTP_PROTOCOL, - INTEGRATION_HOSTNAME, - IntegrationRoute + HTTP_PROTOCOL, + INTEGRATION_HOSTNAME, + IntegrationRoute, ); export const API_DOCS_URL: URL = new URL( - HTTP_PROTOCOL, - API_DOCS_HOSTNAME, - ApiReferenceRoute + HTTP_PROTOCOL, + API_DOCS_HOSTNAME, + ApiReferenceRoute, ); export const ADMIN_DASHBOARD_URL: URL = new URL( - HTTP_PROTOCOL, - ADMIN_DASHBOARD_HOSTNAME, - AdminDashboardRoute + HTTP_PROTOCOL, + ADMIN_DASHBOARD_HOSTNAME, + AdminDashboardRoute, ); export const ACCOUNTS_URL: URL = new URL( - HTTP_PROTOCOL, - ACCOUNTS_HOSTNAME, - AccountsRoute + HTTP_PROTOCOL, + ACCOUNTS_HOSTNAME, + AccountsRoute, ); export const HOME_URL: URL = new URL(HTTP_PROTOCOL, HOME_HOSTNAME, HomeRoute); export const SubscriptionPlans: Array<SubscriptionPlan> = - SubscriptionPlan.getSubscriptionPlans(getAllEnvVars()); + SubscriptionPlan.getSubscriptionPlans(getAllEnvVars()); export const StatusPageCNameRecord: string = - env('STATUS_PAGE_CNAME_RECORD') || ''; + env("STATUS_PAGE_CNAME_RECORD") || ""; -export const AnalyticsKey: string = env('ANALYTICS_KEY') || ''; -export const AnalyticsHost: string = env('ANALYTICS_HOST'); +export const AnalyticsKey: string = env("ANALYTICS_KEY") || ""; +export const AnalyticsHost: string = env("ANALYTICS_HOST"); -export const GitSha: string = env('GIT_SHA') || ''; -export const AppVersion: string = env('APP_VERSION') || ''; +export const GitSha: string = env("GIT_SHA") || ""; +export const AppVersion: string = env("APP_VERSION") || ""; export const OpenTelemetryExporterOtlpEndpoint: URL | null = env( - 'OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT' + "OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT", ) - ? URL.fromString(env('OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT').toString()) - : null; + ? URL.fromString(env("OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT").toString()) + : null; type GetOpenTelemetryExporterOtlpHeadersFunction = () => Dictionary<string>; const getOpenTelemetryExporterOtlpHeaders: GetOpenTelemetryExporterOtlpHeadersFunction = - (): Dictionary<string> => { - if (!env('OPENTELEMETRY_EXPORTER_OTLP_HEADERS')) { - return {}; - } + (): Dictionary<string> => { + if (!env("OPENTELEMETRY_EXPORTER_OTLP_HEADERS")) { + return {}; + } - const headersStrings: Array<string> = env( - 'OPENTELEMETRY_EXPORTER_OTLP_HEADERS' - ) - .toString() - .split(';'); + const headersStrings: Array<string> = env( + "OPENTELEMETRY_EXPORTER_OTLP_HEADERS", + ) + .toString() + .split(";"); - const headers: Dictionary<string> = {}; + const headers: Dictionary<string> = {}; - for (const headerString of headersStrings) { - const header: Array<string> = headerString.split('='); - if (header.length === 2) { - headers[header[0]!.toString()] = header[1]!.toString(); - } - } + for (const headerString of headersStrings) { + const header: Array<string> = headerString.split("="); + if (header.length === 2) { + headers[header[0]!.toString()] = header[1]!.toString(); + } + } - return headers; - }; + return headers; + }; export const OpenTelemetryExporterOtlpHeaders: Dictionary<string> = - getOpenTelemetryExporterOtlpHeaders(); + getOpenTelemetryExporterOtlpHeaders(); diff --git a/CommonUI/src/Container.tsx b/CommonUI/src/Container.tsx index 7a61ae92de..57450f838a 100644 --- a/CommonUI/src/Container.tsx +++ b/CommonUI/src/Container.tsx @@ -1,16 +1,16 @@ -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; type Props = { - children: Array<ReactElement>; - title: string; + children: Array<ReactElement>; + title: string; }; const Container: FunctionComponent<Props> = ({ children, title }: Props) => { - useEffect(() => { - document.title = `OneUptime | ${title}`; - }, []); + useEffect(() => { + document.title = `OneUptime | ${title}`; + }, []); - return <div>{children}</div>; + return <div>{children}</div>; }; export default Container; diff --git a/CommonUI/src/Tests/Components/404.test.tsx b/CommonUI/src/Tests/Components/404.test.tsx index f93b842fef..ff0372c1a1 100644 --- a/CommonUI/src/Tests/Components/404.test.tsx +++ b/CommonUI/src/Tests/Components/404.test.tsx @@ -1,70 +1,70 @@ -import NotFound, { ComponentProps } from '../../Components/404'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen } from '@testing-library/react'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Email from 'Common/Types/Email'; -import * as React from 'react'; +import NotFound, { ComponentProps } from "../../Components/404"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen } from "@testing-library/react"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Email from "Common/Types/Email"; +import * as React from "react"; // Mock the Navigation module to avoid real navigation -jest.mock('../../Utils/Navigation', () => { - return { - navigate: jest.fn(), - }; +jest.mock("../../Utils/Navigation", () => { + return { + navigate: jest.fn(), + }; }); -describe('NotFound Component', () => { - const mockProps: ComponentProps = { - homeRoute: new Route('/'), // Replace with your actual home route object - supportEmail: new Email('support@example.com'), // Replace with your actual support email - }; +describe("NotFound Component", () => { + const mockProps: ComponentProps = { + homeRoute: new Route("/"), // Replace with your actual home route object + supportEmail: new Email("support@example.com"), // Replace with your actual support email + }; - beforeEach(() => { - render(<NotFound {...mockProps} />); - }); + beforeEach(() => { + render(<NotFound {...mockProps} />); + }); - test('should display the 404 message', () => { - const notFoundText: HTMLElement = screen.getByText('404'); - expect(notFoundText).toBeInTheDocument(); - }); + test("should display the 404 message", () => { + const notFoundText: HTMLElement = screen.getByText("404"); + expect(notFoundText).toBeInTheDocument(); + }); - test('should display the "Page not found" title', () => { - const pageTitle: HTMLElement = screen.getByText('Page not found'); - expect(pageTitle).toBeInTheDocument(); - }); + test('should display the "Page not found" title', () => { + const pageTitle: HTMLElement = screen.getByText("Page not found"); + expect(pageTitle).toBeInTheDocument(); + }); - test('should display the "Please check the URL" message', () => { - const errorMessage: HTMLElement = screen.getByText( - 'Please check the URL in the address bar and try again.' - ); - expect(errorMessage).toBeInTheDocument(); - }); + test('should display the "Please check the URL" message', () => { + const errorMessage: HTMLElement = screen.getByText( + "Please check the URL in the address bar and try again.", + ); + expect(errorMessage).toBeInTheDocument(); + }); - test('should display "Go Home" button', () => { - const goHomeButton: HTMLElement = screen.getByText('Go Home'); - expect(goHomeButton).toBeInTheDocument(); - }); + test('should display "Go Home" button', () => { + const goHomeButton: HTMLElement = screen.getByText("Go Home"); + expect(goHomeButton).toBeInTheDocument(); + }); - test('should display "Contact Support" button', () => { - const contactSupportButton: HTMLElement = - screen.getByText('Contact Support'); - expect(contactSupportButton).toBeInTheDocument(); - }); + test('should display "Contact Support" button', () => { + const contactSupportButton: HTMLElement = + screen.getByText("Contact Support"); + expect(contactSupportButton).toBeInTheDocument(); + }); - test('should navigate to the home route when "Go Home" button is clicked', () => { - const goHomeButton: HTMLElement = screen.getByText('Go Home'); - fireEvent.click(goHomeButton); - expect(require('../../Utils/Navigation').navigate).toHaveBeenCalledWith( - mockProps.homeRoute - ); - }); + test('should navigate to the home route when "Go Home" button is clicked', () => { + const goHomeButton: HTMLElement = screen.getByText("Go Home"); + fireEvent.click(goHomeButton); + expect(require("../../Utils/Navigation").navigate).toHaveBeenCalledWith( + mockProps.homeRoute, + ); + }); - test('should navigate to the support email when "Contact Support" button is clicked', () => { - const contactSupportButton: HTMLElement = - screen.getByText('Contact Support'); - fireEvent.click(contactSupportButton); - expect(require('../../Utils/Navigation').navigate).toHaveBeenCalledWith( - URL.fromString('mailto:' + mockProps.supportEmail.toString()) - ); - }); + test('should navigate to the support email when "Contact Support" button is clicked', () => { + const contactSupportButton: HTMLElement = + screen.getByText("Contact Support"); + fireEvent.click(contactSupportButton); + expect(require("../../Utils/Navigation").navigate).toHaveBeenCalledWith( + URL.fromString("mailto:" + mockProps.supportEmail.toString()), + ); + }); }); diff --git a/CommonUI/src/Tests/Components/Alert.test.tsx b/CommonUI/src/Tests/Components/Alert.test.tsx index 38c662d783..d9e97cb6bc 100644 --- a/CommonUI/src/Tests/Components/Alert.test.tsx +++ b/CommonUI/src/Tests/Components/Alert.test.tsx @@ -1,101 +1,101 @@ -import Alert, { AlertType } from '../../Components/Alerts/Alert'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen } from '@testing-library/react'; -import React from 'react'; +import Alert, { AlertType } from "../../Components/Alerts/Alert"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; -describe('alert tests', () => { - test('it should render all props passed', () => { - const handleClick: undefined | (() => void) = jest.fn(); - const handleClose: (() => void) | undefined = jest.fn(); - render( - <Alert - title="title" - strongTitle="strong" - type={AlertType.SUCCESS} - onClick={handleClick} - onClose={handleClose} - /> - ); +describe("alert tests", () => { + test("it should render all props passed", () => { + const handleClick: undefined | (() => void) = jest.fn(); + const handleClose: (() => void) | undefined = jest.fn(); + render( + <Alert + title="title" + strongTitle="strong" + type={AlertType.SUCCESS} + onClick={handleClick} + onClose={handleClose} + />, + ); - const icon: HTMLElement = screen.getByRole('icon'); - expect(icon).toBeInTheDocument(); + const icon: HTMLElement = screen.getByRole("icon"); + expect(icon).toBeInTheDocument(); - const alert: HTMLElement = screen.getByRole('alert'); - expect(alert).toBeInTheDocument(); - alert.click(); + const alert: HTMLElement = screen.getByRole("alert"); + expect(alert).toBeInTheDocument(); + alert.click(); - const alertCloseButton: HTMLElement = - screen.getByRole('alert-close-button'); - expect(alertCloseButton).toBeInTheDocument(); - alertCloseButton.click(); + const alertCloseButton: HTMLElement = + screen.getByRole("alert-close-button"); + expect(alertCloseButton).toBeInTheDocument(); + alertCloseButton.click(); - expect(handleClick).toBeCalled(); - expect(handleClose).toBeCalled(); - }); - test('it should show icon when alert type is equal to success', () => { - render(<Alert dataTestId="test-id" type={AlertType.SUCCESS} />); - const icon: HTMLElement = screen.getByRole('icon'); - expect(icon).toBeInTheDocument(); - const testId: HTMLElement = screen.getByTestId('test-id'); - expect(testId).toHaveClass('rounded-md bg-green-50 p-4'); - }); - test('it should show icon when alert type is equal to info', () => { - render(<Alert dataTestId="test-id" type={AlertType.INFO} />); - const icon: HTMLElement = screen.getByRole('icon'); - expect(icon).toBeInTheDocument(); - const testId: HTMLElement = screen.getByTestId('test-id'); - expect(testId).toHaveClass('rounded-md bg-blue-50 p-4'); - }); - test('it should show icon when alert type is equal to warning', () => { - render(<Alert dataTestId="test-id" type={AlertType.WARNING} />); - const icon: HTMLElement = screen.getByRole('icon'); - expect(icon).toBeInTheDocument(); - const testId: HTMLElement = screen.getByTestId('test-id'); - expect(testId).toHaveClass('rounded-md bg-yellow-50 p-4'); - }); - test('it should show icon when alert type is equal to danger', () => { - render(<Alert dataTestId="test-id" type={AlertType.DANGER} />); - const icon: HTMLElement = screen.getByRole('icon'); - expect(icon).toBeInTheDocument(); - const testId: HTMLElement = screen.getByTestId('test-id'); - expect(testId).toHaveClass('rounded-md bg-red-50 p-4'); - }); - test('it should have a title content displayed in document', () => { - render(<Alert title="title" />); - expect(screen.getByText('title')).toBeInTheDocument(); - expect(screen.getByText('title')).toHaveTextContent('title'); - }); - test('it should have a strong text content displayed in document ', () => { - render(<Alert strongTitle="strong" />); - expect(screen.getByText('strong')).toBeInTheDocument(); - expect(screen.getByText('strong')).toHaveTextContent('strong'); - }); - test('it should handle onClick event', () => { - const handleClick: (() => void) | undefined = jest.fn(); - render(<Alert title="title" onClick={handleClick} />); - fireEvent.click(screen.getByText('title')); - expect(handleClick).toBeCalled(); - }); - test('it should handle onClose event', () => { - const handleClose: undefined | (() => void) = jest.fn(); - render(<Alert title="title" onClose={handleClose} />); + expect(handleClick).toBeCalled(); + expect(handleClose).toBeCalled(); + }); + test("it should show icon when alert type is equal to success", () => { + render(<Alert dataTestId="test-id" type={AlertType.SUCCESS} />); + const icon: HTMLElement = screen.getByRole("icon"); + expect(icon).toBeInTheDocument(); + const testId: HTMLElement = screen.getByTestId("test-id"); + expect(testId).toHaveClass("rounded-md bg-green-50 p-4"); + }); + test("it should show icon when alert type is equal to info", () => { + render(<Alert dataTestId="test-id" type={AlertType.INFO} />); + const icon: HTMLElement = screen.getByRole("icon"); + expect(icon).toBeInTheDocument(); + const testId: HTMLElement = screen.getByTestId("test-id"); + expect(testId).toHaveClass("rounded-md bg-blue-50 p-4"); + }); + test("it should show icon when alert type is equal to warning", () => { + render(<Alert dataTestId="test-id" type={AlertType.WARNING} />); + const icon: HTMLElement = screen.getByRole("icon"); + expect(icon).toBeInTheDocument(); + const testId: HTMLElement = screen.getByTestId("test-id"); + expect(testId).toHaveClass("rounded-md bg-yellow-50 p-4"); + }); + test("it should show icon when alert type is equal to danger", () => { + render(<Alert dataTestId="test-id" type={AlertType.DANGER} />); + const icon: HTMLElement = screen.getByRole("icon"); + expect(icon).toBeInTheDocument(); + const testId: HTMLElement = screen.getByTestId("test-id"); + expect(testId).toHaveClass("rounded-md bg-red-50 p-4"); + }); + test("it should have a title content displayed in document", () => { + render(<Alert title="title" />); + expect(screen.getByText("title")).toBeInTheDocument(); + expect(screen.getByText("title")).toHaveTextContent("title"); + }); + test("it should have a strong text content displayed in document ", () => { + render(<Alert strongTitle="strong" />); + expect(screen.getByText("strong")).toBeInTheDocument(); + expect(screen.getByText("strong")).toHaveTextContent("strong"); + }); + test("it should handle onClick event", () => { + const handleClick: (() => void) | undefined = jest.fn(); + render(<Alert title="title" onClick={handleClick} />); + fireEvent.click(screen.getByText("title")); + expect(handleClick).toBeCalled(); + }); + test("it should handle onClose event", () => { + const handleClose: undefined | (() => void) = jest.fn(); + render(<Alert title="title" onClose={handleClose} />); - const alertCloseButton: HTMLElement = - screen.getByRole('alert-close-button'); - expect(alertCloseButton).toBeInTheDocument(); - alertCloseButton.click(); + const alertCloseButton: HTMLElement = + screen.getByRole("alert-close-button"); + expect(alertCloseButton).toBeInTheDocument(); + alertCloseButton.click(); - expect(handleClose).toBeCalled(); - }); - test('it should display button onClose event', () => { - const handleClose: undefined | (() => void) = jest.fn(); - render(<Alert onClose={handleClose} />); + expect(handleClose).toBeCalled(); + }); + test("it should display button onClose event", () => { + const handleClose: undefined | (() => void) = jest.fn(); + render(<Alert onClose={handleClose} />); - const alert: HTMLElement = screen.getByRole('alert'); - expect(alert).toBeInTheDocument(); + const alert: HTMLElement = screen.getByRole("alert"); + expect(alert).toBeInTheDocument(); - const alertCloseButton: HTMLElement = - screen.getByRole('alert-close-button'); - expect(alertCloseButton).toBeInTheDocument(); - }); + const alertCloseButton: HTMLElement = + screen.getByRole("alert-close-button"); + expect(alertCloseButton).toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/Badge.test.tsx b/CommonUI/src/Tests/Components/Badge.test.tsx index 0d2c3d2868..2e3622eec2 100644 --- a/CommonUI/src/Tests/Components/Badge.test.tsx +++ b/CommonUI/src/Tests/Components/Badge.test.tsx @@ -1,63 +1,59 @@ -import Badge, { BadgeType } from '../../Components/Badge/Badge'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; +import Badge, { BadgeType } from "../../Components/Badge/Badge"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import React from "react"; -describe('Badge', () => { - test('it should render all props', () => { - render( - <Badge id="badge" badgeCount={2} badgeType={BadgeType.SUCCESS} /> - ); - const badge: HTMLElement = screen.getByTestId('badge'); - expect(badge).toBeInTheDocument(); - }); - test('it should show badge when badgetype is equal to success', () => { - render( - <Badge id="badge" badgeCount={1} badgeType={BadgeType.SUCCESS} /> - ); - const badge: HTMLElement = screen.getByTestId('badge'); - expect(badge).toBeInTheDocument(); - const testId: HTMLElement = screen.getByText(1); - expect(testId).toHaveClass('text-emerald-600'); - }); - test('it should show success when badgetype is equal to success', () => { - render(<Badge badgeCount={1} badgeType={BadgeType.SUCCESS} />); - const testId: HTMLElement = screen.getByText(1); - expect(testId).toHaveClass('text-emerald-600'); - }); - test('it should show danger when badgetype is equal to danger', () => { - render(<Badge badgeCount={1} badgeType={BadgeType.DANGER} />); - const testId: HTMLElement = screen.getByText(1); - expect(testId).toHaveClass('text-red-600'); - }); - test('it should show warning when badgetype is equal to warning', () => { - render(<Badge badgeCount={1} badgeType={BadgeType.WARNING} />); - const testId: HTMLElement = screen.getByText(1); - expect(testId).toHaveClass('text-yellow-600'); - }); - test('it should show danger when badgetype is equal to danger', () => { - render(<Badge badgeCount={1} badgeType={BadgeType.DANGER} />); - const testId: HTMLElement = screen.getByText(1); - expect(testId).toHaveClass('text-red-600'); - }); - test('it should badgeCount when badgetype is equal to success', () => { - render(<Badge badgeCount={2} badgeType={BadgeType.SUCCESS} />); - const testId: HTMLElement = screen.getByText(2); - expect(testId).toHaveTextContent('2'); - }); - test('it should badgeCount when badgetype is equal to danger', () => { - render(<Badge badgeCount={2} badgeType={BadgeType.DANGER} />); - const testId: HTMLElement = screen.getByText(2); - expect(testId).toHaveTextContent('2'); - }); - test('it should badgeCount when badgetype is equal to warning', () => { - render(<Badge badgeCount={2} badgeType={BadgeType.WARNING} />); - const testId: HTMLElement = screen.getByText(2); - expect(testId).toHaveTextContent('2'); - }); +describe("Badge", () => { + test("it should render all props", () => { + render(<Badge id="badge" badgeCount={2} badgeType={BadgeType.SUCCESS} />); + const badge: HTMLElement = screen.getByTestId("badge"); + expect(badge).toBeInTheDocument(); + }); + test("it should show badge when badgetype is equal to success", () => { + render(<Badge id="badge" badgeCount={1} badgeType={BadgeType.SUCCESS} />); + const badge: HTMLElement = screen.getByTestId("badge"); + expect(badge).toBeInTheDocument(); + const testId: HTMLElement = screen.getByText(1); + expect(testId).toHaveClass("text-emerald-600"); + }); + test("it should show success when badgetype is equal to success", () => { + render(<Badge badgeCount={1} badgeType={BadgeType.SUCCESS} />); + const testId: HTMLElement = screen.getByText(1); + expect(testId).toHaveClass("text-emerald-600"); + }); + test("it should show danger when badgetype is equal to danger", () => { + render(<Badge badgeCount={1} badgeType={BadgeType.DANGER} />); + const testId: HTMLElement = screen.getByText(1); + expect(testId).toHaveClass("text-red-600"); + }); + test("it should show warning when badgetype is equal to warning", () => { + render(<Badge badgeCount={1} badgeType={BadgeType.WARNING} />); + const testId: HTMLElement = screen.getByText(1); + expect(testId).toHaveClass("text-yellow-600"); + }); + test("it should show danger when badgetype is equal to danger", () => { + render(<Badge badgeCount={1} badgeType={BadgeType.DANGER} />); + const testId: HTMLElement = screen.getByText(1); + expect(testId).toHaveClass("text-red-600"); + }); + test("it should badgeCount when badgetype is equal to success", () => { + render(<Badge badgeCount={2} badgeType={BadgeType.SUCCESS} />); + const testId: HTMLElement = screen.getByText(2); + expect(testId).toHaveTextContent("2"); + }); + test("it should badgeCount when badgetype is equal to danger", () => { + render(<Badge badgeCount={2} badgeType={BadgeType.DANGER} />); + const testId: HTMLElement = screen.getByText(2); + expect(testId).toHaveTextContent("2"); + }); + test("it should badgeCount when badgetype is equal to warning", () => { + render(<Badge badgeCount={2} badgeType={BadgeType.WARNING} />); + const testId: HTMLElement = screen.getByText(2); + expect(testId).toHaveTextContent("2"); + }); - test('should not show a badge when the count is 0', () => { - render(<Badge badgeCount={0} badgeType={BadgeType.WARNING} />); - expect(screen.queryByRole('badge')).toBeFalsy(); - }); + test("should not show a badge when the count is 0", () => { + render(<Badge badgeCount={0} badgeType={BadgeType.WARNING} />); + expect(screen.queryByRole("badge")).toBeFalsy(); + }); }); diff --git a/CommonUI/src/Tests/Components/BasicForm.test.tsx b/CommonUI/src/Tests/Components/BasicForm.test.tsx index a00bda9547..5571bdb9bb 100644 --- a/CommonUI/src/Tests/Components/BasicForm.test.tsx +++ b/CommonUI/src/Tests/Components/BasicForm.test.tsx @@ -1,133 +1,133 @@ -import BasicForm from '../../Components/Forms/BasicForm'; -import Fields from '../../Components/Forms/Types/Fields'; -import FormFieldSchemaType from '../../Components/Forms/Types/FormFieldSchemaType'; -import FormValues from '../../Components/Forms/Types/FormValues'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; -import Route from 'Common/Types/API/Route'; -import * as React from 'react'; +import BasicForm from "../../Components/Forms/BasicForm"; +import Fields from "../../Components/Forms/Types/Fields"; +import FormFieldSchemaType from "../../Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "../../Components/Forms/Types/FormValues"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup"; +import Route from "Common/Types/API/Route"; +import * as React from "react"; -describe('BasicForm test', () => { - const fields: Fields<FormValues<any>> = [ +describe("BasicForm test", () => { + const fields: Fields<FormValues<any>> = [ + { + field: { + email: true, + }, + title: "Email", + fieldType: FormFieldSchemaType.Email, + required: true, + dataTestId: "email", + }, + { + field: { + password: true, + }, + title: "Password", + required: true, + validation: { + minLength: 6, + }, + fieldType: FormFieldSchemaType.Password, + sideLink: { + text: "Forgot password?", + url: new Route("/accounts/forgot-password"), + openLinkInNewTab: false, + }, + dataTestId: "password", + }, + ]; + + test('Should render correctly and has type of "email" and "password" fields', async () => { + const handleSubmit: jest.Mock<any, any> = jest.fn(); + render( + <BasicForm + fields={fields} + id="sample-id" + onSubmit={handleSubmit} + submitButtonText="Login" + footer={<div data-testid="footer">Footer</div>} + />, + ); + + const inputEmail: HTMLElement = screen.getByTestId("email"); + const inputPassword: HTMLElement = screen.getByTestId("password"); + const footer: HTMLElement = screen.getByTestId("footer"); + const forgotPasswordText: HTMLElement = screen.getByTestId( + "login-forgot-password", + ); + expect(inputEmail).toBeInTheDocument(); + expect(inputPassword).toBeInTheDocument(); + expect(footer).toHaveTextContent("Footer"); + expect(forgotPasswordText).toHaveTextContent("Forgot password?"); + }); + + test("Should accept values and submit if valid", async () => { + const handleSubmit: jest.Mock<any, any> = jest.fn(); + const onSubmitSuccessful: jest.Mock<any, any> = jest.fn(); + render( + <BasicForm + fields={fields} + id="sample-id" + initialValues={{ + email: "", + password: "", + }} + onSubmit={(values: FormValues<any>) => { + handleSubmit(values, onSubmitSuccessful); + }} + submitButtonText="Login" + />, + ); + const user: UserEvent = userEvent.setup(); + await user.type(screen.getByTestId("email"), "test@sample.com"); + await user.type(screen.getByTestId("password"), "12345678"); + + const loginButton: HTMLButtonElement = screen.getByTestId("Login"); + await user.click(loginButton); + + await waitFor(() => { + expect(handleSubmit).toHaveBeenCalledWith( { - field: { - email: true, - }, - title: 'Email', - fieldType: FormFieldSchemaType.Email, - required: true, - dataTestId: 'email', + email: "test@sample.com", + password: { + _value: "12345678", + isHashed: false, + }, }, - { - field: { - password: true, - }, - title: 'Password', - required: true, - validation: { - minLength: 6, - }, - fieldType: FormFieldSchemaType.Password, - sideLink: { - text: 'Forgot password?', - url: new Route('/accounts/forgot-password'), - openLinkInNewTab: false, - }, - dataTestId: 'password', - }, - ]; - - test('Should render correctly and has type of "email" and "password" fields', async () => { - const handleSubmit: jest.Mock<any, any> = jest.fn(); - render( - <BasicForm - fields={fields} - id="sample-id" - onSubmit={handleSubmit} - submitButtonText="Login" - footer={<div data-testid="footer">Footer</div>} - /> - ); - - const inputEmail: HTMLElement = screen.getByTestId('email'); - const inputPassword: HTMLElement = screen.getByTestId('password'); - const footer: HTMLElement = screen.getByTestId('footer'); - const forgotPasswordText: HTMLElement = screen.getByTestId( - 'login-forgot-password' - ); - expect(inputEmail).toBeInTheDocument(); - expect(inputPassword).toBeInTheDocument(); - expect(footer).toHaveTextContent('Footer'); - expect(forgotPasswordText).toHaveTextContent('Forgot password?'); + onSubmitSuccessful, + ); }); + }); - test('Should accept values and submit if valid', async () => { - const handleSubmit: jest.Mock<any, any> = jest.fn(); - const onSubmitSuccessful: jest.Mock<any, any> = jest.fn(); - render( - <BasicForm - fields={fields} - id="sample-id" - initialValues={{ - email: '', - password: '', - }} - onSubmit={(values: FormValues<any>) => { - handleSubmit(values, onSubmitSuccessful); - }} - submitButtonText="Login" - /> - ); - const user: UserEvent = userEvent.setup(); - await user.type(screen.getByTestId('email'), 'test@sample.com'); - await user.type(screen.getByTestId('password'), '12345678'); + test("Should display error if values are invalid", async () => { + const handleSubmit: jest.Mock<any, any> = jest.fn(); + render( + <BasicForm + fields={fields} + id="sample-id" + initialValues={{ + email: "", + password: "", + }} + onSubmit={handleSubmit} + submitButtonText="Login" + />, + ); + const user: UserEvent = userEvent.setup(); + await user.type(screen.getByTestId("email"), "humed"); + await user.type(screen.getByTestId("password"), "1238"); - const loginButton: HTMLButtonElement = screen.getByTestId('Login'); - await user.click(loginButton); + const loginButton: HTMLButtonElement = screen.getByTestId("Login"); + await user.click(loginButton); - await waitFor(() => { - expect(handleSubmit).toHaveBeenCalledWith( - { - email: 'test@sample.com', - password: { - _value: '12345678', - isHashed: false, - }, - }, - onSubmitSuccessful - ); - }); - }); + const errorComponent: HTMLElement[] = + screen.getAllByTestId("error-message"); - test('Should display error if values are invalid', async () => { - const handleSubmit: jest.Mock<any, any> = jest.fn(); - render( - <BasicForm - fields={fields} - id="sample-id" - initialValues={{ - email: '', - password: '', - }} - onSubmit={handleSubmit} - submitButtonText="Login" - /> - ); - const user: UserEvent = userEvent.setup(); - await user.type(screen.getByTestId('email'), 'humed'); - await user.type(screen.getByTestId('password'), '1238'); - - const loginButton: HTMLButtonElement = screen.getByTestId('Login'); - await user.click(loginButton); - - const errorComponent: HTMLElement[] = - screen.getAllByTestId('error-message'); - - expect(errorComponent[0]?.innerHTML).toEqual('Email is not valid.'); - expect(errorComponent[1]?.innerHTML).toEqual( - 'Password cannot be less than 6 characters.' - ); - }); + expect(errorComponent[0]?.innerHTML).toEqual("Email is not valid."); + expect(errorComponent[1]?.innerHTML).toEqual( + "Password cannot be less than 6 characters.", + ); + }); }); diff --git a/CommonUI/src/Tests/Components/Breadcrumbs.test.tsx b/CommonUI/src/Tests/Components/Breadcrumbs.test.tsx index 246356af4e..6c872afef1 100644 --- a/CommonUI/src/Tests/Components/Breadcrumbs.test.tsx +++ b/CommonUI/src/Tests/Components/Breadcrumbs.test.tsx @@ -1,83 +1,79 @@ -import Breadcrumbs from '../../Components/Breadcrumbs/Breadcrumbs'; -import Navigation from '../../Utils/Navigation'; -import { describe, expect, test } from '@jest/globals'; -import Route from 'Common/Types/API/Route'; -import Link from 'Common/Types/Link'; -import * as React from 'react'; +import Breadcrumbs from "../../Components/Breadcrumbs/Breadcrumbs"; +import Navigation from "../../Utils/Navigation"; +import { describe, expect, test } from "@jest/globals"; +import Route from "Common/Types/API/Route"; +import Link from "Common/Types/Link"; +import * as React from "react"; import renderer, { - ReactTestInstance, - ReactTestRenderer, -} from 'react-test-renderer'; + ReactTestInstance, + ReactTestRenderer, +} from "react-test-renderer"; -describe('Breadcrumbs', () => { - test('Should render correctly and also contain "Home" and "Projects" string', () => { - const links: Array<Link> = [ - { - title: 'Home', - to: new Route('/'), - }, - { - title: 'Projects', - to: new Route('/projects'), - }, - ]; +describe("Breadcrumbs", () => { + test('Should render correctly and also contain "Home" and "Projects" string', () => { + const links: Array<Link> = [ + { + title: "Home", + to: new Route("/"), + }, + { + title: "Projects", + to: new Route("/projects"), + }, + ]; - const testRenderer: ReactTestRenderer = renderer.create( - <Breadcrumbs links={links} /> - ); - const testInstance: ReactTestInstance = testRenderer.root; - expect(testInstance.findAllByType('li')).toContainEqual( - expect.objectContaining({ - props: expect.objectContaining({ - className: expect.stringContaining('breadcrumb-item'), - }), - }) - ); - expect( - testInstance.findAllByType('a')[0]?.findByType('span').props[ - 'children' - ] - ).toEqual('Home'); - expect( - testInstance.findAllByType('a')[1]?.findByType('span').props[ - 'children' - ] - ).toEqual('Projects'); - }); - - test('Should avoid linking to the current page', () => { - // Mock `window.location.pathname` - Object.defineProperty(window, 'location', { - get() { - return { pathname: '/projects' }; - }, - }); - // Render component - const links: Array<Link> = [ - { - title: 'Home', - to: new Route('/'), - }, - { - title: 'Projects', - to: new Route('/projects'), - }, - ]; - const testRenderer: ReactTestRenderer = renderer.create( - <Breadcrumbs links={links} /> - ); - const testInstance: ReactTestInstance = testRenderer.root; - // Assert cursor style - const anchors: ReactTestInstance[] = testInstance.findAllByType('a'); - expect(anchors[0]?.props['className']).toContain('cursor-pointer'); - expect(anchors[1]?.props['className']).toContain('cursor-default'); - // Set up spy on navigation - jest.spyOn(Navigation, 'navigate'); - // Assert the second link does not navigate - anchors[1]?.props['onClick'](); - expect(Navigation.navigate).not.toHaveBeenCalled(); - // Assert the first link navigates - anchors[0]?.props['onClick'](); - expect(Navigation.navigate).toHaveBeenCalledTimes(1); + const testRenderer: ReactTestRenderer = renderer.create( + <Breadcrumbs links={links} />, + ); + const testInstance: ReactTestInstance = testRenderer.root; + expect(testInstance.findAllByType("li")).toContainEqual( + expect.objectContaining({ + props: expect.objectContaining({ + className: expect.stringContaining("breadcrumb-item"), + }), + }), + ); + expect( + testInstance.findAllByType("a")[0]?.findByType("span").props["children"], + ).toEqual("Home"); + expect( + testInstance.findAllByType("a")[1]?.findByType("span").props["children"], + ).toEqual("Projects"); + }); + + test("Should avoid linking to the current page", () => { + // Mock `window.location.pathname` + Object.defineProperty(window, "location", { + get() { + return { pathname: "/projects" }; + }, }); + // Render component + const links: Array<Link> = [ + { + title: "Home", + to: new Route("/"), + }, + { + title: "Projects", + to: new Route("/projects"), + }, + ]; + const testRenderer: ReactTestRenderer = renderer.create( + <Breadcrumbs links={links} />, + ); + const testInstance: ReactTestInstance = testRenderer.root; + // Assert cursor style + const anchors: ReactTestInstance[] = testInstance.findAllByType("a"); + expect(anchors[0]?.props["className"]).toContain("cursor-pointer"); + expect(anchors[1]?.props["className"]).toContain("cursor-default"); + // Set up spy on navigation + jest.spyOn(Navigation, "navigate"); + // Assert the second link does not navigate + anchors[1]?.props["onClick"](); + expect(Navigation.navigate).not.toHaveBeenCalled(); + // Assert the first link navigates + anchors[0]?.props["onClick"](); + expect(Navigation.navigate).toHaveBeenCalledTimes(1); + }); }); diff --git a/CommonUI/src/Tests/Components/Button.test.tsx b/CommonUI/src/Tests/Components/Button.test.tsx index 17fa6be0c1..a9abdfe0a7 100644 --- a/CommonUI/src/Tests/Components/Button.test.tsx +++ b/CommonUI/src/Tests/Components/Button.test.tsx @@ -1,215 +1,201 @@ import Button, { - ButtonSize, - ButtonStyleType, -} from '../../Components/Button/Button'; -import ButtonType from '../../Components/Button/ButtonTypes'; -import ShortcutKey from '../../Components/ShortcutKey/ShortcutKey'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen } from '@testing-library/react'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React from 'react'; + ButtonSize, + ButtonStyleType, +} from "../../Components/Button/Button"; +import ButtonType from "../../Components/Button/ButtonTypes"; +import ShortcutKey from "../../Components/ShortcutKey/ShortcutKey"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen } from "@testing-library/react"; +import IconProp from "Common/Types/Icon/IconProp"; +import React from "react"; -describe('Button', () => { - test('it should render correctly with title and icon', () => { - render( - <Button - dataTestId="test-id" - title="sample title" - disabled={true} - type={ButtonType.Button} - icon={IconProp.Add} - /> - ); - const title: HTMLElement = screen.getByText('sample title'); - const testId: HTMLElement = screen.getByTestId('test-id'); +describe("Button", () => { + test("it should render correctly with title and icon", () => { + render( + <Button + dataTestId="test-id" + title="sample title" + disabled={true} + type={ButtonType.Button} + icon={IconProp.Add} + />, + ); + const title: HTMLElement = screen.getByText("sample title"); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(title).toBeInTheDocument(); - expect(testId).toBeInTheDocument(); - expect(testId).toHaveAttribute('type', 'button'); - expect(testId).toHaveAttribute('disabled'); - const icon: HTMLElement = screen.getByRole('icon'); - expect(icon).toBeInTheDocument(); - }); + expect(title).toBeInTheDocument(); + expect(testId).toBeInTheDocument(); + expect(testId).toHaveAttribute("type", "button"); + expect(testId).toHaveAttribute("disabled"); + const icon: HTMLElement = screen.getByRole("icon"); + expect(icon).toBeInTheDocument(); + }); - test('it should have shortcutKey Setting', () => { - render( - <Button dataTestId="test-id" shortcutKey={ShortcutKey.Settings} /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have shortcutKey Setting", () => { + render(<Button dataTestId="test-id" shortcutKey={ShortcutKey.Settings} />); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveTextContent(ShortcutKey.Settings); - }); + expect(testId).toHaveTextContent(ShortcutKey.Settings); + }); - test('it should have buttonStyle NORMAL', () => { - render( - <Button dataTestId="test-id" buttonStyle={ButtonStyleType.NORMAL} /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle NORMAL", () => { + render( + <Button dataTestId="test-id" buttonStyle={ButtonStyleType.NORMAL} />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + "inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonStyle DANGER', () => { - render( - <Button dataTestId="test-id" buttonStyle={ButtonStyleType.DANGER} /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle DANGER", () => { + render( + <Button dataTestId="test-id" buttonStyle={ButtonStyleType.DANGER} />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex w-full justify-center rounded-md border border-transparent bg-red-600 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + "inline-flex w-full justify-center rounded-md border border-transparent bg-red-600 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonStyle DANGER_OUTLINE', () => { - render( - <Button - dataTestId="test-id" - buttonStyle={ButtonStyleType.DANGER_OUTLINE} - /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle DANGER_OUTLINE", () => { + render( + <Button + dataTestId="test-id" + buttonStyle={ButtonStyleType.DANGER_OUTLINE} + />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex w-full justify-center rounded-md border border-red-700 bg-white text-base font-medium text-red-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + "inline-flex w-full justify-center rounded-md border border-red-700 bg-white text-base font-medium text-red-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonStyle PRIMARY', () => { - render( - <Button - dataTestId="test-id" - buttonStyle={ButtonStyleType.PRIMARY} - /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle PRIMARY", () => { + render( + <Button dataTestId="test-id" buttonStyle={ButtonStyleType.PRIMARY} />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + "inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonStyle SECONDARY', () => { - render( - <Button - dataTestId="test-id" - buttonStyle={ButtonStyleType.SECONDARY} - /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle SECONDARY", () => { + render( + <Button dataTestId="test-id" buttonStyle={ButtonStyleType.SECONDARY} />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex items-center rounded-md border border-transparent bg-indigo-100 text-sm font-medium text-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 px-3 py-2' - ); - }); + expect(testId).toHaveClass( + "inline-flex items-center rounded-md border border-transparent bg-indigo-100 text-sm font-medium text-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 px-3 py-2", + ); + }); - test('it should have buttonStyle OUTLINE', () => { - render( - <Button dataTestId="test-id" buttonStyle={ButtonStyleType.NORMAL} /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle OUTLINE", () => { + render( + <Button dataTestId="test-id" buttonStyle={ButtonStyleType.NORMAL} />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + "inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonStyle SUCCESS', () => { - render( - <Button - dataTestId="test-id" - buttonStyle={ButtonStyleType.SUCCESS} - /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle SUCCESS", () => { + render( + <Button dataTestId="test-id" buttonStyle={ButtonStyleType.SUCCESS} />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex w-full justify-center rounded-md border border-transparent bg-green-600 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + "inline-flex w-full justify-center rounded-md border border-transparent bg-green-600 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonStyle SUCCESS_OUTLINE', () => { - render( - <Button - dataTestId="test-id" - buttonStyle={ButtonStyleType.SUCCESS_OUTLINE} - /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle SUCCESS_OUTLINE", () => { + render( + <Button + dataTestId="test-id" + buttonStyle={ButtonStyleType.SUCCESS_OUTLINE} + />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex w-full justify-center rounded-md border border-green-700 bg-white text-base font-medium text-green-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + "inline-flex w-full justify-center rounded-md border border-green-700 bg-white text-base font-medium text-green-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonStyle WARNING', () => { - render( - <Button - dataTestId="test-id" - buttonStyle={ButtonStyleType.WARNING} - /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle WARNING", () => { + render( + <Button dataTestId="test-id" buttonStyle={ButtonStyleType.WARNING} />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex w-full justify-center rounded-md border border-transparent bg-yellow-600 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + "inline-flex w-full justify-center rounded-md border border-transparent bg-yellow-600 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonStyle WARNING_OUTLINE', () => { - render( - <Button - dataTestId="test-id" - buttonStyle={ButtonStyleType.WARNING_OUTLINE} - /> - ); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonStyle WARNING_OUTLINE", () => { + render( + <Button + dataTestId="test-id" + buttonStyle={ButtonStyleType.WARNING_OUTLINE} + />, + ); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - ' inline-flex w-full justify-center rounded-md border border-yellow-700 bg-white text-base font-medium text-yellow-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + " inline-flex w-full justify-center rounded-md border border-yellow-700 bg-white text-base font-medium text-yellow-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonSize Normal', () => { - render(<Button dataTestId="test-id" buttonSize={ButtonSize.Normal} />); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonSize Normal", () => { + render(<Button dataTestId="test-id" buttonSize={ButtonSize.Normal} />); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - ' inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2' - ); - }); + expect(testId).toHaveClass( + " inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-3 py-2", + ); + }); - test('it should have buttonSize Small', () => { - render(<Button dataTestId="test-id" buttonSize={ButtonSize.Small} />); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonSize Small", () => { + render(<Button dataTestId="test-id" buttonSize={ButtonSize.Small} />); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - 'inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-2 py-1' - ); - }); + expect(testId).toHaveClass( + "inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-2 py-1", + ); + }); - test('it should have buttonSize Large', () => { - render(<Button dataTestId="test-id" buttonSize={ButtonSize.Large} />); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should have buttonSize Large", () => { + render(<Button dataTestId="test-id" buttonSize={ButtonSize.Large} />); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).toHaveClass( - ' inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-4 py-2' - ); - }); + expect(testId).toHaveClass( + " inline-flex w-full justify-center rounded-md border border-gray-300 bg-white text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm px-4 py-2", + ); + }); - test('it should be enabled', () => { - render(<Button dataTestId="test-id" disabled={false} />); - const testId: HTMLElement = screen.getByTestId('test-id'); + test("it should be enabled", () => { + render(<Button dataTestId="test-id" disabled={false} />); + const testId: HTMLElement = screen.getByTestId("test-id"); - expect(testId).not.toBeDisabled(); - }); + expect(testId).not.toBeDisabled(); + }); - test('it should handle onClick event', () => { - const handleClick: undefined | (() => void) = jest.fn(); - render(<Button dataTestId="test-id" onClick={handleClick} />); - const testId: HTMLElement = screen.getByTestId('test-id'); - fireEvent.click(testId); - expect(handleClick).toBeCalled(); - }); + test("it should handle onClick event", () => { + const handleClick: undefined | (() => void) = jest.fn(); + render(<Button dataTestId="test-id" onClick={handleClick} />); + const testId: HTMLElement = screen.getByTestId("test-id"); + fireEvent.click(testId); + expect(handleClick).toBeCalled(); + }); }); diff --git a/CommonUI/src/Tests/Components/Card.test.tsx b/CommonUI/src/Tests/Components/Card.test.tsx index d069022445..964807790b 100644 --- a/CommonUI/src/Tests/Components/Card.test.tsx +++ b/CommonUI/src/Tests/Components/Card.test.tsx @@ -1,116 +1,110 @@ -import { ButtonStyleType } from '../../Components/Button/Button'; +import { ButtonStyleType } from "../../Components/Button/Button"; import Card, { - CardButtonSchema, - ComponentProps, -} from '../../Components/Card/Card'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen } from '@testing-library/react'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React, { ReactElement } from 'react'; + CardButtonSchema, + ComponentProps, +} from "../../Components/Card/Card"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen } from "@testing-library/react"; +import IconProp from "Common/Types/Icon/IconProp"; +import React, { ReactElement } from "react"; -describe('Card', () => { - const props: ComponentProps = { - title: 'title', - description: 'description', - }; +describe("Card", () => { + const props: ComponentProps = { + title: "title", + description: "description", + }; - type RenderComponentFunction = (props: ComponentProps) => void; + type RenderComponentFunction = (props: ComponentProps) => void; - const renderComponent: RenderComponentFunction = ( - props: ComponentProps - ): void => { - render(<Card {...props} />); - }; + const renderComponent: RenderComponentFunction = ( + props: ComponentProps, + ): void => { + render(<Card {...props} />); + }; - test('should display card title', () => { - renderComponent(props); + test("should display card title", () => { + renderComponent(props); - const title: HTMLElement = screen.getByText(props.title as string); - expect(title).toBeInTheDocument(); - expect(title).toHaveClass( - 'text-lg font-medium leading-6 text-gray-900' - ); - }); + const title: HTMLElement = screen.getByText(props.title as string); + expect(title).toBeInTheDocument(); + expect(title).toHaveClass("text-lg font-medium leading-6 text-gray-900"); + }); - test('should display card description', () => { - renderComponent(props); + test("should display card description", () => { + renderComponent(props); - const description: HTMLElement = screen.getByText( - props.description as string - ); - expect(description).toBeInTheDocument(); - expect(description).toHaveClass('mt-1 text-sm text-gray-500'); - }); + const description: HTMLElement = screen.getByText( + props.description as string, + ); + expect(description).toBeInTheDocument(); + expect(description).toHaveClass("mt-1 text-sm text-gray-500"); + }); - test('should render rightElement passed in the props', () => { - const rightElementText: string = 'right element'; - const rightElement: ReactElement = <div>{rightElementText}</div>; + test("should render rightElement passed in the props", () => { + const rightElementText: string = "right element"; + const rightElement: ReactElement = <div>{rightElementText}</div>; - renderComponent({ ...props, rightElement }); + renderComponent({ ...props, rightElement }); - expect(screen.getByText(rightElementText)).toBeInTheDocument(); - }); + expect(screen.getByText(rightElementText)).toBeInTheDocument(); + }); - test('should render buttons with the button schemas passed in the props', () => { - const buttons: CardButtonSchema[] = [ - { - title: 'btn 1', - buttonStyle: ButtonStyleType.SUCCESS, - onClick: jest.fn(), - icon: IconProp.Success, - className: 'btn-1-class', - }, - { - title: 'btn 2', - buttonStyle: ButtonStyleType.DANGER, - onClick: jest.fn(), - icon: IconProp.Close, - className: 'btn-2-class', - disabled: true, - }, - ]; + test("should render buttons with the button schemas passed in the props", () => { + const buttons: CardButtonSchema[] = [ + { + title: "btn 1", + buttonStyle: ButtonStyleType.SUCCESS, + onClick: jest.fn(), + icon: IconProp.Success, + className: "btn-1-class", + }, + { + title: "btn 2", + buttonStyle: ButtonStyleType.DANGER, + onClick: jest.fn(), + icon: IconProp.Close, + className: "btn-2-class", + disabled: true, + }, + ]; - renderComponent({ ...props, buttons }); + renderComponent({ ...props, buttons }); - const button1: HTMLElement = screen.getByText(buttons[0]?.title ?? ''); - fireEvent.click(button1); - expect(button1).toBeInTheDocument(); - expect(button1).toHaveClass(buttons[0]?.className ?? ''); - expect(buttons[0]?.onClick).toHaveBeenCalled(); - expect(button1.parentElement).not.toHaveStyle({ marginLeft: '10px' }); + const button1: HTMLElement = screen.getByText(buttons[0]?.title ?? ""); + fireEvent.click(button1); + expect(button1).toBeInTheDocument(); + expect(button1).toHaveClass(buttons[0]?.className ?? ""); + expect(buttons[0]?.onClick).toHaveBeenCalled(); + expect(button1.parentElement).not.toHaveStyle({ marginLeft: "10px" }); - const button2: HTMLElement = screen.getByText(buttons[1]?.title ?? ''); - expect(button2).toBeInTheDocument(); - expect(button2).toBeDisabled(); - expect(button2.parentElement).toHaveStyle({ marginLeft: '10px' }); - }); + const button2: HTMLElement = screen.getByText(buttons[1]?.title ?? ""); + expect(button2).toBeInTheDocument(); + expect(button2).toBeDisabled(); + expect(button2.parentElement).toHaveStyle({ marginLeft: "10px" }); + }); - test('should render component children passed in the props and their parent element should have bodyClassName value passed in the props as css class', () => { - const bodyClassName: string = 'body-class'; - const childElementText: string = 'child element'; - const childElement: ReactElement = ( - <div key={0}>{childElementText}</div> - ); + test("should render component children passed in the props and their parent element should have bodyClassName value passed in the props as css class", () => { + const bodyClassName: string = "body-class"; + const childElementText: string = "child element"; + const childElement: ReactElement = <div key={0}>{childElementText}</div>; - renderComponent({ ...props, children: [childElement], bodyClassName }); + renderComponent({ ...props, children: [childElement], bodyClassName }); - const childComponent: HTMLElement = screen.getByText(childElementText); + const childComponent: HTMLElement = screen.getByText(childElementText); - expect(childComponent).toBeInTheDocument(); - expect(childComponent.parentElement).toHaveClass(bodyClassName); - }); + expect(childComponent).toBeInTheDocument(); + expect(childComponent.parentElement).toHaveClass(bodyClassName); + }); - test("should render component children passed in the props and their parent element have css class 'mt-6'", () => { - const childElementText: string = 'child element'; - const childElement: ReactElement = ( - <div key={0}>{childElementText}</div> - ); + test("should render component children passed in the props and their parent element have css class 'mt-6'", () => { + const childElementText: string = "child element"; + const childElement: ReactElement = <div key={0}>{childElementText}</div>; - renderComponent({ ...props, children: [childElement] }); + renderComponent({ ...props, children: [childElement] }); - const childComponent: HTMLElement = screen.getByText(childElementText); + const childComponent: HTMLElement = screen.getByText(childElementText); - expect(childComponent).toBeInTheDocument(); - expect(childComponent.parentElement).toHaveClass('mt-6'); - }); + expect(childComponent).toBeInTheDocument(); + expect(childComponent.parentElement).toHaveClass("mt-6"); + }); }); diff --git a/CommonUI/src/Tests/Components/ColorViewer.test.tsx b/CommonUI/src/Tests/Components/ColorViewer.test.tsx index e13ccbfd1d..85dd2c6aa2 100644 --- a/CommonUI/src/Tests/Components/ColorViewer.test.tsx +++ b/CommonUI/src/Tests/Components/ColorViewer.test.tsx @@ -1,61 +1,61 @@ -import ColorViewer from '../../Components/ColorViewer/ColorViewer'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render } from '@testing-library/react'; -import Color from 'Common/Types/Color'; -import * as React from 'react'; +import ColorViewer from "../../Components/ColorViewer/ColorViewer"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render } from "@testing-library/react"; +import Color from "Common/Types/Color"; +import * as React from "react"; -describe('Color Viewer', () => { - test('Render the component', () => { - const { getByTestId } = render( - <ColorViewer - dataTestId="test-id" - value={new Color('#0000ff')} - placeholder="Blue500" - /> - ); +describe("Color Viewer", () => { + test("Render the component", () => { + const { getByTestId } = render( + <ColorViewer + dataTestId="test-id" + value={new Color("#0000ff")} + placeholder="Blue500" + />, + ); - const parent: HTMLElement = getByTestId('test-id'); - const colorComponent: Element = parent.children[0]!; - const placeholder: Element = parent.children[1]!; + const parent: HTMLElement = getByTestId("test-id"); + const colorComponent: Element = parent.children[0]!; + const placeholder: Element = parent.children[1]!; - expect(parent).toBeInTheDocument(); - expect(colorComponent).toHaveStyle('background-color: #0000ff'); - expect(placeholder).toHaveTextContent('#0000ff'); - }); + expect(parent).toBeInTheDocument(); + expect(colorComponent).toHaveStyle("background-color: #0000ff"); + expect(placeholder).toHaveTextContent("#0000ff"); + }); - test('Render with just a placeholder and the test id', () => { - const { getByTestId } = render( - <ColorViewer dataTestId="test-id" placeholder="Blue500" /> - ); + test("Render with just a placeholder and the test id", () => { + const { getByTestId } = render( + <ColorViewer dataTestId="test-id" placeholder="Blue500" />, + ); - const parent: HTMLElement = getByTestId('test-id'); - const placeholder: Element = parent.children[0]!; + const parent: HTMLElement = getByTestId("test-id"); + const placeholder: Element = parent.children[0]!; - expect(parent).toBeInTheDocument(); - expect(placeholder).toHaveTextContent('Blue500'); - }); + expect(parent).toBeInTheDocument(); + expect(placeholder).toHaveTextContent("Blue500"); + }); - test('Render without any props but the test id', () => { - const { getByTestId } = render(<ColorViewer dataTestId="test-id" />); + test("Render without any props but the test id", () => { + const { getByTestId } = render(<ColorViewer dataTestId="test-id" />); - const parent: HTMLElement = getByTestId('test-id'); - const placeholder: Element = parent.children[0]!; + const parent: HTMLElement = getByTestId("test-id"); + const placeholder: Element = parent.children[0]!; - expect(parent).toBeInTheDocument(); - expect(placeholder).toHaveTextContent('No Color Selected'); - }); + expect(parent).toBeInTheDocument(); + expect(placeholder).toHaveTextContent("No Color Selected"); + }); - test('Render with an alert on click', () => { - const onClick: jest.Mock = jest.fn(); - const { getByTestId } = render( - <ColorViewer dataTestId="test-id" onClick={onClick} /> - ); + test("Render with an alert on click", () => { + const onClick: jest.Mock = jest.fn(); + const { getByTestId } = render( + <ColorViewer dataTestId="test-id" onClick={onClick} />, + ); - const parent: HTMLElement = getByTestId('test-id'); - fireEvent.click(parent); - fireEvent.click(parent); + const parent: HTMLElement = getByTestId("test-id"); + fireEvent.click(parent); + fireEvent.click(parent); - expect(onClick).toHaveBeenCalled(); - expect(onClick).toHaveBeenCalledTimes(2); - }); + expect(onClick).toHaveBeenCalled(); + expect(onClick).toHaveBeenCalledTimes(2); + }); }); diff --git a/CommonUI/src/Tests/Components/ComponentsModal.test.tsx b/CommonUI/src/Tests/Components/ComponentsModal.test.tsx index a5343192dd..9c2b461e05 100644 --- a/CommonUI/src/Tests/Components/ComponentsModal.test.tsx +++ b/CommonUI/src/Tests/Components/ComponentsModal.test.tsx @@ -1,431 +1,422 @@ -import ComponentsModal from '../../Components/Workflow/ComponentsModal'; -import { faker } from '@faker-js/faker'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen } from '@testing-library/react'; -import IconProp from 'Common/Types/Icon/IconProp'; +import ComponentsModal from "../../Components/Workflow/ComponentsModal"; +import { faker } from "@faker-js/faker"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen } from "@testing-library/react"; +import IconProp from "Common/Types/Icon/IconProp"; import ComponentMetadata, { - ComponentCategory, - ComponentType, -} from 'Common/Types/Workflow/Component'; -import React from 'react'; + ComponentCategory, + ComponentType, +} from "Common/Types/Workflow/Component"; +import React from "react"; /// @dev we use different UUID for (id & title), description, and category to ensure that the component is unique type GetComponentMetadataFunction = (category?: string) => ComponentMetadata; const getComponentMetadata: GetComponentMetadataFunction = ( - category?: string + category?: string, ): ComponentMetadata => { - const id: string = faker.datatype.uuid(); - return { - id, - title: id, - description: faker.datatype.uuid(), - category: category || faker.datatype.uuid(), - iconProp: IconProp.Activity, - componentType: ComponentType.Component, - arguments: [], - returnValues: [], - inPorts: [], - outPorts: [], - }; + const id: string = faker.datatype.uuid(); + return { + id, + title: id, + description: faker.datatype.uuid(), + category: category || faker.datatype.uuid(), + iconProp: IconProp.Activity, + componentType: ComponentType.Component, + arguments: [], + returnValues: [], + inPorts: [], + outPorts: [], + }; }; type GetComponentCategoryFunction = (name?: string) => ComponentCategory; const getComponentCategory: GetComponentCategoryFunction = ( - name?: string + name?: string, ): ComponentCategory => { - return { - name: name || faker.datatype.uuid(), - description: `Description for ${name}`, - icon: IconProp.Activity, - }; + return { + name: name || faker.datatype.uuid(), + description: `Description for ${name}`, + icon: IconProp.Activity, + }; }; -describe('ComponentsModal', () => { - const mockedCategories: ComponentCategory[] = [ - getComponentCategory(), - getComponentCategory(), - getComponentCategory(), - getComponentCategory(), - ]; +describe("ComponentsModal", () => { + const mockedCategories: ComponentCategory[] = [ + getComponentCategory(), + getComponentCategory(), + getComponentCategory(), + getComponentCategory(), + ]; - const mockedComponents: ComponentMetadata[] = [ - getComponentMetadata(mockedCategories[0]?.name), - getComponentMetadata(mockedCategories[1]?.name), - getComponentMetadata(mockedCategories[2]?.name), - getComponentMetadata(mockedCategories[3]?.name), - ]; + const mockedComponents: ComponentMetadata[] = [ + getComponentMetadata(mockedCategories[0]?.name), + getComponentMetadata(mockedCategories[1]?.name), + getComponentMetadata(mockedCategories[2]?.name), + getComponentMetadata(mockedCategories[3]?.name), + ]; - const mockOnCloseModal: jest.Mock = jest.fn(); - const mockOnComponentClick: jest.Mock = jest.fn(); + const mockOnCloseModal: jest.Mock = jest.fn(); + const mockOnComponentClick: jest.Mock = jest.fn(); - it('should render without crashing', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); + it("should render without crashing", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + }); + + it("should display search input", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + expect(screen.getByPlaceholderText("Search...")).toBeInTheDocument(); + }); + + it("should display categories and components", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + for (const cat of mockedCategories) { + expect(screen.getByText(cat.name)).toBeInTheDocument(); + } + for (const comp of mockedComponents) { + expect(screen.getByText(comp.title)).toBeInTheDocument(); + } + }); + + it("should call onCloseModal when the close button is clicked", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + fireEvent.click(screen.getByText("Close panel")); + expect(mockOnCloseModal).toHaveBeenCalled(); + }); + + it("should call onComponentClick when a component is selected", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + for (const [idx, comp] of mockedComponents.entries()) { + // simulate selecting a component + fireEvent.click(screen.getByText(comp.title)); + expect(screen.getByText("Create")).not.toBeDisabled(); + + // simulate submitting + fireEvent.click(screen.getByText("Create")); + + // check if onComponentClick was called with the selected component's metadata + expect(mockOnComponentClick).toHaveBeenNthCalledWith(idx + 1, comp); + } + }); + + it("should display a message when no components are available", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={[]} + categories={mockedCategories} + />, + ); + expect( + screen.getByText( + "No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you.", + ), + ).toBeInTheDocument(); + }); + + it("should not display categories when there are no categories", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={[]} + />, + ); + mockedCategories.forEach((category: ComponentCategory) => { + expect(screen.queryByText(category.name)).not.toBeInTheDocument(); }); + }); - it('should display search input', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument(); + it("should display no components message when search yields no results", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + fireEvent.change(screen.getByPlaceholderText("Search..."), { + target: { value: "Non-existent Ccmponent" }, }); + expect( + screen.getByText( + "No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you.", + ), + ).toBeInTheDocument(); + }); - it('should display categories and components', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - for (const cat of mockedCategories) { - expect(screen.getByText(cat.name)).toBeInTheDocument(); - } - for (const comp of mockedComponents) { - expect(screen.getByText(comp.title)).toBeInTheDocument(); - } - }); + it("should disable submit button prop when no component is selected", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + const submitButton: HTMLElement = screen.getByText("Create"); + expect(submitButton).toBeDisabled(); + }); - it('should call onCloseModal when the close button is clicked', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - fireEvent.click(screen.getByText('Close panel')); - expect(mockOnCloseModal).toHaveBeenCalled(); - }); + it("should change submitButtonDisabled to false when a component is selected", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + for (const comp of mockedComponents) { + fireEvent.click(screen.getByText(comp.title)); + const submitButton: HTMLElement = screen.getByText("Create"); + expect(submitButton).not.toBeDisabled(); + } + }); - it('should call onComponentClick when a component is selected', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - for (const [idx, comp] of mockedComponents.entries()) { - // simulate selecting a component - fireEvent.click(screen.getByText(comp.title)); - expect(screen.getByText('Create')).not.toBeDisabled(); + // search tests - // simulate submitting - fireEvent.click(screen.getByText('Create')); + it("should filter components based on search input", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); - // check if onComponentClick was called with the selected component's metadata - expect(mockOnComponentClick).toHaveBeenNthCalledWith(idx + 1, comp); - } - }); + mockedComponents.forEach((comp: ComponentMetadata) => { + const partialTitle: string = comp.title.substring( + 0, + comp.title.length - comp.title.length / 2, + ); + fireEvent.change(screen.getByPlaceholderText("Search..."), { + target: { value: partialTitle }, + }); + expect(screen.getByText(comp.title)).toBeInTheDocument(); - it('should display a message when no components are available', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={[]} - categories={mockedCategories} - /> - ); - expect( - screen.getByText( - 'No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you.' - ) - ).toBeInTheDocument(); - }); - - it('should not display categories when there are no categories', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={[]} - /> - ); - mockedCategories.forEach((category: ComponentCategory) => { - expect(screen.queryByText(category.name)).not.toBeInTheDocument(); + // check other components are not displayed + mockedComponents + .filter((c: ComponentMetadata) => { + return c.title !== comp.title; + }) + .forEach((c: ComponentMetadata) => { + return expect(screen.queryByText(c.title)).not.toBeInTheDocument(); }); }); + }); - it('should display no components message when search yields no results', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - fireEvent.change(screen.getByPlaceholderText('Search...'), { - target: { value: 'Non-existent Ccmponent' }, - }); - expect( - screen.getByText( - 'No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you.' - ) - ).toBeInTheDocument(); - }); + it("should filter components based on description when searching", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + mockedComponents.forEach((comp: ComponentMetadata) => { + fireEvent.change(screen.getByPlaceholderText("Search..."), { + target: { value: comp.description }, + }); + expect(screen.getByText(comp.title)).toBeInTheDocument(); - it('should disable submit button prop when no component is selected', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - const submitButton: HTMLElement = screen.getByText('Create'); - expect(submitButton).toBeDisabled(); - }); - - it('should change submitButtonDisabled to false when a component is selected', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - for (const comp of mockedComponents) { - fireEvent.click(screen.getByText(comp.title)); - const submitButton: HTMLElement = screen.getByText('Create'); - expect(submitButton).not.toBeDisabled(); - } - }); - - // search tests - - it('should filter components based on search input', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - - mockedComponents.forEach((comp: ComponentMetadata) => { - const partialTitle: string = comp.title.substring( - 0, - comp.title.length - comp.title.length / 2 - ); - fireEvent.change(screen.getByPlaceholderText('Search...'), { - target: { value: partialTitle }, - }); - expect(screen.getByText(comp.title)).toBeInTheDocument(); - - // check other components are not displayed - mockedComponents - .filter((c: ComponentMetadata) => { - return c.title !== comp.title; - }) - .forEach((c: ComponentMetadata) => { - return expect( - screen.queryByText(c.title) - ).not.toBeInTheDocument(); - }); + // check other components are not displayed + mockedComponents + .filter((c: ComponentMetadata) => { + return c.title !== comp.title; + }) + .forEach((c: ComponentMetadata) => { + return expect(screen.queryByText(c.title)).not.toBeInTheDocument(); }); }); + }); - it('should filter components based on description when searching', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - mockedComponents.forEach((comp: ComponentMetadata) => { - fireEvent.change(screen.getByPlaceholderText('Search...'), { - target: { value: comp.description }, - }); - expect(screen.getByText(comp.title)).toBeInTheDocument(); + it("should filter components based on category when searching", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + mockedComponents.forEach((comp: ComponentMetadata) => { + fireEvent.change(screen.getByPlaceholderText("Search..."), { + target: { value: comp.category }, + }); + expect(screen.getByText(comp.title)).toBeInTheDocument(); - // check other components are not displayed - mockedComponents - .filter((c: ComponentMetadata) => { - return c.title !== comp.title; - }) - .forEach((c: ComponentMetadata) => { - return expect( - screen.queryByText(c.title) - ).not.toBeInTheDocument(); - }); + // check other components are not displayed + mockedComponents + .filter((c: ComponentMetadata) => { + return c.category !== comp.category; + }) + .forEach((c: ComponentMetadata) => { + return expect(screen.queryByText(c.title)).not.toBeInTheDocument(); }); }); + }); - it('should filter components based on category when searching', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - mockedComponents.forEach((comp: ComponentMetadata) => { - fireEvent.change(screen.getByPlaceholderText('Search...'), { - target: { value: comp.category }, - }); - expect(screen.getByText(comp.title)).toBeInTheDocument(); + it("should show all components when search is cleared", () => { + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + mockedComponents.forEach((comp: ComponentMetadata) => { + const searchInput: HTMLElement = screen.getByPlaceholderText("Search..."); + fireEvent.change(searchInput, { target: { value: comp.title } }); + fireEvent.change(searchInput, { target: { value: "" } }); // clear search - // check other components are not displayed - mockedComponents - .filter((c: ComponentMetadata) => { - return c.category !== comp.category; - }) - .forEach((c: ComponentMetadata) => { - return expect( - screen.queryByText(c.title) - ).not.toBeInTheDocument(); - }); - }); + mockedComponents.forEach((c: ComponentMetadata) => { + return expect(screen.getByText(c.title)).toBeInTheDocument(); + }); }); + }); - it('should show all components when search is cleared', () => { - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - mockedComponents.forEach((comp: ComponentMetadata) => { - const searchInput: HTMLElement = - screen.getByPlaceholderText('Search...'); - fireEvent.change(searchInput, { target: { value: comp.title } }); - fireEvent.change(searchInput, { target: { value: '' } }); // clear search + it("should return multiple components when similar titles match", () => { + // we add a new component where its title is a substring of another component's title + const commonWord: string = mockedComponents[0]?.title.substring(0, 5) || ""; + const newComponent: ComponentMetadata = getComponentMetadata( + mockedCategories[1]?.name, + ); + newComponent.title += commonWord; + mockedComponents.push(newComponent); + const componentsWithCommonWord: ComponentMetadata[] = + mockedComponents.filter((comp: ComponentMetadata) => { + return comp.title.includes(commonWord); + }); - mockedComponents.forEach((c: ComponentMetadata) => { - return expect(screen.getByText(c.title)).toBeInTheDocument(); - }); - }); + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); + + fireEvent.change(screen.getByPlaceholderText("Search..."), { + target: { value: commonWord }, }); - - it('should return multiple components when similar titles match', () => { - // we add a new component where its title is a substring of another component's title - const commonWord: string = - mockedComponents[0]?.title.substring(0, 5) || ''; - const newComponent: ComponentMetadata = getComponentMetadata( - mockedCategories[1]?.name - ); - newComponent.title += commonWord; - mockedComponents.push(newComponent); - const componentsWithCommonWord: ComponentMetadata[] = - mockedComponents.filter((comp: ComponentMetadata) => { - return comp.title.includes(commonWord); - }); - - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); - - fireEvent.change(screen.getByPlaceholderText('Search...'), { - target: { value: commonWord }, - }); - componentsWithCommonWord.forEach((comp: ComponentMetadata) => { - expect(screen.getByText(comp.title)).toBeInTheDocument(); - }); + componentsWithCommonWord.forEach((comp: ComponentMetadata) => { + expect(screen.getByText(comp.title)).toBeInTheDocument(); }); + }); - it('should return return components with similar descriptions', () => { - // we add a new component where its title is a substring of another component's description - const partialDescription: string = - mockedComponents[0]?.description.substring(0, 10) || ''; - const newComponent: ComponentMetadata = getComponentMetadata( - mockedCategories[1]?.name - ); - newComponent.title = partialDescription || ''; - mockedComponents.push(newComponent); - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); + it("should return return components with similar descriptions", () => { + // we add a new component where its title is a substring of another component's description + const partialDescription: string = + mockedComponents[0]?.description.substring(0, 10) || ""; + const newComponent: ComponentMetadata = getComponentMetadata( + mockedCategories[1]?.name, + ); + newComponent.title = partialDescription || ""; + mockedComponents.push(newComponent); + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); - fireEvent.change(screen.getByPlaceholderText('Search...'), { - target: { value: partialDescription }, - }); - expect( - screen.getAllByText(new RegExp(partialDescription, 'i')) - ).toHaveLength(2); + fireEvent.change(screen.getByPlaceholderText("Search..."), { + target: { value: partialDescription }, }); + expect( + screen.getAllByText(new RegExp(partialDescription, "i")), + ).toHaveLength(2); + }); - it('should return components with the same category', () => { - // we add two components with the same category as the first component - const commonCategory: string | undefined = - mockedComponents[0]?.category; - mockedComponents.push(getComponentMetadata(commonCategory)); - mockedComponents.push(getComponentMetadata(commonCategory)); - const componentsInCommonCategory: ComponentMetadata[] = - mockedComponents.filter((comp: ComponentMetadata) => { - return comp.category === commonCategory; - }); + it("should return components with the same category", () => { + // we add two components with the same category as the first component + const commonCategory: string | undefined = mockedComponents[0]?.category; + mockedComponents.push(getComponentMetadata(commonCategory)); + mockedComponents.push(getComponentMetadata(commonCategory)); + const componentsInCommonCategory: ComponentMetadata[] = + mockedComponents.filter((comp: ComponentMetadata) => { + return comp.category === commonCategory; + }); - render( - <ComponentsModal - componentsType={ComponentType.Component} - onCloseModal={mockOnCloseModal} - onComponentClick={mockOnComponentClick} - components={mockedComponents} - categories={mockedCategories} - /> - ); + render( + <ComponentsModal + componentsType={ComponentType.Component} + onCloseModal={mockOnCloseModal} + onComponentClick={mockOnComponentClick} + components={mockedComponents} + categories={mockedCategories} + />, + ); - fireEvent.change(screen.getByPlaceholderText('Search...'), { - target: { value: commonCategory }, - }); - componentsInCommonCategory.forEach((comp: ComponentMetadata) => { - expect(screen.getByText(comp.title)).toBeInTheDocument(); - }); + fireEvent.change(screen.getByPlaceholderText("Search..."), { + target: { value: commonCategory }, }); + componentsInCommonCategory.forEach((comp: ComponentMetadata) => { + expect(screen.getByText(comp.title)).toBeInTheDocument(); + }); + }); }); diff --git a/CommonUI/src/Tests/Components/ConfirmModal.test.tsx b/CommonUI/src/Tests/Components/ConfirmModal.test.tsx index e089033a91..091de3a24e 100644 --- a/CommonUI/src/Tests/Components/ConfirmModal.test.tsx +++ b/CommonUI/src/Tests/Components/ConfirmModal.test.tsx @@ -1,96 +1,91 @@ -import { ButtonStyleType } from '../../Components/Button/Button'; +import { ButtonStyleType } from "../../Components/Button/Button"; import ConfirmModal, { - ComponentProps, -} from '../../Components/Modal/ConfirmModal'; -import { describe, expect, it } from '@jest/globals'; -import { fireEvent, render, screen } from '@testing-library/react'; -import React from 'react'; + ComponentProps, +} from "../../Components/Modal/ConfirmModal"; +import { describe, expect, it } from "@jest/globals"; +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; -describe('ConfirmModal', () => { - const mockProps: ComponentProps = { - title: 'Confirmation Title', - description: 'Are you sure?', - onClose: jest.fn(), - submitButtonText: 'Confirm', - onSubmit: jest.fn(), - submitButtonType: ButtonStyleType.PRIMARY, - closeButtonType: ButtonStyleType.NORMAL, - }; +describe("ConfirmModal", () => { + const mockProps: ComponentProps = { + title: "Confirmation Title", + description: "Are you sure?", + onClose: jest.fn(), + submitButtonText: "Confirm", + onSubmit: jest.fn(), + submitButtonType: ButtonStyleType.PRIMARY, + closeButtonType: ButtonStyleType.NORMAL, + }; - beforeEach(() => { - jest.clearAllMocks(); - }); + beforeEach(() => { + jest.clearAllMocks(); + }); - it('renders correctly', () => { - render(<ConfirmModal {...mockProps} />); + it("renders correctly", () => { + render(<ConfirmModal {...mockProps} />); - const title: string | null = - screen.getByTestId('modal-title')?.textContent; - expect(title).toBe('Confirmation Title'); + const title: string | null = screen.getByTestId("modal-title")?.textContent; + expect(title).toBe("Confirmation Title"); - const description: string | null = screen.getByTestId( - 'confirm-modal-description' - )?.textContent; - expect(description).toBe('Are you sure?'); + const description: string | null = screen.getByTestId( + "confirm-modal-description", + )?.textContent; + expect(description).toBe("Are you sure?"); - const submitButtonText: string | null = screen.getByTestId( - 'modal-footer-submit-button' - )?.textContent; - expect(submitButtonText).toBe('Confirm'); + const submitButtonText: string | null = screen.getByTestId( + "modal-footer-submit-button", + )?.textContent; + expect(submitButtonText).toBe("Confirm"); - const submitButton: DOMTokenList = screen.getByTestId( - 'modal-footer-submit-button' - )?.classList; - expect(submitButton.contains('bg-indigo-600')).toBe(true); + const submitButton: DOMTokenList = screen.getByTestId( + "modal-footer-submit-button", + )?.classList; + expect(submitButton.contains("bg-indigo-600")).toBe(true); - const closeButton: DOMTokenList = screen.getByTestId( - 'modal-footer-close-button' - )?.classList; - expect(closeButton.contains('bg-white')).toBe(true); - }); + const closeButton: DOMTokenList = screen.getByTestId( + "modal-footer-close-button", + )?.classList; + expect(closeButton.contains("bg-white")).toBe(true); + }); - it('closes the comfirm modal when the close button is clicked', () => { - render(<ConfirmModal {...mockProps} />); + it("closes the comfirm modal when the close button is clicked", () => { + render(<ConfirmModal {...mockProps} />); - const closeButton: HTMLElement = screen.getByTestId('close-button'); + const closeButton: HTMLElement = screen.getByTestId("close-button"); - fireEvent.click(closeButton); + fireEvent.click(closeButton); - expect(mockProps.onClose).toHaveBeenCalled(); - }); + expect(mockProps.onClose).toHaveBeenCalled(); + }); - it('calls the onSubmit function when the submit button is clicked', () => { - render(<ConfirmModal {...mockProps} />); + it("calls the onSubmit function when the submit button is clicked", () => { + render(<ConfirmModal {...mockProps} />); - const submitButton: HTMLElement = screen.getByTestId( - 'modal-footer-submit-button' - ); + const submitButton: HTMLElement = screen.getByTestId( + "modal-footer-submit-button", + ); - fireEvent.click(submitButton); + fireEvent.click(submitButton); - expect(mockProps.onSubmit).toHaveBeenCalled(); - }); + expect(mockProps.onSubmit).toHaveBeenCalled(); + }); - it('disables the submit button when isLoading is true', () => { - render(<ConfirmModal {...mockProps} isLoading={true} />); + it("disables the submit button when isLoading is true", () => { + render(<ConfirmModal {...mockProps} isLoading={true} />); - const submitButton: HTMLElement = screen.getByTestId( - 'modal-footer-submit-button' - ); + const submitButton: HTMLElement = screen.getByTestId( + "modal-footer-submit-button", + ); - expect(submitButton.getAttribute('disabled')).toBeTruthy; - }); + expect(submitButton.getAttribute("disabled")).toBeTruthy; + }); - it('should have a title content displayed in document when there is error', () => { - render( - <ConfirmModal {...mockProps} error="This is a error message." /> - ); + it("should have a title content displayed in document when there is error", () => { + render(<ConfirmModal {...mockProps} error="This is a error message." />); - const errorMessage: HTMLElement = screen.getByText( - 'This is a error message.'.trim() - ); - expect(errorMessage.textContent?.trim()).toBe( - 'This is a error message.' - ); - }); + const errorMessage: HTMLElement = screen.getByText( + "This is a error message.".trim(), + ); + expect(errorMessage.textContent?.trim()).toBe("This is a error message."); + }); }); diff --git a/CommonUI/src/Tests/Components/DictionaryOfStrings.test.tsx b/CommonUI/src/Tests/Components/DictionaryOfStrings.test.tsx index ed7e1f9cc5..5215e35183 100644 --- a/CommonUI/src/Tests/Components/DictionaryOfStrings.test.tsx +++ b/CommonUI/src/Tests/Components/DictionaryOfStrings.test.tsx @@ -1,128 +1,119 @@ // Libraries // Custom components import DictionaryOfStrings, { - ComponentProps, -} from '../../Components/Dictionary/DictionaryOfStrings'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen } from '@testing-library/react'; -import Dictionary from 'Common/Types/Dictionary'; -import React from 'react'; + ComponentProps, +} from "../../Components/Dictionary/DictionaryOfStrings"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen } from "@testing-library/react"; +import Dictionary from "Common/Types/Dictionary"; +import React from "react"; -jest.mock('Common/Types/Date', () => { - return { - toDateTimeLocalString: jest.fn(), - asDateForDatabaseQuery: jest.fn(), - fromString: jest.fn(), - toString: jest.fn(), - }; +jest.mock("Common/Types/Date", () => { + return { + toDateTimeLocalString: jest.fn(), + asDateForDatabaseQuery: jest.fn(), + fromString: jest.fn(), + toString: jest.fn(), + }; }); -describe('Dictionary Of Strings', () => { +describe("Dictionary Of Strings", () => { + const initialValue: Dictionary<string> = { + key: "value", + }; + + const defaultProps: ComponentProps = { + initialValue, + onChange: jest.fn(), + keyPlaceholder: "KeyPlaceholder", + valuePlaceholder: "ValuePlaceholder", + addButtonSuffix: "Attribute", + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("Should not show any row if no initialValue is provided", () => { + render(<DictionaryOfStrings {...{ ...defaultProps, initialValue: {} }} />); + + expect( + screen.queryByPlaceholderText(defaultProps.keyPlaceholder as string), + ).not.toBeInTheDocument(); + expect( + screen.getByRole("button", { + name: new RegExp(`Add ${defaultProps.addButtonSuffix}`, "i"), + }), + ).toBeInTheDocument(); + }); + + it("Should show the rows if initialValue is provided", () => { + render(<DictionaryOfStrings {...defaultProps} />); + const key: string = Object.keys(initialValue)[0] || ""; + const value: string = initialValue[key] || ""; + + expect(screen.getByDisplayValue(key)).toBeInTheDocument(); + expect(screen.getByDisplayValue(value)).toBeInTheDocument(); + }); + + it("Should call onChange function with correct values when input changes", () => { + render(<DictionaryOfStrings {...defaultProps} />); + + fireEvent.change( + screen.getByPlaceholderText(defaultProps.keyPlaceholder as string), + { + target: { value: "testKey" }, + }, + ); + fireEvent.change( + screen.getByPlaceholderText(defaultProps.valuePlaceholder as string), + { + target: { value: "testValue" }, + }, + ); + + expect(defaultProps.onChange).toHaveBeenCalledWith({ + testKey: "testValue", + }); + }); + + it("Should add a new row when the Add button is clicked", () => { + render(<DictionaryOfStrings {...defaultProps} />); + fireEvent.click(screen.getByText(`Add ${defaultProps.addButtonSuffix}`)); + + const keyInputs: HTMLInputElement[] = screen.getAllByPlaceholderText( + defaultProps.keyPlaceholder as string, + ); + const valueInputs: HTMLInputElement[] = screen.getAllByPlaceholderText( + defaultProps.valuePlaceholder as string, + ); + + expect(keyInputs.length).toBe(2); + expect(valueInputs.length).toBe(2); + }); + + it("Should delete a row when the Delete button is clicked", () => { const initialValue: Dictionary<string> = { - key: 'value', + key1: "value1", + key2: "value2", }; + render( + <DictionaryOfStrings {...defaultProps} initialValue={initialValue} />, + ); - const defaultProps: ComponentProps = { - initialValue, - onChange: jest.fn(), - keyPlaceholder: 'KeyPlaceholder', - valuePlaceholder: 'ValuePlaceholder', - addButtonSuffix: 'Attribute', - }; + const key1: string = Object.keys(initialValue)[0] || ""; + const value1: string = initialValue[key1] || ""; + const key2: string = Object.keys(initialValue)[1] || ""; + const value2: string = initialValue[key2] || ""; + const deleteButtons: HTMLButtonElement = screen.getByTestId( + `delete-${key2}`, + ); + fireEvent.click(deleteButtons); - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('Should not show any row if no initialValue is provided', () => { - render( - <DictionaryOfStrings {...{ ...defaultProps, initialValue: {} }} /> - ); - - expect( - screen.queryByPlaceholderText(defaultProps.keyPlaceholder as string) - ).not.toBeInTheDocument(); - expect( - screen.getByRole('button', { - name: new RegExp(`Add ${defaultProps.addButtonSuffix}`, 'i'), - }) - ).toBeInTheDocument(); - }); - - it('Should show the rows if initialValue is provided', () => { - render(<DictionaryOfStrings {...defaultProps} />); - const key: string = Object.keys(initialValue)[0] || ''; - const value: string = initialValue[key] || ''; - - expect(screen.getByDisplayValue(key)).toBeInTheDocument(); - expect(screen.getByDisplayValue(value)).toBeInTheDocument(); - }); - - it('Should call onChange function with correct values when input changes', () => { - render(<DictionaryOfStrings {...defaultProps} />); - - fireEvent.change( - screen.getByPlaceholderText(defaultProps.keyPlaceholder as string), - { - target: { value: 'testKey' }, - } - ); - fireEvent.change( - screen.getByPlaceholderText( - defaultProps.valuePlaceholder as string - ), - { - target: { value: 'testValue' }, - } - ); - - expect(defaultProps.onChange).toHaveBeenCalledWith({ - testKey: 'testValue', - }); - }); - - it('Should add a new row when the Add button is clicked', () => { - render(<DictionaryOfStrings {...defaultProps} />); - fireEvent.click( - screen.getByText(`Add ${defaultProps.addButtonSuffix}`) - ); - - const keyInputs: HTMLInputElement[] = screen.getAllByPlaceholderText( - defaultProps.keyPlaceholder as string - ); - const valueInputs: HTMLInputElement[] = screen.getAllByPlaceholderText( - defaultProps.valuePlaceholder as string - ); - - expect(keyInputs.length).toBe(2); - expect(valueInputs.length).toBe(2); - }); - - it('Should delete a row when the Delete button is clicked', () => { - const initialValue: Dictionary<string> = { - key1: 'value1', - key2: 'value2', - }; - render( - <DictionaryOfStrings - {...defaultProps} - initialValue={initialValue} - /> - ); - - const key1: string = Object.keys(initialValue)[0] || ''; - const value1: string = initialValue[key1] || ''; - const key2: string = Object.keys(initialValue)[1] || ''; - const value2: string = initialValue[key2] || ''; - const deleteButtons: HTMLButtonElement = screen.getByTestId( - `delete-${key2}` - ); - fireEvent.click(deleteButtons); - - expect(screen.queryByDisplayValue(key1)).toBeInTheDocument(); - expect(screen.queryByDisplayValue(value1)).toBeInTheDocument(); - expect(screen.queryByDisplayValue(key2)).not.toBeInTheDocument(); - expect(screen.queryByDisplayValue(value2)).not.toBeInTheDocument(); - }); + expect(screen.queryByDisplayValue(key1)).toBeInTheDocument(); + expect(screen.queryByDisplayValue(value1)).toBeInTheDocument(); + expect(screen.queryByDisplayValue(key2)).not.toBeInTheDocument(); + expect(screen.queryByDisplayValue(value2)).not.toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/Dropdown.test.tsx b/CommonUI/src/Tests/Components/Dropdown.test.tsx index d29ecac2ec..8e76e7acb3 100644 --- a/CommonUI/src/Tests/Components/Dropdown.test.tsx +++ b/CommonUI/src/Tests/Components/Dropdown.test.tsx @@ -1,256 +1,244 @@ -import Dropdown, { DropdownOption } from '../../Components/Dropdown/Dropdown'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen } from '@testing-library/react'; -import React from 'react'; +import Dropdown, { DropdownOption } from "../../Components/Dropdown/Dropdown"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; -describe('Dropdown', () => { - const options: DropdownOption[] = [ - { value: '1', label: '1' }, - { value: '2', label: '2' }, - ]; +describe("Dropdown", () => { + const options: DropdownOption[] = [ + { value: "1", label: "1" }, + { value: "2", label: "2" }, + ]; - test('renders with required props only', () => { - const { getByRole } = render( - <Dropdown onChange={() => {}} options={options} /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("renders with required props only", () => { + const { getByRole } = render( + <Dropdown onChange={() => {}} options={options} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - expect(dropdown).toBeInTheDocument(); - }); + expect(dropdown).toBeInTheDocument(); + }); - test('renders with all props', () => { - const { getByRole } = render( - <Dropdown - onClick={() => {}} - onChange={() => {}} - onFocus={() => {}} - onBlur={() => {}} - options={options} - initialValue={{ value: '1', label: '1' }} - value={{ value: '1', label: '1' }} - placeholder="placeholder" - className="class-name" - tabIndex={1} - error="error" - isMultiSelect={true} - /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("renders with all props", () => { + const { getByRole } = render( + <Dropdown + onClick={() => {}} + onChange={() => {}} + onFocus={() => {}} + onBlur={() => {}} + options={options} + initialValue={{ value: "1", label: "1" }} + value={{ value: "1", label: "1" }} + placeholder="placeholder" + className="class-name" + tabIndex={1} + error="error" + isMultiSelect={true} + />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - expect(dropdown).toBeInTheDocument(); - }); + expect(dropdown).toBeInTheDocument(); + }); - test('sets options', async () => { - const { getByRole } = render( - <Dropdown onChange={() => {}} options={options} /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("sets options", async () => { + const { getByRole } = render( + <Dropdown onChange={() => {}} options={options} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - fireEvent.keyDown(dropdown, { key: 'ArrowDown', code: 'ArrowDown' }); + fireEvent.keyDown(dropdown, { key: "ArrowDown", code: "ArrowDown" }); - expect(await screen.findByText('1')).toBeInTheDocument(); - expect(await screen.findByText('2')).toBeInTheDocument(); - }); + expect(await screen.findByText("1")).toBeInTheDocument(); + expect(await screen.findByText("2")).toBeInTheDocument(); + }); - test('renders placeholder', async () => { - const { getByText } = render( - <Dropdown - onChange={() => {}} - options={options} - placeholder="placeholder" - /> - ); + test("renders placeholder", async () => { + const { getByText } = render( + <Dropdown + onChange={() => {}} + options={options} + placeholder="placeholder" + />, + ); - expect(getByText('placeholder')).toBeInTheDocument(); - }); + expect(getByText("placeholder")).toBeInTheDocument(); + }); - test('sets initialValue', async () => { - const { getByText, queryByText } = render( - <Dropdown - onChange={() => {}} - options={options} - initialValue={{ value: '1', label: '1' }} - /> - ); + test("sets initialValue", async () => { + const { getByText, queryByText } = render( + <Dropdown + onChange={() => {}} + options={options} + initialValue={{ value: "1", label: "1" }} + />, + ); - expect(getByText('1')).toBeInTheDocument(); - expect(queryByText('2')).toBeNull(); - }); + expect(getByText("1")).toBeInTheDocument(); + expect(queryByText("2")).toBeNull(); + }); - test('sets initialValue array for multi select', () => { - const { getByText } = render( - <Dropdown - onChange={() => {}} - options={options} - initialValue={[ - { value: '1', label: '1' }, - { value: '2', label: '2' }, - ]} - isMultiSelect={true} - /> - ); + test("sets initialValue array for multi select", () => { + const { getByText } = render( + <Dropdown + onChange={() => {}} + options={options} + initialValue={[ + { value: "1", label: "1" }, + { value: "2", label: "2" }, + ]} + isMultiSelect={true} + />, + ); - expect(getByText('1')).toBeInTheDocument(); - expect(getByText('2')).toBeInTheDocument(); - }); + expect(getByText("1")).toBeInTheDocument(); + expect(getByText("2")).toBeInTheDocument(); + }); - test('sets value', async () => { - const { getByText, queryByText } = render( - <Dropdown - onChange={() => {}} - options={options} - value={{ value: '1', label: '1' }} - /> - ); + test("sets value", async () => { + const { getByText, queryByText } = render( + <Dropdown + onChange={() => {}} + options={options} + value={{ value: "1", label: "1" }} + />, + ); - expect(getByText('1')).toBeInTheDocument(); - expect(queryByText('2')).toBeNull(); - }); + expect(getByText("1")).toBeInTheDocument(); + expect(queryByText("2")).toBeNull(); + }); - test.skip('test value overrides initialValue', () => { - const { getByText, queryByText } = render( - <Dropdown - onChange={() => {}} - options={options} - initialValue={{ value: '1', label: '1' }} - value={{ value: '2', label: '2' }} - /> - ); + test.skip("test value overrides initialValue", () => { + const { getByText, queryByText } = render( + <Dropdown + onChange={() => {}} + options={options} + initialValue={{ value: "1", label: "1" }} + value={{ value: "2", label: "2" }} + />, + ); - expect(getByText('2')).toBeInTheDocument(); - expect(queryByText('1')).toBeNull(); - }); + expect(getByText("2")).toBeInTheDocument(); + expect(queryByText("1")).toBeNull(); + }); - test('sets className', () => { - const { getByRole } = render( - <Dropdown - onChange={() => {}} - options={options} - className="class-name" - /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("sets className", () => { + const { getByRole } = render( + <Dropdown onChange={() => {}} options={options} className="class-name" />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - expect(dropdown.closest('.class-name')).toBeInTheDocument(); - }); + expect(dropdown.closest(".class-name")).toBeInTheDocument(); + }); - test('sets default className', () => { - const { getByRole } = render( - <Dropdown onChange={() => {}} options={options} /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("sets default className", () => { + const { getByRole } = render( + <Dropdown onChange={() => {}} options={options} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - expect(dropdown.closest('div')?.classList.length).toBeGreaterThan(0); - }); + expect(dropdown.closest("div")?.classList.length).toBeGreaterThan(0); + }); - test('sets tabIndex', () => { - const { getByRole } = render( - <Dropdown onChange={() => {}} options={options} tabIndex={1} /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("sets tabIndex", () => { + const { getByRole } = render( + <Dropdown onChange={() => {}} options={options} tabIndex={1} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - expect(dropdown.tabIndex).toBe(1); - }); + expect(dropdown.tabIndex).toBe(1); + }); - test('displays error', () => { - const { getByText } = render( - <Dropdown onChange={() => {}} options={options} error="error" /> - ); + test("displays error", () => { + const { getByText } = render( + <Dropdown onChange={() => {}} options={options} error="error" />, + ); - expect(getByText('error')).toBeInTheDocument(); - }); + expect(getByText("error")).toBeInTheDocument(); + }); - test('sets isMultiSelect', async () => { - const onChange: jest.Mock = jest.fn(); + test("sets isMultiSelect", async () => { + const onChange: jest.Mock = jest.fn(); - const { getByRole, getByText } = render( - <Dropdown - onChange={onChange} - options={options} - isMultiSelect={true} - /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + const { getByRole, getByText } = render( + <Dropdown onChange={onChange} options={options} isMultiSelect={true} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - fireEvent.keyDown(dropdown, { key: 'ArrowDown', code: 'ArrowDown' }); - fireEvent.click(await screen.findByText('1')); + fireEvent.keyDown(dropdown, { key: "ArrowDown", code: "ArrowDown" }); + fireEvent.click(await screen.findByText("1")); - fireEvent.keyDown(dropdown, { key: 'ArrowDown', code: 'ArrowDown' }); - fireEvent.click(await screen.findByText('2')); + fireEvent.keyDown(dropdown, { key: "ArrowDown", code: "ArrowDown" }); + fireEvent.click(await screen.findByText("2")); - expect(getByText('1')).toBeInTheDocument(); - expect(getByText('2')).toBeInTheDocument(); - }); + expect(getByText("1")).toBeInTheDocument(); + expect(getByText("2")).toBeInTheDocument(); + }); - test('calls onChange when option is selected', async () => { - const onChange: jest.Mock = jest.fn(); - const { getByRole } = render( - <Dropdown onChange={onChange} options={options} /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("calls onChange when option is selected", async () => { + const onChange: jest.Mock = jest.fn(); + const { getByRole } = render( + <Dropdown onChange={onChange} options={options} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - fireEvent.keyDown(dropdown, { key: 'ArrowDown', code: 'ArrowDown' }); - fireEvent.click(await screen.findByText('1')); + fireEvent.keyDown(dropdown, { key: "ArrowDown", code: "ArrowDown" }); + fireEvent.click(await screen.findByText("1")); - expect(onChange).toHaveBeenCalled(); - expect(onChange).toHaveBeenCalledWith('1'); - }); + expect(onChange).toHaveBeenCalled(); + expect(onChange).toHaveBeenCalledWith("1"); + }); - test('calls onChange when option is selected for multi select', async () => { - const onChange: jest.Mock = jest.fn(); - const { getByRole } = render( - <Dropdown - onChange={onChange} - options={options} - isMultiSelect={true} - /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("calls onChange when option is selected for multi select", async () => { + const onChange: jest.Mock = jest.fn(); + const { getByRole } = render( + <Dropdown onChange={onChange} options={options} isMultiSelect={true} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - fireEvent.keyDown(dropdown, { key: 'ArrowDown', code: 'ArrowDown' }); - fireEvent.click(await screen.findByText('1')); + fireEvent.keyDown(dropdown, { key: "ArrowDown", code: "ArrowDown" }); + fireEvent.click(await screen.findByText("1")); - fireEvent.keyDown(dropdown, { key: 'ArrowDown', code: 'ArrowDown' }); - fireEvent.click(await screen.findByText('2')); + fireEvent.keyDown(dropdown, { key: "ArrowDown", code: "ArrowDown" }); + fireEvent.click(await screen.findByText("2")); - expect(onChange).toHaveBeenCalled(); - expect(onChange).toHaveBeenCalledWith(['1']); - expect(onChange).toHaveBeenCalledWith(['1', '2']); - }); + expect(onChange).toHaveBeenCalled(); + expect(onChange).toHaveBeenCalledWith(["1"]); + expect(onChange).toHaveBeenCalledWith(["1", "2"]); + }); - test('calls onClick', () => { - const onClick: jest.Mock = jest.fn(); - const { getByRole } = render( - <Dropdown onClick={onClick} options={options} /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("calls onClick", () => { + const onClick: jest.Mock = jest.fn(); + const { getByRole } = render( + <Dropdown onClick={onClick} options={options} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - fireEvent.click(dropdown); + fireEvent.click(dropdown); - expect(onClick).toHaveBeenCalled(); - }); + expect(onClick).toHaveBeenCalled(); + }); - test('calls onFocus', () => { - const onFocus: jest.Mock = jest.fn(); - const { getByRole } = render( - <Dropdown onFocus={onFocus} options={options} /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("calls onFocus", () => { + const onFocus: jest.Mock = jest.fn(); + const { getByRole } = render( + <Dropdown onFocus={onFocus} options={options} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - fireEvent.focus(dropdown); + fireEvent.focus(dropdown); - expect(onFocus).toHaveBeenCalled(); - }); + expect(onFocus).toHaveBeenCalled(); + }); - test('calls onBlur', () => { - const onBlur: jest.Mock = jest.fn(); - const { getByRole } = render( - <Dropdown onBlur={onBlur} options={options} /> - ); - const dropdown: HTMLElement = getByRole('combobox'); + test("calls onBlur", () => { + const onBlur: jest.Mock = jest.fn(); + const { getByRole } = render( + <Dropdown onBlur={onBlur} options={options} />, + ); + const dropdown: HTMLElement = getByRole("combobox"); - fireEvent.blur(dropdown); + fireEvent.blur(dropdown); - expect(onBlur).toHaveBeenCalled(); - }); + expect(onBlur).toHaveBeenCalled(); + }); }); diff --git a/CommonUI/src/Tests/Components/DuplicateModel.test.tsx b/CommonUI/src/Tests/Components/DuplicateModel.test.tsx index dda191c2b6..ceea1bad2e 100644 --- a/CommonUI/src/Tests/Components/DuplicateModel.test.tsx +++ b/CommonUI/src/Tests/Components/DuplicateModel.test.tsx @@ -1,325 +1,309 @@ -import DuplicateModel from '../../Components/DuplicateModel/DuplicateModel'; -import { ModelField } from '../../Components/Forms/ModelForm'; -import Select from '../../Utils/BaseDatabase/Select'; -import { describe, expect, it } from '@jest/globals'; +import DuplicateModel from "../../Components/DuplicateModel/DuplicateModel"; +import { ModelField } from "../../Components/Forms/ModelForm"; +import Select from "../../Utils/BaseDatabase/Select"; +import { describe, expect, it } from "@jest/globals"; import { - fireEvent, - render, - screen, - waitFor, - within, -} from '@testing-library/react'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableMetaData from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import React from 'react'; -import { act } from 'react-test-renderer'; + fireEvent, + render, + screen, + waitFor, + within, +} from "@testing-library/react"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableMetaData from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import React from "react"; +import { act } from "react-test-renderer"; @TableMetaData({ - tableName: 'Foo', - singularName: 'Foo', - pluralName: 'Foos', - icon: IconProp.Wrench, - tableDescription: 'A test model', + tableName: "Foo", + singularName: "Foo", + pluralName: "Foos", + icon: IconProp.Wrench, + tableDescription: "A test model", }) -@CrudApiEndpoint(new Route('/testModel')) +@CrudApiEndpoint(new Route("/testModel")) class TestModel extends BaseModel { - public changeThis?: string = 'original'; + public changeThis?: string = "original"; } -jest.mock('../../Utils/ModelAPI/ModelAPI', () => { - return { - getItem: jest - .fn() - .mockResolvedValueOnce({ - changeThis: 'changed', - setValue: function (key: 'changeThis', value: string) { - this[key] = value; - }, - removeValue: jest.fn(), - }) - .mockResolvedValueOnce({ - changeThis: 'changed', - setValue: function (key: 'changeThis', value: string) { - this[key] = value; - }, - removeValue: jest.fn(), - }) - .mockResolvedValueOnce(undefined), - create: jest - .fn() - .mockResolvedValueOnce({ - data: { - id: 'foobar', - changeThis: 'changed', - }, - }) - .mockResolvedValueOnce(undefined), - }; -}); - -jest.mock('../../Utils/Navigation', () => { - return { - navigate: jest.fn(), - }; -}); - -describe('DuplicateModel', () => { - const fieldsToDuplicate: Select<TestModel> = {}; - const fieldsToChange: Array<ModelField<TestModel>> = [ - { - field: { - changeThis: true, - }, - title: 'Change This', - required: false, - placeholder: 'You can change this', +jest.mock("../../Utils/ModelAPI/ModelAPI", () => { + return { + getItem: jest + .fn() + .mockResolvedValueOnce({ + changeThis: "changed", + setValue: function (key: "changeThis", value: string) { + this[key] = value; }, - ]; - it('renders correctly', () => { - render( - <DuplicateModel - modelType={TestModel} - modelId={new ObjectID('foo')} - fieldsToDuplicate={fieldsToDuplicate} - fieldsToChange={fieldsToChange} - /> - ); - expect(screen.getByTestId('card-details-heading')?.textContent).toBe( - 'Duplicate Foo' - ); - expect(screen.getByTestId('card-description')?.textContent).toBe( - 'Duplicating this foo will create another foo exactly like this one.' - ); - expect(screen.getByTestId('card-button')?.textContent).toBe( - 'Duplicate Foo' - ); - }); - it('shows confirmation modal when duplicate button is clicked', () => { - render( - <DuplicateModel - modelType={TestModel} - modelId={new ObjectID('foo')} - fieldsToDuplicate={fieldsToDuplicate} - fieldsToChange={fieldsToChange} - /> - ); - const button: HTMLElement = screen.getByRole('button', { - name: 'Duplicate Foo', - }); - fireEvent.click(button); - expect(screen.getByRole('dialog')).toBeDefined(); - const confirmDialog: HTMLElement = screen.getByRole('dialog'); - expect( - within(confirmDialog).getByTestId('modal-title')?.textContent - ).toBe('Duplicate Foo'); - expect( - within(confirmDialog).getByTestId('modal-description')?.textContent - ).toBe('Are you sure you want to duplicate this foo?'); - expect( - within(confirmDialog).getByTestId('modal-footer-submit-button') - ?.textContent - ).toBe('Duplicate Foo'); - expect( - within(confirmDialog).getByTestId('modal-footer-close-button') - ?.textContent - ).toBe('Close'); - }); - it('duplicates item when confirmation button is clicked', async () => { - const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); - render( - <DuplicateModel - modelType={TestModel} - modelId={new ObjectID('foo')} - fieldsToDuplicate={fieldsToDuplicate} - fieldsToChange={fieldsToChange} - onDuplicateSuccess={onDuplicateSuccess} - navigateToOnSuccess={new Route('/done')} - /> - ); - const button: HTMLElement = screen.getByRole('button', { - name: 'Duplicate Foo', - }); - void act(() => { - fireEvent.click(button); - }); - const dialog: HTMLElement = screen.getByRole('dialog'); - const confirmationButton: HTMLElement = within(dialog).getByRole( - 'button', - { - name: 'Duplicate Foo', - } - ); - void act(() => { - fireEvent.click(confirmationButton); - }); - await waitFor(() => { - return expect(onDuplicateSuccess).toBeCalledWith({ - id: 'foobar', - changeThis: 'changed', - }); - }); - await waitFor(() => { - return expect( - require('../../Utils/Navigation').navigate - ).toBeCalledWith(new Route('/done/foobar'), { - forceNavigate: true, - }); - }); - }); - it('closes confirmation dialog when close button is clicked', () => { - const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); - render( - <DuplicateModel - modelType={TestModel} - modelId={new ObjectID('foo')} - fieldsToDuplicate={fieldsToDuplicate} - fieldsToChange={fieldsToChange} - onDuplicateSuccess={onDuplicateSuccess} - navigateToOnSuccess={new Route('/done')} - /> - ); - const button: HTMLElement = screen.getByRole('button', { - name: 'Duplicate Foo', - }); - void act(() => { - fireEvent.click(button); - }); - const dialog: HTMLElement = screen.getByRole('dialog'); - const closeButton: HTMLElement = within(dialog).getByRole('button', { - name: 'Close', - }); - void act(() => { - fireEvent.click(closeButton); - }); - expect(screen.queryByRole('dialog')).toBeFalsy(); - }); - it('handles could not create error correctly', async () => { - const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); - render( - <DuplicateModel - modelType={TestModel} - modelId={new ObjectID('foo')} - fieldsToDuplicate={fieldsToDuplicate} - fieldsToChange={fieldsToChange} - onDuplicateSuccess={onDuplicateSuccess} - navigateToOnSuccess={new Route('/done')} - /> - ); - const button: HTMLElement = screen.getByRole('button', { - name: 'Duplicate Foo', - }); - void act(() => { - fireEvent.click(button); - }); - const dialog: HTMLElement = screen.getByRole('dialog'); - const confirmationButton: HTMLElement = within(dialog).getByRole( - 'button', - { - name: 'Duplicate Foo', - } - ); - void act(() => { - fireEvent.click(confirmationButton); - }); - await screen.findByText('Duplicate Error'); - const errorDialog: HTMLElement = screen.getByRole('dialog'); - expect( - within(errorDialog).getByTestId('modal-title')?.textContent - ).toBe('Duplicate Error'); - expect( - within(errorDialog).getByTestId('confirm-modal-description') - ?.textContent - ).toBe('Error: Could not create Foo'); - expect( - within(errorDialog).getByTestId('modal-footer-submit-button') - ?.textContent - ).toBe('Close'); - }); - it('handles item not found error correctly', async () => { - const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); - render( - <DuplicateModel - modelType={TestModel} - modelId={new ObjectID('foo')} - fieldsToDuplicate={fieldsToDuplicate} - fieldsToChange={fieldsToChange} - onDuplicateSuccess={onDuplicateSuccess} - navigateToOnSuccess={new Route('/done')} - /> - ); - const button: HTMLElement = screen.getByRole('button', { - name: 'Duplicate Foo', - }); - void act(() => { - fireEvent.click(button); - }); - const dialog: HTMLElement = screen.getByRole('dialog'); - const confirmationButton: HTMLElement = within(dialog).getByRole( - 'button', - { - name: 'Duplicate Foo', - } - ); - void act(() => { - fireEvent.click(confirmationButton); - }); - await screen.findByText('Duplicate Error'); - const errorDialog: HTMLElement = screen.getByRole('dialog'); - expect( - within(errorDialog).getByTestId('modal-title')?.textContent - ).toBe('Duplicate Error'); - expect( - within(errorDialog).getByTestId('confirm-modal-description') - ?.textContent - ).toBe('Error: Could not find Foo with id foo'); - expect( - within(errorDialog).getByTestId('modal-footer-submit-button') - ?.textContent - ).toBe('Close'); - }); - it('closes error dialog when close button is clicked', async () => { - const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); - render( - <DuplicateModel - modelType={TestModel} - modelId={new ObjectID('foo')} - fieldsToDuplicate={fieldsToDuplicate} - fieldsToChange={fieldsToChange} - onDuplicateSuccess={onDuplicateSuccess} - navigateToOnSuccess={new Route('/done')} - /> - ); - const button: HTMLElement = screen.getByRole('button', { - name: 'Duplicate Foo', - }); - void act(() => { - fireEvent.click(button); - }); - const dialog: HTMLElement = screen.getByRole('dialog'); - const confirmationButton: HTMLElement = within(dialog).getByRole( - 'button', - { - name: 'Duplicate Foo', - } - ); - void act(() => { - fireEvent.click(confirmationButton); - }); - await screen.findByText('Duplicate Error'); - const errorDialog: HTMLElement = screen.getByRole('dialog'); - const closeButton: HTMLElement = within(errorDialog).getByRole( - 'button', - { - name: 'Close', - } - ); - void act(() => { - fireEvent.click(closeButton); - }); - expect(screen.queryByRole('dialog')).toBeFalsy(); - }); + removeValue: jest.fn(), + }) + .mockResolvedValueOnce({ + changeThis: "changed", + setValue: function (key: "changeThis", value: string) { + this[key] = value; + }, + removeValue: jest.fn(), + }) + .mockResolvedValueOnce(undefined), + create: jest + .fn() + .mockResolvedValueOnce({ + data: { + id: "foobar", + changeThis: "changed", + }, + }) + .mockResolvedValueOnce(undefined), + }; +}); + +jest.mock("../../Utils/Navigation", () => { + return { + navigate: jest.fn(), + }; +}); + +describe("DuplicateModel", () => { + const fieldsToDuplicate: Select<TestModel> = {}; + const fieldsToChange: Array<ModelField<TestModel>> = [ + { + field: { + changeThis: true, + }, + title: "Change This", + required: false, + placeholder: "You can change this", + }, + ]; + it("renders correctly", () => { + render( + <DuplicateModel + modelType={TestModel} + modelId={new ObjectID("foo")} + fieldsToDuplicate={fieldsToDuplicate} + fieldsToChange={fieldsToChange} + />, + ); + expect(screen.getByTestId("card-details-heading")?.textContent).toBe( + "Duplicate Foo", + ); + expect(screen.getByTestId("card-description")?.textContent).toBe( + "Duplicating this foo will create another foo exactly like this one.", + ); + expect(screen.getByTestId("card-button")?.textContent).toBe( + "Duplicate Foo", + ); + }); + it("shows confirmation modal when duplicate button is clicked", () => { + render( + <DuplicateModel + modelType={TestModel} + modelId={new ObjectID("foo")} + fieldsToDuplicate={fieldsToDuplicate} + fieldsToChange={fieldsToChange} + />, + ); + const button: HTMLElement = screen.getByRole("button", { + name: "Duplicate Foo", + }); + fireEvent.click(button); + expect(screen.getByRole("dialog")).toBeDefined(); + const confirmDialog: HTMLElement = screen.getByRole("dialog"); + expect(within(confirmDialog).getByTestId("modal-title")?.textContent).toBe( + "Duplicate Foo", + ); + expect( + within(confirmDialog).getByTestId("modal-description")?.textContent, + ).toBe("Are you sure you want to duplicate this foo?"); + expect( + within(confirmDialog).getByTestId("modal-footer-submit-button") + ?.textContent, + ).toBe("Duplicate Foo"); + expect( + within(confirmDialog).getByTestId("modal-footer-close-button") + ?.textContent, + ).toBe("Close"); + }); + it("duplicates item when confirmation button is clicked", async () => { + const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); + render( + <DuplicateModel + modelType={TestModel} + modelId={new ObjectID("foo")} + fieldsToDuplicate={fieldsToDuplicate} + fieldsToChange={fieldsToChange} + onDuplicateSuccess={onDuplicateSuccess} + navigateToOnSuccess={new Route("/done")} + />, + ); + const button: HTMLElement = screen.getByRole("button", { + name: "Duplicate Foo", + }); + void act(() => { + fireEvent.click(button); + }); + const dialog: HTMLElement = screen.getByRole("dialog"); + const confirmationButton: HTMLElement = within(dialog).getByRole("button", { + name: "Duplicate Foo", + }); + void act(() => { + fireEvent.click(confirmationButton); + }); + await waitFor(() => { + return expect(onDuplicateSuccess).toBeCalledWith({ + id: "foobar", + changeThis: "changed", + }); + }); + await waitFor(() => { + return expect(require("../../Utils/Navigation").navigate).toBeCalledWith( + new Route("/done/foobar"), + { + forceNavigate: true, + }, + ); + }); + }); + it("closes confirmation dialog when close button is clicked", () => { + const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); + render( + <DuplicateModel + modelType={TestModel} + modelId={new ObjectID("foo")} + fieldsToDuplicate={fieldsToDuplicate} + fieldsToChange={fieldsToChange} + onDuplicateSuccess={onDuplicateSuccess} + navigateToOnSuccess={new Route("/done")} + />, + ); + const button: HTMLElement = screen.getByRole("button", { + name: "Duplicate Foo", + }); + void act(() => { + fireEvent.click(button); + }); + const dialog: HTMLElement = screen.getByRole("dialog"); + const closeButton: HTMLElement = within(dialog).getByRole("button", { + name: "Close", + }); + void act(() => { + fireEvent.click(closeButton); + }); + expect(screen.queryByRole("dialog")).toBeFalsy(); + }); + it("handles could not create error correctly", async () => { + const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); + render( + <DuplicateModel + modelType={TestModel} + modelId={new ObjectID("foo")} + fieldsToDuplicate={fieldsToDuplicate} + fieldsToChange={fieldsToChange} + onDuplicateSuccess={onDuplicateSuccess} + navigateToOnSuccess={new Route("/done")} + />, + ); + const button: HTMLElement = screen.getByRole("button", { + name: "Duplicate Foo", + }); + void act(() => { + fireEvent.click(button); + }); + const dialog: HTMLElement = screen.getByRole("dialog"); + const confirmationButton: HTMLElement = within(dialog).getByRole("button", { + name: "Duplicate Foo", + }); + void act(() => { + fireEvent.click(confirmationButton); + }); + await screen.findByText("Duplicate Error"); + const errorDialog: HTMLElement = screen.getByRole("dialog"); + expect(within(errorDialog).getByTestId("modal-title")?.textContent).toBe( + "Duplicate Error", + ); + expect( + within(errorDialog).getByTestId("confirm-modal-description")?.textContent, + ).toBe("Error: Could not create Foo"); + expect( + within(errorDialog).getByTestId("modal-footer-submit-button") + ?.textContent, + ).toBe("Close"); + }); + it("handles item not found error correctly", async () => { + const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); + render( + <DuplicateModel + modelType={TestModel} + modelId={new ObjectID("foo")} + fieldsToDuplicate={fieldsToDuplicate} + fieldsToChange={fieldsToChange} + onDuplicateSuccess={onDuplicateSuccess} + navigateToOnSuccess={new Route("/done")} + />, + ); + const button: HTMLElement = screen.getByRole("button", { + name: "Duplicate Foo", + }); + void act(() => { + fireEvent.click(button); + }); + const dialog: HTMLElement = screen.getByRole("dialog"); + const confirmationButton: HTMLElement = within(dialog).getByRole("button", { + name: "Duplicate Foo", + }); + void act(() => { + fireEvent.click(confirmationButton); + }); + await screen.findByText("Duplicate Error"); + const errorDialog: HTMLElement = screen.getByRole("dialog"); + expect(within(errorDialog).getByTestId("modal-title")?.textContent).toBe( + "Duplicate Error", + ); + expect( + within(errorDialog).getByTestId("confirm-modal-description")?.textContent, + ).toBe("Error: Could not find Foo with id foo"); + expect( + within(errorDialog).getByTestId("modal-footer-submit-button") + ?.textContent, + ).toBe("Close"); + }); + it("closes error dialog when close button is clicked", async () => { + const onDuplicateSuccess: (item: TestModel) => void = jest.fn(); + render( + <DuplicateModel + modelType={TestModel} + modelId={new ObjectID("foo")} + fieldsToDuplicate={fieldsToDuplicate} + fieldsToChange={fieldsToChange} + onDuplicateSuccess={onDuplicateSuccess} + navigateToOnSuccess={new Route("/done")} + />, + ); + const button: HTMLElement = screen.getByRole("button", { + name: "Duplicate Foo", + }); + void act(() => { + fireEvent.click(button); + }); + const dialog: HTMLElement = screen.getByRole("dialog"); + const confirmationButton: HTMLElement = within(dialog).getByRole("button", { + name: "Duplicate Foo", + }); + void act(() => { + fireEvent.click(confirmationButton); + }); + await screen.findByText("Duplicate Error"); + const errorDialog: HTMLElement = screen.getByRole("dialog"); + const closeButton: HTMLElement = within(errorDialog).getByRole("button", { + name: "Close", + }); + void act(() => { + fireEvent.click(closeButton); + }); + expect(screen.queryByRole("dialog")).toBeFalsy(); + }); }); diff --git a/CommonUI/src/Tests/Components/EmptyState/EmptyState.test.tsx b/CommonUI/src/Tests/Components/EmptyState/EmptyState.test.tsx index 7f19e906ec..84e99ea7f4 100644 --- a/CommonUI/src/Tests/Components/EmptyState/EmptyState.test.tsx +++ b/CommonUI/src/Tests/Components/EmptyState/EmptyState.test.tsx @@ -1,44 +1,44 @@ -import EmptyState from '../../../Components/EmptyState/EmptyState'; -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React from 'react'; +import EmptyState from "../../../Components/EmptyState/EmptyState"; +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import IconProp from "Common/Types/Icon/IconProp"; +import React from "react"; -describe('EmptyState', () => { - test('renders correctly with all props', () => { - render( - <EmptyState - id="empty-state" - title="Empty State Title" - description="This is an empty state description" - icon={IconProp.User} - footer={<div>This is a footer element</div>} - /> - ); - const titleElement: HTMLElement = screen.getByText('Empty State Title'); - const descriptionElement: HTMLElement = - screen.getByText('Empty State Title'); - const iconElement: HTMLElement = screen.getByRole('icon'); - const footerElement: HTMLElement = screen.getByText( - 'This is a footer element' - ); - expect(titleElement).toBeInTheDocument(); - expect(descriptionElement).toBeInTheDocument(); - expect(iconElement).toBeInTheDocument(); - expect(footerElement).toBeInTheDocument(); - }); - test('renders without an icon', () => { - render( - <EmptyState - id="empty-state" - icon={undefined} - title="Title" - description="Description" - /> - ); - const title: HTMLElement = screen.getByText('Title'); - const description: HTMLElement = screen.getByText('Description'); - expect(title).toBeInTheDocument(); - expect(description).toBeInTheDocument(); - }); +describe("EmptyState", () => { + test("renders correctly with all props", () => { + render( + <EmptyState + id="empty-state" + title="Empty State Title" + description="This is an empty state description" + icon={IconProp.User} + footer={<div>This is a footer element</div>} + />, + ); + const titleElement: HTMLElement = screen.getByText("Empty State Title"); + const descriptionElement: HTMLElement = + screen.getByText("Empty State Title"); + const iconElement: HTMLElement = screen.getByRole("icon"); + const footerElement: HTMLElement = screen.getByText( + "This is a footer element", + ); + expect(titleElement).toBeInTheDocument(); + expect(descriptionElement).toBeInTheDocument(); + expect(iconElement).toBeInTheDocument(); + expect(footerElement).toBeInTheDocument(); + }); + test("renders without an icon", () => { + render( + <EmptyState + id="empty-state" + icon={undefined} + title="Title" + description="Description" + />, + ); + const title: HTMLElement = screen.getByText("Title"); + const description: HTMLElement = screen.getByText("Description"); + expect(title).toBeInTheDocument(); + expect(description).toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/ErrorBoundary.test.tsx b/CommonUI/src/Tests/Components/ErrorBoundary.test.tsx index cf56255163..02832365ef 100644 --- a/CommonUI/src/Tests/Components/ErrorBoundary.test.tsx +++ b/CommonUI/src/Tests/Components/ErrorBoundary.test.tsx @@ -1,50 +1,50 @@ -import ErrorBoundary from '../../Components/ErrorBoundary'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import React, { FunctionComponent, useEffect } from 'react'; +import ErrorBoundary from "../../Components/ErrorBoundary"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import React, { FunctionComponent, useEffect } from "react"; -describe('ErrorBoundary', () => { - const spy: jest.SpyInstance = jest.spyOn(console, 'error'); +describe("ErrorBoundary", () => { + const spy: jest.SpyInstance = jest.spyOn(console, "error"); - beforeAll(() => { - spy.mockImplementation(() => {}); - }); + beforeAll(() => { + spy.mockImplementation(() => {}); + }); - afterAll(() => { - spy.mockRestore(); - }); + afterAll(() => { + spy.mockRestore(); + }); - test('should render children when no error is thrown', () => { - render( - <ErrorBoundary> - <div>Child Component</div> - </ErrorBoundary> - ); + test("should render children when no error is thrown", () => { + render( + <ErrorBoundary> + <div>Child Component</div> + </ErrorBoundary>, + ); - const child: HTMLElement = screen.getByText('Child Component'); + const child: HTMLElement = screen.getByText("Child Component"); - expect(child).toBeInTheDocument(); - }); + expect(child).toBeInTheDocument(); + }); - test('should render error message when an error occurs', () => { - const ErrorThrowingComponent: FunctionComponent = () => { - useEffect(() => { - throw new Error('Test Error'); - }, []); + test("should render error message when an error occurs", () => { + const ErrorThrowingComponent: FunctionComponent = () => { + useEffect(() => { + throw new Error("Test Error"); + }, []); - return <div>Error Throwing Component</div>; - }; + return <div>Error Throwing Component</div>; + }; - render( - <ErrorBoundary> - <ErrorThrowingComponent /> - </ErrorBoundary> - ); + render( + <ErrorBoundary> + <ErrorThrowingComponent /> + </ErrorBoundary>, + ); - const errorText: HTMLElement = screen.getByText( - 'An unexpected error has occurred. Please reload the page to continue' - ); + const errorText: HTMLElement = screen.getByText( + "An unexpected error has occurred. Please reload the page to continue", + ); - expect(errorText).toBeInTheDocument(); - }); + expect(errorText).toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/FilePicker.test.tsx b/CommonUI/src/Tests/Components/FilePicker.test.tsx index 2ed4eac9d5..e717489fdf 100644 --- a/CommonUI/src/Tests/Components/FilePicker.test.tsx +++ b/CommonUI/src/Tests/Components/FilePicker.test.tsx @@ -1,394 +1,390 @@ -import FilePicker from '../../Components/FilePicker/FilePicker'; -import ModelAPI from '../../Utils/ModelAPI/ModelAPI'; -import { faker } from '@faker-js/faker'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom/extend-expect'; +import FilePicker from "../../Components/FilePicker/FilePicker"; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import { faker } from "@faker-js/faker"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom/extend-expect"; import { - fireEvent, - queryAllByAttribute, - queryByAttribute, - queryByTestId, - render, - screen, - waitFor, -} from '@testing-library/react'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import MimeType from 'Common/Types/File/MimeType'; -import ObjectID from 'Common/Types/ObjectID'; -import FileModel from 'Model/Models/File'; -import React from 'react'; -import { act } from 'react-test-renderer'; + fireEvent, + queryAllByAttribute, + queryByAttribute, + queryByTestId, + render, + screen, + waitFor, +} from "@testing-library/react"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import MimeType from "Common/Types/File/MimeType"; +import ObjectID from "Common/Types/ObjectID"; +import FileModel from "Model/Models/File"; +import React from "react"; +import { act } from "react-test-renderer"; const mockOnChange: jest.Mock = jest.fn(); const mockOnBlur: jest.Mock = jest.fn(); -jest.mock('../../Utils/ModelAPI/ModelAPI', () => { - return { - create: jest.fn(), - }; +jest.mock("../../Utils/ModelAPI/ModelAPI", () => { + return { + create: jest.fn(), + }; }); interface DefaultProps { - onBlur: () => void; - onChange: (files: FileModel[]) => void; - mimeTypes: MimeType[]; - isOpen: boolean; - onClose: () => void; - initialValue?: FileModel | FileModel[]; - value?: FileModel[] | undefined; - isMultiFilePicker?: boolean; - readOnly?: boolean; + onBlur: () => void; + onChange: (files: FileModel[]) => void; + mimeTypes: MimeType[]; + isOpen: boolean; + onClose: () => void; + initialValue?: FileModel | FileModel[]; + value?: FileModel[] | undefined; + isMultiFilePicker?: boolean; + readOnly?: boolean; } interface DataTransfer { - dataTransfer: { - files: File[]; - types: string[]; - }; + dataTransfer: { + files: File[]; + types: string[]; + }; } type MockCreateResponseFunction = ( - file: File + file: File, ) => Promise<HTTPResponse<FileModel>>; const mockCreateResponse: MockCreateResponseFunction = async ( - file: File + file: File, ): Promise<HTTPResponse<FileModel>> => { - return new HTTPResponse( - 200, - { - file: (await file.arrayBuffer()) as Buffer, - name: file.name, - type: file.type, - slug: file.name, - isPublic: true, - }, - {} - ); + return new HTTPResponse( + 200, + { + file: (await file.arrayBuffer()) as Buffer, + name: file.name, + type: file.type, + slug: file.name, + isPublic: true, + }, + {}, + ); }; type MockFileModelFunction = (file: File) => Promise<FileModel>; const mockFileModel: MockFileModelFunction = async ( - file: File + file: File, ): Promise<FileModel> => { - const fileModel: FileModel = new FileModel(new ObjectID('123')); - fileModel.name = file.name; - fileModel.type = file.type as MimeType; - fileModel.slug = file.name; - fileModel.isPublic = true; - fileModel.file = (await file.arrayBuffer()) as Buffer; - return fileModel; + const fileModel: FileModel = new FileModel(new ObjectID("123")); + fileModel.name = file.name; + fileModel.type = file.type as MimeType; + fileModel.slug = file.name; + fileModel.isPublic = true; + fileModel.file = (await file.arrayBuffer()) as Buffer; + return fileModel; }; type MockFileFunction = () => File; const mockFile: MockFileFunction = (): File => { - const mockArrayBuffer: jest.Mock = jest.fn(); - mockArrayBuffer.mockResolvedValue(new ArrayBuffer(10)); // Mocked array buffer of size 10 + const mockArrayBuffer: jest.Mock = jest.fn(); + mockArrayBuffer.mockResolvedValue(new ArrayBuffer(10)); // Mocked array buffer of size 10 - const file: File = new File( - [faker.datatype.string()], - faker.system.commonFileName(MimeType.png), - { type: MimeType.png } - ); - file.arrayBuffer = mockArrayBuffer; - return file; + const file: File = new File( + [faker.datatype.string()], + faker.system.commonFileName(MimeType.png), + { type: MimeType.png }, + ); + file.arrayBuffer = mockArrayBuffer; + return file; }; const defaultProps: DefaultProps = { - onBlur: mockOnBlur, - onChange: mockOnChange, - mimeTypes: [MimeType.png], - isOpen: true, - onClose: jest.fn(), + onBlur: mockOnBlur, + onChange: mockOnChange, + mimeTypes: [MimeType.png], + isOpen: true, + onClose: jest.fn(), }; -describe('FilePicker', () => { - const MOCK_FILE_URL: string = 'https://mock-file-url'; +describe("FilePicker", () => { + const MOCK_FILE_URL: string = "https://mock-file-url"; - beforeAll(() => { - global.URL.createObjectURL = jest.fn(() => { - return MOCK_FILE_URL; - }); + beforeAll(() => { + global.URL.createObjectURL = jest.fn(() => { + return MOCK_FILE_URL; + }); + }); + + afterAll(() => { + ( + global.URL.createObjectURL as jest.MockedFunction< + typeof global.URL.createObjectURL + > + ).mockRestore(); + }); + + beforeEach(() => { + delete defaultProps.isMultiFilePicker; + delete defaultProps.initialValue; + delete defaultProps.value; + delete defaultProps.readOnly; + }); + + it("should render without crashing", () => { + render(<FilePicker {...defaultProps} />); + expect(screen.getByText("Upload a file")).toBeInTheDocument(); + expect(screen.getByRole("complementary")).toBeInTheDocument(); // aside element + }); + + it("should render with initial value", async () => { + defaultProps.initialValue = await mockFileModel(mockFile()); + const { container } = render(<FilePicker {...defaultProps} />); + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).toBeInTheDocument(); + }); + + it("should not render if file is missing the `file` attribute", async () => { + const file: FileModel = await mockFileModel(mockFile()); + delete file.file; + defaultProps.initialValue = file; + const { container } = render(<FilePicker {...defaultProps} />); + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).not.toBeInTheDocument(); + }); + + it("should render with initial value as array", async () => { + defaultProps.initialValue = [ + await mockFileModel(mockFile()), + await mockFileModel(mockFile()), + ]; + render(<FilePicker {...defaultProps} />); + const { container } = render(<FilePicker {...defaultProps} />); + expect(queryAllByAttribute("src", container, MOCK_FILE_URL)).toHaveLength( + 2, + ); + }); + + it("should render with value array with one element", async () => { + defaultProps.value = [await mockFileModel(mockFile())]; + const { container } = render(<FilePicker {...defaultProps} />); + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).toBeInTheDocument(); + }); + + it("should render with value array with more than one element", async () => { + defaultProps.value = [ + await mockFileModel(mockFile()), + await mockFileModel(mockFile()), + ]; + render(<FilePicker {...defaultProps} />); + const { container } = render(<FilePicker {...defaultProps} />); + expect(queryAllByAttribute("src", container, MOCK_FILE_URL)).toHaveLength( + 2, + ); + }); + + it("should not upload file when dropped and readOnly is true", async () => { + defaultProps.readOnly = true; + + const file: File = mockFile(); + const data: DataTransfer = { + dataTransfer: { + files: [file], + types: ["Files"], + }, + }; + + const createResponse: HTTPResponse<FileModel> = + await mockCreateResponse(file); + ( + ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> + ).mockResolvedValue(createResponse); + + const { container } = render(<FilePicker {...defaultProps} />); + + const dropzone: HTMLElement = screen.getByLabelText("Upload a file"); + fireEvent.drop(dropzone, data); + + await waitFor(() => { + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).not.toBeInTheDocument(); + }); + }); + + it('should throw an "File too large" when uploading a file that fails on arrayBuffer()', async () => { + const file: File = mockFile(); + file.arrayBuffer = jest.fn().mockRejectedValue(new Error("File too large")); + const data: DataTransfer = { + dataTransfer: { + files: [file], + types: ["Files"], + }, + }; + + const { container } = render(<FilePicker {...defaultProps} />); + + const dropzone: HTMLElement = screen.getByLabelText("Upload a file"); + fireEvent.drop(dropzone, data); + + await waitFor(() => { + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).not.toBeInTheDocument(); + }); + }); + + it("should upload a file when dropped", async () => { + const file: File = mockFile(); + const data: DataTransfer = { + dataTransfer: { + files: [file], + types: ["Files"], + }, + }; + + const createResponse: HTTPResponse<FileModel> = + await mockCreateResponse(file); + ( + ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> + ).mockResolvedValue(createResponse); + + const { container } = render(<FilePicker {...defaultProps} />); + + const dropzone: HTMLElement = screen.getByLabelText("Upload a file"); + await act(async () => { + fireEvent.drop(dropzone, data); }); - afterAll(() => { - ( - global.URL.createObjectURL as jest.MockedFunction< - typeof global.URL.createObjectURL - > - ).mockRestore(); + await waitFor(() => { + expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]); + expect(mockOnBlur).toHaveBeenCalled(); + expect(mockOnBlur).toHaveBeenCalled(); + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).toBeInTheDocument(); + }); + }); + + it("should upload a file when dropped", async () => { + const file: File = mockFile(); + const data: DataTransfer = { + dataTransfer: { + files: [file], + types: ["Files"], + }, + }; + + const createResponse: HTTPResponse<FileModel> = + await mockCreateResponse(file); + ( + ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> + ).mockResolvedValue(createResponse); + + const { container } = render(<FilePicker {...defaultProps} />); + + const dropzone: HTMLElement = screen.getByLabelText("Upload a file"); + await act(async () => { + fireEvent.drop(dropzone, data); }); - beforeEach(() => { - delete defaultProps.isMultiFilePicker; - delete defaultProps.initialValue; - delete defaultProps.value; - delete defaultProps.readOnly; + await waitFor(() => { + expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]); + expect(mockOnBlur).toHaveBeenCalled(); + expect(mockOnBlur).toHaveBeenCalled(); + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).toBeInTheDocument(); + }); + }); + + it("should show loader a file when files are being uploaded", async () => { + const uploadPromise: Promise<unknown> = new Promise((resolve: any) => { + (global as any).mockUploadResolve = resolve; // Store resolve function globally or in a scope accessible outside the test }); - it('should render without crashing', () => { - render(<FilePicker {...defaultProps} />); - expect(screen.getByText('Upload a file')).toBeInTheDocument(); - expect(screen.getByRole('complementary')).toBeInTheDocument(); // aside element + ( + ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> + ).mockImplementation((): any => { + return uploadPromise; }); - it('should render with initial value', async () => { - defaultProps.initialValue = await mockFileModel(mockFile()); - const { container } = render(<FilePicker {...defaultProps} />); - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).toBeInTheDocument(); + const file: File = mockFile(); + const data: DataTransfer = { + dataTransfer: { + files: [file], + types: ["Files"], + }, + }; + + const createResponse: HTTPResponse<FileModel> = + await mockCreateResponse(file); + + const { container } = render(<FilePicker {...defaultProps} />); + + const dropzone: HTMLElement = screen.getByLabelText("Upload a file"); + await act(async () => { + fireEvent.drop(dropzone, data); }); - it('should not render if file is missing the `file` attribute', async () => { - const file: FileModel = await mockFileModel(mockFile()); - delete file.file; - defaultProps.initialValue = file; - const { container } = render(<FilePicker {...defaultProps} />); - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).not.toBeInTheDocument(); + expect(queryByTestId(container, "loader")).toBeInTheDocument(); + + (global as any).mockUploadResolve(createResponse); + + await waitFor(() => { + expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]); + expect(mockOnBlur).toHaveBeenCalled(); + expect(mockOnBlur).toHaveBeenCalled(); + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).toBeInTheDocument(); + }); + }); + + it("should delete an uploaded file when clicking on it", async () => { + const file: File = mockFile(); + const data: DataTransfer = { + dataTransfer: { + files: [file], + types: ["Files"], + }, + }; + + const createResponse: HTTPResponse<FileModel> = + await mockCreateResponse(file); + ( + ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> + ).mockResolvedValue(createResponse); + + const { container } = render(<FilePicker {...defaultProps} />); + + const dropzone: HTMLElement = screen.getByLabelText("Upload a file"); + await act(async () => { + fireEvent.drop(dropzone, data); }); - it('should render with initial value as array', async () => { - defaultProps.initialValue = [ - await mockFileModel(mockFile()), - await mockFileModel(mockFile()), - ]; - render(<FilePicker {...defaultProps} />); - const { container } = render(<FilePicker {...defaultProps} />); - expect( - queryAllByAttribute('src', container, MOCK_FILE_URL) - ).toHaveLength(2); + await waitFor(() => { + // file should be in the dropzone + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).toBeInTheDocument(); }); - it('should render with value array with one element', async () => { - defaultProps.value = [await mockFileModel(mockFile())]; - const { container } = render(<FilePicker {...defaultProps} />); - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).toBeInTheDocument(); - }); - - it('should render with value array with more than one element', async () => { - defaultProps.value = [ - await mockFileModel(mockFile()), - await mockFileModel(mockFile()), - ]; - render(<FilePicker {...defaultProps} />); - const { container } = render(<FilePicker {...defaultProps} />); - expect( - queryAllByAttribute('src', container, MOCK_FILE_URL) - ).toHaveLength(2); - }); - - it('should not upload file when dropped and readOnly is true', async () => { - defaultProps.readOnly = true; - - const file: File = mockFile(); - const data: DataTransfer = { - dataTransfer: { - files: [file], - types: ['Files'], - }, - }; - - const createResponse: HTTPResponse<FileModel> = - await mockCreateResponse(file); - ( - ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> - ).mockResolvedValue(createResponse); - - const { container } = render(<FilePicker {...defaultProps} />); - - const dropzone: HTMLElement = screen.getByLabelText('Upload a file'); - fireEvent.drop(dropzone, data); - - await waitFor(() => { - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).not.toBeInTheDocument(); - }); - }); - - it('should throw an "File too large" when uploading a file that fails on arrayBuffer()', async () => { - const file: File = mockFile(); - file.arrayBuffer = jest - .fn() - .mockRejectedValue(new Error('File too large')); - const data: DataTransfer = { - dataTransfer: { - files: [file], - types: ['Files'], - }, - }; - - const { container } = render(<FilePicker {...defaultProps} />); - - const dropzone: HTMLElement = screen.getByLabelText('Upload a file'); - fireEvent.drop(dropzone, data); - - await waitFor(() => { - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).not.toBeInTheDocument(); - }); - }); - - it('should upload a file when dropped', async () => { - const file: File = mockFile(); - const data: DataTransfer = { - dataTransfer: { - files: [file], - types: ['Files'], - }, - }; - - const createResponse: HTTPResponse<FileModel> = - await mockCreateResponse(file); - ( - ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> - ).mockResolvedValue(createResponse); - - const { container } = render(<FilePicker {...defaultProps} />); - - const dropzone: HTMLElement = screen.getByLabelText('Upload a file'); - await act(async () => { - fireEvent.drop(dropzone, data); - }); - - await waitFor(() => { - expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]); - expect(mockOnBlur).toHaveBeenCalled(); - expect(mockOnBlur).toHaveBeenCalled(); - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).toBeInTheDocument(); - }); - }); - - it('should upload a file when dropped', async () => { - const file: File = mockFile(); - const data: DataTransfer = { - dataTransfer: { - files: [file], - types: ['Files'], - }, - }; - - const createResponse: HTTPResponse<FileModel> = - await mockCreateResponse(file); - ( - ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> - ).mockResolvedValue(createResponse); - - const { container } = render(<FilePicker {...defaultProps} />); - - const dropzone: HTMLElement = screen.getByLabelText('Upload a file'); - await act(async () => { - fireEvent.drop(dropzone, data); - }); - - await waitFor(() => { - expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]); - expect(mockOnBlur).toHaveBeenCalled(); - expect(mockOnBlur).toHaveBeenCalled(); - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).toBeInTheDocument(); - }); - }); - - it('should show loader a file when files are being uploaded', async () => { - const uploadPromise: Promise<unknown> = new Promise((resolve: any) => { - (global as any).mockUploadResolve = resolve; // Store resolve function globally or in a scope accessible outside the test - }); - - ( - ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> - ).mockImplementation((): any => { - return uploadPromise; - }); - - const file: File = mockFile(); - const data: DataTransfer = { - dataTransfer: { - files: [file], - types: ['Files'], - }, - }; - - const createResponse: HTTPResponse<FileModel> = - await mockCreateResponse(file); - - const { container } = render(<FilePicker {...defaultProps} />); - - const dropzone: HTMLElement = screen.getByLabelText('Upload a file'); - await act(async () => { - fireEvent.drop(dropzone, data); - }); - - expect(queryByTestId(container, 'loader')).toBeInTheDocument(); - - (global as any).mockUploadResolve(createResponse); - - await waitFor(() => { - expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]); - expect(mockOnBlur).toHaveBeenCalled(); - expect(mockOnBlur).toHaveBeenCalled(); - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).toBeInTheDocument(); - }); - }); - - it('should delete an uploaded file when clicking on it', async () => { - const file: File = mockFile(); - const data: DataTransfer = { - dataTransfer: { - files: [file], - types: ['Files'], - }, - }; - - const createResponse: HTTPResponse<FileModel> = - await mockCreateResponse(file); - ( - ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create> - ).mockResolvedValue(createResponse); - - const { container } = render(<FilePicker {...defaultProps} />); - - const dropzone: HTMLElement = screen.getByLabelText('Upload a file'); - await act(async () => { - fireEvent.drop(dropzone, data); - }); - - await waitFor(() => { - // file should be in the dropzone - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).toBeInTheDocument(); - }); - - const deleteIcon: ChildNode = screen - .getByRole('icon') - .childNodes.item(0); // svg item - // remove file by clicking on it - if (deleteIcon) { - await act(async () => { - fireEvent.click(deleteIcon.childNodes.item(0), data); - }); - } - - await waitFor(() => { - // file should have been removed - expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]); - expect( - queryByAttribute('src', container, MOCK_FILE_URL) - ).not.toBeInTheDocument(); - }); + const deleteIcon: ChildNode = screen.getByRole("icon").childNodes.item(0); // svg item + // remove file by clicking on it + if (deleteIcon) { + await act(async () => { + fireEvent.click(deleteIcon.childNodes.item(0), data); + }); + } + + await waitFor(() => { + // file should have been removed + expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]); + expect( + queryByAttribute("src", container, MOCK_FILE_URL), + ).not.toBeInTheDocument(); }); + }); }); diff --git a/CommonUI/src/Tests/Components/HiddenText.test.tsx b/CommonUI/src/Tests/Components/HiddenText.test.tsx index 4ecdeadad4..e8037b4e5a 100644 --- a/CommonUI/src/Tests/Components/HiddenText.test.tsx +++ b/CommonUI/src/Tests/Components/HiddenText.test.tsx @@ -1,64 +1,62 @@ -import HiddenText from '../../Components/HiddenText/HiddenText'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import React from 'react'; +import HiddenText from "../../Components/HiddenText/HiddenText"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import React from "react"; -describe('tests for HiddenText component', () => { - test('it should click hidden-text and reveal text in document', async () => { - render(<HiddenText text="Special" />); - const hiddenText: HTMLElement = screen.getByRole('hidden-text'); - fireEvent.click(hiddenText); +describe("tests for HiddenText component", () => { + test("it should click hidden-text and reveal text in document", async () => { + render(<HiddenText text="Special" />); + const hiddenText: HTMLElement = screen.getByRole("hidden-text"); + fireEvent.click(hiddenText); - await waitFor(() => { - expect(screen.getByRole('revealed-text')).toBeInTheDocument(); - }); - - expect(screen.queryByRole('hidden-text')).toBeFalsy(); - expect(screen.queryByRole('revealed-text')).toHaveTextContent( - 'Special' - ); + await waitFor(() => { + expect(screen.getByRole("revealed-text")).toBeInTheDocument(); }); - test('it should not show copy to clipboard if isCopyable is false', async () => { - render(<HiddenText text="text" isCopyable={false} />); - const hiddenText: HTMLElement = screen.getByRole('hidden-text'); - fireEvent.click(hiddenText); + expect(screen.queryByRole("hidden-text")).toBeFalsy(); + expect(screen.queryByRole("revealed-text")).toHaveTextContent("Special"); + }); - await waitFor(() => { - expect(screen.getByRole('revealed-text')).toBeInTheDocument(); - }); + test("it should not show copy to clipboard if isCopyable is false", async () => { + render(<HiddenText text="text" isCopyable={false} />); + const hiddenText: HTMLElement = screen.getByRole("hidden-text"); + fireEvent.click(hiddenText); - expect(screen.queryByRole('hidden-text')).toBeFalsy(); - expect(screen.queryByRole('copy-to-clipboard')).toBeFalsy(); + await waitFor(() => { + expect(screen.getByRole("revealed-text")).toBeInTheDocument(); }); - test('it should click hidden-text and reveal icon', async () => { - render(<HiddenText text="text" isCopyable={true} />); - const hiddenText: HTMLElement = screen.getByRole('hidden-text'); - fireEvent.click(hiddenText); - await waitFor(() => { - expect(screen.getByRole('revealed-text')).toBeInTheDocument(); - }); - expect(screen.getByTestId('hide-text-icon')).toBeTruthy(); + expect(screen.queryByRole("hidden-text")).toBeFalsy(); + expect(screen.queryByRole("copy-to-clipboard")).toBeFalsy(); + }); + + test("it should click hidden-text and reveal icon", async () => { + render(<HiddenText text="text" isCopyable={true} />); + const hiddenText: HTMLElement = screen.getByRole("hidden-text"); + fireEvent.click(hiddenText); + await waitFor(() => { + expect(screen.getByRole("revealed-text")).toBeInTheDocument(); + }); + expect(screen.getByTestId("hide-text-icon")).toBeTruthy(); + }); + + test("it should click hidden-text and copy to clipboard", async () => { + render(<HiddenText text="text" isCopyable={true} />); + const hiddenText: HTMLElement = screen.getByRole("hidden-text"); + fireEvent.click(hiddenText); + await waitFor(() => { + expect(screen.getByRole("revealed-text")).toBeInTheDocument(); }); - test('it should click hidden-text and copy to clipboard', async () => { - render(<HiddenText text="text" isCopyable={true} />); - const hiddenText: HTMLElement = screen.getByRole('hidden-text'); - fireEvent.click(hiddenText); - await waitFor(() => { - expect(screen.getByRole('revealed-text')).toBeInTheDocument(); - }); + expect(screen.getByTestId("copy-to-clipboard-icon")).toBeTruthy(); - expect(screen.getByTestId('copy-to-clipboard-icon')).toBeTruthy(); + const copy: HTMLElement = screen.getByTestId("copy-to-clipboard-icon"); + fireEvent.click(copy); - const copy: HTMLElement = screen.getByTestId('copy-to-clipboard-icon'); - fireEvent.click(copy); - - await waitFor(() => { - expect(screen.getByRole('copy-to-clipboard')).toHaveTextContent( - 'Copied to Clipboard' - ); - }); + await waitFor(() => { + expect(screen.getByRole("copy-to-clipboard")).toHaveTextContent( + "Copied to Clipboard", + ); }); + }); }); diff --git a/CommonUI/src/Tests/Components/Input.test.tsx b/CommonUI/src/Tests/Components/Input.test.tsx index 3aff20640d..53e414ec4b 100644 --- a/CommonUI/src/Tests/Components/Input.test.tsx +++ b/CommonUI/src/Tests/Components/Input.test.tsx @@ -1,283 +1,281 @@ -import Input, { ComponentProps, InputType } from '../../Components/Input/Input'; -import '@testing-library/jest-dom/extend-expect'; -import { cleanup, fireEvent, render, screen } from '@testing-library/react'; -import React from 'react'; - -describe('Input', () => { - afterEach(() => { - cleanup(); - }); - - test('renders input element with no props', () => { - const { getByRole } = render(<Input />); - const input: HTMLElement = getByRole('textbox'); - - expect(input).toBeInTheDocument(); - }); - - test('renders input element with all props set', () => { - const props: ComponentProps = { - initialValue: 'initial value', - onClick: () => {}, - placeholder: 'placeholder', - className: 'className', - onChange: () => {}, - value: 'value', - readOnly: true, - disabled: true, - type: InputType.TEXT, - onFocus: () => {}, - onBlur: () => {}, - dataTestId: 'testid', - tabIndex: 0, - onEnterPress: () => {}, - error: 'error', - outerDivClassName: 'outerDivClassName', - autoFocus: true, - }; +import Input, { ComponentProps, InputType } from "../../Components/Input/Input"; +import "@testing-library/jest-dom/extend-expect"; +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; + +describe("Input", () => { + afterEach(() => { + cleanup(); + }); + + test("renders input element with no props", () => { + const { getByRole } = render(<Input />); + const input: HTMLElement = getByRole("textbox"); + + expect(input).toBeInTheDocument(); + }); + + test("renders input element with all props set", () => { + const props: ComponentProps = { + initialValue: "initial value", + onClick: () => {}, + placeholder: "placeholder", + className: "className", + onChange: () => {}, + value: "value", + readOnly: true, + disabled: true, + type: InputType.TEXT, + onFocus: () => {}, + onBlur: () => {}, + dataTestId: "testid", + tabIndex: 0, + onEnterPress: () => {}, + error: "error", + outerDivClassName: "outerDivClassName", + autoFocus: true, + }; - const { getByRole } = render(<Input {...props} />); - const input: HTMLElement = getByRole('textbox'); + const { getByRole } = render(<Input {...props} />); + const input: HTMLElement = getByRole("textbox"); - expect(input).toBeInTheDocument(); - }); + expect(input).toBeInTheDocument(); + }); - test('calls onChange when input value changes', () => { - const onChange: jest.Mock = jest.fn(); - const newValue: string = 'new value'; + test("calls onChange when input value changes", () => { + const onChange: jest.Mock = jest.fn(); + const newValue: string = "new value"; - const { getByRole } = render(<Input {...{ onChange }} />); - const input: HTMLElement = getByRole('textbox'); - fireEvent.change(input, { target: { value: newValue } }); + const { getByRole } = render(<Input {...{ onChange }} />); + const input: HTMLElement = getByRole("textbox"); + fireEvent.change(input, { target: { value: newValue } }); - expect(onChange).toHaveBeenCalledWith(newValue); - }); + expect(onChange).toHaveBeenCalledWith(newValue); + }); - test('changes input value on input', () => { - const newValue: string = 'new value'; + test("changes input value on input", () => { + const newValue: string = "new value"; - const { getByRole } = render(<Input />); - const input: HTMLElement = getByRole('textbox'); - fireEvent.change(input, { target: { value: newValue } }); + const { getByRole } = render(<Input />); + const input: HTMLElement = getByRole("textbox"); + fireEvent.change(input, { target: { value: newValue } }); - expect(screen.getByDisplayValue(newValue)).toBeInTheDocument(); - }); + expect(screen.getByDisplayValue(newValue)).toBeInTheDocument(); + }); - test('calls onClick when input is clicked', () => { - const onClick: jest.Mock = jest.fn(); + test("calls onClick when input is clicked", () => { + const onClick: jest.Mock = jest.fn(); - const { getByRole } = render(<Input {...{ onClick }} />); - const input: HTMLElement = getByRole('textbox'); - fireEvent.click(input); + const { getByRole } = render(<Input {...{ onClick }} />); + const input: HTMLElement = getByRole("textbox"); + fireEvent.click(input); - expect(onClick).toHaveBeenCalled(); - }); + expect(onClick).toHaveBeenCalled(); + }); - test('calls onFocus when input is focused', () => { - const onFocus: jest.Mock = jest.fn(); + test("calls onFocus when input is focused", () => { + const onFocus: jest.Mock = jest.fn(); - const { getByRole } = render(<Input {...{ onFocus }} />); - const input: HTMLElement = getByRole('textbox'); - fireEvent.focus(input); + const { getByRole } = render(<Input {...{ onFocus }} />); + const input: HTMLElement = getByRole("textbox"); + fireEvent.focus(input); - expect(onFocus).toHaveBeenCalled(); - }); + expect(onFocus).toHaveBeenCalled(); + }); - test('calls onBlur when input loses focus', () => { - const onBlur: jest.Mock = jest.fn(); + test("calls onBlur when input loses focus", () => { + const onBlur: jest.Mock = jest.fn(); - const { getByRole } = render(<Input {...{ onBlur }} />); - const input: HTMLElement = getByRole('textbox'); - fireEvent.blur(input); + const { getByRole } = render(<Input {...{ onBlur }} />); + const input: HTMLElement = getByRole("textbox"); + fireEvent.blur(input); - expect(onBlur).toHaveBeenCalled(); - }); + expect(onBlur).toHaveBeenCalled(); + }); - test('calls onEnterPress when Enter key is pressed', () => { - const onEnterPress: jest.Mock = jest.fn(); + test("calls onEnterPress when Enter key is pressed", () => { + const onEnterPress: jest.Mock = jest.fn(); - const { getByRole } = render(<Input {...{ onEnterPress }} />); - const input: HTMLElement = getByRole('textbox'); - fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' }); + const { getByRole } = render(<Input {...{ onEnterPress }} />); + const input: HTMLElement = getByRole("textbox"); + fireEvent.keyDown(input, { key: "Enter", code: "Enter" }); - expect(onEnterPress).toHaveBeenCalled(); - }); + expect(onEnterPress).toHaveBeenCalled(); + }); - test('sets initialValue', () => { - const initialValue: string = 'initial value'; - render(<Input {...{ initialValue }} />); + test("sets initialValue", () => { + const initialValue: string = "initial value"; + render(<Input {...{ initialValue }} />); - expect(screen.getByDisplayValue(initialValue)).toBeInTheDocument(); - }); + expect(screen.getByDisplayValue(initialValue)).toBeInTheDocument(); + }); - test('sets value', () => { - const value: string = 'value'; - render(<Input {...{ value }} />); + test("sets value", () => { + const value: string = "value"; + render(<Input {...{ value }} />); - expect(screen.getByDisplayValue(value)).toBeInTheDocument(); - }); + expect(screen.getByDisplayValue(value)).toBeInTheDocument(); + }); - test('value overrides initialValue', () => { - const value: string = 'value'; - const initialValue: string = 'initial value'; + test("value overrides initialValue", () => { + const value: string = "value"; + const initialValue: string = "initial value"; - render(<Input {...{ value, initialValue }} />); + render(<Input {...{ value, initialValue }} />); - expect(screen.getByDisplayValue(value)).toBeInTheDocument(); - }); + expect(screen.getByDisplayValue(value)).toBeInTheDocument(); + }); - test('updates input when value changes', () => { - const value: string = 'value'; - const { rerender } = render(<Input {...{ value }} />); + test("updates input when value changes", () => { + const value: string = "value"; + const { rerender } = render(<Input {...{ value }} />); - const newValue: string = 'new value'; - rerender(<Input {...{ value: newValue }} />); + const newValue: string = "new value"; + rerender(<Input {...{ value: newValue }} />); - expect(screen.getByDisplayValue(newValue)).toBeInTheDocument(); - }); + expect(screen.getByDisplayValue(newValue)).toBeInTheDocument(); + }); - test('resets input to initialValue when value changes to empty string', () => { - const value: string = 'value'; - const initialValue: string = 'initial value'; + test("resets input to initialValue when value changes to empty string", () => { + const value: string = "value"; + const initialValue: string = "initial value"; - const { rerender } = render(<Input {...{ value, initialValue }} />); + const { rerender } = render(<Input {...{ value, initialValue }} />); - const newValue: string = ''; - rerender(<Input {...{ value: newValue, initialValue }} />); + const newValue: string = ""; + rerender(<Input {...{ value: newValue, initialValue }} />); - expect(screen.getByDisplayValue(initialValue)).toBeInTheDocument(); - }); + expect(screen.getByDisplayValue(initialValue)).toBeInTheDocument(); + }); - test('sets placeholder attribute', () => { - const placeholder: string = 'placeholder'; - render(<Input {...{ placeholder }} />); + test("sets placeholder attribute", () => { + const placeholder: string = "placeholder"; + render(<Input {...{ placeholder }} />); - expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument(); - }); + expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument(); + }); - test('sets className', () => { - const className: string = 'className'; - const { getByRole } = render(<Input {...{ className }} />); - const input: HTMLElement = getByRole('textbox'); + test("sets className", () => { + const className: string = "className"; + const { getByRole } = render(<Input {...{ className }} />); + const input: HTMLElement = getByRole("textbox"); - expect(Array.from(input.classList.values())).toEqual([className]); - }); + expect(Array.from(input.classList.values())).toEqual([className]); + }); - test('sets default className', () => { - const { getByRole } = render(<Input />); - const input: HTMLElement = getByRole('textbox'); + test("sets default className", () => { + const { getByRole } = render(<Input />); + const input: HTMLElement = getByRole("textbox"); - expect(input.classList.length).toBeGreaterThan(0); - }); + expect(input.classList.length).toBeGreaterThan(0); + }); - test('sets readonly attribute when readOnly is true', () => { - const { getByRole } = render(<Input readOnly={true} />); - const input: HTMLElement = getByRole('textbox'); + test("sets readonly attribute when readOnly is true", () => { + const { getByRole } = render(<Input readOnly={true} />); + const input: HTMLElement = getByRole("textbox"); - expect(input).toHaveAttribute('readonly'); - }); + expect(input).toHaveAttribute("readonly"); + }); - test('sets readonly attribute when disabled is true', () => { - const { getByRole } = render(<Input disabled={true} />); - const input: HTMLElement = getByRole('textbox'); + test("sets readonly attribute when disabled is true", () => { + const { getByRole } = render(<Input disabled={true} />); + const input: HTMLElement = getByRole("textbox"); - expect(input).toHaveAttribute('readonly'); - }); + expect(input).toHaveAttribute("readonly"); + }); - test('uses local date time string for datetime-local type', () => { - const dateTime: string = '2023-04-22'; - const dataTestId: string = 'testid'; + test("uses local date time string for datetime-local type", () => { + const dateTime: string = "2023-04-22"; + const dataTestId: string = "testid"; - render( - <Input - type={InputType.DATETIME_LOCAL} - value={dateTime} - dataTestId={dataTestId} - /> - ); + render( + <Input + type={InputType.DATETIME_LOCAL} + value={dateTime} + dataTestId={dataTestId} + />, + ); - expect(screen.getByTestId<HTMLInputElement>(dataTestId).value).toBe( - '2023-04-22T00:00' - ); - }); + expect(screen.getByTestId<HTMLInputElement>(dataTestId).value).toBe( + "2023-04-22T00:00", + ); + }); - test('uses YYYY-MM-DD for date type', () => { - const date: string = '2023-04-22T00:00:00'; - const dataTestId: string = 'testid'; + test("uses YYYY-MM-DD for date type", () => { + const date: string = "2023-04-22T00:00:00"; + const dataTestId: string = "testid"; - render( - <Input type={InputType.DATE} value={date} dataTestId={dataTestId} /> - ); + render( + <Input type={InputType.DATE} value={date} dataTestId={dataTestId} />, + ); - expect(screen.getByTestId<HTMLInputElement>(dataTestId).value).toBe( - '2023-04-22' - ); - }); + expect(screen.getByTestId<HTMLInputElement>(dataTestId).value).toBe( + "2023-04-22", + ); + }); - test('sets dataTestId', () => { - const dataTestId: string = 'testid'; - const { getByTestId } = render(<Input {...{ dataTestId }} />); + test("sets dataTestId", () => { + const dataTestId: string = "testid"; + const { getByTestId } = render(<Input {...{ dataTestId }} />); - expect(getByTestId(dataTestId)).toBeInTheDocument(); - }); + expect(getByTestId(dataTestId)).toBeInTheDocument(); + }); - test('sets tabIndex', () => { - const tabIndex: number = 0; - const { getByRole } = render(<Input {...{ tabIndex }} />); - const input: HTMLElement = getByRole('textbox'); + test("sets tabIndex", () => { + const tabIndex: number = 0; + const { getByRole } = render(<Input {...{ tabIndex }} />); + const input: HTMLElement = getByRole("textbox"); - expect(input).toHaveAttribute('tabindex', tabIndex.toString()); - }); + expect(input).toHaveAttribute("tabindex", tabIndex.toString()); + }); - test('displays error message', () => { - const error: string = 'error'; - const errorTestId: string = 'error-message'; + test("displays error message", () => { + const error: string = "error"; + const errorTestId: string = "error-message"; - render(<Input {...{ error }} />); + render(<Input {...{ error }} />); - expect(screen.getByTestId(errorTestId)).toBeInTheDocument(); - expect(screen.getByText(error)).toBeInTheDocument(); - }); + expect(screen.getByTestId(errorTestId)).toBeInTheDocument(); + expect(screen.getByText(error)).toBeInTheDocument(); + }); - test('displays error icon', () => { - const error: string = 'error'; + test("displays error icon", () => { + const error: string = "error"; - render(<Input {...{ error }} />); + render(<Input {...{ error }} />); - expect(screen.getByRole('icon')).toBeInTheDocument(); - }); + expect(screen.getByRole("icon")).toBeInTheDocument(); + }); - test('sets error style if error exists', () => { - const error: string = 'error'; + test("sets error style if error exists", () => { + const error: string = "error"; - const { getByRole } = render(<Input {...{ error }} />); - const input: HTMLElement = getByRole('textbox'); + const { getByRole } = render(<Input {...{ error }} />); + const input: HTMLElement = getByRole("textbox"); - const errorInputClass: string = 'border-red-300'; - expect(input).toHaveClass(errorInputClass); - }); + const errorInputClass: string = "border-red-300"; + expect(input).toHaveClass(errorInputClass); + }); - test('sets outerDivClassName', () => { - const outerDivClassName: string = 'outerDivClassName'; + test("sets outerDivClassName", () => { + const outerDivClassName: string = "outerDivClassName"; - const { getByRole } = render(<Input {...{ outerDivClassName }} />); + const { getByRole } = render(<Input {...{ outerDivClassName }} />); - expect(getByRole('textbox').closest('div')).toHaveClass( - outerDivClassName - ); - }); + expect(getByRole("textbox").closest("div")).toHaveClass(outerDivClassName); + }); - test('sets default outerDivClassName', () => { - const { getByRole } = render(<Input />); + test("sets default outerDivClassName", () => { + const { getByRole } = render(<Input />); - expect( - getByRole('textbox').closest('div')?.classList.length - ).toBeGreaterThan(0); - }); + expect( + getByRole("textbox").closest("div")?.classList.length, + ).toBeGreaterThan(0); + }); - test('sets autofocus', () => { - const { getByRole } = render(<Input autoFocus={true} />); + test("sets autofocus", () => { + const { getByRole } = render(<Input autoFocus={true} />); - expect(getByRole('textbox')).toHaveFocus(); - }); + expect(getByRole("textbox")).toHaveFocus(); + }); }); diff --git a/CommonUI/src/Tests/Components/Item.test.tsx b/CommonUI/src/Tests/Components/Item.test.tsx index 242644447f..e3b835d06a 100644 --- a/CommonUI/src/Tests/Components/Item.test.tsx +++ b/CommonUI/src/Tests/Components/Item.test.tsx @@ -1,69 +1,69 @@ -import { ButtonStyleType } from '../../Components/Button/Button'; -import Item, { ComponentProps } from '../../Components/OrderedStatesList/Item'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React from 'react'; +import { ButtonStyleType } from "../../Components/Button/Button"; +import Item, { ComponentProps } from "../../Components/OrderedStatesList/Item"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom"; +import { render } from "@testing-library/react"; +import IconProp from "Common/Types/Icon/IconProp"; +import React from "react"; -describe('Item component', () => { - interface ItemData { - id: number; - name: string; - description: string; - } +describe("Item component", () => { + interface ItemData { + id: number; + name: string; + description: string; + } - const defaultProps: ComponentProps<ItemData> = { - item: { id: 1, name: 'Test Item', description: 'Test description' }, - actionButtons: [ - { - title: 'Edit', - icon: IconProp.Edit, - buttonStyleType: ButtonStyleType.PRIMARY, - onClick: jest.fn(), - }, - { - title: 'Add', - icon: IconProp.Add, - buttonStyleType: ButtonStyleType.NORMAL, - onClick: jest.fn(), - }, - ], - titleField: 'name', - descriptionField: 'description', + const defaultProps: ComponentProps<ItemData> = { + item: { id: 1, name: "Test Item", description: "Test description" }, + actionButtons: [ + { + title: "Edit", + icon: IconProp.Edit, + buttonStyleType: ButtonStyleType.PRIMARY, + onClick: jest.fn(), + }, + { + title: "Add", + icon: IconProp.Add, + buttonStyleType: ButtonStyleType.NORMAL, + onClick: jest.fn(), + }, + ], + titleField: "name", + descriptionField: "description", + }; + + it("renders item information correctly", () => { + const { getByText } = render(<Item {...defaultProps} />); + expect(getByText("Test Item")).toBeInTheDocument(); + expect(getByText("Test description")).toBeInTheDocument(); + }); + it("calls action button onClick function when clicked", () => { + const { getByText } = render(<Item {...defaultProps} />); + const editButton: HTMLElement = getByText("Edit"); + editButton.click(); + expect(editButton).toBeInTheDocument(); + expect(defaultProps.actionButtons?.[0]?.onClick).toHaveBeenCalled(); + }); + it("should render titlefield as string if getTitleElement is not provided", () => { + const { getByText } = render(<Item {...defaultProps} />); + const titleElement: HTMLElement = getByText("Test Item"); + expect(titleElement).toBeInTheDocument(); + }); + it("should render custom title element provided by getTitleElement function", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + getTitleElement: jest.fn(), }; - - it('renders item information correctly', () => { - const { getByText } = render(<Item {...defaultProps} />); - expect(getByText('Test Item')).toBeInTheDocument(); - expect(getByText('Test description')).toBeInTheDocument(); - }); - it('calls action button onClick function when clicked', () => { - const { getByText } = render(<Item {...defaultProps} />); - const editButton: HTMLElement = getByText('Edit'); - editButton.click(); - expect(editButton).toBeInTheDocument(); - expect(defaultProps.actionButtons?.[0]?.onClick).toHaveBeenCalled(); - }); - it('should render titlefield as string if getTitleElement is not provided', () => { - const { getByText } = render(<Item {...defaultProps} />); - const titleElement: HTMLElement = getByText('Test Item'); - expect(titleElement).toBeInTheDocument(); - }); - it('should render custom title element provided by getTitleElement function', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - getTitleElement: jest.fn(), - }; - render(<Item {...props} />); - expect(props.getTitleElement).toHaveBeenCalledWith(props.item); - }); - it('should render custom title element provided by getDescriptionElement function', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - getDescriptionElement: jest.fn(), - }; - render(<Item {...props} />); - expect(props.getDescriptionElement).toHaveBeenCalled(); - }); + render(<Item {...props} />); + expect(props.getTitleElement).toHaveBeenCalledWith(props.item); + }); + it("should render custom title element provided by getDescriptionElement function", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + getDescriptionElement: jest.fn(), + }; + render(<Item {...props} />); + expect(props.getDescriptionElement).toHaveBeenCalled(); + }); }); diff --git a/CommonUI/src/Tests/Components/List.test.tsx b/CommonUI/src/Tests/Components/List.test.tsx index 13211680a0..9725ff1c9b 100644 --- a/CommonUI/src/Tests/Components/List.test.tsx +++ b/CommonUI/src/Tests/Components/List.test.tsx @@ -1,101 +1,99 @@ -import List, { ComponentProps } from '../../Components/List/List'; -import FieldType from '../../Components/Types/FieldType'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom'; -import { fireEvent, render, screen } from '@testing-library/react'; -import React from 'react'; +import List, { ComponentProps } from "../../Components/List/List"; +import FieldType from "../../Components/Types/FieldType"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom"; +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; -describe('List', () => { - interface ListData { - id: string; - name: string; - description: string; - } +describe("List", () => { + interface ListData { + id: string; + name: string; + description: string; + } - const defaultProps: ComponentProps<ListData> = { - data: [ - { - id: '1', - name: 'Item 1', - description: 'Description 1', - }, - { - id: '2', - name: 'Item 2', - description: 'Description 2', - }, - ], - id: 'test-list', - fields: [ - { - title: 'ID', - key: 'id', - fieldType: FieldType.Text, - colSpan: 1, - }, - { - title: 'Name', - key: 'name', - fieldType: FieldType.Text, - colSpan: 2, - }, - { - title: 'Description', - key: 'description', - fieldType: FieldType.Text, - colSpan: 2, - }, - ], - onNavigateToPage: jest.fn(), - currentPageNumber: 1, - totalItemsCount: 10, - itemsOnPage: 5, - error: '', - isLoading: false, - singularLabel: 'Item', - pluralLabel: 'Items', - }; + const defaultProps: ComponentProps<ListData> = { + data: [ + { + id: "1", + name: "Item 1", + description: "Description 1", + }, + { + id: "2", + name: "Item 2", + description: "Description 2", + }, + ], + id: "test-list", + fields: [ + { + title: "ID", + key: "id", + fieldType: FieldType.Text, + colSpan: 1, + }, + { + title: "Name", + key: "name", + fieldType: FieldType.Text, + colSpan: 2, + }, + { + title: "Description", + key: "description", + fieldType: FieldType.Text, + colSpan: 2, + }, + ], + onNavigateToPage: jest.fn(), + currentPageNumber: 1, + totalItemsCount: 10, + itemsOnPage: 5, + error: "", + isLoading: false, + singularLabel: "Item", + pluralLabel: "Items", + }; - it('renders List component with data', () => { - render(<List {...defaultProps} />); + it("renders List component with data", () => { + render(<List {...defaultProps} />); - expect(screen.getByTestId('list-container')).toBeInTheDocument(); - expect(screen.getByTestId('list-pagination')).toBeInTheDocument(); - }); + expect(screen.getByTestId("list-container")).toBeInTheDocument(); + expect(screen.getByTestId("list-pagination")).toBeInTheDocument(); + }); - it('renders loading state', () => { - render(<List {...defaultProps} isLoading={true} />); + it("renders loading state", () => { + render(<List {...defaultProps} isLoading={true} />); - expect(screen.getByTestId('component-loader')).toBeInTheDocument(); - }); + expect(screen.getByTestId("component-loader")).toBeInTheDocument(); + }); - it('renders error state', () => { - const messageError: string = 'Test error'; - render(<List {...defaultProps} error={messageError} />); + it("renders error state", () => { + const messageError: string = "Test error"; + render(<List {...defaultProps} error={messageError} />); - expect(screen.getByText(messageError)).toBeInTheDocument(); - }); + expect(screen.getByText(messageError)).toBeInTheDocument(); + }); - it('renders error state when data is empty', () => { - const messageError: string = 'There are no items'; - render( - <List {...defaultProps} data={[]} noItemsMessage={messageError} /> - ); + it("renders error state when data is empty", () => { + const messageError: string = "There are no items"; + render(<List {...defaultProps} data={[]} noItemsMessage={messageError} />); - expect(screen.getByText(messageError)).toBeInTheDocument(); - }); + expect(screen.getByText(messageError)).toBeInTheDocument(); + }); - it('renders error state default message when data is empty ', () => { - const messageError: string = 'No item'; - render(<List {...defaultProps} data={[]} />); + it("renders error state default message when data is empty ", () => { + const messageError: string = "No item"; + render(<List {...defaultProps} data={[]} />); - expect(screen.getByText(messageError)).toBeInTheDocument(); - }); + expect(screen.getByText(messageError)).toBeInTheDocument(); + }); - it('handles onNavigateToPage callback', () => { - render(<List {...defaultProps} />); - fireEvent.click(screen.getByText('Next')); + it("handles onNavigateToPage callback", () => { + render(<List {...defaultProps} />); + fireEvent.click(screen.getByText("Next")); - expect(defaultProps.onNavigateToPage).toHaveBeenCalledWith(2, 5); - }); + expect(defaultProps.onNavigateToPage).toHaveBeenCalledWith(2, 5); + }); }); diff --git a/CommonUI/src/Tests/Components/Loader.test.tsx b/CommonUI/src/Tests/Components/Loader.test.tsx index 509f4e3cc8..646b3ec307 100644 --- a/CommonUI/src/Tests/Components/Loader.test.tsx +++ b/CommonUI/src/Tests/Components/Loader.test.tsx @@ -1,30 +1,30 @@ -import Loader, { LoaderType } from '../../Components/Loader/Loader'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import Color from 'Common/Types/Color'; -import React from 'react'; +import Loader, { LoaderType } from "../../Components/Loader/Loader"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import Color from "Common/Types/Color"; +import React from "react"; -describe('Loader tests', () => { - test('it should render if bar loader show up', () => { - render( - <Loader - size={50} - color={new Color('#000000')} - loaderType={LoaderType.Bar} - /> - ); - const barLoader: HTMLElement = screen.getByRole('bar-loader'); - expect(barLoader).toBeInTheDocument(); - }); - test('it should render if beats loader show up', () => { - render( - <Loader - size={50} - color={new Color('#000000')} - loaderType={LoaderType.Beats} - /> - ); - const beatLoader: HTMLElement = screen.getByRole('beat-loader'); - expect(beatLoader).toBeInTheDocument(); - }); +describe("Loader tests", () => { + test("it should render if bar loader show up", () => { + render( + <Loader + size={50} + color={new Color("#000000")} + loaderType={LoaderType.Bar} + />, + ); + const barLoader: HTMLElement = screen.getByRole("bar-loader"); + expect(barLoader).toBeInTheDocument(); + }); + test("it should render if beats loader show up", () => { + render( + <Loader + size={50} + color={new Color("#000000")} + loaderType={LoaderType.Beats} + />, + ); + const beatLoader: HTMLElement = screen.getByRole("beat-loader"); + expect(beatLoader).toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/MasterPage.test.tsx b/CommonUI/src/Tests/Components/MasterPage.test.tsx index bee4e99f22..1afc7e6503 100644 --- a/CommonUI/src/Tests/Components/MasterPage.test.tsx +++ b/CommonUI/src/Tests/Components/MasterPage.test.tsx @@ -1,63 +1,62 @@ import MasterPage, { - ComponentProps, -} from '../../Components/MasterPage/MasterPage'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; + ComponentProps, +} from "../../Components/MasterPage/MasterPage"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import React from "react"; -describe('MasterPage', () => { - const defaultProps: ComponentProps = { - children: <div>Children</div>, - isLoading: false, - error: '', - }; +describe("MasterPage", () => { + const defaultProps: ComponentProps = { + children: <div>Children</div>, + isLoading: false, + error: "", + }; - it('should render correctly', () => { - render(<MasterPage {...defaultProps} />); + it("should render correctly", () => { + render(<MasterPage {...defaultProps} />); - const children: HTMLElement = screen.getByText('Children'); - expect(children).toBeInTheDocument(); - }); + const children: HTMLElement = screen.getByText("Children"); + expect(children).toBeInTheDocument(); + }); - it('should render correctly with isLoading', () => { - render(<MasterPage {...defaultProps} isLoading />); + it("should render correctly with isLoading", () => { + render(<MasterPage {...defaultProps} isLoading />); - const loader: HTMLElement = screen.getByRole('bar-loader'); - expect(loader).toBeInTheDocument(); - }); + const loader: HTMLElement = screen.getByRole("bar-loader"); + expect(loader).toBeInTheDocument(); + }); - it('should render correctly with error', () => { - const error: string = 'error'; - render(<MasterPage {...defaultProps} error={error} />); + it("should render correctly with error", () => { + const error: string = "error"; + render(<MasterPage {...defaultProps} error={error} />); - const errorElement: HTMLElement = screen.getByText(error); - expect(errorElement).toBeInTheDocument(); - }); + const errorElement: HTMLElement = screen.getByText(error); + expect(errorElement).toBeInTheDocument(); + }); - it('should render correctly with server error', () => { - const error: string = 'Server Error'; - render(<MasterPage {...defaultProps} error={error} />); + it("should render correctly with server error", () => { + const error: string = "Server Error"; + render(<MasterPage {...defaultProps} error={error} />); - const errorElement: HTMLElement = screen.getByText( - 'Network Error: Please reload the page and try again.' - ); - expect(errorElement).toBeInTheDocument(); - }); + const errorElement: HTMLElement = screen.getByText( + "Network Error: Please reload the page and try again.", + ); + expect(errorElement).toBeInTheDocument(); + }); - it('should render correctly with footer', () => { - const footer: string = 'footer'; - render(<MasterPage {...defaultProps} footer={<div>footer</div>} />); + it("should render correctly with footer", () => { + const footer: string = "footer"; + render(<MasterPage {...defaultProps} footer={<div>footer</div>} />); - const footerElement: HTMLElement = screen.getByText(footer); - expect(footerElement).toBeInTheDocument(); - }); + const footerElement: HTMLElement = screen.getByText(footer); + expect(footerElement).toBeInTheDocument(); + }); - it('should render correctly with makeTopSectionUnstick', () => { - render(<MasterPage {...defaultProps} makeTopSectionUnstick />); + it("should render correctly with makeTopSectionUnstick", () => { + render(<MasterPage {...defaultProps} makeTopSectionUnstick />); - const topSection: HTMLElement = - screen.getByRole('banner').parentElement!; - expect(topSection).not.toHaveClass('sticky'); - }); + const topSection: HTMLElement = screen.getByRole("banner").parentElement!; + expect(topSection).not.toHaveClass("sticky"); + }); }); diff --git a/CommonUI/src/Tests/Components/Modal.test.tsx b/CommonUI/src/Tests/Components/Modal.test.tsx index cda1a0232a..d61b89b17b 100644 --- a/CommonUI/src/Tests/Components/Modal.test.tsx +++ b/CommonUI/src/Tests/Components/Modal.test.tsx @@ -1,295 +1,275 @@ -import { ButtonStyleType } from '../../Components/Button/Button'; -import ButtonType from '../../Components/Button/ButtonTypes'; -import Modal, { ModalWidth } from '../../Components/Modal/Modal'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render } from '@testing-library/react'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React from 'react'; +import { ButtonStyleType } from "../../Components/Button/Button"; +import ButtonType from "../../Components/Button/ButtonTypes"; +import Modal, { ModalWidth } from "../../Components/Modal/Modal"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render } from "@testing-library/react"; +import IconProp from "Common/Types/Icon/IconProp"; +import React from "react"; -describe('Modal', () => { - test('renders the modal with the title and description', () => { - const onSubmit: jest.Mock = jest.fn(); - const { getByTestId, getByText } = render( - <Modal - title="Test Modal Title" - description="Test modal description" - onSubmit={onSubmit} - > - <div>Modal content</div> - </Modal> - ); +describe("Modal", () => { + test("renders the modal with the title and description", () => { + const onSubmit: jest.Mock = jest.fn(); + const { getByTestId, getByText } = render( + <Modal + title="Test Modal Title" + description="Test modal description" + onSubmit={onSubmit} + > + <div>Modal content</div> + </Modal>, + ); - expect(getByTestId('modal-title')).toHaveTextContent( - 'Test Modal Title' - ); - expect(getByText('Test modal description')).toBeInTheDocument(); - expect(getByText('Modal content')).toBeInTheDocument(); - }); + expect(getByTestId("modal-title")).toHaveTextContent("Test Modal Title"); + expect(getByText("Test modal description")).toBeInTheDocument(); + expect(getByText("Modal content")).toBeInTheDocument(); + }); - it('closes the modal when the close button is clicked', () => { - const onCloseMock: jest.Mock = jest.fn(); - const onSubmit: jest.Mock = jest.fn(); + it("closes the modal when the close button is clicked", () => { + const onCloseMock: jest.Mock = jest.fn(); + const onSubmit: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal title="Test Modal" onSubmit={onSubmit} onClose={onCloseMock}> - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal title="Test Modal" onSubmit={onSubmit} onClose={onCloseMock}> + <div>Modal content</div> + </Modal>, + ); - const closeButton: HTMLElement = getByTestId('close-button'); + const closeButton: HTMLElement = getByTestId("close-button"); - fireEvent.click(closeButton); + fireEvent.click(closeButton); - expect(onCloseMock).toHaveBeenCalled(); - }); + expect(onCloseMock).toHaveBeenCalled(); + }); - it('calls the onSubmit function when the submit button is clicked', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("calls the onSubmit function when the submit button is clicked", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal - title="Test Modal" - onSubmit={onSubmitMock} - submitButtonText="Submit" - > - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal + title="Test Modal" + onSubmit={onSubmitMock} + submitButtonText="Submit" + > + <div>Modal content</div> + </Modal>, + ); - const submitButton: HTMLElement = getByTestId( - 'modal-footer-submit-button' - ); + const submitButton: HTMLElement = getByTestId("modal-footer-submit-button"); - fireEvent.click(submitButton); + fireEvent.click(submitButton); - expect(onSubmitMock).toHaveBeenCalled(); - }); + expect(onSubmitMock).toHaveBeenCalled(); + }); - it('displays the modal with the default width when modalWidth is not set', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("displays the modal with the default width when modalWidth is not set", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal title="Test Modal" onSubmit={onSubmitMock}> - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal title="Test Modal" onSubmit={onSubmitMock}> + <div>Modal content</div> + </Modal>, + ); - expect(getByTestId('modal')).toHaveClass('sm:max-w-lg'); - }); + expect(getByTestId("modal")).toHaveClass("sm:max-w-lg"); + }); - it('displays the modal with the correct width when modalWidth is set', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("displays the modal with the correct width when modalWidth is set", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal - title="Test Modal" - onSubmit={onSubmitMock} - modalWidth={ModalWidth.Medium} - > - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal + title="Test Modal" + onSubmit={onSubmitMock} + modalWidth={ModalWidth.Medium} + > + <div>Modal content</div> + </Modal>, + ); - expect(getByTestId('modal')).toHaveClass('sm:max-w-3xl'); - }); + expect(getByTestId("modal")).toHaveClass("sm:max-w-3xl"); + }); - it('displays the children passed to the modal', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("displays the children passed to the modal", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByText } = render( - <Modal title="Test Modal" onSubmit={onSubmitMock}> - <div>Modal content</div> - </Modal> - ); + const { getByText } = render( + <Modal title="Test Modal" onSubmit={onSubmitMock}> + <div>Modal content</div> + </Modal>, + ); - expect(getByText('Modal content')).toBeInTheDocument(); - }); + expect(getByText("Modal content")).toBeInTheDocument(); + }); - it('displays the loader when isBodyLoading is true', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("displays the loader when isBodyLoading is true", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal title="Test Modal" onSubmit={onSubmitMock} isBodyLoading> - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal title="Test Modal" onSubmit={onSubmitMock} isBodyLoading> + <div>Modal content</div> + </Modal>, + ); - expect(getByTestId('loader')).toBeInTheDocument(); - }); + expect(getByTestId("loader")).toBeInTheDocument(); + }); - it('does not display the loader when isBodyLoading is false', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("does not display the loader when isBodyLoading is false", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { queryByTestId } = render( - <Modal title="Test Modal" onSubmit={onSubmitMock}> - <div>Modal content</div> - </Modal> - ); + const { queryByTestId } = render( + <Modal title="Test Modal" onSubmit={onSubmitMock}> + <div>Modal content</div> + </Modal>, + ); - expect(queryByTestId('loader')).not.toBeInTheDocument(); - }); + expect(queryByTestId("loader")).not.toBeInTheDocument(); + }); - it('does not display the loader when isBodyLoading is undefined', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("does not display the loader when isBodyLoading is undefined", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { queryByTestId } = render( - <Modal title="Test Modal" onSubmit={onSubmitMock}> - <div>Modal content</div> - </Modal> - ); + const { queryByTestId } = render( + <Modal title="Test Modal" onSubmit={onSubmitMock}> + <div>Modal content</div> + </Modal>, + ); - expect(queryByTestId('loader')).not.toBeInTheDocument(); - }); + expect(queryByTestId("loader")).not.toBeInTheDocument(); + }); - it('disables the submit button when isLoading is true', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("disables the submit button when isLoading is true", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal - title="Test Modal" - onSubmit={onSubmitMock} - submitButtonText="Submit" - isLoading={true} - > - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal + title="Test Modal" + onSubmit={onSubmitMock} + submitButtonText="Submit" + isLoading={true} + > + <div>Modal content</div> + </Modal>, + ); - const submitButton: HTMLElement = getByTestId( - 'modal-footer-submit-button' - ); + const submitButton: HTMLElement = getByTestId("modal-footer-submit-button"); - expect(submitButton).toBeDisabled(); - }); + expect(submitButton).toBeDisabled(); + }); - it('disables the submit button when disableSubmitButton is true', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("disables the submit button when disableSubmitButton is true", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal - title="Test Modal" - onSubmit={onSubmitMock} - submitButtonText="Submit" - disableSubmitButton={true} - > - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal + title="Test Modal" + onSubmit={onSubmitMock} + submitButtonText="Submit" + disableSubmitButton={true} + > + <div>Modal content</div> + </Modal>, + ); - const submitButton: HTMLElement = getByTestId( - 'modal-footer-submit-button' - ); + const submitButton: HTMLElement = getByTestId("modal-footer-submit-button"); - expect(submitButton).toBeDisabled(); - }); + expect(submitButton).toBeDisabled(); + }); - it('disables the submit button when isBodyLoading is true', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("disables the submit button when isBodyLoading is true", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal - title="Test Modal" - onSubmit={onSubmitMock} - submitButtonText="Submit" - isBodyLoading={true} - > - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal + title="Test Modal" + onSubmit={onSubmitMock} + submitButtonText="Submit" + isBodyLoading={true} + > + <div>Modal content</div> + </Modal>, + ); - const submitButton: HTMLElement = getByTestId( - 'modal-footer-submit-button' - ); + const submitButton: HTMLElement = getByTestId("modal-footer-submit-button"); - expect(submitButton).toBeDisabled(); - }); + expect(submitButton).toBeDisabled(); + }); - it('displays the icon when icon is set', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("displays the icon when icon is set", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal - title="Test Modal" - onSubmit={onSubmitMock} - icon={IconProp.SMS} - > - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal title="Test Modal" onSubmit={onSubmitMock} icon={IconProp.SMS}> + <div>Modal content</div> + </Modal>, + ); - expect(getByTestId('icon')).toBeInTheDocument(); - }); + expect(getByTestId("icon")).toBeInTheDocument(); + }); - it('displays the submit button with the default style when submitButtonStyleType is not set', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("displays the submit button with the default style when submitButtonStyleType is not set", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal title="Test Modal" onSubmit={onSubmitMock}> - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal title="Test Modal" onSubmit={onSubmitMock}> + <div>Modal content</div> + </Modal>, + ); - const submitButton: HTMLElement = getByTestId( - 'modal-footer-submit-button' - ); + const submitButton: HTMLElement = getByTestId("modal-footer-submit-button"); - expect(submitButton).toHaveAttribute('type', 'button'); - }); + expect(submitButton).toHaveAttribute("type", "button"); + }); - it('displays the submit button with the correct style when submitButtonStyleType is set', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("displays the submit button with the correct style when submitButtonStyleType is set", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal - title="Test Modal" - onSubmit={onSubmitMock} - submitButtonType={ButtonType.Reset} - > - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal + title="Test Modal" + onSubmit={onSubmitMock} + submitButtonType={ButtonType.Reset} + > + <div>Modal content</div> + </Modal>, + ); - const submitButton: HTMLElement = getByTestId( - 'modal-footer-submit-button' - ); + const submitButton: HTMLElement = getByTestId("modal-footer-submit-button"); - expect(submitButton).toHaveAttribute('type', 'reset'); - }); + expect(submitButton).toHaveAttribute("type", "reset"); + }); - it('displays the submit button with the default style when submitButtonStyleType is set', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("displays the submit button with the default style when submitButtonStyleType is set", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal - title="Test Modal" - onSubmit={onSubmitMock} - submitButtonStyleType={ButtonStyleType.DANGER} - > - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal + title="Test Modal" + onSubmit={onSubmitMock} + submitButtonStyleType={ButtonStyleType.DANGER} + > + <div>Modal content</div> + </Modal>, + ); - const submitButton: HTMLElement = getByTestId( - 'modal-footer-submit-button' - ); + const submitButton: HTMLElement = getByTestId("modal-footer-submit-button"); - expect(submitButton).toHaveClass('bg-red-600'); - }); + expect(submitButton).toHaveClass("bg-red-600"); + }); - it('displays the right element when rightElement is set', () => { - const onSubmitMock: jest.Mock = jest.fn(); + it("displays the right element when rightElement is set", () => { + const onSubmitMock: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Modal - title="Test Modal" - onSubmit={onSubmitMock} - rightElement={<div>Right element</div>} - > - <div>Modal content</div> - </Modal> - ); + const { getByTestId } = render( + <Modal + title="Test Modal" + onSubmit={onSubmitMock} + rightElement={<div>Right element</div>} + > + <div>Modal content</div> + </Modal>, + ); - expect(getByTestId('right-element')).toBeInTheDocument(); - }); + expect(getByTestId("right-element")).toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/NavBar.test.tsx b/CommonUI/src/Tests/Components/NavBar.test.tsx index 251d9d97bf..18ef858bcf 100644 --- a/CommonUI/src/Tests/Components/NavBar.test.tsx +++ b/CommonUI/src/Tests/Components/NavBar.test.tsx @@ -1,44 +1,44 @@ -import Navbar, { ComponentProps } from '../../Components/Navbar/NavBar'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; -import { ReactElement } from 'react-markdown/lib/react-markdown'; +import Navbar, { ComponentProps } from "../../Components/Navbar/NavBar"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import React from "react"; +import { ReactElement } from "react-markdown/lib/react-markdown"; -describe('Navbar', () => { - const defaultProps: ComponentProps = { - children: <div>Test</div>, +describe("Navbar", () => { + const defaultProps: ComponentProps = { + children: <div>Test</div>, + }; + + it("renders without crashing", () => { + render(<Navbar {...defaultProps} />); + expect(screen.getByText("Test")).toBeInTheDocument(); + }); + + it("renders with a custom className", () => { + const customProps: ComponentProps = { + ...defaultProps, + className: "custom-class", }; + render(<Navbar {...customProps} />); + const container: HTMLElement = screen.getByTestId("nav-children"); + expect(container).toHaveClass("custom-class"); + }); - it('renders without crashing', () => { - render(<Navbar {...defaultProps} />); - expect(screen.getByText('Test')).toBeInTheDocument(); - }); + it("renders with a rightElement", () => { + const rightElement: ReactElement = <div>Right Element</div>; + const customProps: ComponentProps = { ...defaultProps, rightElement }; + render(<Navbar {...customProps} />); + expect(screen.getByText("Right Element")).toBeInTheDocument(); + }); - it('renders with a custom className', () => { - const customProps: ComponentProps = { - ...defaultProps, - className: 'custom-class', - }; - render(<Navbar {...customProps} />); - const container: HTMLElement = screen.getByTestId('nav-children'); - expect(container).toHaveClass('custom-class'); - }); - - it('renders with a rightElement', () => { - const rightElement: ReactElement = <div>Right Element</div>; - const customProps: ComponentProps = { ...defaultProps, rightElement }; - render(<Navbar {...customProps} />); - expect(screen.getByText('Right Element')).toBeInTheDocument(); - }); - - it('renders with multiple children', () => { - const customProps: ComponentProps = { - ...defaultProps, - children: [<div key={1}>Child 1</div>, <div key={2}>Child 2</div>], - }; - render(<Navbar {...customProps} />); - expect(screen.getByText('Child 1')).toBeInTheDocument(); - expect(screen.getByText('Child 2')).toBeInTheDocument(); - }); + it("renders with multiple children", () => { + const customProps: ComponentProps = { + ...defaultProps, + children: [<div key={1}>Child 1</div>, <div key={2}>Child 2</div>], + }; + render(<Navbar {...customProps} />); + expect(screen.getByText("Child 1")).toBeInTheDocument(); + expect(screen.getByText("Child 2")).toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/OrderedStatesList.test.tsx b/CommonUI/src/Tests/Components/OrderedStatesList.test.tsx index 34dd6e8fcd..f0308815df 100644 --- a/CommonUI/src/Tests/Components/OrderedStatesList.test.tsx +++ b/CommonUI/src/Tests/Components/OrderedStatesList.test.tsx @@ -1,131 +1,131 @@ import OrderedStatesList, { - ComponentProps, -} from '../../Components/OrderedStatesList/OrderedStatesList'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; + ComponentProps, +} from "../../Components/OrderedStatesList/OrderedStatesList"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import React from "react"; -describe('OrderedSateList', () => { - interface ItemData { - title: string; - description: string; - order: number; - } +describe("OrderedSateList", () => { + interface ItemData { + title: string; + description: string; + order: number; + } - const defaultProps: ComponentProps<ItemData> = { - data: [{ title: 'item 1', description: 'description 1', order: 1 }], - singularLabel: 'Item', - titleField: 'title', - id: 'new-item-button', - orderField: 'order', - noItemsMessage: 'No Item', - shouldAddItemInTheEnd: true, - shouldAddItemInTheBeginning: true, + const defaultProps: ComponentProps<ItemData> = { + data: [{ title: "item 1", description: "description 1", order: 1 }], + singularLabel: "Item", + titleField: "title", + id: "new-item-button", + orderField: "order", + noItemsMessage: "No Item", + shouldAddItemInTheEnd: true, + shouldAddItemInTheBeginning: true, + }; + + it("should render all components", () => { + render(<OrderedStatesList {...defaultProps} />); + expect(screen.getByText("item 1")).toBeInTheDocument; + }); + it("renders Item components for each item in data prop", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + data: [ + { title: "item 1", description: "description 1", order: 1 }, + { title: "item 2", description: "description 2", order: 2 }, + { title: "item 3", description: "description 3", order: 3 }, + ], }; - - it('should render all components', () => { - render(<OrderedStatesList {...defaultProps} />); - expect(screen.getByText('item 1')).toBeInTheDocument; - }); - it('renders Item components for each item in data prop', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - data: [ - { title: 'item 1', description: 'description 1', order: 1 }, - { title: 'item 2', description: 'description 2', order: 2 }, - { title: 'item 3', description: 'description 3', order: 3 }, - ], - }; - render(<OrderedStatesList {...props} />); - expect(props.data).toHaveLength(3); - }); - it('renders a ComponentLoader if isLoading prop is true', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - isLoading: true, - }; - render(<OrderedStatesList {...props} />); - expect(screen.getByRole('bar-loader')).toBeInTheDocument(); - }); - it('should render an error message if error prop is present', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - error: 'Error Message', - }; - render(<OrderedStatesList {...props} />); - expect(screen.getByText('Error Message')).toBeInTheDocument(); - }); - it('should render "No Item" message when there is no data', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - data: [], - }; - const { getByText } = render(<OrderedStatesList {...props} />); - expect(getByText('No Item')).toBeInTheDocument(); - }); - it('calls the onRefreshClick function when the refresh button is clicked', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - data: [], - onRefreshClick: jest.fn(), - }; - render(<OrderedStatesList {...props} />); - const refreshButton: HTMLElement = screen.getByRole('refresh-button'); - refreshButton.click(); - expect(props.onRefreshClick).toHaveBeenCalled(); - }); - it('should call the onCreateNewItem prop when the "New Item" button is clicked', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - onCreateNewItem: jest.fn(), - }; - const { getByText } = render(<OrderedStatesList {...props} />); - const newItem: HTMLElement = getByText('Add New Item'); - newItem.click(); - expect(props.onCreateNewItem).toHaveBeenCalled(); - }); - it('renders ErrorMessage with error message and callback when error is defined', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - error: 'Failed to load data', - onRefreshClick: jest.fn(), - }; - render(<OrderedStatesList {...props} />); - expect(screen.getByText('Failed to load data')).toBeInTheDocument(); - expect(screen.getByRole('refresh-button')).toBeInTheDocument(); - screen.getByRole('refresh-button').click(); - expect(props.onRefreshClick).toHaveBeenCalled(); - }); - it('renders ErrorMessage with default message and callback when data is empty and noItemsMessage is not defined', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - data: [], - noItemsMessage: undefined, - onRefreshClick: jest.fn(), - }; - render(<OrderedStatesList {...props} />); - expect( - screen.getByText(`No ${props.singularLabel.toLowerCase()}`) - ).toBeInTheDocument(); - expect(screen.getByRole('refresh-button')).toBeInTheDocument(); - screen.getByRole('refresh-button').click(); - expect(props.onRefreshClick).toHaveBeenCalled(); - }); - it('should render custom title element provided by getTitleElement function', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - getTitleElement: jest.fn(), - }; - render(<OrderedStatesList {...props} />); - expect(props.getTitleElement).toHaveBeenCalled(); - }); - it('should render custom title element provided by getDescriptionElement function', () => { - const props: ComponentProps<ItemData> = { - ...defaultProps, - getDescriptionElement: jest.fn(), - }; - render(<OrderedStatesList {...props} />); - expect(props.getDescriptionElement).toHaveBeenCalled(); - }); + render(<OrderedStatesList {...props} />); + expect(props.data).toHaveLength(3); + }); + it("renders a ComponentLoader if isLoading prop is true", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + isLoading: true, + }; + render(<OrderedStatesList {...props} />); + expect(screen.getByRole("bar-loader")).toBeInTheDocument(); + }); + it("should render an error message if error prop is present", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + error: "Error Message", + }; + render(<OrderedStatesList {...props} />); + expect(screen.getByText("Error Message")).toBeInTheDocument(); + }); + it('should render "No Item" message when there is no data', () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + data: [], + }; + const { getByText } = render(<OrderedStatesList {...props} />); + expect(getByText("No Item")).toBeInTheDocument(); + }); + it("calls the onRefreshClick function when the refresh button is clicked", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + data: [], + onRefreshClick: jest.fn(), + }; + render(<OrderedStatesList {...props} />); + const refreshButton: HTMLElement = screen.getByRole("refresh-button"); + refreshButton.click(); + expect(props.onRefreshClick).toHaveBeenCalled(); + }); + it('should call the onCreateNewItem prop when the "New Item" button is clicked', () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + onCreateNewItem: jest.fn(), + }; + const { getByText } = render(<OrderedStatesList {...props} />); + const newItem: HTMLElement = getByText("Add New Item"); + newItem.click(); + expect(props.onCreateNewItem).toHaveBeenCalled(); + }); + it("renders ErrorMessage with error message and callback when error is defined", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + error: "Failed to load data", + onRefreshClick: jest.fn(), + }; + render(<OrderedStatesList {...props} />); + expect(screen.getByText("Failed to load data")).toBeInTheDocument(); + expect(screen.getByRole("refresh-button")).toBeInTheDocument(); + screen.getByRole("refresh-button").click(); + expect(props.onRefreshClick).toHaveBeenCalled(); + }); + it("renders ErrorMessage with default message and callback when data is empty and noItemsMessage is not defined", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + data: [], + noItemsMessage: undefined, + onRefreshClick: jest.fn(), + }; + render(<OrderedStatesList {...props} />); + expect( + screen.getByText(`No ${props.singularLabel.toLowerCase()}`), + ).toBeInTheDocument(); + expect(screen.getByRole("refresh-button")).toBeInTheDocument(); + screen.getByRole("refresh-button").click(); + expect(props.onRefreshClick).toHaveBeenCalled(); + }); + it("should render custom title element provided by getTitleElement function", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + getTitleElement: jest.fn(), + }; + render(<OrderedStatesList {...props} />); + expect(props.getTitleElement).toHaveBeenCalled(); + }); + it("should render custom title element provided by getDescriptionElement function", () => { + const props: ComponentProps<ItemData> = { + ...defaultProps, + getDescriptionElement: jest.fn(), + }; + render(<OrderedStatesList {...props} />); + expect(props.getDescriptionElement).toHaveBeenCalled(); + }); }); diff --git a/CommonUI/src/Tests/Components/Pagination.test.tsx b/CommonUI/src/Tests/Components/Pagination.test.tsx index 72c9c44b64..7f8b5c3d28 100644 --- a/CommonUI/src/Tests/Components/Pagination.test.tsx +++ b/CommonUI/src/Tests/Components/Pagination.test.tsx @@ -1,165 +1,165 @@ import Pagination, { - ComponentProps, -} from '../../Components/Pagination/Pagination'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import React from 'react'; + ComponentProps, +} from "../../Components/Pagination/Pagination"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import React from "react"; -describe('Pagination', () => { - it('renders Component', () => { - const props: ComponentProps = { - currentPageNumber: 1, - totalItemsCount: 20, - itemsOnPage: 10, - onNavigateToPage: jest.fn(), - isLoading: false, - isError: false, - singularLabel: 'Item', - pluralLabel: 'Items', - }; +describe("Pagination", () => { + it("renders Component", () => { + const props: ComponentProps = { + currentPageNumber: 1, + totalItemsCount: 20, + itemsOnPage: 10, + onNavigateToPage: jest.fn(), + isLoading: false, + isError: false, + singularLabel: "Item", + pluralLabel: "Items", + }; - render(<Pagination {...props} />); + render(<Pagination {...props} />); - expect( - screen.getByText(/Showing 1 to 10 on this page/i) - ).toBeInTheDocument(); + expect( + screen.getByText(/Showing 1 to 10 on this page/i), + ).toBeInTheDocument(); + }); + + it("renders with 1 item", () => { + const props: ComponentProps = { + currentPageNumber: 1, + totalItemsCount: 1, + itemsOnPage: 10, + onNavigateToPage: jest.fn(), + isLoading: false, + isError: false, + singularLabel: "Item", + pluralLabel: "Items", + }; + + render(<Pagination {...props} />); + + expect(screen.getByText(/1 Item/i)).toBeInTheDocument(); + }); + + it("calls onNavigateToPage when Next link is clicked", async () => { + const mockOnNavigateToPage: jest.Mock = jest.fn(); + const props: ComponentProps = { + currentPageNumber: 1, + totalItemsCount: 19, + itemsOnPage: 10, + onNavigateToPage: mockOnNavigateToPage, + isLoading: false, + isError: false, + singularLabel: "Item", + pluralLabel: "Items", + }; + + render(<Pagination {...props} />); + + fireEvent.click(screen.getByText("Next")); + + await waitFor(() => { + expect(mockOnNavigateToPage).toHaveBeenCalledWith(2, 10); + }); + }); + + it("calls onNavigateToPage when Previous link is clicked", async () => { + const mockOnNavigateToPage: jest.Mock = jest.fn(); + const props: ComponentProps = { + currentPageNumber: 2, + totalItemsCount: 19, + itemsOnPage: 10, + onNavigateToPage: mockOnNavigateToPage, + isLoading: false, + isError: false, + singularLabel: "Item", + pluralLabel: "Items", + }; + + render(<Pagination {...props} />); + + fireEvent.click(screen.getByText("Previous")); + + await waitFor(() => { + expect(mockOnNavigateToPage).toHaveBeenCalledWith(1, 10); + }); + }); + + it("shows Pagination Modal when button modal is clicked", async () => { + const props: ComponentProps = { + currentPageNumber: 1, + totalItemsCount: 0, + itemsOnPage: 10, + onNavigateToPage: jest.fn(), + isLoading: false, + isError: false, + singularLabel: "Item", + pluralLabel: "Items", + }; + + render(<Pagination {...props} />); + + fireEvent.click(screen.getByTestId("show-pagination-modal-button")); + + await waitFor(() => { + expect(screen.getByText("Navigate to Page")).toBeInTheDocument(); + }); + }); + + it("shows Pagination Modal when current page link is clicked", async () => { + const props: ComponentProps = { + currentPageNumber: 2, + totalItemsCount: 19, + itemsOnPage: 10, + onNavigateToPage: jest.fn(), + isLoading: false, + isError: false, + singularLabel: "Item", + pluralLabel: "Items", + }; + + render(<Pagination {...props} />); + + fireEvent.click(screen.getByTestId("current-page-link")); + + await waitFor(() => { + expect(screen.getByText("Navigate to Page")).toBeInTheDocument(); }); - it('renders with 1 item', () => { - const props: ComponentProps = { - currentPageNumber: 1, - totalItemsCount: 1, - itemsOnPage: 10, - onNavigateToPage: jest.fn(), - isLoading: false, - isError: false, - singularLabel: 'Item', - pluralLabel: 'Items', - }; + fireEvent.click(screen.getByTestId("close-button")); - render(<Pagination {...props} />); + await waitFor(() => { + expect(screen.queryByTestId("pagination-modal")).toBeNull(); + }); + }); - expect(screen.getByText(/1 Item/i)).toBeInTheDocument(); + it("shows Pagination Modal and submit with go to page", async () => { + const mockOnNavigateToPage: jest.Mock = jest.fn(); + const props: ComponentProps = { + currentPageNumber: 1, + totalItemsCount: 20, + itemsOnPage: 10, + onNavigateToPage: mockOnNavigateToPage, + isLoading: false, + isError: false, + singularLabel: "Item", + pluralLabel: "Items", + }; + + render(<Pagination {...props} />); + + fireEvent.click(screen.getByTestId("current-page-link")); + + await waitFor(() => { + expect(screen.getByText("Navigate to Page")).toBeInTheDocument(); }); - it('calls onNavigateToPage when Next link is clicked', async () => { - const mockOnNavigateToPage: jest.Mock = jest.fn(); - const props: ComponentProps = { - currentPageNumber: 1, - totalItemsCount: 19, - itemsOnPage: 10, - onNavigateToPage: mockOnNavigateToPage, - isLoading: false, - isError: false, - singularLabel: 'Item', - pluralLabel: 'Items', - }; + fireEvent.click(screen.getByText("Go to Page")); - render(<Pagination {...props} />); - - fireEvent.click(screen.getByText('Next')); - - await waitFor(() => { - expect(mockOnNavigateToPage).toHaveBeenCalledWith(2, 10); - }); - }); - - it('calls onNavigateToPage when Previous link is clicked', async () => { - const mockOnNavigateToPage: jest.Mock = jest.fn(); - const props: ComponentProps = { - currentPageNumber: 2, - totalItemsCount: 19, - itemsOnPage: 10, - onNavigateToPage: mockOnNavigateToPage, - isLoading: false, - isError: false, - singularLabel: 'Item', - pluralLabel: 'Items', - }; - - render(<Pagination {...props} />); - - fireEvent.click(screen.getByText('Previous')); - - await waitFor(() => { - expect(mockOnNavigateToPage).toHaveBeenCalledWith(1, 10); - }); - }); - - it('shows Pagination Modal when button modal is clicked', async () => { - const props: ComponentProps = { - currentPageNumber: 1, - totalItemsCount: 0, - itemsOnPage: 10, - onNavigateToPage: jest.fn(), - isLoading: false, - isError: false, - singularLabel: 'Item', - pluralLabel: 'Items', - }; - - render(<Pagination {...props} />); - - fireEvent.click(screen.getByTestId('show-pagination-modal-button')); - - await waitFor(() => { - expect(screen.getByText('Navigate to Page')).toBeInTheDocument(); - }); - }); - - it('shows Pagination Modal when current page link is clicked', async () => { - const props: ComponentProps = { - currentPageNumber: 2, - totalItemsCount: 19, - itemsOnPage: 10, - onNavigateToPage: jest.fn(), - isLoading: false, - isError: false, - singularLabel: 'Item', - pluralLabel: 'Items', - }; - - render(<Pagination {...props} />); - - fireEvent.click(screen.getByTestId('current-page-link')); - - await waitFor(() => { - expect(screen.getByText('Navigate to Page')).toBeInTheDocument(); - }); - - fireEvent.click(screen.getByTestId('close-button')); - - await waitFor(() => { - expect(screen.queryByTestId('pagination-modal')).toBeNull(); - }); - }); - - it('shows Pagination Modal and submit with go to page', async () => { - const mockOnNavigateToPage: jest.Mock = jest.fn(); - const props: ComponentProps = { - currentPageNumber: 1, - totalItemsCount: 20, - itemsOnPage: 10, - onNavigateToPage: mockOnNavigateToPage, - isLoading: false, - isError: false, - singularLabel: 'Item', - pluralLabel: 'Items', - }; - - render(<Pagination {...props} />); - - fireEvent.click(screen.getByTestId('current-page-link')); - - await waitFor(() => { - expect(screen.getByText('Navigate to Page')).toBeInTheDocument(); - }); - - fireEvent.click(screen.getByText('Go to Page')); - - await waitFor(() => { - expect(mockOnNavigateToPage).toHaveBeenCalledWith(1, 10); - }); + await waitFor(() => { + expect(mockOnNavigateToPage).toHaveBeenCalledWith(1, 10); }); + }); }); diff --git a/CommonUI/src/Tests/Components/Pill.test.tsx b/CommonUI/src/Tests/Components/Pill.test.tsx index c81194cc74..37a6b46a42 100644 --- a/CommonUI/src/Tests/Components/Pill.test.tsx +++ b/CommonUI/src/Tests/Components/Pill.test.tsx @@ -1,54 +1,48 @@ -import Pill, { PillSize } from '../../Components/Pill/Pill'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import Color from 'Common/Types/Color'; -import * as React from 'react'; +import Pill, { PillSize } from "../../Components/Pill/Pill"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import Color from "Common/Types/Color"; +import * as React from "react"; -describe('<Pill />', () => { - test('Checking text', () => { - const color: Color = new Color('#807149'); - render(<Pill text="Love" color={color} size={PillSize.Small} />); - expect(screen.getByTestId('pill')).toHaveTextContent('Love'); - }); - test('Test for font-size(Small) ', () => { - const color: Color = new Color('#807149'); - render(<Pill text="Love" color={color} size={PillSize.Small} />); - expect(screen.getByTestId('pill')).toHaveStyle('fontSize: 10px'); - }); - test('Test for font-size(Normal) ', () => { - const color: Color = new Color('#807149'); - render(<Pill text="Love" color={color} size={PillSize.Normal} />); - expect(screen.getByTestId('pill')).toHaveStyle('fontSize: 13px'); - }); - test('Test for font-size(Large) ', () => { - const color: Color = new Color('#807149'); - render(<Pill text="Love" color={color} size={PillSize.Large} />); - expect(screen.getByTestId('pill')).toHaveStyle('fontSize: 15px'); - }); - test('Test for font-size(Extra-large) ', () => { - const color: Color = new Color('#807149'); - render(<Pill text="Love" color={color} size={PillSize.ExtraLarge} />); - expect(screen.getByTestId('pill')).toHaveStyle('fontSize: 18px'); - }); - test('Checking for color #fffff', () => { - const color: Color = new Color('#ffffff'); - render(<Pill text="Love" color={color} size={PillSize.ExtraLarge} />); - expect(screen.getByTestId('pill')).toHaveStyle( - 'backgroundColor: #ffffff' - ); - }); - test('Checking for color #ff0000', () => { - const color: Color = new Color('#ff0000'); - render(<Pill text="Love" color={color} size={PillSize.ExtraLarge} />); - expect(screen.getByTestId('pill')).toHaveStyle( - 'backgroundColor: #ff0000' - ); - }); - test('Checking for color #786598', () => { - const color: Color = new Color('#786598'); - render(<Pill text="Love" color={color} size={PillSize.ExtraLarge} />); - expect(screen.getByTestId('pill')).toHaveStyle( - 'backgroundColor: #786598' - ); - }); +describe("<Pill />", () => { + test("Checking text", () => { + const color: Color = new Color("#807149"); + render(<Pill text="Love" color={color} size={PillSize.Small} />); + expect(screen.getByTestId("pill")).toHaveTextContent("Love"); + }); + test("Test for font-size(Small) ", () => { + const color: Color = new Color("#807149"); + render(<Pill text="Love" color={color} size={PillSize.Small} />); + expect(screen.getByTestId("pill")).toHaveStyle("fontSize: 10px"); + }); + test("Test for font-size(Normal) ", () => { + const color: Color = new Color("#807149"); + render(<Pill text="Love" color={color} size={PillSize.Normal} />); + expect(screen.getByTestId("pill")).toHaveStyle("fontSize: 13px"); + }); + test("Test for font-size(Large) ", () => { + const color: Color = new Color("#807149"); + render(<Pill text="Love" color={color} size={PillSize.Large} />); + expect(screen.getByTestId("pill")).toHaveStyle("fontSize: 15px"); + }); + test("Test for font-size(Extra-large) ", () => { + const color: Color = new Color("#807149"); + render(<Pill text="Love" color={color} size={PillSize.ExtraLarge} />); + expect(screen.getByTestId("pill")).toHaveStyle("fontSize: 18px"); + }); + test("Checking for color #fffff", () => { + const color: Color = new Color("#ffffff"); + render(<Pill text="Love" color={color} size={PillSize.ExtraLarge} />); + expect(screen.getByTestId("pill")).toHaveStyle("backgroundColor: #ffffff"); + }); + test("Checking for color #ff0000", () => { + const color: Color = new Color("#ff0000"); + render(<Pill text="Love" color={color} size={PillSize.ExtraLarge} />); + expect(screen.getByTestId("pill")).toHaveStyle("backgroundColor: #ff0000"); + }); + test("Checking for color #786598", () => { + const color: Color = new Color("#786598"); + render(<Pill text="Love" color={color} size={PillSize.ExtraLarge} />); + expect(screen.getByTestId("pill")).toHaveStyle("backgroundColor: #786598"); + }); }); diff --git a/CommonUI/src/Tests/Components/Probe.test.tsx b/CommonUI/src/Tests/Components/Probe.test.tsx index 8b4799a516..53a0bd4642 100644 --- a/CommonUI/src/Tests/Components/Probe.test.tsx +++ b/CommonUI/src/Tests/Components/Probe.test.tsx @@ -1,65 +1,63 @@ -import ProbeElement from '../../Components/Probe/Probe'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import ObjectID from 'Common/Types/ObjectID'; -import Probe from 'Model/Models/Probe'; -import * as React from 'react'; +import ProbeElement from "../../Components/Probe/Probe"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import ObjectID from "Common/Types/ObjectID"; +import Probe from "Model/Models/Probe"; +import * as React from "react"; -describe('ProbeElement Component', () => { - const mockProbe: Probe = new Probe(); - mockProbe.name = 'Test Probe'; - mockProbe.iconFileId = new ObjectID('12345'); +describe("ProbeElement Component", () => { + const mockProbe: Probe = new Probe(); + mockProbe.name = "Test Probe"; + mockProbe.iconFileId = new ObjectID("12345"); - test('should display the probe name', () => { - render(<ProbeElement probe={mockProbe} />); - const probeName: HTMLElement = screen.getByTestId('probe-name'); - expect(probeName).toBeInTheDocument(); - }); + test("should display the probe name", () => { + render(<ProbeElement probe={mockProbe} />); + const probeName: HTMLElement = screen.getByTestId("probe-name"); + expect(probeName).toBeInTheDocument(); + }); - test('should display the image when iconFileId is present', () => { - render(<ProbeElement probe={mockProbe} />); - const imageElement: HTMLImageElement = - screen.getByTestId('probe-image'); - expect(imageElement).toBeInTheDocument(); - expect(imageElement).toHaveAttribute('alt', 'Test Probe'); - }); + test("should display the image when iconFileId is present", () => { + render(<ProbeElement probe={mockProbe} />); + const imageElement: HTMLImageElement = screen.getByTestId("probe-image"); + expect(imageElement).toBeInTheDocument(); + expect(imageElement).toHaveAttribute("alt", "Test Probe"); + }); - test('should display the default icon when iconFileId is not present', () => { - const probeWithoutIcon: Probe = new Probe(); - probeWithoutIcon.name = 'Test Probe'; + test("should display the default icon when iconFileId is not present", () => { + const probeWithoutIcon: Probe = new Probe(); + probeWithoutIcon.name = "Test Probe"; - render(<ProbeElement probe={probeWithoutIcon} />); - const iconElement: HTMLElement = screen.getByTestId('probe-icon'); - expect(iconElement).toBeInTheDocument(); - }); + render(<ProbeElement probe={probeWithoutIcon} />); + const iconElement: HTMLElement = screen.getByTestId("probe-icon"); + expect(iconElement).toBeInTheDocument(); + }); - test('should display "No probe found" when no probe is provided', () => { - render(<ProbeElement probe={null} />); - const noProbeText: HTMLElement = screen.getByTestId('probe-not-found'); - expect(noProbeText).toBeInTheDocument(); - }); + test('should display "No probe found" when no probe is provided', () => { + render(<ProbeElement probe={null} />); + const noProbeText: HTMLElement = screen.getByTestId("probe-not-found"); + expect(noProbeText).toBeInTheDocument(); + }); - test('should display the image with correct src when iconFileId is present', () => { - const probeWithIcon: Probe = new Probe(); - probeWithIcon.iconFileId = new ObjectID('icon123'); - probeWithIcon.name = 'Probe with Icon'; + test("should display the image with correct src when iconFileId is present", () => { + const probeWithIcon: Probe = new Probe(); + probeWithIcon.iconFileId = new ObjectID("icon123"); + probeWithIcon.name = "Probe with Icon"; - render(<ProbeElement probe={probeWithIcon} />); - const imageElement: HTMLImageElement = - screen.getByTestId('probe-image'); - expect(imageElement).toBeInTheDocument(); - expect(imageElement).toHaveAttribute( - 'src', - expect.stringContaining('icon123') - ); - }); + render(<ProbeElement probe={probeWithIcon} />); + const imageElement: HTMLImageElement = screen.getByTestId("probe-image"); + expect(imageElement).toBeInTheDocument(); + expect(imageElement).toHaveAttribute( + "src", + expect.stringContaining("icon123"), + ); + }); - test('should display the default icon when iconFileId is not present', () => { - const probeWithoutIcon: Probe = new Probe(); - probeWithoutIcon.name = 'Probe without Icon'; + test("should display the default icon when iconFileId is not present", () => { + const probeWithoutIcon: Probe = new Probe(); + probeWithoutIcon.name = "Probe without Icon"; - render(<ProbeElement probe={probeWithoutIcon} />); - const iconElement: HTMLElement = screen.getByTestId('probe-icon'); - expect(iconElement).toBeInTheDocument(); - }); + render(<ProbeElement probe={probeWithoutIcon} />); + const iconElement: HTMLElement = screen.getByTestId("probe-icon"); + expect(iconElement).toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/ProgressBar.test.tsx b/CommonUI/src/Tests/Components/ProgressBar.test.tsx index 030e693a33..87d32e92bc 100644 --- a/CommonUI/src/Tests/Components/ProgressBar.test.tsx +++ b/CommonUI/src/Tests/Components/ProgressBar.test.tsx @@ -1,51 +1,51 @@ -import ProgressBar from '../../Components/ProgressBar/ProgressBar'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import * as React from 'react'; +import ProgressBar from "../../Components/ProgressBar/ProgressBar"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import * as React from "react"; -describe('ProgressBar Component', () => { - function getProgressBar(): HTMLElement { - const element: HTMLElement = screen.getByTestId('progress-bar'); - if (!element) { - throw 'Not Found'; - } - return element; +describe("ProgressBar Component", () => { + function getProgressBar(): HTMLElement { + const element: HTMLElement = screen.getByTestId("progress-bar"); + if (!element) { + throw "Not Found"; } + return element; + } - test('should calculate and display the correct percentage', () => { - render(<ProgressBar count={0} totalCount={100} suffix="items" />); - const progressBar: HTMLElement = getProgressBar(); - expect(progressBar).toHaveStyle({ width: '0%' }); - }); + test("should calculate and display the correct percentage", () => { + render(<ProgressBar count={0} totalCount={100} suffix="items" />); + const progressBar: HTMLElement = getProgressBar(); + expect(progressBar).toHaveStyle({ width: "0%" }); + }); - test('should display the correct count and total count with suffix', () => { - render(<ProgressBar count={30} totalCount={99} suffix="items" />); - const countText: HTMLElement = screen.getByTestId('progress-bar-count'); - const totalCountText: HTMLElement = screen.getByTestId( - 'progress-bar-total-count' - ); - expect(countText).toBeInTheDocument(); - expect(totalCountText).toBeInTheDocument(); + test("should display the correct count and total count with suffix", () => { + render(<ProgressBar count={30} totalCount={99} suffix="items" />); + const countText: HTMLElement = screen.getByTestId("progress-bar-count"); + const totalCountText: HTMLElement = screen.getByTestId( + "progress-bar-total-count", + ); + expect(countText).toBeInTheDocument(); + expect(totalCountText).toBeInTheDocument(); - expect(countText.innerHTML).toEqual('30 items'); - expect(totalCountText.innerHTML).toEqual('99 items'); - }); + expect(countText.innerHTML).toEqual("30 items"); + expect(totalCountText.innerHTML).toEqual("99 items"); + }); - test('should handle zero total count without crashing', () => { - render(<ProgressBar count={30} totalCount={0} suffix="items" />); - const progressBar: HTMLElement = getProgressBar(); - expect(progressBar).toHaveStyle({ width: '100%' }); - }); + test("should handle zero total count without crashing", () => { + render(<ProgressBar count={30} totalCount={0} suffix="items" />); + const progressBar: HTMLElement = getProgressBar(); + expect(progressBar).toHaveStyle({ width: "100%" }); + }); - test('should round up the percentage to the nearest integer', () => { - render(<ProgressBar count={33} totalCount={100} suffix="items" />); - const progressBar: HTMLElement = getProgressBar(); - expect(progressBar).toHaveStyle({ width: '33%' }); - }); + test("should round up the percentage to the nearest integer", () => { + render(<ProgressBar count={33} totalCount={100} suffix="items" />); + const progressBar: HTMLElement = getProgressBar(); + expect(progressBar).toHaveStyle({ width: "33%" }); + }); - test('should cap the percentage at 100 if count exceeds total count', () => { - render(<ProgressBar count={150} totalCount={100} suffix="items" />); - const progressBar: HTMLElement = getProgressBar(); - expect(progressBar).toHaveStyle({ width: '100%' }); - }); + test("should cap the percentage at 100 if count exceeds total count", () => { + render(<ProgressBar count={150} totalCount={100} suffix="items" />); + const progressBar: HTMLElement = getProgressBar(); + expect(progressBar).toHaveStyle({ width: "100%" }); + }); }); diff --git a/CommonUI/src/Tests/Components/RadioButtons.test.tsx b/CommonUI/src/Tests/Components/RadioButtons.test.tsx index a1de78f570..4fe0b678e4 100644 --- a/CommonUI/src/Tests/Components/RadioButtons.test.tsx +++ b/CommonUI/src/Tests/Components/RadioButtons.test.tsx @@ -1,112 +1,104 @@ import RadioButtons, { - RadioButton, -} from '../../Components/RadioButtons/GroupRadioButtons'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render } from '@testing-library/react'; -import React from 'react'; + RadioButton, +} from "../../Components/RadioButtons/GroupRadioButtons"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render } from "@testing-library/react"; +import React from "react"; -describe('RadioButtons', () => { - const options: RadioButton[] = [ - { - title: 'title 1', - description: 'description 1', - value: '1', - sideTitle: 'side title 1', - sideDescription: 'side description 1', - }, - { - title: 'title 2', - description: 'description 2', - value: '2', - sideTitle: 'side title 2', - sideDescription: 'side description 2', - }, - ]; +describe("RadioButtons", () => { + const options: RadioButton[] = [ + { + title: "title 1", + description: "description 1", + value: "1", + sideTitle: "side title 1", + sideDescription: "side description 1", + }, + { + title: "title 2", + description: "description 2", + value: "2", + sideTitle: "side title 2", + sideDescription: "side description 2", + }, + ]; - test('renders with required props only', () => { - const { getByRole } = render( - <RadioButtons onChange={() => {}} options={options} /> - ); - const radioButtonsGroup: HTMLElement = getByRole('radiogroup'); - expect(radioButtonsGroup).toBeInTheDocument(); - }); + test("renders with required props only", () => { + const { getByRole } = render( + <RadioButtons onChange={() => {}} options={options} />, + ); + const radioButtonsGroup: HTMLElement = getByRole("radiogroup"); + expect(radioButtonsGroup).toBeInTheDocument(); + }); - test('renders with all props', () => { - const { getByRole } = render( - <RadioButtons - onChange={() => {}} - options={options} - initialValue="1" - error="error" - /> - ); - const radioButtonsGroup: HTMLElement = getByRole('radiogroup'); - expect(radioButtonsGroup).toBeInTheDocument(); - }); + test("renders with all props", () => { + const { getByRole } = render( + <RadioButtons + onChange={() => {}} + options={options} + initialValue="1" + error="error" + />, + ); + const radioButtonsGroup: HTMLElement = getByRole("radiogroup"); + expect(radioButtonsGroup).toBeInTheDocument(); + }); - test('sets options', () => { - const { getAllByRole } = render( - <RadioButtons onChange={() => {}} options={options} /> - ); + test("sets options", () => { + const { getAllByRole } = render( + <RadioButtons onChange={() => {}} options={options} />, + ); - const radioButtons: HTMLElement[] = getAllByRole('radio'); + const radioButtons: HTMLElement[] = getAllByRole("radio"); - expect(radioButtons).toHaveLength(2); - }); + expect(radioButtons).toHaveLength(2); + }); - test('sets initial value', () => { - const { getByLabelText } = render( - <RadioButtons - onChange={() => {}} - options={options} - initialValue="1" - /> - ); + test("sets initial value", () => { + const { getByLabelText } = render( + <RadioButtons onChange={() => {}} options={options} initialValue="1" />, + ); - const radioButton: HTMLElement = getByLabelText('title 1'); - expect(radioButton).toBeChecked(); - }); + const radioButton: HTMLElement = getByLabelText("title 1"); + expect(radioButton).toBeChecked(); + }); - test('displays error', () => { - const { queryByText, rerender, getByText } = render( - <RadioButtons - onChange={() => {}} - options={options} - initialValue="1" - /> - ); - expect(queryByText('error')).toBeNull(); + test("displays error", () => { + const { queryByText, rerender, getByText } = render( + <RadioButtons onChange={() => {}} options={options} initialValue="1" />, + ); + expect(queryByText("error")).toBeNull(); - rerender( - <RadioButtons onChange={() => {}} options={options} error="error" /> - ); - expect(getByText('error')).toBeInTheDocument(); - }); + rerender( + <RadioButtons onChange={() => {}} options={options} error="error" />, + ); + expect(getByText("error")).toBeInTheDocument(); + }); - test('displays titles and descriptions', () => { - const { getByText, getByLabelText } = render( - <RadioButtons - onChange={() => {}} - options={[options[0] as RadioButton]} - /> - ); + test("displays titles and descriptions", () => { + const { getByText, getByLabelText } = render( + <RadioButtons + onChange={() => {}} + options={[options[0] as RadioButton]} + />, + ); - expect(getByLabelText('title 1')).toBeInTheDocument(); - expect(getByText('description 1')).toBeInTheDocument(); - expect(getByText('side title 1')).toBeInTheDocument(); - expect(getByText('side description 1')).toBeInTheDocument(); - }); + expect(getByLabelText("title 1")).toBeInTheDocument(); + expect(getByText("description 1")).toBeInTheDocument(); + expect(getByText("side title 1")).toBeInTheDocument(); + expect(getByText("side description 1")).toBeInTheDocument(); + }); - test('calls onChange', () => { - const onChange: jest.Mock = jest.fn(); - const { getByLabelText } = render( - <RadioButtons onChange={onChange} options={options} /> - ); + test("calls onChange", () => { + const onChange: jest.Mock = jest.fn(); + const { getByLabelText } = render( + <RadioButtons onChange={onChange} options={options} />, + ); - const radioButton: HTMLElement = getByLabelText('title 1'); - fireEvent.click(radioButton); + const radioButton: HTMLElement = getByLabelText("title 1"); + fireEvent.click(radioButton); - expect(onChange).toHaveBeenCalled(); - expect(onChange).toHaveBeenCalledWith('1'); - }); + expect(onChange).toHaveBeenCalled(); + expect(onChange).toHaveBeenCalledWith("1"); + }); }); diff --git a/CommonUI/src/Tests/Components/SideMenuItem.test.tsx b/CommonUI/src/Tests/Components/SideMenuItem.test.tsx index 002bee583b..7729e8fd33 100644 --- a/CommonUI/src/Tests/Components/SideMenuItem.test.tsx +++ b/CommonUI/src/Tests/Components/SideMenuItem.test.tsx @@ -1,152 +1,152 @@ // Libraries -import { BadgeType } from '../../Components/Badge/Badge'; +import { BadgeType } from "../../Components/Badge/Badge"; // Components import SideMenuItem, { - ComponentProps, -} from '../../Components/SideMenu/SideMenuItem'; -import * as Navigation from '../../Utils/Navigation'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen } from '@testing-library/react'; + ComponentProps, +} from "../../Components/SideMenu/SideMenuItem"; +import * as Navigation from "../../Utils/Navigation"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen } from "@testing-library/react"; // Types -import Route from 'Common/Types/API/Route'; -import IconProp from 'Common/Types/Icon/IconProp'; -import React from 'react'; +import Route from "Common/Types/API/Route"; +import IconProp from "Common/Types/Icon/IconProp"; +import React from "react"; const highlightClassList: string = - 'bg-gray-100 text-indigo-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium'; + "bg-gray-100 text-indigo-600 hover:bg-white group rounded-md px-3 py-2 flex items-center text-sm font-medium"; -jest.mock('../../Utils/Navigation.ts', () => { - return { - isOnThisPage: jest.fn().mockReturnValue(false), - navigate: jest.fn(), - }; +jest.mock("../../Utils/Navigation.ts", () => { + return { + isOnThisPage: jest.fn().mockReturnValue(false), + navigate: jest.fn(), + }; }); -describe('Side Menu Item', () => { - afterEach(() => { - jest.clearAllMocks(); - }); +describe("Side Menu Item", () => { + afterEach(() => { + jest.clearAllMocks(); + }); - const defaultProps: ComponentProps = { - link: { - title: 'Home', - to: new Route('/home'), - }, + const defaultProps: ComponentProps = { + link: { + title: "Home", + to: new Route("/home"), + }, + }; + + it("Should render the main link with given title", () => { + render(<SideMenuItem {...defaultProps} />); + + const mainLink: HTMLAnchorElement | null = screen + .getByText(defaultProps.link.title) + .closest("a"); + expect(mainLink).toBeInTheDocument(); + }); + + it("Should call navigate function when clicked", () => { + render(<SideMenuItem {...defaultProps} />); + + const mainLink: HTMLAnchorElement = screen + .getByText(defaultProps.link.title) + .closest("a") as HTMLAnchorElement; + + fireEvent.click(mainLink); + + expect(Navigation.default.navigate).toHaveBeenCalledTimes(1); + }); + + it("Should render icon if provided", () => { + render(<SideMenuItem {...defaultProps} icon={IconProp.Home} />); + + expect(screen.getByRole("icon")).toBeInTheDocument(); + }); + + it("Should render the sub item link with given title and Icon", () => { + const subLink: { + title: string; + to: Route; + } = { + title: "Sub Page", + to: new Route("/sub-page"), }; + render( + <SideMenuItem + {...defaultProps} + subItemLink={subLink} + icon={IconProp.Home} + subItemIcon={IconProp.ExternalLink} + />, + ); - it('Should render the main link with given title', () => { - render(<SideMenuItem {...defaultProps} />); + const subLinkElement: HTMLAnchorElement | null = screen + .getByText(subLink.title) + .closest("a"); - const mainLink: HTMLAnchorElement | null = screen - .getByText(defaultProps.link.title) - .closest('a'); - expect(mainLink).toBeInTheDocument(); - }); + expect(subLinkElement).toBeInTheDocument(); + expect(screen.getAllByRole("icon")).toHaveLength(2); + }); - it('Should call navigate function when clicked', () => { - render(<SideMenuItem {...defaultProps} />); + it("Should render link badge if provided", () => { + const badgeCount: number = 2; + render( + <SideMenuItem + {...defaultProps} + badge={badgeCount} + badgeType={BadgeType.SUCCESS} + />, + ); - const mainLink: HTMLAnchorElement = screen - .getByText(defaultProps.link.title) - .closest('a') as HTMLAnchorElement; + expect(screen.getByText(badgeCount)).toBeInTheDocument(); + }); - fireEvent.click(mainLink); + it("Should show alert", () => { + render(<SideMenuItem {...defaultProps} showAlert={true} />); - expect(Navigation.default.navigate).toHaveBeenCalledTimes(1); - }); + expect(screen.getByRole("icon")).toBeInTheDocument(); + }); - it('Should render icon if provided', () => { - render(<SideMenuItem {...defaultProps} icon={IconProp.Home} />); + it("Should show warning", () => { + render(<SideMenuItem {...defaultProps} showWarning={true} />); - expect(screen.getByRole('icon')).toBeInTheDocument(); - }); + expect(screen.getByRole("icon")).toBeInTheDocument(); + }); - it('Should render the sub item link with given title and Icon', () => { - const subLink: { - title: string; - to: Route; - } = { - title: 'Sub Page', - to: new Route('/sub-page'), - }; - render( - <SideMenuItem - {...defaultProps} - subItemLink={subLink} - icon={IconProp.Home} - subItemIcon={IconProp.ExternalLink} - /> - ); + it("Should highlights the main link when on the same page", () => { + (Navigation.default.isOnThisPage as jest.Mock).mockReturnValue(true); + render(<SideMenuItem {...defaultProps} />); - const subLinkElement: HTMLAnchorElement | null = screen - .getByText(subLink.title) - .closest('a'); + const mainLink: HTMLAnchorElement | null = screen + .getByText(defaultProps.link.title) + .closest("a"); + expect(mainLink).toHaveClass(highlightClassList); + }); - expect(subLinkElement).toBeInTheDocument(); - expect(screen.getAllByRole('icon')).toHaveLength(2); - }); + it("Should highlights sub item link when on the same page", () => { + const subLink: { + title: string; + to: Route; + } = { + title: "Sub Page", + to: new Route("/sub-page"), + }; + Navigation.default.isOnThisPage = jest + .fn() + .mockImplementation((to: Route) => { + return to === subLink.to; + }); + render( + <SideMenuItem + {...defaultProps} + subItemLink={subLink} + icon={IconProp.Home} + subItemIcon={IconProp.ExternalLink} + />, + ); - it('Should render link badge if provided', () => { - const badgeCount: number = 2; - render( - <SideMenuItem - {...defaultProps} - badge={badgeCount} - badgeType={BadgeType.SUCCESS} - /> - ); - - expect(screen.getByText(badgeCount)).toBeInTheDocument(); - }); - - it('Should show alert', () => { - render(<SideMenuItem {...defaultProps} showAlert={true} />); - - expect(screen.getByRole('icon')).toBeInTheDocument(); - }); - - it('Should show warning', () => { - render(<SideMenuItem {...defaultProps} showWarning={true} />); - - expect(screen.getByRole('icon')).toBeInTheDocument(); - }); - - it('Should highlights the main link when on the same page', () => { - (Navigation.default.isOnThisPage as jest.Mock).mockReturnValue(true); - render(<SideMenuItem {...defaultProps} />); - - const mainLink: HTMLAnchorElement | null = screen - .getByText(defaultProps.link.title) - .closest('a'); - expect(mainLink).toHaveClass(highlightClassList); - }); - - it('Should highlights sub item link when on the same page', () => { - const subLink: { - title: string; - to: Route; - } = { - title: 'Sub Page', - to: new Route('/sub-page'), - }; - Navigation.default.isOnThisPage = jest - .fn() - .mockImplementation((to: Route) => { - return to === subLink.to; - }); - render( - <SideMenuItem - {...defaultProps} - subItemLink={subLink} - icon={IconProp.Home} - subItemIcon={IconProp.ExternalLink} - /> - ); - - const subLinkElement: HTMLAnchorElement | null = screen - .getByText(subLink.title) - .closest('a'); - expect(subLinkElement).toHaveClass(highlightClassList); - }); + const subLinkElement: HTMLAnchorElement | null = screen + .getByText(subLink.title) + .closest("a"); + expect(subLinkElement).toHaveClass(highlightClassList); + }); }); diff --git a/CommonUI/src/Tests/Components/SideOver.test.tsx b/CommonUI/src/Tests/Components/SideOver.test.tsx index 3f3b94445e..a9a402753f 100644 --- a/CommonUI/src/Tests/Components/SideOver.test.tsx +++ b/CommonUI/src/Tests/Components/SideOver.test.tsx @@ -1,109 +1,109 @@ -import SideOver, { ComponentProps } from '../../Components/SideOver/SideOver'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render, screen } from '@testing-library/react'; -import React, { ReactElement } from 'react'; +import SideOver, { ComponentProps } from "../../Components/SideOver/SideOver"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render, screen } from "@testing-library/react"; +import React, { ReactElement } from "react"; -describe('SideOver', () => { - const childElementText: string = 'child element'; - const childElement: ReactElement = <div key={0}>{childElementText}</div>; +describe("SideOver", () => { + const childElementText: string = "child element"; + const childElement: ReactElement = <div key={0}>{childElementText}</div>; - const props: ComponentProps = { - title: 'title', - description: 'description', - onClose: jest.fn(), - onSubmit: jest.fn(), - children: childElement, - }; + const props: ComponentProps = { + title: "title", + description: "description", + onClose: jest.fn(), + onSubmit: jest.fn(), + children: childElement, + }; - type RenderComponentFunction = (props: ComponentProps) => void; + type RenderComponentFunction = (props: ComponentProps) => void; - const renderComponent: RenderComponentFunction = ( - props: ComponentProps - ): void => { - render(<SideOver {...props} />); - }; + const renderComponent: RenderComponentFunction = ( + props: ComponentProps, + ): void => { + render(<SideOver {...props} />); + }; - afterEach(() => { - jest.clearAllMocks(); - }); + afterEach(() => { + jest.clearAllMocks(); + }); - test('should display title', () => { - renderComponent(props); + test("should display title", () => { + renderComponent(props); - const title: HTMLElement = screen.getByText(props.title); - expect(title).toBeInTheDocument(); - expect(title).toHaveClass('text-lg font-medium text-gray-900'); - }); + const title: HTMLElement = screen.getByText(props.title); + expect(title).toBeInTheDocument(); + expect(title).toHaveClass("text-lg font-medium text-gray-900"); + }); - test('should display description', () => { - renderComponent(props); + test("should display description", () => { + renderComponent(props); - const description: HTMLElement = screen.getByText(props.description); - expect(description).toBeInTheDocument(); - expect(description).toHaveClass('text-sm text-gray-500'); - }); + const description: HTMLElement = screen.getByText(props.description); + expect(description).toBeInTheDocument(); + expect(description).toHaveClass("text-sm text-gray-500"); + }); - test("should close the call onClose when 'Close Panel' button is clicked", () => { - renderComponent(props); + test("should close the call onClose when 'Close Panel' button is clicked", () => { + renderComponent(props); - const closePanelButton: HTMLElement = screen.getByText('Close panel'); - expect(closePanelButton).toBeInTheDocument(); + const closePanelButton: HTMLElement = screen.getByText("Close panel"); + expect(closePanelButton).toBeInTheDocument(); - fireEvent.click(closePanelButton); - expect(props.onClose).toHaveBeenCalled(); - }); + fireEvent.click(closePanelButton); + expect(props.onClose).toHaveBeenCalled(); + }); - test('should render children component passed in the props', () => { - renderComponent(props); + test("should render children component passed in the props", () => { + renderComponent(props); - expect(screen.getByText(childElementText)).toBeInTheDocument(); - }); + expect(screen.getByText(childElementText)).toBeInTheDocument(); + }); - test('should render leftFooterElement when passed in the props', () => { - const leftFooterElementText: string = 'left element'; - const leftFooterElement: ReactElement = ( - <div key={0}>{leftFooterElementText}</div> - ); + test("should render leftFooterElement when passed in the props", () => { + const leftFooterElementText: string = "left element"; + const leftFooterElement: ReactElement = ( + <div key={0}>{leftFooterElementText}</div> + ); - renderComponent({ ...props, leftFooterElement }); + renderComponent({ ...props, leftFooterElement }); - expect(screen.getByText(leftFooterElementText)).toBeInTheDocument(); - }); + expect(screen.getByText(leftFooterElementText)).toBeInTheDocument(); + }); - test('should call onClose when Close button is clicked', () => { - renderComponent(props); + test("should call onClose when Close button is clicked", () => { + renderComponent(props); - const cancelButton: HTMLElement = screen.getByText('Close'); - expect(cancelButton).toBeInTheDocument(); + const cancelButton: HTMLElement = screen.getByText("Close"); + expect(cancelButton).toBeInTheDocument(); - fireEvent.click(cancelButton); - expect(props.onClose).toHaveBeenCalled(); - }); + fireEvent.click(cancelButton); + expect(props.onClose).toHaveBeenCalled(); + }); - test('should use submitButtonText value passed in the props for submit button text', () => { - const submitButtonText: string = 'Submit'; + test("should use submitButtonText value passed in the props for submit button text", () => { + const submitButtonText: string = "Submit"; - renderComponent({ ...props, submitButtonText }); + renderComponent({ ...props, submitButtonText }); - expect(screen.getByText(submitButtonText)).toBeInTheDocument(); - }); + expect(screen.getByText(submitButtonText)).toBeInTheDocument(); + }); - test('should disable Save button', () => { - renderComponent({ ...props, submitButtonDisabled: true }); + test("should disable Save button", () => { + renderComponent({ ...props, submitButtonDisabled: true }); - const saveButton: HTMLElement = screen.getByText('Save'); + const saveButton: HTMLElement = screen.getByText("Save"); - expect(saveButton).toBeInTheDocument(); - expect(saveButton).toBeDisabled(); - }); + expect(saveButton).toBeInTheDocument(); + expect(saveButton).toBeDisabled(); + }); - test('should call onSubmit when Save button is clicked', () => { - renderComponent(props); + test("should call onSubmit when Save button is clicked", () => { + renderComponent(props); - const saveButton: HTMLElement = screen.getByText('Save'); - expect(saveButton).toBeInTheDocument(); + const saveButton: HTMLElement = screen.getByText("Save"); + expect(saveButton).toBeInTheDocument(); - fireEvent.click(saveButton); - expect(props.onSubmit).toHaveBeenCalled(); - }); + fireEvent.click(saveButton); + expect(props.onSubmit).toHaveBeenCalled(); + }); }); diff --git a/CommonUI/src/Tests/Components/Tabs.test.tsx b/CommonUI/src/Tests/Components/Tabs.test.tsx index d497096be3..be8913287b 100644 --- a/CommonUI/src/Tests/Components/Tabs.test.tsx +++ b/CommonUI/src/Tests/Components/Tabs.test.tsx @@ -1,86 +1,86 @@ -import { Tab } from '../../Components/Tabs/Tab'; -import Tabs from '../../Components/Tabs/Tabs'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render } from '@testing-library/react'; -import React from 'react'; +import { Tab } from "../../Components/Tabs/Tab"; +import Tabs from "../../Components/Tabs/Tabs"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render } from "@testing-library/react"; +import React from "react"; -describe('Tabs', () => { - const activeClass: string = 'bg-gray-100 text-gray-700'; +describe("Tabs", () => { + const activeClass: string = "bg-gray-100 text-gray-700"; - const tab1: Tab = { - name: 'tab1', - children: <div>tab 1 content</div>, - }; + const tab1: Tab = { + name: "tab1", + children: <div>tab 1 content</div>, + }; - const tab2: Tab = { - name: 'tab2', - children: <div>tab 2 content</div>, - }; + const tab2: Tab = { + name: "tab2", + children: <div>tab 2 content</div>, + }; - const tabs: Array<Tab> = [tab1, tab2]; + const tabs: Array<Tab> = [tab1, tab2]; - test('it should render all props passed', () => { - const onTabChange: jest.Mock = jest.fn(); + test("it should render all props passed", () => { + const onTabChange: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Tabs tabs={tabs} onTabChange={onTabChange} /> - ); + const { getByTestId } = render( + <Tabs tabs={tabs} onTabChange={onTabChange} />, + ); - expect(getByTestId('tab-tab1')).toBeInTheDocument(); - expect(getByTestId('tab-tab2')).toBeInTheDocument(); - }); + expect(getByTestId("tab-tab1")).toBeInTheDocument(); + expect(getByTestId("tab-tab2")).toBeInTheDocument(); + }); - test('it should render the first tab as active by default', () => { - const onTabChange: jest.Mock = jest.fn(); + test("it should render the first tab as active by default", () => { + const onTabChange: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Tabs tabs={tabs} onTabChange={onTabChange} /> - ); + const { getByTestId } = render( + <Tabs tabs={tabs} onTabChange={onTabChange} />, + ); - expect(getByTestId('tab-tab1')).toHaveClass(activeClass); - }); + expect(getByTestId("tab-tab1")).toHaveClass(activeClass); + }); - test('it should call onTabChange with the correct tab when a tab is clicked', () => { - const onTabChange: jest.Mock = jest.fn(); + test("it should call onTabChange with the correct tab when a tab is clicked", () => { + const onTabChange: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Tabs tabs={tabs} onTabChange={onTabChange} /> - ); + const { getByTestId } = render( + <Tabs tabs={tabs} onTabChange={onTabChange} />, + ); - fireEvent.click(getByTestId('tab-tab1')); - expect(onTabChange).toHaveBeenCalledWith(tab1); + fireEvent.click(getByTestId("tab-tab1")); + expect(onTabChange).toHaveBeenCalledWith(tab1); - fireEvent.click(getByTestId('tab-tab2')); - expect(onTabChange).toHaveBeenCalledWith(tab2); - }); + fireEvent.click(getByTestId("tab-tab2")); + expect(onTabChange).toHaveBeenCalledWith(tab2); + }); - test('it should show the correct tab as active when a tab is clicked', () => { - const onTabChange: jest.Mock = jest.fn(); + test("it should show the correct tab as active when a tab is clicked", () => { + const onTabChange: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Tabs tabs={tabs} onTabChange={onTabChange} /> - ); + const { getByTestId } = render( + <Tabs tabs={tabs} onTabChange={onTabChange} />, + ); - fireEvent.click(getByTestId('tab-tab2')); + fireEvent.click(getByTestId("tab-tab2")); - expect(getByTestId('tab-tab1')).not.toHaveClass(activeClass); - expect(getByTestId('tab-tab2')).toHaveClass(activeClass); - }); + expect(getByTestId("tab-tab1")).not.toHaveClass(activeClass); + expect(getByTestId("tab-tab2")).toHaveClass(activeClass); + }); - test('it should handle empty tabs array gracefully', () => { - const tabs: Array<Tab> = []; - const onTabChange: jest.Mock = jest.fn(); + test("it should handle empty tabs array gracefully", () => { + const tabs: Array<Tab> = []; + const onTabChange: jest.Mock = jest.fn(); - const { getByTestId } = render( - <Tabs tabs={tabs} onTabChange={onTabChange} /> - ); + const { getByTestId } = render( + <Tabs tabs={tabs} onTabChange={onTabChange} />, + ); - expect(() => { - return getByTestId('tab-tab1'); - }).toThrow(); + expect(() => { + return getByTestId("tab-tab1"); + }).toThrow(); - expect(() => { - return getByTestId('tab-tab2'); - }).toThrow(); - }); + expect(() => { + return getByTestId("tab-tab2"); + }).toThrow(); + }); }); diff --git a/CommonUI/src/Tests/Components/Template/Template.test.tsx b/CommonUI/src/Tests/Components/Template/Template.test.tsx index e7726b6f06..4b9f09e9cf 100644 --- a/CommonUI/src/Tests/Components/Template/Template.test.tsx +++ b/CommonUI/src/Tests/Components/Template/Template.test.tsx @@ -1,17 +1,17 @@ import Component, { - ComponentProps, -} from '../../../Components/Template/Template'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; + ComponentProps, +} from "../../../Components/Template/Template"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import React from "react"; -describe('Template Component', () => { - const props: ComponentProps = { - title: 'Template title', - }; - it('should render component with the correct title', () => { - render(<Component {...props} />); - expect(screen.getByText(props.title)).toBeInTheDocument(); - }); +describe("Template Component", () => { + const props: ComponentProps = { + title: "Template title", + }; + it("should render component with the correct title", () => { + render(<Component {...props} />); + expect(screen.getByText(props.title)).toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/TextArea.test.tsx b/CommonUI/src/Tests/Components/TextArea.test.tsx index 67ffd4c9ab..262347c945 100644 --- a/CommonUI/src/Tests/Components/TextArea.test.tsx +++ b/CommonUI/src/Tests/Components/TextArea.test.tsx @@ -1,157 +1,151 @@ -import TextArea from '../../Components/TextArea/TextArea'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render } from '@testing-library/react'; -import React from 'react'; +import TextArea from "../../Components/TextArea/TextArea"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render } from "@testing-library/react"; +import React from "react"; -describe('TextArea', () => { - test('renders textarea element with initialValue only', () => { - const { getByRole } = render(<TextArea initialValue="initial value" />); - const textarea: HTMLElement = getByRole('textbox'); +describe("TextArea", () => { + test("renders textarea element with initialValue only", () => { + const { getByRole } = render(<TextArea initialValue="initial value" />); + const textarea: HTMLElement = getByRole("textbox"); - expect(textarea).toBeInTheDocument(); - }); + expect(textarea).toBeInTheDocument(); + }); - test('renders textarea element with all props', () => { - const { getByRole } = render( - <TextArea - onChange={() => {}} - onFocus={() => {}} - onBlur={() => {}} - initialValue="initial value" - placeholder="placeholder" - className="class-name" - tabIndex={1} - error="error" - autoFocus={true} - /> - ); - const textarea: HTMLElement = getByRole('textbox'); + test("renders textarea element with all props", () => { + const { getByRole } = render( + <TextArea + onChange={() => {}} + onFocus={() => {}} + onBlur={() => {}} + initialValue="initial value" + placeholder="placeholder" + className="class-name" + tabIndex={1} + error="error" + autoFocus={true} + />, + ); + const textarea: HTMLElement = getByRole("textbox"); - expect(textarea).toBeInTheDocument(); - }); + expect(textarea).toBeInTheDocument(); + }); - test('calls onChange event', () => { - const onChange: jest.Mock = jest.fn(); + test("calls onChange event", () => { + const onChange: jest.Mock = jest.fn(); - const { getByRole } = render( - <TextArea initialValue="initial value" onChange={onChange} /> - ); - const textarea: HTMLElement = getByRole('textbox'); - fireEvent.change(textarea, { target: { value: 'new value' } }); + const { getByRole } = render( + <TextArea initialValue="initial value" onChange={onChange} />, + ); + const textarea: HTMLElement = getByRole("textbox"); + fireEvent.change(textarea, { target: { value: "new value" } }); - expect(onChange).toHaveBeenCalledTimes(1); - expect(onChange).toHaveBeenCalledWith('new value'); - }); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith("new value"); + }); - test('calls onFocus event', () => { - const onFocus: jest.Mock = jest.fn(); + test("calls onFocus event", () => { + const onFocus: jest.Mock = jest.fn(); - const { getByRole } = render( - <TextArea initialValue="initial value" onFocus={onFocus} /> - ); - const textarea: HTMLElement = getByRole('textbox'); - fireEvent.focus(textarea); + const { getByRole } = render( + <TextArea initialValue="initial value" onFocus={onFocus} />, + ); + const textarea: HTMLElement = getByRole("textbox"); + fireEvent.focus(textarea); - expect(onFocus).toHaveBeenCalledTimes(1); - }); + expect(onFocus).toHaveBeenCalledTimes(1); + }); - test('calls onBlur event', () => { - const onBlur: jest.Mock = jest.fn(); + test("calls onBlur event", () => { + const onBlur: jest.Mock = jest.fn(); - const { getByRole } = render( - <TextArea initialValue="initial value" onBlur={onBlur} /> - ); - const textarea: HTMLElement = getByRole('textbox'); - fireEvent.blur(textarea); + const { getByRole } = render( + <TextArea initialValue="initial value" onBlur={onBlur} />, + ); + const textarea: HTMLElement = getByRole("textbox"); + fireEvent.blur(textarea); - expect(onBlur).toHaveBeenCalledTimes(1); - }); + expect(onBlur).toHaveBeenCalledTimes(1); + }); - test('sets initialValue', () => { - const { getByDisplayValue } = render( - <TextArea initialValue="initial value" /> - ); - expect(getByDisplayValue('initial value')).toBeInTheDocument(); - }); + test("sets initialValue", () => { + const { getByDisplayValue } = render( + <TextArea initialValue="initial value" />, + ); + expect(getByDisplayValue("initial value")).toBeInTheDocument(); + }); - test('sets placeholder', () => { - const { getByPlaceholderText } = render( - <TextArea placeholder="placeholder" initialValue="" /> - ); - expect(getByPlaceholderText('placeholder')).toBeInTheDocument(); - }); + test("sets placeholder", () => { + const { getByPlaceholderText } = render( + <TextArea placeholder="placeholder" initialValue="" />, + ); + expect(getByPlaceholderText("placeholder")).toBeInTheDocument(); + }); - test('sets className', () => { - const { getByRole } = render( - <TextArea className="class-name" initialValue="" /> - ); - expect(getByRole('textbox')).toHaveClass('class-name'); - }); + test("sets className", () => { + const { getByRole } = render( + <TextArea className="class-name" initialValue="" />, + ); + expect(getByRole("textbox")).toHaveClass("class-name"); + }); - test('sets default className', () => { - const { getByRole } = render(<TextArea initialValue="" />); - expect(getByRole('textbox').classList.length).toBeGreaterThan(0); - }); + test("sets default className", () => { + const { getByRole } = render(<TextArea initialValue="" />); + expect(getByRole("textbox").classList.length).toBeGreaterThan(0); + }); - test('sets tabIndex', () => { - const { getByRole } = render(<TextArea tabIndex={1} initialValue="" />); - expect(getByRole('textbox')).toHaveAttribute('tabindex', '1'); - }); + test("sets tabIndex", () => { + const { getByRole } = render(<TextArea tabIndex={1} initialValue="" />); + expect(getByRole("textbox")).toHaveAttribute("tabindex", "1"); + }); - test('sets autoFocus', () => { - const { getByRole } = render( - <TextArea autoFocus={true} initialValue="" /> - ); - expect(getByRole('textbox')).toHaveFocus(); - }); + test("sets autoFocus", () => { + const { getByRole } = render(<TextArea autoFocus={true} initialValue="" />); + expect(getByRole("textbox")).toHaveFocus(); + }); - test('displays error', () => { - const errorTestId: string = 'error-message'; - const { getByText, getByTestId } = render( - <TextArea error="error" initialValue="" /> - ); + test("displays error", () => { + const errorTestId: string = "error-message"; + const { getByText, getByTestId } = render( + <TextArea error="error" initialValue="" />, + ); - expect(getByText('error')).toBeInTheDocument(); - expect(getByTestId(errorTestId)).toBeInTheDocument(); - }); + expect(getByText("error")).toBeInTheDocument(); + expect(getByTestId(errorTestId)).toBeInTheDocument(); + }); - test('displays error icon', () => { - const { getByRole } = render( - <TextArea error="error" initialValue="" /> - ); - expect(getByRole('icon')).toBeInTheDocument(); - }); + test("displays error icon", () => { + const { getByRole } = render(<TextArea error="error" initialValue="" />); + expect(getByRole("icon")).toBeInTheDocument(); + }); - test('does not display error icon without error', () => { - const { queryByRole } = render(<TextArea initialValue="" />); - expect(queryByRole('icon')).not.toBeInTheDocument(); - }); + test("does not display error icon without error", () => { + const { queryByRole } = render(<TextArea initialValue="" />); + expect(queryByRole("icon")).not.toBeInTheDocument(); + }); - test('applies error styles', () => { - const { getByRole } = render( - <TextArea error="error" initialValue="" /> - ); - expect(getByRole('textbox')).toHaveClass('border-red-300'); - }); + test("applies error styles", () => { + const { getByRole } = render(<TextArea error="error" initialValue="" />); + expect(getByRole("textbox")).toHaveClass("border-red-300"); + }); - test('does not apply error styles without error', () => { - const { getByRole } = render(<TextArea initialValue="" />); - expect(getByRole('textbox')).not.toHaveClass('border-red-300'); - }); + test("does not apply error styles without error", () => { + const { getByRole } = render(<TextArea initialValue="" />); + expect(getByRole("textbox")).not.toHaveClass("border-red-300"); + }); - test('parses sole initial newline as empty string', () => { - const onChange: jest.Mock = jest.fn(); + test("parses sole initial newline as empty string", () => { + const onChange: jest.Mock = jest.fn(); - const { getByRole, getByDisplayValue, queryByDisplayValue } = render( - <TextArea initialValue="initial value" onChange={onChange} /> - ); + const { getByRole, getByDisplayValue, queryByDisplayValue } = render( + <TextArea initialValue="initial value" onChange={onChange} />, + ); - const textarea: HTMLElement = getByRole('textbox'); - fireEvent.change(textarea, { target: { value: '\n' } }); + const textarea: HTMLElement = getByRole("textbox"); + fireEvent.change(textarea, { target: { value: "\n" } }); - expect(onChange).toHaveBeenCalled(); - expect(onChange).toHaveBeenCalledWith(''); - expect(getByDisplayValue('')).toBeInTheDocument(); - expect(queryByDisplayValue('\n')).not.toBeInTheDocument(); - }); + expect(onChange).toHaveBeenCalled(); + expect(onChange).toHaveBeenCalledWith(""); + expect(getByDisplayValue("")).toBeInTheDocument(); + expect(queryByDisplayValue("\n")).not.toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Components/Toast.test.tsx b/CommonUI/src/Tests/Components/Toast.test.tsx index 89ca414d8b..58763dea2f 100644 --- a/CommonUI/src/Tests/Components/Toast.test.tsx +++ b/CommonUI/src/Tests/Components/Toast.test.tsx @@ -1,133 +1,132 @@ -import Toast, { ToastType } from '../../Components/Toast/Toast'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; -import OneUptimeDate from 'Common/Types/Date'; -import * as React from 'react'; +import Toast, { ToastType } from "../../Components/Toast/Toast"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup"; +import OneUptimeDate from "Common/Types/Date"; +import * as React from "react"; -describe('Test for Toast.tsx', () => { - test('should render the component', () => { - const date: Date = new Date(); - render( - <Toast - type={ToastType.SUCCESS} - title="Spread" - description="Love" - createdAt={date} - /> - ); - expect(screen.getByTestId('toast-main')).toHaveClass( - 'position-fixed top-0 end-0 p-3' - ); - }); +describe("Test for Toast.tsx", () => { + test("should render the component", () => { + const date: Date = new Date(); + render( + <Toast + type={ToastType.SUCCESS} + title="Spread" + description="Love" + createdAt={date} + />, + ); + expect(screen.getByTestId("toast-main")).toHaveClass( + "position-fixed top-0 end-0 p-3", + ); + }); - test('Checking Title', () => { - const date: Date = new Date(); - render( - <Toast - type={ToastType.SUCCESS} - title="Spread" - description="Love" - createdAt={date} - /> - ); - expect(screen.getByTestId('toast-strong')).toHaveTextContent('Spread'); - }); + test("Checking Title", () => { + const date: Date = new Date(); + render( + <Toast + type={ToastType.SUCCESS} + title="Spread" + description="Love" + createdAt={date} + />, + ); + expect(screen.getByTestId("toast-strong")).toHaveTextContent("Spread"); + }); - test('Checking Description', () => { - const date: Date = new Date(); - render( - <Toast - type={ToastType.SUCCESS} - title="Spread" - description="Love" - createdAt={date} - /> - ); - expect(screen.getByTestId('toast-desc')).toHaveTextContent('Love'); - }); - test('Should say "seconds ago"', () => { - const date: Date = new Date(); - render( - <Toast - type={ToastType.SUCCESS} - title="Spread" - description="Love" - createdAt={date} - /> - ); - const now_date: string = OneUptimeDate.fromNow(date); + test("Checking Description", () => { + const date: Date = new Date(); + render( + <Toast + type={ToastType.SUCCESS} + title="Spread" + description="Love" + createdAt={date} + />, + ); + expect(screen.getByTestId("toast-desc")).toHaveTextContent("Love"); + }); + test('Should say "seconds ago"', () => { + const date: Date = new Date(); + render( + <Toast + type={ToastType.SUCCESS} + title="Spread" + description="Love" + createdAt={date} + />, + ); + const now_date: string = OneUptimeDate.fromNow(date); - expect(screen.getByTestId('toast-time')).toHaveTextContent(now_date); - }); - test('Checking if Toast is for SUCCESS', () => { - const date: Date = new Date(); - render( - <Toast - type={ToastType.SUCCESS} - title="Spread" - description="Love" - createdAt={date} - /> - ); - expect(screen.getByTestId('toast-status')).toHaveClass('text-success'); - }); - test('Checking if Toast is for INFO', () => { - const date: Date = new Date(); - render( - <Toast - type={ToastType.INFO} - title="Spread" - description="Love" - createdAt={date} - /> - ); - expect(screen.getByTestId('toast-status')).toHaveClass('text-info'); - }); + expect(screen.getByTestId("toast-time")).toHaveTextContent(now_date); + }); + test("Checking if Toast is for SUCCESS", () => { + const date: Date = new Date(); + render( + <Toast + type={ToastType.SUCCESS} + title="Spread" + description="Love" + createdAt={date} + />, + ); + expect(screen.getByTestId("toast-status")).toHaveClass("text-success"); + }); + test("Checking if Toast is for INFO", () => { + const date: Date = new Date(); + render( + <Toast + type={ToastType.INFO} + title="Spread" + description="Love" + createdAt={date} + />, + ); + expect(screen.getByTestId("toast-status")).toHaveClass("text-info"); + }); - test('Checking if Toast is for Warning', () => { - const date: Date = new Date(); - render( - <Toast - type={ToastType.WARNING} - title="Spread" - description="Love" - createdAt={date} - /> - ); - expect(screen.getByTestId('toast-status')).toHaveClass('text-warning'); - }); + test("Checking if Toast is for Warning", () => { + const date: Date = new Date(); + render( + <Toast + type={ToastType.WARNING} + title="Spread" + description="Love" + createdAt={date} + />, + ); + expect(screen.getByTestId("toast-status")).toHaveClass("text-warning"); + }); - test('Checking if Toast is for Normal', () => { - const date: Date = new Date(); - render( - <Toast - type={ToastType.NORMAL} - title="Spread" - description="Love" - createdAt={date} - /> - ); - expect(screen.getByTestId('toast-status')).toHaveClass('text-normal'); - }); - test('simulates button click and sets the state to flase, closing the toast', async () => { - const date: Date = new Date(); - const user: UserEvent = userEvent.setup(); + test("Checking if Toast is for Normal", () => { + const date: Date = new Date(); + render( + <Toast + type={ToastType.NORMAL} + title="Spread" + description="Love" + createdAt={date} + />, + ); + expect(screen.getByTestId("toast-status")).toHaveClass("text-normal"); + }); + test("simulates button click and sets the state to flase, closing the toast", async () => { + const date: Date = new Date(); + const user: UserEvent = userEvent.setup(); - render( - <Toast - type={ToastType.SUCCESS} - title="Spread" - description="Love" - createdAt={date} - /> - ); + render( + <Toast + type={ToastType.SUCCESS} + title="Spread" + description="Love" + createdAt={date} + />, + ); - const loginButton: HTMLButtonElement = - screen.getByTestId('toast-button'); - await user.click(loginButton); + const loginButton: HTMLButtonElement = screen.getByTestId("toast-button"); + await user.click(loginButton); - expect(screen.queryByTestId('toast-main')).toBeFalsy(); - }); + expect(screen.queryByTestId("toast-main")).toBeFalsy(); + }); }); diff --git a/CommonUI/src/Tests/Components/Toggle.test.tsx b/CommonUI/src/Tests/Components/Toggle.test.tsx index ca073274e8..c1b6181b09 100644 --- a/CommonUI/src/Tests/Components/Toggle.test.tsx +++ b/CommonUI/src/Tests/Components/Toggle.test.tsx @@ -1,176 +1,168 @@ -import Toggle from '../../Components/Toggle/Toggle'; -import '@testing-library/jest-dom/extend-expect'; -import { fireEvent, render } from '@testing-library/react'; -import React from 'react'; +import Toggle from "../../Components/Toggle/Toggle"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render } from "@testing-library/react"; +import React from "react"; -describe('Toggle', () => { - test('renders toggle element with required props only', () => { - const { getByRole } = render( - <Toggle onChange={() => {}} initialValue={false} /> - ); - const toggle: HTMLElement = getByRole('switch'); +describe("Toggle", () => { + test("renders toggle element with required props only", () => { + const { getByRole } = render( + <Toggle onChange={() => {}} initialValue={false} />, + ); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toBeInTheDocument(); - }); + expect(toggle).toBeInTheDocument(); + }); - test('renders toggle element with all props', () => { - const { getByRole } = render( - <Toggle - onChange={() => {}} - onFocus={() => {}} - onBlur={() => {}} - initialValue={false} - tabIndex={1} - title="title" - description="description" - error="error" - /> - ); - const toggle: HTMLElement = getByRole('switch'); + test("renders toggle element with all props", () => { + const { getByRole } = render( + <Toggle + onChange={() => {}} + onFocus={() => {}} + onBlur={() => {}} + initialValue={false} + tabIndex={1} + title="title" + description="description" + error="error" + />, + ); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toBeInTheDocument(); - }); + expect(toggle).toBeInTheDocument(); + }); - test('calls onChange', () => { - const onChange: jest.Mock = jest.fn(); + test("calls onChange", () => { + const onChange: jest.Mock = jest.fn(); - const { getByRole } = render( - <Toggle onChange={onChange} initialValue={false} /> - ); - const toggle: HTMLElement = getByRole('switch'); - fireEvent.click(toggle); + const { getByRole } = render( + <Toggle onChange={onChange} initialValue={false} />, + ); + const toggle: HTMLElement = getByRole("switch"); + fireEvent.click(toggle); - expect(onChange).toHaveBeenCalled(); - expect(onChange).toHaveBeenCalledWith(true); - }); + expect(onChange).toHaveBeenCalled(); + expect(onChange).toHaveBeenCalledWith(true); + }); - test('calls onFocus', () => { - const onFocus: jest.Mock = jest.fn(); + test("calls onFocus", () => { + const onFocus: jest.Mock = jest.fn(); - const { getByRole } = render( - <Toggle - onFocus={onFocus} - initialValue={false} - onChange={() => {}} - /> - ); - const toggle: HTMLElement = getByRole('switch'); - fireEvent.focus(toggle); + const { getByRole } = render( + <Toggle onFocus={onFocus} initialValue={false} onChange={() => {}} />, + ); + const toggle: HTMLElement = getByRole("switch"); + fireEvent.focus(toggle); - expect(onFocus).toHaveBeenCalledTimes(1); - }); + expect(onFocus).toHaveBeenCalledTimes(1); + }); - test('calls onBlur', () => { - const onBlur: jest.Mock = jest.fn(); + test("calls onBlur", () => { + const onBlur: jest.Mock = jest.fn(); - const { getByRole } = render( - <Toggle onBlur={onBlur} initialValue={false} onChange={() => {}} /> - ); - const toggle: HTMLElement = getByRole('switch'); - fireEvent.blur(toggle); + const { getByRole } = render( + <Toggle onBlur={onBlur} initialValue={false} onChange={() => {}} />, + ); + const toggle: HTMLElement = getByRole("switch"); + fireEvent.blur(toggle); - expect(onBlur).toHaveBeenCalledTimes(1); - }); + expect(onBlur).toHaveBeenCalledTimes(1); + }); - test('displays error', () => { - const { getByText } = render( - <Toggle onChange={() => {}} initialValue={false} error="error" /> - ); + test("displays error", () => { + const { getByText } = render( + <Toggle onChange={() => {}} initialValue={false} error="error" />, + ); - expect(getByText('error')).toBeInTheDocument(); - }); + expect(getByText("error")).toBeInTheDocument(); + }); - test('displays title', () => { - const { getByText } = render( - <Toggle - onChange={() => {}} - initialValue={false} - title="title" - description="description" - /> - ); + test("displays title", () => { + const { getByText } = render( + <Toggle + onChange={() => {}} + initialValue={false} + title="title" + description="description" + />, + ); - expect(getByText('title')).toBeInTheDocument(); - }); + expect(getByText("title")).toBeInTheDocument(); + }); - test('displays description', () => { - const { getByText } = render( - <Toggle - onChange={() => {}} - initialValue={false} - description="description" - /> - ); - expect(getByText('description')).toBeInTheDocument(); - }); + test("displays description", () => { + const { getByText } = render( + <Toggle + onChange={() => {}} + initialValue={false} + description="description" + />, + ); + expect(getByText("description")).toBeInTheDocument(); + }); - test('sets tabIndex', () => { - const { getByRole } = render( - <Toggle onChange={() => {}} initialValue={false} tabIndex={1} /> - ); - const toggle: HTMLElement = getByRole('switch'); + test("sets tabIndex", () => { + const { getByRole } = render( + <Toggle onChange={() => {}} initialValue={false} tabIndex={1} />, + ); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toHaveAttribute('tabindex', '1'); - }); + expect(toggle).toHaveAttribute("tabindex", "1"); + }); - test('sets initial value', () => { - const { getByRole } = render( - <Toggle onChange={() => {}} initialValue={true} /> - ); - const toggle: HTMLElement = getByRole('switch'); + test("sets initial value", () => { + const { getByRole } = render( + <Toggle onChange={() => {}} initialValue={true} />, + ); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toHaveAttribute('aria-checked', 'true'); - }); + expect(toggle).toHaveAttribute("aria-checked", "true"); + }); - test('sets initial value to false', () => { - const { getByRole } = render( - <Toggle onChange={() => {}} initialValue={false} /> - ); - const toggle: HTMLElement = getByRole('switch'); + test("sets initial value to false", () => { + const { getByRole } = render( + <Toggle onChange={() => {}} initialValue={false} />, + ); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toHaveAttribute('aria-checked', 'false'); - }); + expect(toggle).toHaveAttribute("aria-checked", "false"); + }); - test('sets initial value to undefined', () => { - const { getByRole } = render(<Toggle onChange={() => {}} />); - const toggle: HTMLElement = getByRole('switch'); + test("sets initial value to undefined", () => { + const { getByRole } = render(<Toggle onChange={() => {}} />); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toHaveAttribute('aria-checked', 'false'); - }); + expect(toggle).toHaveAttribute("aria-checked", "false"); + }); - test('sets value', () => { - const { getByRole } = render( - <Toggle onChange={() => {}} value={true} /> - ); - const toggle: HTMLElement = getByRole('switch'); + test("sets value", () => { + const { getByRole } = render(<Toggle onChange={() => {}} value={true} />); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toHaveAttribute('aria-checked', 'true'); - }); + expect(toggle).toHaveAttribute("aria-checked", "true"); + }); - test('sets value to false', () => { - const { getByRole } = render( - <Toggle onChange={() => {}} value={false} /> - ); - const toggle: HTMLElement = getByRole('switch'); + test("sets value to false", () => { + const { getByRole } = render(<Toggle onChange={() => {}} value={false} />); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toHaveAttribute('aria-checked', 'false'); - }); + expect(toggle).toHaveAttribute("aria-checked", "false"); + }); - test('sets value to undefined', () => { - const { getByRole } = render(<Toggle onChange={() => {}} />); - const toggle: HTMLElement = getByRole('switch'); + test("sets value to undefined", () => { + const { getByRole } = render(<Toggle onChange={() => {}} />); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toHaveAttribute('aria-checked', 'false'); - }); + expect(toggle).toHaveAttribute("aria-checked", "false"); + }); - test('styles toggle correctly', () => { - const { getByRole } = render( - <Toggle onChange={() => {}} initialValue={false} /> - ); - const toggle: HTMLElement = getByRole('switch'); + test("styles toggle correctly", () => { + const { getByRole } = render( + <Toggle onChange={() => {}} initialValue={false} />, + ); + const toggle: HTMLElement = getByRole("switch"); - expect(toggle).toHaveClass('bg-gray-200'); - fireEvent.click(toggle); - expect(toggle).toHaveClass('bg-indigo-600'); - }); + expect(toggle).toHaveClass("bg-gray-200"); + fireEvent.click(toggle); + expect(toggle).toHaveClass("bg-indigo-600"); + }); }); diff --git a/CommonUI/src/Tests/Components/TopSection.test.tsx b/CommonUI/src/Tests/Components/TopSection.test.tsx index af3f8af39e..721e3085bf 100644 --- a/CommonUI/src/Tests/Components/TopSection.test.tsx +++ b/CommonUI/src/Tests/Components/TopSection.test.tsx @@ -1,48 +1,45 @@ import TopSection, { - ComponentProps, -} from '../../Components/TopSection/TopSection'; -import { describe, expect, it } from '@jest/globals'; -import '@testing-library/jest-dom/extend-expect'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; + ComponentProps, +} from "../../Components/TopSection/TopSection"; +import { describe, expect, it } from "@jest/globals"; +import "@testing-library/jest-dom/extend-expect"; +import { render, screen } from "@testing-library/react"; +import React from "react"; -describe('TopSection', () => { - const defaultProps: ComponentProps = { - header: <div>Header</div>, - navbar: <div>Navbar</div>, - }; - const defaultClassName: string = 'bg-white shadow'; +describe("TopSection", () => { + const defaultProps: ComponentProps = { + header: <div>Header</div>, + navbar: <div>Navbar</div>, + }; + const defaultClassName: string = "bg-white shadow"; - it('should render correctly', () => { - render(<TopSection {...defaultProps} />); + it("should render correctly", () => { + render(<TopSection {...defaultProps} />); - const banner: HTMLElement = screen.getByRole('banner'); - expect(banner).toHaveClass(defaultClassName); + const banner: HTMLElement = screen.getByRole("banner"); + expect(banner).toHaveClass(defaultClassName); - const header: HTMLElement = screen.getByText('Header'); - expect(header).toBeInTheDocument(); + const header: HTMLElement = screen.getByText("Header"); + expect(header).toBeInTheDocument(); - const navbar: HTMLElement = screen.getByText('Navbar'); - expect(navbar).toBeInTheDocument(); - }); + const navbar: HTMLElement = screen.getByText("Navbar"); + expect(navbar).toBeInTheDocument(); + }); - it('should render correctly with custom className', () => { - const customClassName: string = 'customClassName'; - render( - <TopSection - {...defaultProps} - className={customClassName} - ></TopSection> - ); + it("should render correctly with custom className", () => { + const customClassName: string = "customClassName"; + render( + <TopSection {...defaultProps} className={customClassName}></TopSection>, + ); - const banner: HTMLElement = screen.getByRole('banner'); - expect(banner).toHaveClass(customClassName); - }); + const banner: HTMLElement = screen.getByRole("banner"); + expect(banner).toHaveClass(customClassName); + }); - it('should render correctly with hideHeader', () => { - render(<TopSection {...defaultProps} hideHeader />); + it("should render correctly with hideHeader", () => { + render(<TopSection {...defaultProps} hideHeader />); - const header: HTMLElement | null = screen.queryByText('Header'); - expect(header).not.toBeInTheDocument(); - }); + const header: HTMLElement | null = screen.queryByText("Header"); + expect(header).not.toBeInTheDocument(); + }); }); diff --git a/CommonUI/src/Tests/Index.tsx b/CommonUI/src/Tests/Index.tsx index d1888315f0..667191404f 100644 --- a/CommonUI/src/Tests/Index.tsx +++ b/CommonUI/src/Tests/Index.tsx @@ -1 +1 @@ -import 'jest-environment-jsdom'; +import "jest-environment-jsdom"; diff --git a/CommonUI/src/Types/AlignItem.ts b/CommonUI/src/Types/AlignItem.ts index 0fdcecaeae..c96e9c8bd5 100644 --- a/CommonUI/src/Types/AlignItem.ts +++ b/CommonUI/src/Types/AlignItem.ts @@ -1,7 +1,7 @@ enum AlignItem { - Left = 'Left', - Right = 'Right', - Center = 'Center', + Left = "Left", + Right = "Right", + Center = "Center", } export default AlignItem; diff --git a/CommonUI/src/Types/EntityFieldType.ts b/CommonUI/src/Types/EntityFieldType.ts index 1dc6b046de..cb7994c1ad 100644 --- a/CommonUI/src/Types/EntityFieldType.ts +++ b/CommonUI/src/Types/EntityFieldType.ts @@ -1,20 +1,20 @@ -import Hostname from 'Common/Types/API/Hostname'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Email from 'Common/Types/Email'; -import Name from 'Common/Types/Name'; -import ObjectID from 'Common/Types/ObjectID'; +import Hostname from "Common/Types/API/Hostname"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Email from "Common/Types/Email"; +import Name from "Common/Types/Name"; +import ObjectID from "Common/Types/ObjectID"; type FormFieldType = - | string - | number - | boolean - | ObjectID - | Hostname - | Email - | Date - | Name - | Route - | URL; + | string + | number + | boolean + | ObjectID + | Hostname + | Email + | Date + | Name + | Route + | URL; export default FormFieldType; diff --git a/CommonUI/src/Types/FunctionTypes.ts b/CommonUI/src/Types/FunctionTypes.ts index 4d4f0a4ba3..9f97bbd041 100644 --- a/CommonUI/src/Types/FunctionTypes.ts +++ b/CommonUI/src/Types/FunctionTypes.ts @@ -1,4 +1,4 @@ -import { ReactElement } from 'react'; +import { ReactElement } from "react"; export type GetReactElementFunction = () => ReactElement; diff --git a/CommonUI/src/Types/HtmlEvents.ts b/CommonUI/src/Types/HtmlEvents.ts index eb2ecaa3df..2aad41bfdd 100644 --- a/CommonUI/src/Types/HtmlEvents.ts +++ b/CommonUI/src/Types/HtmlEvents.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; export type MouseEventProp = React.MouseEvent<HTMLElement>; export type MouseOnClick = (e?: MouseEventProp) => void; diff --git a/CommonUI/src/Types/RequiredEntityFields.ts b/CommonUI/src/Types/RequiredEntityFields.ts index ab3ec1593a..34091fc7a7 100644 --- a/CommonUI/src/Types/RequiredEntityFields.ts +++ b/CommonUI/src/Types/RequiredEntityFields.ts @@ -1,11 +1,11 @@ -import FormFieldSchemaTypes from './EntityFieldType'; +import FormFieldSchemaTypes from "./EntityFieldType"; type RequiredFormField<Property> = Property extends FormFieldSchemaTypes - ? boolean - : unknown; + ? boolean + : unknown; declare type RequiredFormFields<Entity> = { - [P in keyof Entity]?: RequiredFormField<NonNullable<Entity[P]>>; + [P in keyof Entity]?: RequiredFormField<NonNullable<Entity[P]>>; }; export default RequiredFormFields; diff --git a/CommonUI/src/Types/SelectEntityField.ts b/CommonUI/src/Types/SelectEntityField.ts index 3043b80ba4..652a251195 100644 --- a/CommonUI/src/Types/SelectEntityField.ts +++ b/CommonUI/src/Types/SelectEntityField.ts @@ -1,11 +1,11 @@ -import FormFieldSchemaTypes from './EntityFieldType'; +import FormFieldSchemaTypes from "./EntityFieldType"; export type SelectFormField<Property> = Property extends FormFieldSchemaTypes - ? boolean - : unknown; + ? boolean + : unknown; declare type SelectFormFields<Entity> = { - [P in keyof Entity]?: SelectFormField<NonNullable<Entity[P]>>; + [P in keyof Entity]?: SelectFormField<NonNullable<Entity[P]>>; }; export default SelectFormFields; diff --git a/CommonUI/src/Types/UseComponentOutsideClick.ts b/CommonUI/src/Types/UseComponentOutsideClick.ts index 38fbc854ab..de8a925dde 100644 --- a/CommonUI/src/Types/UseComponentOutsideClick.ts +++ b/CommonUI/src/Types/UseComponentOutsideClick.ts @@ -1,46 +1,42 @@ import React, { - MouseEvent, - MouseEventHandler, - useEffect, - useRef, - useState, -} from 'react'; + MouseEvent, + MouseEventHandler, + useEffect, + useRef, + useState, +} from "react"; type UseComponentOutsideClickFunction = (isVisible: boolean) => { - ref: any; - isComponentVisible: boolean; - setIsComponentVisible: React.Dispatch<React.SetStateAction<boolean>>; + ref: any; + isComponentVisible: boolean; + setIsComponentVisible: React.Dispatch<React.SetStateAction<boolean>>; }; const useComponentOutsideClick: UseComponentOutsideClickFunction = ( - isVisible: boolean + isVisible: boolean, ): { - ref: any; - isComponentVisible: boolean; - setIsComponentVisible: React.Dispatch<React.SetStateAction<boolean>>; + ref: any; + isComponentVisible: boolean; + setIsComponentVisible: React.Dispatch<React.SetStateAction<boolean>>; } => { - const [isComponentVisible, setIsComponentVisible] = - useState<boolean>(isVisible); - const ref: any = useRef<any>(null); + const [isComponentVisible, setIsComponentVisible] = + useState<boolean>(isVisible); + const ref: any = useRef<any>(null); - const handleClickOutside: MouseEventHandler = (event: MouseEvent) => { - if (ref.current && !ref.current.contains(event.target)) { - setIsComponentVisible(false); - } + const handleClickOutside: MouseEventHandler = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target)) { + setIsComponentVisible(false); + } + }; + + useEffect(() => { + document.addEventListener("click", handleClickOutside as any, true); + return () => { + document.removeEventListener("click", handleClickOutside as any, true); }; + }, []); - useEffect(() => { - document.addEventListener('click', handleClickOutside as any, true); - return () => { - document.removeEventListener( - 'click', - handleClickOutside as any, - true - ); - }; - }, []); - - return { ref, isComponentVisible, setIsComponentVisible }; + return { ref, isComponentVisible, setIsComponentVisible }; }; export default useComponentOutsideClick; diff --git a/CommonUI/src/Utils/API/API.ts b/CommonUI/src/Utils/API/API.ts index 92f045f2a0..63fa68cb89 100644 --- a/CommonUI/src/Utils/API/API.ts +++ b/CommonUI/src/Utils/API/API.ts @@ -1,150 +1,148 @@ -import LocalStorage from '../LocalStorage'; -import Navigation from '../Navigation'; -import PermissionUtil from '../Permission'; -import User from '../User'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import Headers from 'Common/Types/API/Headers'; -import Hostname from 'Common/Types/API/Hostname'; -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import APIException from 'Common/Types/Exception/ApiException'; -import Exception from 'Common/Types/Exception/Exception'; -import JSONFunctions from 'Common/Types/JSONFunctions'; +import LocalStorage from "../LocalStorage"; +import Navigation from "../Navigation"; +import PermissionUtil from "../Permission"; +import User from "../User"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import Headers from "Common/Types/API/Headers"; +import Hostname from "Common/Types/API/Hostname"; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import APIException from "Common/Types/Exception/ApiException"; +import Exception from "Common/Types/Exception/Exception"; +import JSONFunctions from "Common/Types/JSONFunctions"; import { - UserGlobalAccessPermission, - UserTenantAccessPermission, -} from 'Common/Types/Permission'; -import API from 'Common/Utils/API'; -import Cookies from 'universal-cookie'; + UserGlobalAccessPermission, + UserTenantAccessPermission, +} from "Common/Types/Permission"; +import API from "Common/Utils/API"; +import Cookies from "universal-cookie"; class BaseAPI extends API { - public constructor(protocol: Protocol, hostname: Hostname, route?: Route) { - super(protocol, hostname, route); + public constructor(protocol: Protocol, hostname: Hostname, route?: Route) { + super(protocol, hostname, route); + } + + public static fromURL(url: URL): BaseAPI { + return new BaseAPI(url.protocol, url.hostname, url.route); + } + + protected static override async onResponseSuccessHeaders( + headers: Dictionary<string>, + ): Promise<Dictionary<string>> { + if (headers && headers["global-permissions"]) { + PermissionUtil.setGlobalPermissions( + JSONFunctions.deserialize( + JSONFunctions.parseJSONObject(headers["global-permissions"]), + ) as UserGlobalAccessPermission, + ); } - public static fromURL(url: URL): BaseAPI { - return new BaseAPI(url.protocol, url.hostname, url.route); + if (headers && headers["global-permissions-hash"]) { + LocalStorage.setItem( + "global-permissions-hash", + headers["global-permissions-hash"], + ); } - protected static override async onResponseSuccessHeaders( - headers: Dictionary<string> - ): Promise<Dictionary<string>> { - if (headers && headers['global-permissions']) { - PermissionUtil.setGlobalPermissions( - JSONFunctions.deserialize( - JSONFunctions.parseJSONObject(headers['global-permissions']) - ) as UserGlobalAccessPermission - ); - } - - if (headers && headers['global-permissions-hash']) { - LocalStorage.setItem( - 'global-permissions-hash', - headers['global-permissions-hash'] - ); - } - - if (headers && headers['project-permissions']) { - PermissionUtil.setProjectPermissions( - JSONFunctions.deserialize( - JSONFunctions.parseJSONObject( - headers['project-permissions'] - ) - ) as UserTenantAccessPermission - ); - } - - if (headers && headers['project-permissions-hash']) { - LocalStorage.setItem( - 'project-permissions-hash', - headers['project-permissions-hash'] - ); - } - - return Promise.resolve(headers); + if (headers && headers["project-permissions"]) { + PermissionUtil.setProjectPermissions( + JSONFunctions.deserialize( + JSONFunctions.parseJSONObject(headers["project-permissions"]), + ) as UserTenantAccessPermission, + ); } - protected static override getHeaders(): Headers { - let defaultHeaders: Headers = this.getDefaultHeaders(); - - const headers: Headers = {}; - - const globalPermissionsHash: string = LocalStorage.getItem( - 'global-permissions-hash' - ) as string; - if (globalPermissionsHash) { - headers['global-permissions-hash'] = globalPermissionsHash; - } - - const projectPermissionsHash: string = LocalStorage.getItem( - 'project-permissions-hash' - ) as string; - - if (projectPermissionsHash) { - headers['project-permissions-hash'] = projectPermissionsHash; - } - - defaultHeaders = { - ...defaultHeaders, - ...headers, - }; - - return defaultHeaders; + if (headers && headers["project-permissions-hash"]) { + LocalStorage.setItem( + "project-permissions-hash", + headers["project-permissions-hash"], + ); } - protected static override handleError( - error: HTTPErrorResponse | APIException - ): HTTPErrorResponse | APIException { - // 405 Status - Tenant not found. If Project was deleted. - // 401 Status - User is not logged in. - if ( - error instanceof HTTPErrorResponse && - (error.statusCode === 401 || error.statusCode === 405) - ) { - const loginRoute: Route = this.getLoginRoute(); + return Promise.resolve(headers); + } - const cookies: Cookies = new Cookies(); - cookies.remove('admin-data', { path: '/' }); - cookies.remove('data', { path: '/' }); - User.clear(); + protected static override getHeaders(): Headers { + let defaultHeaders: Headers = this.getDefaultHeaders(); - if (Navigation.getQueryStringByName('token')) { - Navigation.navigate(loginRoute.addRouteParam('sso', 'true'), { - forceNavigate: true, - }); - } else { - Navigation.navigate(loginRoute, { - forceNavigate: true, - }); - } - } + const headers: Headers = {}; - return error; + const globalPermissionsHash: string = LocalStorage.getItem( + "global-permissions-hash", + ) as string; + if (globalPermissionsHash) { + headers["global-permissions-hash"] = globalPermissionsHash; } - protected static getLoginRoute(): Route { - return new Route('/accounts/login'); + const projectPermissionsHash: string = LocalStorage.getItem( + "project-permissions-hash", + ) as string; + + if (projectPermissionsHash) { + headers["project-permissions-hash"] = projectPermissionsHash; } - public static getFriendlyMessage( - err: HTTPErrorResponse | Exception | unknown - ): string { - if (err instanceof HTTPErrorResponse) { - if (err.statusCode === 502) { - return 'Error connecting to server. Please try again in few minutes.'; - } + defaultHeaders = { + ...defaultHeaders, + ...headers, + }; - if (err.statusCode === 504) { - return 'Error connecting to server. Please try again in few minutes.'; - } + return defaultHeaders; + } - return err.message; - } + protected static override handleError( + error: HTTPErrorResponse | APIException, + ): HTTPErrorResponse | APIException { + // 405 Status - Tenant not found. If Project was deleted. + // 401 Status - User is not logged in. + if ( + error instanceof HTTPErrorResponse && + (error.statusCode === 401 || error.statusCode === 405) + ) { + const loginRoute: Route = this.getLoginRoute(); - return err?.toString() || 'Server Error. Please try again'; + const cookies: Cookies = new Cookies(); + cookies.remove("admin-data", { path: "/" }); + cookies.remove("data", { path: "/" }); + User.clear(); + + if (Navigation.getQueryStringByName("token")) { + Navigation.navigate(loginRoute.addRouteParam("sso", "true"), { + forceNavigate: true, + }); + } else { + Navigation.navigate(loginRoute, { + forceNavigate: true, + }); + } } + + return error; + } + + protected static getLoginRoute(): Route { + return new Route("/accounts/login"); + } + + public static getFriendlyMessage( + err: HTTPErrorResponse | Exception | unknown, + ): string { + if (err instanceof HTTPErrorResponse) { + if (err.statusCode === 502) { + return "Error connecting to server. Please try again in few minutes."; + } + + if (err.statusCode === 504) { + return "Error connecting to server. Please try again in few minutes."; + } + + return err.message; + } + + return err?.toString() || "Server Error. Please try again"; + } } export default BaseAPI; diff --git a/CommonUI/src/Utils/API/ApiDocsAPI.ts b/CommonUI/src/Utils/API/ApiDocsAPI.ts index 08084ca806..d9598c23f7 100644 --- a/CommonUI/src/Utils/API/ApiDocsAPI.ts +++ b/CommonUI/src/Utils/API/ApiDocsAPI.ts @@ -1,11 +1,11 @@ -import { API_DOCS_HOSTNAME, HTTP_PROTOCOL } from '../../Config'; -import { ApiReferenceRoute } from 'Common/ServiceRoute'; -import API from 'Common/Utils/API'; +import { API_DOCS_HOSTNAME, HTTP_PROTOCOL } from "../../Config"; +import { ApiReferenceRoute } from "Common/ServiceRoute"; +import API from "Common/Utils/API"; class ApiDocsRoute extends API { - public constructor() { - super(HTTP_PROTOCOL, API_DOCS_HOSTNAME, ApiReferenceRoute); - } + public constructor() { + super(HTTP_PROTOCOL, API_DOCS_HOSTNAME, ApiReferenceRoute); + } } export default new ApiDocsRoute(); diff --git a/CommonUI/src/Utils/API/DashboardAPI.ts b/CommonUI/src/Utils/API/DashboardAPI.ts index b812680128..00cf27422e 100755 --- a/CommonUI/src/Utils/API/DashboardAPI.ts +++ b/CommonUI/src/Utils/API/DashboardAPI.ts @@ -1,11 +1,11 @@ -import { APP_HOSTNAME, HTTP_PROTOCOL } from '../../Config'; -import BaseAPI from './API'; -import { AppApiRoute } from 'Common/ServiceRoute'; +import { APP_HOSTNAME, HTTP_PROTOCOL } from "../../Config"; +import BaseAPI from "./API"; +import { AppApiRoute } from "Common/ServiceRoute"; class BackendAPI extends BaseAPI { - public constructor() { - super(HTTP_PROTOCOL, APP_HOSTNAME, AppApiRoute); - } + public constructor() { + super(HTTP_PROTOCOL, APP_HOSTNAME, AppApiRoute); + } } export default new BackendAPI(); diff --git a/CommonUI/src/Utils/API/DashboardFrontendAPI.ts b/CommonUI/src/Utils/API/DashboardFrontendAPI.ts index 9617a1e78a..178b7dfa4f 100644 --- a/CommonUI/src/Utils/API/DashboardFrontendAPI.ts +++ b/CommonUI/src/Utils/API/DashboardFrontendAPI.ts @@ -1,10 +1,10 @@ -import { DASHBOARD_HOSTNAME, HTTP_PROTOCOL } from '../../Config'; -import API from 'Common/Utils/API'; +import { DASHBOARD_HOSTNAME, HTTP_PROTOCOL } from "../../Config"; +import API from "Common/Utils/API"; class DashboardFrontendAPI extends API { - public constructor() { - super(HTTP_PROTOCOL, DASHBOARD_HOSTNAME); - } + public constructor() { + super(HTTP_PROTOCOL, DASHBOARD_HOSTNAME); + } } export default new DashboardFrontendAPI(); diff --git a/CommonUI/src/Utils/API/HelmAPI.ts b/CommonUI/src/Utils/API/HelmAPI.ts index eb143c307f..27b17bccc7 100644 --- a/CommonUI/src/Utils/API/HelmAPI.ts +++ b/CommonUI/src/Utils/API/HelmAPI.ts @@ -1,10 +1,10 @@ -import { HELM_HOSTNAME, HTTP_PROTOCOL } from '../../Config'; -import API from 'Common/Utils/API'; +import { HELM_HOSTNAME, HTTP_PROTOCOL } from "../../Config"; +import API from "Common/Utils/API"; class HelmAPI extends API { - public constructor() { - super(HTTP_PROTOCOL, HELM_HOSTNAME); - } + public constructor() { + super(HTTP_PROTOCOL, HELM_HOSTNAME); + } } export default new HelmAPI(); diff --git a/CommonUI/src/Utils/API/IdentityAPI.ts b/CommonUI/src/Utils/API/IdentityAPI.ts index 588471702d..dda53cb56c 100755 --- a/CommonUI/src/Utils/API/IdentityAPI.ts +++ b/CommonUI/src/Utils/API/IdentityAPI.ts @@ -1,11 +1,11 @@ -import { HTTP_PROTOCOL, IDENTITY_HOSTNAME } from '../../Config'; -import BaseAPI from './API'; -import { IdentityRoute } from 'Common/ServiceRoute'; +import { HTTP_PROTOCOL, IDENTITY_HOSTNAME } from "../../Config"; +import BaseAPI from "./API"; +import { IdentityRoute } from "Common/ServiceRoute"; class IdentityAPI extends BaseAPI { - public constructor() { - super(HTTP_PROTOCOL, IDENTITY_HOSTNAME, IdentityRoute); - } + public constructor() { + super(HTTP_PROTOCOL, IDENTITY_HOSTNAME, IdentityRoute); + } } export default new IdentityAPI(); diff --git a/CommonUI/src/Utils/API/IntegrationAPI.ts b/CommonUI/src/Utils/API/IntegrationAPI.ts index 7714e29735..fbba999252 100644 --- a/CommonUI/src/Utils/API/IntegrationAPI.ts +++ b/CommonUI/src/Utils/API/IntegrationAPI.ts @@ -1,10 +1,10 @@ -import { HTTP_PROTOCOL, INTEGRATION_HOSTNAME } from '../../Config'; -import BaseAPI from './API'; +import { HTTP_PROTOCOL, INTEGRATION_HOSTNAME } from "../../Config"; +import BaseAPI from "./API"; class BackendAPI extends BaseAPI { - public constructor() { - super(HTTP_PROTOCOL, INTEGRATION_HOSTNAME); - } + public constructor() { + super(HTTP_PROTOCOL, INTEGRATION_HOSTNAME); + } } export default new BackendAPI(); diff --git a/CommonUI/src/Utils/Analytics.ts b/CommonUI/src/Utils/Analytics.ts index 1dde2d12bf..ff540db734 100644 --- a/CommonUI/src/Utils/Analytics.ts +++ b/CommonUI/src/Utils/Analytics.ts @@ -1,5 +1,5 @@ -import { AnalyticsHost, AnalyticsKey } from '../Config'; -import Analytics from 'Common/Utils/Analytics'; +import { AnalyticsHost, AnalyticsKey } from "../Config"; +import Analytics from "Common/Utils/Analytics"; const UiAnalytics: Analytics = new Analytics(AnalyticsHost, AnalyticsKey); diff --git a/CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts b/CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts index 25f7b910dd..01462f65b6 100644 --- a/CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +++ b/CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts @@ -1,488 +1,465 @@ -import { FormType } from '../../Components/Forms/ModelForm'; -import { APP_API_URL } from '../../Config'; -import API from '../API/API'; -import GroupBy from '../BaseDatabase/GroupBy'; -import BaseListResult from '../BaseDatabase/ListResult'; -import Query from '../BaseDatabase/Query'; -import RequestOptions from '../BaseDatabase/RequestOptions'; -import Select from '../BaseDatabase/Select'; -import Sort from '../BaseDatabase/Sort'; -import Navigation from '../Navigation'; -import ProjectUtil from '../Project'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPMethod from 'Common/Types/API/HTTPMethod'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import Project from 'Model/Models/Project'; +import { FormType } from "../../Components/Forms/ModelForm"; +import { APP_API_URL } from "../../Config"; +import API from "../API/API"; +import GroupBy from "../BaseDatabase/GroupBy"; +import BaseListResult from "../BaseDatabase/ListResult"; +import Query from "../BaseDatabase/Query"; +import RequestOptions from "../BaseDatabase/RequestOptions"; +import Select from "../BaseDatabase/Select"; +import Sort from "../BaseDatabase/Sort"; +import Navigation from "../Navigation"; +import ProjectUtil from "../Project"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPMethod from "Common/Types/API/HTTPMethod"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import Project from "Model/Models/Project"; export interface ListResult<TAnalyticsBaseModel extends AnalyticsBaseModel> - extends BaseListResult<TAnalyticsBaseModel> {} + extends BaseListResult<TAnalyticsBaseModel> {} export default class ModelAPI { - public static async create< - TAnalyticsBaseModel extends AnalyticsBaseModel - >(data: { - model: TAnalyticsBaseModel; - modelType: { new (): TAnalyticsBaseModel }; - requestOptions?: RequestOptions | undefined; - }): Promise< - HTTPResponse< - | JSONObject - | JSONArray - | TAnalyticsBaseModel - | Array<TAnalyticsBaseModel> - > - > { - const { model, modelType, requestOptions } = data; + public static async create< + TAnalyticsBaseModel extends AnalyticsBaseModel, + >(data: { + model: TAnalyticsBaseModel; + modelType: { new (): TAnalyticsBaseModel }; + requestOptions?: RequestOptions | undefined; + }): Promise< + HTTPResponse< + JSONObject | JSONArray | TAnalyticsBaseModel | Array<TAnalyticsBaseModel> + > + > { + const { model, modelType, requestOptions } = data; - return await ModelAPI.createOrUpdate({ - model, - modelType, - formType: FormType.Create, - miscDataProps: {}, - requestOptions, - }); - } + return await ModelAPI.createOrUpdate({ + model, + modelType, + formType: FormType.Create, + miscDataProps: {}, + requestOptions, + }); + } - public static async update<TAnalyticsBaseModel extends AnalyticsBaseModel>( - model: TAnalyticsBaseModel, - modelType: { new (): TAnalyticsBaseModel } - ): Promise< - HTTPResponse< - | JSONObject - | JSONArray - | TAnalyticsBaseModel - | Array<TAnalyticsBaseModel> - > - > { - return await ModelAPI.createOrUpdate({ - model, - modelType, - formType: FormType.Update, - }); - } + public static async update<TAnalyticsBaseModel extends AnalyticsBaseModel>( + model: TAnalyticsBaseModel, + modelType: { new (): TAnalyticsBaseModel }, + ): Promise< + HTTPResponse< + JSONObject | JSONArray | TAnalyticsBaseModel | Array<TAnalyticsBaseModel> + > + > { + return await ModelAPI.createOrUpdate({ + model, + modelType, + formType: FormType.Update, + }); + } - public static async updateById< - TAnalyticsBaseModel extends AnalyticsBaseModel - >(args: { - modelType: { new (): TAnalyticsBaseModel }; - id: ObjectID; - data: JSONObject; - apiUrlOverride?: URL; - requestOptions?: RequestOptions; - }): Promise< - HTTPResponse< - | JSONObject - | JSONArray - | TAnalyticsBaseModel - | Array<TAnalyticsBaseModel> - > - > { - const { modelType, id, data, apiUrlOverride, requestOptions } = args; + public static async updateById< + TAnalyticsBaseModel extends AnalyticsBaseModel, + >(args: { + modelType: { new (): TAnalyticsBaseModel }; + id: ObjectID; + data: JSONObject; + apiUrlOverride?: URL; + requestOptions?: RequestOptions; + }): Promise< + HTTPResponse< + JSONObject | JSONArray | TAnalyticsBaseModel | Array<TAnalyticsBaseModel> + > + > { + const { modelType, id, data, apiUrlOverride, requestOptions } = args; - const model: AnalyticsBaseModel = new modelType(); - let apiUrl: URL | null = apiUrlOverride || null; + const model: AnalyticsBaseModel = new modelType(); + let apiUrl: URL | null = apiUrlOverride || null; - if (!apiUrl) { - const apiPath: Route | null = model.crudApiPath; - if (!apiPath) { - throw new BadDataException( - 'This model does not support create or update operations.' - ); - } - - apiUrl = URL.fromURL(APP_API_URL).addRoute(apiPath); - } - - apiUrl = apiUrl.addRoute(`/${id.toString()}`); - - const result: HTTPResponse< - | JSONObject - | JSONArray - | TAnalyticsBaseModel - | Array<TAnalyticsBaseModel> - > = await API.fetch< - | JSONObject - | JSONArray - | TAnalyticsBaseModel - | Array<TAnalyticsBaseModel> - >( - HTTPMethod.PUT, - apiUrl, - { - data: data, - }, - this.getCommonHeaders(requestOptions) + if (!apiUrl) { + const apiPath: Route | null = model.crudApiPath; + if (!apiPath) { + throw new BadDataException( + "This model does not support create or update operations.", ); + } - if (result.isSuccess()) { - return result; - } - - this.checkStatusCode(result); - - throw result; + apiUrl = URL.fromURL(APP_API_URL).addRoute(apiPath); } - public static async createOrUpdate< - TAnalyticsBaseModel extends AnalyticsBaseModel - >(data: { - model: TAnalyticsBaseModel; - modelType: { new (): TAnalyticsBaseModel }; - formType: FormType; - miscDataProps?: JSONObject; - requestOptions?: RequestOptions | undefined; - }): Promise<HTTPResponse<TAnalyticsBaseModel>> { - const { model, modelType, formType, miscDataProps, requestOptions } = - data; + apiUrl = apiUrl.addRoute(`/${id.toString()}`); - let apiUrl: URL | null = requestOptions?.overrideRequestUrl || null; - - if (!apiUrl) { - const apiPath: Route | null = model.crudApiPath; - if (!apiPath) { - throw new BadDataException( - 'This model does not support create or update operations.' - ); - } - - apiUrl = URL.fromURL(APP_API_URL).addRoute(apiPath); - } - - const httpMethod: HTTPMethod = - formType === FormType.Create ? HTTPMethod.POST : HTTPMethod.PUT; - - if (httpMethod === HTTPMethod.PUT) { - apiUrl = apiUrl.addRoute(`/${model._id}`); - } - - const apiResult: HTTPErrorResponse | HTTPResponse<TAnalyticsBaseModel> = - await API.fetch<TAnalyticsBaseModel>( - httpMethod, - apiUrl, - { - data: JSONFunctions.serialize( - AnalyticsBaseModel.toJSON(model, modelType) - ), - miscDataProps: miscDataProps || {}, - }, - { - ...this.getCommonHeaders(requestOptions), - ...(requestOptions?.requestHeaders || {}), - } - ); - - if (apiResult.isSuccess() && apiResult instanceof HTTPResponse) { - const result: HTTPResponse<TAnalyticsBaseModel> = - apiResult as HTTPResponse<TAnalyticsBaseModel>; - - result.data = AnalyticsBaseModel.fromJSON( - result.data, - modelType - ) as TAnalyticsBaseModel; - - return result; - } - - this.checkStatusCode(apiResult); - - throw apiResult; - } - - public static async getList< - TAnalyticsBaseModel extends AnalyticsBaseModel - >(data: { - modelType: { new (): TAnalyticsBaseModel }; - query: Query<TAnalyticsBaseModel>; - groupBy?: GroupBy<TAnalyticsBaseModel> | undefined; - limit: number; - skip: number; - select: Select<TAnalyticsBaseModel>; - sort: Sort<TAnalyticsBaseModel>; - requestOptions?: RequestOptions | undefined; - }): Promise<ListResult<TAnalyticsBaseModel>> { - const { - modelType, - query, - limit, - skip, - select, - sort, - requestOptions, - groupBy, - } = data; - - const model: TAnalyticsBaseModel = new modelType(); - const apiPath: Route | null = model.crudApiPath; - if (!apiPath) { - throw new BadDataException( - 'This model does not support list operations.' - ); - } - - let apiUrl: URL = URL.fromURL(APP_API_URL) - .addRoute(apiPath) - .addRoute('/get-list'); - - if (requestOptions?.overrideRequestUrl) { - apiUrl = requestOptions.overrideRequestUrl; - } - - if (!apiUrl) { - throw new BadDataException( - 'This model does not support list operations.' - ); - } - - const headers: Dictionary<string> = - this.getCommonHeaders(requestOptions); - - const result: HTTPResponse<JSONArray> | HTTPErrorResponse = - await API.fetch<JSONArray>( - HTTPMethod.POST, - apiUrl, - { - query: JSONFunctions.serialize(query as JSONObject), - select: JSONFunctions.serialize(select as JSONObject), - sort: JSONFunctions.serialize(sort as JSONObject), - groupBy: JSONFunctions.serialize(groupBy as JSONObject), - }, - headers, - { - limit: limit.toString(), - skip: skip.toString(), - } - ); - - if (result.isSuccess()) { - const list: Array<TAnalyticsBaseModel> = - AnalyticsBaseModel.fromJSONArray( - result.data as JSONArray, - modelType - ); - - return { - data: list, - count: result.count, - skip: result.skip, - limit: result.limit, - }; - } - - this.checkStatusCode(result); - - throw result; - } - - public static async count<TAnalyticsBaseModel extends AnalyticsBaseModel>( - modelType: { new (): TAnalyticsBaseModel }, - query: Query<TAnalyticsBaseModel>, - requestOptions?: RequestOptions | undefined - ): Promise<number> { - const model: TAnalyticsBaseModel = new modelType(); - const apiPath: Route | null = model.crudApiPath; - if (!apiPath) { - throw new BadDataException( - 'This model does not support list operations.' - ); - } - - let apiUrl: URL = URL.fromURL(APP_API_URL) - .addRoute(apiPath) - .addRoute('/count'); - - if (requestOptions?.overrideRequestUrl) { - apiUrl = requestOptions.overrideRequestUrl; - } - - if (!apiUrl) { - throw new BadDataException( - 'This model does not support count operations.' - ); - } - - const headers: Dictionary<string> = - this.getCommonHeaders(requestOptions); - - const result: HTTPResponse<JSONObject> | HTTPErrorResponse = - await API.fetch<JSONObject>( - HTTPMethod.POST, - apiUrl, - { - query: JSONFunctions.serialize(query as JSONObject), - }, - headers - ); - - if (result.isSuccess()) { - const count: number = result.data['count'] as number; - - return count; - } - - this.checkStatusCode(result); - - throw result; - } - - public static getCommonHeaders( - requestOptions?: RequestOptions - ): Dictionary<string> { - let headers: Dictionary<string> = {}; - - if (!requestOptions || Object.keys(requestOptions).length === 0) { - const project: Project | null = ProjectUtil.getCurrentProject(); - - if (project && project.id) { - headers['tenantid'] = project.id.toString(); - } - } - - // add SSO headers. - - headers = { - ...headers, - }; - - return headers; - } - - public static async getItem< - TAnalyticsBaseModel extends AnalyticsBaseModel - >(data: { - modelType: { new (): TAnalyticsBaseModel }; - id: ObjectID; - select: Select<TAnalyticsBaseModel>; - requestOptions?: RequestOptions | undefined; - }): Promise<TAnalyticsBaseModel | null> { - const { modelType, id, select, requestOptions } = data; - - const apiPath: Route | null = new modelType().crudApiPath; - if (!apiPath) { - throw new BadDataException( - 'This model does not support get operations.' - ); - } - - let apiUrl: URL = URL.fromURL(APP_API_URL) - .addRoute(apiPath) - .addRoute('/' + id.toString()) - .addRoute('/get-item'); - - if (requestOptions?.overrideRequestUrl) { - apiUrl = requestOptions.overrideRequestUrl; - } - - if (!apiUrl) { - throw new BadDataException( - 'This model does not support get operations.' - ); - } - - return this.post<TAnalyticsBaseModel>( - modelType, - apiUrl, - select, - requestOptions - ); - } - - public static async post<TAnalyticsBaseModel extends AnalyticsBaseModel>( - modelType: { new (): TAnalyticsBaseModel }, - apiUrl: URL, - select?: Select<TAnalyticsBaseModel> | undefined, - requestOptions?: RequestOptions | undefined - ): Promise<TAnalyticsBaseModel | null> { - const result: HTTPResponse<TAnalyticsBaseModel> | HTTPErrorResponse = - await API.fetch<TAnalyticsBaseModel>( - HTTPMethod.POST, - apiUrl, - { - select: JSONFunctions.serialize(select as JSONObject) || {}, - }, - this.getCommonHeaders(requestOptions) - ); - - if (result.isSuccess()) { - return AnalyticsBaseModel.fromJSON( - result.data, - modelType - ) as TAnalyticsBaseModel; - } - - this.checkStatusCode(result); - - throw result; - } - - public static async deleteItem< - TAnalyticsBaseModel extends AnalyticsBaseModel - >(data: { - modelType: { new (): TAnalyticsBaseModel }; - id: ObjectID; - requestOptions?: RequestOptions | undefined; - }): Promise<void> { - const { modelType, id, requestOptions } = data; - - const apiPath: Route | null = new modelType().crudApiPath; - if (!apiPath) { - throw new BadDataException( - 'This model does not support delete operations.' - ); - } - - const apiUrl: URL = URL.fromURL(APP_API_URL) - .addRoute(apiPath) - .addRoute('/' + id.toString()); - - if (!apiUrl) { - throw new BadDataException( - 'This model does not support delete operations.' - ); - } - - const result: HTTPResponse<TAnalyticsBaseModel> | HTTPErrorResponse = - await API.fetch<TAnalyticsBaseModel>( - HTTPMethod.DELETE, - apiUrl, - undefined, - this.getCommonHeaders(requestOptions) - ); - - if (result.isSuccess()) { - return; - } - - this.checkStatusCode(result); - - throw result; - } - - private static checkStatusCode< - TAnalyticsBaseModel extends AnalyticsBaseModel + const result: HTTPResponse< + JSONObject | JSONArray | TAnalyticsBaseModel | Array<TAnalyticsBaseModel> + > = await API.fetch< + JSONObject | JSONArray | TAnalyticsBaseModel | Array<TAnalyticsBaseModel> >( - result: - | HTTPResponse< - | TAnalyticsBaseModel - | JSONObject - | JSONArray - | Array<TAnalyticsBaseModel> - > - | HTTPErrorResponse - ): void { - if (result.statusCode === 406) { - const project: Project | null = ProjectUtil.getCurrentProject(); + HTTPMethod.PUT, + apiUrl, + { + data: data, + }, + this.getCommonHeaders(requestOptions), + ); - if (project && project.id) { - Navigation.navigate(new Route(`/dashboard/${project._id}/sso`)); - } - } + if (result.isSuccess()) { + return result; } + + this.checkStatusCode(result); + + throw result; + } + + public static async createOrUpdate< + TAnalyticsBaseModel extends AnalyticsBaseModel, + >(data: { + model: TAnalyticsBaseModel; + modelType: { new (): TAnalyticsBaseModel }; + formType: FormType; + miscDataProps?: JSONObject; + requestOptions?: RequestOptions | undefined; + }): Promise<HTTPResponse<TAnalyticsBaseModel>> { + const { model, modelType, formType, miscDataProps, requestOptions } = data; + + let apiUrl: URL | null = requestOptions?.overrideRequestUrl || null; + + if (!apiUrl) { + const apiPath: Route | null = model.crudApiPath; + if (!apiPath) { + throw new BadDataException( + "This model does not support create or update operations.", + ); + } + + apiUrl = URL.fromURL(APP_API_URL).addRoute(apiPath); + } + + const httpMethod: HTTPMethod = + formType === FormType.Create ? HTTPMethod.POST : HTTPMethod.PUT; + + if (httpMethod === HTTPMethod.PUT) { + apiUrl = apiUrl.addRoute(`/${model._id}`); + } + + const apiResult: HTTPErrorResponse | HTTPResponse<TAnalyticsBaseModel> = + await API.fetch<TAnalyticsBaseModel>( + httpMethod, + apiUrl, + { + data: JSONFunctions.serialize( + AnalyticsBaseModel.toJSON(model, modelType), + ), + miscDataProps: miscDataProps || {}, + }, + { + ...this.getCommonHeaders(requestOptions), + ...(requestOptions?.requestHeaders || {}), + }, + ); + + if (apiResult.isSuccess() && apiResult instanceof HTTPResponse) { + const result: HTTPResponse<TAnalyticsBaseModel> = + apiResult as HTTPResponse<TAnalyticsBaseModel>; + + result.data = AnalyticsBaseModel.fromJSON( + result.data, + modelType, + ) as TAnalyticsBaseModel; + + return result; + } + + this.checkStatusCode(apiResult); + + throw apiResult; + } + + public static async getList< + TAnalyticsBaseModel extends AnalyticsBaseModel, + >(data: { + modelType: { new (): TAnalyticsBaseModel }; + query: Query<TAnalyticsBaseModel>; + groupBy?: GroupBy<TAnalyticsBaseModel> | undefined; + limit: number; + skip: number; + select: Select<TAnalyticsBaseModel>; + sort: Sort<TAnalyticsBaseModel>; + requestOptions?: RequestOptions | undefined; + }): Promise<ListResult<TAnalyticsBaseModel>> { + const { + modelType, + query, + limit, + skip, + select, + sort, + requestOptions, + groupBy, + } = data; + + const model: TAnalyticsBaseModel = new modelType(); + const apiPath: Route | null = model.crudApiPath; + if (!apiPath) { + throw new BadDataException( + "This model does not support list operations.", + ); + } + + let apiUrl: URL = URL.fromURL(APP_API_URL) + .addRoute(apiPath) + .addRoute("/get-list"); + + if (requestOptions?.overrideRequestUrl) { + apiUrl = requestOptions.overrideRequestUrl; + } + + if (!apiUrl) { + throw new BadDataException( + "This model does not support list operations.", + ); + } + + const headers: Dictionary<string> = this.getCommonHeaders(requestOptions); + + const result: HTTPResponse<JSONArray> | HTTPErrorResponse = + await API.fetch<JSONArray>( + HTTPMethod.POST, + apiUrl, + { + query: JSONFunctions.serialize(query as JSONObject), + select: JSONFunctions.serialize(select as JSONObject), + sort: JSONFunctions.serialize(sort as JSONObject), + groupBy: JSONFunctions.serialize(groupBy as JSONObject), + }, + headers, + { + limit: limit.toString(), + skip: skip.toString(), + }, + ); + + if (result.isSuccess()) { + const list: Array<TAnalyticsBaseModel> = AnalyticsBaseModel.fromJSONArray( + result.data as JSONArray, + modelType, + ); + + return { + data: list, + count: result.count, + skip: result.skip, + limit: result.limit, + }; + } + + this.checkStatusCode(result); + + throw result; + } + + public static async count<TAnalyticsBaseModel extends AnalyticsBaseModel>( + modelType: { new (): TAnalyticsBaseModel }, + query: Query<TAnalyticsBaseModel>, + requestOptions?: RequestOptions | undefined, + ): Promise<number> { + const model: TAnalyticsBaseModel = new modelType(); + const apiPath: Route | null = model.crudApiPath; + if (!apiPath) { + throw new BadDataException( + "This model does not support list operations.", + ); + } + + let apiUrl: URL = URL.fromURL(APP_API_URL) + .addRoute(apiPath) + .addRoute("/count"); + + if (requestOptions?.overrideRequestUrl) { + apiUrl = requestOptions.overrideRequestUrl; + } + + if (!apiUrl) { + throw new BadDataException( + "This model does not support count operations.", + ); + } + + const headers: Dictionary<string> = this.getCommonHeaders(requestOptions); + + const result: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.fetch<JSONObject>( + HTTPMethod.POST, + apiUrl, + { + query: JSONFunctions.serialize(query as JSONObject), + }, + headers, + ); + + if (result.isSuccess()) { + const count: number = result.data["count"] as number; + + return count; + } + + this.checkStatusCode(result); + + throw result; + } + + public static getCommonHeaders( + requestOptions?: RequestOptions, + ): Dictionary<string> { + let headers: Dictionary<string> = {}; + + if (!requestOptions || Object.keys(requestOptions).length === 0) { + const project: Project | null = ProjectUtil.getCurrentProject(); + + if (project && project.id) { + headers["tenantid"] = project.id.toString(); + } + } + + // add SSO headers. + + headers = { + ...headers, + }; + + return headers; + } + + public static async getItem< + TAnalyticsBaseModel extends AnalyticsBaseModel, + >(data: { + modelType: { new (): TAnalyticsBaseModel }; + id: ObjectID; + select: Select<TAnalyticsBaseModel>; + requestOptions?: RequestOptions | undefined; + }): Promise<TAnalyticsBaseModel | null> { + const { modelType, id, select, requestOptions } = data; + + const apiPath: Route | null = new modelType().crudApiPath; + if (!apiPath) { + throw new BadDataException("This model does not support get operations."); + } + + let apiUrl: URL = URL.fromURL(APP_API_URL) + .addRoute(apiPath) + .addRoute("/" + id.toString()) + .addRoute("/get-item"); + + if (requestOptions?.overrideRequestUrl) { + apiUrl = requestOptions.overrideRequestUrl; + } + + if (!apiUrl) { + throw new BadDataException("This model does not support get operations."); + } + + return this.post<TAnalyticsBaseModel>( + modelType, + apiUrl, + select, + requestOptions, + ); + } + + public static async post<TAnalyticsBaseModel extends AnalyticsBaseModel>( + modelType: { new (): TAnalyticsBaseModel }, + apiUrl: URL, + select?: Select<TAnalyticsBaseModel> | undefined, + requestOptions?: RequestOptions | undefined, + ): Promise<TAnalyticsBaseModel | null> { + const result: HTTPResponse<TAnalyticsBaseModel> | HTTPErrorResponse = + await API.fetch<TAnalyticsBaseModel>( + HTTPMethod.POST, + apiUrl, + { + select: JSONFunctions.serialize(select as JSONObject) || {}, + }, + this.getCommonHeaders(requestOptions), + ); + + if (result.isSuccess()) { + return AnalyticsBaseModel.fromJSON( + result.data, + modelType, + ) as TAnalyticsBaseModel; + } + + this.checkStatusCode(result); + + throw result; + } + + public static async deleteItem< + TAnalyticsBaseModel extends AnalyticsBaseModel, + >(data: { + modelType: { new (): TAnalyticsBaseModel }; + id: ObjectID; + requestOptions?: RequestOptions | undefined; + }): Promise<void> { + const { modelType, id, requestOptions } = data; + + const apiPath: Route | null = new modelType().crudApiPath; + if (!apiPath) { + throw new BadDataException( + "This model does not support delete operations.", + ); + } + + const apiUrl: URL = URL.fromURL(APP_API_URL) + .addRoute(apiPath) + .addRoute("/" + id.toString()); + + if (!apiUrl) { + throw new BadDataException( + "This model does not support delete operations.", + ); + } + + const result: HTTPResponse<TAnalyticsBaseModel> | HTTPErrorResponse = + await API.fetch<TAnalyticsBaseModel>( + HTTPMethod.DELETE, + apiUrl, + undefined, + this.getCommonHeaders(requestOptions), + ); + + if (result.isSuccess()) { + return; + } + + this.checkStatusCode(result); + + throw result; + } + + private static checkStatusCode< + TAnalyticsBaseModel extends AnalyticsBaseModel, + >( + result: + | HTTPResponse< + | TAnalyticsBaseModel + | JSONObject + | JSONArray + | Array<TAnalyticsBaseModel> + > + | HTTPErrorResponse, + ): void { + if (result.statusCode === 406) { + const project: Project | null = ProjectUtil.getCurrentProject(); + + if (project && project.id) { + Navigation.navigate(new Route(`/dashboard/${project._id}/sso`)); + } + } + } } diff --git a/CommonUI/src/Utils/BaseDatabase/GroupBy.ts b/CommonUI/src/Utils/BaseDatabase/GroupBy.ts index 4a9614f334..c198e2ced0 100644 --- a/CommonUI/src/Utils/BaseDatabase/GroupBy.ts +++ b/CommonUI/src/Utils/BaseDatabase/GroupBy.ts @@ -1,9 +1,9 @@ -import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel'; -import BaseModel from 'Common/Models/BaseModel'; -import { JSONObject } from 'Common/Types/JSON'; +import AnalyticsDataModel from "Common/AnalyticsModels/BaseModel"; +import BaseModel from "Common/Models/BaseModel"; +import { JSONObject } from "Common/Types/JSON"; type GroupBy<TBaseModel extends AnalyticsDataModel | BaseModel | JSONObject> = { - [P in keyof TBaseModel]?: true; + [P in keyof TBaseModel]?: true; }; export default GroupBy; diff --git a/CommonUI/src/Utils/BaseDatabase/ListResult.ts b/CommonUI/src/Utils/BaseDatabase/ListResult.ts index 2a3863f11c..c12705cf22 100644 --- a/CommonUI/src/Utils/BaseDatabase/ListResult.ts +++ b/CommonUI/src/Utils/BaseDatabase/ListResult.ts @@ -1,12 +1,12 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import BaseModel from 'Common/Models/BaseModel'; -import { JSONObject } from 'Common/Types/JSON'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import BaseModel from "Common/Models/BaseModel"; +import { JSONObject } from "Common/Types/JSON"; export default interface ListResult< - TBaseModel extends BaseModel | AnalyticsBaseModel + TBaseModel extends BaseModel | AnalyticsBaseModel, > extends JSONObject { - data: Array<TBaseModel>; - count: number; - skip: number; - limit: number; + data: Array<TBaseModel>; + count: number; + skip: number; + limit: number; } diff --git a/CommonUI/src/Utils/BaseDatabase/Query.ts b/CommonUI/src/Utils/BaseDatabase/Query.ts index 988385f1fc..c4a21dc48f 100644 --- a/CommonUI/src/Utils/BaseDatabase/Query.ts +++ b/CommonUI/src/Utils/BaseDatabase/Query.ts @@ -1,25 +1,25 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import BaseModel from 'Common/Models/BaseModel'; -import InBetween from 'Common/Types/BaseDatabase/InBetween'; -import NotNull from 'Common/Types/BaseDatabase/NotNull'; -import Search from 'Common/Types/BaseDatabase/Search'; -import CompareBase from 'Common/Types/Database/CompareBase'; -import GenericObject from 'Common/Types/GenericObject'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import BaseModel from "Common/Models/BaseModel"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import NotNull from "Common/Types/BaseDatabase/NotNull"; +import Search from "Common/Types/BaseDatabase/Search"; +import CompareBase from "Common/Types/Database/CompareBase"; +import GenericObject from "Common/Types/GenericObject"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; type Query< - TBaseModel extends - | BaseModel - | AnalyticsBaseModel - | JSONObject - | GenericObject + TBaseModel extends + | BaseModel + | AnalyticsBaseModel + | JSONObject + | GenericObject, > = { - [P in keyof TBaseModel]?: - | JSONValue - | Search - | InBetween - | NotNull - | CompareBase; + [P in keyof TBaseModel]?: + | JSONValue + | Search + | InBetween + | NotNull + | CompareBase; }; export default Query; diff --git a/CommonUI/src/Utils/BaseDatabase/RequestOptions.ts b/CommonUI/src/Utils/BaseDatabase/RequestOptions.ts index 7efcfdaabf..1b4dfe8308 100644 --- a/CommonUI/src/Utils/BaseDatabase/RequestOptions.ts +++ b/CommonUI/src/Utils/BaseDatabase/RequestOptions.ts @@ -1,7 +1,7 @@ -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; export default interface RequestOptions { - requestHeaders?: Dictionary<string> | undefined; - overrideRequestUrl?: URL | undefined; + requestHeaders?: Dictionary<string> | undefined; + overrideRequestUrl?: URL | undefined; } diff --git a/CommonUI/src/Utils/BaseDatabase/Select.ts b/CommonUI/src/Utils/BaseDatabase/Select.ts index 1f05678701..134b524e72 100644 --- a/CommonUI/src/Utils/BaseDatabase/Select.ts +++ b/CommonUI/src/Utils/BaseDatabase/Select.ts @@ -1,9 +1,9 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import BaseModel from 'Common/Models/BaseModel'; -import { JSONObject } from 'Common/Types/JSON'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import BaseModel from "Common/Models/BaseModel"; +import { JSONObject } from "Common/Types/JSON"; type Select<TBaseModel extends BaseModel | AnalyticsBaseModel | JSONObject> = { - [P in keyof TBaseModel]?: boolean | JSONObject; + [P in keyof TBaseModel]?: boolean | JSONObject; }; export default Select; diff --git a/CommonUI/src/Utils/BaseDatabase/Sort.ts b/CommonUI/src/Utils/BaseDatabase/Sort.ts index c0cd61855c..8365e78ae0 100644 --- a/CommonUI/src/Utils/BaseDatabase/Sort.ts +++ b/CommonUI/src/Utils/BaseDatabase/Sort.ts @@ -1,10 +1,10 @@ -import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel'; -import BaseModel from 'Common/Models/BaseModel'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { JSONObject } from 'Common/Types/JSON'; +import AnalyticsDataModel from "Common/AnalyticsModels/BaseModel"; +import BaseModel from "Common/Models/BaseModel"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { JSONObject } from "Common/Types/JSON"; type Sort<TBaseModel extends AnalyticsDataModel | BaseModel | JSONObject> = { - [P in keyof TBaseModel]?: SortOrder; + [P in keyof TBaseModel]?: SortOrder; }; export default Sort; diff --git a/CommonUI/src/Utils/Clipboard.ts b/CommonUI/src/Utils/Clipboard.ts index d8eebc5d58..df2dd4fc4c 100644 --- a/CommonUI/src/Utils/Clipboard.ts +++ b/CommonUI/src/Utils/Clipboard.ts @@ -1,5 +1,5 @@ export default class Clipboard { - public static async copyToClipboard(text: string): Promise<void> { - await navigator.clipboard?.writeText(text); - } + public static async copyToClipboard(text: string): Promise<void> { + await navigator.clipboard?.writeText(text); + } } diff --git a/CommonUI/src/Utils/Cookie.ts b/CommonUI/src/Utils/Cookie.ts index 3f23e393ba..3a501b2dd8 100644 --- a/CommonUI/src/Utils/Cookie.ts +++ b/CommonUI/src/Utils/Cookie.ts @@ -1,71 +1,71 @@ -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Email from 'Common/Types/Email'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Typeof from 'Common/Types/Typeof'; -import UniversalCookies, { CookieSetOptions } from 'universal-cookie'; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Email from "Common/Types/Email"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Typeof from "Common/Types/Typeof"; +import UniversalCookies, { CookieSetOptions } from "universal-cookie"; export default class Cookie { - public static setItem( - key: string, - value: JSONValue | Email | URL, - options?: - | { - httpOnly?: boolean | undefined; - path: Route; - maxAgeInDays?: number | undefined; - } - | undefined - ): void { - if (typeof value === Typeof.Object) { - // if of type jsonobject. - value = JSON.stringify( - JSONFunctions.serializeValue(value as JSONValue) as JSONObject - ); + public static setItem( + key: string, + value: JSONValue | Email | URL, + options?: + | { + httpOnly?: boolean | undefined; + path: Route; + maxAgeInDays?: number | undefined; } - - const cookies: UniversalCookies = new UniversalCookies(); - - const cookieOptions: CookieSetOptions = { - httpOnly: options?.httpOnly || false, - path: options?.path ? options.path.toString() : '/', - }; - - if (options?.maxAgeInDays) { - cookieOptions.maxAge = OneUptimeDate.getMillisecondsInDays( - options.maxAgeInDays - ); - } - - cookies.set(key, value as string, cookieOptions); + | undefined, + ): void { + if (typeof value === Typeof.Object) { + // if of type jsonobject. + value = JSON.stringify( + JSONFunctions.serializeValue(value as JSONValue) as JSONObject, + ); } - public static getItem(key: string): JSONValue { - const cookies: UniversalCookies = new UniversalCookies(); - const value: JSONValue = cookies.get(key) as JSONValue; + const cookies: UniversalCookies = new UniversalCookies(); - try { - if (value) { - return JSONFunctions.deserializeValue( - JSONFunctions.parse(value?.toString()) - ); - } - return value; - } catch (err) { - return value; - } + const cookieOptions: CookieSetOptions = { + httpOnly: options?.httpOnly || false, + path: options?.path ? options.path.toString() : "/", + }; + + if (options?.maxAgeInDays) { + cookieOptions.maxAge = OneUptimeDate.getMillisecondsInDays( + options.maxAgeInDays, + ); } - public static removeItem(key: string): void { - const cookies: UniversalCookies = new UniversalCookies(); - cookies.remove(key); - } + cookies.set(key, value as string, cookieOptions); + } - // check if cookie exists - public static exists(key: string): boolean { - const cookies: UniversalCookies = new UniversalCookies(); - return Boolean(cookies.get(key)); + public static getItem(key: string): JSONValue { + const cookies: UniversalCookies = new UniversalCookies(); + const value: JSONValue = cookies.get(key) as JSONValue; + + try { + if (value) { + return JSONFunctions.deserializeValue( + JSONFunctions.parse(value?.toString()), + ); + } + return value; + } catch (err) { + return value; } + } + + public static removeItem(key: string): void { + const cookies: UniversalCookies = new UniversalCookies(); + cookies.remove(key); + } + + // check if cookie exists + public static exists(key: string): boolean { + const cookies: UniversalCookies = new UniversalCookies(); + return Boolean(cookies.get(key)); + } } diff --git a/CommonUI/src/Utils/Dropdown.ts b/CommonUI/src/Utils/Dropdown.ts index 9a43d16fba..536f889af7 100644 --- a/CommonUI/src/Utils/Dropdown.ts +++ b/CommonUI/src/Utils/Dropdown.ts @@ -1,58 +1,58 @@ -import { DropdownOption } from '../Components/Dropdown/Dropdown'; -import BaseModel from 'Common/Models/BaseModel'; +import { DropdownOption } from "../Components/Dropdown/Dropdown"; +import BaseModel from "Common/Models/BaseModel"; type Enum<E> = Record<keyof E, number | string> & { [k: number]: string }; export default class DropdownUtil { - public static getDropdownOptionsFromEnum<T>( - obj: Enum<T>, - useKeyAsLebel: boolean = false - ): Array<DropdownOption> { - return Object.keys(obj).map((key: string) => { - return { - label: useKeyAsLebel ? key : (obj as any)[key].toString(), - value: (obj as any)[key].toString(), - }; - }); - } + public static getDropdownOptionsFromEnum<T>( + obj: Enum<T>, + useKeyAsLebel: boolean = false, + ): Array<DropdownOption> { + return Object.keys(obj).map((key: string) => { + return { + label: useKeyAsLebel ? key : (obj as any)[key].toString(), + value: (obj as any)[key].toString(), + }; + }); + } - public static getDropdownOptionFromEnumForValue<T>( - enumObject: Enum<T>, - value: string - ): DropdownOption | undefined { - const options: Array<DropdownOption> = - DropdownUtil.getDropdownOptionsFromEnum(enumObject); - const option: DropdownOption | undefined = options.find( - (option: DropdownOption) => { - return option.value === value; - } - ); - return option; - } + public static getDropdownOptionFromEnumForValue<T>( + enumObject: Enum<T>, + value: string, + ): DropdownOption | undefined { + const options: Array<DropdownOption> = + DropdownUtil.getDropdownOptionsFromEnum(enumObject); + const option: DropdownOption | undefined = options.find( + (option: DropdownOption) => { + return option.value === value; + }, + ); + return option; + } - public static getDropdownOptionsFromEntityArray< - TBaseModel extends BaseModel - >(data: { - array: Array<TBaseModel>; - labelField: string; - valueField: string; - }): Array<DropdownOption> { - return data.array.map((item: TBaseModel) => { - return { - label: item.getColumnValue(data.labelField) as string, - value: item.getColumnValue(data.valueField) as string, - }; - }); - } + public static getDropdownOptionsFromEntityArray< + TBaseModel extends BaseModel, + >(data: { + array: Array<TBaseModel>; + labelField: string; + valueField: string; + }): Array<DropdownOption> { + return data.array.map((item: TBaseModel) => { + return { + label: item.getColumnValue(data.labelField) as string, + value: item.getColumnValue(data.valueField) as string, + }; + }); + } - public static getDropdownOptionsFromArray( - arr: Array<string> - ): Array<DropdownOption> { - return arr.map((item: string) => { - return { - label: item, - value: item, - }; - }); - } + public static getDropdownOptionsFromArray( + arr: Array<string>, + ): Array<DropdownOption> { + return arr.map((item: string) => { + return { + label: item, + value: item, + }; + }); + } } diff --git a/CommonUI/src/Utils/File.ts b/CommonUI/src/Utils/File.ts index 52cbb5afbd..577b85f0b3 100644 --- a/CommonUI/src/Utils/File.ts +++ b/CommonUI/src/Utils/File.ts @@ -1,11 +1,11 @@ -import { FILE_URL } from '../Config'; -import URL from 'Common/Types/API/URL'; -import ObjectID from 'Common/Types/ObjectID'; +import { FILE_URL } from "../Config"; +import URL from "Common/Types/API/URL"; +import ObjectID from "Common/Types/ObjectID"; export default class FileUtil { - public static getFileURL(fileId: ObjectID): URL { - return URL.fromString(FILE_URL.toString()) - .addRoute('/image') - .addRoute(`/${fileId.toString()}`); - } + public static getFileURL(fileId: ObjectID): URL { + return URL.fromString(FILE_URL.toString()) + .addRoute("/image") + .addRoute(`/${fileId.toString()}`); + } } diff --git a/CommonUI/src/Utils/GlobalEvents.ts b/CommonUI/src/Utils/GlobalEvents.ts index 47436dd29a..7893ed1893 100644 --- a/CommonUI/src/Utils/GlobalEvents.ts +++ b/CommonUI/src/Utils/GlobalEvents.ts @@ -1,30 +1,30 @@ -import { JSONObject } from 'Common/Types/JSON'; +import { JSONObject } from "Common/Types/JSON"; export default class GlobalEvents { - public static addEventListener( - name: string, - eventFunction: (event: CustomEvent) => any - ): void { - window.addEventListener(name as any, eventFunction); - } + public static addEventListener( + name: string, + eventFunction: (event: CustomEvent) => any, + ): void { + window.addEventListener(name as any, eventFunction); + } - public static removeEventListener( - name: string, - eventFunction: (event: CustomEvent) => any - ): void { - window.removeEventListener(name as any, eventFunction); - } + public static removeEventListener( + name: string, + eventFunction: (event: CustomEvent) => any, + ): void { + window.removeEventListener(name as any, eventFunction); + } - public static dispatchEvent( - name: string, - data?: JSONObject | undefined - ): void { - // Create a custom event with data - const event: CustomEvent = new CustomEvent(name, { - detail: data || {}, - }); + public static dispatchEvent( + name: string, + data?: JSONObject | undefined, + ): void { + // Create a custom event with data + const event: CustomEvent = new CustomEvent(name, { + detail: data || {}, + }); - // Dispatch the event - window.dispatchEvent(event); - } + // Dispatch the event + window.dispatchEvent(event); + } } diff --git a/CommonUI/src/Utils/History.ts b/CommonUI/src/Utils/History.ts index 9937105a69..ec3d07f210 100644 --- a/CommonUI/src/Utils/History.ts +++ b/CommonUI/src/Utils/History.ts @@ -1,3 +1,3 @@ -import { createBrowserHistory } from 'history'; +import { createBrowserHistory } from "history"; export default createBrowserHistory(); diff --git a/CommonUI/src/Utils/JsonWebToken.ts b/CommonUI/src/Utils/JsonWebToken.ts index 25152cb76d..cbd59efe3c 100644 --- a/CommonUI/src/Utils/JsonWebToken.ts +++ b/CommonUI/src/Utils/JsonWebToken.ts @@ -1,14 +1,14 @@ -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; export default class JsonWebToken { - public static decode(token: string): JSONObject | null { - if (token && token.includes('.')) { - return JSONFunctions.parseJSONObject( - window.atob(token.split('.')[1] as string) - ); - } - - return null; + public static decode(token: string): JSONObject | null { + if (token && token.includes(".")) { + return JSONFunctions.parseJSONObject( + window.atob(token.split(".")[1] as string), + ); } + + return null; + } } diff --git a/CommonUI/src/Utils/LocalStorage.ts b/CommonUI/src/Utils/LocalStorage.ts index b7cd3eaeeb..3f9ff81be4 100644 --- a/CommonUI/src/Utils/LocalStorage.ts +++ b/CommonUI/src/Utils/LocalStorage.ts @@ -1,45 +1,45 @@ -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import Email from 'Common/Types/Email'; -import { JSONObject, JSONValue } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Typeof from 'Common/Types/Typeof'; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import Email from "Common/Types/Email"; +import { JSONObject, JSONValue } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Typeof from "Common/Types/Typeof"; export default class LocalStorage { - public static setItem(key: string, value: JSONValue | Email | URL): void { - if (typeof value === Typeof.Object) { - // if of type jsonobject. - value = JSON.stringify( - JSONFunctions.serializeValue(value as JSONValue) as JSONObject - ); - } - localStorage.setItem(key, value as string); + public static setItem(key: string, value: JSONValue | Email | URL): void { + if (typeof value === Typeof.Object) { + // if of type jsonobject. + value = JSON.stringify( + JSONFunctions.serializeValue(value as JSONValue) as JSONObject, + ); } + localStorage.setItem(key, value as string); + } - public static getItem(key: string): JSONValue { - const value: JSONValue = localStorage.getItem(key) as JSONValue; + public static getItem(key: string): JSONValue { + const value: JSONValue = localStorage.getItem(key) as JSONValue; - try { - if (value) { - return JSONFunctions.deserializeValue( - JSONFunctions.parse(value?.toString()) - ); - } - return value; - } catch (err) { - return value; - } + try { + if (value) { + return JSONFunctions.deserializeValue( + JSONFunctions.parse(value?.toString()), + ); + } + return value; + } catch (err) { + return value; } + } - public static removeItem(key: string): void { - return localStorage.removeItem(key); - } + public static removeItem(key: string): void { + return localStorage.removeItem(key); + } - public static clear(): void { - localStorage.clear(); - } + public static clear(): void { + localStorage.clear(); + } - public static getAllItems(): Dictionary<string> { - return { ...localStorage }; - } + public static getAllItems(): Dictionary<string> { + return { ...localStorage }; + } } diff --git a/CommonUI/src/Utils/Logger.ts b/CommonUI/src/Utils/Logger.ts index 3b2026e809..de64c79a2c 100644 --- a/CommonUI/src/Utils/Logger.ts +++ b/CommonUI/src/Utils/Logger.ts @@ -1,21 +1,21 @@ export class Logger { - public static warn(text: string): void { - //eslint-disable-next-line + public static warn(text: string): void { + //eslint-disable-next-line console.warn(text); - } + } - public static error(text: string): void { - //eslint-disable-next-line + public static error(text: string): void { + //eslint-disable-next-line console.error(text); - } + } - public static log(text: string): void { - //eslint-disable-next-line + public static log(text: string): void { + //eslint-disable-next-line console.log(text); - } + } - public static info(text: string): void { - //eslint-disable-next-line + public static info(text: string): void { + //eslint-disable-next-line this.log(text); - } + } } diff --git a/CommonUI/src/Utils/Login.ts b/CommonUI/src/Utils/Login.ts index 95b3105cb1..507ac1418a 100644 --- a/CommonUI/src/Utils/Login.ts +++ b/CommonUI/src/Utils/Login.ts @@ -1,33 +1,33 @@ -import { DASHBOARD_URL } from '../Config'; -import Analytics from '../Utils/Analytics'; -import Navigation from '../Utils/Navigation'; -import UserUtil from '../Utils/User'; -import BaseModel from 'Common/Models/BaseModel'; -import Email from 'Common/Types/Email'; -import { JSONObject } from 'Common/Types/JSON'; -import Name from 'Common/Types/Name'; -import ObjectID from 'Common/Types/ObjectID'; -import User from 'Model/Models/User'; +import { DASHBOARD_URL } from "../Config"; +import Analytics from "../Utils/Analytics"; +import Navigation from "../Utils/Navigation"; +import UserUtil from "../Utils/User"; +import BaseModel from "Common/Models/BaseModel"; +import Email from "Common/Types/Email"; +import { JSONObject } from "Common/Types/JSON"; +import Name from "Common/Types/Name"; +import ObjectID from "Common/Types/ObjectID"; +import User from "Model/Models/User"; export default abstract class LoginUtil { - public static login(value: JSONObject): void { - const user: User = BaseModel.fromJSON( - value['user'] as JSONObject, - User - ) as User; + public static login(value: JSONObject): void { + const user: User = BaseModel.fromJSON( + value["user"] as JSONObject, + User, + ) as User; - UserUtil.setEmail(user.email as Email); - UserUtil.setUserId(user.id as ObjectID); - UserUtil.setName(user.name || new Name('')); - UserUtil.setIsMasterAdmin(user.isMasterAdmin as boolean); + UserUtil.setEmail(user.email as Email); + UserUtil.setUserId(user.id as ObjectID); + UserUtil.setName(user.name || new Name("")); + UserUtil.setIsMasterAdmin(user.isMasterAdmin as boolean); - if (user.profilePictureId) { - UserUtil.setProfilePicId(user.profilePictureId); - } - - Analytics.userAuth(user.email!); - - // go to dashboard, user should be logged in. - Navigation.navigate(DASHBOARD_URL); + if (user.profilePictureId) { + UserUtil.setProfilePicId(user.profilePictureId); } + + Analytics.userAuth(user.email!); + + // go to dashboard, user should be logged in. + Navigation.navigate(DASHBOARD_URL); + } } diff --git a/CommonUI/src/Utils/ModelAPI/ModelAPI.ts b/CommonUI/src/Utils/ModelAPI/ModelAPI.ts index 898c049d91..ffc0361f4d 100644 --- a/CommonUI/src/Utils/ModelAPI/ModelAPI.ts +++ b/CommonUI/src/Utils/ModelAPI/ModelAPI.ts @@ -1,467 +1,452 @@ -import { FormType } from '../../Components/Forms/ModelForm'; -import { APP_API_URL } from '../../Config'; -import API from '../../Utils/API/API'; -import GroupBy from '../BaseDatabase/GroupBy'; -import BaseListResult from '../BaseDatabase/ListResult'; -import Query from '../BaseDatabase/Query'; -import BaseRequestOptions from '../BaseDatabase/RequestOptions'; -import Select from '../BaseDatabase/Select'; -import Sort from '../BaseDatabase/Sort'; -import Navigation from '../Navigation'; -import ProjectUtil from '../Project'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPMethod from 'Common/Types/API/HTTPMethod'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import Project from 'Model/Models/Project'; +import { FormType } from "../../Components/Forms/ModelForm"; +import { APP_API_URL } from "../../Config"; +import API from "../../Utils/API/API"; +import GroupBy from "../BaseDatabase/GroupBy"; +import BaseListResult from "../BaseDatabase/ListResult"; +import Query from "../BaseDatabase/Query"; +import BaseRequestOptions from "../BaseDatabase/RequestOptions"; +import Select from "../BaseDatabase/Select"; +import Sort from "../BaseDatabase/Sort"; +import Navigation from "../Navigation"; +import ProjectUtil from "../Project"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPMethod from "Common/Types/API/HTTPMethod"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import Project from "Model/Models/Project"; export class ModelAPIHttpResponse< - TBaseModel extends BaseModel + TBaseModel extends BaseModel, > extends HTTPResponse<TBaseModel> { - public miscData?: JSONObject | undefined; + public miscData?: JSONObject | undefined; } export interface ListResult<TBaseModel extends BaseModel> - extends BaseListResult<TBaseModel> {} + extends BaseListResult<TBaseModel> {} export interface RequestOptions extends BaseRequestOptions { - isMultiTenantRequest?: boolean | undefined; + isMultiTenantRequest?: boolean | undefined; } export default class ModelAPI { - public static async create<TBaseModel extends BaseModel>(data: { - model: TBaseModel; - modelType: { new (): TBaseModel }; - requestOptions?: RequestOptions | undefined; - }): Promise< - HTTPResponse<JSONObject | JSONArray | TBaseModel | Array<TBaseModel>> - > { - return await ModelAPI.createOrUpdate({ - model: data.model, - modelType: data.modelType, - formType: FormType.Create, - miscDataProps: {}, - requestOptions: data.requestOptions, - }); - } + public static async create<TBaseModel extends BaseModel>(data: { + model: TBaseModel; + modelType: { new (): TBaseModel }; + requestOptions?: RequestOptions | undefined; + }): Promise< + HTTPResponse<JSONObject | JSONArray | TBaseModel | Array<TBaseModel>> + > { + return await ModelAPI.createOrUpdate({ + model: data.model, + modelType: data.modelType, + formType: FormType.Create, + miscDataProps: {}, + requestOptions: data.requestOptions, + }); + } - public static async update<TBaseModel extends BaseModel>(data: { - model: TBaseModel; - modelType: { new (): TBaseModel }; - }): Promise< - HTTPResponse<JSONObject | JSONArray | TBaseModel | Array<TBaseModel>> - > { - return await ModelAPI.createOrUpdate({ - model: data.model, - modelType: data.modelType, - formType: FormType.Update, - }); - } + public static async update<TBaseModel extends BaseModel>(data: { + model: TBaseModel; + modelType: { new (): TBaseModel }; + }): Promise< + HTTPResponse<JSONObject | JSONArray | TBaseModel | Array<TBaseModel>> + > { + return await ModelAPI.createOrUpdate({ + model: data.model, + modelType: data.modelType, + formType: FormType.Update, + }); + } - public static async updateById<TBaseModel extends BaseModel>(data: { - modelType: { new (): TBaseModel }; - id: ObjectID; - data: JSONObject; - apiUrlOverride?: URL; - requestOptions?: RequestOptions; - }): Promise< - HTTPResponse<JSONObject | JSONArray | TBaseModel | Array<TBaseModel>> - > { - const model: BaseModel = new data.modelType(); - let apiUrl: URL | null = data.apiUrlOverride || null; + public static async updateById<TBaseModel extends BaseModel>(data: { + modelType: { new (): TBaseModel }; + id: ObjectID; + data: JSONObject; + apiUrlOverride?: URL; + requestOptions?: RequestOptions; + }): Promise< + HTTPResponse<JSONObject | JSONArray | TBaseModel | Array<TBaseModel>> + > { + const model: BaseModel = new data.modelType(); + let apiUrl: URL | null = data.apiUrlOverride || null; - if (!apiUrl) { - const apiPath: Route | null = model.getCrudApiPath(); - if (!apiPath) { - throw new BadDataException( - 'This model does not support create or update operations.' - ); - } - - apiUrl = URL.fromURL(APP_API_URL).addRoute(apiPath); - } - - apiUrl = apiUrl.addRoute(`/${data.id.toString()}`); - - const result: HTTPResponse< - JSONObject | JSONArray | TBaseModel | Array<TBaseModel> - > = await API.fetch< - JSONObject | JSONArray | TBaseModel | Array<TBaseModel> - >( - HTTPMethod.PUT, - apiUrl, - { - data: data.data, - }, - this.getCommonHeaders(data.requestOptions) + if (!apiUrl) { + const apiPath: Route | null = model.getCrudApiPath(); + if (!apiPath) { + throw new BadDataException( + "This model does not support create or update operations.", ); + } - if (result.isSuccess()) { - return result; - } - - this.checkStatusCode(result); - - throw result; + apiUrl = URL.fromURL(APP_API_URL).addRoute(apiPath); } - public static async createOrUpdate<TBaseModel extends BaseModel>(data: { - model: TBaseModel; - modelType: { new (): TBaseModel }; - formType: FormType; - miscDataProps?: JSONObject; - requestOptions?: RequestOptions | undefined; - }): Promise<ModelAPIHttpResponse<TBaseModel>> { - let apiUrl: URL | null = - data.requestOptions?.overrideRequestUrl || null; + apiUrl = apiUrl.addRoute(`/${data.id.toString()}`); - if (!apiUrl) { - const apiPath: Route | null = data.model.getCrudApiPath(); - if (!apiPath) { - throw new BadDataException( - 'This model does not support create or update operations.' - ); - } + const result: HTTPResponse< + JSONObject | JSONArray | TBaseModel | Array<TBaseModel> + > = await API.fetch< + JSONObject | JSONArray | TBaseModel | Array<TBaseModel> + >( + HTTPMethod.PUT, + apiUrl, + { + data: data.data, + }, + this.getCommonHeaders(data.requestOptions), + ); - apiUrl = URL.fromURL(APP_API_URL).addRoute(apiPath); - } - - const httpMethod: HTTPMethod = - data.formType === FormType.Create - ? HTTPMethod.POST - : HTTPMethod.PUT; - - if ( - httpMethod === HTTPMethod.PUT && - !data.requestOptions?.overrideRequestUrl - ) { - apiUrl = apiUrl.addRoute(`/${data.model._id}`); - } - - const apiResult: HTTPErrorResponse | HTTPResponse<TBaseModel> = - await API.fetch<TBaseModel>( - httpMethod, - apiUrl, - { - data: JSONFunctions.serialize( - BaseModel.toJSON(data.model, data.modelType) - ), - miscDataProps: data.miscDataProps || {}, - }, - { - ...this.getCommonHeaders(data.requestOptions), - ...(data.requestOptions?.requestHeaders || {}), - } - ); - - if (apiResult.isSuccess() && apiResult instanceof HTTPResponse) { - const result: ModelAPIHttpResponse<TBaseModel> = - apiResult as ModelAPIHttpResponse<TBaseModel>; - - if ((result.data as any)['_miscData']) { - result.miscData = (result.data as any)[ - '_miscData' - ] as JSONObject; - delete (result.data as any)['_miscData']; - } - - result.data = BaseModel.fromJSONObject(result.data, data.modelType); - - return result; - } - - this.checkStatusCode(apiResult); - - throw apiResult; + if (result.isSuccess()) { + return result; } - public static async getList<TBaseModel extends BaseModel>(data: { - modelType: { new (): TBaseModel }; - query: Query<TBaseModel>; - groupBy?: GroupBy<TBaseModel> | undefined; - limit: number; - skip: number; - select: Select<TBaseModel>; - sort: Sort<TBaseModel>; - requestOptions?: RequestOptions | undefined; - }): Promise<ListResult<TBaseModel>> { - const model: TBaseModel = new data.modelType(); - const apiPath: Route | null = model.getCrudApiPath(); - if (!apiPath) { - throw new BadDataException( - 'This model does not support list operations.' - ); - } + this.checkStatusCode(result); - let apiUrl: URL = URL.fromURL(APP_API_URL) - .addRoute(apiPath) - .addRoute('/get-list'); + throw result; + } - if (data.requestOptions?.overrideRequestUrl) { - apiUrl = data.requestOptions.overrideRequestUrl; - } + public static async createOrUpdate<TBaseModel extends BaseModel>(data: { + model: TBaseModel; + modelType: { new (): TBaseModel }; + formType: FormType; + miscDataProps?: JSONObject; + requestOptions?: RequestOptions | undefined; + }): Promise<ModelAPIHttpResponse<TBaseModel>> { + let apiUrl: URL | null = data.requestOptions?.overrideRequestUrl || null; - if (!apiUrl) { - throw new BadDataException( - 'This model does not support list operations.' - ); - } - - const headers: Dictionary<string> = this.getCommonHeaders( - data.requestOptions + if (!apiUrl) { + const apiPath: Route | null = data.model.getCrudApiPath(); + if (!apiPath) { + throw new BadDataException( + "This model does not support create or update operations.", ); - if (data.requestOptions && data.requestOptions.isMultiTenantRequest) { - headers['isMultiTenantRequest'] = 'true'; - } + } - const result: HTTPResponse<JSONArray> | HTTPErrorResponse = - await API.fetch<JSONArray>( - HTTPMethod.POST, - apiUrl, - { - query: JSONFunctions.serialize(data.query as JSONObject), - select: JSONFunctions.serialize(data.select as JSONObject), - sort: JSONFunctions.serialize(data.sort as JSONObject), - groupBy: JSONFunctions.serialize( - data.groupBy as JSONObject - ), - }, - headers, - { - limit: data.limit.toString(), - skip: data.skip.toString(), - } - ); - - if (result.isSuccess()) { - const list: Array<TBaseModel> = BaseModel.fromJSONArray( - result.data as JSONArray, - data.modelType - ); - - return { - data: list, - count: result.count, - skip: result.skip, - limit: result.limit, - }; - } - - this.checkStatusCode(result); - - throw result; + apiUrl = URL.fromURL(APP_API_URL).addRoute(apiPath); } - public static async count<TBaseModel extends BaseModel>(data: { - modelType: { new (): TBaseModel }; - query: Query<TBaseModel>; - requestOptions?: RequestOptions | undefined; - }): Promise<number> { - const model: TBaseModel = new data.modelType(); - const apiPath: Route | null = model.getCrudApiPath(); - if (!apiPath) { - throw new BadDataException( - 'This model does not support list operations.' - ); - } + const httpMethod: HTTPMethod = + data.formType === FormType.Create ? HTTPMethod.POST : HTTPMethod.PUT; - let apiUrl: URL = URL.fromURL(APP_API_URL) - .addRoute(apiPath) - .addRoute('/count'); - - if (data.requestOptions?.overrideRequestUrl) { - apiUrl = data.requestOptions.overrideRequestUrl; - } - - if (!apiUrl) { - throw new BadDataException( - 'This model does not support count operations.' - ); - } - - const headers: Dictionary<string> = this.getCommonHeaders( - data.requestOptions - ); - if (data.requestOptions && data.requestOptions.isMultiTenantRequest) { - headers['is-multi-tenant-query'] = 'true'; - } - - const result: HTTPResponse<JSONObject> | HTTPErrorResponse = - await API.fetch<JSONObject>( - HTTPMethod.POST, - apiUrl, - { - query: JSONFunctions.serialize(data.query as JSONObject), - }, - headers - ); - - if (result.isSuccess()) { - const count: number = result.data['count'] as number; - - return count; - } - - this.checkStatusCode(result); - - throw result; + if ( + httpMethod === HTTPMethod.PUT && + !data.requestOptions?.overrideRequestUrl + ) { + apiUrl = apiUrl.addRoute(`/${data.model._id}`); } - public static getCommonHeaders( - requestOptions?: RequestOptions - ): Dictionary<string> { - let headers: Dictionary<string> = {}; + const apiResult: HTTPErrorResponse | HTTPResponse<TBaseModel> = + await API.fetch<TBaseModel>( + httpMethod, + apiUrl, + { + data: JSONFunctions.serialize( + BaseModel.toJSON(data.model, data.modelType), + ), + miscDataProps: data.miscDataProps || {}, + }, + { + ...this.getCommonHeaders(data.requestOptions), + ...(data.requestOptions?.requestHeaders || {}), + }, + ); - if ( - !requestOptions || - !requestOptions.isMultiTenantRequest || - Object.keys(requestOptions).length === 0 - ) { - const project: Project | null = ProjectUtil.getCurrentProject(); + if (apiResult.isSuccess() && apiResult instanceof HTTPResponse) { + const result: ModelAPIHttpResponse<TBaseModel> = + apiResult as ModelAPIHttpResponse<TBaseModel>; - if (project && project.id) { - headers['tenantid'] = project.id.toString(); - } - } + if ((result.data as any)["_miscData"]) { + result.miscData = (result.data as any)["_miscData"] as JSONObject; + delete (result.data as any)["_miscData"]; + } - // add SSO headers. + result.data = BaseModel.fromJSONObject(result.data, data.modelType); - headers = { - ...headers, - }; - - if (requestOptions && requestOptions.isMultiTenantRequest) { - headers['is-multi-tenant-query'] = 'true'; - } - - return headers; + return result; } - public static async getItem<TBaseModel extends BaseModel>(data: { - modelType: { new (): TBaseModel }; - id: ObjectID; - select: Select<TBaseModel>; - requestOptions?: RequestOptions | undefined; - }): Promise<TBaseModel | null> { - const apiPath: Route | null = new data.modelType().getCrudApiPath(); - if (!apiPath) { - throw new BadDataException( - 'This model does not support get operations.' - ); - } + this.checkStatusCode(apiResult); - let apiUrl: URL = URL.fromURL(APP_API_URL) - .addRoute(apiPath) - .addRoute('/' + data.id.toString()) - .addRoute('/get-item'); + throw apiResult; + } - if (data.requestOptions?.overrideRequestUrl) { - apiUrl = data.requestOptions.overrideRequestUrl; - } - - if (!apiUrl) { - throw new BadDataException( - 'This model does not support get operations.' - ); - } - - return this.post<TBaseModel>({ - modelType: data.modelType, - apiUrl: apiUrl, - select: data.select, - requestOptions: data.requestOptions, - }); + public static async getList<TBaseModel extends BaseModel>(data: { + modelType: { new (): TBaseModel }; + query: Query<TBaseModel>; + groupBy?: GroupBy<TBaseModel> | undefined; + limit: number; + skip: number; + select: Select<TBaseModel>; + sort: Sort<TBaseModel>; + requestOptions?: RequestOptions | undefined; + }): Promise<ListResult<TBaseModel>> { + const model: TBaseModel = new data.modelType(); + const apiPath: Route | null = model.getCrudApiPath(); + if (!apiPath) { + throw new BadDataException( + "This model does not support list operations.", + ); } - public static async post<TBaseModel extends BaseModel>(data: { - modelType: { new (): TBaseModel }; - apiUrl: URL; - select?: Select<TBaseModel> | undefined; - requestOptions?: RequestOptions | undefined; - }): Promise<TBaseModel | null> { - const result: HTTPResponse<TBaseModel> | HTTPErrorResponse = - await API.fetch<TBaseModel>( - HTTPMethod.POST, - data.apiUrl, - { - select: - JSONFunctions.serialize(data.select as JSONObject) || - {}, - }, - this.getCommonHeaders(data.requestOptions) - ); + let apiUrl: URL = URL.fromURL(APP_API_URL) + .addRoute(apiPath) + .addRoute("/get-list"); - if (result.isSuccess()) { - return BaseModel.fromJSONObject( - result.data as JSONObject, - data.modelType - ); - } - - this.checkStatusCode(result); - - throw result; + if (data.requestOptions?.overrideRequestUrl) { + apiUrl = data.requestOptions.overrideRequestUrl; } - public static async deleteItem<TBaseModel extends BaseModel>(data: { - modelType: { new (): TBaseModel }; - id: ObjectID; - requestOptions?: RequestOptions | undefined; - }): Promise<void> { - const apiPath: Route | null = new data.modelType().getCrudApiPath(); - if (!apiPath) { - throw new BadDataException( - 'This model does not support delete operations.' - ); - } - - const apiUrl: URL = URL.fromURL(APP_API_URL) - .addRoute(apiPath) - .addRoute('/' + data.id.toString()); - - if (!apiUrl) { - throw new BadDataException( - 'This model does not support delete operations.' - ); - } - - const result: HTTPResponse<TBaseModel> | HTTPErrorResponse = - await API.fetch<TBaseModel>( - HTTPMethod.DELETE, - apiUrl, - undefined, - this.getCommonHeaders(data.requestOptions) - ); - - if (result.isSuccess()) { - return; - } - - this.checkStatusCode(result); - - throw result; + if (!apiUrl) { + throw new BadDataException( + "This model does not support list operations.", + ); } - private static checkStatusCode<TBaseModel extends BaseModel>( - result: - | HTTPResponse< - TBaseModel | JSONObject | JSONArray | Array<TBaseModel> - > - | HTTPErrorResponse - ): void { - if (result.statusCode === 406) { - const project: Project | null = ProjectUtil.getCurrentProject(); - - if (project && project.id) { - Navigation.navigate(new Route(`/dashboard/${project._id}/sso`)); - } - } + const headers: Dictionary<string> = this.getCommonHeaders( + data.requestOptions, + ); + if (data.requestOptions && data.requestOptions.isMultiTenantRequest) { + headers["isMultiTenantRequest"] = "true"; } + + const result: HTTPResponse<JSONArray> | HTTPErrorResponse = + await API.fetch<JSONArray>( + HTTPMethod.POST, + apiUrl, + { + query: JSONFunctions.serialize(data.query as JSONObject), + select: JSONFunctions.serialize(data.select as JSONObject), + sort: JSONFunctions.serialize(data.sort as JSONObject), + groupBy: JSONFunctions.serialize(data.groupBy as JSONObject), + }, + headers, + { + limit: data.limit.toString(), + skip: data.skip.toString(), + }, + ); + + if (result.isSuccess()) { + const list: Array<TBaseModel> = BaseModel.fromJSONArray( + result.data as JSONArray, + data.modelType, + ); + + return { + data: list, + count: result.count, + skip: result.skip, + limit: result.limit, + }; + } + + this.checkStatusCode(result); + + throw result; + } + + public static async count<TBaseModel extends BaseModel>(data: { + modelType: { new (): TBaseModel }; + query: Query<TBaseModel>; + requestOptions?: RequestOptions | undefined; + }): Promise<number> { + const model: TBaseModel = new data.modelType(); + const apiPath: Route | null = model.getCrudApiPath(); + if (!apiPath) { + throw new BadDataException( + "This model does not support list operations.", + ); + } + + let apiUrl: URL = URL.fromURL(APP_API_URL) + .addRoute(apiPath) + .addRoute("/count"); + + if (data.requestOptions?.overrideRequestUrl) { + apiUrl = data.requestOptions.overrideRequestUrl; + } + + if (!apiUrl) { + throw new BadDataException( + "This model does not support count operations.", + ); + } + + const headers: Dictionary<string> = this.getCommonHeaders( + data.requestOptions, + ); + if (data.requestOptions && data.requestOptions.isMultiTenantRequest) { + headers["is-multi-tenant-query"] = "true"; + } + + const result: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.fetch<JSONObject>( + HTTPMethod.POST, + apiUrl, + { + query: JSONFunctions.serialize(data.query as JSONObject), + }, + headers, + ); + + if (result.isSuccess()) { + const count: number = result.data["count"] as number; + + return count; + } + + this.checkStatusCode(result); + + throw result; + } + + public static getCommonHeaders( + requestOptions?: RequestOptions, + ): Dictionary<string> { + let headers: Dictionary<string> = {}; + + if ( + !requestOptions || + !requestOptions.isMultiTenantRequest || + Object.keys(requestOptions).length === 0 + ) { + const project: Project | null = ProjectUtil.getCurrentProject(); + + if (project && project.id) { + headers["tenantid"] = project.id.toString(); + } + } + + // add SSO headers. + + headers = { + ...headers, + }; + + if (requestOptions && requestOptions.isMultiTenantRequest) { + headers["is-multi-tenant-query"] = "true"; + } + + return headers; + } + + public static async getItem<TBaseModel extends BaseModel>(data: { + modelType: { new (): TBaseModel }; + id: ObjectID; + select: Select<TBaseModel>; + requestOptions?: RequestOptions | undefined; + }): Promise<TBaseModel | null> { + const apiPath: Route | null = new data.modelType().getCrudApiPath(); + if (!apiPath) { + throw new BadDataException("This model does not support get operations."); + } + + let apiUrl: URL = URL.fromURL(APP_API_URL) + .addRoute(apiPath) + .addRoute("/" + data.id.toString()) + .addRoute("/get-item"); + + if (data.requestOptions?.overrideRequestUrl) { + apiUrl = data.requestOptions.overrideRequestUrl; + } + + if (!apiUrl) { + throw new BadDataException("This model does not support get operations."); + } + + return this.post<TBaseModel>({ + modelType: data.modelType, + apiUrl: apiUrl, + select: data.select, + requestOptions: data.requestOptions, + }); + } + + public static async post<TBaseModel extends BaseModel>(data: { + modelType: { new (): TBaseModel }; + apiUrl: URL; + select?: Select<TBaseModel> | undefined; + requestOptions?: RequestOptions | undefined; + }): Promise<TBaseModel | null> { + const result: HTTPResponse<TBaseModel> | HTTPErrorResponse = + await API.fetch<TBaseModel>( + HTTPMethod.POST, + data.apiUrl, + { + select: JSONFunctions.serialize(data.select as JSONObject) || {}, + }, + this.getCommonHeaders(data.requestOptions), + ); + + if (result.isSuccess()) { + return BaseModel.fromJSONObject( + result.data as JSONObject, + data.modelType, + ); + } + + this.checkStatusCode(result); + + throw result; + } + + public static async deleteItem<TBaseModel extends BaseModel>(data: { + modelType: { new (): TBaseModel }; + id: ObjectID; + requestOptions?: RequestOptions | undefined; + }): Promise<void> { + const apiPath: Route | null = new data.modelType().getCrudApiPath(); + if (!apiPath) { + throw new BadDataException( + "This model does not support delete operations.", + ); + } + + const apiUrl: URL = URL.fromURL(APP_API_URL) + .addRoute(apiPath) + .addRoute("/" + data.id.toString()); + + if (!apiUrl) { + throw new BadDataException( + "This model does not support delete operations.", + ); + } + + const result: HTTPResponse<TBaseModel> | HTTPErrorResponse = + await API.fetch<TBaseModel>( + HTTPMethod.DELETE, + apiUrl, + undefined, + this.getCommonHeaders(data.requestOptions), + ); + + if (result.isSuccess()) { + return; + } + + this.checkStatusCode(result); + + throw result; + } + + private static checkStatusCode<TBaseModel extends BaseModel>( + result: + | HTTPResponse<TBaseModel | JSONObject | JSONArray | Array<TBaseModel>> + | HTTPErrorResponse, + ): void { + if (result.statusCode === 406) { + const project: Project | null = ProjectUtil.getCurrentProject(); + + if (project && project.id) { + Navigation.navigate(new Route(`/dashboard/${project._id}/sso`)); + } + } + } } diff --git a/CommonUI/src/Utils/Navigation.ts b/CommonUI/src/Utils/Navigation.ts index c276f7574e..96ab0c7a1a 100644 --- a/CommonUI/src/Utils/Navigation.ts +++ b/CommonUI/src/Utils/Navigation.ts @@ -1,253 +1,243 @@ -import { AgnosticRouteMatch } from '@remix-run/router'; -import Hostname from 'Common/Types/API/Hostname'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; +import { AgnosticRouteMatch } from "@remix-run/router"; +import Hostname from "Common/Types/API/Hostname"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; import { - Location, - NavigateFunction, - Params, - matchRoutes, -} from 'react-router-dom'; + Location, + NavigateFunction, + Params, + matchRoutes, +} from "react-router-dom"; abstract class Navigation { - private static navigateHook: NavigateFunction; - private static location: Location; - private static params: Params; + private static navigateHook: NavigateFunction; + private static location: Location; + private static params: Params; - public static setNavigateHook(navigateHook: NavigateFunction): void { - this.navigateHook = navigateHook; + public static setNavigateHook(navigateHook: NavigateFunction): void { + this.navigateHook = navigateHook; + } + + public static setLocation(location: Location): void { + this.location = location; + } + + public static setParams(params: Params): void { + this.params = params; + } + + public static getParams(): Params { + return this.params; + } + + public static getCurrentPath(): Route { + return new Route(window.location.pathname); + } + + public static getBreadcrumbRoute(level: number): Route { + const paths: Array<string> = this.location.pathname.split("/"); + // +2 because we want to include the first 2 paths which are empty, dashboard and project id + const indexToSplice: number = level + 2; + return new Route(paths.splice(0, indexToSplice).join("/")); + } + + public static getRoutePath(routes: Array<{ path: string }>): string { + const pathes: AgnosticRouteMatch[] | null = matchRoutes( + routes, + this.location.pathname, + ); + return pathes?.[0]?.route.path || ""; + } + + public static getQueryStringByName(paramName: string): string | null { + const urlSearchParams: URLSearchParams = new URLSearchParams( + window.location.search, + ); + const params: Dictionary<string> = Object.fromEntries( + urlSearchParams.entries(), + ); + if (params && params[paramName]) { + return params[paramName] as string; } - public static setLocation(location: Location): void { - this.location = location; + return null; + } + + public static getParamByName( + paramName: string, + routeTemplate: Route, + ): string | null { + const currentPath: Array<string> = this.location.pathname.split("/"); + + if (!paramName.startsWith(":")) { + paramName = ":" + paramName; } - public static setParams(params: Params): void { - this.params = params; + const routeParamTemplateIndex: number = routeTemplate + .toString() + .split("/") + .indexOf(paramName); + + if (routeParamTemplateIndex === -1) { + throw new BadDataException( + `Param ${paramName} not found in template ${routeTemplate.toString()}`, + ); } - public static getParams(): Params { - return this.params; + if (currentPath[routeParamTemplateIndex]) { + return currentPath[routeParamTemplateIndex] as string; } - public static getCurrentPath(): Route { - return new Route(window.location.pathname); + return null; + } + + public static getLastParam(getFromLastRoute?: number): Route | null { + return URL.fromString(window.location.href).getLastRoute(getFromLastRoute); + } + + public static getFirstParam(getFromFirstRoute?: number): string | undefined { + const pathname: string = window.location.pathname; + + return pathname.split("/")[getFromFirstRoute || 1]; + } + + public static getLastParamAsString(getFromLastRoute?: number): string { + const param: Route | null = URL.fromString( + window.location.href, + ).getLastRoute(getFromLastRoute); + + return param?.toString().replace("/", "") || ""; + } + + public static getLastParamAsObjectID(getFromLastRoute?: number): ObjectID { + return new ObjectID(this.getLastParamAsString(getFromLastRoute)); + } + + public static getCurrentRoute(): Route { + return new Route(this.location.pathname); + } + + public static getHostname(): Hostname { + return new Hostname(window.location.hostname); + } + + public static getCurrentURL(): URL { + return URL.fromString(window.location.href); + } + + public static reload(): void { + window.location.reload(); + } + + public static containsInPath(text: string): boolean { + return window.location.pathname.includes(text); + } + + public static isStartWith(route: Route): boolean { + const current: Route = this.getCurrentRoute(); + const routeItems: Array<string> = route.toString().split("/"); + let anotherRouteItems: Array<string> = current.toString().split("/"); + + if (routeItems.length > anotherRouteItems.length) { + return false; } - public static getBreadcrumbRoute(level: number): Route { - const paths: Array<string> = this.location.pathname.split('/'); - // +2 because we want to include the first 2 paths which are empty, dashboard and project id - const indexToSplice: number = level + 2; - return new Route(paths.splice(0, indexToSplice).join('/')); + anotherRouteItems = anotherRouteItems.splice(0, routeItems.length); + + let start: number = 0; + let startsWith: boolean = true; + for (const item of anotherRouteItems) { + if (routeItems[start]?.startsWith(":") && item) { + start++; + continue; + } + + if (routeItems[start]?.toString() !== item.toString()) { + startsWith = false; + break; + } + + start++; } + return startsWith; + } - public static getRoutePath(routes: Array<{ path: string }>): string { - const pathes: AgnosticRouteMatch[] | null = matchRoutes( - routes, - this.location.pathname - ); - return pathes?.[0]?.route.path || ''; - } + public static isOnThisPage(route: Route | URL): boolean { + if (route instanceof Route) { + const current: Route = this.getCurrentRoute(); - public static getQueryStringByName(paramName: string): string | null { - const urlSearchParams: URLSearchParams = new URLSearchParams( - window.location.search - ); - const params: Dictionary<string> = Object.fromEntries( - urlSearchParams.entries() - ); - if (params && params[paramName]) { - return params[paramName] as string; - } - - return null; - } - - public static getParamByName( - paramName: string, - routeTemplate: Route - ): string | null { - const currentPath: Array<string> = this.location.pathname.split('/'); - - if (!paramName.startsWith(':')) { - paramName = ':' + paramName; - } - - const routeParamTemplateIndex: number = routeTemplate - .toString() - .split('/') - .indexOf(paramName); - - if (routeParamTemplateIndex === -1) { - throw new BadDataException( - `Param ${paramName} not found in template ${routeTemplate.toString()}` - ); - } - - if (currentPath[routeParamTemplateIndex]) { - return currentPath[routeParamTemplateIndex] as string; - } - - return null; - } - - public static getLastParam(getFromLastRoute?: number): Route | null { - return URL.fromString(window.location.href).getLastRoute( - getFromLastRoute - ); - } - - public static getFirstParam( - getFromFirstRoute?: number - ): string | undefined { - const pathname: string = window.location.pathname; - - return pathname.split('/')[getFromFirstRoute || 1]; - } - - public static getLastParamAsString(getFromLastRoute?: number): string { - const param: Route | null = URL.fromString( - window.location.href - ).getLastRoute(getFromLastRoute); - - return param?.toString().replace('/', '') || ''; - } - - public static getLastParamAsObjectID(getFromLastRoute?: number): ObjectID { - return new ObjectID(this.getLastParamAsString(getFromLastRoute)); - } - - public static getCurrentRoute(): Route { - return new Route(this.location.pathname); - } - - public static getHostname(): Hostname { - return new Hostname(window.location.hostname); - } - - public static getCurrentURL(): URL { - return URL.fromString(window.location.href); - } - - public static reload(): void { - window.location.reload(); - } - - public static containsInPath(text: string): boolean { - return window.location.pathname.includes(text); - } - - public static isStartWith(route: Route): boolean { - const current: Route = this.getCurrentRoute(); - const routeItems: Array<string> = route.toString().split('/'); - let anotherRouteItems: Array<string> = current.toString().split('/'); - - if (routeItems.length > anotherRouteItems.length) { - return false; - } - - anotherRouteItems = anotherRouteItems.splice(0, routeItems.length); - - let start: number = 0; - let startsWith: boolean = true; - for (const item of anotherRouteItems) { - if (routeItems[start]?.startsWith(':') && item) { - start++; - continue; - } - - if (routeItems[start]?.toString() !== item.toString()) { - startsWith = false; - break; - } - - start++; - } - return startsWith; - } - - public static isOnThisPage(route: Route | URL): boolean { - if (route instanceof Route) { - const current: Route = this.getCurrentRoute(); - - let isOnThisPage: boolean = true; - - const routeItems: Array<string> = route.toString().split('/'); - const currentPathItems: Array<string> = current - .toString() - .split('/'); - if (routeItems.length !== currentPathItems.length) { - return false; - } - - let start: number = 0; - for (const item of currentPathItems) { - if (routeItems[start]?.startsWith(':') && item) { - start++; - continue; - } - - if (routeItems[start]?.toString() !== item.toString()) { - isOnThisPage = false; - break; - } - - start++; - } - - return isOnThisPage; - } - - if (route instanceof URL) { - const current: URL = this.getCurrentURL(); - - if (current.toString() === route.toString()) { - return true; - } - - return false; - } + let isOnThisPage: boolean = true; + const routeItems: Array<string> = route.toString().split("/"); + const currentPathItems: Array<string> = current.toString().split("/"); + if (routeItems.length !== currentPathItems.length) { return false; + } + + let start: number = 0; + for (const item of currentPathItems) { + if (routeItems[start]?.startsWith(":") && item) { + start++; + continue; + } + + if (routeItems[start]?.toString() !== item.toString()) { + isOnThisPage = false; + break; + } + + start++; + } + + return isOnThisPage; } - public static goBack(): void { - this.navigateHook(-1); + if (route instanceof URL) { + const current: URL = this.getCurrentURL(); + + if (current.toString() === route.toString()) { + return true; + } + + return false; } - public static navigate( - to: Route | URL, - options?: { - openInNewTab?: boolean | undefined; - forceNavigate?: boolean | undefined; - } - ): void { - if (options?.openInNewTab) { - // open in new tab - window.open(to.toString(), '_blank'); - return; - } + return false; + } - if (options?.forceNavigate && to instanceof Route) { - window.location.href = to.toString(); - } + public static goBack(): void { + this.navigateHook(-1); + } - if ( - this.navigateHook && - to instanceof Route && - !this.isOnThisPage(to) - ) { - this.navigateHook(to.toString()); - } - - // if its an external link outside of react. - if (to instanceof URL) { - window.location.href = to.toString(); - } + public static navigate( + to: Route | URL, + options?: { + openInNewTab?: boolean | undefined; + forceNavigate?: boolean | undefined; + }, + ): void { + if (options?.openInNewTab) { + // open in new tab + window.open(to.toString(), "_blank"); + return; } + + if (options?.forceNavigate && to instanceof Route) { + window.location.href = to.toString(); + } + + if (this.navigateHook && to instanceof Route && !this.isOnThisPage(to)) { + this.navigateHook(to.toString()); + } + + // if its an external link outside of react. + if (to instanceof URL) { + window.location.href = to.toString(); + } + } } export default Navigation; diff --git a/CommonUI/src/Utils/Permission.ts b/CommonUI/src/Utils/Permission.ts index d9fcce1147..758d80ec9b 100644 --- a/CommonUI/src/Utils/Permission.ts +++ b/CommonUI/src/Utils/Permission.ts @@ -1,90 +1,90 @@ -import { DropdownOption } from '../Components/Dropdown/Dropdown'; -import LocalStorage from './LocalStorage'; -import { JSONObject } from 'Common/Types/JSON'; +import { DropdownOption } from "../Components/Dropdown/Dropdown"; +import LocalStorage from "./LocalStorage"; +import { JSONObject } from "Common/Types/JSON"; import Permission, { - PermissionHelper, - PermissionProps, - UserGlobalAccessPermission, - UserPermission, - UserTenantAccessPermission, -} from 'Common/Types/Permission'; + PermissionHelper, + PermissionProps, + UserGlobalAccessPermission, + UserPermission, + UserTenantAccessPermission, +} from "Common/Types/Permission"; export default class PermissionUtil { - public static getGlobalPermissions(): UserGlobalAccessPermission | null { - if (!LocalStorage.getItem('global_permissions')) { - return null; - } - const globalPermissions: JSONObject = LocalStorage.getItem( - 'global_permissions' - ) as JSONObject; + public static getGlobalPermissions(): UserGlobalAccessPermission | null { + if (!LocalStorage.getItem("global_permissions")) { + return null; + } + const globalPermissions: JSONObject = LocalStorage.getItem( + "global_permissions", + ) as JSONObject; - return globalPermissions as UserGlobalAccessPermission; + return globalPermissions as UserGlobalAccessPermission; + } + + public static getAllPermissions(): Array<Permission> { + let permissions: Array<Permission> = []; + + const globalPermissions: UserGlobalAccessPermission | null = + this.getGlobalPermissions(); + + if (globalPermissions) { + permissions = [...globalPermissions.globalPermissions]; } - public static getAllPermissions(): Array<Permission> { - let permissions: Array<Permission> = []; + const projectPermissions: UserTenantAccessPermission | null = + this.getProjectPermissions(); - const globalPermissions: UserGlobalAccessPermission | null = - this.getGlobalPermissions(); - - if (globalPermissions) { - permissions = [...globalPermissions.globalPermissions]; - } - - const projectPermissions: UserTenantAccessPermission | null = - this.getProjectPermissions(); - - if (projectPermissions) { - permissions = [ - ...permissions, - ...projectPermissions.permissions.map((i: UserPermission) => { - return i.permission; - }), - ]; - } - - return permissions; + if (projectPermissions) { + permissions = [ + ...permissions, + ...projectPermissions.permissions.map((i: UserPermission) => { + return i.permission; + }), + ]; } - public static getProjectPermissions(): UserTenantAccessPermission | null { - if (!LocalStorage.getItem('project_permissions')) { - return null; - } - const permissions: JSONObject = LocalStorage.getItem( - 'project_permissions' - ) as JSONObject; + return permissions; + } - const userTenantAccessPermission: UserTenantAccessPermission = - permissions as UserTenantAccessPermission; - userTenantAccessPermission._type = 'UserTenantAccessPermission'; - return userTenantAccessPermission; + public static getProjectPermissions(): UserTenantAccessPermission | null { + if (!LocalStorage.getItem("project_permissions")) { + return null; } + const permissions: JSONObject = LocalStorage.getItem( + "project_permissions", + ) as JSONObject; - public static projectPermissionsAsDropdownOptions(): Array<DropdownOption> { - const permissions: Array<PermissionProps> = - PermissionHelper.getTenantPermissionProps(); + const userTenantAccessPermission: UserTenantAccessPermission = + permissions as UserTenantAccessPermission; + userTenantAccessPermission._type = "UserTenantAccessPermission"; + return userTenantAccessPermission; + } - return permissions.map((permissionProp: PermissionProps) => { - return { - value: permissionProp.permission, - label: permissionProp.title, - }; - }); - } + public static projectPermissionsAsDropdownOptions(): Array<DropdownOption> { + const permissions: Array<PermissionProps> = + PermissionHelper.getTenantPermissionProps(); - public static setGlobalPermissions( - permissions: UserGlobalAccessPermission - ): void { - LocalStorage.setItem('global_permissions', permissions); - } + return permissions.map((permissionProp: PermissionProps) => { + return { + value: permissionProp.permission, + label: permissionProp.title, + }; + }); + } - public static setProjectPermissions( - permissions: UserTenantAccessPermission - ): void { - LocalStorage.setItem('project_permissions', permissions); - } + public static setGlobalPermissions( + permissions: UserGlobalAccessPermission, + ): void { + LocalStorage.setItem("global_permissions", permissions); + } - public static clearProjectPermissions(): void { - LocalStorage.setItem('project_permissions', null); - } + public static setProjectPermissions( + permissions: UserTenantAccessPermission, + ): void { + LocalStorage.setItem("project_permissions", permissions); + } + + public static clearProjectPermissions(): void { + LocalStorage.setItem("project_permissions", null); + } } diff --git a/CommonUI/src/Utils/PricingPlan.ts b/CommonUI/src/Utils/PricingPlan.ts index 06663713f9..d0542e774f 100644 --- a/CommonUI/src/Utils/PricingPlan.ts +++ b/CommonUI/src/Utils/PricingPlan.ts @@ -1,116 +1,116 @@ -import { env } from '../Config'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import PricingPlanType from 'Common/Types/PricingPlan'; +import { env } from "../Config"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import PricingPlanType from "Common/Types/PricingPlan"; export default class PricingPlan { - public static getPlans(): Array<PricingPlanType> { - if ( - env('STRIPE_PUBLIC_KEY') && - env('STRIPE_PUBLIC_KEY').startsWith('pk_test') - ) { - return [ - { - category: 'Startup', - planId: 'plan_GoWIYiX2L8hwzx', - type: 'month', - amount: 25, - details: '$25 / Month / User', - }, - { - category: 'Startup', - planId: 'plan_GoWIqpBpStiqQp', - type: 'annual', - amount: 264, - details: '$22/mo per user paid annually. ', - }, - { - category: 'Growth', - planId: 'plan_GoWKgxRnPPBJWy', - type: 'month', - amount: 59, - details: '$59 / Month / User', - }, - { - category: 'Growth', - planId: 'plan_GoWKiTdQ6NiQFw', - type: 'annual', - amount: 588, - details: '$49/mo per user paid annually. ', - }, - { - category: 'Scale', - planId: 'plan_H9Iox3l2YqLTDR', - type: 'month', - amount: 99, - details: '$120 / Month / User', - }, - { - category: 'Scale', - planId: 'plan_H9IlBKhsFz4hV2', - type: 'annual', - amount: 1188, - details: '$99/mo per user paid annually. ', - }, - ]; - } - return [ - { - category: 'Startup', - planId: 'plan_GoVgVbvNdbWwlm', - type: 'month', - amount: 25, - details: '$25 / Month / User', - }, - { - category: 'Startup', - planId: 'plan_GoVgJu5PKMLRJU', - type: 'annual', - amount: 264, - details: '$22/mo per user paid annually. ', - }, - { - category: 'Growth', - planId: 'plan_GoVi9EIa6MU0fG', - type: 'month', - amount: 59, - details: '$59 / Month / User', - }, - { - category: 'Growth', - planId: 'plan_GoViZshjqzZ0vv', - type: 'annual', - amount: 588, - details: '$49/mo per user paid annually. ', - }, - { - category: 'Scale', - planId: 'plan_H9Ii6Qj3HLdtty', - type: 'month', - amount: 99, - details: '$120 / Month / User', - }, - { - category: 'Scale', - planId: 'plan_H9IjvX2Flsvlcg', - type: 'annual', - amount: 1188, - details: '$99/mo per user paid annually. ', - }, - ]; + public static getPlans(): Array<PricingPlanType> { + if ( + env("STRIPE_PUBLIC_KEY") && + env("STRIPE_PUBLIC_KEY").startsWith("pk_test") + ) { + return [ + { + category: "Startup", + planId: "plan_GoWIYiX2L8hwzx", + type: "month", + amount: 25, + details: "$25 / Month / User", + }, + { + category: "Startup", + planId: "plan_GoWIqpBpStiqQp", + type: "annual", + amount: 264, + details: "$22/mo per user paid annually. ", + }, + { + category: "Growth", + planId: "plan_GoWKgxRnPPBJWy", + type: "month", + amount: 59, + details: "$59 / Month / User", + }, + { + category: "Growth", + planId: "plan_GoWKiTdQ6NiQFw", + type: "annual", + amount: 588, + details: "$49/mo per user paid annually. ", + }, + { + category: "Scale", + planId: "plan_H9Iox3l2YqLTDR", + type: "month", + amount: 99, + details: "$120 / Month / User", + }, + { + category: "Scale", + planId: "plan_H9IlBKhsFz4hV2", + type: "annual", + amount: 1188, + details: "$99/mo per user paid annually. ", + }, + ]; + } + return [ + { + category: "Startup", + planId: "plan_GoVgVbvNdbWwlm", + type: "month", + amount: 25, + details: "$25 / Month / User", + }, + { + category: "Startup", + planId: "plan_GoVgJu5PKMLRJU", + type: "annual", + amount: 264, + details: "$22/mo per user paid annually. ", + }, + { + category: "Growth", + planId: "plan_GoVi9EIa6MU0fG", + type: "month", + amount: 59, + details: "$59 / Month / User", + }, + { + category: "Growth", + planId: "plan_GoViZshjqzZ0vv", + type: "annual", + amount: 588, + details: "$49/mo per user paid annually. ", + }, + { + category: "Scale", + planId: "plan_H9Ii6Qj3HLdtty", + type: "month", + amount: 99, + details: "$120 / Month / User", + }, + { + category: "Scale", + planId: "plan_H9IjvX2Flsvlcg", + type: "annual", + amount: 1188, + details: "$99/mo per user paid annually. ", + }, + ]; + } + + public static getPlanById(id: string): PricingPlanType { + const plans: Array<PricingPlanType> = this.getPlans(); + const plan: PricingPlanType | undefined = plans.find( + (plan: PricingPlanType) => { + return plan.planId === id; + }, + ); + + if (plan) { + return plan; } - public static getPlanById(id: string): PricingPlanType { - const plans: Array<PricingPlanType> = this.getPlans(); - const plan: PricingPlanType | undefined = plans.find( - (plan: PricingPlanType) => { - return plan.planId === id; - } - ); - - if (plan) { - return plan; - } - - throw new BadDataException(`Plan with id ${id} not found`); - } + throw new BadDataException(`Plan with id ${id} not found`); + } } diff --git a/CommonUI/src/Utils/Project.ts b/CommonUI/src/Utils/Project.ts index c859ad7fa0..a3ad4b9648 100644 --- a/CommonUI/src/Utils/Project.ts +++ b/CommonUI/src/Utils/Project.ts @@ -1,52 +1,52 @@ -import { BILLING_ENABLED, getAllEnvVars } from '../Config'; -import LocalStorage from './LocalStorage'; -import BaseModel from 'Common/Models/BaseModel'; +import { BILLING_ENABLED, getAllEnvVars } from "../Config"; +import LocalStorage from "./LocalStorage"; +import BaseModel from "Common/Models/BaseModel"; import SubscriptionPlan, { - PlanSelect, -} from 'Common/Types/Billing/SubscriptionPlan'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Project from 'Model/Models/Project'; + PlanSelect, +} from "Common/Types/Billing/SubscriptionPlan"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Project from "Model/Models/Project"; export default class ProjectUtil { - public static getCurrentProject(): Project | null { - if (!LocalStorage.getItem('current_project')) { - return null; - } - const projectJson: JSONObject = LocalStorage.getItem( - 'current_project' - ) as JSONObject; - return BaseModel.fromJSON(projectJson, Project) as Project; + public static getCurrentProject(): Project | null { + if (!LocalStorage.getItem("current_project")) { + return null; + } + const projectJson: JSONObject = LocalStorage.getItem( + "current_project", + ) as JSONObject; + return BaseModel.fromJSON(projectJson, Project) as Project; + } + + public static getCurrentProjectId(): ObjectID | null { + return this.getCurrentProject()?.id || null; + } + + public static setCurrentProject(project: JSONObject | Project): void { + if (project instanceof Project) { + project = BaseModel.toJSON(project, Project); + } + LocalStorage.setItem("current_project", project); + } + + public static clearCurrentProject(): void { + LocalStorage.setItem("current_project", null); + } + + public static getCurrentPlan(): PlanSelect | null { + if (!BILLING_ENABLED) { + return null; } - public static getCurrentProjectId(): ObjectID | null { - return this.getCurrentProject()?.id || null; + const project: Project | null = this.getCurrentProject(); + if (!project || !project.paymentProviderPlanId) { + return null; } - public static setCurrentProject(project: JSONObject | Project): void { - if (project instanceof Project) { - project = BaseModel.toJSON(project, Project); - } - LocalStorage.setItem('current_project', project); - } - - public static clearCurrentProject(): void { - LocalStorage.setItem('current_project', null); - } - - public static getCurrentPlan(): PlanSelect | null { - if (!BILLING_ENABLED) { - return null; - } - - const project: Project | null = this.getCurrentProject(); - if (!project || !project.paymentProviderPlanId) { - return null; - } - - return SubscriptionPlan.getPlanSelect( - project.paymentProviderPlanId, - getAllEnvVars() - ); - } + return SubscriptionPlan.getPlanSelect( + project.paymentProviderPlanId, + getAllEnvVars(), + ); + } } diff --git a/CommonUI/src/Utils/Realtime.ts b/CommonUI/src/Utils/Realtime.ts index 8c0a4d7968..8eeaf9f366 100644 --- a/CommonUI/src/Utils/Realtime.ts +++ b/CommonUI/src/Utils/Realtime.ts @@ -1,133 +1,128 @@ -import { HOST, HTTP_PROTOCOL } from '../Config'; -import Query from './BaseDatabase/Query'; -import Select from './BaseDatabase/Select'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import BaseModel from 'Common/Models/BaseModel'; -import { RealtimeRoute } from 'Common/ServiceRoute'; -import URL from 'Common/Types/API/URL'; -import DatabaseType from 'Common/Types/BaseDatabase/DatabaseType'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; +import { HOST, HTTP_PROTOCOL } from "../Config"; +import Query from "./BaseDatabase/Query"; +import Select from "./BaseDatabase/Select"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import BaseModel from "Common/Models/BaseModel"; +import { RealtimeRoute } from "Common/ServiceRoute"; +import URL from "Common/Types/API/URL"; +import DatabaseType from "Common/Types/BaseDatabase/DatabaseType"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; import RealtimeUtil, { - EventName, - ListenToModelEventJSON, - ModelEventType, -} from 'Common/Utils/Realtime'; -import SocketIO, { Socket } from 'socket.io-client'; + EventName, + ListenToModelEventJSON, + ModelEventType, +} from "Common/Utils/Realtime"; +import SocketIO, { Socket } from "socket.io-client"; export interface ListenToModelEvent< - Model extends AnalyticsBaseModel | BaseModel + Model extends AnalyticsBaseModel | BaseModel, > { - modelType: { new (): Model }; - query: Query<Model>; - eventType: ModelEventType; - tenantId: ObjectID; - select: Select<Model>; + modelType: { new (): Model }; + query: Query<Model>; + eventType: ModelEventType; + tenantId: ObjectID; + select: Select<Model>; } export default abstract class Reatime { - private static socket: Socket; + private static socket: Socket; - public static init(): void { - const socket: Socket = SocketIO( - new URL(HTTP_PROTOCOL, HOST).toString(), - { - path: RealtimeRoute.toString(), - } - ); + public static init(): void { + const socket: Socket = SocketIO(new URL(HTTP_PROTOCOL, HOST).toString(), { + path: RealtimeRoute.toString(), + }); - this.socket = socket; + this.socket = socket; + } + + public static listenToModelEvent<Model extends BaseModel>( + listenToModelEvent: ListenToModelEvent<Model>, + onEvent: (model: Model) => void, + ): () => void { + // conver this to json and send it to the server. + + if (!this.socket) { + this.init(); } - public static listenToModelEvent<Model extends BaseModel>( - listenToModelEvent: ListenToModelEvent<Model>, - onEvent: (model: Model) => void - ): () => void { - // conver this to json and send it to the server. + const listenToModelEventJSON: ListenToModelEventJSON = { + eventType: listenToModelEvent.eventType, + modelType: DatabaseType.Database, + modelName: listenToModelEvent.modelType.name, + query: JSONFunctions.serialize(listenToModelEvent.query), + tenantId: listenToModelEvent.tenantId.toString(), + select: JSONFunctions.serialize(listenToModelEvent.select), + }; - if (!this.socket) { - this.init(); - } + this.emit(EventName.ListenToModalEvent, listenToModelEventJSON as any); - const listenToModelEventJSON: ListenToModelEventJSON = { - eventType: listenToModelEvent.eventType, - modelType: DatabaseType.Database, - modelName: listenToModelEvent.modelType.name, - query: JSONFunctions.serialize(listenToModelEvent.query), - tenantId: listenToModelEvent.tenantId.toString(), - select: JSONFunctions.serialize(listenToModelEvent.select), - }; + const roomId: string = RealtimeUtil.getRoomId( + listenToModelEvent.tenantId, + listenToModelEvent.modelType.name, + listenToModelEvent.eventType, + ); - this.emit(EventName.ListenToModalEvent, listenToModelEventJSON as any); + this.socket.on(roomId, (model: JSONObject) => { + onEvent(BaseModel.fromJSON(model, listenToModelEvent.modelType) as Model); + }); - const roomId: string = RealtimeUtil.getRoomId( - listenToModelEvent.tenantId, - listenToModelEvent.modelType.name, - listenToModelEvent.eventType - ); + // Stop listening to the event. + const stopListening: () => void = (): void => { + this.socket.off(roomId); + }; - this.socket.on(roomId, (model: JSONObject) => { - onEvent( - BaseModel.fromJSON(model, listenToModelEvent.modelType) as Model - ); - }); + return stopListening; + } - // Stop listening to the event. - const stopListening: () => void = (): void => { - this.socket.off(roomId); - }; - - return stopListening; + public static listenToAnalyticsModelEvent<Model extends AnalyticsBaseModel>( + listenToModelEvent: ListenToModelEvent<Model>, + onEvent: (model: Model) => void, + ): () => void { + if (!this.socket) { + this.init(); } - public static listenToAnalyticsModelEvent<Model extends AnalyticsBaseModel>( - listenToModelEvent: ListenToModelEvent<Model>, - onEvent: (model: Model) => void - ): () => void { - if (!this.socket) { - this.init(); - } + const listenToModelEventJSON: ListenToModelEventJSON = { + eventType: listenToModelEvent.eventType, + modelType: DatabaseType.AnalyticsDatabase, + modelName: listenToModelEvent.modelType.name, + query: JSONFunctions.serialize(listenToModelEvent.query), + tenantId: listenToModelEvent.tenantId.toString(), + select: JSONFunctions.serialize(listenToModelEvent.select), + }; - const listenToModelEventJSON: ListenToModelEventJSON = { - eventType: listenToModelEvent.eventType, - modelType: DatabaseType.AnalyticsDatabase, - modelName: listenToModelEvent.modelType.name, - query: JSONFunctions.serialize(listenToModelEvent.query), - tenantId: listenToModelEvent.tenantId.toString(), - select: JSONFunctions.serialize(listenToModelEvent.select), - }; + this.emit(EventName.ListenToModalEvent, listenToModelEventJSON as any); - this.emit(EventName.ListenToModalEvent, listenToModelEventJSON as any); + const roomId: string = RealtimeUtil.getRoomId( + listenToModelEvent.tenantId, + listenToModelEvent.modelType.name, + listenToModelEvent.eventType, + ); - const roomId: string = RealtimeUtil.getRoomId( - listenToModelEvent.tenantId, - listenToModelEvent.modelType.name, - listenToModelEvent.eventType - ); + this.socket.on(roomId, (model: JSONObject) => { + onEvent( + AnalyticsBaseModel.fromJSON( + model, + listenToModelEvent.modelType, + ) as Model, + ); + }); - this.socket.on(roomId, (model: JSONObject) => { - onEvent( - AnalyticsBaseModel.fromJSON( - model, - listenToModelEvent.modelType - ) as Model - ); - }); + // Stop listening to the event. + const stopListening: () => void = (): void => { + this.socket.off(roomId); + }; - // Stop listening to the event. - const stopListening: () => void = (): void => { - this.socket.off(roomId); - }; + return stopListening; + } - return stopListening; + public static emit(eventName: string, data: JSONObject): void { + if (!this.socket) { + this.init(); } - public static emit(eventName: string, data: JSONObject): void { - if (!this.socket) { - this.init(); - } - - this.socket.emit(eventName, data); - } + this.socket.emit(eventName, data); + } } diff --git a/CommonUI/src/Utils/StatusPage.ts b/CommonUI/src/Utils/StatusPage.ts index 7d9380a0cf..b2366e0ba5 100644 --- a/CommonUI/src/Utils/StatusPage.ts +++ b/CommonUI/src/Utils/StatusPage.ts @@ -1,115 +1,115 @@ import { - CategoryCheckboxOption, - CheckboxCategory, -} from '../Components/CategoryCheckbox/CategoryCheckboxTypes'; -import { CategoryCheckboxOptionsAndCategories } from '../Components/CategoryCheckbox/Index'; -import ModelAPI, { ListResult } from '../Utils/ModelAPI/ModelAPI'; -import URL from 'Common/Types/API/URL'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import ObjectID from 'Common/Types/ObjectID'; -import StatusPageGroup from 'Model/Models/StatusPageGroup'; -import StatusPageResource from 'Model/Models/StatusPageResource'; + CategoryCheckboxOption, + CheckboxCategory, +} from "../Components/CategoryCheckbox/CategoryCheckboxTypes"; +import { CategoryCheckboxOptionsAndCategories } from "../Components/CategoryCheckbox/Index"; +import ModelAPI, { ListResult } from "../Utils/ModelAPI/ModelAPI"; +import URL from "Common/Types/API/URL"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import ObjectID from "Common/Types/ObjectID"; +import StatusPageGroup from "Model/Models/StatusPageGroup"; +import StatusPageResource from "Model/Models/StatusPageResource"; export default class StatusPageUtil { - public static async getCategoryCheckboxPropsBasedOnResources( - statusPageId: ObjectID, - overrideRequestUrl?: URL - ): Promise<CategoryCheckboxOptionsAndCategories> { - const categories: Array<CheckboxCategory> = []; - const options: Array<CategoryCheckboxOption> = []; + public static async getCategoryCheckboxPropsBasedOnResources( + statusPageId: ObjectID, + overrideRequestUrl?: URL, + ): Promise<CategoryCheckboxOptionsAndCategories> { + const categories: Array<CheckboxCategory> = []; + const options: Array<CategoryCheckboxOption> = []; - let resources: Array<StatusPageResource> = - await StatusPageUtil.getResources(statusPageId, overrideRequestUrl); + let resources: Array<StatusPageResource> = + await StatusPageUtil.getResources(statusPageId, overrideRequestUrl); - let resourceGroups: Array<StatusPageGroup> = resources - .map((resource: StatusPageResource) => { - return resource.statusPageGroup; - }) - .filter((group: StatusPageGroup | undefined) => { - return Boolean(group); - }) as Array<StatusPageGroup>; + let resourceGroups: Array<StatusPageGroup> = resources + .map((resource: StatusPageResource) => { + return resource.statusPageGroup; + }) + .filter((group: StatusPageGroup | undefined) => { + return Boolean(group); + }) as Array<StatusPageGroup>; - // now sort by order. + // now sort by order. - resourceGroups = resourceGroups.sort( - (a: StatusPageGroup, b: StatusPageGroup) => { - return a.order! - b.order!; + resourceGroups = resourceGroups.sort( + (a: StatusPageGroup, b: StatusPageGroup) => { + return a.order! - b.order!; + }, + ); + + // add categories. + + resourceGroups.forEach((group: StatusPageGroup) => { + //before we add make sure it doesn't already exist. + + if ( + categories.find((category: CheckboxCategory) => { + return category.id === group._id; + }) + ) { + return; + } + + categories.push({ + id: group._id!, + title: group.name!, + }); + }); + + // sort resources by order. + + resources = resources.sort( + (a: StatusPageResource, b: StatusPageResource) => { + return a.order! - b.order!; + }, + ); + + // add options. + + resources.forEach((resource: StatusPageResource) => { + options.push({ + value: resource._id!, + label: resource.displayName!, + categoryId: resource.statusPageGroup?._id || "", + }); + }); + + return { + categories, + options, + }; + } + + public static async getResources( + statusPageId: ObjectID, + overrideRequestUrl?: URL, + ): Promise<Array<StatusPageResource>> { + const resources: ListResult<StatusPageResource> = + await ModelAPI.getList<StatusPageResource>({ + modelType: StatusPageResource, + query: { + statusPageId: statusPageId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + displayName: true, + order: true, + statusPageGroup: { + _id: true, + name: true, + order: true, + }, + }, + sort: {}, + requestOptions: overrideRequestUrl + ? { + overrideRequestUrl: overrideRequestUrl, } - ); + : undefined, + }); - // add categories. - - resourceGroups.forEach((group: StatusPageGroup) => { - //before we add make sure it doesn't already exist. - - if ( - categories.find((category: CheckboxCategory) => { - return category.id === group._id; - }) - ) { - return; - } - - categories.push({ - id: group._id!, - title: group.name!, - }); - }); - - // sort resources by order. - - resources = resources.sort( - (a: StatusPageResource, b: StatusPageResource) => { - return a.order! - b.order!; - } - ); - - // add options. - - resources.forEach((resource: StatusPageResource) => { - options.push({ - value: resource._id!, - label: resource.displayName!, - categoryId: resource.statusPageGroup?._id || '', - }); - }); - - return { - categories, - options, - }; - } - - public static async getResources( - statusPageId: ObjectID, - overrideRequestUrl?: URL - ): Promise<Array<StatusPageResource>> { - const resources: ListResult<StatusPageResource> = - await ModelAPI.getList<StatusPageResource>({ - modelType: StatusPageResource, - query: { - statusPageId: statusPageId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - displayName: true, - order: true, - statusPageGroup: { - _id: true, - name: true, - order: true, - }, - }, - sort: {}, - requestOptions: overrideRequestUrl - ? { - overrideRequestUrl: overrideRequestUrl, - } - : undefined, - }); - - return resources.data; - } + return resources.data; + } } diff --git a/CommonUI/src/Utils/Tailwind.ts b/CommonUI/src/Utils/Tailwind.ts index 1381f254d7..396f7a9dca 100644 --- a/CommonUI/src/Utils/Tailwind.ts +++ b/CommonUI/src/Utils/Tailwind.ts @@ -1,5 +1,5 @@ export default class TailwindUtil { - public static getMarginNumberByLevel(level: number): number { - return level * 4; - } + public static getMarginNumberByLevel(level: number): number { + return level * 4; + } } diff --git a/CommonUI/src/Utils/Telemetry.ts b/CommonUI/src/Utils/Telemetry.ts index 454c956a2f..582e748a2a 100644 --- a/CommonUI/src/Utils/Telemetry.ts +++ b/CommonUI/src/Utils/Telemetry.ts @@ -1,57 +1,53 @@ import { - OpenTelemetryExporterOtlpEndpoint, - OpenTelemetryExporterOtlpHeaders, -} from '../Config'; -import { ZoneContextManager } from '@opentelemetry/context-zone'; -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; -import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; -import { Resource } from '@opentelemetry/resources'; + OpenTelemetryExporterOtlpEndpoint, + OpenTelemetryExporterOtlpHeaders, +} from "../Config"; +import { ZoneContextManager } from "@opentelemetry/context-zone"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { registerInstrumentations } from "@opentelemetry/instrumentation"; +import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch"; +import { XMLHttpRequestInstrumentation } from "@opentelemetry/instrumentation-xml-http-request"; +import { Resource } from "@opentelemetry/resources"; import { - BatchSpanProcessor, - TracerConfig, - WebTracerProvider, -} from '@opentelemetry/sdk-trace-web'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; + BatchSpanProcessor, + TracerConfig, + WebTracerProvider, +} from "@opentelemetry/sdk-trace-web"; +import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; export default class Telemetry { - public static init(data: { serviceName: string }): void { - const hasHeaders: boolean = - Object.keys(OpenTelemetryExporterOtlpHeaders).length > 0; + public static init(data: { serviceName: string }): void { + const hasHeaders: boolean = + Object.keys(OpenTelemetryExporterOtlpHeaders).length > 0; - if (OpenTelemetryExporterOtlpEndpoint && hasHeaders) { - const providerConfig: TracerConfig = { - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: data.serviceName, - }), - }; + if (OpenTelemetryExporterOtlpEndpoint && hasHeaders) { + const providerConfig: TracerConfig = { + resource: new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: data.serviceName, + }), + }; - const provider: WebTracerProvider = new WebTracerProvider( - providerConfig - ); + const provider: WebTracerProvider = new WebTracerProvider(providerConfig); - provider.addSpanProcessor( - new BatchSpanProcessor( - new OTLPTraceExporter({ - url: - OpenTelemetryExporterOtlpEndpoint?.toString() + - '/v1/traces', - headers: OpenTelemetryExporterOtlpHeaders, - }) - ) - ); + provider.addSpanProcessor( + new BatchSpanProcessor( + new OTLPTraceExporter({ + url: OpenTelemetryExporterOtlpEndpoint?.toString() + "/v1/traces", + headers: OpenTelemetryExporterOtlpHeaders, + }), + ), + ); - provider.register({ - contextManager: new ZoneContextManager(), - }); + provider.register({ + contextManager: new ZoneContextManager(), + }); - registerInstrumentations({ - instrumentations: [ - new FetchInstrumentation(), - new XMLHttpRequestInstrumentation(), - ], - }); - } + registerInstrumentations({ + instrumentations: [ + new FetchInstrumentation(), + new XMLHttpRequestInstrumentation(), + ], + }); } + } } diff --git a/CommonUI/src/Utils/User.ts b/CommonUI/src/Utils/User.ts index dc3fd4db16..fa9a8e5a31 100644 --- a/CommonUI/src/Utils/User.ts +++ b/CommonUI/src/Utils/User.ts @@ -1,141 +1,138 @@ -import { IDENTITY_URL } from '../Config'; -import LocalStorage from './LocalStorage'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import Email from 'Common/Types/Email'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import Name from 'Common/Types/Name'; -import ObjectID from 'Common/Types/ObjectID'; -import API from 'Common/Utils/API'; +import { IDENTITY_URL } from "../Config"; +import LocalStorage from "./LocalStorage"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import Email from "Common/Types/Email"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import Name from "Common/Types/Name"; +import ObjectID from "Common/Types/ObjectID"; +import API from "Common/Utils/API"; export default class User { - public static setProfilePicId(id: ObjectID | null): void { - if (!id) { - LocalStorage.removeItem('profile_pic_id'); - return; - } - - LocalStorage.setItem('profile_pic_id', id.toString()); + public static setProfilePicId(id: ObjectID | null): void { + if (!id) { + LocalStorage.removeItem("profile_pic_id"); + return; } - public static getProfilePicId(): ObjectID | null { - if (!LocalStorage.getItem('profile_pic_id')) { - return null; - } + LocalStorage.setItem("profile_pic_id", id.toString()); + } - return new ObjectID( - (LocalStorage.getItem('profile_pic_id') as string) || '' - ); + public static getProfilePicId(): ObjectID | null { + if (!LocalStorage.getItem("profile_pic_id")) { + return null; } - public static isCardRegistered(): boolean { - return Boolean(LocalStorage.getItem('cardRegistered')); + return new ObjectID( + (LocalStorage.getItem("profile_pic_id") as string) || "", + ); + } + + public static isCardRegistered(): boolean { + return Boolean(LocalStorage.getItem("cardRegistered")); + } + + public static setCardRegistered(value: boolean): void { + LocalStorage.setItem("cardRegistered", value.toString()); + } + + public static setUserId(id: ObjectID): void { + LocalStorage.setItem("user_id", id.toString()); + } + + public static getUserId(): ObjectID { + return new ObjectID((LocalStorage.getItem("user_id") as string) || ""); + } + + public static getName(): Name { + return new Name((LocalStorage.getItem("user_name") as string) || ""); + } + + public static setName(name: Name): void { + LocalStorage.setItem("user_name", name.toString()); + } + + public static getEmail(): Email | null { + if (!LocalStorage.getItem("user_email")) { + return null; } - public static setCardRegistered(value: boolean): void { - LocalStorage.setItem('cardRegistered', value.toString()); + return new Email(LocalStorage.getItem("user_email") as string); + } + + public static setEmail(email: Email): void { + LocalStorage.setItem("user_email", email); + } + + public static initialUrl(): URL { + if (LocalStorage.getItem("initialUrl")) { + return URL.fromString(LocalStorage.getItem("initialUrl") as string); } - public static setUserId(id: ObjectID): void { - LocalStorage.setItem('user_id', id.toString()); + throw new BadDataException("Initial URL not found"); + } + + public static setInitialUrl(url: URL): void { + LocalStorage.setItem("initialUrl", url); + } + + // TODO: Fix project type + public static setProject(project: JSONObject): void { + LocalStorage.setItem("project", project); + } + + public static getProject(): JSONObject { + return LocalStorage.getItem("project") as JSONObject; + } + + public static clear(): void { + LocalStorage.clear(); + } + + public static removeUserId(): void { + LocalStorage.removeItem("user_id"); + } + + public static removeAccessToken(): void { + LocalStorage.removeItem("token"); + } + + public static removeInitialUrl(): void { + return sessionStorage.removeItem("initialUrl"); + } + + public static isMasterAdmin(): boolean { + return LocalStorage.getItem("is_master_admin") as boolean; + } + + public static setIsMasterAdmin(isMasterAdmin: boolean): void { + LocalStorage.setItem("is_master_admin", isMasterAdmin); + } + + public static isLoggedIn(): boolean { + return Boolean(this.getEmail()); + } + + public static async logout(): Promise<void> { + await API.post(URL.fromString(IDENTITY_URL.toString()).addRoute("/logout")); + LocalStorage.clear(); + } + + public static getUtmParams(): Dictionary<string> { + const localStorageItems: Dictionary<string> = LocalStorage.getAllItems(); + const result: Dictionary<string> = {}; + + for (const key in localStorageItems) { + if (!localStorageItems[key]) { + continue; + } + + if (key.startsWith("utm")) { + result[key] = localStorageItems[key] as string; + } } - public static getUserId(): ObjectID { - return new ObjectID((LocalStorage.getItem('user_id') as string) || ''); - } - - public static getName(): Name { - return new Name((LocalStorage.getItem('user_name') as string) || ''); - } - - public static setName(name: Name): void { - LocalStorage.setItem('user_name', name.toString()); - } - - public static getEmail(): Email | null { - if (!LocalStorage.getItem('user_email')) { - return null; - } - - return new Email(LocalStorage.getItem('user_email') as string); - } - - public static setEmail(email: Email): void { - LocalStorage.setItem('user_email', email); - } - - public static initialUrl(): URL { - if (LocalStorage.getItem('initialUrl')) { - return URL.fromString(LocalStorage.getItem('initialUrl') as string); - } - - throw new BadDataException('Initial URL not found'); - } - - public static setInitialUrl(url: URL): void { - LocalStorage.setItem('initialUrl', url); - } - - // TODO: Fix project type - public static setProject(project: JSONObject): void { - LocalStorage.setItem('project', project); - } - - public static getProject(): JSONObject { - return LocalStorage.getItem('project') as JSONObject; - } - - public static clear(): void { - LocalStorage.clear(); - } - - public static removeUserId(): void { - LocalStorage.removeItem('user_id'); - } - - public static removeAccessToken(): void { - LocalStorage.removeItem('token'); - } - - public static removeInitialUrl(): void { - return sessionStorage.removeItem('initialUrl'); - } - - public static isMasterAdmin(): boolean { - return LocalStorage.getItem('is_master_admin') as boolean; - } - - public static setIsMasterAdmin(isMasterAdmin: boolean): void { - LocalStorage.setItem('is_master_admin', isMasterAdmin); - } - - public static isLoggedIn(): boolean { - return Boolean(this.getEmail()); - } - - public static async logout(): Promise<void> { - await API.post( - URL.fromString(IDENTITY_URL.toString()).addRoute('/logout') - ); - LocalStorage.clear(); - } - - public static getUtmParams(): Dictionary<string> { - const localStorageItems: Dictionary<string> = - LocalStorage.getAllItems(); - const result: Dictionary<string> = {}; - - for (const key in localStorageItems) { - if (!localStorageItems[key]) { - continue; - } - - if (key.startsWith('utm')) { - result[key] = localStorageItems[key] as string; - } - } - - return result; - } + return result; + } } diff --git a/Copilot/Config.ts b/Copilot/Config.ts index faab4c9414..865986e4c1 100644 --- a/Copilot/Config.ts +++ b/Copilot/Config.ts @@ -1,31 +1,31 @@ -import URL from 'Common/Types/API/URL'; +import URL from "Common/Types/API/URL"; type GetStringFunction = () => string; type GetStringOrNullFunction = () => string | null; type GetURLFunction = () => URL; export const GetOneUptimeURL: GetURLFunction = () => { - return URL.fromString( - process.env['ONEUPTIME_URL'] || 'https://oneuptime.com' - ); + return URL.fromString( + process.env["ONEUPTIME_URL"] || "https://oneuptime.com", + ); }; export const GetRepositorySecretKey: GetStringOrNullFunction = (): - | string - | null => { - return process.env['ONEUPTIME_REPOSITORY_SECRET_KEY'] || null; + | string + | null => { + return process.env["ONEUPTIME_REPOSITORY_SECRET_KEY"] || null; }; export const GetLocalRepositoryPath: GetStringFunction = (): string => { - return process.env['ONEUPTIME_LOCAL_REPOSITORY_PATH'] || '/repository'; + return process.env["ONEUPTIME_LOCAL_REPOSITORY_PATH"] || "/repository"; }; export const GetGitHubToken: GetStringOrNullFunction = (): string | null => { - const token: string | null = process.env['GITHUB_TOKEN'] || null; - return token; + const token: string | null = process.env["GITHUB_TOKEN"] || null; + return token; }; export const GetGitHubUsername: GetStringOrNullFunction = (): string | null => { - const username: string | null = process.env['GITHUB_USERNAME'] || null; - return username; + const username: string | null = process.env["GITHUB_USERNAME"] || null; + return username; }; diff --git a/Copilot/Index.ts b/Copilot/Index.ts index 2b3e439518..e45ab0e164 100644 --- a/Copilot/Index.ts +++ b/Copilot/Index.ts @@ -1,59 +1,59 @@ import CodeRepositoryUtil, { - CodeRepositoryResult, -} from './Utils/CodeRepository'; -import InitUtil from './Utils/Init'; -import ServiceRepositoryUtil from './Utils/ServiceRepository'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import Dictionary from 'Common/Types/Dictionary'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import CodeRepositoryFile from 'CommonServer/Utils/CodeRepository/CodeRepositoryFile'; -import logger from 'CommonServer/Utils/Logger'; -import dotenv from 'dotenv'; + CodeRepositoryResult, +} from "./Utils/CodeRepository"; +import InitUtil from "./Utils/Init"; +import ServiceRepositoryUtil from "./Utils/ServiceRepository"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import Dictionary from "Common/Types/Dictionary"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import CodeRepositoryFile from "CommonServer/Utils/CodeRepository/CodeRepositoryFile"; +import logger from "CommonServer/Utils/Logger"; +import dotenv from "dotenv"; dotenv.config(); -logger.info('OneUptime Copilot is starting...'); +logger.info("OneUptime Copilot is starting..."); const init: PromiseVoidFunction = async (): Promise<void> => { - const codeRepositoryResult: CodeRepositoryResult = await InitUtil.init(); + const codeRepositoryResult: CodeRepositoryResult = await InitUtil.init(); - for (const serviceRepository of codeRepositoryResult.servicesRepository) { - const filesInService: Dictionary<CodeRepositoryFile> = - await ServiceRepositoryUtil.getFilesInServiceDirectory({ - serviceRepository, - }); + for (const serviceRepository of codeRepositoryResult.servicesRepository) { + const filesInService: Dictionary<CodeRepositoryFile> = + await ServiceRepositoryUtil.getFilesInServiceDirectory({ + serviceRepository, + }); - logger.info( - `Files found in ${serviceRepository.serviceCatalog?.name}: ${ - Object.keys(filesInService).length - }` - ); - } + logger.info( + `Files found in ${serviceRepository.serviceCatalog?.name}: ${ + Object.keys(filesInService).length + }`, + ); + } }; init() - .then(() => { - process.exit(0); - }) - .catch(async (error: Error | HTTPErrorResponse) => { - try { - await CodeRepositoryUtil.discardChanges(); + .then(() => { + process.exit(0); + }) + .catch(async (error: Error | HTTPErrorResponse) => { + try { + await CodeRepositoryUtil.discardChanges(); - // change back to main branch. - await CodeRepositoryUtil.checkoutMainBranch(); - } catch (e) { - // do nothing. - } + // change back to main branch. + await CodeRepositoryUtil.checkoutMainBranch(); + } catch (e) { + // do nothing. + } - logger.error('Error in starting OneUptime Copilot: '); + logger.error("Error in starting OneUptime Copilot: "); - if (error instanceof HTTPErrorResponse) { - logger.error(error.message); - } else if (error instanceof Error) { - logger.error(error.message); - } else { - logger.error(error); - } + if (error instanceof HTTPErrorResponse) { + logger.error(error.message); + } else if (error instanceof Error) { + logger.error(error.message); + } else { + logger.error(error); + } - process.exit(1); - }); + process.exit(1); + }); diff --git a/Copilot/Utils/CodeRepository.ts b/Copilot/Utils/CodeRepository.ts index 3c702238ce..1e266eddb7 100644 --- a/Copilot/Utils/CodeRepository.ts +++ b/Copilot/Utils/CodeRepository.ts @@ -1,431 +1,418 @@ import { - GetGitHubToken, - GetGitHubUsername, - GetLocalRepositoryPath, - GetOneUptimeURL, - GetRepositorySecretKey, -} from '../Config'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import CodeRepositoryType from 'Common/Types/CodeRepository/CodeRepositoryType'; -import PullRequest from 'Common/Types/CodeRepository/PullRequest'; -import PullRequestState from 'Common/Types/CodeRepository/PullRequestState'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import API from 'Common/Utils/API'; -import CodeRepositoryServerUtil from 'CommonServer/Utils/CodeRepository/CodeRepository'; -import GitHubUtil from 'CommonServer/Utils/CodeRepository/GitHub/GitHub'; -import logger from 'CommonServer/Utils/Logger'; -import CodeRepositoryModel from 'Model/Models/CodeRepository'; -import ServiceRepository from 'Model/Models/ServiceRepository'; + GetGitHubToken, + GetGitHubUsername, + GetLocalRepositoryPath, + GetOneUptimeURL, + GetRepositorySecretKey, +} from "../Config"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import CodeRepositoryType from "Common/Types/CodeRepository/CodeRepositoryType"; +import PullRequest from "Common/Types/CodeRepository/PullRequest"; +import PullRequestState from "Common/Types/CodeRepository/PullRequestState"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import API from "Common/Utils/API"; +import CodeRepositoryServerUtil from "CommonServer/Utils/CodeRepository/CodeRepository"; +import GitHubUtil from "CommonServer/Utils/CodeRepository/GitHub/GitHub"; +import logger from "CommonServer/Utils/Logger"; +import CodeRepositoryModel from "Model/Models/CodeRepository"; +import ServiceRepository from "Model/Models/ServiceRepository"; export interface CodeRepositoryResult { - codeRepository: CodeRepositoryModel; - servicesRepository: Array<ServiceRepository>; + codeRepository: CodeRepositoryModel; + servicesRepository: Array<ServiceRepository>; } export default class CodeRepositoryUtil { - public static codeRepositoryResult: CodeRepositoryResult | null = null; - public static gitHubUtil: GitHubUtil | null = null; + public static codeRepositoryResult: CodeRepositoryResult | null = null; + public static gitHubUtil: GitHubUtil | null = null; - public static getGitHubUtil(): GitHubUtil { - if (!this.gitHubUtil) { - const gitHubToken: string | null = GetGitHubToken(); + public static getGitHubUtil(): GitHubUtil { + if (!this.gitHubUtil) { + const gitHubToken: string | null = GetGitHubToken(); - const gitHubUsername: string | null = GetGitHubUsername(); + const gitHubUsername: string | null = GetGitHubUsername(); - if (!gitHubUsername) { - throw new BadDataException('GitHub Username is required'); - } + if (!gitHubUsername) { + throw new BadDataException("GitHub Username is required"); + } - if (!gitHubToken) { - throw new BadDataException('GitHub Token is required'); - } + if (!gitHubToken) { + throw new BadDataException("GitHub Token is required"); + } - this.gitHubUtil = new GitHubUtil({ - authToken: gitHubToken, - username: gitHubUsername!, - }); - } - - return this.gitHubUtil; + this.gitHubUtil = new GitHubUtil({ + authToken: gitHubToken, + username: gitHubUsername!, + }); } - public static getBranchName(data: { - branchName: string; - serviceRepository: ServiceRepository; - }): string { - if (!data.serviceRepository.serviceCatalog) { - throw new BadDataException('Service Catalog is required'); - } + return this.gitHubUtil; + } - if (!data.serviceRepository.serviceCatalog.name) { - throw new BadDataException('Service Catalog Name is required'); - } + public static getBranchName(data: { + branchName: string; + serviceRepository: ServiceRepository; + }): string { + if (!data.serviceRepository.serviceCatalog) { + throw new BadDataException("Service Catalog is required"); + } - return ( - 'oneuptime-' + - data.serviceRepository.serviceCatalog?.name?.toLowerCase() + - '-' + - data.branchName + if (!data.serviceRepository.serviceCatalog.name) { + throw new BadDataException("Service Catalog Name is required"); + } + + return ( + "oneuptime-" + + data.serviceRepository.serviceCatalog?.name?.toLowerCase() + + "-" + + data.branchName + ); + } + + public static async createBranch(data: { + branchName: string; + serviceRepository: ServiceRepository; + }): Promise<void> { + const branchName: string = this.getBranchName({ + branchName: data.branchName, + serviceRepository: data.serviceRepository, + }); + + await CodeRepositoryServerUtil.createBranch({ + repoPath: GetLocalRepositoryPath(), + branchName: branchName, + }); + } + + public static async createOrCheckoutBranch(data: { + serviceRepository: ServiceRepository; + branchName: string; + }): Promise<void> { + const branchName: string = this.getBranchName({ + branchName: data.branchName, + serviceRepository: data.serviceRepository, + }); + + await CodeRepositoryServerUtil.createOrCheckoutBranch({ + repoPath: GetLocalRepositoryPath(), + branchName: branchName, + }); + } + + public static async writeToFile(data: { + filePath: string; + content: string; + }): Promise<void> { + await CodeRepositoryServerUtil.writeToFile({ + repoPath: GetLocalRepositoryPath(), + filePath: data.filePath, + content: data.content, + }); + } + + public static async createDirectory(data: { + directoryPath: string; + }): Promise<void> { + await CodeRepositoryServerUtil.createDirectory({ + repoPath: GetLocalRepositoryPath(), + directoryPath: data.directoryPath, + }); + } + + public static async deleteFile(data: { filePath: string }): Promise<void> { + await CodeRepositoryServerUtil.deleteFile({ + repoPath: GetLocalRepositoryPath(), + filePath: data.filePath, + }); + } + + public static async deleteDirectory(data: { + directoryPath: string; + }): Promise<void> { + await CodeRepositoryServerUtil.deleteDirectory({ + repoPath: GetLocalRepositoryPath(), + directoryPath: data.directoryPath, + }); + } + + public static async discardChanges(): Promise<void> { + await CodeRepositoryServerUtil.discardChanges({ + repoPath: GetLocalRepositoryPath(), + }); + } + + public static async checkoutBranch(data: { + branchName: string; + }): Promise<void> { + await CodeRepositoryServerUtil.checkoutBranch({ + repoPath: GetLocalRepositoryPath(), + branchName: data.branchName, + }); + } + + public static async checkoutMainBranch(): Promise<void> { + const codeRepository: CodeRepositoryModel = await this.getCodeRepository(); + + if (!codeRepository.mainBranchName) { + throw new BadDataException("Main Branch Name is required"); + } + + await this.checkoutBranch({ + branchName: codeRepository.mainBranchName!, + }); + } + + public static async addFilesToGit(data: { + filePaths: Array<string>; + }): Promise<void> { + await CodeRepositoryServerUtil.addFilesToGit({ + repoPath: GetLocalRepositoryPath(), + filePaths: data.filePaths, + }); + } + + public static async commitChanges(data: { message: string }): Promise<void> { + let username: string | null = null; + + if ( + this.codeRepositoryResult?.codeRepository.repositoryHostedAt === + CodeRepositoryType.GitHub + ) { + username = GetGitHubUsername(); + } + + if (!username) { + throw new BadDataException("Username is required"); + } + + await CodeRepositoryServerUtil.commitChanges({ + repoPath: GetLocalRepositoryPath(), + message: data.message, + username: username, + }); + } + + public static async pushChanges(data: { + branchName: string; + serviceRepository: ServiceRepository; + }): Promise<void> { + const branchName: string = this.getBranchName({ + branchName: data.branchName, + serviceRepository: data.serviceRepository, + }); + + const codeRepository: CodeRepositoryModel = await this.getCodeRepository(); + + if (!codeRepository.mainBranchName) { + throw new BadDataException("Main Branch Name is required"); + } + + if (!codeRepository.organizationName) { + throw new BadDataException("Organization Name is required"); + } + + if (!codeRepository.repositoryName) { + throw new BadDataException("Repository Name is required"); + } + + if (codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub) { + return await this.getGitHubUtil().pushChanges({ + repoPath: GetLocalRepositoryPath(), + branchName: branchName, + organizationName: codeRepository.organizationName, + repositoryName: codeRepository.repositoryName, + }); + } + } + + public static async createPullRequest(data: { + branchName: string; + title: string; + body: string; + serviceRepository: ServiceRepository; + }): Promise<PullRequest> { + const branchName: string = this.getBranchName({ + branchName: data.branchName, + serviceRepository: data.serviceRepository, + }); + + const codeRepository: CodeRepositoryModel = await this.getCodeRepository(); + + if (!codeRepository.mainBranchName) { + throw new BadDataException("Main Branch Name is required"); + } + + if (!codeRepository.organizationName) { + throw new BadDataException("Organization Name is required"); + } + + if (!codeRepository.repositoryName) { + throw new BadDataException("Repository Name is required"); + } + + if (codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub) { + return await this.getGitHubUtil().createPullRequest({ + headBranchName: branchName, + baseBranchName: codeRepository.mainBranchName, + organizationName: codeRepository.organizationName, + repositoryName: codeRepository.repositoryName, + title: data.title, + body: data.body, + }); + } + throw new BadDataException("Code Repository type not supported"); + } + + public static async getServicesToImproveCode(data: { + codeRepository: CodeRepositoryModel; + services: Array<ServiceRepository>; + }): Promise<Array<ServiceRepository>> { + const servicesToImproveCode: Array<ServiceRepository> = []; + + for (const service of data.services) { + if (!data.codeRepository.mainBranchName) { + throw new BadDataException("Main Branch Name is required"); + } + + if (!data.codeRepository.organizationName) { + throw new BadDataException("Organization Name is required"); + } + + if (!data.codeRepository.repositoryName) { + throw new BadDataException("Repository Name is required"); + } + + if (!service.limitNumberOfOpenPullRequestsCount) { + throw new BadDataException( + "Limit Number Of Open Pull Requests Count is required", ); - } + } - public static async createBranch(data: { - branchName: string; - serviceRepository: ServiceRepository; - }): Promise<void> { - const branchName: string = this.getBranchName({ - branchName: data.branchName, - serviceRepository: data.serviceRepository, - }); + if ( + data.codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub + ) { + const gitHuhbToken: string | null = GetGitHubToken(); - await CodeRepositoryServerUtil.createBranch({ - repoPath: GetLocalRepositoryPath(), - branchName: branchName, - }); - } - - public static async createOrCheckoutBranch(data: { - serviceRepository: ServiceRepository; - branchName: string; - }): Promise<void> { - const branchName: string = this.getBranchName({ - branchName: data.branchName, - serviceRepository: data.serviceRepository, - }); - - await CodeRepositoryServerUtil.createOrCheckoutBranch({ - repoPath: GetLocalRepositoryPath(), - branchName: branchName, - }); - } - - public static async writeToFile(data: { - filePath: string; - content: string; - }): Promise<void> { - await CodeRepositoryServerUtil.writeToFile({ - repoPath: GetLocalRepositoryPath(), - filePath: data.filePath, - content: data.content, - }); - } - - public static async createDirectory(data: { - directoryPath: string; - }): Promise<void> { - await CodeRepositoryServerUtil.createDirectory({ - repoPath: GetLocalRepositoryPath(), - directoryPath: data.directoryPath, - }); - } - - public static async deleteFile(data: { filePath: string }): Promise<void> { - await CodeRepositoryServerUtil.deleteFile({ - repoPath: GetLocalRepositoryPath(), - filePath: data.filePath, - }); - } - - public static async deleteDirectory(data: { - directoryPath: string; - }): Promise<void> { - await CodeRepositoryServerUtil.deleteDirectory({ - repoPath: GetLocalRepositoryPath(), - directoryPath: data.directoryPath, - }); - } - - public static async discardChanges(): Promise<void> { - await CodeRepositoryServerUtil.discardChanges({ - repoPath: GetLocalRepositoryPath(), - }); - } - - public static async checkoutBranch(data: { - branchName: string; - }): Promise<void> { - await CodeRepositoryServerUtil.checkoutBranch({ - repoPath: GetLocalRepositoryPath(), - branchName: data.branchName, - }); - } - - public static async checkoutMainBranch(): Promise<void> { - const codeRepository: CodeRepositoryModel = - await this.getCodeRepository(); - - if (!codeRepository.mainBranchName) { - throw new BadDataException('Main Branch Name is required'); + if (!gitHuhbToken) { + throw new BadDataException("GitHub Token is required"); } - await this.checkoutBranch({ - branchName: codeRepository.mainBranchName!, - }); - } - - public static async addFilesToGit(data: { - filePaths: Array<string>; - }): Promise<void> { - await CodeRepositoryServerUtil.addFilesToGit({ - repoPath: GetLocalRepositoryPath(), - filePaths: data.filePaths, - }); - } - - public static async commitChanges(data: { - message: string; - }): Promise<void> { - let username: string | null = null; + const numberOfPullRequestForThisService: number = + await this.getGitHubUtil().getNumberOfPullRequestsExistForService({ + serviceRepository: service, + pullRequestState: PullRequestState.Open, + baseBranchName: data.codeRepository.mainBranchName, + organizationName: data.codeRepository.organizationName, + repositoryName: data.codeRepository.repositoryName, + }); if ( - this.codeRepositoryResult?.codeRepository.repositoryHostedAt === - CodeRepositoryType.GitHub + numberOfPullRequestForThisService < + service.limitNumberOfOpenPullRequestsCount ) { - username = GetGitHubUsername(); + servicesToImproveCode.push(service); + logger.info( + `Service ${service.serviceCatalog?.name} has ${numberOfPullRequestForThisService} open pull requests. Limit is ${service.limitNumberOfOpenPullRequestsCount}. Adding to the list to improve code...`, + ); + } else { + logger.warn( + `Service ${service.serviceCatalog?.name} has ${numberOfPullRequestForThisService} open pull requests. Limit is ${service.limitNumberOfOpenPullRequestsCount}. Skipping...`, + ); } - - if (!username) { - throw new BadDataException('Username is required'); - } - - await CodeRepositoryServerUtil.commitChanges({ - repoPath: GetLocalRepositoryPath(), - message: data.message, - username: username, - }); + } } - public static async pushChanges(data: { - branchName: string; - serviceRepository: ServiceRepository; - }): Promise<void> { - const branchName: string = this.getBranchName({ - branchName: data.branchName, - serviceRepository: data.serviceRepository, - }); + return servicesToImproveCode; + } - const codeRepository: CodeRepositoryModel = - await this.getCodeRepository(); - - if (!codeRepository.mainBranchName) { - throw new BadDataException('Main Branch Name is required'); - } - - if (!codeRepository.organizationName) { - throw new BadDataException('Organization Name is required'); - } - - if (!codeRepository.repositoryName) { - throw new BadDataException('Repository Name is required'); - } - - if (codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub) { - return await this.getGitHubUtil().pushChanges({ - repoPath: GetLocalRepositoryPath(), - branchName: branchName, - organizationName: codeRepository.organizationName, - repositoryName: codeRepository.repositoryName, - }); - } + public static async getCodeRepositoryResult(): Promise<CodeRepositoryResult> { + if (this.codeRepositoryResult) { + return this.codeRepositoryResult; } - public static async createPullRequest(data: { - branchName: string; - title: string; - body: string; - serviceRepository: ServiceRepository; - }): Promise<PullRequest> { - const branchName: string = this.getBranchName({ - branchName: data.branchName, - serviceRepository: data.serviceRepository, - }); + const repositorySecretKey: string | null = GetRepositorySecretKey(); - const codeRepository: CodeRepositoryModel = - await this.getCodeRepository(); - - if (!codeRepository.mainBranchName) { - throw new BadDataException('Main Branch Name is required'); - } - - if (!codeRepository.organizationName) { - throw new BadDataException('Organization Name is required'); - } - - if (!codeRepository.repositoryName) { - throw new BadDataException('Repository Name is required'); - } - - if (codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub) { - return await this.getGitHubUtil().createPullRequest({ - headBranchName: branchName, - baseBranchName: codeRepository.mainBranchName, - organizationName: codeRepository.organizationName, - repositoryName: codeRepository.repositoryName, - title: data.title, - body: data.body, - }); - } - throw new BadDataException('Code Repository type not supported'); + if (!repositorySecretKey) { + throw new BadDataException("Repository Secret Key is required"); } - public static async getServicesToImproveCode(data: { - codeRepository: CodeRepositoryModel; - services: Array<ServiceRepository>; - }): Promise<Array<ServiceRepository>> { - const servicesToImproveCode: Array<ServiceRepository> = []; + const url: URL = URL.fromString( + GetOneUptimeURL().toString() + "/api", + ).addRoute( + `${new CodeRepositoryModel() + .getCrudApiPath() + ?.toString()}/get-code-repository/${repositorySecretKey}`, + ); - for (const service of data.services) { - if (!data.codeRepository.mainBranchName) { - throw new BadDataException('Main Branch Name is required'); - } + const codeRepositoryResult: HTTPErrorResponse | HTTPResponse<JSONObject> = + await API.get(url); - if (!data.codeRepository.organizationName) { - throw new BadDataException('Organization Name is required'); - } - - if (!data.codeRepository.repositoryName) { - throw new BadDataException('Repository Name is required'); - } - - if (!service.limitNumberOfOpenPullRequestsCount) { - throw new BadDataException( - 'Limit Number Of Open Pull Requests Count is required' - ); - } - - if ( - data.codeRepository.repositoryHostedAt === - CodeRepositoryType.GitHub - ) { - const gitHuhbToken: string | null = GetGitHubToken(); - - if (!gitHuhbToken) { - throw new BadDataException('GitHub Token is required'); - } - - const numberOfPullRequestForThisService: number = - await this.getGitHubUtil().getNumberOfPullRequestsExistForService( - { - serviceRepository: service, - pullRequestState: PullRequestState.Open, - baseBranchName: data.codeRepository.mainBranchName, - organizationName: - data.codeRepository.organizationName, - repositoryName: data.codeRepository.repositoryName, - } - ); - - if ( - numberOfPullRequestForThisService < - service.limitNumberOfOpenPullRequestsCount - ) { - servicesToImproveCode.push(service); - logger.info( - `Service ${service.serviceCatalog?.name} has ${numberOfPullRequestForThisService} open pull requests. Limit is ${service.limitNumberOfOpenPullRequestsCount}. Adding to the list to improve code...` - ); - } else { - logger.warn( - `Service ${service.serviceCatalog?.name} has ${numberOfPullRequestForThisService} open pull requests. Limit is ${service.limitNumberOfOpenPullRequestsCount}. Skipping...` - ); - } - } - } - - return servicesToImproveCode; + if (codeRepositoryResult instanceof HTTPErrorResponse) { + throw codeRepositoryResult; } - public static async getCodeRepositoryResult(): Promise<CodeRepositoryResult> { - if (this.codeRepositoryResult) { - return this.codeRepositoryResult; - } + const codeRepository: CodeRepositoryModel = CodeRepositoryModel.fromJSON( + codeRepositoryResult.data["codeRepository"] as JSONObject, + CodeRepositoryModel, + ) as CodeRepositoryModel; - const repositorySecretKey: string | null = GetRepositorySecretKey(); + const servicesRepository: Array<ServiceRepository> = ( + codeRepositoryResult.data["servicesRepository"] as JSONArray + ).map((serviceRepository: JSONObject) => { + return ServiceRepository.fromJSON( + serviceRepository, + ServiceRepository, + ) as ServiceRepository; + }); - if (!repositorySecretKey) { - throw new BadDataException('Repository Secret Key is required'); - } - - const url: URL = URL.fromString( - GetOneUptimeURL().toString() + '/api' - ).addRoute( - `${new CodeRepositoryModel() - .getCrudApiPath() - ?.toString()}/get-code-repository/${repositorySecretKey}` - ); - - const codeRepositoryResult: - | HTTPErrorResponse - | HTTPResponse<JSONObject> = await API.get(url); - - if (codeRepositoryResult instanceof HTTPErrorResponse) { - throw codeRepositoryResult; - } - - const codeRepository: CodeRepositoryModel = - CodeRepositoryModel.fromJSON( - codeRepositoryResult.data['codeRepository'] as JSONObject, - CodeRepositoryModel - ) as CodeRepositoryModel; - - const servicesRepository: Array<ServiceRepository> = ( - codeRepositoryResult.data['servicesRepository'] as JSONArray - ).map((serviceRepository: JSONObject) => { - return ServiceRepository.fromJSON( - serviceRepository, - ServiceRepository - ) as ServiceRepository; - }); - - if (!codeRepository) { - throw new BadDataException( - 'Code Repository not found with the secret key provided.' - ); - } - - if (!servicesRepository || servicesRepository.length === 0) { - throw new BadDataException( - 'No services attached to this repository. Please attach services to this repository on OneUptime Dashboard.' - ); - } - - logger.info(`Code Repository found: ${codeRepository.name}`); - - logger.info('Services found in the repository:'); - - servicesRepository.forEach((serviceRepository: ServiceRepository) => { - logger.info(`- ${serviceRepository.serviceCatalog?.name}`); - }); - - this.codeRepositoryResult = { - codeRepository, - servicesRepository, - }; - - return this.codeRepositoryResult; + if (!codeRepository) { + throw new BadDataException( + "Code Repository not found with the secret key provided.", + ); } - public static async getCodeRepository(): Promise<CodeRepositoryModel> { - if (!this.codeRepositoryResult) { - const result: CodeRepositoryResult = - await this.getCodeRepositoryResult(); - return result.codeRepository; - } - - return this.codeRepositoryResult.codeRepository; + if (!servicesRepository || servicesRepository.length === 0) { + throw new BadDataException( + "No services attached to this repository. Please attach services to this repository on OneUptime Dashboard.", + ); } - public static async getServiceRepositories(): Promise< - Array<ServiceRepository> - > { - if (!this.codeRepositoryResult) { - const result: CodeRepositoryResult = - await this.getCodeRepositoryResult(); - return result.servicesRepository; - } + logger.info(`Code Repository found: ${codeRepository.name}`); - return this.codeRepositoryResult.servicesRepository; + logger.info("Services found in the repository:"); + + servicesRepository.forEach((serviceRepository: ServiceRepository) => { + logger.info(`- ${serviceRepository.serviceCatalog?.name}`); + }); + + this.codeRepositoryResult = { + codeRepository, + servicesRepository, + }; + + return this.codeRepositoryResult; + } + + public static async getCodeRepository(): Promise<CodeRepositoryModel> { + if (!this.codeRepositoryResult) { + const result: CodeRepositoryResult = await this.getCodeRepositoryResult(); + return result.codeRepository; } + + return this.codeRepositoryResult.codeRepository; + } + + public static async getServiceRepositories(): Promise< + Array<ServiceRepository> + > { + if (!this.codeRepositoryResult) { + const result: CodeRepositoryResult = await this.getCodeRepositoryResult(); + return result.servicesRepository; + } + + return this.codeRepositoryResult.servicesRepository; + } } diff --git a/Copilot/Utils/Init.ts b/Copilot/Utils/Init.ts index 8ed05a9182..56680b0b6b 100644 --- a/Copilot/Utils/Init.ts +++ b/Copilot/Utils/Init.ts @@ -1,45 +1,45 @@ -import { GetGitHubToken, GetRepositorySecretKey } from '../Config'; -import CodeRepositoryUtil, { CodeRepositoryResult } from './CodeRepository'; -import CodeRepositoryType from 'Common/Types/CodeRepository/CodeRepositoryType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ServiceRepository from 'Model/Models/ServiceRepository'; +import { GetGitHubToken, GetRepositorySecretKey } from "../Config"; +import CodeRepositoryUtil, { CodeRepositoryResult } from "./CodeRepository"; +import CodeRepositoryType from "Common/Types/CodeRepository/CodeRepositoryType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ServiceRepository from "Model/Models/ServiceRepository"; export default class InitUtil { - public static async init(): Promise<CodeRepositoryResult> { - if (!GetRepositorySecretKey()) { - throw new BadDataException('Repository Secret Key is required'); - } - - const codeRepositoryResult: CodeRepositoryResult = - await CodeRepositoryUtil.getCodeRepositoryResult(); - - // Check if the repository type is GitHub and the GitHub token is provided - - if (codeRepositoryResult.servicesRepository.length === 0) { - throw new BadDataException( - 'No services found in the repository. Please add services to the repository in OneUptime Dashboard.' - ); - } - - if ( - codeRepositoryResult.codeRepository.repositoryHostedAt === - CodeRepositoryType.GitHub && - !GetGitHubToken() - ) { - throw new BadDataException( - 'GitHub token is required for this repository. Please provide the GitHub token in the environment variables.' - ); - } - - const servicesToImrpoveCode: Array<ServiceRepository> = - await CodeRepositoryUtil.getServicesToImproveCode({ - codeRepository: codeRepositoryResult.codeRepository, - services: codeRepositoryResult.servicesRepository, - }); - - return { - codeRepository: codeRepositoryResult.codeRepository, - servicesRepository: servicesToImrpoveCode, - }; + public static async init(): Promise<CodeRepositoryResult> { + if (!GetRepositorySecretKey()) { + throw new BadDataException("Repository Secret Key is required"); } + + const codeRepositoryResult: CodeRepositoryResult = + await CodeRepositoryUtil.getCodeRepositoryResult(); + + // Check if the repository type is GitHub and the GitHub token is provided + + if (codeRepositoryResult.servicesRepository.length === 0) { + throw new BadDataException( + "No services found in the repository. Please add services to the repository in OneUptime Dashboard.", + ); + } + + if ( + codeRepositoryResult.codeRepository.repositoryHostedAt === + CodeRepositoryType.GitHub && + !GetGitHubToken() + ) { + throw new BadDataException( + "GitHub token is required for this repository. Please provide the GitHub token in the environment variables.", + ); + } + + const servicesToImrpoveCode: Array<ServiceRepository> = + await CodeRepositoryUtil.getServicesToImproveCode({ + codeRepository: codeRepositoryResult.codeRepository, + services: codeRepositoryResult.servicesRepository, + }); + + return { + codeRepository: codeRepositoryResult.codeRepository, + servicesRepository: servicesToImrpoveCode, + }; + } } diff --git a/Copilot/Utils/ServiceFileTypes.ts b/Copilot/Utils/ServiceFileTypes.ts index 244c127416..74f6e067d2 100644 --- a/Copilot/Utils/ServiceFileTypes.ts +++ b/Copilot/Utils/ServiceFileTypes.ts @@ -1,165 +1,160 @@ -import ServiceLanguage from 'Common/Types/ServiceCatalog/ServiceLanguage'; +import ServiceLanguage from "Common/Types/ServiceCatalog/ServiceLanguage"; export default class ServiceFileTypesUtil { - private static getCommonDirectoriesToIgnore(): string[] { - return [ - 'node_modules', - '.git', - 'build', - 'dist', - 'coverage', - 'logs', - 'tmp', - 'temp', - 'temporal', - 'tempfiles', - 'tempfiles', + private static getCommonDirectoriesToIgnore(): string[] { + return [ + "node_modules", + ".git", + "build", + "dist", + "coverage", + "logs", + "tmp", + "temp", + "temporal", + "tempfiles", + "tempfiles", + ]; + } + + private static getCommonFilesToIgnore(): string[] { + return [".DS_Store", "Thumbs.db", ".gitignore", ".gitattributes"]; + } + + public static getCommonFilesToIgnoreByServiceLanguage( + serviceLanguage: ServiceLanguage, + ): string[] { + let filesToIgnore: string[] = []; + + switch (serviceLanguage) { + case ServiceLanguage.NodeJS: + filesToIgnore = ["package-lock.json"]; + break; + case ServiceLanguage.Python: + filesToIgnore = ["__pycache__"]; + break; + case ServiceLanguage.Ruby: + filesToIgnore = ["Gemfile.lock"]; + break; + case ServiceLanguage.Go: + filesToIgnore = ["go.sum", "go.mod"]; + break; + case ServiceLanguage.Java: + filesToIgnore = ["pom.xml"]; + break; + case ServiceLanguage.PHP: + filesToIgnore = ["composer.lock"]; + break; + case ServiceLanguage.CSharp: + filesToIgnore = ["packages", "bin", "obj"]; + break; + case ServiceLanguage.CPlusPlus: + filesToIgnore = ["build", "CMakeFiles", "CMakeCache.txt", "Makefile"]; + break; + case ServiceLanguage.Rust: + filesToIgnore = ["Cargo.lock"]; + break; + case ServiceLanguage.Swift: + filesToIgnore = ["Podfile.lock"]; + break; + case ServiceLanguage.Kotlin: + filesToIgnore = [ + "gradle", + "build", + "gradlew", + "gradlew.bat", + "gradle.properties", ]; + break; + case ServiceLanguage.TypeScript: + filesToIgnore = ["node_modules", "package-lock.json"]; + break; + case ServiceLanguage.JavaScript: + filesToIgnore = ["node_modules", "package-lock.json"]; + break; + case ServiceLanguage.Shell: + filesToIgnore = []; + break; + case ServiceLanguage.React: + filesToIgnore = ["node_modules", "package-lock.json"]; + break; + case ServiceLanguage.Other: + filesToIgnore = []; + break; + default: + filesToIgnore = []; } - private static getCommonFilesToIgnore(): string[] { - return ['.DS_Store', 'Thumbs.db', '.gitignore', '.gitattributes']; + return filesToIgnore + .concat(this.getCommonFilesToIgnore()) + .concat(this.getCommonDirectoriesToIgnore()); + } + + private static getCommonFilesExtentions(): string[] { + // return markdown, dockerfile, etc. + return [".md", "dockerfile", ".yml", ".yaml", ".sh", ".gitignore"]; + } + + public static getFileExtentionsByServiceLanguage( + serviceLanguage: ServiceLanguage, + ): string[] { + let fileExtentions: Array<string> = []; + + switch (serviceLanguage) { + case ServiceLanguage.NodeJS: + fileExtentions = [".js", ".ts", ".json", ".mjs"]; + break; + case ServiceLanguage.Python: + fileExtentions = [".py"]; + break; + case ServiceLanguage.Ruby: + fileExtentions = [".rb"]; + break; + case ServiceLanguage.Go: + fileExtentions = [".go"]; + break; + case ServiceLanguage.Java: + fileExtentions = [".java"]; + break; + case ServiceLanguage.PHP: + fileExtentions = [".php"]; + break; + case ServiceLanguage.CSharp: + fileExtentions = [".cs"]; + break; + case ServiceLanguage.CPlusPlus: + fileExtentions = [".cpp", ".c"]; + break; + case ServiceLanguage.Rust: + fileExtentions = [".rs"]; + break; + case ServiceLanguage.Swift: + fileExtentions = [".swift"]; + break; + case ServiceLanguage.Kotlin: + fileExtentions = [".kt", ".kts"]; + break; + case ServiceLanguage.TypeScript: + fileExtentions = [".ts", ".tsx"]; + break; + case ServiceLanguage.JavaScript: + fileExtentions = [".js", ".jsx"]; + break; + case ServiceLanguage.Shell: + fileExtentions = [".sh"]; + break; + case ServiceLanguage.React: + fileExtentions = [".js", ".ts", ".jsx", ".tsx"]; + break; + case ServiceLanguage.Other: + fileExtentions = []; + break; + default: + fileExtentions = []; } - public static getCommonFilesToIgnoreByServiceLanguage( - serviceLanguage: ServiceLanguage - ): string[] { - let filesToIgnore: string[] = []; + // add common files extentions - switch (serviceLanguage) { - case ServiceLanguage.NodeJS: - filesToIgnore = ['package-lock.json']; - break; - case ServiceLanguage.Python: - filesToIgnore = ['__pycache__']; - break; - case ServiceLanguage.Ruby: - filesToIgnore = ['Gemfile.lock']; - break; - case ServiceLanguage.Go: - filesToIgnore = ['go.sum', 'go.mod']; - break; - case ServiceLanguage.Java: - filesToIgnore = ['pom.xml']; - break; - case ServiceLanguage.PHP: - filesToIgnore = ['composer.lock']; - break; - case ServiceLanguage.CSharp: - filesToIgnore = ['packages', 'bin', 'obj']; - break; - case ServiceLanguage.CPlusPlus: - filesToIgnore = [ - 'build', - 'CMakeFiles', - 'CMakeCache.txt', - 'Makefile', - ]; - break; - case ServiceLanguage.Rust: - filesToIgnore = ['Cargo.lock']; - break; - case ServiceLanguage.Swift: - filesToIgnore = ['Podfile.lock']; - break; - case ServiceLanguage.Kotlin: - filesToIgnore = [ - 'gradle', - 'build', - 'gradlew', - 'gradlew.bat', - 'gradle.properties', - ]; - break; - case ServiceLanguage.TypeScript: - filesToIgnore = ['node_modules', 'package-lock.json']; - break; - case ServiceLanguage.JavaScript: - filesToIgnore = ['node_modules', 'package-lock.json']; - break; - case ServiceLanguage.Shell: - filesToIgnore = []; - break; - case ServiceLanguage.React: - filesToIgnore = ['node_modules', 'package-lock.json']; - break; - case ServiceLanguage.Other: - filesToIgnore = []; - break; - default: - filesToIgnore = []; - } - - return filesToIgnore - .concat(this.getCommonFilesToIgnore()) - .concat(this.getCommonDirectoriesToIgnore()); - } - - private static getCommonFilesExtentions(): string[] { - // return markdown, dockerfile, etc. - return ['.md', 'dockerfile', '.yml', '.yaml', '.sh', '.gitignore']; - } - - public static getFileExtentionsByServiceLanguage( - serviceLanguage: ServiceLanguage - ): string[] { - let fileExtentions: Array<string> = []; - - switch (serviceLanguage) { - case ServiceLanguage.NodeJS: - fileExtentions = ['.js', '.ts', '.json', '.mjs']; - break; - case ServiceLanguage.Python: - fileExtentions = ['.py']; - break; - case ServiceLanguage.Ruby: - fileExtentions = ['.rb']; - break; - case ServiceLanguage.Go: - fileExtentions = ['.go']; - break; - case ServiceLanguage.Java: - fileExtentions = ['.java']; - break; - case ServiceLanguage.PHP: - fileExtentions = ['.php']; - break; - case ServiceLanguage.CSharp: - fileExtentions = ['.cs']; - break; - case ServiceLanguage.CPlusPlus: - fileExtentions = ['.cpp', '.c']; - break; - case ServiceLanguage.Rust: - fileExtentions = ['.rs']; - break; - case ServiceLanguage.Swift: - fileExtentions = ['.swift']; - break; - case ServiceLanguage.Kotlin: - fileExtentions = ['.kt', '.kts']; - break; - case ServiceLanguage.TypeScript: - fileExtentions = ['.ts', '.tsx']; - break; - case ServiceLanguage.JavaScript: - fileExtentions = ['.js', '.jsx']; - break; - case ServiceLanguage.Shell: - fileExtentions = ['.sh']; - break; - case ServiceLanguage.React: - fileExtentions = ['.js', '.ts', '.jsx', '.tsx']; - break; - case ServiceLanguage.Other: - fileExtentions = []; - break; - default: - fileExtentions = []; - } - - // add common files extentions - - return fileExtentions.concat(this.getCommonFilesExtentions()); - } + return fileExtentions.concat(this.getCommonFilesExtentions()); + } } diff --git a/Copilot/Utils/ServiceRepository.ts b/Copilot/Utils/ServiceRepository.ts index b8a97a4e52..05c9835222 100644 --- a/Copilot/Utils/ServiceRepository.ts +++ b/Copilot/Utils/ServiceRepository.ts @@ -1,37 +1,37 @@ -import { GetLocalRepositoryPath } from '../Config'; -import ServiceFileTypesUtil from './ServiceFileTypes'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import CodeRepositoryCommonServerUtil from 'CommonServer/Utils/CodeRepository/CodeRepository'; -import CodeRepositoryFile from 'CommonServer/Utils/CodeRepository/CodeRepositoryFile'; -import ServiceRepository from 'Model/Models/ServiceRepository'; +import { GetLocalRepositoryPath } from "../Config"; +import ServiceFileTypesUtil from "./ServiceFileTypes"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import CodeRepositoryCommonServerUtil from "CommonServer/Utils/CodeRepository/CodeRepository"; +import CodeRepositoryFile from "CommonServer/Utils/CodeRepository/CodeRepositoryFile"; +import ServiceRepository from "Model/Models/ServiceRepository"; export default class ServiceRepositoryUtil { - public static async getFilesInServiceDirectory(data: { - serviceRepository: ServiceRepository; - }): Promise<Dictionary<CodeRepositoryFile>> { - const { serviceRepository } = data; + public static async getFilesInServiceDirectory(data: { + serviceRepository: ServiceRepository; + }): Promise<Dictionary<CodeRepositoryFile>> { + const { serviceRepository } = data; - if (!serviceRepository.serviceCatalog?.serviceLanguage) { - throw new BadDataException( - 'Service language is not defined in the service catalog' - ); - } - - const allFiles: Dictionary<CodeRepositoryFile> = - await CodeRepositoryCommonServerUtil.getFilesInDirectoryRecursive({ - repoPath: GetLocalRepositoryPath(), - directoryPath: serviceRepository.servicePathInRepository || '.', - acceptedFileExtensions: - ServiceFileTypesUtil.getFileExtentionsByServiceLanguage( - serviceRepository.serviceCatalog!.serviceLanguage! - ), - ignoreFilesOrDirectories: - ServiceFileTypesUtil.getCommonFilesToIgnoreByServiceLanguage( - serviceRepository.serviceCatalog!.serviceLanguage! - ), - }); - - return allFiles; + if (!serviceRepository.serviceCatalog?.serviceLanguage) { + throw new BadDataException( + "Service language is not defined in the service catalog", + ); } + + const allFiles: Dictionary<CodeRepositoryFile> = + await CodeRepositoryCommonServerUtil.getFilesInDirectoryRecursive({ + repoPath: GetLocalRepositoryPath(), + directoryPath: serviceRepository.servicePathInRepository || ".", + acceptedFileExtensions: + ServiceFileTypesUtil.getFileExtentionsByServiceLanguage( + serviceRepository.serviceCatalog!.serviceLanguage!, + ), + ignoreFilesOrDirectories: + ServiceFileTypesUtil.getCommonFilesToIgnoreByServiceLanguage( + serviceRepository.serviceCatalog!.serviceLanguage!, + ), + }); + + return allFiles; + } } diff --git a/Dashboard/Serve.ts b/Dashboard/Serve.ts index 7893b70d4a..8c6ca2344d 100755 --- a/Dashboard/Serve.ts +++ b/Dashboard/Serve.ts @@ -1,38 +1,38 @@ -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import App from 'CommonServer/Utils/StartServer'; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import App from "CommonServer/Utils/StartServer"; -export const APP_NAME: string = 'dashboard'; +export const APP_NAME: string = "dashboard"; const app: ExpressApplication = Express.getExpressApp(); const init: PromiseVoidFunction = async (): Promise<void> => { - try { - // init the app - await App.init({ - appName: APP_NAME, - port: undefined, - isFrontendApp: true, - statusOptions: { - liveCheck: async () => {}, - readyCheck: async () => {}, - }, - }); + try { + // init the app + await App.init({ + appName: APP_NAME, + port: undefined, + isFrontendApp: true, + statusOptions: { + liveCheck: async () => {}, + readyCheck: async () => {}, + }, + }); - // add default routes - await App.addDefaultRoutes(); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); export default app; diff --git a/Dashboard/index.d.ts b/Dashboard/index.d.ts index cf14fde98c..a0ad0eb5c4 100644 --- a/Dashboard/index.d.ts +++ b/Dashboard/index.d.ts @@ -1,4 +1,4 @@ -declare module '*.png'; -declare module '*.svg'; -declare module '*.jpg'; -declare module '*.gif'; +declare module "*.png"; +declare module "*.svg"; +declare module "*.jpg"; +declare module "*.gif"; diff --git a/Dashboard/src/App.tsx b/Dashboard/src/App.tsx index 58dab8f7ab..5d001ef04d 100644 --- a/Dashboard/src/App.tsx +++ b/Dashboard/src/App.tsx @@ -1,496 +1,454 @@ -import MasterPage from './Components/MasterPage/MasterPage'; -import ActiveIncidents from './Pages/Global/NewIncidents'; -import ProjectInvitations from './Pages/Global/ProjectInvitations'; +import MasterPage from "./Components/MasterPage/MasterPage"; +import ActiveIncidents from "./Pages/Global/NewIncidents"; +import ProjectInvitations from "./Pages/Global/ProjectInvitations"; // User Profile -import UserProfileOverview from './Pages/Global/UserProfile/Index'; -import UserProfilePassword from './Pages/Global/UserProfile/Password'; -import UserProfilePicture from './Pages/Global/UserProfile/Picture'; +import UserProfileOverview from "./Pages/Global/UserProfile/Index"; +import UserProfilePassword from "./Pages/Global/UserProfile/Password"; +import UserProfilePicture from "./Pages/Global/UserProfile/Picture"; // Pages -import Home from './Pages/Home/Home'; -import NotOperationalMonitors from './Pages/Home/NotOperationalMonitors'; -import OngoingScheduledEvents from './Pages/Home/OngoingScheduledMaintenance'; -import Logout from './Pages/Logout/Logout'; -import Sso from './Pages/Onboarding/SSO'; -import Welcome from './Pages/Onboarding/Welcome'; -import PageComponentProps from './Pages/PageComponentProps'; -import PageNotFound from './Pages/PageNotFound/PageNotFound'; -import SettingsDangerZone from './Pages/Settings/DangerZone'; -import AICopilotRoutes from './Routes/AICopilotRoutes'; -import IncidentsRoutes from './Routes/IncidentsRoutes'; +import Home from "./Pages/Home/Home"; +import NotOperationalMonitors from "./Pages/Home/NotOperationalMonitors"; +import OngoingScheduledEvents from "./Pages/Home/OngoingScheduledMaintenance"; +import Logout from "./Pages/Logout/Logout"; +import Sso from "./Pages/Onboarding/SSO"; +import Welcome from "./Pages/Onboarding/Welcome"; +import PageComponentProps from "./Pages/PageComponentProps"; +import PageNotFound from "./Pages/PageNotFound/PageNotFound"; +import SettingsDangerZone from "./Pages/Settings/DangerZone"; +import AICopilotRoutes from "./Routes/AICopilotRoutes"; +import IncidentsRoutes from "./Routes/IncidentsRoutes"; //Routes -import InitRoutes from './Routes/InitRoutes'; -import MonitorGroupRoutes from './Routes/MonitorGroupRoutes'; -import MonitorsRoutes from './Routes/MonitorsRoutes'; -import OnCallDutyRoutes from './Routes/OnCallDutyRoutes'; -import ScheduledMaintenanceEventsRoutes from './Routes/ScheduleMaintenaceEventsRoutes'; -import ServiceCatalogRoutes from './Routes/ServiceCatalogRoutes'; -import SettingsRoutes from './Routes/SettingsRoutes'; -import StatusPagesRoutes from './Routes/StatusPagesRoutes'; -import TelemetryRoutes from './Routes/TelemetryRoutes'; -import UserSettingsRoutes from './Routes/UserSettingsRoutes'; -import WorkflowRoutes from './Routes/WorkflowRoutes'; -import EventName from './Utils/EventName'; -import PageMap from './Utils/PageMap'; -import RouteMap from './Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { APP_API_URL, BILLING_ENABLED } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import GlobalEvents from 'CommonUI/src/Utils/GlobalEvents'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import BillingPaymentMethod from 'Model/Models/BillingPaymentMethod'; -import Project from 'Model/Models/Project'; -import React, { useEffect, useState } from 'react'; +import InitRoutes from "./Routes/InitRoutes"; +import MonitorGroupRoutes from "./Routes/MonitorGroupRoutes"; +import MonitorsRoutes from "./Routes/MonitorsRoutes"; +import OnCallDutyRoutes from "./Routes/OnCallDutyRoutes"; +import ScheduledMaintenanceEventsRoutes from "./Routes/ScheduleMaintenaceEventsRoutes"; +import ServiceCatalogRoutes from "./Routes/ServiceCatalogRoutes"; +import SettingsRoutes from "./Routes/SettingsRoutes"; +import StatusPagesRoutes from "./Routes/StatusPagesRoutes"; +import TelemetryRoutes from "./Routes/TelemetryRoutes"; +import UserSettingsRoutes from "./Routes/UserSettingsRoutes"; +import WorkflowRoutes from "./Routes/WorkflowRoutes"; +import EventName from "./Utils/EventName"; +import PageMap from "./Utils/PageMap"; +import RouteMap from "./Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { APP_API_URL, BILLING_ENABLED } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import GlobalEvents from "CommonUI/src/Utils/GlobalEvents"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import BillingPaymentMethod from "Model/Models/BillingPaymentMethod"; +import Project from "Model/Models/Project"; +import React, { useEffect, useState } from "react"; import { - Route as PageRoute, - Routes, - useLocation, - useNavigate, - useParams, -} from 'react-router-dom'; -import useAsyncEffect from 'use-async-effect'; + Route as PageRoute, + Routes, + useLocation, + useNavigate, + useParams, +} from "react-router-dom"; +import useAsyncEffect from "use-async-effect"; const App: () => JSX.Element = () => { - Navigation.setNavigateHook(useNavigate()); - Navigation.setLocation(useLocation()); - Navigation.setParams(useParams()); + Navigation.setNavigateHook(useNavigate()); + Navigation.setLocation(useLocation()); + Navigation.setParams(useParams()); - const [isLoading, setLoading] = useState<boolean>(true); - const [showProjectModal, setShowProjectModal] = useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [projects, setProjects] = useState<Array<Project>>([]); + const [isLoading, setLoading] = useState<boolean>(true); + const [showProjectModal, setShowProjectModal] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [projects, setProjects] = useState<Array<Project>>([]); - const [selectedProject, setSelectedProject] = useState<Project | null>( - null + const [selectedProject, setSelectedProject] = useState<Project | null>(null); + + const [ispaymentMethodsCountLoading, setPaymentMethodsCountLoading] = + useState<boolean>(false); + const [paymentMethodsCount, setPaymentMethodsCount] = useState< + number | undefined + >(undefined); + + const [hasPaymentMethod, setHasPaymentMethod] = useState<boolean>(false); + + useAsyncEffect(async () => { + try { + if (selectedProject && selectedProject._id) { + ProjectUtil.setCurrentProject(selectedProject); + } + + if (selectedProject && selectedProject._id && BILLING_ENABLED) { + setPaymentMethodsCountLoading(true); + + const paymentMethodsCount: number = await ModelAPI.count({ + modelType: BillingPaymentMethod, + query: { projectId: selectedProject._id }, + }); + + setPaymentMethodsCount(paymentMethodsCount); + + if (paymentMethodsCount && paymentMethodsCount > 0) { + setHasPaymentMethod(true); + } else { + setHasPaymentMethod(false); + } + } + + if (!BILLING_ENABLED) { + setHasPaymentMethod(true); + } + + setPaymentMethodsCountLoading(false); + } catch (e) { + setError(API.getFriendlyMessage(e)); + setPaymentMethodsCountLoading(false); + } + }, [selectedProject?._id]); + + const onProjectSelected: (project: Project) => void = ( + project: Project, + ): void => { + setSelectedProject(project); + + if ( + projects.filter((i: Project) => { + return i && i._id === project._id; + }).length === 0 + ) { + setProjects([...projects, project]); + } + + const currentRoute: Route = Navigation.getCurrentRoute(); + + if (!currentRoute.toString().includes(project._id!)) { + Navigation.navigate(new Route("/dashboard/" + project._id), { + forceNavigate: true, + }); + } + }; + + useEffect(() => { + GlobalEvents.addEventListener( + EventName.PROJECT_INVITATIONS_REFRESH, + fetchProjects, ); - const [ispaymentMethodsCountLoading, setPaymentMethodsCountLoading] = - useState<boolean>(false); - const [paymentMethodsCount, setPaymentMethodsCount] = useState< - number | undefined - >(undefined); - - const [hasPaymentMethod, setHasPaymentMethod] = useState<boolean>(false); - - useAsyncEffect(async () => { - try { - if (selectedProject && selectedProject._id) { - ProjectUtil.setCurrentProject(selectedProject); - } - - if (selectedProject && selectedProject._id && BILLING_ENABLED) { - setPaymentMethodsCountLoading(true); - - const paymentMethodsCount: number = await ModelAPI.count({ - modelType: BillingPaymentMethod, - query: { projectId: selectedProject._id }, - }); - - setPaymentMethodsCount(paymentMethodsCount); - - if (paymentMethodsCount && paymentMethodsCount > 0) { - setHasPaymentMethod(true); - } else { - setHasPaymentMethod(false); - } - } - - if (!BILLING_ENABLED) { - setHasPaymentMethod(true); - } - - setPaymentMethodsCountLoading(false); - } catch (e) { - setError(API.getFriendlyMessage(e)); - setPaymentMethodsCountLoading(false); - } - }, [selectedProject?._id]); - - const onProjectSelected: (project: Project) => void = ( - project: Project - ): void => { - setSelectedProject(project); - - if ( - projects.filter((i: Project) => { - return i && i._id === project._id; - }).length === 0 - ) { - setProjects([...projects, project]); - } - - const currentRoute: Route = Navigation.getCurrentRoute(); - - if (!currentRoute.toString().includes(project._id!)) { - Navigation.navigate(new Route('/dashboard/' + project._id), { - forceNavigate: true, - }); - } + return () => { + // on unmount. + GlobalEvents.removeEventListener( + EventName.PROJECT_INVITATIONS_REFRESH, + fetchProjects, + ); }; + }, []); - useEffect(() => { - GlobalEvents.addEventListener( - EventName.PROJECT_INVITATIONS_REFRESH, - fetchProjects - ); + const fetchProjects: PromiseVoidFunction = async (): Promise<void> => { + setLoading(true); - return () => { - // on unmount. - GlobalEvents.removeEventListener( - EventName.PROJECT_INVITATIONS_REFRESH, - fetchProjects - ); - }; - }, []); + // get list of projects. + try { + const result: ListResult<Project> = await ModelAPI.getList<Project>({ + modelType: Project, + query: {}, + limit: 50, + skip: 0, + select: {}, + sort: {}, - const fetchProjects: PromiseVoidFunction = async (): Promise<void> => { - setLoading(true); + requestOptions: { + isMultiTenantRequest: true, + overrideRequestUrl: URL.fromString(APP_API_URL.toString()).addRoute( + "/project/list-user-projects", + ), + }, + }); + setProjects(result.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - // get list of projects. - try { - const result: ListResult<Project> = await ModelAPI.getList<Project>( - { - modelType: Project, - query: {}, - limit: 50, - skip: 0, - select: {}, - sort: {}, + setLoading(false); + }; - requestOptions: { - isMultiTenantRequest: true, - overrideRequestUrl: URL.fromString( - APP_API_URL.toString() - ).addRoute('/project/list-user-projects'), - }, - } - ); - setProjects(result.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + useAsyncEffect(async () => { + await fetchProjects(); + }, []); - setLoading(false); - }; + const commonPageProps: PageComponentProps = { + currentProject: selectedProject, + hasPaymentMethod: hasPaymentMethod, + pageRoute: Navigation.getCurrentRoute(), // this will be overwritten by different pages + }; - useAsyncEffect(async () => { - await fetchProjects(); - }, []); + return ( + <MasterPage + isLoading={isLoading || ispaymentMethodsCountLoading} + projects={projects} + error={error} + paymentMethodsCount={paymentMethodsCount} + onProjectSelected={onProjectSelected} + showProjectModal={showProjectModal} + onProjectModalClose={() => { + setShowProjectModal(false); + }} + selectedProject={selectedProject} + hideNavBarOn={[RouteMap[PageMap.PROJECT_SSO]!]} + > + <Routes> + <PageRoute + path="/*" + element={ + <InitRoutes + {...commonPageProps} + projects={projects} + isLoading={isLoading} + /> + } + /> - const commonPageProps: PageComponentProps = { - currentProject: selectedProject, - hasPaymentMethod: hasPaymentMethod, - pageRoute: Navigation.getCurrentRoute(), // this will be overwritten by different pages - }; + <PageRoute + path={RouteMap[PageMap.WELCOME]?.toString() || ""} + element={ + <Welcome + {...commonPageProps} + pageRoute={RouteMap[PageMap.WELCOME] as Route} + onClickShowProjectModal={() => { + setShowProjectModal(true); + }} + /> + } + /> - return ( - <MasterPage - isLoading={isLoading || ispaymentMethodsCountLoading} - projects={projects} - error={error} - paymentMethodsCount={paymentMethodsCount} - onProjectSelected={onProjectSelected} - showProjectModal={showProjectModal} - onProjectModalClose={() => { - setShowProjectModal(false); - }} - selectedProject={selectedProject} - hideNavBarOn={[RouteMap[PageMap.PROJECT_SSO]!]} - > - <Routes> - <PageRoute - path="/*" - element={ - <InitRoutes - {...commonPageProps} - projects={projects} - isLoading={isLoading} - /> - } - /> + {/* Home */} - <PageRoute - path={RouteMap[PageMap.WELCOME]?.toString() || ''} - element={ - <Welcome - {...commonPageProps} - pageRoute={RouteMap[PageMap.WELCOME] as Route} - onClickShowProjectModal={() => { - setShowProjectModal(true); - }} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.HOME]?.toString() || ""} + element={ + <Home + {...commonPageProps} + pageRoute={RouteMap[PageMap.HOME] as Route} + projects={projects} + isLoadingProjects={isLoading} + /> + } + /> + <PageRoute + path={RouteMap[PageMap.PROJECT_SSO]?.toString() || ""} + element={ + <Sso + {...commonPageProps} + pageRoute={RouteMap[PageMap.PROJECT_SSO] as Route} + /> + } + /> - {/* Home */} + <PageRoute + path={ + RouteMap[PageMap.HOME_NOT_OPERATIONAL_MONITORS]?.toString() || "" + } + element={ + <NotOperationalMonitors + {...commonPageProps} + pageRoute={ + RouteMap[PageMap.HOME_NOT_OPERATIONAL_MONITORS] as Route + } + /> + } + /> - <PageRoute - path={RouteMap[PageMap.HOME]?.toString() || ''} - element={ - <Home - {...commonPageProps} - pageRoute={RouteMap[PageMap.HOME] as Route} - projects={projects} - isLoadingProjects={isLoading} - /> - } - /> - <PageRoute - path={RouteMap[PageMap.PROJECT_SSO]?.toString() || ''} - element={ - <Sso - {...commonPageProps} - pageRoute={RouteMap[PageMap.PROJECT_SSO] as Route} - /> - } - /> + <PageRoute + path={ + RouteMap[ + PageMap.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS + ]?.toString() || + "" || + "" + } + element={ + <OngoingScheduledEvents + {...commonPageProps} + pageRoute={ + RouteMap[ + PageMap.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS + ] as Route + } + /> + } + /> + {/* Telemetry */} + <PageRoute + path={RouteMap[PageMap.TELEMETRY_ROOT]?.toString() || ""} + element={<TelemetryRoutes {...commonPageProps} />} + /> - <PageRoute - path={ - RouteMap[ - PageMap.HOME_NOT_OPERATIONAL_MONITORS - ]?.toString() || '' - } - element={ - <NotOperationalMonitors - {...commonPageProps} - pageRoute={ - RouteMap[ - PageMap.HOME_NOT_OPERATIONAL_MONITORS - ] as Route - } - /> - } - /> + {/* Monitors */} + <PageRoute + path={RouteMap[PageMap.MONITORS_ROOT]?.toString() || ""} + element={<MonitorsRoutes {...commonPageProps} />} + /> - <PageRoute - path={ - RouteMap[ - PageMap.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS - ]?.toString() || - '' || - '' - } - element={ - <OngoingScheduledEvents - {...commonPageProps} - pageRoute={ - RouteMap[ - PageMap - .HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS - ] as Route - } - /> - } - /> - {/* Telemetry */} - <PageRoute - path={RouteMap[PageMap.TELEMETRY_ROOT]?.toString() || ''} - element={<TelemetryRoutes {...commonPageProps} />} - /> + {/* Workflows */} + <PageRoute + path={RouteMap[PageMap.WORKFLOWS_ROOT]?.toString() || ""} + element={<WorkflowRoutes {...commonPageProps} />} + /> - {/* Monitors */} - <PageRoute - path={RouteMap[PageMap.MONITORS_ROOT]?.toString() || ''} - element={<MonitorsRoutes {...commonPageProps} />} - /> + {/* Status Pages */} + <PageRoute + path={RouteMap[PageMap.STATUS_PAGES_ROOT]?.toString() || ""} + element={<StatusPagesRoutes {...commonPageProps} />} + /> - {/* Workflows */} - <PageRoute - path={RouteMap[PageMap.WORKFLOWS_ROOT]?.toString() || ''} - element={<WorkflowRoutes {...commonPageProps} />} - /> + {/* Service Catalog */} + <PageRoute + path={RouteMap[PageMap.SERVICE_CATALOG_ROOT]?.toString() || ""} + element={<ServiceCatalogRoutes {...commonPageProps} />} + /> - {/* Status Pages */} - <PageRoute - path={RouteMap[PageMap.STATUS_PAGES_ROOT]?.toString() || ''} - element={<StatusPagesRoutes {...commonPageProps} />} - /> + {/** AI Copilot */} + <PageRoute + path={RouteMap[PageMap.AI_COPILOT_ROOT]?.toString() || ""} + element={<AICopilotRoutes {...commonPageProps} />} + /> - {/* Service Catalog */} - <PageRoute - path={ - RouteMap[PageMap.SERVICE_CATALOG_ROOT]?.toString() || '' - } - element={<ServiceCatalogRoutes {...commonPageProps} />} - /> + {/* Incidents */} + <PageRoute + path={RouteMap[PageMap.INCIDENTS_ROOT]?.toString() || ""} + element={<IncidentsRoutes {...commonPageProps} />} + /> - {/** AI Copilot */} - <PageRoute - path={RouteMap[PageMap.AI_COPILOT_ROOT]?.toString() || ''} - element={<AICopilotRoutes {...commonPageProps} />} - /> + {/* Scheduled Events */} - {/* Incidents */} - <PageRoute - path={RouteMap[PageMap.INCIDENTS_ROOT]?.toString() || ''} - element={<IncidentsRoutes {...commonPageProps} />} - /> + <PageRoute + path={ + RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS_ROOT]?.toString() || + "" + } + element={<ScheduledMaintenanceEventsRoutes {...commonPageProps} />} + /> - {/* Scheduled Events */} + {/* Settings Routes */} - <PageRoute - path={ - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_EVENTS_ROOT - ]?.toString() || '' - } - element={ - <ScheduledMaintenanceEventsRoutes - {...commonPageProps} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.SETTINGS_ROOT]?.toString() || ""} + element={<SettingsRoutes {...commonPageProps} />} + /> - {/* Settings Routes */} + {/* As this one has dependencies with the selected project and etc, we need to put it here for now. */} + <PageRoute + path={RouteMap[PageMap.SETTINGS_DANGERZONE]?.toString() || ""} + element={ + <SettingsDangerZone + onProjectDeleted={async () => { + setSelectedProject(null); + setProjects([]); + await fetchProjects(); + Navigation.navigate(RouteMap[PageMap.INIT]!); + }} + {...commonPageProps} + pageRoute={RouteMap[PageMap.SETTINGS_DANGERZONE] as Route} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.SETTINGS_ROOT]?.toString() || ''} - element={<SettingsRoutes {...commonPageProps} />} - /> + {/* On-Call Duty */} - {/* As this one has dependencies with the selected project and etc, we need to put it here for now. */} - <PageRoute - path={ - RouteMap[PageMap.SETTINGS_DANGERZONE]?.toString() || '' - } - element={ - <SettingsDangerZone - onProjectDeleted={async () => { - setSelectedProject(null); - setProjects([]); - await fetchProjects(); - Navigation.navigate(RouteMap[PageMap.INIT]!); - }} - {...commonPageProps} - pageRoute={ - RouteMap[PageMap.SETTINGS_DANGERZONE] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.ON_CALL_DUTY_ROOT]?.toString() || ""} + element={<OnCallDutyRoutes {...commonPageProps} />} + /> - {/* On-Call Duty */} + {/* Misc Routes */} + <PageRoute + path={RouteMap[PageMap.LOGOUT]?.toString() || ""} + element={ + <Logout + {...commonPageProps} + pageRoute={RouteMap[PageMap.LOGOUT] as Route} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.ON_CALL_DUTY_ROOT]?.toString() || ''} - element={<OnCallDutyRoutes {...commonPageProps} />} - /> + {/* Global Routes */} + <PageRoute + path={RouteMap[PageMap.USER_PROFILE_PICTURE]?.toString() || ""} + element={ + <UserProfilePicture + {...commonPageProps} + pageRoute={RouteMap[PageMap.USER_PROFILE_PICTURE] as Route} + /> + } + /> - {/* Misc Routes */} - <PageRoute - path={RouteMap[PageMap.LOGOUT]?.toString() || ''} - element={ - <Logout - {...commonPageProps} - pageRoute={RouteMap[PageMap.LOGOUT] as Route} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.USER_PROFILE_OVERVIEW]?.toString() || ""} + element={ + <UserProfileOverview + {...commonPageProps} + pageRoute={RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route} + /> + } + /> - {/* Global Routes */} - <PageRoute - path={ - RouteMap[PageMap.USER_PROFILE_PICTURE]?.toString() || '' - } - element={ - <UserProfilePicture - {...commonPageProps} - pageRoute={ - RouteMap[PageMap.USER_PROFILE_PICTURE] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.USER_PROFILE_PASSWORD]?.toString() || ""} + element={ + <UserProfilePassword + {...commonPageProps} + pageRoute={RouteMap[PageMap.USER_PROFILE_PASSWORD] as Route} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.USER_PROFILE_OVERVIEW]?.toString() || - '' - } - element={ - <UserProfileOverview - {...commonPageProps} - pageRoute={ - RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PROJECT_INVITATIONS]?.toString() || ""} + element={ + <ProjectInvitations + {...commonPageProps} + pageRoute={RouteMap[PageMap.PROJECT_INVITATIONS] as Route} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.USER_PROFILE_PASSWORD]?.toString() || - '' - } - element={ - <UserProfilePassword - {...commonPageProps} - pageRoute={ - RouteMap[PageMap.USER_PROFILE_PASSWORD] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.NEW_INCIDENTS]?.toString() || ""} + element={ + <ActiveIncidents + {...commonPageProps} + pageRoute={RouteMap[PageMap.NEW_INCIDENTS] as Route} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.PROJECT_INVITATIONS]?.toString() || '' - } - element={ - <ProjectInvitations - {...commonPageProps} - pageRoute={ - RouteMap[PageMap.PROJECT_INVITATIONS] as Route - } - /> - } - /> + {/* User Settings */} - <PageRoute - path={RouteMap[PageMap.NEW_INCIDENTS]?.toString() || ''} - element={ - <ActiveIncidents - {...commonPageProps} - pageRoute={RouteMap[PageMap.NEW_INCIDENTS] as Route} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.USER_SETTINGS_ROOT]?.toString() || ""} + element={<UserSettingsRoutes {...commonPageProps} />} + /> - {/* User Settings */} + {/** Monitor Groups */} - <PageRoute - path={ - RouteMap[PageMap.USER_SETTINGS_ROOT]?.toString() || '' - } - element={<UserSettingsRoutes {...commonPageProps} />} - /> + <PageRoute + path={RouteMap[PageMap.MONITOR_GROUPS_ROOT]?.toString() || ""} + element={<MonitorGroupRoutes {...commonPageProps} />} + /> - {/** Monitor Groups */} - - <PageRoute - path={ - RouteMap[PageMap.MONITOR_GROUPS_ROOT]?.toString() || '' - } - element={<MonitorGroupRoutes {...commonPageProps} />} - /> - - {/* 👇️ only match this when no other routes match */} - <PageRoute - path="*" - element={ - <PageNotFound - {...commonPageProps} - pageRoute={RouteMap[PageMap.LOGOUT] as Route} - /> - } - /> - </Routes> - </MasterPage> - ); + {/* 👇️ only match this when no other routes match */} + <PageRoute + path="*" + element={ + <PageNotFound + {...commonPageProps} + pageRoute={RouteMap[PageMap.LOGOUT] as Route} + /> + } + /> + </Routes> + </MasterPage> + ); }; export default App; diff --git a/Dashboard/src/Components/CallSMS/CallSMSConfigTable.tsx b/Dashboard/src/Components/CallSMS/CallSMSConfigTable.tsx index 571cf6a40b..986bcc8542 100644 --- a/Dashboard/src/Components/CallSMS/CallSMSConfigTable.tsx +++ b/Dashboard/src/Components/CallSMS/CallSMSConfigTable.tsx @@ -1,440 +1,431 @@ -import EmptyResponseData from 'Common/Types/API/EmptyResponse'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { NOTIFICATION_URL } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig'; +import EmptyResponseData from "Common/Types/API/EmptyResponse"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { NOTIFICATION_URL } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const CustomCallSMSTable: FunctionComponent = (): ReactElement => { - const [showCallTestModal, setShowCallTestModal] = useState<boolean>(false); - const [showCallSuccessModal, setCallShowSuccessModal] = - useState<boolean>(false); + const [showCallTestModal, setShowCallTestModal] = useState<boolean>(false); + const [showCallSuccessModal, setCallShowSuccessModal] = + useState<boolean>(false); - const [showSMSTestModal, setShowSMSTestModal] = useState<boolean>(false); - const [showSMSSuccessModal, setSMSShowSuccessModal] = - useState<boolean>(false); + const [showSMSTestModal, setShowSMSTestModal] = useState<boolean>(false); + const [showSMSSuccessModal, setSMSShowSuccessModal] = + useState<boolean>(false); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - const [currentCallSMSTestConfig, setCurrentCallSMSTestConfig] = - useState<ProjectCallSMSConfig | null>(null); + const [currentCallSMSTestConfig, setCurrentCallSMSTestConfig] = + useState<ProjectCallSMSConfig | null>(null); - const [isCallSMSTestLoading, setIsCallSMSTestLoading] = - useState<boolean>(false); + const [isCallSMSTestLoading, setIsCallSMSTestLoading] = + useState<boolean>(false); - useEffect(() => { - setError(''); - }, [showCallTestModal, showSMSTestModal]); + useEffect(() => { + setError(""); + }, [showCallTestModal, showSMSTestModal]); - return ( - <> - <ModelTable<ProjectCallSMSConfig> - modelType={ProjectCallSMSConfig} - id="call-sms-table" - actionButtons={[ - { - title: 'Send Test SMS', - buttonStyleType: ButtonStyleType.OUTLINE, - icon: IconProp.SMS, - onClick: async ( - item: ProjectCallSMSConfig, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentCallSMSTestConfig(item); - setShowSMSTestModal(true); + return ( + <> + <ModelTable<ProjectCallSMSConfig> + modelType={ProjectCallSMSConfig} + id="call-sms-table" + actionButtons={[ + { + title: "Send Test SMS", + buttonStyleType: ButtonStyleType.OUTLINE, + icon: IconProp.SMS, + onClick: async ( + item: ProjectCallSMSConfig, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentCallSMSTestConfig(item); + setShowSMSTestModal(true); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - { - title: 'Send Test Call', - buttonStyleType: ButtonStyleType.OUTLINE, - icon: IconProp.Call, - onClick: async ( - item: ProjectCallSMSConfig, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentCallSMSTestConfig(item); - setShowCallTestModal(true); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + { + title: "Send Test Call", + buttonStyleType: ButtonStyleType.OUTLINE, + icon: IconProp.Call, + onClick: async ( + item: ProjectCallSMSConfig, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentCallSMSTestConfig(item); + setShowCallTestModal(true); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - isDeleteable={true} - createVerb="Create Twilio Config" - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Twilio Config', - description: - 'Configure your Twilio account to send SMS and make calls.', - }} - formSteps={[ - { - title: 'Basic', - id: 'basic-info', - }, - { - title: 'Twilio Config', - id: 'twilio-info', - }, - ]} - name="Settings > Custom CallSMS Config" - noItemsMessage={'No Twilio config found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - description: - 'Friendly name for this config so you remember what this is about.', - placeholder: 'Company CallSMS Server', - stepId: 'basic-info', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - stepId: 'basic-info', - description: - 'Friendly description for this config so you remember what this is about.', - placeholder: 'Company CallSMS server hosted on AWS', - }, - { - field: { - twilioAccountSID: true, - }, - title: 'Twilio Account SID', - fieldType: FormFieldSchemaType.Text, - stepId: 'twilio-info', - required: true, - description: - 'You can find this in your Twilio console.', - placeholder: '', - validation: { - minLength: 2, - }, - }, - { - field: { - twilioAuthToken: true, - }, - title: 'Twilio Auth Token', - stepId: 'twilio-info', - fieldType: FormFieldSchemaType.Text, - required: true, - description: - 'You can find this in your Twilio console.', - placeholder: '', - validation: { - minLength: 2, - }, - }, - { - field: { - twilioPhoneNumber: true, - }, - title: 'Twilio Phone Number', - stepId: 'twilio-info', - fieldType: FormFieldSchemaType.Phone, - required: true, - description: - 'You can find this in your Twilio console.', - placeholder: '', - validation: { - minLength: 2, - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - title: 'Name', - type: FieldType.Text, - field: { - name: true, - }, - }, - { - title: 'Description', - type: FieldType.Text, - field: { - description: true, - }, - }, - { - title: 'Twilio Account SID', - type: FieldType.Text, - field: { - twilioAccountSID: true, - }, - }, - { - title: 'Twilio Phone Number', - type: FieldType.Phone, - field: { - twilioPhoneNumber: true, - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - twilioAccountSID: true, - }, - title: 'Twilio Account SID', - type: FieldType.Text, - }, - { - field: { - twilioPhoneNumber: true, - }, - title: 'Twilio Phone Number', - type: FieldType.Phone, - }, - ]} - /> + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + isDeleteable={true} + createVerb="Create Twilio Config" + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Twilio Config", + description: + "Configure your Twilio account to send SMS and make calls.", + }} + formSteps={[ + { + title: "Basic", + id: "basic-info", + }, + { + title: "Twilio Config", + id: "twilio-info", + }, + ]} + name="Settings > Custom CallSMS Config" + noItemsMessage={"No Twilio config found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + description: + "Friendly name for this config so you remember what this is about.", + placeholder: "Company CallSMS Server", + stepId: "basic-info", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + stepId: "basic-info", + description: + "Friendly description for this config so you remember what this is about.", + placeholder: "Company CallSMS server hosted on AWS", + }, + { + field: { + twilioAccountSID: true, + }, + title: "Twilio Account SID", + fieldType: FormFieldSchemaType.Text, + stepId: "twilio-info", + required: true, + description: "You can find this in your Twilio console.", + placeholder: "", + validation: { + minLength: 2, + }, + }, + { + field: { + twilioAuthToken: true, + }, + title: "Twilio Auth Token", + stepId: "twilio-info", + fieldType: FormFieldSchemaType.Text, + required: true, + description: "You can find this in your Twilio console.", + placeholder: "", + validation: { + minLength: 2, + }, + }, + { + field: { + twilioPhoneNumber: true, + }, + title: "Twilio Phone Number", + stepId: "twilio-info", + fieldType: FormFieldSchemaType.Phone, + required: true, + description: "You can find this in your Twilio console.", + placeholder: "", + validation: { + minLength: 2, + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + title: "Name", + type: FieldType.Text, + field: { + name: true, + }, + }, + { + title: "Description", + type: FieldType.Text, + field: { + description: true, + }, + }, + { + title: "Twilio Account SID", + type: FieldType.Text, + field: { + twilioAccountSID: true, + }, + }, + { + title: "Twilio Phone Number", + type: FieldType.Phone, + field: { + twilioPhoneNumber: true, + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + twilioAccountSID: true, + }, + title: "Twilio Account SID", + type: FieldType.Text, + }, + { + field: { + twilioPhoneNumber: true, + }, + title: "Twilio Phone Number", + type: FieldType.Phone, + }, + ]} + /> - {/** SMS */} + {/** SMS */} - {showSMSTestModal && currentCallSMSTestConfig ? ( - <BasicFormModal - title={`Send Test SMS`} - description={`Send a test sms to verify your twilio config.`} - formProps={{ - error: error, - fields: [ - { - field: { - toPhone: true, - }, - title: 'Phone Number', - description: - 'Phone number to send test sms to.', - fieldType: FormFieldSchemaType.Phone, - required: true, - placeholder: '+1234567890', - }, - ], - }} - submitButtonText={'Send Test SMS'} - onClose={() => { - setShowSMSTestModal(false); - setError(''); - }} - isLoading={isCallSMSTestLoading} - onSubmit={async (values: JSONObject) => { - try { - setIsCallSMSTestLoading(true); - setError(''); + {showSMSTestModal && currentCallSMSTestConfig ? ( + <BasicFormModal + title={`Send Test SMS`} + description={`Send a test sms to verify your twilio config.`} + formProps={{ + error: error, + fields: [ + { + field: { + toPhone: true, + }, + title: "Phone Number", + description: "Phone number to send test sms to.", + fieldType: FormFieldSchemaType.Phone, + required: true, + placeholder: "+1234567890", + }, + ], + }} + submitButtonText={"Send Test SMS"} + onClose={() => { + setShowSMSTestModal(false); + setError(""); + }} + isLoading={isCallSMSTestLoading} + onSubmit={async (values: JSONObject) => { + try { + setIsCallSMSTestLoading(true); + setError(""); - // test CallSMS config - const response: - | HTTPResponse<EmptyResponseData> - | HTTPErrorResponse = await API.post( - URL.fromString( - NOTIFICATION_URL.toString() - ).addRoute(`/sms/test`), + // test CallSMS config + const response: + | HTTPResponse<EmptyResponseData> + | HTTPErrorResponse = await API.post( + URL.fromString(NOTIFICATION_URL.toString()).addRoute( + `/sms/test`, + ), - { - toPhone: values['toPhone'], - callSMSConfigId: new ObjectID( - currentCallSMSTestConfig['_id'] - ? currentCallSMSTestConfig[ - '_id' - ].toString() - : '' - ).toString(), - } - ); - if (response.isSuccess()) { - setIsCallSMSTestLoading(false); - setShowSMSTestModal(false); - setSMSShowSuccessModal(true); - } + { + toPhone: values["toPhone"], + callSMSConfigId: new ObjectID( + currentCallSMSTestConfig["_id"] + ? currentCallSMSTestConfig["_id"].toString() + : "", + ).toString(), + }, + ); + if (response.isSuccess()) { + setIsCallSMSTestLoading(false); + setShowSMSTestModal(false); + setSMSShowSuccessModal(true); + } - if (response instanceof HTTPErrorResponse) { - throw response; - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - setIsCallSMSTestLoading(false); - } - }} - /> - ) : ( - <></> - )} + if (response instanceof HTTPErrorResponse) { + throw response; + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + setIsCallSMSTestLoading(false); + } + }} + /> + ) : ( + <></> + )} - {showSMSSuccessModal ? ( - <ConfirmModal - title={`SMS Sent`} - error={ - error === - 'Error connecting to server. Please try again in few minutes.' - ? 'Request timed out. Please check your twilio credentials and make sure they are correct.' - : error - } - description={`SMS sent successfully. It should take couple of minutes to arrive, please don't forget to check spam.`} - submitButtonType={ButtonStyleType.NORMAL} - submitButtonText={'Close'} - onSubmit={async () => { - setSMSShowSuccessModal(false); - setError(''); - }} - /> - ) : ( - <></> - )} + {showSMSSuccessModal ? ( + <ConfirmModal + title={`SMS Sent`} + error={ + error === + "Error connecting to server. Please try again in few minutes." + ? "Request timed out. Please check your twilio credentials and make sure they are correct." + : error + } + description={`SMS sent successfully. It should take couple of minutes to arrive, please don't forget to check spam.`} + submitButtonType={ButtonStyleType.NORMAL} + submitButtonText={"Close"} + onSubmit={async () => { + setSMSShowSuccessModal(false); + setError(""); + }} + /> + ) : ( + <></> + )} - {/** Call */} + {/** Call */} - {showCallTestModal && currentCallSMSTestConfig ? ( - <BasicFormModal - title={`Send Test Call`} - description={`Send a test call to verify your twilio config.`} - formProps={{ - error: error, - fields: [ - { - field: { - toPhone: true, - }, - title: 'Phone Number', - description: - 'Phone number to send test call to.', - fieldType: FormFieldSchemaType.Phone, - required: true, - placeholder: '+1234567890', - }, - ], - }} - submitButtonText={'Send Test Call'} - onClose={() => { - setShowCallTestModal(false); - setError(''); - }} - isLoading={isCallSMSTestLoading} - onSubmit={async (values: JSONObject) => { - try { - setIsCallSMSTestLoading(true); - setError(''); + {showCallTestModal && currentCallSMSTestConfig ? ( + <BasicFormModal + title={`Send Test Call`} + description={`Send a test call to verify your twilio config.`} + formProps={{ + error: error, + fields: [ + { + field: { + toPhone: true, + }, + title: "Phone Number", + description: "Phone number to send test call to.", + fieldType: FormFieldSchemaType.Phone, + required: true, + placeholder: "+1234567890", + }, + ], + }} + submitButtonText={"Send Test Call"} + onClose={() => { + setShowCallTestModal(false); + setError(""); + }} + isLoading={isCallSMSTestLoading} + onSubmit={async (values: JSONObject) => { + try { + setIsCallSMSTestLoading(true); + setError(""); - // test CallSMS config - const response: - | HTTPResponse<EmptyResponseData> - | HTTPErrorResponse = await API.post( - URL.fromString( - NOTIFICATION_URL.toString() - ).addRoute(`/call/test`), + // test CallSMS config + const response: + | HTTPResponse<EmptyResponseData> + | HTTPErrorResponse = await API.post( + URL.fromString(NOTIFICATION_URL.toString()).addRoute( + `/call/test`, + ), - { - toPhone: values['toPhone'], - callSMSConfigId: new ObjectID( - currentCallSMSTestConfig['_id'] - ? currentCallSMSTestConfig[ - '_id' - ].toString() - : '' - ).toString(), - } - ); - if (response.isSuccess()) { - setIsCallSMSTestLoading(false); - setShowCallTestModal(false); - setCallShowSuccessModal(true); - } + { + toPhone: values["toPhone"], + callSMSConfigId: new ObjectID( + currentCallSMSTestConfig["_id"] + ? currentCallSMSTestConfig["_id"].toString() + : "", + ).toString(), + }, + ); + if (response.isSuccess()) { + setIsCallSMSTestLoading(false); + setShowCallTestModal(false); + setCallShowSuccessModal(true); + } - if (response instanceof HTTPErrorResponse) { - throw response; - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - setIsCallSMSTestLoading(false); - } - }} - /> - ) : ( - <></> - )} + if (response instanceof HTTPErrorResponse) { + throw response; + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + setIsCallSMSTestLoading(false); + } + }} + /> + ) : ( + <></> + )} - {showCallSuccessModal ? ( - <ConfirmModal - title={`Call Sent`} - error={ - error === - 'Error connecting to server. Please try again in few minutes.' - ? 'Request timed out. Please check your twilio credentials and make sure they are correct.' - : error - } - description={`Call sent successfully. It should take couple of minutes to arrive, please don't forget to check spam.`} - submitButtonType={ButtonStyleType.NORMAL} - submitButtonText={'Close'} - onSubmit={async () => { - setCallShowSuccessModal(false); - setError(''); - }} - /> - ) : ( - <></> - )} - </> - ); + {showCallSuccessModal ? ( + <ConfirmModal + title={`Call Sent`} + error={ + error === + "Error connecting to server. Please try again in few minutes." + ? "Request timed out. Please check your twilio credentials and make sure they are correct." + : error + } + description={`Call sent successfully. It should take couple of minutes to arrive, please don't forget to check spam.`} + submitButtonType={ButtonStyleType.NORMAL} + submitButtonText={"Close"} + onSubmit={async () => { + setCallShowSuccessModal(false); + setError(""); + }} + /> + ) : ( + <></> + )} + </> + ); }; export default CustomCallSMSTable; diff --git a/Dashboard/src/Components/CustomSMTP/CustomSMTPTable.tsx b/Dashboard/src/Components/CustomSMTP/CustomSMTPTable.tsx index f76e356ad9..8b0732f7ec 100644 --- a/Dashboard/src/Components/CustomSMTP/CustomSMTPTable.tsx +++ b/Dashboard/src/Components/CustomSMTP/CustomSMTPTable.tsx @@ -1,339 +1,336 @@ -import EmptyResponseData from 'Common/Types/API/EmptyResponse'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { NOTIFICATION_URL } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectSmtpConfig from 'Model/Models/ProjectSmtpConfig'; +import EmptyResponseData from "Common/Types/API/EmptyResponse"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { NOTIFICATION_URL } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectSmtpConfig from "Model/Models/ProjectSmtpConfig"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const CustomSMTPTable: FunctionComponent = (): ReactElement => { - const [showSMTPTestModal, setShowSMTPTestModal] = useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [currentSMTPTestConfig, setCurrentSMTPTestConfig] = - useState<ProjectSmtpConfig | null>(null); - const [isSMTPTestLoading, setIsSMTPTestLoading] = useState<boolean>(false); + const [showSMTPTestModal, setShowSMTPTestModal] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [currentSMTPTestConfig, setCurrentSMTPTestConfig] = + useState<ProjectSmtpConfig | null>(null); + const [isSMTPTestLoading, setIsSMTPTestLoading] = useState<boolean>(false); - const [showSuccessModal, setShowSuccessModal] = useState<boolean>(false); + const [showSuccessModal, setShowSuccessModal] = useState<boolean>(false); - useEffect(() => { - setError(''); - }, [showSMTPTestModal]); + useEffect(() => { + setError(""); + }, [showSMTPTestModal]); - return ( - <> - <ModelTable<ProjectSmtpConfig> - modelType={ProjectSmtpConfig} - id="smtp-table" - actionButtons={[ - { - title: 'Send Test Email', - buttonStyleType: ButtonStyleType.OUTLINE, - icon: IconProp.Play, - onClick: async ( - item: ProjectSmtpConfig, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentSMTPTestConfig(item); - setShowSMTPTestModal(true); + return ( + <> + <ModelTable<ProjectSmtpConfig> + modelType={ProjectSmtpConfig} + id="smtp-table" + actionButtons={[ + { + title: "Send Test Email", + buttonStyleType: ButtonStyleType.OUTLINE, + icon: IconProp.Play, + onClick: async ( + item: ProjectSmtpConfig, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentSMTPTestConfig(item); + setShowSMTPTestModal(true); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Custom SMTP Configs', - description: - 'If you need OneUptime to send emails through your SMTP Server, please enter the server details here.', - }} - formSteps={[ - { - title: 'Basic', - id: 'basic-info', - }, - { - title: 'SMTP Server', - id: 'server-info', - }, - { - title: 'Authentication', - id: 'authentication', - }, - { - title: 'Email', - id: 'email-info', - }, - ]} - name="Settings > Custom SMTP Config" - noItemsMessage={'No SMTP Server Configs found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - description: - 'Friendly name for this config so you remember what this is about.', - placeholder: 'Company SMTP Server', - stepId: 'basic-info', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - stepId: 'basic-info', - description: - 'Friendly description for this config so you remember what this is about.', - placeholder: 'Company SMTP server hosted on AWS', - }, - { - field: { - hostname: true, - }, - title: 'Hostname', - stepId: 'server-info', - fieldType: FormFieldSchemaType.Hostname, - required: true, - placeholder: 'smtp.server.com', - }, - { - field: { - port: true, - }, - title: 'Port', - stepId: 'server-info', - fieldType: FormFieldSchemaType.Port, - required: true, - placeholder: '587', - }, - { - field: { - secure: true, - }, - title: 'Use SSL / TLS', - stepId: 'server-info', - fieldType: FormFieldSchemaType.Toggle, - description: 'Make email communication secure?', - }, - { - field: { - username: true, - }, - title: 'Username', - stepId: 'authentication', - fieldType: FormFieldSchemaType.Text, - required: false, - placeholder: 'emailuser', - }, - { - field: { - password: true, - }, - title: 'Password', - stepId: 'authentication', - fieldType: FormFieldSchemaType.EncryptedText, - required: false, - placeholder: 'Password', - }, - { - field: { - fromEmail: true, - }, - title: 'Email From', - stepId: 'email-info', - fieldType: FormFieldSchemaType.Email, - required: true, - description: - 'This is the display email your team and customers see, when they receive emails from OneUptime.', - placeholder: 'email@company.com', - }, - { - field: { - fromName: true, - }, - title: 'From Name', - stepId: 'email-info', - fieldType: FormFieldSchemaType.Text, - required: true, - description: - 'This is the display name your team and customers see, when they receive emails from OneUptime.', - placeholder: 'Company, Inc.', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - title: 'Name', - type: FieldType.Text, - field: { - name: true, - }, - }, - { - title: 'Description', - type: FieldType.Text, - field: { - description: true, - }, - }, - { - title: 'Server Host', - type: FieldType.Text, - field: { - hostname: true, - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - hostname: true, - }, - title: 'Server Host', - type: FieldType.Text, - }, - ]} - /> + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Custom SMTP Configs", + description: + "If you need OneUptime to send emails through your SMTP Server, please enter the server details here.", + }} + formSteps={[ + { + title: "Basic", + id: "basic-info", + }, + { + title: "SMTP Server", + id: "server-info", + }, + { + title: "Authentication", + id: "authentication", + }, + { + title: "Email", + id: "email-info", + }, + ]} + name="Settings > Custom SMTP Config" + noItemsMessage={"No SMTP Server Configs found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + description: + "Friendly name for this config so you remember what this is about.", + placeholder: "Company SMTP Server", + stepId: "basic-info", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + stepId: "basic-info", + description: + "Friendly description for this config so you remember what this is about.", + placeholder: "Company SMTP server hosted on AWS", + }, + { + field: { + hostname: true, + }, + title: "Hostname", + stepId: "server-info", + fieldType: FormFieldSchemaType.Hostname, + required: true, + placeholder: "smtp.server.com", + }, + { + field: { + port: true, + }, + title: "Port", + stepId: "server-info", + fieldType: FormFieldSchemaType.Port, + required: true, + placeholder: "587", + }, + { + field: { + secure: true, + }, + title: "Use SSL / TLS", + stepId: "server-info", + fieldType: FormFieldSchemaType.Toggle, + description: "Make email communication secure?", + }, + { + field: { + username: true, + }, + title: "Username", + stepId: "authentication", + fieldType: FormFieldSchemaType.Text, + required: false, + placeholder: "emailuser", + }, + { + field: { + password: true, + }, + title: "Password", + stepId: "authentication", + fieldType: FormFieldSchemaType.EncryptedText, + required: false, + placeholder: "Password", + }, + { + field: { + fromEmail: true, + }, + title: "Email From", + stepId: "email-info", + fieldType: FormFieldSchemaType.Email, + required: true, + description: + "This is the display email your team and customers see, when they receive emails from OneUptime.", + placeholder: "email@company.com", + }, + { + field: { + fromName: true, + }, + title: "From Name", + stepId: "email-info", + fieldType: FormFieldSchemaType.Text, + required: true, + description: + "This is the display name your team and customers see, when they receive emails from OneUptime.", + placeholder: "Company, Inc.", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + title: "Name", + type: FieldType.Text, + field: { + name: true, + }, + }, + { + title: "Description", + type: FieldType.Text, + field: { + description: true, + }, + }, + { + title: "Server Host", + type: FieldType.Text, + field: { + hostname: true, + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + hostname: true, + }, + title: "Server Host", + type: FieldType.Text, + }, + ]} + /> - {showSMTPTestModal && currentSMTPTestConfig ? ( - <BasicFormModal - title={`Send Test Email`} - description={`Send a test email to verify your SMTP config.`} - formProps={{ - error: error, - fields: [ - { - field: { - toEmail: true, - }, - title: 'Email', - description: - 'Email address to send test email to.', - fieldType: FormFieldSchemaType.Email, - required: true, - placeholder: 'test@company.com', - }, - ], - }} - submitButtonText={'Send Test Email'} - onClose={() => { - setShowSMTPTestModal(false); - setError(''); - }} - isLoading={isSMTPTestLoading} - onSubmit={async (values: JSONObject) => { - try { - setIsSMTPTestLoading(true); - setError(''); + {showSMTPTestModal && currentSMTPTestConfig ? ( + <BasicFormModal + title={`Send Test Email`} + description={`Send a test email to verify your SMTP config.`} + formProps={{ + error: error, + fields: [ + { + field: { + toEmail: true, + }, + title: "Email", + description: "Email address to send test email to.", + fieldType: FormFieldSchemaType.Email, + required: true, + placeholder: "test@company.com", + }, + ], + }} + submitButtonText={"Send Test Email"} + onClose={() => { + setShowSMTPTestModal(false); + setError(""); + }} + isLoading={isSMTPTestLoading} + onSubmit={async (values: JSONObject) => { + try { + setIsSMTPTestLoading(true); + setError(""); - // test SMTP config - const response: - | HTTPResponse<EmptyResponseData> - | HTTPErrorResponse = await API.post( - URL.fromString( - NOTIFICATION_URL.toString() - ).addRoute(`/smtp-config/test`), + // test SMTP config + const response: + | HTTPResponse<EmptyResponseData> + | HTTPErrorResponse = await API.post( + URL.fromString(NOTIFICATION_URL.toString()).addRoute( + `/smtp-config/test`, + ), - { - toEmail: values['toEmail'], - smtpConfigId: new ObjectID( - currentSMTPTestConfig['_id'] - ? currentSMTPTestConfig[ - '_id' - ].toString() - : '' - ).toString(), - } - ); - if (response.isSuccess()) { - setIsSMTPTestLoading(false); - setShowSMTPTestModal(false); - setShowSuccessModal(true); - } + { + toEmail: values["toEmail"], + smtpConfigId: new ObjectID( + currentSMTPTestConfig["_id"] + ? currentSMTPTestConfig["_id"].toString() + : "", + ).toString(), + }, + ); + if (response.isSuccess()) { + setIsSMTPTestLoading(false); + setShowSMTPTestModal(false); + setShowSuccessModal(true); + } - if (response instanceof HTTPErrorResponse) { - throw response; - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - setIsSMTPTestLoading(false); - } - }} - /> - ) : ( - <></> - )} + if (response instanceof HTTPErrorResponse) { + throw response; + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + setIsSMTPTestLoading(false); + } + }} + /> + ) : ( + <></> + )} - {showSuccessModal ? ( - <ConfirmModal - title={`Email Sent`} - error={ - error === - 'Error connecting to server. Please try again in few minutes.' - ? 'Request timed out. Please check your SMTP credentials and make sure they are correct.' - : error - } - description={`Email sent successfully. It should take couple of minutes to arrive, please don't forget to check spam.`} - submitButtonType={ButtonStyleType.NORMAL} - submitButtonText={'Close'} - onSubmit={async () => { - setShowSuccessModal(false); - setError(''); - }} - /> - ) : ( - <></> - )} - </> - ); + {showSuccessModal ? ( + <ConfirmModal + title={`Email Sent`} + error={ + error === + "Error connecting to server. Please try again in few minutes." + ? "Request timed out. Please check your SMTP credentials and make sure they are correct." + : error + } + description={`Email sent successfully. It should take couple of minutes to arrive, please don't forget to check spam.`} + submitButtonType={ButtonStyleType.NORMAL} + submitButtonText={"Close"} + onSubmit={async () => { + setShowSuccessModal(false); + setError(""); + }} + /> + ) : ( + <></> + )} + </> + ); }; export default CustomSMTPTable; diff --git a/Dashboard/src/Components/CustomSMTP/CustomSMTPView.tsx b/Dashboard/src/Components/CustomSMTP/CustomSMTPView.tsx index b3cb430351..45a805753d 100644 --- a/Dashboard/src/Components/CustomSMTP/CustomSMTPView.tsx +++ b/Dashboard/src/Components/CustomSMTP/CustomSMTPView.tsx @@ -1,37 +1,37 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import Link from 'CommonUI/src/Components/Link/Link'; -import ProjectSmtpConfig from 'Model/Models/ProjectSmtpConfig'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import Link from "CommonUI/src/Components/Link/Link"; +import ProjectSmtpConfig from "Model/Models/ProjectSmtpConfig"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - smtp?: ProjectSmtpConfig | undefined; - onNavigateComplete?: (() => void) | undefined; + smtp?: ProjectSmtpConfig | undefined; + onNavigateComplete?: (() => void) | undefined; } const CustomSMTPElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.smtp) { - return <span>OneUptime Mail Server</span>; - } + if (!props.smtp) { + return <span>OneUptime Mail Server</span>; + } - if (props.smtp._id) { - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_NOTIFICATION_SETTINGS] as Route - )} - > - <span>{props.smtp.name}</span> - </Link> - ); - } + if (props.smtp._id) { + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_NOTIFICATION_SETTINGS] as Route, + )} + > + <span>{props.smtp.name}</span> + </Link> + ); + } - return <span>{props.smtp.name}</span>; + return <span>{props.smtp.name}</span>; }; export default CustomSMTPElement; diff --git a/Dashboard/src/Components/Footer/Footer.tsx b/Dashboard/src/Components/Footer/Footer.tsx index bb1218635e..30bc4fdc14 100644 --- a/Dashboard/src/Components/Footer/Footer.tsx +++ b/Dashboard/src/Components/Footer/Footer.tsx @@ -1,122 +1,114 @@ -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { JSONObject } from 'Common/Types/JSON'; -import API from 'Common/Utils/API'; -import Footer from 'CommonUI/src/Components/Footer/Footer'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import { HOST, HTTP_PROTOCOL } from 'CommonUI/src/Config'; -import React from 'react'; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { JSONObject } from "Common/Types/JSON"; +import API from "Common/Utils/API"; +import Footer from "CommonUI/src/Components/Footer/Footer"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import { HOST, HTTP_PROTOCOL } from "CommonUI/src/Config"; +import React from "react"; const DashboardFooter: () => JSX.Element = () => { - const [showAboutModal, setShowAboutModal] = React.useState<boolean>(false); - const [isAboutModalLoading, setIsAboutModalLoading] = - React.useState<boolean>(false); - const [versionText, setVersionText] = React.useState<Dictionary<string>>( - {} + const [showAboutModal, setShowAboutModal] = React.useState<boolean>(false); + const [isAboutModalLoading, setIsAboutModalLoading] = + React.useState<boolean>(false); + const [versionText, setVersionText] = React.useState<Dictionary<string>>({}); + + const fetchVersions: PromiseVoidFunction = async (): Promise<void> => { + setIsAboutModalLoading(true); + + try { + const verText: Dictionary<string> = {}; + const apps: Array<{ + name: string; + path: string; + }> = [ + { + name: "API", + path: "/api", + }, + { + name: "Dashboard", + path: "/dashboard", + }, + ]; + + for (const app of apps) { + const version: JSONObject = await fetchAppVersion(app.path); + verText[app.name] = + `${app.name}: ${version["version"]} (${version["commit"]})`; + } + + setVersionText(verText); + } catch (err) { + setVersionText({ + error: "Version data is not available: " + (err as Error).message, + }); + } + + setIsAboutModalLoading(false); + }; + + const fetchAppVersion: (appName: string) => Promise<JSONObject> = async ( + appName: string, + ): Promise<JSONObject> => { + const response: HTTPResponse<JSONObject> = await API.get<JSONObject>( + URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`), ); - const fetchVersions: PromiseVoidFunction = async (): Promise<void> => { - setIsAboutModalLoading(true); + if (response.data) { + return response.data as JSONObject; + } + throw new BadDataException("Version data is not available"); + }; - try { - const verText: Dictionary<string> = {}; - const apps: Array<{ - name: string; - path: string; - }> = [ - { - name: 'API', - path: '/api', - }, - { - name: 'Dashboard', - path: '/dashboard', - }, - ]; + return ( + <> + <Footer + className="bg-white h-16 inset-x-0 bottom-0 px-8" + copyright="HackerBay, Inc." + links={[ + { + title: "Help and Support", + to: URL.fromString("https://oneuptime.com/support"), + }, + { + title: "Legal", + to: URL.fromString("https://oneuptime.com/legal"), + }, + { + title: "Version", + onClick: async () => { + setShowAboutModal(true); + await fetchVersions(); + }, + }, + ]} + /> - for (const app of apps) { - const version: JSONObject = await fetchAppVersion(app.path); - verText[ - app.name - ] = `${app.name}: ${version['version']} (${version['commit']})`; - } - - setVersionText(verText); - } catch (err) { - setVersionText({ - error: - 'Version data is not available: ' + (err as Error).message, - }); - } - - setIsAboutModalLoading(false); - }; - - const fetchAppVersion: (appName: string) => Promise<JSONObject> = async ( - appName: string - ): Promise<JSONObject> => { - const response: HTTPResponse<JSONObject> = await API.get<JSONObject>( - URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`) - ); - - if (response.data) { - return response.data as JSONObject; - } - throw new BadDataException('Version data is not available'); - }; - - return ( - <> - <Footer - className="bg-white h-16 inset-x-0 bottom-0 px-8" - copyright="HackerBay, Inc." - links={[ - { - title: 'Help and Support', - to: URL.fromString('https://oneuptime.com/support'), - }, - { - title: 'Legal', - to: URL.fromString('https://oneuptime.com/legal'), - }, - { - title: 'Version', - onClick: async () => { - setShowAboutModal(true); - await fetchVersions(); - }, - }, - ]} - /> - - {showAboutModal ? ( - <ConfirmModal - title={`OneUptime Version`} - description={ - <div> - {Object.keys(versionText).map( - (key: string, i: number) => { - return ( - <div key={i}>{versionText[key]}</div> - ); - } - )} - </div> - } - isLoading={isAboutModalLoading} - submitButtonText={'Close'} - onSubmit={() => { - return setShowAboutModal(false); - }} - /> - ) : ( - <></> - )} - </> - ); + {showAboutModal ? ( + <ConfirmModal + title={`OneUptime Version`} + description={ + <div> + {Object.keys(versionText).map((key: string, i: number) => { + return <div key={i}>{versionText[key]}</div>; + })} + </div> + } + isLoading={isAboutModalLoading} + submitButtonText={"Close"} + onSubmit={() => { + return setShowAboutModal(false); + }} + /> + ) : ( + <></> + )} + </> + ); }; export default DashboardFooter; diff --git a/Dashboard/src/Components/Form/Monitor/CriteriaFilter.tsx b/Dashboard/src/Components/Form/Monitor/CriteriaFilter.tsx index e6b071fc84..522ca638f4 100644 --- a/Dashboard/src/Components/Form/Monitor/CriteriaFilter.tsx +++ b/Dashboard/src/Components/Form/Monitor/CriteriaFilter.tsx @@ -1,395 +1,357 @@ -import CriteriaFilterUiUtil from '../../../Utils/Form/Monitor/CriteriaFilter'; -import Route from 'Common/Types/API/Route'; -import IconProp from 'Common/Types/Icon/IconProp'; +import CriteriaFilterUiUtil from "../../../Utils/Form/Monitor/CriteriaFilter"; +import Route from "Common/Types/API/Route"; +import IconProp from "Common/Types/Icon/IconProp"; import { - CheckOn, - CriteriaFilter, - CriteriaFilterUtil, - EvaluateOverTimeOptions, - EvaluateOverTimeType, - FilterType, -} from 'Common/Types/Monitor/CriteriaFilter'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; + CheckOn, + CriteriaFilter, + CriteriaFilterUtil, + EvaluateOverTimeOptions, + EvaluateOverTimeType, + FilterType, +} from "Common/Types/Monitor/CriteriaFilter"; +import MonitorType from "Common/Types/Monitor/MonitorType"; import Button, { - ButtonSize, - ButtonStyleType, -} from 'CommonUI/src/Components/Button/Button'; -import CheckboxElement from 'CommonUI/src/Components/Checkbox/Checkbox'; -import FieldLabelElement from 'CommonUI/src/Components/Detail/FieldLabel'; + ButtonSize, + ButtonStyleType, +} from "CommonUI/src/Components/Button/Button"; +import CheckboxElement from "CommonUI/src/Components/Checkbox/Checkbox"; +import FieldLabelElement from "CommonUI/src/Components/Detail/FieldLabel"; import Dropdown, { - DropdownOption, - DropdownValue, -} from 'CommonUI/src/Components/Dropdown/Dropdown'; -import Input from 'CommonUI/src/Components/Input/Input'; -import Link from 'CommonUI/src/Components/Link/Link'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; + DropdownOption, + DropdownValue, +} from "CommonUI/src/Components/Dropdown/Dropdown"; +import Input from "CommonUI/src/Components/Input/Input"; +import Link from "CommonUI/src/Components/Link/Link"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - initialValue: CriteriaFilter | undefined; - onChange?: undefined | ((value: CriteriaFilter) => void); - onDelete?: undefined | (() => void); - monitorType: MonitorType; + initialValue: CriteriaFilter | undefined; + onChange?: undefined | ((value: CriteriaFilter) => void); + onDelete?: undefined | (() => void); + monitorType: MonitorType; } const CriteriaFilterElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [criteriaFilter, setCriteriaFilter] = React.useState< - CriteriaFilter | undefined - >(props.initialValue); + const [criteriaFilter, setCriteriaFilter] = React.useState< + CriteriaFilter | undefined + >(props.initialValue); - const [valuePlaceholder, setValuePlaceholder] = React.useState<string>(''); + const [valuePlaceholder, setValuePlaceholder] = React.useState<string>(""); - const [checkOnOptions, setCheckOnOptions] = React.useState< - Array<DropdownOption> - >([]); + const [checkOnOptions, setCheckOnOptions] = React.useState< + Array<DropdownOption> + >([]); - const [isLoading, setIsLoading] = React.useState<boolean>(true); + const [isLoading, setIsLoading] = React.useState<boolean>(true); - useEffect(() => { - setCheckOnOptions( - CriteriaFilterUiUtil.getCheckOnOptionsByMonitorType( - props.monitorType - ) - ); - setIsLoading(false); - }, [props.monitorType]); - - const [filterTypeOptions, setFilterTypeOptions] = React.useState< - Array<DropdownOption> - >([]); - - useEffect(() => { - setFilterTypeOptions( - criteriaFilter?.checkOn - ? CriteriaFilterUiUtil.getFilterTypeOptionsByCheckOn( - criteriaFilter?.checkOn - ) - : [] - ); - setValuePlaceholder( - criteriaFilter?.checkOn - ? CriteriaFilterUiUtil.getFilterTypePlaceholderValueByCheckOn({ - monitorType: props.monitorType, - checkOn: criteriaFilter?.checkOn, - }) - : '' - ); - }, [criteriaFilter]); - - useEffect(() => { - if (props.onChange && criteriaFilter) { - props.onChange(criteriaFilter); - } - }, [criteriaFilter]); - - if (isLoading) { - return <></>; - } - - const filterConditionValue: DropdownOption | undefined = - filterTypeOptions.find((i: DropdownOption) => { - return i.value === criteriaFilter?.filterType; - }); - - const evaluateOverTimeMinutesValue: DropdownOption | undefined = - CriteriaFilterUiUtil.getEvaluateOverTimeMinutesOptions().find( - (item: DropdownOption) => { - return ( - item.value === - criteriaFilter?.evaluateOverTimeOptions?.timeValueInMinutes - ); - } - ); - - const evaluateOverTimeTypeValue: DropdownOption | undefined = - DropdownUtil.getDropdownOptionsFromEnum(EvaluateOverTimeType).find( - (item: DropdownOption) => { - return ( - item.value === - criteriaFilter?.evaluateOverTimeOptions - ?.evaluateOverTimeType - ); - } - ); - - return ( - <div> - <div className="rounded-md p-2 bg-gray-50 my-5 border-gray-200 border-solid border-2"> - <div className=""> - <FieldLabelElement title="Filter Type" /> - <Dropdown - value={checkOnOptions.find((i: DropdownOption) => { - return i.value === criteriaFilter?.checkOn; - })} - options={checkOnOptions} - onChange={( - value: DropdownValue | Array<DropdownValue> | null - ) => { - setCriteriaFilter({ - checkOn: value?.toString() as CheckOn, - filterType: undefined, - value: undefined, - eveluateOverTime: false, - evaluateOverTimeOptions: undefined, - }); - }} - /> - </div> - - {criteriaFilter?.checkOn && - criteriaFilter?.checkOn === CheckOn.DiskUsagePercent && ( - <div className="mt-1"> - <FieldLabelElement title="Disk Path" /> - - <Input - placeholder={'C:\\ or /mnt/data or /dev/sda1'} - value={criteriaFilter?.serverMonitorOptions?.diskPath?.toString()} - onChange={(value: string) => { - setCriteriaFilter({ - ...criteriaFilter, - serverMonitorOptions: { - diskPath: value, - }, - }); - }} - /> - </div> - )} - - {/** checkbox for evaluateOverTime */} - - {criteriaFilter?.checkOn && - CriteriaFilterUtil.isEvaluateOverTimeFilter( - criteriaFilter?.checkOn - ) && ( - <div className="mt-3"> - <CheckboxElement - value={criteriaFilter?.eveluateOverTime} - title={ - 'Evaluate this criteria over a period of time' - } - onChange={(value: boolean) => { - setCriteriaFilter({ - ...criteriaFilter, - eveluateOverTime: value, - }); - }} - /> - </div> - )} - - {criteriaFilter?.checkOn && - criteriaFilter?.checkOn && - CriteriaFilterUtil.isEvaluateOverTimeFilter( - criteriaFilter?.checkOn - ) && - criteriaFilter.eveluateOverTime ? ( - <div className="mt-1"> - <FieldLabelElement title="Evaluate" /> - <Dropdown - value={evaluateOverTimeTypeValue} - options={DropdownUtil.getDropdownOptionsFromEnum( - EvaluateOverTimeType - )} - onChange={( - value: - | DropdownValue - | Array<DropdownValue> - | null - ) => { - const evaluateOverTimeOption: EvaluateOverTimeOptions = - criteriaFilter?.evaluateOverTimeOptions - ? { - ...criteriaFilter?.evaluateOverTimeOptions, - } - : { - timeValueInMinutes: 5, - evaluateOverTimeType: - EvaluateOverTimeType.AllValues, - }; - - setCriteriaFilter({ - ...criteriaFilter, - eveluateOverTime: true, - evaluateOverTimeOptions: { - ...evaluateOverTimeOption, - evaluateOverTimeType: - value?.toString() as EvaluateOverTimeType, - }, - }); - }} - /> - </div> - ) : ( - <></> - )} - - {criteriaFilter?.checkOn && - criteriaFilter?.checkOn && - CriteriaFilterUtil.isEvaluateOverTimeFilter( - criteriaFilter?.checkOn - ) && - criteriaFilter.eveluateOverTime ? ( - <div className="mt-1"> - <FieldLabelElement title="For the last (in minutes)" /> - <Dropdown - value={evaluateOverTimeMinutesValue} - options={CriteriaFilterUiUtil.getEvaluateOverTimeMinutesOptions()} - onChange={( - value: - | DropdownValue - | Array<DropdownValue> - | null - ) => { - const evaluateOverTimeOption: EvaluateOverTimeOptions = - criteriaFilter?.evaluateOverTimeOptions - ? { - ...criteriaFilter?.evaluateOverTimeOptions, - } - : { - timeValueInMinutes: 5, - evaluateOverTimeType: - EvaluateOverTimeType.AllValues, - }; - - setCriteriaFilter({ - ...criteriaFilter, - eveluateOverTime: true, - evaluateOverTimeOptions: { - ...evaluateOverTimeOption, - timeValueInMinutes: value as number, - }, - }); - }} - /> - </div> - ) : ( - <></> - )} - - {!criteriaFilter?.checkOn || - (criteriaFilter?.checkOn && ( - <div className="mt-1"> - <FieldLabelElement title="Filter Condition" /> - <Dropdown - value={filterConditionValue} - options={filterTypeOptions} - onChange={( - value: - | DropdownValue - | Array<DropdownValue> - | null - ) => { - setCriteriaFilter({ - ...criteriaFilter, - filterType: - value?.toString() as FilterType, - value: undefined, - }); - }} - /> - </div> - ))} - - {!criteriaFilter?.checkOn || - (criteriaFilter?.checkOn && - CriteriaFilterUiUtil.hasValueField({ - checkOn: criteriaFilter?.checkOn, - filterType: criteriaFilter?.filterType, - }) && - !CriteriaFilterUiUtil.isDropdownValueField({ - checkOn: criteriaFilter?.checkOn, - }) && ( - <div className="mt-1"> - <FieldLabelElement title="Value" /> - <Input - placeholder={valuePlaceholder} - value={criteriaFilter?.value?.toString()} - onChange={(value: string) => { - setCriteriaFilter({ - ...criteriaFilter, - value: value || '', - }); - }} - /> - </div> - ))} - - {!criteriaFilter?.checkOn || - (criteriaFilter?.checkOn && - CriteriaFilterUiUtil.hasValueField({ - checkOn: criteriaFilter?.checkOn, - filterType: criteriaFilter?.filterType, - }) && - CriteriaFilterUiUtil.isDropdownValueField({ - checkOn: criteriaFilter?.checkOn, - }) && ( - <div className="mt-1"> - <FieldLabelElement title="Value" /> - <Dropdown - options={CriteriaFilterUiUtil.getDropdownOptionsByCheckOn( - { - checkOn: criteriaFilter?.checkOn, - } - )} - value={CriteriaFilterUiUtil.getDropdownOptionsByCheckOn( - { - checkOn: criteriaFilter?.checkOn, - } - ).find((i: DropdownOption) => { - return ( - i.value === criteriaFilter?.value - ); - })} - onChange={( - value: - | DropdownValue - | Array<DropdownValue> - | null - ) => { - setCriteriaFilter({ - ...criteriaFilter, - value: value?.toString(), - }); - }} - /> - </div> - ))} - - <div className="mt-3 -mr-2 w-full flex justify-end"> - <Button - title="Delete Filter" - buttonStyle={ButtonStyleType.DANGER_OUTLINE} - icon={IconProp.Trash} - buttonSize={ButtonSize.Small} - onClick={() => { - props.onDelete?.(); - }} - /> - </div> - </div> - {criteriaFilter?.checkOn === CheckOn.JavaScriptExpression ? ( - <div className="mt-1 text-sm text-gray-500 underline"> - <Link - to={Route.fromString( - '/docs/monitor/javascript-expression' - )} - openInNewTab={true} - > - <p> - {' '} - Read documentation for using JavaScript expressions - here.{' '} - </p> - </Link>{' '} - </div> - ) : ( - <></> - )} - </div> + useEffect(() => { + setCheckOnOptions( + CriteriaFilterUiUtil.getCheckOnOptionsByMonitorType(props.monitorType), ); + setIsLoading(false); + }, [props.monitorType]); + + const [filterTypeOptions, setFilterTypeOptions] = React.useState< + Array<DropdownOption> + >([]); + + useEffect(() => { + setFilterTypeOptions( + criteriaFilter?.checkOn + ? CriteriaFilterUiUtil.getFilterTypeOptionsByCheckOn( + criteriaFilter?.checkOn, + ) + : [], + ); + setValuePlaceholder( + criteriaFilter?.checkOn + ? CriteriaFilterUiUtil.getFilterTypePlaceholderValueByCheckOn({ + monitorType: props.monitorType, + checkOn: criteriaFilter?.checkOn, + }) + : "", + ); + }, [criteriaFilter]); + + useEffect(() => { + if (props.onChange && criteriaFilter) { + props.onChange(criteriaFilter); + } + }, [criteriaFilter]); + + if (isLoading) { + return <></>; + } + + const filterConditionValue: DropdownOption | undefined = + filterTypeOptions.find((i: DropdownOption) => { + return i.value === criteriaFilter?.filterType; + }); + + const evaluateOverTimeMinutesValue: DropdownOption | undefined = + CriteriaFilterUiUtil.getEvaluateOverTimeMinutesOptions().find( + (item: DropdownOption) => { + return ( + item.value === + criteriaFilter?.evaluateOverTimeOptions?.timeValueInMinutes + ); + }, + ); + + const evaluateOverTimeTypeValue: DropdownOption | undefined = + DropdownUtil.getDropdownOptionsFromEnum(EvaluateOverTimeType).find( + (item: DropdownOption) => { + return ( + item.value === + criteriaFilter?.evaluateOverTimeOptions?.evaluateOverTimeType + ); + }, + ); + + return ( + <div> + <div className="rounded-md p-2 bg-gray-50 my-5 border-gray-200 border-solid border-2"> + <div className=""> + <FieldLabelElement title="Filter Type" /> + <Dropdown + value={checkOnOptions.find((i: DropdownOption) => { + return i.value === criteriaFilter?.checkOn; + })} + options={checkOnOptions} + onChange={(value: DropdownValue | Array<DropdownValue> | null) => { + setCriteriaFilter({ + checkOn: value?.toString() as CheckOn, + filterType: undefined, + value: undefined, + eveluateOverTime: false, + evaluateOverTimeOptions: undefined, + }); + }} + /> + </div> + + {criteriaFilter?.checkOn && + criteriaFilter?.checkOn === CheckOn.DiskUsagePercent && ( + <div className="mt-1"> + <FieldLabelElement title="Disk Path" /> + + <Input + placeholder={"C:\\ or /mnt/data or /dev/sda1"} + value={criteriaFilter?.serverMonitorOptions?.diskPath?.toString()} + onChange={(value: string) => { + setCriteriaFilter({ + ...criteriaFilter, + serverMonitorOptions: { + diskPath: value, + }, + }); + }} + /> + </div> + )} + + {/** checkbox for evaluateOverTime */} + + {criteriaFilter?.checkOn && + CriteriaFilterUtil.isEvaluateOverTimeFilter( + criteriaFilter?.checkOn, + ) && ( + <div className="mt-3"> + <CheckboxElement + value={criteriaFilter?.eveluateOverTime} + title={"Evaluate this criteria over a period of time"} + onChange={(value: boolean) => { + setCriteriaFilter({ + ...criteriaFilter, + eveluateOverTime: value, + }); + }} + /> + </div> + )} + + {criteriaFilter?.checkOn && + criteriaFilter?.checkOn && + CriteriaFilterUtil.isEvaluateOverTimeFilter(criteriaFilter?.checkOn) && + criteriaFilter.eveluateOverTime ? ( + <div className="mt-1"> + <FieldLabelElement title="Evaluate" /> + <Dropdown + value={evaluateOverTimeTypeValue} + options={DropdownUtil.getDropdownOptionsFromEnum( + EvaluateOverTimeType, + )} + onChange={( + value: DropdownValue | Array<DropdownValue> | null, + ) => { + const evaluateOverTimeOption: EvaluateOverTimeOptions = + criteriaFilter?.evaluateOverTimeOptions + ? { + ...criteriaFilter?.evaluateOverTimeOptions, + } + : { + timeValueInMinutes: 5, + evaluateOverTimeType: EvaluateOverTimeType.AllValues, + }; + + setCriteriaFilter({ + ...criteriaFilter, + eveluateOverTime: true, + evaluateOverTimeOptions: { + ...evaluateOverTimeOption, + evaluateOverTimeType: + value?.toString() as EvaluateOverTimeType, + }, + }); + }} + /> + </div> + ) : ( + <></> + )} + + {criteriaFilter?.checkOn && + criteriaFilter?.checkOn && + CriteriaFilterUtil.isEvaluateOverTimeFilter(criteriaFilter?.checkOn) && + criteriaFilter.eveluateOverTime ? ( + <div className="mt-1"> + <FieldLabelElement title="For the last (in minutes)" /> + <Dropdown + value={evaluateOverTimeMinutesValue} + options={CriteriaFilterUiUtil.getEvaluateOverTimeMinutesOptions()} + onChange={( + value: DropdownValue | Array<DropdownValue> | null, + ) => { + const evaluateOverTimeOption: EvaluateOverTimeOptions = + criteriaFilter?.evaluateOverTimeOptions + ? { + ...criteriaFilter?.evaluateOverTimeOptions, + } + : { + timeValueInMinutes: 5, + evaluateOverTimeType: EvaluateOverTimeType.AllValues, + }; + + setCriteriaFilter({ + ...criteriaFilter, + eveluateOverTime: true, + evaluateOverTimeOptions: { + ...evaluateOverTimeOption, + timeValueInMinutes: value as number, + }, + }); + }} + /> + </div> + ) : ( + <></> + )} + + {!criteriaFilter?.checkOn || + (criteriaFilter?.checkOn && ( + <div className="mt-1"> + <FieldLabelElement title="Filter Condition" /> + <Dropdown + value={filterConditionValue} + options={filterTypeOptions} + onChange={( + value: DropdownValue | Array<DropdownValue> | null, + ) => { + setCriteriaFilter({ + ...criteriaFilter, + filterType: value?.toString() as FilterType, + value: undefined, + }); + }} + /> + </div> + ))} + + {!criteriaFilter?.checkOn || + (criteriaFilter?.checkOn && + CriteriaFilterUiUtil.hasValueField({ + checkOn: criteriaFilter?.checkOn, + filterType: criteriaFilter?.filterType, + }) && + !CriteriaFilterUiUtil.isDropdownValueField({ + checkOn: criteriaFilter?.checkOn, + }) && ( + <div className="mt-1"> + <FieldLabelElement title="Value" /> + <Input + placeholder={valuePlaceholder} + value={criteriaFilter?.value?.toString()} + onChange={(value: string) => { + setCriteriaFilter({ + ...criteriaFilter, + value: value || "", + }); + }} + /> + </div> + ))} + + {!criteriaFilter?.checkOn || + (criteriaFilter?.checkOn && + CriteriaFilterUiUtil.hasValueField({ + checkOn: criteriaFilter?.checkOn, + filterType: criteriaFilter?.filterType, + }) && + CriteriaFilterUiUtil.isDropdownValueField({ + checkOn: criteriaFilter?.checkOn, + }) && ( + <div className="mt-1"> + <FieldLabelElement title="Value" /> + <Dropdown + options={CriteriaFilterUiUtil.getDropdownOptionsByCheckOn({ + checkOn: criteriaFilter?.checkOn, + })} + value={CriteriaFilterUiUtil.getDropdownOptionsByCheckOn({ + checkOn: criteriaFilter?.checkOn, + }).find((i: DropdownOption) => { + return i.value === criteriaFilter?.value; + })} + onChange={( + value: DropdownValue | Array<DropdownValue> | null, + ) => { + setCriteriaFilter({ + ...criteriaFilter, + value: value?.toString(), + }); + }} + /> + </div> + ))} + + <div className="mt-3 -mr-2 w-full flex justify-end"> + <Button + title="Delete Filter" + buttonStyle={ButtonStyleType.DANGER_OUTLINE} + icon={IconProp.Trash} + buttonSize={ButtonSize.Small} + onClick={() => { + props.onDelete?.(); + }} + /> + </div> + </div> + {criteriaFilter?.checkOn === CheckOn.JavaScriptExpression ? ( + <div className="mt-1 text-sm text-gray-500 underline"> + <Link + to={Route.fromString("/docs/monitor/javascript-expression")} + openInNewTab={true} + > + <p> Read documentation for using JavaScript expressions here. </p> + </Link>{" "} + </div> + ) : ( + <></> + )} + </div> + ); }; export default CriteriaFilterElement; diff --git a/Dashboard/src/Components/Form/Monitor/CriteriaFilters.tsx b/Dashboard/src/Components/Form/Monitor/CriteriaFilters.tsx index 9546ba204a..eafcde0434 100644 --- a/Dashboard/src/Components/Form/Monitor/CriteriaFilters.tsx +++ b/Dashboard/src/Components/Form/Monitor/CriteriaFilters.tsx @@ -1,107 +1,107 @@ -import CriteriaFilterElement from './CriteriaFilter'; -import IconProp from 'Common/Types/Icon/IconProp'; +import CriteriaFilterElement from "./CriteriaFilter"; +import IconProp from "Common/Types/Icon/IconProp"; import { - CheckOn, - CriteriaFilter, - FilterType, -} from 'Common/Types/Monitor/CriteriaFilter'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; + CheckOn, + CriteriaFilter, + FilterType, +} from "Common/Types/Monitor/CriteriaFilter"; +import MonitorType from "Common/Types/Monitor/MonitorType"; import Button, { - ButtonSize, - ButtonStyleType, -} from 'CommonUI/src/Components/Button/Button'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; + ButtonSize, + ButtonStyleType, +} from "CommonUI/src/Components/Button/Button"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - initialValue: Array<CriteriaFilter> | undefined; - onChange?: undefined | ((value: Array<CriteriaFilter>) => void); - monitorType: MonitorType; + initialValue: Array<CriteriaFilter> | undefined; + onChange?: undefined | ((value: Array<CriteriaFilter>) => void); + monitorType: MonitorType; } const CriteriaFilters: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [criteriaFilters, setCriteriaFilters] = React.useState< - Array<CriteriaFilter> - >(props.initialValue || []); + const [criteriaFilters, setCriteriaFilters] = React.useState< + Array<CriteriaFilter> + >(props.initialValue || []); - const [showCantDeleteModal, setShowCantDeleteModal] = - React.useState<boolean>(false); + const [showCantDeleteModal, setShowCantDeleteModal] = + React.useState<boolean>(false); - useEffect(() => { - if (criteriaFilters && props.onChange) { - props.onChange(criteriaFilters); - } - }, [criteriaFilters]); + useEffect(() => { + if (criteriaFilters && props.onChange) { + props.onChange(criteriaFilters); + } + }, [criteriaFilters]); - return ( - <div> - {criteriaFilters.map((i: CriteriaFilter, index: number) => { - return ( - <CriteriaFilterElement - monitorType={props.monitorType} - key={index} - initialValue={i} - onDelete={() => { - if (criteriaFilters.length === 1) { - setShowCantDeleteModal(true); - return; - } + return ( + <div> + {criteriaFilters.map((i: CriteriaFilter, index: number) => { + return ( + <CriteriaFilterElement + monitorType={props.monitorType} + key={index} + initialValue={i} + onDelete={() => { + if (criteriaFilters.length === 1) { + setShowCantDeleteModal(true); + return; + } - // remove the criteria filter - const index: number = criteriaFilters.indexOf(i); - const newCriteriaFilters: Array<CriteriaFilter> = [ - ...criteriaFilters, - ]; - newCriteriaFilters.splice(index, 1); - setCriteriaFilters(newCriteriaFilters); - }} - onChange={(value: CriteriaFilter) => { - const index: number = criteriaFilters.indexOf(i); - const newCriteriaFilters: Array<CriteriaFilter> = [ - ...criteriaFilters, - ]; - newCriteriaFilters[index] = value; - setCriteriaFilters(newCriteriaFilters); - }} - /> - ); - })} - <div className="mt-3 -ml-3"> - <Button - title="Add Filter" - buttonSize={ButtonSize.Small} - icon={IconProp.Add} - onClick={() => { - const newCriteriaFilters: Array<CriteriaFilter> = [ - ...criteriaFilters, - ]; - newCriteriaFilters.push({ - checkOn: CheckOn.IsOnline, - filterType: FilterType.EqualTo, - value: '', - }); + // remove the criteria filter + const index: number = criteriaFilters.indexOf(i); + const newCriteriaFilters: Array<CriteriaFilter> = [ + ...criteriaFilters, + ]; + newCriteriaFilters.splice(index, 1); + setCriteriaFilters(newCriteriaFilters); + }} + onChange={(value: CriteriaFilter) => { + const index: number = criteriaFilters.indexOf(i); + const newCriteriaFilters: Array<CriteriaFilter> = [ + ...criteriaFilters, + ]; + newCriteriaFilters[index] = value; + setCriteriaFilters(newCriteriaFilters); + }} + /> + ); + })} + <div className="mt-3 -ml-3"> + <Button + title="Add Filter" + buttonSize={ButtonSize.Small} + icon={IconProp.Add} + onClick={() => { + const newCriteriaFilters: Array<CriteriaFilter> = [ + ...criteriaFilters, + ]; + newCriteriaFilters.push({ + checkOn: CheckOn.IsOnline, + filterType: FilterType.EqualTo, + value: "", + }); - setCriteriaFilters(newCriteriaFilters); - }} - /> - </div> - {showCantDeleteModal ? ( - <ConfirmModal - description={`We need at least one filter for this criteria. We cant delete one remaining filter. If you don't need filters, please feel free to delete criteria instead.`} - title={`Cannot delete last remaining filter.`} - onSubmit={() => { - setShowCantDeleteModal(false); - }} - submitButtonType={ButtonStyleType.NORMAL} - submitButtonText="Close" - /> - ) : ( - <></> - )} - </div> - ); + setCriteriaFilters(newCriteriaFilters); + }} + /> + </div> + {showCantDeleteModal ? ( + <ConfirmModal + description={`We need at least one filter for this criteria. We cant delete one remaining filter. If you don't need filters, please feel free to delete criteria instead.`} + title={`Cannot delete last remaining filter.`} + onSubmit={() => { + setShowCantDeleteModal(false); + }} + submitButtonType={ButtonStyleType.NORMAL} + submitButtonText="Close" + /> + ) : ( + <></> + )} + </div> + ); }; export default CriteriaFilters; diff --git a/Dashboard/src/Components/Form/Monitor/MonitorCriteria.tsx b/Dashboard/src/Components/Form/Monitor/MonitorCriteria.tsx index 7373fd4731..3e934f3fbd 100644 --- a/Dashboard/src/Components/Form/Monitor/MonitorCriteria.tsx +++ b/Dashboard/src/Components/Form/Monitor/MonitorCriteria.tsx @@ -1,177 +1,158 @@ -import MonitorCriteriaInstanceElement from './MonitorCriteriaInstance'; -import IconProp from 'Common/Types/Icon/IconProp'; -import MonitorCriteria from 'Common/Types/Monitor/MonitorCriteria'; -import MonitorCriteriaInstance from 'Common/Types/Monitor/MonitorCriteriaInstance'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; +import MonitorCriteriaInstanceElement from "./MonitorCriteriaInstance"; +import IconProp from "Common/Types/Icon/IconProp"; +import MonitorCriteria from "Common/Types/Monitor/MonitorCriteria"; +import MonitorCriteriaInstance from "Common/Types/Monitor/MonitorCriteriaInstance"; +import MonitorType from "Common/Types/Monitor/MonitorType"; import Button, { - ButtonSize, - ButtonStyleType, -} from 'CommonUI/src/Components/Button/Button'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; + ButtonSize, + ButtonStyleType, +} from "CommonUI/src/Components/Button/Button"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - initialValue: MonitorCriteria | undefined; - onChange?: undefined | ((value: MonitorCriteria) => void); - monitorStatusDropdownOptions: Array<DropdownOption>; - incidentSeverityDropdownOptions: Array<DropdownOption>; - onCallPolicyDropdownOptions: Array<DropdownOption>; - monitorType: MonitorType; + initialValue: MonitorCriteria | undefined; + onChange?: undefined | ((value: MonitorCriteria) => void); + monitorStatusDropdownOptions: Array<DropdownOption>; + incidentSeverityDropdownOptions: Array<DropdownOption>; + onCallPolicyDropdownOptions: Array<DropdownOption>; + monitorType: MonitorType; } const MonitorCriteriaElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [showCantDeleteModal, setShowCantDeleteModal] = - React.useState<boolean>(false); + const [showCantDeleteModal, setShowCantDeleteModal] = + React.useState<boolean>(false); - const [monitorCriteria, setMonitorCriteria] = - React.useState<MonitorCriteria>( - props.initialValue || new MonitorCriteria() - ); + const [monitorCriteria, setMonitorCriteria] = React.useState<MonitorCriteria>( + props.initialValue || new MonitorCriteria(), + ); - useEffect(() => { - if (monitorCriteria && props.onChange) { - props.onChange(monitorCriteria); - } - }, [monitorCriteria]); + useEffect(() => { + if (monitorCriteria && props.onChange) { + props.onChange(monitorCriteria); + } + }, [monitorCriteria]); - return ( - <div className="mt-4"> - {monitorCriteria.data?.monitorCriteriaInstanceArray.map( - (i: MonitorCriteriaInstance) => { - return ( - <div className="mt-10 mb-10" key={i.data?.id}> - <MonitorCriteriaInstanceElement - monitorType={props.monitorType} - monitorStatusDropdownOptions={ - props.monitorStatusDropdownOptions - } - incidentSeverityDropdownOptions={ - props.incidentSeverityDropdownOptions - } - onCallPolicyDropdownOptions={ - props.onCallPolicyDropdownOptions - } - initialValue={i} - onDelete={() => { - if ( - monitorCriteria.data - ?.monitorCriteriaInstanceArray - .length === 1 - ) { - setShowCantDeleteModal(true); - return; - } - - // remove the criteria filter - const index: number | undefined = - monitorCriteria.data?.monitorCriteriaInstanceArray.findIndex( - (item: MonitorCriteriaInstance) => { - return ( - item.data?.id === i.data?.id - ); - } - ); - - if (index === undefined) { - return; - } - - const newMonitorCriterias: Array<MonitorCriteriaInstance> = - [ - ...(monitorCriteria.data - ?.monitorCriteriaInstanceArray || - []), - ]; - newMonitorCriterias.splice(index, 1); - setMonitorCriteria( - MonitorCriteria.fromJSON({ - _type: 'MonitorCriteria', - value: { - monitorCriteriaInstanceArray: [ - ...newMonitorCriterias, - ], - }, - }) - ); - }} - onChange={(value: MonitorCriteriaInstance) => { - const index: number | undefined = - monitorCriteria.data?.monitorCriteriaInstanceArray.findIndex( - (item: MonitorCriteriaInstance) => { - return ( - item.data?.id === - value.data?.id - ); - } - ); - - if (index === undefined) { - return; - } - const newMonitorCriterias: Array<MonitorCriteriaInstance> = - [ - ...(monitorCriteria.data - ?.monitorCriteriaInstanceArray || - []), - ]; - newMonitorCriterias[index] = value; - setMonitorCriteria( - MonitorCriteria.fromJSON({ - _type: 'MonitorCriteria', - value: { - monitorCriteriaInstanceArray: - newMonitorCriterias, - }, - }) - ); - }} - /> - </div> - ); + return ( + <div className="mt-4"> + {monitorCriteria.data?.monitorCriteriaInstanceArray.map( + (i: MonitorCriteriaInstance) => { + return ( + <div className="mt-10 mb-10" key={i.data?.id}> + <MonitorCriteriaInstanceElement + monitorType={props.monitorType} + monitorStatusDropdownOptions={ + props.monitorStatusDropdownOptions } - )} - <div className="mt-4 -ml-3"> - <Button - title="Add Criteria" - buttonSize={ButtonSize.Small} - icon={IconProp.Add} - onClick={() => { - const newMonitorCriterias: Array<MonitorCriteriaInstance> = - [ - ...(monitorCriteria.data - ?.monitorCriteriaInstanceArray || []), - ]; - newMonitorCriterias.push(new MonitorCriteriaInstance()); - setMonitorCriteria( - MonitorCriteria.fromJSON({ - _type: 'MonitorCriteria', - value: { - monitorCriteriaInstanceArray: - newMonitorCriterias, - }, - }) - ); - }} - /> + incidentSeverityDropdownOptions={ + props.incidentSeverityDropdownOptions + } + onCallPolicyDropdownOptions={props.onCallPolicyDropdownOptions} + initialValue={i} + onDelete={() => { + if ( + monitorCriteria.data?.monitorCriteriaInstanceArray + .length === 1 + ) { + setShowCantDeleteModal(true); + return; + } + + // remove the criteria filter + const index: number | undefined = + monitorCriteria.data?.monitorCriteriaInstanceArray.findIndex( + (item: MonitorCriteriaInstance) => { + return item.data?.id === i.data?.id; + }, + ); + + if (index === undefined) { + return; + } + + const newMonitorCriterias: Array<MonitorCriteriaInstance> = [ + ...(monitorCriteria.data?.monitorCriteriaInstanceArray || + []), + ]; + newMonitorCriterias.splice(index, 1); + setMonitorCriteria( + MonitorCriteria.fromJSON({ + _type: "MonitorCriteria", + value: { + monitorCriteriaInstanceArray: [...newMonitorCriterias], + }, + }), + ); + }} + onChange={(value: MonitorCriteriaInstance) => { + const index: number | undefined = + monitorCriteria.data?.monitorCriteriaInstanceArray.findIndex( + (item: MonitorCriteriaInstance) => { + return item.data?.id === value.data?.id; + }, + ); + + if (index === undefined) { + return; + } + const newMonitorCriterias: Array<MonitorCriteriaInstance> = [ + ...(monitorCriteria.data?.monitorCriteriaInstanceArray || + []), + ]; + newMonitorCriterias[index] = value; + setMonitorCriteria( + MonitorCriteria.fromJSON({ + _type: "MonitorCriteria", + value: { + monitorCriteriaInstanceArray: newMonitorCriterias, + }, + }), + ); + }} + /> </div> - {showCantDeleteModal ? ( - <ConfirmModal - description={`We need at least one criteria for this monitor. We cant delete one remaining criteria.`} - title={`Cannot delete last remaining criteria.`} - onSubmit={() => { - setShowCantDeleteModal(false); - }} - submitButtonType={ButtonStyleType.NORMAL} - submitButtonText="Close" - /> - ) : ( - <></> - )} - </div> - ); + ); + }, + )} + <div className="mt-4 -ml-3"> + <Button + title="Add Criteria" + buttonSize={ButtonSize.Small} + icon={IconProp.Add} + onClick={() => { + const newMonitorCriterias: Array<MonitorCriteriaInstance> = [ + ...(monitorCriteria.data?.monitorCriteriaInstanceArray || []), + ]; + newMonitorCriterias.push(new MonitorCriteriaInstance()); + setMonitorCriteria( + MonitorCriteria.fromJSON({ + _type: "MonitorCriteria", + value: { + monitorCriteriaInstanceArray: newMonitorCriterias, + }, + }), + ); + }} + /> + </div> + {showCantDeleteModal ? ( + <ConfirmModal + description={`We need at least one criteria for this monitor. We cant delete one remaining criteria.`} + title={`Cannot delete last remaining criteria.`} + onSubmit={() => { + setShowCantDeleteModal(false); + }} + submitButtonType={ButtonStyleType.NORMAL} + submitButtonText="Close" + /> + ) : ( + <></> + )} + </div> + ); }; export default MonitorCriteriaElement; diff --git a/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentForm.tsx b/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentForm.tsx index 9961356ea4..c2b4d8aa34 100644 --- a/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentForm.tsx +++ b/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentForm.tsx @@ -1,98 +1,97 @@ -import { CriteriaIncident } from 'Common/Types/Monitor/CriteriaIncident'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import BasicForm from 'CommonUI/src/Components/Forms/BasicForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import Incident from 'Model/Models/Incident'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { CriteriaIncident } from "Common/Types/Monitor/CriteriaIncident"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import BasicForm from "CommonUI/src/Components/Forms/BasicForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import Incident from "Model/Models/Incident"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - initialValue?: undefined | CriteriaIncident; - onChange?: undefined | ((value: CriteriaIncident) => void); - incidentSeverityDropdownOptions: Array<DropdownOption>; - onCallPolicyDropdownOptions: Array<DropdownOption>; - // onDelete?: undefined | (() => void); + initialValue?: undefined | CriteriaIncident; + onChange?: undefined | ((value: CriteriaIncident) => void); + incidentSeverityDropdownOptions: Array<DropdownOption>; + onCallPolicyDropdownOptions: Array<DropdownOption>; + // onDelete?: undefined | (() => void); } const MonitorCriteriaIncidentForm: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mt-4"> - <BasicForm - modelType={Incident} - hideSubmitButton={true} - initialValues={props.initialValue} - onChange={(values: FormValues<CriteriaIncident>) => { - props.onChange && - props.onChange(values as CriteriaIncident); - }} - disableAutofocus={true} - fields={[ - { - field: { - title: true, - }, - title: 'Incident Title', - fieldType: FormFieldSchemaType.Text, - stepId: 'incident-details', - required: true, - placeholder: 'Incident Title', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Incident Description', - stepId: 'incident-details', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - incidentSeverityId: true, - }, - title: 'Incident Severity', - stepId: 'incident-details', - description: 'What type of incident is this?', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: props.incidentSeverityDropdownOptions, - required: true, - placeholder: 'Incident Severity', - id: 'incident-severity', - }, - { - field: { - onCallPolicyIds: true, - }, - title: 'On-Call Policy', - stepId: 'incident-details', - description: - 'Execute these on-call policies when this incident is created.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownOptions: props.onCallPolicyDropdownOptions, - required: false, - placeholder: 'Select On-Call Policies', - }, - { - field: { - autoResolveIncident: true, - }, - title: 'Auto Resolve Incident', - stepId: 'incident-details', - description: - 'Automatically resolve this incident when this criteria is no longer met.', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - ]} - /> + return ( + <div className="mt-4"> + <BasicForm + modelType={Incident} + hideSubmitButton={true} + initialValues={props.initialValue} + onChange={(values: FormValues<CriteriaIncident>) => { + props.onChange && props.onChange(values as CriteriaIncident); + }} + disableAutofocus={true} + fields={[ + { + field: { + title: true, + }, + title: "Incident Title", + fieldType: FormFieldSchemaType.Text, + stepId: "incident-details", + required: true, + placeholder: "Incident Title", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Incident Description", + stepId: "incident-details", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + incidentSeverityId: true, + }, + title: "Incident Severity", + stepId: "incident-details", + description: "What type of incident is this?", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: props.incidentSeverityDropdownOptions, + required: true, + placeholder: "Incident Severity", + id: "incident-severity", + }, + { + field: { + onCallPolicyIds: true, + }, + title: "On-Call Policy", + stepId: "incident-details", + description: + "Execute these on-call policies when this incident is created.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownOptions: props.onCallPolicyDropdownOptions, + required: false, + placeholder: "Select On-Call Policies", + }, + { + field: { + autoResolveIncident: true, + }, + title: "Auto Resolve Incident", + stepId: "incident-details", + description: + "Automatically resolve this incident when this criteria is no longer met.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + ]} + /> - {/* <div className='mt-4'> + {/* <div className='mt-4'> <Button onClick={() => { if (props.onDelete) { @@ -102,8 +101,8 @@ const MonitorCriteriaIncidentForm: FunctionComponent<ComponentProps> = ( title="Delete" /> </div> */} - </div> - ); + </div> + ); }; export default MonitorCriteriaIncidentForm; diff --git a/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentsForm.tsx b/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentsForm.tsx index 60e17ebf6e..8288cf567c 100644 --- a/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentsForm.tsx +++ b/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentsForm.tsx @@ -1,72 +1,68 @@ -import MonitorCriteriaIncidentForm from './MonitorCriteriaIncidentForm'; -import { CriteriaIncident } from 'Common/Types/Monitor/CriteriaIncident'; -import ObjectID from 'Common/Types/ObjectID'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import MonitorCriteriaIncidentForm from "./MonitorCriteriaIncidentForm"; +import { CriteriaIncident } from "Common/Types/Monitor/CriteriaIncident"; +import ObjectID from "Common/Types/ObjectID"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - initialValue: Array<CriteriaIncident> | undefined; - onChange?: undefined | ((value: Array<CriteriaIncident>) => void); - incidentSeverityDropdownOptions: Array<DropdownOption>; - onCallPolicyDropdownOptions: Array<DropdownOption>; + initialValue: Array<CriteriaIncident> | undefined; + onChange?: undefined | ((value: Array<CriteriaIncident>) => void); + incidentSeverityDropdownOptions: Array<DropdownOption>; + onCallPolicyDropdownOptions: Array<DropdownOption>; } const MonitorCriteriaIncidentsForm: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [incidents, setIncidents] = React.useState<Array<CriteriaIncident>>( - props.initialValue || [ - { - title: '', - description: '', - incidentSeverityId: undefined, - id: ObjectID.generate().toString(), - }, - ] - ); + const [incidents, setIncidents] = React.useState<Array<CriteriaIncident>>( + props.initialValue || [ + { + title: "", + description: "", + incidentSeverityId: undefined, + id: ObjectID.generate().toString(), + }, + ], + ); - useEffect(() => { - if (incidents && props.onChange) { - props.onChange(incidents); - } - }, [incidents]); + useEffect(() => { + if (incidents && props.onChange) { + props.onChange(incidents); + } + }, [incidents]); - return ( - <div className="mt-4"> - {incidents.map((i: CriteriaIncident, index: number) => { - return ( - <MonitorCriteriaIncidentForm - key={index} - incidentSeverityDropdownOptions={ - props.incidentSeverityDropdownOptions - } - onCallPolicyDropdownOptions={ - props.onCallPolicyDropdownOptions - } - initialValue={i} - // onDelete={() => { - // // remove the criteria filter - // const index: number = incidents.indexOf(i); - // const newIncidents: Array<CriteriaIncident> = [ - // ...incidents, - // ]; - // newIncidents.splice(index, 1); - // setIncidents(newIncidents); - // }} - onChange={(value: CriteriaIncident) => { - const index: number = incidents.indexOf(i); - const newIncidents: Array<CriteriaIncident> = [ - ...incidents, - ]; - newIncidents[index] = value; - setIncidents(newIncidents); - }} - /> - ); - })} + return ( + <div className="mt-4"> + {incidents.map((i: CriteriaIncident, index: number) => { + return ( + <MonitorCriteriaIncidentForm + key={index} + incidentSeverityDropdownOptions={ + props.incidentSeverityDropdownOptions + } + onCallPolicyDropdownOptions={props.onCallPolicyDropdownOptions} + initialValue={i} + // onDelete={() => { + // // remove the criteria filter + // const index: number = incidents.indexOf(i); + // const newIncidents: Array<CriteriaIncident> = [ + // ...incidents, + // ]; + // newIncidents.splice(index, 1); + // setIncidents(newIncidents); + // }} + onChange={(value: CriteriaIncident) => { + const index: number = incidents.indexOf(i); + const newIncidents: Array<CriteriaIncident> = [...incidents]; + newIncidents[index] = value; + setIncidents(newIncidents); + }} + /> + ); + })} - {/** Future Proofing */} - {/* <Button + {/** Future Proofing */} + {/* <Button title="Add Incident" onClick={() => { const newIncidents: Array<CriteriaIncident> = [ @@ -79,8 +75,8 @@ const MonitorCriteriaIncidentsForm: FunctionComponent<ComponentProps> = ( }); }} /> */} - </div> - ); + </div> + ); }; export default MonitorCriteriaIncidentsForm; diff --git a/Dashboard/src/Components/Form/Monitor/MonitorCriteriaInstance.tsx b/Dashboard/src/Components/Form/Monitor/MonitorCriteriaInstance.tsx index c7a3bb7a6b..c6f6401fe7 100644 --- a/Dashboard/src/Components/Form/Monitor/MonitorCriteriaInstance.tsx +++ b/Dashboard/src/Components/Form/Monitor/MonitorCriteriaInstance.tsx @@ -1,400 +1,359 @@ -import CriteriaFilters from './CriteriaFilters'; -import MonitorCriteriaIncidentsForm from './MonitorCriteriaIncidentsForm'; -import Dictionary from 'Common/Types/Dictionary'; -import IconProp from 'Common/Types/Icon/IconProp'; +import CriteriaFilters from "./CriteriaFilters"; +import MonitorCriteriaIncidentsForm from "./MonitorCriteriaIncidentsForm"; +import Dictionary from "Common/Types/Dictionary"; +import IconProp from "Common/Types/Icon/IconProp"; import { - CriteriaFilter, - FilterCondition, -} from 'Common/Types/Monitor/CriteriaFilter'; -import { CriteriaIncident } from 'Common/Types/Monitor/CriteriaIncident'; -import MonitorCriteriaInstance from 'Common/Types/Monitor/MonitorCriteriaInstance'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; + CriteriaFilter, + FilterCondition, +} from "Common/Types/Monitor/CriteriaFilter"; +import { CriteriaIncident } from "Common/Types/Monitor/CriteriaIncident"; +import MonitorCriteriaInstance from "Common/Types/Monitor/MonitorCriteriaInstance"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; import Button, { - ButtonSize, - ButtonStyleType, -} from 'CommonUI/src/Components/Button/Button'; + ButtonSize, + ButtonStyleType, +} from "CommonUI/src/Components/Button/Button"; import Dropdown, { - DropdownOption, - DropdownValue, -} from 'CommonUI/src/Components/Dropdown/Dropdown'; -import FieldLabelElement from 'CommonUI/src/Components/Forms/Fields/FieldLabel'; -import HorizontalRule from 'CommonUI/src/Components/HorizontalRule/HorizontalRule'; -import Input from 'CommonUI/src/Components/Input/Input'; -import Radio from 'CommonUI/src/Components/Radio/Radio'; -import TextArea from 'CommonUI/src/Components/TextArea/TextArea'; -import Toggle from 'CommonUI/src/Components/Toggle/Toggle'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; + DropdownOption, + DropdownValue, +} from "CommonUI/src/Components/Dropdown/Dropdown"; +import FieldLabelElement from "CommonUI/src/Components/Forms/Fields/FieldLabel"; +import HorizontalRule from "CommonUI/src/Components/HorizontalRule/HorizontalRule"; +import Input from "CommonUI/src/Components/Input/Input"; +import Radio from "CommonUI/src/Components/Radio/Radio"; +import TextArea from "CommonUI/src/Components/TextArea/TextArea"; +import Toggle from "CommonUI/src/Components/Toggle/Toggle"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - monitorStatusDropdownOptions: Array<DropdownOption>; - incidentSeverityDropdownOptions: Array<DropdownOption>; - onCallPolicyDropdownOptions: Array<DropdownOption>; - monitorType: MonitorType; - initialValue?: undefined | MonitorCriteriaInstance; - onChange?: undefined | ((value: MonitorCriteriaInstance) => void); - onDelete?: undefined | (() => void); + monitorStatusDropdownOptions: Array<DropdownOption>; + incidentSeverityDropdownOptions: Array<DropdownOption>; + onCallPolicyDropdownOptions: Array<DropdownOption>; + monitorType: MonitorType; + initialValue?: undefined | MonitorCriteriaInstance; + onChange?: undefined | ((value: MonitorCriteriaInstance) => void); + onDelete?: undefined | (() => void); } const MonitorCriteriaInstanceElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [monitorCriteriaInstance, setMonitorCriteriaInstance] = - useState<MonitorCriteriaInstance>( - props.initialValue || new MonitorCriteriaInstance() - ); + const [monitorCriteriaInstance, setMonitorCriteriaInstance] = + useState<MonitorCriteriaInstance>( + props.initialValue || new MonitorCriteriaInstance(), + ); - const [defaultMonitorStatusId, setDefaultMonitorStatusId] = useState< - ObjectID | undefined - >(monitorCriteriaInstance?.data?.monitorStatusId); + const [defaultMonitorStatusId, setDefaultMonitorStatusId] = useState< + ObjectID | undefined + >(monitorCriteriaInstance?.data?.monitorStatusId); - useEffect(() => { - if (props.onChange && monitorCriteriaInstance) { - props.onChange(monitorCriteriaInstance); - } - }, [monitorCriteriaInstance]); + useEffect(() => { + if (props.onChange && monitorCriteriaInstance) { + props.onChange(monitorCriteriaInstance); + } + }, [monitorCriteriaInstance]); - const filterConditionOptions: Array<DropdownOption> = - DropdownUtil.getDropdownOptionsFromEnum(FilterCondition); + const filterConditionOptions: Array<DropdownOption> = + DropdownUtil.getDropdownOptionsFromEnum(FilterCondition); - const [errors, setErrors] = useState<Dictionary<string>>({}); - const [touched, setTouched] = useState<Dictionary<boolean>>({}); + const [errors, setErrors] = useState<Dictionary<string>>({}); + const [touched, setTouched] = useState<Dictionary<boolean>>({}); - useEffect(() => { - // set first value as default - if ( - props.monitorStatusDropdownOptions.length > 0 && - !defaultMonitorStatusId && - props.monitorStatusDropdownOptions[0] && - props.monitorStatusDropdownOptions[0].value - ) { - setDefaultMonitorStatusId( - new ObjectID( - props.monitorStatusDropdownOptions[0].value.toString() - ) + useEffect(() => { + // set first value as default + if ( + props.monitorStatusDropdownOptions.length > 0 && + !defaultMonitorStatusId && + props.monitorStatusDropdownOptions[0] && + props.monitorStatusDropdownOptions[0].value + ) { + setDefaultMonitorStatusId( + new ObjectID(props.monitorStatusDropdownOptions[0].value.toString()), + ); + } + }, [props.monitorStatusDropdownOptions]); + + const [showMonitorStatusChangeControl, setShowMonitorStatusChangeControl] = + useState<boolean>( + Boolean(props.initialValue?.data?.monitorStatusId?.id) || false, + ); + const [showIncidentControl, setShowIncidentControl] = useState<boolean>( + (props.initialValue?.data?.incidents?.length || 0) > 0, + ); + + return ( + <div className="mt-4"> + <div className="mt-5"> + <FieldLabelElement + title={"Criteria Name"} + description={ + "Any friendly name for this criteria, that will help you remember later." + } + required={true} + /> + <Input + value={monitorCriteriaInstance?.data?.name?.toString() || ""} + onBlur={() => { + setTouched({ + ...touched, + name: true, + }); + + if (!monitorCriteriaInstance?.data?.name) { + setErrors({ + ...errors, + name: "Name is required", + }); + } else { + setErrors({ + ...errors, + name: "", + }); + } + }} + error={touched["name"] && errors["name"] ? errors["name"] : undefined} + placeholder="Online Criteria" + onChange={(value: string) => { + if (!value) { + setErrors({ + ...errors, + name: "Name is required", + }); + } else { + setErrors({ + ...errors, + name: "", + }); + } + + monitorCriteriaInstance.setName(value); + setMonitorCriteriaInstance( + MonitorCriteriaInstance.clone(monitorCriteriaInstance), ); - } - }, [props.monitorStatusDropdownOptions]); + }} + /> + </div> + <div className="mt-5"> + <FieldLabelElement + title={"Criteria Description"} + description={ + "Any friendly description for this criteria, that will help you remember later." + } + required={true} + /> + <TextArea + value={monitorCriteriaInstance?.data?.description?.toString() || ""} + onBlur={() => { + setTouched({ + ...touched, + description: true, + }); - const [showMonitorStatusChangeControl, setShowMonitorStatusChangeControl] = - useState<boolean>( - Boolean(props.initialValue?.data?.monitorStatusId?.id) || false - ); - const [showIncidentControl, setShowIncidentControl] = useState<boolean>( - (props.initialValue?.data?.incidents?.length || 0) > 0 - ); + if (!monitorCriteriaInstance?.data?.description) { + setErrors({ + ...errors, + description: "Description is required", + }); + } else { + setErrors({ + ...errors, + description: "", + }); + } + }} + error={ + touched["description"] && errors["description"] + ? errors["description"] + : undefined + } + onChange={(value: string) => { + if (!value) { + setErrors({ + ...errors, + description: "Description is required", + }); + } else { + setErrors({ + ...errors, + description: "", + }); + } + monitorCriteriaInstance.setDescription(value); + setMonitorCriteriaInstance( + MonitorCriteriaInstance.clone(monitorCriteriaInstance), + ); + }} + placeholder="This criteria checks if the monitor is online." + /> + </div> + <div className="mt-4"> + <FieldLabelElement + title="Filter Condition" + description="Select All if you want all the criteria to be met. Select any if you like any criteria to be met." + required={true} + /> + <Radio + value={filterConditionOptions.find((i: DropdownOption) => { + return ( + i.value === + (monitorCriteriaInstance?.data?.filterCondition || + FilterCondition.All) + ); + })} + options={filterConditionOptions} + onChange={(value: DropdownValue | Array<DropdownValue> | null) => { + monitorCriteriaInstance.setFilterCondition( + value as FilterCondition, + ); + setMonitorCriteriaInstance( + MonitorCriteriaInstance.clone(monitorCriteriaInstance), + ); + }} + /> + </div> + <div className="mt-4"> + <FieldLabelElement + title="Filters" + required={true} + description="Add criteria for different monitor properties." + /> - return ( + <CriteriaFilters + monitorType={props.monitorType} + initialValue={monitorCriteriaInstance?.data?.filters || []} + onChange={(value: Array<CriteriaFilter>) => { + monitorCriteriaInstance.setFilters(value); + setMonitorCriteriaInstance( + MonitorCriteriaInstance.clone(monitorCriteriaInstance), + ); + }} + /> + </div> + + <div className="mt-4"> + <Toggle + value={Boolean(showMonitorStatusChangeControl)} + title="When filters match, change monitor status." + onChange={(value: boolean) => { + setShowMonitorStatusChangeControl(value); + monitorCriteriaInstance.setChangeMonitorStatus(value); + + if (!value) { + monitorCriteriaInstance.setMonitorStatusId(undefined); + } + + setMonitorCriteriaInstance( + MonitorCriteriaInstance.clone(monitorCriteriaInstance), + ); + }} + /> + </div> + + {showMonitorStatusChangeControl && ( <div className="mt-4"> - <div className="mt-5"> - <FieldLabelElement - title={'Criteria Name'} - description={ - 'Any friendly name for this criteria, that will help you remember later.' - } - required={true} - /> - <Input - value={ - monitorCriteriaInstance?.data?.name?.toString() || '' - } - onBlur={() => { - setTouched({ - ...touched, - name: true, - }); - - if (!monitorCriteriaInstance?.data?.name) { - setErrors({ - ...errors, - name: 'Name is required', - }); - } else { - setErrors({ - ...errors, - name: '', - }); - } - }} - error={ - touched['name'] && errors['name'] - ? errors['name'] - : undefined - } - placeholder="Online Criteria" - onChange={(value: string) => { - if (!value) { - setErrors({ - ...errors, - name: 'Name is required', - }); - } else { - setErrors({ - ...errors, - name: '', - }); - } - - monitorCriteriaInstance.setName(value); - setMonitorCriteriaInstance( - MonitorCriteriaInstance.clone( - monitorCriteriaInstance - ) - ); - }} - /> - </div> - <div className="mt-5"> - <FieldLabelElement - title={'Criteria Description'} - description={ - 'Any friendly description for this criteria, that will help you remember later.' - } - required={true} - /> - <TextArea - value={ - monitorCriteriaInstance?.data?.description?.toString() || - '' - } - onBlur={() => { - setTouched({ - ...touched, - description: true, - }); - - if (!monitorCriteriaInstance?.data?.description) { - setErrors({ - ...errors, - description: 'Description is required', - }); - } else { - setErrors({ - ...errors, - description: '', - }); - } - }} - error={ - touched['description'] && errors['description'] - ? errors['description'] - : undefined - } - onChange={(value: string) => { - if (!value) { - setErrors({ - ...errors, - description: 'Description is required', - }); - } else { - setErrors({ - ...errors, - description: '', - }); - } - monitorCriteriaInstance.setDescription(value); - setMonitorCriteriaInstance( - MonitorCriteriaInstance.clone( - monitorCriteriaInstance - ) - ); - }} - placeholder="This criteria checks if the monitor is online." - /> - </div> - <div className="mt-4"> - <FieldLabelElement - title="Filter Condition" - description="Select All if you want all the criteria to be met. Select any if you like any criteria to be met." - required={true} - /> - <Radio - value={filterConditionOptions.find((i: DropdownOption) => { - return ( - i.value === - (monitorCriteriaInstance?.data?.filterCondition || - FilterCondition.All) - ); - })} - options={filterConditionOptions} - onChange={( - value: DropdownValue | Array<DropdownValue> | null - ) => { - monitorCriteriaInstance.setFilterCondition( - value as FilterCondition - ); - setMonitorCriteriaInstance( - MonitorCriteriaInstance.clone( - monitorCriteriaInstance - ) - ); - }} - /> - </div> - <div className="mt-4"> - <FieldLabelElement - title="Filters" - required={true} - description="Add criteria for different monitor properties." - /> - - <CriteriaFilters - monitorType={props.monitorType} - initialValue={monitorCriteriaInstance?.data?.filters || []} - onChange={(value: Array<CriteriaFilter>) => { - monitorCriteriaInstance.setFilters(value); - setMonitorCriteriaInstance( - MonitorCriteriaInstance.clone( - monitorCriteriaInstance - ) - ); - }} - /> - </div> - - <div className="mt-4"> - <Toggle - value={Boolean(showMonitorStatusChangeControl)} - title="When filters match, change monitor status." - onChange={(value: boolean) => { - setShowMonitorStatusChangeControl(value); - monitorCriteriaInstance.setChangeMonitorStatus(value); - - if (!value) { - monitorCriteriaInstance.setMonitorStatusId( - undefined - ); - } - - setMonitorCriteriaInstance( - MonitorCriteriaInstance.clone( - monitorCriteriaInstance - ) - ); - }} - /> - </div> - - {showMonitorStatusChangeControl && ( - <div className="mt-4"> - <FieldLabelElement - title="Change monitor status to" - description="What would you like the monitor status to be when the criteria have been met?" - /> - <Dropdown - value={props.monitorStatusDropdownOptions.find( - (i: DropdownOption) => { - return ( - i.value === - monitorCriteriaInstance?.data - ?.monitorStatusId?.id || undefined - ); - } - )} - options={props.monitorStatusDropdownOptions} - onChange={( - value: DropdownValue | Array<DropdownValue> | null - ) => { - monitorCriteriaInstance.setMonitorStatusId( - value - ? new ObjectID(value.toString()) - : undefined - ); - setMonitorCriteriaInstance( - MonitorCriteriaInstance.clone( - monitorCriteriaInstance - ) - ); - }} - /> - </div> + <FieldLabelElement + title="Change monitor status to" + description="What would you like the monitor status to be when the criteria have been met?" + /> + <Dropdown + value={props.monitorStatusDropdownOptions.find( + (i: DropdownOption) => { + return ( + i.value === + monitorCriteriaInstance?.data?.monitorStatusId?.id || + undefined + ); + }, )} - - <div className="mt-4"> - <Toggle - value={showIncidentControl} - title="When filters match, create an incident." - onChange={(value: boolean) => { - setShowIncidentControl(value); - monitorCriteriaInstance.setCreateIncidents(value); - - if ( - (value && - !monitorCriteriaInstance.data?.incidents) || - monitorCriteriaInstance.data?.incidents?.length === - 0 - ) { - monitorCriteriaInstance.setIncidents([ - { - title: '', - description: '', - incidentSeverityId: undefined, - id: ObjectID.generate().toString(), - }, - ]); - } - if (!value) { - monitorCriteriaInstance.setIncidents([]); - } - - setMonitorCriteriaInstance( - MonitorCriteriaInstance.clone( - monitorCriteriaInstance - ) - ); - }} - /> - </div> - - {showIncidentControl && ( - <div className="mt-4"> - <FieldLabelElement title="Create Incident" /> - - <MonitorCriteriaIncidentsForm - initialValue={ - monitorCriteriaInstance?.data?.incidents || [] - } - incidentSeverityDropdownOptions={ - props.incidentSeverityDropdownOptions - } - onCallPolicyDropdownOptions={ - props.onCallPolicyDropdownOptions - } - onChange={(value: Array<CriteriaIncident>) => { - monitorCriteriaInstance.setIncidents(value); - setMonitorCriteriaInstance( - MonitorCriteriaInstance.clone( - monitorCriteriaInstance - ) - ); - }} - /> - </div> - )} - - <div className="mt-4 -ml-3"> - <Button - onClick={() => { - if (props.onDelete) { - props.onDelete(); - } - }} - buttonSize={ButtonSize.Small} - buttonStyle={ButtonStyleType.DANGER_OUTLINE} - icon={IconProp.Trash} - title="Delete Criteria" - /> - </div> - - <HorizontalRule /> + options={props.monitorStatusDropdownOptions} + onChange={(value: DropdownValue | Array<DropdownValue> | null) => { + monitorCriteriaInstance.setMonitorStatusId( + value ? new ObjectID(value.toString()) : undefined, + ); + setMonitorCriteriaInstance( + MonitorCriteriaInstance.clone(monitorCriteriaInstance), + ); + }} + /> </div> - ); + )} + + <div className="mt-4"> + <Toggle + value={showIncidentControl} + title="When filters match, create an incident." + onChange={(value: boolean) => { + setShowIncidentControl(value); + monitorCriteriaInstance.setCreateIncidents(value); + + if ( + (value && !monitorCriteriaInstance.data?.incidents) || + monitorCriteriaInstance.data?.incidents?.length === 0 + ) { + monitorCriteriaInstance.setIncidents([ + { + title: "", + description: "", + incidentSeverityId: undefined, + id: ObjectID.generate().toString(), + }, + ]); + } + if (!value) { + monitorCriteriaInstance.setIncidents([]); + } + + setMonitorCriteriaInstance( + MonitorCriteriaInstance.clone(monitorCriteriaInstance), + ); + }} + /> + </div> + + {showIncidentControl && ( + <div className="mt-4"> + <FieldLabelElement title="Create Incident" /> + + <MonitorCriteriaIncidentsForm + initialValue={monitorCriteriaInstance?.data?.incidents || []} + incidentSeverityDropdownOptions={ + props.incidentSeverityDropdownOptions + } + onCallPolicyDropdownOptions={props.onCallPolicyDropdownOptions} + onChange={(value: Array<CriteriaIncident>) => { + monitorCriteriaInstance.setIncidents(value); + setMonitorCriteriaInstance( + MonitorCriteriaInstance.clone(monitorCriteriaInstance), + ); + }} + /> + </div> + )} + + <div className="mt-4 -ml-3"> + <Button + onClick={() => { + if (props.onDelete) { + props.onDelete(); + } + }} + buttonSize={ButtonSize.Small} + buttonStyle={ButtonStyleType.DANGER_OUTLINE} + icon={IconProp.Trash} + title="Delete Criteria" + /> + </div> + + <HorizontalRule /> + </div> + ); }; export default MonitorCriteriaInstanceElement; diff --git a/Dashboard/src/Components/Form/Monitor/MonitorStep.tsx b/Dashboard/src/Components/Form/Monitor/MonitorStep.tsx index de86029236..14b514c369 100644 --- a/Dashboard/src/Components/Form/Monitor/MonitorStep.tsx +++ b/Dashboard/src/Components/Form/Monitor/MonitorStep.tsx @@ -1,88 +1,88 @@ -import MonitorCriteriaElement from './MonitorCriteria'; -import HTTPMethod from 'Common/Types/API/HTTPMethod'; -import Hostname from 'Common/Types/API/Hostname'; -import URL from 'Common/Types/API/URL'; -import CodeType from 'Common/Types/Code/CodeType'; -import Dictionary from 'Common/Types/Dictionary'; -import Exception from 'Common/Types/Exception/Exception'; -import IP from 'Common/Types/IP/IP'; -import MonitorCriteria from 'Common/Types/Monitor/MonitorCriteria'; -import MonitorStep from 'Common/Types/Monitor/MonitorStep'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import BrowserType from 'Common/Types/Monitor/SyntheticMonitors/BrowserType'; -import Port from 'Common/Types/Port'; -import ScreenSizeType from 'Common/Types/ScreenSizeType'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; +import MonitorCriteriaElement from "./MonitorCriteria"; +import HTTPMethod from "Common/Types/API/HTTPMethod"; +import Hostname from "Common/Types/API/Hostname"; +import URL from "Common/Types/API/URL"; +import CodeType from "Common/Types/Code/CodeType"; +import Dictionary from "Common/Types/Dictionary"; +import Exception from "Common/Types/Exception/Exception"; +import IP from "Common/Types/IP/IP"; +import MonitorCriteria from "Common/Types/Monitor/MonitorCriteria"; +import MonitorStep from "Common/Types/Monitor/MonitorStep"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import BrowserType from "Common/Types/Monitor/SyntheticMonitors/BrowserType"; +import Port from "Common/Types/Port"; +import ScreenSizeType from "Common/Types/ScreenSizeType"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; import CheckBoxList, { - CategoryCheckboxValue, - enumToCategoryCheckboxOption, -} from 'CommonUI/src/Components/CategoryCheckbox/CheckboxList'; -import CodeEditor from 'CommonUI/src/Components/CodeEditor/CodeEditor'; -import DictionaryOfStrings from 'CommonUI/src/Components/Dictionary/DictionaryOfStrings'; + CategoryCheckboxValue, + enumToCategoryCheckboxOption, +} from "CommonUI/src/Components/CategoryCheckbox/CheckboxList"; +import CodeEditor from "CommonUI/src/Components/CodeEditor/CodeEditor"; +import DictionaryOfStrings from "CommonUI/src/Components/Dictionary/DictionaryOfStrings"; import Dropdown, { - DropdownOption, - DropdownValue, -} from 'CommonUI/src/Components/Dropdown/Dropdown'; -import FieldLabelElement from 'CommonUI/src/Components/Forms/Fields/FieldLabel'; -import HorizontalRule from 'CommonUI/src/Components/HorizontalRule/HorizontalRule'; -import Input from 'CommonUI/src/Components/Input/Input'; -import Link from 'CommonUI/src/Components/Link/Link'; -import { DOCS_URL } from 'CommonUI/src/Config'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; + DropdownOption, + DropdownValue, +} from "CommonUI/src/Components/Dropdown/Dropdown"; +import FieldLabelElement from "CommonUI/src/Components/Forms/Fields/FieldLabel"; +import HorizontalRule from "CommonUI/src/Components/HorizontalRule/HorizontalRule"; +import Input from "CommonUI/src/Components/Input/Input"; +import Link from "CommonUI/src/Components/Link/Link"; +import { DOCS_URL } from "CommonUI/src/Config"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - monitorStatusDropdownOptions: Array<DropdownOption>; - incidentSeverityDropdownOptions: Array<DropdownOption>; - onCallPolicyDropdownOptions: Array<DropdownOption>; - initialValue?: undefined | MonitorStep; - onChange?: undefined | ((value: MonitorStep) => void); - // onDelete?: undefined | (() => void); - monitorType: MonitorType; + monitorStatusDropdownOptions: Array<DropdownOption>; + incidentSeverityDropdownOptions: Array<DropdownOption>; + onCallPolicyDropdownOptions: Array<DropdownOption>; + initialValue?: undefined | MonitorStep; + onChange?: undefined | ((value: MonitorStep) => void); + // onDelete?: undefined | (() => void); + monitorType: MonitorType; } const MonitorStepElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [ - showAdvancedOptionsRequestBodyAndHeaders, - setShowAdvancedOptionsRequestBodyAndHeaders, - ] = useState<boolean>(false); + const [ + showAdvancedOptionsRequestBodyAndHeaders, + setShowAdvancedOptionsRequestBodyAndHeaders, + ] = useState<boolean>(false); - const [monitorStep, setMonitorStep] = useState<MonitorStep>( - props.initialValue || new MonitorStep() - ); + const [monitorStep, setMonitorStep] = useState<MonitorStep>( + props.initialValue || new MonitorStep(), + ); - useEffect(() => { - if (props.onChange && monitorStep) { - props.onChange(monitorStep); - } - }, [monitorStep]); + useEffect(() => { + if (props.onChange && monitorStep) { + props.onChange(monitorStep); + } + }, [monitorStep]); - const [errors, setErrors] = useState<Dictionary<string>>({}); - const [touched, setTouched] = useState<Dictionary<boolean>>({}); + const [errors, setErrors] = useState<Dictionary<string>>({}); + const [touched, setTouched] = useState<Dictionary<boolean>>({}); - const [destinationFieldTitle, setDestinationFieldTitle] = - useState<string>('URL'); - const [destinationFieldDescription, setDestinationFieldDescription] = - useState<string>(''); + const [destinationFieldTitle, setDestinationFieldTitle] = + useState<string>("URL"); + const [destinationFieldDescription, setDestinationFieldDescription] = + useState<string>(""); - const requestTypeDropdownOptions: Array<DropdownOption> = - DropdownUtil.getDropdownOptionsFromEnum(HTTPMethod); + const requestTypeDropdownOptions: Array<DropdownOption> = + DropdownUtil.getDropdownOptionsFromEnum(HTTPMethod); - const [destinationInputValue, setDestinationInputValue] = useState<string>( - props.initialValue?.data?.monitorDestination?.toString() || '' - ); + const [destinationInputValue, setDestinationInputValue] = useState<string>( + props.initialValue?.data?.monitorDestination?.toString() || "", + ); - let codeEditorPlaceholder: string = ''; + let codeEditorPlaceholder: string = ""; - if (props.monitorType === MonitorType.CustomJavaScriptCode) { - codeEditorPlaceholder = ` + if (props.monitorType === MonitorType.CustomJavaScriptCode) { + codeEditorPlaceholder = ` // You can use axios, http modules here. await axios.get('https://example.com'); @@ -91,10 +91,10 @@ const MonitorStepElement: FunctionComponent<ComponentProps> = ( return { data: 'Hello World' };`; - } + } - if (props.monitorType === MonitorType.SyntheticMonitor) { - codeEditorPlaceholder = ` + if (props.monitorType === MonitorType.SyntheticMonitor) { + codeEditorPlaceholder = ` // You can use axios module, and page object from Playwright here. // Page Object is a class that represents a single page in a browser. @@ -115,485 +115,415 @@ const MonitorStepElement: FunctionComponent<ComponentProps> = ( data: 'Hello World', screenshots: screenshots // obj containing screenshots };`; + } + + useEffect(() => { + if (props.monitorType === MonitorType.API) { + setDestinationFieldTitle("API URL"); + setDestinationFieldDescription( + "Whats the URL of the API you want to monitor?", + ); + } else if (props.monitorType === MonitorType.Website) { + setDestinationFieldTitle("Website URL"); + setDestinationFieldDescription( + "Whats the URL of the website you want to monitor?", + ); + } else if (props.monitorType === MonitorType.Ping) { + setDestinationFieldTitle("Ping Hostname or IP address"); + setDestinationFieldDescription( + "Whats the Hostname or IP address of the resource you want to ping?", + ); + } else if (props.monitorType === MonitorType.IP) { + setDestinationFieldTitle("IP Address"); + setDestinationFieldDescription( + "Whats the IP address you want to monitor?", + ); + } else if (props.monitorType === MonitorType.Port) { + setDestinationFieldTitle("Hostname or IP address"); + setDestinationFieldDescription( + "Whats the Hostname or IP address of the resource you want to ping?", + ); } + }, [props.monitorType]); - useEffect(() => { - if (props.monitorType === MonitorType.API) { - setDestinationFieldTitle('API URL'); - setDestinationFieldDescription( - 'Whats the URL of the API you want to monitor?' - ); - } else if (props.monitorType === MonitorType.Website) { - setDestinationFieldTitle('Website URL'); - setDestinationFieldDescription( - 'Whats the URL of the website you want to monitor?' - ); - } else if (props.monitorType === MonitorType.Ping) { - setDestinationFieldTitle('Ping Hostname or IP address'); - setDestinationFieldDescription( - 'Whats the Hostname or IP address of the resource you want to ping?' - ); - } else if (props.monitorType === MonitorType.IP) { - setDestinationFieldTitle('IP Address'); - setDestinationFieldDescription( - 'Whats the IP address you want to monitor?' - ); - } else if (props.monitorType === MonitorType.Port) { - setDestinationFieldTitle('Hostname or IP address'); - setDestinationFieldDescription( - 'Whats the Hostname or IP address of the resource you want to ping?' - ); - } - }, [props.monitorType]); + const hasMonitorDestination: boolean = + props.monitorType === MonitorType.IP || + props.monitorType === MonitorType.Ping || + props.monitorType === MonitorType.Port || + props.monitorType === MonitorType.Website || + props.monitorType === MonitorType.API || + props.monitorType === MonitorType.SSLCertificate; - const hasMonitorDestination: boolean = - props.monitorType === MonitorType.IP || - props.monitorType === MonitorType.Ping || - props.monitorType === MonitorType.Port || - props.monitorType === MonitorType.Website || - props.monitorType === MonitorType.API || - props.monitorType === MonitorType.SSLCertificate; + const isCodeMonitor: boolean = + props.monitorType === MonitorType.CustomJavaScriptCode || + props.monitorType === MonitorType.SyntheticMonitor; - const isCodeMonitor: boolean = - props.monitorType === MonitorType.CustomJavaScriptCode || - props.monitorType === MonitorType.SyntheticMonitor; + return ( + <div className="mt-5"> + {hasMonitorDestination && ( + <div> + <div className="mt-5"> + <FieldLabelElement + title={destinationFieldTitle} + description={destinationFieldDescription} + required={true} + /> + <Input + initialValue={destinationInputValue} + onBlur={() => { + setTouched({ + ...touched, + destination: true, + }); - return ( - <div className="mt-5"> - {hasMonitorDestination && ( - <div> - <div className="mt-5"> - <FieldLabelElement - title={destinationFieldTitle} - description={destinationFieldDescription} - required={true} - /> - <Input - initialValue={destinationInputValue} - onBlur={() => { - setTouched({ - ...touched, - destination: true, - }); + if (!monitorStep?.data?.monitorDestination?.toString()) { + setErrors({ + ...errors, + destination: "Destination is required", + }); + } else { + setErrors({ + ...errors, + destination: "", + }); + setDestinationInputValue( + monitorStep?.data?.monitorDestination?.toString(), + ); + } + }} + error={ + touched["destination"] && errors["destination"] + ? errors["destination"] + : undefined + } + onChange={(value: string) => { + let destination: IP | URL | Hostname | undefined = undefined; - if ( - !monitorStep?.data?.monitorDestination?.toString() - ) { - setErrors({ - ...errors, - destination: 'Destination is required', - }); - } else { - setErrors({ - ...errors, - destination: '', - }); - setDestinationInputValue( - monitorStep?.data?.monitorDestination?.toString() - ); - } - }} - error={ - touched['destination'] && errors['destination'] - ? errors['destination'] - : undefined - } - onChange={(value: string) => { - let destination: - | IP - | URL - | Hostname - | undefined = undefined; + try { + if (props.monitorType === MonitorType.IP) { + destination = IP.fromString(value); + } else if (props.monitorType === MonitorType.Ping) { + if (IP.isIP(value)) { + destination = IP.fromString(value); + } else { + destination = Hostname.fromString(value); + } + } else if (props.monitorType === MonitorType.Port) { + if (IP.isIP(value)) { + destination = IP.fromString(value); + } else { + destination = Hostname.fromString(value); + } + } else if (props.monitorType === MonitorType.Website) { + destination = URL.fromString(value); + } else if (props.monitorType === MonitorType.API) { + destination = URL.fromString(value); + } else if (props.monitorType === MonitorType.SSLCertificate) { + destination = URL.fromString(value); + } - try { - if (props.monitorType === MonitorType.IP) { - destination = IP.fromString(value); - } else if ( - props.monitorType === MonitorType.Ping - ) { - if (IP.isIP(value)) { - destination = IP.fromString(value); - } else { - destination = - Hostname.fromString(value); - } - } else if ( - props.monitorType === MonitorType.Port - ) { - if (IP.isIP(value)) { - destination = IP.fromString(value); - } else { - destination = - Hostname.fromString(value); - } - } else if ( - props.monitorType === - MonitorType.Website - ) { - destination = URL.fromString(value); - } else if ( - props.monitorType === MonitorType.API - ) { - destination = URL.fromString(value); - } else if ( - props.monitorType === - MonitorType.SSLCertificate - ) { - destination = URL.fromString(value); - } + setErrors({ + ...errors, + destination: "", + }); + } catch (err) { + if (err instanceof Exception) { + setErrors({ + ...errors, + destination: err.message, + }); + } else { + setErrors({ + ...errors, + destination: "Invalid Destination", + }); + } + } - setErrors({ - ...errors, - destination: '', - }); - } catch (err) { - if (err instanceof Exception) { - setErrors({ - ...errors, - destination: err.message, - }); - } else { - setErrors({ - ...errors, - destination: 'Invalid Destination', - }); - } - } - - if (destination) { - monitorStep.setMonitorDestination( - destination - ); - } - - setDestinationInputValue(value); - setMonitorStep(MonitorStep.clone(monitorStep)); - }} - /> - </div> - {props.monitorType === MonitorType.Port && ( - <div className="mt-5"> - <FieldLabelElement - title={'Port'} - description={ - 'Whats the port you want to monitor?' - } - required={true} - /> - <Input - initialValue={monitorStep?.data?.monitorDestinationPort?.toString()} - onChange={(value: string) => { - const port: Port = new Port(value); - monitorStep.setPort(port); - setMonitorStep( - MonitorStep.clone(monitorStep) - ); - }} - /> - </div> - )} - - {props.monitorType === MonitorType.API && ( - <div className="mt-5"> - <FieldLabelElement - title={'API Request Type'} - description={ - 'What is the type of the API request?' - } - required={true} - /> - <Dropdown - initialValue={requestTypeDropdownOptions.find( - (i: DropdownOption) => { - return ( - i.value === - (monitorStep?.data?.requestType || - HTTPMethod.GET) - ); - } - )} - options={requestTypeDropdownOptions} - onChange={( - value: - | DropdownValue - | Array<DropdownValue> - | null - ) => { - monitorStep.setRequestType( - (value?.toString() as HTTPMethod) || - HTTPMethod.GET - ); - setMonitorStep( - MonitorStep.clone(monitorStep) - ); - }} - /> - </div> - )} - - {!showAdvancedOptionsRequestBodyAndHeaders && - props.monitorType === MonitorType.API && ( - <div className="mt-1 -ml-3"> - <Button - title="Advanced: Add Request Headers and Body" - buttonStyle={ButtonStyleType.SECONDARY_LINK} - onClick={() => { - setShowAdvancedOptionsRequestBodyAndHeaders( - true - ); - }} - /> - </div> - )} - {showAdvancedOptionsRequestBodyAndHeaders && - props.monitorType === MonitorType.API && ( - <div className="mt-5"> - <FieldLabelElement - title={'Request Headers'} - description={ - <p> - Request Headers to send.{' '} - <Link - className="underline" - openInNewTab={true} - to={URL.fromString( - DOCS_URL.toString() + - '/monitor/monitor-secrets' - )} - > - You can use secrets here. - </Link> - </p> - } - required={false} - /> - <DictionaryOfStrings - addButtonSuffix="Request Header" - keyPlaceholder={'Header Name'} - valuePlaceholder={'Header Value'} - initialValue={ - monitorStep.data?.requestHeaders || {} - } - onChange={(value: Dictionary<string>) => { - monitorStep.setRequestHeaders(value); - setMonitorStep( - MonitorStep.clone(monitorStep) - ); - }} - /> - </div> - )} - - {showAdvancedOptionsRequestBodyAndHeaders && - props.monitorType === MonitorType.API && ( - <div className="mt-5"> - <FieldLabelElement - title={'Request Body (in JSON)'} - description={ - <p> - Request Headers to send in JSON.{' '} - <Link - className="underline" - openInNewTab={true} - to={URL.fromString( - DOCS_URL.toString() + - '/monitor/monitor-secrets' - )} - > - You can use secrets here. - </Link> - </p> - } - required={false} - /> - <CodeEditor - type={CodeType.JSON} - onBlur={() => { - setTouched({ - ...touched, - requestBody: true, - }); - }} - error={ - touched['requestBody'] && - errors['requestBody'] - ? errors['requestBody'] - : undefined - } - initialValue={monitorStep.data?.requestBody} - onChange={(value: string) => { - try { - JSON.parse(value); - setErrors({ - ...errors, - requestBody: '', - }); - } catch (err) { - setErrors({ - ...errors, - requestBody: 'Invalid JSON', - }); - } - - monitorStep.setRequestBody(value); - setMonitorStep( - MonitorStep.clone(monitorStep) - ); - }} - /> - </div> - )} - - <HorizontalRule /> - </div> - )} - - {isCodeMonitor && ( - <div className="mt-5"> - <FieldLabelElement - title={ - props.monitorType === - MonitorType.CustomJavaScriptCode - ? 'JavaScript Code' - : 'Playwright Code' - } - description={ - props.monitorType === - MonitorType.CustomJavaScriptCode ? ( - <p> - Write your JavaScript code here.{' '} - <Link - className="underline" - openInNewTab={true} - to={URL.fromString( - DOCS_URL.toString() + - '/monitor/monitor-secrets' - )} - > - You can use secrets here. - </Link> - </p> - ) : ( - <p> - Write your Playwright code here. Playwright - is a Node.js library to automate Chromium, - Firefox and WebKit with a single API.{' '} - <Link - className="underline" - openInNewTab={true} - to={URL.fromString( - DOCS_URL.toString() + - '/monitor/monitor-secrets' - )} - > - You can use secrets here. - </Link> - </p> - ) - } - required={true} - /> - <div className="mt-1"> - <CodeEditor - initialValue={monitorStep?.data?.customCode?.toString()} - type={CodeType.JavaScript} - onChange={(value: string) => { - monitorStep.setCustomCode(value); - setMonitorStep(MonitorStep.clone(monitorStep)); - }} - placeholder={codeEditorPlaceholder} - /> - </div> - </div> - )} - - {props.monitorType === MonitorType.SyntheticMonitor && ( - <div className="mt-5"> - <FieldLabelElement - title={'Browser Type'} - description={'Select the browser type.'} - required={true} - /> - <div className="mt-1"> - <CheckBoxList - options={enumToCategoryCheckboxOption(BrowserType)} - initialValue={ - props.initialValue?.data?.browserTypes || [] - } - onChange={( - values: Array<CategoryCheckboxValue> - ) => { - monitorStep.setBrowserTypes( - values as Array<BrowserType> - ); - setMonitorStep(MonitorStep.clone(monitorStep)); - }} - /> - </div> - </div> - )} - - {props.monitorType === MonitorType.SyntheticMonitor && ( - <div className="mt-5"> - <FieldLabelElement - title={'Screen Type'} - description={ - 'Which screen type should we use to run this test?' - } - required={true} - /> - <div className="mt-1"> - <CheckBoxList - options={enumToCategoryCheckboxOption( - ScreenSizeType - )} - initialValue={ - props.initialValue?.data?.screenSizeTypes || [] - } - onChange={( - values: Array<CategoryCheckboxValue> - ) => { - monitorStep.setScreenSizeTypes( - values as Array<ScreenSizeType> - ); - setMonitorStep(MonitorStep.clone(monitorStep)); - }} - /> - </div> - </div> - )} + if (destination) { + monitorStep.setMonitorDestination(destination); + } + setDestinationInputValue(value); + setMonitorStep(MonitorStep.clone(monitorStep)); + }} + /> + </div> + {props.monitorType === MonitorType.Port && ( <div className="mt-5"> - {props.monitorType !== MonitorType.IncomingRequest && ( - <FieldLabelElement - title="Monitor Criteria" - isHeading={true} - description={ - 'Add Monitoring Criteria for this monitor. Monitor different properties.' - } - required={true} - /> - )} - <MonitorCriteriaElement - monitorType={props.monitorType} - monitorStatusDropdownOptions={ - props.monitorStatusDropdownOptions - } - incidentSeverityDropdownOptions={ - props.incidentSeverityDropdownOptions - } - onCallPolicyDropdownOptions={ - props.onCallPolicyDropdownOptions - } - initialValue={monitorStep?.data?.monitorCriteria} - onChange={(value: MonitorCriteria) => { - monitorStep.setMonitorCriteria(value); - setMonitorStep(MonitorStep.clone(monitorStep)); - }} - /> + <FieldLabelElement + title={"Port"} + description={"Whats the port you want to monitor?"} + required={true} + /> + <Input + initialValue={monitorStep?.data?.monitorDestinationPort?.toString()} + onChange={(value: string) => { + const port: Port = new Port(value); + monitorStep.setPort(port); + setMonitorStep(MonitorStep.clone(monitorStep)); + }} + /> </div> + )} - {/* <div className='mt-5 -ml-3'> + {props.monitorType === MonitorType.API && ( + <div className="mt-5"> + <FieldLabelElement + title={"API Request Type"} + description={"What is the type of the API request?"} + required={true} + /> + <Dropdown + initialValue={requestTypeDropdownOptions.find( + (i: DropdownOption) => { + return ( + i.value === + (monitorStep?.data?.requestType || HTTPMethod.GET) + ); + }, + )} + options={requestTypeDropdownOptions} + onChange={( + value: DropdownValue | Array<DropdownValue> | null, + ) => { + monitorStep.setRequestType( + (value?.toString() as HTTPMethod) || HTTPMethod.GET, + ); + setMonitorStep(MonitorStep.clone(monitorStep)); + }} + /> + </div> + )} + + {!showAdvancedOptionsRequestBodyAndHeaders && + props.monitorType === MonitorType.API && ( + <div className="mt-1 -ml-3"> + <Button + title="Advanced: Add Request Headers and Body" + buttonStyle={ButtonStyleType.SECONDARY_LINK} + onClick={() => { + setShowAdvancedOptionsRequestBodyAndHeaders(true); + }} + /> + </div> + )} + {showAdvancedOptionsRequestBodyAndHeaders && + props.monitorType === MonitorType.API && ( + <div className="mt-5"> + <FieldLabelElement + title={"Request Headers"} + description={ + <p> + Request Headers to send.{" "} + <Link + className="underline" + openInNewTab={true} + to={URL.fromString( + DOCS_URL.toString() + "/monitor/monitor-secrets", + )} + > + You can use secrets here. + </Link> + </p> + } + required={false} + /> + <DictionaryOfStrings + addButtonSuffix="Request Header" + keyPlaceholder={"Header Name"} + valuePlaceholder={"Header Value"} + initialValue={monitorStep.data?.requestHeaders || {}} + onChange={(value: Dictionary<string>) => { + monitorStep.setRequestHeaders(value); + setMonitorStep(MonitorStep.clone(monitorStep)); + }} + /> + </div> + )} + + {showAdvancedOptionsRequestBodyAndHeaders && + props.monitorType === MonitorType.API && ( + <div className="mt-5"> + <FieldLabelElement + title={"Request Body (in JSON)"} + description={ + <p> + Request Headers to send in JSON.{" "} + <Link + className="underline" + openInNewTab={true} + to={URL.fromString( + DOCS_URL.toString() + "/monitor/monitor-secrets", + )} + > + You can use secrets here. + </Link> + </p> + } + required={false} + /> + <CodeEditor + type={CodeType.JSON} + onBlur={() => { + setTouched({ + ...touched, + requestBody: true, + }); + }} + error={ + touched["requestBody"] && errors["requestBody"] + ? errors["requestBody"] + : undefined + } + initialValue={monitorStep.data?.requestBody} + onChange={(value: string) => { + try { + JSON.parse(value); + setErrors({ + ...errors, + requestBody: "", + }); + } catch (err) { + setErrors({ + ...errors, + requestBody: "Invalid JSON", + }); + } + + monitorStep.setRequestBody(value); + setMonitorStep(MonitorStep.clone(monitorStep)); + }} + /> + </div> + )} + + <HorizontalRule /> + </div> + )} + + {isCodeMonitor && ( + <div className="mt-5"> + <FieldLabelElement + title={ + props.monitorType === MonitorType.CustomJavaScriptCode + ? "JavaScript Code" + : "Playwright Code" + } + description={ + props.monitorType === MonitorType.CustomJavaScriptCode ? ( + <p> + Write your JavaScript code here.{" "} + <Link + className="underline" + openInNewTab={true} + to={URL.fromString( + DOCS_URL.toString() + "/monitor/monitor-secrets", + )} + > + You can use secrets here. + </Link> + </p> + ) : ( + <p> + Write your Playwright code here. Playwright is a Node.js + library to automate Chromium, Firefox and WebKit with a single + API.{" "} + <Link + className="underline" + openInNewTab={true} + to={URL.fromString( + DOCS_URL.toString() + "/monitor/monitor-secrets", + )} + > + You can use secrets here. + </Link> + </p> + ) + } + required={true} + /> + <div className="mt-1"> + <CodeEditor + initialValue={monitorStep?.data?.customCode?.toString()} + type={CodeType.JavaScript} + onChange={(value: string) => { + monitorStep.setCustomCode(value); + setMonitorStep(MonitorStep.clone(monitorStep)); + }} + placeholder={codeEditorPlaceholder} + /> + </div> + </div> + )} + + {props.monitorType === MonitorType.SyntheticMonitor && ( + <div className="mt-5"> + <FieldLabelElement + title={"Browser Type"} + description={"Select the browser type."} + required={true} + /> + <div className="mt-1"> + <CheckBoxList + options={enumToCategoryCheckboxOption(BrowserType)} + initialValue={props.initialValue?.data?.browserTypes || []} + onChange={(values: Array<CategoryCheckboxValue>) => { + monitorStep.setBrowserTypes(values as Array<BrowserType>); + setMonitorStep(MonitorStep.clone(monitorStep)); + }} + /> + </div> + </div> + )} + + {props.monitorType === MonitorType.SyntheticMonitor && ( + <div className="mt-5"> + <FieldLabelElement + title={"Screen Type"} + description={"Which screen type should we use to run this test?"} + required={true} + /> + <div className="mt-1"> + <CheckBoxList + options={enumToCategoryCheckboxOption(ScreenSizeType)} + initialValue={props.initialValue?.data?.screenSizeTypes || []} + onChange={(values: Array<CategoryCheckboxValue>) => { + monitorStep.setScreenSizeTypes(values as Array<ScreenSizeType>); + setMonitorStep(MonitorStep.clone(monitorStep)); + }} + /> + </div> + </div> + )} + + <div className="mt-5"> + {props.monitorType !== MonitorType.IncomingRequest && ( + <FieldLabelElement + title="Monitor Criteria" + isHeading={true} + description={ + "Add Monitoring Criteria for this monitor. Monitor different properties." + } + required={true} + /> + )} + <MonitorCriteriaElement + monitorType={props.monitorType} + monitorStatusDropdownOptions={props.monitorStatusDropdownOptions} + incidentSeverityDropdownOptions={ + props.incidentSeverityDropdownOptions + } + onCallPolicyDropdownOptions={props.onCallPolicyDropdownOptions} + initialValue={monitorStep?.data?.monitorCriteria} + onChange={(value: MonitorCriteria) => { + monitorStep.setMonitorCriteria(value); + setMonitorStep(MonitorStep.clone(monitorStep)); + }} + /> + </div> + + {/* <div className='mt-5 -ml-3'> <Button onClick={() => { if (props.onDelete) { @@ -605,8 +535,8 @@ const MonitorStepElement: FunctionComponent<ComponentProps> = ( title="Delete Monitor Step" /> </div> */} - </div> - ); + </div> + ); }; export default MonitorStepElement; diff --git a/Dashboard/src/Components/Form/Monitor/MonitorSteps.tsx b/Dashboard/src/Components/Form/Monitor/MonitorSteps.tsx index 83e71ec9ab..fbd0db62ee 100644 --- a/Dashboard/src/Components/Form/Monitor/MonitorSteps.tsx +++ b/Dashboard/src/Components/Form/Monitor/MonitorSteps.tsx @@ -1,266 +1,249 @@ -import MonitorStepElement from './MonitorStep'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import MonitorStep from 'Common/Types/Monitor/MonitorStep'; -import MonitorSteps from 'Common/Types/Monitor/MonitorSteps'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; +import MonitorStepElement from "./MonitorStep"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import MonitorStep from "Common/Types/Monitor/MonitorStep"; +import MonitorSteps from "Common/Types/Monitor/MonitorSteps"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; import Dropdown, { - DropdownOption, - DropdownValue, -} from 'CommonUI/src/Components/Dropdown/Dropdown'; -import FieldLabelElement from 'CommonUI/src/Components/Forms/Fields/FieldLabel'; -import { CustomElementProps } from 'CommonUI/src/Components/Forms/Types/Field'; -import HorizontalRule from 'CommonUI/src/Components/HorizontalRule/HorizontalRule'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; -import useAsyncEffect from 'use-async-effect'; + DropdownOption, + DropdownValue, +} from "CommonUI/src/Components/Dropdown/Dropdown"; +import FieldLabelElement from "CommonUI/src/Components/Forms/Fields/FieldLabel"; +import { CustomElementProps } from "CommonUI/src/Components/Forms/Types/Field"; +import HorizontalRule from "CommonUI/src/Components/HorizontalRule/HorizontalRule"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps extends CustomElementProps { - error?: string | undefined; - onChange?: ((value: MonitorSteps) => void) | undefined; - onBlur?: () => void; - initialValue?: MonitorSteps; - monitorType: MonitorType; - monitorName?: string | undefined; // this is used to prefill incident title and description. If not provided then it will be empty. + error?: string | undefined; + onChange?: ((value: MonitorSteps) => void) | undefined; + onBlur?: () => void; + initialValue?: MonitorSteps; + monitorType: MonitorType; + monitorName?: string | undefined; // this is used to prefill incident title and description. If not provided then it will be empty. } const MonitorStepsElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [monitorStatusDropdownOptions, setMonitorStatusDropdownOptions] = - React.useState<Array<DropdownOption>>([]); + const [monitorStatusDropdownOptions, setMonitorStatusDropdownOptions] = + React.useState<Array<DropdownOption>>([]); - const [ - incidentSeverityDropdownOptions, - setIncidentSeverityDropdownOptions, - ] = React.useState<Array<DropdownOption>>([]); + const [incidentSeverityDropdownOptions, setIncidentSeverityDropdownOptions] = + React.useState<Array<DropdownOption>>([]); - const [onCallPolicyDropdownOptions, setOnCallPolicyDropdownOptions] = - React.useState<Array<DropdownOption>>([]); + const [onCallPolicyDropdownOptions, setOnCallPolicyDropdownOptions] = + React.useState<Array<DropdownOption>>([]); - const [isLoading, setIsLoading] = React.useState<boolean>(false); - const [error, setError] = React.useState<string>(); + const [isLoading, setIsLoading] = React.useState<boolean>(false); + const [error, setError] = React.useState<string>(); - useEffect(() => { - setError(props.error); - }, [props.error]); + useEffect(() => { + setError(props.error); + }, [props.error]); - const fetchDropdownOptions: () => Promise<void> = - async (): Promise<void> => { - setIsLoading(true); + const fetchDropdownOptions: () => Promise<void> = async (): Promise<void> => { + setIsLoading(true); - try { - const monitorStatusList: ListResult<MonitorStatus> = - await ModelAPI.getList({ - modelType: MonitorStatus, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - isOperationalState: true, - isOfflineState: true, - }, + try { + const monitorStatusList: ListResult<MonitorStatus> = + await ModelAPI.getList({ + modelType: MonitorStatus, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + isOperationalState: true, + isOfflineState: true, + }, - sort: {}, - }); + sort: {}, + }); - if (monitorStatusList.data) { - setMonitorStatusDropdownOptions( - monitorStatusList.data.map((i: MonitorStatus) => { - return { - value: i._id!, - label: i.name!, - }; - }) - ); - } + if (monitorStatusList.data) { + setMonitorStatusDropdownOptions( + monitorStatusList.data.map((i: MonitorStatus) => { + return { + value: i._id!, + label: i.name!, + }; + }), + ); + } - const incidentSeverityList: ListResult<IncidentSeverity> = - await ModelAPI.getList({ - modelType: IncidentSeverity, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - order: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); + const incidentSeverityList: ListResult<IncidentSeverity> = + await ModelAPI.getList({ + modelType: IncidentSeverity, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + order: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); - const onCallPolicyList: ListResult<OnCallDutyPolicy> = - await ModelAPI.getList({ - modelType: OnCallDutyPolicy, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - }, - sort: {}, - }); + const onCallPolicyList: ListResult<OnCallDutyPolicy> = + await ModelAPI.getList({ + modelType: OnCallDutyPolicy, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + }, + sort: {}, + }); - if (incidentSeverityList.data) { - setIncidentSeverityDropdownOptions( - incidentSeverityList.data.map((i: IncidentSeverity) => { - return { - value: i._id!, - label: i.name!, - }; - }) - ); - } + if (incidentSeverityList.data) { + setIncidentSeverityDropdownOptions( + incidentSeverityList.data.map((i: IncidentSeverity) => { + return { + value: i._id!, + label: i.name!, + }; + }), + ); + } - if (onCallPolicyList.data) { - setOnCallPolicyDropdownOptions( - onCallPolicyList.data.map((i: OnCallDutyPolicy) => { - return { - value: i._id!, - label: i.name!, - }; - }) - ); - } + if (onCallPolicyList.data) { + setOnCallPolicyDropdownOptions( + onCallPolicyList.data.map((i: OnCallDutyPolicy) => { + return { + value: i._id!, + label: i.name!, + }; + }), + ); + } - // if there is no initial value then.... + // if there is no initial value then.... - if (!monitorSteps) { - setMonitorSteps( - MonitorSteps.getDefaultMonitorSteps({ - monitorType: props.monitorType, - monitorName: props.monitorName || '', - defaultMonitorStatusId: monitorStatusList.data.find( - (i: MonitorStatus) => { - return i.isOperationalState; - } - )!.id!, - onlineMonitorStatusId: monitorStatusList.data.find( - (i: MonitorStatus) => { - return i.isOperationalState; - } - )!.id!, - offlineMonitorStatusId: monitorStatusList.data.find( - (i: MonitorStatus) => { - return i.isOfflineState; - } - )!.id!, - defaultIncidentSeverityId: - incidentSeverityList.data[0]!.id!, - }) - ); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - useAsyncEffect(async () => { - await fetchDropdownOptions(); - }, []); - - const [monitorSteps, setMonitorSteps] = React.useState< - MonitorSteps | undefined - >(props.initialValue); - - useEffect(() => { - if (monitorSteps && props.onChange) { - props.onChange(monitorSteps); - } - - if (props.onBlur) { - props.onBlur(); - } - }, [monitorSteps]); - - if (isLoading) { - return <ComponentLoader></ComponentLoader>; + if (!monitorSteps) { + setMonitorSteps( + MonitorSteps.getDefaultMonitorSteps({ + monitorType: props.monitorType, + monitorName: props.monitorName || "", + defaultMonitorStatusId: monitorStatusList.data.find( + (i: MonitorStatus) => { + return i.isOperationalState; + }, + )!.id!, + onlineMonitorStatusId: monitorStatusList.data.find( + (i: MonitorStatus) => { + return i.isOperationalState; + }, + )!.id!, + offlineMonitorStatusId: monitorStatusList.data.find( + (i: MonitorStatus) => { + return i.isOfflineState; + }, + )!.id!, + defaultIncidentSeverityId: incidentSeverityList.data[0]!.id!, + }), + ); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); } - return ( - <div> - {monitorSteps?.data?.monitorStepsInstanceArray.map( - (i: MonitorStep, index: number) => { - return ( - <MonitorStepElement - monitorType={props.monitorType} - key={index} - monitorStatusDropdownOptions={ - monitorStatusDropdownOptions - } - incidentSeverityDropdownOptions={ - incidentSeverityDropdownOptions - } - onCallPolicyDropdownOptions={ - onCallPolicyDropdownOptions - } - initialValue={i} - // onDelete={() => { - // // remove the criteria filter - // const index: number | undefined = - // monitorSteps.data?.monitorStepsInstanceArray.findIndex((item: MonitorStep) => { - // return item.data?.id === value.data?.id; - // }) + setIsLoading(false); + }; + useAsyncEffect(async () => { + await fetchDropdownOptions(); + }, []); - // if (index === undefined) { - // return; - // } - // const newMonitorSteps: Array<MonitorStep> = [ - // ...(monitorSteps.data - // ?.monitorStepsInstanceArray || []), - // ]; - // newMonitorSteps.splice(index, 1); - // setMonitorSteps( - // new MonitorSteps().fromJSON({ - // _type: 'MonitorSteps', - // value: { - // monitorStepsInstanceArray: - // newMonitorSteps, - // }, - // }) - // ); - // }} - onChange={(value: MonitorStep) => { - const index: number | undefined = - monitorSteps.data?.monitorStepsInstanceArray.findIndex( - (item: MonitorStep) => { - return ( - item.data?.id === value.data?.id - ); - } - ); + const [monitorSteps, setMonitorSteps] = React.useState< + MonitorSteps | undefined + >(props.initialValue); - if (index === undefined) { - return; - } + useEffect(() => { + if (monitorSteps && props.onChange) { + props.onChange(monitorSteps); + } - const newMonitorSteps: Array<MonitorStep> = [ - ...(monitorSteps.data - ?.monitorStepsInstanceArray || []), - ]; - newMonitorSteps[index] = value; - monitorSteps.setMonitorStepsInstanceArray( - newMonitorSteps - ); - setMonitorSteps( - MonitorSteps.clone(monitorSteps) - ); - }} - /> - ); + if (props.onBlur) { + props.onBlur(); + } + }, [monitorSteps]); + + if (isLoading) { + return <ComponentLoader></ComponentLoader>; + } + + return ( + <div> + {monitorSteps?.data?.monitorStepsInstanceArray.map( + (i: MonitorStep, index: number) => { + return ( + <MonitorStepElement + monitorType={props.monitorType} + key={index} + monitorStatusDropdownOptions={monitorStatusDropdownOptions} + incidentSeverityDropdownOptions={incidentSeverityDropdownOptions} + onCallPolicyDropdownOptions={onCallPolicyDropdownOptions} + initialValue={i} + // onDelete={() => { + // // remove the criteria filter + // const index: number | undefined = + // monitorSteps.data?.monitorStepsInstanceArray.findIndex((item: MonitorStep) => { + // return item.data?.id === value.data?.id; + // }) + + // if (index === undefined) { + // return; + // } + // const newMonitorSteps: Array<MonitorStep> = [ + // ...(monitorSteps.data + // ?.monitorStepsInstanceArray || []), + // ]; + // newMonitorSteps.splice(index, 1); + // setMonitorSteps( + // new MonitorSteps().fromJSON({ + // _type: 'MonitorSteps', + // value: { + // monitorStepsInstanceArray: + // newMonitorSteps, + // }, + // }) + // ); + // }} + onChange={(value: MonitorStep) => { + const index: number | undefined = + monitorSteps.data?.monitorStepsInstanceArray.findIndex( + (item: MonitorStep) => { + return item.data?.id === value.data?.id; + }, + ); + + if (index === undefined) { + return; } - )} - {/* <Button + const newMonitorSteps: Array<MonitorStep> = [ + ...(monitorSteps.data?.monitorStepsInstanceArray || []), + ]; + newMonitorSteps[index] = value; + monitorSteps.setMonitorStepsInstanceArray(newMonitorSteps); + setMonitorSteps(MonitorSteps.clone(monitorSteps)); + }} + /> + ); + }, + )} + + {/* <Button title="Add Step" onClick={() => { const newMonitorSteps: Array<MonitorStep> = [ @@ -278,53 +261,44 @@ const MonitorStepsElement: FunctionComponent<ComponentProps> = ( }} /> */} - <HorizontalRule /> + <HorizontalRule /> - <div className="mt-4"> - <FieldLabelElement - title="Default Monitor Status" - description="What should the monitor status be when none of the above criteria is met?" - required={true} - /> + <div className="mt-4"> + <FieldLabelElement + title="Default Monitor Status" + description="What should the monitor status be when none of the above criteria is met?" + required={true} + /> - <Dropdown - value={monitorStatusDropdownOptions.find( - (i: DropdownOption) => { - return ( - i.value === - monitorSteps?.data?.defaultMonitorStatusId?.toString() || - undefined - ); - } - )} - options={monitorStatusDropdownOptions} - onChange={( - value: DropdownValue | Array<DropdownValue> | null - ) => { - monitorSteps?.setDefaultMonitorStatusId( - value ? new ObjectID(value.toString()) : undefined - ); - setMonitorSteps( - MonitorSteps.clone( - monitorSteps || new MonitorSteps() - ) - ); - }} - /> - </div> + <Dropdown + value={monitorStatusDropdownOptions.find((i: DropdownOption) => { + return ( + i.value === + monitorSteps?.data?.defaultMonitorStatusId?.toString() || + undefined + ); + })} + options={monitorStatusDropdownOptions} + onChange={(value: DropdownValue | Array<DropdownValue> | null) => { + monitorSteps?.setDefaultMonitorStatusId( + value ? new ObjectID(value.toString()) : undefined, + ); + setMonitorSteps( + MonitorSteps.clone(monitorSteps || new MonitorSteps()), + ); + }} + /> + </div> - {error ? ( - <p - data-testid="error-message" - className="mt-3 text-sm text-red-400" - > - {error} - </p> - ) : ( - <></> - )} - </div> - ); + {error ? ( + <p data-testid="error-message" className="mt-3 text-sm text-red-400"> + {error} + </p> + ) : ( + <></> + )} + </div> + ); }; export default MonitorStepsElement; diff --git a/Dashboard/src/Components/Header/Header.tsx b/Dashboard/src/Components/Header/Header.tsx index 932431be2b..946cc66d85 100644 --- a/Dashboard/src/Components/Header/Header.tsx +++ b/Dashboard/src/Components/Header/Header.tsx @@ -1,234 +1,224 @@ -import EventName from '../../Utils/EventName'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; +import EventName from "../../Utils/EventName"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; // import SearchBox from './SearchBox'; // import Notifications from './Notifications'; -import Help from './Help'; -import Logo from './Logo'; -import ProjectPicker from './ProjectPicker'; -import Upgrade from './Upgrade'; -import UserProfile from './UserProfile'; -import Route from 'Common/Types/API/Route'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import OneUptimeDate from 'Common/Types/Date'; -import { VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Header from 'CommonUI/src/Components/Header/Header'; -import HeaderAlert from 'CommonUI/src/Components/HeaderAlert/HeaderAlert'; -import HeaderModelAlert from 'CommonUI/src/Components/HeaderAlert/HeaderModelAlert'; -import { SizeProp } from 'CommonUI/src/Components/Icon/Icon'; -import { BILLING_ENABLED, getAllEnvVars } from 'CommonUI/src/Config'; -import GlobalEvents from 'CommonUI/src/Utils/GlobalEvents'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import User from 'CommonUI/src/Utils/User'; -import Incident from 'Model/Models/Incident'; -import Project from 'Model/Models/Project'; -import TeamMember from 'Model/Models/TeamMember'; +import Help from "./Help"; +import Logo from "./Logo"; +import ProjectPicker from "./ProjectPicker"; +import Upgrade from "./Upgrade"; +import UserProfile from "./UserProfile"; +import Route from "Common/Types/API/Route"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import OneUptimeDate from "Common/Types/Date"; +import { VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Header from "CommonUI/src/Components/Header/Header"; +import HeaderAlert from "CommonUI/src/Components/HeaderAlert/HeaderAlert"; +import HeaderModelAlert from "CommonUI/src/Components/HeaderAlert/HeaderModelAlert"; +import { SizeProp } from "CommonUI/src/Components/Icon/Icon"; +import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config"; +import GlobalEvents from "CommonUI/src/Utils/GlobalEvents"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import User from "CommonUI/src/Utils/User"; +import Incident from "Model/Models/Incident"; +import Project from "Model/Models/Project"; +import TeamMember from "Model/Models/TeamMember"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - projects: Array<Project>; - onProjectSelected: (project: Project) => void; - showProjectModal: boolean; - onProjectModalClose: () => void; - selectedProject: Project | null; - paymentMethodsCount?: number | undefined; + projects: Array<Project>; + onProjectSelected: (project: Project) => void; + showProjectModal: boolean; + onProjectModalClose: () => void; + selectedProject: Project | null; + paymentMethodsCount?: number | undefined; } const DashboardHeader: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [activeIncidentToggleRefresh, setActiveIncidentToggleRefresh] = - useState<boolean>(true); + const [activeIncidentToggleRefresh, setActiveIncidentToggleRefresh] = + useState<boolean>(true); - const refreshIncidentCount: VoidFunction = () => { - setActiveIncidentToggleRefresh(!activeIncidentToggleRefresh); + const refreshIncidentCount: VoidFunction = () => { + setActiveIncidentToggleRefresh(!activeIncidentToggleRefresh); + }; + + useEffect(() => { + GlobalEvents.addEventListener( + EventName.ACTIVE_INCIDENTS_COUNT_REFRESH, + refreshIncidentCount, + ); + + return () => { + // on unmount. + GlobalEvents.removeEventListener( + EventName.ACTIVE_INCIDENTS_COUNT_REFRESH, + refreshIncidentCount, + ); }; + }, []); - useEffect(() => { - GlobalEvents.addEventListener( - EventName.ACTIVE_INCIDENTS_COUNT_REFRESH, - refreshIncidentCount - ); + const showAddCardButton: boolean = Boolean( + BILLING_ENABLED && + props.selectedProject?.id && + props.selectedProject.paymentProviderPlanId && + !SubscriptionPlan.isFreePlan( + props.selectedProject.paymentProviderPlanId, + getAllEnvVars(), + ) && + !SubscriptionPlan.isCustomPricingPlan( + props.selectedProject.paymentProviderPlanId, + getAllEnvVars(), + ) && + props.paymentMethodsCount !== undefined && + props.paymentMethodsCount === 0 && + !props.selectedProject.resellerId, + ); - return () => { - // on unmount. - GlobalEvents.removeEventListener( - EventName.ACTIVE_INCIDENTS_COUNT_REFRESH, - refreshIncidentCount - ); - }; - }, []); + const showTrialButton: boolean = Boolean( + props.selectedProject?.trialEndsAt && + BILLING_ENABLED && + showAddCardButton && + OneUptimeDate.getNumberOfDaysBetweenDatesInclusive( + OneUptimeDate.getCurrentDate(), + props.selectedProject?.trialEndsAt, + ) > 0 && + !props.selectedProject.resellerId, + ); - const showAddCardButton: boolean = Boolean( - BILLING_ENABLED && - props.selectedProject?.id && - props.selectedProject.paymentProviderPlanId && - !SubscriptionPlan.isFreePlan( - props.selectedProject.paymentProviderPlanId, - getAllEnvVars() - ) && - !SubscriptionPlan.isCustomPricingPlan( - props.selectedProject.paymentProviderPlanId, - getAllEnvVars() - ) && - props.paymentMethodsCount !== undefined && - props.paymentMethodsCount === 0 && - !props.selectedProject.resellerId - ); + return ( + <> + <Header + leftComponents={ + <> + {props.projects.length === 0 && <Logo onClick={() => {}} />} - const showTrialButton: boolean = Boolean( - props.selectedProject?.trialEndsAt && - BILLING_ENABLED && - showAddCardButton && - OneUptimeDate.getNumberOfDaysBetweenDatesInclusive( - OneUptimeDate.getCurrentDate(), - props.selectedProject?.trialEndsAt - ) > 0 && - !props.selectedProject.resellerId - ); + <ProjectPicker + showProjectModal={props.showProjectModal} + onProjectModalClose={props.onProjectModalClose} + projects={props.projects} + onProjectSelected={props.onProjectSelected} + selectedProject={props.selectedProject} + /> - return ( - <> - <Header - leftComponents={ - <> - {props.projects.length === 0 && ( - <Logo onClick={() => {}} /> - )} + <div className="flex ml-3"> + <HeaderModelAlert<TeamMember> + icon={IconProp.Folder} + className="rounded-md m-3 bg-indigo-500 p-3 hover:bg-indigo-600 cursor-pointer ml-0" + modelType={TeamMember} + query={{ + userId: User.getUserId(), + hasAcceptedInvitation: false, + }} + singularName="Project Invitation" + pluralName="Project Invitations" + requestOptions={{ + isMultiTenantRequest: true, + }} + onClick={() => { + Navigation.navigate(RouteMap[PageMap.PROJECT_INVITATIONS]!); + }} + /> - <ProjectPicker - showProjectModal={props.showProjectModal} - onProjectModalClose={props.onProjectModalClose} - projects={props.projects} - onProjectSelected={props.onProjectSelected} - selectedProject={props.selectedProject} - /> + <HeaderModelAlert<Incident> + icon={IconProp.Alert} + modelType={Incident} + className="rounded-md m-3 bg-red-500 p-3 hover:bg-red-600 cursor-pointer ml-0" + query={{ + currentIncidentState: { + order: 1, + }, + }} + refreshToggle={activeIncidentToggleRefresh} + singularName="New Incident" + pluralName="New Incidents" + requestOptions={{ + isMultiTenantRequest: true, + }} + onClick={() => { + Navigation.navigate(RouteMap[PageMap.NEW_INCIDENTS]!); + }} + /> - <div className="flex ml-3"> - <HeaderModelAlert<TeamMember> - icon={IconProp.Folder} - className="rounded-md m-3 bg-indigo-500 p-3 hover:bg-indigo-600 cursor-pointer ml-0" - modelType={TeamMember} - query={{ - userId: User.getUserId(), - hasAcceptedInvitation: false, - }} - singularName="Project Invitation" - pluralName="Project Invitations" - requestOptions={{ - isMultiTenantRequest: true, - }} - onClick={() => { - Navigation.navigate( - RouteMap[PageMap.PROJECT_INVITATIONS]! - ); - }} - /> - - <HeaderModelAlert<Incident> - icon={IconProp.Alert} - modelType={Incident} - className="rounded-md m-3 bg-red-500 p-3 hover:bg-red-600 cursor-pointer ml-0" - query={{ - currentIncidentState: { - order: 1, - }, - }} - refreshToggle={activeIncidentToggleRefresh} - singularName="New Incident" - pluralName="New Incidents" - requestOptions={{ - isMultiTenantRequest: true, - }} - onClick={() => { - Navigation.navigate( - RouteMap[PageMap.NEW_INCIDENTS]! - ); - }} - /> - - {showTrialButton && ( - <HeaderAlert - icon={IconProp.Clock} - className="rounded-md m-3 bg-indigo-500 p-3 ml-0" - title={`Trial ends in ${OneUptimeDate.getNumberOfDaysBetweenDatesInclusive( - OneUptimeDate.getCurrentDate(), - props.selectedProject!.trialEndsAt! - )} ${ - OneUptimeDate.getNumberOfDaysBetweenDatesInclusive( - OneUptimeDate.getCurrentDate(), - props.selectedProject!.trialEndsAt! - ) > 1 - ? 'days' - : 'day' - }`} - /> - )} - </div> - </> - } - centerComponents={ - <> - {/* <SearchBox + {showTrialButton && ( + <HeaderAlert + icon={IconProp.Clock} + className="rounded-md m-3 bg-indigo-500 p-3 ml-0" + title={`Trial ends in ${OneUptimeDate.getNumberOfDaysBetweenDatesInclusive( + OneUptimeDate.getCurrentDate(), + props.selectedProject!.trialEndsAt!, + )} ${ + OneUptimeDate.getNumberOfDaysBetweenDatesInclusive( + OneUptimeDate.getCurrentDate(), + props.selectedProject!.trialEndsAt!, + ) > 1 + ? "days" + : "day" + }`} + /> + )} + </div> + </> + } + centerComponents={ + <> + {/* <SearchBox key={2} selectedProject={props.selectedProject} onChange={(_value: string) => { }} />{' '} */} - </> - } - rightComponents={ - <> - {/* <Notifications /> */} - {showAddCardButton ? ( - <Button - title="Add Card Details" - onClick={() => { - Navigation.navigate( - RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_BILLING - ] as Route - ) - ); - }} - buttonStyle={ButtonStyleType.LINK} - icon={IconProp.Billing} - iconSize={SizeProp.Larger} - ></Button> - ) : ( - <></> - )} - {BILLING_ENABLED && - props.selectedProject?.id && - props.selectedProject.paymentProviderPlanId && - SubscriptionPlan.isFreePlan( - props.selectedProject.paymentProviderPlanId, - getAllEnvVars() - ) ? ( - <Upgrade /> - ) : ( - <></> - )} - <Help /> - <UserProfile - onClickUserProfile={() => { - Navigation.navigate( - RouteMap[PageMap.USER_PROFILE_OVERVIEW]! - ); - }} - /> - </> - } + </> + } + rightComponents={ + <> + {/* <Notifications /> */} + {showAddCardButton ? ( + <Button + title="Add Card Details" + onClick={() => { + Navigation.navigate( + RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_BILLING] as Route, + ), + ); + }} + buttonStyle={ButtonStyleType.LINK} + icon={IconProp.Billing} + iconSize={SizeProp.Larger} + ></Button> + ) : ( + <></> + )} + {BILLING_ENABLED && + props.selectedProject?.id && + props.selectedProject.paymentProviderPlanId && + SubscriptionPlan.isFreePlan( + props.selectedProject.paymentProviderPlanId, + getAllEnvVars(), + ) ? ( + <Upgrade /> + ) : ( + <></> + )} + <Help /> + <UserProfile + onClickUserProfile={() => { + Navigation.navigate(RouteMap[PageMap.USER_PROFILE_OVERVIEW]!); + }} /> - </> - ); + </> + } + /> + </> + ); }; export default DashboardHeader; diff --git a/Dashboard/src/Components/Header/Help.tsx b/Dashboard/src/Components/Header/Help.tsx index 9e4ae94178..2bdb536591 100644 --- a/Dashboard/src/Components/Header/Help.tsx +++ b/Dashboard/src/Components/Header/Help.tsx @@ -1,60 +1,58 @@ -import URL from 'Common/Types/API/URL'; -import IconProp from 'Common/Types/Icon/IconProp'; -import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton'; -import IconDropdownItem from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownItem'; -import IconDropdownMenu from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu'; -import IconDropdownRow from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownRow'; -import React, { ReactElement, useState } from 'react'; +import URL from "Common/Types/API/URL"; +import IconProp from "Common/Types/Icon/IconProp"; +import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton"; +import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem"; +import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu"; +import IconDropdownRow from "CommonUI/src/Components/Header/IconDropdown/IconDropdownRow"; +import React, { ReactElement, useState } from "react"; const Help: () => JSX.Element = (): ReactElement => { - const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false); + const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false); - return ( - <HeaderIconDropdownButton - icon={IconProp.Help} - name="Help" - showDropdown={isDropdownVisible} + return ( + <HeaderIconDropdownButton + icon={IconProp.Help} + name="Help" + showDropdown={isDropdownVisible} + onClick={() => { + setIsDropdownVisible(true); + }} + > + <IconDropdownMenu> + <IconDropdownRow> + <IconDropdownItem + title="Support Email" + icon={IconProp.Email} + openInNewTab={true} + url={URL.fromString("mailto:support@oneuptime.com")} onClick={() => { - setIsDropdownVisible(true); + setIsDropdownVisible(false); }} - > - <IconDropdownMenu> - <IconDropdownRow> - <IconDropdownItem - title="Support Email" - icon={IconProp.Email} - openInNewTab={true} - url={URL.fromString('mailto:support@oneuptime.com')} - onClick={() => { - setIsDropdownVisible(false); - }} - /> - <IconDropdownItem - title="Chat on Slack" - icon={IconProp.Slack} - openInNewTab={true} - onClick={() => { - setIsDropdownVisible(false); - }} - url={URL.fromString( - 'https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ' - )} - /> - <IconDropdownItem - title="Request Demo" - icon={IconProp.Window} - onClick={() => { - setIsDropdownVisible(false); - }} - openInNewTab={true} - url={URL.fromString( - 'https://oneuptime.com/enterprise/demo' - )} - /> - </IconDropdownRow> - </IconDropdownMenu> - </HeaderIconDropdownButton> - ); + /> + <IconDropdownItem + title="Chat on Slack" + icon={IconProp.Slack} + openInNewTab={true} + onClick={() => { + setIsDropdownVisible(false); + }} + url={URL.fromString( + "https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ", + )} + /> + <IconDropdownItem + title="Request Demo" + icon={IconProp.Window} + onClick={() => { + setIsDropdownVisible(false); + }} + openInNewTab={true} + url={URL.fromString("https://oneuptime.com/enterprise/demo")} + /> + </IconDropdownRow> + </IconDropdownMenu> + </HeaderIconDropdownButton> + ); }; export default Help; diff --git a/Dashboard/src/Components/Header/Logo.tsx b/Dashboard/src/Components/Header/Logo.tsx index bcccffa878..c76f235eda 100644 --- a/Dashboard/src/Components/Header/Logo.tsx +++ b/Dashboard/src/Components/Header/Logo.tsx @@ -1,30 +1,30 @@ // Tailwind -import Route from 'Common/Types/API/Route'; -import Image from 'CommonUI/src/Components/Image/Image'; -import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Route from "Common/Types/API/Route"; +import Image from "CommonUI/src/Components/Image/Image"; +import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onClick: () => void; + onClick: () => void; } const Logo: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="relative z-10 flex px-2 lg:px-0"> - <div className="flex flex-shrink-0 items-center"> - <Image - className="block h-8 w-auto" - onClick={() => { - props.onClick && props.onClick(); - }} - imageUrl={Route.fromString(`${OneUptimeLogo}`)} - alt={'OneUptime'} - /> - </div> - </div> - ); + return ( + <div className="relative z-10 flex px-2 lg:px-0"> + <div className="flex flex-shrink-0 items-center"> + <Image + className="block h-8 w-auto" + onClick={() => { + props.onClick && props.onClick(); + }} + imageUrl={Route.fromString(`${OneUptimeLogo}`)} + alt={"OneUptime"} + /> + </div> + </div> + ); }; export default Logo; diff --git a/Dashboard/src/Components/Header/Notifications.tsx b/Dashboard/src/Components/Header/Notifications.tsx index 4d45d04ae6..24bcd01e53 100644 --- a/Dashboard/src/Components/Header/Notifications.tsx +++ b/Dashboard/src/Components/Header/Notifications.tsx @@ -1,35 +1,35 @@ -import IconProp from 'Common/Types/Icon/IconProp'; -import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton'; -import NotificationItem from 'CommonUI/src/Components/Header/Notifications/NotificationItem'; -import Notifications from 'CommonUI/src/Components/Header/Notifications/Notifications'; -import React, { ReactElement, useState } from 'react'; +import IconProp from "Common/Types/Icon/IconProp"; +import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton"; +import NotificationItem from "CommonUI/src/Components/Header/Notifications/NotificationItem"; +import Notifications from "CommonUI/src/Components/Header/Notifications/Notifications"; +import React, { ReactElement, useState } from "react"; const DashboardHeader: () => JSX.Element = (): ReactElement => { - const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false); + const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false); - return ( - <HeaderIconDropdownButton - name="Notifications" - onClick={() => { - setIsDropdownVisible(true); - }} - showDropdown={isDropdownVisible} - icon={IconProp.Notification} - badge={4} - > - <Notifications> - <NotificationItem - title="Sample Title" - description="Sample Description" - createdAt={new Date()} - icon={IconProp.Home} - onClick={() => { - setIsDropdownVisible(false); - }} - /> - </Notifications> - </HeaderIconDropdownButton> - ); + return ( + <HeaderIconDropdownButton + name="Notifications" + onClick={() => { + setIsDropdownVisible(true); + }} + showDropdown={isDropdownVisible} + icon={IconProp.Notification} + badge={4} + > + <Notifications> + <NotificationItem + title="Sample Title" + description="Sample Description" + createdAt={new Date()} + icon={IconProp.Home} + onClick={() => { + setIsDropdownVisible(false); + }} + /> + </Notifications> + </HeaderIconDropdownButton> + ); }; export default DashboardHeader; diff --git a/Dashboard/src/Components/Header/ProjectPicker.tsx b/Dashboard/src/Components/Header/ProjectPicker.tsx index 315b776720..c8d4a4c5e6 100644 --- a/Dashboard/src/Components/Header/ProjectPicker.tsx +++ b/Dashboard/src/Components/Header/ProjectPicker.tsx @@ -1,334 +1,324 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import { VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONValue } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import Field from 'CommonUI/src/Components/Forms/Types/Field'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ProjectPicker from 'CommonUI/src/Components/Header/ProjectPicker/ProjectPicker'; -import ModelFormModal from 'CommonUI/src/Components/ModelFormModal/ModelFormModal'; -import { RadioButton } from 'CommonUI/src/Components/RadioButtons/GroupRadioButtons'; -import Toggle from 'CommonUI/src/Components/Toggle/Toggle'; -import { BILLING_ENABLED, getAllEnvVars } from 'CommonUI/src/Config'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Project from 'Model/Models/Project'; +import DashboardNavigation from "../../Utils/Navigation"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import { VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONValue } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import Field from "CommonUI/src/Components/Forms/Types/Field"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ProjectPicker from "CommonUI/src/Components/Header/ProjectPicker/ProjectPicker"; +import ModelFormModal from "CommonUI/src/Components/ModelFormModal/ModelFormModal"; +import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons"; +import Toggle from "CommonUI/src/Components/Toggle/Toggle"; +import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Project from "Model/Models/Project"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - projects: Array<Project>; - onProjectSelected: (project: Project) => void; - showProjectModal: boolean; - onProjectModalClose: () => void; - selectedProject: Project | null; + projects: Array<Project>; + onProjectSelected: (project: Project) => void; + showProjectModal: boolean; + onProjectModalClose: () => void; + selectedProject: Project | null; } const DashboardProjectPicker: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [showModal, setShowModal] = useState<boolean>(false); + const [showModal, setShowModal] = useState<boolean>(false); - const [initialValues, setInitialValues] = useState<any>({}); + const [initialValues, setInitialValues] = useState<any>({}); - useEffect(() => { - // check if promocode exists in localstorage and if it does, add it to initialValues. - const promoCode: JSONValue = LocalStorage.getItem('promoCode'); + useEffect(() => { + // check if promocode exists in localstorage and if it does, add it to initialValues. + const promoCode: JSONValue = LocalStorage.getItem("promoCode"); - if (promoCode) { - setInitialValues({ - paymentProviderPromoCode: promoCode, - }); - } - }, []); + if (promoCode) { + setInitialValues({ + paymentProviderPromoCode: promoCode, + }); + } + }, []); - useEffect(() => { - refreshFields(); - }, [initialValues]); + useEffect(() => { + refreshFields(); + }, [initialValues]); - const getFooter: GetReactElementFunction = (): ReactElement => { - if (!BILLING_ENABLED) { - return <></>; - } - - return ( - <Toggle - title="Yearly Plan" - value={isSubscriptionPlanYearly} - description="(Save 20%)" - onChange={(value: boolean) => { - setIsSubscriptionPlanYearly(value); - }} - /> - ); - }; - - const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] = - useState<boolean>(true); - - const [fields, setFields] = useState<Array<Field<Project>>>([]); - - useEffect(() => { - if (props.showProjectModal) { - setShowModal(true); - } - }, [props.showProjectModal]); - - type GetCurrentProjectFunction = () => Project | null; - - const getCurrentProject: GetCurrentProjectFunction = (): Project | null => { - // see nav params first, then local storage, then default to first project. - const projectId: ObjectID | null = DashboardNavigation.getProjectId(); - - if (projectId) { - // check if this project is in the list. - - const project: Project | undefined = props.projects.find( - (project: Project) => { - return project._id?.toString() === projectId.toString(); - } - ); - - if (project) { - return project; - } - } - - const currentProject: Project | null = ProjectUtil.getCurrentProject(); - - if (currentProject) { - return currentProject; - } - - if (props.projects.length > 0) { - return props.projects[0] || null; - } - - return null; - }; - - useEffect(() => { - const currentProject: Project | null = getCurrentProject(); - - if (currentProject && props.onProjectSelected) { - props.onProjectSelected(currentProject); - } - }, []); - - useEffect(() => { - if ( - props.projects && - props.projects.length > 0 && - !props.selectedProject && - props.projects[0] - ) { - const currentProject: Project | null = getCurrentProject(); - - if (!currentProject) { - props.onProjectSelected(props.projects[0]); - } else if ( - props.projects.filter((project: Project) => { - return project._id === currentProject._id; - }).length > 0 - ) { - props.onProjectSelected( - props.projects.filter((project: Project) => { - return project._id === currentProject._id; - })[0] as Project - ); - } else { - props.onProjectSelected(props.projects[0]); - } - } - }, [props.projects]); - - useEffect(() => { - refreshFields(); - }, [isSubscriptionPlanYearly]); - - const refreshFields: VoidFunction = (): void => { - let formFields: Array<Field<Project>> = [ - { - field: { - name: true, - }, - validation: { - minLength: 4, - }, - fieldType: FormFieldSchemaType.Text, - placeholder: 'My Project', - description: 'Pick a friendly name.', - title: 'Project Name', - required: true, - stepId: BILLING_ENABLED ? 'basic' : undefined, - }, - ]; - - if (BILLING_ENABLED) { - formFields = [ - ...formFields, - { - field: { - paymentProviderPlanId: true, - }, - stepId: 'plan', - validation: { - minLength: 6, - }, - footerElement: getFooter(), - fieldType: FormFieldSchemaType.RadioButton, - radioButtonOptions: SubscriptionPlan.getSubscriptionPlans( - getAllEnvVars() - ).map((plan: SubscriptionPlan): RadioButton => { - let description: string = plan.isCustomPricing() - ? `Our sales team will contact you soon.` - : `Billed ${ - isSubscriptionPlanYearly - ? 'yearly' - : 'monthly' - }. ${ - plan.getTrialPeriod() > 0 - ? `Free ${plan.getTrialPeriod()} days trial.` - : '' - }`; - - if ( - isSubscriptionPlanYearly && - plan.getYearlySubscriptionAmountInUSD() === 0 - ) { - description = 'This plan is free, forever. '; - } - - if ( - !isSubscriptionPlanYearly && - plan.getMonthlySubscriptionAmountInUSD() === 0 - ) { - description = 'This plan is free, forever. '; - } - - return { - value: isSubscriptionPlanYearly - ? plan.getYearlyPlanId() - : plan.getMonthlyPlanId(), - title: plan.getName(), - description: description, - sideTitle: plan.isCustomPricing() - ? 'Custom Price' - : isSubscriptionPlanYearly - ? '$' + - plan - .getYearlySubscriptionAmountInUSD() - .toString() + - '/mo billed yearly' - : '$' + - plan - .getMonthlySubscriptionAmountInUSD() - .toString(), - sideDescription: plan.isCustomPricing() - ? '' - : isSubscriptionPlanYearly - ? `~ $${ - plan.getYearlySubscriptionAmountInUSD() * - 12 - } per user / year` - : `/month per user`, - }; - }), - title: 'Please select a plan.', - required: true, - }, - { - field: { - paymentProviderPromoCode: true, - }, - fieldType: FormFieldSchemaType.Text, - placeholder: 'Promo Code (Optional)', - description: 'If you have a coupon code, enter it here.', - title: 'Promo Code', - required: false, - stepId: 'plan', - disabled: Boolean(initialValues.paymentProviderPromoCode), - }, - ]; - } - - setFields(formFields); - }; + const getFooter: GetReactElementFunction = (): ReactElement => { + if (!BILLING_ENABLED) { + return <></>; + } return ( - <> - {props.projects.length !== 0 && ( - <ProjectPicker - selectedProjectName={props.selectedProject?.name || ''} - selectedProjectIcon={IconProp.Folder} - projects={props.projects} - onCreateProjectButtonClicked={() => { - setShowModal(true); - props.onProjectModalClose(); - }} - onProjectSelected={(project: Project) => { - props.onProjectSelected(project); - }} - /> - )} - {showModal ? ( - <ModelFormModal<Project> - modelType={Project} - initialValues={initialValues} - name="Create New Project" - title="Create New Project" - description="Please create a new OneUptime project to get started." - onClose={() => { - setShowModal(false); - props.onProjectModalClose(); - }} - submitButtonText="Create Project" - onSuccess={(project: Project | null) => { - LocalStorage.removeItem('promoCode'); - if (project && props.onProjectSelected) { - props.onProjectSelected(project); - } - if (project && props.onProjectSelected) { - props.onProjectSelected(project); - } - setShowModal(false); - props.onProjectModalClose(); - }} - formProps={{ - name: 'Create New Project', - steps: BILLING_ENABLED - ? [ - { - title: 'Basic', - id: 'basic', - }, - { - title: 'Select Plan', - id: 'plan', - }, - ] - : undefined, - saveRequestOptions: { - isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request - }, - modelType: Project, - id: 'create-project-from', - fields: [...fields], - formType: FormType.Create, - }} - /> - ) : ( - <></> - )} - </> + <Toggle + title="Yearly Plan" + value={isSubscriptionPlanYearly} + description="(Save 20%)" + onChange={(value: boolean) => { + setIsSubscriptionPlanYearly(value); + }} + /> ); + }; + + const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] = + useState<boolean>(true); + + const [fields, setFields] = useState<Array<Field<Project>>>([]); + + useEffect(() => { + if (props.showProjectModal) { + setShowModal(true); + } + }, [props.showProjectModal]); + + type GetCurrentProjectFunction = () => Project | null; + + const getCurrentProject: GetCurrentProjectFunction = (): Project | null => { + // see nav params first, then local storage, then default to first project. + const projectId: ObjectID | null = DashboardNavigation.getProjectId(); + + if (projectId) { + // check if this project is in the list. + + const project: Project | undefined = props.projects.find( + (project: Project) => { + return project._id?.toString() === projectId.toString(); + }, + ); + + if (project) { + return project; + } + } + + const currentProject: Project | null = ProjectUtil.getCurrentProject(); + + if (currentProject) { + return currentProject; + } + + if (props.projects.length > 0) { + return props.projects[0] || null; + } + + return null; + }; + + useEffect(() => { + const currentProject: Project | null = getCurrentProject(); + + if (currentProject && props.onProjectSelected) { + props.onProjectSelected(currentProject); + } + }, []); + + useEffect(() => { + if ( + props.projects && + props.projects.length > 0 && + !props.selectedProject && + props.projects[0] + ) { + const currentProject: Project | null = getCurrentProject(); + + if (!currentProject) { + props.onProjectSelected(props.projects[0]); + } else if ( + props.projects.filter((project: Project) => { + return project._id === currentProject._id; + }).length > 0 + ) { + props.onProjectSelected( + props.projects.filter((project: Project) => { + return project._id === currentProject._id; + })[0] as Project, + ); + } else { + props.onProjectSelected(props.projects[0]); + } + } + }, [props.projects]); + + useEffect(() => { + refreshFields(); + }, [isSubscriptionPlanYearly]); + + const refreshFields: VoidFunction = (): void => { + let formFields: Array<Field<Project>> = [ + { + field: { + name: true, + }, + validation: { + minLength: 4, + }, + fieldType: FormFieldSchemaType.Text, + placeholder: "My Project", + description: "Pick a friendly name.", + title: "Project Name", + required: true, + stepId: BILLING_ENABLED ? "basic" : undefined, + }, + ]; + + if (BILLING_ENABLED) { + formFields = [ + ...formFields, + { + field: { + paymentProviderPlanId: true, + }, + stepId: "plan", + validation: { + minLength: 6, + }, + footerElement: getFooter(), + fieldType: FormFieldSchemaType.RadioButton, + radioButtonOptions: SubscriptionPlan.getSubscriptionPlans( + getAllEnvVars(), + ).map((plan: SubscriptionPlan): RadioButton => { + let description: string = plan.isCustomPricing() + ? `Our sales team will contact you soon.` + : `Billed ${isSubscriptionPlanYearly ? "yearly" : "monthly"}. ${ + plan.getTrialPeriod() > 0 + ? `Free ${plan.getTrialPeriod()} days trial.` + : "" + }`; + + if ( + isSubscriptionPlanYearly && + plan.getYearlySubscriptionAmountInUSD() === 0 + ) { + description = "This plan is free, forever. "; + } + + if ( + !isSubscriptionPlanYearly && + plan.getMonthlySubscriptionAmountInUSD() === 0 + ) { + description = "This plan is free, forever. "; + } + + return { + value: isSubscriptionPlanYearly + ? plan.getYearlyPlanId() + : plan.getMonthlyPlanId(), + title: plan.getName(), + description: description, + sideTitle: plan.isCustomPricing() + ? "Custom Price" + : isSubscriptionPlanYearly + ? "$" + + plan.getYearlySubscriptionAmountInUSD().toString() + + "/mo billed yearly" + : "$" + plan.getMonthlySubscriptionAmountInUSD().toString(), + sideDescription: plan.isCustomPricing() + ? "" + : isSubscriptionPlanYearly + ? `~ $${ + plan.getYearlySubscriptionAmountInUSD() * 12 + } per user / year` + : `/month per user`, + }; + }), + title: "Please select a plan.", + required: true, + }, + { + field: { + paymentProviderPromoCode: true, + }, + fieldType: FormFieldSchemaType.Text, + placeholder: "Promo Code (Optional)", + description: "If you have a coupon code, enter it here.", + title: "Promo Code", + required: false, + stepId: "plan", + disabled: Boolean(initialValues.paymentProviderPromoCode), + }, + ]; + } + + setFields(formFields); + }; + + return ( + <> + {props.projects.length !== 0 && ( + <ProjectPicker + selectedProjectName={props.selectedProject?.name || ""} + selectedProjectIcon={IconProp.Folder} + projects={props.projects} + onCreateProjectButtonClicked={() => { + setShowModal(true); + props.onProjectModalClose(); + }} + onProjectSelected={(project: Project) => { + props.onProjectSelected(project); + }} + /> + )} + {showModal ? ( + <ModelFormModal<Project> + modelType={Project} + initialValues={initialValues} + name="Create New Project" + title="Create New Project" + description="Please create a new OneUptime project to get started." + onClose={() => { + setShowModal(false); + props.onProjectModalClose(); + }} + submitButtonText="Create Project" + onSuccess={(project: Project | null) => { + LocalStorage.removeItem("promoCode"); + if (project && props.onProjectSelected) { + props.onProjectSelected(project); + } + if (project && props.onProjectSelected) { + props.onProjectSelected(project); + } + setShowModal(false); + props.onProjectModalClose(); + }} + formProps={{ + name: "Create New Project", + steps: BILLING_ENABLED + ? [ + { + title: "Basic", + id: "basic", + }, + { + title: "Select Plan", + id: "plan", + }, + ] + : undefined, + saveRequestOptions: { + isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request + }, + modelType: Project, + id: "create-project-from", + fields: [...fields], + formType: FormType.Create, + }} + /> + ) : ( + <></> + )} + </> + ); }; export default DashboardProjectPicker; diff --git a/Dashboard/src/Components/Header/SearchBox.tsx b/Dashboard/src/Components/Header/SearchBox.tsx index eb2148856e..055a9af8e4 100644 --- a/Dashboard/src/Components/Header/SearchBox.tsx +++ b/Dashboard/src/Components/Header/SearchBox.tsx @@ -1,20 +1,20 @@ -import SearchBox from 'CommonUI/src/Components/Header/SearchBox'; -import Project from 'Model/Models/Project'; -import React, { FunctionComponent, ReactElement } from 'react'; +import SearchBox from "CommonUI/src/Components/Header/SearchBox"; +import Project from "Model/Models/Project"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onChange: (search: string) => void; - selectedProject: Project | null; + onChange: (search: string) => void; + selectedProject: Project | null; } const Search: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.selectedProject) { - return <></>; - } + if (!props.selectedProject) { + return <></>; + } - return <SearchBox key={2} onChange={props.onChange} />; + return <SearchBox key={2} onChange={props.onChange} />; }; export default Search; diff --git a/Dashboard/src/Components/Header/Upgrade.tsx b/Dashboard/src/Components/Header/Upgrade.tsx index 4be84f5dfb..74ae2a662c 100644 --- a/Dashboard/src/Components/Header/Upgrade.tsx +++ b/Dashboard/src/Components/Header/Upgrade.tsx @@ -1,162 +1,142 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelFormModal from 'CommonUI/src/Components/ModelFormModal/ModelFormModal'; -import { RadioButton } from 'CommonUI/src/Components/RadioButtons/GroupRadioButtons'; -import Toggle from 'CommonUI/src/Components/Toggle/Toggle'; -import { BILLING_ENABLED, getAllEnvVars } from 'CommonUI/src/Config'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Project from 'Model/Models/Project'; -import React, { ReactElement, useState } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import IconProp from "Common/Types/Icon/IconProp"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelFormModal from "CommonUI/src/Components/ModelFormModal/ModelFormModal"; +import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons"; +import Toggle from "CommonUI/src/Components/Toggle/Toggle"; +import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Project from "Model/Models/Project"; +import React, { ReactElement, useState } from "react"; const Upgrade: () => JSX.Element = (): ReactElement => { - const [showModal, setShowModal] = useState<boolean>(false); - const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] = - useState<boolean>(true); + const [showModal, setShowModal] = useState<boolean>(false); + const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] = + useState<boolean>(true); - const getFooter: GetReactElementFunction = (): ReactElement => { - if (!BILLING_ENABLED) { - return <></>; - } - - return ( - <Toggle - title="Yearly Plan" - value={isSubscriptionPlanYearly} - description="(Save 20%)" - onChange={(value: boolean) => { - setIsSubscriptionPlanYearly(value); - }} - /> - ); - }; + const getFooter: GetReactElementFunction = (): ReactElement => { + if (!BILLING_ENABLED) { + return <></>; + } return ( - <> - <Button - title="Upgrade Plan" - onClick={() => { - setShowModal(true); - }} - buttonStyle={ButtonStyleType.LINK} - icon={IconProp.Star} - ></Button> - {showModal ? ( - <ModelFormModal<Project> - modelType={Project} - title="Change Plan" - name="Change Plan" - modelIdToEdit={DashboardNavigation.getProjectId()!} - onClose={() => { - setShowModal(false); - }} - submitButtonText="Change Plan" - onSuccess={() => { - Navigation.reload(); - }} - formProps={{ - name: 'Change Plan', - saveRequestOptions: { - isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request - }, - modelType: Project, - id: 'create-project-from', - fields: [ - { - field: { - paymentProviderPlanId: true, - }, - validation: { - minLength: 6, - }, - fieldType: FormFieldSchemaType.RadioButton, - radioButtonOptions: - SubscriptionPlan.getSubscriptionPlans( - getAllEnvVars() - ).map( - ( - plan: SubscriptionPlan - ): RadioButton => { - let description: string = - plan.isCustomPricing() - ? `Our sales team will contact you soon.` - : `Billed ${ - isSubscriptionPlanYearly - ? 'yearly' - : 'monthly' - }. ${ - plan.getTrialPeriod() > - 0 - ? `Free ${plan.getTrialPeriod()} days trial.` - : '' - }`; - - if ( - isSubscriptionPlanYearly && - plan.getYearlySubscriptionAmountInUSD() === - 0 - ) { - description = - 'This plan is free, forever. '; - } - - if ( - !isSubscriptionPlanYearly && - plan.getMonthlySubscriptionAmountInUSD() === - 0 - ) { - description = - 'This plan is free, forever. '; - } - - return { - value: isSubscriptionPlanYearly - ? plan.getYearlyPlanId() - : plan.getMonthlyPlanId(), - title: plan.getName(), - description: description, - sideTitle: - plan.isCustomPricing() - ? 'Custom Price' - : isSubscriptionPlanYearly - ? '$' + - plan - .getYearlySubscriptionAmountInUSD() - .toString() + - '/mo billed yearly' - : '$' + - plan - .getMonthlySubscriptionAmountInUSD() - .toString(), - sideDescription: - plan.isCustomPricing() - ? '' - : isSubscriptionPlanYearly - ? `~ $${ - plan.getYearlySubscriptionAmountInUSD() * - 12 - } per user / year` - : `/month per user`, - }; - } - ), - title: 'Please select a plan.', - required: true, - footerElement: getFooter(), - }, - ], - formType: FormType.Update, - }} - /> - ) : ( - <></> - )} - </> + <Toggle + title="Yearly Plan" + value={isSubscriptionPlanYearly} + description="(Save 20%)" + onChange={(value: boolean) => { + setIsSubscriptionPlanYearly(value); + }} + /> ); + }; + + return ( + <> + <Button + title="Upgrade Plan" + onClick={() => { + setShowModal(true); + }} + buttonStyle={ButtonStyleType.LINK} + icon={IconProp.Star} + ></Button> + {showModal ? ( + <ModelFormModal<Project> + modelType={Project} + title="Change Plan" + name="Change Plan" + modelIdToEdit={DashboardNavigation.getProjectId()!} + onClose={() => { + setShowModal(false); + }} + submitButtonText="Change Plan" + onSuccess={() => { + Navigation.reload(); + }} + formProps={{ + name: "Change Plan", + saveRequestOptions: { + isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request + }, + modelType: Project, + id: "create-project-from", + fields: [ + { + field: { + paymentProviderPlanId: true, + }, + validation: { + minLength: 6, + }, + fieldType: FormFieldSchemaType.RadioButton, + radioButtonOptions: SubscriptionPlan.getSubscriptionPlans( + getAllEnvVars(), + ).map((plan: SubscriptionPlan): RadioButton => { + let description: string = plan.isCustomPricing() + ? `Our sales team will contact you soon.` + : `Billed ${ + isSubscriptionPlanYearly ? "yearly" : "monthly" + }. ${ + plan.getTrialPeriod() > 0 + ? `Free ${plan.getTrialPeriod()} days trial.` + : "" + }`; + + if ( + isSubscriptionPlanYearly && + plan.getYearlySubscriptionAmountInUSD() === 0 + ) { + description = "This plan is free, forever. "; + } + + if ( + !isSubscriptionPlanYearly && + plan.getMonthlySubscriptionAmountInUSD() === 0 + ) { + description = "This plan is free, forever. "; + } + + return { + value: isSubscriptionPlanYearly + ? plan.getYearlyPlanId() + : plan.getMonthlyPlanId(), + title: plan.getName(), + description: description, + sideTitle: plan.isCustomPricing() + ? "Custom Price" + : isSubscriptionPlanYearly + ? "$" + + plan.getYearlySubscriptionAmountInUSD().toString() + + "/mo billed yearly" + : "$" + + plan.getMonthlySubscriptionAmountInUSD().toString(), + sideDescription: plan.isCustomPricing() + ? "" + : isSubscriptionPlanYearly + ? `~ $${ + plan.getYearlySubscriptionAmountInUSD() * 12 + } per user / year` + : `/month per user`, + }; + }), + title: "Please select a plan.", + required: true, + footerElement: getFooter(), + }, + ], + formType: FormType.Update, + }} + /> + ) : ( + <></> + )} + </> + ); }; export default Upgrade; diff --git a/Dashboard/src/Components/Header/UserProfile.tsx b/Dashboard/src/Components/Header/UserProfile.tsx index 042021e559..64c66a2222 100644 --- a/Dashboard/src/Components/Header/UserProfile.tsx +++ b/Dashboard/src/Components/Header/UserProfile.tsx @@ -1,113 +1,113 @@ -import EventName from '../../Utils/EventName'; -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 ObjectID from 'Common/Types/ObjectID'; -import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton'; -import IconDropdownItem from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownItem'; -import IconDropdownMenu from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu'; -import { ADMIN_DASHBOARD_URL } from 'CommonUI/src/Config'; -import BlankProfilePic from 'CommonUI/src/Images/users/blank-profile.svg'; -import FileUtil from 'CommonUI/src/Utils/File'; -import GlobalEvents from 'CommonUI/src/Utils/GlobalEvents'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import User from 'CommonUI/src/Utils/User'; +import EventName from "../../Utils/EventName"; +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 ObjectID from "Common/Types/ObjectID"; +import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton"; +import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem"; +import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu"; +import { ADMIN_DASHBOARD_URL } from "CommonUI/src/Config"; +import BlankProfilePic from "CommonUI/src/Images/users/blank-profile.svg"; +import FileUtil from "CommonUI/src/Utils/File"; +import GlobalEvents from "CommonUI/src/Utils/GlobalEvents"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import User from "CommonUI/src/Utils/User"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - onClickUserProfile: () => void; + onClickUserProfile: () => void; } const DashboardUserProfile: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false); + const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false); - const [profilePictureId, setProfilePictureId] = useState<ObjectID | null>( - User.getProfilePicId() + const [profilePictureId, setProfilePictureId] = useState<ObjectID | null>( + User.getProfilePicId(), + ); + + type SetPictureFunction = (event: CustomEvent) => void; + + const setPicture: SetPictureFunction = (event: CustomEvent): void => { + // get data from event. + const id: ObjectID = event.detail.id as ObjectID; + + setProfilePictureId(id); + }; + + useEffect(() => { + GlobalEvents.addEventListener( + EventName.SET_NEW_PROFILE_PICTURE, + setPicture, ); - type SetPictureFunction = (event: CustomEvent) => void; - - const setPicture: SetPictureFunction = (event: CustomEvent): void => { - // get data from event. - const id: ObjectID = event.detail.id as ObjectID; - - setProfilePictureId(id); + return () => { + // on unmount. + GlobalEvents.removeEventListener( + EventName.SET_NEW_PROFILE_PICTURE, + setPicture, + ); }; + }, []); - useEffect(() => { - GlobalEvents.addEventListener( - EventName.SET_NEW_PROFILE_PICTURE, - setPicture - ); + return ( + <> + <HeaderIconDropdownButton + iconImageUrl={ + profilePictureId + ? FileUtil.getFileURL(profilePictureId) + : BlankProfilePic + } + name="User Profile" + showDropdown={isDropdownVisible} + onClick={() => { + setIsDropdownVisible(true); + }} + > + <IconDropdownMenu> + <IconDropdownItem + title="Profile" + onClick={() => { + setIsDropdownVisible(false); + props.onClickUserProfile(); + }} + icon={IconProp.User} + /> - return () => { - // on unmount. - GlobalEvents.removeEventListener( - EventName.SET_NEW_PROFILE_PICTURE, - setPicture - ); - }; - }, []); + {User.isMasterAdmin() ? ( + <IconDropdownItem + title="Admin Settings" + onClick={() => { + setIsDropdownVisible(false); + Navigation.navigate(ADMIN_DASHBOARD_URL); + }} + icon={IconProp.Settings} + /> + ) : ( + <></> + )} - return ( - <> - <HeaderIconDropdownButton - iconImageUrl={ - profilePictureId - ? FileUtil.getFileURL(profilePictureId) - : BlankProfilePic - } - name="User Profile" - showDropdown={isDropdownVisible} - onClick={() => { - setIsDropdownVisible(true); - }} - > - <IconDropdownMenu> - <IconDropdownItem - title="Profile" - onClick={() => { - setIsDropdownVisible(false); - props.onClickUserProfile(); - }} - icon={IconProp.User} - /> - - {User.isMasterAdmin() ? ( - <IconDropdownItem - title="Admin Settings" - onClick={() => { - setIsDropdownVisible(false); - Navigation.navigate(ADMIN_DASHBOARD_URL); - }} - icon={IconProp.Settings} - /> - ) : ( - <></> - )} - - <IconDropdownItem - title="Log out" - onClick={() => { - setIsDropdownVisible(false); - }} - url={RouteUtil.populateRouteParams( - RouteMap[PageMap.LOGOUT] as Route - )} - icon={IconProp.Logout} - /> - </IconDropdownMenu> - </HeaderIconDropdownButton> - </> - ); + <IconDropdownItem + title="Log out" + onClick={() => { + setIsDropdownVisible(false); + }} + url={RouteUtil.populateRouteParams( + RouteMap[PageMap.LOGOUT] as Route, + )} + icon={IconProp.Logout} + /> + </IconDropdownMenu> + </HeaderIconDropdownButton> + </> + ); }; export default DashboardUserProfile; diff --git a/Dashboard/src/Components/Incident/ChangeState.tsx b/Dashboard/src/Components/Incident/ChangeState.tsx index f6368692b6..9d17170078 100644 --- a/Dashboard/src/Components/Incident/ChangeState.tsx +++ b/Dashboard/src/Components/Incident/ChangeState.tsx @@ -1,234 +1,231 @@ -import UserElement from '../User/User'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; +import UserElement from "../User/User"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; import Button, { - ButtonSize, - ButtonStyleType, -} from 'CommonUI/src/Components/Button/Button'; -import { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelFormModal from 'CommonUI/src/Components/ModelFormModal/ModelFormModal'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; + ButtonSize, + ButtonStyleType, +} from "CommonUI/src/Components/Button/Button"; +import { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelFormModal from "CommonUI/src/Components/ModelFormModal/ModelFormModal"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export enum IncidentType { - Ack, - Resolve, + Ack, + Resolve, } export interface ComponentProps { - incidentId: ObjectID; - incidentTimeline: Array<IncidentStateTimeline>; - incidentType: IncidentType; - onActionComplete: () => void; + incidentId: ObjectID; + incidentTimeline: Array<IncidentStateTimeline>; + incidentType: IncidentType; + onActionComplete: () => void; } const ChangeIncidentState: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [incidentTimeline, setIncidentTimeline] = useState< - IncidentStateTimeline | undefined - >(undefined); + const [incidentTimeline, setIncidentTimeline] = useState< + IncidentStateTimeline | undefined + >(undefined); - const [showModal, setShowModal] = useState<boolean>(false); + const [showModal, setShowModal] = useState<boolean>(false); - useEffect(() => { - for (const event of props.incidentTimeline) { - if ( - event.incidentState && - (event.incidentState.isAcknowledgedState || - event.incidentState.isResolvedState) && - props.incidentType === IncidentType.Ack && - event.id - ) { - setIncidentTimeline(event); - } + useEffect(() => { + for (const event of props.incidentTimeline) { + if ( + event.incidentState && + (event.incidentState.isAcknowledgedState || + event.incidentState.isResolvedState) && + props.incidentType === IncidentType.Ack && + event.id + ) { + setIncidentTimeline(event); + } - if ( - event.incidentState && - event.incidentState.isResolvedState && - props.incidentType === IncidentType.Resolve && - event.id - ) { - setIncidentTimeline(event); - } - } - }, [props.incidentTimeline]); - - if (incidentTimeline && incidentTimeline.createdAt) { - return ( - <div> - {incidentTimeline.createdByUser && ( - <UserElement user={incidentTimeline.createdByUser} /> - )} - {!incidentTimeline.createdByUser && ( - <p> - {props.incidentType === IncidentType.Ack - ? 'Acknowledged' - : 'Resolved'}{' '} - by OneUptime - </p> - )} - {OneUptimeDate.getDateAsLocalFormattedString( - incidentTimeline.createdAt - )} - </div> - ); + if ( + event.incidentState && + event.incidentState.isResolvedState && + props.incidentType === IncidentType.Resolve && + event.id + ) { + setIncidentTimeline(event); + } } + }, [props.incidentTimeline]); + if (incidentTimeline && incidentTimeline.createdAt) { return ( - <div className="-ml-3 mt-1"> - <Button - buttonSize={ButtonSize.Small} - title={ - props.incidentType === IncidentType.Ack - ? 'Acknowledge Incident' - : 'Resolve Incident' - } - icon={ - props.incidentType === IncidentType.Ack - ? IconProp.Circle - : IconProp.CheckCircle - } - buttonStyle={ - props.incidentType === IncidentType.Ack - ? ButtonStyleType.WARNING_OUTLINE - : ButtonStyleType.SUCCESS_OUTLINE - } - onClick={async () => { - setShowModal(true); - }} - /> - - {showModal && ( - <ModelFormModal - modelType={IncidentStateTimeline} - name={ - props.incidentType === IncidentType.Ack - ? 'Acknowledge Incident' - : 'Resolve Incident' - } - title={ - props.incidentType === IncidentType.Ack - ? 'Acknowledge Incident' - : 'Resolve Incident' - } - description={ - props.incidentType === IncidentType.Ack - ? 'Mark this incident as acknowledged.' - : 'Mark this incident as resolved.' - } - onClose={() => { - setShowModal(false); - }} - submitButtonText="Save" - onBeforeCreate={async (model: IncidentStateTimeline) => { - const projectId: ObjectID | undefined | null = - ProjectUtil.getCurrentProject()?.id; - - if (!projectId) { - throw new BadDataException('ProjectId not found.'); - } - - const incidentStates: ListResult<IncidentState> = - await ModelAPI.getList<IncidentState>({ - modelType: IncidentState, - query: { - projectId: projectId, - }, - limit: 99, - skip: 0, - select: { - _id: true, - isResolvedState: true, - isAcknowledgedState: true, - isCreatedState: true, - }, - sort: {}, - requestOptions: {}, - }); - - let stateId: ObjectID | null = null; - - for (const state of incidentStates.data) { - if ( - props.incidentType === IncidentType.Ack && - state.isAcknowledgedState - ) { - stateId = state.id; - break; - } - - if ( - props.incidentType === IncidentType.Resolve && - state.isResolvedState - ) { - stateId = state.id; - break; - } - } - - if (!stateId) { - throw new BadDataException( - 'Incident State not found.' - ); - } - - model.projectId = projectId; - model.incidentId = props.incidentId; - model.incidentStateId = stateId; - - return model; - }} - onSuccess={() => { - setShowModal(false); - props.onActionComplete(); - }} - formProps={{ - name: 'create-scheduled-maintenance-state-timeline', - modelType: IncidentStateTimeline, - id: 'create-scheduled-maintenance-state-timeline', - fields: [ - { - field: { - publicNote: true, - } as any, - fieldType: FormFieldSchemaType.Markdown, - description: - 'Post a public note about this state change to the status page.', - title: 'Public Note', - required: false, - overrideFieldKey: 'publicNote', - showEvenIfPermissionDoesNotExist: true, - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, - fieldType: FormFieldSchemaType.Checkbox, - description: - 'Notify subscribers of this state change.', - title: 'Notify Status Page Subscribers', - required: false, - defaultValue: true, - }, - ], - formType: FormType.Create, - }} - /> - )} - </div> + <div> + {incidentTimeline.createdByUser && ( + <UserElement user={incidentTimeline.createdByUser} /> + )} + {!incidentTimeline.createdByUser && ( + <p> + {props.incidentType === IncidentType.Ack + ? "Acknowledged" + : "Resolved"}{" "} + by OneUptime + </p> + )} + {OneUptimeDate.getDateAsLocalFormattedString( + incidentTimeline.createdAt, + )} + </div> ); + } + + return ( + <div className="-ml-3 mt-1"> + <Button + buttonSize={ButtonSize.Small} + title={ + props.incidentType === IncidentType.Ack + ? "Acknowledge Incident" + : "Resolve Incident" + } + icon={ + props.incidentType === IncidentType.Ack + ? IconProp.Circle + : IconProp.CheckCircle + } + buttonStyle={ + props.incidentType === IncidentType.Ack + ? ButtonStyleType.WARNING_OUTLINE + : ButtonStyleType.SUCCESS_OUTLINE + } + onClick={async () => { + setShowModal(true); + }} + /> + + {showModal && ( + <ModelFormModal + modelType={IncidentStateTimeline} + name={ + props.incidentType === IncidentType.Ack + ? "Acknowledge Incident" + : "Resolve Incident" + } + title={ + props.incidentType === IncidentType.Ack + ? "Acknowledge Incident" + : "Resolve Incident" + } + description={ + props.incidentType === IncidentType.Ack + ? "Mark this incident as acknowledged." + : "Mark this incident as resolved." + } + onClose={() => { + setShowModal(false); + }} + submitButtonText="Save" + onBeforeCreate={async (model: IncidentStateTimeline) => { + const projectId: ObjectID | undefined | null = + ProjectUtil.getCurrentProject()?.id; + + if (!projectId) { + throw new BadDataException("ProjectId not found."); + } + + const incidentStates: ListResult<IncidentState> = + await ModelAPI.getList<IncidentState>({ + modelType: IncidentState, + query: { + projectId: projectId, + }, + limit: 99, + skip: 0, + select: { + _id: true, + isResolvedState: true, + isAcknowledgedState: true, + isCreatedState: true, + }, + sort: {}, + requestOptions: {}, + }); + + let stateId: ObjectID | null = null; + + for (const state of incidentStates.data) { + if ( + props.incidentType === IncidentType.Ack && + state.isAcknowledgedState + ) { + stateId = state.id; + break; + } + + if ( + props.incidentType === IncidentType.Resolve && + state.isResolvedState + ) { + stateId = state.id; + break; + } + } + + if (!stateId) { + throw new BadDataException("Incident State not found."); + } + + model.projectId = projectId; + model.incidentId = props.incidentId; + model.incidentStateId = stateId; + + return model; + }} + onSuccess={() => { + setShowModal(false); + props.onActionComplete(); + }} + formProps={{ + name: "create-scheduled-maintenance-state-timeline", + modelType: IncidentStateTimeline, + id: "create-scheduled-maintenance-state-timeline", + fields: [ + { + field: { + publicNote: true, + } as any, + fieldType: FormFieldSchemaType.Markdown, + description: + "Post a public note about this state change to the status page.", + title: "Public Note", + required: false, + overrideFieldKey: "publicNote", + showEvenIfPermissionDoesNotExist: true, + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, + fieldType: FormFieldSchemaType.Checkbox, + description: "Notify subscribers of this state change.", + title: "Notify Status Page Subscribers", + required: false, + defaultValue: true, + }, + ], + formType: FormType.Create, + }} + /> + )} + </div> + ); }; export default ChangeIncidentState; diff --git a/Dashboard/src/Components/Incident/Incident.tsx b/Dashboard/src/Components/Incident/Incident.tsx index f4850f64de..829c8ee07b 100644 --- a/Dashboard/src/Components/Incident/Incident.tsx +++ b/Dashboard/src/Components/Incident/Incident.tsx @@ -1,37 +1,37 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import Link from 'CommonUI/src/Components/Link/Link'; -import Incident from 'Model/Models/Incident'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import Link from "CommonUI/src/Components/Link/Link"; +import Incident from "Model/Models/Incident"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - incident: Incident; - onNavigateComplete?: (() => void) | undefined; + incident: Incident; + onNavigateComplete?: (() => void) | undefined; } const IncidentElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.incident._id) { - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENT_VIEW] as Route, - { - modelId: new ObjectID(props.incident._id as string), - } - )} - > - <span>{props.incident.title}</span> - </Link> - ); - } + if (props.incident._id) { + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENT_VIEW] as Route, + { + modelId: new ObjectID(props.incident._id as string), + }, + )} + > + <span>{props.incident.title}</span> + </Link> + ); + } - return <span>{props.incident.title}</span>; + return <span>{props.incident.title}</span>; }; export default IncidentElement; diff --git a/Dashboard/src/Components/Incident/IncidentsTable.tsx b/Dashboard/src/Components/Incident/IncidentsTable.tsx index 107ca5bedf..b8d4113612 100644 --- a/Dashboard/src/Components/Incident/IncidentsTable.tsx +++ b/Dashboard/src/Components/Incident/IncidentsTable.tsx @@ -1,707 +1,670 @@ -import LabelsElement from '../../Components/Label/Labels'; -import MonitorsElement from '../../Components/Monitor/Monitors'; -import EventName from '../../Utils/EventName'; -import DashboardNavigation from '../../Utils/Navigation'; -import ProjectUser from '../../Utils/ProjectUser'; -import IncidentElement from './Incident'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { Black } from 'Common/Types/BrandColors'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import { ModalTableBulkDefaultActions } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import API from 'CommonUI/src/Utils/API/API'; -import Query from 'CommonUI/src/Utils/BaseDatabase/Query'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import GlobalEvents from 'CommonUI/src/Utils/GlobalEvents'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Incident from 'Model/Models/Incident'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentTemplate from 'Model/Models/IncidentTemplate'; -import IncidentTemplateOwnerTeam from 'Model/Models/IncidentTemplateOwnerTeam'; -import IncidentTemplateOwnerUser from 'Model/Models/IncidentTemplateOwnerUser'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import Team from 'Model/Models/Team'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import MonitorsElement from "../../Components/Monitor/Monitors"; +import EventName from "../../Utils/EventName"; +import DashboardNavigation from "../../Utils/Navigation"; +import ProjectUser from "../../Utils/ProjectUser"; +import IncidentElement from "./Incident"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { Black } from "Common/Types/BrandColors"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import { ModalTableBulkDefaultActions } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import API from "CommonUI/src/Utils/API/API"; +import Query from "CommonUI/src/Utils/BaseDatabase/Query"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import GlobalEvents from "CommonUI/src/Utils/GlobalEvents"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Incident from "Model/Models/Incident"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentTemplate from "Model/Models/IncidentTemplate"; +import IncidentTemplateOwnerTeam from "Model/Models/IncidentTemplateOwnerTeam"; +import IncidentTemplateOwnerUser from "Model/Models/IncidentTemplateOwnerUser"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import Team from "Model/Models/Team"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - query?: Query<Incident> | undefined; - viewPageRoute?: Route; - noItemsMessage?: string | undefined; - title?: string | undefined; - description?: string | undefined; - createInitialValues?: FormValues<Incident> | undefined; + query?: Query<Incident> | undefined; + viewPageRoute?: Route; + noItemsMessage?: string | undefined; + title?: string | undefined; + description?: string | undefined; + createInitialValues?: FormValues<Incident> | undefined; } const IncidentsTable: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [incidentTemplates, setIncidentTemplates] = useState< - Array<IncidentTemplate> - >([]); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); - const [showIncidentTemplateModal, setShowIncidentTemplateModal] = - useState<boolean>(false); - const [initialValuesForIncident, setInitialValuesForIncident] = - useState<JSONObject>({}); + const [incidentTemplates, setIncidentTemplates] = useState< + Array<IncidentTemplate> + >([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [showIncidentTemplateModal, setShowIncidentTemplateModal] = + useState<boolean>(false); + const [initialValuesForIncident, setInitialValuesForIncident] = + useState<JSONObject>({}); - const fetchIncidentTemplate: (id: ObjectID) => Promise<void> = async ( - id: ObjectID - ): Promise<void> => { - setError(''); - setIsLoading(true); + const fetchIncidentTemplate: (id: ObjectID) => Promise<void> = async ( + id: ObjectID, + ): Promise<void> => { + setError(""); + setIsLoading(true); - try { - //fetch incident template + try { + //fetch incident template - const incidentTemplate: IncidentTemplate | null = - await ModelAPI.getItem<IncidentTemplate>({ - modelType: IncidentTemplate, - id: id, - select: { - title: true, - description: true, - incidentSeverityId: true, - monitors: true, - onCallDutyPolicies: true, - labels: true, - changeMonitorStatusToId: true, - }, - }); + const incidentTemplate: IncidentTemplate | null = + await ModelAPI.getItem<IncidentTemplate>({ + modelType: IncidentTemplate, + id: id, + select: { + title: true, + description: true, + incidentSeverityId: true, + monitors: true, + onCallDutyPolicies: true, + labels: true, + changeMonitorStatusToId: true, + }, + }); - const teamsListResult: ListResult<IncidentTemplateOwnerTeam> = - await ModelAPI.getList<IncidentTemplateOwnerTeam>({ - modelType: IncidentTemplateOwnerTeam, - query: { - incidentTemplate: id, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - teamId: true, - }, - sort: {}, - }); + const teamsListResult: ListResult<IncidentTemplateOwnerTeam> = + await ModelAPI.getList<IncidentTemplateOwnerTeam>({ + modelType: IncidentTemplateOwnerTeam, + query: { + incidentTemplate: id, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + teamId: true, + }, + sort: {}, + }); - const usersListResult: ListResult<IncidentTemplateOwnerUser> = - await ModelAPI.getList<IncidentTemplateOwnerUser>({ - modelType: IncidentTemplateOwnerUser, - query: { - incidentTemplate: id, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - userId: true, - }, - sort: {}, - }); + const usersListResult: ListResult<IncidentTemplateOwnerUser> = + await ModelAPI.getList<IncidentTemplateOwnerUser>({ + modelType: IncidentTemplateOwnerUser, + query: { + incidentTemplate: id, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + userId: true, + }, + sort: {}, + }); - if (incidentTemplate) { - const initialValue: JSONObject = { - ...BaseModel.toJSONObject( - incidentTemplate, - IncidentTemplate - ), - incidentSeverity: - incidentTemplate.incidentSeverityId?.toString(), - monitors: incidentTemplate.monitors?.map( - (monitor: Monitor) => { - return monitor.id!.toString(); - } - ), - labels: incidentTemplate.labels?.map((label: Label) => { - return label.id!.toString(); - }), - changeMonitorStatusTo: - incidentTemplate.changeMonitorStatusToId?.toString(), - onCallDutyPolicies: - incidentTemplate.onCallDutyPolicies?.map( - (onCallPolicy: OnCallDutyPolicy) => { - return onCallPolicy.id!.toString(); - } - ), - ownerUsers: usersListResult.data.map( - (user: IncidentTemplateOwnerUser): string => { - return user.userId!.toString() || ''; - } - ), - ownerTeams: teamsListResult.data.map( - (team: IncidentTemplateOwnerTeam): string => { - return team.teamId!.toString() || ''; - } - ), - }; - - setInitialValuesForIncident(initialValue); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - setShowIncidentTemplateModal(false); - }; - - const fetchIncidentTemplates: () => Promise<void> = - async (): Promise<void> => { - setError(''); - setIsLoading(true); - setInitialValuesForIncident({}); - - try { - const listResult: ListResult<IncidentTemplate> = - await ModelAPI.getList<IncidentTemplate>({ - modelType: IncidentTemplate, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - templateName: true, - _id: true, - }, - sort: {}, - }); - - setIncidentTemplates(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); + if (incidentTemplate) { + const initialValue: JSONObject = { + ...BaseModel.toJSONObject(incidentTemplate, IncidentTemplate), + incidentSeverity: incidentTemplate.incidentSeverityId?.toString(), + monitors: incidentTemplate.monitors?.map((monitor: Monitor) => { + return monitor.id!.toString(); + }), + labels: incidentTemplate.labels?.map((label: Label) => { + return label.id!.toString(); + }), + changeMonitorStatusTo: + incidentTemplate.changeMonitorStatusToId?.toString(), + onCallDutyPolicies: incidentTemplate.onCallDutyPolicies?.map( + (onCallPolicy: OnCallDutyPolicy) => { + return onCallPolicy.id!.toString(); + }, + ), + ownerUsers: usersListResult.data.map( + (user: IncidentTemplateOwnerUser): string => { + return user.userId!.toString() || ""; + }, + ), + ownerTeams: teamsListResult.data.map( + (team: IncidentTemplateOwnerTeam): string => { + return team.teamId!.toString() || ""; + }, + ), }; - return ( - <> - <ModelTable<Incident> - name="Incidents" - bulkActions={{ - buttons: [ModalTableBulkDefaultActions.Delete], - }} - onCreateEditModalClose={(): void => { - setInitialValuesForIncident({}); - }} - modelType={Incident} - id="incidents-table" - isDeleteable={false} - showCreateForm={ - Object.keys(initialValuesForIncident).length > 0 - } - onCreateSuccess={(incident: Incident): Promise<Incident> => { - GlobalEvents.dispatchEvent( - EventName.ACTIVE_INCIDENTS_COUNT_REFRESH - ); + setInitialValuesForIncident(initialValue); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - return Promise.resolve(incident); - }} - query={props.query || {}} - isEditable={false} - isCreateable={true} - isViewable={true} - createInitialValues={ - Object.keys(initialValuesForIncident).length > 0 - ? initialValuesForIncident - : props.createInitialValues - } - cardProps={{ - title: props.title || 'Incidents', - buttons: [ - { - title: 'Create from Template', - icon: IconProp.Template, - buttonStyle: ButtonStyleType.OUTLINE, - onClick: async (): Promise<void> => { - setShowIncidentTemplateModal(true); - await fetchIncidentTemplates(); - }, - }, - ], - description: - props.description || - 'Here is a list of incidents for this project.', - }} - noItemsMessage={props.noItemsMessage || 'No incidents found.'} - formSteps={[ - { - title: 'Incident Details', - id: 'incident-details', - }, - { - title: 'Resources Affected', - id: 'resources-affected', - }, - { - title: 'On-Call', - id: 'on-call', - }, - { - title: 'Owners', - id: 'owners', - }, - { - title: 'More', - id: 'more', - }, - ]} - formFields={[ - { - field: { - title: true, - }, - title: 'Title', - fieldType: FormFieldSchemaType.Text, - stepId: 'incident-details', - required: true, - placeholder: 'Incident Title', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - stepId: 'incident-details', - fieldType: FormFieldSchemaType.Markdown, - required: true, - }, - { - field: { - incidentSeverity: true, - }, - title: 'Incident Severity', - stepId: 'incident-details', - description: 'What type of incident is this?', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: IncidentSeverity, - labelField: 'name', - valueField: '_id', - }, - required: true, - placeholder: 'Incident Severity', - }, - { - field: { - monitors: true, - }, - title: 'Monitors affected', - stepId: 'resources-affected', - description: - 'Select monitors affected by this incident.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Monitor, - labelField: 'name', - valueField: '_id', - }, - required: true, - placeholder: 'Monitors affected', - }, - { - field: { - onCallDutyPolicies: true, - }, - title: 'On-Call Policy', - stepId: 'on-call', - description: - 'Select on-call duty policy to execute when this incident is created.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: OnCallDutyPolicy, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select on-call policies', - }, - { - field: { - changeMonitorStatusTo: true, - }, - title: 'Change Monitor Status to ', - stepId: 'resources-affected', - description: - 'This will change the status of all the monitors attached to this incident.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: MonitorStatus, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Monitor Status', - }, - { - overrideField: { - ownerTeams: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Owner - Teams', - stepId: 'owners', - description: - 'Select which teams own this incident. They will be notified when the incident is created or updated.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select Teams', - overrideFieldKey: 'ownerTeams', - }, - { - overrideField: { - ownerUsers: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Owner - Users', - stepId: 'owners', - description: - 'Select which users own this incident. They will be notified when the incident is created or updated.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - required: false, - placeholder: 'Select Users', - overrideFieldKey: 'ownerUsers', - }, - { - field: { - labels: true, - }, + setIsLoading(false); + setShowIncidentTemplateModal(false); + }; - title: 'Labels ', - stepId: 'more', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedOnIncidentCreated: - true, - }, + const fetchIncidentTemplates: () => Promise<void> = + async (): Promise<void> => { + setError(""); + setIsLoading(true); + setInitialValuesForIncident({}); - title: 'Notify Status Page Subscribers', - stepId: 'more', - description: - 'Should status page subscribers be notified when this incident is created?', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - }, - ]} - showRefreshButton={true} - showViewIdButton={true} - viewPageRoute={props.viewPageRoute} - filters={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - incidentSeverity: { - name: true, - }, - }, - title: 'Severity', - type: FieldType.Entity, + try { + const listResult: ListResult<IncidentTemplate> = + await ModelAPI.getList<IncidentTemplate>({ + modelType: IncidentTemplate, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + templateName: true, + _id: true, + }, + sort: {}, + }); - filterEntityType: IncidentSeverity, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - currentIncidentState: { - name: true, - color: true, - }, - }, - title: 'State', - type: FieldType.Entity, + setIncidentTemplates(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - filterEntityType: IncidentState, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - monitors: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Monitors Affected', - type: FieldType.EntityArray, + setIsLoading(false); + }; - filterEntityType: Monitor, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Created', - type: FieldType.Date, - }, - { - field: { - labels: { - name: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, + return ( + <> + <ModelTable<Incident> + name="Incidents" + bulkActions={{ + buttons: [ModalTableBulkDefaultActions.Delete], + }} + onCreateEditModalClose={(): void => { + setInitialValuesForIncident({}); + }} + modelType={Incident} + id="incidents-table" + isDeleteable={false} + showCreateForm={Object.keys(initialValuesForIncident).length > 0} + onCreateSuccess={(incident: Incident): Promise<Incident> => { + GlobalEvents.dispatchEvent(EventName.ACTIVE_INCIDENTS_COUNT_REFRESH); - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Element, - getElement: (item: Incident): ReactElement => { - return <IncidentElement incident={item} />; - }, - }, - { - field: { - currentIncidentState: { - name: true, - color: true, - }, - }, - title: 'State', - type: FieldType.Entity, + return Promise.resolve(incident); + }} + query={props.query || {}} + isEditable={false} + isCreateable={true} + isViewable={true} + createInitialValues={ + Object.keys(initialValuesForIncident).length > 0 + ? initialValuesForIncident + : props.createInitialValues + } + cardProps={{ + title: props.title || "Incidents", + buttons: [ + { + title: "Create from Template", + icon: IconProp.Template, + buttonStyle: ButtonStyleType.OUTLINE, + onClick: async (): Promise<void> => { + setShowIncidentTemplateModal(true); + await fetchIncidentTemplates(); + }, + }, + ], + description: + props.description || + "Here is a list of incidents for this project.", + }} + noItemsMessage={props.noItemsMessage || "No incidents found."} + formSteps={[ + { + title: "Incident Details", + id: "incident-details", + }, + { + title: "Resources Affected", + id: "resources-affected", + }, + { + title: "On-Call", + id: "on-call", + }, + { + title: "Owners", + id: "owners", + }, + { + title: "More", + id: "more", + }, + ]} + formFields={[ + { + field: { + title: true, + }, + title: "Title", + fieldType: FormFieldSchemaType.Text, + stepId: "incident-details", + required: true, + placeholder: "Incident Title", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + stepId: "incident-details", + fieldType: FormFieldSchemaType.Markdown, + required: true, + }, + { + field: { + incidentSeverity: true, + }, + title: "Incident Severity", + stepId: "incident-details", + description: "What type of incident is this?", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: IncidentSeverity, + labelField: "name", + valueField: "_id", + }, + required: true, + placeholder: "Incident Severity", + }, + { + field: { + monitors: true, + }, + title: "Monitors affected", + stepId: "resources-affected", + description: "Select monitors affected by this incident.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Monitor, + labelField: "name", + valueField: "_id", + }, + required: true, + placeholder: "Monitors affected", + }, + { + field: { + onCallDutyPolicies: true, + }, + title: "On-Call Policy", + stepId: "on-call", + description: + "Select on-call duty policy to execute when this incident is created.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: OnCallDutyPolicy, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select on-call policies", + }, + { + field: { + changeMonitorStatusTo: true, + }, + title: "Change Monitor Status to ", + stepId: "resources-affected", + description: + "This will change the status of all the monitors attached to this incident.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: MonitorStatus, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Monitor Status", + }, + { + overrideField: { + ownerTeams: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Owner - Teams", + stepId: "owners", + description: + "Select which teams own this incident. They will be notified when the incident is created or updated.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select Teams", + overrideFieldKey: "ownerTeams", + }, + { + overrideField: { + ownerUsers: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Owner - Users", + stepId: "owners", + description: + "Select which users own this incident. They will be notified when the incident is created or updated.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + required: false, + placeholder: "Select Users", + overrideFieldKey: "ownerUsers", + }, + { + field: { + labels: true, + }, - getElement: (item: Incident): ReactElement => { - if (item['currentIncidentState']) { - return ( - <Pill - isMinimal={true} - color={ - item.currentIncidentState.color || - Black - } - text={ - item.currentIncidentState.name || - 'Unknown' - } - /> - ); - } + title: "Labels ", + stepId: "more", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedOnIncidentCreated: true, + }, - return <></>; - }, - }, - { - field: { - incidentSeverity: { - name: true, - color: true, - }, - }, + title: "Notify Status Page Subscribers", + stepId: "more", + description: + "Should status page subscribers be notified when this incident is created?", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + }, + ]} + showRefreshButton={true} + showViewIdButton={true} + viewPageRoute={props.viewPageRoute} + filters={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + incidentSeverity: { + name: true, + }, + }, + title: "Severity", + type: FieldType.Entity, - title: 'Severity', - type: FieldType.Entity, - getElement: (item: Incident): ReactElement => { - if (item['incidentSeverity']) { - return ( - <Pill - isMinimal={true} - color={ - item.incidentSeverity.color || Black - } - text={ - item.incidentSeverity.name || - 'Unknown' - } - /> - ); - } + filterEntityType: IncidentSeverity, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + currentIncidentState: { + name: true, + color: true, + }, + }, + title: "State", + type: FieldType.Entity, - return <></>; - }, - }, - { - field: { - monitors: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Monitors Affected', - type: FieldType.EntityArray, + filterEntityType: IncidentState, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + monitors: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Monitors Affected", + type: FieldType.EntityArray, - getElement: (item: Incident): ReactElement => { - return ( - <MonitorsElement - monitors={item['monitors'] || []} - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Created', - type: FieldType.DateTime, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, + filterEntityType: Monitor, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Created", + type: FieldType.Date, + }, + { + field: { + labels: { + name: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, - getElement: (item: Incident): ReactElement => { - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Element, + getElement: (item: Incident): ReactElement => { + return <IncidentElement incident={item} />; + }, + }, + { + field: { + currentIncidentState: { + name: true, + color: true, + }, + }, + title: "State", + type: FieldType.Entity, - {incidentTemplates.length === 0 && - showIncidentTemplateModal && - !isLoading && ( - <ConfirmModal - title={`No Incident Templates`} - description={`No incident templates have been created yet. You can create these in Project Settings > Incident Templates.`} - submitButtonText={'Close'} - onSubmit={() => { - return setShowIncidentTemplateModal(false); - }} - /> - )} + getElement: (item: Incident): ReactElement => { + if (item["currentIncidentState"]) { + return ( + <Pill + isMinimal={true} + color={item.currentIncidentState.color || Black} + text={item.currentIncidentState.name || "Unknown"} + /> + ); + } - {error && ( - <ConfirmModal - title={`Error`} - description={`${error}`} - submitButtonText={'Close'} - onSubmit={() => { - return setError(''); - }} - /> - )} + return <></>; + }, + }, + { + field: { + incidentSeverity: { + name: true, + color: true, + }, + }, - {showIncidentTemplateModal && incidentTemplates.length > 0 ? ( - <BasicFormModal<JSONObject> - title="Create Incident from Template" - isLoading={isLoading} - submitButtonText="Create from Template" - onClose={() => { - setShowIncidentTemplateModal(false); - setIsLoading(false); - }} - onSubmit={async (data: JSONObject) => { - await fetchIncidentTemplate( - data['incidentTemplateId'] as ObjectID - ); - }} - formProps={{ - initialValues: {}, - fields: [ - { - field: { - incidentTemplateId: true, - }, - title: 'Select Incident Template', - description: - 'Select an incident template to create an incident from.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEntityArray( - { - array: incidentTemplates, - labelField: 'templateName', - valueField: '_id', - } - ), - required: true, - placeholder: 'Select Template', - }, - ], - }} - /> - ) : ( - <> </> - )} - </> - ); + title: "Severity", + type: FieldType.Entity, + getElement: (item: Incident): ReactElement => { + if (item["incidentSeverity"]) { + return ( + <Pill + isMinimal={true} + color={item.incidentSeverity.color || Black} + text={item.incidentSeverity.name || "Unknown"} + /> + ); + } + + return <></>; + }, + }, + { + field: { + monitors: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Monitors Affected", + type: FieldType.EntityArray, + + getElement: (item: Incident): ReactElement => { + return <MonitorsElement monitors={item["monitors"] || []} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Created", + type: FieldType.DateTime, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + + getElement: (item: Incident): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + + {incidentTemplates.length === 0 && + showIncidentTemplateModal && + !isLoading && ( + <ConfirmModal + title={`No Incident Templates`} + description={`No incident templates have been created yet. You can create these in Project Settings > Incident Templates.`} + submitButtonText={"Close"} + onSubmit={() => { + return setShowIncidentTemplateModal(false); + }} + /> + )} + + {error && ( + <ConfirmModal + title={`Error`} + description={`${error}`} + submitButtonText={"Close"} + onSubmit={() => { + return setError(""); + }} + /> + )} + + {showIncidentTemplateModal && incidentTemplates.length > 0 ? ( + <BasicFormModal<JSONObject> + title="Create Incident from Template" + isLoading={isLoading} + submitButtonText="Create from Template" + onClose={() => { + setShowIncidentTemplateModal(false); + setIsLoading(false); + }} + onSubmit={async (data: JSONObject) => { + await fetchIncidentTemplate(data["incidentTemplateId"] as ObjectID); + }} + formProps={{ + initialValues: {}, + fields: [ + { + field: { + incidentTemplateId: true, + }, + title: "Select Incident Template", + description: + "Select an incident template to create an incident from.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: DropdownUtil.getDropdownOptionsFromEntityArray( + { + array: incidentTemplates, + labelField: "templateName", + valueField: "_id", + }, + ), + required: true, + placeholder: "Select Template", + }, + ], + }} + /> + ) : ( + <> </> + )} + </> + ); }; export default IncidentsTable; diff --git a/Dashboard/src/Components/Label/Label.tsx b/Dashboard/src/Components/Label/Label.tsx index 5f8f29cb92..ea381df606 100644 --- a/Dashboard/src/Components/Label/Label.tsx +++ b/Dashboard/src/Components/Label/Label.tsx @@ -1,24 +1,24 @@ -import { Black } from 'Common/Types/BrandColors'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import Label from 'Model/Models/Label'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { Black } from "Common/Types/BrandColors"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import Label from "Model/Models/Label"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - label: Label; + label: Label; } const LabelElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Pill - color={props.label.color || Black} - text={props.label.name || ''} - style={{ - marginRight: '5px', - }} - /> - ); + return ( + <Pill + color={props.label.color || Black} + text={props.label.name || ""} + style={{ + marginRight: "5px", + }} + /> + ); }; export default LabelElement; diff --git a/Dashboard/src/Components/Label/Labels.tsx b/Dashboard/src/Components/Label/Labels.tsx index 17690576b6..ee1c732d57 100644 --- a/Dashboard/src/Components/Label/Labels.tsx +++ b/Dashboard/src/Components/Label/Labels.tsx @@ -1,31 +1,31 @@ -import LabelElement from './Label'; -import TableColumnListComponent from 'CommonUI/src/Components/TableColumnList/TableColumnListComponent'; -import Label from 'Model/Models/Label'; -import React, { FunctionComponent, ReactElement } from 'react'; +import LabelElement from "./Label"; +import TableColumnListComponent from "CommonUI/src/Components/TableColumnList/TableColumnListComponent"; +import Label from "Model/Models/Label"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - labels: Array<Label>; + labels: Array<Label>; } const LabelsElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - // {/** >4 because 3 labels are shown by default and then the more text is shown */} - <TableColumnListComponent - items={props.labels} - moreText={props.labels.length > 4 ? 'more labels' : 'more label'} - className={props.labels.length > 0 ? '-mb-1 -mt-1' : ''} - getEachElement={(label: Label) => { - return ( - <div className={props.labels.length > 0 ? 'my-2' : ''}> - <LabelElement label={label} /> - </div> - ); - }} - noItemsMessage="No labels attached." - /> - ); + return ( + // {/** >4 because 3 labels are shown by default and then the more text is shown */} + <TableColumnListComponent + items={props.labels} + moreText={props.labels.length > 4 ? "more labels" : "more label"} + className={props.labels.length > 0 ? "-mb-1 -mt-1" : ""} + getEachElement={(label: Label) => { + return ( + <div className={props.labels.length > 0 ? "my-2" : ""}> + <LabelElement label={label} /> + </div> + ); + }} + noItemsMessage="No labels attached." + /> + ); }; export default LabelsElement; diff --git a/Dashboard/src/Components/Loader/Loader.tsx b/Dashboard/src/Components/Loader/Loader.tsx index b4fd3dba9c..f588256ba8 100644 --- a/Dashboard/src/Components/Loader/Loader.tsx +++ b/Dashboard/src/Components/Loader/Loader.tsx @@ -1,5 +1,5 @@ -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import React, { ReactElement } from 'react'; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import React, { ReactElement } from "react"; const Loader: ReactElement = <PageLoader isVisible={true} />; diff --git a/Dashboard/src/Components/Logs/LogsViewer.tsx b/Dashboard/src/Components/Logs/LogsViewer.tsx index 805ce55f61..8c9a6907f1 100644 --- a/Dashboard/src/Components/Logs/LogsViewer.tsx +++ b/Dashboard/src/Components/Logs/LogsViewer.tsx @@ -1,167 +1,165 @@ -import InBetween from 'Common/Types/BaseDatabase/InBetween'; -import Includes from 'Common/Types/BaseDatabase/Includes'; -import Search from 'Common/Types/BaseDatabase/Search'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import { ModelEventType } from 'Common/Utils/Realtime'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { FilterOption } from 'CommonUI/src/Components/LogsViewer/LogsFilters'; -import LogsViewer from 'CommonUI/src/Components/LogsViewer/LogsViewer'; -import API from 'CommonUI/src/Utils/API/API'; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import Includes from "Common/Types/BaseDatabase/Includes"; +import Search from "Common/Types/BaseDatabase/Search"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import { ModelEventType } from "Common/Utils/Realtime"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { FilterOption } from "CommonUI/src/Components/LogsViewer/LogsFilters"; +import LogsViewer from "CommonUI/src/Components/LogsViewer/LogsViewer"; +import API from "CommonUI/src/Utils/API/API"; import AnalyticsModelAPI, { - ListResult, -} from 'CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI'; -import Query from 'CommonUI/src/Utils/BaseDatabase/Query'; -import Select from 'CommonUI/src/Utils/BaseDatabase/Select'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Realtime from 'CommonUI/src/Utils/Realtime'; -import Log from 'Model/AnalyticsModels/Log'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; + ListResult, +} from "CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI"; +import Query from "CommonUI/src/Utils/BaseDatabase/Query"; +import Select from "CommonUI/src/Utils/BaseDatabase/Select"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Realtime from "CommonUI/src/Utils/Realtime"; +import Log from "Model/AnalyticsModels/Log"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - id: string; - telemetryServiceIds?: Array<ObjectID> | undefined; - enableRealtime?: boolean; - traceIds?: Array<string>; - spanIds?: Array<string>; - showFilters?: boolean | undefined; - noLogsMessage?: string | undefined; + id: string; + telemetryServiceIds?: Array<ObjectID> | undefined; + enableRealtime?: boolean; + traceIds?: Array<string>; + spanIds?: Array<string>; + showFilters?: boolean | undefined; + noLogsMessage?: string | undefined; } const DashboardLogsViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [logs, setLogs] = React.useState<Array<Log>>([]); - const [error, setError] = React.useState<string>(''); - const [isLoading, setIsLoading] = React.useState<boolean>(false); - const [filterOptions, setFilterOptions] = React.useState<FilterOption>({}); + const [logs, setLogs] = React.useState<Array<Log>>([]); + const [error, setError] = React.useState<string>(""); + const [isLoading, setIsLoading] = React.useState<boolean>(false); + const [filterOptions, setFilterOptions] = React.useState<FilterOption>({}); - const select: Select<Log> = { - body: true, - time: true, - projectId: true, - serviceId: true, - spanId: true, - traceId: true, - severityText: true, - attributes: true, - }; + const select: Select<Log> = { + body: true, + time: true, + projectId: true, + serviceId: true, + spanId: true, + traceId: true, + severityText: true, + attributes: true, + }; - type GetQueryFunction = () => Query<Log>; + type GetQueryFunction = () => Query<Log>; - const getQuery: GetQueryFunction = (): Query<Log> => { - const query: Query<Log> = {}; + const getQuery: GetQueryFunction = (): Query<Log> => { + const query: Query<Log> = {}; - if (props.telemetryServiceIds && props.telemetryServiceIds.length > 0) { - query.serviceId = new Includes(props.telemetryServiceIds); - } - - if (filterOptions.searchText) { - query.body = new Search(filterOptions.searchText); - } - - if (filterOptions.endTime && filterOptions.startTime) { - query.createdAt = new InBetween( - filterOptions.startTime, - filterOptions.endTime - ); - } - - if (filterOptions.logSeverity) { - query.severityText = filterOptions.logSeverity; - } - - if (props.traceIds && props.traceIds.length > 0) { - query.traceId = new Includes(props.traceIds); - } - - if (props.spanIds && props.spanIds.length > 0) { - query.spanId = new Includes(props.spanIds); - } - - return query; - }; - - useEffect(() => { - fetchItems().catch((err: unknown) => { - setError(API.getFriendlyMessage(err)); - }); - }, [filterOptions]); - - const fetchItems: PromiseVoidFunction = async (): Promise<void> => { - setError(''); - setIsLoading(true); - - try { - const listResult: ListResult<Log> = - await AnalyticsModelAPI.getList<Log>({ - modelType: Log, - query: getQuery(), - limit: LIMIT_PER_PROJECT, - skip: 0, - select: select, - sort: { - time: SortOrder.Descending, - }, - requestOptions: {}, - }); - - // reverse the logs so that the newest logs are at the bottom - listResult.data.reverse(); - - setLogs(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - useEffect(() => { - if (!props.enableRealtime) { - return; - } - - const disconnectFunction: () => void = - Realtime.listenToAnalyticsModelEvent( - { - modelType: Log, - query: {}, - eventType: ModelEventType.Create, - tenantId: ProjectUtil.getCurrentProjectId()!, - select: select, - }, - (model: Log) => { - setLogs((logs: Array<Log>) => { - return [...logs, model]; - }); - } - ); - - return () => { - disconnectFunction(); - }; - }, []); - - if (error) { - return <ErrorMessage error={error} />; + if (props.telemetryServiceIds && props.telemetryServiceIds.length > 0) { + query.serviceId = new Includes(props.telemetryServiceIds); } - return ( - <div id={props.id}> - <LogsViewer - isLoading={isLoading} - onFilterChanged={(filterOptions: FilterOption) => { - setFilterOptions(filterOptions); - }} - logs={logs} - showFilters={props.showFilters} - noLogsMessage={props.noLogsMessage} - /> - </div> + if (filterOptions.searchText) { + query.body = new Search(filterOptions.searchText); + } + + if (filterOptions.endTime && filterOptions.startTime) { + query.createdAt = new InBetween( + filterOptions.startTime, + filterOptions.endTime, + ); + } + + if (filterOptions.logSeverity) { + query.severityText = filterOptions.logSeverity; + } + + if (props.traceIds && props.traceIds.length > 0) { + query.traceId = new Includes(props.traceIds); + } + + if (props.spanIds && props.spanIds.length > 0) { + query.spanId = new Includes(props.spanIds); + } + + return query; + }; + + useEffect(() => { + fetchItems().catch((err: unknown) => { + setError(API.getFriendlyMessage(err)); + }); + }, [filterOptions]); + + const fetchItems: PromiseVoidFunction = async (): Promise<void> => { + setError(""); + setIsLoading(true); + + try { + const listResult: ListResult<Log> = await AnalyticsModelAPI.getList<Log>({ + modelType: Log, + query: getQuery(), + limit: LIMIT_PER_PROJECT, + skip: 0, + select: select, + sort: { + time: SortOrder.Descending, + }, + requestOptions: {}, + }); + + // reverse the logs so that the newest logs are at the bottom + listResult.data.reverse(); + + setLogs(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + if (!props.enableRealtime) { + return; + } + + const disconnectFunction: () => void = Realtime.listenToAnalyticsModelEvent( + { + modelType: Log, + query: {}, + eventType: ModelEventType.Create, + tenantId: ProjectUtil.getCurrentProjectId()!, + select: select, + }, + (model: Log) => { + setLogs((logs: Array<Log>) => { + return [...logs, model]; + }); + }, ); + + return () => { + disconnectFunction(); + }; + }, []); + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <div id={props.id}> + <LogsViewer + isLoading={isLoading} + onFilterChanged={(filterOptions: FilterOption) => { + setFilterOptions(filterOptions); + }} + logs={logs} + showFilters={props.showFilters} + noLogsMessage={props.noLogsMessage} + /> + </div> + ); }; export default DashboardLogsViewer; diff --git a/Dashboard/src/Components/MasterPage/MasterPage.tsx b/Dashboard/src/Components/MasterPage/MasterPage.tsx index bc19683d3f..9d0a208661 100644 --- a/Dashboard/src/Components/MasterPage/MasterPage.tsx +++ b/Dashboard/src/Components/MasterPage/MasterPage.tsx @@ -1,119 +1,113 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Footer from '../Footer/Footer'; -import Header from '../Header/Header'; -import NavBar from '../NavBar/NavBar'; -import Route from 'Common/Types/API/Route'; -import { SubscriptionStatusUtil } from 'Common/Types/Billing/SubscriptionStatus'; -import SSOAuthorizationException from 'Common/Types/Exception/SsoAuthorizationException'; -import Link from 'CommonUI/src/Components/Link/Link'; -import MasterPage from 'CommonUI/src/Components/MasterPage/MasterPage'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Footer from "../Footer/Footer"; +import Header from "../Header/Header"; +import NavBar from "../NavBar/NavBar"; +import Route from "Common/Types/API/Route"; +import { SubscriptionStatusUtil } from "Common/Types/Billing/SubscriptionStatus"; +import SSOAuthorizationException from "Common/Types/Exception/SsoAuthorizationException"; +import Link from "CommonUI/src/Components/Link/Link"; +import MasterPage from "CommonUI/src/Components/MasterPage/MasterPage"; import TopAlert, { - TopAlertType, -} from 'CommonUI/src/Components/TopAlert/TopAlert'; -import { BILLING_ENABLED } from 'CommonUI/src/Config'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Project from 'Model/Models/Project'; -import React, { FunctionComponent, ReactElement } from 'react'; + TopAlertType, +} from "CommonUI/src/Components/TopAlert/TopAlert"; +import { BILLING_ENABLED } from "CommonUI/src/Config"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Project from "Model/Models/Project"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; - isLoading: boolean; - projects: Array<Project>; - error: string; - onProjectSelected: (project: Project) => void; - showProjectModal: boolean; - paymentMethodsCount?: number | undefined; - onProjectModalClose: () => void; - selectedProject: Project | null; - hideNavBarOn: Array<Route>; + children: ReactElement | Array<ReactElement>; + isLoading: boolean; + projects: Array<Project>; + error: string; + onProjectSelected: (project: Project) => void; + showProjectModal: boolean; + paymentMethodsCount?: number | undefined; + onProjectModalClose: () => void; + selectedProject: Project | null; + hideNavBarOn: Array<Route>; } const DashboardMasterPage: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let isOnHideNavbarPage: boolean = false; + let isOnHideNavbarPage: boolean = false; - for (const route of props.hideNavBarOn) { - if (Navigation.isOnThisPage(route)) { - isOnHideNavbarPage = true; - } + for (const route of props.hideNavBarOn) { + if (Navigation.isOnThisPage(route)) { + isOnHideNavbarPage = true; } + } - let error: string = ''; + let error: string = ""; - if (props.error && SSOAuthorizationException.isException(props.error)) { - Navigation.navigate( - RouteUtil.populateRouteParams( - RouteMap[PageMap.PROJECT_SSO] as Route - ) - ); - } else { - error = props.error; - } - - let isSubscriptionInactive: boolean = false; - - if (props.selectedProject) { - const isMeteredSubscriptionInactive: boolean = - SubscriptionStatusUtil.isSubscriptionInactive( - props.selectedProject?.paymentProviderMeteredSubscriptionStatus - ); - const isProjectSubscriptionInactive: boolean = - SubscriptionStatusUtil.isSubscriptionInactive( - props.selectedProject?.paymentProviderSubscriptionStatus - ); - - isSubscriptionInactive = - isMeteredSubscriptionInactive || isProjectSubscriptionInactive; - } - - return ( - <div> - {BILLING_ENABLED && isSubscriptionInactive && ( - <TopAlert - alertType={TopAlertType.DANGER} - title="Your project is not active because some invoices are unpaid." - description={ - <Link - className="underline" - to={RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_BILLING_INVOICES - ] as Route - )} - > - Click here to pay your unpaid invoices. - </Link> - } - /> - )} - - <MasterPage - footer={<Footer />} - header={ - <Header - projects={props.projects} - onProjectSelected={props.onProjectSelected} - showProjectModal={props.showProjectModal} - onProjectModalClose={props.onProjectModalClose} - selectedProject={props.selectedProject || null} - paymentMethodsCount={props.paymentMethodsCount} - /> - } - navBar={ - <NavBar - show={props.projects.length > 0 && !isOnHideNavbarPage} - /> - } - isLoading={props.isLoading} - error={error} - className="flex flex-col h-screen justify-between" - > - {props.children} - </MasterPage> - </div> + if (props.error && SSOAuthorizationException.isException(props.error)) { + Navigation.navigate( + RouteUtil.populateRouteParams(RouteMap[PageMap.PROJECT_SSO] as Route), ); + } else { + error = props.error; + } + + let isSubscriptionInactive: boolean = false; + + if (props.selectedProject) { + const isMeteredSubscriptionInactive: boolean = + SubscriptionStatusUtil.isSubscriptionInactive( + props.selectedProject?.paymentProviderMeteredSubscriptionStatus, + ); + const isProjectSubscriptionInactive: boolean = + SubscriptionStatusUtil.isSubscriptionInactive( + props.selectedProject?.paymentProviderSubscriptionStatus, + ); + + isSubscriptionInactive = + isMeteredSubscriptionInactive || isProjectSubscriptionInactive; + } + + return ( + <div> + {BILLING_ENABLED && isSubscriptionInactive && ( + <TopAlert + alertType={TopAlertType.DANGER} + title="Your project is not active because some invoices are unpaid." + description={ + <Link + className="underline" + to={RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_BILLING_INVOICES] as Route, + )} + > + Click here to pay your unpaid invoices. + </Link> + } + /> + )} + + <MasterPage + footer={<Footer />} + header={ + <Header + projects={props.projects} + onProjectSelected={props.onProjectSelected} + showProjectModal={props.showProjectModal} + onProjectModalClose={props.onProjectModalClose} + selectedProject={props.selectedProject || null} + paymentMethodsCount={props.paymentMethodsCount} + /> + } + navBar={ + <NavBar show={props.projects.length > 0 && !isOnHideNavbarPage} /> + } + isLoading={props.isLoading} + error={error} + className="flex flex-col h-screen justify-between" + > + {props.children} + </MasterPage> + </div> + ); }; export default DashboardMasterPage; diff --git a/Dashboard/src/Components/Metrics/MetricVIew.tsx b/Dashboard/src/Components/Metrics/MetricVIew.tsx index 12ccc31157..60d062ed5c 100644 --- a/Dashboard/src/Components/Metrics/MetricVIew.tsx +++ b/Dashboard/src/Components/Metrics/MetricVIew.tsx @@ -1,100 +1,100 @@ -import ObjectID from 'Common/Types/ObjectID'; +import ObjectID from "Common/Types/ObjectID"; import LineChart, { - AxisType, - XScalePrecision, - XScaleType, - YScaleType, -} from 'CommonUI/src/Components/Charts/Line/LineChart'; -import FiltersForm from 'CommonUI/src/Components/Filters/FiltersForm'; -import FilterData from 'CommonUI/src/Components/Filters/Types/FilterData'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Metric from 'Model/AnalyticsModels/Metric'; + AxisType, + XScalePrecision, + XScaleType, + YScaleType, +} from "CommonUI/src/Components/Charts/Line/LineChart"; +import FiltersForm from "CommonUI/src/Components/Filters/FiltersForm"; +import FilterData from "CommonUI/src/Components/Filters/Types/FilterData"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Metric from "Model/AnalyticsModels/Metric"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useEffect, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useEffect, +} from "react"; export interface ComponentProps { - metricName: string; - serviceId: ObjectID; + metricName: string; + serviceId: ObjectID; } const MetricView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [filterData, setFilterData] = React.useState<FilterData<Metric>>({ - name: props.metricName, - serviceId: props.serviceId, - }); + const [filterData, setFilterData] = React.useState<FilterData<Metric>>({ + name: props.metricName, + serviceId: props.serviceId, + }); - // const [isLoading, setIsLoading] = React.useState<boolean>(true); + // const [isLoading, setIsLoading] = React.useState<boolean>(true); - // const [values, setValues] = React.useState<Metric[]>([]); + // const [values, setValues] = React.useState<Metric[]>([]); - useEffect(() => {}, []); + useEffect(() => {}, []); - return ( - <Fragment> - <div> - <FiltersForm<Metric> - showFilter={true} - id="metrics-filter" - filterData={filterData} - onFilterChanged={(filterData: FilterData<Metric>) => { - setFilterData(filterData); - }} - filters={[ - { - key: 'name', - title: 'Name', - type: FieldType.Text, - }, - { - key: 'createdAt', - title: 'Created At', - type: FieldType.Date, - }, - { - key: 'serviceId', - title: 'Service', - type: FieldType.Dropdown, - filterDropdownOptions: [], - }, - ]} - /> + return ( + <Fragment> + <div> + <FiltersForm<Metric> + showFilter={true} + id="metrics-filter" + filterData={filterData} + onFilterChanged={(filterData: FilterData<Metric>) => { + setFilterData(filterData); + }} + filters={[ + { + key: "name", + title: "Name", + type: FieldType.Text, + }, + { + key: "createdAt", + title: "Created At", + type: FieldType.Date, + }, + { + key: "serviceId", + title: "Service", + type: FieldType.Dropdown, + filterDropdownOptions: [], + }, + ]} + /> - <LineChart - xScale={{ - type: XScaleType.TIME, - max: 'auto', - min: 'auto', - precision: XScalePrecision.MINUTE, - }} - yScale={{ - type: YScaleType.LINEAR, - min: 'auto', - max: 'auto', - }} - axisBottom={{ - type: AxisType.Time, - legend: 'Time', - }} - axisLeft={{ - type: AxisType.Number, - legend: 'Value', - }} - data={[ - { - seriesName: props.metricName, - data: [{ x: new Date(), y: 0 }], - }, - ]} - /> - </div> - </Fragment> - ); + <LineChart + xScale={{ + type: XScaleType.TIME, + max: "auto", + min: "auto", + precision: XScalePrecision.MINUTE, + }} + yScale={{ + type: YScaleType.LINEAR, + min: "auto", + max: "auto", + }} + axisBottom={{ + type: AxisType.Time, + legend: "Time", + }} + axisLeft={{ + type: AxisType.Number, + legend: "Value", + }} + data={[ + { + seriesName: props.metricName, + data: [{ x: new Date(), y: 0 }], + }, + ]} + /> + </div> + </Fragment> + ); }; export default MetricView; diff --git a/Dashboard/src/Components/Monitor/DisabledWarning.tsx b/Dashboard/src/Components/Monitor/DisabledWarning.tsx index 57ff5e5af7..c2b026f6d1 100644 --- a/Dashboard/src/Components/Monitor/DisabledWarning.tsx +++ b/Dashboard/src/Components/Monitor/DisabledWarning.tsx @@ -1,79 +1,79 @@ -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Monitor from 'Model/Models/Monitor'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; -import { useAsyncEffect } from 'use-async-effect'; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import Alert, { AlertType } from "CommonUI/src/Components/Alerts/Alert"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Monitor from "Model/Models/Monitor"; +import React, { FunctionComponent, ReactElement, useState } from "react"; +import { useAsyncEffect } from "use-async-effect"; export interface ComponentProps { - monitorId: ObjectID; - refreshToggle?: boolean | undefined; + monitorId: ObjectID; + refreshToggle?: boolean | undefined; } const DisabledWarning: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isDisabled, setIsDisabled] = useState<boolean>(false); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [message, setMessage] = useState<string>(''); + const [isDisabled, setIsDisabled] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [message, setMessage] = useState<string>(""); - useAsyncEffect(async () => { - setIsLoading(true); + useAsyncEffect(async () => { + setIsLoading(true); - const monitor: Monitor | null = await ModelAPI.getItem({ - modelType: Monitor, - id: props.monitorId, - select: { - disableActiveMonitoring: true, - disableActiveMonitoringBecauseOfManualIncident: true, - disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: true, - monitorType: true, - }, - }); + const monitor: Monitor | null = await ModelAPI.getItem({ + modelType: Monitor, + id: props.monitorId, + select: { + disableActiveMonitoring: true, + disableActiveMonitoringBecauseOfManualIncident: true, + disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: true, + monitorType: true, + }, + }); - if (monitor?.monitorType === MonitorType.Manual) { - setIsLoading(false); - return; - } - - if (monitor?.disableActiveMonitoring) { - setIsDisabled(true); - setMessage( - 'We are not monitoring this monitor since it is disabled. To enable active monitoring, please go to Settings.' - ); - } else if (monitor?.disableActiveMonitoringBecauseOfManualIncident) { - setIsDisabled(true); - setMessage( - 'We are not monitoring this monitor since it is disabled because of an active incident. To enable active monitoring, please resolve the incident.' - ); - } else if ( - monitor?.disableActiveMonitoringBecauseOfScheduledMaintenanceEvent - ) { - setIsDisabled(true); - setMessage( - 'We are not monitoring this monitor since it is disabled because of an ongoing scheduled maintenance event. To enable active monitoring, please resolve the scheduled maintenance event.' - ); - } - - setIsLoading(false); - }, [props.refreshToggle]); - - if (isLoading) { - return <></>; + if (monitor?.monitorType === MonitorType.Manual) { + setIsLoading(false); + return; } - if (isDisabled) { - return ( - <Alert - type={AlertType.DANGER} - strongTitle="This monitor is disabled" - title={message} - /> - ); + if (monitor?.disableActiveMonitoring) { + setIsDisabled(true); + setMessage( + "We are not monitoring this monitor since it is disabled. To enable active monitoring, please go to Settings.", + ); + } else if (monitor?.disableActiveMonitoringBecauseOfManualIncident) { + setIsDisabled(true); + setMessage( + "We are not monitoring this monitor since it is disabled because of an active incident. To enable active monitoring, please resolve the incident.", + ); + } else if ( + monitor?.disableActiveMonitoringBecauseOfScheduledMaintenanceEvent + ) { + setIsDisabled(true); + setMessage( + "We are not monitoring this monitor since it is disabled because of an ongoing scheduled maintenance event. To enable active monitoring, please resolve the scheduled maintenance event.", + ); } + setIsLoading(false); + }, [props.refreshToggle]); + + if (isLoading) { return <></>; + } + + if (isDisabled) { + return ( + <Alert + type={AlertType.DANGER} + strongTitle="This monitor is disabled" + title={message} + /> + ); + } + + return <></>; }; export default DisabledWarning; diff --git a/Dashboard/src/Components/Monitor/IncomingRequestMonitor/IncomingMonitorLink.tsx b/Dashboard/src/Components/Monitor/IncomingRequestMonitor/IncomingMonitorLink.tsx index d58c27b80b..e18409b20c 100644 --- a/Dashboard/src/Components/Monitor/IncomingRequestMonitor/IncomingMonitorLink.tsx +++ b/Dashboard/src/Components/Monitor/IncomingRequestMonitor/IncomingMonitorLink.tsx @@ -1,43 +1,42 @@ -import URL from 'Common/Types/API/URL'; -import ObjectID from 'Common/Types/ObjectID'; -import Card from 'CommonUI/src/Components/Card/Card'; -import Link from 'CommonUI/src/Components/Link/Link'; -import { HOST, HTTP_PROTOCOL } from 'CommonUI/src/Config'; -import React, { FunctionComponent, ReactElement } from 'react'; +import URL from "Common/Types/API/URL"; +import ObjectID from "Common/Types/ObjectID"; +import Card from "CommonUI/src/Components/Card/Card"; +import Link from "CommonUI/src/Components/Link/Link"; +import { HOST, HTTP_PROTOCOL } from "CommonUI/src/Config"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - secretKey: ObjectID; + secretKey: ObjectID; } const IncomingMonitorLink: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <> - <Card - title={`Incoming Request URL / Heartbeat URL`} - description={ - <span> - Please send inbound heartbeat GET or POST requests to - this URL{' '} - <Link - openInNewTab={true} - to={new URL(HTTP_PROTOCOL, HOST) - .addRoute('/heartbeat') - .addRoute(`/${props.secretKey.toString()}`)} - > - <span> - {new URL(HTTP_PROTOCOL, HOST) - .addRoute('/heartbeat') - .addRoute(`/${props.secretKey.toString()}`) - .toString()} - </span> - </Link> - </span> - } - /> - </> - ); + return ( + <> + <Card + title={`Incoming Request URL / Heartbeat URL`} + description={ + <span> + Please send inbound heartbeat GET or POST requests to this URL{" "} + <Link + openInNewTab={true} + to={new URL(HTTP_PROTOCOL, HOST) + .addRoute("/heartbeat") + .addRoute(`/${props.secretKey.toString()}`)} + > + <span> + {new URL(HTTP_PROTOCOL, HOST) + .addRoute("/heartbeat") + .addRoute(`/${props.secretKey.toString()}`) + .toString()} + </span> + </Link> + </span> + } + /> + </> + ); }; export default IncomingMonitorLink; diff --git a/Dashboard/src/Components/Monitor/Monitor.tsx b/Dashboard/src/Components/Monitor/Monitor.tsx index bd9d033141..ab40ed04f9 100644 --- a/Dashboard/src/Components/Monitor/Monitor.tsx +++ b/Dashboard/src/Components/Monitor/Monitor.tsx @@ -1,50 +1,47 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import Icon from 'CommonUI/src/Components/Icon/Icon'; -import Link from 'CommonUI/src/Components/Link/Link'; -import Monitor from 'Model/Models/Monitor'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import Icon from "CommonUI/src/Components/Icon/Icon"; +import Link from "CommonUI/src/Components/Link/Link"; +import Monitor from "Model/Models/Monitor"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitor: Monitor; - onNavigateComplete?: (() => void) | undefined; - showIcon?: boolean; + monitor: Monitor; + onNavigateComplete?: (() => void) | undefined; + showIcon?: boolean; } const MonitorElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.monitor._id) { - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_VIEW] as Route, - { - modelId: new ObjectID(props.monitor._id as string), - } - )} - > - <span className="flex"> - {props.showIcon ? ( - <Icon - icon={IconProp.AltGlobe} - className="w-5 h-5 mr-1" - /> - ) : ( - <></> - )}{' '} - {props.monitor.name} - </span> - </Link> - ); - } + if (props.monitor._id) { + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW] as Route, + { + modelId: new ObjectID(props.monitor._id as string), + }, + )} + > + <span className="flex"> + {props.showIcon ? ( + <Icon icon={IconProp.AltGlobe} className="w-5 h-5 mr-1" /> + ) : ( + <></> + )}{" "} + {props.monitor.name} + </span> + </Link> + ); + } - return <span>{props.monitor.name}</span>; + return <span>{props.monitor.name}</span>; }; export default MonitorElement; diff --git a/Dashboard/src/Components/Monitor/MonitorCharts/MonitorChart.tsx b/Dashboard/src/Components/Monitor/MonitorCharts/MonitorChart.tsx index 77abcd072e..b8cd474d9c 100644 --- a/Dashboard/src/Components/Monitor/MonitorCharts/MonitorChart.tsx +++ b/Dashboard/src/Components/Monitor/MonitorCharts/MonitorChart.tsx @@ -1,361 +1,354 @@ -import MonitorChartTooltip from './MonitorChartTooltip'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import { CheckOn } from 'Common/Types/Monitor/CriteriaFilter'; -import Text from 'Common/Types/Text'; +import MonitorChartTooltip from "./MonitorChartTooltip"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import { CheckOn } from "Common/Types/Monitor/CriteriaFilter"; +import Text from "Common/Types/Text"; import { - Chart, - ChartType, -} from 'CommonUI/src/Components/Charts/ChartGroup/ChartGroup'; + Chart, + ChartType, +} from "CommonUI/src/Components/Charts/ChartGroup/ChartGroup"; import { - AxisBottom, - AxisLeft, - AxisType, - ChartCurve, - LineChartData, - LineChartDataItem, - LineChartPoint, - XScale, - XScalePrecision, - XScaleType, - YScale, - YScaleType, -} from 'CommonUI/src/Components/Charts/Line/LineChart'; + AxisBottom, + AxisLeft, + AxisType, + ChartCurve, + LineChartData, + LineChartDataItem, + LineChartPoint, + XScale, + XScalePrecision, + XScaleType, + YScale, + YScaleType, +} from "CommonUI/src/Components/Charts/Line/LineChart"; import MonitorMetricsByMinute, { - MonitorMetricsMiscData, -} from 'Model/AnalyticsModels/MonitorMetricsByMinute'; -import Probe from 'Model/Models/Probe'; -import React from 'react'; + MonitorMetricsMiscData, +} from "Model/AnalyticsModels/MonitorMetricsByMinute"; +import Probe from "Model/Models/Probe"; +import React from "react"; export class MonitorCharts { - public static getDataForCharts(data: { - monitorMetricsByMinute: Array<MonitorMetricsByMinute>; - checkOn: CheckOn; - miscData: MonitorMetricsMiscData | undefined; - }): Array<LineChartDataItem> { - return data.monitorMetricsByMinute - .filter((item: MonitorMetricsByMinute) => { - return ( - item.metricType === data.checkOn && - JSONFunctions.isEqualObject( - item.miscData as JSONObject, - data.miscData as JSONObject - ) - ); - }) - .map((item: MonitorMetricsByMinute) => { - return { - x: item.createdAt!, - y: item.metricValue!, - }; - }); - } - - public static getDistinctMiscDataFromMonitorMetricsByMinute(data: { - monitorMetricsByMinute: Array<MonitorMetricsByMinute>; - checkOn: CheckOn; - }): Array<MonitorMetricsMiscData> { - const miscData: Array<MonitorMetricsMiscData | undefined> = - data.monitorMetricsByMinute - .filter((item: MonitorMetricsByMinute) => { - return item.metricType === data.checkOn; - }) - .map((item: MonitorMetricsByMinute) => { - return item.miscData || undefined; - }); - - return miscData - .filter( - ( - value: MonitorMetricsMiscData | undefined, - index: number, - self: Array<MonitorMetricsMiscData | undefined> - ) => { - return ( - self.findIndex( - (t: MonitorMetricsMiscData | undefined) => { - return JSONFunctions.isEqualObject( - t as JSONObject, - value as JSONObject - ); - } - ) === index - ); - } - ) - .filter((item: MonitorMetricsMiscData | undefined) => { - return Boolean(item); - }) as Array<MonitorMetricsMiscData>; - } - - public static getSeriesName(data: { - checkOn: CheckOn; - miscData: MonitorMetricsMiscData | undefined; - probes: Array<Probe>; - }): string { - if (data.miscData) { - if (data.miscData.diskPath) { - return data.miscData.diskPath; - } - - if (data.miscData.probeId) { - const probe: Probe | undefined = data.probes.find( - (probe: Probe) => { - return ( - probe._id?.toString() === - data.miscData?.probeId?.toString() - ); - } - ); - - if (probe) { - return probe.name || 'Probe'; - } - } - } - - return data.checkOn; - } - - public static getChartData(data: { - monitorMetricsByMinute: Array<MonitorMetricsByMinute>; - checkOn: CheckOn; - miscData: MonitorMetricsMiscData | undefined; - probes?: Array<Probe>; - }): LineChartData { - const { monitorMetricsByMinute, checkOn } = data; - + public static getDataForCharts(data: { + monitorMetricsByMinute: Array<MonitorMetricsByMinute>; + checkOn: CheckOn; + miscData: MonitorMetricsMiscData | undefined; + }): Array<LineChartDataItem> { + return data.monitorMetricsByMinute + .filter((item: MonitorMetricsByMinute) => { + return ( + item.metricType === data.checkOn && + JSONFunctions.isEqualObject( + item.miscData as JSONObject, + data.miscData as JSONObject, + ) + ); + }) + .map((item: MonitorMetricsByMinute) => { return { - seriesName: this.getSeriesName({ - checkOn: checkOn, - miscData: data.miscData, - probes: data.probes || [], - }), - data: MonitorCharts.getDataForCharts({ - monitorMetricsByMinute, - checkOn: checkOn, - miscData: data.miscData, - }), + x: item.createdAt!, + y: item.metricValue!, }; - } + }); + } - public static getChartProps(data: { - monitorMetricsByMinute: Array<MonitorMetricsByMinute>; - checkOn: CheckOn; - miscData: Array<MonitorMetricsMiscData> | undefined; - probes: Array<Probe>; - }): Chart { - const { monitorMetricsByMinute, checkOn } = data; - - const axisBottom: AxisBottom = MonitorCharts.getAxisBottomFor(); - - const axisLeft: AxisLeft = MonitorCharts.getAxisLeftFor({ - checkOn: checkOn, + public static getDistinctMiscDataFromMonitorMetricsByMinute(data: { + monitorMetricsByMinute: Array<MonitorMetricsByMinute>; + checkOn: CheckOn; + }): Array<MonitorMetricsMiscData> { + const miscData: Array<MonitorMetricsMiscData | undefined> = + data.monitorMetricsByMinute + .filter((item: MonitorMetricsByMinute) => { + return item.metricType === data.checkOn; + }) + .map((item: MonitorMetricsByMinute) => { + return item.miscData || undefined; }); - const chartData: Array<LineChartData> = []; + return miscData + .filter( + ( + value: MonitorMetricsMiscData | undefined, + index: number, + self: Array<MonitorMetricsMiscData | undefined>, + ) => { + return ( + self.findIndex((t: MonitorMetricsMiscData | undefined) => { + return JSONFunctions.isEqualObject( + t as JSONObject, + value as JSONObject, + ); + }) === index + ); + }, + ) + .filter((item: MonitorMetricsMiscData | undefined) => { + return Boolean(item); + }) as Array<MonitorMetricsMiscData>; + } - if (!data.miscData) { - chartData.push( - MonitorCharts.getChartData({ - monitorMetricsByMinute, - checkOn, - miscData: undefined, - }) - ); + public static getSeriesName(data: { + checkOn: CheckOn; + miscData: MonitorMetricsMiscData | undefined; + probes: Array<Probe>; + }): string { + if (data.miscData) { + if (data.miscData.diskPath) { + return data.miscData.diskPath; + } + + if (data.miscData.probeId) { + const probe: Probe | undefined = data.probes.find((probe: Probe) => { + return probe._id?.toString() === data.miscData?.probeId?.toString(); + }); + + if (probe) { + return probe.name || "Probe"; } + } + } - for (const miscData of data.miscData || []) { - chartData.push( - MonitorCharts.getChartData({ - monitorMetricsByMinute, - checkOn, - miscData: miscData, - probes: data.probes, - }) - ); - } + return data.checkOn; + } - return { - id: `chart-${Text.generateRandomNumber()}`, - type: ChartType.LINE, - title: MonitorCharts.getChartTitle({ - checkOn: checkOn, - }), - description: MonitorCharts.getChartDescription({ - checkOn: checkOn, - }), - props: { - data: chartData, - xScale: MonitorCharts.getXScaleFor({ - monitorMetricsByMinute, + public static getChartData(data: { + monitorMetricsByMinute: Array<MonitorMetricsByMinute>; + checkOn: CheckOn; + miscData: MonitorMetricsMiscData | undefined; + probes?: Array<Probe>; + }): LineChartData { + const { monitorMetricsByMinute, checkOn } = data; + + return { + seriesName: this.getSeriesName({ + checkOn: checkOn, + miscData: data.miscData, + probes: data.probes || [], + }), + data: MonitorCharts.getDataForCharts({ + monitorMetricsByMinute, + checkOn: checkOn, + miscData: data.miscData, + }), + }; + } + + public static getChartProps(data: { + monitorMetricsByMinute: Array<MonitorMetricsByMinute>; + checkOn: CheckOn; + miscData: Array<MonitorMetricsMiscData> | undefined; + probes: Array<Probe>; + }): Chart { + const { monitorMetricsByMinute, checkOn } = data; + + const axisBottom: AxisBottom = MonitorCharts.getAxisBottomFor(); + + const axisLeft: AxisLeft = MonitorCharts.getAxisLeftFor({ + checkOn: checkOn, + }); + + const chartData: Array<LineChartData> = []; + + if (!data.miscData) { + chartData.push( + MonitorCharts.getChartData({ + monitorMetricsByMinute, + checkOn, + miscData: undefined, + }), + ); + } + + for (const miscData of data.miscData || []) { + chartData.push( + MonitorCharts.getChartData({ + monitorMetricsByMinute, + checkOn, + miscData: miscData, + probes: data.probes, + }), + ); + } + + return { + id: `chart-${Text.generateRandomNumber()}`, + type: ChartType.LINE, + title: MonitorCharts.getChartTitle({ + checkOn: checkOn, + }), + description: MonitorCharts.getChartDescription({ + checkOn: checkOn, + }), + props: { + data: chartData, + xScale: MonitorCharts.getXScaleFor({ + monitorMetricsByMinute, + }), + yScale: MonitorCharts.getYScaleFor({ + checkOn: checkOn, + }), + axisBottom: axisBottom, + curve: MonitorCharts.getCurveFor({ checkOn: checkOn }), + axisLeft: axisLeft, + getHoverTooltip: (data: { points: Array<LineChartPoint> }) => { + return ( + <MonitorChartTooltip + axisBottom={{ + ...axisBottom, + legend: MonitorCharts.getAxisBottomLegend(), + }} + axisLeft={{ + ...axisLeft, + legend: MonitorCharts.getAxisLeftLegend({ + checkOn, }), - yScale: MonitorCharts.getYScaleFor({ - checkOn: checkOn, - }), - axisBottom: axisBottom, - curve: MonitorCharts.getCurveFor({ checkOn: checkOn }), - axisLeft: axisLeft, - getHoverTooltip: (data: { points: Array<LineChartPoint> }) => { - return ( - <MonitorChartTooltip - axisBottom={{ - ...axisBottom, - legend: MonitorCharts.getAxisBottomLegend(), - }} - axisLeft={{ - ...axisLeft, - legend: MonitorCharts.getAxisLeftLegend({ - checkOn, - }), - }} - points={data.points} - /> - ); - }, - }, - sync: true, - }; + }} + points={data.points} + /> + ); + }, + }, + sync: true, + }; + } + + public static getMonitorCharts(data: { + monitorMetricsByMinute: Array<MonitorMetricsByMinute>; + checkOns: Array<CheckOn>; + probes: Array<Probe>; + }): Array<Chart> { + const { monitorMetricsByMinute, checkOns } = data; + + const charts: Array<Chart> = []; + + for (const checkOn of checkOns) { + const distinctMiscData: Array<MonitorMetricsMiscData> = + MonitorCharts.getDistinctMiscDataFromMonitorMetricsByMinute({ + monitorMetricsByMinute, + checkOn, + }).filter((item: MonitorMetricsMiscData | undefined) => { + return Boolean(item); + }); + + if (distinctMiscData.length > 0) { + charts.push( + MonitorCharts.getChartProps({ + monitorMetricsByMinute, + checkOn, + miscData: distinctMiscData, + probes: data.probes, + }), + ); + } else { + charts.push( + MonitorCharts.getChartProps({ + monitorMetricsByMinute, + checkOn, + miscData: undefined, + probes: data.probes, + }), + ); + } } - public static getMonitorCharts(data: { - monitorMetricsByMinute: Array<MonitorMetricsByMinute>; - checkOns: Array<CheckOn>; - probes: Array<Probe>; - }): Array<Chart> { - const { monitorMetricsByMinute, checkOns } = data; + return charts; + } - const charts: Array<Chart> = []; + private static getAxisBottomLegend(): string { + return "Time"; + } - for (const checkOn of checkOns) { - const distinctMiscData: Array<MonitorMetricsMiscData> = - MonitorCharts.getDistinctMiscDataFromMonitorMetricsByMinute({ - monitorMetricsByMinute, - checkOn, - }).filter((item: MonitorMetricsMiscData | undefined) => { - return Boolean(item); - }); + public static getAxisLeftLegend(data: { checkOn: CheckOn }): string { + return data.checkOn; + } - if (distinctMiscData.length > 0) { - charts.push( - MonitorCharts.getChartProps({ - monitorMetricsByMinute, - checkOn, - miscData: distinctMiscData, - probes: data.probes, - }) - ); - } else { - charts.push( - MonitorCharts.getChartProps({ - monitorMetricsByMinute, - checkOn, - miscData: undefined, - probes: data.probes, - }) - ); - } - } + private static getAxisBottomFor(): AxisBottom { + return { + legend: "", + type: AxisType.Time, + }; + } - return charts; + private static getAxisLeftFor(data: { checkOn: CheckOn }): AxisLeft { + return { + legend: data.checkOn, + type: AxisType.Number, + }; + } + + private static getCurveFor(data: { checkOn: CheckOn }): ChartCurve { + if (data.checkOn === CheckOn.ResponseStatusCode) { + return ChartCurve.STEP_AFTER; } - private static getAxisBottomLegend(): string { - return 'Time'; + return ChartCurve.LINEAR; + } + + public static getChartTitle(data: { checkOn: CheckOn }): string { + return data.checkOn; + } + + public static getChartDescription(data: { checkOn: CheckOn }): string { + if (data.checkOn === CheckOn.ResponseTime) { + return "Response Time in ms for this monitor."; + } else if (data.checkOn === CheckOn.ResponseStatusCode) { + return "Response Status Code for this monitor."; + } else if (data.checkOn === CheckOn.DiskUsagePercent) { + return "Disk Usage in % for this server."; + } else if (data.checkOn === CheckOn.MemoryUsagePercent) { + return "Memory Usage in % for this server."; + } else if (data.checkOn === CheckOn.CPUUsagePercent) { + return "CPU Usage in % for this server."; } - public static getAxisLeftLegend(data: { checkOn: CheckOn }): string { - return data.checkOn; + return ""; + } + + public static getXScaleFor(data: { + monitorMetricsByMinute: Array<MonitorMetricsByMinute>; + }): XScale { + const startTime: Date | undefined = + data.monitorMetricsByMinute[0]?.createdAt || undefined; + const endTime: Date | undefined = + data.monitorMetricsByMinute[data.monitorMetricsByMinute.length - 1] + ?.createdAt || undefined; + + return { + type: XScaleType.TIME, + min: startTime || "auto", + max: endTime || "auto", + precision: XScalePrecision.MINUTE, + }; + } + + public static getYScaleFor(data: { checkOn: CheckOn }): YScale { + if (data.checkOn === CheckOn.ResponseTime) { + return { + type: YScaleType.LINEAR, + min: 0, + max: 10000, + }; + } else if (data.checkOn === CheckOn.ResponseStatusCode) { + return { + type: YScaleType.LINEAR, + min: 0, + max: 600, + }; + } else if ( + data.checkOn === CheckOn.DiskUsagePercent || + data.checkOn === CheckOn.MemoryUsagePercent || + data.checkOn === CheckOn.CPUUsagePercent + ) { + return { + type: YScaleType.LINEAR, + min: 0, + max: 100, + }; } - private static getAxisBottomFor(): AxisBottom { - return { - legend: '', - type: AxisType.Time, - }; - } - - private static getAxisLeftFor(data: { checkOn: CheckOn }): AxisLeft { - return { - legend: data.checkOn, - type: AxisType.Number, - }; - } - - private static getCurveFor(data: { checkOn: CheckOn }): ChartCurve { - if (data.checkOn === CheckOn.ResponseStatusCode) { - return ChartCurve.STEP_AFTER; - } - - return ChartCurve.LINEAR; - } - - public static getChartTitle(data: { checkOn: CheckOn }): string { - return data.checkOn; - } - - public static getChartDescription(data: { checkOn: CheckOn }): string { - if (data.checkOn === CheckOn.ResponseTime) { - return 'Response Time in ms for this monitor.'; - } else if (data.checkOn === CheckOn.ResponseStatusCode) { - return 'Response Status Code for this monitor.'; - } else if (data.checkOn === CheckOn.DiskUsagePercent) { - return 'Disk Usage in % for this server.'; - } else if (data.checkOn === CheckOn.MemoryUsagePercent) { - return 'Memory Usage in % for this server.'; - } else if (data.checkOn === CheckOn.CPUUsagePercent) { - return 'CPU Usage in % for this server.'; - } - - return ''; - } - - public static getXScaleFor(data: { - monitorMetricsByMinute: Array<MonitorMetricsByMinute>; - }): XScale { - const startTime: Date | undefined = - data.monitorMetricsByMinute[0]?.createdAt || undefined; - const endTime: Date | undefined = - data.monitorMetricsByMinute[data.monitorMetricsByMinute.length - 1] - ?.createdAt || undefined; - - return { - type: XScaleType.TIME, - min: startTime || 'auto', - max: endTime || 'auto', - precision: XScalePrecision.MINUTE, - }; - } - - public static getYScaleFor(data: { checkOn: CheckOn }): YScale { - if (data.checkOn === CheckOn.ResponseTime) { - return { - type: YScaleType.LINEAR, - min: 0, - max: 10000, - }; - } else if (data.checkOn === CheckOn.ResponseStatusCode) { - return { - type: YScaleType.LINEAR, - min: 0, - max: 600, - }; - } else if ( - data.checkOn === CheckOn.DiskUsagePercent || - data.checkOn === CheckOn.MemoryUsagePercent || - data.checkOn === CheckOn.CPUUsagePercent - ) { - return { - type: YScaleType.LINEAR, - min: 0, - max: 100, - }; - } - - return { - type: YScaleType.LINEAR, - min: 'auto', - max: 'auto', - }; - } + return { + type: YScaleType.LINEAR, + min: "auto", + max: "auto", + }; + } } diff --git a/Dashboard/src/Components/Monitor/MonitorCharts/MonitorChartTooltip.tsx b/Dashboard/src/Components/Monitor/MonitorCharts/MonitorChartTooltip.tsx index 3bc3bbec4b..f2db580932 100644 --- a/Dashboard/src/Components/Monitor/MonitorCharts/MonitorChartTooltip.tsx +++ b/Dashboard/src/Components/Monitor/MonitorCharts/MonitorChartTooltip.tsx @@ -1,91 +1,84 @@ -import OneUptimeDate from 'Common/Types/Date'; +import OneUptimeDate from "Common/Types/Date"; import { - AxisBottom, - AxisLeft, - AxisType, - LineChartPoint, - XValue, - YValue, -} from 'CommonUI/src/Components/Charts/Line/LineChart'; -import React, { FunctionComponent, ReactElement } from 'react'; + AxisBottom, + AxisLeft, + AxisType, + LineChartPoint, + XValue, + YValue, +} from "CommonUI/src/Components/Charts/Line/LineChart"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - points: Array<LineChartPoint>; - axisBottom: AxisBottom; - axisLeft: AxisLeft; + points: Array<LineChartPoint>; + axisBottom: AxisBottom; + axisLeft: AxisLeft; } const MonitorChartTooltip: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - type FormatAxisValueFunction = ( - value: XValue | YValue, - type: AxisType - ) => string; + type FormatAxisValueFunction = ( + value: XValue | YValue, + type: AxisType, + ) => string; - const formatAxisValue: FormatAxisValueFunction = ( - value: XValue | YValue, - type: AxisType - ): string => { - if (typeof value === 'number') { - return value.toFixed(2); - } + const formatAxisValue: FormatAxisValueFunction = ( + value: XValue | YValue, + type: AxisType, + ): string => { + if (typeof value === "number") { + return value.toFixed(2); + } - if (type === AxisType.Date) { - return OneUptimeDate.getDateAsLocalFormattedString(value); - } + if (type === AxisType.Date) { + return OneUptimeDate.getDateAsLocalFormattedString(value); + } - if (type === AxisType.Time) { - return OneUptimeDate.getLocalHourAndMinuteFromDate(value); - } + if (type === AxisType.Time) { + return OneUptimeDate.getLocalHourAndMinuteFromDate(value); + } - return value.toString(); - }; + return value.toString(); + }; - return ( - <div className="bg-white rounded-md shadow-md p-5 text-sm space-y-2"> - {props.points.map((point: LineChartPoint, index: number) => { - return ( - <div key={index} className="space-y-1"> - <div className="font-medium flex"> - <div> - <div - className="w-3 h-3 mr-2 mt-1 rounded-full" - style={{ - backgroundColor: - point.seriesColor.toString(), - }} - ></div> - </div> - {point.seriesName} - </div> - <div className="flex text-gray-600 text-xs"> - <div className="w-1/2 text-left font-medium"> - {props.axisLeft.legend} - </div> - <div className="w-1/2 text-right"> - {formatAxisValue( - point.y.toString(), - props.axisLeft.type - )} - </div> - </div> - <div className="flex text-gray-600 text-xs"> - <div className="w-1/2 text-left font-medium"> - {props.axisBottom.legend} - </div> - <div className="w-1/2 text-right"> - {formatAxisValue( - point.x.toString(), - props.axisBottom.type - )} - </div> - </div> - </div> - ); - })} - </div> - ); + return ( + <div className="bg-white rounded-md shadow-md p-5 text-sm space-y-2"> + {props.points.map((point: LineChartPoint, index: number) => { + return ( + <div key={index} className="space-y-1"> + <div className="font-medium flex"> + <div> + <div + className="w-3 h-3 mr-2 mt-1 rounded-full" + style={{ + backgroundColor: point.seriesColor.toString(), + }} + ></div> + </div> + {point.seriesName} + </div> + <div className="flex text-gray-600 text-xs"> + <div className="w-1/2 text-left font-medium"> + {props.axisLeft.legend} + </div> + <div className="w-1/2 text-right"> + {formatAxisValue(point.y.toString(), props.axisLeft.type)} + </div> + </div> + <div className="flex text-gray-600 text-xs"> + <div className="w-1/2 text-left font-medium"> + {props.axisBottom.legend} + </div> + <div className="w-1/2 text-right"> + {formatAxisValue(point.x.toString(), props.axisBottom.type)} + </div> + </div> + </div> + ); + })} + </div> + ); }; export default MonitorChartTooltip; diff --git a/Dashboard/src/Components/Monitor/MonitorSteps/CriteriaFilter.tsx b/Dashboard/src/Components/Monitor/MonitorSteps/CriteriaFilter.tsx index 9ba7ee7d17..074b1a4e00 100644 --- a/Dashboard/src/Components/Monitor/MonitorSteps/CriteriaFilter.tsx +++ b/Dashboard/src/Components/Monitor/MonitorSteps/CriteriaFilter.tsx @@ -1,36 +1,36 @@ -import CriteriaFilterUtil from '../../../Utils/Form/Monitor/CriteriaFilter'; +import CriteriaFilterUtil from "../../../Utils/Form/Monitor/CriteriaFilter"; import { - CriteriaFilter, - FilterCondition, -} from 'Common/Types/Monitor/CriteriaFilter'; -import React, { FunctionComponent, ReactElement } from 'react'; + CriteriaFilter, + FilterCondition, +} from "Common/Types/Monitor/CriteriaFilter"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - criteriaFilter: CriteriaFilter | undefined; - filterCondition?: FilterCondition | undefined; + criteriaFilter: CriteriaFilter | undefined; + filterCondition?: FilterCondition | undefined; } const CriteriaFilterElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let text: string = ''; + let text: string = ""; - if (props.criteriaFilter) { - text = CriteriaFilterUtil.translateFilterToText( - props.criteriaFilter, - props.filterCondition - ); - } - - return ( - <div className="flex w-full -ml-3"> - <div className="flex"> - <div className="ml-1 flex-auto py-0.5 text-sm leading-5 text-gray-500"> - <span className="font-medium text-gray-900">{text}</span>{' '} - </div> - </div> - </div> + if (props.criteriaFilter) { + text = CriteriaFilterUtil.translateFilterToText( + props.criteriaFilter, + props.filterCondition, ); + } + + return ( + <div className="flex w-full -ml-3"> + <div className="flex"> + <div className="ml-1 flex-auto py-0.5 text-sm leading-5 text-gray-500"> + <span className="font-medium text-gray-900">{text}</span>{" "} + </div> + </div> + </div> + ); }; export default CriteriaFilterElement; diff --git a/Dashboard/src/Components/Monitor/MonitorSteps/CriteriaFilters.tsx b/Dashboard/src/Components/Monitor/MonitorSteps/CriteriaFilters.tsx index 256db38566..cd8cef6487 100644 --- a/Dashboard/src/Components/Monitor/MonitorSteps/CriteriaFilters.tsx +++ b/Dashboard/src/Components/Monitor/MonitorSteps/CriteriaFilters.tsx @@ -1,51 +1,47 @@ -import CriteriaFilterElement from './CriteriaFilter'; +import CriteriaFilterElement from "./CriteriaFilter"; import { - CriteriaFilter, - FilterCondition, -} from 'Common/Types/Monitor/CriteriaFilter'; -import React, { FunctionComponent, ReactElement } from 'react'; + CriteriaFilter, + FilterCondition, +} from "Common/Types/Monitor/CriteriaFilter"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - criteriaFilters: Array<CriteriaFilter>; - filterCondition: FilterCondition; + criteriaFilters: Array<CriteriaFilter>; + filterCondition: FilterCondition; } const CriteriaFilters: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="ml-5 mt-5 mb-5 bg-gray-50 rounded rounded-xl p-5 border border-2 border-gray-100"> - <ul role="list" className="space-y-6"> - {props.criteriaFilters.map( - (i: CriteriaFilter, index: number) => { - const isLastItem: boolean = - index === props.criteriaFilters.length - 1; - return ( - <li className="relative flex gap-x-4" key={index}> - {!isLastItem && ( - <div className="absolute left-0 top-0 flex w-6 justify-center -bottom-6"> - <div className="w-px bg-gray-200"></div> - </div> - )} - <div className="relative flex h-6 w-6 flex-none items-center justify-center bg-gray-50"> - <div className="h-1.5 w-1.5 rounded-full bg-gray-100 ring-1 ring-gray-300"></div> - </div> - <CriteriaFilterElement - key={index} - criteriaFilter={i} - filterCondition={ - !isLastItem - ? props.filterCondition - : undefined - } - />{' '} - </li> - ); - } - )} - </ul> - </div> - ); + return ( + <div className="ml-5 mt-5 mb-5 bg-gray-50 rounded rounded-xl p-5 border border-2 border-gray-100"> + <ul role="list" className="space-y-6"> + {props.criteriaFilters.map((i: CriteriaFilter, index: number) => { + const isLastItem: boolean = + index === props.criteriaFilters.length - 1; + return ( + <li className="relative flex gap-x-4" key={index}> + {!isLastItem && ( + <div className="absolute left-0 top-0 flex w-6 justify-center -bottom-6"> + <div className="w-px bg-gray-200"></div> + </div> + )} + <div className="relative flex h-6 w-6 flex-none items-center justify-center bg-gray-50"> + <div className="h-1.5 w-1.5 rounded-full bg-gray-100 ring-1 ring-gray-300"></div> + </div> + <CriteriaFilterElement + key={index} + criteriaFilter={i} + filterCondition={ + !isLastItem ? props.filterCondition : undefined + } + />{" "} + </li> + ); + })} + </ul> + </div> + ); }; export default CriteriaFilters; diff --git a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteria.tsx b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteria.tsx index 6762407e47..661bafce7a 100644 --- a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteria.tsx +++ b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteria.tsx @@ -1,71 +1,64 @@ -import MonitorCriteriaInstanceElement from './MonitorCriteriaInstance'; -import MonitorCriteria from 'Common/Types/Monitor/MonitorCriteria'; -import MonitorCriteriaInstance from 'Common/Types/Monitor/MonitorCriteriaInstance'; -import Text from 'Common/Types/Text'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitorCriteriaInstanceElement from "./MonitorCriteriaInstance"; +import MonitorCriteria from "Common/Types/Monitor/MonitorCriteria"; +import MonitorCriteriaInstance from "Common/Types/Monitor/MonitorCriteriaInstance"; +import Text from "Common/Types/Text"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitorCriteria: MonitorCriteria; - monitorStatusOptions: Array<MonitorStatus>; - incidentSeverityOptions: Array<IncidentSeverity>; - onCallPolicyOptions: Array<OnCallDutyPolicy>; + monitorCriteria: MonitorCriteria; + monitorStatusOptions: Array<MonitorStatus>; + incidentSeverityOptions: Array<IncidentSeverity>; + onCallPolicyOptions: Array<OnCallDutyPolicy>; } const MonitorCriteriaElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mt-4"> - <ul role="list" className="space-y-6"> - {props.monitorCriteria.data?.monitorCriteriaInstanceArray.map( - (i: MonitorCriteriaInstance, index: number) => { - return ( - <li className="relative flex gap-x-4" key={index}> - <div className="absolute left-0 top-0 flex w-6 justify-center -bottom-6"> - <div className="w-px bg-slate-200"></div> - </div> - <div className="relative flex h-6 w-6 flex-none items-center justify-center bg-white"> - <div className="h-1.5 w-1.5 rounded-full bg-slate-100 ring-1 ring-slate-300"></div> - </div> + return ( + <div className="mt-4"> + <ul role="list" className="space-y-6"> + {props.monitorCriteria.data?.monitorCriteriaInstanceArray.map( + (i: MonitorCriteriaInstance, index: number) => { + return ( + <li className="relative flex gap-x-4" key={index}> + <div className="absolute left-0 top-0 flex w-6 justify-center -bottom-6"> + <div className="w-px bg-slate-200"></div> + </div> + <div className="relative flex h-6 w-6 flex-none items-center justify-center bg-white"> + <div className="h-1.5 w-1.5 rounded-full bg-slate-100 ring-1 ring-slate-300"></div> + </div> - <div className="flex-auto py-0.5 text-sm leading-5 text-gray-500"> - <span className="font-medium text-gray-900"> - {i.data?.name || 'Criteria'} - </span>{' '} - This criteria will be checked{' '} - {Text.convertNumberToWords(index + 1)}. - <div className="mt-10 mb-10" key={index}> - <MonitorCriteriaInstanceElement - monitorStatusOptions={ - props.monitorStatusOptions - } - onCallPolicyOptions={ - props.onCallPolicyOptions - } - incidentSeverityOptions={ - props.incidentSeverityOptions - } - monitorCriteriaInstance={i} - isLastCriteria={ - index === - (props.monitorCriteria.data - ?.monitorCriteriaInstanceArray - .length || 1) - - 1 - } - /> - </div> - </div> - </li> - ); - } - )} - </ul> - </div> - ); + <div className="flex-auto py-0.5 text-sm leading-5 text-gray-500"> + <span className="font-medium text-gray-900"> + {i.data?.name || "Criteria"} + </span>{" "} + This criteria will be checked{" "} + {Text.convertNumberToWords(index + 1)}. + <div className="mt-10 mb-10" key={index}> + <MonitorCriteriaInstanceElement + monitorStatusOptions={props.monitorStatusOptions} + onCallPolicyOptions={props.onCallPolicyOptions} + incidentSeverityOptions={props.incidentSeverityOptions} + monitorCriteriaInstance={i} + isLastCriteria={ + index === + (props.monitorCriteria.data + ?.monitorCriteriaInstanceArray.length || 1) - + 1 + } + /> + </div> + </div> + </li> + ); + }, + )} + </ul> + </div> + ); }; export default MonitorCriteriaElement; diff --git a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaIncident.tsx b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaIncident.tsx index 8a117297b9..5038852885 100644 --- a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaIncident.tsx +++ b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaIncident.tsx @@ -1,125 +1,117 @@ -import OnCallDutyPoliciesView from '../../OnCallPolicy/OnCallPolicies'; -import { Black } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import { JSONObject } from 'Common/Types/JSON'; -import { CriteriaIncident } from 'Common/Types/Monitor/CriteriaIncident'; -import ObjectID from 'Common/Types/ObjectID'; -import Detail from 'CommonUI/src/Components/Detail/Detail'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { FunctionComponent, ReactElement } from 'react'; +import OnCallDutyPoliciesView from "../../OnCallPolicy/OnCallPolicies"; +import { Black } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import { JSONObject } from "Common/Types/JSON"; +import { CriteriaIncident } from "Common/Types/Monitor/CriteriaIncident"; +import ObjectID from "Common/Types/ObjectID"; +import Detail from "CommonUI/src/Components/Detail/Detail"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - incident: CriteriaIncident; - incidentSeverityOptions: Array<IncidentSeverity>; - onCallPolicyOptions: Array<OnCallDutyPolicy>; + incident: CriteriaIncident; + incidentSeverityOptions: Array<IncidentSeverity>; + onCallPolicyOptions: Array<OnCallDutyPolicy>; } const MonitorCriteriaIncidentForm: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mt-4 bg-gray-50 rounded rounded-xl p-5 border border-2 border-gray-100"> - <Detail - id={'monitor-criteria-instance'} - item={props.incident as any} - showDetailsInNumberOfColumns={1} - fields={[ - { - key: 'title', - title: 'Incident Title', - fieldType: FieldType.Text, - placeholder: 'No data entered', - }, - { - key: 'description', - title: 'Incident Description', - fieldType: FieldType.LongText, - placeholder: 'No data entered', - }, - { - key: 'incidentSeverityId', - title: 'Incident Severity', - fieldType: FieldType.Dropdown, - placeholder: 'No data entered', - getElement: (item: JSONObject): ReactElement => { - if (item['incidentSeverityId']) { - return ( - <Pill - isMinimal={true} - color={ - (props.incidentSeverityOptions.find( - (option: IncidentSeverity) => { - return ( - option.id?.toString() === - item[ - 'incidentSeverityId' - ]!.toString() - ); - } - )?.color as Color) || Black - } - text={ - (props.incidentSeverityOptions.find( - (option: IncidentSeverity) => { - return ( - option.id?.toString() === - item[ - 'incidentSeverityId' - ]!.toString() - ); - } - )?.name as string) || '' - } - /> - ); - } + return ( + <div className="mt-4 bg-gray-50 rounded rounded-xl p-5 border border-2 border-gray-100"> + <Detail + id={"monitor-criteria-instance"} + item={props.incident as any} + showDetailsInNumberOfColumns={1} + fields={[ + { + key: "title", + title: "Incident Title", + fieldType: FieldType.Text, + placeholder: "No data entered", + }, + { + key: "description", + title: "Incident Description", + fieldType: FieldType.LongText, + placeholder: "No data entered", + }, + { + key: "incidentSeverityId", + title: "Incident Severity", + fieldType: FieldType.Dropdown, + placeholder: "No data entered", + getElement: (item: JSONObject): ReactElement => { + if (item["incidentSeverityId"]) { + return ( + <Pill + isMinimal={true} + color={ + (props.incidentSeverityOptions.find( + (option: IncidentSeverity) => { + return ( + option.id?.toString() === + item["incidentSeverityId"]!.toString() + ); + }, + )?.color as Color) || Black + } + text={ + (props.incidentSeverityOptions.find( + (option: IncidentSeverity) => { + return ( + option.id?.toString() === + item["incidentSeverityId"]!.toString() + ); + }, + )?.name as string) || "" + } + /> + ); + } - return <></>; - }, + return <></>; + }, + }, + { + key: "autoResolveIncident", + title: "Auto Resolve Incident", + description: + "Automatically resolve this incident when this criteria is no longer met.", + fieldType: FieldType.Boolean, + placeholder: "No", + }, + { + key: "onCallPolicyIds", + title: "On-Call Policies", + description: + "These are the on-call policies that will be executed when this incident is created.", + fieldType: FieldType.Element, + getElement: (item: JSONObject): ReactElement => { + return ( + <OnCallDutyPoliciesView + onCallPolicies={props.onCallPolicyOptions.filter( + (policy: OnCallDutyPolicy) => { + return ( + (item["onCallPolicyIds"] as Array<ObjectID>) || [] + ) + .map((id: ObjectID) => { + return id.toString(); + }) + .includes(policy.id?.toString() || ""); }, - { - key: 'autoResolveIncident', - title: 'Auto Resolve Incident', - description: - 'Automatically resolve this incident when this criteria is no longer met.', - fieldType: FieldType.Boolean, - placeholder: 'No', - }, - { - key: 'onCallPolicyIds', - title: 'On-Call Policies', - description: - 'These are the on-call policies that will be executed when this incident is created.', - fieldType: FieldType.Element, - getElement: (item: JSONObject): ReactElement => { - return ( - <OnCallDutyPoliciesView - onCallPolicies={props.onCallPolicyOptions.filter( - (policy: OnCallDutyPolicy) => { - return ( - (item[ - 'onCallPolicyIds' - ] as Array<ObjectID>) || [] - ) - .map((id: ObjectID) => { - return id.toString(); - }) - .includes( - policy.id?.toString() || '' - ); - } - )} - /> - ); - }, - }, - ]} - /> - </div> - ); + )} + /> + ); + }, + }, + ]} + /> + </div> + ); }; export default MonitorCriteriaIncidentForm; diff --git a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaIncidents.tsx b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaIncidents.tsx index 559baa053e..e6b271f376 100644 --- a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaIncidents.tsx +++ b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaIncidents.tsx @@ -1,32 +1,32 @@ -import MonitorCriteriaIncident from './MonitorCriteriaIncident'; -import { CriteriaIncident } from 'Common/Types/Monitor/CriteriaIncident'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitorCriteriaIncident from "./MonitorCriteriaIncident"; +import { CriteriaIncident } from "Common/Types/Monitor/CriteriaIncident"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - incidents: Array<CriteriaIncident>; - incidentSeverityOptions: Array<IncidentSeverity>; - onCallPolicyOptions: Array<OnCallDutyPolicy>; + incidents: Array<CriteriaIncident>; + incidentSeverityOptions: Array<IncidentSeverity>; + onCallPolicyOptions: Array<OnCallDutyPolicy>; } const MonitorCriteriaIncidentsForm: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mt-4 ml-5"> - {props.incidents.map((i: CriteriaIncident, index: number) => { - return ( - <MonitorCriteriaIncident - key={index} - onCallPolicyOptions={props.onCallPolicyOptions} - incidentSeverityOptions={props.incidentSeverityOptions} - incident={i} - /> - ); - })} - </div> - ); + return ( + <div className="mt-4 ml-5"> + {props.incidents.map((i: CriteriaIncident, index: number) => { + return ( + <MonitorCriteriaIncident + key={index} + onCallPolicyOptions={props.onCallPolicyOptions} + incidentSeverityOptions={props.incidentSeverityOptions} + incident={i} + /> + ); + })} + </div> + ); }; export default MonitorCriteriaIncidentsForm; diff --git a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaInstance.tsx b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaInstance.tsx index 96d6d994ac..a0b895764b 100644 --- a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaInstance.tsx +++ b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorCriteriaInstance.tsx @@ -1,141 +1,117 @@ -import CriteriaFilters from './CriteriaFilters'; -import MonitorCriteriaIncidents from './MonitorCriteriaIncidents'; -import { Black } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { FilterCondition } from 'Common/Types/Monitor/CriteriaFilter'; -import MonitorCriteriaInstance from 'Common/Types/Monitor/MonitorCriteriaInstance'; -import HorizontalRule from 'CommonUI/src/Components/HorizontalRule/HorizontalRule'; -import Icon from 'CommonUI/src/Components/Icon/Icon'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { FunctionComponent, ReactElement } from 'react'; +import CriteriaFilters from "./CriteriaFilters"; +import MonitorCriteriaIncidents from "./MonitorCriteriaIncidents"; +import { Black } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import IconProp from "Common/Types/Icon/IconProp"; +import { FilterCondition } from "Common/Types/Monitor/CriteriaFilter"; +import MonitorCriteriaInstance from "Common/Types/Monitor/MonitorCriteriaInstance"; +import HorizontalRule from "CommonUI/src/Components/HorizontalRule/HorizontalRule"; +import Icon from "CommonUI/src/Components/Icon/Icon"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitorStatusOptions: Array<MonitorStatus>; - incidentSeverityOptions: Array<IncidentSeverity>; - isLastCriteria: boolean; - monitorCriteriaInstance: MonitorCriteriaInstance; - onCallPolicyOptions: Array<OnCallDutyPolicy>; + monitorStatusOptions: Array<MonitorStatus>; + incidentSeverityOptions: Array<IncidentSeverity>; + isLastCriteria: boolean; + monitorCriteriaInstance: MonitorCriteriaInstance; + onCallPolicyOptions: Array<OnCallDutyPolicy>; } const MonitorCriteriaInstanceElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="mb-4"> - {props.monitorCriteriaInstance.data?.description && ( - <div className="-mt-8"> - {props.monitorCriteriaInstance.data?.description} - </div> - )} - - <div className="mt-4"> - <div className="flex"> - <Icon - icon={IconProp.Filter} - className="h-5 w-5 text-gray-900" - /> - <div className="ml-1 -mt-0.5 flex-auto py-0.5 text-sm leading-5 text-gray-500"> - <span className="font-medium text-gray-900"> - Filters ( - { - props.monitorCriteriaInstance.data - ?.filterCondition - } - ) - </span>{' '} - {props.monitorCriteriaInstance.data?.filterCondition} of - these can match for this criteria to be met: - </div> - </div> - - <CriteriaFilters - criteriaFilters={ - props.monitorCriteriaInstance?.data?.filters || [] - } - filterCondition={ - props.monitorCriteriaInstance?.data?.filterCondition || - FilterCondition.Any - } - /> - </div> - - {props.monitorCriteriaInstance.data?.monitorStatusId && ( - <div className="mt-4"> - <div className="flex"> - <Icon - icon={IconProp.AltGlobe} - className="h-5 w-5 text-gray-900" - /> - <div className="ml-1 -mt-0.5 flex-auto py-0.5 text-sm leading-5 text-gray-500"> - <span className="font-medium text-gray-900"> - Change Monitor Status - </span>{' '} - when this criteria is met. Change monitor status to: - <div className="mt-3"> - <Statusbubble - color={ - (props.monitorStatusOptions.find( - (option: IncidentSeverity) => { - return ( - option.id?.toString() === - props.monitorCriteriaInstance.data?.monitorStatusId?.toString() - ); - } - )?.color as Color) || Black - } - shouldAnimate={false} - text={ - (props.monitorStatusOptions.find( - (option: IncidentSeverity) => { - return ( - option.id?.toString() === - props.monitorCriteriaInstance.data?.monitorStatusId?.toString() - ); - } - )?.name as string) || '' - } - /> - </div> - </div> - </div> - </div> - )} - - {(props.monitorCriteriaInstance?.data?.incidents?.length || 0) > - 0 && ( - <div className="mt-4"> - <div className="flex"> - <Icon - icon={IconProp.Alert} - className="h-5 w-5 text-gray-900" - /> - <div className="ml-1 flex-auto py-0.5 text-sm leading-5 text-gray-500"> - <span className="font-medium text-gray-900"> - Create incident - </span>{' '} - when this criteria is met. These are the incident - details:{' '} - </div> - </div> - <MonitorCriteriaIncidents - incidents={ - props.monitorCriteriaInstance?.data?.incidents || [] - } - onCallPolicyOptions={props.onCallPolicyOptions} - incidentSeverityOptions={props.incidentSeverityOptions} - /> - </div> - )} - - <div className="mt-10"> - {!props.isLastCriteria && <HorizontalRule />} - </div> + return ( + <div className="mb-4"> + {props.monitorCriteriaInstance.data?.description && ( + <div className="-mt-8"> + {props.monitorCriteriaInstance.data?.description} </div> - ); + )} + + <div className="mt-4"> + <div className="flex"> + <Icon icon={IconProp.Filter} className="h-5 w-5 text-gray-900" /> + <div className="ml-1 -mt-0.5 flex-auto py-0.5 text-sm leading-5 text-gray-500"> + <span className="font-medium text-gray-900"> + Filters ({props.monitorCriteriaInstance.data?.filterCondition}) + </span>{" "} + {props.monitorCriteriaInstance.data?.filterCondition} of these can + match for this criteria to be met: + </div> + </div> + + <CriteriaFilters + criteriaFilters={props.monitorCriteriaInstance?.data?.filters || []} + filterCondition={ + props.monitorCriteriaInstance?.data?.filterCondition || + FilterCondition.Any + } + /> + </div> + + {props.monitorCriteriaInstance.data?.monitorStatusId && ( + <div className="mt-4"> + <div className="flex"> + <Icon icon={IconProp.AltGlobe} className="h-5 w-5 text-gray-900" /> + <div className="ml-1 -mt-0.5 flex-auto py-0.5 text-sm leading-5 text-gray-500"> + <span className="font-medium text-gray-900"> + Change Monitor Status + </span>{" "} + when this criteria is met. Change monitor status to: + <div className="mt-3"> + <Statusbubble + color={ + (props.monitorStatusOptions.find( + (option: IncidentSeverity) => { + return ( + option.id?.toString() === + props.monitorCriteriaInstance.data?.monitorStatusId?.toString() + ); + }, + )?.color as Color) || Black + } + shouldAnimate={false} + text={ + (props.monitorStatusOptions.find( + (option: IncidentSeverity) => { + return ( + option.id?.toString() === + props.monitorCriteriaInstance.data?.monitorStatusId?.toString() + ); + }, + )?.name as string) || "" + } + /> + </div> + </div> + </div> + </div> + )} + + {(props.monitorCriteriaInstance?.data?.incidents?.length || 0) > 0 && ( + <div className="mt-4"> + <div className="flex"> + <Icon icon={IconProp.Alert} className="h-5 w-5 text-gray-900" /> + <div className="ml-1 flex-auto py-0.5 text-sm leading-5 text-gray-500"> + <span className="font-medium text-gray-900">Create incident</span>{" "} + when this criteria is met. These are the incident details:{" "} + </div> + </div> + <MonitorCriteriaIncidents + incidents={props.monitorCriteriaInstance?.data?.incidents || []} + onCallPolicyOptions={props.onCallPolicyOptions} + incidentSeverityOptions={props.incidentSeverityOptions} + /> + </div> + )} + + <div className="mt-10">{!props.isLastCriteria && <HorizontalRule />}</div> + </div> + ); }; export default MonitorCriteriaInstanceElement; diff --git a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorStep.tsx b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorStep.tsx index 2c1ed8b0b0..e5cce2ff1a 100644 --- a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorStep.tsx +++ b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorStep.tsx @@ -1,204 +1,199 @@ -import MonitorCriteriaElement from './MonitorCriteria'; -import MonitorCriteria from 'Common/Types/Monitor/MonitorCriteria'; -import MonitorStep, { MonitorStepType } from 'Common/Types/Monitor/MonitorStep'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import Detail from 'CommonUI/src/Components/Detail/Detail'; -import Field from 'CommonUI/src/Components/Detail/Field'; -import FieldLabelElement from 'CommonUI/src/Components/Forms/Fields/FieldLabel'; -import HorizontalRule from 'CommonUI/src/Components/HorizontalRule/HorizontalRule'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; +import MonitorCriteriaElement from "./MonitorCriteria"; +import MonitorCriteria from "Common/Types/Monitor/MonitorCriteria"; +import MonitorStep, { MonitorStepType } from "Common/Types/Monitor/MonitorStep"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import Detail from "CommonUI/src/Components/Detail/Detail"; +import Field from "CommonUI/src/Components/Detail/Field"; +import FieldLabelElement from "CommonUI/src/Components/Forms/Fields/FieldLabel"; +import HorizontalRule from "CommonUI/src/Components/HorizontalRule/HorizontalRule"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - monitorStatusOptions: Array<MonitorStatus>; - incidentSeverityOptions: Array<IncidentSeverity>; - monitorStep: MonitorStep; - monitorType: MonitorType; - onCallPolicyOptions: Array<OnCallDutyPolicy>; + monitorStatusOptions: Array<MonitorStatus>; + incidentSeverityOptions: Array<IncidentSeverity>; + monitorStep: MonitorStep; + monitorType: MonitorType; + onCallPolicyOptions: Array<OnCallDutyPolicy>; } const MonitorStepElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [requestDetailsFields, setRequestDetailsFields] = useState< - Array<Field<MonitorStepType>> - >([]); + const [requestDetailsFields, setRequestDetailsFields] = useState< + Array<Field<MonitorStepType>> + >([]); - useEffect(() => { - let fields: Array<Field<MonitorStepType>> = []; + useEffect(() => { + let fields: Array<Field<MonitorStepType>> = []; - if (props.monitorType === MonitorType.API) { - fields = [ - { - key: 'monitorDestination', - title: 'API URL', - description: 'URL of the API you want to monitor.', - fieldType: FieldType.Text, - placeholder: 'No data entered', - }, - { - key: 'requestType', - title: 'Request Type', - description: 'Whats the type of the API request?', - fieldType: FieldType.Text, - placeholder: 'No data entered', - }, - { - key: 'requestBody', - title: 'Request Body', - description: 'Request Body to send, if any.', - fieldType: FieldType.JSON, - placeholder: 'No data entered', - }, - { - key: 'requestHeaders', - title: 'Request Headers', - description: 'Request Headers to send, if any.', - fieldType: FieldType.DictionaryOfStrings, - placeholder: 'No data entered', - }, - ]; - } else if (props.monitorType === MonitorType.Website) { - fields = [ - { - key: 'monitorDestination', - title: 'Website URL', - description: 'URL of the website you want to monitor.', - fieldType: FieldType.Text, - placeholder: 'No data entered', - }, - ]; - } else if (props.monitorType === MonitorType.Ping) { - fields = [ - { - key: 'monitorDestination', - title: 'Ping Hostname or IP Address', - description: - 'Hostname or IP Address of the resource you would like us to ping.', - fieldType: FieldType.Text, - placeholder: 'No data entered', - }, - ]; - } else if (props.monitorType === MonitorType.Port) { - fields = [ - { - key: 'monitorDestination', - title: 'Ping Hostname or IP Address', - description: - 'Hostname or IP Address of the resource you would like us to ping.', - fieldType: FieldType.Text, - placeholder: 'No data entered', - }, - { - key: 'monitorDestinationPort', - title: 'Port', - description: - 'Port of the resource you would like us to ping.', - fieldType: FieldType.Port, - placeholder: 'No port entered', - }, - ]; - } else if (props.monitorType === MonitorType.IP) { - fields = [ - { - key: 'monitorDestination', - title: 'IP Address', - description: - 'IP Address of the resource you would like us to ping.', - fieldType: FieldType.Text, - placeholder: 'No data entered', - }, - ]; - } else if (props.monitorType === MonitorType.CustomJavaScriptCode) { - fields = [ - { - key: 'customCode', - title: 'JavaScript Code', - description: 'JavaScript code to run.', - fieldType: FieldType.JavaScript, - placeholder: 'No data entered', - }, - ]; - } else if (props.monitorType === MonitorType.SyntheticMonitor) { - fields = [ - { - key: 'customCode', - title: 'JavaScript Code', - description: 'JavaScript code to run.', - fieldType: FieldType.JavaScript, - placeholder: 'No data entered', - }, - { - key: 'browserTypes', - title: 'Browser Types', - description: - 'Browser types to run the synthetic monitor on.', - fieldType: FieldType.ArrayOfText, - placeholder: 'No data entered', - }, - { - key: 'screenSizeTypes', - title: 'Screen Size Types', - description: - 'Screen size types to run the synthetic monitor on.', - fieldType: FieldType.ArrayOfText, - placeholder: 'No data entered', - }, - ]; + if (props.monitorType === MonitorType.API) { + fields = [ + { + key: "monitorDestination", + title: "API URL", + description: "URL of the API you want to monitor.", + fieldType: FieldType.Text, + placeholder: "No data entered", + }, + { + key: "requestType", + title: "Request Type", + description: "Whats the type of the API request?", + fieldType: FieldType.Text, + placeholder: "No data entered", + }, + { + key: "requestBody", + title: "Request Body", + description: "Request Body to send, if any.", + fieldType: FieldType.JSON, + placeholder: "No data entered", + }, + { + key: "requestHeaders", + title: "Request Headers", + description: "Request Headers to send, if any.", + fieldType: FieldType.DictionaryOfStrings, + placeholder: "No data entered", + }, + ]; + } else if (props.monitorType === MonitorType.Website) { + fields = [ + { + key: "monitorDestination", + title: "Website URL", + description: "URL of the website you want to monitor.", + fieldType: FieldType.Text, + placeholder: "No data entered", + }, + ]; + } else if (props.monitorType === MonitorType.Ping) { + fields = [ + { + key: "monitorDestination", + title: "Ping Hostname or IP Address", + description: + "Hostname or IP Address of the resource you would like us to ping.", + fieldType: FieldType.Text, + placeholder: "No data entered", + }, + ]; + } else if (props.monitorType === MonitorType.Port) { + fields = [ + { + key: "monitorDestination", + title: "Ping Hostname or IP Address", + description: + "Hostname or IP Address of the resource you would like us to ping.", + fieldType: FieldType.Text, + placeholder: "No data entered", + }, + { + key: "monitorDestinationPort", + title: "Port", + description: "Port of the resource you would like us to ping.", + fieldType: FieldType.Port, + placeholder: "No port entered", + }, + ]; + } else if (props.monitorType === MonitorType.IP) { + fields = [ + { + key: "monitorDestination", + title: "IP Address", + description: "IP Address of the resource you would like us to ping.", + fieldType: FieldType.Text, + placeholder: "No data entered", + }, + ]; + } else if (props.monitorType === MonitorType.CustomJavaScriptCode) { + fields = [ + { + key: "customCode", + title: "JavaScript Code", + description: "JavaScript code to run.", + fieldType: FieldType.JavaScript, + placeholder: "No data entered", + }, + ]; + } else if (props.monitorType === MonitorType.SyntheticMonitor) { + fields = [ + { + key: "customCode", + title: "JavaScript Code", + description: "JavaScript code to run.", + fieldType: FieldType.JavaScript, + placeholder: "No data entered", + }, + { + key: "browserTypes", + title: "Browser Types", + description: "Browser types to run the synthetic monitor on.", + fieldType: FieldType.ArrayOfText, + placeholder: "No data entered", + }, + { + key: "screenSizeTypes", + title: "Screen Size Types", + description: "Screen size types to run the synthetic monitor on.", + fieldType: FieldType.ArrayOfText, + placeholder: "No data entered", + }, + ]; + } + setRequestDetailsFields(fields); + }, [props.monitorType]); + + return ( + <div className="mt-5"> + <FieldLabelElement + title={"Request Details"} + description={ + "Here are the details of the request we will send to monitor your resource status." } - setRequestDetailsFields(fields); - }, [props.monitorType]); + required={true} + isHeading={true} + /> + <div className="mt-5"> + <Detail + id={"monitor-step"} + item={props.monitorStep.data as any} + fields={requestDetailsFields} + /> + </div> - return ( - <div className="mt-5"> - <FieldLabelElement - title={'Request Details'} - description={ - 'Here are the details of the request we will send to monitor your resource status.' - } - required={true} - isHeading={true} - /> - <div className="mt-5"> - <Detail - id={'monitor-step'} - item={props.monitorStep.data as any} - fields={requestDetailsFields} - /> - </div> + <HorizontalRule /> - <HorizontalRule /> + <div className="mt-5"> + <FieldLabelElement + title="Criteria" + isHeading={true} + description={ + "Criteria we will use to determine your resource status." + } + required={true} + /> - <div className="mt-5"> - <FieldLabelElement - title="Criteria" - isHeading={true} - description={ - 'Criteria we will use to determine your resource status.' - } - required={true} - /> - - <MonitorCriteriaElement - onCallPolicyOptions={props.onCallPolicyOptions} - monitorStatusOptions={props.monitorStatusOptions} - incidentSeverityOptions={props.incidentSeverityOptions} - monitorCriteria={ - props.monitorStep?.data - ?.monitorCriteria as MonitorCriteria - } - /> - </div> - </div> - ); + <MonitorCriteriaElement + onCallPolicyOptions={props.onCallPolicyOptions} + monitorStatusOptions={props.monitorStatusOptions} + incidentSeverityOptions={props.incidentSeverityOptions} + monitorCriteria={ + props.monitorStep?.data?.monitorCriteria as MonitorCriteria + } + /> + </div> + </div> + ); }; export default MonitorStepElement; diff --git a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorSteps.tsx b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorSteps.tsx index 8d07745a86..db2cec6abc 100644 --- a/Dashboard/src/Components/Monitor/MonitorSteps/MonitorSteps.tsx +++ b/Dashboard/src/Components/Monitor/MonitorSteps/MonitorSteps.tsx @@ -1,206 +1,198 @@ -import MonitorStepElement from './MonitorStep'; -import { Black } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import IconProp from 'Common/Types/Icon/IconProp'; -import MonitorStep from 'Common/Types/Monitor/MonitorStep'; -import MonitorSteps from 'Common/Types/Monitor/MonitorSteps'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { CustomElementProps } from 'CommonUI/src/Components/Forms/Types/Field'; -import Icon from 'CommonUI/src/Components/Icon/Icon'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import MonitorStepElement from "./MonitorStep"; +import { Black } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import IconProp from "Common/Types/Icon/IconProp"; +import MonitorStep from "Common/Types/Monitor/MonitorStep"; +import MonitorSteps from "Common/Types/Monitor/MonitorSteps"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { CustomElementProps } from "CommonUI/src/Components/Forms/Types/Field"; +import Icon from "CommonUI/src/Components/Icon/Icon"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { FunctionComponent, ReactElement, useState } from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps extends CustomElementProps { - monitorSteps: MonitorSteps; - monitorType: MonitorType; + monitorSteps: MonitorSteps; + monitorType: MonitorType; } const MonitorStepsElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [monitorStatusOptions, setMonitorStatusOptions] = React.useState< - Array<MonitorStatus> - >([]); + const [monitorStatusOptions, setMonitorStatusOptions] = React.useState< + Array<MonitorStatus> + >([]); - const [incidentSeverityOptions, setIncidentSeverityOptions] = - React.useState<Array<IncidentSeverity>>([]); + const [incidentSeverityOptions, setIncidentSeverityOptions] = React.useState< + Array<IncidentSeverity> + >([]); - const [onCallPolicyOptions, setOnCallPolicyOptions] = React.useState< - Array<OnCallDutyPolicy> - >([]); + const [onCallPolicyOptions, setOnCallPolicyOptions] = React.useState< + Array<OnCallDutyPolicy> + >([]); - const [isLoading, setIsLoading] = React.useState<boolean>(false); - const [error, setError] = React.useState<string>(''); + const [isLoading, setIsLoading] = React.useState<boolean>(false); + const [error, setError] = React.useState<string>(""); - const [defaultMonitorStatus, setDefaultMonitorStatus] = useState< - MonitorStatus | undefined - >(undefined); + const [defaultMonitorStatus, setDefaultMonitorStatus] = useState< + MonitorStatus | undefined + >(undefined); - const fetchDropdownOptions: () => Promise<void> = - async (): Promise<void> => { - setIsLoading(true); + const fetchDropdownOptions: () => Promise<void> = async (): Promise<void> => { + setIsLoading(true); - try { - const monitorStatusList: ListResult<MonitorStatus> = - await ModelAPI.getList({ - modelType: MonitorStatus, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - color: true, - isOperationalState: true, - }, - sort: {}, - }); + try { + const monitorStatusList: ListResult<MonitorStatus> = + await ModelAPI.getList({ + modelType: MonitorStatus, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + color: true, + isOperationalState: true, + }, + sort: {}, + }); - if (monitorStatusList.data) { - setMonitorStatusOptions(monitorStatusList.data); - setDefaultMonitorStatus( - monitorStatusList.data.find((status: MonitorStatus) => { - return status?.isOperationalState; - }) - ); - } + if (monitorStatusList.data) { + setMonitorStatusOptions(monitorStatusList.data); + setDefaultMonitorStatus( + monitorStatusList.data.find((status: MonitorStatus) => { + return status?.isOperationalState; + }), + ); + } - const incidentSeverityList: ListResult<IncidentSeverity> = - await ModelAPI.getList({ - modelType: IncidentSeverity, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - color: true, - }, - sort: {}, - }); + const incidentSeverityList: ListResult<IncidentSeverity> = + await ModelAPI.getList({ + modelType: IncidentSeverity, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + color: true, + }, + sort: {}, + }); - const onCallPolicyList: ListResult<OnCallDutyPolicy> = - await ModelAPI.getList({ - modelType: OnCallDutyPolicy, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - }, - sort: {}, - }); + const onCallPolicyList: ListResult<OnCallDutyPolicy> = + await ModelAPI.getList({ + modelType: OnCallDutyPolicy, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + }, + sort: {}, + }); - if (incidentSeverityList.data) { - setIncidentSeverityOptions( - incidentSeverityList.data as Array<IncidentSeverity> - ); - } + if (incidentSeverityList.data) { + setIncidentSeverityOptions( + incidentSeverityList.data as Array<IncidentSeverity>, + ); + } - if (onCallPolicyList.data) { - setOnCallPolicyOptions( - onCallPolicyList.data as Array<OnCallDutyPolicy> - ); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - useAsyncEffect(async () => { - await fetchDropdownOptions(); - }, []); - - if (isLoading) { - return <ComponentLoader></ComponentLoader>; + if (onCallPolicyList.data) { + setOnCallPolicyOptions( + onCallPolicyList.data as Array<OnCallDutyPolicy>, + ); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (!props.monitorSteps) { - return <div>Monitor Criteria not defined for this resource.</div>; - } + setIsLoading(false); + }; + useAsyncEffect(async () => { + await fetchDropdownOptions(); + }, []); - if (error) { - return <ErrorMessage error={error} />; - } + if (isLoading) { + return <ComponentLoader></ComponentLoader>; + } - return ( - <div> - {props.monitorSteps.data?.monitorStepsInstanceArray.map( - (i: MonitorStep, index: number) => { - return ( - <MonitorStepElement - monitorType={props.monitorType} - key={index} - monitorStatusOptions={monitorStatusOptions} - incidentSeverityOptions={incidentSeverityOptions} - monitorStep={i} - onCallPolicyOptions={onCallPolicyOptions} - /> - ); - } - )} + if (!props.monitorSteps) { + return <div>Monitor Criteria not defined for this resource.</div>; + } - <div className="mt-4 ml-0.5"> - <div className="flex"> - <Icon - icon={IconProp.AltGlobe} - className="h-5 w-5 text-gray-900" - /> - <div className="ml-1 -mt-0.5 flex-auto py-0.5 text-sm leading-5 text-gray-500"> - <span className="font-medium text-gray-900"> - Default Monitor Status - </span>{' '} - When no criteria is met, monitor status should be: - <div className="mt-3"> - {props.monitorSteps.data - ?.defaultMonitorStatusId && ( - <Statusbubble - color={ - (monitorStatusOptions.find( - (option: IncidentSeverity) => { - return ( - option.id?.toString() === - props.monitorSteps.data?.defaultMonitorStatusId?.toString() - ); - } - )?.color as Color) || Black - } - shouldAnimate={false} - text={ - (monitorStatusOptions.find( - (option: IncidentSeverity) => { - return ( - option.id?.toString() === - props.monitorSteps.data?.defaultMonitorStatusId?.toString() - ); - } - )?.name as string) || '' - } - /> - )} + if (error) { + return <ErrorMessage error={error} />; + } - {!props.monitorSteps.data?.defaultMonitorStatusId && - defaultMonitorStatus && ( - <Statusbubble - color={defaultMonitorStatus.color!} - text={defaultMonitorStatus.name!} - shouldAnimate={false} - /> - )} - </div> - </div> - </div> + return ( + <div> + {props.monitorSteps.data?.monitorStepsInstanceArray.map( + (i: MonitorStep, index: number) => { + return ( + <MonitorStepElement + monitorType={props.monitorType} + key={index} + monitorStatusOptions={monitorStatusOptions} + incidentSeverityOptions={incidentSeverityOptions} + monitorStep={i} + onCallPolicyOptions={onCallPolicyOptions} + /> + ); + }, + )} + + <div className="mt-4 ml-0.5"> + <div className="flex"> + <Icon icon={IconProp.AltGlobe} className="h-5 w-5 text-gray-900" /> + <div className="ml-1 -mt-0.5 flex-auto py-0.5 text-sm leading-5 text-gray-500"> + <span className="font-medium text-gray-900"> + Default Monitor Status + </span>{" "} + When no criteria is met, monitor status should be: + <div className="mt-3"> + {props.monitorSteps.data?.defaultMonitorStatusId && ( + <Statusbubble + color={ + (monitorStatusOptions.find((option: IncidentSeverity) => { + return ( + option.id?.toString() === + props.monitorSteps.data?.defaultMonitorStatusId?.toString() + ); + })?.color as Color) || Black + } + shouldAnimate={false} + text={ + (monitorStatusOptions.find((option: IncidentSeverity) => { + return ( + option.id?.toString() === + props.monitorSteps.data?.defaultMonitorStatusId?.toString() + ); + })?.name as string) || "" + } + /> + )} + + {!props.monitorSteps.data?.defaultMonitorStatusId && + defaultMonitorStatus && ( + <Statusbubble + color={defaultMonitorStatus.color!} + text={defaultMonitorStatus.name!} + shouldAnimate={false} + /> + )} </div> + </div> </div> - ); + </div> + </div> + ); }; export default MonitorStepsElement; diff --git a/Dashboard/src/Components/Monitor/MonitorTable.tsx b/Dashboard/src/Components/Monitor/MonitorTable.tsx index 1b3c345b11..15d4a91724 100644 --- a/Dashboard/src/Components/Monitor/MonitorTable.tsx +++ b/Dashboard/src/Components/Monitor/MonitorTable.tsx @@ -1,483 +1,441 @@ -import LabelsElement from '../../Components/Label/Labels'; -import MonitoringInterval from '../../Utils/MonitorIntervalDropdownOptions'; -import MonitorTypeUtil from '../../Utils/MonitorType'; -import DashboardNavigation from '../../Utils/Navigation'; -import MonitorSteps from '../Form/Monitor/MonitorSteps'; -import Route from 'Common/Types/API/Route'; -import { Black, Gray500 } from 'Common/Types/BrandColors'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import MonitorStepsType from 'Common/Types/Monitor/MonitorSteps'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; +import LabelsElement from "../../Components/Label/Labels"; +import MonitoringInterval from "../../Utils/MonitorIntervalDropdownOptions"; +import MonitorTypeUtil from "../../Utils/MonitorType"; +import DashboardNavigation from "../../Utils/Navigation"; +import MonitorSteps from "../Form/Monitor/MonitorSteps"; +import Route from "Common/Types/API/Route"; +import { Black, Gray500 } from "Common/Types/BrandColors"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import MonitorStepsType from "Common/Types/Monitor/MonitorSteps"; +import MonitorType from "Common/Types/Monitor/MonitorType"; import { - BulkActionFailed, - BulkActionOnClickProps, -} from 'CommonUI/src/Components/BulkUpdate/BulkUpdateForm'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; + BulkActionFailed, + BulkActionOnClickProps, +} from "CommonUI/src/Components/BulkUpdate/BulkUpdateForm"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; import { - CustomElementProps, - FormFieldStyleType, -} from 'CommonUI/src/Components/Forms/Types/Field'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import { ModalTableBulkDefaultActions } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import API from 'CommonUI/src/Utils/API/API'; -import Query from 'CommonUI/src/Utils/BaseDatabase/Query'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import React, { FunctionComponent, ReactElement } from 'react'; + CustomElementProps, + FormFieldStyleType, +} from "CommonUI/src/Components/Forms/Types/Field"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import { ModalTableBulkDefaultActions } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import API from "CommonUI/src/Utils/API/API"; +import Query from "CommonUI/src/Utils/BaseDatabase/Query"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - query?: Query<Monitor> | undefined; - viewPageRoute?: Route; - noItemsMessage?: string | undefined; - title?: string | undefined; - description?: string | undefined; + query?: Query<Monitor> | undefined; + viewPageRoute?: Route; + noItemsMessage?: string | undefined; + title?: string | undefined; + description?: string | undefined; } const MonitorsTable: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <ModelTable<Monitor> - modelType={Monitor} - name="Monitors" - id="Monitors-table" - bulkActions={{ - buttons: [ - { - title: 'Disable Monitor', - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - props: BulkActionOnClickProps<Monitor> - ) => { - const inProgressItems: Array<Monitor> = [ - ...props.items, - ]; // items to be disabled - const totalItems: Array<Monitor> = [...props.items]; // total items - const successItems: Array<Monitor> = []; // items that are disabled - const failedItems: Array< - BulkActionFailed<Monitor> - > = []; // items that failed to disable + return ( + <ModelTable<Monitor> + modelType={Monitor} + name="Monitors" + id="Monitors-table" + bulkActions={{ + buttons: [ + { + title: "Disable Monitor", + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async (props: BulkActionOnClickProps<Monitor>) => { + const inProgressItems: Array<Monitor> = [...props.items]; // items to be disabled + const totalItems: Array<Monitor> = [...props.items]; // total items + const successItems: Array<Monitor> = []; // items that are disabled + const failedItems: Array<BulkActionFailed<Monitor>> = []; // items that failed to disable - props.onBulkActionStart(); + props.onBulkActionStart(); - for (const monitor of totalItems) { - // remove this item from inProgressItems + for (const monitor of totalItems) { + // remove this item from inProgressItems - inProgressItems.splice( - inProgressItems.indexOf(monitor), - 1 - ); + inProgressItems.splice(inProgressItems.indexOf(monitor), 1); - try { - if (!monitor.id) { - throw new BadDataException( - 'Monitor ID not found' - ); - } + try { + if (!monitor.id) { + throw new BadDataException("Monitor ID not found"); + } - await ModelAPI.updateById<Monitor>({ - id: monitor.id, - modelType: Monitor, - data: { - disableActiveMonitoring: true, - }, - }); + await ModelAPI.updateById<Monitor>({ + id: monitor.id, + modelType: Monitor, + data: { + disableActiveMonitoring: true, + }, + }); - successItems.push(monitor); - } catch (err) { - failedItems.push({ - item: monitor, - failedMessage: - API.getFriendlyMessage(err), - }); - } + successItems.push(monitor); + } catch (err) { + failedItems.push({ + item: monitor, + failedMessage: API.getFriendlyMessage(err), + }); + } - props.onProgressInfo({ - totalItems: totalItems, - failed: failedItems, - successItems: successItems, - inProgressItems: inProgressItems, - }); - } + props.onProgressInfo({ + totalItems: totalItems, + failed: failedItems, + successItems: successItems, + inProgressItems: inProgressItems, + }); + } - props.onBulkActionEnd(); - }, + props.onBulkActionEnd(); + }, - icon: IconProp.Stop, - confirmTitle: (items: Array<Monitor>) => { - return `Disable ${items.length} Monitor(s)`; - }, - confirmMessage: (items: Array<Monitor>) => { - return `Are you sure you want to disable ${items.length} monitor(s)?`; - }, - }, - { - title: 'Enable Monitor', - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - props: BulkActionOnClickProps<Monitor> - ) => { - const inProgressItems: Array<Monitor> = [ - ...props.items, - ]; // items to be disabled - const totalItems: Array<Monitor> = [...props.items]; // total items - const successItems: Array<Monitor> = []; // items that are disabled - const failedItems: Array< - BulkActionFailed<Monitor> - > = []; // items that failed to disable + icon: IconProp.Stop, + confirmTitle: (items: Array<Monitor>) => { + return `Disable ${items.length} Monitor(s)`; + }, + confirmMessage: (items: Array<Monitor>) => { + return `Are you sure you want to disable ${items.length} monitor(s)?`; + }, + }, + { + title: "Enable Monitor", + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async (props: BulkActionOnClickProps<Monitor>) => { + const inProgressItems: Array<Monitor> = [...props.items]; // items to be disabled + const totalItems: Array<Monitor> = [...props.items]; // total items + const successItems: Array<Monitor> = []; // items that are disabled + const failedItems: Array<BulkActionFailed<Monitor>> = []; // items that failed to disable - props.onBulkActionStart(); + props.onBulkActionStart(); - for (const monitor of totalItems) { - // remove this item from inProgressItems + for (const monitor of totalItems) { + // remove this item from inProgressItems - inProgressItems.splice( - inProgressItems.indexOf(monitor), - 1 - ); + inProgressItems.splice(inProgressItems.indexOf(monitor), 1); - try { - if (!monitor.id) { - throw new BadDataException( - 'Monitor ID not found' - ); - } + try { + if (!monitor.id) { + throw new BadDataException("Monitor ID not found"); + } - await ModelAPI.updateById<Monitor>({ - id: monitor.id, - modelType: Monitor, - data: { - disableActiveMonitoring: false, - }, - }); + await ModelAPI.updateById<Monitor>({ + id: monitor.id, + modelType: Monitor, + data: { + disableActiveMonitoring: false, + }, + }); - successItems.push(monitor); - } catch (err) { - failedItems.push({ - item: monitor, - failedMessage: - API.getFriendlyMessage(err), - }); - } + successItems.push(monitor); + } catch (err) { + failedItems.push({ + item: monitor, + failedMessage: API.getFriendlyMessage(err), + }); + } - props.onProgressInfo({ - totalItems: totalItems, - failed: failedItems, - successItems: successItems, - inProgressItems: inProgressItems, - }); - } + props.onProgressInfo({ + totalItems: totalItems, + failed: failedItems, + successItems: successItems, + inProgressItems: inProgressItems, + }); + } - props.onBulkActionEnd(); - }, + props.onBulkActionEnd(); + }, - icon: IconProp.Play, - confirmTitle: (items: Array<Monitor>) => { - return `Enable ${items.length} Monitor(s)`; - }, - confirmMessage: (items: Array<Monitor>) => { - return `Are you sure you want to enable ${items.length} monitor(s) for active monitoring?`; - }, - }, - ModalTableBulkDefaultActions.Delete, - ], - }} - isDeleteable={false} - showViewIdButton={true} - isEditable={false} - isCreateable={true} - isViewable={true} - query={props.query || {}} - createEditModalWidth={ModalWidth.Large} - formSteps={[ - { - title: 'Monitor Info', - id: 'monitor-info', - }, - { - title: 'Criteria', - id: 'criteria', - showIf: (values: FormValues<Monitor>) => { - return values.monitorType !== MonitorType.Manual; - }, - }, - { - title: 'Interval', - id: 'monitoring-interval', - showIf: (values: FormValues<Monitor>) => { - return ( - values.monitorType !== MonitorType.Manual && - values.monitorType !== - MonitorType.IncomingRequest && - values.monitorType !== MonitorType.Server - ); - }, - }, - ]} - cardProps={{ - title: props.title || 'Monitors', - description: - props.description || - 'Here is a list of monitors for this project.', - }} - selectMoreFields={{ - disableActiveMonitoring: true, - }} - noItemsMessage={props.noItemsMessage || 'No monitors found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - stepId: 'monitor-info', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Monitor Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'monitor-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: false, - placeholder: 'Description', - }, - { - field: { - monitorType: true, - }, - title: 'Monitor Type', - stepId: 'monitor-info', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select Monitor Type', - dropdownOptions: - MonitorTypeUtil.monitorTypesAsDropdownOptions(), - }, - { - field: { - monitorSteps: true, - }, - stepId: 'criteria', - styleType: FormFieldStyleType.Heading, - title: 'Monitor Details', - fieldType: FormFieldSchemaType.CustomComponent, - required: true, - customValidation: (values: FormValues<Monitor>) => { - const error: string | null = - MonitorStepsType.getValidationError( - values.monitorSteps as MonitorStepsType, - values.monitorType as MonitorType - ); + icon: IconProp.Play, + confirmTitle: (items: Array<Monitor>) => { + return `Enable ${items.length} Monitor(s)`; + }, + confirmMessage: (items: Array<Monitor>) => { + return `Are you sure you want to enable ${items.length} monitor(s) for active monitoring?`; + }, + }, + ModalTableBulkDefaultActions.Delete, + ], + }} + isDeleteable={false} + showViewIdButton={true} + isEditable={false} + isCreateable={true} + isViewable={true} + query={props.query || {}} + createEditModalWidth={ModalWidth.Large} + formSteps={[ + { + title: "Monitor Info", + id: "monitor-info", + }, + { + title: "Criteria", + id: "criteria", + showIf: (values: FormValues<Monitor>) => { + return values.monitorType !== MonitorType.Manual; + }, + }, + { + title: "Interval", + id: "monitoring-interval", + showIf: (values: FormValues<Monitor>) => { + return ( + values.monitorType !== MonitorType.Manual && + values.monitorType !== MonitorType.IncomingRequest && + values.monitorType !== MonitorType.Server + ); + }, + }, + ]} + cardProps={{ + title: props.title || "Monitors", + description: + props.description || "Here is a list of monitors for this project.", + }} + selectMoreFields={{ + disableActiveMonitoring: true, + }} + noItemsMessage={props.noItemsMessage || "No monitors found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + stepId: "monitor-info", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Monitor Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "monitor-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: false, + placeholder: "Description", + }, + { + field: { + monitorType: true, + }, + title: "Monitor Type", + stepId: "monitor-info", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select Monitor Type", + dropdownOptions: MonitorTypeUtil.monitorTypesAsDropdownOptions(), + }, + { + field: { + monitorSteps: true, + }, + stepId: "criteria", + styleType: FormFieldStyleType.Heading, + title: "Monitor Details", + fieldType: FormFieldSchemaType.CustomComponent, + required: true, + customValidation: (values: FormValues<Monitor>) => { + const error: string | null = MonitorStepsType.getValidationError( + values.monitorSteps as MonitorStepsType, + values.monitorType as MonitorType, + ); - return error; - }, - getCustomElement: ( - value: FormValues<Monitor>, - props: CustomElementProps - ) => { - return ( - <MonitorSteps - {...props} - monitorType={ - value.monitorType || MonitorType.Manual - } - monitorName={value.name || ''} - /> - ); - }, - }, - { - field: { - monitoringInterval: true, - }, - stepId: 'monitoring-interval', - title: 'Monitoring Interval', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - fetchDropdownOptions: (item: FormValues<Monitor>) => { - let interval: Array<DropdownOption> = [ - ...MonitoringInterval, - ]; + return error; + }, + getCustomElement: ( + value: FormValues<Monitor>, + props: CustomElementProps, + ) => { + return ( + <MonitorSteps + {...props} + monitorType={value.monitorType || MonitorType.Manual} + monitorName={value.name || ""} + /> + ); + }, + }, + { + field: { + monitoringInterval: true, + }, + stepId: "monitoring-interval", + title: "Monitoring Interval", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + fetchDropdownOptions: (item: FormValues<Monitor>) => { + let interval: Array<DropdownOption> = [...MonitoringInterval]; - if ( - item && - (item.monitorType === - MonitorType.SyntheticMonitor || - item.monitorType === - MonitorType.CustomJavaScriptCode || - item.monitorType === MonitorType.SSLCertificate) - ) { - // remove the every minute option, every 5 minsm every 10 minutes - interval = interval.filter( - (option: DropdownOption) => { - return ( - option.value !== '* * * * *' && - option.value !== '*/5 * * * *' && - option.value !== '*/10 * * * *' - ); - } - ); + if ( + item && + (item.monitorType === MonitorType.SyntheticMonitor || + item.monitorType === MonitorType.CustomJavaScriptCode || + item.monitorType === MonitorType.SSLCertificate) + ) { + // remove the every minute option, every 5 minsm every 10 minutes + interval = interval.filter((option: DropdownOption) => { + return ( + option.value !== "* * * * *" && + option.value !== "*/5 * * * *" && + option.value !== "*/10 * * * *" + ); + }); - return Promise.resolve(interval); - } + return Promise.resolve(interval); + } - return Promise.resolve(interval); - }, + return Promise.resolve(interval); + }, - placeholder: 'Select Monitoring Interval', - }, - ]} - showRefreshButton={true} - viewPageRoute={props.viewPageRoute} - filters={[ - { - title: 'Name', - type: FieldType.Text, - field: { - name: true, - }, - }, - { - title: 'Monitor Type', - type: FieldType.Text, - field: { - monitorType: true, - }, - filterDropdownOptions: - MonitorTypeUtil.monitorTypesAsDropdownOptions(), - }, - { - title: 'Monitor Status', - type: FieldType.Entity, - field: { - currentMonitorStatus: { - color: true, - name: true, - }, - }, - filterEntityType: MonitorStatus, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - title: 'Labels', - type: FieldType.EntityArray, - field: { - labels: { - name: true, - color: true, - }, - }, - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - title: 'Created At', - type: FieldType.Date, - field: { - createdAt: true, - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - monitorType: true, - }, - title: 'Monitor Type', - type: FieldType.Text, - }, - { - field: { - currentMonitorStatus: { - color: true, - name: true, - }, - }, + placeholder: "Select Monitoring Interval", + }, + ]} + showRefreshButton={true} + viewPageRoute={props.viewPageRoute} + filters={[ + { + title: "Name", + type: FieldType.Text, + field: { + name: true, + }, + }, + { + title: "Monitor Type", + type: FieldType.Text, + field: { + monitorType: true, + }, + filterDropdownOptions: + MonitorTypeUtil.monitorTypesAsDropdownOptions(), + }, + { + title: "Monitor Status", + type: FieldType.Entity, + field: { + currentMonitorStatus: { + color: true, + name: true, + }, + }, + filterEntityType: MonitorStatus, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + title: "Labels", + type: FieldType.EntityArray, + field: { + labels: { + name: true, + color: true, + }, + }, + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + title: "Created At", + type: FieldType.Date, + field: { + createdAt: true, + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + monitorType: true, + }, + title: "Monitor Type", + type: FieldType.Text, + }, + { + field: { + currentMonitorStatus: { + color: true, + name: true, + }, + }, - title: 'Monitor Status', - type: FieldType.Entity, - getElement: (item: Monitor): ReactElement => { - if (!item['currentMonitorStatus']) { - throw new BadDataException( - 'Monitor Status not found' - ); - } + title: "Monitor Status", + type: FieldType.Entity, + getElement: (item: Monitor): ReactElement => { + if (!item["currentMonitorStatus"]) { + throw new BadDataException("Monitor Status not found"); + } - if (item && item['disableActiveMonitoring']) { - return ( - <Statusbubble - shouldAnimate={false} - color={Gray500} - text={'Disabled'} - /> - ); - } + if (item && item["disableActiveMonitoring"]) { + return ( + <Statusbubble + shouldAnimate={false} + color={Gray500} + text={"Disabled"} + /> + ); + } - return ( - <Statusbubble - color={item.currentMonitorStatus.color || Black} - shouldAnimate={true} - text={ - item.currentMonitorStatus.name || 'Unknown' - } - /> - ); - }, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, + return ( + <Statusbubble + color={item.currentMonitorStatus.color || Black} + shouldAnimate={true} + text={item.currentMonitorStatus.name || "Unknown"} + /> + ); + }, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, - getElement: (item: Monitor): ReactElement => { - return <LabelsElement labels={item['labels'] || []} />; - }, - }, - ]} - /> - ); + getElement: (item: Monitor): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + ); }; export default MonitorsTable; diff --git a/Dashboard/src/Components/Monitor/MonitoringIntervalElement.tsx b/Dashboard/src/Components/Monitor/MonitoringIntervalElement.tsx index 6d29604b3d..f92b5dabd9 100644 --- a/Dashboard/src/Components/Monitor/MonitoringIntervalElement.tsx +++ b/Dashboard/src/Components/Monitor/MonitoringIntervalElement.tsx @@ -1,27 +1,27 @@ -import MonitoringInterval from '../../Utils/MonitorIntervalDropdownOptions'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitoringInterval from "../../Utils/MonitorIntervalDropdownOptions"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitoringInterval: string; + monitoringInterval: string; } const MonitoringIntervalElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.monitoringInterval) { - return ( - <div> - { - MonitoringInterval.find((item: DropdownOption) => { - return item.value === props.monitoringInterval; - })?.label - } - </div> - ); - } + if (props.monitoringInterval) { + return ( + <div> + { + MonitoringInterval.find((item: DropdownOption) => { + return item.value === props.monitoringInterval; + })?.label + } + </div> + ); + } - return <div>No interval defined</div>; + return <div>No interval defined</div>; }; export default MonitoringIntervalElement; diff --git a/Dashboard/src/Components/Monitor/Monitors.tsx b/Dashboard/src/Components/Monitor/Monitors.tsx index 28749449bd..b8256a287d 100644 --- a/Dashboard/src/Components/Monitor/Monitors.tsx +++ b/Dashboard/src/Components/Monitor/Monitors.tsx @@ -1,31 +1,31 @@ -import MonitorElement from './Monitor'; -import TableColumnListComponent from 'CommonUI/src/Components/TableColumnList/TableColumnListComponent'; -import Monitor from 'Model/Models/Monitor'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitorElement from "./Monitor"; +import TableColumnListComponent from "CommonUI/src/Components/TableColumnList/TableColumnListComponent"; +import Monitor from "Model/Models/Monitor"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitors: Array<Monitor>; - onNavigateComplete?: (() => void) | undefined; + monitors: Array<Monitor>; + onNavigateComplete?: (() => void) | undefined; } const MonitorsElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <TableColumnListComponent - items={props.monitors} - moreText="more monitors" - getEachElement={(monitor: Monitor) => { - return ( - <MonitorElement - monitor={monitor} - onNavigateComplete={props.onNavigateComplete} - /> - ); - }} - noItemsMessage="No monitors." - /> - ); + return ( + <TableColumnListComponent + items={props.monitors} + moreText="more monitors" + getEachElement={(monitor: Monitor) => { + return ( + <MonitorElement + monitor={monitor} + onNavigateComplete={props.onNavigateComplete} + /> + ); + }} + noItemsMessage="No monitors." + /> + ); }; export default MonitorsElement; diff --git a/Dashboard/src/Components/Monitor/ServerMonitor/Documentation.tsx b/Dashboard/src/Components/Monitor/ServerMonitor/Documentation.tsx index 912fb3c5e2..22798e7edc 100644 --- a/Dashboard/src/Components/Monitor/ServerMonitor/Documentation.tsx +++ b/Dashboard/src/Components/Monitor/ServerMonitor/Documentation.tsx @@ -1,34 +1,34 @@ -import ObjectID from 'Common/Types/ObjectID'; -import Card from 'CommonUI/src/Components/Card/Card'; -import CodeBlock from 'CommonUI/src/Components/CodeBlock/CodeBlock'; -import { HOST, HTTP_PROTOCOL } from 'CommonUI/src/Config'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ObjectID from "Common/Types/ObjectID"; +import Card from "CommonUI/src/Components/Card/Card"; +import CodeBlock from "CommonUI/src/Components/CodeBlock/CodeBlock"; +import { HOST, HTTP_PROTOCOL } from "CommonUI/src/Config"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - secretKey: ObjectID; + secretKey: ObjectID; } const ServerMonitorDocumentation: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const host: string = `${HTTP_PROTOCOL}${HOST}`; + const host: string = `${HTTP_PROTOCOL}${HOST}`; - return ( - <> - <Card - title={`Set up your Server Monitor`} - description={ - <div className="space-y-2 w-full mt-5"> - <CodeBlock - language="bash" - code={` + return ( + <> + <Card + title={`Set up your Server Monitor`} + description={ + <div className="space-y-2 w-full mt-5"> + <CodeBlock + language="bash" + code={` # Install the agent curl -s ${HTTP_PROTOCOL}${HOST.toString()}/docs/static/scripts/infrastructure-agent/install.sh | sudo bash # Configure the agent sudo oneuptime-infrastructure-agent configure --secret-key=${props.secretKey.toString()} ${ - '--oneuptime-url=' + host - } + "--oneuptime-url=" + host + } # To Start sudo oneuptime-infrastructure-agent start @@ -36,12 +36,12 @@ sudo oneuptime-infrastructure-agent start # To Stop sudo oneuptime-infrastructure-agent stop `} - /> - </div> - } /> - </> - ); + </div> + } + /> + </> + ); }; export default ServerMonitorDocumentation; diff --git a/Dashboard/src/Components/Monitor/SummaryView/CustomMonitorSummaryView.tsx b/Dashboard/src/Components/Monitor/SummaryView/CustomMonitorSummaryView.tsx index 8230e2013d..7a143a5ec2 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/CustomMonitorSummaryView.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/CustomMonitorSummaryView.tsx @@ -1,145 +1,139 @@ -import OneUptimeDate from 'Common/Types/Date'; -import CustomCodeMonitorResponse from 'Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Detail from 'CommonUI/src/Components/Detail/Detail'; -import Field from 'CommonUI/src/Components/Detail/Field'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import InfoCard from 'CommonUI/src/Components/InfoCard/InfoCard'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import React, { FunctionComponent, ReactElement } from 'react'; +import OneUptimeDate from "Common/Types/Date"; +import CustomCodeMonitorResponse from "Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Detail from "CommonUI/src/Components/Detail/Detail"; +import Field from "CommonUI/src/Components/Detail/Field"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import InfoCard from "CommonUI/src/Components/InfoCard/InfoCard"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - customCodeMonitorResponse: CustomCodeMonitorResponse; - moreDetailElement?: ReactElement; - monitoredAt: Date; + customCodeMonitorResponse: CustomCodeMonitorResponse; + moreDetailElement?: ReactElement; + monitoredAt: Date; } const CustomCodeMonitorSummaryView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.customCodeMonitorResponse) { - return ( - <ErrorMessage error="No summary available for the selected probe. Should be few minutes for summary to show up. " /> - ); - } - - const [showMoreDetails, setShowMoreDetails] = - React.useState<boolean>(false); - - const customMonitorResponse: CustomCodeMonitorResponse = - props.customCodeMonitorResponse; - - let executionTimeInMS: number = - customMonitorResponse.executionTimeInMS || 0; - - if (executionTimeInMS > 0) { - executionTimeInMS = Math.round(executionTimeInMS); - } - - const fields: Array<Field<CustomCodeMonitorResponse>> = []; - - if ( - customMonitorResponse.logMessages && - customMonitorResponse.logMessages.length > 0 - ) { - fields.push({ - key: 'logMessages', - title: 'Log Messages', - description: 'Log messages from the script execution.', - fieldType: FieldType.JSON, - }); - } - - if (customMonitorResponse.result) { - fields.push({ - key: 'result', - title: 'Result', - description: 'Result of the script execution.', - fieldType: FieldType.JSON, - }); - } - - if (customMonitorResponse.scriptError) { - fields.push({ - key: 'scriptError', - title: 'Script Error', - description: 'Error message from script execution.', - fieldType: FieldType.Text, - }); - } - + if (!props.customCodeMonitorResponse) { return ( - <div className="space-y-5"> - <div className="space-y-5"> - <div className="flex space-x-3 w-full"> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Execution Time (in ms)" - value={ - executionTimeInMS ? executionTimeInMS + ' ms' : '-' - } - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Error" - value={customMonitorResponse.scriptError ? 'Yes' : 'No'} - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Monitored At" - value={ - props.monitoredAt - ? OneUptimeDate.getDateAsLocalFormattedString( - props.monitoredAt - ) - : '-' - } - /> - </div> - - {showMoreDetails && ( - <div> - <Detail<CustomCodeMonitorResponse> - id={'custom-code-monitor-summary-detail'} - item={customMonitorResponse} - fields={fields} - showDetailsInNumberOfColumns={1} - /> - - {props.moreDetailElement && props.moreDetailElement} - </div> - )} - - {!showMoreDetails && ( - <div className="-ml-2"> - <Button - buttonStyle={ButtonStyleType.SECONDARY_LINK} - title="Show More Details" - onClick={() => { - return setShowMoreDetails(true); - }} - /> - </div> - )} - - {/* Hide details button */} - - {showMoreDetails && ( - <div className="-ml-2"> - <Button - buttonStyle={ButtonStyleType.SECONDARY_LINK} - title="Hide Details" - onClick={() => { - return setShowMoreDetails(false); - }} - /> - </div> - )} - </div> - </div> + <ErrorMessage error="No summary available for the selected probe. Should be few minutes for summary to show up. " /> ); + } + + const [showMoreDetails, setShowMoreDetails] = React.useState<boolean>(false); + + const customMonitorResponse: CustomCodeMonitorResponse = + props.customCodeMonitorResponse; + + let executionTimeInMS: number = customMonitorResponse.executionTimeInMS || 0; + + if (executionTimeInMS > 0) { + executionTimeInMS = Math.round(executionTimeInMS); + } + + const fields: Array<Field<CustomCodeMonitorResponse>> = []; + + if ( + customMonitorResponse.logMessages && + customMonitorResponse.logMessages.length > 0 + ) { + fields.push({ + key: "logMessages", + title: "Log Messages", + description: "Log messages from the script execution.", + fieldType: FieldType.JSON, + }); + } + + if (customMonitorResponse.result) { + fields.push({ + key: "result", + title: "Result", + description: "Result of the script execution.", + fieldType: FieldType.JSON, + }); + } + + if (customMonitorResponse.scriptError) { + fields.push({ + key: "scriptError", + title: "Script Error", + description: "Error message from script execution.", + fieldType: FieldType.Text, + }); + } + + return ( + <div className="space-y-5"> + <div className="space-y-5"> + <div className="flex space-x-3 w-full"> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Execution Time (in ms)" + value={executionTimeInMS ? executionTimeInMS + " ms" : "-"} + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Error" + value={customMonitorResponse.scriptError ? "Yes" : "No"} + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Monitored At" + value={ + props.monitoredAt + ? OneUptimeDate.getDateAsLocalFormattedString(props.monitoredAt) + : "-" + } + /> + </div> + + {showMoreDetails && ( + <div> + <Detail<CustomCodeMonitorResponse> + id={"custom-code-monitor-summary-detail"} + item={customMonitorResponse} + fields={fields} + showDetailsInNumberOfColumns={1} + /> + + {props.moreDetailElement && props.moreDetailElement} + </div> + )} + + {!showMoreDetails && ( + <div className="-ml-2"> + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Show More Details" + onClick={() => { + return setShowMoreDetails(true); + }} + /> + </div> + )} + + {/* Hide details button */} + + {showMoreDetails && ( + <div className="-ml-2"> + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Hide Details" + onClick={() => { + return setShowMoreDetails(false); + }} + /> + </div> + )} + </div> + </div> + ); }; export default CustomCodeMonitorSummaryView; diff --git a/Dashboard/src/Components/Monitor/SummaryView/IncomingRequestMonitorSummaryView.tsx b/Dashboard/src/Components/Monitor/SummaryView/IncomingRequestMonitorSummaryView.tsx index 3e1f400416..eaf834791f 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/IncomingRequestMonitorSummaryView.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/IncomingRequestMonitorSummaryView.tsx @@ -1,100 +1,98 @@ -import OneUptimeDate from 'Common/Types/Date'; -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Detail from 'CommonUI/src/Components/Detail/Detail'; -import Field from 'CommonUI/src/Components/Detail/Field'; -import InfoCard from 'CommonUI/src/Components/InfoCard/InfoCard'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import React, { FunctionComponent, ReactElement } from 'react'; +import OneUptimeDate from "Common/Types/Date"; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Detail from "CommonUI/src/Components/Detail/Detail"; +import Field from "CommonUI/src/Components/Detail/Field"; +import InfoCard from "CommonUI/src/Components/InfoCard/InfoCard"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - incomingMonitorRequest: IncomingMonitorRequest; + incomingMonitorRequest: IncomingMonitorRequest; } const IncomingRequestMonitorView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [showMoreDetails, setShowMoreDetails] = - React.useState<boolean>(false); + const [showMoreDetails, setShowMoreDetails] = React.useState<boolean>(false); - const fields: Array<Field<IncomingMonitorRequest>> = [ - { - key: 'requestHeaders', - title: 'Request Headers', - description: 'Request headers of the request.', - fieldType: FieldType.JSON, - }, - ]; + const fields: Array<Field<IncomingMonitorRequest>> = [ + { + key: "requestHeaders", + title: "Request Headers", + description: "Request headers of the request.", + fieldType: FieldType.JSON, + }, + ]; - if (props.incomingMonitorRequest?.requestBody) { - fields.push({ - key: 'requestBody', - title: 'Request Body', - description: 'Request body of the request.', - fieldType: FieldType.JSON, - }); - } + if (props.incomingMonitorRequest?.requestBody) { + fields.push({ + key: "requestBody", + title: "Request Body", + description: "Request body of the request.", + fieldType: FieldType.JSON, + }); + } - return ( - <div className="space-y-5"> - <div className="flex space-x-3 justify-between"> - <InfoCard - className="w-1/2 shadow-none border-2 border-gray-100" - title="Request Received At" - value={ - props.incomingMonitorRequest?.incomingRequestReceivedAt - ? OneUptimeDate.getDateAsLocalFormattedString( - props.incomingMonitorRequest - .incomingRequestReceivedAt - ) - : '-' - } - /> - <InfoCard - className="w-1/2 shadow-none border-2 border-gray-100" - title="Request Method" - value={props.incomingMonitorRequest.requestMethod || '-'} - /> - </div> + return ( + <div className="space-y-5"> + <div className="flex space-x-3 justify-between"> + <InfoCard + className="w-1/2 shadow-none border-2 border-gray-100" + title="Request Received At" + value={ + props.incomingMonitorRequest?.incomingRequestReceivedAt + ? OneUptimeDate.getDateAsLocalFormattedString( + props.incomingMonitorRequest.incomingRequestReceivedAt, + ) + : "-" + } + /> + <InfoCard + className="w-1/2 shadow-none border-2 border-gray-100" + title="Request Method" + value={props.incomingMonitorRequest.requestMethod || "-"} + /> + </div> - {showMoreDetails && ( - <div> - <Detail<IncomingMonitorRequest> - id={'website-monitor-summary-detail'} - item={props.incomingMonitorRequest} - fields={fields} - showDetailsInNumberOfColumns={1} - /> - </div> - )} - - {!showMoreDetails && ( - <div className="-ml-2"> - <Button - buttonStyle={ButtonStyleType.SECONDARY_LINK} - title="Show More Details" - onClick={() => { - return setShowMoreDetails(true); - }} - /> - </div> - )} - - {/* Hide details button */} - - {showMoreDetails && ( - <div className="-ml-3"> - <Button - buttonStyle={ButtonStyleType.SECONDARY_LINK} - title="Hide Details" - onClick={() => { - return setShowMoreDetails(false); - }} - /> - </div> - )} + {showMoreDetails && ( + <div> + <Detail<IncomingMonitorRequest> + id={"website-monitor-summary-detail"} + item={props.incomingMonitorRequest} + fields={fields} + showDetailsInNumberOfColumns={1} + /> </div> - ); + )} + + {!showMoreDetails && ( + <div className="-ml-2"> + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Show More Details" + onClick={() => { + return setShowMoreDetails(true); + }} + /> + </div> + )} + + {/* Hide details button */} + + {showMoreDetails && ( + <div className="-ml-3"> + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Hide Details" + onClick={() => { + return setShowMoreDetails(false); + }} + /> + </div> + )} + </div> + ); }; export default IncomingRequestMonitorView; diff --git a/Dashboard/src/Components/Monitor/SummaryView/PingMonitorView.tsx b/Dashboard/src/Components/Monitor/SummaryView/PingMonitorView.tsx index aadf17984a..fbaf267553 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/PingMonitorView.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/PingMonitorView.tsx @@ -1,67 +1,62 @@ -import OneUptimeDate from 'Common/Types/Date'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import InfoCard from 'CommonUI/src/Components/InfoCard/InfoCard'; -import React, { FunctionComponent, ReactElement } from 'react'; +import OneUptimeDate from "Common/Types/Date"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import InfoCard from "CommonUI/src/Components/InfoCard/InfoCard"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - probeMonitorResponse: ProbeMonitorResponse; + probeMonitorResponse: ProbeMonitorResponse; } const PingMonitorView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let responseTimeInMs: number = - props.probeMonitorResponse?.responseTimeInMs || 0; + let responseTimeInMs: number = + props.probeMonitorResponse?.responseTimeInMs || 0; - if (responseTimeInMs > 0) { - responseTimeInMs = Math.round(responseTimeInMs); - } + if (responseTimeInMs > 0) { + responseTimeInMs = Math.round(responseTimeInMs); + } - return ( - <div className="space-y-5"> - <div className="flex space-x-3"> - <InfoCard - className="w-full shadow-none border-2 border-gray-100 " - title="Hostname or IP address" - value={ - (props.probeMonitorResponse.monitorDestination?.toString() || - '') + - (props.probeMonitorResponse.monitorDestinationPort?.toString() - ? `:${props.probeMonitorResponse.monitorDestinationPort.toString()}` - : '') || '-' - } - /> - </div> - <div className="flex space-x-3"> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Status" - value={ - props.probeMonitorResponse.isOnline - ? 'Online' - : 'Offline' - } - /> + return ( + <div className="space-y-5"> + <div className="flex space-x-3"> + <InfoCard + className="w-full shadow-none border-2 border-gray-100 " + title="Hostname or IP address" + value={ + (props.probeMonitorResponse.monitorDestination?.toString() || "") + + (props.probeMonitorResponse.monitorDestinationPort?.toString() + ? `:${props.probeMonitorResponse.monitorDestinationPort.toString()}` + : "") || "-" + } + /> + </div> + <div className="flex space-x-3"> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Status" + value={props.probeMonitorResponse.isOnline ? "Online" : "Offline"} + /> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Response Time (in ms)" - value={responseTimeInMs ? responseTimeInMs + ' ms' : '-'} - /> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Monitored At" - value={ - props.probeMonitorResponse?.monitoredAt - ? OneUptimeDate.getDateAsLocalFormattedString( - props.probeMonitorResponse.monitoredAt - ) - : '-' - } - /> - </div> - </div> - ); + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Response Time (in ms)" + value={responseTimeInMs ? responseTimeInMs + " ms" : "-"} + /> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Monitored At" + value={ + props.probeMonitorResponse?.monitoredAt + ? OneUptimeDate.getDateAsLocalFormattedString( + props.probeMonitorResponse.monitoredAt, + ) + : "-" + } + /> + </div> + </div> + ); }; export default PingMonitorView; diff --git a/Dashboard/src/Components/Monitor/SummaryView/ProbeNowButton.tsx b/Dashboard/src/Components/Monitor/SummaryView/ProbeNowButton.tsx index 3cd93d4e8c..1aa40e6495 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/ProbeNowButton.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/ProbeNowButton.tsx @@ -1,7 +1,7 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; const ProbeNowButton: FunctionComponent = (): ReactElement => { - return <div></div>; + return <div></div>; }; export default ProbeNowButton; diff --git a/Dashboard/src/Components/Monitor/SummaryView/ProbePicker.tsx b/Dashboard/src/Components/Monitor/SummaryView/ProbePicker.tsx index b174dd531e..07320c387a 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/ProbePicker.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/ProbePicker.tsx @@ -1,62 +1,55 @@ import Dropdown, { - DropdownOption, - DropdownValue, -} from 'CommonUI/src/Components/Dropdown/Dropdown'; -import FieldLabelElement from 'CommonUI/src/Components/Forms/Fields/FieldLabel'; -import Probe from 'Model/Models/Probe'; -import React, { FunctionComponent, ReactElement } from 'react'; + DropdownOption, + DropdownValue, +} from "CommonUI/src/Components/Dropdown/Dropdown"; +import FieldLabelElement from "CommonUI/src/Components/Forms/Fields/FieldLabel"; +import Probe from "Model/Models/Probe"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onProbeSelected?: (probe: Probe) => void; - probes: Array<Probe>; - selectedProbe?: Probe; + onProbeSelected?: (probe: Probe) => void; + probes: Array<Probe>; + selectedProbe?: Probe; } const ProbePicker: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const dropdownOptions: Array<DropdownOption> = props.probes.map( - (probe: Probe) => { - return { - label: probe.name?.toString() || 'Unknown', - value: probe._id?.toString() || '', - }; - } - ); + const dropdownOptions: Array<DropdownOption> = props.probes.map( + (probe: Probe) => { + return { + label: probe.name?.toString() || "Unknown", + value: probe._id?.toString() || "", + }; + }, + ); - return ( - <div className="flex"> - <div className="w-fit mr-2 flex h-full align-middle items-center"> - <FieldLabelElement title="Select Probe:" required={true} /> - </div> - <div> - <Dropdown - value={dropdownOptions.find((option: DropdownOption) => { - return ( - option.value === - props.selectedProbe?._id?.toString() - ); - })} - options={dropdownOptions} - onChange={( - value: DropdownValue | Array<DropdownValue> | null - ) => { - value = value?.toString() || ''; + return ( + <div className="flex"> + <div className="w-fit mr-2 flex h-full align-middle items-center"> + <FieldLabelElement title="Select Probe:" required={true} /> + </div> + <div> + <Dropdown + value={dropdownOptions.find((option: DropdownOption) => { + return option.value === props.selectedProbe?._id?.toString(); + })} + options={dropdownOptions} + onChange={(value: DropdownValue | Array<DropdownValue> | null) => { + value = value?.toString() || ""; - const probe: Probe | undefined = props.probes.find( - (p: Probe) => { - return p._id?.toString() === value; - } - ); + const probe: Probe | undefined = props.probes.find((p: Probe) => { + return p._id?.toString() === value; + }); - if (props.onProbeSelected && probe) { - props.onProbeSelected(probe); - } - }} - /> - </div> - </div> - ); + if (props.onProbeSelected && probe) { + props.onProbeSelected(probe); + } + }} + /> + </div> + </div> + ); }; export default ProbePicker; diff --git a/Dashboard/src/Components/Monitor/SummaryView/SSLCertificateMonitorView.tsx b/Dashboard/src/Components/Monitor/SummaryView/SSLCertificateMonitorView.tsx index 6ba9c4c768..1664c9a00b 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/SSLCertificateMonitorView.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/SSLCertificateMonitorView.tsx @@ -1,175 +1,166 @@ -import OneUptimeDate from 'Common/Types/Date'; -import SslMonitorResponse from 'Common/Types/Monitor/SSLMonitor/SslMonitorResponse'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import InfoCard from 'CommonUI/src/Components/InfoCard/InfoCard'; -import React, { FunctionComponent, ReactElement } from 'react'; +import OneUptimeDate from "Common/Types/Date"; +import SslMonitorResponse from "Common/Types/Monitor/SSLMonitor/SslMonitorResponse"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import InfoCard from "CommonUI/src/Components/InfoCard/InfoCard"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - probeMonitorResponse: ProbeMonitorResponse; + probeMonitorResponse: ProbeMonitorResponse; } const SSLCertificateMonitorView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if ( - !props.probeMonitorResponse || - !props.probeMonitorResponse.sslResponse - ) { - return ( - <ErrorMessage error="No summary available for the selected probe. Should be few minutes for summary to show up. " /> - ); - } - - const sslResponse: SslMonitorResponse = - props.probeMonitorResponse.sslResponse; - - const [showMoreDetails, setShowMoreDetails] = - React.useState<boolean>(false); - + if (!props.probeMonitorResponse || !props.probeMonitorResponse.sslResponse) { return ( - <div className="space-y-5"> - <div className="space-y-5"> - <div className="flex space-x-3"> - <InfoCard - className="w-full shadow-none border-2 border-gray-100 " - title="URL" - value={ - props.probeMonitorResponse.monitorDestination?.toString() || - '-' - } - /> - </div> - <div className="flex space-x-3 w-full"> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="SSL Status" - value={ - sslResponse.isSelfSigned - ? 'Self Signed' - : 'Signed by a CA' - } - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Issued At" - value={ - sslResponse.createdAt - ? OneUptimeDate.getDateAsLocalFormattedString( - sslResponse.createdAt - ) - : '-' - } - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Expires At" - value={ - sslResponse.expiresAt - ? OneUptimeDate.getDateAsLocalFormattedString( - sslResponse.expiresAt - ) - : '-' - } - /> - </div> - - {showMoreDetails && ( - <div className="space-y-5"> - <div className="flex space-x-3 w-full"> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Common Name" - value={sslResponse.commonName || '-'} - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Organizational Unit" - value={sslResponse.organizationalUnit || '-'} - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Organization" - value={sslResponse.organization || '-'} - /> - </div> - <div className="flex space-x-3 w-full"> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Locality" - value={sslResponse.locality || '-'} - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="State" - value={sslResponse.state || '-'} - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Country" - value={sslResponse.country || '-'} - /> - </div> - <div className="flex space-x-3 w-full"> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Serial Number" - value={sslResponse.serialNumber || '-'} - textClassName="text-xs truncate" - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Fingerprint" - value={sslResponse.fingerprint || '-'} - textClassName="text-xs truncate" - /> - - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Fingerprint 256" - value={sslResponse.fingerprint256 || '-'} - textClassName="text-xs truncate" - /> - </div> - </div> - )} - - {!showMoreDetails && ( - <div className="-ml-2"> - <Button - buttonStyle={ButtonStyleType.SECONDARY_LINK} - title="Show More Details" - onClick={() => { - return setShowMoreDetails(true); - }} - /> - </div> - )} - - {/* Hide details button */} - - {showMoreDetails && ( - <div className="-ml-2"> - <Button - buttonStyle={ButtonStyleType.SECONDARY_LINK} - title="Hide Details" - onClick={() => { - return setShowMoreDetails(false); - }} - /> - </div> - )} - </div> - </div> + <ErrorMessage error="No summary available for the selected probe. Should be few minutes for summary to show up. " /> ); + } + + const sslResponse: SslMonitorResponse = + props.probeMonitorResponse.sslResponse; + + const [showMoreDetails, setShowMoreDetails] = React.useState<boolean>(false); + + return ( + <div className="space-y-5"> + <div className="space-y-5"> + <div className="flex space-x-3"> + <InfoCard + className="w-full shadow-none border-2 border-gray-100 " + title="URL" + value={ + props.probeMonitorResponse.monitorDestination?.toString() || "-" + } + /> + </div> + <div className="flex space-x-3 w-full"> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="SSL Status" + value={sslResponse.isSelfSigned ? "Self Signed" : "Signed by a CA"} + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Issued At" + value={ + sslResponse.createdAt + ? OneUptimeDate.getDateAsLocalFormattedString( + sslResponse.createdAt, + ) + : "-" + } + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Expires At" + value={ + sslResponse.expiresAt + ? OneUptimeDate.getDateAsLocalFormattedString( + sslResponse.expiresAt, + ) + : "-" + } + /> + </div> + + {showMoreDetails && ( + <div className="space-y-5"> + <div className="flex space-x-3 w-full"> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Common Name" + value={sslResponse.commonName || "-"} + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Organizational Unit" + value={sslResponse.organizationalUnit || "-"} + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Organization" + value={sslResponse.organization || "-"} + /> + </div> + <div className="flex space-x-3 w-full"> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Locality" + value={sslResponse.locality || "-"} + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="State" + value={sslResponse.state || "-"} + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Country" + value={sslResponse.country || "-"} + /> + </div> + <div className="flex space-x-3 w-full"> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Serial Number" + value={sslResponse.serialNumber || "-"} + textClassName="text-xs truncate" + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Fingerprint" + value={sslResponse.fingerprint || "-"} + textClassName="text-xs truncate" + /> + + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Fingerprint 256" + value={sslResponse.fingerprint256 || "-"} + textClassName="text-xs truncate" + /> + </div> + </div> + )} + + {!showMoreDetails && ( + <div className="-ml-2"> + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Show More Details" + onClick={() => { + return setShowMoreDetails(true); + }} + /> + </div> + )} + + {/* Hide details button */} + + {showMoreDetails && ( + <div className="-ml-2"> + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Hide Details" + onClick={() => { + return setShowMoreDetails(false); + }} + /> + </div> + )} + </div> + </div> + ); }; export default SSLCertificateMonitorView; diff --git a/Dashboard/src/Components/Monitor/SummaryView/Screenshot.tsx b/Dashboard/src/Components/Monitor/SummaryView/Screenshot.tsx index 18ca234163..c4083ab5db 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/Screenshot.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/Screenshot.tsx @@ -1,29 +1,29 @@ -import { Screenshot } from 'Common/Types/Monitor/SyntheticMonitors/Screenshot'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { Screenshot } from "Common/Types/Monitor/SyntheticMonitors/Screenshot"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - screenshot: Screenshot; - screenshotName: string; + screenshot: Screenshot; + screenshotName: string; } const SummarysScreenshot: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - /// props.scresnshot is in base64 format - return ( - <div className="w-fit"> - <div className=""> - <img - className="rounded-md w-fit h-fit shadow-md m-1" - src={`data:image/png;base64,${props.screenshot}`} - alt={props.screenshotName} - /> - </div> - <div className="text-gray-500 m-1 w-full flex justify-center"> - {props.screenshotName} - </div> - </div> - ); + /// props.scresnshot is in base64 format + return ( + <div className="w-fit"> + <div className=""> + <img + className="rounded-md w-fit h-fit shadow-md m-1" + src={`data:image/png;base64,${props.screenshot}`} + alt={props.screenshotName} + /> + </div> + <div className="text-gray-500 m-1 w-full flex justify-center"> + {props.screenshotName} + </div> + </div> + ); }; export default SummarysScreenshot; diff --git a/Dashboard/src/Components/Monitor/SummaryView/ScreenshotGroup.tsx b/Dashboard/src/Components/Monitor/SummaryView/ScreenshotGroup.tsx index 72c481d5a2..5c315bec82 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/ScreenshotGroup.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/ScreenshotGroup.tsx @@ -1,51 +1,43 @@ -import SummarysScreenshot from './Screenshot'; +import SummarysScreenshot from "./Screenshot"; import Screenshots, { - Screenshot, -} from 'Common/Types/Monitor/SyntheticMonitors/Screenshot'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import React, { FunctionComponent, ReactElement } from 'react'; + Screenshot, +} from "Common/Types/Monitor/SyntheticMonitors/Screenshot"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - screenshots: Screenshots | undefined; + screenshots: Screenshots | undefined; } const SummaryScreenshotGroup: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div> - <div className="mt-2 mb-2">Screenshots:</div> - <div className="space-y-5"> - {!props.screenshots || - Object.keys(props.screenshots).length === 0 ? ( - <ErrorMessage error="No screenshots available." /> - ) : ( - Object.keys(props.screenshots)?.map( - (screenshotName: string, index: number) => { - if ( - !props.screenshots || - !props.screenshots[screenshotName] - ) { - return <></>; - } + return ( + <div> + <div className="mt-2 mb-2">Screenshots:</div> + <div className="space-y-5"> + {!props.screenshots || Object.keys(props.screenshots).length === 0 ? ( + <ErrorMessage error="No screenshots available." /> + ) : ( + Object.keys(props.screenshots)?.map( + (screenshotName: string, index: number) => { + if (!props.screenshots || !props.screenshots[screenshotName]) { + return <></>; + } - return ( - <SummarysScreenshot - key={index} - screenshot={ - props.screenshots[ - screenshotName - ] as Screenshot - } - screenshotName={screenshotName} - /> - ); - } - ) - )} - </div> - </div> - ); + return ( + <SummarysScreenshot + key={index} + screenshot={props.screenshots[screenshotName] as Screenshot} + screenshotName={screenshotName} + /> + ); + }, + ) + )} + </div> + </div> + ); }; export default SummaryScreenshotGroup; diff --git a/Dashboard/src/Components/Monitor/SummaryView/Summary.tsx b/Dashboard/src/Components/Monitor/SummaryView/Summary.tsx index 6b96416226..dba8c73c76 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/Summary.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/Summary.tsx @@ -1,91 +1,91 @@ -import ProbePicker from './ProbePicker'; -import SummaryInfo from './SummaryInfo'; -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; +import ProbePicker from "./ProbePicker"; +import SummaryInfo from "./SummaryInfo"; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; import MonitorType, { - MonitorTypeHelper, -} from 'Common/Types/Monitor/MonitorType'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import Card from 'CommonUI/src/Components/Card/Card'; -import { MonitorStepProbeResponse } from 'Model/Models/MonitorProbe'; -import Probe from 'Model/Models/Probe'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; + MonitorTypeHelper, +} from "Common/Types/Monitor/MonitorType"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import Card from "CommonUI/src/Components/Card/Card"; +import { MonitorStepProbeResponse } from "Model/Models/MonitorProbe"; +import Probe from "Model/Models/Probe"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - probeMonitorResponses?: Array<MonitorStepProbeResponse> | undefined; - incomingMonitorRequest?: IncomingMonitorRequest | undefined; - probes?: Array<Probe>; - monitorType: MonitorType; + probeMonitorResponses?: Array<MonitorStepProbeResponse> | undefined; + incomingMonitorRequest?: IncomingMonitorRequest | undefined; + probes?: Array<Probe>; + monitorType: MonitorType; } const Summary: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [selectedProbe, setSelectedProbe] = React.useState<Probe | undefined>( - undefined - ); + const [selectedProbe, setSelectedProbe] = React.useState<Probe | undefined>( + undefined, + ); - useEffect(() => { - // slect first probe if exists + useEffect(() => { + // slect first probe if exists - if (props.probes && props.probes.length > 0) { - setSelectedProbe(props.probes[0]); - } - }, [props.probes]); - - if (props.monitorType === MonitorType.Manual) { - return <></>; + if (props.probes && props.probes.length > 0) { + setSelectedProbe(props.probes[0]); } + }, [props.probes]); - const probeResponses: Array<ProbeMonitorResponse> = []; + if (props.monitorType === MonitorType.Manual) { + return <></>; + } - for (const probeResponse of props.probeMonitorResponses || []) { - for (const monitorStepId in probeResponse) { - const probeMonitorResponse: ProbeMonitorResponse = probeResponse[ - monitorStepId - ] as ProbeMonitorResponse; - if ( - probeMonitorResponse.probeId?.toString() === - selectedProbe?.id?.toString() - ) { - probeResponses.push(probeMonitorResponse); - } - } + const probeResponses: Array<ProbeMonitorResponse> = []; + + for (const probeResponse of props.probeMonitorResponses || []) { + for (const monitorStepId in probeResponse) { + const probeMonitorResponse: ProbeMonitorResponse = probeResponse[ + monitorStepId + ] as ProbeMonitorResponse; + if ( + probeMonitorResponse.probeId?.toString() === + selectedProbe?.id?.toString() + ) { + probeResponses.push(probeMonitorResponse); + } } + } - if (props.monitorType === MonitorType.Server) { - return <></>; - } + if (props.monitorType === MonitorType.Server) { + return <></>; + } - return ( - <Card - title="Monitor Summary" - description="Here is how your monitor is performing at this moment." - rightElement={ - MonitorTypeHelper.isProbableMonitors(props.monitorType) && - props.probes && - props.probes.length > 0 && - selectedProbe ? ( - <ProbePicker - probes={props.probes} - selectedProbe={selectedProbe} - onProbeSelected={(probe: Probe) => { - setSelectedProbe(probe); - }} - /> - ) : ( - <></> - ) - } - > - <div> - <SummaryInfo - monitorType={props.monitorType} - probeMonitorResponses={probeResponses} - incomingMonitorRequest={props.incomingMonitorRequest} - /> - </div> - </Card> - ); + return ( + <Card + title="Monitor Summary" + description="Here is how your monitor is performing at this moment." + rightElement={ + MonitorTypeHelper.isProbableMonitors(props.monitorType) && + props.probes && + props.probes.length > 0 && + selectedProbe ? ( + <ProbePicker + probes={props.probes} + selectedProbe={selectedProbe} + onProbeSelected={(probe: Probe) => { + setSelectedProbe(probe); + }} + /> + ) : ( + <></> + ) + } + > + <div> + <SummaryInfo + monitorType={props.monitorType} + probeMonitorResponses={probeResponses} + incomingMonitorRequest={props.incomingMonitorRequest} + /> + </div> + </Card> + ); }; export default Summary; diff --git a/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx b/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx index cb2d9d59a7..7f1d1a5ca2 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/SummaryInfo.tsx @@ -1,142 +1,135 @@ -import IncomingRequestMonitorView from './IncomingRequestMonitorSummaryView'; -import PingMonitorView from './PingMonitorView'; -import SSLCertificateMonitorView from './SSLCertificateMonitorView'; -import SyntheticMonitorView from './SyntheticMonitorView'; -import WebsiteMonitorSummaryView from './WebsiteMonitorView'; -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; +import IncomingRequestMonitorView from "./IncomingRequestMonitorSummaryView"; +import PingMonitorView from "./PingMonitorView"; +import SSLCertificateMonitorView from "./SSLCertificateMonitorView"; +import SyntheticMonitorView from "./SyntheticMonitorView"; +import WebsiteMonitorSummaryView from "./WebsiteMonitorView"; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; import MonitorType, { - MonitorTypeHelper, -} from 'Common/Types/Monitor/MonitorType'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import React, { FunctionComponent, ReactElement } from 'react'; + MonitorTypeHelper, +} from "Common/Types/Monitor/MonitorType"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitorType: MonitorType; - probeMonitorResponses?: Array<ProbeMonitorResponse> | undefined; // this is an array because of multiple monitor steps. - incomingMonitorRequest?: IncomingMonitorRequest | undefined; + monitorType: MonitorType; + probeMonitorResponses?: Array<ProbeMonitorResponse> | undefined; // this is an array because of multiple monitor steps. + incomingMonitorRequest?: IncomingMonitorRequest | undefined; } const SummaryInfo: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - type GetProbeableMonitorSummarysInfo = ( - probeMonitorResponse: ProbeMonitorResponse, - key: number - ) => ReactElement; + type GetProbeableMonitorSummarysInfo = ( + probeMonitorResponse: ProbeMonitorResponse, + key: number, + ) => ReactElement; - const getProbableMonitorSummarysInfo: GetProbeableMonitorSummarysInfo = ( - probeMonitorResponse: ProbeMonitorResponse, - key: number - ): ReactElement => { - if (!probeMonitorResponse) { - return ( - <ErrorMessage - error={ - 'No summary available for the selected probe. Should be few minutes for summary to show up. ' - } - /> - ); - } - - if ( - props.monitorType === MonitorType.Website || - props.monitorType === MonitorType.API - ) { - return ( - <WebsiteMonitorSummaryView - key={key} - probeMonitorResponse={probeMonitorResponse} - /> - ); - } - - if ( - props.monitorType === MonitorType.Ping || - props.monitorType === MonitorType.IP || - props.monitorType === MonitorType.Port - ) { - return ( - <PingMonitorView - key={key} - probeMonitorResponse={probeMonitorResponse} - /> - ); - } - - if (props.monitorType === MonitorType.SSLCertificate) { - return ( - <SSLCertificateMonitorView - key={key} - probeMonitorResponse={probeMonitorResponse} - /> - ); - } - - if (props.monitorType === MonitorType.SyntheticMonitor) { - return ( - <SyntheticMonitorView - key={key} - probeMonitorResponse={probeMonitorResponse} - /> - ); - } - - return <></>; - }; - - if ( - MonitorTypeHelper.isProbableMonitors(props.monitorType) && - (!props.probeMonitorResponses || - props.probeMonitorResponses.length === 0) - ) { - return ( - <ErrorMessage - error={ - 'No summary available for the selected probe. Should be few minutes for summary to show up. ' - } - /> - ); + const getProbableMonitorSummarysInfo: GetProbeableMonitorSummarysInfo = ( + probeMonitorResponse: ProbeMonitorResponse, + key: number, + ): ReactElement => { + if (!probeMonitorResponse) { + return ( + <ErrorMessage + error={ + "No summary available for the selected probe. Should be few minutes for summary to show up. " + } + /> + ); } if ( - !props.incomingMonitorRequest && - props.monitorType === MonitorType.IncomingRequest + props.monitorType === MonitorType.Website || + props.monitorType === MonitorType.API ) { - return ( - <ErrorMessage - error={ - 'No summary available for the selected probe. Should be few minutes for summary to show up. ' - } - /> - ); + return ( + <WebsiteMonitorSummaryView + key={key} + probeMonitorResponse={probeMonitorResponse} + /> + ); } + if ( + props.monitorType === MonitorType.Ping || + props.monitorType === MonitorType.IP || + props.monitorType === MonitorType.Port + ) { + return ( + <PingMonitorView + key={key} + probeMonitorResponse={probeMonitorResponse} + /> + ); + } + + if (props.monitorType === MonitorType.SSLCertificate) { + return ( + <SSLCertificateMonitorView + key={key} + probeMonitorResponse={probeMonitorResponse} + /> + ); + } + + if (props.monitorType === MonitorType.SyntheticMonitor) { + return ( + <SyntheticMonitorView + key={key} + probeMonitorResponse={probeMonitorResponse} + /> + ); + } + + return <></>; + }; + + if ( + MonitorTypeHelper.isProbableMonitors(props.monitorType) && + (!props.probeMonitorResponses || props.probeMonitorResponses.length === 0) + ) { return ( - <div> - {props.probeMonitorResponses && - props.probeMonitorResponses.map( - ( - probeMonitorResponse: ProbeMonitorResponse, - index: number - ) => { - return getProbableMonitorSummarysInfo( - probeMonitorResponse, - index - ); - } - )} - - {props.incomingMonitorRequest && - props.monitorType === MonitorType.IncomingRequest ? ( - <IncomingRequestMonitorView - incomingMonitorRequest={props.incomingMonitorRequest} - /> - ) : ( - <></> - )} - </div> + <ErrorMessage + error={ + "No summary available for the selected probe. Should be few minutes for summary to show up. " + } + /> ); + } + + if ( + !props.incomingMonitorRequest && + props.monitorType === MonitorType.IncomingRequest + ) { + return ( + <ErrorMessage + error={ + "No summary available for the selected probe. Should be few minutes for summary to show up. " + } + /> + ); + } + + return ( + <div> + {props.probeMonitorResponses && + props.probeMonitorResponses.map( + (probeMonitorResponse: ProbeMonitorResponse, index: number) => { + return getProbableMonitorSummarysInfo(probeMonitorResponse, index); + }, + )} + + {props.incomingMonitorRequest && + props.monitorType === MonitorType.IncomingRequest ? ( + <IncomingRequestMonitorView + incomingMonitorRequest={props.incomingMonitorRequest} + /> + ) : ( + <></> + )} + </div> + ); }; export default SummaryInfo; diff --git a/Dashboard/src/Components/Monitor/SummaryView/SyntheticMonitorItemView.tsx b/Dashboard/src/Components/Monitor/SummaryView/SyntheticMonitorItemView.tsx index adc7cc2f7f..d4920be869 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/SyntheticMonitorItemView.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/SyntheticMonitorItemView.tsx @@ -1,52 +1,50 @@ -import CustomCodeMonitorSummaryView from './CustomMonitorSummaryView'; -import SummaryScreenshotGroup from './ScreenshotGroup'; -import SyntheticMonitorResponse from 'Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import React, { FunctionComponent, ReactElement } from 'react'; +import CustomCodeMonitorSummaryView from "./CustomMonitorSummaryView"; +import SummaryScreenshotGroup from "./ScreenshotGroup"; +import SyntheticMonitorResponse from "Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - syntheticMonitorResponse: SyntheticMonitorResponse; - monitoredAt: Date; + syntheticMonitorResponse: SyntheticMonitorResponse; + monitoredAt: Date; } const SyntheticMonitorItemView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.syntheticMonitorResponse) { - return ( - <ErrorMessage error="No summary available for the selected probe. Should be few minutes for summary to show up. " /> - ); - } - - const syntheticMonitorResponse: SyntheticMonitorResponse = - props.syntheticMonitorResponse; - - const getMoreDetails: GetReactElementFunction = (): ReactElement => { - return ( - <div> - <SummaryScreenshotGroup - screenshots={ - props.syntheticMonitorResponse.screenshots || {} - } - /> - </div> - ); - }; - + if (!props.syntheticMonitorResponse) { return ( - <div> - <div className="mb-3"> - {props.syntheticMonitorResponse.browserType} -{' '} - {props.syntheticMonitorResponse.screenSizeType} - </div> - <CustomCodeMonitorSummaryView - customCodeMonitorResponse={syntheticMonitorResponse} - monitoredAt={props.monitoredAt} - moreDetailElement={getMoreDetails()} - /> - </div> + <ErrorMessage error="No summary available for the selected probe. Should be few minutes for summary to show up. " /> ); + } + + const syntheticMonitorResponse: SyntheticMonitorResponse = + props.syntheticMonitorResponse; + + const getMoreDetails: GetReactElementFunction = (): ReactElement => { + return ( + <div> + <SummaryScreenshotGroup + screenshots={props.syntheticMonitorResponse.screenshots || {}} + /> + </div> + ); + }; + + return ( + <div> + <div className="mb-3"> + {props.syntheticMonitorResponse.browserType} -{" "} + {props.syntheticMonitorResponse.screenSizeType} + </div> + <CustomCodeMonitorSummaryView + customCodeMonitorResponse={syntheticMonitorResponse} + monitoredAt={props.monitoredAt} + moreDetailElement={getMoreDetails()} + /> + </div> + ); }; export default SyntheticMonitorItemView; diff --git a/Dashboard/src/Components/Monitor/SummaryView/SyntheticMonitorView.tsx b/Dashboard/src/Components/Monitor/SummaryView/SyntheticMonitorView.tsx index aef482f0c5..b67de8a1d9 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/SyntheticMonitorView.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/SyntheticMonitorView.tsx @@ -1,58 +1,53 @@ -import SyntheticMonitorItemView from './SyntheticMonitorItemView'; -import SyntheticMonitorResponse from 'Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import HorizontalRule from 'CommonUI/src/Components/HorizontalRule/HorizontalRule'; -import React, { FunctionComponent, ReactElement } from 'react'; +import SyntheticMonitorItemView from "./SyntheticMonitorItemView"; +import SyntheticMonitorResponse from "Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import HorizontalRule from "CommonUI/src/Components/HorizontalRule/HorizontalRule"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - probeMonitorResponse: ProbeMonitorResponse; + probeMonitorResponse: ProbeMonitorResponse; } const SyntheticMonitorView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if ( - !props.probeMonitorResponse || - !props.probeMonitorResponse.syntheticMonitorResponse - ) { - return ( - <ErrorMessage error="No summary available for the selected probe. Should be few minutes for summary to show up. " /> - ); - } - - const syntheticMonitorResponses: Array<SyntheticMonitorResponse> = - props.probeMonitorResponse.syntheticMonitorResponse; - + if ( + !props.probeMonitorResponse || + !props.probeMonitorResponse.syntheticMonitorResponse + ) { return ( - <div> - {syntheticMonitorResponses && - syntheticMonitorResponses.map( - ( - syntheticMonitorResponse: SyntheticMonitorResponse, - index: number - ) => { - return ( - <div key={index}> - <SyntheticMonitorItemView - key={index} - syntheticMonitorResponse={ - syntheticMonitorResponse - } - monitoredAt={ - props.probeMonitorResponse.monitoredAt - } - /> - {index !== - syntheticMonitorResponses.length - 1 && ( - <HorizontalRule /> - )} - </div> - ); - } - )} - </div> + <ErrorMessage error="No summary available for the selected probe. Should be few minutes for summary to show up. " /> ); + } + + const syntheticMonitorResponses: Array<SyntheticMonitorResponse> = + props.probeMonitorResponse.syntheticMonitorResponse; + + return ( + <div> + {syntheticMonitorResponses && + syntheticMonitorResponses.map( + ( + syntheticMonitorResponse: SyntheticMonitorResponse, + index: number, + ) => { + return ( + <div key={index}> + <SyntheticMonitorItemView + key={index} + syntheticMonitorResponse={syntheticMonitorResponse} + monitoredAt={props.probeMonitorResponse.monitoredAt} + /> + {index !== syntheticMonitorResponses.length - 1 && ( + <HorizontalRule /> + )} + </div> + ); + }, + )} + </div> + ); }; export default SyntheticMonitorView; diff --git a/Dashboard/src/Components/Monitor/SummaryView/WebsiteMonitorView.tsx b/Dashboard/src/Components/Monitor/SummaryView/WebsiteMonitorView.tsx index 50f5ae4a19..c67d8cb652 100644 --- a/Dashboard/src/Components/Monitor/SummaryView/WebsiteMonitorView.tsx +++ b/Dashboard/src/Components/Monitor/SummaryView/WebsiteMonitorView.tsx @@ -1,139 +1,131 @@ -import OneUptimeDate from 'Common/Types/Date'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Detail from 'CommonUI/src/Components/Detail/Detail'; -import Field from 'CommonUI/src/Components/Detail/Field'; -import InfoCard from 'CommonUI/src/Components/InfoCard/InfoCard'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import React, { FunctionComponent, ReactElement } from 'react'; +import OneUptimeDate from "Common/Types/Date"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Detail from "CommonUI/src/Components/Detail/Detail"; +import Field from "CommonUI/src/Components/Detail/Field"; +import InfoCard from "CommonUI/src/Components/InfoCard/InfoCard"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - probeMonitorResponse: ProbeMonitorResponse; + probeMonitorResponse: ProbeMonitorResponse; } const WebsiteMonitorSummaryView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [showMoreDetails, setShowMoreDetails] = - React.useState<boolean>(false); + const [showMoreDetails, setShowMoreDetails] = React.useState<boolean>(false); - let responseTimeInMs: number = - props.probeMonitorResponse?.responseTimeInMs || 0; + let responseTimeInMs: number = + props.probeMonitorResponse?.responseTimeInMs || 0; - if (responseTimeInMs > 0) { - responseTimeInMs = Math.round(responseTimeInMs); - } + if (responseTimeInMs > 0) { + responseTimeInMs = Math.round(responseTimeInMs); + } - const fields: Array<Field<ProbeMonitorResponse>> = []; + const fields: Array<Field<ProbeMonitorResponse>> = []; - if (props.probeMonitorResponse?.responseHeaders) { - fields.push({ - key: 'responseHeaders', - title: 'Response Headers', - description: 'Response headers of the request.', - fieldType: FieldType.JSON, - }); - } + if (props.probeMonitorResponse?.responseHeaders) { + fields.push({ + key: "responseHeaders", + title: "Response Headers", + description: "Response headers of the request.", + fieldType: FieldType.JSON, + }); + } - if (props.probeMonitorResponse?.responseBody) { - fields.push({ - key: 'responseBody', - title: 'Response Body', - description: 'Response body of the request.', - fieldType: FieldType.JSON, - }); - } + if (props.probeMonitorResponse?.responseBody) { + fields.push({ + key: "responseBody", + title: "Response Body", + description: "Response body of the request.", + fieldType: FieldType.JSON, + }); + } - return ( - <div className="space-y-5"> - <div className="flex space-x-3"> - <InfoCard - className="w-full shadow-none border-2 border-gray-100 " - title="URL" - value={ - props.probeMonitorResponse.monitorDestination?.toString() || - '-' - } - /> - </div> - <div className="flex space-x-3"> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Response Staus Code" - value={ - props.probeMonitorResponse?.responseCode?.toString() || - '-' - } - /> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Response Time (in ms)" - value={responseTimeInMs ? responseTimeInMs + ' ms' : '-'} - /> - <InfoCard - className="w-1/3 shadow-none border-2 border-gray-100 " - title="Monitored At" - value={ - props.probeMonitorResponse?.monitoredAt - ? OneUptimeDate.getDateAsLocalFormattedString( - props.probeMonitorResponse.monitoredAt - ) - : '-' - } - /> - </div> + return ( + <div className="space-y-5"> + <div className="flex space-x-3"> + <InfoCard + className="w-full shadow-none border-2 border-gray-100 " + title="URL" + value={ + props.probeMonitorResponse.monitorDestination?.toString() || "-" + } + /> + </div> + <div className="flex space-x-3"> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Response Staus Code" + value={props.probeMonitorResponse?.responseCode?.toString() || "-"} + /> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Response Time (in ms)" + value={responseTimeInMs ? responseTimeInMs + " ms" : "-"} + /> + <InfoCard + className="w-1/3 shadow-none border-2 border-gray-100 " + title="Monitored At" + value={ + props.probeMonitorResponse?.monitoredAt + ? OneUptimeDate.getDateAsLocalFormattedString( + props.probeMonitorResponse.monitoredAt, + ) + : "-" + } + /> + </div> - {props.probeMonitorResponse.failureCause && ( - <div className="flex space-x-3"> - <InfoCard - className="w-full shadow-none border-2 border-gray-100 " - title="Error" - value={ - props.probeMonitorResponse.failureCause?.toString() || - '-' - } - /> - </div> - )} - - {showMoreDetails && fields.length > 0 && ( - <div> - <Detail<ProbeMonitorResponse> - id={'website-monitor-summary-detail'} - item={props.probeMonitorResponse} - fields={fields} - showDetailsInNumberOfColumns={1} - /> - </div> - )} - - {!showMoreDetails && fields.length > 0 && ( - <div className="-ml-2"> - <Button - buttonStyle={ButtonStyleType.SECONDARY_LINK} - title="Show More Details" - onClick={() => { - return setShowMoreDetails(true); - }} - /> - </div> - )} - - {/* Hide details button */} - - {showMoreDetails && fields.length > 0 && ( - <div className="-ml-3"> - <Button - buttonStyle={ButtonStyleType.SECONDARY_LINK} - title="Hide Details" - onClick={() => { - return setShowMoreDetails(false); - }} - /> - </div> - )} + {props.probeMonitorResponse.failureCause && ( + <div className="flex space-x-3"> + <InfoCard + className="w-full shadow-none border-2 border-gray-100 " + title="Error" + value={props.probeMonitorResponse.failureCause?.toString() || "-"} + /> </div> - ); + )} + + {showMoreDetails && fields.length > 0 && ( + <div> + <Detail<ProbeMonitorResponse> + id={"website-monitor-summary-detail"} + item={props.probeMonitorResponse} + fields={fields} + showDetailsInNumberOfColumns={1} + /> + </div> + )} + + {!showMoreDetails && fields.length > 0 && ( + <div className="-ml-2"> + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Show More Details" + onClick={() => { + return setShowMoreDetails(true); + }} + /> + </div> + )} + + {/* Hide details button */} + + {showMoreDetails && fields.length > 0 && ( + <div className="-ml-3"> + <Button + buttonStyle={ButtonStyleType.SECONDARY_LINK} + title="Hide Details" + onClick={() => { + return setShowMoreDetails(false); + }} + /> + </div> + )} + </div> + ); }; export default WebsiteMonitorSummaryView; diff --git a/Dashboard/src/Components/MonitorGroup/CurrentStatus.tsx b/Dashboard/src/Components/MonitorGroup/CurrentStatus.tsx index 3879382d43..6cc61cbf24 100644 --- a/Dashboard/src/Components/MonitorGroup/CurrentStatus.tsx +++ b/Dashboard/src/Components/MonitorGroup/CurrentStatus.tsx @@ -1,76 +1,76 @@ -import URL from 'Common/Types/API/URL'; -import Color from 'Common/Types/Color'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import Loader from 'CommonUI/src/Components/Loader/Loader'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import { APP_API_URL } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import MonitorGroup from 'Model/Models/MonitorGroup'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import URL from "Common/Types/API/URL"; +import Color from "Common/Types/Color"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import Loader from "CommonUI/src/Components/Loader/Loader"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import { APP_API_URL } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import MonitorGroup from "Model/Models/MonitorGroup"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - monitorGroupId: ObjectID; + monitorGroupId: ObjectID; } const CurrentStatusElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = React.useState<boolean>(true); + const [isLoading, setIsLoading] = React.useState<boolean>(true); - const [currentGroupStatus, setCurrentGroupStatus] = - React.useState<MonitorStatus | null>(null); + const [currentGroupStatus, setCurrentGroupStatus] = + React.useState<MonitorStatus | null>(null); - const [error, setError] = React.useState<string | undefined>(undefined); + const [error, setError] = React.useState<string | undefined>(undefined); - const loadCurrentStatus: PromiseVoidFunction = async (): Promise<void> => { - setIsLoading(true); + const loadCurrentStatus: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); - try { - const currentStatus: MonitorStatus | null = - await ModelAPI.post<MonitorStatus>({ - modelType: MonitorStatus, - apiUrl: URL.fromString(APP_API_URL.toString()) - .addRoute(new MonitorGroup().getCrudApiPath()!) - .addRoute('/current-status/') - .addRoute(`/${props.monitorGroupId.toString()}`), - }); + try { + const currentStatus: MonitorStatus | null = + await ModelAPI.post<MonitorStatus>({ + modelType: MonitorStatus, + apiUrl: URL.fromString(APP_API_URL.toString()) + .addRoute(new MonitorGroup().getCrudApiPath()!) + .addRoute("/current-status/") + .addRoute(`/${props.monitorGroupId.toString()}`), + }); - setCurrentGroupStatus(currentStatus); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - useEffect(() => { - loadCurrentStatus().catch(() => {}); - }, []); - - if (isLoading) { - return <Loader />; + setCurrentGroupStatus(currentStatus); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (error) { - return <ErrorMessage error={error} />; - } + setIsLoading(false); + }; - if (!currentGroupStatus) { - throw new BadDataException('Current Group Status not found'); - } + useEffect(() => { + loadCurrentStatus().catch(() => {}); + }, []); - return ( - <Statusbubble - color={currentGroupStatus.color! as Color} - text={currentGroupStatus.name! as string} - shouldAnimate={true} - /> - ); + if (isLoading) { + return <Loader />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + if (!currentGroupStatus) { + throw new BadDataException("Current Group Status not found"); + } + + return ( + <Statusbubble + color={currentGroupStatus.color! as Color} + text={currentGroupStatus.name! as string} + shouldAnimate={true} + /> + ); }; export default CurrentStatusElement; diff --git a/Dashboard/src/Components/MonitorGroup/MonitorGroupElement.tsx b/Dashboard/src/Components/MonitorGroup/MonitorGroupElement.tsx index a1cbfcb9c2..6ddb24f7d0 100644 --- a/Dashboard/src/Components/MonitorGroup/MonitorGroupElement.tsx +++ b/Dashboard/src/Components/MonitorGroup/MonitorGroupElement.tsx @@ -1,50 +1,47 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import Icon from 'CommonUI/src/Components/Icon/Icon'; -import Link from 'CommonUI/src/Components/Link/Link'; -import MonitorGroup from 'Model/Models/MonitorGroup'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import Icon from "CommonUI/src/Components/Icon/Icon"; +import Link from "CommonUI/src/Components/Link/Link"; +import MonitorGroup from "Model/Models/MonitorGroup"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitorGroup: MonitorGroup; - onNavigateComplete?: (() => void) | undefined; - showIcon?: boolean; + monitorGroup: MonitorGroup; + onNavigateComplete?: (() => void) | undefined; + showIcon?: boolean; } const MonitorGroupElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.monitorGroup._id) { - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_GROUP_VIEW] as Route, - { - modelId: new ObjectID(props.monitorGroup._id as string), - } - )} - > - <span className="flex"> - {props.showIcon ? ( - <Icon - icon={IconProp.Squares} - className="w-5 h-5 mr-1" - /> - ) : ( - <></> - )}{' '} - {props.monitorGroup.name} - </span> - </Link> - ); - } + if (props.monitorGroup._id) { + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_GROUP_VIEW] as Route, + { + modelId: new ObjectID(props.monitorGroup._id as string), + }, + )} + > + <span className="flex"> + {props.showIcon ? ( + <Icon icon={IconProp.Squares} className="w-5 h-5 mr-1" /> + ) : ( + <></> + )}{" "} + {props.monitorGroup.name} + </span> + </Link> + ); + } - return <span>{props.monitorGroup.name}</span>; + return <span>{props.monitorGroup.name}</span>; }; export default MonitorGroupElement; diff --git a/Dashboard/src/Components/MonitorStatus/MonitorStatusElement.tsx b/Dashboard/src/Components/MonitorStatus/MonitorStatusElement.tsx index 589c68f30e..372411ffc2 100644 --- a/Dashboard/src/Components/MonitorStatus/MonitorStatusElement.tsx +++ b/Dashboard/src/Components/MonitorStatus/MonitorStatusElement.tsx @@ -1,14 +1,14 @@ -import MonitorStatus from 'Model/Models/MonitorStatus'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitorStatus from "Model/Models/MonitorStatus"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitorStatus: MonitorStatus; + monitorStatus: MonitorStatus; } const TeamElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return <span>{props.monitorStatus.name}</span>; + return <span>{props.monitorStatus.name}</span>; }; export default TeamElement; diff --git a/Dashboard/src/Components/MonitorStatus/MonitorStatusesElement.tsx b/Dashboard/src/Components/MonitorStatus/MonitorStatusesElement.tsx index 8578d1987d..05f8326c46 100644 --- a/Dashboard/src/Components/MonitorStatus/MonitorStatusesElement.tsx +++ b/Dashboard/src/Components/MonitorStatus/MonitorStatusesElement.tsx @@ -1,36 +1,30 @@ -import MonitorStatusElement from './MonitorStatusElement'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitorStatusElement from "./MonitorStatusElement"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitorStatuses: Array<MonitorStatus>; + monitorStatuses: Array<MonitorStatus>; } const MonitorStatusesElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.monitorStatuses || props.monitorStatuses.length === 0) { - return <p>No monitor status attached.</p>; - } + if (!props.monitorStatuses || props.monitorStatuses.length === 0) { + return <p>No monitor status attached.</p>; + } - return ( - <div> - {props.monitorStatuses.map( - (monitorStatus: MonitorStatus, i: number) => { - return ( - <span key={i}> - <MonitorStatusElement - monitorStatus={monitorStatus} - /> - {i !== props.monitorStatuses.length - 1 && ( - <span>, </span> - )} - </span> - ); - } - )} - </div> - ); + return ( + <div> + {props.monitorStatuses.map((monitorStatus: MonitorStatus, i: number) => { + return ( + <span key={i}> + <MonitorStatusElement monitorStatus={monitorStatus} /> + {i !== props.monitorStatuses.length - 1 && <span>, </span>} + </span> + ); + })} + </div> + ); }; export default MonitorStatusesElement; diff --git a/Dashboard/src/Components/NavBar/NavBar.tsx b/Dashboard/src/Components/NavBar/NavBar.tsx index 112c15fcdc..f3c3f0b33e 100644 --- a/Dashboard/src/Components/NavBar/NavBar.tsx +++ b/Dashboard/src/Components/NavBar/NavBar.tsx @@ -1,152 +1,149 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import NavBar from 'CommonUI/src/Components/Navbar/NavBar'; -import NavBarItem from 'CommonUI/src/Components/Navbar/NavBarItem'; -import NavBarMenu from 'CommonUI/src/Components/Navbar/NavBarMenu'; -import NavBarMenuItem from 'CommonUI/src/Components/Navbar/NavBarMenuItem'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import NavBar from "CommonUI/src/Components/Navbar/NavBar"; +import NavBarItem from "CommonUI/src/Components/Navbar/NavBarItem"; +import NavBarMenu from "CommonUI/src/Components/Navbar/NavBarMenu"; +import NavBarMenuItem from "CommonUI/src/Components/Navbar/NavBarMenuItem"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - show: boolean; + show: boolean; } const DashboardNavbar: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isComponentVisible, setIsComponentVisible] = - useState<boolean>(false); - const [moreMenuTimeout, setMoreMenuTimeout] = useState<ReturnType< - typeof setTimeout - > | null>(null); + const [isComponentVisible, setIsComponentVisible] = useState<boolean>(false); + const [moreMenuTimeout, setMoreMenuTimeout] = useState<ReturnType< + typeof setTimeout + > | null>(null); - const hideMoreMenu: VoidFunction = (): void => { - if (moreMenuTimeout) { - clearTimeout(moreMenuTimeout); - setMoreMenuTimeout(null); - } - - const timeout: ReturnType<typeof setTimeout> = setTimeout(() => { - setIsComponentVisible(false); - }, 500); - - setMoreMenuTimeout(timeout); - }; - - const forceHideMoreMenu: VoidFunction = (): void => { - if (moreMenuTimeout) { - clearTimeout(moreMenuTimeout); - setMoreMenuTimeout(null); - } - - setIsComponentVisible(false); - }; - - const showMoreMenu: VoidFunction = (): void => { - if (moreMenuTimeout) { - clearTimeout(moreMenuTimeout); - } - setIsComponentVisible(true); - }; - - if (!props.show) { - return <></>; + const hideMoreMenu: VoidFunction = (): void => { + if (moreMenuTimeout) { + clearTimeout(moreMenuTimeout); + setMoreMenuTimeout(null); } - return ( - <NavBar - rightElement={ - <NavBarItem - title="User Settings" - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.USER_SETTINGS] as Route - )} - activeRoute={RouteMap[PageMap.USER_SETTINGS]} - icon={IconProp.User} - /> - } + const timeout: ReturnType<typeof setTimeout> = setTimeout(() => { + setIsComponentVisible(false); + }, 500); + + setMoreMenuTimeout(timeout); + }; + + const forceHideMoreMenu: VoidFunction = (): void => { + if (moreMenuTimeout) { + clearTimeout(moreMenuTimeout); + setMoreMenuTimeout(null); + } + + setIsComponentVisible(false); + }; + + const showMoreMenu: VoidFunction = (): void => { + if (moreMenuTimeout) { + clearTimeout(moreMenuTimeout); + } + setIsComponentVisible(true); + }; + + if (!props.show) { + return <></>; + } + + return ( + <NavBar + rightElement={ + <NavBarItem + title="User Settings" + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_SETTINGS] as Route, + )} + activeRoute={RouteMap[PageMap.USER_SETTINGS]} + icon={IconProp.User} + /> + } + > + <NavBarItem + title="Home" + icon={IconProp.Home} + activeRoute={RouteMap[PageMap.HOME]} + route={RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route)} + ></NavBarItem> + + <NavBarItem + title="Monitors" + activeRoute={RouteMap[PageMap.MONITORS]} + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS] as Route, + )} + icon={IconProp.AltGlobe} + ></NavBarItem> + + <NavBarItem + title="Incidents" + activeRoute={RouteMap[PageMap.INCIDENTS]} + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENTS] as Route, + )} + icon={IconProp.Alert} + ></NavBarItem> + + <NavBarItem + title="Telemetry and APM" + activeRoute={RouteMap[PageMap.TELEMETRY]} + icon={IconProp.Cube} + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY] as Route, + )} + ></NavBarItem> + + <NavBarItem + title="Status Pages" + activeRoute={RouteMap[PageMap.STATUS_PAGES]} + icon={IconProp.CheckCircle} + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGES] as Route, + )} + ></NavBarItem> + + <NavBarItem + title="More" + icon={IconProp.More} + onMouseLeave={() => { + hideMoreMenu(); + }} + onMouseOver={() => { + showMoreMenu(); + }} + onClick={() => { + showMoreMenu(); + }} + > + <div + onMouseOver={() => { + showMoreMenu(); + }} + onMouseLeave={() => { + hideMoreMenu(); + }} > - <NavBarItem - title="Home" - icon={IconProp.Home} - activeRoute={RouteMap[PageMap.HOME]} - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - )} - ></NavBarItem> - - <NavBarItem - title="Monitors" - activeRoute={RouteMap[PageMap.MONITORS]} - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS] as Route - )} - icon={IconProp.AltGlobe} - ></NavBarItem> - - <NavBarItem - title="Incidents" - activeRoute={RouteMap[PageMap.INCIDENTS]} - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENTS] as Route - )} - icon={IconProp.Alert} - ></NavBarItem> - - <NavBarItem - title="Telemetry and APM" - activeRoute={RouteMap[PageMap.TELEMETRY]} - icon={IconProp.Cube} - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.TELEMETRY] as Route - )} - ></NavBarItem> - - <NavBarItem - title="Status Pages" - activeRoute={RouteMap[PageMap.STATUS_PAGES]} - icon={IconProp.CheckCircle} - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGES] as Route - )} - ></NavBarItem> - - <NavBarItem - title="More" - icon={IconProp.More} - onMouseLeave={() => { - hideMoreMenu(); - }} - onMouseOver={() => { - showMoreMenu(); - }} - onClick={() => { - showMoreMenu(); - }} + {isComponentVisible && ( + <NavBarMenu + footer={{ + title: "Report a bug or request a feature.", + description: + "We embrace open source! Please report any issue you find and make feature requests on GitHub.", + link: URL.fromString( + "https://github.com/OneUptime/oneuptime/issues/new/choose", + ), + }} > - <div - onMouseOver={() => { - showMoreMenu(); - }} - onMouseLeave={() => { - hideMoreMenu(); - }} - > - {isComponentVisible && ( - <NavBarMenu - footer={{ - title: 'Report a bug or request a feature.', - description: - 'We embrace open source! Please report any issue you find and make feature requests on GitHub.', - link: URL.fromString( - 'https://github.com/OneUptime/oneuptime/issues/new/choose' - ), - }} - > - {/* <NavBarMenuItem + {/* <NavBarMenuItem title="AI Copilot" description="Fix and improve your code automatically." route={RouteUtil.populateRouteParams( @@ -158,68 +155,66 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = ( }} /> */} - <NavBarMenuItem - title="Service Catalog" - description="Manage your services and their dependencies." - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.SERVICE_CATALOG] as Route - )} - icon={IconProp.SquareStack} - onClick={() => { - forceHideMoreMenu(); - }} - /> + <NavBarMenuItem + title="Service Catalog" + description="Manage your services and their dependencies." + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.SERVICE_CATALOG] as Route, + )} + icon={IconProp.SquareStack} + onClick={() => { + forceHideMoreMenu(); + }} + /> - <NavBarMenuItem - title="Scheduled Maintenance" - description="Manage your scheduled maintenance events." - route={RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_EVENTS - ] as Route - )} - icon={IconProp.Clock} - onClick={() => { - forceHideMoreMenu(); - }} - /> + <NavBarMenuItem + title="Scheduled Maintenance" + description="Manage your scheduled maintenance events." + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route, + )} + icon={IconProp.Clock} + onClick={() => { + forceHideMoreMenu(); + }} + /> - <NavBarMenuItem - title="On-Call Duty" - description="Manage your on-call schedules, escalations and more." - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.ON_CALL_DUTY] as Route - )} - icon={IconProp.Call} - onClick={() => { - forceHideMoreMenu(); - }} - /> + <NavBarMenuItem + title="On-Call Duty" + description="Manage your on-call schedules, escalations and more." + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY] as Route, + )} + icon={IconProp.Call} + onClick={() => { + forceHideMoreMenu(); + }} + /> - <NavBarMenuItem - title="Workflows" - description="Integrate OneUptime with the rest of your ecosystem." - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOWS] as Route - )} - icon={IconProp.Workflow} - onClick={() => { - forceHideMoreMenu(); - }} - /> - <NavBarMenuItem - title="Project Settings" - description="Review or manage settings related to this project here." - route={RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS] as Route - )} - icon={IconProp.Settings} - onClick={() => { - forceHideMoreMenu(); - }} - /> + <NavBarMenuItem + title="Workflows" + description="Integrate OneUptime with the rest of your ecosystem." + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOWS] as Route, + )} + icon={IconProp.Workflow} + onClick={() => { + forceHideMoreMenu(); + }} + /> + <NavBarMenuItem + title="Project Settings" + description="Review or manage settings related to this project here." + route={RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS] as Route, + )} + icon={IconProp.Settings} + onClick={() => { + forceHideMoreMenu(); + }} + /> - {/* <NavBarMenuItem + {/* <NavBarMenuItem title="Logs Management" description='Manage your application logs.' route={RouteUtil.populateRouteParams( @@ -244,12 +239,12 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = ( )} icon={IconProp.Report} /> */} - </NavBarMenu> - )} - </div> - </NavBarItem> - </NavBar> - ); + </NavBarMenu> + )} + </div> + </NavBarItem> + </NavBar> + ); }; export default DashboardNavbar; diff --git a/Dashboard/src/Components/NotificationMethods/Call.tsx b/Dashboard/src/Components/NotificationMethods/Call.tsx index e1479b0067..405ba321cd 100644 --- a/Dashboard/src/Components/NotificationMethods/Call.tsx +++ b/Dashboard/src/Components/NotificationMethods/Call.tsx @@ -1,287 +1,281 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { APP_API_URL } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import User from 'CommonUI/src/Utils/User'; -import UserCall from 'Model/Models/UserCall'; -import React, { ReactElement, useEffect, useState } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { APP_API_URL } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import User from "CommonUI/src/Utils/User"; +import UserCall from "Model/Models/UserCall"; +import React, { ReactElement, useEffect, useState } from "react"; const Call: () => JSX.Element = (): ReactElement => { - const [showVerificationCodeModal, setShowVerificationCodeModal] = - useState<boolean>(false); + const [showVerificationCodeModal, setShowVerificationCodeModal] = + useState<boolean>(false); - const [showResendCodeModal, setShowResendCodeModal] = - useState<boolean>(false); + const [showResendCodeModal, setShowResendCodeModal] = + useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [currentItem, setCurrentItem] = useState<UserCall | null>(null); - const [refreshToggle, setRefreshToggle] = useState<boolean>(false); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [currentItem, setCurrentItem] = useState<UserCall | null>(null); + const [refreshToggle, setRefreshToggle] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); - const [ - showVerificationCodeResentModal, - setShowVerificationCodeResentModal, - ] = useState<boolean>(false); + const [showVerificationCodeResentModal, setShowVerificationCodeResentModal] = + useState<boolean>(false); - useEffect(() => { - setError(''); - }, [showVerificationCodeModal]); + useEffect(() => { + setError(""); + }, [showVerificationCodeModal]); - return ( - <> - <ModelTable<UserCall> - modelType={UserCall} - query={{ + return ( + <> + <ModelTable<UserCall> + modelType={UserCall} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + userId: User.getUserId().toString(), + }} + filters={[]} + refreshToggle={refreshToggle} + onBeforeCreate={(model: UserCall): Promise<UserCall> => { + model.projectId = DashboardNavigation.getProjectId()!; + model.userId = User.getUserId(); + return Promise.resolve(model); + }} + createVerb={"Add"} + actionButtons={[ + { + title: "Verify", + buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, + icon: IconProp.Check, + isVisible: (item: UserCall): boolean => { + if (item["isVerified"]) { + return false; + } + + return true; + }, + onClick: async ( + item: UserCall, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentItem(item); + setShowVerificationCodeModal(true); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + { + title: "Resend Code", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.Call, + isVisible: (item: UserCall): boolean => { + if (item["isVerified"]) { + return false; + } + + return true; + }, + onClick: async ( + item: UserCall, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentItem(item); + setShowResendCodeModal(true); + + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + id="user-call" + name="User Settings > Notification Methods > Call" + isDeleteable={true} + isEditable={false} + isCreateable={true} + cardProps={{ + title: "Phone Numbers for Call Notifications", + description: + "Manage Phone Numbers that will receive call notifications for this project.", + }} + noItemsMessage={ + "No phone numbers found. Please add one to receive notifications." + } + formFields={[ + { + field: { + phone: true, + }, + title: "Phone Number", + fieldType: FormFieldSchemaType.Phone, + required: true, + placeholder: "+11234567890", + validation: { + minLength: 2, + }, + }, + ]} + showRefreshButton={true} + columns={[ + { + field: { + phone: true, + }, + title: "Phone Number", + type: FieldType.Phone, + }, + { + field: { + isVerified: true, + }, + title: "Verified", + type: FieldType.Boolean, + }, + ]} + /> + + {showVerificationCodeModal && currentItem ? ( + <BasicFormModal + title={"Verify Phone Number"} + onClose={() => { + setShowVerificationCodeModal(false); + }} + isLoading={isLoading} + submitButtonText={"Verify"} + onSubmit={async (item: JSONObject) => { + setIsLoading(true); + try { + const response: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.post( + URL.fromString(APP_API_URL.toString()).addRoute( + "/user-call/verify", + ), + { + code: item["code"], projectId: DashboardNavigation.getProjectId()?.toString(), - userId: User.getUserId().toString(), - }} - filters={[]} - refreshToggle={refreshToggle} - onBeforeCreate={(model: UserCall): Promise<UserCall> => { - model.projectId = DashboardNavigation.getProjectId()!; - model.userId = User.getUserId(); - return Promise.resolve(model); - }} - createVerb={'Add'} - actionButtons={[ - { - title: 'Verify', - buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, - icon: IconProp.Check, - isVisible: (item: UserCall): boolean => { - if (item['isVerified']) { - return false; - } + itemId: currentItem["_id"], + }, + ); - return true; - }, - onClick: async ( - item: UserCall, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentItem(item); - setShowVerificationCodeModal(true); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - { - title: 'Resend Code', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.Call, - isVisible: (item: UserCall): boolean => { - if (item['isVerified']) { - return false; - } + if (response.isFailure()) { + setError(API.getFriendlyMessage(response)); + setIsLoading(false); + } else { + setIsLoading(false); + setShowVerificationCodeModal(false); + setRefreshToggle(!refreshToggle); + } + } catch (e) { + setError(API.getFriendlyMessage(e)); + setIsLoading(false); + } + }} + formProps={{ + name: "Verify Phone Number", + error: error || "", + fields: [ + { + title: "Verification Code", + description: `We're calling you with your verification code. Please make sure this device can receive calls.`, + field: { + code: true, + }, + placeholder: "123456", + required: true, + validation: { + minLength: 6, + maxLength: 6, + }, + fieldType: FormFieldSchemaType.Number, + }, + ], + }} + /> + ) : ( + <></> + )} - return true; - }, - onClick: async ( - item: UserCall, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentItem(item); - setShowResendCodeModal(true); + {showResendCodeModal && currentItem ? ( + <ConfirmModal + title={`Resend Code`} + error={error} + description={ + "Are you sure you want to resend verification code? We will make a call to this number." + } + submitButtonText={"Resend Code"} + onClose={() => { + setShowResendCodeModal(false); + setError(""); + }} + isLoading={isLoading} + onSubmit={async () => { + try { + const response: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.post( + URL.fromString(APP_API_URL.toString()).addRoute( + "/user-call/resend-verification-code", + ), + { + projectId: DashboardNavigation.getProjectId()?.toString(), + itemId: currentItem["_id"], + }, + ); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - id="user-call" - name="User Settings > Notification Methods > Call" - isDeleteable={true} - isEditable={false} - isCreateable={true} - cardProps={{ - title: 'Phone Numbers for Call Notifications', - description: - 'Manage Phone Numbers that will receive call notifications for this project.', - }} - noItemsMessage={ - 'No phone numbers found. Please add one to receive notifications.' - } - formFields={[ - { - field: { - phone: true, - }, - title: 'Phone Number', - fieldType: FormFieldSchemaType.Phone, - required: true, - placeholder: '+11234567890', - validation: { - minLength: 2, - }, - }, - ]} - showRefreshButton={true} - columns={[ - { - field: { - phone: true, - }, - title: 'Phone Number', - type: FieldType.Phone, - }, - { - field: { - isVerified: true, - }, - title: 'Verified', - type: FieldType.Boolean, - }, - ]} - /> + if (response.isFailure()) { + setError(API.getFriendlyMessage(response)); + setIsLoading(false); + } else { + setIsLoading(false); + setShowResendCodeModal(false); + setShowVerificationCodeResentModal(true); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }} + /> + ) : ( + <></> + )} - {showVerificationCodeModal && currentItem ? ( - <BasicFormModal - title={'Verify Phone Number'} - onClose={() => { - setShowVerificationCodeModal(false); - }} - isLoading={isLoading} - submitButtonText={'Verify'} - onSubmit={async (item: JSONObject) => { - setIsLoading(true); - try { - const response: - | HTTPResponse<JSONObject> - | HTTPErrorResponse = await API.post( - URL.fromString(APP_API_URL.toString()).addRoute( - '/user-call/verify' - ), - { - code: item['code'], - projectId: - DashboardNavigation.getProjectId()?.toString(), - itemId: currentItem['_id'], - } - ); - - if (response.isFailure()) { - setError(API.getFriendlyMessage(response)); - setIsLoading(false); - } else { - setIsLoading(false); - setShowVerificationCodeModal(false); - setRefreshToggle(!refreshToggle); - } - } catch (e) { - setError(API.getFriendlyMessage(e)); - setIsLoading(false); - } - }} - formProps={{ - name: 'Verify Phone Number', - error: error || '', - fields: [ - { - title: 'Verification Code', - description: `We're calling you with your verification code. Please make sure this device can receive calls.`, - field: { - code: true, - }, - placeholder: '123456', - required: true, - validation: { - minLength: 6, - maxLength: 6, - }, - fieldType: FormFieldSchemaType.Number, - }, - ], - }} - /> - ) : ( - <></> - )} - - {showResendCodeModal && currentItem ? ( - <ConfirmModal - title={`Resend Code`} - error={error} - description={ - 'Are you sure you want to resend verification code? We will make a call to this number.' - } - submitButtonText={'Resend Code'} - onClose={() => { - setShowResendCodeModal(false); - setError(''); - }} - isLoading={isLoading} - onSubmit={async () => { - try { - const response: - | HTTPResponse<JSONObject> - | HTTPErrorResponse = await API.post( - URL.fromString(APP_API_URL.toString()).addRoute( - '/user-call/resend-verification-code' - ), - { - projectId: - DashboardNavigation.getProjectId()?.toString(), - itemId: currentItem['_id'], - } - ); - - if (response.isFailure()) { - setError(API.getFriendlyMessage(response)); - setIsLoading(false); - } else { - setIsLoading(false); - setShowResendCodeModal(false); - setShowVerificationCodeResentModal(true); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }} - /> - ) : ( - <></> - )} - - {showVerificationCodeResentModal ? ( - <ConfirmModal - title={`Calling you with your verification code`} - error={error} - description={ - 'We are calling you with your verification code. Please make sure this device can receive calls.' - } - submitButtonText={'Close'} - onSubmit={async () => { - setShowVerificationCodeResentModal(false); - setError(''); - }} - /> - ) : ( - <></> - )} - </> - ); + {showVerificationCodeResentModal ? ( + <ConfirmModal + title={`Calling you with your verification code`} + error={error} + description={ + "We are calling you with your verification code. Please make sure this device can receive calls." + } + submitButtonText={"Close"} + onSubmit={async () => { + setShowVerificationCodeResentModal(false); + setError(""); + }} + /> + ) : ( + <></> + )} + </> + ); }; export default Call; diff --git a/Dashboard/src/Components/NotificationMethods/Email.tsx b/Dashboard/src/Components/NotificationMethods/Email.tsx index be03162565..9fbedb4b4f 100644 --- a/Dashboard/src/Components/NotificationMethods/Email.tsx +++ b/Dashboard/src/Components/NotificationMethods/Email.tsx @@ -1,285 +1,277 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { APP_API_URL } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import User from 'CommonUI/src/Utils/User'; -import UserEmail from 'Model/Models/UserEmail'; -import React, { ReactElement, useEffect, useState } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { APP_API_URL } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import User from "CommonUI/src/Utils/User"; +import UserEmail from "Model/Models/UserEmail"; +import React, { ReactElement, useEffect, useState } from "react"; const Email: () => JSX.Element = (): ReactElement => { - const [showVerificationCodeModal, setShowVerificationCodeModal] = - useState<boolean>(false); + const [showVerificationCodeModal, setShowVerificationCodeModal] = + useState<boolean>(false); - const [showResendCodeModal, setShowResendCodeModal] = - useState<boolean>(false); + const [showResendCodeModal, setShowResendCodeModal] = + useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [currentItem, setCurrentItem] = useState<UserEmail | null>(null); - const [refreshToggle, setRefreshToggle] = useState<boolean>(false); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [currentItem, setCurrentItem] = useState<UserEmail | null>(null); + const [refreshToggle, setRefreshToggle] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); - const [ - showVerificationCodeResentModal, - setShowVerificationCodeResentModal, - ] = useState<boolean>(false); + const [showVerificationCodeResentModal, setShowVerificationCodeResentModal] = + useState<boolean>(false); - useEffect(() => { - setError(''); - }, [showVerificationCodeModal]); + useEffect(() => { + setError(""); + }, [showVerificationCodeModal]); - return ( - <> - <ModelTable<UserEmail> - modelType={UserEmail} - query={{ + return ( + <> + <ModelTable<UserEmail> + modelType={UserEmail} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + userId: User.getUserId().toString(), + }} + refreshToggle={refreshToggle} + onBeforeCreate={(model: UserEmail): Promise<UserEmail> => { + model.projectId = DashboardNavigation.getProjectId()!; + model.userId = User.getUserId(); + return Promise.resolve(model); + }} + createVerb={"Add"} + actionButtons={[ + { + title: "Verify", + buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, + icon: IconProp.Check, + isVisible: (item: UserEmail): boolean => { + if (item["isVerified"]) { + return false; + } + + return true; + }, + onClick: async ( + item: UserEmail, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentItem(item); + setShowVerificationCodeModal(true); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + { + title: "Resend Code", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.Email, + isVisible: (item: UserEmail): boolean => { + if (item["isVerified"]) { + return false; + } + + return true; + }, + onClick: async ( + item: UserEmail, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentItem(item); + setShowResendCodeModal(true); + + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + id="user-emails" + name="User Settings > Notification Methods > Emails" + isDeleteable={true} + isEditable={false} + isCreateable={true} + cardProps={{ + title: "Emails for Notifications", + description: + "Manage emails that will receive notifications for this project.", + }} + noItemsMessage={ + "No emails found. Please add one to receive notifications." + } + formFields={[ + { + field: { + email: true, + }, + title: "Email", + fieldType: FormFieldSchemaType.Email, + required: true, + placeholder: "you@company.com", + validation: { + minLength: 2, + }, + }, + ]} + showRefreshButton={true} + filters={[]} // No filters + columns={[ + { + field: { + email: true, + }, + title: "Email", + type: FieldType.Email, + }, + { + field: { + isVerified: true, + }, + title: "Verified", + type: FieldType.Boolean, + }, + ]} + /> + + {showVerificationCodeModal && currentItem ? ( + <BasicFormModal + title={"Verify Email"} + onClose={() => { + setShowVerificationCodeModal(false); + }} + isLoading={isLoading} + submitButtonText={"Verify"} + onSubmit={async (item: JSONObject) => { + setIsLoading(true); + try { + const response: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.post( + URL.fromString(APP_API_URL.toString()).addRoute( + "/user-email/verify", + ), + { + code: item["code"], projectId: DashboardNavigation.getProjectId()?.toString(), - userId: User.getUserId().toString(), - }} - refreshToggle={refreshToggle} - onBeforeCreate={(model: UserEmail): Promise<UserEmail> => { - model.projectId = DashboardNavigation.getProjectId()!; - model.userId = User.getUserId(); - return Promise.resolve(model); - }} - createVerb={'Add'} - actionButtons={[ - { - title: 'Verify', - buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, - icon: IconProp.Check, - isVisible: (item: UserEmail): boolean => { - if (item['isVerified']) { - return false; - } + itemId: currentItem["_id"], + }, + ); - return true; - }, - onClick: async ( - item: UserEmail, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentItem(item); - setShowVerificationCodeModal(true); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - { - title: 'Resend Code', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.Email, - isVisible: (item: UserEmail): boolean => { - if (item['isVerified']) { - return false; - } + if (response.isFailure()) { + setError(API.getFriendlyMessage(response)); + setIsLoading(false); + } else { + setIsLoading(false); + setShowVerificationCodeModal(false); + setRefreshToggle(!refreshToggle); + } + } catch (e) { + setError(API.getFriendlyMessage(e)); + setIsLoading(false); + } + }} + formProps={{ + name: "Verify Email", + error: error || "", + fields: [ + { + title: "Verification Code", + description: `We have sent verification code to your email. Please don't forget to check your spam.`, + field: { + code: true, + }, + placeholder: "123456", + required: true, + validation: { + minLength: 6, + maxLength: 6, + }, + fieldType: FormFieldSchemaType.Number, + }, + ], + }} + /> + ) : ( + <></> + )} - return true; - }, - onClick: async ( - item: UserEmail, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentItem(item); - setShowResendCodeModal(true); + {showResendCodeModal && currentItem ? ( + <ConfirmModal + title={`Resend Code`} + error={error} + description={"Are you sure you want to resend verification code?"} + submitButtonText={"Resend Code"} + onClose={() => { + setShowResendCodeModal(false); + setError(""); + }} + isLoading={isLoading} + onSubmit={async () => { + try { + const response: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.post( + URL.fromString(APP_API_URL.toString()).addRoute( + "/user-email/resend-verification-code", + ), + { + projectId: DashboardNavigation.getProjectId()?.toString(), + itemId: currentItem["_id"], + }, + ); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - id="user-emails" - name="User Settings > Notification Methods > Emails" - isDeleteable={true} - isEditable={false} - isCreateable={true} - cardProps={{ - title: 'Emails for Notifications', - description: - 'Manage emails that will receive notifications for this project.', - }} - noItemsMessage={ - 'No emails found. Please add one to receive notifications.' - } - formFields={[ - { - field: { - email: true, - }, - title: 'Email', - fieldType: FormFieldSchemaType.Email, - required: true, - placeholder: 'you@company.com', - validation: { - minLength: 2, - }, - }, - ]} - showRefreshButton={true} - filters={[]} // No filters - columns={[ - { - field: { - email: true, - }, - title: 'Email', - type: FieldType.Email, - }, - { - field: { - isVerified: true, - }, - title: 'Verified', - type: FieldType.Boolean, - }, - ]} - /> + if (response.isFailure()) { + setError(API.getFriendlyMessage(response)); + setIsLoading(false); + } else { + setIsLoading(false); + setShowResendCodeModal(false); + setShowVerificationCodeResentModal(true); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }} + /> + ) : ( + <></> + )} - {showVerificationCodeModal && currentItem ? ( - <BasicFormModal - title={'Verify Email'} - onClose={() => { - setShowVerificationCodeModal(false); - }} - isLoading={isLoading} - submitButtonText={'Verify'} - onSubmit={async (item: JSONObject) => { - setIsLoading(true); - try { - const response: - | HTTPResponse<JSONObject> - | HTTPErrorResponse = await API.post( - URL.fromString(APP_API_URL.toString()).addRoute( - '/user-email/verify' - ), - { - code: item['code'], - projectId: - DashboardNavigation.getProjectId()?.toString(), - itemId: currentItem['_id'], - } - ); - - if (response.isFailure()) { - setError(API.getFriendlyMessage(response)); - setIsLoading(false); - } else { - setIsLoading(false); - setShowVerificationCodeModal(false); - setRefreshToggle(!refreshToggle); - } - } catch (e) { - setError(API.getFriendlyMessage(e)); - setIsLoading(false); - } - }} - formProps={{ - name: 'Verify Email', - error: error || '', - fields: [ - { - title: 'Verification Code', - description: `We have sent verification code to your email. Please don't forget to check your spam.`, - field: { - code: true, - }, - placeholder: '123456', - required: true, - validation: { - minLength: 6, - maxLength: 6, - }, - fieldType: FormFieldSchemaType.Number, - }, - ], - }} - /> - ) : ( - <></> - )} - - {showResendCodeModal && currentItem ? ( - <ConfirmModal - title={`Resend Code`} - error={error} - description={ - 'Are you sure you want to resend verification code?' - } - submitButtonText={'Resend Code'} - onClose={() => { - setShowResendCodeModal(false); - setError(''); - }} - isLoading={isLoading} - onSubmit={async () => { - try { - const response: - | HTTPResponse<JSONObject> - | HTTPErrorResponse = await API.post( - URL.fromString(APP_API_URL.toString()).addRoute( - '/user-email/resend-verification-code' - ), - { - projectId: - DashboardNavigation.getProjectId()?.toString(), - itemId: currentItem['_id'], - } - ); - - if (response.isFailure()) { - setError(API.getFriendlyMessage(response)); - setIsLoading(false); - } else { - setIsLoading(false); - setShowResendCodeModal(false); - setShowVerificationCodeResentModal(true); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }} - /> - ) : ( - <></> - )} - - {showVerificationCodeResentModal ? ( - <ConfirmModal - title={`Code sent successfully`} - error={error} - description={`We have sent a verification code to your email. Please don't forget to check your spam.`} - submitButtonText={'Close'} - onSubmit={async () => { - setShowVerificationCodeResentModal(false); - setError(''); - }} - /> - ) : ( - <></> - )} - </> - ); + {showVerificationCodeResentModal ? ( + <ConfirmModal + title={`Code sent successfully`} + error={error} + description={`We have sent a verification code to your email. Please don't forget to check your spam.`} + submitButtonText={"Close"} + onSubmit={async () => { + setShowVerificationCodeResentModal(false); + setError(""); + }} + /> + ) : ( + <></> + )} + </> + ); }; export default Email; diff --git a/Dashboard/src/Components/NotificationMethods/NotificationMethod.tsx b/Dashboard/src/Components/NotificationMethods/NotificationMethod.tsx index 57de653522..357b280078 100644 --- a/Dashboard/src/Components/NotificationMethods/NotificationMethod.tsx +++ b/Dashboard/src/Components/NotificationMethods/NotificationMethod.tsx @@ -1,51 +1,48 @@ -import BaseModel, { BaseModelType } from 'Common/Models/BaseModel'; -import { JSONObject } from 'Common/Types/JSON'; -import React, { FunctionComponent, ReactElement } from 'react'; +import BaseModel, { BaseModelType } from "Common/Models/BaseModel"; +import { JSONObject } from "Common/Types/JSON"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - item: BaseModel; - modelType: BaseModelType; + item: BaseModel; + modelType: BaseModelType; } const NotificationMethodView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const item: BaseModel = BaseModel.fromJSONObject( - props.item, - props.modelType - ); + const item: BaseModel = BaseModel.fromJSONObject(props.item, props.modelType); - return ( - <div> - {item.getColumnValue('userEmail') && - (item.getColumnValue('userEmail') as JSONObject)['email'] && ( - <p> - Email:{' '} - {(item.getColumnValue('userEmail') as JSONObject)[ - 'email' - ]?.toString()} - </p> - )} - {item.getColumnValue('userCall') && - (item.getColumnValue('userCall') as JSONObject)['phone'] && ( - <p> - Call:{' '} - {(item.getColumnValue('userCall') as JSONObject)[ - 'phone' - ]?.toString()} - </p> - )} - {item.getColumnValue('userSms') && - (item.getColumnValue('userSms') as JSONObject)['phone'] && ( - <p> - SMS:{' '} - {(item.getColumnValue('userSms') as JSONObject)[ - 'phone' - ]?.toString()} - </p> - )} - </div> - ); + return ( + <div> + {item.getColumnValue("userEmail") && + (item.getColumnValue("userEmail") as JSONObject)["email"] && ( + <p> + Email:{" "} + {(item.getColumnValue("userEmail") as JSONObject)[ + "email" + ]?.toString()} + </p> + )} + {item.getColumnValue("userCall") && + (item.getColumnValue("userCall") as JSONObject)["phone"] && ( + <p> + Call:{" "} + {(item.getColumnValue("userCall") as JSONObject)[ + "phone" + ]?.toString()} + </p> + )} + {item.getColumnValue("userSms") && + (item.getColumnValue("userSms") as JSONObject)["phone"] && ( + <p> + SMS:{" "} + {(item.getColumnValue("userSms") as JSONObject)[ + "phone" + ]?.toString()} + </p> + )} + </div> + ); }; export default NotificationMethodView; diff --git a/Dashboard/src/Components/NotificationMethods/SMS.tsx b/Dashboard/src/Components/NotificationMethods/SMS.tsx index 0ba7af5711..bfda6a651e 100644 --- a/Dashboard/src/Components/NotificationMethods/SMS.tsx +++ b/Dashboard/src/Components/NotificationMethods/SMS.tsx @@ -1,285 +1,277 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { APP_API_URL } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import User from 'CommonUI/src/Utils/User'; -import UserSMS from 'Model/Models/UserSMS'; -import React, { ReactElement, useEffect, useState } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { APP_API_URL } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import User from "CommonUI/src/Utils/User"; +import UserSMS from "Model/Models/UserSMS"; +import React, { ReactElement, useEffect, useState } from "react"; const SMS: () => JSX.Element = (): ReactElement => { - const [showVerificationCodeModal, setShowVerificationCodeModal] = - useState<boolean>(false); + const [showVerificationCodeModal, setShowVerificationCodeModal] = + useState<boolean>(false); - const [showResendCodeModal, setShowResendCodeModal] = - useState<boolean>(false); + const [showResendCodeModal, setShowResendCodeModal] = + useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [currentItem, setCurrentItem] = useState<UserSMS | null>(null); - const [refreshToggle, setRefreshToggle] = useState<boolean>(false); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [currentItem, setCurrentItem] = useState<UserSMS | null>(null); + const [refreshToggle, setRefreshToggle] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); - const [ - showVerificationCodeResentModal, - setShowVerificationCodeResentModal, - ] = useState<boolean>(false); + const [showVerificationCodeResentModal, setShowVerificationCodeResentModal] = + useState<boolean>(false); - useEffect(() => { - setError(''); - }, [showVerificationCodeModal]); + useEffect(() => { + setError(""); + }, [showVerificationCodeModal]); - return ( - <> - <ModelTable<UserSMS> - modelType={UserSMS} - query={{ + return ( + <> + <ModelTable<UserSMS> + modelType={UserSMS} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + userId: User.getUserId().toString(), + }} + refreshToggle={refreshToggle} + onBeforeCreate={(model: UserSMS): Promise<UserSMS> => { + model.projectId = DashboardNavigation.getProjectId()!; + model.userId = User.getUserId(); + return Promise.resolve(model); + }} + createVerb={"Add"} + actionButtons={[ + { + title: "Verify", + buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, + icon: IconProp.Check, + isVisible: (item: UserSMS): boolean => { + if (item["isVerified"]) { + return false; + } + + return true; + }, + onClick: async ( + item: UserSMS, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentItem(item); + setShowVerificationCodeModal(true); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + { + title: "Resend Code", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.SMS, + isVisible: (item: UserSMS): boolean => { + if (item["isVerified"]) { + return false; + } + + return true; + }, + onClick: async ( + item: UserSMS, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentItem(item); + setShowResendCodeModal(true); + + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + id="user-sms" + name="User Settings > Notification Methods > SMS" + isDeleteable={true} + isEditable={false} + isCreateable={true} + cardProps={{ + title: "Phone Numbers for SMS Notifications", + description: + "Manage Phone Numbers that will receive SMS notifications for this project.", + }} + noItemsMessage={ + "No phone numbers found. Please add one to receive notifications." + } + formFields={[ + { + field: { + phone: true, + }, + title: "Phone Number", + fieldType: FormFieldSchemaType.Phone, + required: true, + placeholder: "+11234567890", + validation: { + minLength: 2, + }, + }, + ]} + showRefreshButton={true} + filters={[]} + columns={[ + { + field: { + phone: true, + }, + title: "Phone Number", + type: FieldType.Phone, + }, + { + field: { + isVerified: true, + }, + title: "Verified", + type: FieldType.Boolean, + }, + ]} + /> + + {showVerificationCodeModal && currentItem ? ( + <BasicFormModal + title={"Verify Phone Number"} + onClose={() => { + setShowVerificationCodeModal(false); + }} + isLoading={isLoading} + submitButtonText={"Verify"} + onSubmit={async (item: JSONObject) => { + setIsLoading(true); + try { + const response: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.post( + URL.fromString(APP_API_URL.toString()).addRoute( + "/user-sms/verify", + ), + { + code: item["code"], projectId: DashboardNavigation.getProjectId()?.toString(), - userId: User.getUserId().toString(), - }} - refreshToggle={refreshToggle} - onBeforeCreate={(model: UserSMS): Promise<UserSMS> => { - model.projectId = DashboardNavigation.getProjectId()!; - model.userId = User.getUserId(); - return Promise.resolve(model); - }} - createVerb={'Add'} - actionButtons={[ - { - title: 'Verify', - buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, - icon: IconProp.Check, - isVisible: (item: UserSMS): boolean => { - if (item['isVerified']) { - return false; - } + itemId: currentItem["_id"], + }, + ); - return true; - }, - onClick: async ( - item: UserSMS, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentItem(item); - setShowVerificationCodeModal(true); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - { - title: 'Resend Code', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.SMS, - isVisible: (item: UserSMS): boolean => { - if (item['isVerified']) { - return false; - } + if (response.isFailure()) { + setError(API.getFriendlyMessage(response)); + setIsLoading(false); + } else { + setIsLoading(false); + setShowVerificationCodeModal(false); + setRefreshToggle(!refreshToggle); + } + } catch (e) { + setError(API.getFriendlyMessage(e)); + setIsLoading(false); + } + }} + formProps={{ + name: "Verify Phone Number", + error: error || "", + fields: [ + { + title: "Verification Code", + description: `We have sent a SMS with your verification code. Please don't forget to check your spam.`, + field: { + code: true, + }, + placeholder: "123456", + required: true, + validation: { + minLength: 6, + maxLength: 6, + }, + fieldType: FormFieldSchemaType.Number, + }, + ], + }} + /> + ) : ( + <></> + )} - return true; - }, - onClick: async ( - item: UserSMS, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentItem(item); - setShowResendCodeModal(true); + {showResendCodeModal && currentItem ? ( + <ConfirmModal + title={`Resend Code`} + error={error} + description={"Are you sure you want to resend verification code?"} + submitButtonText={"Resend Code"} + onClose={() => { + setShowResendCodeModal(false); + setError(""); + }} + isLoading={isLoading} + onSubmit={async () => { + try { + const response: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.post( + URL.fromString(APP_API_URL.toString()).addRoute( + "/user-sms/resend-verification-code", + ), + { + projectId: DashboardNavigation.getProjectId()?.toString(), + itemId: currentItem["_id"], + }, + ); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - id="user-sms" - name="User Settings > Notification Methods > SMS" - isDeleteable={true} - isEditable={false} - isCreateable={true} - cardProps={{ - title: 'Phone Numbers for SMS Notifications', - description: - 'Manage Phone Numbers that will receive SMS notifications for this project.', - }} - noItemsMessage={ - 'No phone numbers found. Please add one to receive notifications.' - } - formFields={[ - { - field: { - phone: true, - }, - title: 'Phone Number', - fieldType: FormFieldSchemaType.Phone, - required: true, - placeholder: '+11234567890', - validation: { - minLength: 2, - }, - }, - ]} - showRefreshButton={true} - filters={[]} - columns={[ - { - field: { - phone: true, - }, - title: 'Phone Number', - type: FieldType.Phone, - }, - { - field: { - isVerified: true, - }, - title: 'Verified', - type: FieldType.Boolean, - }, - ]} - /> + if (response.isFailure()) { + setError(API.getFriendlyMessage(response)); + setIsLoading(false); + } else { + setIsLoading(false); + setShowResendCodeModal(false); + setShowVerificationCodeResentModal(true); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }} + /> + ) : ( + <></> + )} - {showVerificationCodeModal && currentItem ? ( - <BasicFormModal - title={'Verify Phone Number'} - onClose={() => { - setShowVerificationCodeModal(false); - }} - isLoading={isLoading} - submitButtonText={'Verify'} - onSubmit={async (item: JSONObject) => { - setIsLoading(true); - try { - const response: - | HTTPResponse<JSONObject> - | HTTPErrorResponse = await API.post( - URL.fromString(APP_API_URL.toString()).addRoute( - '/user-sms/verify' - ), - { - code: item['code'], - projectId: - DashboardNavigation.getProjectId()?.toString(), - itemId: currentItem['_id'], - } - ); - - if (response.isFailure()) { - setError(API.getFriendlyMessage(response)); - setIsLoading(false); - } else { - setIsLoading(false); - setShowVerificationCodeModal(false); - setRefreshToggle(!refreshToggle); - } - } catch (e) { - setError(API.getFriendlyMessage(e)); - setIsLoading(false); - } - }} - formProps={{ - name: 'Verify Phone Number', - error: error || '', - fields: [ - { - title: 'Verification Code', - description: `We have sent a SMS with your verification code. Please don't forget to check your spam.`, - field: { - code: true, - }, - placeholder: '123456', - required: true, - validation: { - minLength: 6, - maxLength: 6, - }, - fieldType: FormFieldSchemaType.Number, - }, - ], - }} - /> - ) : ( - <></> - )} - - {showResendCodeModal && currentItem ? ( - <ConfirmModal - title={`Resend Code`} - error={error} - description={ - 'Are you sure you want to resend verification code?' - } - submitButtonText={'Resend Code'} - onClose={() => { - setShowResendCodeModal(false); - setError(''); - }} - isLoading={isLoading} - onSubmit={async () => { - try { - const response: - | HTTPResponse<JSONObject> - | HTTPErrorResponse = await API.post( - URL.fromString(APP_API_URL.toString()).addRoute( - '/user-sms/resend-verification-code' - ), - { - projectId: - DashboardNavigation.getProjectId()?.toString(), - itemId: currentItem['_id'], - } - ); - - if (response.isFailure()) { - setError(API.getFriendlyMessage(response)); - setIsLoading(false); - } else { - setIsLoading(false); - setShowResendCodeModal(false); - setShowVerificationCodeResentModal(true); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }} - /> - ) : ( - <></> - )} - - {showVerificationCodeResentModal ? ( - <ConfirmModal - title={`Code sent successfully`} - error={error} - description={`We have sent a verification code via SMS. Please don't forget to check your spam.`} - submitButtonText={'Close'} - onSubmit={async () => { - setShowVerificationCodeResentModal(false); - setError(''); - }} - /> - ) : ( - <></> - )} - </> - ); + {showVerificationCodeResentModal ? ( + <ConfirmModal + title={`Code sent successfully`} + error={error} + description={`We have sent a verification code via SMS. Please don't forget to check your spam.`} + submitButtonText={"Close"} + onSubmit={async () => { + setShowVerificationCodeResentModal(false); + setError(""); + }} + /> + ) : ( + <></> + )} + </> + ); }; export default SMS; diff --git a/Dashboard/src/Components/NotificationRule/NotifyAfterMinutesDropdownOptions.ts b/Dashboard/src/Components/NotificationRule/NotifyAfterMinutesDropdownOptions.ts index 337c384b76..4f06c77370 100644 --- a/Dashboard/src/Components/NotificationRule/NotifyAfterMinutesDropdownOptions.ts +++ b/Dashboard/src/Components/NotificationRule/NotifyAfterMinutesDropdownOptions.ts @@ -1,30 +1,30 @@ -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; const NotifyAfterMinutesDropdownOptions: Array<DropdownOption> = [ - { - value: 0, - label: 'Immediately', - }, - { - value: 5, - label: '5 minutes', - }, - { - value: 10, - label: '10 minutes', - }, - { - value: 15, - label: '15 minutes', - }, - { - value: 30, - label: '30 minutes', - }, - { - value: 60, - label: '1 hour', - }, + { + value: 0, + label: "Immediately", + }, + { + value: 5, + label: "5 minutes", + }, + { + value: 10, + label: "10 minutes", + }, + { + value: 15, + label: "15 minutes", + }, + { + value: 30, + label: "30 minutes", + }, + { + value: 60, + label: "1 hour", + }, ]; export default NotifyAfterMinutesDropdownOptions; diff --git a/Dashboard/src/Components/OnCallDutySchedule/ScheduleElement.tsx b/Dashboard/src/Components/OnCallDutySchedule/ScheduleElement.tsx index 7b5a0086e3..7b745b62e3 100644 --- a/Dashboard/src/Components/OnCallDutySchedule/ScheduleElement.tsx +++ b/Dashboard/src/Components/OnCallDutySchedule/ScheduleElement.tsx @@ -1,42 +1,42 @@ -import Route from 'Common/Types/API/Route'; -import Link from 'CommonUI/src/Components/Link/Link'; -import OnCallDutySchedule from 'Model/Models/OnCallDutyPolicySchedule'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Route from "Common/Types/API/Route"; +import Link from "CommonUI/src/Components/Link/Link"; +import OnCallDutySchedule from "Model/Models/OnCallDutyPolicySchedule"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - schedule: OnCallDutySchedule; - onNavigateComplete?: (() => void) | undefined; + schedule: OnCallDutySchedule; + onNavigateComplete?: (() => void) | undefined; } const OnCallDutyScheduleElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if ( - props.schedule._id && - (props.schedule.projectId || - (props.schedule.project && props.schedule.project._id)) - ) { - const projectId: string | undefined = props.schedule.projectId - ? props.schedule.projectId.toString() - : props.schedule.project - ? props.schedule.project._id - : ''; - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={ - new Route( - `/dashboard/${projectId?.toString()}/on-call-duty/schedules/${props.schedule._id.toString()}` - ) - } - > - <span>{props.schedule.name}</span> - </Link> - ); - } + if ( + props.schedule._id && + (props.schedule.projectId || + (props.schedule.project && props.schedule.project._id)) + ) { + const projectId: string | undefined = props.schedule.projectId + ? props.schedule.projectId.toString() + : props.schedule.project + ? props.schedule.project._id + : ""; + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={ + new Route( + `/dashboard/${projectId?.toString()}/on-call-duty/schedules/${props.schedule._id.toString()}`, + ) + } + > + <span>{props.schedule.name}</span> + </Link> + ); + } - return <span>{props.schedule.name}</span>; + return <span>{props.schedule.name}</span>; }; export default OnCallDutyScheduleElement; diff --git a/Dashboard/src/Components/OnCallDutySchedule/SchedulesElement.tsx b/Dashboard/src/Components/OnCallDutySchedule/SchedulesElement.tsx index e147e6cfe8..25436475d4 100644 --- a/Dashboard/src/Components/OnCallDutySchedule/SchedulesElement.tsx +++ b/Dashboard/src/Components/OnCallDutySchedule/SchedulesElement.tsx @@ -1,36 +1,34 @@ -import OnCallDutyScheduleElement from './ScheduleElement'; -import OnCallDutySchedule from 'Model/Models/OnCallDutyPolicySchedule'; -import React, { FunctionComponent, ReactElement } from 'react'; +import OnCallDutyScheduleElement from "./ScheduleElement"; +import OnCallDutySchedule from "Model/Models/OnCallDutyPolicySchedule"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - schedules: Array<OnCallDutySchedule>; - onNavigateComplete?: (() => void) | undefined; + schedules: Array<OnCallDutySchedule>; + onNavigateComplete?: (() => void) | undefined; } const OnCallDutySchedulesElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.schedules || props.schedules.length === 0) { - return <p>No on call schedules.</p>; - } + if (!props.schedules || props.schedules.length === 0) { + return <p>No on call schedules.</p>; + } - return ( - <div> - {props.schedules.map((schedule: OnCallDutySchedule, i: number) => { - return ( - <span key={i}> - <OnCallDutyScheduleElement - schedule={schedule} - onNavigateComplete={props.onNavigateComplete} - /> - {i !== props.schedules.length - 1 && ( - <span>, </span> - )} - </span> - ); - })} - </div> - ); + return ( + <div> + {props.schedules.map((schedule: OnCallDutySchedule, i: number) => { + return ( + <span key={i}> + <OnCallDutyScheduleElement + schedule={schedule} + onNavigateComplete={props.onNavigateComplete} + /> + {i !== props.schedules.length - 1 && <span>, </span>} + </span> + ); + })} + </div> + ); }; export default OnCallDutySchedulesElement; diff --git a/Dashboard/src/Components/OnCallPolicy/EscalationRule/EscalationRule.tsx b/Dashboard/src/Components/OnCallPolicy/EscalationRule/EscalationRule.tsx index ecb4bb2431..8b98c09521 100644 --- a/Dashboard/src/Components/OnCallPolicy/EscalationRule/EscalationRule.tsx +++ b/Dashboard/src/Components/OnCallPolicy/EscalationRule/EscalationRule.tsx @@ -1,39 +1,39 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import Link from 'CommonUI/src/Components/Link/Link'; -import OnCallDutyPolicyEscalationRule from 'Model/Models/OnCallDutyPolicyEscalationRule'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import Link from "CommonUI/src/Components/Link/Link"; +import OnCallDutyPolicyEscalationRule from "Model/Models/OnCallDutyPolicyEscalationRule"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - escalationRule: OnCallDutyPolicyEscalationRule; - onNavigateComplete?: (() => void) | undefined; + escalationRule: OnCallDutyPolicyEscalationRule; + onNavigateComplete?: (() => void) | undefined; } const EscalationRuleView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.escalationRule.onCallDutyPolicyId) { - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={RouteUtil.populateRouteParams( - RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route, - { - modelId: new ObjectID( - props.escalationRule.onCallDutyPolicyId.toString() as string - ), - } - )} - > - <span>{props.escalationRule.name}</span> - </Link> - ); - } + if (props.escalationRule.onCallDutyPolicyId) { + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route, + { + modelId: new ObjectID( + props.escalationRule.onCallDutyPolicyId.toString() as string, + ), + }, + )} + > + <span>{props.escalationRule.name}</span> + </Link> + ); + } - return <span>{props.escalationRule.name}</span>; + return <span>{props.escalationRule.name}</span>; }; export default EscalationRuleView; diff --git a/Dashboard/src/Components/OnCallPolicy/EscalationRule/OnCallScheduleView.tsx b/Dashboard/src/Components/OnCallPolicy/EscalationRule/OnCallScheduleView.tsx index 8551f6f5df..02bc0bbe05 100644 --- a/Dashboard/src/Components/OnCallPolicy/EscalationRule/OnCallScheduleView.tsx +++ b/Dashboard/src/Components/OnCallPolicy/EscalationRule/OnCallScheduleView.tsx @@ -1,79 +1,78 @@ -import SchedulesElement from '../../OnCallDutySchedule/SchedulesElement'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import OnCallDutyPolicyEscalationRuleSchedule from 'Model/Models/OnCallDutyPolicyEscalationRuleSchedule'; -import OnCallDutyPolicySchedule from 'Model/Models/OnCallDutyPolicySchedule'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import SchedulesElement from "../../OnCallDutySchedule/SchedulesElement"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import OnCallDutyPolicyEscalationRuleSchedule from "Model/Models/OnCallDutyPolicyEscalationRuleSchedule"; +import OnCallDutyPolicySchedule from "Model/Models/OnCallDutyPolicySchedule"; +import React, { FunctionComponent, ReactElement, useState } from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps { - escalationRuleId: ObjectID; + escalationRuleId: ObjectID; } const OnCallDutyScheduleView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [schedules, setSchedules] = useState<Array<OnCallDutyPolicySchedule>>( - [] - ); + const [schedules, setSchedules] = useState<Array<OnCallDutyPolicySchedule>>( + [], + ); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - useAsyncEffect(async () => { - try { - setIsLoading(true); + useAsyncEffect(async () => { + try { + setIsLoading(true); - const onCallSchedules: ListResult<OnCallDutyPolicyEscalationRuleSchedule> = - await ModelAPI.getList({ - modelType: OnCallDutyPolicyEscalationRuleSchedule, - query: { - onCallDutyPolicyEscalationRuleId: - props.escalationRuleId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - onCallDutyPolicySchedule: { - name: true, - }, - }, - sort: {}, - }); + const onCallSchedules: ListResult<OnCallDutyPolicyEscalationRuleSchedule> = + await ModelAPI.getList({ + modelType: OnCallDutyPolicyEscalationRuleSchedule, + query: { + onCallDutyPolicyEscalationRuleId: props.escalationRuleId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + onCallDutyPolicySchedule: { + name: true, + }, + }, + sort: {}, + }); - const schedules: Array<OnCallDutyPolicySchedule> = - onCallSchedules.data.map( - (onCallUser: OnCallDutyPolicyEscalationRuleSchedule) => { - return onCallUser.onCallDutyPolicySchedule!; - } - ); - - setSchedules(schedules); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }, []); - - if (isLoading) { - return ( - <div className="flex justify-center w-full"> - <ComponentLoader /> - </div> + const schedules: Array<OnCallDutyPolicySchedule> = + onCallSchedules.data.map( + (onCallUser: OnCallDutyPolicyEscalationRuleSchedule) => { + return onCallUser.onCallDutyPolicySchedule!; + }, ); + + setSchedules(schedules); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (error) { - return <ErrorMessage error={error} />; - } + setIsLoading(false); + }, []); - return <SchedulesElement schedules={schedules} />; + if (isLoading) { + return ( + <div className="flex justify-center w-full"> + <ComponentLoader /> + </div> + ); + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return <SchedulesElement schedules={schedules} />; }; export default OnCallDutyScheduleView; diff --git a/Dashboard/src/Components/OnCallPolicy/EscalationRule/TeamView.tsx b/Dashboard/src/Components/OnCallPolicy/EscalationRule/TeamView.tsx index daaecbe8cf..2ebe03d69e 100644 --- a/Dashboard/src/Components/OnCallPolicy/EscalationRule/TeamView.tsx +++ b/Dashboard/src/Components/OnCallPolicy/EscalationRule/TeamView.tsx @@ -1,76 +1,75 @@ -import TeamsElement from '../../Team/TeamsElement'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import OnCallDutyPolicyEscalationRuleTeam from 'Model/Models/OnCallDutyPolicyEscalationRuleTeam'; -import Team from 'Model/Models/Team'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import TeamsElement from "../../Team/TeamsElement"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import OnCallDutyPolicyEscalationRuleTeam from "Model/Models/OnCallDutyPolicyEscalationRuleTeam"; +import Team from "Model/Models/Team"; +import React, { FunctionComponent, ReactElement, useState } from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps { - escalationRuleId: ObjectID; + escalationRuleId: ObjectID; } const TeamView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [teams, setTeams] = useState<Array<Team>>([]); + const [teams, setTeams] = useState<Array<Team>>([]); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - useAsyncEffect(async () => { - try { - setIsLoading(true); + useAsyncEffect(async () => { + try { + setIsLoading(true); - const onCallTeams: ListResult<OnCallDutyPolicyEscalationRuleTeam> = - await ModelAPI.getList({ - modelType: OnCallDutyPolicyEscalationRuleTeam, - query: { - onCallDutyPolicyEscalationRuleId: - props.escalationRuleId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - team: { - name: true, - }, - }, - sort: {}, - }); + const onCallTeams: ListResult<OnCallDutyPolicyEscalationRuleTeam> = + await ModelAPI.getList({ + modelType: OnCallDutyPolicyEscalationRuleTeam, + query: { + onCallDutyPolicyEscalationRuleId: props.escalationRuleId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + team: { + name: true, + }, + }, + sort: {}, + }); - const teams: Array<Team> = onCallTeams.data.map( - (onCallUser: OnCallDutyPolicyEscalationRuleTeam) => { - return onCallUser.team!; - } - ); + const teams: Array<Team> = onCallTeams.data.map( + (onCallUser: OnCallDutyPolicyEscalationRuleTeam) => { + return onCallUser.team!; + }, + ); - setTeams(teams); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }, []); - - if (isLoading) { - return ( - <div className="flex justify-center w-full"> - <ComponentLoader /> - </div> - ); + setTeams(teams); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (error) { - return <ErrorMessage error={error} />; - } + setIsLoading(false); + }, []); - return <TeamsElement teams={teams} />; + if (isLoading) { + return ( + <div className="flex justify-center w-full"> + <ComponentLoader /> + </div> + ); + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return <TeamsElement teams={teams} />; }; export default TeamView; diff --git a/Dashboard/src/Components/OnCallPolicy/EscalationRule/UserView.tsx b/Dashboard/src/Components/OnCallPolicy/EscalationRule/UserView.tsx index c00d43fa29..3b733dee5d 100644 --- a/Dashboard/src/Components/OnCallPolicy/EscalationRule/UserView.tsx +++ b/Dashboard/src/Components/OnCallPolicy/EscalationRule/UserView.tsx @@ -1,78 +1,77 @@ -import UsersElement from '../../User/Users'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import OnCallDutyPolicyEscalationRuleUser from 'Model/Models/OnCallDutyPolicyEscalationRuleUser'; -import User from 'Model/Models/User'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import UsersElement from "../../User/Users"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import OnCallDutyPolicyEscalationRuleUser from "Model/Models/OnCallDutyPolicyEscalationRuleUser"; +import User from "Model/Models/User"; +import React, { FunctionComponent, ReactElement, useState } from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps { - escalationRuleId: ObjectID; + escalationRuleId: ObjectID; } const UserView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [users, setUsers] = useState<Array<User>>([]); + const [users, setUsers] = useState<Array<User>>([]); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - useAsyncEffect(async () => { - try { - setIsLoading(true); + useAsyncEffect(async () => { + try { + setIsLoading(true); - const onCallUsers: ListResult<OnCallDutyPolicyEscalationRuleUser> = - await ModelAPI.getList({ - modelType: OnCallDutyPolicyEscalationRuleUser, - query: { - onCallDutyPolicyEscalationRuleId: - props.escalationRuleId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - user: { - name: true, - email: true, - profilePictureId: true, - }, - }, - sort: {}, - }); + const onCallUsers: ListResult<OnCallDutyPolicyEscalationRuleUser> = + await ModelAPI.getList({ + modelType: OnCallDutyPolicyEscalationRuleUser, + query: { + onCallDutyPolicyEscalationRuleId: props.escalationRuleId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + user: { + name: true, + email: true, + profilePictureId: true, + }, + }, + sort: {}, + }); - const users: Array<User> = onCallUsers.data.map( - (onCallUser: OnCallDutyPolicyEscalationRuleUser) => { - return onCallUser.user!; - } - ); + const users: Array<User> = onCallUsers.data.map( + (onCallUser: OnCallDutyPolicyEscalationRuleUser) => { + return onCallUser.user!; + }, + ); - setUsers(users); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }, []); - - if (isLoading) { - return ( - <div className="flex justify-center w-full"> - <ComponentLoader /> - </div> - ); + setUsers(users); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (error) { - return <ErrorMessage error={error} />; - } + setIsLoading(false); + }, []); - return <UsersElement users={users} />; + if (isLoading) { + return ( + <div className="flex justify-center w-full"> + <ComponentLoader /> + </div> + ); + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return <UsersElement users={users} />; }; export default UserView; diff --git a/Dashboard/src/Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable.tsx b/Dashboard/src/Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable.tsx index be279fc0ed..c4f43411c7 100644 --- a/Dashboard/src/Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable.tsx +++ b/Dashboard/src/Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable.tsx @@ -1,272 +1,244 @@ -import IncidentView from '../../../Components/Incident/Incident'; -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import OnCallPolicyView from '../OnCallPolicy'; -import { Green, Red, Yellow } from 'Common/Types/BrandColors'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import OnCallDutyPolicyStatus from 'Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import Filter from 'CommonUI/src/Components/ModelFilter/Filter'; -import Columns from 'CommonUI/src/Components/ModelTable/Columns'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Query from 'CommonUI/src/Utils/BaseDatabase/Query'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Incident from 'Model/Models/Incident'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import OnCallDutyPolicyExecutionLog from 'Model/Models/OnCallDutyPolicyExecutionLog'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import IncidentView from "../../../Components/Incident/Incident"; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import OnCallPolicyView from "../OnCallPolicy"; +import { Green, Red, Yellow } from "Common/Types/BrandColors"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import OnCallDutyPolicyStatus from "Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import Filter from "CommonUI/src/Components/ModelFilter/Filter"; +import Columns from "CommonUI/src/Components/ModelTable/Columns"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Query from "CommonUI/src/Utils/BaseDatabase/Query"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Incident from "Model/Models/Incident"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import OnCallDutyPolicyExecutionLog from "Model/Models/OnCallDutyPolicyExecutionLog"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - onCallDutyPolicyId?: ObjectID | undefined; // if this is undefined. then it'll show logs for all policies. + onCallDutyPolicyId?: ObjectID | undefined; // if this is undefined. then it'll show logs for all policies. } const ExecutionLogsTable: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [showViewStatusMessageModal, setShowViewStatusMessageModal] = - useState<boolean>(false); - const [statusMessage, setStatusMessage] = useState<string>(''); + const [showViewStatusMessageModal, setShowViewStatusMessageModal] = + useState<boolean>(false); + const [statusMessage, setStatusMessage] = useState<string>(""); - const query: Query<OnCallDutyPolicyExecutionLog> = { - projectId: DashboardNavigation.getProjectId()?.toString(), - }; + const query: Query<OnCallDutyPolicyExecutionLog> = { + projectId: DashboardNavigation.getProjectId()?.toString(), + }; - if (props.onCallDutyPolicyId) { - query.onCallDutyPolicyId = props.onCallDutyPolicyId.toString(); - } + if (props.onCallDutyPolicyId) { + query.onCallDutyPolicyId = props.onCallDutyPolicyId.toString(); + } - let columns: Columns<OnCallDutyPolicyExecutionLog> = []; - let filters: Array<Filter<OnCallDutyPolicyExecutionLog>> = []; + let columns: Columns<OnCallDutyPolicyExecutionLog> = []; + let filters: Array<Filter<OnCallDutyPolicyExecutionLog>> = []; - if (!props.onCallDutyPolicyId) { - // add a column for the policy name - columns = columns.concat([ - { - field: { - onCallDutyPolicy: { - name: true, - }, - }, - title: 'Policy Name', - type: FieldType.Element, - getElement: ( - item: OnCallDutyPolicyExecutionLog - ): ReactElement => { - if (item['onCallDutyPolicy']) { - return ( - <OnCallPolicyView - onCallPolicy={ - item['onCallDutyPolicy'] as OnCallDutyPolicy - } - /> - ); - } - return <p>No on-call policy.</p>; - }, - }, - ]); - - filters = filters.concat([ - { - title: 'On Call Policy', - type: FieldType.Entity, - field: { - onCallDutyPolicy: true, - }, - filterEntityType: OnCallDutyPolicy, - filterQuery: { - projectId: DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]); - } + if (!props.onCallDutyPolicyId) { + // add a column for the policy name + columns = columns.concat([ + { + field: { + onCallDutyPolicy: { + name: true, + }, + }, + title: "Policy Name", + type: FieldType.Element, + getElement: (item: OnCallDutyPolicyExecutionLog): ReactElement => { + if (item["onCallDutyPolicy"]) { + return ( + <OnCallPolicyView + onCallPolicy={item["onCallDutyPolicy"] as OnCallDutyPolicy} + /> + ); + } + return <p>No on-call policy.</p>; + }, + }, + ]); filters = filters.concat([ - { - title: 'Status', - type: FieldType.Dropdown, - field: { - status: true, - }, - filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum( - OnCallDutyPolicyStatus - ), + { + title: "On Call Policy", + type: FieldType.Entity, + field: { + onCallDutyPolicy: true, }, - { - title: 'Triggered at', - type: FieldType.Date, - field: { - createdAt: true, - }, + filterEntityType: OnCallDutyPolicy, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, ]); + } - columns = columns.concat([ - { - field: { - triggeredByIncident: { - title: true, - }, - }, - title: 'Triggered By Incident', - type: FieldType.Element, - getElement: (item: OnCallDutyPolicyExecutionLog): ReactElement => { - if (item['triggeredByIncident']) { - return ( - <IncidentView - incident={item['triggeredByIncident'] as Incident} - /> - ); - } - return <p>No incident.</p>; - }, + filters = filters.concat([ + { + title: "Status", + type: FieldType.Dropdown, + field: { + status: true, + }, + filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum( + OnCallDutyPolicyStatus, + ), + }, + { + title: "Triggered at", + type: FieldType.Date, + field: { + createdAt: true, + }, + }, + ]); + + columns = columns.concat([ + { + field: { + triggeredByIncident: { + title: true, }, - { - field: { - createdAt: true, - }, - title: 'Triggered at', - type: FieldType.DateTime, + }, + title: "Triggered By Incident", + type: FieldType.Element, + getElement: (item: OnCallDutyPolicyExecutionLog): ReactElement => { + if (item["triggeredByIncident"]) { + return ( + <IncidentView incident={item["triggeredByIncident"] as Incident} /> + ); + } + return <p>No incident.</p>; + }, + }, + { + field: { + createdAt: true, + }, + title: "Triggered at", + type: FieldType.DateTime, + }, + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Element, + + getElement: (item: OnCallDutyPolicyExecutionLog): ReactElement => { + if (item["status"] === OnCallDutyPolicyStatus.Completed) { + return <Pill color={Green} text={OnCallDutyPolicyStatus.Completed} />; + } else if (item["status"] === OnCallDutyPolicyStatus.Started) { + return <Pill color={Yellow} text={OnCallDutyPolicyStatus.Started} />; + } else if (item["status"] === OnCallDutyPolicyStatus.Scheduled) { + return ( + <Pill color={Yellow} text={OnCallDutyPolicyStatus.Scheduled} /> + ); + } else if (item["status"] === OnCallDutyPolicyStatus.Executing) { + return ( + <Pill color={Yellow} text={OnCallDutyPolicyStatus.Executing} /> + ); + } + + return <Pill color={Red} text={OnCallDutyPolicyStatus.Error} />; + }, + }, + { + field: { + acknowledgedByUser: { + name: true, + email: true, }, - { - field: { - status: true, + }, + title: "Acknowledged By", + type: FieldType.Element, + getElement: (item: OnCallDutyPolicyExecutionLog): ReactElement => { + if (item["acknowledgedByUser"]) { + return <UserElement user={item["acknowledgedByUser"]} />; + } + + return <p>-</p>; + }, + }, + ]); + + return ( + <> + <ModelTable<OnCallDutyPolicyExecutionLog> + modelType={OnCallDutyPolicyExecutionLog} + query={query} + id="execution-logs-table" + name="On-Call Policy > Logs" + isDeleteable={false} + isEditable={false} + isCreateable={false} + isViewable={true} + cardProps={{ + title: "On-Call Policy Logs", + description: + "Here are all the notification logs. This will help you to debug any notification issues that your team may face.", + }} + selectMoreFields={{ + statusMessage: true, + onCallDutyPolicyId: true, + }} + noItemsMessage={"This policy has not executed so far."} + viewPageRoute={Navigation.getCurrentRoute()} + showRefreshButton={true} + showViewIdButton={true} + filters={filters} + actionButtons={[ + { + title: "View Status Message", + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async ( + item: OnCallDutyPolicyExecutionLog, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setStatusMessage(item["statusMessage"] as string); + setShowViewStatusMessageModal(true); + + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } }, - title: 'Status', - type: FieldType.Element, + }, + ]} + viewButtonText={"View Timeline"} + columns={columns} + /> - getElement: (item: OnCallDutyPolicyExecutionLog): ReactElement => { - if (item['status'] === OnCallDutyPolicyStatus.Completed) { - return ( - <Pill - color={Green} - text={OnCallDutyPolicyStatus.Completed} - /> - ); - } else if (item['status'] === OnCallDutyPolicyStatus.Started) { - return ( - <Pill - color={Yellow} - text={OnCallDutyPolicyStatus.Started} - /> - ); - } else if ( - item['status'] === OnCallDutyPolicyStatus.Scheduled - ) { - return ( - <Pill - color={Yellow} - text={OnCallDutyPolicyStatus.Scheduled} - /> - ); - } else if ( - item['status'] === OnCallDutyPolicyStatus.Executing - ) { - return ( - <Pill - color={Yellow} - text={OnCallDutyPolicyStatus.Executing} - /> - ); - } - - return <Pill color={Red} text={OnCallDutyPolicyStatus.Error} />; - }, - }, - { - field: { - acknowledgedByUser: { - name: true, - email: true, - }, - }, - title: 'Acknowledged By', - type: FieldType.Element, - getElement: (item: OnCallDutyPolicyExecutionLog): ReactElement => { - if (item['acknowledgedByUser']) { - return <UserElement user={item['acknowledgedByUser']} />; - } - - return <p>-</p>; - }, - }, - ]); - - return ( - <> - <ModelTable<OnCallDutyPolicyExecutionLog> - modelType={OnCallDutyPolicyExecutionLog} - query={query} - id="execution-logs-table" - name="On-Call Policy > Logs" - isDeleteable={false} - isEditable={false} - isCreateable={false} - isViewable={true} - cardProps={{ - title: 'On-Call Policy Logs', - description: - 'Here are all the notification logs. This will help you to debug any notification issues that your team may face.', - }} - selectMoreFields={{ - statusMessage: true, - onCallDutyPolicyId: true, - }} - noItemsMessage={'This policy has not executed so far.'} - viewPageRoute={Navigation.getCurrentRoute()} - showRefreshButton={true} - showViewIdButton={true} - filters={filters} - actionButtons={[ - { - title: 'View Status Message', - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - item: OnCallDutyPolicyExecutionLog, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setStatusMessage( - item['statusMessage'] as string - ); - setShowViewStatusMessageModal(true); - - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - viewButtonText={'View Timeline'} - columns={columns} - /> - - {showViewStatusMessageModal ? ( - <ConfirmModal - title={'Status Message'} - description={statusMessage} - submitButtonText={'Close'} - onSubmit={async () => { - setShowViewStatusMessageModal(false); - }} - /> - ) : ( - <></> - )} - </> - ); + {showViewStatusMessageModal ? ( + <ConfirmModal + title={"Status Message"} + description={statusMessage} + submitButtonText={"Close"} + onSubmit={async () => { + setShowViewStatusMessageModal(false); + }} + /> + ) : ( + <></> + )} + </> + ); }; export default ExecutionLogsTable; diff --git a/Dashboard/src/Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTimelineTable.tsx b/Dashboard/src/Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTimelineTable.tsx index c776b321c6..c9f106fee5 100644 --- a/Dashboard/src/Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTimelineTable.tsx +++ b/Dashboard/src/Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTimelineTable.tsx @@ -1,316 +1,291 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import UserElement from '../../User/User'; -import EscalationRule from '../EscalationRule/EscalationRule'; -import BaseModel from 'Common/Models/BaseModel'; -import { Green, Red, Yellow } from 'Common/Types/BrandColors'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import OnCallDutyExecutionLogTimelineStatus from 'Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import OnCallDutyPolicyEscalationRule from 'Model/Models/OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicyExecutionLogTimeline from 'Model/Models/OnCallDutyPolicyExecutionLogTimeline'; -import User from 'Model/Models/User'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import UserElement from "../../User/User"; +import EscalationRule from "../EscalationRule/EscalationRule"; +import BaseModel from "Common/Models/BaseModel"; +import { Green, Red, Yellow } from "Common/Types/BrandColors"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import OnCallDutyExecutionLogTimelineStatus from "Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import OnCallDutyPolicyEscalationRule from "Model/Models/OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicyExecutionLogTimeline from "Model/Models/OnCallDutyPolicyExecutionLogTimeline"; +import User from "Model/Models/User"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - onCallPolicyExecutionLogId: ObjectID; + onCallPolicyExecutionLogId: ObjectID; } const ExecutionLogTimelineTable: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [showViewStatusMessageModal, setShowViewStatusMessageModal] = - useState<boolean>(false); - const [statusMessage, setStatusMessage] = useState<string>(''); - - const getModelTable: GetReactElementFunction = (): ReactElement => { - return ( - <ModelTable<OnCallDutyPolicyExecutionLogTimeline> - modelType={OnCallDutyPolicyExecutionLogTimeline} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - onCallDutyPolicyExecutionLogId: - props.onCallPolicyExecutionLogId.toString(), - }} - id="notification-logs-timeline-table" - name="On-Call > Execution Logs > Timeline" - isDeleteable={false} - isEditable={false} - isCreateable={false} - cardProps={{ - title: 'Policy Execution Timeline', - description: - 'You can view the timeline of the execution of the policy here. You can also view the status of the notification sent out to the users.', - }} - selectMoreFields={{ - statusMessage: true, - }} - noItemsMessage={'No notifications sent out so far.'} - showRefreshButton={true} - showViewIdButton={true} - actionButtons={[ - { - title: 'View Status Message', - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - item: OnCallDutyPolicyExecutionLogTimeline, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setStatusMessage( - item['statusMessage'] as string - ); - setShowViewStatusMessageModal(true); - - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - filters={[ - { - field: { - onCallDutyPolicyEscalationRule: true, - }, - type: FieldType.Entity, - title: 'Escalation Rule', - filterEntityType: OnCallDutyPolicyEscalationRule, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - type: FieldType.Date, - title: 'Started At', - }, - { - field: { - acknowledgedAt: true, - }, - type: FieldType.Date, - title: 'Acknowledged At', - }, - { - field: { - status: true, - }, - type: FieldType.Dropdown, - title: 'Status', - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - OnCallDutyExecutionLogTimelineStatus - ), - }, - ]} - columns={[ - { - field: { - onCallDutyPolicyEscalationRule: { - name: true, - onCallDutyPolicyId: true, - }, - }, - title: 'Escalation Rule', - type: FieldType.Element, - getElement: ( - item: OnCallDutyPolicyExecutionLogTimeline - ): ReactElement => { - if ( - item && - item['onCallDutyPolicyEscalationRule'] - ) { - return ( - <EscalationRule - escalationRule={ - item[ - 'onCallDutyPolicyEscalationRule' - ] as OnCallDutyPolicyEscalationRule - } - /> - ); - } - return <p>No escalation rule found.</p>; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Started At', - type: FieldType.DateTime, - }, - { - field: { - alertSentToUser: { - name: true, - email: true, - }, - }, - title: 'Notification Sent To', - type: FieldType.Element, - getElement: ( - item: OnCallDutyPolicyExecutionLogTimeline - ): ReactElement => { - if (item['alertSentToUser']) { - return ( - <UserElement - user={ - BaseModel.fromJSON( - item['alertSentToUser'], - User - ) as User - } - /> - ); - } - - return <p>-</p>; - }, - }, - { - field: { - acknowledgedAt: true, - }, - title: 'Acknowledged At', - type: FieldType.DateTime, - - noValueMessage: '-', - }, - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Element, - - getElement: ( - item: OnCallDutyPolicyExecutionLogTimeline - ): ReactElement => { - if ( - item['status'] === - OnCallDutyExecutionLogTimelineStatus.NotificationSent - ) { - return ( - <Pill - color={Green} - text={ - OnCallDutyExecutionLogTimelineStatus.NotificationSent - } - /> - ); - } else if ( - item['status'] === - OnCallDutyExecutionLogTimelineStatus.SuccessfullyAcknowledged - ) { - return ( - <Pill - color={Green} - text={ - OnCallDutyExecutionLogTimelineStatus.SuccessfullyAcknowledged - } - /> - ); - } else if ( - item['status'] === - OnCallDutyExecutionLogTimelineStatus.Error - ) { - return ( - <Pill - color={Red} - text={ - OnCallDutyExecutionLogTimelineStatus.Error - } - /> - ); - } else if ( - item['status'] === - OnCallDutyExecutionLogTimelineStatus.Skipped - ) { - return ( - <Pill - color={Yellow} - text={ - OnCallDutyExecutionLogTimelineStatus.Skipped - } - /> - ); - } else if ( - item['status'] === - OnCallDutyExecutionLogTimelineStatus.Executing - ) { - return ( - <Pill - color={Yellow} - text={ - OnCallDutyExecutionLogTimelineStatus.Executing - } - /> - ); - } else if ( - item['status'] === - OnCallDutyExecutionLogTimelineStatus.Started - ) { - return ( - <Pill - color={Yellow} - text={ - OnCallDutyExecutionLogTimelineStatus.Started - } - /> - ); - } - - return ( - <Pill - color={Red} - text={ - OnCallDutyExecutionLogTimelineStatus.Error - } - /> - ); - }, - }, - ]} - /> - ); - }; + const [showViewStatusMessageModal, setShowViewStatusMessageModal] = + useState<boolean>(false); + const [statusMessage, setStatusMessage] = useState<string>(""); + const getModelTable: GetReactElementFunction = (): ReactElement => { return ( - <> - {getModelTable()} + <ModelTable<OnCallDutyPolicyExecutionLogTimeline> + modelType={OnCallDutyPolicyExecutionLogTimeline} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + onCallDutyPolicyExecutionLogId: + props.onCallPolicyExecutionLogId.toString(), + }} + id="notification-logs-timeline-table" + name="On-Call > Execution Logs > Timeline" + isDeleteable={false} + isEditable={false} + isCreateable={false} + cardProps={{ + title: "Policy Execution Timeline", + description: + "You can view the timeline of the execution of the policy here. You can also view the status of the notification sent out to the users.", + }} + selectMoreFields={{ + statusMessage: true, + }} + noItemsMessage={"No notifications sent out so far."} + showRefreshButton={true} + showViewIdButton={true} + actionButtons={[ + { + title: "View Status Message", + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async ( + item: OnCallDutyPolicyExecutionLogTimeline, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setStatusMessage(item["statusMessage"] as string); + setShowViewStatusMessageModal(true); - {showViewStatusMessageModal ? ( - <ConfirmModal - title={'Status Message'} - description={statusMessage} - submitButtonText={'Close'} - onSubmit={async () => { - setShowViewStatusMessageModal(false); - }} + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + filters={[ + { + field: { + onCallDutyPolicyEscalationRule: true, + }, + type: FieldType.Entity, + title: "Escalation Rule", + filterEntityType: OnCallDutyPolicyEscalationRule, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + type: FieldType.Date, + title: "Started At", + }, + { + field: { + acknowledgedAt: true, + }, + type: FieldType.Date, + title: "Acknowledged At", + }, + { + field: { + status: true, + }, + type: FieldType.Dropdown, + title: "Status", + filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum( + OnCallDutyExecutionLogTimelineStatus, + ), + }, + ]} + columns={[ + { + field: { + onCallDutyPolicyEscalationRule: { + name: true, + onCallDutyPolicyId: true, + }, + }, + title: "Escalation Rule", + type: FieldType.Element, + getElement: ( + item: OnCallDutyPolicyExecutionLogTimeline, + ): ReactElement => { + if (item && item["onCallDutyPolicyEscalationRule"]) { + return ( + <EscalationRule + escalationRule={ + item[ + "onCallDutyPolicyEscalationRule" + ] as OnCallDutyPolicyEscalationRule + } + /> + ); + } + return <p>No escalation rule found.</p>; + }, + }, + { + field: { + createdAt: true, + }, + title: "Started At", + type: FieldType.DateTime, + }, + { + field: { + alertSentToUser: { + name: true, + email: true, + }, + }, + title: "Notification Sent To", + type: FieldType.Element, + getElement: ( + item: OnCallDutyPolicyExecutionLogTimeline, + ): ReactElement => { + if (item["alertSentToUser"]) { + return ( + <UserElement + user={ + BaseModel.fromJSON(item["alertSentToUser"], User) as User + } + /> + ); + } + + return <p>-</p>; + }, + }, + { + field: { + acknowledgedAt: true, + }, + title: "Acknowledged At", + type: FieldType.DateTime, + + noValueMessage: "-", + }, + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Element, + + getElement: ( + item: OnCallDutyPolicyExecutionLogTimeline, + ): ReactElement => { + if ( + item["status"] === + OnCallDutyExecutionLogTimelineStatus.NotificationSent + ) { + return ( + <Pill + color={Green} + text={OnCallDutyExecutionLogTimelineStatus.NotificationSent} + /> + ); + } else if ( + item["status"] === + OnCallDutyExecutionLogTimelineStatus.SuccessfullyAcknowledged + ) { + return ( + <Pill + color={Green} + text={ + OnCallDutyExecutionLogTimelineStatus.SuccessfullyAcknowledged + } + /> + ); + } else if ( + item["status"] === OnCallDutyExecutionLogTimelineStatus.Error + ) { + return ( + <Pill + color={Red} + text={OnCallDutyExecutionLogTimelineStatus.Error} + /> + ); + } else if ( + item["status"] === OnCallDutyExecutionLogTimelineStatus.Skipped + ) { + return ( + <Pill + color={Yellow} + text={OnCallDutyExecutionLogTimelineStatus.Skipped} + /> + ); + } else if ( + item["status"] === + OnCallDutyExecutionLogTimelineStatus.Executing + ) { + return ( + <Pill + color={Yellow} + text={OnCallDutyExecutionLogTimelineStatus.Executing} + /> + ); + } else if ( + item["status"] === OnCallDutyExecutionLogTimelineStatus.Started + ) { + return ( + <Pill + color={Yellow} + text={OnCallDutyExecutionLogTimelineStatus.Started} + /> + ); + } + + return ( + <Pill + color={Red} + text={OnCallDutyExecutionLogTimelineStatus.Error} /> - ) : ( - <></> - )} - </> + ); + }, + }, + ]} + /> ); + }; + + return ( + <> + {getModelTable()} + + {showViewStatusMessageModal ? ( + <ConfirmModal + title={"Status Message"} + description={statusMessage} + submitButtonText={"Close"} + onSubmit={async () => { + setShowViewStatusMessageModal(false); + }} + /> + ) : ( + <></> + )} + </> + ); }; export default ExecutionLogTimelineTable; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallPolicies.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallPolicies.tsx index fa683e489d..09add8f176 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallPolicies.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallPolicies.tsx @@ -1,37 +1,35 @@ -import OnCallPolicyElement from './OnCallPolicy'; -import Monitor from 'Model/Models/Monitor'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { FunctionComponent, ReactElement } from 'react'; +import OnCallPolicyElement from "./OnCallPolicy"; +import Monitor from "Model/Models/Monitor"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onCallPolicies: Array<OnCallDutyPolicy>; - onNavigateComplete?: (() => void) | undefined; + onCallPolicies: Array<OnCallDutyPolicy>; + onNavigateComplete?: (() => void) | undefined; } const OnCallDutyPoliciesView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.onCallPolicies || props.onCallPolicies.length === 0) { - return <p>No on-call policies.</p>; - } + if (!props.onCallPolicies || props.onCallPolicies.length === 0) { + return <p>No on-call policies.</p>; + } - return ( - <div> - {props.onCallPolicies.map((onCallPolicy: Monitor, i: number) => { - return ( - <span key={i}> - <OnCallPolicyElement - onCallPolicy={onCallPolicy} - onNavigateComplete={props.onNavigateComplete} - /> - {i !== props.onCallPolicies.length - 1 && ( - <span>, </span> - )} - </span> - ); - })} - </div> - ); + return ( + <div> + {props.onCallPolicies.map((onCallPolicy: Monitor, i: number) => { + return ( + <span key={i}> + <OnCallPolicyElement + onCallPolicy={onCallPolicy} + onNavigateComplete={props.onNavigateComplete} + /> + {i !== props.onCallPolicies.length - 1 && <span>, </span>} + </span> + ); + })} + </div> + ); }; export default OnCallDutyPoliciesView; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallPolicy.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallPolicy.tsx index ad6aab4ca2..87244de1b6 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallPolicy.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallPolicy.tsx @@ -1,37 +1,37 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import Link from 'CommonUI/src/Components/Link/Link'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import Link from "CommonUI/src/Components/Link/Link"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onCallPolicy: OnCallDutyPolicy; - onNavigateComplete?: (() => void) | undefined; + onCallPolicy: OnCallDutyPolicy; + onNavigateComplete?: (() => void) | undefined; } const OnCallPolicyView: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.onCallPolicy._id) { - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={RouteUtil.populateRouteParams( - RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route, - { - modelId: new ObjectID(props.onCallPolicy._id as string), - } - )} - > - <span>{props.onCallPolicy.name}</span> - </Link> - ); - } + if (props.onCallPolicy._id) { + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route, + { + modelId: new ObjectID(props.onCallPolicy._id as string), + }, + )} + > + <span>{props.onCallPolicy.name}</span> + </Link> + ); + } - return <span>{props.onCallPolicy.name}</span>; + return <span>{props.onCallPolicy.name}</span>; }; export default OnCallPolicyView; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/Layer.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/Layer.tsx index 3031dedd6b..3a414f6f6c 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/Layer.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/Layer.tsx @@ -1,152 +1,139 @@ -import LayerBasicInfo from './LayerBasicInfo'; -import LayerPreview from './LayerPreview'; -import LayerReestrictionTimes from './LayerRestrictionTimes'; -import LayerRotation from './LayerRotation'; -import LayerStartsAt from './LayerStartTime'; -import LayerUser from './LayerUser'; -import BaseModel from 'Common/Models/BaseModel'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Card from 'CommonUI/src/Components/Card/Card'; -import HorizontalRule from 'CommonUI/src/Components/HorizontalRule/HorizontalRule'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import OnCallDutyPolicyScheduleLayerUser from 'Model/Models/OnCallDutyPolicyScheduleLayerUser'; +import LayerBasicInfo from "./LayerBasicInfo"; +import LayerPreview from "./LayerPreview"; +import LayerReestrictionTimes from "./LayerRestrictionTimes"; +import LayerRotation from "./LayerRotation"; +import LayerStartsAt from "./LayerStartTime"; +import LayerUser from "./LayerUser"; +import BaseModel from "Common/Models/BaseModel"; +import IconProp from "Common/Types/Icon/IconProp"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Card from "CommonUI/src/Components/Card/Card"; +import HorizontalRule from "CommonUI/src/Components/HorizontalRule/HorizontalRule"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import OnCallDutyPolicyScheduleLayerUser from "Model/Models/OnCallDutyPolicyScheduleLayerUser"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - layer: OnCallDutyPolicyScheduleLayer; - onDeleteLayer: () => void; - onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; - isDeleteButtonLoading: boolean; - onLayerUsersUpdateOrLoaded: ( - layerUsers: Array<OnCallDutyPolicyScheduleLayerUser> - ) => void; + layer: OnCallDutyPolicyScheduleLayer; + onDeleteLayer: () => void; + onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; + isDeleteButtonLoading: boolean; + onLayerUsersUpdateOrLoaded: ( + layerUsers: Array<OnCallDutyPolicyScheduleLayerUser>, + ) => void; } const Layer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [layerUsers, setLayerUsers] = useState< - Array<OnCallDutyPolicyScheduleLayerUser> - >([]); + const [layerUsers, setLayerUsers] = useState< + Array<OnCallDutyPolicyScheduleLayerUser> + >([]); - useEffect(() => { - props.onLayerUsersUpdateOrLoaded(layerUsers); - }, [layerUsers]); + useEffect(() => { + props.onLayerUsersUpdateOrLoaded(layerUsers); + }, [layerUsers]); - const [layer, setLayer] = useState<OnCallDutyPolicyScheduleLayer>( - props.layer - ); + const [layer, setLayer] = useState<OnCallDutyPolicyScheduleLayer>( + props.layer, + ); - type UpdateLayerFunction = ( - updatedLayer: OnCallDutyPolicyScheduleLayer - ) => void; + type UpdateLayerFunction = ( + updatedLayer: OnCallDutyPolicyScheduleLayer, + ) => void; - const updateLayer: UpdateLayerFunction = ( - updatedLayer: OnCallDutyPolicyScheduleLayer - ): void => { - updatedLayer = BaseModel.fromJSON( - BaseModel.toJSON(updatedLayer, OnCallDutyPolicyScheduleLayer), - OnCallDutyPolicyScheduleLayer - ) as OnCallDutyPolicyScheduleLayer; + const updateLayer: UpdateLayerFunction = ( + updatedLayer: OnCallDutyPolicyScheduleLayer, + ): void => { + updatedLayer = BaseModel.fromJSON( + BaseModel.toJSON(updatedLayer, OnCallDutyPolicyScheduleLayer), + OnCallDutyPolicyScheduleLayer, + ) as OnCallDutyPolicyScheduleLayer; - setLayer(updatedLayer); - props.onLayerChange(updatedLayer); - }; + setLayer(updatedLayer); + props.onLayerChange(updatedLayer); + }; - return ( - <div className="mb-10 "> - <Card - title={`Layer ${props.layer.order?.toString() || ''}`} - description={ - 'On Call Schedule Layer. Layers on top have priority.' - } - buttons={[ - { - title: 'Delete Layer', - onClick: props.onDeleteLayer, - icon: IconProp.Trash, - buttonStyle: ButtonStyleType.NORMAL, - isLoading: props.isDeleteButtonLoading, - }, - ]} - > - <div className="bg-gray-50 -ml-6 -mr-6 pl-6 pr-6 pt-6 -mb-6 pb-6"> - <LayerBasicInfo - layer={layer} - onLayerChange={( - updatedLayer: OnCallDutyPolicyScheduleLayer - ) => { - layer.name = updatedLayer.name!; - layer.description = updatedLayer.description!; + return ( + <div className="mb-10 "> + <Card + title={`Layer ${props.layer.order?.toString() || ""}`} + description={"On Call Schedule Layer. Layers on top have priority."} + buttons={[ + { + title: "Delete Layer", + onClick: props.onDeleteLayer, + icon: IconProp.Trash, + buttonStyle: ButtonStyleType.NORMAL, + isLoading: props.isDeleteButtonLoading, + }, + ]} + > + <div className="bg-gray-50 -ml-6 -mr-6 pl-6 pr-6 pt-6 -mb-6 pb-6"> + <LayerBasicInfo + layer={layer} + onLayerChange={(updatedLayer: OnCallDutyPolicyScheduleLayer) => { + layer.name = updatedLayer.name!; + layer.description = updatedLayer.description!; - updateLayer(layer); - }} - /> + updateLayer(layer); + }} + /> - <HorizontalRule /> + <HorizontalRule /> - <LayerUser - onUpdateUsers={( - list: Array<OnCallDutyPolicyScheduleLayerUser> - ) => { - setLayerUsers(list); - }} - layer={layer} - /> + <LayerUser + onUpdateUsers={(list: Array<OnCallDutyPolicyScheduleLayerUser>) => { + setLayerUsers(list); + }} + layer={layer} + /> - <HorizontalRule /> + <HorizontalRule /> - <LayerStartsAt - layer={layer} - onLayerChange={( - updatedLayer: OnCallDutyPolicyScheduleLayer - ) => { - layer.startsAt = updatedLayer.startsAt!; - updateLayer(layer); - }} - /> + <LayerStartsAt + layer={layer} + onLayerChange={(updatedLayer: OnCallDutyPolicyScheduleLayer) => { + layer.startsAt = updatedLayer.startsAt!; + updateLayer(layer); + }} + /> - <HorizontalRule /> + <HorizontalRule /> - <LayerRotation - layer={layer} - onLayerChange={( - updatedLayer: OnCallDutyPolicyScheduleLayer - ) => { - layer.rotation = updatedLayer.rotation!; - layer.handOffTime = updatedLayer.handOffTime!; + <LayerRotation + layer={layer} + onLayerChange={(updatedLayer: OnCallDutyPolicyScheduleLayer) => { + layer.rotation = updatedLayer.rotation!; + layer.handOffTime = updatedLayer.handOffTime!; - updateLayer(layer); - }} - /> + updateLayer(layer); + }} + /> - <HorizontalRule /> + <HorizontalRule /> - <LayerReestrictionTimes - layer={layer} - onLayerChange={( - updatedLayer: OnCallDutyPolicyScheduleLayer - ) => { - layer.restrictionTimes = - updatedLayer.restrictionTimes!; + <LayerReestrictionTimes + layer={layer} + onLayerChange={(updatedLayer: OnCallDutyPolicyScheduleLayer) => { + layer.restrictionTimes = updatedLayer.restrictionTimes!; - updateLayer(layer); - }} - /> + updateLayer(layer); + }} + /> - <HorizontalRule /> + <HorizontalRule /> - <LayerPreview layer={layer} layerUsers={layerUsers} /> - </div> - </Card> + <LayerPreview layer={layer} layerUsers={layerUsers} /> </div> - ); + </Card> + </div> + ); }; export default Layer; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerBasicInfo.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerBasicInfo.tsx index fd0f8734bc..629492fb14 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerBasicInfo.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerBasicInfo.tsx @@ -1,48 +1,48 @@ -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - layer: OnCallDutyPolicyScheduleLayer; - onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; + layer: OnCallDutyPolicyScheduleLayer; + onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; } const LayerBasicInfo: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <ModelForm<OnCallDutyPolicyScheduleLayer> - modelType={OnCallDutyPolicyScheduleLayer} - name="Basic Info" - id="Basic Info" - fields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Name, - required: true, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: false, - }, - ]} - onSuccess={(item: OnCallDutyPolicyScheduleLayer) => { - props.onLayerChange(item); - }} - submitButtonText={'Save Changes'} - formType={FormType.Update} - modelIdToEdit={props.layer.id!} - maxPrimaryButtonWidth={false} - /> - ); + return ( + <ModelForm<OnCallDutyPolicyScheduleLayer> + modelType={OnCallDutyPolicyScheduleLayer} + name="Basic Info" + id="Basic Info" + fields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Name, + required: true, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: false, + }, + ]} + onSuccess={(item: OnCallDutyPolicyScheduleLayer) => { + props.onLayerChange(item); + }} + submitButtonText={"Save Changes"} + formType={FormType.Update} + modelIdToEdit={props.layer.id!} + maxPrimaryButtonWidth={false} + /> + ); }; export default LayerBasicInfo; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerPreview.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerPreview.tsx index b690e56c6d..59faadac5d 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerPreview.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerPreview.tsx @@ -1,26 +1,26 @@ -import LayersPreview from './LayersPreview'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import OnCallDutyPolicyScheduleLayerUser from 'Model/Models/OnCallDutyPolicyScheduleLayerUser'; -import React, { FunctionComponent, ReactElement } from 'react'; +import LayersPreview from "./LayersPreview"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import OnCallDutyPolicyScheduleLayerUser from "Model/Models/OnCallDutyPolicyScheduleLayerUser"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - layer: OnCallDutyPolicyScheduleLayer; - layerUsers: Array<OnCallDutyPolicyScheduleLayerUser>; - id?: string | undefined; + layer: OnCallDutyPolicyScheduleLayer; + layerUsers: Array<OnCallDutyPolicyScheduleLayerUser>; + id?: string | undefined; } const LayerPreview: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <LayersPreview - layers={[props.layer]} - showFieldLabel={true} - allLayerUsers={{ - [props.layer.id?.toString() || '']: props.layerUsers, - }} - /> - ); + return ( + <LayersPreview + layers={[props.layer]} + showFieldLabel={true} + allLayerUsers={{ + [props.layer.id?.toString() || ""]: props.layerUsers, + }} + /> + ); }; export default LayerPreview; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerRestrictionTimes.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerRestrictionTimes.tsx index b41d60488a..f9beb78281 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerRestrictionTimes.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerRestrictionTimes.tsx @@ -1,57 +1,55 @@ -import RestrictionTimesFieldElement from './RestrictionTimesFieldElement'; -import RestrictionTimes from 'Common/Types/OnCallDutyPolicy/RestrictionTimes'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import { CustomElementProps } from 'CommonUI/src/Components/Forms/Types/Field'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import React, { FunctionComponent, ReactElement } from 'react'; +import RestrictionTimesFieldElement from "./RestrictionTimesFieldElement"; +import RestrictionTimes from "Common/Types/OnCallDutyPolicy/RestrictionTimes"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import { CustomElementProps } from "CommonUI/src/Components/Forms/Types/Field"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - layer: OnCallDutyPolicyScheduleLayer; - onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; + layer: OnCallDutyPolicyScheduleLayer; + onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; } const LayerReestrictionTimes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <ModelForm<OnCallDutyPolicyScheduleLayer> - modelType={OnCallDutyPolicyScheduleLayer} - name="Restriction Times" - id="restriction-times" - fields={[ - { - field: { - restrictionTimes: true, - }, - title: 'Restriction Times', - fieldType: FormFieldSchemaType.CustomComponent, - required: true, - getCustomElement: ( - value: FormValues<OnCallDutyPolicyScheduleLayer>, - props: CustomElementProps - ) => { - return ( - <RestrictionTimesFieldElement - {...props} - value={ - value.restrictionTimes as RestrictionTimes - } - /> - ); - }, - }, - ]} - onSuccess={(item: OnCallDutyPolicyScheduleLayer) => { - props.onLayerChange(item); - }} - submitButtonText={'Save Changes'} - formType={FormType.Update} - modelIdToEdit={props.layer.id!} - maxPrimaryButtonWidth={false} - /> - ); + return ( + <ModelForm<OnCallDutyPolicyScheduleLayer> + modelType={OnCallDutyPolicyScheduleLayer} + name="Restriction Times" + id="restriction-times" + fields={[ + { + field: { + restrictionTimes: true, + }, + title: "Restriction Times", + fieldType: FormFieldSchemaType.CustomComponent, + required: true, + getCustomElement: ( + value: FormValues<OnCallDutyPolicyScheduleLayer>, + props: CustomElementProps, + ) => { + return ( + <RestrictionTimesFieldElement + {...props} + value={value.restrictionTimes as RestrictionTimes} + /> + ); + }, + }, + ]} + onSuccess={(item: OnCallDutyPolicyScheduleLayer) => { + props.onLayerChange(item); + }} + submitButtonText={"Save Changes"} + formType={FormType.Update} + modelIdToEdit={props.layer.id!} + maxPrimaryButtonWidth={false} + /> + ); }; export default LayerReestrictionTimes; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerRotation.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerRotation.tsx index b1cc162a74..9e5b423508 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerRotation.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerRotation.tsx @@ -1,63 +1,63 @@ -import Recurring from 'Common/Types/Events/Recurring'; -import RecurringFieldElement from 'CommonUI/src/Components/Events/RecurringFieldElement'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import { CustomElementProps } from 'CommonUI/src/Components/Forms/Types/Field'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Recurring from "Common/Types/Events/Recurring"; +import RecurringFieldElement from "CommonUI/src/Components/Events/RecurringFieldElement"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import { CustomElementProps } from "CommonUI/src/Components/Forms/Types/Field"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - layer: OnCallDutyPolicyScheduleLayer; - onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; + layer: OnCallDutyPolicyScheduleLayer; + onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; } const LayerRotation: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <ModelForm<OnCallDutyPolicyScheduleLayer> - modelType={OnCallDutyPolicyScheduleLayer} - name="Rotation" - id="rotation" - fields={[ - { - field: { - rotation: true, - }, - title: 'Rotation Policy', - fieldType: FormFieldSchemaType.CustomComponent, - getCustomElement: ( - value: FormValues<OnCallDutyPolicyScheduleLayer>, - props: CustomElementProps - ) => { - return ( - <RecurringFieldElement - {...props} - initialValue={value.rotation as Recurring} - /> - ); - }, - required: true, - }, - { - field: { - handOffTime: true, - }, - title: 'Hand Off Time', - fieldType: FormFieldSchemaType.DateTime, - required: true, - }, - ]} - onSuccess={(item: OnCallDutyPolicyScheduleLayer) => { - props.onLayerChange(item); - }} - submitButtonText={'Save Changes'} - formType={FormType.Update} - modelIdToEdit={props.layer.id!} - maxPrimaryButtonWidth={false} - /> - ); + return ( + <ModelForm<OnCallDutyPolicyScheduleLayer> + modelType={OnCallDutyPolicyScheduleLayer} + name="Rotation" + id="rotation" + fields={[ + { + field: { + rotation: true, + }, + title: "Rotation Policy", + fieldType: FormFieldSchemaType.CustomComponent, + getCustomElement: ( + value: FormValues<OnCallDutyPolicyScheduleLayer>, + props: CustomElementProps, + ) => { + return ( + <RecurringFieldElement + {...props} + initialValue={value.rotation as Recurring} + /> + ); + }, + required: true, + }, + { + field: { + handOffTime: true, + }, + title: "Hand Off Time", + fieldType: FormFieldSchemaType.DateTime, + required: true, + }, + ]} + onSuccess={(item: OnCallDutyPolicyScheduleLayer) => { + props.onLayerChange(item); + }} + submitButtonText={"Save Changes"} + formType={FormType.Update} + modelIdToEdit={props.layer.id!} + maxPrimaryButtonWidth={false} + /> + ); }; export default LayerRotation; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerStartTime.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerStartTime.tsx index 3f448f0379..5b1f025b9c 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerStartTime.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerStartTime.tsx @@ -1,40 +1,40 @@ -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - layer: OnCallDutyPolicyScheduleLayer; - onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; + layer: OnCallDutyPolicyScheduleLayer; + onLayerChange: (layer: OnCallDutyPolicyScheduleLayer) => void; } const LayerStartsAt: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <ModelForm<OnCallDutyPolicyScheduleLayer> - modelType={OnCallDutyPolicyScheduleLayer} - name="Start Time" - id="start-time" - fields={[ - { - field: { - startsAt: true, - }, - title: 'Starts At', - fieldType: FormFieldSchemaType.DateTime, - required: true, - }, - ]} - onSuccess={(item: OnCallDutyPolicyScheduleLayer) => { - props.onLayerChange(item); - }} - submitButtonText={'Save Changes'} - formType={FormType.Update} - modelIdToEdit={props.layer.id!} - maxPrimaryButtonWidth={false} - /> - ); + return ( + <ModelForm<OnCallDutyPolicyScheduleLayer> + modelType={OnCallDutyPolicyScheduleLayer} + name="Start Time" + id="start-time" + fields={[ + { + field: { + startsAt: true, + }, + title: "Starts At", + fieldType: FormFieldSchemaType.DateTime, + required: true, + }, + ]} + onSuccess={(item: OnCallDutyPolicyScheduleLayer) => { + props.onLayerChange(item); + }} + submitButtonText={"Save Changes"} + formType={FormType.Update} + modelIdToEdit={props.layer.id!} + maxPrimaryButtonWidth={false} + /> + ); }; export default LayerStartsAt; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerUser.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerUser.tsx index efe89c80cc..457820be2d 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerUser.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayerUser.tsx @@ -1,131 +1,126 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import UserElement from '../../User/User'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import Button from 'CommonUI/src/Components/Button/Button'; -import { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelFormModal from 'CommonUI/src/Components/ModelFormModal/ModelFormModal'; -import ModelList from 'CommonUI/src/Components/ModelList/ModelList'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import OnCallDutyPolicyScheduleLayerUser from 'Model/Models/OnCallDutyPolicyScheduleLayerUser'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import UserElement from "../../User/User"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import Button from "CommonUI/src/Components/Button/Button"; +import { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelFormModal from "CommonUI/src/Components/ModelFormModal/ModelFormModal"; +import ModelList from "CommonUI/src/Components/ModelList/ModelList"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import OnCallDutyPolicyScheduleLayerUser from "Model/Models/OnCallDutyPolicyScheduleLayerUser"; +import React, { FunctionComponent, ReactElement, useState } from "react"; export interface ComponentProps { - layer: OnCallDutyPolicyScheduleLayer; - onUpdateUsers: ( - layerUsers: Array<OnCallDutyPolicyScheduleLayerUser> - ) => void; + layer: OnCallDutyPolicyScheduleLayer; + onUpdateUsers: (layerUsers: Array<OnCallDutyPolicyScheduleLayerUser>) => void; } const LayerUser: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [showAddUserModal, setShowAddUserModal] = useState<boolean>(false); - const [reloadList, setReloadList] = useState<boolean>(false); - - const getAddUserButton: GetReactElementFunction = (): ReactElement => { - return ( - <div className="flex w-full justify-center mt-5"> - <Button - title="Add User" - onClick={() => { - setShowAddUserModal(true); - }} - /> - </div> - ); - }; + const [showAddUserModal, setShowAddUserModal] = useState<boolean>(false); + const [reloadList, setReloadList] = useState<boolean>(false); + const getAddUserButton: GetReactElementFunction = (): ReactElement => { return ( - <div> - <ModelList<OnCallDutyPolicyScheduleLayerUser> - id="user-list" - modelType={OnCallDutyPolicyScheduleLayerUser} - titleField="" - query={{ - onCallDutyPolicyScheduleId: - props.layer.onCallDutyPolicyScheduleId, - projectId: props.layer.projectId, - onCallDutyPolicyScheduleLayerId: props.layer.id, - }} - sortBy="order" - sortOrder={SortOrder.Ascending} - customElement={(item: OnCallDutyPolicyScheduleLayerUser) => { - return <UserElement user={item.user} />; - }} - onListLoaded={(list: OnCallDutyPolicyScheduleLayerUser[]) => { - props.onUpdateUsers(list); - }} - descriptionField="" - select={{ - user: { - name: true, - email: true, - _id: true, - profilePictureId: true, - }, - _id: true, - }} - enableDragAndDrop={true} - isDeleteable={true} - refreshToggle={reloadList} - noItemsMessage="No users added to this layer. Please add users to this layer." - footer={getAddUserButton()} - dragDropIdField="_id" - dragDropIndexField="order" - /> - - {showAddUserModal && ( - <ModelFormModal - modelType={OnCallDutyPolicyScheduleLayerUser} - name="Add user to layer" - title="Add User" - onClose={() => { - setShowAddUserModal(false); - }} - submitButtonText="Add User to Layer" - onBeforeCreate={async ( - model: OnCallDutyPolicyScheduleLayerUser - ) => { - model.onCallDutyPolicyScheduleId = - props.layer.onCallDutyPolicyScheduleId!; - model.projectId = props.layer.projectId!; - model.onCallDutyPolicyScheduleLayerId = props.layer.id!; - - return model; // return the model - }} - onSuccess={() => { - setShowAddUserModal(false); - // reload the list - setReloadList(!reloadList); - }} - formProps={{ - name: 'Add user to layer', - modelType: OnCallDutyPolicyScheduleLayerUser, - id: 'add-user-to-layer', - fields: [ - { - field: { - user: true, - }, - fieldType: FormFieldSchemaType.Dropdown, - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - required: true, - placeholder: 'Select User', - }, - ], - formType: FormType.Create, - }} - /> - )} - </div> + <div className="flex w-full justify-center mt-5"> + <Button + title="Add User" + onClick={() => { + setShowAddUserModal(true); + }} + /> + </div> ); + }; + + return ( + <div> + <ModelList<OnCallDutyPolicyScheduleLayerUser> + id="user-list" + modelType={OnCallDutyPolicyScheduleLayerUser} + titleField="" + query={{ + onCallDutyPolicyScheduleId: props.layer.onCallDutyPolicyScheduleId, + projectId: props.layer.projectId, + onCallDutyPolicyScheduleLayerId: props.layer.id, + }} + sortBy="order" + sortOrder={SortOrder.Ascending} + customElement={(item: OnCallDutyPolicyScheduleLayerUser) => { + return <UserElement user={item.user} />; + }} + onListLoaded={(list: OnCallDutyPolicyScheduleLayerUser[]) => { + props.onUpdateUsers(list); + }} + descriptionField="" + select={{ + user: { + name: true, + email: true, + _id: true, + profilePictureId: true, + }, + _id: true, + }} + enableDragAndDrop={true} + isDeleteable={true} + refreshToggle={reloadList} + noItemsMessage="No users added to this layer. Please add users to this layer." + footer={getAddUserButton()} + dragDropIdField="_id" + dragDropIndexField="order" + /> + + {showAddUserModal && ( + <ModelFormModal + modelType={OnCallDutyPolicyScheduleLayerUser} + name="Add user to layer" + title="Add User" + onClose={() => { + setShowAddUserModal(false); + }} + submitButtonText="Add User to Layer" + onBeforeCreate={async (model: OnCallDutyPolicyScheduleLayerUser) => { + model.onCallDutyPolicyScheduleId = + props.layer.onCallDutyPolicyScheduleId!; + model.projectId = props.layer.projectId!; + model.onCallDutyPolicyScheduleLayerId = props.layer.id!; + + return model; // return the model + }} + onSuccess={() => { + setShowAddUserModal(false); + // reload the list + setReloadList(!reloadList); + }} + formProps={{ + name: "Add user to layer", + modelType: OnCallDutyPolicyScheduleLayerUser, + id: "add-user-to-layer", + fields: [ + { + field: { + user: true, + }, + fieldType: FormFieldSchemaType.Dropdown, + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + required: true, + placeholder: "Select User", + }, + ], + formType: FormType.Create, + }} + /> + )} + </div> + ); }; export default LayerUser; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/Layers.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/Layers.tsx index 1b81109f88..f52df3e21d 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/Layers.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/Layers.tsx @@ -1,341 +1,312 @@ -import Layer from './Layer'; -import LayersPreview from './LayersPreview'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import Recurring from 'Common/Types/Events/Recurring'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import RestrictionTimes from 'Common/Types/OnCallDutyPolicy/RestrictionTimes'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Card from 'CommonUI/src/Components/Card/Card'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import HorizontalRule from 'CommonUI/src/Components/HorizontalRule/HorizontalRule'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import OnCallDutyPolicyScheduleLayerUser from 'Model/Models/OnCallDutyPolicyScheduleLayerUser'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import Layer from "./Layer"; +import LayersPreview from "./LayersPreview"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import Recurring from "Common/Types/Events/Recurring"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import RestrictionTimes from "Common/Types/OnCallDutyPolicy/RestrictionTimes"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Card from "CommonUI/src/Components/Card/Card"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import HorizontalRule from "CommonUI/src/Components/HorizontalRule/HorizontalRule"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import OnCallDutyPolicyScheduleLayerUser from "Model/Models/OnCallDutyPolicyScheduleLayerUser"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - onCallDutyPolicyScheduleId: ObjectID; - projectId: ObjectID; + onCallDutyPolicyScheduleId: ObjectID; + projectId: ObjectID; } const Layers: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = React.useState<boolean>(false); + const [isLoading, setIsLoading] = React.useState<boolean>(false); - const [layers, setLayers] = React.useState< - Array<OnCallDutyPolicyScheduleLayer> - >([]); + const [layers, setLayers] = React.useState< + Array<OnCallDutyPolicyScheduleLayer> + >([]); - const [layerUsers, setLayerUsers] = React.useState< - Dictionary<Array<OnCallDutyPolicyScheduleLayerUser>> - >({}); + const [layerUsers, setLayerUsers] = React.useState< + Dictionary<Array<OnCallDutyPolicyScheduleLayerUser>> + >({}); - const [isAddbuttonLoading, setIsAddButtonLoading] = - React.useState<boolean>(false); + const [isAddbuttonLoading, setIsAddButtonLoading] = + React.useState<boolean>(false); - const [error, setError] = React.useState<string>(''); + const [error, setError] = React.useState<string>(""); - const [isDeletetingLayerId, setIsDeletingLayerId] = React.useState< - Array<ObjectID> - >([]); + const [isDeletetingLayerId, setIsDeletingLayerId] = React.useState< + Array<ObjectID> + >([]); - const [showCannotDeleteOnlyLayerError, setShowCannotDeleteOnlyLayerError] = - React.useState<boolean>(false); + const [showCannotDeleteOnlyLayerError, setShowCannotDeleteOnlyLayerError] = + React.useState<boolean>(false); - useEffect(() => { - //fetch layers. - fetchLayers().catch((err: Error) => { - setError(err.message); - }); - }, []); + useEffect(() => { + //fetch layers. + fetchLayers().catch((err: Error) => { + setError(err.message); + }); + }, []); - const addLayer: PromiseVoidFunction = async (): Promise<void> => { - setIsAddButtonLoading(true); + const addLayer: PromiseVoidFunction = async (): Promise<void> => { + setIsAddButtonLoading(true); - try { - const onCallPolicyScheduleLayer: OnCallDutyPolicyScheduleLayer = - new OnCallDutyPolicyScheduleLayer(); - onCallPolicyScheduleLayer.onCallDutyPolicyScheduleId = - props.onCallDutyPolicyScheduleId; - onCallPolicyScheduleLayer.projectId = props.projectId; + try { + const onCallPolicyScheduleLayer: OnCallDutyPolicyScheduleLayer = + new OnCallDutyPolicyScheduleLayer(); + onCallPolicyScheduleLayer.onCallDutyPolicyScheduleId = + props.onCallDutyPolicyScheduleId; + onCallPolicyScheduleLayer.projectId = props.projectId; - // count the layers and generate a unique name for this layer. - const newLayerName: string = `Layer ${layers.length + 1}`; - onCallPolicyScheduleLayer.name = newLayerName; + // count the layers and generate a unique name for this layer. + const newLayerName: string = `Layer ${layers.length + 1}`; + onCallPolicyScheduleLayer.name = newLayerName; - onCallPolicyScheduleLayer.handOffTime = OneUptimeDate.addRemoveDays( - OneUptimeDate.getCurrentDate(), - 1 - ); + onCallPolicyScheduleLayer.handOffTime = OneUptimeDate.addRemoveDays( + OneUptimeDate.getCurrentDate(), + 1, + ); - // count the description and generate a unique description for this layer. - const newLayerDescription: string = `Layer ${ - layers.length + 1 - } description.`; - onCallPolicyScheduleLayer.description = newLayerDescription; - onCallPolicyScheduleLayer.order = layers.length + 1; - onCallPolicyScheduleLayer.restrictionTimes = - RestrictionTimes.getDefault(); - onCallPolicyScheduleLayer.rotation = Recurring.getDefault(); - onCallPolicyScheduleLayer.startsAt = OneUptimeDate.getCurrentDate(); + // count the description and generate a unique description for this layer. + const newLayerDescription: string = `Layer ${ + layers.length + 1 + } description.`; + onCallPolicyScheduleLayer.description = newLayerDescription; + onCallPolicyScheduleLayer.order = layers.length + 1; + onCallPolicyScheduleLayer.restrictionTimes = + RestrictionTimes.getDefault(); + onCallPolicyScheduleLayer.rotation = Recurring.getDefault(); + onCallPolicyScheduleLayer.startsAt = OneUptimeDate.getCurrentDate(); - const newLayer: HTTPResponse< - | OnCallDutyPolicyScheduleLayer - | OnCallDutyPolicyScheduleLayer[] - | JSONObject - | JSONArray - > = await ModelAPI.create<OnCallDutyPolicyScheduleLayer>({ - model: onCallPolicyScheduleLayer, - modelType: OnCallDutyPolicyScheduleLayer, - }); + const newLayer: HTTPResponse< + | OnCallDutyPolicyScheduleLayer + | OnCallDutyPolicyScheduleLayer[] + | JSONObject + | JSONArray + > = await ModelAPI.create<OnCallDutyPolicyScheduleLayer>({ + model: onCallPolicyScheduleLayer, + modelType: OnCallDutyPolicyScheduleLayer, + }); - // add this layer to layers array and set it. - setLayers([ - ...layers, - newLayer.data as OnCallDutyPolicyScheduleLayer, - ]); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsAddButtonLoading(false); - }; - - type DeleteLayerFunction = (item: OnCallDutyPolicyScheduleLayer) => void; - - const deleteLayer: DeleteLayerFunction = async ( - item: OnCallDutyPolicyScheduleLayer - ) => { - if (!item.id) { - throw new BadDataException('item.id cannot be null'); - } - - if (layers.length === 1) { - setShowCannotDeleteOnlyLayerError(true); - return; - } - - // push this layer id to isDeletetingLayerId array. - setIsDeletingLayerId([...isDeletetingLayerId, item.id]); - - try { - await ModelAPI.deleteItem<OnCallDutyPolicyScheduleLayer>({ - modelType: OnCallDutyPolicyScheduleLayer, - id: item.id, - }); - - // remove this layer from layers array and set it. - - const newLayers: Array<OnCallDutyPolicyScheduleLayer> = - layers.filter((layer: OnCallDutyPolicyScheduleLayer) => { - return layer.id?.toString() !== item.id?.toString(); - }); - - setLayers(newLayers); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - // remove this layer id from isDeletetingLayerId array. - setIsDeletingLayerId( - isDeletetingLayerId.filter((id: ObjectID) => { - return id?.toString() !== item.id?.toString(); - }) - ); - }; - - const addLayerButton: GetReactElementFunction = (): ReactElement => { - return ( - <div className="-ml-3 mt-5"> - <Button - title="Add New Layer" - isLoading={isAddbuttonLoading} - onClick={async () => { - await addLayer(); - }} - icon={IconProp.Add} - /> - </div> - ); - }; - - const fetchLayers: PromiseVoidFunction = async (): Promise<void> => { - setIsLoading(true); - - try { - const layers: ListResult<OnCallDutyPolicyScheduleLayer> = - await ModelAPI.getList<OnCallDutyPolicyScheduleLayer>({ - modelType: OnCallDutyPolicyScheduleLayer, - query: { - onCallDutyPolicyScheduleId: - props.onCallDutyPolicyScheduleId, - projectId: props.projectId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - order: true, - name: true, - description: true, - startsAt: true, - restrictionTimes: true, - rotation: true, - onCallDutyPolicyScheduleId: true, - projectId: true, - handOffTime: true, - }, - sort: { - order: SortOrder.Ascending, - }, - }); - - setLayers(layers.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - if (isLoading) { - return <ComponentLoader />; + // add this layer to layers array and set it. + setLayers([...layers, newLayer.data as OnCallDutyPolicyScheduleLayer]); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (error) { - return <ErrorMessage error={error} />; + setIsAddButtonLoading(false); + }; + + type DeleteLayerFunction = (item: OnCallDutyPolicyScheduleLayer) => void; + + const deleteLayer: DeleteLayerFunction = async ( + item: OnCallDutyPolicyScheduleLayer, + ) => { + if (!item.id) { + throw new BadDataException("item.id cannot be null"); } - return ( - <div> - <div> - {layers.map( - (layer: OnCallDutyPolicyScheduleLayer, i: number) => { - return ( - <Layer - key={i} - isDeleteButtonLoading={Boolean( - isDeletetingLayerId.find((id: ObjectID) => { - return ( - id.toString() === - layer.id?.toString() - ); - }) - )} - layer={layer} - onDeleteLayer={() => { - deleteLayer(layer); - }} - onLayerUsersUpdateOrLoaded={( - users: Array<OnCallDutyPolicyScheduleLayerUser> - ) => { - setLayerUsers({ - ...layerUsers, - [layer.id?.toString() || '']: [ - ...users, - ], - }); - }} - onLayerChange={( - layer: OnCallDutyPolicyScheduleLayer - ) => { - // update this layer in layers array and set it. - const newLayers: Array<OnCallDutyPolicyScheduleLayer> = - layers.map( - ( - item: OnCallDutyPolicyScheduleLayer - ) => { - if ( - item.id?.toString() === - layer.id?.toString() - ) { - return BaseModel.fromJSON( - BaseModel.toJSON( - layer, - OnCallDutyPolicyScheduleLayer - ), - OnCallDutyPolicyScheduleLayer - ) as OnCallDutyPolicyScheduleLayer; - } - return BaseModel.fromJSON( - BaseModel.toJSON( - item, - OnCallDutyPolicyScheduleLayer - ), - OnCallDutyPolicyScheduleLayer - ) as OnCallDutyPolicyScheduleLayer; - } - ); + if (layers.length === 1) { + setShowCannotDeleteOnlyLayerError(true); + return; + } - setLayers([...newLayers]); - }} - /> - ); - } - )} - </div> + // push this layer id to isDeletetingLayerId array. + setIsDeletingLayerId([...isDeletetingLayerId, item.id]); - {layers.length === 0 && ( - <EmptyState - footer={addLayerButton()} - showSolidBackground={false} - id="no-layers" - title={'No Layers in this On Call Schedule'} - description={ - 'No layers in this on-call schedule. Please add one.' - } - icon={IconProp.SquareStack} - /> - )} + try { + await ModelAPI.deleteItem<OnCallDutyPolicyScheduleLayer>({ + modelType: OnCallDutyPolicyScheduleLayer, + id: item.id, + }); - {layers.length > 0 && addLayerButton()} + // remove this layer from layers array and set it. - {showCannotDeleteOnlyLayerError ? ( - <ConfirmModal - title={`Cannot delete layer`} - description={ - <div>Schedule must have at least one layer.</div> - } - isLoading={false} - submitButtonText={'Close'} - submitButtonType={ButtonStyleType.NORMAL} - onSubmit={() => { - return setShowCannotDeleteOnlyLayerError(false); - }} - /> - ) : ( - <></> - )} + const newLayers: Array<OnCallDutyPolicyScheduleLayer> = layers.filter( + (layer: OnCallDutyPolicyScheduleLayer) => { + return layer.id?.toString() !== item.id?.toString(); + }, + ); - {layers.length > 0 && <HorizontalRule />} + setLayers(newLayers); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - {layers.length > 0 && ( - <Card - title={`Final Schedule Preview`} - description={ - 'Here is the final preview of who is on call and when. This is based on your local timezone - ' + - OneUptimeDate.getCurrentTimezoneString() - } - > - <LayersPreview layers={layers} allLayerUsers={layerUsers} /> - </Card> - )} - </div> + // remove this layer id from isDeletetingLayerId array. + setIsDeletingLayerId( + isDeletetingLayerId.filter((id: ObjectID) => { + return id?.toString() !== item.id?.toString(); + }), ); + }; + + const addLayerButton: GetReactElementFunction = (): ReactElement => { + return ( + <div className="-ml-3 mt-5"> + <Button + title="Add New Layer" + isLoading={isAddbuttonLoading} + onClick={async () => { + await addLayer(); + }} + icon={IconProp.Add} + /> + </div> + ); + }; + + const fetchLayers: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); + + try { + const layers: ListResult<OnCallDutyPolicyScheduleLayer> = + await ModelAPI.getList<OnCallDutyPolicyScheduleLayer>({ + modelType: OnCallDutyPolicyScheduleLayer, + query: { + onCallDutyPolicyScheduleId: props.onCallDutyPolicyScheduleId, + projectId: props.projectId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + order: true, + name: true, + description: true, + startsAt: true, + restrictionTimes: true, + rotation: true, + onCallDutyPolicyScheduleId: true, + projectId: true, + handOffTime: true, + }, + sort: { + order: SortOrder.Ascending, + }, + }); + + setLayers(layers.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + if (isLoading) { + return <ComponentLoader />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <div> + <div> + {layers.map((layer: OnCallDutyPolicyScheduleLayer, i: number) => { + return ( + <Layer + key={i} + isDeleteButtonLoading={Boolean( + isDeletetingLayerId.find((id: ObjectID) => { + return id.toString() === layer.id?.toString(); + }), + )} + layer={layer} + onDeleteLayer={() => { + deleteLayer(layer); + }} + onLayerUsersUpdateOrLoaded={( + users: Array<OnCallDutyPolicyScheduleLayerUser>, + ) => { + setLayerUsers({ + ...layerUsers, + [layer.id?.toString() || ""]: [...users], + }); + }} + onLayerChange={(layer: OnCallDutyPolicyScheduleLayer) => { + // update this layer in layers array and set it. + const newLayers: Array<OnCallDutyPolicyScheduleLayer> = + layers.map((item: OnCallDutyPolicyScheduleLayer) => { + if (item.id?.toString() === layer.id?.toString()) { + return BaseModel.fromJSON( + BaseModel.toJSON(layer, OnCallDutyPolicyScheduleLayer), + OnCallDutyPolicyScheduleLayer, + ) as OnCallDutyPolicyScheduleLayer; + } + return BaseModel.fromJSON( + BaseModel.toJSON(item, OnCallDutyPolicyScheduleLayer), + OnCallDutyPolicyScheduleLayer, + ) as OnCallDutyPolicyScheduleLayer; + }); + + setLayers([...newLayers]); + }} + /> + ); + })} + </div> + + {layers.length === 0 && ( + <EmptyState + footer={addLayerButton()} + showSolidBackground={false} + id="no-layers" + title={"No Layers in this On Call Schedule"} + description={"No layers in this on-call schedule. Please add one."} + icon={IconProp.SquareStack} + /> + )} + + {layers.length > 0 && addLayerButton()} + + {showCannotDeleteOnlyLayerError ? ( + <ConfirmModal + title={`Cannot delete layer`} + description={<div>Schedule must have at least one layer.</div>} + isLoading={false} + submitButtonText={"Close"} + submitButtonType={ButtonStyleType.NORMAL} + onSubmit={() => { + return setShowCannotDeleteOnlyLayerError(false); + }} + /> + ) : ( + <></> + )} + + {layers.length > 0 && <HorizontalRule />} + + {layers.length > 0 && ( + <Card + title={`Final Schedule Preview`} + description={ + "Here is the final preview of who is on call and when. This is based on your local timezone - " + + OneUptimeDate.getCurrentTimezoneString() + } + > + <LayersPreview layers={layers} allLayerUsers={layerUsers} /> + </Card> + )} + </div> + ); }; export default Layers; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayersPreview.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayersPreview.tsx index 3c2bf58d90..1e187a29eb 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayersPreview.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/LayersPreview.tsx @@ -1,148 +1,146 @@ -import { Blue500, BrightColors } from 'Common/Types/BrandColors'; -import CalendarEvent from 'Common/Types/Calendar/CalendarEvent'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import HashCode from 'Common/Types/HashCode'; -import LayerUtil, { LayerProps } from 'Common/Types/OnCallDutyPolicy/Layer'; -import StartAndEndTime from 'Common/Types/Time/StartAndEndTime'; -import Calendar from 'CommonUI/src/Components/Calendar/Calendar'; -import FieldLabelElement from 'CommonUI/src/Components/Forms/Fields/FieldLabel'; -import OnCallDutyPolicyScheduleLayer from 'Model/Models/OnCallDutyPolicyScheduleLayer'; -import OnCallDutyPolicyScheduleLayerUser from 'Model/Models/OnCallDutyPolicyScheduleLayerUser'; -import User from 'Model/Models/User'; +import { Blue500, BrightColors } from "Common/Types/BrandColors"; +import CalendarEvent from "Common/Types/Calendar/CalendarEvent"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import HashCode from "Common/Types/HashCode"; +import LayerUtil, { LayerProps } from "Common/Types/OnCallDutyPolicy/Layer"; +import StartAndEndTime from "Common/Types/Time/StartAndEndTime"; +import Calendar from "CommonUI/src/Components/Calendar/Calendar"; +import FieldLabelElement from "CommonUI/src/Components/Forms/Fields/FieldLabel"; +import OnCallDutyPolicyScheduleLayer from "Model/Models/OnCallDutyPolicyScheduleLayer"; +import OnCallDutyPolicyScheduleLayerUser from "Model/Models/OnCallDutyPolicyScheduleLayerUser"; +import User from "Model/Models/User"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - layers: Array<OnCallDutyPolicyScheduleLayer>; - allLayerUsers: Dictionary<Array<OnCallDutyPolicyScheduleLayerUser>>; - showFieldLabel?: boolean; - id?: string | undefined; + layers: Array<OnCallDutyPolicyScheduleLayer>; + allLayerUsers: Dictionary<Array<OnCallDutyPolicyScheduleLayerUser>>; + showFieldLabel?: boolean; + id?: string | undefined; } const LayersPreview: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [startTime, setStartTime] = useState<Date>( - OneUptimeDate.getStartOfDay(OneUptimeDate.getCurrentDate()) - ); - const [endTime, setEndTime] = useState<Date>( - OneUptimeDate.getEndOfDay(OneUptimeDate.getCurrentDate()) - ); + const [startTime, setStartTime] = useState<Date>( + OneUptimeDate.getStartOfDay(OneUptimeDate.getCurrentDate()), + ); + const [endTime, setEndTime] = useState<Date>( + OneUptimeDate.getEndOfDay(OneUptimeDate.getCurrentDate()), + ); - const [calendarEvents, setCalendarEvents] = useState<Array<CalendarEvent>>( - [] - ); + const [calendarEvents, setCalendarEvents] = useState<Array<CalendarEvent>>( + [], + ); - useEffect(() => { - setCalendarEvents(getCalendarEvents(startTime, endTime)); - }, [props.layers, props.allLayerUsers, startTime, endTime]); + useEffect(() => { + setCalendarEvents(getCalendarEvents(startTime, endTime)); + }, [props.layers, props.allLayerUsers, startTime, endTime]); - type GetCalendarEventsFunction = ( - calendarStartTime: Date, - calendarEndTime: Date - ) => Array<CalendarEvent>; + type GetCalendarEventsFunction = ( + calendarStartTime: Date, + calendarEndTime: Date, + ) => Array<CalendarEvent>; - const getCalendarEvents: GetCalendarEventsFunction = ( - calendarStartTime: Date, - calendarEndTime: Date - ): Array<CalendarEvent> => { - const layerProps: Array<LayerProps> = []; + const getCalendarEvents: GetCalendarEventsFunction = ( + calendarStartTime: Date, + calendarEndTime: Date, + ): Array<CalendarEvent> => { + const layerProps: Array<LayerProps> = []; - const users: Array<User> = []; + const users: Array<User> = []; - for (const key in props.allLayerUsers) { - const layerUsers: Array<OnCallDutyPolicyScheduleLayerUser> = - props.allLayerUsers[key] || []; + for (const key in props.allLayerUsers) { + const layerUsers: Array<OnCallDutyPolicyScheduleLayerUser> = + props.allLayerUsers[key] || []; - for (const layerUser of layerUsers) { - users.push(layerUser.user!); - } - } + for (const layerUser of layerUsers) { + users.push(layerUser.user!); + } + } - for (const layer of props.layers) { - const layerUsers: Array<OnCallDutyPolicyScheduleLayerUser> = - props.allLayerUsers[layer.id?.toString() || ''] || []; + for (const layer of props.layers) { + const layerUsers: Array<OnCallDutyPolicyScheduleLayerUser> = + props.allLayerUsers[layer.id?.toString() || ""] || []; - layerProps.push({ - users: layerUsers.map( - (layerUser: OnCallDutyPolicyScheduleLayerUser) => { - return layerUser.user!; - } - ), - startDateTimeOfLayer: layer.startsAt!, - handOffTime: layer.handOffTime!, - rotation: layer.rotation!, - restrictionTimes: layer.restrictionTimes!, - }); - } + layerProps.push({ + users: layerUsers.map( + (layerUser: OnCallDutyPolicyScheduleLayerUser) => { + return layerUser.user!; + }, + ), + startDateTimeOfLayer: layer.startsAt!, + handOffTime: layer.handOffTime!, + rotation: layer.rotation!, + restrictionTimes: layer.restrictionTimes!, + }); + } - const events: Array<CalendarEvent> = LayerUtil.getMultiLayerEvents({ - calendarEndDate: calendarEndTime, - calendarStartDate: calendarStartTime, - layers: layerProps, - }); + const events: Array<CalendarEvent> = LayerUtil.getMultiLayerEvents({ + calendarEndDate: calendarEndTime, + calendarStartDate: calendarStartTime, + layers: layerProps, + }); - // Assign colors to each user based on id. Hash the id and mod it by the length of the color list. + // Assign colors to each user based on id. Hash the id and mod it by the length of the color list. - const colorListLength: number = BrightColors.length; + const colorListLength: number = BrightColors.length; - events.forEach((event: CalendarEvent) => { - const userId: string = event.title; + events.forEach((event: CalendarEvent) => { + const userId: string = event.title; - const user: User | undefined = users.find((user: User) => { - return user.id?.toString() === userId; - }); + const user: User | undefined = users.find((user: User) => { + return user.id?.toString() === userId; + }); - if (!user) { - return; - } + if (!user) { + return; + } - const colorIndex: number = - HashCode.fromString(userId) % colorListLength; + const colorIndex: number = HashCode.fromString(userId) % colorListLength; - event.color = - (BrightColors[colorIndex] as Color)?.toString() || - Blue500.toString(); + event.color = + (BrightColors[colorIndex] as Color)?.toString() || Blue500.toString(); - event.title = `${ - (user.name?.toString() || '') + - ' ' + - '(' + - (user.email?.toString() || '') + - ')' - }`; - }); + event.title = `${ + (user.name?.toString() || "") + + " " + + "(" + + (user.email?.toString() || "") + + ")" + }`; + }); - return events; - }; + return events; + }; - return ( - <div id={props.id}> - {props.showFieldLabel && ( - <FieldLabelElement - required={true} - title="Layer Preview" - description={ - 'Here is a preview of who is on call and when. This is based on your local timezone - ' + - OneUptimeDate.getCurrentTimezoneString() - } - /> - )} - <Calendar - events={calendarEvents} - onRangeChange={(startEndTime: StartAndEndTime) => { - setStartTime(startEndTime.startTime); - setEndTime(startEndTime.endTime); - }} - /> - </div> - ); + return ( + <div id={props.id}> + {props.showFieldLabel && ( + <FieldLabelElement + required={true} + title="Layer Preview" + description={ + "Here is a preview of who is on call and when. This is based on your local timezone - " + + OneUptimeDate.getCurrentTimezoneString() + } + /> + )} + <Calendar + events={calendarEvents} + onRangeChange={(startEndTime: StartAndEndTime) => { + setStartTime(startEndTime.startTime); + setEndTime(startEndTime.endTime); + }} + /> + </div> + ); }; export default LayersPreview; diff --git a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/RestrictionTimesFieldElement.tsx b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/RestrictionTimesFieldElement.tsx index 15e53d2b09..e0245f39ac 100644 --- a/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/RestrictionTimesFieldElement.tsx +++ b/Dashboard/src/Components/OnCallPolicy/OnCallScheduleLayer/RestrictionTimesFieldElement.tsx @@ -1,439 +1,401 @@ -import OneUptimeDate from 'Common/Types/Date'; -import DayOfWeek from 'Common/Types/Day/DayOfWeek'; -import IconProp from 'Common/Types/Icon/IconProp'; +import OneUptimeDate from "Common/Types/Date"; +import DayOfWeek from "Common/Types/Day/DayOfWeek"; +import IconProp from "Common/Types/Icon/IconProp"; import RestrictionTimes, { - RestrictionType, - WeeklyResctriction, -} from 'Common/Types/OnCallDutyPolicy/RestrictionTimes'; -import Typeof from 'Common/Types/Typeof'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import FieldLabelElement from 'CommonUI/src/Components/Detail/FieldLabel'; -import Dropdown from 'CommonUI/src/Components/Dropdown/Dropdown'; -import Input, { InputType } from 'CommonUI/src/Components/Input/Input'; -import BasicRadioButtons from 'CommonUI/src/Components/RadioButtons/BasicRadioButtons'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; + RestrictionType, + WeeklyResctriction, +} from "Common/Types/OnCallDutyPolicy/RestrictionTimes"; +import Typeof from "Common/Types/Typeof"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import FieldLabelElement from "CommonUI/src/Components/Detail/FieldLabel"; +import Dropdown from "CommonUI/src/Components/Dropdown/Dropdown"; +import Input, { InputType } from "CommonUI/src/Components/Input/Input"; +import BasicRadioButtons from "CommonUI/src/Components/RadioButtons/BasicRadioButtons"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - error?: string | undefined; - onChange?: ((value: RestrictionTimes) => void) | undefined; - value?: RestrictionTimes | undefined; + error?: string | undefined; + onChange?: ((value: RestrictionTimes) => void) | undefined; + value?: RestrictionTimes | undefined; } const RestrictionTimesFieldElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [restrictionTimes, setRestrictionTimes] = useState< - RestrictionTimes | undefined - >(props.value ? RestrictionTimes.fromJSON(props.value) : undefined); + const [restrictionTimes, setRestrictionTimes] = useState< + RestrictionTimes | undefined + >(props.value ? RestrictionTimes.fromJSON(props.value) : undefined); - useEffect(() => { - if (props.value) { - setRestrictionTimes(RestrictionTimes.fromJSON(props.value)); - } else { - setRestrictionTimes(undefined); - } - }, [props.value]); + useEffect(() => { + if (props.value) { + setRestrictionTimes(RestrictionTimes.fromJSON(props.value)); + } else { + setRestrictionTimes(undefined); + } + }, [props.value]); - const getDailyRestriction: GetReactElementFunction = (): ReactElement => { - // show start time to end time input fields - - return ( - <div className="flex space-x-3"> - <div> - <FieldLabelElement title="From:" /> - <Input - type={InputType.TIME} - value={OneUptimeDate.toString( - restrictionTimes?.dayRestrictionTimes?.startTime - )} - onChange={(value: any) => { - let date: Date = OneUptimeDate.getCurrentDate(); - - if (value instanceof Date) { - date = value; - } - - if (typeof value === Typeof.String) { - date = OneUptimeDate.fromString(value); - } - - let tempRestrictionTimes: - | RestrictionTimes - | undefined = restrictionTimes; - - if (!tempRestrictionTimes) { - tempRestrictionTimes = new RestrictionTimes(); - } - - if (!tempRestrictionTimes.dayRestrictionTimes) { - tempRestrictionTimes.dayRestrictionTimes = { - startTime: date, - endTime: date, - }; - } - - tempRestrictionTimes.dayRestrictionTimes.startTime = - date; - - updateRestrictionTimes(tempRestrictionTimes); - }} - /> - </div> - <div> - <FieldLabelElement title="To:" /> - <Input - type={InputType.TIME} - value={OneUptimeDate.toString( - restrictionTimes?.dayRestrictionTimes?.endTime - )} - onChange={(value: any) => { - let date: Date = OneUptimeDate.getCurrentDate(); - - if (value instanceof Date) { - date = value; - } - - if (typeof value === Typeof.String) { - date = OneUptimeDate.fromString(value); - } - - let tempRestrictionTimes: - | RestrictionTimes - | undefined = restrictionTimes; - - if (!tempRestrictionTimes) { - tempRestrictionTimes = new RestrictionTimes(); - } - - if (!tempRestrictionTimes.dayRestrictionTimes) { - tempRestrictionTimes.dayRestrictionTimes = { - startTime: date, - endTime: date, - }; - } - - tempRestrictionTimes.dayRestrictionTimes.endTime = - date; - - updateRestrictionTimes(tempRestrictionTimes); - }} - /> - </div> - </div> - ); - }; - - const getWeeklyTimeRestrictions: GetReactElementFunction = - (): ReactElement => { - return ( - <div> - <div className="ml-8"> - {/** LIST */} - - {restrictionTimes?.weeklyRestrictionTimes?.map( - ( - weeklyRestriction: WeeklyResctriction, - i: number - ) => { - return ( - <div key={i} className="flex"> - <div> - {getWeeklyTimeRestriction({ - weeklyRestriction, - onChange: ( - value: WeeklyResctriction - ) => { - let tempRestrictionTimes: - | RestrictionTimes - | undefined = restrictionTimes; - - if (!tempRestrictionTimes) { - tempRestrictionTimes = - new RestrictionTimes(); - } - - if ( - !tempRestrictionTimes.weeklyRestrictionTimes - ) { - tempRestrictionTimes.weeklyRestrictionTimes = - []; - } - - tempRestrictionTimes.weeklyRestrictionTimes[ - i - ] = value; - - updateRestrictionTimes( - tempRestrictionTimes - ); - }, - onDelete: () => { - let tempRestrictionTimes: - | RestrictionTimes - | undefined = restrictionTimes; - - if (!tempRestrictionTimes) { - tempRestrictionTimes = - new RestrictionTimes(); - } - - if ( - !tempRestrictionTimes.weeklyRestrictionTimes - ) { - tempRestrictionTimes.weeklyRestrictionTimes = - []; - } - - tempRestrictionTimes.weeklyRestrictionTimes.splice( - i, - 1 - ); - - updateRestrictionTimes( - tempRestrictionTimes - ); - }, - })} - </div> - </div> - ); - } - )} - </div> - - <div className="ml-5 mt-3"> - {/** show add button */} - <Button - title="Add Restriction Time" - buttonStyle={ButtonStyleType.NORMAL} - icon={IconProp.Add} - onClick={() => { - let tempRestrictionTimes: - | RestrictionTimes - | undefined = restrictionTimes; - - if (!tempRestrictionTimes) { - tempRestrictionTimes = - new RestrictionTimes(); - } - - if ( - !tempRestrictionTimes.weeklyRestrictionTimes - ) { - tempRestrictionTimes.weeklyRestrictionTimes = - []; - } - - tempRestrictionTimes.weeklyRestrictionTimes.push( - RestrictionTimes.getDefaultWeeklyRestrictionTIme() - ); - - updateRestrictionTimes(tempRestrictionTimes); - }} - /> - </div> - </div> - ); - }; - - type GetWeeklyRestrictionFunction = (params: { - weeklyRestriction: WeeklyResctriction; - onChange: (value: WeeklyResctriction) => void; - onDelete: () => void; - }) => ReactElement; - - const getWeeklyTimeRestriction: GetWeeklyRestrictionFunction = (params: { - weeklyRestriction: WeeklyResctriction; - onChange: (value: WeeklyResctriction) => void; - onDelete: () => void; - }): ReactElement => { - // show start time to end time input fields - - return ( - <div className="flex space-x-3 mt-2"> - <div> - <FieldLabelElement title="From:" /> - <div className="space-x-3 flex"> - <div> - <Dropdown - options={DropdownUtil.getDropdownOptionsFromEnum( - DayOfWeek - )} - value={DropdownUtil.getDropdownOptionFromEnumForValue( - DayOfWeek, - params.weeklyRestriction.startDay - )} - onChange={(value: any) => { - params.weeklyRestriction.startDay = value; - params.onChange(params.weeklyRestriction); - }} - /> - </div> - <div> - <Input - type={InputType.TIME} - value={OneUptimeDate.toString( - params.weeklyRestriction?.startTime - )} - onChange={(value: any) => { - let date: Date = - OneUptimeDate.getCurrentDate(); - - if (value instanceof Date) { - date = value; - } - - if (typeof value === Typeof.String) { - date = OneUptimeDate.fromString(value); - } - - params.weeklyRestriction.startTime = date; - - params.onChange(params.weeklyRestriction); - }} - /> - </div> - </div> - </div> - <div className="ml-5"> - <FieldLabelElement title="To:" /> - <div className="space-x-3 flex"> - <div> - <Dropdown - options={DropdownUtil.getDropdownOptionsFromEnum( - DayOfWeek - )} - value={DropdownUtil.getDropdownOptionFromEnumForValue( - DayOfWeek, - params.weeklyRestriction.endDay - )} - onChange={(value: any) => { - params.weeklyRestriction.endDay = value; - params.onChange(params.weeklyRestriction); - }} - /> - </div> - <div> - <Input - type={InputType.TIME} - value={OneUptimeDate.toString( - params.weeklyRestriction?.endTime - )} - onChange={(value: any) => { - let date: Date = - OneUptimeDate.getCurrentDate(); - - if (value instanceof Date) { - date = value; - } - - if (typeof value === Typeof.String) { - date = OneUptimeDate.fromString(value); - } - - params.weeklyRestriction.endTime = date; - - params.onChange(params.weeklyRestriction); - }} - /> - </div> - </div> - </div> - <div className="mt-8"> - {/* Dellete Button */} - <Button - title="Delete" - buttonStyle={ButtonStyleType.NORMAL} - icon={IconProp.Trash} - onClick={() => { - params.onDelete(); - }} - /> - </div> - </div> - ); - }; - - type UpdateRestrictionTimesFunction = ( - restrictionTimes: RestrictionTimes - ) => void; - - const updateRestrictionTimes: UpdateRestrictionTimesFunction = ( - restrictionTimes: RestrictionTimes - ): void => { - setRestrictionTimes( - RestrictionTimes.fromJSON(restrictionTimes.toJSON()) - ); - if (props.onChange) { - props.onChange(restrictionTimes); - } - }; + const getDailyRestriction: GetReactElementFunction = (): ReactElement => { + // show start time to end time input fields return ( + <div className="flex space-x-3"> <div> - <BasicRadioButtons - onChange={(value: string) => { - let tempRestrictionTimes: RestrictionTimes | undefined = - restrictionTimes; - - if (!tempRestrictionTimes) { - tempRestrictionTimes = new RestrictionTimes(); - } - - if (value === RestrictionType.None) { - // remove all restrictions - tempRestrictionTimes.removeAllRestrictions(); - updateRestrictionTimes(tempRestrictionTimes); - } else if (value === RestrictionType.Daily) { - // remove all restrictions - tempRestrictionTimes.removeAllRestrictions(); - // add daily restriction - tempRestrictionTimes.addDefaultDailyRestriction(); - updateRestrictionTimes(tempRestrictionTimes); - } else if (value === RestrictionType.Weekly) { - // remove all restrictions - tempRestrictionTimes.removeAllRestrictions(); - // add weekly restriction - tempRestrictionTimes.addDefaultWeeklyRestriction(); - updateRestrictionTimes(tempRestrictionTimes); - } - }} - initialValue={restrictionTimes?.restictionType} - options={[ - { - title: 'No Restrictions', - value: RestrictionType.None, - }, - { - title: 'Specific Times of the Day', - value: RestrictionType.Daily, - children: getDailyRestriction(), - }, - { - title: 'Specific Times of the Week', - value: RestrictionType.Weekly, - children: getWeeklyTimeRestrictions(), - }, - ]} - /> - - {props.error && ( - <p - data-testid="error-message" - className="mt-1 text-sm text-red-400" - > - {props.error} - </p> + <FieldLabelElement title="From:" /> + <Input + type={InputType.TIME} + value={OneUptimeDate.toString( + restrictionTimes?.dayRestrictionTimes?.startTime, )} + onChange={(value: any) => { + let date: Date = OneUptimeDate.getCurrentDate(); + + if (value instanceof Date) { + date = value; + } + + if (typeof value === Typeof.String) { + date = OneUptimeDate.fromString(value); + } + + let tempRestrictionTimes: RestrictionTimes | undefined = + restrictionTimes; + + if (!tempRestrictionTimes) { + tempRestrictionTimes = new RestrictionTimes(); + } + + if (!tempRestrictionTimes.dayRestrictionTimes) { + tempRestrictionTimes.dayRestrictionTimes = { + startTime: date, + endTime: date, + }; + } + + tempRestrictionTimes.dayRestrictionTimes.startTime = date; + + updateRestrictionTimes(tempRestrictionTimes); + }} + /> </div> + <div> + <FieldLabelElement title="To:" /> + <Input + type={InputType.TIME} + value={OneUptimeDate.toString( + restrictionTimes?.dayRestrictionTimes?.endTime, + )} + onChange={(value: any) => { + let date: Date = OneUptimeDate.getCurrentDate(); + + if (value instanceof Date) { + date = value; + } + + if (typeof value === Typeof.String) { + date = OneUptimeDate.fromString(value); + } + + let tempRestrictionTimes: RestrictionTimes | undefined = + restrictionTimes; + + if (!tempRestrictionTimes) { + tempRestrictionTimes = new RestrictionTimes(); + } + + if (!tempRestrictionTimes.dayRestrictionTimes) { + tempRestrictionTimes.dayRestrictionTimes = { + startTime: date, + endTime: date, + }; + } + + tempRestrictionTimes.dayRestrictionTimes.endTime = date; + + updateRestrictionTimes(tempRestrictionTimes); + }} + /> + </div> + </div> ); + }; + + const getWeeklyTimeRestrictions: GetReactElementFunction = + (): ReactElement => { + return ( + <div> + <div className="ml-8"> + {/** LIST */} + + {restrictionTimes?.weeklyRestrictionTimes?.map( + (weeklyRestriction: WeeklyResctriction, i: number) => { + return ( + <div key={i} className="flex"> + <div> + {getWeeklyTimeRestriction({ + weeklyRestriction, + onChange: (value: WeeklyResctriction) => { + let tempRestrictionTimes: + | RestrictionTimes + | undefined = restrictionTimes; + + if (!tempRestrictionTimes) { + tempRestrictionTimes = new RestrictionTimes(); + } + + if (!tempRestrictionTimes.weeklyRestrictionTimes) { + tempRestrictionTimes.weeklyRestrictionTimes = []; + } + + tempRestrictionTimes.weeklyRestrictionTimes[i] = + value; + + updateRestrictionTimes(tempRestrictionTimes); + }, + onDelete: () => { + let tempRestrictionTimes: + | RestrictionTimes + | undefined = restrictionTimes; + + if (!tempRestrictionTimes) { + tempRestrictionTimes = new RestrictionTimes(); + } + + if (!tempRestrictionTimes.weeklyRestrictionTimes) { + tempRestrictionTimes.weeklyRestrictionTimes = []; + } + + tempRestrictionTimes.weeklyRestrictionTimes.splice( + i, + 1, + ); + + updateRestrictionTimes(tempRestrictionTimes); + }, + })} + </div> + </div> + ); + }, + )} + </div> + + <div className="ml-5 mt-3"> + {/** show add button */} + <Button + title="Add Restriction Time" + buttonStyle={ButtonStyleType.NORMAL} + icon={IconProp.Add} + onClick={() => { + let tempRestrictionTimes: RestrictionTimes | undefined = + restrictionTimes; + + if (!tempRestrictionTimes) { + tempRestrictionTimes = new RestrictionTimes(); + } + + if (!tempRestrictionTimes.weeklyRestrictionTimes) { + tempRestrictionTimes.weeklyRestrictionTimes = []; + } + + tempRestrictionTimes.weeklyRestrictionTimes.push( + RestrictionTimes.getDefaultWeeklyRestrictionTIme(), + ); + + updateRestrictionTimes(tempRestrictionTimes); + }} + /> + </div> + </div> + ); + }; + + type GetWeeklyRestrictionFunction = (params: { + weeklyRestriction: WeeklyResctriction; + onChange: (value: WeeklyResctriction) => void; + onDelete: () => void; + }) => ReactElement; + + const getWeeklyTimeRestriction: GetWeeklyRestrictionFunction = (params: { + weeklyRestriction: WeeklyResctriction; + onChange: (value: WeeklyResctriction) => void; + onDelete: () => void; + }): ReactElement => { + // show start time to end time input fields + + return ( + <div className="flex space-x-3 mt-2"> + <div> + <FieldLabelElement title="From:" /> + <div className="space-x-3 flex"> + <div> + <Dropdown + options={DropdownUtil.getDropdownOptionsFromEnum(DayOfWeek)} + value={DropdownUtil.getDropdownOptionFromEnumForValue( + DayOfWeek, + params.weeklyRestriction.startDay, + )} + onChange={(value: any) => { + params.weeklyRestriction.startDay = value; + params.onChange(params.weeklyRestriction); + }} + /> + </div> + <div> + <Input + type={InputType.TIME} + value={OneUptimeDate.toString( + params.weeklyRestriction?.startTime, + )} + onChange={(value: any) => { + let date: Date = OneUptimeDate.getCurrentDate(); + + if (value instanceof Date) { + date = value; + } + + if (typeof value === Typeof.String) { + date = OneUptimeDate.fromString(value); + } + + params.weeklyRestriction.startTime = date; + + params.onChange(params.weeklyRestriction); + }} + /> + </div> + </div> + </div> + <div className="ml-5"> + <FieldLabelElement title="To:" /> + <div className="space-x-3 flex"> + <div> + <Dropdown + options={DropdownUtil.getDropdownOptionsFromEnum(DayOfWeek)} + value={DropdownUtil.getDropdownOptionFromEnumForValue( + DayOfWeek, + params.weeklyRestriction.endDay, + )} + onChange={(value: any) => { + params.weeklyRestriction.endDay = value; + params.onChange(params.weeklyRestriction); + }} + /> + </div> + <div> + <Input + type={InputType.TIME} + value={OneUptimeDate.toString( + params.weeklyRestriction?.endTime, + )} + onChange={(value: any) => { + let date: Date = OneUptimeDate.getCurrentDate(); + + if (value instanceof Date) { + date = value; + } + + if (typeof value === Typeof.String) { + date = OneUptimeDate.fromString(value); + } + + params.weeklyRestriction.endTime = date; + + params.onChange(params.weeklyRestriction); + }} + /> + </div> + </div> + </div> + <div className="mt-8"> + {/* Dellete Button */} + <Button + title="Delete" + buttonStyle={ButtonStyleType.NORMAL} + icon={IconProp.Trash} + onClick={() => { + params.onDelete(); + }} + /> + </div> + </div> + ); + }; + + type UpdateRestrictionTimesFunction = ( + restrictionTimes: RestrictionTimes, + ) => void; + + const updateRestrictionTimes: UpdateRestrictionTimesFunction = ( + restrictionTimes: RestrictionTimes, + ): void => { + setRestrictionTimes(RestrictionTimes.fromJSON(restrictionTimes.toJSON())); + if (props.onChange) { + props.onChange(restrictionTimes); + } + }; + + return ( + <div> + <BasicRadioButtons + onChange={(value: string) => { + let tempRestrictionTimes: RestrictionTimes | undefined = + restrictionTimes; + + if (!tempRestrictionTimes) { + tempRestrictionTimes = new RestrictionTimes(); + } + + if (value === RestrictionType.None) { + // remove all restrictions + tempRestrictionTimes.removeAllRestrictions(); + updateRestrictionTimes(tempRestrictionTimes); + } else if (value === RestrictionType.Daily) { + // remove all restrictions + tempRestrictionTimes.removeAllRestrictions(); + // add daily restriction + tempRestrictionTimes.addDefaultDailyRestriction(); + updateRestrictionTimes(tempRestrictionTimes); + } else if (value === RestrictionType.Weekly) { + // remove all restrictions + tempRestrictionTimes.removeAllRestrictions(); + // add weekly restriction + tempRestrictionTimes.addDefaultWeeklyRestriction(); + updateRestrictionTimes(tempRestrictionTimes); + } + }} + initialValue={restrictionTimes?.restictionType} + options={[ + { + title: "No Restrictions", + value: RestrictionType.None, + }, + { + title: "Specific Times of the Day", + value: RestrictionType.Daily, + children: getDailyRestriction(), + }, + { + title: "Specific Times of the Week", + value: RestrictionType.Weekly, + children: getWeeklyTimeRestrictions(), + }, + ]} + /> + + {props.error && ( + <p data-testid="error-message" className="mt-1 text-sm text-red-400"> + {props.error} + </p> + )} + </div> + ); }; export default RestrictionTimesFieldElement; diff --git a/Dashboard/src/Components/Probe/ProbeStatus.tsx b/Dashboard/src/Components/Probe/ProbeStatus.tsx index 3f6b290016..7b04c48889 100644 --- a/Dashboard/src/Components/Probe/ProbeStatus.tsx +++ b/Dashboard/src/Components/Probe/ProbeStatus.tsx @@ -1,37 +1,33 @@ -import { Green, Red } from 'Common/Types/BrandColors'; -import OneUptimeDate from 'Common/Types/Date'; -import { JSONObject } from 'Common/Types/JSON'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import Probe from 'Model/Models/Probe'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { Green, Red } from "Common/Types/BrandColors"; +import OneUptimeDate from "Common/Types/Date"; +import { JSONObject } from "Common/Types/JSON"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import Probe from "Model/Models/Probe"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - probe: Probe | JSONObject; + probe: Probe | JSONObject; } const ProbeStatusElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if ( - props.probe && - props.probe['lastAlive'] && - OneUptimeDate.getNumberOfMinutesBetweenDates( - OneUptimeDate.fromString(props.probe['lastAlive'] as string), - OneUptimeDate.getCurrentDate() - ) < 5 - ) { - return ( - <Statusbubble - text={'Connected'} - color={Green} - shouldAnimate={true} - /> - ); - } - + if ( + props.probe && + props.probe["lastAlive"] && + OneUptimeDate.getNumberOfMinutesBetweenDates( + OneUptimeDate.fromString(props.probe["lastAlive"] as string), + OneUptimeDate.getCurrentDate(), + ) < 5 + ) { return ( - <Statusbubble text={'Disconnected'} color={Red} shouldAnimate={false} /> + <Statusbubble text={"Connected"} color={Green} shouldAnimate={true} /> ); + } + + return ( + <Statusbubble text={"Disconnected"} color={Red} shouldAnimate={false} /> + ); }; export default ProbeStatusElement; diff --git a/Dashboard/src/Components/Project/Project.tsx b/Dashboard/src/Components/Project/Project.tsx index 223ee8fd6f..079df9e803 100644 --- a/Dashboard/src/Components/Project/Project.tsx +++ b/Dashboard/src/Components/Project/Project.tsx @@ -1,32 +1,30 @@ -import Route from 'Common/Types/API/Route'; -import Link from 'CommonUI/src/Components/Link/Link'; -import Project from 'Model/Models/Project'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Route from "Common/Types/API/Route"; +import Link from "CommonUI/src/Components/Link/Link"; +import Project from "Model/Models/Project"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - project: Project; - onNavigateComplete?: (() => void) | undefined; + project: Project; + onNavigateComplete?: (() => void) | undefined; } const ProjectElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (props.project && props.project._id) { - const _id: string = props.project._id - ? props.project._id.toString() - : ''; - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={new Route(`/dashboard/${_id}`)} - > - <span>{props.project.name}</span> - </Link> - ); - } + if (props.project && props.project._id) { + const _id: string = props.project._id ? props.project._id.toString() : ""; + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={new Route(`/dashboard/${_id}`)} + > + <span>{props.project.name}</span> + </Link> + ); + } - return <span>{props.project.name}</span>; + return <span>{props.project.name}</span>; }; export default ProjectElement; diff --git a/Dashboard/src/Components/ProjectCallSMSConfig/ProjectCallSMSConfig.tsx b/Dashboard/src/Components/ProjectCallSMSConfig/ProjectCallSMSConfig.tsx index b61fd1fa8b..766fb417c5 100644 --- a/Dashboard/src/Components/ProjectCallSMSConfig/ProjectCallSMSConfig.tsx +++ b/Dashboard/src/Components/ProjectCallSMSConfig/ProjectCallSMSConfig.tsx @@ -1,14 +1,14 @@ -import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - callSmsConfig: ProjectCallSMSConfig; + callSmsConfig: ProjectCallSMSConfig; } const ProjectCallSMSConfigElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return <span>{props.callSmsConfig.name}</span>; + return <span>{props.callSmsConfig.name}</span>; }; export default ProjectCallSMSConfigElement; diff --git a/Dashboard/src/Components/ProjectSMTPConfig/ProjectSMTPConfig.tsx b/Dashboard/src/Components/ProjectSMTPConfig/ProjectSMTPConfig.tsx index b460002879..bf7cd4d5ef 100644 --- a/Dashboard/src/Components/ProjectSMTPConfig/ProjectSMTPConfig.tsx +++ b/Dashboard/src/Components/ProjectSMTPConfig/ProjectSMTPConfig.tsx @@ -1,14 +1,14 @@ -import ProjectSmtpConfig from 'Model/Models/ProjectSmtpConfig'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ProjectSmtpConfig from "Model/Models/ProjectSmtpConfig"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - smtpConfig: ProjectSmtpConfig; + smtpConfig: ProjectSmtpConfig; } const ProjectSMTPConfig: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return <span>{props.smtpConfig.name}</span>; + return <span>{props.smtpConfig.name}</span>; }; export default ProjectSMTPConfig; diff --git a/Dashboard/src/Components/ScheduledMaintenance/ChangeState.tsx b/Dashboard/src/Components/ScheduledMaintenance/ChangeState.tsx index ee4800161d..489ffa8f25 100644 --- a/Dashboard/src/Components/ScheduledMaintenance/ChangeState.tsx +++ b/Dashboard/src/Components/ScheduledMaintenance/ChangeState.tsx @@ -1,232 +1,225 @@ -import UserElement from '../User/User'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; +import UserElement from "../User/User"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; import Button, { - ButtonSize, - ButtonStyleType, -} from 'CommonUI/src/Components/Button/Button'; -import { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelFormModal from 'CommonUI/src/Components/ModelFormModal/ModelFormModal'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; + ButtonSize, + ButtonStyleType, +} from "CommonUI/src/Components/Button/Button"; +import { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelFormModal from "CommonUI/src/Components/ModelFormModal/ModelFormModal"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export enum StateType { - Ongoing, - Completed, + Ongoing, + Completed, } export interface ComponentProps { - scheduledMaintenanceId: ObjectID; - scheduledMaintenanceTimeline: Array<ScheduledMaintenanceStateTimeline>; - stateType: StateType; - onActionComplete: () => void; + scheduledMaintenanceId: ObjectID; + scheduledMaintenanceTimeline: Array<ScheduledMaintenanceStateTimeline>; + stateType: StateType; + onActionComplete: () => void; } const ChangeScheduledMaintenanceState: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [scheduledMaintenanceTimeline, setScheduledMaintenanceTimeline] = - useState<ScheduledMaintenanceStateTimeline | undefined>(undefined); + const [scheduledMaintenanceTimeline, setScheduledMaintenanceTimeline] = + useState<ScheduledMaintenanceStateTimeline | undefined>(undefined); - const [showModal, setShowModal] = useState<boolean>(false); + const [showModal, setShowModal] = useState<boolean>(false); - useEffect(() => { - for (const event of props.scheduledMaintenanceTimeline) { - if ( - event.scheduledMaintenanceState && - (event.scheduledMaintenanceState.isOngoingState || - event.scheduledMaintenanceState.isResolvedState) && - props.stateType === StateType.Ongoing && - event.id - ) { - setScheduledMaintenanceTimeline(event); - } + useEffect(() => { + for (const event of props.scheduledMaintenanceTimeline) { + if ( + event.scheduledMaintenanceState && + (event.scheduledMaintenanceState.isOngoingState || + event.scheduledMaintenanceState.isResolvedState) && + props.stateType === StateType.Ongoing && + event.id + ) { + setScheduledMaintenanceTimeline(event); + } - if ( - event.scheduledMaintenanceState && - event.scheduledMaintenanceState.isResolvedState && - props.stateType === StateType.Completed && - event.id - ) { - setScheduledMaintenanceTimeline(event); - } - } - }, [props.scheduledMaintenanceTimeline]); - - if ( - scheduledMaintenanceTimeline && - scheduledMaintenanceTimeline.createdAt - ) { - return ( - <div> - <UserElement - user={scheduledMaintenanceTimeline.createdByUser} - prefix="Changed by" - /> - {OneUptimeDate.getDateAsLocalFormattedString( - scheduledMaintenanceTimeline.createdAt - )} - </div> - ); + if ( + event.scheduledMaintenanceState && + event.scheduledMaintenanceState.isResolvedState && + props.stateType === StateType.Completed && + event.id + ) { + setScheduledMaintenanceTimeline(event); + } } + }, [props.scheduledMaintenanceTimeline]); + if (scheduledMaintenanceTimeline && scheduledMaintenanceTimeline.createdAt) { return ( - <div className="-ml-3 mt-2"> - <Button - buttonSize={ButtonSize.Small} - title={ - props.stateType === StateType.Ongoing - ? 'Mark as Ongoing' - : 'Mark as Complete' - } - icon={ - props.stateType === StateType.Ongoing - ? IconProp.Circle - : IconProp.CheckCircle - } - buttonStyle={ - props.stateType === StateType.Ongoing - ? ButtonStyleType.WARNING_OUTLINE - : ButtonStyleType.SUCCESS_OUTLINE - } - onClick={async () => { - setShowModal(true); - }} - /> - - {showModal && ( - <ModelFormModal - modelType={ScheduledMaintenanceStateTimeline} - name={ - props.stateType === StateType.Ongoing - ? 'Mark as Ongoing' - : 'Mark as Complete' - } - title={ - props.stateType === StateType.Ongoing - ? 'Mark as Ongoing' - : 'Mark as Complete' - } - description={ - props.stateType === StateType.Ongoing - ? 'Mark this scheduled maintenance as ongoing.' - : 'Mark this scheduled maintenance as complete.' - } - onClose={() => { - setShowModal(false); - }} - submitButtonText="Save" - onBeforeCreate={async ( - model: ScheduledMaintenanceStateTimeline - ) => { - const projectId: ObjectID | undefined | null = - ProjectUtil.getCurrentProject()?.id; - - if (!projectId) { - throw new BadDataException('ProjectId not found.'); - } - - const scheduledMaintenanceStates: ListResult<ScheduledMaintenanceState> = - await ModelAPI.getList<ScheduledMaintenanceState>({ - modelType: ScheduledMaintenanceState, - query: { - projectId: projectId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - isResolvedState: true, - isOngoingState: true, - isScheduledState: true, - }, - sort: {}, - }); - - let stateId: ObjectID | null = null; - - for (const state of scheduledMaintenanceStates.data) { - if ( - props.stateType === StateType.Ongoing && - state.isOngoingState - ) { - stateId = state.id; - break; - } - - if ( - props.stateType === StateType.Completed && - state.isResolvedState - ) { - stateId = state.id; - break; - } - } - - if (!stateId) { - throw new BadDataException( - 'Scheduled Maintenance State not found.' - ); - } - - model.projectId = projectId; - model.scheduledMaintenanceId = - props.scheduledMaintenanceId; - model.scheduledMaintenanceStateId = stateId; - - return model; - }} - onSuccess={() => { - setShowModal(false); - props.onActionComplete(); - }} - formProps={{ - name: 'create-scheduled-maintenance-state-timeline', - modelType: ScheduledMaintenanceStateTimeline, - id: 'create-scheduled-maintenance-state-timeline', - fields: [ - { - field: { - publicNote: true, - } as any, - fieldType: FormFieldSchemaType.Markdown, - description: - 'Post a public note about this state change to the status page.', - title: 'Public Note', - required: false, - overrideFieldKey: 'publicNote', - showEvenIfPermissionDoesNotExist: true, - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, - fieldType: FormFieldSchemaType.Checkbox, - description: - 'Notify subscribers of this state change.', - title: 'Notify Status Page Subscribers', - required: false, - defaultValue: true, - }, - ], - formType: FormType.Create, - }} - /> - )} - </div> + <div> + <UserElement + user={scheduledMaintenanceTimeline.createdByUser} + prefix="Changed by" + /> + {OneUptimeDate.getDateAsLocalFormattedString( + scheduledMaintenanceTimeline.createdAt, + )} + </div> ); + } + + return ( + <div className="-ml-3 mt-2"> + <Button + buttonSize={ButtonSize.Small} + title={ + props.stateType === StateType.Ongoing + ? "Mark as Ongoing" + : "Mark as Complete" + } + icon={ + props.stateType === StateType.Ongoing + ? IconProp.Circle + : IconProp.CheckCircle + } + buttonStyle={ + props.stateType === StateType.Ongoing + ? ButtonStyleType.WARNING_OUTLINE + : ButtonStyleType.SUCCESS_OUTLINE + } + onClick={async () => { + setShowModal(true); + }} + /> + + {showModal && ( + <ModelFormModal + modelType={ScheduledMaintenanceStateTimeline} + name={ + props.stateType === StateType.Ongoing + ? "Mark as Ongoing" + : "Mark as Complete" + } + title={ + props.stateType === StateType.Ongoing + ? "Mark as Ongoing" + : "Mark as Complete" + } + description={ + props.stateType === StateType.Ongoing + ? "Mark this scheduled maintenance as ongoing." + : "Mark this scheduled maintenance as complete." + } + onClose={() => { + setShowModal(false); + }} + submitButtonText="Save" + onBeforeCreate={async (model: ScheduledMaintenanceStateTimeline) => { + const projectId: ObjectID | undefined | null = + ProjectUtil.getCurrentProject()?.id; + + if (!projectId) { + throw new BadDataException("ProjectId not found."); + } + + const scheduledMaintenanceStates: ListResult<ScheduledMaintenanceState> = + await ModelAPI.getList<ScheduledMaintenanceState>({ + modelType: ScheduledMaintenanceState, + query: { + projectId: projectId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + isResolvedState: true, + isOngoingState: true, + isScheduledState: true, + }, + sort: {}, + }); + + let stateId: ObjectID | null = null; + + for (const state of scheduledMaintenanceStates.data) { + if ( + props.stateType === StateType.Ongoing && + state.isOngoingState + ) { + stateId = state.id; + break; + } + + if ( + props.stateType === StateType.Completed && + state.isResolvedState + ) { + stateId = state.id; + break; + } + } + + if (!stateId) { + throw new BadDataException( + "Scheduled Maintenance State not found.", + ); + } + + model.projectId = projectId; + model.scheduledMaintenanceId = props.scheduledMaintenanceId; + model.scheduledMaintenanceStateId = stateId; + + return model; + }} + onSuccess={() => { + setShowModal(false); + props.onActionComplete(); + }} + formProps={{ + name: "create-scheduled-maintenance-state-timeline", + modelType: ScheduledMaintenanceStateTimeline, + id: "create-scheduled-maintenance-state-timeline", + fields: [ + { + field: { + publicNote: true, + } as any, + fieldType: FormFieldSchemaType.Markdown, + description: + "Post a public note about this state change to the status page.", + title: "Public Note", + required: false, + overrideFieldKey: "publicNote", + showEvenIfPermissionDoesNotExist: true, + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, + fieldType: FormFieldSchemaType.Checkbox, + description: "Notify subscribers of this state change.", + title: "Notify Status Page Subscribers", + required: false, + defaultValue: true, + }, + ], + formType: FormType.Create, + }} + /> + )} + </div> + ); }; export default ChangeScheduledMaintenanceState; diff --git a/Dashboard/src/Components/ScheduledMaintenance/ScheduledMaintenanceTable.tsx b/Dashboard/src/Components/ScheduledMaintenance/ScheduledMaintenanceTable.tsx index ea4580b7f0..7ecc0c2b53 100644 --- a/Dashboard/src/Components/ScheduledMaintenance/ScheduledMaintenanceTable.tsx +++ b/Dashboard/src/Components/ScheduledMaintenance/ScheduledMaintenanceTable.tsx @@ -1,489 +1,471 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import ProjectUser from '../../Utils/ProjectUser'; -import LabelsElement from '../Label/Labels'; -import MonitorsElement from '../Monitor/Monitors'; -import StatusPagesElement from '../StatusPage/StatusPagesLabel'; -import Route from 'Common/Types/API/Route'; -import { Black } from 'Common/Types/BrandColors'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Query from 'CommonUI/src/Utils/BaseDatabase/Query'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import StatusPage from 'Model/Models/StatusPage'; -import Team from 'Model/Models/Team'; -import React, { FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import ProjectUser from "../../Utils/ProjectUser"; +import LabelsElement from "../Label/Labels"; +import MonitorsElement from "../Monitor/Monitors"; +import StatusPagesElement from "../StatusPage/StatusPagesLabel"; +import Route from "Common/Types/API/Route"; +import { Black } from "Common/Types/BrandColors"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Query from "CommonUI/src/Utils/BaseDatabase/Query"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import StatusPage from "Model/Models/StatusPage"; +import Team from "Model/Models/Team"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - query?: Query<ScheduledMaintenance> | undefined; - viewPageRoute?: Route; - noItemsMessage?: string | undefined; - title?: string | undefined; - description?: string | undefined; + query?: Query<ScheduledMaintenance> | undefined; + viewPageRoute?: Route; + noItemsMessage?: string | undefined; + title?: string | undefined; + description?: string | undefined; } const ScheduledMaintenancesTable: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <ModelTable<ScheduledMaintenance> - modelType={ScheduledMaintenance} - id="scheduledMaintenances-table" - name="Scheduled Maintenance Events" - isDeleteable={false} - query={props.query || {}} - isEditable={false} - isCreateable={true} - isViewable={true} - cardProps={{ - title: props.title || 'Scheduled Maintenance Events', - description: - props.description || - 'Here is a list of scheduled maintenance events for this project.', - }} - noItemsMessage={ - props.noItemsMessage || 'No scheduled Maintenance Event found.' + return ( + <ModelTable<ScheduledMaintenance> + modelType={ScheduledMaintenance} + id="scheduledMaintenances-table" + name="Scheduled Maintenance Events" + isDeleteable={false} + query={props.query || {}} + isEditable={false} + isCreateable={true} + isViewable={true} + cardProps={{ + title: props.title || "Scheduled Maintenance Events", + description: + props.description || + "Here is a list of scheduled maintenance events for this project.", + }} + noItemsMessage={ + props.noItemsMessage || "No scheduled Maintenance Event found." + } + formSteps={[ + { + title: "Event Info", + id: "event-info", + }, + { + title: "Event Time", + id: "event-time", + }, + { + title: "Resources Affected", + id: "resources-affected", + }, + { + title: "Status Pages", + id: "status-pages", + }, + { + title: "Owners", + id: "owners", + }, + { + title: "More", + id: "more", + }, + ]} + formFields={[ + { + field: { + title: true, + }, + title: "Title", + stepId: "event-info", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Event Title", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + stepId: "event-info", + fieldType: FormFieldSchemaType.Markdown, + required: true, + }, + { + field: { + startsAt: true, + }, + title: "Event Starts At", + stepId: "event-time", + fieldType: FormFieldSchemaType.DateTime, + required: true, + placeholder: "Pick Date and Time", + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + stepId: "event-time", + fieldType: FormFieldSchemaType.DateTime, + required: true, + placeholder: "Pick Date and Time", + }, + { + field: { + monitors: true, + }, + title: "Monitors affected ", + stepId: "resources-affected", + description: + "Select monitors affected by this scheduled maintenance.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Monitor, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Monitors affected", + }, + { + field: { + changeMonitorStatusTo: true, + }, + title: "Change Monitor Status to ", + stepId: "resources-affected", + description: + "This will change the status of all the monitors attached when the event starts.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: MonitorStatus, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Monitor Status", + }, + { + field: { + statusPages: true, + }, + title: "Show event on these status pages ", + stepId: "status-pages", + description: "Select status pages to show this event on", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: StatusPage, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select Status Pages", + }, + { + overrideField: { + ownerTeams: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Owner - Teams", + stepId: "owners", + description: + "Select which teams own this event. They will be notified when event status changes.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select Teams", + overrideFieldKey: "ownerTeams", + }, + { + overrideField: { + ownerUsers: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Owner - Users", + stepId: "owners", + description: + "Select which users own this event. They will be notified when event status changes.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + required: false, + placeholder: "Select Users", + overrideFieldKey: "ownerUsers", + }, + { + field: { + labels: true, + }, + title: "Labels ", + stepId: "more", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedOnEventCreated: true, + }, + + title: "Event Created: Notify Status Page Subscribers", + stepId: "more", + description: + "Should status page subscribers be notified when this event is created?", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing: + true, + }, + + title: "Event Ongoing: Notify Status Page Subscribers", + stepId: "more", + description: + "Should status page subscribers be notified when this event state changes to ongoing?", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded: true, + }, + + title: "Event Ended: Notify Status Page Subscribers", + stepId: "more", + description: + "Should status page subscribers be notified when this event state changes to ended?", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + }, + ]} + showViewIdButton={true} + viewButtonText="View Event" + showRefreshButton={true} + viewPageRoute={props.viewPageRoute} + filters={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + currentScheduledMaintenanceState: { + name: true, + }, + }, + title: "Current State", + type: FieldType.Entity, + filterEntityType: ScheduledMaintenanceState, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + monitors: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Monitors Affected", + type: FieldType.EntityArray, + filterEntityType: Monitor, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + statusPages: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Shown on Status Page", + type: FieldType.EntityArray, + filterEntityType: StatusPage, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + type: FieldType.Date, + }, + { + field: { + startsAt: true, + }, + title: "Starts At", + type: FieldType.Date, + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + type: FieldType.Date, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + currentScheduledMaintenanceState: { + name: true, + color: true, + }, + }, + title: "Current State", + type: FieldType.Entity, + + getElement: (item: ScheduledMaintenance): ReactElement => { + if (item["currentScheduledMaintenanceState"]) { + return ( + <Pill + color={item.currentScheduledMaintenanceState.color || Black} + text={item.currentScheduledMaintenanceState.name || "Unknown"} + /> + ); } - formSteps={[ - { - title: 'Event Info', - id: 'event-info', - }, - { - title: 'Event Time', - id: 'event-time', - }, - { - title: 'Resources Affected', - id: 'resources-affected', - }, - { - title: 'Status Pages', - id: 'status-pages', - }, - { - title: 'Owners', - id: 'owners', - }, - { - title: 'More', - id: 'more', - }, - ]} - formFields={[ - { - field: { - title: true, - }, - title: 'Title', - stepId: 'event-info', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Event Title', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - stepId: 'event-info', - fieldType: FormFieldSchemaType.Markdown, - required: true, - }, - { - field: { - startsAt: true, - }, - title: 'Event Starts At', - stepId: 'event-time', - fieldType: FormFieldSchemaType.DateTime, - required: true, - placeholder: 'Pick Date and Time', - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - stepId: 'event-time', - fieldType: FormFieldSchemaType.DateTime, - required: true, - placeholder: 'Pick Date and Time', - }, - { - field: { - monitors: true, - }, - title: 'Monitors affected ', - stepId: 'resources-affected', - description: - 'Select monitors affected by this scheduled maintenance.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Monitor, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Monitors affected', - }, - { - field: { - changeMonitorStatusTo: true, - }, - title: 'Change Monitor Status to ', - stepId: 'resources-affected', - description: - 'This will change the status of all the monitors attached when the event starts.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: MonitorStatus, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Monitor Status', - }, - { - field: { - statusPages: true, - }, - title: 'Show event on these status pages ', - stepId: 'status-pages', - description: 'Select status pages to show this event on', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: StatusPage, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select Status Pages', - }, - { - overrideField: { - ownerTeams: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Owner - Teams', - stepId: 'owners', - description: - 'Select which teams own this event. They will be notified when event status changes.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select Teams', - overrideFieldKey: 'ownerTeams', - }, - { - overrideField: { - ownerUsers: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Owner - Users', - stepId: 'owners', - description: - 'Select which users own this event. They will be notified when event status changes.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - required: false, - placeholder: 'Select Users', - overrideFieldKey: 'ownerUsers', - }, - { - field: { - labels: true, - }, - title: 'Labels ', - stepId: 'more', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedOnEventCreated: - true, - }, - title: 'Event Created: Notify Status Page Subscribers', - stepId: 'more', - description: - 'Should status page subscribers be notified when this event is created?', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing: - true, - }, + return <></>; + }, + }, - title: 'Event Ongoing: Notify Status Page Subscribers', - stepId: 'more', - description: - 'Should status page subscribers be notified when this event state changes to ongoing?', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded: - true, - }, + { + field: { + monitors: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Monitors Affected", + type: FieldType.EntityArray, - title: 'Event Ended: Notify Status Page Subscribers', - stepId: 'more', - description: - 'Should status page subscribers be notified when this event state changes to ended?', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - }, - ]} - showViewIdButton={true} - viewButtonText="View Event" - showRefreshButton={true} - viewPageRoute={props.viewPageRoute} - filters={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - currentScheduledMaintenanceState: { - name: true, - }, - }, - title: 'Current State', - type: FieldType.Entity, - filterEntityType: ScheduledMaintenanceState, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - monitors: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Monitors Affected', - type: FieldType.EntityArray, - filterEntityType: Monitor, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - statusPages: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Shown on Status Page', - type: FieldType.EntityArray, - filterEntityType: StatusPage, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - type: FieldType.Date, - }, - { - field: { - startsAt: true, - }, - title: 'Starts At', - type: FieldType.Date, - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - type: FieldType.Date, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - currentScheduledMaintenanceState: { - name: true, - color: true, - }, - }, - title: 'Current State', - type: FieldType.Entity, + getElement: (item: ScheduledMaintenance): ReactElement => { + return <MonitorsElement monitors={item["monitors"] || []} />; + }, + }, + { + field: { + statusPages: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Shown on Status Page", + type: FieldType.EntityArray, - getElement: (item: ScheduledMaintenance): ReactElement => { - if (item['currentScheduledMaintenanceState']) { - return ( - <Pill - color={ - item.currentScheduledMaintenanceState - .color || Black - } - text={ - item.currentScheduledMaintenanceState - .name || 'Unknown' - } - /> - ); - } + getElement: (item: ScheduledMaintenance): ReactElement => { + return ( + <StatusPagesElement statusPages={item["statusPages"] || []} /> + ); + }, + }, + { + field: { + startsAt: true, + }, + title: "Starts At", + type: FieldType.DateTime, + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + type: FieldType.DateTime, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, - return <></>; - }, - }, - - { - field: { - monitors: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Monitors Affected', - type: FieldType.EntityArray, - - getElement: (item: ScheduledMaintenance): ReactElement => { - return ( - <MonitorsElement - monitors={item['monitors'] || []} - /> - ); - }, - }, - { - field: { - statusPages: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Shown on Status Page', - type: FieldType.EntityArray, - - getElement: (item: ScheduledMaintenance): ReactElement => { - return ( - <StatusPagesElement - statusPages={item['statusPages'] || []} - /> - ); - }, - }, - { - field: { - startsAt: true, - }, - title: 'Starts At', - type: FieldType.DateTime, - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - type: FieldType.DateTime, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - - getElement: (item: ScheduledMaintenance): ReactElement => { - return <LabelsElement labels={item['labels'] || []} />; - }, - }, - ]} - /> - ); + getElement: (item: ScheduledMaintenance): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + ); }; export default ScheduledMaintenancesTable; diff --git a/Dashboard/src/Components/ServiceCatalog/ServiceElement.tsx b/Dashboard/src/Components/ServiceCatalog/ServiceElement.tsx index dcb69538c1..d7d94c706e 100644 --- a/Dashboard/src/Components/ServiceCatalog/ServiceElement.tsx +++ b/Dashboard/src/Components/ServiceCatalog/ServiceElement.tsx @@ -1,59 +1,57 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import { Black } from 'Common/Types/BrandColors'; -import ObjectID from 'Common/Types/ObjectID'; -import ColorSquareCube from 'CommonUI/src/Components/ColorSquareCube/ColorSquareCube'; -import Link from 'CommonUI/src/Components/Link/Link'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import ServiceCatalog from 'Model/Models/ServiceCatalog'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import { Black } from "Common/Types/BrandColors"; +import ObjectID from "Common/Types/ObjectID"; +import ColorSquareCube from "CommonUI/src/Components/ColorSquareCube/ColorSquareCube"; +import Link from "CommonUI/src/Components/Link/Link"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import ServiceCatalog from "Model/Models/ServiceCatalog"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - serviceCatalog: ServiceCatalog; - onNavigateComplete?: (() => void) | undefined; - serviceCatalogNameClassName?: string; + serviceCatalog: ServiceCatalog; + onNavigateComplete?: (() => void) | undefined; + serviceCatalogNameClassName?: string; } const ServiceCatalogElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const getServiceElement: GetReactElementFunction = (): ReactElement => { - return ( - <div className="flex space-x-2"> - <div className="mt-1"> - <ColorSquareCube - color={props.serviceCatalog.serviceColor || Black} - tooltip={`${props.serviceCatalog.name?.toString()} Service Color`} - /> - </div> - <div className={props.serviceCatalogNameClassName}> - {props.serviceCatalog.name?.toString()} - </div> - </div> - ); - }; + const getServiceElement: GetReactElementFunction = (): ReactElement => { + return ( + <div className="flex space-x-2"> + <div className="mt-1"> + <ColorSquareCube + color={props.serviceCatalog.serviceColor || Black} + tooltip={`${props.serviceCatalog.name?.toString()} Service Color`} + /> + </div> + <div className={props.serviceCatalogNameClassName}> + {props.serviceCatalog.name?.toString()} + </div> + </div> + ); + }; - if (props.serviceCatalog._id) { - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={RouteUtil.populateRouteParams( - RouteMap[PageMap.SERVICE_CATALOG_VIEW] as Route, - { - modelId: new ObjectID( - props.serviceCatalog._id as string - ), - } - )} - > - {getServiceElement()} - </Link> - ); - } + if (props.serviceCatalog._id) { + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={RouteUtil.populateRouteParams( + RouteMap[PageMap.SERVICE_CATALOG_VIEW] as Route, + { + modelId: new ObjectID(props.serviceCatalog._id as string), + }, + )} + > + {getServiceElement()} + </Link> + ); + } - return <div>{getServiceElement()}</div>; + return <div>{getServiceElement()}</div>; }; export default ServiceCatalogElement; diff --git a/Dashboard/src/Components/Span/SpanStatusElement.tsx b/Dashboard/src/Components/Span/SpanStatusElement.tsx index 484494aaec..1923e36e33 100644 --- a/Dashboard/src/Components/Span/SpanStatusElement.tsx +++ b/Dashboard/src/Components/Span/SpanStatusElement.tsx @@ -1,44 +1,41 @@ -import { Green, Red } from 'Common/Types/BrandColors'; -import ColorCircle from 'CommonUI/src/Components/ColorCircle/ColorCircle'; -import Span, { SpanStatus } from 'Model/AnalyticsModels/Span'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { Green, Red } from "Common/Types/BrandColors"; +import ColorCircle from "CommonUI/src/Components/ColorCircle/ColorCircle"; +import Span, { SpanStatus } from "Model/AnalyticsModels/Span"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - span: Span; - title?: string | undefined; - titleClassName?: string | undefined; + span: Span; + title?: string | undefined; + titleClassName?: string | undefined; } const SpanStatusElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const { span } = props; + const { span } = props; - return ( - <div className="flex space-x-2"> - <div className="mt-1"> - {span && - (span.statusCode === SpanStatus.Unset || !span.statusCode) ? ( - <ColorCircle color={Green} tooltip="Span Status: Unset" /> - ) : ( - <></> - )} - {span && span.statusCode === SpanStatus.Ok ? ( - <ColorCircle color={Green} tooltip="Span Status: Ok" /> - ) : ( - <></> - )} - {span && span.statusCode === SpanStatus.Error ? ( - <ColorCircle color={Red} tooltip="Span Status: Error" /> - ) : ( - <></> - )} - </div> - {props.title && ( - <div className={props.titleClassName}>{props.title}</div> - )} - </div> - ); + return ( + <div className="flex space-x-2"> + <div className="mt-1"> + {span && (span.statusCode === SpanStatus.Unset || !span.statusCode) ? ( + <ColorCircle color={Green} tooltip="Span Status: Unset" /> + ) : ( + <></> + )} + {span && span.statusCode === SpanStatus.Ok ? ( + <ColorCircle color={Green} tooltip="Span Status: Ok" /> + ) : ( + <></> + )} + {span && span.statusCode === SpanStatus.Error ? ( + <ColorCircle color={Red} tooltip="Span Status: Error" /> + ) : ( + <></> + )} + </div> + {props.title && <div className={props.titleClassName}>{props.title}</div>} + </div> + ); }; export default SpanStatusElement; diff --git a/Dashboard/src/Components/Span/SpanViewer.tsx b/Dashboard/src/Components/Span/SpanViewer.tsx index 5f915dff17..4b4fa9f9e2 100644 --- a/Dashboard/src/Components/Span/SpanViewer.tsx +++ b/Dashboard/src/Components/Span/SpanViewer.tsx @@ -1,573 +1,530 @@ -import SpanUtil, { DivisibilityFactor } from '../../Utils/SpanUtil'; -import TelemetryServiceElement from '../TelemetryService/TelemetryServiceElement'; -import SpanStatusElement from './SpanStatusElement'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import CodeType from 'Common/Types/Code/CodeType'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Accordion from 'CommonUI/src/Components/Accordion/Accordion'; -import AccordionGroup from 'CommonUI/src/Components/Accordion/AccordionGroup'; -import CodeEditor from 'CommonUI/src/Components/CodeEditor/CodeEditor'; -import Detail from 'CommonUI/src/Components/Detail/Detail'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LogsViewer from 'CommonUI/src/Components/LogsViewer/LogsViewer'; -import { TabType } from 'CommonUI/src/Components/Tabs/Tab'; -import Tabs from 'CommonUI/src/Components/Tabs/Tabs'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import API from 'CommonUI/src/Utils/API/API'; +import SpanUtil, { DivisibilityFactor } from "../../Utils/SpanUtil"; +import TelemetryServiceElement from "../TelemetryService/TelemetryServiceElement"; +import SpanStatusElement from "./SpanStatusElement"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import CodeType from "Common/Types/Code/CodeType"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Accordion from "CommonUI/src/Components/Accordion/Accordion"; +import AccordionGroup from "CommonUI/src/Components/Accordion/AccordionGroup"; +import CodeEditor from "CommonUI/src/Components/CodeEditor/CodeEditor"; +import Detail from "CommonUI/src/Components/Detail/Detail"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LogsViewer from "CommonUI/src/Components/LogsViewer/LogsViewer"; +import { TabType } from "CommonUI/src/Components/Tabs/Tab"; +import Tabs from "CommonUI/src/Components/Tabs/Tabs"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import API from "CommonUI/src/Utils/API/API"; import AnalyticsModelAPI, { - ListResult, -} from 'CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI'; -import Select from 'CommonUI/src/Utils/BaseDatabase/Select'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Log from 'Model/AnalyticsModels/Log'; -import Span, { SpanEvent, SpanEventType } from 'Model/AnalyticsModels/Span'; -import TelemetryService from 'Model/Models/TelemetryService'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; + ListResult, +} from "CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI"; +import Select from "CommonUI/src/Utils/BaseDatabase/Select"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Log from "Model/AnalyticsModels/Log"; +import Span, { SpanEvent, SpanEventType } from "Model/AnalyticsModels/Span"; +import TelemetryService from "Model/Models/TelemetryService"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps { - id: string; - openTelemetrySpanId: string; - traceStartTimeInUnixNano: number; - onClose: () => void; - telemetryService: TelemetryService; - divisibilityFactor: DivisibilityFactor; + id: string; + openTelemetrySpanId: string; + traceStartTimeInUnixNano: number; + onClose: () => void; + telemetryService: TelemetryService; + divisibilityFactor: DivisibilityFactor; } const SpanViewer: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [logs, setLogs] = React.useState<Array<Log>>([]); - const [error, setError] = React.useState<string>(''); - const [isLoading, setIsLoading] = React.useState<boolean>(false); - const [span, setSpan] = React.useState<Span | null>(null); + const [logs, setLogs] = React.useState<Array<Log>>([]); + const [error, setError] = React.useState<string>(""); + const [isLoading, setIsLoading] = React.useState<boolean>(false); + const [span, setSpan] = React.useState<Span | null>(null); - const { telemetryService, onClose } = props; + const { telemetryService, onClose } = props; - const selectLog: Select<Log> = { - body: true, - time: true, - projectId: true, - serviceId: true, - spanId: true, - traceId: true, - severityText: true, - attributes: true, - }; + const selectLog: Select<Log> = { + body: true, + time: true, + projectId: true, + serviceId: true, + spanId: true, + traceId: true, + severityText: true, + attributes: true, + }; - const selectSpan: Select<Span> = { - projectId: true, - serviceId: true, - spanId: true, - traceId: true, - events: true, - startTime: true, - endTime: true, - startTimeUnixNano: true, - endTimeUnixNano: true, - attributes: true, - durationUnixNano: true, - name: true, - }; + const selectSpan: Select<Span> = { + projectId: true, + serviceId: true, + spanId: true, + traceId: true, + events: true, + startTime: true, + endTime: true, + startTimeUnixNano: true, + endTimeUnixNano: true, + attributes: true, + durationUnixNano: true, + name: true, + }; - useEffect(() => { - fetchItems().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); + useEffect(() => { + fetchItems().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, []); + + const fetchItems: PromiseVoidFunction = async (): Promise<void> => { + setError(""); + setIsLoading(true); + + try { + const listResult: ListResult<Log> = await AnalyticsModelAPI.getList<Log>({ + modelType: Log, + query: { + spanId: props.openTelemetrySpanId, + projectId: ProjectUtil.getCurrentProjectId()!, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: selectLog, + sort: { + time: SortOrder.Descending, + }, + requestOptions: {}, + }); + + // reverse the logs so that the newest logs are at the bottom + listResult.data.reverse(); + + setLogs(listResult.data); + + const spanResult: ListResult<Span> = + await AnalyticsModelAPI.getList<Span>({ + modelType: Span, + query: { + spanId: props.openTelemetrySpanId, + projectId: ProjectUtil.getCurrentProjectId()!, + }, + select: selectSpan, + limit: 1, + skip: 0, + sort: {}, + requestOptions: {}, }); - }, []); - const fetchItems: PromiseVoidFunction = async (): Promise<void> => { - setError(''); - setIsLoading(true); - - try { - const listResult: ListResult<Log> = - await AnalyticsModelAPI.getList<Log>({ - modelType: Log, - query: { - spanId: props.openTelemetrySpanId, - projectId: ProjectUtil.getCurrentProjectId()!, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: selectLog, - sort: { - time: SortOrder.Descending, - }, - requestOptions: {}, - }); - - // reverse the logs so that the newest logs are at the bottom - listResult.data.reverse(); - - setLogs(listResult.data); - - const spanResult: ListResult<Span> = - await AnalyticsModelAPI.getList<Span>({ - modelType: Span, - query: { - spanId: props.openTelemetrySpanId, - projectId: ProjectUtil.getCurrentProjectId()!, - }, - select: selectSpan, - limit: 1, - skip: 0, - sort: {}, - requestOptions: {}, - }); - - if (spanResult.data.length > 0) { - setSpan(spanResult.data[0] || null); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - if (error) { - return <ErrorMessage error={error} />; + if (spanResult.data.length > 0) { + setSpan(spanResult.data[0] || null); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (isLoading) { - return <PageLoader isVisible={true} />; - } + setIsLoading(false); + }; - const getLogsContentElement: GetReactElementFunction = (): ReactElement => { - return ( - <LogsViewer - isLoading={isLoading} - onFilterChanged={() => {}} - logs={logs} - showFilters={false} - noLogsMessage={'No logs found for this span.'} - /> - ); - }; + if (error) { + return <ErrorMessage error={error} />; + } - const getAttributesContentElement: GetReactElementFunction = - (): ReactElement => { - if (!span) { - return <ErrorMessage error="Span not found" />; - } + if (isLoading) { + return <PageLoader isVisible={true} />; + } - return ( - <Detail<Span> - item={span} - fields={[ - { - key: 'attributes', - title: 'Span Attributes', - description: 'The attributes of the span.', - fieldType: FieldType.Element, - getElement: (span: Span) => { - return ( - <CodeEditor - type={CodeType.JSON} - initialValue={JSONFunctions.toFormattedString( - JSONFunctions.nestJson( - span.attributes || {} - ) - )} - readOnly={true} - /> - ); - }, - }, - ]} - /> - ); - }; + const getLogsContentElement: GetReactElementFunction = (): ReactElement => { + return ( + <LogsViewer + isLoading={isLoading} + onFilterChanged={() => {}} + logs={logs} + showFilters={false} + noLogsMessage={"No logs found for this span."} + /> + ); + }; - type GetEvebtContentElementFunction = (event: SpanEvent) => ReactElement; + const getAttributesContentElement: GetReactElementFunction = + (): ReactElement => { + if (!span) { + return <ErrorMessage error="Span not found" />; + } - const getEventContentElement: GetEvebtContentElementFunction = ( - event: SpanEvent - ): ReactElement => { - if (!span) { - return <ErrorMessage error="No span found" />; - } - - if (!event) { - return <ErrorMessage error="No event found" />; - } - - return ( - <Detail<SpanEvent> - item={event} - fields={[ - { - key: 'name', - title: 'Event Name', - description: 'The name of the event.', - }, - { - key: 'timeUnixNano', - title: 'Time in Trace', - description: - 'The time the event occurred in the trace.', - fieldType: FieldType.Element, - getElement: (event: SpanEvent) => { - return ( - <div> - {SpanUtil.getSpanEventTimeAsString({ - timelineStartTimeUnixNano: - props.traceStartTimeInUnixNano, - divisibilityFactor: - props.divisibilityFactor, - spanEventTimeUnixNano: - event.timeUnixNano!, - })} - </div> - ); - }, - }, - { - key: 'timeUnixNano', - title: 'Time in Span', - description: 'The time the event occurred in this span', - fieldType: FieldType.Element, - getElement: (event: SpanEvent) => { - return ( - <div> - {SpanUtil.getSpanEventTimeAsString({ - timelineStartTimeUnixNano: - span!.startTimeUnixNano!, - divisibilityFactor: - props.divisibilityFactor, - spanEventTimeUnixNano: - event!.timeUnixNano!, - })} - </div> - ); - }, - }, - { - key: 'time', - title: 'Seen At', - description: 'The time the event occurred.', - fieldType: FieldType.DateTime, - }, - { - key: 'attributes', - title: 'Event Attributes', - description: 'The attributes of the event.', - fieldType: FieldType.Element, - getElement: (event: SpanEvent) => { - return ( - <CodeEditor - type={CodeType.JSON} - initialValue={JSONFunctions.toFormattedString( - JSONFunctions.nestJson( - event.attributes || {} - ) - )} - readOnly={true} - /> - ); - }, - }, - ]} - /> - ); - }; - - const getEventsContentElement: GetReactElementFunction = - (): ReactElement => { - return getEvents(SpanEventType.Event); - }; - - type GetEventsFunction = (eventType: SpanEventType) => ReactElement; - const getEvents: GetEventsFunction = ( - eventType: SpanEventType - ): ReactElement => { - const eventsToShow: SpanEvent[] | undefined = span?.events?.filter( - (event: SpanEvent) => { - if (eventType === SpanEventType.Exception) { - // name of the event is exception - return event.name === SpanEventType.Exception.toLowerCase(); - } - return event.name !== SpanEventType.Exception.toLowerCase(); - } - ); - - if (!eventsToShow || eventsToShow.length === 0) { - if (eventType === SpanEventType.Exception) { + return ( + <Detail<Span> + item={span} + fields={[ + { + key: "attributes", + title: "Span Attributes", + description: "The attributes of the span.", + fieldType: FieldType.Element, + getElement: (span: Span) => { return ( - <ErrorMessage error="No exceptions found for this span." /> + <CodeEditor + type={CodeType.JSON} + initialValue={JSONFunctions.toFormattedString( + JSONFunctions.nestJson(span.attributes || {}), + )} + readOnly={true} + /> ); - } - return <ErrorMessage error="No events found for this span." />; - } - - let bgColorClassName: string = 'bg-indigo-500'; - - if (eventType === SpanEventType.Exception) { - bgColorClassName = 'bg-red-500'; - } - - return ( - <AccordionGroup> - {eventsToShow.map((event: SpanEvent, index: number) => { - return ( - <Accordion - titleClassName="text-sm" - title={ - <div className="flex space-x-2"> - <div className="flex space-x-2"> - <div - className={`rounded-md text-white p-1 text-xs font-semibold ${bgColorClassName}`} - > - {eventType}: {index + 1} - </div> - <div className="flex space-x-1"> - <div className="mt-0.5 font-medium"> - {event.name} - </div> - <div className="text-gray-500 mt-0.5"> - {' '} - at{' '} - {SpanUtil.getSpanEventTimeAsString( - { - timelineStartTimeUnixNano: - props.traceStartTimeInUnixNano, - divisibilityFactor: - props.divisibilityFactor, - spanEventTimeUnixNano: - event.timeUnixNano!, - } - )} - </div> - </div> - </div> - </div> - } - key={index} - > - {getEventContentElement(event)} - </Accordion> - ); - })} - </AccordionGroup> - ); + }, + }, + ]} + /> + ); }; - const getExceptionsContentElement: GetReactElementFunction = - (): ReactElement => { - return getEvents(SpanEventType.Exception); - }; + type GetEvebtContentElementFunction = (event: SpanEvent) => ReactElement; - const getBasicInfo: GetReactElementFunction = (): ReactElement => { - if (!span) { - return <ErrorMessage error="Span not found" />; - } + const getEventContentElement: GetEvebtContentElementFunction = ( + event: SpanEvent, + ): ReactElement => { + if (!span) { + return <ErrorMessage error="No span found" />; + } - return ( - <Detail<Span> - item={span} - fields={[ - { - key: 'spanId', - title: 'Span ID', - description: 'The unique identifier of the span.', - fieldType: FieldType.Text, - opts: { - isCopyable: true, - }, - }, - { - key: 'name', - title: 'Span Name', - description: 'The name of the span.', - fieldType: FieldType.Text, - }, - { - key: 'statusCode', - title: 'Span Status', - description: 'The status of the span.', - fieldType: FieldType.Element, - getElement: (span: Span) => { - return ( - <div> - <SpanStatusElement - span={span} - title={ - 'Status: ' + - SpanUtil.getSpanStatusCodeFriendlyName( - span.statusCode! - ) - } - />{' '} - </div> - ); - }, - }, - { - key: 'traceId', - title: 'Trace ID', - description: 'The unique identifier of the trace.', - fieldType: FieldType.Text, - opts: { - isCopyable: true, - }, - }, - { - key: 'serviceId', - title: 'Telemetry Service', - description: 'The unique identifier of the service.', - fieldType: FieldType.Element, - getElement: () => { - return ( - <TelemetryServiceElement - telemetryService={telemetryService} - onNavigateComplete={() => { - onClose(); - }} - /> - ); - }, - }, - { - key: 'startTime', - title: 'Start Time', - description: 'The time the span started.', - fieldType: FieldType.DateTime, - }, - { - key: 'endTime', - title: 'End Time', - description: 'The time the span ended.', - fieldType: FieldType.DateTime, - }, - { - key: 'startTimeUnixNano', - title: 'Starts At', - description: 'When did this span start in this trace?', - fieldType: FieldType.Element, - getElement: (span: Span) => { - return ( - <div> - {SpanUtil.getSpanStartsAtAsString({ - timelineStartTimeUnixNano: - props.traceStartTimeInUnixNano, - divisibilityFactor: - props.divisibilityFactor, - spanStartTimeUnixNano: - span.startTimeUnixNano!, - })} - </div> - ); - }, - }, - { - key: 'endTimeUnixNano', - title: 'Ends At', - description: 'When did this span end in this trace?', - fieldType: FieldType.Element, - getElement: (span: Span) => { - return ( - <div> - {SpanUtil.getSpanEndsAtAsString({ - timelineStartTimeUnixNano: - props.traceStartTimeInUnixNano, - divisibilityFactor: - props.divisibilityFactor, - spanEndTimeUnixNano: - span.endTimeUnixNano!, - })} - </div> - ); - }, - }, - { - key: 'durationUnixNano', - title: 'Duration', - description: 'The duration of the span.', - fieldType: FieldType.Element, - getElement: (span: Span) => { - return ( - <div> - {SpanUtil.getSpanDurationAsString({ - divisibilityFactor: - props.divisibilityFactor, - spanDurationInUnixNano: - span.durationUnixNano!, - })} - </div> - ); - }, - }, - { - key: 'kind', - title: 'Span Kind', - description: 'The kind of span.', - fieldType: FieldType.Element, - getElement: (span: Span) => { - return ( - <div> - {SpanUtil.getSpanKindFriendlyName( - span.kind! - )} - </div> - ); - }, - }, - ]} - /> - ); - }; + if (!event) { + return <ErrorMessage error="No event found" />; + } return ( - <div id={props.id}> - <Tabs - tabs={[ - { - name: 'Basic Info', - children: getBasicInfo(), - }, - { - name: 'Logs', - children: getLogsContentElement(), - countBadge: logs.length, - tabType: TabType.Info, - }, - { - name: 'Attributes', - children: getAttributesContentElement(), - }, - { - name: 'Events', - children: getEventsContentElement(), - countBadge: span?.events?.filter((event: SpanEvent) => { - return ( - event.name !== - SpanEventType.Exception.toLowerCase() - ); - }).length, - tabType: TabType.Info, - }, - { - name: 'Exceptions', - children: getExceptionsContentElement(), - tabType: TabType.Error, - countBadge: span?.events?.filter((event: SpanEvent) => { - return ( - event.name === - SpanEventType.Exception.toLowerCase() - ); - }).length, - }, - ]} - onTabChange={() => {}} - /> - - {span && <></>} - </div> + <Detail<SpanEvent> + item={event} + fields={[ + { + key: "name", + title: "Event Name", + description: "The name of the event.", + }, + { + key: "timeUnixNano", + title: "Time in Trace", + description: "The time the event occurred in the trace.", + fieldType: FieldType.Element, + getElement: (event: SpanEvent) => { + return ( + <div> + {SpanUtil.getSpanEventTimeAsString({ + timelineStartTimeUnixNano: props.traceStartTimeInUnixNano, + divisibilityFactor: props.divisibilityFactor, + spanEventTimeUnixNano: event.timeUnixNano!, + })} + </div> + ); + }, + }, + { + key: "timeUnixNano", + title: "Time in Span", + description: "The time the event occurred in this span", + fieldType: FieldType.Element, + getElement: (event: SpanEvent) => { + return ( + <div> + {SpanUtil.getSpanEventTimeAsString({ + timelineStartTimeUnixNano: span!.startTimeUnixNano!, + divisibilityFactor: props.divisibilityFactor, + spanEventTimeUnixNano: event!.timeUnixNano!, + })} + </div> + ); + }, + }, + { + key: "time", + title: "Seen At", + description: "The time the event occurred.", + fieldType: FieldType.DateTime, + }, + { + key: "attributes", + title: "Event Attributes", + description: "The attributes of the event.", + fieldType: FieldType.Element, + getElement: (event: SpanEvent) => { + return ( + <CodeEditor + type={CodeType.JSON} + initialValue={JSONFunctions.toFormattedString( + JSONFunctions.nestJson(event.attributes || {}), + )} + readOnly={true} + /> + ); + }, + }, + ]} + /> ); + }; + + const getEventsContentElement: GetReactElementFunction = (): ReactElement => { + return getEvents(SpanEventType.Event); + }; + + type GetEventsFunction = (eventType: SpanEventType) => ReactElement; + const getEvents: GetEventsFunction = ( + eventType: SpanEventType, + ): ReactElement => { + const eventsToShow: SpanEvent[] | undefined = span?.events?.filter( + (event: SpanEvent) => { + if (eventType === SpanEventType.Exception) { + // name of the event is exception + return event.name === SpanEventType.Exception.toLowerCase(); + } + return event.name !== SpanEventType.Exception.toLowerCase(); + }, + ); + + if (!eventsToShow || eventsToShow.length === 0) { + if (eventType === SpanEventType.Exception) { + return <ErrorMessage error="No exceptions found for this span." />; + } + return <ErrorMessage error="No events found for this span." />; + } + + let bgColorClassName: string = "bg-indigo-500"; + + if (eventType === SpanEventType.Exception) { + bgColorClassName = "bg-red-500"; + } + + return ( + <AccordionGroup> + {eventsToShow.map((event: SpanEvent, index: number) => { + return ( + <Accordion + titleClassName="text-sm" + title={ + <div className="flex space-x-2"> + <div className="flex space-x-2"> + <div + className={`rounded-md text-white p-1 text-xs font-semibold ${bgColorClassName}`} + > + {eventType}: {index + 1} + </div> + <div className="flex space-x-1"> + <div className="mt-0.5 font-medium">{event.name}</div> + <div className="text-gray-500 mt-0.5"> + {" "} + at{" "} + {SpanUtil.getSpanEventTimeAsString({ + timelineStartTimeUnixNano: + props.traceStartTimeInUnixNano, + divisibilityFactor: props.divisibilityFactor, + spanEventTimeUnixNano: event.timeUnixNano!, + })} + </div> + </div> + </div> + </div> + } + key={index} + > + {getEventContentElement(event)} + </Accordion> + ); + })} + </AccordionGroup> + ); + }; + + const getExceptionsContentElement: GetReactElementFunction = + (): ReactElement => { + return getEvents(SpanEventType.Exception); + }; + + const getBasicInfo: GetReactElementFunction = (): ReactElement => { + if (!span) { + return <ErrorMessage error="Span not found" />; + } + + return ( + <Detail<Span> + item={span} + fields={[ + { + key: "spanId", + title: "Span ID", + description: "The unique identifier of the span.", + fieldType: FieldType.Text, + opts: { + isCopyable: true, + }, + }, + { + key: "name", + title: "Span Name", + description: "The name of the span.", + fieldType: FieldType.Text, + }, + { + key: "statusCode", + title: "Span Status", + description: "The status of the span.", + fieldType: FieldType.Element, + getElement: (span: Span) => { + return ( + <div> + <SpanStatusElement + span={span} + title={ + "Status: " + + SpanUtil.getSpanStatusCodeFriendlyName(span.statusCode!) + } + />{" "} + </div> + ); + }, + }, + { + key: "traceId", + title: "Trace ID", + description: "The unique identifier of the trace.", + fieldType: FieldType.Text, + opts: { + isCopyable: true, + }, + }, + { + key: "serviceId", + title: "Telemetry Service", + description: "The unique identifier of the service.", + fieldType: FieldType.Element, + getElement: () => { + return ( + <TelemetryServiceElement + telemetryService={telemetryService} + onNavigateComplete={() => { + onClose(); + }} + /> + ); + }, + }, + { + key: "startTime", + title: "Start Time", + description: "The time the span started.", + fieldType: FieldType.DateTime, + }, + { + key: "endTime", + title: "End Time", + description: "The time the span ended.", + fieldType: FieldType.DateTime, + }, + { + key: "startTimeUnixNano", + title: "Starts At", + description: "When did this span start in this trace?", + fieldType: FieldType.Element, + getElement: (span: Span) => { + return ( + <div> + {SpanUtil.getSpanStartsAtAsString({ + timelineStartTimeUnixNano: props.traceStartTimeInUnixNano, + divisibilityFactor: props.divisibilityFactor, + spanStartTimeUnixNano: span.startTimeUnixNano!, + })} + </div> + ); + }, + }, + { + key: "endTimeUnixNano", + title: "Ends At", + description: "When did this span end in this trace?", + fieldType: FieldType.Element, + getElement: (span: Span) => { + return ( + <div> + {SpanUtil.getSpanEndsAtAsString({ + timelineStartTimeUnixNano: props.traceStartTimeInUnixNano, + divisibilityFactor: props.divisibilityFactor, + spanEndTimeUnixNano: span.endTimeUnixNano!, + })} + </div> + ); + }, + }, + { + key: "durationUnixNano", + title: "Duration", + description: "The duration of the span.", + fieldType: FieldType.Element, + getElement: (span: Span) => { + return ( + <div> + {SpanUtil.getSpanDurationAsString({ + divisibilityFactor: props.divisibilityFactor, + spanDurationInUnixNano: span.durationUnixNano!, + })} + </div> + ); + }, + }, + { + key: "kind", + title: "Span Kind", + description: "The kind of span.", + fieldType: FieldType.Element, + getElement: (span: Span) => { + return <div>{SpanUtil.getSpanKindFriendlyName(span.kind!)}</div>; + }, + }, + ]} + /> + ); + }; + + return ( + <div id={props.id}> + <Tabs + tabs={[ + { + name: "Basic Info", + children: getBasicInfo(), + }, + { + name: "Logs", + children: getLogsContentElement(), + countBadge: logs.length, + tabType: TabType.Info, + }, + { + name: "Attributes", + children: getAttributesContentElement(), + }, + { + name: "Events", + children: getEventsContentElement(), + countBadge: span?.events?.filter((event: SpanEvent) => { + return event.name !== SpanEventType.Exception.toLowerCase(); + }).length, + tabType: TabType.Info, + }, + { + name: "Exceptions", + children: getExceptionsContentElement(), + tabType: TabType.Error, + countBadge: span?.events?.filter((event: SpanEvent) => { + return event.name === SpanEventType.Exception.toLowerCase(); + }).length, + }, + ]} + onTabChange={() => {}} + /> + + {span && <></>} + </div> + ); }; export default SpanViewer; diff --git a/Dashboard/src/Components/StatusPage/StatusPageLabel.tsx b/Dashboard/src/Components/StatusPage/StatusPageLabel.tsx index 9f0a46bc8c..886a91cc90 100644 --- a/Dashboard/src/Components/StatusPage/StatusPageLabel.tsx +++ b/Dashboard/src/Components/StatusPage/StatusPageLabel.tsx @@ -1,42 +1,42 @@ -import Route from 'Common/Types/API/Route'; -import Link from 'CommonUI/src/Components/Link/Link'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Route from "Common/Types/API/Route"; +import Link from "CommonUI/src/Components/Link/Link"; +import StatusPage from "Model/Models/StatusPage"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - statusPage: StatusPage; - onNavigateComplete?: (() => void) | undefined; + statusPage: StatusPage; + onNavigateComplete?: (() => void) | undefined; } const StatusPageElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if ( - props.statusPage._id && - (props.statusPage.projectId || - (props.statusPage.project && props.statusPage.project._id)) - ) { - const projectId: string | undefined = props.statusPage.projectId - ? props.statusPage.projectId.toString() - : props.statusPage.project - ? props.statusPage.project._id - : ''; - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={ - new Route( - `/dashboard/${projectId}/status-pages/${props.statusPage._id}` - ) - } - > - <span>{props.statusPage.name}</span> - </Link> - ); - } + if ( + props.statusPage._id && + (props.statusPage.projectId || + (props.statusPage.project && props.statusPage.project._id)) + ) { + const projectId: string | undefined = props.statusPage.projectId + ? props.statusPage.projectId.toString() + : props.statusPage.project + ? props.statusPage.project._id + : ""; + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={ + new Route( + `/dashboard/${projectId}/status-pages/${props.statusPage._id}`, + ) + } + > + <span>{props.statusPage.name}</span> + </Link> + ); + } - return <span>{props.statusPage.name}</span>; + return <span>{props.statusPage.name}</span>; }; export default StatusPageElement; diff --git a/Dashboard/src/Components/StatusPage/StatusPagesLabel.tsx b/Dashboard/src/Components/StatusPage/StatusPagesLabel.tsx index 3c616c324d..79fb017e8a 100644 --- a/Dashboard/src/Components/StatusPage/StatusPagesLabel.tsx +++ b/Dashboard/src/Components/StatusPage/StatusPagesLabel.tsx @@ -1,30 +1,30 @@ -import StatusPageElement from './StatusPageLabel'; -import TableColumnListComponent from 'CommonUI/src/Components/TableColumnList/TableColumnListComponent'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { FunctionComponent, ReactElement } from 'react'; +import StatusPageElement from "./StatusPageLabel"; +import TableColumnListComponent from "CommonUI/src/Components/TableColumnList/TableColumnListComponent"; +import StatusPage from "Model/Models/StatusPage"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - statusPages: Array<StatusPage>; - onNavigateComplete?: (() => void) | undefined; + statusPages: Array<StatusPage>; + onNavigateComplete?: (() => void) | undefined; } const StatusPagesElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <TableColumnListComponent - items={props.statusPages} - getEachElement={(statusPage: StatusPage) => { - return ( - <StatusPageElement - statusPage={statusPage} - onNavigateComplete={props.onNavigateComplete} - /> - ); - }} - noItemsMessage="No Status Page." - /> - ); + return ( + <TableColumnListComponent + items={props.statusPages} + getEachElement={(statusPage: StatusPage) => { + return ( + <StatusPageElement + statusPage={statusPage} + onNavigateComplete={props.onNavigateComplete} + /> + ); + }} + noItemsMessage="No Status Page." + /> + ); }; export default StatusPagesElement; diff --git a/Dashboard/src/Components/Team/Team.tsx b/Dashboard/src/Components/Team/Team.tsx index 0c1b99ab53..03e021dfa4 100644 --- a/Dashboard/src/Components/Team/Team.tsx +++ b/Dashboard/src/Components/Team/Team.tsx @@ -1,41 +1,39 @@ -import Route from 'Common/Types/API/Route'; -import Link from 'CommonUI/src/Components/Link/Link'; -import Team from 'Model/Models/Team'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Route from "Common/Types/API/Route"; +import Link from "CommonUI/src/Components/Link/Link"; +import Team from "Model/Models/Team"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - team: Team; - onNavigateComplete?: (() => void) | undefined; + team: Team; + onNavigateComplete?: (() => void) | undefined; } const TeamElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if ( - props.team._id && - (props.team.projectId || (props.team.project && props.team.project._id)) - ) { - const projectId: string | undefined = props.team.projectId - ? props.team.projectId.toString() - : props.team.project - ? props.team.project._id - : ''; - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={ - new Route( - `/dashboard/${projectId}/settings/teams/${props.team._id}` - ) - } - > - <span>{props.team.name}</span> - </Link> - ); - } + if ( + props.team._id && + (props.team.projectId || (props.team.project && props.team.project._id)) + ) { + const projectId: string | undefined = props.team.projectId + ? props.team.projectId.toString() + : props.team.project + ? props.team.project._id + : ""; + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={ + new Route(`/dashboard/${projectId}/settings/teams/${props.team._id}`) + } + > + <span>{props.team.name}</span> + </Link> + ); + } - return <span>{props.team.name}</span>; + return <span>{props.team.name}</span>; }; export default TeamElement; diff --git a/Dashboard/src/Components/Team/TeamsElement.tsx b/Dashboard/src/Components/Team/TeamsElement.tsx index f92ab4ca5e..2f66b4fe00 100644 --- a/Dashboard/src/Components/Team/TeamsElement.tsx +++ b/Dashboard/src/Components/Team/TeamsElement.tsx @@ -1,34 +1,34 @@ -import TeamElement from './Team'; -import Team from 'Model/Models/Team'; -import React, { FunctionComponent, ReactElement } from 'react'; +import TeamElement from "./Team"; +import Team from "Model/Models/Team"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - teams: Array<Team>; - onNavigateComplete?: (() => void) | undefined; + teams: Array<Team>; + onNavigateComplete?: (() => void) | undefined; } const TeamsElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.teams || props.teams.length === 0) { - return <p>No teams.</p>; - } + if (!props.teams || props.teams.length === 0) { + return <p>No teams.</p>; + } - return ( - <div> - {props.teams.map((team: Team, i: number) => { - return ( - <span key={i}> - <TeamElement - team={team} - onNavigateComplete={props.onNavigateComplete} - /> - {i !== props.teams.length - 1 && <span>, </span>} - </span> - ); - })} - </div> - ); + return ( + <div> + {props.teams.map((team: Team, i: number) => { + return ( + <span key={i}> + <TeamElement + team={team} + onNavigateComplete={props.onNavigateComplete} + /> + {i !== props.teams.length - 1 && <span>, </span>} + </span> + ); + })} + </div> + ); }; export default TeamsElement; diff --git a/Dashboard/src/Components/Telemetry/Documentation.tsx b/Dashboard/src/Components/Telemetry/Documentation.tsx index 7512c6d59c..01b7ddec0a 100644 --- a/Dashboard/src/Components/Telemetry/Documentation.tsx +++ b/Dashboard/src/Components/Telemetry/Documentation.tsx @@ -1,127 +1,127 @@ -import CSharpImage from '../Images/SvgImages/csharp.svg'; -import DockerImage from '../Images/SvgImages/docker.svg'; -import GoImage from '../Images/SvgImages/go.svg'; -import JavaImage from '../Images/SvgImages/java.svg'; -import JavaScriptImage from '../Images/SvgImages/javascript.svg'; -import MoreSourcesImage from '../Images/SvgImages/moresources.svg'; -import MySQLImage from '../Images/SvgImages/mysql.svg'; -import NodeImage from '../Images/SvgImages/node.svg'; -import PostgresSQLImage from '../Images/SvgImages/postgres.svg'; -import PythonImage from '../Images/SvgImages/python.svg'; -import ReactImage from '../Images/SvgImages/react.svg'; -import RustImage from '../Images/SvgImages/rust.svg'; -import SyslogImage from '../Images/SvgImages/syslog.svg'; -import SystemdImage from '../Images/SvgImages/systemd.svg'; -import TypeScriptImage from '../Images/SvgImages/typescript.svg'; -import Route from 'Common/Types/API/Route'; -import Card from 'CommonUI/src/Components/Card/Card'; -import ImageTiles from 'CommonUI/src/Components/ImageTiles/ImageTiles'; -import React, { FunctionComponent, ReactElement } from 'react'; +import CSharpImage from "../Images/SvgImages/csharp.svg"; +import DockerImage from "../Images/SvgImages/docker.svg"; +import GoImage from "../Images/SvgImages/go.svg"; +import JavaImage from "../Images/SvgImages/java.svg"; +import JavaScriptImage from "../Images/SvgImages/javascript.svg"; +import MoreSourcesImage from "../Images/SvgImages/moresources.svg"; +import MySQLImage from "../Images/SvgImages/mysql.svg"; +import NodeImage from "../Images/SvgImages/node.svg"; +import PostgresSQLImage from "../Images/SvgImages/postgres.svg"; +import PythonImage from "../Images/SvgImages/python.svg"; +import ReactImage from "../Images/SvgImages/react.svg"; +import RustImage from "../Images/SvgImages/rust.svg"; +import SyslogImage from "../Images/SvgImages/syslog.svg"; +import SystemdImage from "../Images/SvgImages/systemd.svg"; +import TypeScriptImage from "../Images/SvgImages/typescript.svg"; +import Route from "Common/Types/API/Route"; +import Card from "CommonUI/src/Components/Card/Card"; +import ImageTiles from "CommonUI/src/Components/ImageTiles/ImageTiles"; +import React, { FunctionComponent, ReactElement } from "react"; const TelemetryDocumentation: FunctionComponent = (): ReactElement => { - const openTelemetryDocUrl: Route = Route.fromString( - '/docs/telemetry/open-telemetry' - ); + const openTelemetryDocUrl: Route = Route.fromString( + "/docs/telemetry/open-telemetry", + ); - const fluentdDocUrl: Route = Route.fromString('/docs/telemetry/fluentd'); + const fluentdDocUrl: Route = Route.fromString("/docs/telemetry/fluentd"); - return ( - <Card - title={'Documentation'} - description={ - 'Learn how to integrate OneUptime with your application or resources to collect logs, metrics and traces data.' - } - > - <ImageTiles - title="Integrate with OpenTelemetry" - description="OneUptime supports a native integration with OpenTelemetry. OpenTelemetry is a collection of tools, APIs, and SDKs used to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) for analysis in order to understand your software performance and behavior." - tiles={[ - { - image: JavaScriptImage, - navigateToUrl: openTelemetryDocUrl, - title: 'JavaScript', - }, - { - image: TypeScriptImage, - navigateToUrl: openTelemetryDocUrl, - title: 'TypeScript', - }, - { - image: ReactImage, - navigateToUrl: openTelemetryDocUrl, - title: 'React', - }, - { - image: NodeImage, - navigateToUrl: openTelemetryDocUrl, - title: 'Node', - }, - { - image: RustImage, - navigateToUrl: openTelemetryDocUrl, - title: 'Rust', - }, - { - image: GoImage, - navigateToUrl: openTelemetryDocUrl, - title: 'Go', - }, - { - image: PythonImage, - navigateToUrl: openTelemetryDocUrl, - title: 'Python', - }, - { - image: JavaImage, - navigateToUrl: openTelemetryDocUrl, - title: 'Java', - }, - { - image: CSharpImage, - navigateToUrl: openTelemetryDocUrl, - title: 'C#', - }, - ]} - /> + return ( + <Card + title={"Documentation"} + description={ + "Learn how to integrate OneUptime with your application or resources to collect logs, metrics and traces data." + } + > + <ImageTiles + title="Integrate with OpenTelemetry" + description="OneUptime supports a native integration with OpenTelemetry. OpenTelemetry is a collection of tools, APIs, and SDKs used to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) for analysis in order to understand your software performance and behavior." + tiles={[ + { + image: JavaScriptImage, + navigateToUrl: openTelemetryDocUrl, + title: "JavaScript", + }, + { + image: TypeScriptImage, + navigateToUrl: openTelemetryDocUrl, + title: "TypeScript", + }, + { + image: ReactImage, + navigateToUrl: openTelemetryDocUrl, + title: "React", + }, + { + image: NodeImage, + navigateToUrl: openTelemetryDocUrl, + title: "Node", + }, + { + image: RustImage, + navigateToUrl: openTelemetryDocUrl, + title: "Rust", + }, + { + image: GoImage, + navigateToUrl: openTelemetryDocUrl, + title: "Go", + }, + { + image: PythonImage, + navigateToUrl: openTelemetryDocUrl, + title: "Python", + }, + { + image: JavaImage, + navigateToUrl: openTelemetryDocUrl, + title: "Java", + }, + { + image: CSharpImage, + navigateToUrl: openTelemetryDocUrl, + title: "C#", + }, + ]} + /> - <ImageTiles - title="Integrate with Fluentd" - description="OneUptime supports a native integration with Fluentd. Fluentd is an open source data collector for unified logging layer. Fluentd allows you to unify data collection and consumption for a better use and understanding of data." - tiles={[ - { - image: DockerImage, - navigateToUrl: fluentdDocUrl, - title: 'Docker', - }, - { - image: SyslogImage, - navigateToUrl: fluentdDocUrl, - title: 'Syslog', - }, - { - image: PostgresSQLImage, - navigateToUrl: fluentdDocUrl, - title: 'PostgresSQL', - }, - { - image: MySQLImage, - navigateToUrl: fluentdDocUrl, - title: 'MySQL', - }, - { - image: SystemdImage, - navigateToUrl: fluentdDocUrl, - title: 'Systemd', - }, - { - image: MoreSourcesImage, - navigateToUrl: fluentdDocUrl, - title: '+ 1000 more sources', - }, - ]} - /> - </Card> - ); + <ImageTiles + title="Integrate with Fluentd" + description="OneUptime supports a native integration with Fluentd. Fluentd is an open source data collector for unified logging layer. Fluentd allows you to unify data collection and consumption for a better use and understanding of data." + tiles={[ + { + image: DockerImage, + navigateToUrl: fluentdDocUrl, + title: "Docker", + }, + { + image: SyslogImage, + navigateToUrl: fluentdDocUrl, + title: "Syslog", + }, + { + image: PostgresSQLImage, + navigateToUrl: fluentdDocUrl, + title: "PostgresSQL", + }, + { + image: MySQLImage, + navigateToUrl: fluentdDocUrl, + title: "MySQL", + }, + { + image: SystemdImage, + navigateToUrl: fluentdDocUrl, + title: "Systemd", + }, + { + image: MoreSourcesImage, + navigateToUrl: fluentdDocUrl, + title: "+ 1000 more sources", + }, + ]} + /> + </Card> + ); }; export default TelemetryDocumentation; diff --git a/Dashboard/src/Components/TelemetryService/TelemetryServiceElement.tsx b/Dashboard/src/Components/TelemetryService/TelemetryServiceElement.tsx index 420a48ac37..0c51293b2c 100644 --- a/Dashboard/src/Components/TelemetryService/TelemetryServiceElement.tsx +++ b/Dashboard/src/Components/TelemetryService/TelemetryServiceElement.tsx @@ -1,59 +1,57 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import { Black } from 'Common/Types/BrandColors'; -import ObjectID from 'Common/Types/ObjectID'; -import ColorSquareCube from 'CommonUI/src/Components/ColorSquareCube/ColorSquareCube'; -import Link from 'CommonUI/src/Components/Link/Link'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import TelemetryService from 'Model/Models/TelemetryService'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import { Black } from "Common/Types/BrandColors"; +import ObjectID from "Common/Types/ObjectID"; +import ColorSquareCube from "CommonUI/src/Components/ColorSquareCube/ColorSquareCube"; +import Link from "CommonUI/src/Components/Link/Link"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import TelemetryService from "Model/Models/TelemetryService"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - telemetryService: TelemetryService; - onNavigateComplete?: (() => void) | undefined; - telemetryServiceNameClassName?: string; + telemetryService: TelemetryService; + onNavigateComplete?: (() => void) | undefined; + telemetryServiceNameClassName?: string; } const TelemetryServiceElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const getServiceElement: GetReactElementFunction = (): ReactElement => { - return ( - <div className="flex space-x-2"> - <div className="mt-1"> - <ColorSquareCube - color={props.telemetryService.serviceColor || Black} - tooltip={`${props.telemetryService.name?.toString()} Service Color`} - /> - </div> - <div className={props.telemetryServiceNameClassName}> - {props.telemetryService.name?.toString()} - </div> - </div> - ); - }; + const getServiceElement: GetReactElementFunction = (): ReactElement => { + return ( + <div className="flex space-x-2"> + <div className="mt-1"> + <ColorSquareCube + color={props.telemetryService.serviceColor || Black} + tooltip={`${props.telemetryService.name?.toString()} Service Color`} + /> + </div> + <div className={props.telemetryServiceNameClassName}> + {props.telemetryService.name?.toString()} + </div> + </div> + ); + }; - if (props.telemetryService._id) { - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={RouteUtil.populateRouteParams( - RouteMap[PageMap.TELEMETRY_SERVICES_VIEW] as Route, - { - modelId: new ObjectID( - props.telemetryService._id as string - ), - } - )} - > - {getServiceElement()} - </Link> - ); - } + if (props.telemetryService._id) { + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW] as Route, + { + modelId: new ObjectID(props.telemetryService._id as string), + }, + )} + > + {getServiceElement()} + </Link> + ); + } - return <div>{getServiceElement()}</div>; + return <div>{getServiceElement()}</div>; }; export default TelemetryServiceElement; diff --git a/Dashboard/src/Components/User/User.tsx b/Dashboard/src/Components/User/User.tsx index 38e6fca45c..1cf41f7c2c 100644 --- a/Dashboard/src/Components/User/User.tsx +++ b/Dashboard/src/Components/User/User.tsx @@ -1,133 +1,114 @@ -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Image from 'CommonUI/src/Components/Image/Image'; -import { FILE_URL } from 'CommonUI/src/Config'; -import BlankProfilePic from 'CommonUI/src/Images/users/blank-profile.svg'; -import User from 'Model/Models/User'; -import React, { FunctionComponent, ReactElement } from 'react'; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Image from "CommonUI/src/Components/Image/Image"; +import { FILE_URL } from "CommonUI/src/Config"; +import BlankProfilePic from "CommonUI/src/Images/users/blank-profile.svg"; +import User from "Model/Models/User"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - user?: User | JSONObject | undefined | null; - prefix?: string | undefined; - suffix?: string | undefined; - suffixClassName?: string | undefined; - usernameClassName?: string | undefined; - prefixClassName?: string | undefined; + user?: User | JSONObject | undefined | null; + prefix?: string | undefined; + suffix?: string | undefined; + suffixClassName?: string | undefined; + usernameClassName?: string | undefined; + prefixClassName?: string | undefined; } const UserElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let user: JSONObject | null | undefined = null; + let user: JSONObject | null | undefined = null; - if (props.user instanceof User) { - user = BaseModel.toJSONObject(props.user, User); - } else { - user = props.user; - } + if (props.user instanceof User) { + user = BaseModel.toJSONObject(props.user, User); + } else { + user = props.user; + } - if (JSONFunctions.isEmptyObject(user)) { - return ( - <div className="flex"> - <div> - <Image - className="h-8 w-8 rounded-full" - imageUrl={Route.fromString(`${BlankProfilePic}`)} - alt={'Automation'} - /> - </div> - <div className="mt-1 mr-1 ml-3"> - <div> - <span - className={ - props.prefixClassName - ? props.prefixClassName - : '' - } - > - {props.prefix} - </span>{' '} - <span - className={ - props.usernameClassName - ? props.usernameClassName - : '' - } - > - {'OneUptime'} - </span>{' '} - </div> - </div> - {props.suffix && ( - <div> - <p className={props.suffixClassName}>{props.suffix}</p> - </div> - )} - </div> - ); - } + if (JSONFunctions.isEmptyObject(user)) { + return ( + <div className="flex"> + <div> + <Image + className="h-8 w-8 rounded-full" + imageUrl={Route.fromString(`${BlankProfilePic}`)} + alt={"Automation"} + /> + </div> + <div className="mt-1 mr-1 ml-3"> + <div> + <span + className={props.prefixClassName ? props.prefixClassName : ""} + > + {props.prefix} + </span>{" "} + <span + className={props.usernameClassName ? props.usernameClassName : ""} + > + {"OneUptime"} + </span>{" "} + </div> + </div> + {props.suffix && ( + <div> + <p className={props.suffixClassName}>{props.suffix}</p> + </div> + )} + </div> + ); + } - if (user) { - return ( - <div className="flex"> - <div> - {props.user?.profilePictureId && ( - <Image - className="h-8 w-8 rounded-full" - imageUrl={URL.fromString( - FILE_URL.toString() - ).addRoute( - '/image/' + - props.user?.profilePictureId.toString() - )} - alt={user['name']?.toString() || 'User'} - /> - )} - {!props.user?.profilePictureId && ( - <Image - className="h-8 w-8 rounded-full" - imageUrl={Route.fromString(`${BlankProfilePic}`)} - alt={user['name']?.toString() || 'User'} - /> - )} - </div> - <div className="mt-1 mr-1 ml-3"> - <div> - <span - className={ - props.prefixClassName - ? props.prefixClassName - : '' - } - > - {props.prefix} - </span>{' '} - <span - className={ - props.usernameClassName - ? props.usernameClassName - : '' - } - >{`${ - (user['name']?.toString() as string) || - (user['email']?.toString() as string) || - '' - }`}</span>{' '} - </div> - </div> - {props.suffix && ( - <div> - <p className={props.suffixClassName}>{props.suffix}</p> - </div> - )} - </div> - ); - } + if (user) { + return ( + <div className="flex"> + <div> + {props.user?.profilePictureId && ( + <Image + className="h-8 w-8 rounded-full" + imageUrl={URL.fromString(FILE_URL.toString()).addRoute( + "/image/" + props.user?.profilePictureId.toString(), + )} + alt={user["name"]?.toString() || "User"} + /> + )} + {!props.user?.profilePictureId && ( + <Image + className="h-8 w-8 rounded-full" + imageUrl={Route.fromString(`${BlankProfilePic}`)} + alt={user["name"]?.toString() || "User"} + /> + )} + </div> + <div className="mt-1 mr-1 ml-3"> + <div> + <span + className={props.prefixClassName ? props.prefixClassName : ""} + > + {props.prefix} + </span>{" "} + <span + className={props.usernameClassName ? props.usernameClassName : ""} + >{`${ + (user["name"]?.toString() as string) || + (user["email"]?.toString() as string) || + "" + }`}</span>{" "} + </div> + </div> + {props.suffix && ( + <div> + <p className={props.suffixClassName}>{props.suffix}</p> + </div> + )} + </div> + ); + } - return <></>; + return <></>; }; export default UserElement; diff --git a/Dashboard/src/Components/User/Users.tsx b/Dashboard/src/Components/User/Users.tsx index 6d5d4b67ef..5c08b2a4c2 100644 --- a/Dashboard/src/Components/User/Users.tsx +++ b/Dashboard/src/Components/User/Users.tsx @@ -1,40 +1,40 @@ -import UserElement from './User'; -import User from 'Model/Models/User'; -import React, { FunctionComponent, ReactElement } from 'react'; +import UserElement from "./User"; +import User from "Model/Models/User"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - users?: Array<User>; - prefix?: string | undefined; - suffix?: string | undefined; - suffixClassName?: string | undefined; - usernameClassName?: string | undefined; - prefixClassName?: string | undefined; + users?: Array<User>; + prefix?: string | undefined; + suffix?: string | undefined; + suffixClassName?: string | undefined; + usernameClassName?: string | undefined; + prefixClassName?: string | undefined; } const UsersElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.users || props.users.length === 0) { - return <p>No users.</p>; - } + if (!props.users || props.users.length === 0) { + return <p>No users.</p>; + } - return ( - <div className="space-y-2 mt-2 mb-2"> - {props.users?.map((user: User) => { - return ( - <UserElement - key={user.id?.toString()} - user={user} - prefix={props.prefix} - suffix={props.suffix} - suffixClassName={props.suffixClassName} - usernameClassName={props.usernameClassName} - prefixClassName={props.prefixClassName} - /> - ); - })} - </div> - ); + return ( + <div className="space-y-2 mt-2 mb-2"> + {props.users?.map((user: User) => { + return ( + <UserElement + key={user.id?.toString()} + user={user} + prefix={props.prefix} + suffix={props.suffix} + suffixClassName={props.suffixClassName} + usernameClassName={props.usernameClassName} + prefixClassName={props.prefixClassName} + /> + ); + })} + </div> + ); }; export default UsersElement; diff --git a/Dashboard/src/Components/Workflow/WorkflowElement.tsx b/Dashboard/src/Components/Workflow/WorkflowElement.tsx index 722e5d70d3..0dc96847a7 100644 --- a/Dashboard/src/Components/Workflow/WorkflowElement.tsx +++ b/Dashboard/src/Components/Workflow/WorkflowElement.tsx @@ -1,42 +1,40 @@ -import Route from 'Common/Types/API/Route'; -import Link from 'CommonUI/src/Components/Link/Link'; -import Workflow from 'Model/Models/Workflow'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Route from "Common/Types/API/Route"; +import Link from "CommonUI/src/Components/Link/Link"; +import Workflow from "Model/Models/Workflow"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - workflow: Workflow; - onNavigateComplete?: (() => void) | undefined; + workflow: Workflow; + onNavigateComplete?: (() => void) | undefined; } const WorkflowElement: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if ( - props.workflow._id && - (props.workflow.projectId || - (props.workflow.project && props.workflow.project._id)) - ) { - const projectId: string | undefined = props.workflow.projectId - ? props.workflow.projectId.toString() - : props.workflow.project - ? props.workflow.project._id - : ''; - return ( - <Link - onNavigateComplete={props.onNavigateComplete} - className="hover:underline" - to={ - new Route( - `/dashboard/${projectId}/workflows/${props.workflow._id}` - ) - } - > - <span>{props.workflow.name}</span> - </Link> - ); - } + if ( + props.workflow._id && + (props.workflow.projectId || + (props.workflow.project && props.workflow.project._id)) + ) { + const projectId: string | undefined = props.workflow.projectId + ? props.workflow.projectId.toString() + : props.workflow.project + ? props.workflow.project._id + : ""; + return ( + <Link + onNavigateComplete={props.onNavigateComplete} + className="hover:underline" + to={ + new Route(`/dashboard/${projectId}/workflows/${props.workflow._id}`) + } + > + <span>{props.workflow.name}</span> + </Link> + ); + } - return <span>{props.workflow.name}</span>; + return <span>{props.workflow.name}</span>; }; export default WorkflowElement; diff --git a/Dashboard/src/Index.tsx b/Dashboard/src/Index.tsx index 6f99e172a5..e0ea4ffe50 100644 --- a/Dashboard/src/Index.tsx +++ b/Dashboard/src/Index.tsx @@ -1,19 +1,19 @@ -import App from './App'; -import Telemetry from 'CommonUI/src/Utils/Telemetry'; -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { BrowserRouter } from 'react-router-dom'; +import App from "./App"; +import Telemetry from "CommonUI/src/Utils/Telemetry"; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; Telemetry.init({ - serviceName: 'Dashboard', + serviceName: "Dashboard", }); const root: any = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById("root") as HTMLElement, ); root.render( - <BrowserRouter> - <App /> - </BrowserRouter> + <BrowserRouter> + <App /> + </BrowserRouter>, ); diff --git a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Delete.tsx b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Delete.tsx index 20a0867b5b..44cc748fad 100644 --- a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Delete.tsx +++ b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Delete.tsx @@ -1,31 +1,31 @@ -import PageMap from '../../../../Utils/PageMap'; -import RouteMap from '../../../../Utils/RouteMap'; -import PageComponentProps from '../../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import CodeRepository from 'Model/Models/CodeRepository'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../../Utils/PageMap"; +import RouteMap from "../../../../Utils/RouteMap"; +import PageComponentProps from "../../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import CodeRepository from "Model/Models/CodeRepository"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const CodeRepositoryDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={CodeRepository} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.AI_COPILOT_CODE_REPOSITORY] as Route - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={CodeRepository} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate( + RouteMap[PageMap.AI_COPILOT_CODE_REPOSITORY] as Route, + ); + }} + /> + </Fragment> + ); }; export default CodeRepositoryDelete; diff --git a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Index.tsx b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Index.tsx index 698442288d..808ae80d46 100644 --- a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Index.tsx +++ b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Index.tsx @@ -1,214 +1,206 @@ -import LabelsElement from '../../../../Components/Label/Labels'; -import PageComponentProps from '../../../PageComponentProps'; -import CodeRepositoryType from 'Common/Types/CodeRepository/CodeRepositoryType'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import CodeRepository from 'Model/Models/CodeRepository'; -import Label from 'Model/Models/Label'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../../../Components/Label/Labels"; +import PageComponentProps from "../../../PageComponentProps"; +import CodeRepositoryType from "Common/Types/CodeRepository/CodeRepositoryType"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import CodeRepository from "Model/Models/CodeRepository"; +import Label from "Model/Models/Label"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - {/* CodeRepository View */} - <CardModelDetail<CodeRepository> - name="Git Repository > Repository Details" - cardProps={{ - title: 'Repository Details', - description: 'Here are more details for this repository.', - }} - formSteps={[ - { - title: 'Repository Info', - id: 'repository-info', - }, - { - title: 'Details', - id: 'details', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - stepId: 'repository-info', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Service Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'repository-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - mainBranchName: true, - }, - title: 'Main Branch Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'master', - validation: { - minLength: 2, - noSpaces: true, - noSpecialCharacters: true, - }, - stepId: 'details', - }, - { - field: { - repositoryHostedAt: true, - }, - title: 'Repository Hosted At', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - CodeRepositoryType - ), - stepId: 'details', - }, - { - field: { - organizationName: true, - }, - title: 'Organization Name (on GitHub, GitLab, etc.)', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'org-name', - stepId: 'details', - }, - { - field: { - repositoryName: true, - }, - title: 'Repository Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'repo-name', - stepId: 'details', - }, - { - field: { - labels: true, - }, - title: 'Labels ', - stepId: 'labels', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: CodeRepository, - id: 'model-detail-service-catalog', - fields: [ - { - field: { - _id: true, - }, - title: 'Service ID', - }, - { - field: { - name: true, - }, - title: 'Service Name', - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: ( - item: CodeRepository - ): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - }, - { - field: { - mainBranchName: true, - }, - title: 'Main Branch Name', - }, - { - field: { - organizationName: true, - }, - title: 'Organization Name', - }, - { - field: { - repositoryName: true, - }, - title: 'Repository Name', - }, - { - field: { - repositoryHostedAt: true, - }, - title: 'Repository Hosted At', - }, - { - field: { - secretToken: true, - }, - title: 'Secret Token', - fieldType: FieldType.HiddenText, - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + return ( + <Fragment> + {/* CodeRepository View */} + <CardModelDetail<CodeRepository> + name="Git Repository > Repository Details" + cardProps={{ + title: "Repository Details", + description: "Here are more details for this repository.", + }} + formSteps={[ + { + title: "Repository Info", + id: "repository-info", + }, + { + title: "Details", + id: "details", + }, + { + title: "Labels", + id: "labels", + }, + ]} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + stepId: "repository-info", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Service Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "repository-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + mainBranchName: true, + }, + title: "Main Branch Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "master", + validation: { + minLength: 2, + noSpaces: true, + noSpecialCharacters: true, + }, + stepId: "details", + }, + { + field: { + repositoryHostedAt: true, + }, + title: "Repository Hosted At", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + dropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(CodeRepositoryType), + stepId: "details", + }, + { + field: { + organizationName: true, + }, + title: "Organization Name (on GitHub, GitLab, etc.)", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "org-name", + stepId: "details", + }, + { + field: { + repositoryName: true, + }, + title: "Repository Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "repo-name", + stepId: "details", + }, + { + field: { + labels: true, + }, + title: "Labels ", + stepId: "labels", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: CodeRepository, + id: "model-detail-service-catalog", + fields: [ + { + field: { + _id: true, + }, + title: "Service ID", + }, + { + field: { + name: true, + }, + title: "Service Name", + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: CodeRepository): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + }, + { + field: { + mainBranchName: true, + }, + title: "Main Branch Name", + }, + { + field: { + organizationName: true, + }, + title: "Organization Name", + }, + { + field: { + repositoryName: true, + }, + title: "Repository Name", + }, + { + field: { + repositoryHostedAt: true, + }, + title: "Repository Hosted At", + }, + { + field: { + secretToken: true, + }, + title: "Secret Token", + fieldType: FieldType.HiddenText, + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default StatusPageView; diff --git a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Layout.tsx b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Layout.tsx index 94aa9fd052..7620efcb44 100644 --- a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Layout.tsx +++ b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Layout.tsx @@ -1,32 +1,32 @@ -import { getCodeRepositoryBreadcrumbs } from '../../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../../Utils/RouteMap'; -import PageComponentProps from '../../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import CodeRepository from 'Model/Models/CodeRepository'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getCodeRepositoryBreadcrumbs } from "../../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../../Utils/RouteMap"; +import PageComponentProps from "../../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import CodeRepository from "Model/Models/CodeRepository"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const CodeRepositoryViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="Repository" - modelType={CodeRepository} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={getCodeRepositoryBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <ModelPage + title="Repository" + modelType={CodeRepository} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={getCodeRepositoryBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default CodeRepositoryViewLayout; diff --git a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Services.tsx b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Services.tsx index 0a41e4e43d..7edd70a65e 100644 --- a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Services.tsx +++ b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Services.tsx @@ -1,180 +1,177 @@ -import ServiceCatalogElement from '../../../../Components/ServiceCatalog/ServiceElement'; -import DashboardNavigation from '../../../../Utils/Navigation'; -import PageComponentProps from '../../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ServiceCatalog from 'Model/Models/ServiceCatalog'; -import ServiceRepository from 'Model/Models/ServiceRepository'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import ServiceCatalogElement from "../../../../Components/ServiceCatalog/ServiceElement"; +import DashboardNavigation from "../../../../Utils/Navigation"; +import PageComponentProps from "../../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ServiceCatalog from "Model/Models/ServiceCatalog"; +import ServiceRepository from "Model/Models/ServiceRepository"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceRepositoryPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const codeRepositoryId: ObjectID = Navigation.getLastParamAsObjectID(1); + const codeRepositoryId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<ServiceRepository> - modelType={ServiceRepository} - id="table-service-repository-page" - name="Code Repository > Service Repository" - isDeleteable={true} - createVerb={'Add'} - isCreateable={true} - isEditable={true} - isViewable={false} - showViewIdButton={true} - query={{ - codeRepositoryId: codeRepositoryId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: ServiceRepository - ): Promise<ServiceRepository> => { - item.codeRepositoryId = codeRepositoryId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Services', - description: - 'List of services that are associated with this code repository.', - }} - noItemsMessage={ - 'No services associated with this code repository so far. Please add some to activate copilot.' - } - formFields={[ - { - field: { - serviceCatalog: true, - }, - title: 'Service', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select Service', - dropdownModal: { - type: ServiceCatalog, - labelField: 'name', - valueField: '_id', - }, - doNotShowWhenEditing: true, - }, - { - field: { - servicePathInRepository: true, - }, - title: 'Service Path in Repository', - fieldType: FormFieldSchemaType.Text, - required: true, - description: - 'If this repository is a mono-repo, please provide the path to the service in the repository. If this repository is a single service repository, please provide /.', - placeholder: '/', - }, - { - field: { - limitNumberOfOpenPullRequestsCount: true, - }, - title: 'Number of Open Pull Requests for this service', - fieldType: FormFieldSchemaType.Number, - defaultValue: 5, - required: true, - description: - 'OneUptime will not create a new pull request if the number of open pull requests for this service is more than the limit specified here.', - placeholder: '/', - }, - { - field: { - enablePullRequests: true, - }, - title: 'Enable Pull Requests', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - description: - 'If enabled, OneUptime will create pull requests for this service and automatically improve code.', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - serviceCatalog: true, - }, - type: FieldType.Entity, - title: 'Service', - filterEntityType: ServiceCatalog, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - servicePathInRepository: true, - }, - title: 'Service Path in Repository', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - serviceCatalog: { - name: true, - serviceColor: true, - }, - }, - title: 'Service', - type: FieldType.Entity, + return ( + <Fragment> + <ModelTable<ServiceRepository> + modelType={ServiceRepository} + id="table-service-repository-page" + name="Code Repository > Service Repository" + isDeleteable={true} + createVerb={"Add"} + isCreateable={true} + isEditable={true} + isViewable={false} + showViewIdButton={true} + query={{ + codeRepositoryId: codeRepositoryId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: ServiceRepository, + ): Promise<ServiceRepository> => { + item.codeRepositoryId = codeRepositoryId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Services", + description: + "List of services that are associated with this code repository.", + }} + noItemsMessage={ + "No services associated with this code repository so far. Please add some to activate copilot." + } + formFields={[ + { + field: { + serviceCatalog: true, + }, + title: "Service", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select Service", + dropdownModal: { + type: ServiceCatalog, + labelField: "name", + valueField: "_id", + }, + doNotShowWhenEditing: true, + }, + { + field: { + servicePathInRepository: true, + }, + title: "Service Path in Repository", + fieldType: FormFieldSchemaType.Text, + required: true, + description: + "If this repository is a mono-repo, please provide the path to the service in the repository. If this repository is a single service repository, please provide /.", + placeholder: "/", + }, + { + field: { + limitNumberOfOpenPullRequestsCount: true, + }, + title: "Number of Open Pull Requests for this service", + fieldType: FormFieldSchemaType.Number, + defaultValue: 5, + required: true, + description: + "OneUptime will not create a new pull request if the number of open pull requests for this service is more than the limit specified here.", + placeholder: "/", + }, + { + field: { + enablePullRequests: true, + }, + title: "Enable Pull Requests", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + description: + "If enabled, OneUptime will create pull requests for this service and automatically improve code.", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + serviceCatalog: true, + }, + type: FieldType.Entity, + title: "Service", + filterEntityType: ServiceCatalog, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + servicePathInRepository: true, + }, + title: "Service Path in Repository", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + serviceCatalog: { + name: true, + serviceColor: true, + }, + }, + title: "Service", + type: FieldType.Entity, - getElement: (item: ServiceRepository): ReactElement => { - if (!item['serviceCatalog']) { - throw new BadDataException('Service not found'); - } + getElement: (item: ServiceRepository): ReactElement => { + if (!item["serviceCatalog"]) { + throw new BadDataException("Service not found"); + } - return ( - <ServiceCatalogElement - serviceCatalog={ - item['serviceCatalog'] as ServiceCatalog - } - /> - ); - }, - }, - { - field: { - servicePathInRepository: true, - }, - title: 'Service Path in Repository', - type: FieldType.Text, - }, - { - field: { - limitNumberOfOpenPullRequestsCount: true, - }, - title: 'Number of Open Pull Requests', - type: FieldType.Number, - }, - { - field: { - enablePullRequests: true, - }, - title: 'Enable Pull Requests', - type: FieldType.Boolean, - }, - ]} - /> - </Fragment> - ); + return ( + <ServiceCatalogElement + serviceCatalog={item["serviceCatalog"] as ServiceCatalog} + /> + ); + }, + }, + { + field: { + servicePathInRepository: true, + }, + title: "Service Path in Repository", + type: FieldType.Text, + }, + { + field: { + limitNumberOfOpenPullRequestsCount: true, + }, + title: "Number of Open Pull Requests", + type: FieldType.Number, + }, + { + field: { + enablePullRequests: true, + }, + title: "Enable Pull Requests", + type: FieldType.Boolean, + }, + ]} + /> + </Fragment> + ); }; export default ServiceRepositoryPage; diff --git a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Settings.tsx b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Settings.tsx index f485f4e001..22408b02ab 100644 --- a/Dashboard/src/Pages/AICopilot/CodeRepository/View/Settings.tsx +++ b/Dashboard/src/Pages/AICopilot/CodeRepository/View/Settings.tsx @@ -1,26 +1,26 @@ -import PageComponentProps from '../../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import ResetObjectID from 'CommonUI/src/Components/ResetObjectID/ResetObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import CodeRepository from 'Model/Models/CodeRepository'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import ResetObjectID from "CommonUI/src/Components/ResetObjectID/ResetObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import CodeRepository from "Model/Models/CodeRepository"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ResetObjectID<CodeRepository> - modelType={CodeRepository} - fieldName={'secretToken'} - title={'Reset Secret Token'} - description={'Reset the secret token to a new value.'} - modelId={modelId} - /> - </Fragment> - ); + return ( + <Fragment> + <ResetObjectID<CodeRepository> + modelType={CodeRepository} + fieldName={"secretToken"} + title={"Reset Secret Token"} + description={"Reset the secret token to a new value."} + modelId={modelId} + /> + </Fragment> + ); }; export default ServiceDelete; diff --git a/Dashboard/src/Pages/AICopilot/CodeRepository/View/SideMenu.tsx b/Dashboard/src/Pages/AICopilot/CodeRepository/View/SideMenu.tsx index 46a8707078..85d70c4075 100644 --- a/Dashboard/src/Pages/AICopilot/CodeRepository/View/SideMenu.tsx +++ b/Dashboard/src/Pages/AICopilot/CodeRepository/View/SideMenu.tsx @@ -1,78 +1,74 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> - <SideMenuItem - link={{ - title: 'Services', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.SquareStack} - /> - </SideMenuSection> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> + <SideMenuItem + link={{ + title: "Services", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES + ] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.SquareStack} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Settings', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Settings} - /> - <SideMenuItem - link={{ - title: 'Delete Service', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Settings", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS + ] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Settings} + /> + <SideMenuItem + link={{ + title: "Delete Service", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/AICopilot/Index.tsx b/Dashboard/src/Pages/AICopilot/Index.tsx index 47a2661109..a51fcc3264 100644 --- a/Dashboard/src/Pages/AICopilot/Index.tsx +++ b/Dashboard/src/Pages/AICopilot/Index.tsx @@ -1,216 +1,208 @@ -import LabelsElement from '../../Components/Label/Labels'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import CodeRepositoryType from 'Common/Types/CodeRepository/CodeRepositoryType'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Page from 'CommonUI/src/Components/Page/Page'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import CodeRepository from 'Model/Models/CodeRepository'; -import Label from 'Model/Models/Label'; -import React, { FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import CodeRepositoryType from "Common/Types/CodeRepository/CodeRepositoryType"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Page from "CommonUI/src/Components/Page/Page"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import CodeRepository from "Model/Models/CodeRepository"; +import Label from "Model/Models/Label"; +import React, { FunctionComponent, ReactElement } from "react"; const CodeRepositoryPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'AI Copilot'} - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'AI Copilot', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.AI_COPILOT] as Route - ), - }, - ]} - > - <ModelTable<CodeRepository> - modelType={CodeRepository} - id="service-catalog-table" - isDeleteable={false} - isEditable={false} - isCreateable={true} - name="Git Repositories" - isViewable={true} - cardProps={{ - title: 'Git Repository', - description: - 'Git repositores where the AI Copilot can improve your code.', - }} - viewPageRoute={ - new Route( - Navigation.getCurrentRoute().toString() + - '/code-repository' - ) - } - showViewIdButton={true} - noItemsMessage={'No repositories found.'} - formSteps={[ - { - title: 'Repository Info', - id: 'repository-info', - }, - { - title: 'Details', - id: 'details', - }, - ]} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Friendly Name', - validation: { - minLength: 2, - }, - stepId: 'repository-info', - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - stepId: 'repository-info', - }, - { - field: { - mainBranchName: true, - }, - title: 'Main Branch Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'master', - validation: { - minLength: 2, - noSpaces: true, - noSpecialCharacters: true, - }, - stepId: 'details', - }, - { - field: { - repositoryHostedAt: true, - }, - title: 'Repository Hosted At', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - CodeRepositoryType - ), - stepId: 'details', - }, - { - field: { - organizationName: true, - }, - title: 'Organization Name (on GitHub, GitLab, etc.)', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'org-name', - stepId: 'details', - }, - { - field: { - repositoryName: true, - }, - title: 'Repository Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'repo-name', - stepId: 'details', - }, - ]} - showRefreshButton={true} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, + return ( + <Page + title={"AI Copilot"} + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "AI Copilot", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.AI_COPILOT] as Route, + ), + }, + ]} + > + <ModelTable<CodeRepository> + modelType={CodeRepository} + id="service-catalog-table" + isDeleteable={false} + isEditable={false} + isCreateable={true} + name="Git Repositories" + isViewable={true} + cardProps={{ + title: "Git Repository", + description: + "Git repositores where the AI Copilot can improve your code.", + }} + viewPageRoute={ + new Route( + Navigation.getCurrentRoute().toString() + "/code-repository", + ) + } + showViewIdButton={true} + noItemsMessage={"No repositories found."} + formSteps={[ + { + title: "Repository Info", + id: "repository-info", + }, + { + title: "Details", + id: "details", + }, + ]} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Friendly Name", + validation: { + minLength: 2, + }, + stepId: "repository-info", + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + stepId: "repository-info", + }, + { + field: { + mainBranchName: true, + }, + title: "Main Branch Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "master", + validation: { + minLength: 2, + noSpaces: true, + noSpecialCharacters: true, + }, + stepId: "details", + }, + { + field: { + repositoryHostedAt: true, + }, + title: "Repository Hosted At", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + dropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(CodeRepositoryType), + stepId: "details", + }, + { + field: { + organizationName: true, + }, + title: "Organization Name (on GitHub, GitLab, etc.)", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "org-name", + stepId: "details", + }, + { + field: { + repositoryName: true, + }, + title: "Repository Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "repo-name", + stepId: "details", + }, + ]} + showRefreshButton={true} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, - getElement: (item: CodeRepository): ReactElement => { - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> - </Page> - ); + getElement: (item: CodeRepository): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + </Page> + ); }; export default CodeRepositoryPage; diff --git a/Dashboard/src/Pages/Global/NewIncidents.tsx b/Dashboard/src/Pages/Global/NewIncidents.tsx index 9a84e9ee14..6a0392ffc6 100644 --- a/Dashboard/src/Pages/Global/NewIncidents.tsx +++ b/Dashboard/src/Pages/Global/NewIncidents.tsx @@ -1,217 +1,198 @@ -import MonitorsElement from '../../Components/Monitor/Monitors'; -import ProjectElement from '../../Components/Project/Project'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import { Black } from 'Common/Types/BrandColors'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Page from 'CommonUI/src/Components/Page/Page'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { RequestOptions } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Incident from 'Model/Models/Incident'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitorsElement from "../../Components/Monitor/Monitors"; +import ProjectElement from "../../Components/Project/Project"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import { Black } from "Common/Types/BrandColors"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Page from "CommonUI/src/Components/Page/Page"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { RequestOptions } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Incident from "Model/Models/Incident"; +import React, { FunctionComponent, ReactElement } from "react"; const Home: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'New Incidents'} - breadcrumbLinks={[ - { - title: 'Home', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'New Incidents', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.NEW_INCIDENTS] as Route - ), - }, - ]} - > - <ModelTable<Incident> - modelType={Incident} - name="New Incidents" - id="incident-table" - isDeleteable={false} - query={{ - currentIncidentState: { - order: 1, - }, - }} - fetchRequestOptions={ - { - isMultiTenantRequest: true, - } as RequestOptions - } - selectMoreFields={{ - projectId: true, - }} - isEditable={false} - showRefreshButton={true} - isCreateable={false} - isViewable={true} - cardProps={{ - title: 'New Incidents', - description: - 'Here is a list of new incidents for all of the projects you are a part of.', - }} - noItemsMessage={'No incident found.'} - singularName="New Incident" - pluralName="New Incidents" - onViewPage={(item: Incident): Promise<Route> => { - return Promise.resolve( - new Route( - `/dashboard/${ - item.projectId || item.project?._id || '' - }/incidents/${item._id}` - ) - ); - }} - filters={[ - { - field: { - _id: true, - }, - type: FieldType.ObjectID, - title: 'Incident ID', - }, - { - field: { - title: true, - }, - type: FieldType.Text, - title: 'Title', - }, - { - field: { - createdAt: true, - }, - type: FieldType.Date, - title: 'Created At', - }, - ]} - columns={[ - { - field: { - project: { - name: true, - _id: true, - }, - }, - title: 'Project', - type: FieldType.Text, + return ( + <Page + title={"New Incidents"} + breadcrumbLinks={[ + { + title: "Home", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "New Incidents", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.NEW_INCIDENTS] as Route, + ), + }, + ]} + > + <ModelTable<Incident> + modelType={Incident} + name="New Incidents" + id="incident-table" + isDeleteable={false} + query={{ + currentIncidentState: { + order: 1, + }, + }} + fetchRequestOptions={ + { + isMultiTenantRequest: true, + } as RequestOptions + } + selectMoreFields={{ + projectId: true, + }} + isEditable={false} + showRefreshButton={true} + isCreateable={false} + isViewable={true} + cardProps={{ + title: "New Incidents", + description: + "Here is a list of new incidents for all of the projects you are a part of.", + }} + noItemsMessage={"No incident found."} + singularName="New Incident" + pluralName="New Incidents" + onViewPage={(item: Incident): Promise<Route> => { + return Promise.resolve( + new Route( + `/dashboard/${ + item.projectId || item.project?._id || "" + }/incidents/${item._id}`, + ), + ); + }} + filters={[ + { + field: { + _id: true, + }, + type: FieldType.ObjectID, + title: "Incident ID", + }, + { + field: { + title: true, + }, + type: FieldType.Text, + title: "Title", + }, + { + field: { + createdAt: true, + }, + type: FieldType.Date, + title: "Created At", + }, + ]} + columns={[ + { + field: { + project: { + name: true, + _id: true, + }, + }, + title: "Project", + type: FieldType.Text, - selectedProperty: 'name', - getElement: (item: Incident): ReactElement => { - return ( - <ProjectElement project={item['project']!} /> - ); - }, - }, - { - field: { - _id: true, - }, - title: 'Incident ID', - type: FieldType.Text, - }, - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - currentIncidentState: { - name: true, - color: true, - }, - }, - title: 'Current State', - type: FieldType.Entity, - getElement: (item: Incident): ReactElement => { - if (item['currentIncidentState']) { - return ( - <Pill - color={ - item.currentIncidentState.color || - Black - } - text={ - item.currentIncidentState.name || - 'Unknown' - } - /> - ); - } + selectedProperty: "name", + getElement: (item: Incident): ReactElement => { + return <ProjectElement project={item["project"]!} />; + }, + }, + { + field: { + _id: true, + }, + title: "Incident ID", + type: FieldType.Text, + }, + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + currentIncidentState: { + name: true, + color: true, + }, + }, + title: "Current State", + type: FieldType.Entity, + getElement: (item: Incident): ReactElement => { + if (item["currentIncidentState"]) { + return ( + <Pill + color={item.currentIncidentState.color || Black} + text={item.currentIncidentState.name || "Unknown"} + /> + ); + } - return <></>; - }, - }, - { - field: { - incidentSeverity: { - name: true, - color: true, - }, - }, - title: 'Incident Severity', - type: FieldType.Entity, - getElement: (item: Incident): ReactElement => { - if (item['incidentSeverity']) { - return ( - <Pill - color={ - item.incidentSeverity.color || Black - } - text={ - item.incidentSeverity.name || - 'Unknown' - } - /> - ); - } + return <></>; + }, + }, + { + field: { + incidentSeverity: { + name: true, + color: true, + }, + }, + title: "Incident Severity", + type: FieldType.Entity, + getElement: (item: Incident): ReactElement => { + if (item["incidentSeverity"]) { + return ( + <Pill + color={item.incidentSeverity.color || Black} + text={item.incidentSeverity.name || "Unknown"} + /> + ); + } - return <></>; - }, - }, - { - field: { - monitors: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Monitors Affected', - type: FieldType.Text, - getElement: (item: Incident): ReactElement => { - return ( - <MonitorsElement - monitors={item['monitors'] || []} - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - type: FieldType.DateTime, - }, - ]} - /> - </Page> - ); + return <></>; + }, + }, + { + field: { + monitors: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Monitors Affected", + type: FieldType.Text, + getElement: (item: Incident): ReactElement => { + return <MonitorsElement monitors={item["monitors"] || []} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + type: FieldType.DateTime, + }, + ]} + /> + </Page> + ); }; export default Home; diff --git a/Dashboard/src/Pages/Global/ProjectInvitations.tsx b/Dashboard/src/Pages/Global/ProjectInvitations.tsx index 8297779cf4..5afc880cdb 100644 --- a/Dashboard/src/Pages/Global/ProjectInvitations.tsx +++ b/Dashboard/src/Pages/Global/ProjectInvitations.tsx @@ -1,168 +1,158 @@ -import EventName from '../../Utils/EventName'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Page from 'CommonUI/src/Components/Page/Page'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import GlobalEvents from 'CommonUI/src/Utils/GlobalEvents'; -import ModelAPI, { RequestOptions } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import User from 'CommonUI/src/Utils/User'; -import TeamMember from 'Model/Models/TeamMember'; -import React, { FunctionComponent, ReactElement } from 'react'; +import EventName from "../../Utils/EventName"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Page from "CommonUI/src/Components/Page/Page"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import GlobalEvents from "CommonUI/src/Utils/GlobalEvents"; +import ModelAPI, { RequestOptions } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import User from "CommonUI/src/Utils/User"; +import TeamMember from "Model/Models/TeamMember"; +import React, { FunctionComponent, ReactElement } from "react"; const Home: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'Project Invitations'} - breadcrumbLinks={[ - { - title: 'Home', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Project Invitations', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.PROJECT_INVITATIONS] as Route - ), - }, - ]} - > - <ModelTable<TeamMember> - modelType={TeamMember} - name="Project Invitations" - id="team-member-table" - isDeleteable={true} - query={{ - userId: User.getUserId(), - hasAcceptedInvitation: false, - }} - fetchRequestOptions={ - { - isMultiTenantRequest: true, - } as RequestOptions - } - deleteRequestOptions={ - { - isMultiTenantRequest: true, - } as RequestOptions - } - isEditable={false} - showRefreshButton={true} - isCreateable={false} - isViewable={false} - cardProps={{ - title: 'Pending Invitations', - description: - 'Here is a list of projects and teams you have been invited to.', - }} - noItemsMessage={ - 'No project or team invitations for you so far.' - } - singularName="Project Invitation" - pluralName="Project Invitations" - onItemDeleted={() => { - GlobalEvents.dispatchEvent( - EventName.PROJECT_INVITATIONS_REFRESH - ); - }} - actionButtons={[ - { - title: 'Accept', - buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, - icon: IconProp.Check, - onClick: async ( - item: TeamMember, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - // accept invite. - await ModelAPI.updateById({ - modelType: TeamMember, - id: new ObjectID( - item['_id'] - ? item['_id'].toString() - : '' - ), - data: { - hasAcceptedInvitation: true, - invitationAcceptedAt: new Date(), - }, + return ( + <Page + title={"Project Invitations"} + breadcrumbLinks={[ + { + title: "Home", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Project Invitations", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.PROJECT_INVITATIONS] as Route, + ), + }, + ]} + > + <ModelTable<TeamMember> + modelType={TeamMember} + name="Project Invitations" + id="team-member-table" + isDeleteable={true} + query={{ + userId: User.getUserId(), + hasAcceptedInvitation: false, + }} + fetchRequestOptions={ + { + isMultiTenantRequest: true, + } as RequestOptions + } + deleteRequestOptions={ + { + isMultiTenantRequest: true, + } as RequestOptions + } + isEditable={false} + showRefreshButton={true} + isCreateable={false} + isViewable={false} + cardProps={{ + title: "Pending Invitations", + description: + "Here is a list of projects and teams you have been invited to.", + }} + noItemsMessage={"No project or team invitations for you so far."} + singularName="Project Invitation" + pluralName="Project Invitations" + onItemDeleted={() => { + GlobalEvents.dispatchEvent(EventName.PROJECT_INVITATIONS_REFRESH); + }} + actionButtons={[ + { + title: "Accept", + buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, + icon: IconProp.Check, + onClick: async ( + item: TeamMember, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + // accept invite. + await ModelAPI.updateById({ + modelType: TeamMember, + id: new ObjectID(item["_id"] ? item["_id"].toString() : ""), + data: { + hasAcceptedInvitation: true, + invitationAcceptedAt: new Date(), + }, - requestOptions: { - isMultiTenantRequest: true, - }, - }); + requestOptions: { + isMultiTenantRequest: true, + }, + }); - onCompleteAction(); - Navigation.reload(); - } catch (err) { - GlobalEvents.dispatchEvent( - EventName.PROJECT_INVITATIONS_REFRESH - ); - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - deleteButtonText="Reject" - filters={[ - { - field: { - project: { - name: true, - }, - }, - type: FieldType.Text, - title: 'Project', - }, - { - field: { - team: { - name: true, - }, - }, - type: FieldType.Text, - title: 'Team', - }, - ]} - columns={[ - { - field: { - project: { - name: true, - }, - }, - title: 'Project Invited to', - type: FieldType.Text, - selectedProperty: 'name', - }, - { - field: { - team: { - name: true, - }, - }, - title: 'Team Invited to', - type: FieldType.Text, - selectedProperty: 'name', - }, - ]} - /> - </Page> - ); + onCompleteAction(); + Navigation.reload(); + } catch (err) { + GlobalEvents.dispatchEvent( + EventName.PROJECT_INVITATIONS_REFRESH, + ); + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + deleteButtonText="Reject" + filters={[ + { + field: { + project: { + name: true, + }, + }, + type: FieldType.Text, + title: "Project", + }, + { + field: { + team: { + name: true, + }, + }, + type: FieldType.Text, + title: "Team", + }, + ]} + columns={[ + { + field: { + project: { + name: true, + }, + }, + title: "Project Invited to", + type: FieldType.Text, + selectedProperty: "name", + }, + { + field: { + team: { + name: true, + }, + }, + title: "Team Invited to", + type: FieldType.Text, + selectedProperty: "name", + }, + ]} + /> + </Page> + ); }; export default Home; diff --git a/Dashboard/src/Pages/Global/UserProfile/Index.tsx b/Dashboard/src/Pages/Global/UserProfile/Index.tsx index 8355349fcf..174c426b9a 100644 --- a/Dashboard/src/Pages/Global/UserProfile/Index.tsx +++ b/Dashboard/src/Pages/Global/UserProfile/Index.tsx @@ -1,113 +1,109 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import Route from 'Common/Types/API/Route'; -import TimezoneCode from 'Common/Types/TimezoneCode'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import Page from 'CommonUI/src/Components/Page/Page'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import UserUtil from 'CommonUI/src/Utils/User'; -import User from 'Model/Models/User'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import Route from "Common/Types/API/Route"; +import TimezoneCode from "Common/Types/TimezoneCode"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import Page from "CommonUI/src/Components/Page/Page"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import UserUtil from "CommonUI/src/Utils/User"; +import User from "Model/Models/User"; +import React, { FunctionComponent, ReactElement } from "react"; const Home: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'User Profile'} - breadcrumbLinks={[ - { - title: 'Home', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'User Profile', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route - ), - }, - ]} - sideMenu={<SideMenu />} - > - <CardModelDetail - cardProps={{ - title: 'Basic Info', - description: 'Here are some of your details.', - }} - name="User Profile > Basic Info" - isEditable={true} - formFields={[ - { - field: { - email: true, - }, - fieldType: FormFieldSchemaType.Email, - placeholder: 'jeff@example.com', - required: true, - title: 'Email', - description: - 'You will have to verify your email again if you change it', - }, - { - field: { - name: true, - }, - fieldType: FormFieldSchemaType.Text, - placeholder: 'Jeff Smith', - required: true, - title: 'Full Name', - }, - { - field: { - timezone: true, - }, - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - TimezoneCode - ), - placeholder: 'Select Timezone', - description: - 'Select your timezone. This will be used for all date and time related notifications sent out to you.', - required: false, - title: 'Timezone', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: User, - id: 'user-profile', - fields: [ - { - field: { - name: true, - }, - title: 'Name', - }, - { - field: { - email: true, - }, - title: 'Email', - }, - { - field: { - timezone: true, - }, - title: 'Timezone', - placeholder: 'No timezone selected', - }, - ], - modelId: UserUtil.getUserId(), - }} - /> - </Page> - ); + return ( + <Page + title={"User Profile"} + breadcrumbLinks={[ + { + title: "Home", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "User Profile", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route, + ), + }, + ]} + sideMenu={<SideMenu />} + > + <CardModelDetail + cardProps={{ + title: "Basic Info", + description: "Here are some of your details.", + }} + name="User Profile > Basic Info" + isEditable={true} + formFields={[ + { + field: { + email: true, + }, + fieldType: FormFieldSchemaType.Email, + placeholder: "jeff@example.com", + required: true, + title: "Email", + description: + "You will have to verify your email again if you change it", + }, + { + field: { + name: true, + }, + fieldType: FormFieldSchemaType.Text, + placeholder: "Jeff Smith", + required: true, + title: "Full Name", + }, + { + field: { + timezone: true, + }, + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(TimezoneCode), + placeholder: "Select Timezone", + description: + "Select your timezone. This will be used for all date and time related notifications sent out to you.", + required: false, + title: "Timezone", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: User, + id: "user-profile", + fields: [ + { + field: { + name: true, + }, + title: "Name", + }, + { + field: { + email: true, + }, + title: "Email", + }, + { + field: { + timezone: true, + }, + title: "Timezone", + placeholder: "No timezone selected", + }, + ], + modelId: UserUtil.getUserId(), + }} + /> + </Page> + ); }; export default Home; diff --git a/Dashboard/src/Pages/Global/UserProfile/Password.tsx b/Dashboard/src/Pages/Global/UserProfile/Password.tsx index 8ad0fe58c5..1c82cec0c2 100644 --- a/Dashboard/src/Pages/Global/UserProfile/Password.tsx +++ b/Dashboard/src/Pages/Global/UserProfile/Password.tsx @@ -1,113 +1,108 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import Route from 'Common/Types/API/Route'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Card from 'CommonUI/src/Components/Card/Card'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Page from 'CommonUI/src/Components/Page/Page'; -import UserUtil from 'CommonUI/src/Utils/User'; -import User from 'Model/Models/User'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import Route from "Common/Types/API/Route"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Card from "CommonUI/src/Components/Card/Card"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Page from "CommonUI/src/Components/Page/Page"; +import UserUtil from "CommonUI/src/Utils/User"; +import User from "Model/Models/User"; +import React, { FunctionComponent, ReactElement, useState } from "react"; class UserWithConfirmPassword extends User { - public confirmPassword: string = ''; + public confirmPassword: string = ""; } const Home: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [hasPasswordChanged, setHasPasswordChanged] = - useState<boolean>(false); + const [hasPasswordChanged, setHasPasswordChanged] = useState<boolean>(false); - return ( - <Page - title={'User Profile'} - breadcrumbLinks={[ - { - title: 'Home', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), + return ( + <Page + title={"User Profile"} + breadcrumbLinks={[ + { + title: "Home", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "User Profile", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route, + ), + }, + { + title: "Password Management", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_PROFILE_PASSWORD] as Route, + ), + }, + ]} + sideMenu={<SideMenu />} + > + <Card + title={"Update Password"} + description={"You can set a new password here if you wish to do so."} + > + {!hasPasswordChanged ? ( + <ModelForm<UserWithConfirmPassword> + modelType={UserWithConfirmPassword} + name="Change Password Form" + onSuccess={() => { + setHasPasswordChanged(true); + }} + submitButtonStyleType={ButtonStyleType.PRIMARY} + id="change-password-form" + showAsColumns={1} + doNotFetchExistingModel={true} + modelIdToEdit={UserUtil.getUserId()} + maxPrimaryButtonWidth={true} + initialValues={{ + password: "", + confirmPassword: "", + }} + fields={[ + { + field: { + password: true, }, - { - title: 'User Profile', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route - ), + fieldType: FormFieldSchemaType.Password, + validation: { + minLength: 6, }, - { - title: 'Password Management', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.USER_PROFILE_PASSWORD] as Route - ), + placeholder: "Password", + title: "Password", + required: true, + showEvenIfPermissionDoesNotExist: true, + }, + { + field: { + confirmPassword: true, }, + validation: { + minLength: 6, + toMatchField: "password", + }, + fieldType: FormFieldSchemaType.Password, + placeholder: "Confirm Password", + title: "Confirm Password", + required: true, + showEvenIfPermissionDoesNotExist: true, + }, ]} - sideMenu={<SideMenu />} - > - <Card - title={'Update Password'} - description={ - 'You can set a new password here if you wish to do so.' - } - > - {!hasPasswordChanged ? ( - <ModelForm<UserWithConfirmPassword> - modelType={UserWithConfirmPassword} - name="Change Password Form" - onSuccess={() => { - setHasPasswordChanged(true); - }} - submitButtonStyleType={ButtonStyleType.PRIMARY} - id="change-password-form" - showAsColumns={1} - doNotFetchExistingModel={true} - modelIdToEdit={UserUtil.getUserId()} - maxPrimaryButtonWidth={true} - initialValues={{ - password: '', - confirmPassword: '', - }} - fields={[ - { - field: { - password: true, - }, - fieldType: FormFieldSchemaType.Password, - validation: { - minLength: 6, - }, - placeholder: 'Password', - title: 'Password', - required: true, - showEvenIfPermissionDoesNotExist: true, - }, - { - field: { - confirmPassword: true, - }, - validation: { - minLength: 6, - toMatchField: 'password', - }, - fieldType: FormFieldSchemaType.Password, - placeholder: 'Confirm Password', - title: 'Confirm Password', - required: true, - showEvenIfPermissionDoesNotExist: true, - }, - ]} - formType={FormType.Update} - submitButtonText={'Update Password'} - /> - ) : ( - <p>Your password has been updated.</p> - )} - </Card> - </Page> - ); + formType={FormType.Update} + submitButtonText={"Update Password"} + /> + ) : ( + <p>Your password has been updated.</p> + )} + </Card> + </Page> + ); }; export default Home; diff --git a/Dashboard/src/Pages/Global/UserProfile/Picture.tsx b/Dashboard/src/Pages/Global/UserProfile/Picture.tsx index a49ec3e4bb..171408a7bc 100644 --- a/Dashboard/src/Pages/Global/UserProfile/Picture.tsx +++ b/Dashboard/src/Pages/Global/UserProfile/Picture.tsx @@ -1,106 +1,102 @@ -import EventName from '../../../Utils/EventName'; -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import Route from 'Common/Types/API/Route'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import Page from 'CommonUI/src/Components/Page/Page'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import GlobalEvents from 'CommonUI/src/Utils/GlobalEvents'; -import UserUtil from 'CommonUI/src/Utils/User'; -import User from 'Model/Models/User'; -import React, { FunctionComponent, ReactElement } from 'react'; +import EventName from "../../../Utils/EventName"; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import Route from "Common/Types/API/Route"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import Page from "CommonUI/src/Components/Page/Page"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import GlobalEvents from "CommonUI/src/Utils/GlobalEvents"; +import UserUtil from "CommonUI/src/Utils/User"; +import User from "Model/Models/User"; +import React, { FunctionComponent, ReactElement } from "react"; const Home: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'User Profile'} - breadcrumbLinks={[ - { - title: 'Home', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'User Profile', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route - ), - }, + return ( + <Page + title={"User Profile"} + breadcrumbLinks={[ + { + title: "Home", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "User Profile", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route, + ), + }, - { - title: 'Profile Picture', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.USER_PROFILE_PICTURE] as Route - ), + { + title: "Profile Picture", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_PROFILE_PICTURE] as Route, + ), + }, + ]} + sideMenu={<SideMenu />} + > + <CardModelDetail<User> + name="User Profile > Profile Picture" + cardProps={{ + title: "Profile Picture", + description: "Please update your profile picture here.", + }} + isEditable={true} + editButtonText={"Update Profile Picture"} + formFields={[ + { + field: { + profilePictureFile: true, + }, + title: "Profile Picture", + fieldType: FormFieldSchemaType.ImageFile, + required: false, + placeholder: "Upload profile picture", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + selectMoreFields: { + profilePictureId: true, + }, + onItemLoaded: (item: User) => { + if (item.profilePictureId) { + UserUtil.setProfilePicId(item.profilePictureId); + GlobalEvents.dispatchEvent(EventName.SET_NEW_PROFILE_PICTURE, { + id: item.profilePictureId, + }); + } else { + UserUtil.setProfilePicId(null); + GlobalEvents.dispatchEvent(EventName.SET_NEW_PROFILE_PICTURE, { + id: null, + }); + } + }, + modelType: User, + id: "model-detail-user-profile-picture", + fields: [ + { + field: { + profilePictureFile: { + file: true, + type: true, }, - ]} - sideMenu={<SideMenu />} - > - <CardModelDetail<User> - name="User Profile > Profile Picture" - cardProps={{ - title: 'Profile Picture', - description: 'Please update your profile picture here.', - }} - isEditable={true} - editButtonText={'Update Profile Picture'} - formFields={[ - { - field: { - profilePictureFile: true, - }, - title: 'Profile Picture', - fieldType: FormFieldSchemaType.ImageFile, - required: false, - placeholder: 'Upload profile picture', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - selectMoreFields: { - profilePictureId: true, - }, - onItemLoaded: (item: User) => { - if (item.profilePictureId) { - UserUtil.setProfilePicId(item.profilePictureId); - GlobalEvents.dispatchEvent( - EventName.SET_NEW_PROFILE_PICTURE, - { id: item.profilePictureId } - ); - } else { - UserUtil.setProfilePicId(null); - GlobalEvents.dispatchEvent( - EventName.SET_NEW_PROFILE_PICTURE, - { id: null } - ); - } - }, - modelType: User, - id: 'model-detail-user-profile-picture', - fields: [ - { - field: { - profilePictureFile: { - file: true, - type: true, - }, - }, - fieldType: FieldType.ImageFile, - title: 'Profile Picture', - placeholder: 'No profile picture uploaded.', - }, - ], - modelId: UserUtil.getUserId(), - }} - /> - </Page> - ); + }, + fieldType: FieldType.ImageFile, + title: "Profile Picture", + placeholder: "No profile picture uploaded.", + }, + ], + modelId: UserUtil.getUserId(), + }} + /> + </Page> + ); }; export default Home; diff --git a/Dashboard/src/Pages/Global/UserProfile/SideMenu.tsx b/Dashboard/src/Pages/Global/UserProfile/SideMenu.tsx index 83a6012e6f..fe418ed16b 100644 --- a/Dashboard/src/Pages/Global/UserProfile/SideMenu.tsx +++ b/Dashboard/src/Pages/Global/UserProfile/SideMenu.tsx @@ -1,45 +1,45 @@ -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 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import React, { ReactElement } from 'react'; +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 "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import React, { ReactElement } from "react"; const DashboardSideMenu: () => JSX.Element = (): ReactElement => { - return ( - <SideMenu> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route - ), - }} - icon={IconProp.Info} - /> + return ( + <SideMenu> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_PROFILE_OVERVIEW] as Route, + ), + }} + icon={IconProp.Info} + /> - <SideMenuItem - link={{ - title: 'Password Management', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.USER_PROFILE_PASSWORD] as Route - ), - }} - icon={IconProp.Lock} - /> + <SideMenuItem + link={{ + title: "Password Management", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_PROFILE_PASSWORD] as Route, + ), + }} + icon={IconProp.Lock} + /> - <SideMenuItem - link={{ - title: 'Profile Picture', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.USER_PROFILE_PICTURE] as Route - ), - }} - icon={IconProp.Image} - /> - </SideMenu> - ); + <SideMenuItem + link={{ + title: "Profile Picture", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_PROFILE_PICTURE] as Route, + ), + }} + icon={IconProp.Image} + /> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Home/Home.tsx b/Dashboard/src/Pages/Home/Home.tsx index 21307715bd..fe1cf02c28 100644 --- a/Dashboard/src/Pages/Home/Home.tsx +++ b/Dashboard/src/Pages/Home/Home.tsx @@ -1,125 +1,115 @@ -import IncidentsTable from '../../Components/Incident/IncidentsTable'; -import IncidentStateUtil from '../../Utils/IncidentState'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import DashboardSideMenu from './SideMenu'; -import Route from 'Common/Types/API/Route'; -import Includes from 'Common/Types/BaseDatabase/Includes'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import Page from 'CommonUI/src/Components/Page/Page'; -import API from 'CommonUI/src/Utils/API/API'; -import UiAnalytics from 'CommonUI/src/Utils/Analytics'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentState from 'Model/Models/IncidentState'; -import Project from 'Model/Models/Project'; +import IncidentsTable from "../../Components/Incident/IncidentsTable"; +import IncidentStateUtil from "../../Utils/IncidentState"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import DashboardSideMenu from "./SideMenu"; +import Route from "Common/Types/API/Route"; +import Includes from "Common/Types/BaseDatabase/Includes"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import Page from "CommonUI/src/Components/Page/Page"; +import API from "CommonUI/src/Utils/API/API"; +import UiAnalytics from "CommonUI/src/Utils/Analytics"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentState from "Model/Models/IncidentState"; +import Project from "Model/Models/Project"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps extends PageComponentProps { - isLoadingProjects: boolean; - projects: Array<Project>; + isLoadingProjects: boolean; + projects: Array<Project>; } const Home: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [unresolvedIncidentStates, setUnresolvedIncidentStates] = useState< - Array<IncidentState> - >([]); - const [error, setError] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [unresolvedIncidentStates, setUnresolvedIncidentStates] = useState< + Array<IncidentState> + >([]); + const [error, setError] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(false); - const fetchIncidentStates: PromiseVoidFunction = - async (): Promise<void> => { - setIsLoading(true); + const fetchIncidentStates: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); - try { - setUnresolvedIncidentStates( - await IncidentStateUtil.getUnresolvedIncidentStates( - DashboardNavigation.getProjectId()! - ) - ); - setError(''); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + try { + setUnresolvedIncidentStates( + await IncidentStateUtil.getUnresolvedIncidentStates( + DashboardNavigation.getProjectId()!, + ), + ); + setError(""); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - setIsLoading(false); - }; + setIsLoading(false); + }; - useEffect(() => { - if (!props.isLoadingProjects && props.projects.length === 0) { - Navigation.navigate(RouteMap[PageMap.WELCOME] as Route); - return; - } - UiAnalytics.capture('dashboard/home', { - projectId: DashboardNavigation.getProjectId()?.toString(), - }); + useEffect(() => { + if (!props.isLoadingProjects && props.projects.length === 0) { + Navigation.navigate(RouteMap[PageMap.WELCOME] as Route); + return; + } + UiAnalytics.capture("dashboard/home", { + projectId: DashboardNavigation.getProjectId()?.toString(), + }); - fetchIncidentStates().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); - }); - }, [props.projects]); + fetchIncidentStates().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, [props.projects]); - return ( - <Page - title={'Home'} - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Home', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - ]} - sideMenu={ - <DashboardSideMenu - project={props.currentProject || undefined} - /> - } - > - <div> - {isLoading && <PageLoader isVisible={true} />} - {error && <ErrorMessage error={error} />} + return ( + <Page + title={"Home"} + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Home", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + ]} + sideMenu={ + <DashboardSideMenu project={props.currentProject || undefined} /> + } + > + <div> + {isLoading && <PageLoader isVisible={true} />} + {error && <ErrorMessage error={error} />} - {!isLoading && !error && unresolvedIncidentStates.length > 0 && ( - <IncidentsTable - viewPageRoute={RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENTS] as Route - )} - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - currentIncidentStateId: new Includes( - unresolvedIncidentStates.map( - (state: IncidentState) => { - return state.id!; - } - ) - ), - }} - noItemsMessage="Nice work! No Active Incidents so far." - title="Active Incidents" - description="Here is a list of all the Active Incidents for this project." - /> - )} - </div> - </Page> - ); + {!isLoading && !error && unresolvedIncidentStates.length > 0 && ( + <IncidentsTable + viewPageRoute={RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENTS] as Route, + )} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + currentIncidentStateId: new Includes( + unresolvedIncidentStates.map((state: IncidentState) => { + return state.id!; + }), + ), + }} + noItemsMessage="Nice work! No Active Incidents so far." + title="Active Incidents" + description="Here is a list of all the Active Incidents for this project." + /> + )} + </div> + </Page> + ); }; export default Home; diff --git a/Dashboard/src/Pages/Home/NotOperationalMonitors.tsx b/Dashboard/src/Pages/Home/NotOperationalMonitors.tsx index 4ed404a161..79ec9676a8 100644 --- a/Dashboard/src/Pages/Home/NotOperationalMonitors.tsx +++ b/Dashboard/src/Pages/Home/NotOperationalMonitors.tsx @@ -1,61 +1,53 @@ -import MonitorTable from '../../Components/Monitor/MonitorTable'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import DashboardSideMenu from './SideMenu'; -import Route from 'Common/Types/API/Route'; -import Page from 'CommonUI/src/Components/Page/Page'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitorTable from "../../Components/Monitor/MonitorTable"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import DashboardSideMenu from "./SideMenu"; +import Route from "Common/Types/API/Route"; +import Page from "CommonUI/src/Components/Page/Page"; +import React, { FunctionComponent, ReactElement } from "react"; const NotOperationalMonitors: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'Home'} - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Home', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Inoperational Monitors ', - to: RouteMap[ - PageMap.HOME_NOT_OPERATIONAL_MONITORS - ] as Route, - }, - ]} - sideMenu={ - <DashboardSideMenu - project={props.currentProject || undefined} - /> - } - > - <MonitorTable - viewPageRoute={RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS] as Route - )} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - currentMonitorStatus: { - isOperationalState: false, - }, - }} - noItemsMessage="All monitors in operational state." - title="Inoperational Monitors" - description="Here is a list of all the monitors which are not in operational state." - /> - </Page> - ); + return ( + <Page + title={"Home"} + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Home", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Inoperational Monitors ", + to: RouteMap[PageMap.HOME_NOT_OPERATIONAL_MONITORS] as Route, + }, + ]} + sideMenu={ + <DashboardSideMenu project={props.currentProject || undefined} /> + } + > + <MonitorTable + viewPageRoute={RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS] as Route, + )} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + currentMonitorStatus: { + isOperationalState: false, + }, + }} + noItemsMessage="All monitors in operational state." + title="Inoperational Monitors" + description="Here is a list of all the monitors which are not in operational state." + /> + </Page> + ); }; export default NotOperationalMonitors; diff --git a/Dashboard/src/Pages/Home/OngoingScheduledMaintenance.tsx b/Dashboard/src/Pages/Home/OngoingScheduledMaintenance.tsx index a8d3290013..ef79272eda 100644 --- a/Dashboard/src/Pages/Home/OngoingScheduledMaintenance.tsx +++ b/Dashboard/src/Pages/Home/OngoingScheduledMaintenance.tsx @@ -1,57 +1,51 @@ -import ScheduledMaintenanceTable from '../../Components/ScheduledMaintenance/ScheduledMaintenanceTable'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import SideMenu from './SideMenu'; -import Route from 'Common/Types/API/Route'; -import Page from 'CommonUI/src/Components/Page/Page'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ScheduledMaintenanceTable from "../../Components/ScheduledMaintenance/ScheduledMaintenanceTable"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import SideMenu from "./SideMenu"; +import Route from "Common/Types/API/Route"; +import Page from "CommonUI/src/Components/Page/Page"; +import React, { FunctionComponent, ReactElement } from "react"; const ScheduledMaintenancesPage: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'Home'} - sideMenu={<SideMenu project={props.currentProject || undefined} />} - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Home', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Ongoing Scheduled Maintenance', - to: RouteMap[ - PageMap.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS - ] as Route, - }, - ]} - > - <ScheduledMaintenanceTable - viewPageRoute={ - RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route - } - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - currentScheduledMaintenanceState: { - isOngoingState: true, - }, - }} - noItemsMessage="No ongoing events so far." - title="Ongoing Scheduled Maintenances" - description="Here is a list of all the ongoing events for this project." - /> - </Page> - ); + return ( + <Page + title={"Home"} + sideMenu={<SideMenu project={props.currentProject || undefined} />} + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Home", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Ongoing Scheduled Maintenance", + to: RouteMap[ + PageMap.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS + ] as Route, + }, + ]} + > + <ScheduledMaintenanceTable + viewPageRoute={RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + currentScheduledMaintenanceState: { + isOngoingState: true, + }, + }} + noItemsMessage="No ongoing events so far." + title="Ongoing Scheduled Maintenances" + description="Here is a list of all the ongoing events for this project." + /> + </Page> + ); }; export default ScheduledMaintenancesPage; diff --git a/Dashboard/src/Pages/Home/SideMenu.tsx b/Dashboard/src/Pages/Home/SideMenu.tsx index 16fbd0b8c2..78a6bfb13d 100644 --- a/Dashboard/src/Pages/Home/SideMenu.tsx +++ b/Dashboard/src/Pages/Home/SideMenu.tsx @@ -1,130 +1,122 @@ -import IncidentStateUtil from '../../Utils/IncidentState'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import Includes from 'Common/Types/BaseDatabase/Includes'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { BadgeType } from 'CommonUI/src/Components/Badge/Badge'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/CountModelSideMenuItem'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import Incident from 'Model/Models/Incident'; -import IncidentState from 'Model/Models/IncidentState'; -import Monitor from 'Model/Models/Monitor'; -import Project from 'Model/Models/Project'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; +import IncidentStateUtil from "../../Utils/IncidentState"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import Includes from "Common/Types/BaseDatabase/Includes"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { BadgeType } from "CommonUI/src/Components/Badge/Badge"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/CountModelSideMenuItem"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import Incident from "Model/Models/Incident"; +import IncidentState from "Model/Models/IncidentState"; +import Monitor from "Model/Models/Monitor"; +import Project from "Model/Models/Project"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; export interface ComponentProps { - project?: Project | undefined; + project?: Project | undefined; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [unresolvedIncidentStates, setUnresolvedIncidentStates] = useState< - Array<IncidentState> - >([]); + const [unresolvedIncidentStates, setUnresolvedIncidentStates] = useState< + Array<IncidentState> + >([]); - const fetchIncidentStates: PromiseVoidFunction = - async (): Promise<void> => { - try { - if (props.project?.id) { - const unresolvedIncidentStates: Array<IncidentState> = - await IncidentStateUtil.getUnresolvedIncidentStates( - props.project?.id - ); - setUnresolvedIncidentStates(unresolvedIncidentStates); - } - } catch (err) { - // maybe show an error message - } - }; + const fetchIncidentStates: PromiseVoidFunction = async (): Promise<void> => { + try { + if (props.project?.id) { + const unresolvedIncidentStates: Array<IncidentState> = + await IncidentStateUtil.getUnresolvedIncidentStates( + props.project?.id, + ); + setUnresolvedIncidentStates(unresolvedIncidentStates); + } + } catch (err) { + // maybe show an error message + } + }; - useEffect(() => { - fetchIncidentStates().catch((_err: Error) => { - // do nothing - }); - }, []); + useEffect(() => { + fetchIncidentStates().catch((_err: Error) => { + // do nothing + }); + }, []); - return ( - <SideMenu> - <SideMenuSection title="Incidents"> - <SideMenuItem<Incident> - link={{ - title: 'Active', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }} - icon={IconProp.Alert} - badgeType={BadgeType.DANGER} - modelType={Incident} - countQuery={{ - projectId: props.project?._id, - currentIncidentStateId: new Includes( - unresolvedIncidentStates.map( - (state: IncidentState) => { - return state.id!; - } - ) - ), - }} - /> - </SideMenuSection> + return ( + <SideMenu> + <SideMenuSection title="Incidents"> + <SideMenuItem<Incident> + link={{ + title: "Active", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }} + icon={IconProp.Alert} + badgeType={BadgeType.DANGER} + modelType={Incident} + countQuery={{ + projectId: props.project?._id, + currentIncidentStateId: new Includes( + unresolvedIncidentStates.map((state: IncidentState) => { + return state.id!; + }), + ), + }} + /> + </SideMenuSection> - <SideMenuSection title="Monitors"> - <SideMenuItem<Monitor> - link={{ - title: 'Inoperational', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.HOME_NOT_OPERATIONAL_MONITORS - ] as Route - ), - }} - icon={IconProp.AltGlobe} - countQuery={{ - projectId: props.project?._id, - currentMonitorStatus: { - isOperationalState: false, - }, - }} - modelType={Monitor} - badgeType={BadgeType.DANGER} - /> - </SideMenuSection> + <SideMenuSection title="Monitors"> + <SideMenuItem<Monitor> + link={{ + title: "Inoperational", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.HOME_NOT_OPERATIONAL_MONITORS] as Route, + ), + }} + icon={IconProp.AltGlobe} + countQuery={{ + projectId: props.project?._id, + currentMonitorStatus: { + isOperationalState: false, + }, + }} + modelType={Monitor} + badgeType={BadgeType.DANGER} + /> + </SideMenuSection> - <SideMenuSection title="Scheduled Events"> - <SideMenuItem<ScheduledMaintenance> - link={{ - title: 'Ongoing', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap - .HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS - ] as Route - ), - }} - icon={IconProp.Clock} - countQuery={{ - projectId: props.project?._id, - currentScheduledMaintenanceState: { - isOngoingState: true, - }, - }} - modelType={ScheduledMaintenance} - badgeType={BadgeType.WARNING} - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuSection title="Scheduled Events"> + <SideMenuItem<ScheduledMaintenance> + link={{ + title: "Ongoing", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS + ] as Route, + ), + }} + icon={IconProp.Clock} + countQuery={{ + projectId: props.project?._id, + currentScheduledMaintenanceState: { + isOngoingState: true, + }, + }} + modelType={ScheduledMaintenance} + badgeType={BadgeType.WARNING} + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Incidents/Incidents.tsx b/Dashboard/src/Pages/Incidents/Incidents.tsx index d7090d3f7c..13886368ff 100644 --- a/Dashboard/src/Pages/Incidents/Incidents.tsx +++ b/Dashboard/src/Pages/Incidents/Incidents.tsx @@ -1,20 +1,20 @@ -import IncidentsTable from '../../Components/Incident/IncidentsTable'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; +import IncidentsTable from "../../Components/Incident/IncidentsTable"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; const IncidentsPage: FunctionComponent< - PageComponentProps + PageComponentProps > = (): ReactElement => { - return ( - <IncidentsTable - viewPageRoute={Navigation.getCurrentRoute()} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - /> - ); + return ( + <IncidentsTable + viewPageRoute={Navigation.getCurrentRoute()} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + /> + ); }; export default IncidentsPage; diff --git a/Dashboard/src/Pages/Incidents/Layout.tsx b/Dashboard/src/Pages/Incidents/Layout.tsx index 22d8f04218..fb7ee0719c 100644 --- a/Dashboard/src/Pages/Incidents/Layout.tsx +++ b/Dashboard/src/Pages/Incidents/Layout.tsx @@ -1,25 +1,25 @@ -import { getIncidentsBreadcrumbs } from '../../Utils/Breadcrumbs/IncidentBreadcrumbs'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import SideMenu from './SideMenu'; -import Page from 'CommonUI/src/Components/Page/Page'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; +import { getIncidentsBreadcrumbs } from "../../Utils/Breadcrumbs/IncidentBreadcrumbs"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import SideMenu from "./SideMenu"; +import Page from "CommonUI/src/Components/Page/Page"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet } from "react-router-dom"; const IncidentsLayout: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <Page - title={'Incidents'} - sideMenu={<SideMenu project={props.currentProject || undefined} />} - breadcrumbLinks={getIncidentsBreadcrumbs(path)} - > - <Outlet /> - </Page> - ); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <Page + title={"Incidents"} + sideMenu={<SideMenu project={props.currentProject || undefined} />} + breadcrumbLinks={getIncidentsBreadcrumbs(path)} + > + <Outlet /> + </Page> + ); }; export default IncidentsLayout; diff --git a/Dashboard/src/Pages/Incidents/SideMenu.tsx b/Dashboard/src/Pages/Incidents/SideMenu.tsx index fe5f12adaf..7c697bf04c 100644 --- a/Dashboard/src/Pages/Incidents/SideMenu.tsx +++ b/Dashboard/src/Pages/Incidents/SideMenu.tsx @@ -1,56 +1,56 @@ -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 { BadgeType } from 'CommonUI/src/Components/Badge/Badge'; -import CountModelSideMenuItem from 'CommonUI/src/Components/SideMenu/CountModelSideMenuItem'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import Incident from 'Model/Models/Incident'; -import Project from 'Model/Models/Project'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 { BadgeType } from "CommonUI/src/Components/Badge/Badge"; +import CountModelSideMenuItem from "CommonUI/src/Components/SideMenu/CountModelSideMenuItem"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import Incident from "Model/Models/Incident"; +import Project from "Model/Models/Project"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - project?: Project | undefined; + project?: Project | undefined; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Overview"> - <SideMenuItem - link={{ - title: 'All Incidents', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENTS] as Route - ), - }} - icon={IconProp.List} - /> + return ( + <SideMenu> + <SideMenuSection title="Overview"> + <SideMenuItem + link={{ + title: "All Incidents", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENTS] as Route, + ), + }} + icon={IconProp.List} + /> - <CountModelSideMenuItem<Incident> - link={{ - title: 'Active Incidents', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.UNRESOLVED_INCIDENTS] as Route - ), - }} - icon={IconProp.Alert} - badgeType={BadgeType.DANGER} - modelType={Incident} - countQuery={{ - projectId: props.project?._id, - currentIncidentState: { - isResolvedState: false, - }, - }} - /> - </SideMenuSection> - </SideMenu> - ); + <CountModelSideMenuItem<Incident> + link={{ + title: "Active Incidents", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.UNRESOLVED_INCIDENTS] as Route, + ), + }} + icon={IconProp.Alert} + badgeType={BadgeType.DANGER} + modelType={Incident} + countQuery={{ + projectId: props.project?._id, + currentIncidentState: { + isResolvedState: false, + }, + }} + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Incidents/Unresolved.tsx b/Dashboard/src/Pages/Incidents/Unresolved.tsx index 977c37466a..c7f7664100 100644 --- a/Dashboard/src/Pages/Incidents/Unresolved.tsx +++ b/Dashboard/src/Pages/Incidents/Unresolved.tsx @@ -1,30 +1,30 @@ -import IncidentsTable from '../../Components/Incident/IncidentsTable'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import React, { FunctionComponent, ReactElement } from 'react'; +import IncidentsTable from "../../Components/Incident/IncidentsTable"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import React, { FunctionComponent, ReactElement } from "react"; const IncidentsPage: FunctionComponent< - PageComponentProps + PageComponentProps > = (): ReactElement => { - return ( - <IncidentsTable - viewPageRoute={RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENTS] as Route - )} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - currentIncidentState: { - isResolvedState: false, - }, - }} - noItemsMessage="Nice work! No Active Incidents so far." - title="Active Incidents" - description="Here is a list of all the Active Incidents for this project." - /> - ); + return ( + <IncidentsTable + viewPageRoute={RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENTS] as Route, + )} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + currentIncidentState: { + isResolvedState: false, + }, + }} + noItemsMessage="Nice work! No Active Incidents so far." + title="Active Incidents" + description="Here is a list of all the Active Incidents for this project." + /> + ); }; export default IncidentsPage; diff --git a/Dashboard/src/Pages/Incidents/View/CustomFields.tsx b/Dashboard/src/Pages/Incidents/View/CustomFields.tsx index db7786aa86..bfab02dbe6 100644 --- a/Dashboard/src/Pages/Incidents/View/CustomFields.tsx +++ b/Dashboard/src/Pages/Incidents/View/CustomFields.tsx @@ -1,28 +1,28 @@ -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import CustomFieldsDetail from 'CommonUI/src/Components/CustomFields/CustomFieldsDetail'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Incident from 'Model/Models/Incident'; -import IncidentCustomField from 'Model/Models/IncidentCustomField'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import CustomFieldsDetail from "CommonUI/src/Components/CustomFields/CustomFieldsDetail"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Incident from "Model/Models/Incident"; +import IncidentCustomField from "Model/Models/IncidentCustomField"; +import React, { FunctionComponent, ReactElement } from "react"; const IncidentCustomFields: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <CustomFieldsDetail - title="Incident Custom Fields" - description="Custom fields help you add new fields to your resources in OneUptime." - modelType={Incident} - customFieldType={IncidentCustomField} - name="Incident Custom Fields" - projectId={ProjectUtil.getCurrentProject()!.id!} - modelId={modelId} - /> - ); + return ( + <CustomFieldsDetail + title="Incident Custom Fields" + description="Custom fields help you add new fields to your resources in OneUptime." + modelType={Incident} + customFieldType={IncidentCustomField} + name="Incident Custom Fields" + projectId={ProjectUtil.getCurrentProject()!.id!} + modelId={modelId} + /> + ); }; export default IncidentCustomFields; diff --git a/Dashboard/src/Pages/Incidents/View/Delete.tsx b/Dashboard/src/Pages/Incidents/View/Delete.tsx index e6f5c3c731..fabe2c2be3 100644 --- a/Dashboard/src/Pages/Incidents/View/Delete.tsx +++ b/Dashboard/src/Pages/Incidents/View/Delete.tsx @@ -1,32 +1,31 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Incident from 'Model/Models/Incident'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Incident from "Model/Models/Incident"; +import React, { FunctionComponent, ReactElement } from "react"; const IncidentDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <ModelDelete - modelType={Incident} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENTS] as Route, - { modelId } - ) - ); - }} - /> - ); + return ( + <ModelDelete + modelType={Incident} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate( + RouteUtil.populateRouteParams(RouteMap[PageMap.INCIDENTS] as Route, { + modelId, + }), + ); + }} + /> + ); }; export default IncidentDelete; diff --git a/Dashboard/src/Pages/Incidents/View/Index.tsx b/Dashboard/src/Pages/Incidents/View/Index.tsx index 6bbe0c68ca..d697d544e0 100644 --- a/Dashboard/src/Pages/Incidents/View/Index.tsx +++ b/Dashboard/src/Pages/Incidents/View/Index.tsx @@ -1,637 +1,596 @@ import ChangeIncidentState, { - IncidentType, -} from '../../../Components/Incident/ChangeState'; -import LabelsElement from '../../../Components/Label/Labels'; -import MonitorsElement from '../../../Components/Monitor/Monitors'; -import OnCallDutyPoliciesView from '../../../Components/OnCallPolicy/OnCallPolicies'; -import EventName from '../../../Utils/EventName'; -import PageComponentProps from '../../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { Black } from 'Common/Types/BrandColors'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import CheckboxViewer from 'CommonUI/src/Components/Checkbox/CheckboxViewer'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import InfoCard from 'CommonUI/src/Components/InfoCard/InfoCard'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import BaseAPI from 'CommonUI/src/Utils/API/API'; -import GlobalEvent from 'CommonUI/src/Utils/GlobalEvents'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Incident from 'Model/Models/Incident'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import Label from 'Model/Models/Label'; + IncidentType, +} from "../../../Components/Incident/ChangeState"; +import LabelsElement from "../../../Components/Label/Labels"; +import MonitorsElement from "../../../Components/Monitor/Monitors"; +import OnCallDutyPoliciesView from "../../../Components/OnCallPolicy/OnCallPolicies"; +import EventName from "../../../Utils/EventName"; +import PageComponentProps from "../../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { Black } from "Common/Types/BrandColors"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import CheckboxViewer from "CommonUI/src/Components/Checkbox/CheckboxViewer"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import InfoCard from "CommonUI/src/Components/InfoCard/InfoCard"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import BaseAPI from "CommonUI/src/Utils/API/API"; +import GlobalEvent from "CommonUI/src/Utils/GlobalEvents"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Incident from "Model/Models/Incident"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import Label from "Model/Models/Label"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const IncidentView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - const [incidentStateTimeline, setIncidentStateTimeline] = useState< - IncidentStateTimeline[] - >([]); - const [incidentStates, setIncidentStates] = useState<IncidentState[]>([]); + const [incidentStateTimeline, setIncidentStateTimeline] = useState< + IncidentStateTimeline[] + >([]); + const [incidentStates, setIncidentStates] = useState<IncidentState[]>([]); - const [error, setError] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(false); - const fetchData: PromiseVoidFunction = async (): Promise<void> => { - try { - setIsLoading(true); + const fetchData: PromiseVoidFunction = async (): Promise<void> => { + try { + setIsLoading(true); + + const incidentTimelines: ListResult<IncidentStateTimeline> = + await ModelAPI.getList({ + modelType: IncidentStateTimeline, + query: { + incidentId: modelId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + startsAt: true, + createdByUser: { + name: true, + email: true, + profilePictureId: true, + }, + incidentStateId: true, + }, + sort: { + startsAt: SortOrder.Ascending, + }, + }); + + const incidentStates: ListResult<IncidentState> = await ModelAPI.getList({ + modelType: IncidentState, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + name: true, + isAcknowledgedState: true, + isResolvedState: true, + }, + sort: {}, + }); + + setIncidentStates(incidentStates.data as IncidentState[]); + setIncidentStateTimeline( + incidentTimelines.data as IncidentStateTimeline[], + ); + setError(""); + } catch (err) { + setError(BaseAPI.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchData().catch((err: Error) => { + setError(BaseAPI.getFriendlyMessage(err)); + }); + }, []); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + type GetIncidentStateFunction = () => IncidentState | undefined; + + const getAcknowledgeState: GetIncidentStateFunction = (): + | IncidentState + | undefined => { + return incidentStates.find((state: IncidentState) => { + return state.isAcknowledgedState; + }); + }; + + const getResolvedState: GetIncidentStateFunction = (): + | IncidentState + | undefined => { + return incidentStates.find((state: IncidentState) => { + return state.isResolvedState; + }); + }; + + type getTimeFunction = () => string; + + const getTimeToAcknowledge: getTimeFunction = (): string => { + const incidentStartTime: Date = + incidentStateTimeline[0]?.startsAt || new Date(); + + const acknowledgeTime: Date | undefined = incidentStateTimeline.find( + (timeline: IncidentStateTimeline) => { + return ( + timeline.incidentStateId?.toString() === + getAcknowledgeState()?._id?.toString() + ); + }, + )?.startsAt; + + const resolveTime: Date | undefined = incidentStateTimeline.find( + (timeline: IncidentStateTimeline) => { + return ( + timeline.incidentStateId?.toString() === + getResolvedState()?._id?.toString() + ); + }, + )?.startsAt; + + if (!acknowledgeTime && !resolveTime) { + return ( + "Not yet " + + (getAcknowledgeState()?.name?.toLowerCase() || "acknowledged") + ); + } + + if (!acknowledgeTime && resolveTime) { + return OneUptimeDate.convertMinutesToDaysHoursAndMinutes( + OneUptimeDate.getDifferenceInMinutes(resolveTime, incidentStartTime), + ); + } + + return OneUptimeDate.convertMinutesToDaysHoursAndMinutes( + OneUptimeDate.getDifferenceInMinutes(acknowledgeTime!, incidentStartTime), + ); + }; + + const getTimeToResolve: getTimeFunction = (): string => { + const incidentStartTime: Date = + incidentStateTimeline[0]?.startsAt || new Date(); + + const resolveTime: Date | undefined = incidentStateTimeline.find( + (timeline: IncidentStateTimeline) => { + return ( + timeline.incidentStateId?.toString() === + getResolvedState()?._id?.toString() + ); + }, + )?.startsAt; + + if (!resolveTime) { + return ( + "Not yet " + (getResolvedState()?.name?.toLowerCase() || "resolved") + ); + } + + return OneUptimeDate.convertMinutesToDaysHoursAndMinutes( + OneUptimeDate.getDifferenceInMinutes(resolveTime, incidentStartTime), + ); + }; + + type GetInfoCardFunction = (value: string) => ReactElement; + + const getInfoCardValue: GetInfoCardFunction = ( + value: string, + ): ReactElement => { + return <div className="font-medium text-gray-900 text-lg">{value}</div>; + }; + + return ( + <Fragment> + {/* Incident View */} + <CardModelDetail<Incident> + name="Incident Details" + cardProps={{ + title: "Incident Details", + description: "Here are more details for this incident.", + }} + isEditable={true} + formSteps={[ + { + title: "Incident Details", + id: "incident-details", + }, + { + title: "Labels", + id: "labels", + }, + ]} + formFields={[ + { + field: { + title: true, + }, + title: "Incident Title", + stepId: "incident-details", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Incident Title", + validation: { + minLength: 2, + }, + }, + + { + field: { + incidentSeverity: true, + }, + title: "Incident Severity", + description: "What type of incident is this?", + fieldType: FormFieldSchemaType.Dropdown, + stepId: "incident-details", + dropdownModal: { + type: IncidentSeverity, + labelField: "name", + valueField: "_id", + }, + required: true, + placeholder: "Incident Severity", + }, + { + field: { + labels: true, + }, + title: "Labels ", + stepId: "labels", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + onBeforeFetch: async (): Promise<JSONObject> => { + // get ack incident. const incidentTimelines: ListResult<IncidentStateTimeline> = - await ModelAPI.getList({ - modelType: IncidentStateTimeline, - query: { - incidentId: modelId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - startsAt: true, - createdByUser: { - name: true, - email: true, - profilePictureId: true, - }, - incidentStateId: true, - }, - sort: { - startsAt: SortOrder.Ascending, - }, - }); + await ModelAPI.getList({ + modelType: IncidentStateTimeline, + query: { + incidentId: modelId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, - const incidentStates: ListResult<IncidentState> = - await ModelAPI.getList({ - modelType: IncidentState, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - name: true, - isAcknowledgedState: true, - isResolvedState: true, - }, - sort: {}, - }); + createdAt: true, + createdByUser: { + name: true, + email: true, + profilePictureId: true, + }, + incidentState: { + name: true, + isResolvedState: true, + isAcknowledgedState: true, + }, + }, + sort: {}, + }); - setIncidentStates(incidentStates.data as IncidentState[]); - setIncidentStateTimeline( - incidentTimelines.data as IncidentStateTimeline[] - ); - setError(''); - } catch (err) { - setError(BaseAPI.getFriendlyMessage(err)); - } + return incidentTimelines; + }, + showDetailsInNumberOfColumns: 2, + modelType: Incident, + id: "model-detail-incidents", + fields: [ + { + field: { + _id: true, + }, + title: "Incident ID", + fieldType: FieldType.ObjectID, + }, + { + field: { + title: true, + }, + title: "Incident Title", + fieldType: FieldType.Text, + }, - setIsLoading(false); - }; + { + field: { + currentIncidentState: { + color: true, + name: true, + }, + }, + title: "Current State", + fieldType: FieldType.Entity, + getElement: (item: Incident): ReactElement => { + if (!item["currentIncidentState"]) { + throw new BadDataException("Incident Status not found"); + } - useEffect(() => { - fetchData().catch((err: Error) => { - setError(BaseAPI.getFriendlyMessage(err)); - }); - }, []); - - if (isLoading) { - return <PageLoader isVisible={true} />; - } - - if (error) { - return <ErrorMessage error={error} />; - } - - type GetIncidentStateFunction = () => IncidentState | undefined; - - const getAcknowledgeState: GetIncidentStateFunction = (): - | IncidentState - | undefined => { - return incidentStates.find((state: IncidentState) => { - return state.isAcknowledgedState; - }); - }; - - const getResolvedState: GetIncidentStateFunction = (): - | IncidentState - | undefined => { - return incidentStates.find((state: IncidentState) => { - return state.isResolvedState; - }); - }; - - type getTimeFunction = () => string; - - const getTimeToAcknowledge: getTimeFunction = (): string => { - const incidentStartTime: Date = - incidentStateTimeline[0]?.startsAt || new Date(); - - const acknowledgeTime: Date | undefined = incidentStateTimeline.find( - (timeline: IncidentStateTimeline) => { return ( - timeline.incidentStateId?.toString() === - getAcknowledgeState()?._id?.toString() + <Pill + color={item.currentIncidentState.color || Black} + text={item.currentIncidentState.name || "Unknown"} + /> ); - } - )?.startsAt; + }, + }, + { + field: { + incidentSeverity: { + color: true, + name: true, + }, + }, + title: "Incident Severity", + fieldType: FieldType.Entity, + getElement: (item: Incident): ReactElement => { + if (!item["incidentSeverity"]) { + throw new BadDataException("Incident Severity not found"); + } - const resolveTime: Date | undefined = incidentStateTimeline.find( - (timeline: IncidentStateTimeline) => { return ( - timeline.incidentStateId?.toString() === - getResolvedState()?._id?.toString() + <Pill + color={item.incidentSeverity.color || Black} + text={item.incidentSeverity.name || "Unknown"} + /> ); - } - )?.startsAt; - - if (!acknowledgeTime && !resolveTime) { - return ( - 'Not yet ' + - (getAcknowledgeState()?.name?.toLowerCase() || 'acknowledged') - ); - } - - if (!acknowledgeTime && resolveTime) { - return OneUptimeDate.convertMinutesToDaysHoursAndMinutes( - OneUptimeDate.getDifferenceInMinutes( - resolveTime, - incidentStartTime - ) - ); - } - - return OneUptimeDate.convertMinutesToDaysHoursAndMinutes( - OneUptimeDate.getDifferenceInMinutes( - acknowledgeTime!, - incidentStartTime - ) - ); - }; - - const getTimeToResolve: getTimeFunction = (): string => { - const incidentStartTime: Date = - incidentStateTimeline[0]?.startsAt || new Date(); - - const resolveTime: Date | undefined = incidentStateTimeline.find( - (timeline: IncidentStateTimeline) => { + }, + }, + { + field: { + monitors: { + name: true, + _id: true, + }, + }, + title: "Monitors Affected", + fieldType: FieldType.Element, + getElement: (item: Incident): ReactElement => { + return <MonitorsElement monitors={item["monitors"] || []} />; + }, + }, + { + field: { + onCallDutyPolicies: { + name: true, + _id: true, + }, + }, + title: "On-Call Duty Policies", + fieldType: FieldType.Element, + getElement: (item: Incident): ReactElement => { return ( - timeline.incidentStateId?.toString() === - getResolvedState()?._id?.toString() + <OnCallDutyPoliciesView + onCallPolicies={item.onCallDutyPolicies || []} + /> ); - } - )?.startsAt; + }, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + fieldType: FieldType.DateTime, + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedOnIncidentCreated: true, + }, + title: "Notify Status Page Subscribers", + fieldType: FieldType.Boolean, + getElement: (item: Incident): ReactElement => { + return ( + <div className=""> + <CheckboxViewer + isChecked={ + item[ + "shouldStatusPageSubscribersBeNotifiedOnIncidentCreated" + ] as boolean + } + text={ + item[ + "shouldStatusPageSubscribersBeNotifiedOnIncidentCreated" + ] + ? "Subscribers Notified" + : "Subscribers Not Notified" + } + />{" "} + </div> + ); + }, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: Incident): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + _id: true, + }, + title: "Acknowledge Incident", + fieldType: FieldType.Element, + getElement: ( + _item: Incident, + onBeforeFetchData: JSONObject | undefined, + ): ReactElement => { + return ( + <ChangeIncidentState + incidentId={modelId} + incidentTimeline={ + onBeforeFetchData + ? (onBeforeFetchData["data"] as Array<BaseModel>) + : [] + } + incidentType={IncidentType.Ack} + onActionComplete={async () => { + await fetchData(); + }} + /> + ); + }, + }, + { + field: { + _id: true, + }, + title: "Resolve Incident", + fieldType: FieldType.Element, + getElement: ( + _item: Incident, + onBeforeFetchData: JSONObject | undefined, + ): ReactElement => { + return ( + <ChangeIncidentState + incidentId={modelId} + incidentTimeline={ + onBeforeFetchData + ? (onBeforeFetchData["data"] as Array<BaseModel>) + : [] + } + incidentType={IncidentType.Resolve} + onActionComplete={async () => { + GlobalEvent.dispatchEvent( + EventName.ACTIVE_INCIDENTS_COUNT_REFRESH, + ); + await fetchData(); + }} + /> + ); + }, + }, + ], + modelId: modelId, + }} + /> - if (!resolveTime) { - return ( - 'Not yet ' + - (getResolvedState()?.name?.toLowerCase() || 'resolved') - ); - } + <div className="flex space-x-5 mt-5 mb-5 w-full justify-between"> + <InfoCard + title={`${getAcknowledgeState()?.name || "Acknowledged"} in`} + value={getInfoCardValue(getTimeToAcknowledge())} + className="w-1/2" + /> + <InfoCard + title={`${getResolvedState()?.name || "Resolved"} in`} + value={getInfoCardValue(getTimeToResolve())} + className="w-1/2" + /> + </div> - return OneUptimeDate.convertMinutesToDaysHoursAndMinutes( - OneUptimeDate.getDifferenceInMinutes(resolveTime, incidentStartTime) - ); - }; + <CardModelDetail + name="Incident Description" + cardProps={{ + title: "Incident Description", + description: + "Description of this incident. This is visible on Status Page and is in markdown format.", + }} + editButtonText="Edit Incident Description" + isEditable={true} + formFields={[ + { + field: { + description: true, + }, + title: "Description", - type GetInfoCardFunction = (value: string) => ReactElement; + fieldType: FormFieldSchemaType.Markdown, + required: true, + placeholder: "Description", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: Incident, + id: "model-detail-incident-description", + fields: [ + { + field: { + description: true, + }, + title: "Description", + fieldType: FieldType.Markdown, + }, + ], + modelId: modelId, + }} + /> - const getInfoCardValue: GetInfoCardFunction = ( - value: string - ): ReactElement => { - return <div className="font-medium text-gray-900 text-lg">{value}</div>; - }; - - return ( - <Fragment> - {/* Incident View */} - <CardModelDetail<Incident> - name="Incident Details" - cardProps={{ - title: 'Incident Details', - description: 'Here are more details for this incident.', - }} - isEditable={true} - formSteps={[ - { - title: 'Incident Details', - id: 'incident-details', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - formFields={[ - { - field: { - title: true, - }, - title: 'Incident Title', - stepId: 'incident-details', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Incident Title', - validation: { - minLength: 2, - }, - }, - - { - field: { - incidentSeverity: true, - }, - title: 'Incident Severity', - description: 'What type of incident is this?', - fieldType: FormFieldSchemaType.Dropdown, - stepId: 'incident-details', - dropdownModal: { - type: IncidentSeverity, - labelField: 'name', - valueField: '_id', - }, - required: true, - placeholder: 'Incident Severity', - }, - { - field: { - labels: true, - }, - title: 'Labels ', - stepId: 'labels', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - onBeforeFetch: async (): Promise<JSONObject> => { - // get ack incident. - - const incidentTimelines: ListResult<IncidentStateTimeline> = - await ModelAPI.getList({ - modelType: IncidentStateTimeline, - query: { - incidentId: modelId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - - createdAt: true, - createdByUser: { - name: true, - email: true, - profilePictureId: true, - }, - incidentState: { - name: true, - isResolvedState: true, - isAcknowledgedState: true, - }, - }, - sort: {}, - }); - - return incidentTimelines; - }, - showDetailsInNumberOfColumns: 2, - modelType: Incident, - id: 'model-detail-incidents', - fields: [ - { - field: { - _id: true, - }, - title: 'Incident ID', - fieldType: FieldType.ObjectID, - }, - { - field: { - title: true, - }, - title: 'Incident Title', - fieldType: FieldType.Text, - }, - - { - field: { - currentIncidentState: { - color: true, - name: true, - }, - }, - title: 'Current State', - fieldType: FieldType.Entity, - getElement: (item: Incident): ReactElement => { - if (!item['currentIncidentState']) { - throw new BadDataException( - 'Incident Status not found' - ); - } - - return ( - <Pill - color={ - item.currentIncidentState.color || - Black - } - text={ - item.currentIncidentState.name || - 'Unknown' - } - /> - ); - }, - }, - { - field: { - incidentSeverity: { - color: true, - name: true, - }, - }, - title: 'Incident Severity', - fieldType: FieldType.Entity, - getElement: (item: Incident): ReactElement => { - if (!item['incidentSeverity']) { - throw new BadDataException( - 'Incident Severity not found' - ); - } - - return ( - <Pill - color={ - item.incidentSeverity.color || Black - } - text={ - item.incidentSeverity.name || - 'Unknown' - } - /> - ); - }, - }, - { - field: { - monitors: { - name: true, - _id: true, - }, - }, - title: 'Monitors Affected', - fieldType: FieldType.Element, - getElement: (item: Incident): ReactElement => { - return ( - <MonitorsElement - monitors={item['monitors'] || []} - /> - ); - }, - }, - { - field: { - onCallDutyPolicies: { - name: true, - _id: true, - }, - }, - title: 'On-Call Duty Policies', - fieldType: FieldType.Element, - getElement: (item: Incident): ReactElement => { - return ( - <OnCallDutyPoliciesView - onCallPolicies={ - item.onCallDutyPolicies || [] - } - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - fieldType: FieldType.DateTime, - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedOnIncidentCreated: - true, - }, - title: 'Notify Status Page Subscribers', - fieldType: FieldType.Boolean, - getElement: (item: Incident): ReactElement => { - return ( - <div className=""> - <CheckboxViewer - isChecked={ - item[ - 'shouldStatusPageSubscribersBeNotifiedOnIncidentCreated' - ] as boolean - } - text={ - item[ - 'shouldStatusPageSubscribersBeNotifiedOnIncidentCreated' - ] - ? 'Subscribers Notified' - : 'Subscribers Not Notified' - } - />{' '} - </div> - ); - }, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: (item: Incident): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - _id: true, - }, - title: 'Acknowledge Incident', - fieldType: FieldType.Element, - getElement: ( - _item: Incident, - onBeforeFetchData: JSONObject | undefined - ): ReactElement => { - return ( - <ChangeIncidentState - incidentId={modelId} - incidentTimeline={ - onBeforeFetchData - ? (onBeforeFetchData[ - 'data' - ] as Array<BaseModel>) - : [] - } - incidentType={IncidentType.Ack} - onActionComplete={async () => { - await fetchData(); - }} - /> - ); - }, - }, - { - field: { - _id: true, - }, - title: 'Resolve Incident', - fieldType: FieldType.Element, - getElement: ( - _item: Incident, - onBeforeFetchData: JSONObject | undefined - ): ReactElement => { - return ( - <ChangeIncidentState - incidentId={modelId} - incidentTimeline={ - onBeforeFetchData - ? (onBeforeFetchData[ - 'data' - ] as Array<BaseModel>) - : [] - } - incidentType={IncidentType.Resolve} - onActionComplete={async () => { - GlobalEvent.dispatchEvent( - EventName.ACTIVE_INCIDENTS_COUNT_REFRESH - ); - await fetchData(); - }} - /> - ); - }, - }, - ], - modelId: modelId, - }} - /> - - <div className="flex space-x-5 mt-5 mb-5 w-full justify-between"> - <InfoCard - title={`${ - getAcknowledgeState()?.name || 'Acknowledged' - } in`} - value={getInfoCardValue(getTimeToAcknowledge())} - className="w-1/2" - /> - <InfoCard - title={`${getResolvedState()?.name || 'Resolved'} in`} - value={getInfoCardValue(getTimeToResolve())} - className="w-1/2" - /> - </div> - - <CardModelDetail - name="Incident Description" - cardProps={{ - title: 'Incident Description', - description: - 'Description of this incident. This is visible on Status Page and is in markdown format.', - }} - editButtonText="Edit Incident Description" - isEditable={true} - formFields={[ - { - field: { - description: true, - }, - title: 'Description', - - fieldType: FormFieldSchemaType.Markdown, - required: true, - placeholder: 'Description', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: Incident, - id: 'model-detail-incident-description', - fields: [ - { - field: { - description: true, - }, - title: 'Description', - fieldType: FieldType.Markdown, - }, - ], - modelId: modelId, - }} - /> - - <CardModelDetail - name="Root Cause" - cardProps={{ - title: 'Root Cause', - description: - 'Why did this incident happen? Here is the root cause of this incident.', - }} - isEditable={false} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: Incident, - id: 'model-detail-incident-root-cause', - fields: [ - { - field: { - rootCause: true, - }, - title: '', - placeholder: - 'No root cause identified for this incident.', - fieldType: FieldType.Markdown, - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + <CardModelDetail + name="Root Cause" + cardProps={{ + title: "Root Cause", + description: + "Why did this incident happen? Here is the root cause of this incident.", + }} + isEditable={false} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: Incident, + id: "model-detail-incident-root-cause", + fields: [ + { + field: { + rootCause: true, + }, + title: "", + placeholder: "No root cause identified for this incident.", + fieldType: FieldType.Markdown, + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default IncidentView; diff --git a/Dashboard/src/Pages/Incidents/View/InternalNote.tsx b/Dashboard/src/Pages/Incidents/View/InternalNote.tsx index 0fa4023efc..25f7d228ef 100644 --- a/Dashboard/src/Pages/Incidents/View/InternalNote.tsx +++ b/Dashboard/src/Pages/Incidents/View/InternalNote.tsx @@ -1,339 +1,322 @@ -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import { ShowAs } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import AlignItem from 'CommonUI/src/Types/AlignItem'; -import API from 'CommonUI/src/Utils/API/API'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentInternalNote from 'Model/Models/IncidentInternalNote'; -import IncidentNoteTemplate from 'Model/Models/IncidentNoteTemplate'; -import User from 'Model/Models/User'; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import { ShowAs } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import AlignItem from "CommonUI/src/Types/AlignItem"; +import API from "CommonUI/src/Utils/API/API"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentInternalNote from "Model/Models/IncidentInternalNote"; +import IncidentNoteTemplate from "Model/Models/IncidentNoteTemplate"; +import User from "Model/Models/User"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const IncidentDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [incidentNoteTemplates, setIncidentNoteTemplates] = useState< - Array<IncidentNoteTemplate> - >([]); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); - const [showIncidentNoteTemplateModal, setShowIncidentNoteTemplateModal] = - useState<boolean>(false); - const [initialValuesForIncident, setInitialValuesForIncident] = - useState<JSONObject>({}); + const [incidentNoteTemplates, setIncidentNoteTemplates] = useState< + Array<IncidentNoteTemplate> + >([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [showIncidentNoteTemplateModal, setShowIncidentNoteTemplateModal] = + useState<boolean>(false); + const [initialValuesForIncident, setInitialValuesForIncident] = + useState<JSONObject>({}); - const fetchIncidentNoteTemplate: (id: ObjectID) => Promise<void> = async ( - id: ObjectID - ): Promise<void> => { - setError(''); - setIsLoading(true); + const fetchIncidentNoteTemplate: (id: ObjectID) => Promise<void> = async ( + id: ObjectID, + ): Promise<void> => { + setError(""); + setIsLoading(true); - try { - //fetch incident template + try { + //fetch incident template - const incidentNoteTemplate: IncidentNoteTemplate | null = - await ModelAPI.getItem<IncidentNoteTemplate>({ - modelType: IncidentNoteTemplate, - id, - select: { - note: true, - }, - }); + const incidentNoteTemplate: IncidentNoteTemplate | null = + await ModelAPI.getItem<IncidentNoteTemplate>({ + modelType: IncidentNoteTemplate, + id, + select: { + note: true, + }, + }); - if (incidentNoteTemplate) { - const initialValue: JSONObject = { - ...BaseModel.toJSONObject( - incidentNoteTemplate, - IncidentNoteTemplate - ), - }; - - setInitialValuesForIncident(initialValue); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - setShowIncidentNoteTemplateModal(false); - }; - - const fetchIncidentNoteTemplates: () => Promise<void> = - async (): Promise<void> => { - setError(''); - setIsLoading(true); - setInitialValuesForIncident({}); - - try { - const listResult: ListResult<IncidentNoteTemplate> = - await ModelAPI.getList<IncidentNoteTemplate>({ - modelType: IncidentNoteTemplate, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - templateName: true, - _id: true, - }, - sort: {}, - }); - - setIncidentNoteTemplates(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); + if (incidentNoteTemplate) { + const initialValue: JSONObject = { + ...BaseModel.toJSONObject(incidentNoteTemplate, IncidentNoteTemplate), }; - return ( - <Fragment> - <ModelTable<IncidentInternalNote> - modelType={IncidentInternalNote} - id="table-incident-internal-note" - showCreateForm={ - Object.keys(initialValuesForIncident).length > 0 - } - createInitialValues={initialValuesForIncident} - name="Monitor > Internal Note" - isDeleteable={true} - showViewIdButton={true} - isCreateable={true} - isEditable={true} - isViewable={false} - createEditModalWidth={ModalWidth.Large} - query={{ - incidentId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: IncidentInternalNote - ): Promise<IncidentInternalNote> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.incidentId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Private Notes', - description: 'Here are private notes for this incident.', - buttons: [ - { - title: 'Create from Template', - icon: IconProp.Template, - buttonStyle: ButtonStyleType.OUTLINE, - onClick: async (): Promise<void> => { - setShowIncidentNoteTemplateModal(true); - await fetchIncidentNoteTemplates(); - }, - }, - ], - }} - noItemsMessage={ - 'No private notes created for this incident so far.' - } - formFields={[ - { - field: { - note: true, - }, - title: 'Private Incident Note', - fieldType: FormFieldSchemaType.Markdown, - required: true, - description: - 'Add a private note to this incident here. This is private to your team and is not visible on Status Page. This is in Markdown.', - }, - ]} - showAs={ShowAs.List} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - createdByUser: true, - }, - type: FieldType.Entity, - title: 'Created By', - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - note: true, - }, - type: FieldType.Text, - title: 'Note', - }, - { - field: { - createdAt: true, - }, - type: FieldType.Date, - title: 'Created At', - }, - ]} - columns={[ - { - field: { - createdByUser: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: '', + setInitialValuesForIncident(initialValue); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - type: FieldType.Entity, + setIsLoading(false); + setShowIncidentNoteTemplateModal(false); + }; - getElement: ( - item: IncidentInternalNote - ): ReactElement => { - return ( - <UserElement - user={item['createdByUser']} - suffix={'wrote'} - usernameClassName={ - 'text-base text-gray-900' - } - suffixClassName={ - 'text-base text-gray-500 mt-1' - } - /> - ); - }, - }, - { - field: { - createdAt: true, - }, + const fetchIncidentNoteTemplates: () => Promise<void> = + async (): Promise<void> => { + setError(""); + setIsLoading(true); + setInitialValuesForIncident({}); - alignItem: AlignItem.Right, - title: '', - type: FieldType.DateTime, - contentClassName: - 'mt-1 whitespace-nowrap text-sm text-gray-600 sm:mt-0 sm:ml-3 text-right', - }, - { - field: { - note: true, - }, + try { + const listResult: ListResult<IncidentNoteTemplate> = + await ModelAPI.getList<IncidentNoteTemplate>({ + modelType: IncidentNoteTemplate, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + templateName: true, + _id: true, + }, + sort: {}, + }); - title: '', - type: FieldType.Markdown, - contentClassName: - '-mt-3 space-y-6 text-sm text-gray-800', - colSpan: 2, - }, - ]} - /> + setIncidentNoteTemplates(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - {incidentNoteTemplates.length === 0 && - showIncidentNoteTemplateModal && - !isLoading ? ( - <ConfirmModal - title={`No Incident Note Templates`} - description={`No incident note templates have been created yet. You can create these in Project Settings > Incident > Note Templates.`} - submitButtonText={'Close'} - onSubmit={() => { - return setShowIncidentNoteTemplateModal(false); - }} + setIsLoading(false); + }; + + return ( + <Fragment> + <ModelTable<IncidentInternalNote> + modelType={IncidentInternalNote} + id="table-incident-internal-note" + showCreateForm={Object.keys(initialValuesForIncident).length > 0} + createInitialValues={initialValuesForIncident} + name="Monitor > Internal Note" + isDeleteable={true} + showViewIdButton={true} + isCreateable={true} + isEditable={true} + isViewable={false} + createEditModalWidth={ModalWidth.Large} + query={{ + incidentId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: IncidentInternalNote, + ): Promise<IncidentInternalNote> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.incidentId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Private Notes", + description: "Here are private notes for this incident.", + buttons: [ + { + title: "Create from Template", + icon: IconProp.Template, + buttonStyle: ButtonStyleType.OUTLINE, + onClick: async (): Promise<void> => { + setShowIncidentNoteTemplateModal(true); + await fetchIncidentNoteTemplates(); + }, + }, + ], + }} + noItemsMessage={"No private notes created for this incident so far."} + formFields={[ + { + field: { + note: true, + }, + title: "Private Incident Note", + fieldType: FormFieldSchemaType.Markdown, + required: true, + description: + "Add a private note to this incident here. This is private to your team and is not visible on Status Page. This is in Markdown.", + }, + ]} + showAs={ShowAs.List} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + createdByUser: true, + }, + type: FieldType.Entity, + title: "Created By", + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + note: true, + }, + type: FieldType.Text, + title: "Note", + }, + { + field: { + createdAt: true, + }, + type: FieldType.Date, + title: "Created At", + }, + ]} + columns={[ + { + field: { + createdByUser: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "", + + type: FieldType.Entity, + + getElement: (item: IncidentInternalNote): ReactElement => { + return ( + <UserElement + user={item["createdByUser"]} + suffix={"wrote"} + usernameClassName={"text-base text-gray-900"} + suffixClassName={"text-base text-gray-500 mt-1"} /> - ) : ( - <></> - )} + ); + }, + }, + { + field: { + createdAt: true, + }, - {error ? ( - <ConfirmModal - title={`Error`} - description={`${error}`} - submitButtonText={'Close'} - onSubmit={() => { - return setError(''); - }} - /> - ) : ( - <></> - )} + alignItem: AlignItem.Right, + title: "", + type: FieldType.DateTime, + contentClassName: + "mt-1 whitespace-nowrap text-sm text-gray-600 sm:mt-0 sm:ml-3 text-right", + }, + { + field: { + note: true, + }, - {showIncidentNoteTemplateModal && - incidentNoteTemplates.length > 0 ? ( - <BasicFormModal<JSONObject> - title="Create Note from Template" - isLoading={isLoading} - submitButtonText="Create from Template" - onClose={() => { - setShowIncidentNoteTemplateModal(false); - setIsLoading(false); - }} - onSubmit={async (data: JSONObject) => { - await fetchIncidentNoteTemplate( - data['incidentNoteTemplateId'] as ObjectID - ); - }} - formProps={{ - initialValues: {}, - fields: [ - { - field: { - incidentNoteTemplateId: true, - }, - title: 'Select Note Template', - description: - 'Select a template to create a note from.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEntityArray( - { - array: incidentNoteTemplates, - labelField: 'templateName', - valueField: '_id', - } - ), - required: true, - placeholder: 'Select Template', - }, - ], - }} - /> - ) : ( - <> </> - )} - </Fragment> - ); + title: "", + type: FieldType.Markdown, + contentClassName: "-mt-3 space-y-6 text-sm text-gray-800", + colSpan: 2, + }, + ]} + /> + + {incidentNoteTemplates.length === 0 && + showIncidentNoteTemplateModal && + !isLoading ? ( + <ConfirmModal + title={`No Incident Note Templates`} + description={`No incident note templates have been created yet. You can create these in Project Settings > Incident > Note Templates.`} + submitButtonText={"Close"} + onSubmit={() => { + return setShowIncidentNoteTemplateModal(false); + }} + /> + ) : ( + <></> + )} + + {error ? ( + <ConfirmModal + title={`Error`} + description={`${error}`} + submitButtonText={"Close"} + onSubmit={() => { + return setError(""); + }} + /> + ) : ( + <></> + )} + + {showIncidentNoteTemplateModal && incidentNoteTemplates.length > 0 ? ( + <BasicFormModal<JSONObject> + title="Create Note from Template" + isLoading={isLoading} + submitButtonText="Create from Template" + onClose={() => { + setShowIncidentNoteTemplateModal(false); + setIsLoading(false); + }} + onSubmit={async (data: JSONObject) => { + await fetchIncidentNoteTemplate( + data["incidentNoteTemplateId"] as ObjectID, + ); + }} + formProps={{ + initialValues: {}, + fields: [ + { + field: { + incidentNoteTemplateId: true, + }, + title: "Select Note Template", + description: "Select a template to create a note from.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: DropdownUtil.getDropdownOptionsFromEntityArray( + { + array: incidentNoteTemplates, + labelField: "templateName", + valueField: "_id", + }, + ), + required: true, + placeholder: "Select Template", + }, + ], + }} + /> + ) : ( + <> </> + )} + </Fragment> + ); }; export default IncidentDelete; diff --git a/Dashboard/src/Pages/Incidents/View/Layout.tsx b/Dashboard/src/Pages/Incidents/View/Layout.tsx index 7d6a8ac28b..2c37058b2e 100644 --- a/Dashboard/src/Pages/Incidents/View/Layout.tsx +++ b/Dashboard/src/Pages/Incidents/View/Layout.tsx @@ -1,32 +1,32 @@ -import { getIncidentsBreadcrumbs } from '../../../Utils/Breadcrumbs/IncidentBreadcrumbs'; -import { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Incident from 'Model/Models/Incident'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getIncidentsBreadcrumbs } from "../../../Utils/Breadcrumbs/IncidentBreadcrumbs"; +import { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Incident from "Model/Models/Incident"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const IncidentViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="Incident" - modelType={Incident} - modelId={modelId} - modelNameField="title" - breadcrumbLinks={getIncidentsBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <ModelPage + title="Incident" + modelType={Incident} + modelId={modelId} + modelNameField="title" + breadcrumbLinks={getIncidentsBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default IncidentViewLayout; diff --git a/Dashboard/src/Pages/Incidents/View/Owners.tsx b/Dashboard/src/Pages/Incidents/View/Owners.tsx index 9b8eb11ba4..5175dcdd4c 100644 --- a/Dashboard/src/Pages/Incidents/View/Owners.tsx +++ b/Dashboard/src/Pages/Incidents/View/Owners.tsx @@ -1,231 +1,226 @@ -import TeamElement from '../../../Components/Team/Team'; -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentOwnerTeam from 'Model/Models/IncidentOwnerTeam'; -import IncidentOwnerUser from 'Model/Models/IncidentOwnerUser'; -import Team from 'Model/Models/Team'; -import User from 'Model/Models/User'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import TeamElement from "../../../Components/Team/Team"; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentOwnerTeam from "Model/Models/IncidentOwnerTeam"; +import IncidentOwnerUser from "Model/Models/IncidentOwnerUser"; +import Team from "Model/Models/Team"; +import User from "Model/Models/User"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const IncidentOwners: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<IncidentOwnerTeam> - modelType={IncidentOwnerTeam} - id="table-incident-owner-team" - name="Incident > Owner Team" - singularName="Team" - isDeleteable={true} - createVerb={'Add'} - isCreateable={true} - isViewable={false} - showViewIdButton={true} - query={{ - incidentId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: IncidentOwnerTeam - ): Promise<IncidentOwnerTeam> => { - item.incidentId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Teams)', - description: - 'Here is list of teams that own this incident. They will be alerted when this incident is created or updated.', - }} - noItemsMessage={ - 'No teams associated with this incident so far.' - } - formFields={[ - { - field: { - team: true, - }, - title: 'Team', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select Team', - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - team: true, - }, - type: FieldType.Entity, - title: 'Team', - filterEntityType: Team, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - team: { - name: true, - }, - }, - title: 'Team', - type: FieldType.Entity, + return ( + <Fragment> + <ModelTable<IncidentOwnerTeam> + modelType={IncidentOwnerTeam} + id="table-incident-owner-team" + name="Incident > Owner Team" + singularName="Team" + isDeleteable={true} + createVerb={"Add"} + isCreateable={true} + isViewable={false} + showViewIdButton={true} + query={{ + incidentId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: IncidentOwnerTeam, + ): Promise<IncidentOwnerTeam> => { + item.incidentId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Teams)", + description: + "Here is list of teams that own this incident. They will be alerted when this incident is created or updated.", + }} + noItemsMessage={"No teams associated with this incident so far."} + formFields={[ + { + field: { + team: true, + }, + title: "Team", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select Team", + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + team: true, + }, + type: FieldType.Entity, + title: "Team", + filterEntityType: Team, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + team: { + name: true, + }, + }, + title: "Team", + type: FieldType.Entity, - getElement: (item: IncidentOwnerTeam): ReactElement => { - if (!item['team']) { - throw new BadDataException('Team not found'); - } + getElement: (item: IncidentOwnerTeam): ReactElement => { + if (!item["team"]) { + throw new BadDataException("Team not found"); + } - return <TeamElement team={item['team'] as Team} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> + return <TeamElement team={item["team"] as Team} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> - <ModelTable<IncidentOwnerUser> - modelType={IncidentOwnerUser} - id="table-incident-owner-team" - name="Incident > Owner Team" - isDeleteable={true} - singularName="User" - isCreateable={true} - isViewable={false} - showViewIdButton={true} - createVerb={'Add'} - query={{ - incidentId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: IncidentOwnerUser - ): Promise<IncidentOwnerUser> => { - item.incidentId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Users)', - description: - 'Here is list of users that own this incident. They will be alerted when this incident is created or updated.', - }} - noItemsMessage={ - 'No users associated with this incident so far.' - } - formFields={[ - { - field: { - user: true, - }, - title: 'User', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select User', - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - user: true, - }, - title: 'User', - type: FieldType.Entity, - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - user: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: 'User', - type: FieldType.Entity, + <ModelTable<IncidentOwnerUser> + modelType={IncidentOwnerUser} + id="table-incident-owner-team" + name="Incident > Owner Team" + isDeleteable={true} + singularName="User" + isCreateable={true} + isViewable={false} + showViewIdButton={true} + createVerb={"Add"} + query={{ + incidentId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: IncidentOwnerUser, + ): Promise<IncidentOwnerUser> => { + item.incidentId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Users)", + description: + "Here is list of users that own this incident. They will be alerted when this incident is created or updated.", + }} + noItemsMessage={"No users associated with this incident so far."} + formFields={[ + { + field: { + user: true, + }, + title: "User", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select User", + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + user: true, + }, + title: "User", + type: FieldType.Entity, + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + user: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "User", + type: FieldType.Entity, - getElement: (item: IncidentOwnerUser): ReactElement => { - if (!item['user']) { - throw new BadDataException('User not found'); - } + getElement: (item: IncidentOwnerUser): ReactElement => { + if (!item["user"]) { + throw new BadDataException("User not found"); + } - return <UserElement user={item['user'] as User} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + return <UserElement user={item["user"] as User} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default IncidentOwners; diff --git a/Dashboard/src/Pages/Incidents/View/PublicNote.tsx b/Dashboard/src/Pages/Incidents/View/PublicNote.tsx index 495295cf33..5f0d0ed1b4 100644 --- a/Dashboard/src/Pages/Incidents/View/PublicNote.tsx +++ b/Dashboard/src/Pages/Incidents/View/PublicNote.tsx @@ -1,401 +1,377 @@ -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import CheckboxViewer from 'CommonUI/src/Components/Checkbox/CheckboxViewer'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import { ShowAs } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import AlignItem from 'CommonUI/src/Types/AlignItem'; -import API from 'CommonUI/src/Utils/API/API'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentNoteTemplate from 'Model/Models/IncidentNoteTemplate'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import User from 'Model/Models/User'; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import CheckboxViewer from "CommonUI/src/Components/Checkbox/CheckboxViewer"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import { ShowAs } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import AlignItem from "CommonUI/src/Types/AlignItem"; +import API from "CommonUI/src/Utils/API/API"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentNoteTemplate from "Model/Models/IncidentNoteTemplate"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import User from "Model/Models/User"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const PublicNote: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [incidentNoteTemplates, setIncidentNoteTemplates] = useState< - Array<IncidentNoteTemplate> - >([]); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); - const [showIncidentNoteTemplateModal, setShowIncidentNoteTemplateModal] = - useState<boolean>(false); - const [initialValuesForIncident, setInitialValuesForIncident] = - useState<JSONObject>({}); + const [incidentNoteTemplates, setIncidentNoteTemplates] = useState< + Array<IncidentNoteTemplate> + >([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [showIncidentNoteTemplateModal, setShowIncidentNoteTemplateModal] = + useState<boolean>(false); + const [initialValuesForIncident, setInitialValuesForIncident] = + useState<JSONObject>({}); - const fetchIncidentNoteTemplate: (id: ObjectID) => Promise<void> = async ( - id: ObjectID - ): Promise<void> => { - setError(''); - setIsLoading(true); + const fetchIncidentNoteTemplate: (id: ObjectID) => Promise<void> = async ( + id: ObjectID, + ): Promise<void> => { + setError(""); + setIsLoading(true); - try { - //fetch incident template + try { + //fetch incident template - const incidentNoteTemplate: IncidentNoteTemplate | null = - await ModelAPI.getItem<IncidentNoteTemplate>({ - modelType: IncidentNoteTemplate, - id, - select: { - note: true, - }, - }); + const incidentNoteTemplate: IncidentNoteTemplate | null = + await ModelAPI.getItem<IncidentNoteTemplate>({ + modelType: IncidentNoteTemplate, + id, + select: { + note: true, + }, + }); - if (incidentNoteTemplate) { - const initialValue: JSONObject = { - ...BaseModel.toJSONObject( - incidentNoteTemplate, - IncidentNoteTemplate - ), - }; - - setInitialValuesForIncident(initialValue); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - setShowIncidentNoteTemplateModal(false); - }; - - const fetchIncidentNoteTemplates: () => Promise<void> = - async (): Promise<void> => { - setError(''); - setIsLoading(true); - setInitialValuesForIncident({}); - - try { - const listResult: ListResult<IncidentNoteTemplate> = - await ModelAPI.getList<IncidentNoteTemplate>({ - modelType: IncidentNoteTemplate, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - templateName: true, - _id: true, - }, - sort: {}, - }); - - setIncidentNoteTemplates(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); + if (incidentNoteTemplate) { + const initialValue: JSONObject = { + ...BaseModel.toJSONObject(incidentNoteTemplate, IncidentNoteTemplate), }; - return ( - <Fragment> - <ModelTable<IncidentPublicNote> - modelType={IncidentPublicNote} - id="table-incident-internal-note" - name="Monitor > Public Note" - isDeleteable={true} - showCreateForm={ - Object.keys(initialValuesForIncident).length > 0 - } - createInitialValues={initialValuesForIncident} - isCreateable={true} - showViewIdButton={true} - isEditable={true} - createEditModalWidth={ModalWidth.Large} - isViewable={false} - query={{ - incidentId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: IncidentPublicNote - ): Promise<IncidentPublicNote> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); + setInitialValuesForIncident(initialValue); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + setShowIncidentNoteTemplateModal(false); + }; + + const fetchIncidentNoteTemplates: () => Promise<void> = + async (): Promise<void> => { + setError(""); + setIsLoading(true); + setInitialValuesForIncident({}); + + try { + const listResult: ListResult<IncidentNoteTemplate> = + await ModelAPI.getList<IncidentNoteTemplate>({ + modelType: IncidentNoteTemplate, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + templateName: true, + _id: true, + }, + sort: {}, + }); + + setIncidentNoteTemplates(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + return ( + <Fragment> + <ModelTable<IncidentPublicNote> + modelType={IncidentPublicNote} + id="table-incident-internal-note" + name="Monitor > Public Note" + isDeleteable={true} + showCreateForm={Object.keys(initialValuesForIncident).length > 0} + createInitialValues={initialValuesForIncident} + isCreateable={true} + showViewIdButton={true} + isEditable={true} + createEditModalWidth={ModalWidth.Large} + isViewable={false} + query={{ + incidentId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: IncidentPublicNote, + ): Promise<IncidentPublicNote> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.incidentId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Public Notes", + buttons: [ + { + title: "Create from Template", + icon: IconProp.Template, + buttonStyle: ButtonStyleType.OUTLINE, + onClick: async (): Promise<void> => { + setShowIncidentNoteTemplateModal(true); + await fetchIncidentNoteTemplates(); + }, + }, + ], + description: + "Here are public notes for this incident. This will show up on the status page.", + }} + noItemsMessage={"No public notes created for this incident so far."} + formFields={[ + { + field: { + note: true, + }, + title: "Public Incident Note", + fieldType: FormFieldSchemaType.Markdown, + required: true, + description: + "This note is visible on your Status Page. This is in Markdown.", + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true, + }, + + title: "Notify Status Page Subscribers", + stepId: "more", + description: "Should status page subscribers be notified?", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + }, + { + field: { + postedAt: true, + }, + title: "Posted At", + fieldType: FormFieldSchemaType.DateTime, + required: true, + description: + "This is the date and time this note was posted. This is in " + + OneUptimeDate.getCurrentTimezoneString() + + ".", + defaultValue: OneUptimeDate.getCurrentDate(), + }, + ]} + showAs={ShowAs.List} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + createdByUser: true, + }, + type: FieldType.Entity, + title: "Created By", + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + note: true, + }, + type: FieldType.Text, + title: "Note", + }, + { + field: { + createdAt: true, + }, + type: FieldType.Date, + title: "Created At", + }, + ]} + columns={[ + { + field: { + createdByUser: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "", + + type: FieldType.Entity, + + getElement: (item: IncidentPublicNote): ReactElement => { + return ( + <UserElement + user={item["createdByUser"]} + suffix={"wrote"} + usernameClassName={"text-base text-gray-900"} + suffixClassName={"text-base text-gray-500 mt-1"} + /> + ); + }, + }, + { + field: { + postedAt: true, + }, + + alignItem: AlignItem.Right, + title: "", + type: FieldType.DateTime, + contentClassName: + "mt-1 whitespace-nowrap text-sm text-gray-600 sm:mt-0 sm:ml-3 text-right", + }, + + { + field: { + note: true, + }, + + title: "", + type: FieldType.Markdown, + contentClassName: "-mt-3 space-y-1 text-sm text-gray-800", + colSpan: 2, + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true, + }, + title: "", + type: FieldType.Boolean, + colSpan: 2, + getElement: (item: IncidentPublicNote): ReactElement => { + return ( + <div className="-mt-5"> + <CheckboxViewer + isChecked={ + item[ + "shouldStatusPageSubscribersBeNotifiedOnNoteCreated" + ] as boolean } - item.incidentId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Public Notes', - buttons: [ - { - title: 'Create from Template', - icon: IconProp.Template, - buttonStyle: ButtonStyleType.OUTLINE, - onClick: async (): Promise<void> => { - setShowIncidentNoteTemplateModal(true); - await fetchIncidentNoteTemplates(); - }, - }, - ], - description: - 'Here are public notes for this incident. This will show up on the status page.', - }} - noItemsMessage={ - 'No public notes created for this incident so far.' - } - formFields={[ - { - field: { - note: true, - }, - title: 'Public Incident Note', - fieldType: FormFieldSchemaType.Markdown, - required: true, - description: - 'This note is visible on your Status Page. This is in Markdown.', - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedOnNoteCreated: - true, - }, + text={ + item["shouldStatusPageSubscribersBeNotifiedOnNoteCreated"] + ? "Status Page Subscribers Notified" + : "Status Page Subscribers Not Notified" + } + />{" "} + </div> + ); + }, + }, + ]} + /> - title: 'Notify Status Page Subscribers', - stepId: 'more', - description: - 'Should status page subscribers be notified?', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - }, - { - field: { - postedAt: true, - }, - title: 'Posted At', - fieldType: FormFieldSchemaType.DateTime, - required: true, - description: - 'This is the date and time this note was posted. This is in ' + - OneUptimeDate.getCurrentTimezoneString() + - '.', - defaultValue: OneUptimeDate.getCurrentDate(), - }, - ]} - showAs={ShowAs.List} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - createdByUser: true, - }, - type: FieldType.Entity, - title: 'Created By', - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - note: true, - }, - type: FieldType.Text, - title: 'Note', - }, - { - field: { - createdAt: true, - }, - type: FieldType.Date, - title: 'Created At', - }, - ]} - columns={[ - { - field: { - createdByUser: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: '', + {incidentNoteTemplates.length === 0 && + showIncidentNoteTemplateModal && + !isLoading ? ( + <ConfirmModal + title={`No Incident Note Templates`} + description={`No incident note templates have been created yet. You can create these in Project Settings > Incident > Note Templates.`} + submitButtonText={"Close"} + onSubmit={() => { + return setShowIncidentNoteTemplateModal(false); + }} + /> + ) : ( + <></> + )} - type: FieldType.Entity, + {error ? ( + <ConfirmModal + title={`Error`} + description={`${error}`} + submitButtonText={"Close"} + onSubmit={() => { + return setError(""); + }} + /> + ) : ( + <></> + )} - getElement: ( - item: IncidentPublicNote - ): ReactElement => { - return ( - <UserElement - user={item['createdByUser']} - suffix={'wrote'} - usernameClassName={ - 'text-base text-gray-900' - } - suffixClassName={ - 'text-base text-gray-500 mt-1' - } - /> - ); - }, - }, - { - field: { - postedAt: true, - }, - - alignItem: AlignItem.Right, - title: '', - type: FieldType.DateTime, - contentClassName: - 'mt-1 whitespace-nowrap text-sm text-gray-600 sm:mt-0 sm:ml-3 text-right', - }, - - { - field: { - note: true, - }, - - title: '', - type: FieldType.Markdown, - contentClassName: - '-mt-3 space-y-1 text-sm text-gray-800', - colSpan: 2, - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedOnNoteCreated: - true, - }, - title: '', - type: FieldType.Boolean, - colSpan: 2, - getElement: ( - item: IncidentPublicNote - ): ReactElement => { - return ( - <div className="-mt-5"> - <CheckboxViewer - isChecked={ - item[ - 'shouldStatusPageSubscribersBeNotifiedOnNoteCreated' - ] as boolean - } - text={ - item[ - 'shouldStatusPageSubscribersBeNotifiedOnNoteCreated' - ] - ? 'Status Page Subscribers Notified' - : 'Status Page Subscribers Not Notified' - } - />{' '} - </div> - ); - }, - }, - ]} - /> - - {incidentNoteTemplates.length === 0 && - showIncidentNoteTemplateModal && - !isLoading ? ( - <ConfirmModal - title={`No Incident Note Templates`} - description={`No incident note templates have been created yet. You can create these in Project Settings > Incident > Note Templates.`} - submitButtonText={'Close'} - onSubmit={() => { - return setShowIncidentNoteTemplateModal(false); - }} - /> - ) : ( - <></> - )} - - {error ? ( - <ConfirmModal - title={`Error`} - description={`${error}`} - submitButtonText={'Close'} - onSubmit={() => { - return setError(''); - }} - /> - ) : ( - <></> - )} - - {showIncidentNoteTemplateModal && - incidentNoteTemplates.length > 0 ? ( - <BasicFormModal<JSONObject> - title="Create Note from Template" - isLoading={isLoading} - submitButtonText="Create from Template" - onClose={() => { - setShowIncidentNoteTemplateModal(false); - setIsLoading(false); - }} - onSubmit={async (data: JSONObject) => { - await fetchIncidentNoteTemplate( - data['incidentNoteTemplateId'] as ObjectID - ); - }} - formProps={{ - initialValues: {}, - fields: [ - { - field: { - incidentNoteTemplateId: true, - }, - title: 'Select Note Template', - description: - 'Select a template to create a note from.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEntityArray( - { - array: incidentNoteTemplates, - labelField: 'templateName', - valueField: '_id', - } - ), - required: true, - placeholder: 'Select Template', - }, - ], - }} - /> - ) : ( - <> </> - )} - </Fragment> - ); + {showIncidentNoteTemplateModal && incidentNoteTemplates.length > 0 ? ( + <BasicFormModal<JSONObject> + title="Create Note from Template" + isLoading={isLoading} + submitButtonText="Create from Template" + onClose={() => { + setShowIncidentNoteTemplateModal(false); + setIsLoading(false); + }} + onSubmit={async (data: JSONObject) => { + await fetchIncidentNoteTemplate( + data["incidentNoteTemplateId"] as ObjectID, + ); + }} + formProps={{ + initialValues: {}, + fields: [ + { + field: { + incidentNoteTemplateId: true, + }, + title: "Select Note Template", + description: "Select a template to create a note from.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: DropdownUtil.getDropdownOptionsFromEntityArray( + { + array: incidentNoteTemplates, + labelField: "templateName", + valueField: "_id", + }, + ), + required: true, + placeholder: "Select Template", + }, + ], + }} + /> + ) : ( + <> </> + )} + </Fragment> + ); }; export default PublicNote; diff --git a/Dashboard/src/Pages/Incidents/View/SideMenu.tsx b/Dashboard/src/Pages/Incidents/View/SideMenu.tsx index 28bafda509..9af61cbcc9 100644 --- a/Dashboard/src/Pages/Incidents/View/SideMenu.tsx +++ b/Dashboard/src/Pages/Incidents/View/SideMenu.tsx @@ -1,109 +1,105 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENT_VIEW] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> - <SideMenuItem - link={{ - title: 'State Timeline', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.INCIDENT_VIEW_STATE_TIMELINE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.List} - /> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENT_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> + <SideMenuItem + link={{ + title: "State Timeline", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENT_VIEW_STATE_TIMELINE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.List} + /> - <SideMenuItem - link={{ - title: 'Owners', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENT_VIEW_OWNERS] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Team} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Owners", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENT_VIEW_OWNERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Team} + /> + </SideMenuSection> - <SideMenuSection title="Incident Notes"> - <SideMenuItem - link={{ - title: 'Private Notes', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Lock} - /> - <SideMenuItem - link={{ - title: 'Public Notes', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENT_PUBLIC_NOTE] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Public} - /> - </SideMenuSection> + <SideMenuSection title="Incident Notes"> + <SideMenuItem + link={{ + title: "Private Notes", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Lock} + /> + <SideMenuItem + link={{ + title: "Public Notes", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENT_PUBLIC_NOTE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Public} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.INCIDENT_VIEW_CUSTOM_FIELDS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.TableCells} - /> + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENT_VIEW_CUSTOM_FIELDS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.TableCells} + /> - <SideMenuItem - link={{ - title: 'Delete Incident', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.INCIDENT_VIEW_DELETE] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuItem + link={{ + title: "Delete Incident", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.INCIDENT_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Incidents/View/StateTimeline.tsx b/Dashboard/src/Pages/Incidents/View/StateTimeline.tsx index 2496919957..f9ab2d9c10 100644 --- a/Dashboard/src/Pages/Incidents/View/StateTimeline.tsx +++ b/Dashboard/src/Pages/Incidents/View/StateTimeline.tsx @@ -1,300 +1,280 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import MarkdownViewer from 'CommonUI/src/Components/Markdown.tsx/MarkdownViewer'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import Modal, { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import SimpleLogViewer from 'CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import MarkdownViewer from "CommonUI/src/Components/Markdown.tsx/MarkdownViewer"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import Modal, { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import SimpleLogViewer from "CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const IncidentViewStateTimeline: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); - const [logs, setLogs] = useState<string>(''); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); + const [logs, setLogs] = useState<string>(""); - const [showRootCause, setShowRootCause] = useState<boolean>(false); - const [rootCause, setRootCause] = useState<string>(''); + const [showRootCause, setShowRootCause] = useState<boolean>(false); + const [rootCause, setRootCause] = useState<string>(""); - return ( - <Fragment> - <ModelTable<IncidentStateTimeline> - modelType={IncidentStateTimeline} - id="table-incident-status-timeline" - name="Monitor > State Timeline" - isEditable={false} - isDeleteable={true} - isCreateable={true} - isViewable={false} - showViewIdButton={true} - query={{ - incidentId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - selectMoreFields={{ - stateChangeLog: true, - rootCause: true, - }} - actionButtons={[ - { - title: 'View Cause', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.TransparentCube, - onClick: async ( - item: IncidentStateTimeline, - onCompleteAction: VoidFunction - ) => { - setRootCause( - item['rootCause'] - ? item['rootCause'].toString() - : 'No root cause identified.' - ); - setShowRootCause(true); + return ( + <Fragment> + <ModelTable<IncidentStateTimeline> + modelType={IncidentStateTimeline} + id="table-incident-status-timeline" + name="Monitor > State Timeline" + isEditable={false} + isDeleteable={true} + isCreateable={true} + isViewable={false} + showViewIdButton={true} + query={{ + incidentId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + selectMoreFields={{ + stateChangeLog: true, + rootCause: true, + }} + actionButtons={[ + { + title: "View Cause", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.TransparentCube, + onClick: async ( + item: IncidentStateTimeline, + onCompleteAction: VoidFunction, + ) => { + setRootCause( + item["rootCause"] + ? item["rootCause"].toString() + : "No root cause identified.", + ); + setShowRootCause(true); - onCompleteAction(); - }, - }, - { - title: 'View Logs', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.List, - onClick: async ( - item: IncidentStateTimeline, - onCompleteAction: VoidFunction - ) => { - setLogs( - item['stateChangeLog'] - ? JSON.stringify( - item['stateChangeLog'], - null, - 2 - ) - : 'No logs for this state event.' - ); - setShowViewLogsModal(true); + onCompleteAction(); + }, + }, + { + title: "View Logs", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.List, + onClick: async ( + item: IncidentStateTimeline, + onCompleteAction: VoidFunction, + ) => { + setLogs( + item["stateChangeLog"] + ? JSON.stringify(item["stateChangeLog"], null, 2) + : "No logs for this state event.", + ); + setShowViewLogsModal(true); - onCompleteAction(); - }, - }, - ]} - onBeforeCreate={( - item: IncidentStateTimeline - ): Promise<IncidentStateTimeline> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.incidentId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Status Timeline', - description: - 'Here is the status timeline for this incident', - }} - noItemsMessage={ - 'No status timeline created for this incident so far.' - } - formFields={[ - { - field: { - incidentState: true, - }, - title: 'Incident Status', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Incident Status', - dropdownModal: { - type: IncidentState, - labelField: 'name', - valueField: '_id', - }, - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, + onCompleteAction(); + }, + }, + ]} + onBeforeCreate={( + item: IncidentStateTimeline, + ): Promise<IncidentStateTimeline> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.incidentId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Status Timeline", + description: "Here is the status timeline for this incident", + }} + noItemsMessage={"No status timeline created for this incident so far."} + formFields={[ + { + field: { + incidentState: true, + }, + title: "Incident Status", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Incident Status", + dropdownModal: { + type: IncidentState, + labelField: "name", + valueField: "_id", + }, + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, - title: 'Notify Status Page Subscribers', - description: - 'Should status page subscribers be notified?', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - incidentState: { - name: true, - }, - }, - title: 'Incident State', - type: FieldType.Entity, - filterEntityType: IncidentState, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Starts At', - type: FieldType.Date, - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - incidentState: { - name: true, - color: true, - }, - }, - title: 'Incident Status', - type: FieldType.Text, + title: "Notify Status Page Subscribers", + description: "Should status page subscribers be notified?", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + incidentState: { + name: true, + }, + }, + title: "Incident State", + type: FieldType.Entity, + filterEntityType: IncidentState, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Starts At", + type: FieldType.Date, + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + incidentState: { + name: true, + color: true, + }, + }, + title: "Incident Status", + type: FieldType.Text, - getElement: ( - item: IncidentStateTimeline - ): ReactElement => { - if (!item['incidentState']) { - throw new BadDataException( - 'Incident Status not found' - ); - } + getElement: (item: IncidentStateTimeline): ReactElement => { + if (!item["incidentState"]) { + throw new BadDataException("Incident Status not found"); + } - return ( - <Pill - color={ - item['incidentState']['color'] as Color - } - text={ - item['incidentState']['name'] as string - } - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Starts At', - type: FieldType.DateTime, - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - type: FieldType.DateTime, - noValueMessage: 'Currently Active', - }, - { - field: { - endsAt: true, - }, - title: 'Duration', - type: FieldType.Text, - getElement: ( - item: IncidentStateTimeline - ): ReactElement => { - return ( - <p> - {OneUptimeDate.differenceBetweenTwoDatesAsFromattedString( - item['createdAt'] as Date, - (item['endsAt'] as Date) || - OneUptimeDate.getCurrentDate() - )} - </p> - ); - }, - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, - title: 'Subscribers Notified', - type: FieldType.Boolean, - }, - ]} - /> - {showViewLogsModal ? ( - <Modal - title={'Why did the status change?'} - description="Here is more information about why the status changed for this monitor." - isLoading={false} - modalWidth={ModalWidth.Large} - onSubmit={() => { - setShowViewLogsModal(false); - }} - submitButtonText={'Close'} - submitButtonStyleType={ButtonStyleType.NORMAL} - > - <SimpleLogViewer> - {logs.split('\n').map((log: string, i: number) => { - return <div key={i}>{log}</div>; - })} - </SimpleLogViewer> - </Modal> - ) : ( - <></> - )} - - {showRootCause ? ( - <ConfirmModal - title={'Root Cause'} - description={ - <div> - <MarkdownViewer text={rootCause} /> - </div> - } - isLoading={false} - onSubmit={() => { - setShowRootCause(false); - }} - submitButtonText={'Close'} - submitButtonType={ButtonStyleType.NORMAL} + return ( + <Pill + color={item["incidentState"]["color"] as Color} + text={item["incidentState"]["name"] as string} /> - ) : ( - <></> - )} - </Fragment> - ); + ); + }, + }, + { + field: { + createdAt: true, + }, + title: "Starts At", + type: FieldType.DateTime, + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + type: FieldType.DateTime, + noValueMessage: "Currently Active", + }, + { + field: { + endsAt: true, + }, + title: "Duration", + type: FieldType.Text, + getElement: (item: IncidentStateTimeline): ReactElement => { + return ( + <p> + {OneUptimeDate.differenceBetweenTwoDatesAsFromattedString( + item["createdAt"] as Date, + (item["endsAt"] as Date) || OneUptimeDate.getCurrentDate(), + )} + </p> + ); + }, + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, + title: "Subscribers Notified", + type: FieldType.Boolean, + }, + ]} + /> + {showViewLogsModal ? ( + <Modal + title={"Why did the status change?"} + description="Here is more information about why the status changed for this monitor." + isLoading={false} + modalWidth={ModalWidth.Large} + onSubmit={() => { + setShowViewLogsModal(false); + }} + submitButtonText={"Close"} + submitButtonStyleType={ButtonStyleType.NORMAL} + > + <SimpleLogViewer> + {logs.split("\n").map((log: string, i: number) => { + return <div key={i}>{log}</div>; + })} + </SimpleLogViewer> + </Modal> + ) : ( + <></> + )} + + {showRootCause ? ( + <ConfirmModal + title={"Root Cause"} + description={ + <div> + <MarkdownViewer text={rootCause} /> + </div> + } + isLoading={false} + onSubmit={() => { + setShowRootCause(false); + }} + submitButtonText={"Close"} + submitButtonType={ButtonStyleType.NORMAL} + /> + ) : ( + <></> + )} + </Fragment> + ); }; export default IncidentViewStateTimeline; diff --git a/Dashboard/src/Pages/Init/Init.tsx b/Dashboard/src/Pages/Init/Init.tsx index ed208bfc07..a9a7235971 100644 --- a/Dashboard/src/Pages/Init/Init.tsx +++ b/Dashboard/src/Pages/Init/Init.tsx @@ -1,44 +1,44 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import Page from 'CommonUI/src/Components/Page/Page'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Project from 'Model/Models/Project'; -import React, { FunctionComponent, ReactElement, useEffect } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import Page from "CommonUI/src/Components/Page/Page"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Project from "Model/Models/Project"; +import React, { FunctionComponent, ReactElement, useEffect } from "react"; export interface ComponentProps extends PageComponentProps { - isLoadingProjects: boolean; - projects: Array<Project>; + isLoadingProjects: boolean; + projects: Array<Project>; } const Init: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - useEffect(() => { - // set slug to latest project and redirect to home. + useEffect(() => { + // set slug to latest project and redirect to home. - if (props.currentProject && props.currentProject._id) { - Navigation.navigate( - new Route('/dashboard/' + props.currentProject._id + '/home/') - ); - } - }, [props.currentProject]); + if (props.currentProject && props.currentProject._id) { + Navigation.navigate( + new Route("/dashboard/" + props.currentProject._id + "/home/"), + ); + } + }, [props.currentProject]); - useEffect(() => { - // set slug to latest project and redirect to home. + useEffect(() => { + // set slug to latest project and redirect to home. - if (!props.isLoadingProjects && props.projects.length === 0) { - Navigation.navigate(RouteMap[PageMap.WELCOME] as Route); - } - }, [props.projects]); + if (!props.isLoadingProjects && props.projects.length === 0) { + Navigation.navigate(RouteMap[PageMap.WELCOME] as Route); + } + }, [props.projects]); - return ( - <Page title={''} breadcrumbLinks={[]}> - <PageLoader isVisible={true} /> - </Page> - ); + return ( + <Page title={""} breadcrumbLinks={[]}> + <PageLoader isVisible={true} /> + </Page> + ); }; export default Init; diff --git a/Dashboard/src/Pages/Logout/Logout.tsx b/Dashboard/src/Pages/Logout/Logout.tsx index b7caace603..61aecff687 100644 --- a/Dashboard/src/Pages/Logout/Logout.tsx +++ b/Dashboard/src/Pages/Logout/Logout.tsx @@ -1,52 +1,50 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import Page from 'CommonUI/src/Components/Page/Page'; -import { ACCOUNTS_URL } from 'CommonUI/src/Config'; -import UiAnalytics from 'CommonUI/src/Utils/Analytics'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import UserUtil from 'CommonUI/src/Utils/User'; -import React, { FunctionComponent, ReactElement } from 'react'; -import useAsyncEffect from 'use-async-effect'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import Page from "CommonUI/src/Components/Page/Page"; +import { ACCOUNTS_URL } from "CommonUI/src/Config"; +import UiAnalytics from "CommonUI/src/Utils/Analytics"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import UserUtil from "CommonUI/src/Utils/User"; +import React, { FunctionComponent, ReactElement } from "react"; +import useAsyncEffect from "use-async-effect"; const Logout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [error, setError] = React.useState<string | null>(null); + const [error, setError] = React.useState<string | null>(null); - useAsyncEffect(async () => { - try { - UiAnalytics.logout(); - await UserUtil.logout(); - Navigation.navigate(ACCOUNTS_URL); - } catch (err: unknown) { - if (err instanceof Error) { - setError(err.message || err.toString()); - } else { - setError('Unknown error'); - } - } - }, []); + useAsyncEffect(async () => { + try { + UiAnalytics.logout(); + await UserUtil.logout(); + Navigation.navigate(ACCOUNTS_URL); + } catch (err: unknown) { + if (err instanceof Error) { + setError(err.message || err.toString()); + } else { + setError("Unknown error"); + } + } + }, []); - return ( - <Page - title={'Logout'} - breadcrumbLinks={[ - { - title: 'Logout', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.LOGOUT] as Route - ), - }, - ]} - > - {!error ? <PageLoader isVisible={true} /> : <></>} - {error ? <ErrorMessage error={error} /> : <></>} - </Page> - ); + return ( + <Page + title={"Logout"} + breadcrumbLinks={[ + { + title: "Logout", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.LOGOUT] as Route), + }, + ]} + > + {!error ? <PageLoader isVisible={true} /> : <></>} + {error ? <ErrorMessage error={error} /> : <></>} + </Page> + ); }; export default Logout; diff --git a/Dashboard/src/Pages/Monitor/DisabledMonitors.tsx b/Dashboard/src/Pages/Monitor/DisabledMonitors.tsx index 7eca28a48c..1928b6ee9e 100644 --- a/Dashboard/src/Pages/Monitor/DisabledMonitors.tsx +++ b/Dashboard/src/Pages/Monitor/DisabledMonitors.tsx @@ -1,25 +1,25 @@ -import MonitorTable from '../../Components/Monitor/MonitorTable'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitorTable from "../../Components/Monitor/MonitorTable"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import React, { FunctionComponent, ReactElement } from "react"; const DisabledMonitors: FunctionComponent = (): ReactElement => { - return ( - <MonitorTable - viewPageRoute={RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS] as Route - )} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - disableActiveMonitoring: true, - }} - noItemsMessage="No disabled monitors. All monitors in active state." - title="Disabled Monitors" - description="Here is a list of all the monitors which are in disabled state." - /> - ); + return ( + <MonitorTable + viewPageRoute={RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS] as Route, + )} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + disableActiveMonitoring: true, + }} + noItemsMessage="No disabled monitors. All monitors in active state." + title="Disabled Monitors" + description="Here is a list of all the monitors which are in disabled state." + /> + ); }; export default DisabledMonitors; diff --git a/Dashboard/src/Pages/Monitor/Layout.tsx b/Dashboard/src/Pages/Monitor/Layout.tsx index e4da0f73fd..2f1fe3c06c 100644 --- a/Dashboard/src/Pages/Monitor/Layout.tsx +++ b/Dashboard/src/Pages/Monitor/Layout.tsx @@ -1,29 +1,27 @@ -import { getMonitorBreadcrumbs } from '../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import DashboardSideMenu from './SideMenu'; -import Page from 'CommonUI/src/Components/Page/Page'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; +import { getMonitorBreadcrumbs } from "../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import DashboardSideMenu from "./SideMenu"; +import Page from "CommonUI/src/Components/Page/Page"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet } from "react-router-dom"; const MonitorLayout: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <Page - title={'Monitors'} - breadcrumbLinks={getMonitorBreadcrumbs(path)} - sideMenu={ - <DashboardSideMenu - project={props.currentProject || undefined} - /> - } - > - <Outlet /> - </Page> - ); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <Page + title={"Monitors"} + breadcrumbLinks={getMonitorBreadcrumbs(path)} + sideMenu={ + <DashboardSideMenu project={props.currentProject || undefined} /> + } + > + <Outlet /> + </Page> + ); }; export default MonitorLayout; diff --git a/Dashboard/src/Pages/Monitor/Monitors.tsx b/Dashboard/src/Pages/Monitor/Monitors.tsx index 3aa2b4fad3..375e1a9cc4 100644 --- a/Dashboard/src/Pages/Monitor/Monitors.tsx +++ b/Dashboard/src/Pages/Monitor/Monitors.tsx @@ -1,32 +1,32 @@ -import MonitorTable from '../../Components/Monitor/MonitorTable'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import MonitorTable from "../../Components/Monitor/MonitorTable"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const MonitorPage: FunctionComponent<PageComponentProps> = (): ReactElement => { - return ( - <Fragment> - <Banner - openInNewTab={true} - title="Monitoring Demo" - description="Watch this video which will help monitor any resource you have with OneUptime" - link={URL.fromString('https://youtu.be/_fQ_F4EisBQ')} - /> - <MonitorTable - viewPageRoute={RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS] as Route - )} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - /> - </Fragment> - ); + return ( + <Fragment> + <Banner + openInNewTab={true} + title="Monitoring Demo" + description="Watch this video which will help monitor any resource you have with OneUptime" + link={URL.fromString("https://youtu.be/_fQ_F4EisBQ")} + /> + <MonitorTable + viewPageRoute={RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS] as Route, + )} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + /> + </Fragment> + ); }; export default MonitorPage; diff --git a/Dashboard/src/Pages/Monitor/NotOperationalMonitors.tsx b/Dashboard/src/Pages/Monitor/NotOperationalMonitors.tsx index 93a2138952..df46af5b7b 100644 --- a/Dashboard/src/Pages/Monitor/NotOperationalMonitors.tsx +++ b/Dashboard/src/Pages/Monitor/NotOperationalMonitors.tsx @@ -1,27 +1,27 @@ -import MonitorTable from '../../Components/Monitor/MonitorTable'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MonitorTable from "../../Components/Monitor/MonitorTable"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import React, { FunctionComponent, ReactElement } from "react"; const NotOperationalMonitors: FunctionComponent = (): ReactElement => { - return ( - <MonitorTable - viewPageRoute={RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS] as Route - )} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - currentMonitorStatus: { - isOperationalState: false, - }, - }} - noItemsMessage="All monitors in operational state." - title="Inoperational Monitors" - description="Here is a list of all the monitors which are not in operational state." - /> - ); + return ( + <MonitorTable + viewPageRoute={RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS] as Route, + )} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + currentMonitorStatus: { + isOperationalState: false, + }, + }} + noItemsMessage="All monitors in operational state." + title="Inoperational Monitors" + description="Here is a list of all the monitors which are not in operational state." + /> + ); }; export default NotOperationalMonitors; diff --git a/Dashboard/src/Pages/Monitor/SideMenu.tsx b/Dashboard/src/Pages/Monitor/SideMenu.tsx index 5eaff6e267..a4a143860e 100644 --- a/Dashboard/src/Pages/Monitor/SideMenu.tsx +++ b/Dashboard/src/Pages/Monitor/SideMenu.tsx @@ -1,87 +1,87 @@ -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 { BadgeType } from 'CommonUI/src/Components/Badge/Badge'; -import CountModelSideMenuItem from 'CommonUI/src/Components/SideMenu/CountModelSideMenuItem'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import Monitor from 'Model/Models/Monitor'; -import Project from 'Model/Models/Project'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 { BadgeType } from "CommonUI/src/Components/Badge/Badge"; +import CountModelSideMenuItem from "CommonUI/src/Components/SideMenu/CountModelSideMenuItem"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import Monitor from "Model/Models/Monitor"; +import Project from "Model/Models/Project"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - project?: Project | undefined; + project?: Project | undefined; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Overview"> - <SideMenuItem - link={{ - title: 'All Monitors', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS] as Route - ), - }} - icon={IconProp.List} - /> + return ( + <SideMenu> + <SideMenuSection title="Overview"> + <SideMenuItem + link={{ + title: "All Monitors", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS] as Route, + ), + }} + icon={IconProp.List} + /> - <CountModelSideMenuItem<Monitor> - link={{ - title: 'Inoperational Monitors', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS_INOPERATIONAL] as Route - ), - }} - icon={IconProp.Alert} - badgeType={BadgeType.DANGER} - modelType={Monitor} - countQuery={{ - projectId: props.project?._id, - currentMonitorStatus: { - isOperationalState: false, - }, - }} - /> - <CountModelSideMenuItem<Monitor> - link={{ - title: 'Disabled Monitors', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS_DISABLED] as Route - ), - }} - icon={IconProp.Error} - badgeType={BadgeType.DANGER} - modelType={Monitor} - countQuery={{ - projectId: props.project?._id, - disableActiveMonitoring: true, - }} - /> - </SideMenuSection> + <CountModelSideMenuItem<Monitor> + link={{ + title: "Inoperational Monitors", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS_INOPERATIONAL] as Route, + ), + }} + icon={IconProp.Alert} + badgeType={BadgeType.DANGER} + modelType={Monitor} + countQuery={{ + projectId: props.project?._id, + currentMonitorStatus: { + isOperationalState: false, + }, + }} + /> + <CountModelSideMenuItem<Monitor> + link={{ + title: "Disabled Monitors", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS_DISABLED] as Route, + ), + }} + icon={IconProp.Error} + badgeType={BadgeType.DANGER} + modelType={Monitor} + countQuery={{ + projectId: props.project?._id, + disableActiveMonitoring: true, + }} + /> + </SideMenuSection> - {props.project?.isFeatureFlagMonitorGroupsEnabled ? ( - <SideMenuSection title="Monitor Groups"> - <SideMenuItem - link={{ - title: 'All Groups', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_GROUPS] as Route - ), - }} - icon={IconProp.Squares} - /> - </SideMenuSection> - ) : ( - <></> - )} - </SideMenu> - ); + {props.project?.isFeatureFlagMonitorGroupsEnabled ? ( + <SideMenuSection title="Monitor Groups"> + <SideMenuItem + link={{ + title: "All Groups", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_GROUPS] as Route, + ), + }} + icon={IconProp.Squares} + /> + </SideMenuSection> + ) : ( + <></> + )} + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Monitor/View/Criteria.tsx b/Dashboard/src/Pages/Monitor/View/Criteria.tsx index e3e96b6c63..951cb6301e 100644 --- a/Dashboard/src/Pages/Monitor/View/Criteria.tsx +++ b/Dashboard/src/Pages/Monitor/View/Criteria.tsx @@ -1,189 +1,180 @@ -import MonitorStepsForm from '../../../Components/Form/Monitor/MonitorSteps'; -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import MonitorStepsViewer from '../../../Components/Monitor/MonitorSteps/MonitorSteps'; -import PageComponentProps from '../../PageComponentProps'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import MonitorStepsType from 'Common/Types/Monitor/MonitorSteps'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; +import MonitorStepsForm from "../../../Components/Form/Monitor/MonitorSteps"; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import MonitorStepsViewer from "../../../Components/Monitor/MonitorSteps/MonitorSteps"; +import PageComponentProps from "../../PageComponentProps"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import MonitorStepsType from "Common/Types/Monitor/MonitorSteps"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; import { - CustomElementProps, - FormFieldStyleType, -} from 'CommonUI/src/Components/Forms/Types/Field'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; + CustomElementProps, + FormFieldStyleType, +} from "CommonUI/src/Components/Forms/Types/Field"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; -import { useAsyncEffect } from 'use-async-effect'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; +import { useAsyncEffect } from "use-async-effect"; const MonitorCriteria: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [isLoading, setIsLoading] = useState<boolean>(true); + const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - // get item. - setIsLoading(true); + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + // get item. + setIsLoading(true); - setError(''); - try { - const item: Monitor | null = await ModelAPI.getItem({ - modelType: Monitor, - id: modelId, - select: { - monitorType: true, - }, - }); + setError(""); + try { + const item: Monitor | null = await ModelAPI.getItem({ + modelType: Monitor, + id: modelId, + select: { + monitorType: true, + }, + }); - if (!item) { - setError(`Monitor not found`); + if (!item) { + setError(`Monitor not found`); - return; - } + return; + } - setMonitorType(item.monitorType); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - setIsLoading(false); - }; + setMonitorType(item.monitorType); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + setIsLoading(false); + }; - const [monitorType, setMonitorType] = useState<MonitorType | undefined>( - undefined - ); + const [monitorType, setMonitorType] = useState<MonitorType | undefined>( + undefined, + ); - useAsyncEffect(async () => { - // fetch the model - await fetchItem(); - }, []); + useAsyncEffect(async () => { + // fetch the model + await fetchItem(); + }, []); - const getPageContent: GetReactElementFunction = (): ReactElement => { - if (!monitorType || isLoading) { - return <ComponentLoader />; - } + const getPageContent: GetReactElementFunction = (): ReactElement => { + if (!monitorType || isLoading) { + return <ComponentLoader />; + } - if (error) { - return <ErrorMessage error={error} />; - } + if (error) { + return <ErrorMessage error={error} />; + } - if (monitorType === MonitorType.Manual) { - return ( - <EmptyState - id="monitoring-criteria-empty-state" - icon={IconProp.Criteria} - title={'No Criteria for Manual Monitors'} - description={ - <> - This is a manual monitor and it cannot have any - criteria set. You can have monitoring criteria on - other monitor types.{' '} - </> - } - /> - ); - } - - return ( - <CardModelDetail - name="Monitoring Criteria" - editButtonText="Edit Monitoring Criteria" - cardProps={{ - title: 'Monitoring Criteria', - description: - 'Here is the criteria we use to monitor this resource.', - }} - createEditModalWidth={ModalWidth.Large} - isEditable={true} - formFields={[ - { - field: { - monitorSteps: true, - }, - stepId: 'criteria', - styleType: FormFieldStyleType.Heading, - title: 'Monitor Details', - fieldType: FormFieldSchemaType.CustomComponent, - required: true, - customValidation: (values: FormValues<Monitor>) => { - const error: string | null = - MonitorStepsType.getValidationError( - values.monitorSteps as MonitorStepsType, - monitorType - ); - - return error; - }, - getCustomElement: ( - _value: FormValues<Monitor>, - props: CustomElementProps - ) => { - return ( - <MonitorStepsForm - {...props} - monitorType={ - monitorType || MonitorType.Manual - } - error={''} - /> - ); - }, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: Monitor, - id: 'model-detail-monitors', - fields: [ - { - field: { - monitorSteps: true, - }, - title: '', - getElement: (item: Monitor): ReactElement => { - return ( - <MonitorStepsViewer - monitorSteps={ - item[ - 'monitorSteps' - ] as MonitorStepsType - } - monitorType={monitorType} - /> - ); - }, - }, - ], - modelId: modelId, - }} - /> - ); - }; + if (monitorType === MonitorType.Manual) { + return ( + <EmptyState + id="monitoring-criteria-empty-state" + icon={IconProp.Criteria} + title={"No Criteria for Manual Monitors"} + description={ + <> + This is a manual monitor and it cannot have any criteria set. You + can have monitoring criteria on other monitor types.{" "} + </> + } + /> + ); + } return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> - {getPageContent()} - </Fragment> + <CardModelDetail + name="Monitoring Criteria" + editButtonText="Edit Monitoring Criteria" + cardProps={{ + title: "Monitoring Criteria", + description: "Here is the criteria we use to monitor this resource.", + }} + createEditModalWidth={ModalWidth.Large} + isEditable={true} + formFields={[ + { + field: { + monitorSteps: true, + }, + stepId: "criteria", + styleType: FormFieldStyleType.Heading, + title: "Monitor Details", + fieldType: FormFieldSchemaType.CustomComponent, + required: true, + customValidation: (values: FormValues<Monitor>) => { + const error: string | null = MonitorStepsType.getValidationError( + values.monitorSteps as MonitorStepsType, + monitorType, + ); + + return error; + }, + getCustomElement: ( + _value: FormValues<Monitor>, + props: CustomElementProps, + ) => { + return ( + <MonitorStepsForm + {...props} + monitorType={monitorType || MonitorType.Manual} + error={""} + /> + ); + }, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: Monitor, + id: "model-detail-monitors", + fields: [ + { + field: { + monitorSteps: true, + }, + title: "", + getElement: (item: Monitor): ReactElement => { + return ( + <MonitorStepsViewer + monitorSteps={item["monitorSteps"] as MonitorStepsType} + monitorType={monitorType} + /> + ); + }, + }, + ], + modelId: modelId, + }} + /> ); + }; + + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + {getPageContent()} + </Fragment> + ); }; export default MonitorCriteria; diff --git a/Dashboard/src/Pages/Monitor/View/CustomFields.tsx b/Dashboard/src/Pages/Monitor/View/CustomFields.tsx index cb6e751db6..124100ee3f 100644 --- a/Dashboard/src/Pages/Monitor/View/CustomFields.tsx +++ b/Dashboard/src/Pages/Monitor/View/CustomFields.tsx @@ -1,32 +1,32 @@ -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import CustomFieldsDetail from 'CommonUI/src/Components/CustomFields/CustomFieldsDetail'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Monitor from 'Model/Models/Monitor'; -import MonitorCustomField from 'Model/Models/MonitorCustomField'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import CustomFieldsDetail from "CommonUI/src/Components/CustomFields/CustomFieldsDetail"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Monitor from "Model/Models/Monitor"; +import MonitorCustomField from "Model/Models/MonitorCustomField"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const MonitorCustomFields: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> - <CustomFieldsDetail - title="Monitor Custom Fields" - description="Custom fields help you add new fields to your resources in OneUptime." - modelType={Monitor} - customFieldType={MonitorCustomField} - name="Monitor Custom Fields" - projectId={ProjectUtil.getCurrentProject()!.id!} - modelId={modelId} - /> - </Fragment> - ); + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + <CustomFieldsDetail + title="Monitor Custom Fields" + description="Custom fields help you add new fields to your resources in OneUptime." + modelType={Monitor} + customFieldType={MonitorCustomField} + name="Monitor Custom Fields" + projectId={ProjectUtil.getCurrentProject()!.id!} + modelId={modelId} + /> + </Fragment> + ); }; export default MonitorCustomFields; diff --git a/Dashboard/src/Pages/Monitor/View/Delete.tsx b/Dashboard/src/Pages/Monitor/View/Delete.tsx index 11d49d624a..01b6e33c59 100644 --- a/Dashboard/src/Pages/Monitor/View/Delete.tsx +++ b/Dashboard/src/Pages/Monitor/View/Delete.tsx @@ -1,36 +1,35 @@ -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const MonitorDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> - <ModelDelete - modelType={Monitor} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS] as Route, - { modelId } - ) - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + <ModelDelete + modelType={Monitor} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate( + RouteUtil.populateRouteParams(RouteMap[PageMap.MONITORS] as Route, { + modelId, + }), + ); + }} + /> + </Fragment> + ); }; export default MonitorDelete; diff --git a/Dashboard/src/Pages/Monitor/View/Documentation.tsx b/Dashboard/src/Pages/Monitor/View/Documentation.tsx index 5d5aa5bc94..b3283cd484 100644 --- a/Dashboard/src/Pages/Monitor/View/Documentation.tsx +++ b/Dashboard/src/Pages/Monitor/View/Documentation.tsx @@ -1,105 +1,102 @@ -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import IncomingMonitorLink from '../../../Components/Monitor/IncomingRequestMonitor/IncomingMonitorLink'; -import ServerMonitorDocumentation from '../../../Components/Monitor/ServerMonitor/Documentation'; -import PageComponentProps from '../../PageComponentProps'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import IncomingMonitorLink from "../../../Components/Monitor/IncomingRequestMonitor/IncomingMonitorLink"; +import ServerMonitorDocumentation from "../../../Components/Monitor/ServerMonitor/Documentation"; +import PageComponentProps from "../../PageComponentProps"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; const MonitorDocumentation: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [error, setError] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(true); - const [monitorType, setMonitorType] = useState<MonitorType | undefined>( - undefined - ); + const [monitorType, setMonitorType] = useState<MonitorType | undefined>( + undefined, + ); - const [monitor, setMonitor] = useState<Monitor | null>(null); + const [monitor, setMonitor] = useState<Monitor | null>(null); - useAsyncEffect(async () => { - await fetchItem(); - }, []); + useAsyncEffect(async () => { + await fetchItem(); + }, []); - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - setIsLoading(true); - setError(''); + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); + setError(""); - try { - const item: Monitor | null = await ModelAPI.getItem({ - modelType: Monitor, - id: modelId, - select: { - monitorType: true, - incomingRequestSecretKey: true, - serverMonitorSecretKey: true, - }, - }); + try { + const item: Monitor | null = await ModelAPI.getItem({ + modelType: Monitor, + id: modelId, + select: { + monitorType: true, + incomingRequestSecretKey: true, + serverMonitorSecretKey: true, + }, + }); - setMonitor(item); + setMonitor(item); - if (!item) { - setError(`Monitor not found`); + if (!item) { + setError(`Monitor not found`); - return; - } + return; + } - setMonitorType(item.monitorType); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - if (error) { - return <ErrorMessage error={error} />; + setMonitorType(item.monitorType); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (isLoading) { - return <PageLoader isVisible={true} />; - } + setIsLoading(false); + }; - return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> + if (error) { + return <ErrorMessage error={error} />; + } - {/* Heartbeat URL */} - {monitorType === MonitorType.IncomingRequest && - monitor?.incomingRequestSecretKey ? ( - <IncomingMonitorLink - secretKey={monitor?.incomingRequestSecretKey} - /> - ) : ( - <></> - )} + if (isLoading) { + return <PageLoader isVisible={true} />; + } - {monitorType === MonitorType.Server && - monitor?.serverMonitorSecretKey ? ( - <ServerMonitorDocumentation - secretKey={monitor?.serverMonitorSecretKey} - /> - ) : ( - <></> - )} - </Fragment> - ); + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + + {/* Heartbeat URL */} + {monitorType === MonitorType.IncomingRequest && + monitor?.incomingRequestSecretKey ? ( + <IncomingMonitorLink secretKey={monitor?.incomingRequestSecretKey} /> + ) : ( + <></> + )} + + {monitorType === MonitorType.Server && monitor?.serverMonitorSecretKey ? ( + <ServerMonitorDocumentation + secretKey={monitor?.serverMonitorSecretKey} + /> + ) : ( + <></> + )} + </Fragment> + ); }; export default MonitorDocumentation; diff --git a/Dashboard/src/Pages/Monitor/View/Incidents.tsx b/Dashboard/src/Pages/Monitor/View/Incidents.tsx index 08cdda647f..131cc55a33 100644 --- a/Dashboard/src/Pages/Monitor/View/Incidents.tsx +++ b/Dashboard/src/Pages/Monitor/View/Incidents.tsx @@ -1,31 +1,31 @@ -import IncidentsTable from '../../../Components/Incident/IncidentsTable'; -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import IncidentsTable from "../../../Components/Incident/IncidentsTable"; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const MonitorIncidents: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> - <IncidentsTable - viewPageRoute={Navigation.getCurrentRoute()} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - monitors: [modelId.toString()], - }} - createInitialValues={{ - monitors: [modelId.toString()], - }} - /> - </Fragment> - ); + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + <IncidentsTable + viewPageRoute={Navigation.getCurrentRoute()} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + monitors: [modelId.toString()], + }} + createInitialValues={{ + monitors: [modelId.toString()], + }} + /> + </Fragment> + ); }; export default MonitorIncidents; diff --git a/Dashboard/src/Pages/Monitor/View/Index.tsx b/Dashboard/src/Pages/Monitor/View/Index.tsx index cb6ae3316a..cb1dc01876 100644 --- a/Dashboard/src/Pages/Monitor/View/Index.tsx +++ b/Dashboard/src/Pages/Monitor/View/Index.tsx @@ -1,563 +1,541 @@ -import LabelsElement from '../../../Components/Label/Labels'; -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import IncomingMonitorLink from '../../../Components/Monitor/IncomingRequestMonitor/IncomingMonitorLink'; -import { MonitorCharts } from '../../../Components/Monitor/MonitorCharts/MonitorChart'; -import ServerMonitorDocumentation from '../../../Components/Monitor/ServerMonitor/Documentation'; -import Metrics from '../../../Components/Monitor/SummaryView/Summary'; -import ProbeUtil from '../../../Utils/Probe'; -import PageComponentProps from '../../PageComponentProps'; -import InBetween from 'Common/Types/BaseDatabase/InBetween'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { Black, Gray500, Green } from 'Common/Types/BrandColors'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; +import LabelsElement from "../../../Components/Label/Labels"; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import IncomingMonitorLink from "../../../Components/Monitor/IncomingRequestMonitor/IncomingMonitorLink"; +import { MonitorCharts } from "../../../Components/Monitor/MonitorCharts/MonitorChart"; +import ServerMonitorDocumentation from "../../../Components/Monitor/ServerMonitor/Documentation"; +import Metrics from "../../../Components/Monitor/SummaryView/Summary"; +import ProbeUtil from "../../../Utils/Probe"; +import PageComponentProps from "../../PageComponentProps"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { Black, Gray500, Green } from "Common/Types/BrandColors"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; import { - CheckOn, - CriteriaFilterUtil, -} from 'Common/Types/Monitor/CriteriaFilter'; -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; + CheckOn, + CriteriaFilterUtil, +} from "Common/Types/Monitor/CriteriaFilter"; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; import MonitorType, { - MonitorTypeHelper, -} from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import Card from 'CommonUI/src/Components/Card/Card'; + MonitorTypeHelper, +} from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import Card from "CommonUI/src/Components/Card/Card"; import ChartGroup, { - Chart, - ChartGroupInterval, -} from 'CommonUI/src/Components/Charts/ChartGroup/ChartGroup'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import MonitorUptimeGraph from 'CommonUI/src/Components/MonitorGraphs/Uptime'; -import UptimeUtil from 'CommonUI/src/Components/MonitorGraphs/UptimeUtil'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import API from 'CommonUI/src/Utils/API/API'; + Chart, + ChartGroupInterval, +} from "CommonUI/src/Components/Charts/ChartGroup/ChartGroup"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import MonitorUptimeGraph from "CommonUI/src/Components/MonitorGraphs/Uptime"; +import UptimeUtil from "CommonUI/src/Components/MonitorGraphs/UptimeUtil"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import API from "CommonUI/src/Utils/API/API"; import AnalyticsModelAPI, { - ListResult as AnalyticsListResult, -} from 'CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import MonitorMetricsByMinute from 'Model/AnalyticsModels/MonitorMetricsByMinute'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; + ListResult as AnalyticsListResult, +} from "CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import MonitorMetricsByMinute from "Model/AnalyticsModels/MonitorMetricsByMinute"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; import MonitorProbe, { - MonitorStepProbeResponse, -} from 'Model/Models/MonitorProbe'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import Probe from 'Model/Models/Probe'; -import { UptimePrecision } from 'Model/Models/StatusPageResource'; + MonitorStepProbeResponse, +} from "Model/Models/MonitorProbe"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import Probe from "Model/Models/Probe"; +import { UptimePrecision } from "Model/Models/StatusPageResource"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; const MonitorView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - const [statusTimelines, setStatusTimelines] = useState< - Array<MonitorStatusTimeline> - >([]); - const [error, setError] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(true); - const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); - const endDate: Date = OneUptimeDate.getCurrentDate(); - const [downTimeMonitorStatues, setDowntimeMonitorStatues] = useState< - Array<MonitorStatus> - >([]); - const [currentMonitorStatus, setCurrentMonitorStatus] = useState< - MonitorStatus | undefined - >(undefined); + const [statusTimelines, setStatusTimelines] = useState< + Array<MonitorStatusTimeline> + >([]); + const [error, setError] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(true); + const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); + const endDate: Date = OneUptimeDate.getCurrentDate(); + const [downTimeMonitorStatues, setDowntimeMonitorStatues] = useState< + Array<MonitorStatus> + >([]); + const [currentMonitorStatus, setCurrentMonitorStatus] = useState< + MonitorStatus | undefined + >(undefined); - const [monitorType, setMonitorType] = useState<MonitorType | undefined>( - undefined - ); + const [monitorType, setMonitorType] = useState<MonitorType | undefined>( + undefined, + ); - const [monitorMetricsByMinute, setMonitorMetricsByMinute] = useState< - Array<MonitorMetricsByMinute> - >([]); + const [monitorMetricsByMinute, setMonitorMetricsByMinute] = useState< + Array<MonitorMetricsByMinute> + >([]); - const [shouldFetchMonitorMetrics, setShouldFetchMonitorMetrics] = - useState<boolean>(false); + const [shouldFetchMonitorMetrics, setShouldFetchMonitorMetrics] = + useState<boolean>(false); - const [monitor, setMonitor] = useState<Monitor | null>(null); + const [monitor, setMonitor] = useState<Monitor | null>(null); - const [probes, setProbes] = useState<Array<Probe>>([]); + const [probes, setProbes] = useState<Array<Probe>>([]); - const [probeResponses, setProbeResponses] = useState< - Array<MonitorStepProbeResponse> | undefined - >(undefined); + const [probeResponses, setProbeResponses] = useState< + Array<MonitorStepProbeResponse> | undefined + >(undefined); - const [incomingMonitorRequest, setIncomingMonitorRequest] = useState< - IncomingMonitorRequest | undefined - >(undefined); - - const getUptimePercent: () => ReactElement = (): ReactElement => { - if (isLoading) { - return <></>; - } - - const uptimePercent: number = UptimeUtil.calculateUptimePercentage( - statusTimelines, - UptimePrecision.THREE_DECIMAL, - downTimeMonitorStatues - ); - - return ( - <div - className="font-medium mt-5" - style={{ - color: - currentMonitorStatus?.color?.toString() || - Green.toString(), - }} - > - {uptimePercent}% uptime - </div> - ); - }; - - useAsyncEffect(async () => { - await fetchItem(); - }, []); - - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - setIsLoading(true); - setError(''); - - try { - const monitorStatus: ListResult<MonitorStatusTimeline> = - await ModelAPI.getList({ - modelType: MonitorStatusTimeline, - query: { - createdAt: new InBetween(startDate, endDate), - monitorId: modelId, - projectId: ProjectUtil.getCurrentProjectId(), - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - createdAt: true, - monitorId: true, - startsAt: true, - endsAt: true, - monitorStatus: { - name: true, - color: true, - isOperationalState: true, - priority: true, - }, - }, - sort: { - createdAt: SortOrder.Ascending, - }, - }); - - const item: Monitor | null = await ModelAPI.getItem({ - modelType: Monitor, - id: modelId, - select: { - monitorType: true, - currentMonitorStatus: { - name: true, - color: true, - }, - incomingRequestSecretKey: true, - serverMonitorSecretKey: true, - serverMonitorRequestReceivedAt: true, - incomingRequestReceivedAt: true, - incomingMonitorRequest: true, - }, - }); - - setMonitor(item); - - if (item?.incomingMonitorRequest) { - setIncomingMonitorRequest(item.incomingMonitorRequest); - } - - const monitorStatuses: ListResult<MonitorStatus> = - await ModelAPI.getList({ - modelType: MonitorStatus, - query: { - projectId: ProjectUtil.getCurrentProjectId(), - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - priority: true, - isOperationalState: true, - name: true, - color: true, - }, - sort: { - priority: SortOrder.Ascending, - }, - }); - - let monitorMetricsByMinute: AnalyticsListResult<MonitorMetricsByMinute> = - { - data: [], - count: 0, - limit: 0, - skip: 0, - }; - - if (!item) { - setError(`Monitor not found`); - return; - } - - const shouldFetchMonitorMetrics: boolean = - CriteriaFilterUtil.getTimeFiltersByMonitorType( - item.monitorType! - ).length > 0; - - setShouldFetchMonitorMetrics(shouldFetchMonitorMetrics); - - if (shouldFetchMonitorMetrics) { - monitorMetricsByMinute = await AnalyticsModelAPI.getList({ - query: { - monitorId: modelId, - }, - modelType: MonitorMetricsByMinute, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - createdAt: true, - metricType: true, - metricValue: true, - miscData: true, - }, - sort: { - createdAt: SortOrder.Descending, - }, - }); - } - - setMonitorType(item.monitorType); - setCurrentMonitorStatus(item.currentMonitorStatus); - setDowntimeMonitorStatues( - monitorStatuses.data.filter((status: MonitorStatus) => { - return !status.isOperationalState; - }) - ); - setStatusTimelines(monitorStatus.data); - setMonitorMetricsByMinute(monitorMetricsByMinute.data.reverse()); - - const isMonitoredByProbe: boolean = item.monitorType - ? MonitorTypeHelper.isProbableMonitors(item.monitorType) - : false; - - if (isMonitoredByProbe) { - // get a list of probes - const probes: Array<Probe> = await ProbeUtil.getAllProbes(); - setProbes(probes); - - // get probe responses for this monitor - - const monitorProbes: ListResult<MonitorProbe> = - await ModelAPI.getList({ - modelType: MonitorProbe, - query: { - monitorId: modelId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - sort: { - createdAt: SortOrder.Descending, - }, - select: { - probeId: true, - lastMonitoringLog: true, - }, - }); - - const probeMonitorResponses: Array<MonitorStepProbeResponse> = - []; - - for (let i: number = 0; i < monitorProbes.data.length; i++) { - const monitorProbe: MonitorProbe | undefined = - monitorProbes.data[i]; - - if (!monitorProbe) { - continue; - } - - if (!monitorProbe.probeId) { - continue; - } - - if (!monitorProbe.lastMonitoringLog) { - continue; - } - - probeMonitorResponses.push(monitorProbe?.lastMonitoringLog); - } - - setProbeResponses(probeMonitorResponses); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - const getMonitorMetricsChartGroup: GetReactElementFunction = - (): ReactElement => { - if (isLoading) { - return <></>; - } - - if (!shouldFetchMonitorMetrics) { - return <></>; - } - - const chartsByDataType: Array<CheckOn> = - CriteriaFilterUtil.getTimeFiltersByMonitorType(monitorType!); - - const charts: Array<Chart> = MonitorCharts.getMonitorCharts({ - monitorMetricsByMinute: monitorMetricsByMinute, - checkOns: chartsByDataType, - probes: probes, - }); - - return ( - <ChartGroup - interval={ChartGroupInterval.ONE_HOUR} - charts={charts} - /> - ); - }; + const [incomingMonitorRequest, setIncomingMonitorRequest] = useState< + IncomingMonitorRequest | undefined + >(undefined); + const getUptimePercent: () => ReactElement = (): ReactElement => { if (isLoading) { - return <PageLoader isVisible={true} />; + return <></>; } - if (error) { - return <ErrorMessage error={error} />; - } + const uptimePercent: number = UptimeUtil.calculateUptimePercentage( + statusTimelines, + UptimePrecision.THREE_DECIMAL, + downTimeMonitorStatues, + ); return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> - - {/* Monitor View */} - <CardModelDetail<Monitor> - name="Monitor Details" - formSteps={[ - { - title: 'Monitor Info', - id: 'monitor-info', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - cardProps={{ - title: 'Monitor Details', - description: 'Here are more details for this monitor.', - }} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - stepId: 'monitor-info', - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Monitor Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'monitor-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - labels: true, - }, - stepId: 'labels', - title: 'Labels ', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - selectMoreFields: { - disableActiveMonitoring: true, - }, - showDetailsInNumberOfColumns: 2, - modelType: Monitor, - id: 'model-detail-monitors', - fields: [ - { - field: { - _id: true, - }, - title: 'Monitor ID', - }, - { - field: { - name: true, - }, - title: 'Monitor Name', - }, - { - field: { - currentMonitorStatus: { - color: true, - name: true, - }, - }, - title: 'Current Status', - fieldType: FieldType.Element, - getElement: (item: Monitor): ReactElement => { - if (!item['currentMonitorStatus']) { - throw new BadDataException( - 'Monitor Status not found' - ); - } - - if (item && item['disableActiveMonitoring']) { - return ( - <Statusbubble - color={Gray500} - text={'Disabled'} - shouldAnimate={false} - /> - ); - } - - return ( - <Statusbubble - color={ - item.currentMonitorStatus.color || - Black - } - shouldAnimate={true} - text={ - item.currentMonitorStatus.name || - 'Unknown' - } - /> - ); - }, - }, - - { - field: { - monitorType: true, - }, - title: 'Monitor Type', - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: (item: Monitor): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - }, - ], - modelId: modelId, - }} - /> - - {/* Heartbeat URL */} - {monitorType === MonitorType.IncomingRequest && - monitor?.incomingRequestSecretKey && - !monitor.incomingRequestReceivedAt ? ( - <IncomingMonitorLink - secretKey={monitor?.incomingRequestSecretKey} - /> - ) : ( - <></> - )} - - {monitorType === MonitorType.Server && - monitor?.serverMonitorSecretKey && - !monitor.serverMonitorRequestReceivedAt ? ( - <ServerMonitorDocumentation - secretKey={monitor?.serverMonitorSecretKey} - /> - ) : ( - <></> - )} - - <Card - title="Uptime Graph" - description="Here the 90 day uptime history of this monitor." - rightElement={getUptimePercent()} - > - <MonitorUptimeGraph - error={error} - items={statusTimelines} - startDate={OneUptimeDate.getSomeDaysAgo(90)} - endDate={OneUptimeDate.getCurrentDate()} - isLoading={isLoading} - defaultBarColor={Green} - downtimeMonitorStatuses={downTimeMonitorStatues} - /> - </Card> - - <Metrics - monitorType={monitorType!} - probes={probes} - incomingMonitorRequest={incomingMonitorRequest} - probeMonitorResponses={probeResponses} - /> - - {shouldFetchMonitorMetrics && getMonitorMetricsChartGroup()} - </Fragment> + <div + className="font-medium mt-5" + style={{ + color: currentMonitorStatus?.color?.toString() || Green.toString(), + }} + > + {uptimePercent}% uptime + </div> ); + }; + + useAsyncEffect(async () => { + await fetchItem(); + }, []); + + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); + setError(""); + + try { + const monitorStatus: ListResult<MonitorStatusTimeline> = + await ModelAPI.getList({ + modelType: MonitorStatusTimeline, + query: { + createdAt: new InBetween(startDate, endDate), + monitorId: modelId, + projectId: ProjectUtil.getCurrentProjectId(), + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + createdAt: true, + monitorId: true, + startsAt: true, + endsAt: true, + monitorStatus: { + name: true, + color: true, + isOperationalState: true, + priority: true, + }, + }, + sort: { + createdAt: SortOrder.Ascending, + }, + }); + + const item: Monitor | null = await ModelAPI.getItem({ + modelType: Monitor, + id: modelId, + select: { + monitorType: true, + currentMonitorStatus: { + name: true, + color: true, + }, + incomingRequestSecretKey: true, + serverMonitorSecretKey: true, + serverMonitorRequestReceivedAt: true, + incomingRequestReceivedAt: true, + incomingMonitorRequest: true, + }, + }); + + setMonitor(item); + + if (item?.incomingMonitorRequest) { + setIncomingMonitorRequest(item.incomingMonitorRequest); + } + + const monitorStatuses: ListResult<MonitorStatus> = await ModelAPI.getList( + { + modelType: MonitorStatus, + query: { + projectId: ProjectUtil.getCurrentProjectId(), + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + priority: true, + isOperationalState: true, + name: true, + color: true, + }, + sort: { + priority: SortOrder.Ascending, + }, + }, + ); + + let monitorMetricsByMinute: AnalyticsListResult<MonitorMetricsByMinute> = + { + data: [], + count: 0, + limit: 0, + skip: 0, + }; + + if (!item) { + setError(`Monitor not found`); + return; + } + + const shouldFetchMonitorMetrics: boolean = + CriteriaFilterUtil.getTimeFiltersByMonitorType(item.monitorType!) + .length > 0; + + setShouldFetchMonitorMetrics(shouldFetchMonitorMetrics); + + if (shouldFetchMonitorMetrics) { + monitorMetricsByMinute = await AnalyticsModelAPI.getList({ + query: { + monitorId: modelId, + }, + modelType: MonitorMetricsByMinute, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + createdAt: true, + metricType: true, + metricValue: true, + miscData: true, + }, + sort: { + createdAt: SortOrder.Descending, + }, + }); + } + + setMonitorType(item.monitorType); + setCurrentMonitorStatus(item.currentMonitorStatus); + setDowntimeMonitorStatues( + monitorStatuses.data.filter((status: MonitorStatus) => { + return !status.isOperationalState; + }), + ); + setStatusTimelines(monitorStatus.data); + setMonitorMetricsByMinute(monitorMetricsByMinute.data.reverse()); + + const isMonitoredByProbe: boolean = item.monitorType + ? MonitorTypeHelper.isProbableMonitors(item.monitorType) + : false; + + if (isMonitoredByProbe) { + // get a list of probes + const probes: Array<Probe> = await ProbeUtil.getAllProbes(); + setProbes(probes); + + // get probe responses for this monitor + + const monitorProbes: ListResult<MonitorProbe> = await ModelAPI.getList({ + modelType: MonitorProbe, + query: { + monitorId: modelId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + sort: { + createdAt: SortOrder.Descending, + }, + select: { + probeId: true, + lastMonitoringLog: true, + }, + }); + + const probeMonitorResponses: Array<MonitorStepProbeResponse> = []; + + for (let i: number = 0; i < monitorProbes.data.length; i++) { + const monitorProbe: MonitorProbe | undefined = monitorProbes.data[i]; + + if (!monitorProbe) { + continue; + } + + if (!monitorProbe.probeId) { + continue; + } + + if (!monitorProbe.lastMonitoringLog) { + continue; + } + + probeMonitorResponses.push(monitorProbe?.lastMonitoringLog); + } + + setProbeResponses(probeMonitorResponses); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + const getMonitorMetricsChartGroup: GetReactElementFunction = + (): ReactElement => { + if (isLoading) { + return <></>; + } + + if (!shouldFetchMonitorMetrics) { + return <></>; + } + + const chartsByDataType: Array<CheckOn> = + CriteriaFilterUtil.getTimeFiltersByMonitorType(monitorType!); + + const charts: Array<Chart> = MonitorCharts.getMonitorCharts({ + monitorMetricsByMinute: monitorMetricsByMinute, + checkOns: chartsByDataType, + probes: probes, + }); + + return ( + <ChartGroup interval={ChartGroupInterval.ONE_HOUR} charts={charts} /> + ); + }; + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + + {/* Monitor View */} + <CardModelDetail<Monitor> + name="Monitor Details" + formSteps={[ + { + title: "Monitor Info", + id: "monitor-info", + }, + { + title: "Labels", + id: "labels", + }, + ]} + cardProps={{ + title: "Monitor Details", + description: "Here are more details for this monitor.", + }} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + stepId: "monitor-info", + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Monitor Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "monitor-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + labels: true, + }, + stepId: "labels", + title: "Labels ", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + selectMoreFields: { + disableActiveMonitoring: true, + }, + showDetailsInNumberOfColumns: 2, + modelType: Monitor, + id: "model-detail-monitors", + fields: [ + { + field: { + _id: true, + }, + title: "Monitor ID", + }, + { + field: { + name: true, + }, + title: "Monitor Name", + }, + { + field: { + currentMonitorStatus: { + color: true, + name: true, + }, + }, + title: "Current Status", + fieldType: FieldType.Element, + getElement: (item: Monitor): ReactElement => { + if (!item["currentMonitorStatus"]) { + throw new BadDataException("Monitor Status not found"); + } + + if (item && item["disableActiveMonitoring"]) { + return ( + <Statusbubble + color={Gray500} + text={"Disabled"} + shouldAnimate={false} + /> + ); + } + + return ( + <Statusbubble + color={item.currentMonitorStatus.color || Black} + shouldAnimate={true} + text={item.currentMonitorStatus.name || "Unknown"} + /> + ); + }, + }, + + { + field: { + monitorType: true, + }, + title: "Monitor Type", + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: Monitor): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + }, + ], + modelId: modelId, + }} + /> + + {/* Heartbeat URL */} + {monitorType === MonitorType.IncomingRequest && + monitor?.incomingRequestSecretKey && + !monitor.incomingRequestReceivedAt ? ( + <IncomingMonitorLink secretKey={monitor?.incomingRequestSecretKey} /> + ) : ( + <></> + )} + + {monitorType === MonitorType.Server && + monitor?.serverMonitorSecretKey && + !monitor.serverMonitorRequestReceivedAt ? ( + <ServerMonitorDocumentation + secretKey={monitor?.serverMonitorSecretKey} + /> + ) : ( + <></> + )} + + <Card + title="Uptime Graph" + description="Here the 90 day uptime history of this monitor." + rightElement={getUptimePercent()} + > + <MonitorUptimeGraph + error={error} + items={statusTimelines} + startDate={OneUptimeDate.getSomeDaysAgo(90)} + endDate={OneUptimeDate.getCurrentDate()} + isLoading={isLoading} + defaultBarColor={Green} + downtimeMonitorStatuses={downTimeMonitorStatues} + /> + </Card> + + <Metrics + monitorType={monitorType!} + probes={probes} + incomingMonitorRequest={incomingMonitorRequest} + probeMonitorResponses={probeResponses} + /> + + {shouldFetchMonitorMetrics && getMonitorMetricsChartGroup()} + </Fragment> + ); }; export default MonitorView; diff --git a/Dashboard/src/Pages/Monitor/View/Interval.tsx b/Dashboard/src/Pages/Monitor/View/Interval.tsx index 60a737de74..f310915315 100644 --- a/Dashboard/src/Pages/Monitor/View/Interval.tsx +++ b/Dashboard/src/Pages/Monitor/View/Interval.tsx @@ -1,177 +1,172 @@ -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import MonitoringIntervalElement from '../../../Components/Monitor/MonitoringIntervalElement'; -import MonitoringInterval from '../../../Utils/MonitorIntervalDropdownOptions'; -import PageComponentProps from '../../PageComponentProps'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import MonitoringIntervalElement from "../../../Components/Monitor/MonitoringIntervalElement"; +import MonitoringInterval from "../../../Utils/MonitorIntervalDropdownOptions"; +import PageComponentProps from "../../PageComponentProps"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; const MonitorCriteria: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [isLoading, setIsLoading] = useState<boolean>(true); + const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - // get item. - setIsLoading(true); + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + // get item. + setIsLoading(true); - setError(''); - try { - const item: Monitor | null = await ModelAPI.getItem({ - modelType: Monitor, - id: modelId, - select: { - monitorType: true, - }, - }); + setError(""); + try { + const item: Monitor | null = await ModelAPI.getItem({ + modelType: Monitor, + id: modelId, + select: { + monitorType: true, + }, + }); - if (!item) { - setError(`Monitor not found`); + if (!item) { + setError(`Monitor not found`); - return; - } + return; + } - setMonitorType(item.monitorType); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - setIsLoading(false); - }; + setMonitorType(item.monitorType); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + setIsLoading(false); + }; - const [monitorType, setMonitorType] = useState<MonitorType | undefined>( - undefined - ); + const [monitorType, setMonitorType] = useState<MonitorType | undefined>( + undefined, + ); - useAsyncEffect(async () => { - // fetch the model - await fetchItem(); - }, []); + useAsyncEffect(async () => { + // fetch the model + await fetchItem(); + }, []); - const getPageContent: GetReactElementFunction = (): ReactElement => { - if (!monitorType || isLoading) { - return <ComponentLoader />; - } + const getPageContent: GetReactElementFunction = (): ReactElement => { + if (!monitorType || isLoading) { + return <ComponentLoader />; + } - if (error) { - return <ErrorMessage error={error} />; - } + if (error) { + return <ErrorMessage error={error} />; + } - if (monitorType === MonitorType.Manual) { - return ( - <EmptyState - id="empty-state-monitoring-interval" - icon={IconProp.Clock} - title={'No Monitoring Interval for Manual Monitors'} - description={ - <> - This is a manual monitor. It does not monitor - anything and so, it cannot have monitoring interval - set. You can have monitoring interval on other - monitor types.{' '} - </> - } - /> - ); - } + if (monitorType === MonitorType.Manual) { + return ( + <EmptyState + id="empty-state-monitoring-interval" + icon={IconProp.Clock} + title={"No Monitoring Interval for Manual Monitors"} + description={ + <> + This is a manual monitor. It does not monitor anything and so, it + cannot have monitoring interval set. You can have monitoring + interval on other monitor types.{" "} + </> + } + /> + ); + } - if (monitorType === MonitorType.IncomingRequest) { - return ( - <EmptyState - id="empty-state-monitoring-interval" - icon={IconProp.Clock} - title={ - 'No Monitoring Interval for Incoming Request / Heartbeat Monitors' - } - description={ - <> - This is a incoming request / heartbeat monitor. - Since OneUptime does not send an outbound request, - we do not need monitoring interval. You can have - monitoring interval on other monitor types.{' '} - </> - } - /> - ); - } - - return ( - <CardModelDetail - name="Monitoring Interval" - editButtonText="Edit Monitoring Interval" - cardProps={{ - title: 'Monitoring Interval', - description: - 'Here is how often we will check your monitor status.', - }} - isEditable={true} - formFields={[ - { - field: { - monitoringInterval: true, - }, - - title: 'Monitoring Interval', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: MonitoringInterval, - required: true, - placeholder: 'Monitoring Interval', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: Monitor, - id: 'model-detail-monitors', - fields: [ - { - field: { - monitoringInterval: true, - }, - title: 'Monitoring Interval', - getElement: (item: Monitor): ReactElement => { - return ( - <MonitoringIntervalElement - monitoringInterval={ - item['monitoringInterval'] as string - } - /> - ); - }, - }, - ], - modelId: modelId, - }} - /> - ); - }; + if (monitorType === MonitorType.IncomingRequest) { + return ( + <EmptyState + id="empty-state-monitoring-interval" + icon={IconProp.Clock} + title={ + "No Monitoring Interval for Incoming Request / Heartbeat Monitors" + } + description={ + <> + This is a incoming request / heartbeat monitor. Since OneUptime + does not send an outbound request, we do not need monitoring + interval. You can have monitoring interval on other monitor types.{" "} + </> + } + /> + ); + } return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> - {getPageContent()} - </Fragment> + <CardModelDetail + name="Monitoring Interval" + editButtonText="Edit Monitoring Interval" + cardProps={{ + title: "Monitoring Interval", + description: "Here is how often we will check your monitor status.", + }} + isEditable={true} + formFields={[ + { + field: { + monitoringInterval: true, + }, + + title: "Monitoring Interval", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: MonitoringInterval, + required: true, + placeholder: "Monitoring Interval", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: Monitor, + id: "model-detail-monitors", + fields: [ + { + field: { + monitoringInterval: true, + }, + title: "Monitoring Interval", + getElement: (item: Monitor): ReactElement => { + return ( + <MonitoringIntervalElement + monitoringInterval={item["monitoringInterval"] as string} + /> + ); + }, + }, + ], + modelId: modelId, + }} + /> ); + }; + + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + {getPageContent()} + </Fragment> + ); }; export default MonitorCriteria; diff --git a/Dashboard/src/Pages/Monitor/View/Layout.tsx b/Dashboard/src/Pages/Monitor/View/Layout.tsx index b116931332..c5d210f623 100644 --- a/Dashboard/src/Pages/Monitor/View/Layout.tsx +++ b/Dashboard/src/Pages/Monitor/View/Layout.tsx @@ -1,84 +1,81 @@ -import { getMonitorBreadcrumbs } from '../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../Utils/RouteMap'; -import SideMenu from './SideMenu'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; -import useAsyncEffect from 'use-async-effect'; +import { getMonitorBreadcrumbs } from "../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../Utils/RouteMap"; +import SideMenu from "./SideMenu"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; +import React, { FunctionComponent, ReactElement, useState } from "react"; +import { Outlet, useParams } from "react-router-dom"; +import useAsyncEffect from "use-async-effect"; const MonitorViewLayout: FunctionComponent = (): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - const [monitor, setMonitor] = useState<Monitor | null>(null); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); + const [monitor, setMonitor] = useState<Monitor | null>(null); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); - useAsyncEffect(async () => { - await fetchItem(); - }, []); + useAsyncEffect(async () => { + await fetchItem(); + }, []); - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - setIsLoading(true); - setError(''); + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); + setError(""); - try { - const item: Monitor | null = await ModelAPI.getItem({ - modelType: Monitor, - id: modelId, - select: { - monitorType: true, - }, - }); + try { + const item: Monitor | null = await ModelAPI.getItem({ + modelType: Monitor, + id: modelId, + select: { + monitorType: true, + }, + }); - setMonitor(item); + setMonitor(item); - if (!item) { - setError(`Monitor not found`); + if (!item) { + setError(`Monitor not found`); - return; - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - if (error) { - return <ErrorMessage error={error} />; + return; + } + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (isLoading || !monitor?.monitorType) { - return <PageLoader isVisible={true} />; - } + setIsLoading(false); + }; - return ( - <ModelPage - title="Monitor" - modelType={Monitor} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={getMonitorBreadcrumbs(path)} - sideMenu={ - <SideMenu - monitorType={monitor?.monitorType} - modelId={modelId} - /> - } - > - <Outlet /> - </ModelPage> - ); + if (error) { + return <ErrorMessage error={error} />; + } + + if (isLoading || !monitor?.monitorType) { + return <PageLoader isVisible={true} />; + } + + return ( + <ModelPage + title="Monitor" + modelType={Monitor} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={getMonitorBreadcrumbs(path)} + sideMenu={ + <SideMenu monitorType={monitor?.monitorType} modelId={modelId} /> + } + > + <Outlet /> + </ModelPage> + ); }; export default MonitorViewLayout; diff --git a/Dashboard/src/Pages/Monitor/View/Owners.tsx b/Dashboard/src/Pages/Monitor/View/Owners.tsx index 93a1a385d2..4de96ab396 100644 --- a/Dashboard/src/Pages/Monitor/View/Owners.tsx +++ b/Dashboard/src/Pages/Monitor/View/Owners.tsx @@ -1,227 +1,222 @@ -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import TeamElement from '../../../Components/Team/Team'; -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import MonitorOwnerTeam from 'Model/Models/MonitorOwnerTeam'; -import MonitorOwnerUser from 'Model/Models/MonitorOwnerUser'; -import Team from 'Model/Models/Team'; -import User from 'Model/Models/User'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import TeamElement from "../../../Components/Team/Team"; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import MonitorOwnerTeam from "Model/Models/MonitorOwnerTeam"; +import MonitorOwnerUser from "Model/Models/MonitorOwnerUser"; +import Team from "Model/Models/Team"; +import User from "Model/Models/User"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const MonitorOwners: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> - <ModelTable<MonitorOwnerTeam> - modelType={MonitorOwnerTeam} - id="table-monitor-owner-team" - name="Monitor > Owner Team" - singularName="Team" - isDeleteable={true} - createVerb={'Add'} - isCreateable={true} - isViewable={false} - showViewIdButton={true} - query={{ - monitorId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: MonitorOwnerTeam - ): Promise<MonitorOwnerTeam> => { - item.monitorId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Teams)', - description: - 'Here is list of teams that own this monitor. They will be alerted when this monitor is created or updated.', - }} - noItemsMessage={'No teams associated with this monitor so far.'} - formFields={[ - { - field: { - team: true, - }, - title: 'Team', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select Team', - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - team: true, - }, - type: FieldType.Entity, - title: 'Team', - filterEntityType: Team, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - team: { - name: true, - }, - }, - title: 'Team', - type: FieldType.Entity, - getElement: (item: MonitorOwnerTeam): ReactElement => { - if (!item['team']) { - throw new BadDataException('Team not found'); - } + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + <ModelTable<MonitorOwnerTeam> + modelType={MonitorOwnerTeam} + id="table-monitor-owner-team" + name="Monitor > Owner Team" + singularName="Team" + isDeleteable={true} + createVerb={"Add"} + isCreateable={true} + isViewable={false} + showViewIdButton={true} + query={{ + monitorId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={(item: MonitorOwnerTeam): Promise<MonitorOwnerTeam> => { + item.monitorId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Teams)", + description: + "Here is list of teams that own this monitor. They will be alerted when this monitor is created or updated.", + }} + noItemsMessage={"No teams associated with this monitor so far."} + formFields={[ + { + field: { + team: true, + }, + title: "Team", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select Team", + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + team: true, + }, + type: FieldType.Entity, + title: "Team", + filterEntityType: Team, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + team: { + name: true, + }, + }, + title: "Team", + type: FieldType.Entity, + getElement: (item: MonitorOwnerTeam): ReactElement => { + if (!item["team"]) { + throw new BadDataException("Team not found"); + } - return <TeamElement team={item['team'] as Team} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> + return <TeamElement team={item["team"] as Team} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> - <ModelTable<MonitorOwnerUser> - modelType={MonitorOwnerUser} - id="table-monitor-owner-team" - name="Monitor > Owner Team" - isDeleteable={true} - singularName="User" - isCreateable={true} - isViewable={false} - showViewIdButton={true} - createVerb={'Add'} - query={{ - monitorId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: MonitorOwnerUser - ): Promise<MonitorOwnerUser> => { - item.monitorId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Users)', - description: - 'Here is list of users that own this monitor. They will be alerted when this monitor is created or updated.', - }} - noItemsMessage={'No users associated with this monitor so far.'} - formFields={[ - { - field: { - user: true, - }, - title: 'User', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select User', - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - user: true, - }, - title: 'User', - type: FieldType.Entity, - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - user: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: 'User', - type: FieldType.Entity, - getElement: (item: MonitorOwnerUser): ReactElement => { - if (!item['user']) { - throw new BadDataException('User not found'); - } + <ModelTable<MonitorOwnerUser> + modelType={MonitorOwnerUser} + id="table-monitor-owner-team" + name="Monitor > Owner Team" + isDeleteable={true} + singularName="User" + isCreateable={true} + isViewable={false} + showViewIdButton={true} + createVerb={"Add"} + query={{ + monitorId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={(item: MonitorOwnerUser): Promise<MonitorOwnerUser> => { + item.monitorId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Users)", + description: + "Here is list of users that own this monitor. They will be alerted when this monitor is created or updated.", + }} + noItemsMessage={"No users associated with this monitor so far."} + formFields={[ + { + field: { + user: true, + }, + title: "User", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select User", + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + user: true, + }, + title: "User", + type: FieldType.Entity, + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + user: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "User", + type: FieldType.Entity, + getElement: (item: MonitorOwnerUser): ReactElement => { + if (!item["user"]) { + throw new BadDataException("User not found"); + } - return <UserElement user={item['user'] as User} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + return <UserElement user={item["user"] as User} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default MonitorOwners; diff --git a/Dashboard/src/Pages/Monitor/View/Probes.tsx b/Dashboard/src/Pages/Monitor/View/Probes.tsx index 32f17a7b34..26a83452e9 100644 --- a/Dashboard/src/Pages/Monitor/View/Probes.tsx +++ b/Dashboard/src/Pages/Monitor/View/Probes.tsx @@ -1,299 +1,289 @@ -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import ProbeStatusElement from '../../../Components/Probe/ProbeStatus'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProbeUtil from '../../../Utils/Probe'; -import PageComponentProps from '../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Modal, { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import ProbeElement from 'CommonUI/src/Components/Probe/Probe'; -import SimpleLogViewer from 'CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; -import MonitorProbe from 'Model/Models/MonitorProbe'; -import Probe from 'Model/Models/Probe'; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import ProbeStatusElement from "../../../Components/Probe/ProbeStatus"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProbeUtil from "../../../Utils/Probe"; +import PageComponentProps from "../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Modal, { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import ProbeElement from "CommonUI/src/Components/Probe/Probe"; +import SimpleLogViewer from "CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; +import MonitorProbe from "Model/Models/MonitorProbe"; +import Probe from "Model/Models/Probe"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; const MonitorProbes: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); - const [logs, setLogs] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(true); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); + const [logs, setLogs] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - const [probes, setProbes] = useState<Array<Probe>>([]); + const [probes, setProbes] = useState<Array<Probe>>([]); - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - // get item. - setIsLoading(true); + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + // get item. + setIsLoading(true); - setError(''); - try { - const item: Monitor | null = await ModelAPI.getItem({ - modelType: Monitor, - id: modelId, - select: { - monitorType: true, - }, - }); + setError(""); + try { + const item: Monitor | null = await ModelAPI.getItem({ + modelType: Monitor, + id: modelId, + select: { + monitorType: true, + }, + }); - if (!item) { - setError(`Monitor not found`); + if (!item) { + setError(`Monitor not found`); - return; - } + return; + } - const probes: Array<Probe> = await ProbeUtil.getAllProbes(); + const probes: Array<Probe> = await ProbeUtil.getAllProbes(); - setProbes(probes); - setMonitorType(item.monitorType); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - setIsLoading(false); - }; + setProbes(probes); + setMonitorType(item.monitorType); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + setIsLoading(false); + }; - const [monitorType, setMonitorType] = useState<MonitorType | undefined>( - undefined - ); + const [monitorType, setMonitorType] = useState<MonitorType | undefined>( + undefined, + ); - useAsyncEffect(async () => { - // fetch the model - await fetchItem(); - }, []); + useAsyncEffect(async () => { + // fetch the model + await fetchItem(); + }, []); - const getPageContent: GetReactElementFunction = (): ReactElement => { - if (!monitorType || isLoading) { - return <ComponentLoader />; - } + const getPageContent: GetReactElementFunction = (): ReactElement => { + if (!monitorType || isLoading) { + return <ComponentLoader />; + } - if (error) { - return <ErrorMessage error={error} />; - } + if (error) { + return <ErrorMessage error={error} />; + } - if (monitorType === MonitorType.Manual) { - return ( - <EmptyState - id="monitoring-probes-empty-state" - icon={IconProp.Signal} - title={'No Monitoring Probes for Manual Monitors'} - description={ - <> - This is a manual monitor. It does not monitor - anything and so, it cannot have monitoring probes - set. You can have monitoring probes on other monitor - types.{' '} - </> - } - /> - ); - } - - return ( - <ModelTable<MonitorProbe> - modelType={MonitorProbe} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - monitorId: modelId.toString(), - }} - onBeforeCreate={(item: MonitorProbe): Promise<MonitorProbe> => { - item.monitorId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - - return Promise.resolve(item); - }} - id="probes-table" - name="Monitor > Monitor Probes" - isDeleteable={false} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Probes', - description: - 'List of probes that help you monitor this resource.', - }} - noItemsMessage={ - 'No probes found for this resource. However, you can add some probes to monitor this resource.' - } - viewPageRoute={Navigation.getCurrentRoute()} - selectMoreFields={{ - lastMonitoringLog: true, - }} - actionButtons={[ - { - title: 'View Logs', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.List, - onClick: async ( - item: MonitorProbe, - onCompleteAction: VoidFunction - ) => { - setLogs( - item['lastMonitoringLog'] - ? JSON.stringify( - item['lastMonitoringLog'], - null, - 2 - ) - : 'Not monitored yet' - ); - setShowViewLogsModal(true); - - onCompleteAction(); - }, - }, - ]} - formFields={[ - { - field: { - probe: true, - }, - title: 'Probe', - stepId: 'incident-details', - description: 'Which probe do you want to use?', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: probes.map((probe: Probe) => { - if (!probe.name || !probe._id) { - throw new BadDataException( - `Probe name or id is missing` - ); - } - - return { - label: probe.name, - value: probe._id, - }; - }), - required: true, - placeholder: 'Probe', - }, - - { - field: { - isEnabled: true, - }, - title: 'Enabled', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - ]} - showRefreshButton={true} - filters={[ - { - field: { - probe: { - name: true, - }, - }, - type: FieldType.Text, - title: 'Probe Name', - }, - { - field: { - isEnabled: true, - }, - title: 'Enabled', - type: FieldType.Boolean, - }, - ]} - columns={[ - { - field: { - probe: { - name: true, - iconFileId: true, - }, - }, - - title: 'Probe', - type: FieldType.Entity, - getElement: (item: MonitorProbe): ReactElement => { - return <ProbeElement probe={item['probe']} />; - }, - }, - { - field: { - probe: { - lastAlive: true, - }, - }, - title: 'Probe Status', - type: FieldType.Text, - - getElement: (item: MonitorProbe): ReactElement => { - return ( - <ProbeStatusElement probe={item['probe']!} /> - ); - }, - }, - { - field: { - lastPingAt: true, - }, - title: 'Last Monitored At', - type: FieldType.DateTime, - - noValueMessage: 'Will be picked up by this probe soon.', - }, - { - field: { - isEnabled: true, - }, - title: 'Enabled', - type: FieldType.Boolean, - }, - ]} - /> - ); - }; + if (monitorType === MonitorType.Manual) { + return ( + <EmptyState + id="monitoring-probes-empty-state" + icon={IconProp.Signal} + title={"No Monitoring Probes for Manual Monitors"} + description={ + <> + This is a manual monitor. It does not monitor anything and so, it + cannot have monitoring probes set. You can have monitoring probes + on other monitor types.{" "} + </> + } + /> + ); + } return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> - {getPageContent()} - {showViewLogsModal && ( - <Modal - title={'Monitoring Logs'} - description="Here are the latest monitoring log for this resource." - isLoading={false} - modalWidth={ModalWidth.Large} - onSubmit={() => { - setShowViewLogsModal(false); - }} - submitButtonText={'Close'} - submitButtonStyleType={ButtonStyleType.NORMAL} - > - <SimpleLogViewer> - {logs.split('\n').map((log: string, i: number) => { - return <div key={i}>{log}</div>; - })} - </SimpleLogViewer> - </Modal> - )} - </Fragment> + <ModelTable<MonitorProbe> + modelType={MonitorProbe} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + monitorId: modelId.toString(), + }} + onBeforeCreate={(item: MonitorProbe): Promise<MonitorProbe> => { + item.monitorId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + + return Promise.resolve(item); + }} + id="probes-table" + name="Monitor > Monitor Probes" + isDeleteable={false} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Probes", + description: "List of probes that help you monitor this resource.", + }} + noItemsMessage={ + "No probes found for this resource. However, you can add some probes to monitor this resource." + } + viewPageRoute={Navigation.getCurrentRoute()} + selectMoreFields={{ + lastMonitoringLog: true, + }} + actionButtons={[ + { + title: "View Logs", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.List, + onClick: async ( + item: MonitorProbe, + onCompleteAction: VoidFunction, + ) => { + setLogs( + item["lastMonitoringLog"] + ? JSON.stringify(item["lastMonitoringLog"], null, 2) + : "Not monitored yet", + ); + setShowViewLogsModal(true); + + onCompleteAction(); + }, + }, + ]} + formFields={[ + { + field: { + probe: true, + }, + title: "Probe", + stepId: "incident-details", + description: "Which probe do you want to use?", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: probes.map((probe: Probe) => { + if (!probe.name || !probe._id) { + throw new BadDataException(`Probe name or id is missing`); + } + + return { + label: probe.name, + value: probe._id, + }; + }), + required: true, + placeholder: "Probe", + }, + + { + field: { + isEnabled: true, + }, + title: "Enabled", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + ]} + showRefreshButton={true} + filters={[ + { + field: { + probe: { + name: true, + }, + }, + type: FieldType.Text, + title: "Probe Name", + }, + { + field: { + isEnabled: true, + }, + title: "Enabled", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + probe: { + name: true, + iconFileId: true, + }, + }, + + title: "Probe", + type: FieldType.Entity, + getElement: (item: MonitorProbe): ReactElement => { + return <ProbeElement probe={item["probe"]} />; + }, + }, + { + field: { + probe: { + lastAlive: true, + }, + }, + title: "Probe Status", + type: FieldType.Text, + + getElement: (item: MonitorProbe): ReactElement => { + return <ProbeStatusElement probe={item["probe"]!} />; + }, + }, + { + field: { + lastPingAt: true, + }, + title: "Last Monitored At", + type: FieldType.DateTime, + + noValueMessage: "Will be picked up by this probe soon.", + }, + { + field: { + isEnabled: true, + }, + title: "Enabled", + type: FieldType.Boolean, + }, + ]} + /> ); + }; + + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + {getPageContent()} + {showViewLogsModal && ( + <Modal + title={"Monitoring Logs"} + description="Here are the latest monitoring log for this resource." + isLoading={false} + modalWidth={ModalWidth.Large} + onSubmit={() => { + setShowViewLogsModal(false); + }} + submitButtonText={"Close"} + submitButtonStyleType={ButtonStyleType.NORMAL} + > + <SimpleLogViewer> + {logs.split("\n").map((log: string, i: number) => { + return <div key={i}>{log}</div>; + })} + </SimpleLogViewer> + </Modal> + )} + </Fragment> + ); }; export default MonitorProbes; diff --git a/Dashboard/src/Pages/Monitor/View/Settings.tsx b/Dashboard/src/Pages/Monitor/View/Settings.tsx index a0073c683f..91c56f53d6 100644 --- a/Dashboard/src/Pages/Monitor/View/Settings.tsx +++ b/Dashboard/src/Pages/Monitor/View/Settings.tsx @@ -1,251 +1,242 @@ -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import DuplicateModel from 'CommonUI/src/Components/DuplicateModel/DuplicateModel'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import InlineCode from 'CommonUI/src/Components/InlineCode/InlineCode'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ResetObjectID from 'CommonUI/src/Components/ResetObjectID/ResetObjectID'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import DuplicateModel from "CommonUI/src/Components/DuplicateModel/DuplicateModel"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import InlineCode from "CommonUI/src/Components/InlineCode/InlineCode"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ResetObjectID from "CommonUI/src/Components/ResetObjectID/ResetObjectID"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; const MonitorCriteria: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [alertRefreshToggle, setAlertRefreshToggle] = - useState<boolean>(false); + const [alertRefreshToggle, setAlertRefreshToggle] = useState<boolean>(false); - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [isLoading, setIsLoading] = useState<boolean>(true); + const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - const [monitor, setMonitor] = useState<Monitor | null>(null); + const [monitor, setMonitor] = useState<Monitor | null>(null); - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - // get item. - setIsLoading(true); + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + // get item. + setIsLoading(true); - setError(''); - try { - const monitor: Monitor | null = await ModelAPI.getItem<Monitor>({ - modelType: Monitor, - id: modelId, - select: { - monitorType: true, - incomingRequestSecretKey: true, - serverMonitorSecretKey: true, - }, - requestOptions: {}, - }); + setError(""); + try { + const monitor: Monitor | null = await ModelAPI.getItem<Monitor>({ + modelType: Monitor, + id: modelId, + select: { + monitorType: true, + incomingRequestSecretKey: true, + serverMonitorSecretKey: true, + }, + requestOptions: {}, + }); - if (!monitor) { - setError(`Monitor not found`); + if (!monitor) { + setError(`Monitor not found`); - return; - } + return; + } - setMonitor(monitor); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - setIsLoading(false); - }; + setMonitor(monitor); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + setIsLoading(false); + }; - useAsyncEffect(async () => { - // fetch the model - await fetchItem(); - }, []); + useAsyncEffect(async () => { + // fetch the model + await fetchItem(); + }, []); - const getPageContent: GetReactElementFunction = (): ReactElement => { - if (!monitor?.monitorType || isLoading) { - return <ComponentLoader />; - } + const getPageContent: GetReactElementFunction = (): ReactElement => { + if (!monitor?.monitorType || isLoading) { + return <ComponentLoader />; + } - if (error) { - return <ErrorMessage error={error} />; - } - - return ( - <div> - {monitor?.monitorType !== MonitorType.Manual && ( - <CardModelDetail - name="Monitor Settings" - editButtonText="Edit Settings" - cardProps={{ - title: 'Monitor Settings', - description: - 'Here are some advanced settings for this monitor.', - }} - onSaveSuccess={() => { - setAlertRefreshToggle(!alertRefreshToggle); - }} - isEditable={true} - formFields={[ - { - field: { - disableActiveMonitoring: true, - }, - - title: 'Disable Active Monitoring', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: Monitor, - id: 'model-detail-monitors', - fields: [ - { - field: { - disableActiveMonitoring: true, - }, - title: 'Disable Active Monitoring', - fieldType: FieldType.Boolean, - }, - ], - modelId: modelId, - }} - /> - )} - - {monitor?.monitorType === MonitorType.IncomingRequest ? ( - <div className="mt-5"> - <ResetObjectID<Monitor> - modelType={Monitor} - onUpdateComplete={async () => { - await fetchItem(); - }} - fieldName={'incomingRequestSecretKey'} - title={'Reset Incoming Request Secret Key'} - description={ - <p className="mt-2"> - Your current incoming request secret key is{' '} - {' '} - <InlineCode - text={ - monitor.incomingRequestSecretKey?.toString() || - 'No key generated' - } - />{' '} - Resetting the secret key will generate a new - key. Secret is used to authenticate incoming - requests. - </p> - } - modelId={modelId} - /> - </div> - ) : ( - <></> - )} - - {monitor?.monitorType === MonitorType.Server ? ( - <div className="mt-5"> - <ResetObjectID<Monitor> - modelType={Monitor} - onUpdateComplete={async () => { - await fetchItem(); - }} - fieldName={'serverMonitorSecretKey'} - title={'Reset Server Monitor Secret Key'} - description={ - <p className="mt-2"> - Your current server monitor secret key is{' '} - {' '} - <InlineCode - text={ - monitor.serverMonitorSecretKey?.toString() || - 'No key generated' - } - />{' '} - Resetting the secret key will generate a new - key. Secret is used to authenticate - monitoring agents deployed on the. - </p> - } - modelId={modelId} - /> - </div> - ) : ( - <></> - )} - - <div className="mt-5"> - <DuplicateModel - modelId={modelId} - modelType={Monitor} - fieldsToDuplicate={{ - description: true, - monitorType: true, - monitorSteps: true, - monitoringInterval: true, - labels: true, - customFields: true, - }} - navigateToOnSuccess={RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS] as Route - )} - fieldsToChange={[ - { - field: { - name: true, - }, - title: 'New Monitor Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'New Monitor Name', - validation: { - minLength: 2, - }, - }, - { - field: { - disableActiveMonitoring: true, - }, - title: 'Disable Monitor', - description: - 'Should the new monitor be disabled when its duplicated?', - fieldType: FormFieldSchemaType.Toggle, - defaultValue: true, - required: false, - }, - ]} - /> - </div> - </div> - ); - }; + if (error) { + return <ErrorMessage error={error} />; + } return ( - <Fragment> - <DisabledWarning - monitorId={modelId} - refreshToggle={alertRefreshToggle} + <div> + {monitor?.monitorType !== MonitorType.Manual && ( + <CardModelDetail + name="Monitor Settings" + editButtonText="Edit Settings" + cardProps={{ + title: "Monitor Settings", + description: "Here are some advanced settings for this monitor.", + }} + onSaveSuccess={() => { + setAlertRefreshToggle(!alertRefreshToggle); + }} + isEditable={true} + formFields={[ + { + field: { + disableActiveMonitoring: true, + }, + + title: "Disable Active Monitoring", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: Monitor, + id: "model-detail-monitors", + fields: [ + { + field: { + disableActiveMonitoring: true, + }, + title: "Disable Active Monitoring", + fieldType: FieldType.Boolean, + }, + ], + modelId: modelId, + }} + /> + )} + + {monitor?.monitorType === MonitorType.IncomingRequest ? ( + <div className="mt-5"> + <ResetObjectID<Monitor> + modelType={Monitor} + onUpdateComplete={async () => { + await fetchItem(); + }} + fieldName={"incomingRequestSecretKey"} + title={"Reset Incoming Request Secret Key"} + description={ + <p className="mt-2"> + Your current incoming request secret key is {" "} + <InlineCode + text={ + monitor.incomingRequestSecretKey?.toString() || + "No key generated" + } + />{" "} + Resetting the secret key will generate a new key. Secret is + used to authenticate incoming requests. + </p> + } + modelId={modelId} /> - {getPageContent()} - </Fragment> + </div> + ) : ( + <></> + )} + + {monitor?.monitorType === MonitorType.Server ? ( + <div className="mt-5"> + <ResetObjectID<Monitor> + modelType={Monitor} + onUpdateComplete={async () => { + await fetchItem(); + }} + fieldName={"serverMonitorSecretKey"} + title={"Reset Server Monitor Secret Key"} + description={ + <p className="mt-2"> + Your current server monitor secret key is {" "} + <InlineCode + text={ + monitor.serverMonitorSecretKey?.toString() || + "No key generated" + } + />{" "} + Resetting the secret key will generate a new key. Secret is + used to authenticate monitoring agents deployed on the. + </p> + } + modelId={modelId} + /> + </div> + ) : ( + <></> + )} + + <div className="mt-5"> + <DuplicateModel + modelId={modelId} + modelType={Monitor} + fieldsToDuplicate={{ + description: true, + monitorType: true, + monitorSteps: true, + monitoringInterval: true, + labels: true, + customFields: true, + }} + navigateToOnSuccess={RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS] as Route, + )} + fieldsToChange={[ + { + field: { + name: true, + }, + title: "New Monitor Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "New Monitor Name", + validation: { + minLength: 2, + }, + }, + { + field: { + disableActiveMonitoring: true, + }, + title: "Disable Monitor", + description: + "Should the new monitor be disabled when its duplicated?", + fieldType: FormFieldSchemaType.Toggle, + defaultValue: true, + required: false, + }, + ]} + /> + </div> + </div> ); + }; + + return ( + <Fragment> + <DisabledWarning monitorId={modelId} refreshToggle={alertRefreshToggle} /> + {getPageContent()} + </Fragment> + ); }; export default MonitorCriteria; diff --git a/Dashboard/src/Pages/Monitor/View/SideMenu.tsx b/Dashboard/src/Pages/Monitor/View/SideMenu.tsx index b452474c95..7a90189bc8 100644 --- a/Dashboard/src/Pages/Monitor/View/SideMenu.tsx +++ b/Dashboard/src/Pages/Monitor/View/SideMenu.tsx @@ -1,181 +1,169 @@ -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 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 MonitorType, { - MonitorTypeHelper, -} from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; + MonitorTypeHelper, +} from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; - monitorType: MonitorType; + modelId: ObjectID; + monitorType: MonitorType; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const isProbeableMonitor: boolean = MonitorTypeHelper.isProbableMonitors( - props.monitorType - ); + const isProbeableMonitor: boolean = MonitorTypeHelper.isProbableMonitors( + props.monitorType, + ); - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_VIEW] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> - <SideMenuItem - link={{ - title: 'Owners', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_VIEW_OWNERS] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Team} - /> - {MonitorTypeHelper.doesMonitorTypeHaveCriteria( - props.monitorType - ) ? ( - <SideMenuItem - link={{ - title: 'Criteria', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.MONITOR_VIEW_CRITERIA - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Criteria} - /> - ) : ( - <></> - )} - {MonitorTypeHelper.isProbableMonitors(props.monitorType) ? ( - <SideMenuItem - link={{ - title: 'Interval', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.MONITOR_VIEW_INTERVAL - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Clock} - /> - ) : ( - <></> - )} - </SideMenuSection> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> + <SideMenuItem + link={{ + title: "Owners", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_OWNERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Team} + /> + {MonitorTypeHelper.doesMonitorTypeHaveCriteria(props.monitorType) ? ( + <SideMenuItem + link={{ + title: "Criteria", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_CRITERIA] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Criteria} + /> + ) : ( + <></> + )} + {MonitorTypeHelper.isProbableMonitors(props.monitorType) ? ( + <SideMenuItem + link={{ + title: "Interval", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_INTERVAL] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Clock} + /> + ) : ( + <></> + )} + </SideMenuSection> - <SideMenuSection title="Timeline and Incidents"> - <SideMenuItem - link={{ - title: 'Status Timeline', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.MONITOR_VIEW_STATUS_TIMELINE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.List} - /> - <SideMenuItem - link={{ - title: 'Incidents', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_VIEW_INCIDENTS] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Alert} - /> - </SideMenuSection> + <SideMenuSection title="Timeline and Incidents"> + <SideMenuItem + link={{ + title: "Status Timeline", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_STATUS_TIMELINE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.List} + /> + <SideMenuItem + link={{ + title: "Incidents", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_INCIDENTS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Alert} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - {MonitorTypeHelper.doesMonitorTypeHaveDocumentation( - props.monitorType - ) ? ( - <SideMenuItem - link={{ - title: 'Documentation', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.MONITOR_VIEW_DOCUMENTATION - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Book} - /> - ) : ( - <></> - )} - {isProbeableMonitor ? ( - <SideMenuItem - link={{ - title: 'Probes', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_VIEW_PROBES] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Signal} - /> - ) : ( - <></> - )} - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.MONITOR_VIEW_CUSTOM_FIELDS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.TableCells} - /> - <SideMenuItem - link={{ - title: 'Settings', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_VIEW_SETTINGS] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Settings} - /> - <SideMenuItem - link={{ - title: 'Delete Monitor', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_VIEW_DELETE] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuSection title="Advanced"> + {MonitorTypeHelper.doesMonitorTypeHaveDocumentation( + props.monitorType, + ) ? ( + <SideMenuItem + link={{ + title: "Documentation", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_DOCUMENTATION] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Book} + /> + ) : ( + <></> + )} + {isProbeableMonitor ? ( + <SideMenuItem + link={{ + title: "Probes", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_PROBES] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Signal} + /> + ) : ( + <></> + )} + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_CUSTOM_FIELDS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.TableCells} + /> + <SideMenuItem + link={{ + title: "Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_SETTINGS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Settings} + /> + <SideMenuItem + link={{ + title: "Delete Monitor", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Monitor/View/StatusTimeline.tsx b/Dashboard/src/Pages/Monitor/View/StatusTimeline.tsx index 044b370c8a..7a3514b0bc 100644 --- a/Dashboard/src/Pages/Monitor/View/StatusTimeline.tsx +++ b/Dashboard/src/Pages/Monitor/View/StatusTimeline.tsx @@ -1,278 +1,264 @@ -import DisabledWarning from '../../../Components/Monitor/DisabledWarning'; -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import { Black } from 'Common/Types/BrandColors'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import MarkdownViewer from 'CommonUI/src/Components/Markdown.tsx/MarkdownViewer'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import Modal, { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import SimpleLogViewer from 'CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; +import DisabledWarning from "../../../Components/Monitor/DisabledWarning"; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import { Black } from "Common/Types/BrandColors"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import MarkdownViewer from "CommonUI/src/Components/Markdown.tsx/MarkdownViewer"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import Modal, { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import SimpleLogViewer from "CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const StatusTimeline: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); - const [logs, setLogs] = useState<string>(''); + const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); + const [logs, setLogs] = useState<string>(""); - const [showRootCause, setShowRootCause] = useState<boolean>(false); - const [rootCause, setRootCause] = useState<string>(''); + const [showRootCause, setShowRootCause] = useState<boolean>(false); + const [rootCause, setRootCause] = useState<string>(""); - return ( - <Fragment> - <DisabledWarning monitorId={modelId} /> - <ModelTable<MonitorStatusTimeline> - modelType={MonitorStatusTimeline} - id="table-monitor-status-timeline" - name="Monitor > Status Timeline" - isDeleteable={true} - showViewIdButton={true} - isCreateable={true} - isViewable={false} - selectMoreFields={{ - statusChangeLog: true, - rootCause: true, - }} - actionButtons={[ - { - title: 'View Cause', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.TransparentCube, - onClick: async ( - item: MonitorStatusTimeline, - onCompleteAction: VoidFunction - ) => { - setRootCause( - item['rootCause'] - ? item['rootCause'].toString() - : 'No root cause. This monitor status could be created manually.' - ); - setShowRootCause(true); + return ( + <Fragment> + <DisabledWarning monitorId={modelId} /> + <ModelTable<MonitorStatusTimeline> + modelType={MonitorStatusTimeline} + id="table-monitor-status-timeline" + name="Monitor > Status Timeline" + isDeleteable={true} + showViewIdButton={true} + isCreateable={true} + isViewable={false} + selectMoreFields={{ + statusChangeLog: true, + rootCause: true, + }} + actionButtons={[ + { + title: "View Cause", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.TransparentCube, + onClick: async ( + item: MonitorStatusTimeline, + onCompleteAction: VoidFunction, + ) => { + setRootCause( + item["rootCause"] + ? item["rootCause"].toString() + : "No root cause. This monitor status could be created manually.", + ); + setShowRootCause(true); - onCompleteAction(); - }, - }, - { - title: 'View Logs', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.List, - onClick: async ( - item: MonitorStatusTimeline, - onCompleteAction: VoidFunction - ) => { - setLogs( - item['statusChangeLog'] - ? JSON.stringify( - item['statusChangeLog'], - null, - 2 - ) - : 'No logs for this status event.' - ); - setShowViewLogsModal(true); + onCompleteAction(); + }, + }, + { + title: "View Logs", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.List, + onClick: async ( + item: MonitorStatusTimeline, + onCompleteAction: VoidFunction, + ) => { + setLogs( + item["statusChangeLog"] + ? JSON.stringify(item["statusChangeLog"], null, 2) + : "No logs for this status event.", + ); + setShowViewLogsModal(true); - onCompleteAction(); - }, - }, - ]} - query={{ - monitorId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: MonitorStatusTimeline - ): Promise<MonitorStatusTimeline> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.monitorId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Status Timeline', - description: 'Here is the status timeline for this monitor', - }} - noItemsMessage={ - 'No status timeline created for this monitor so far.' - } - formFields={[ - { - field: { - monitorStatus: true, - }, - title: 'Monitor Status', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Monitor Status', - dropdownModal: { - type: MonitorStatus, - labelField: 'name', - valueField: '_id', - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - monitorStatus: { - name: true, - }, - }, - title: 'Monitor Status', - type: FieldType.Entity, - filterEntityType: MonitorStatus, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Starts At', - type: FieldType.Date, - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - monitorStatus: { - name: true, - color: true, - }, - }, - title: 'Monitor Status', - type: FieldType.Text, - getElement: ( - item: MonitorStatusTimeline - ): ReactElement => { - if (!item['monitorStatus']) { - throw new BadDataException( - 'Monitor Status not found' - ); - } + onCompleteAction(); + }, + }, + ]} + query={{ + monitorId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: MonitorStatusTimeline, + ): Promise<MonitorStatusTimeline> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.monitorId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Status Timeline", + description: "Here is the status timeline for this monitor", + }} + noItemsMessage={"No status timeline created for this monitor so far."} + formFields={[ + { + field: { + monitorStatus: true, + }, + title: "Monitor Status", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Monitor Status", + dropdownModal: { + type: MonitorStatus, + labelField: "name", + valueField: "_id", + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + monitorStatus: { + name: true, + }, + }, + title: "Monitor Status", + type: FieldType.Entity, + filterEntityType: MonitorStatus, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Starts At", + type: FieldType.Date, + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + monitorStatus: { + name: true, + color: true, + }, + }, + title: "Monitor Status", + type: FieldType.Text, + getElement: (item: MonitorStatusTimeline): ReactElement => { + if (!item["monitorStatus"]) { + throw new BadDataException("Monitor Status not found"); + } - return ( - <Statusbubble - color={item.monitorStatus.color || Black} - shouldAnimate={false} - text={item.monitorStatus.name || 'Unknown'} - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Starts At', - type: FieldType.DateTime, - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - type: FieldType.DateTime, - noValueMessage: 'Currently Active', - }, - { - field: { - endsAt: true, - }, - title: 'Duration', - type: FieldType.Text, - getElement: ( - item: MonitorStatusTimeline - ): ReactElement => { - return ( - <p> - {OneUptimeDate.differenceBetweenTwoDatesAsFromattedString( - item['createdAt'] as Date, - (item['endsAt'] as Date) || - OneUptimeDate.getCurrentDate() - )} - </p> - ); - }, - }, - ]} - /> - {showViewLogsModal ? ( - <Modal - title={'Why did the status change?'} - description="Here is more information about why the status changed for this monitor." - isLoading={false} - modalWidth={ModalWidth.Large} - onSubmit={() => { - setShowViewLogsModal(false); - }} - submitButtonText={'Close'} - submitButtonStyleType={ButtonStyleType.NORMAL} - > - <SimpleLogViewer> - {logs.split('\n').map((log: string, i: number) => { - return <div key={i}>{log}</div>; - })} - </SimpleLogViewer> - </Modal> - ) : ( - <></> - )} - - {showRootCause ? ( - <ConfirmModal - title={'Root Cause'} - description={ - <div> - <MarkdownViewer text={rootCause} /> - </div> - } - isLoading={false} - onSubmit={() => { - setShowRootCause(false); - }} - submitButtonText={'Close'} - submitButtonType={ButtonStyleType.NORMAL} + return ( + <Statusbubble + color={item.monitorStatus.color || Black} + shouldAnimate={false} + text={item.monitorStatus.name || "Unknown"} /> - ) : ( - <></> - )} - </Fragment> - ); + ); + }, + }, + { + field: { + createdAt: true, + }, + title: "Starts At", + type: FieldType.DateTime, + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + type: FieldType.DateTime, + noValueMessage: "Currently Active", + }, + { + field: { + endsAt: true, + }, + title: "Duration", + type: FieldType.Text, + getElement: (item: MonitorStatusTimeline): ReactElement => { + return ( + <p> + {OneUptimeDate.differenceBetweenTwoDatesAsFromattedString( + item["createdAt"] as Date, + (item["endsAt"] as Date) || OneUptimeDate.getCurrentDate(), + )} + </p> + ); + }, + }, + ]} + /> + {showViewLogsModal ? ( + <Modal + title={"Why did the status change?"} + description="Here is more information about why the status changed for this monitor." + isLoading={false} + modalWidth={ModalWidth.Large} + onSubmit={() => { + setShowViewLogsModal(false); + }} + submitButtonText={"Close"} + submitButtonStyleType={ButtonStyleType.NORMAL} + > + <SimpleLogViewer> + {logs.split("\n").map((log: string, i: number) => { + return <div key={i}>{log}</div>; + })} + </SimpleLogViewer> + </Modal> + ) : ( + <></> + )} + + {showRootCause ? ( + <ConfirmModal + title={"Root Cause"} + description={ + <div> + <MarkdownViewer text={rootCause} /> + </div> + } + isLoading={false} + onSubmit={() => { + setShowRootCause(false); + }} + submitButtonText={"Close"} + submitButtonType={ButtonStyleType.NORMAL} + /> + ) : ( + <></> + )} + </Fragment> + ); }; export default StatusTimeline; diff --git a/Dashboard/src/Pages/MonitorGroup/MonitorGroups.tsx b/Dashboard/src/Pages/MonitorGroup/MonitorGroups.tsx index 926aaf52f9..6d2d26dc6d 100644 --- a/Dashboard/src/Pages/MonitorGroup/MonitorGroups.tsx +++ b/Dashboard/src/Pages/MonitorGroup/MonitorGroups.tsx @@ -1,175 +1,163 @@ -import LabelsElement from '../../Components/Label/Labels'; -import CurrentStatusElement from '../../Components/MonitorGroup/CurrentStatus'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import DashboardSideMenu from '../Monitor/SideMenu'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Page from 'CommonUI/src/Components/Page/Page'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import MonitorGroup from 'Model/Models/MonitorGroup'; -import React, { FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import CurrentStatusElement from "../../Components/MonitorGroup/CurrentStatus"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import DashboardSideMenu from "../Monitor/SideMenu"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Page from "CommonUI/src/Components/Page/Page"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import MonitorGroup from "Model/Models/MonitorGroup"; +import React, { FunctionComponent, ReactElement } from "react"; const MonitorGroupPage: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'Monitors'} - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Monitors', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITORS] as Route - ), - }, - { - title: 'Monitor Groups', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_GROUPS] as Route - ), - }, - ]} - sideMenu={ - <DashboardSideMenu - project={props.currentProject || undefined} + return ( + <Page + title={"Monitors"} + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Monitors", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITORS] as Route, + ), + }, + { + title: "Monitor Groups", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_GROUPS] as Route, + ), + }, + ]} + sideMenu={ + <DashboardSideMenu project={props.currentProject || undefined} /> + } + > + <ModelTable<MonitorGroup> + modelType={MonitorGroup} + name="Monitor Groups" + id="monitors-group-table" + isDeleteable={false} + showViewIdButton={true} + isEditable={false} + isCreateable={true} + isViewable={true} + cardProps={{ + title: "Monitor Groups", + description: "Here is a list of monitors groups for this project.", + }} + noItemsMessage={"No monitor groups found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Monitor Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + ]} + viewPageRoute={Navigation.getCurrentRoute()} + showRefreshButton={true} + filters={[ + { + field: { + name: true, + }, + title: "Group Name", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Group Name", + type: FieldType.Text, + }, + { + field: { + _id: true, + }, + title: "Current Status", + type: FieldType.Element, + getElement: (item: MonitorGroup): ReactElement => { + if (!item["_id"]) { + throw new BadDataException("Monitor Group ID not found"); + } + + return ( + <CurrentStatusElement + monitorGroupId={new ObjectID(item["_id"].toString())} /> - } - > - <ModelTable<MonitorGroup> - modelType={MonitorGroup} - name="Monitor Groups" - id="monitors-group-table" - isDeleteable={false} - showViewIdButton={true} - isEditable={false} - isCreateable={true} - isViewable={true} - cardProps={{ - title: 'Monitor Groups', - description: - 'Here is a list of monitors groups for this project.', - }} - noItemsMessage={'No monitor groups found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', + ); + }, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Monitor Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - ]} - viewPageRoute={Navigation.getCurrentRoute()} - showRefreshButton={true} - filters={[ - { - field: { - name: true, - }, - title: 'Group Name', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Group Name', - type: FieldType.Text, - }, - { - field: { - _id: true, - }, - title: 'Current Status', - type: FieldType.Element, - getElement: (item: MonitorGroup): ReactElement => { - if (!item['_id']) { - throw new BadDataException( - 'Monitor Group ID not found' - ); - } - - return ( - <CurrentStatusElement - monitorGroupId={ - new ObjectID(item['_id'].toString()) - } - /> - ); - }, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - - getElement: (item: MonitorGroup): ReactElement => { - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> - </Page> - ); + getElement: (item: MonitorGroup): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + </Page> + ); }; export default MonitorGroupPage; diff --git a/Dashboard/src/Pages/MonitorGroup/View/Delete.tsx b/Dashboard/src/Pages/MonitorGroup/View/Delete.tsx index 886d7cc222..eb44c11117 100644 --- a/Dashboard/src/Pages/MonitorGroup/View/Delete.tsx +++ b/Dashboard/src/Pages/MonitorGroup/View/Delete.tsx @@ -1,34 +1,34 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import MonitorGroup from 'Model/Models/MonitorGroup'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import MonitorGroup from "Model/Models/MonitorGroup"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const MonitorGroupDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={MonitorGroup} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_GROUPS] as Route, - { modelId } - ) - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={MonitorGroup} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate( + RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_GROUPS] as Route, + { modelId }, + ), + ); + }} + /> + </Fragment> + ); }; export default MonitorGroupDelete; diff --git a/Dashboard/src/Pages/MonitorGroup/View/Incidents.tsx b/Dashboard/src/Pages/MonitorGroup/View/Incidents.tsx index 98f830e1e4..9cdb99a5d3 100644 --- a/Dashboard/src/Pages/MonitorGroup/View/Incidents.tsx +++ b/Dashboard/src/Pages/MonitorGroup/View/Incidents.tsx @@ -1,88 +1,88 @@ -import IncidentsTable from '../../../Components/Incident/IncidentsTable'; -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import MonitorGroupResource from 'Model/Models/MonitorGroupResource'; +import IncidentsTable from "../../../Components/Incident/IncidentsTable"; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import MonitorGroupResource from "Model/Models/MonitorGroupResource"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useEffect, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useEffect, +} from "react"; const MonitorIncidents: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [isLoading, setIsLoading] = React.useState<boolean>(true); + const [isLoading, setIsLoading] = React.useState<boolean>(true); - const [monitorIds, setMonitorIds] = React.useState<ObjectID[]>([]); + const [monitorIds, setMonitorIds] = React.useState<ObjectID[]>([]); - const [error, setError] = React.useState<string | undefined>(undefined); + const [error, setError] = React.useState<string | undefined>(undefined); - const loadMonitorsIds: PromiseVoidFunction = async (): Promise<void> => { - setIsLoading(true); + const loadMonitorsIds: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); - try { - const monitorGroupResources: ListResult<MonitorGroupResource> = - await ModelAPI.getList({ - modelType: MonitorGroupResource, - query: { - monitorGroupId: modelId.toString(), - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - monitorId: true, - }, - sort: {}, - }); + try { + const monitorGroupResources: ListResult<MonitorGroupResource> = + await ModelAPI.getList({ + modelType: MonitorGroupResource, + query: { + monitorGroupId: modelId.toString(), + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + monitorId: true, + }, + sort: {}, + }); - const monitorIds: Array<ObjectID> = monitorGroupResources.data.map( - (monitorGroupResource: MonitorGroupResource): ObjectID => { - return monitorGroupResource.monitorId!; - } - ); + const monitorIds: Array<ObjectID> = monitorGroupResources.data.map( + (monitorGroupResource: MonitorGroupResource): ObjectID => { + return monitorGroupResource.monitorId!; + }, + ); - setMonitorIds(monitorIds); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - useEffect(() => { - loadMonitorsIds().catch(() => {}); - }, []); - - if (isLoading) { - return <PageLoader isVisible={true} />; + setMonitorIds(monitorIds); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (error) { - return <ErrorMessage error={error} />; - } + setIsLoading(false); + }; - return ( - <Fragment> - <IncidentsTable - viewPageRoute={Navigation.getCurrentRoute()} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - monitors: monitorIds, - }} - /> - </Fragment> - ); + useEffect(() => { + loadMonitorsIds().catch(() => {}); + }, []); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <Fragment> + <IncidentsTable + viewPageRoute={Navigation.getCurrentRoute()} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + monitors: monitorIds, + }} + /> + </Fragment> + ); }; export default MonitorIncidents; diff --git a/Dashboard/src/Pages/MonitorGroup/View/Index.tsx b/Dashboard/src/Pages/MonitorGroup/View/Index.tsx index c20714c2cd..83db144c96 100644 --- a/Dashboard/src/Pages/MonitorGroup/View/Index.tsx +++ b/Dashboard/src/Pages/MonitorGroup/View/Index.tsx @@ -1,309 +1,301 @@ -import LabelsElement from '../../../Components/Label/Labels'; -import PageComponentProps from '../../PageComponentProps'; -import URL from 'Common/Types/API/URL'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { Green } from 'Common/Types/BrandColors'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import Card from 'CommonUI/src/Components/Card/Card'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import MonitorUptimeGraph from 'CommonUI/src/Components/MonitorGraphs/Uptime'; -import UptimeUtil from 'CommonUI/src/Components/MonitorGraphs/UptimeUtil'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { APP_API_URL } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Label from 'Model/Models/Label'; -import MonitorGroup from 'Model/Models/MonitorGroup'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import { UptimePrecision } from 'Model/Models/StatusPageResource'; +import LabelsElement from "../../../Components/Label/Labels"; +import PageComponentProps from "../../PageComponentProps"; +import URL from "Common/Types/API/URL"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { Green } from "Common/Types/BrandColors"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import Card from "CommonUI/src/Components/Card/Card"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import MonitorUptimeGraph from "CommonUI/src/Components/MonitorGraphs/Uptime"; +import UptimeUtil from "CommonUI/src/Components/MonitorGraphs/UptimeUtil"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { APP_API_URL } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Label from "Model/Models/Label"; +import MonitorGroup from "Model/Models/MonitorGroup"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import { UptimePrecision } from "Model/Models/StatusPageResource"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; const MonitorGroupView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - const [currentGroupStatus, setCurrentGroupStatus] = - React.useState<MonitorStatus | null>(null); + const [currentGroupStatus, setCurrentGroupStatus] = + React.useState<MonitorStatus | null>(null); - const [statusTimelines, setStatusTimelines] = useState< - Array<MonitorStatusTimeline> - >([]); - const [downTimeMonitorStatues, setDowntimeMonitorStatues] = useState< - Array<MonitorStatus> - >([]); + const [statusTimelines, setStatusTimelines] = useState< + Array<MonitorStatusTimeline> + >([]); + const [downTimeMonitorStatues, setDowntimeMonitorStatues] = useState< + Array<MonitorStatus> + >([]); - const [error, setError] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(true); - - const getUptimePercent: () => ReactElement = (): ReactElement => { - if (isLoading) { - return <></>; - } - - const uptimePercent: number = UptimeUtil.calculateUptimePercentage( - statusTimelines, - UptimePrecision.THREE_DECIMAL, - downTimeMonitorStatues - ); - - return ( - <div - className="font-medium mt-5" - style={{ - color: - currentGroupStatus?.color?.toString() || - Green.toString(), - }} - > - {uptimePercent}% uptime - </div> - ); - }; - - const getCurrentStatusBubble: () => ReactElement = (): ReactElement => { - if (isLoading) { - return <></>; - } - - return ( - <Statusbubble - text={currentGroupStatus?.name || 'Operational'} - color={currentGroupStatus?.color || Green} - shouldAnimate={true} - /> - ); - }; - - useAsyncEffect(async () => { - await fetchItem(); - }, []); - - const fetchItem: PromiseVoidFunction = async (): Promise<void> => { - setIsLoading(true); - setError(''); - - try { - const statusTimelines: ListResult<MonitorStatusTimeline> = - await ModelAPI.getList({ - modelType: MonitorStatusTimeline, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: {}, - sort: {}, - requestOptions: { - overrideRequestUrl: URL.fromString( - APP_API_URL.toString() - ) - .addRoute(new MonitorGroup().getCrudApiPath()!) - .addRoute('/timeline/') - .addRoute(`/${modelId.toString()}`), - }, - }); - - const monitorStatuses: ListResult<MonitorStatus> = - await ModelAPI.getList({ - modelType: MonitorStatus, - query: { - projectId: ProjectUtil.getCurrentProjectId(), - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - priority: true, - isOperationalState: true, - name: true, - color: true, - }, - sort: { - priority: SortOrder.Ascending, - }, - }); - - const currentStatus: MonitorStatus | null = - await ModelAPI.post<MonitorStatus>({ - modelType: MonitorStatus, - apiUrl: URL.fromString(APP_API_URL.toString()) - .addRoute(new MonitorGroup().getCrudApiPath()!) - .addRoute('/current-status/') - .addRoute(`/${modelId.toString()}`), - }); - - setCurrentGroupStatus(currentStatus); - setStatusTimelines(statusTimelines.data); - setDowntimeMonitorStatues( - monitorStatuses.data.filter((status: MonitorStatus) => { - return !status.isOperationalState; - }) - ); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; + const [error, setError] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(true); + const getUptimePercent: () => ReactElement = (): ReactElement => { if (isLoading) { - return <PageLoader isVisible={true} />; + return <></>; } - if (error) { - return <ErrorMessage error={error} />; + const uptimePercent: number = UptimeUtil.calculateUptimePercentage( + statusTimelines, + UptimePrecision.THREE_DECIMAL, + downTimeMonitorStatues, + ); + + return ( + <div + className="font-medium mt-5" + style={{ + color: currentGroupStatus?.color?.toString() || Green.toString(), + }} + > + {uptimePercent}% uptime + </div> + ); + }; + + const getCurrentStatusBubble: () => ReactElement = (): ReactElement => { + if (isLoading) { + return <></>; } return ( - <Fragment> - {/* MonitorGroup View */} - <CardModelDetail<MonitorGroup> - name="MonitorGroup Details" - formSteps={[ - { - title: 'Monitor Group Info', - id: 'monitor-info', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - cardProps={{ - title: 'Monitor Group Details', - description: - 'Here are more details for this monitor group.', - }} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - stepId: 'monitor-info', - title: 'Group Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Monitor Group Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'monitor-info', - title: 'Group Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - labels: true, - }, - stepId: 'labels', - title: 'Labels ', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: MonitorGroup, - id: 'model-detail-monitors', - fields: [ - { - field: { - _id: true, - }, - title: 'Monitor Group ID', - }, - { - field: { - name: true, - }, - title: 'Monitor Group Name', - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: (item: MonitorGroup): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - }, - { - field: { - _id: true, - }, - fieldType: FieldType.Element, - title: 'Current Status', - getElement: () => { - return getCurrentStatusBubble(); - }, - }, - ], - modelId: modelId, - }} - /> - - <Card - title="Uptime Graph" - description="Here the 90 day uptime history of this monitor group." - rightElement={getUptimePercent()} - > - <MonitorUptimeGraph - error={error} - items={statusTimelines} - startDate={OneUptimeDate.getSomeDaysAgo(90)} - endDate={OneUptimeDate.getCurrentDate()} - isLoading={isLoading} - defaultBarColor={Green} - downtimeMonitorStatuses={downTimeMonitorStatues} - /> - </Card> - </Fragment> + <Statusbubble + text={currentGroupStatus?.name || "Operational"} + color={currentGroupStatus?.color || Green} + shouldAnimate={true} + /> ); + }; + + useAsyncEffect(async () => { + await fetchItem(); + }, []); + + const fetchItem: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); + setError(""); + + try { + const statusTimelines: ListResult<MonitorStatusTimeline> = + await ModelAPI.getList({ + modelType: MonitorStatusTimeline, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: {}, + sort: {}, + requestOptions: { + overrideRequestUrl: URL.fromString(APP_API_URL.toString()) + .addRoute(new MonitorGroup().getCrudApiPath()!) + .addRoute("/timeline/") + .addRoute(`/${modelId.toString()}`), + }, + }); + + const monitorStatuses: ListResult<MonitorStatus> = await ModelAPI.getList( + { + modelType: MonitorStatus, + query: { + projectId: ProjectUtil.getCurrentProjectId(), + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + priority: true, + isOperationalState: true, + name: true, + color: true, + }, + sort: { + priority: SortOrder.Ascending, + }, + }, + ); + + const currentStatus: MonitorStatus | null = + await ModelAPI.post<MonitorStatus>({ + modelType: MonitorStatus, + apiUrl: URL.fromString(APP_API_URL.toString()) + .addRoute(new MonitorGroup().getCrudApiPath()!) + .addRoute("/current-status/") + .addRoute(`/${modelId.toString()}`), + }); + + setCurrentGroupStatus(currentStatus); + setStatusTimelines(statusTimelines.data); + setDowntimeMonitorStatues( + monitorStatuses.data.filter((status: MonitorStatus) => { + return !status.isOperationalState; + }), + ); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <Fragment> + {/* MonitorGroup View */} + <CardModelDetail<MonitorGroup> + name="MonitorGroup Details" + formSteps={[ + { + title: "Monitor Group Info", + id: "monitor-info", + }, + { + title: "Labels", + id: "labels", + }, + ]} + cardProps={{ + title: "Monitor Group Details", + description: "Here are more details for this monitor group.", + }} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + stepId: "monitor-info", + title: "Group Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Monitor Group Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "monitor-info", + title: "Group Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + labels: true, + }, + stepId: "labels", + title: "Labels ", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: MonitorGroup, + id: "model-detail-monitors", + fields: [ + { + field: { + _id: true, + }, + title: "Monitor Group ID", + }, + { + field: { + name: true, + }, + title: "Monitor Group Name", + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: MonitorGroup): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + }, + { + field: { + _id: true, + }, + fieldType: FieldType.Element, + title: "Current Status", + getElement: () => { + return getCurrentStatusBubble(); + }, + }, + ], + modelId: modelId, + }} + /> + + <Card + title="Uptime Graph" + description="Here the 90 day uptime history of this monitor group." + rightElement={getUptimePercent()} + > + <MonitorUptimeGraph + error={error} + items={statusTimelines} + startDate={OneUptimeDate.getSomeDaysAgo(90)} + endDate={OneUptimeDate.getCurrentDate()} + isLoading={isLoading} + defaultBarColor={Green} + downtimeMonitorStatuses={downTimeMonitorStatues} + /> + </Card> + </Fragment> + ); }; export default MonitorGroupView; diff --git a/Dashboard/src/Pages/MonitorGroup/View/Layout.tsx b/Dashboard/src/Pages/MonitorGroup/View/Layout.tsx index 2cc3f2a56b..13c7e3aec3 100644 --- a/Dashboard/src/Pages/MonitorGroup/View/Layout.tsx +++ b/Dashboard/src/Pages/MonitorGroup/View/Layout.tsx @@ -1,32 +1,32 @@ -import { getMonitorGroupBreadcrumbs } from '../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import MonitorGroup from 'Model/Models/MonitorGroup'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getMonitorGroupBreadcrumbs } from "../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import MonitorGroup from "Model/Models/MonitorGroup"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const MonitorGroupViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="Monitor Group" - modelType={MonitorGroup} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={getMonitorGroupBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <ModelPage + title="Monitor Group" + modelType={MonitorGroup} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={getMonitorGroupBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default MonitorGroupViewLayout; diff --git a/Dashboard/src/Pages/MonitorGroup/View/Monitors.tsx b/Dashboard/src/Pages/MonitorGroup/View/Monitors.tsx index 582298d3db..38306f2ae5 100644 --- a/Dashboard/src/Pages/MonitorGroup/View/Monitors.tsx +++ b/Dashboard/src/Pages/MonitorGroup/View/Monitors.tsx @@ -1,246 +1,213 @@ -import MonitorElement from '../../../Components/Monitor/Monitor'; -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import Color from 'Common/Types/Color'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; -import MonitorGroupResource from 'Model/Models/MonitorGroupResource'; -import MonitorStatus from 'Model/Models/MonitorStatus'; +import MonitorElement from "../../../Components/Monitor/Monitor"; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import Color from "Common/Types/Color"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; +import MonitorGroupResource from "Model/Models/MonitorGroupResource"; +import MonitorStatus from "Model/Models/MonitorStatus"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useEffect, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useEffect, +} from "react"; const MonitorGroupResources: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [isLoading, setIsLoading] = React.useState<boolean>(true); + const [isLoading, setIsLoading] = React.useState<boolean>(true); - const [monitorStatuses, setMonitorStatuses] = React.useState< - MonitorStatus[] - >([]); + const [monitorStatuses, setMonitorStatuses] = React.useState<MonitorStatus[]>( + [], + ); - const [error, setError] = React.useState<string | undefined>(undefined); + const [error, setError] = React.useState<string | undefined>(undefined); - const loadMonitorStatuses: PromiseVoidFunction = - async (): Promise<void> => { - setIsLoading(true); + const loadMonitorStatuses: PromiseVoidFunction = async (): Promise<void> => { + setIsLoading(true); - try { - const monitorStatuses: ListResult<MonitorStatus> = - await ModelAPI.getList<MonitorStatus>({ - modelType: MonitorStatus, - query: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - name: true, - color: true, - }, - sort: {}, - }); + try { + const monitorStatuses: ListResult<MonitorStatus> = + await ModelAPI.getList<MonitorStatus>({ + modelType: MonitorStatus, + query: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + name: true, + color: true, + }, + sort: {}, + }); - setMonitorStatuses(monitorStatuses.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); + setMonitorStatuses(monitorStatuses.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + loadMonitorStatuses().catch(() => {}); + }, []); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <Fragment> + <> + <ModelTable<MonitorGroupResource> + modelType={MonitorGroupResource} + id={`monitor-group-resources`} + isDeleteable={true} + name="Monitor Group > Resources" + showViewIdButton={true} + isCreateable={true} + isViewable={false} + isEditable={true} + query={{ + monitorGroupId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: MonitorGroupResource, + ): Promise<MonitorGroupResource> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); } + item.monitorGroupId = modelId; + item.projectId = new ObjectID(props.currentProject._id); - setIsLoading(false); - }; + return Promise.resolve(item); + }} + cardProps={{ + title: `Monitor Group Resources`, + description: "Resources that belong to this monitor group.", + }} + noItemsMessage={"No resources have been added to this monitor group."} + formFields={[ + { + field: { + monitor: true, + }, + title: "Monitor", + description: "Select monitor that will be added to this group.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: Monitor, + labelField: "name", + valueField: "_id", + }, + required: true, + placeholder: "Select Monitor", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + monitor: { + name: true, + }, + }, + type: FieldType.Entity, + filterEntityType: Monitor, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + title: "Monitor Name", + }, + ]} + columns={[ + { + field: { + monitor: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Monitor", + type: FieldType.Entity, - useEffect(() => { - loadMonitorStatuses().catch(() => {}); - }, []); + getElement: (item: MonitorGroupResource): ReactElement => { + return <MonitorElement monitor={item["monitor"]!} />; + }, + }, + { + field: { + monitor: { + currentMonitorStatusId: true, + }, + }, + title: "Current Status", + type: FieldType.Element, - if (isLoading) { - return <PageLoader isVisible={true} />; - } + getElement: (item: MonitorGroupResource): ReactElement => { + if (!item["monitor"]) { + throw new BadDataException("Monitor not found"); + } - if (error) { - return <ErrorMessage error={error} />; - } + if (!item["monitor"]["currentMonitorStatusId"]) { + throw new BadDataException("Monitor Status not found"); + } - return ( - <Fragment> - <> - <ModelTable<MonitorGroupResource> - modelType={MonitorGroupResource} - id={`monitor-group-resources`} - isDeleteable={true} - name="Monitor Group > Resources" - showViewIdButton={true} - isCreateable={true} - isViewable={false} - isEditable={true} - query={{ - monitorGroupId: modelId, - projectId: - DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: MonitorGroupResource - ): Promise<MonitorGroupResource> => { - if ( - !props.currentProject || - !props.currentProject._id - ) { - throw new BadDataException( - 'Project ID cannot be null' - ); - } - item.monitorGroupId = modelId; - item.projectId = new ObjectID(props.currentProject._id); + const monitorStatus: MonitorStatus | undefined = + monitorStatuses.find((monitorStatus: MonitorStatus) => { + return ( + monitorStatus._id === + item["monitor"]!["currentMonitorStatusId"]?.toString() + ); + }); - return Promise.resolve(item); - }} - cardProps={{ - title: `Monitor Group Resources`, - description: - 'Resources that belong to this monitor group.', - }} - noItemsMessage={ - 'No resources have been added to this monitor group.' - } - formFields={[ - { - field: { - monitor: true, - }, - title: 'Monitor', - description: - 'Select monitor that will be added to this group.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: Monitor, - labelField: 'name', - valueField: '_id', - }, - required: true, - placeholder: 'Select Monitor', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - monitor: { - name: true, - }, - }, - type: FieldType.Entity, - filterEntityType: Monitor, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - title: 'Monitor Name', - }, - ]} - columns={[ - { - field: { - monitor: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Monitor', - type: FieldType.Entity, + if (!monitorStatus) { + throw new BadDataException("Monitor Status not found"); + } - getElement: ( - item: MonitorGroupResource - ): ReactElement => { - return ( - <MonitorElement - monitor={item['monitor']!} - /> - ); - }, - }, - { - field: { - monitor: { - currentMonitorStatusId: true, - }, - }, - title: 'Current Status', - type: FieldType.Element, - - getElement: ( - item: MonitorGroupResource - ): ReactElement => { - if (!item['monitor']) { - throw new BadDataException( - 'Monitor not found' - ); - } - - if ( - !item['monitor']['currentMonitorStatusId'] - ) { - throw new BadDataException( - 'Monitor Status not found' - ); - } - - const monitorStatus: MonitorStatus | undefined = - monitorStatuses.find( - (monitorStatus: MonitorStatus) => { - return ( - monitorStatus._id === - item['monitor']![ - 'currentMonitorStatusId' - ]?.toString() - ); - } - ); - - if (!monitorStatus) { - throw new BadDataException( - 'Monitor Status not found' - ); - } - - return ( - <Statusbubble - color={monitorStatus.color! as Color} - text={monitorStatus.name! as string} - shouldAnimate={true} - /> - ); - }, - }, - ]} - /> - </> - </Fragment> - ); + return ( + <Statusbubble + color={monitorStatus.color! as Color} + text={monitorStatus.name! as string} + shouldAnimate={true} + /> + ); + }, + }, + ]} + /> + </> + </Fragment> + ); }; export default MonitorGroupResources; diff --git a/Dashboard/src/Pages/MonitorGroup/View/Owners.tsx b/Dashboard/src/Pages/MonitorGroup/View/Owners.tsx index ff70b0586f..bd76245143 100644 --- a/Dashboard/src/Pages/MonitorGroup/View/Owners.tsx +++ b/Dashboard/src/Pages/MonitorGroup/View/Owners.tsx @@ -1,235 +1,224 @@ -import TeamElement from '../../../Components/Team/Team'; -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import MonitorGroupOwnerTeam from 'Model/Models/MonitorGroupOwnerTeam'; -import MonitorGroupOwnerUser from 'Model/Models/MonitorGroupOwnerUser'; -import Team from 'Model/Models/Team'; -import User from 'Model/Models/User'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import TeamElement from "../../../Components/Team/Team"; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import MonitorGroupOwnerTeam from "Model/Models/MonitorGroupOwnerTeam"; +import MonitorGroupOwnerUser from "Model/Models/MonitorGroupOwnerUser"; +import Team from "Model/Models/Team"; +import User from "Model/Models/User"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const MonitorGroupOwners: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<MonitorGroupOwnerTeam> - modelType={MonitorGroupOwnerTeam} - id="table-monitor-group-owner-team" - name="MonitorGroup > Owner Team" - singularName="Team" - isDeleteable={true} - createVerb={'Add'} - isCreateable={true} - isViewable={false} - showViewIdButton={true} - query={{ - monitorGroupId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: MonitorGroupOwnerTeam - ): Promise<MonitorGroupOwnerTeam> => { - item.monitorGroupId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Teams)', - description: - 'Here is list of teams that own this monitor group. ', - }} - noItemsMessage={ - 'No teams associated with this monitor group so far.' - } - formFields={[ - { - field: { - team: true, - }, - title: 'Team', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select Team', - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - team: true, - }, - type: FieldType.Entity, - title: 'Team', - filterEntityType: Team, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - team: { - name: true, - }, - }, - title: 'Team', - type: FieldType.Entity, + return ( + <Fragment> + <ModelTable<MonitorGroupOwnerTeam> + modelType={MonitorGroupOwnerTeam} + id="table-monitor-group-owner-team" + name="MonitorGroup > Owner Team" + singularName="Team" + isDeleteable={true} + createVerb={"Add"} + isCreateable={true} + isViewable={false} + showViewIdButton={true} + query={{ + monitorGroupId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: MonitorGroupOwnerTeam, + ): Promise<MonitorGroupOwnerTeam> => { + item.monitorGroupId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Teams)", + description: "Here is list of teams that own this monitor group. ", + }} + noItemsMessage={"No teams associated with this monitor group so far."} + formFields={[ + { + field: { + team: true, + }, + title: "Team", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select Team", + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + team: true, + }, + type: FieldType.Entity, + title: "Team", + filterEntityType: Team, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + team: { + name: true, + }, + }, + title: "Team", + type: FieldType.Entity, - getElement: ( - item: MonitorGroupOwnerTeam - ): ReactElement => { - if (!item['team']) { - throw new BadDataException('Team not found'); - } + getElement: (item: MonitorGroupOwnerTeam): ReactElement => { + if (!item["team"]) { + throw new BadDataException("Team not found"); + } - return <TeamElement team={item['team'] as Team} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> + return <TeamElement team={item["team"] as Team} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> - <ModelTable<MonitorGroupOwnerUser> - modelType={MonitorGroupOwnerUser} - id="table-monitor-group-owner-team" - name="MonitorGroup > Owner Team" - isDeleteable={true} - singularName="User" - isCreateable={true} - isViewable={false} - showViewIdButton={true} - createVerb={'Add'} - query={{ - monitorGroupId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: MonitorGroupOwnerUser - ): Promise<MonitorGroupOwnerUser> => { - item.monitorGroupId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Users)', - description: - 'Here is list of users that own this monitor group.', - }} - noItemsMessage={ - 'No users associated with this monitor group so far.' - } - formFields={[ - { - field: { - user: true, - }, - title: 'User', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select User', - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - user: true, - }, - title: 'User', - type: FieldType.Entity, - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - user: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: 'User', - type: FieldType.Entity, + <ModelTable<MonitorGroupOwnerUser> + modelType={MonitorGroupOwnerUser} + id="table-monitor-group-owner-team" + name="MonitorGroup > Owner Team" + isDeleteable={true} + singularName="User" + isCreateable={true} + isViewable={false} + showViewIdButton={true} + createVerb={"Add"} + query={{ + monitorGroupId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: MonitorGroupOwnerUser, + ): Promise<MonitorGroupOwnerUser> => { + item.monitorGroupId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Users)", + description: "Here is list of users that own this monitor group.", + }} + noItemsMessage={"No users associated with this monitor group so far."} + formFields={[ + { + field: { + user: true, + }, + title: "User", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select User", + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + user: true, + }, + title: "User", + type: FieldType.Entity, + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + user: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "User", + type: FieldType.Entity, - getElement: ( - item: MonitorGroupOwnerUser - ): ReactElement => { - if (!item['user']) { - throw new BadDataException('User not found'); - } + getElement: (item: MonitorGroupOwnerUser): ReactElement => { + if (!item["user"]) { + throw new BadDataException("User not found"); + } - return <UserElement user={item['user'] as User} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + return <UserElement user={item["user"] as User} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default MonitorGroupOwners; diff --git a/Dashboard/src/Pages/MonitorGroup/View/SideMenu.tsx b/Dashboard/src/Pages/MonitorGroup/View/SideMenu.tsx index 4bb4f8330d..af4371b777 100644 --- a/Dashboard/src/Pages/MonitorGroup/View/SideMenu.tsx +++ b/Dashboard/src/Pages/MonitorGroup/View/SideMenu.tsx @@ -1,93 +1,85 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.MONITOR_GROUP_VIEW] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_GROUP_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> - <SideMenuItem - link={{ - title: 'Owners', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.MONITOR_GROUP_VIEW_OWNERS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Team} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Owners", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_GROUP_VIEW_OWNERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Team} + /> + </SideMenuSection> - <SideMenuSection title="Monitors and Incidents"> - <SideMenuItem - link={{ - title: 'Monitors', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.MONITOR_GROUP_VIEW_MONITORS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.AltGlobe} - /> + <SideMenuSection title="Monitors and Incidents"> + <SideMenuItem + link={{ + title: "Monitors", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_GROUP_VIEW_MONITORS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.AltGlobe} + /> - <SideMenuItem - link={{ - title: 'Incidents', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.MONITOR_GROUP_VIEW_INCIDENTS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Alert} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Incidents", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_GROUP_VIEW_INCIDENTS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Alert} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Delete Group', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.MONITOR_GROUP_VIEW_DELETE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Delete Group", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.MONITOR_GROUP_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/OnCallDuty/Layout.tsx b/Dashboard/src/Pages/OnCallDuty/Layout.tsx index 9e93679116..ce366ce2f3 100644 --- a/Dashboard/src/Pages/OnCallDuty/Layout.tsx +++ b/Dashboard/src/Pages/OnCallDuty/Layout.tsx @@ -1,37 +1,37 @@ -import { getOnCallDutyBreadcrumbs } from '../../Utils/Breadcrumbs'; -import PageMap from '../../Utils/PageMap'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import DashboardSideMenu from './SideMenu'; -import Dictionary from 'Common/Types/Dictionary'; -import Page from 'CommonUI/src/Components/Page/Page'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; +import { getOnCallDutyBreadcrumbs } from "../../Utils/Breadcrumbs"; +import PageMap from "../../Utils/PageMap"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import DashboardSideMenu from "./SideMenu"; +import Dictionary from "Common/Types/Dictionary"; +import Page from "CommonUI/src/Components/Page/Page"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet } from "react-router-dom"; const PageTitleMap: Dictionary<string> = { - [RouteUtil.getLastPathForKey(PageMap.ON_CALL_DUTY_POLICIES)]: - 'On-Call Duty Policies', - [RouteUtil.getLastPathForKey(PageMap.ON_CALL_DUTY_SCHEDULES)]: - 'On-Call Duty Schedules', - [RouteUtil.getLastPathForKey(PageMap.ON_CALL_DUTY_EXECUTION_LOGS)]: - 'On-Call Duty Logs', + [RouteUtil.getLastPathForKey(PageMap.ON_CALL_DUTY_POLICIES)]: + "On-Call Duty Policies", + [RouteUtil.getLastPathForKey(PageMap.ON_CALL_DUTY_SCHEDULES)]: + "On-Call Duty Schedules", + [RouteUtil.getLastPathForKey(PageMap.ON_CALL_DUTY_EXECUTION_LOGS)]: + "On-Call Duty Logs", }; const OnCallDutyLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - const lastPath: string = RouteUtil.getLastPath(path); - return ( - <Page - title={PageTitleMap[lastPath]} - breadcrumbLinks={getOnCallDutyBreadcrumbs(path)} - sideMenu={<DashboardSideMenu />} - > - <Outlet /> - </Page> - ); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + const lastPath: string = RouteUtil.getLastPath(path); + return ( + <Page + title={PageTitleMap[lastPath]} + breadcrumbLinks={getOnCallDutyBreadcrumbs(path)} + sideMenu={<DashboardSideMenu />} + > + <Outlet /> + </Page> + ); }; export default OnCallDutyLayout; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyExecutionLogView.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyExecutionLogView.tsx index 37afc44d13..f6bb0d73f3 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyExecutionLogView.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyExecutionLogView.tsx @@ -1,15 +1,15 @@ -import ExecutionLogTimelineTable from '../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTimelineTable'; -import PageComponentProps from '../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ExecutionLogTimelineTable from "../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTimelineTable"; +import PageComponentProps from "../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return <ExecutionLogTimelineTable onCallPolicyExecutionLogId={modelId} />; + return <ExecutionLogTimelineTable onCallPolicyExecutionLogId={modelId} />; }; export default Settings; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyExecutionLogs.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyExecutionLogs.tsx index 891a2dae19..bd4f0ba3d5 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyExecutionLogs.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyExecutionLogs.tsx @@ -1,11 +1,11 @@ -import ExecutionLogsTable from '../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable'; -import PageComponentProps from '../PageComponentProps'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ExecutionLogsTable from "../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable"; +import PageComponentProps from "../PageComponentProps"; +import React, { FunctionComponent, ReactElement } from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return <ExecutionLogsTable />; + return <ExecutionLogsTable />; }; export default Settings; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicies.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicies.tsx index d69fc58099..5d4dc93c3f 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicies.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicies.tsx @@ -1,156 +1,153 @@ -import LabelsElement from '../../Components/Label/Labels'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import URL from 'Common/Types/API/URL'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import URL from "Common/Types/API/URL"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const OnCallDutyPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <Banner - openInNewTab={true} - title="Learn how on-call policy works" - description="Watch this video to learn how to build effective on-call policies for your team." - link={URL.fromString('https://youtu.be/HzhKmCryYdc')} - /> + return ( + <Fragment> + <Banner + openInNewTab={true} + title="Learn how on-call policy works" + description="Watch this video to learn how to build effective on-call policies for your team." + link={URL.fromString("https://youtu.be/HzhKmCryYdc")} + /> - <ModelTable<OnCallDutyPolicy> - modelType={OnCallDutyPolicy} - id="on-call-duty-table" - isDeleteable={false} - name="On-Call > Policies" - showViewIdButton={true} - isEditable={false} - isCreateable={true} - isViewable={true} - cardProps={{ - title: 'On-Call Duty Policies', - description: - 'Here is a list of on-call-duty policies for this project.', - }} - noItemsMessage={'No on-call policy found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'On-Call Duty Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - labels: true, - }, - title: 'Labels ', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, + <ModelTable<OnCallDutyPolicy> + modelType={OnCallDutyPolicy} + id="on-call-duty-table" + isDeleteable={false} + name="On-Call > Policies" + showViewIdButton={true} + isEditable={false} + isCreateable={true} + isViewable={true} + cardProps={{ + title: "On-Call Duty Policies", + description: + "Here is a list of on-call-duty policies for this project.", + }} + noItemsMessage={"No on-call policy found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "On-Call Duty Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + labels: true, + }, + title: "Labels ", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, - getElement: (item: OnCallDutyPolicy): ReactElement => { - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> - </Fragment> - ); + getElement: (item: OnCallDutyPolicy): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + </Fragment> + ); }; export default OnCallDutyPage; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/CustomFields.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/CustomFields.tsx index afa571513d..1cbb1fabf1 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/CustomFields.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/CustomFields.tsx @@ -1,30 +1,30 @@ -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import CustomFieldsDetail from 'CommonUI/src/Components/CustomFields/CustomFieldsDetail'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import OnCallDutyPolicyCustomField from 'Model/Models/OnCallDutyPolicyCustomField'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import CustomFieldsDetail from "CommonUI/src/Components/CustomFields/CustomFieldsDetail"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import OnCallDutyPolicyCustomField from "Model/Models/OnCallDutyPolicyCustomField"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const OnCallDutyPolicyCustomFields: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CustomFieldsDetail - title="Custom Fields" - description="Custom fields help you add new fields to your resources in OneUptime." - modelType={OnCallDutyPolicy} - customFieldType={OnCallDutyPolicyCustomField} - name="Custom Fields" - projectId={ProjectUtil.getCurrentProject()!.id!} - modelId={modelId} - /> - </Fragment> - ); + return ( + <Fragment> + <CustomFieldsDetail + title="Custom Fields" + description="Custom fields help you add new fields to your resources in OneUptime." + modelType={OnCallDutyPolicy} + customFieldType={OnCallDutyPolicyCustomField} + name="Custom Fields" + projectId={ProjectUtil.getCurrentProject()!.id!} + modelId={modelId} + /> + </Fragment> + ); }; export default OnCallDutyPolicyCustomFields; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Delete.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Delete.tsx index 7341785ec6..e4cc78ddb6 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Delete.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Delete.tsx @@ -1,31 +1,29 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const OnCallPolicyDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={OnCallDutyPolicy} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.ON_CALL_DUTY] as Route - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={OnCallDutyPolicy} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate(RouteMap[PageMap.ON_CALL_DUTY] as Route); + }} + /> + </Fragment> + ); }; export default OnCallPolicyDelete; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Escalation.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Escalation.tsx index 50cef1ff3e..1767b0ccee 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Escalation.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Escalation.tsx @@ -1,347 +1,337 @@ -import OnCallDutyScheduleView from '../../../Components/OnCallPolicy/EscalationRule/OnCallScheduleView'; -import TeamView from '../../../Components/OnCallPolicy/EscalationRule/TeamView'; -import UserView from '../../../Components/OnCallPolicy/EscalationRule/UserView'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import { ShowAs } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import OnCallDutyEscalationRule from 'Model/Models/OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicySchedule from 'Model/Models/OnCallDutyPolicySchedule'; -import Team from 'Model/Models/Team'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import OnCallDutyScheduleView from "../../../Components/OnCallPolicy/EscalationRule/OnCallScheduleView"; +import TeamView from "../../../Components/OnCallPolicy/EscalationRule/TeamView"; +import UserView from "../../../Components/OnCallPolicy/EscalationRule/UserView"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import { ShowAs } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import OnCallDutyEscalationRule from "Model/Models/OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicySchedule from "Model/Models/OnCallDutyPolicySchedule"; +import Team from "Model/Models/Team"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const OnCallPolicyDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<OnCallDutyEscalationRule> - modelType={OnCallDutyEscalationRule} - id="table-scheduled-maintenance-internal-note" - name="Scheduled Maintenance Events > Public Notes" - isDeleteable={true} - isCreateable={true} - isEditable={false} - sortBy="order" - sortOrder={SortOrder.Ascending} - showViewIdButton={true} - isViewable={false} - enableDragAndDrop={true} - dragDropIndexField="order" - listDetailOptions={{ - showDetailsInNumberOfColumns: 2, - }} - query={{ - onCallDutyPolicyId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: OnCallDutyEscalationRule - ): Promise<OnCallDutyEscalationRule> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.onCallDutyPolicyId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Escalation Rules', - description: - 'Escalation rules are used to determine who to contact and when to contact them when an incident is triggered.', - }} - noItemsMessage={ - 'There are no escalation rules for this on-call policy.' - } - formSteps={[ - { - title: 'Overview', - id: 'overview', - }, - { - title: 'Notification', - id: 'notification', - }, - { - title: 'Escalation', - id: 'escalation', - }, - ]} - formFields={[ - { - field: { - name: true, - }, - stepId: 'overview', - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - description: - 'The name of the escalation rule. This is used to identify the rule.', - }, - { - field: { - description: true, - }, - title: 'Description', - stepId: 'overview', - fieldType: FormFieldSchemaType.LongText, - required: true, - description: - 'The description of the escalation rule. This is used to describe the rule.', - }, - { - overrideField: { - teams: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Teams', - stepId: 'notification', - description: - 'Select which teams will be notified when incident is triggered.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select Teams', - overrideFieldKey: 'teams', - }, - { - overrideField: { - onCallSchedules: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'On Call Duty Schedules', - stepId: 'notification', - description: - 'Select which on-call duty schedules will be notified when incident is triggered.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: OnCallDutyPolicySchedule, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select On Call Duty Schedules', - overrideFieldKey: 'onCallSchedules', - }, - { - overrideField: { - users: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Users', - stepId: 'notification', - description: - 'Select which users will be notified when incident is triggered.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - required: false, - placeholder: 'Select Users', - overrideFieldKey: 'users', - }, - { - field: { - escalateAfterInMinutes: true, - }, - stepId: 'escalation', - title: 'Escalate after (in minutes)', - fieldType: FormFieldSchemaType.Number, - placeholder: '30', - required: true, - description: - 'The amount of time to wait before escalating to the next escalation rule.', - }, - ]} - showRefreshButton={true} - showAs={ShowAs.List} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - order: true, - }, + return ( + <Fragment> + <ModelTable<OnCallDutyEscalationRule> + modelType={OnCallDutyEscalationRule} + id="table-scheduled-maintenance-internal-note" + name="Scheduled Maintenance Events > Public Notes" + isDeleteable={true} + isCreateable={true} + isEditable={false} + sortBy="order" + sortOrder={SortOrder.Ascending} + showViewIdButton={true} + isViewable={false} + enableDragAndDrop={true} + dragDropIndexField="order" + listDetailOptions={{ + showDetailsInNumberOfColumns: 2, + }} + query={{ + onCallDutyPolicyId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: OnCallDutyEscalationRule, + ): Promise<OnCallDutyEscalationRule> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.onCallDutyPolicyId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Escalation Rules", + description: + "Escalation rules are used to determine who to contact and when to contact them when an incident is triggered.", + }} + noItemsMessage={ + "There are no escalation rules for this on-call policy." + } + formSteps={[ + { + title: "Overview", + id: "overview", + }, + { + title: "Notification", + id: "notification", + }, + { + title: "Escalation", + id: "escalation", + }, + ]} + formFields={[ + { + field: { + name: true, + }, + stepId: "overview", + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + description: + "The name of the escalation rule. This is used to identify the rule.", + }, + { + field: { + description: true, + }, + title: "Description", + stepId: "overview", + fieldType: FormFieldSchemaType.LongText, + required: true, + description: + "The description of the escalation rule. This is used to describe the rule.", + }, + { + overrideField: { + teams: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Teams", + stepId: "notification", + description: + "Select which teams will be notified when incident is triggered.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select Teams", + overrideFieldKey: "teams", + }, + { + overrideField: { + onCallSchedules: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "On Call Duty Schedules", + stepId: "notification", + description: + "Select which on-call duty schedules will be notified when incident is triggered.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: OnCallDutyPolicySchedule, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select On Call Duty Schedules", + overrideFieldKey: "onCallSchedules", + }, + { + overrideField: { + users: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Users", + stepId: "notification", + description: + "Select which users will be notified when incident is triggered.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + required: false, + placeholder: "Select Users", + overrideFieldKey: "users", + }, + { + field: { + escalateAfterInMinutes: true, + }, + stepId: "escalation", + title: "Escalate after (in minutes)", + fieldType: FormFieldSchemaType.Number, + placeholder: "30", + required: true, + description: + "The amount of time to wait before escalating to the next escalation rule.", + }, + ]} + showRefreshButton={true} + showAs={ShowAs.List} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + order: true, + }, - title: 'Escalation Rule Order', - description: 'The order of the escalation rule.', - type: FieldType.Number, - }, - { - field: { - name: true, - }, + title: "Escalation Rule Order", + description: "The order of the escalation rule.", + type: FieldType.Number, + }, + { + field: { + name: true, + }, - title: 'Name', - description: 'The name of the escalation rule.', - type: FieldType.Text, - }, - { - field: { - description: true, - }, + title: "Name", + description: "The name of the escalation rule.", + type: FieldType.Text, + }, + { + field: { + description: true, + }, - title: 'Description', - description: 'The description of the escalation rule.', - type: FieldType.Text, - }, + title: "Description", + description: "The description of the escalation rule.", + type: FieldType.Text, + }, - { - field: { - name: true, - }, - title: 'Teams', - description: - 'Teams who will be notified when incident is triggered.', - type: FieldType.Element, - getElement: ( - item: OnCallDutyEscalationRule - ): ReactElement => { - return <TeamView escalationRuleId={item.id!} />; - }, - }, - { - field: { - name: true, - }, - title: 'On Call Schedules', - description: - 'On call schedules which will be executed when incident is triggered.', - type: FieldType.Element, - getElement: ( - item: OnCallDutyEscalationRule - ): ReactElement => { - return ( - <OnCallDutyScheduleView - escalationRuleId={item.id!} - /> - ); - }, - }, - { - field: { - escalateAfterInMinutes: true, - }, + { + field: { + name: true, + }, + title: "Teams", + description: + "Teams who will be notified when incident is triggered.", + type: FieldType.Element, + getElement: (item: OnCallDutyEscalationRule): ReactElement => { + return <TeamView escalationRuleId={item.id!} />; + }, + }, + { + field: { + name: true, + }, + title: "On Call Schedules", + description: + "On call schedules which will be executed when incident is triggered.", + type: FieldType.Element, + getElement: (item: OnCallDutyEscalationRule): ReactElement => { + return <OnCallDutyScheduleView escalationRuleId={item.id!} />; + }, + }, + { + field: { + escalateAfterInMinutes: true, + }, - title: 'Escalate after (in minutes)', - description: - 'The amount of minutes to wait before escalating to the next escalation rule.', - type: FieldType.Minutes, - }, - { - field: { - name: true, - }, - title: 'Users', - description: - 'Users who will be notified when incident is triggered.', - type: FieldType.Element, - getElement: ( - item: OnCallDutyEscalationRule - ): ReactElement => { - return <UserView escalationRuleId={item.id!} />; - }, - }, - ]} - /> + title: "Escalate after (in minutes)", + description: + "The amount of minutes to wait before escalating to the next escalation rule.", + type: FieldType.Minutes, + }, + { + field: { + name: true, + }, + title: "Users", + description: + "Users who will be notified when incident is triggered.", + type: FieldType.Element, + getElement: (item: OnCallDutyEscalationRule): ReactElement => { + return <UserView escalationRuleId={item.id!} />; + }, + }, + ]} + /> - <CardModelDetail - name="On-Call Policy > On-Call Policy Details" - cardProps={{ - title: 'Repeat Policy', - description: - 'Repeat policies are used to determine how often an on-call policy should be repeated.', - }} - isEditable={true} - formFields={[ - { - field: { - repeatPolicyIfNoOneAcknowledges: true, - }, - title: 'Repeat Policy If No One Acknowledges', - fieldType: FormFieldSchemaType.Toggle, - required: false, - description: - 'If enabled, the on-call policy will repeat if no one acknowledges the incident.', - validation: { - minLength: 2, - }, - }, - { - field: { - repeatPolicyIfNoOneAcknowledgesNoOfTimes: true, - }, - title: 'Number of Times to Repeat', - fieldType: FormFieldSchemaType.Number, - required: false, - description: - 'The number of times to repeat the on-call policy if no one acknowledges the incident.', - placeholder: '3', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: OnCallDutyPolicy, - id: 'model-detail-monitors', - fields: [ - { - field: { - repeatPolicyIfNoOneAcknowledges: true, - }, - title: 'Repeat Policy If No One Acknowledges', - fieldType: FieldType.Boolean, - description: - 'If enabled, the on-call policy will repeat if no one acknowledges the incident.', - placeholder: 'No', - }, - { - field: { - repeatPolicyIfNoOneAcknowledgesNoOfTimes: true, - }, - title: 'Number of Times to Repeat', - fieldType: FieldType.Number, - placeholder: '0', - description: - 'The number of times to repeat the on-call policy if no one acknowledges the incident.', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + <CardModelDetail + name="On-Call Policy > On-Call Policy Details" + cardProps={{ + title: "Repeat Policy", + description: + "Repeat policies are used to determine how often an on-call policy should be repeated.", + }} + isEditable={true} + formFields={[ + { + field: { + repeatPolicyIfNoOneAcknowledges: true, + }, + title: "Repeat Policy If No One Acknowledges", + fieldType: FormFieldSchemaType.Toggle, + required: false, + description: + "If enabled, the on-call policy will repeat if no one acknowledges the incident.", + validation: { + minLength: 2, + }, + }, + { + field: { + repeatPolicyIfNoOneAcknowledgesNoOfTimes: true, + }, + title: "Number of Times to Repeat", + fieldType: FormFieldSchemaType.Number, + required: false, + description: + "The number of times to repeat the on-call policy if no one acknowledges the incident.", + placeholder: "3", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: OnCallDutyPolicy, + id: "model-detail-monitors", + fields: [ + { + field: { + repeatPolicyIfNoOneAcknowledges: true, + }, + title: "Repeat Policy If No One Acknowledges", + fieldType: FieldType.Boolean, + description: + "If enabled, the on-call policy will repeat if no one acknowledges the incident.", + placeholder: "No", + }, + { + field: { + repeatPolicyIfNoOneAcknowledgesNoOfTimes: true, + }, + title: "Number of Times to Repeat", + fieldType: FieldType.Number, + placeholder: "0", + description: + "The number of times to repeat the on-call policy if no one acknowledges the incident.", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default OnCallPolicyDelete; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogView.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogView.tsx index 8de4f83237..38ccb0d7f5 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogView.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogView.tsx @@ -1,31 +1,31 @@ -import ExecutionLogTimelineTable from '../../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTimelineTable'; -import PageMap from '../../../Utils/PageMap'; -import RouteMap from '../../../Utils/RouteMap'; -import RouteParams from '../../../Utils/RouteParams'; -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import ExecutionLogTimelineTable from "../../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTimelineTable"; +import PageMap from "../../../Utils/PageMap"; +import RouteMap from "../../../Utils/RouteMap"; +import RouteParams from "../../../Utils/RouteParams"; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const onCallDutyPolicyIdString: string | null = Navigation.getParamByName( - RouteParams.ModelID, - RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW]! - ); + const onCallDutyPolicyIdString: string | null = Navigation.getParamByName( + RouteParams.ModelID, + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW]!, + ); - if (!onCallDutyPolicyIdString) { - throw new Error('No on call duty policy id found'); - } + if (!onCallDutyPolicyIdString) { + throw new Error("No on call duty policy id found"); + } - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - <ExecutionLogTimelineTable onCallPolicyExecutionLogId={modelId} /> - </Fragment> - ); + return ( + <Fragment> + <ExecutionLogTimelineTable onCallPolicyExecutionLogId={modelId} /> + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogs.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogs.tsx index cd1272dda0..9ef7f6382a 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogs.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogs.tsx @@ -1,24 +1,24 @@ -import ExecutionLogsTable from '../../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable'; -import PageMap from '../../../Utils/PageMap'; -import RouteMap from '../../../Utils/RouteMap'; -import RouteParams from '../../../Utils/RouteParams'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ExecutionLogsTable from "../../../Components/OnCallPolicy/ExecutionLogs/ExecutionLogsTable"; +import PageMap from "../../../Utils/PageMap"; +import RouteMap from "../../../Utils/RouteMap"; +import RouteParams from "../../../Utils/RouteParams"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = new ObjectID( - Navigation.getParamByName( - RouteParams.ModelID, - RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS]! as Route - ) as string - ); + const modelId: ObjectID = new ObjectID( + Navigation.getParamByName( + RouteParams.ModelID, + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS]! as Route, + ) as string, + ); - return <ExecutionLogsTable onCallDutyPolicyId={modelId} />; + return <ExecutionLogsTable onCallDutyPolicyId={modelId} />; }; export default Settings; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Index.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Index.tsx index 9d388b7bfa..7204c57dca 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Index.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Index.tsx @@ -1,130 +1,123 @@ -import LabelsElement from '../../../Components/Label/Labels'; -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../../Components/Label/Labels"; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const OnCallDutyPolicyView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - {/* OnCallDutyPolicy View */} - <CardModelDetail<OnCallDutyPolicy> - name="On-Call Policy > On-Call Policy Details" - cardProps={{ - title: 'On-Call Policy Details', - description: - 'Here are more details for this on-call policy.', - }} - formSteps={[ - { - title: 'On-Call Policy Info', - id: 'on-call-policy-info', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - stepId: 'on-call-policy-info', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'On-Call Policy Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'on-call-policy-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - labels: true, - }, - title: 'Labels ', - stepId: 'labels', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: OnCallDutyPolicy, - id: 'model-detail-monitors', - fields: [ - { - field: { - _id: true, - }, - title: 'On-Call Policy ID', - }, - { - field: { - name: true, - }, - title: 'Name', - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: ( - item: OnCallDutyPolicy - ): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + return ( + <Fragment> + {/* OnCallDutyPolicy View */} + <CardModelDetail<OnCallDutyPolicy> + name="On-Call Policy > On-Call Policy Details" + cardProps={{ + title: "On-Call Policy Details", + description: "Here are more details for this on-call policy.", + }} + formSteps={[ + { + title: "On-Call Policy Info", + id: "on-call-policy-info", + }, + { + title: "Labels", + id: "labels", + }, + ]} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + stepId: "on-call-policy-info", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "On-Call Policy Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "on-call-policy-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + labels: true, + }, + title: "Labels ", + stepId: "labels", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: OnCallDutyPolicy, + id: "model-detail-monitors", + fields: [ + { + field: { + _id: true, + }, + title: "On-Call Policy ID", + }, + { + field: { + name: true, + }, + title: "Name", + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: OnCallDutyPolicy): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default OnCallDutyPolicyView; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Layout.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Layout.tsx index d13abf4c24..166cb99ec1 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Layout.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/Layout.tsx @@ -1,32 +1,32 @@ -import { getOnCallDutyBreadcrumbs } from '../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getOnCallDutyBreadcrumbs } from "../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const OnCallDutyPolicyViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="On-Call Policy" - modelType={OnCallDutyPolicy} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={getOnCallDutyBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <ModelPage + title="On-Call Policy" + modelType={OnCallDutyPolicy} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={getOnCallDutyBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default OnCallDutyPolicyViewLayout; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/SideMenu.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/SideMenu.tsx index d967b78df0..8099619053 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/SideMenu.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutyPolicy/SideMenu.tsx @@ -1,109 +1,103 @@ -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 Link from 'Common/Types/Link'; -import ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 Link from "Common/Types/Link"; +import ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - let subItemMenuLink: Link | undefined = undefined; + let subItemMenuLink: Link | undefined = undefined; - if ( - Navigation.isOnThisPage( - RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW]! - ) - ) { - subItemMenuLink = { - title: 'Timeline', - to: Navigation.getCurrentRoute(), - }; - } + if ( + Navigation.isOnThisPage( + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW]!, + ) + ) { + subItemMenuLink = { + title: "Timeline", + to: Navigation.getCurrentRoute(), + }; + } - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> - <SideMenuItem - link={{ - title: 'Escalation Rules', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.BarsArrowDown} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Escalation Rules", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.BarsArrowDown} + /> + </SideMenuSection> - <SideMenuSection title="Logs"> - <SideMenuItem - link={{ - title: 'Execution Logs', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Logs} - subItemLink={subItemMenuLink} - subItemIcon={IconProp.Clock} - /> - </SideMenuSection> + <SideMenuSection title="Logs"> + <SideMenuItem + link={{ + title: "Execution Logs", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS + ] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Logs} + subItemLink={subItemMenuLink} + subItemIcon={IconProp.Clock} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.TableCells} - /> - <SideMenuItem - link={{ - title: 'Delete Policy', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.TableCells} + /> + <SideMenuItem + link={{ + title: "Delete Policy", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Delete.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Delete.tsx index 856c1ce945..ac59329937 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Delete.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Delete.tsx @@ -1,31 +1,29 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import OnCallDutySchedule from 'Model/Models/OnCallDutyPolicySchedule'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import OnCallDutySchedule from "Model/Models/OnCallDutyPolicySchedule"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const OnCallScheduleDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={OnCallDutySchedule} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.ON_CALL_DUTY] as Route - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={OnCallDutySchedule} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate(RouteMap[PageMap.ON_CALL_DUTY] as Route); + }} + /> + </Fragment> + ); }; export default OnCallScheduleDelete; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Index.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Index.tsx index 82707c6e8f..a32eba00b7 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Index.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Index.tsx @@ -1,130 +1,123 @@ -import LabelsElement from '../../../Components/Label/Labels'; -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import OnCallDutySchedule from 'Model/Models/OnCallDutyPolicySchedule'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../../Components/Label/Labels"; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import OnCallDutySchedule from "Model/Models/OnCallDutyPolicySchedule"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const OnCallDutyScheduleView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - {/* OnCallDutySchedule View */} - <CardModelDetail<OnCallDutySchedule> - name="On-Call Schedule > On-Call Schedule Details" - cardProps={{ - title: 'On-Call Schedule Details', - description: - 'Here are more details for this on-call Schedule.', - }} - formSteps={[ - { - title: 'On-Call Schedule Info', - id: 'on-call-Schedule-info', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - stepId: 'on-call-Schedule-info', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'On-Call Schedule Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'on-call-Schedule-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - labels: true, - }, - title: 'Labels ', - stepId: 'labels', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: OnCallDutySchedule, - id: 'model-detail-monitors', - fields: [ - { - field: { - _id: true, - }, - title: 'On-Call Schedule ID', - }, - { - field: { - name: true, - }, - title: 'Name', - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: ( - item: OnCallDutySchedule - ): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + return ( + <Fragment> + {/* OnCallDutySchedule View */} + <CardModelDetail<OnCallDutySchedule> + name="On-Call Schedule > On-Call Schedule Details" + cardProps={{ + title: "On-Call Schedule Details", + description: "Here are more details for this on-call Schedule.", + }} + formSteps={[ + { + title: "On-Call Schedule Info", + id: "on-call-Schedule-info", + }, + { + title: "Labels", + id: "labels", + }, + ]} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + stepId: "on-call-Schedule-info", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "On-Call Schedule Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "on-call-Schedule-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + labels: true, + }, + title: "Labels ", + stepId: "labels", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: OnCallDutySchedule, + id: "model-detail-monitors", + fields: [ + { + field: { + _id: true, + }, + title: "On-Call Schedule ID", + }, + { + field: { + name: true, + }, + title: "Name", + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: OnCallDutySchedule): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default OnCallDutyScheduleView; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Layers.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Layers.tsx index df00b66c5f..11233bb812 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Layers.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Layers.tsx @@ -1,31 +1,31 @@ -import Layers from '../../../Components/OnCallPolicy/OnCallScheduleLayer/Layers'; -import PageComponentProps from '../../PageComponentProps'; -import URL from 'Common/Types/API/URL'; -import ObjectID from 'Common/Types/ObjectID'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import Layers from "../../../Components/OnCallPolicy/OnCallScheduleLayer/Layers"; +import PageComponentProps from "../../PageComponentProps"; +import URL from "Common/Types/API/URL"; +import ObjectID from "Common/Types/ObjectID"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const OnCallScheduleDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <Banner - openInNewTab={true} - title="Learn how on-call policy works" - description="Watch this video to learn how to build effective on-call policies for your team." - link={URL.fromString('https://youtu.be/HzhKmCryYdc')} - /> - <Layers - onCallDutyPolicyScheduleId={modelId} - projectId={ProjectUtil.getCurrentProjectId()!} - /> - </Fragment> - ); + return ( + <Fragment> + <Banner + openInNewTab={true} + title="Learn how on-call policy works" + description="Watch this video to learn how to build effective on-call policies for your team." + link={URL.fromString("https://youtu.be/HzhKmCryYdc")} + /> + <Layers + onCallDutyPolicyScheduleId={modelId} + projectId={ProjectUtil.getCurrentProjectId()!} + /> + </Fragment> + ); }; export default OnCallScheduleDelete; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Layout.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Layout.tsx index c6b8a874b9..f8b331a99a 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Layout.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/Layout.tsx @@ -1,33 +1,33 @@ -import { getOnCallDutyBreadcrumbs } from '../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import OnCallDutySchedule from 'Model/Models/OnCallDutyPolicySchedule'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getOnCallDutyBreadcrumbs } from "../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import OnCallDutySchedule from "Model/Models/OnCallDutyPolicySchedule"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const OnCallDutyScheduleViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="On-Call Schedule" - modelType={OnCallDutySchedule} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={getOnCallDutyBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + return ( + <ModelPage + title="On-Call Schedule" + modelType={OnCallDutySchedule} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={getOnCallDutyBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default OnCallDutyScheduleViewLayout; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/SideMenu.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/SideMenu.tsx index 46e202e673..713b6561ec 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/SideMenu.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedule/SideMenu.tsx @@ -1,67 +1,61 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.ON_CALL_DUTY_SCHEDULE_VIEW - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> - <SideMenuItem - link={{ - title: 'Layers', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.SquareStack} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Layers", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.SquareStack} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Delete Schedule', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Delete Schedule", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedules.tsx b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedules.tsx index 9378fb3edd..c0038539a1 100644 --- a/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedules.tsx +++ b/Dashboard/src/Pages/OnCallDuty/OnCallDutySchedules.tsx @@ -1,146 +1,141 @@ -import LabelsElement from '../../Components/Label/Labels'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import OnCallDutySchedule from 'Model/Models/OnCallDutyPolicySchedule'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import OnCallDutySchedule from "Model/Models/OnCallDutyPolicySchedule"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const OnCallDutyPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<OnCallDutySchedule> - modelType={OnCallDutySchedule} - id="on-call-duty-table" - isDeleteable={false} - name="On-Call > Schedules" - showViewIdButton={true} - isEditable={false} - isCreateable={true} - isViewable={true} - cardProps={{ - title: 'On-Call Duty Schedules', - description: - 'Here is a list of on-call-duty schedules for this project.', - }} - noItemsMessage={'No on-call schedule found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Schedule Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - labels: true, - }, - title: 'Labels ', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - name: true, - }, - type: FieldType.Text, - title: 'Name', - }, - { - field: { - description: true, - }, - type: FieldType.Text, - title: 'Description', - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - type: FieldType.EntityArray, - title: 'Labels', - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - getElement: ( - item: OnCallDutySchedule - ): ReactElement => { - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<OnCallDutySchedule> + modelType={OnCallDutySchedule} + id="on-call-duty-table" + isDeleteable={false} + name="On-Call > Schedules" + showViewIdButton={true} + isEditable={false} + isCreateable={true} + isViewable={true} + cardProps={{ + title: "On-Call Duty Schedules", + description: + "Here is a list of on-call-duty schedules for this project.", + }} + noItemsMessage={"No on-call schedule found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Schedule Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + labels: true, + }, + title: "Labels ", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + name: true, + }, + type: FieldType.Text, + title: "Name", + }, + { + field: { + description: true, + }, + type: FieldType.Text, + title: "Description", + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + type: FieldType.EntityArray, + title: "Labels", + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + getElement: (item: OnCallDutySchedule): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + </Fragment> + ); }; export default OnCallDutyPage; diff --git a/Dashboard/src/Pages/OnCallDuty/SideMenu.tsx b/Dashboard/src/Pages/OnCallDuty/SideMenu.tsx index 5ceeff6970..02f57adfca 100644 --- a/Dashboard/src/Pages/OnCallDuty/SideMenu.tsx +++ b/Dashboard/src/Pages/OnCallDuty/SideMenu.tsx @@ -1,69 +1,67 @@ -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 Link from 'Common/Types/Link'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { ReactElement } from 'react'; +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 Link from "Common/Types/Link"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { ReactElement } from "react"; const DashboardSideMenu: () => JSX.Element = (): ReactElement => { - let subItemMenuLink: Link | undefined = undefined; + let subItemMenuLink: Link | undefined = undefined; - if ( - Navigation.isOnThisPage( - RouteMap[PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE]! - ) - ) { - subItemMenuLink = { - title: 'Timeline', - to: Navigation.getCurrentRoute(), - }; - } + if ( + Navigation.isOnThisPage( + RouteMap[PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE]!, + ) + ) { + subItemMenuLink = { + title: "Timeline", + to: Navigation.getCurrentRoute(), + }; + } - return ( - <SideMenu> - <SideMenuSection title="Policies"> - <SideMenuItem - link={{ - title: 'On-Call Policies', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.ON_CALL_DUTY_POLICIES] as Route - ), - }} - icon={IconProp.Call} - /> - </SideMenuSection> - <SideMenuSection title="Schedules"> - <SideMenuItem - link={{ - title: 'On-Call Schedules', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.ON_CALL_DUTY_SCHEDULES] as Route - ), - }} - icon={IconProp.Calendar} - /> - </SideMenuSection> - <SideMenuSection title="More"> - <SideMenuItem - link={{ - title: 'Execution Logs', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.ON_CALL_DUTY_EXECUTION_LOGS - ] as Route - ), - }} - icon={IconProp.Logs} - subItemIcon={IconProp.Clock} - subItemLink={subItemMenuLink} - /> - </SideMenuSection> - </SideMenu> - ); + return ( + <SideMenu> + <SideMenuSection title="Policies"> + <SideMenuItem + link={{ + title: "On-Call Policies", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_POLICIES] as Route, + ), + }} + icon={IconProp.Call} + /> + </SideMenuSection> + <SideMenuSection title="Schedules"> + <SideMenuItem + link={{ + title: "On-Call Schedules", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_SCHEDULES] as Route, + ), + }} + icon={IconProp.Calendar} + /> + </SideMenuSection> + <SideMenuSection title="More"> + <SideMenuItem + link={{ + title: "Execution Logs", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ON_CALL_DUTY_EXECUTION_LOGS] as Route, + ), + }} + icon={IconProp.Logs} + subItemIcon={IconProp.Clock} + subItemLink={subItemMenuLink} + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Onboarding/SSO.tsx b/Dashboard/src/Pages/Onboarding/SSO.tsx index 53d60b1136..c91185bf3e 100644 --- a/Dashboard/src/Pages/Onboarding/SSO.tsx +++ b/Dashboard/src/Pages/Onboarding/SSO.tsx @@ -1,79 +1,71 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Card from 'CommonUI/src/Components/Card/Card'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import ModelList from 'CommonUI/src/Components/ModelList/ModelList'; -import Page from 'CommonUI/src/Components/Page/Page'; -import { APP_API_URL, IDENTITY_URL } from 'CommonUI/src/Config'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectSSO from 'Model/Models/ProjectSso'; -import React, { FunctionComponent, ReactElement, useState } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Card from "CommonUI/src/Components/Card/Card"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import ModelList from "CommonUI/src/Components/ModelList/ModelList"; +import Page from "CommonUI/src/Components/Page/Page"; +import { APP_API_URL, IDENTITY_URL } from "CommonUI/src/Config"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectSSO from "Model/Models/ProjectSso"; +import React, { FunctionComponent, ReactElement, useState } from "react"; const SSO: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); - return ( - <Page title={''} breadcrumbLinks={[]}> - <div className="flex justify-center w-full mt-20"> - {isLoading && <PageLoader isVisible={true} />} - {!isLoading && ( - <div className="w-1/3 min-w-lg"> - <Card - title={'Single Sign On (SSO)'} - description="Please select an SSO provider to log in to this project." - > - <div className="mt-6 -ml-6 -mr-6 border-t border-gray-200"> - <div className="ml-6 mr-6 pt-6"> - <ModelList<ProjectSSO> - id="sso-list" - overrideFetchApiUrl={URL.fromString( - APP_API_URL.toString() - ) - .addRoute('/project-sso') - .addRoute( - `/${DashboardNavigation.getProjectId()}` - ) - .addRoute('/sso-list')} - modelType={ProjectSSO} - titleField="name" - descriptionField="description" - select={{ - name: true, - description: true, - _id: true, - }} - noItemsMessage="No SSO Providers Configured or Enabled" - onSelectChange={( - list: Array<ProjectSSO> - ) => { - if (list && list.length > 0) { - setIsLoading(true); - Navigation.navigate( - URL.fromURL( - IDENTITY_URL - ).addRoute( - new Route( - `/sso/${DashboardNavigation.getProjectId()}/${ - list[0]?._id - }` - ) - ) - ); - } - }} - /> - </div> - </div> - </Card> - </div> - )} - </div> - </Page> - ); + return ( + <Page title={""} breadcrumbLinks={[]}> + <div className="flex justify-center w-full mt-20"> + {isLoading && <PageLoader isVisible={true} />} + {!isLoading && ( + <div className="w-1/3 min-w-lg"> + <Card + title={"Single Sign On (SSO)"} + description="Please select an SSO provider to log in to this project." + > + <div className="mt-6 -ml-6 -mr-6 border-t border-gray-200"> + <div className="ml-6 mr-6 pt-6"> + <ModelList<ProjectSSO> + id="sso-list" + overrideFetchApiUrl={URL.fromString(APP_API_URL.toString()) + .addRoute("/project-sso") + .addRoute(`/${DashboardNavigation.getProjectId()}`) + .addRoute("/sso-list")} + modelType={ProjectSSO} + titleField="name" + descriptionField="description" + select={{ + name: true, + description: true, + _id: true, + }} + noItemsMessage="No SSO Providers Configured or Enabled" + onSelectChange={(list: Array<ProjectSSO>) => { + if (list && list.length > 0) { + setIsLoading(true); + Navigation.navigate( + URL.fromURL(IDENTITY_URL).addRoute( + new Route( + `/sso/${DashboardNavigation.getProjectId()}/${ + list[0]?._id + }`, + ), + ), + ); + } + }} + /> + </div> + </div> + </Card> + </div> + )} + </div> + </Page> + ); }; export default SSO; diff --git a/Dashboard/src/Pages/Onboarding/Welcome.tsx b/Dashboard/src/Pages/Onboarding/Welcome.tsx index 079c77cc6a..e5c52ce267 100644 --- a/Dashboard/src/Pages/Onboarding/Welcome.tsx +++ b/Dashboard/src/Pages/Onboarding/Welcome.tsx @@ -1,46 +1,44 @@ -import PageComponentProps from '../PageComponentProps'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import Page from 'CommonUI/src/Components/Page/Page'; -import { BILLING_ENABLED } from 'CommonUI/src/Config'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import IconProp from "Common/Types/Icon/IconProp"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import Page from "CommonUI/src/Components/Page/Page"; +import { BILLING_ENABLED } from "CommonUI/src/Config"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps extends PageComponentProps { - onClickShowProjectModal: () => void; + onClickShowProjectModal: () => void; } const Welcome: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Page title={''} breadcrumbLinks={[]}> - <EmptyState - id="empty-state-no-projects" - icon={IconProp.AddFolder} - title={'No projects'} - description={ - <> - Get started by creating a new project.{' '} - {BILLING_ENABLED && ( - <span> No credit card required.</span> - )} - </> - } - footer={ - <Button - icon={IconProp.Add} - title={'Create New Project'} - buttonStyle={ButtonStyleType.PRIMARY} - onClick={() => { - props.onClickShowProjectModal(); - }} - dataTestId="create-new-project-button" - /> - } - /> - </Page> - ); + return ( + <Page title={""} breadcrumbLinks={[]}> + <EmptyState + id="empty-state-no-projects" + icon={IconProp.AddFolder} + title={"No projects"} + description={ + <> + Get started by creating a new project.{" "} + {BILLING_ENABLED && <span> No credit card required.</span>} + </> + } + footer={ + <Button + icon={IconProp.Add} + title={"Create New Project"} + buttonStyle={ButtonStyleType.PRIMARY} + onClick={() => { + props.onClickShowProjectModal(); + }} + dataTestId="create-new-project-button" + /> + } + /> + </Page> + ); }; export default Welcome; diff --git a/Dashboard/src/Pages/PageComponentProps.ts b/Dashboard/src/Pages/PageComponentProps.ts index e6b33bf818..3a0cc54c64 100644 --- a/Dashboard/src/Pages/PageComponentProps.ts +++ b/Dashboard/src/Pages/PageComponentProps.ts @@ -1,8 +1,8 @@ -import Route from 'Common/Types/API/Route'; -import Project from 'Model/Models/Project'; +import Route from "Common/Types/API/Route"; +import Project from "Model/Models/Project"; export default interface ComponentProps { - pageRoute: Route; - currentProject: Project | null; - hasPaymentMethod: boolean; + pageRoute: Route; + currentProject: Project | null; + hasPaymentMethod: boolean; } diff --git a/Dashboard/src/Pages/PageNotFound/PageNotFound.tsx b/Dashboard/src/Pages/PageNotFound/PageNotFound.tsx index f27a9e67d9..222e131ca1 100644 --- a/Dashboard/src/Pages/PageNotFound/PageNotFound.tsx +++ b/Dashboard/src/Pages/PageNotFound/PageNotFound.tsx @@ -1,22 +1,22 @@ // Tailwind -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import Email from 'Common/Types/Email'; -import NotFound from 'CommonUI/src/Components/404'; -import Page from 'CommonUI/src/Components/Page/Page'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import Email from "Common/Types/Email"; +import NotFound from "CommonUI/src/Components/404"; +import Page from "CommonUI/src/Components/Page/Page"; +import React, { FunctionComponent, ReactElement } from "react"; const PageNotFound: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Page title={''} breadcrumbLinks={[]}> - <NotFound - homeRoute={new Route('/dashboard')} - supportEmail={new Email('support@oneuptime.com')} - /> - </Page> - ); + return ( + <Page title={""} breadcrumbLinks={[]}> + <NotFound + homeRoute={new Route("/dashboard")} + supportEmail={new Email("support@oneuptime.com")} + /> + </Page> + ); }; export default PageNotFound; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/Layout.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/Layout.tsx index 038f499431..2a9e855486 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/Layout.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/Layout.tsx @@ -1,25 +1,25 @@ -import { getScheduleMaintenanceBreadcrumbs } from '../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import SideMenu from './SideMenu'; -import Page from 'CommonUI/src/Components/Page/Page'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; +import { getScheduleMaintenanceBreadcrumbs } from "../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import SideMenu from "./SideMenu"; +import Page from "CommonUI/src/Components/Page/Page"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet } from "react-router-dom"; const ScheduledMaintenancesLayout: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <Page - title={'Scheduled Maintenance Events'} - sideMenu={<SideMenu project={props.currentProject || undefined} />} - breadcrumbLinks={getScheduleMaintenanceBreadcrumbs(path)} - > - <Outlet /> - </Page> - ); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <Page + title={"Scheduled Maintenance Events"} + sideMenu={<SideMenu project={props.currentProject || undefined} />} + breadcrumbLinks={getScheduleMaintenanceBreadcrumbs(path)} + > + <Outlet /> + </Page> + ); }; export default ScheduledMaintenancesLayout; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/Ongoing.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/Ongoing.tsx index e06e71801a..2ef5815d42 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/Ongoing.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/Ongoing.tsx @@ -1,30 +1,28 @@ -import ScheduledMaintenanceTable from '../../Components/ScheduledMaintenance/ScheduledMaintenanceTable'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import React, { FunctionComponent, ReactElement } from 'react'; +import ScheduledMaintenanceTable from "../../Components/ScheduledMaintenance/ScheduledMaintenanceTable"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import React, { FunctionComponent, ReactElement } from "react"; const ScheduledMaintenancesPage: FunctionComponent< - PageComponentProps + PageComponentProps > = (): ReactElement => { - return ( - <ScheduledMaintenanceTable - viewPageRoute={ - RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route - } - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - currentScheduledMaintenanceState: { - isOngoingState: true, - }, - }} - noItemsMessage="No ongoing events so far." - title="Ongoing Scheduled Maintenances" - description="Here is a list of all the ongoing events for this project." - /> - ); + return ( + <ScheduledMaintenanceTable + viewPageRoute={RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + currentScheduledMaintenanceState: { + isOngoingState: true, + }, + }} + noItemsMessage="No ongoing events so far." + title="Ongoing Scheduled Maintenances" + description="Here is a list of all the ongoing events for this project." + /> + ); }; export default ScheduledMaintenancesPage; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/ScheduledMaintenanceEvents.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/ScheduledMaintenanceEvents.tsx index 36fc72a410..845dc9cf2a 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/ScheduledMaintenanceEvents.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/ScheduledMaintenanceEvents.tsx @@ -1,23 +1,23 @@ -import ScheduledMaintenancesTable from '../../Components/ScheduledMaintenance/ScheduledMaintenanceTable'; -import PageComponentProps from '../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { useParams } from 'react-router-dom'; +import ScheduledMaintenancesTable from "../../Components/ScheduledMaintenance/ScheduledMaintenanceTable"; +import PageComponentProps from "../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; +import { useParams } from "react-router-dom"; const ScheduledMaintenancesPage: FunctionComponent< - PageComponentProps + PageComponentProps > = (): ReactElement => { - const { projectId } = useParams(); - const projectObjectId: ObjectID = new ObjectID(projectId || ''); - return ( - <ScheduledMaintenancesTable - viewPageRoute={Navigation.getCurrentRoute()} - query={{ - projectId: projectObjectId, - }} - /> - ); + const { projectId } = useParams(); + const projectObjectId: ObjectID = new ObjectID(projectId || ""); + return ( + <ScheduledMaintenancesTable + viewPageRoute={Navigation.getCurrentRoute()} + query={{ + projectId: projectObjectId, + }} + /> + ); }; export default ScheduledMaintenancesPage; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/SideMenu.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/SideMenu.tsx index 14078cb360..5329964bc8 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/SideMenu.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/SideMenu.tsx @@ -1,60 +1,56 @@ -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 { BadgeType } from 'CommonUI/src/Components/Badge/Badge'; -import CountModelSideMenuItem from 'CommonUI/src/Components/SideMenu/CountModelSideMenuItem'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import Project from 'Model/Models/Project'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 { BadgeType } from "CommonUI/src/Components/Badge/Badge"; +import CountModelSideMenuItem from "CommonUI/src/Components/SideMenu/CountModelSideMenuItem"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import Project from "Model/Models/Project"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - project?: Project | undefined; + project?: Project | undefined; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Overview"> - <SideMenuItem - link={{ - title: 'All Events', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_EVENTS - ] as Route - ), - }} - icon={IconProp.List} - /> + return ( + <SideMenu> + <SideMenuSection title="Overview"> + <SideMenuItem + link={{ + title: "All Events", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route, + ), + }} + icon={IconProp.List} + /> - <CountModelSideMenuItem<ScheduledMaintenance> - link={{ - title: 'Ongoing Events', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS - ] as Route - ), - }} - icon={IconProp.Clock} - badgeType={BadgeType.WARNING} - modelType={ScheduledMaintenance} - countQuery={{ - projectId: props.project?._id, - currentScheduledMaintenanceState: { - isOngoingState: true, - }, - }} - /> - </SideMenuSection> - </SideMenu> - ); + <CountModelSideMenuItem<ScheduledMaintenance> + link={{ + title: "Ongoing Events", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS] as Route, + ), + }} + icon={IconProp.Clock} + badgeType={BadgeType.WARNING} + modelType={ScheduledMaintenance} + countQuery={{ + projectId: props.project?._id, + currentScheduledMaintenanceState: { + isOngoingState: true, + }, + }} + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/CustomFields.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/CustomFields.tsx index c49c2c91fe..e0560389ba 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/CustomFields.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/CustomFields.tsx @@ -1,30 +1,30 @@ -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import CustomFieldsDetail from 'CommonUI/src/Components/CustomFields/CustomFieldsDetail'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceCustomField from 'Model/Models/ScheduledMaintenanceCustomField'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import CustomFieldsDetail from "CommonUI/src/Components/CustomFields/CustomFieldsDetail"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceCustomField from "Model/Models/ScheduledMaintenanceCustomField"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ScheduledMaintenanceCustomFields: FunctionComponent< - PageComponentProps + PageComponentProps > = (_props: PageComponentProps): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CustomFieldsDetail - title="Scheduled Maintenance Custom Fields" - description="Custom fields help you add new fields to your resources in OneUptime." - modelType={ScheduledMaintenance} - customFieldType={ScheduledMaintenanceCustomField} - name="Scheduled Maintenance Custom Fields" - projectId={ProjectUtil.getCurrentProject()!.id!} - modelId={modelId} - /> - </Fragment> - ); + return ( + <Fragment> + <CustomFieldsDetail + title="Scheduled Maintenance Custom Fields" + description="Custom fields help you add new fields to your resources in OneUptime." + modelType={ScheduledMaintenance} + customFieldType={ScheduledMaintenanceCustomField} + name="Scheduled Maintenance Custom Fields" + projectId={ProjectUtil.getCurrentProject()!.id!} + modelId={modelId} + /> + </Fragment> + ); }; export default ScheduledMaintenanceCustomFields; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Delete.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Delete.tsx index f2487d16a7..0f968bee46 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Delete.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Delete.tsx @@ -1,31 +1,31 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const IncidentDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={ScheduledMaintenance} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={ScheduledMaintenance} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate( + RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route, + ); + }} + /> + </Fragment> + ); }; export default IncidentDelete; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Index.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Index.tsx index 9be3bfa286..9884103f4b 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Index.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Index.tsx @@ -1,486 +1,453 @@ -import LabelsElement from '../../../Components/Label/Labels'; -import MonitorsElement from '../../../Components/Monitor/Monitors'; +import LabelsElement from "../../../Components/Label/Labels"; +import MonitorsElement from "../../../Components/Monitor/Monitors"; import ChangeScheduledMaintenanceState, { - StateType, -} from '../../../Components/ScheduledMaintenance/ChangeState'; -import StatusPagesElement from '../../../Components/StatusPage/StatusPagesLabel'; -import PageComponentProps from '../../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import { Black } from 'Common/Types/BrandColors'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import CheckboxViewer from 'CommonUI/src/Components/Checkbox/CheckboxViewer'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; + StateType, +} from "../../../Components/ScheduledMaintenance/ChangeState"; +import StatusPagesElement from "../../../Components/StatusPage/StatusPagesLabel"; +import PageComponentProps from "../../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import { Black } from "Common/Types/BrandColors"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import CheckboxViewer from "CommonUI/src/Components/Checkbox/CheckboxViewer"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import StatusPage from "Model/Models/StatusPage"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ScheduledMaintenanceView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - {/* ScheduledMaintenance View */} - <CardModelDetail<ScheduledMaintenance> - name="Scheduled Maintenance Details" - cardProps={{ - title: 'Scheduled Maintenance Details', - description: 'Here are more details for this event.', - }} - formSteps={[ - { - title: 'Event Info', - id: 'event-info', - }, + return ( + <Fragment> + {/* ScheduledMaintenance View */} + <CardModelDetail<ScheduledMaintenance> + name="Scheduled Maintenance Details" + cardProps={{ + title: "Scheduled Maintenance Details", + description: "Here are more details for this event.", + }} + formSteps={[ + { + title: "Event Info", + id: "event-info", + }, - { - title: 'Resources Affected', - id: 'resources-affected', - }, - { - title: 'Status Pages', - id: 'status-pages', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - isEditable={true} - formFields={[ - { - field: { - title: true, - }, - stepId: 'event-info', - title: 'Scheduled Maintenance Title', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Scheduled Maintenance Title', - validation: { - minLength: 2, - }, - }, + { + title: "Resources Affected", + id: "resources-affected", + }, + { + title: "Status Pages", + id: "status-pages", + }, + { + title: "Labels", + id: "labels", + }, + ]} + isEditable={true} + formFields={[ + { + field: { + title: true, + }, + stepId: "event-info", + title: "Scheduled Maintenance Title", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Scheduled Maintenance Title", + validation: { + minLength: 2, + }, + }, - { - field: { - startsAt: true, - }, - stepId: 'event-info', - title: 'Event Starts At', - fieldType: FormFieldSchemaType.DateTime, - required: true, - placeholder: 'Pick Date and Time', - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - stepId: 'event-info', - fieldType: FormFieldSchemaType.DateTime, - required: true, - placeholder: 'Pick Date and Time', - }, - { - field: { - monitors: true, - }, - title: 'Monitors affected ', - stepId: 'resources-affected', - description: - 'Select monitors affected by this scheduled maintenance.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Monitor, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Monitors affected', - }, - { - field: { - statusPages: true, - }, - title: 'Show event on these status pages ', - stepId: 'status-pages', - description: - 'Select status pages to show this event on', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: StatusPage, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select Status Pages', - }, - { - field: { - labels: true, - }, - title: 'Labels ', - stepId: 'labels', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - onBeforeFetch: async (): Promise<JSONObject> => { - // get ack scheduledMaintenance. + { + field: { + startsAt: true, + }, + stepId: "event-info", + title: "Event Starts At", + fieldType: FormFieldSchemaType.DateTime, + required: true, + placeholder: "Pick Date and Time", + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + stepId: "event-info", + fieldType: FormFieldSchemaType.DateTime, + required: true, + placeholder: "Pick Date and Time", + }, + { + field: { + monitors: true, + }, + title: "Monitors affected ", + stepId: "resources-affected", + description: + "Select monitors affected by this scheduled maintenance.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Monitor, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Monitors affected", + }, + { + field: { + statusPages: true, + }, + title: "Show event on these status pages ", + stepId: "status-pages", + description: "Select status pages to show this event on", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: StatusPage, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select Status Pages", + }, + { + field: { + labels: true, + }, + title: "Labels ", + stepId: "labels", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + onBeforeFetch: async (): Promise<JSONObject> => { + // get ack scheduledMaintenance. - const scheduledMaintenanceTimelines: ListResult<ScheduledMaintenanceStateTimeline> = - await ModelAPI.getList({ - modelType: ScheduledMaintenanceStateTimeline, - query: { - scheduledMaintenanceId: modelId, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, + const scheduledMaintenanceTimelines: ListResult<ScheduledMaintenanceStateTimeline> = + await ModelAPI.getList({ + modelType: ScheduledMaintenanceStateTimeline, + query: { + scheduledMaintenanceId: modelId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, - createdAt: true, - createdByUser: { - name: true, - email: true, - profilePictureId: true, - }, - scheduledMaintenanceState: { - name: true, - isResolvedState: true, - isOngoingState: true, - isScheduledState: true, - }, - }, - sort: {}, - }); + createdAt: true, + createdByUser: { + name: true, + email: true, + profilePictureId: true, + }, + scheduledMaintenanceState: { + name: true, + isResolvedState: true, + isOngoingState: true, + isScheduledState: true, + }, + }, + sort: {}, + }); - return scheduledMaintenanceTimelines; - }, - showDetailsInNumberOfColumns: 2, - modelType: ScheduledMaintenance, - id: 'model-detail-scheduledMaintenances', - selectMoreFields: { - shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing: - true, - shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded: - true, - }, - fields: [ - { - field: { - _id: true, - }, - title: 'Scheduled Maintenance ID', - fieldType: FieldType.ObjectID, - }, - { - field: { - title: true, - }, - title: 'Scheduled Maintenance Title', - fieldType: FieldType.Text, - }, - { - field: { - currentScheduledMaintenanceState: { - color: true, - name: true, - }, - }, - title: 'Current State', - fieldType: FieldType.Entity, - getElement: ( - item: ScheduledMaintenance - ): ReactElement => { - if (!item['currentScheduledMaintenanceState']) { - throw new BadDataException( - 'Scheduled Maintenance Status not found' - ); - } + return scheduledMaintenanceTimelines; + }, + showDetailsInNumberOfColumns: 2, + modelType: ScheduledMaintenance, + id: "model-detail-scheduledMaintenances", + selectMoreFields: { + shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing: + true, + shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded: true, + }, + fields: [ + { + field: { + _id: true, + }, + title: "Scheduled Maintenance ID", + fieldType: FieldType.ObjectID, + }, + { + field: { + title: true, + }, + title: "Scheduled Maintenance Title", + fieldType: FieldType.Text, + }, + { + field: { + currentScheduledMaintenanceState: { + color: true, + name: true, + }, + }, + title: "Current State", + fieldType: FieldType.Entity, + getElement: (item: ScheduledMaintenance): ReactElement => { + if (!item["currentScheduledMaintenanceState"]) { + throw new BadDataException( + "Scheduled Maintenance Status not found", + ); + } - return ( - <Pill - color={ - item - .currentScheduledMaintenanceState - .color || Black - } - text={ - item - .currentScheduledMaintenanceState - .name || 'Unknown' - } - /> - ); - }, - }, - { - field: { - monitors: { - name: true, - _id: true, - }, - }, - title: 'Monitors Affected', - fieldType: FieldType.Element, - getElement: ( - item: ScheduledMaintenance - ): ReactElement => { - return ( - <MonitorsElement - monitors={item.monitors || []} - /> - ); - }, - }, - { - field: { - statusPages: { - name: true, - _id: true, - }, - }, - title: 'Shown on Status Pages', - fieldType: FieldType.Element, - getElement: ( - item: ScheduledMaintenance - ): ReactElement => { - return ( - <StatusPagesElement - statusPages={item.statusPages || []} - /> - ); - }, - }, - { - field: { - startsAt: true, - }, - title: 'Starts At', - fieldType: FieldType.DateTime, - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - fieldType: FieldType.DateTime, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - fieldType: FieldType.DateTime, - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedOnEventCreated: - true, - }, - title: 'Notify Status Page Subscribers', - fieldType: FieldType.Boolean, - getElement: ( - item: ScheduledMaintenance - ): ReactElement => { - return ( - <div> - <div className=""> - <CheckboxViewer - isChecked={ - item[ - 'shouldStatusPageSubscribersBeNotifiedOnEventCreated' - ] as boolean - } - text={ - item[ - 'shouldStatusPageSubscribersBeNotifiedOnEventCreated' - ] - ? 'Event Created: Notify Subscribers' - : 'Event Created: Do Not Notify Subscribers' - } - />{' '} - </div> - <div className=""> - <CheckboxViewer - isChecked={ - item[ - 'shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing' - ] as boolean - } - text={ - item[ - 'shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing' - ] - ? 'Event Ongoing: Notify Subscribers' - : 'Event Ongoing: Do Not Notify Subscribers' - } - />{' '} - </div> - <div className=""> - <CheckboxViewer - isChecked={ - item[ - 'shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded' - ] as boolean - } - text={ - item[ - 'shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded' - ] - ? 'Event Ended: Notify Subscribers' - : 'Event Ended: Do Not Notify Subscribers' - } - />{' '} - </div> - </div> - ); - }, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: ( - item: ScheduledMaintenance - ): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - _id: true, - }, - title: 'Change State to Ongoing', - fieldType: FieldType.Element, - getElement: ( - _item: ScheduledMaintenance, - onBeforeFetchData: JSONObject | undefined, - fetchItems: VoidFunction | undefined - ): ReactElement => { - return ( - <ChangeScheduledMaintenanceState - scheduledMaintenanceId={modelId} - scheduledMaintenanceTimeline={ - onBeforeFetchData - ? (onBeforeFetchData[ - 'data' - ] as Array<BaseModel>) - : [] - } - stateType={StateType.Ongoing} - onActionComplete={() => { - fetchItems && fetchItems(); - }} - /> - ); - }, - }, - { - field: { - _id: true, - }, - title: 'Change State to Completed', - fieldType: FieldType.Element, - getElement: ( - _item: ScheduledMaintenance, - onBeforeFetchData: JSONObject | undefined, - fetchItems: VoidFunction | undefined - ): ReactElement => { - return ( - <ChangeScheduledMaintenanceState - scheduledMaintenanceId={modelId} - scheduledMaintenanceTimeline={ - onBeforeFetchData - ? (onBeforeFetchData[ - 'data' - ] as Array<BaseModel>) - : [] - } - stateType={StateType.Completed} - onActionComplete={() => { - fetchItems && fetchItems(); - }} - /> - ); - }, - }, - ], - modelId: modelId, - }} - /> + return ( + <Pill + color={item.currentScheduledMaintenanceState.color || Black} + text={ + item.currentScheduledMaintenanceState.name || "Unknown" + } + /> + ); + }, + }, + { + field: { + monitors: { + name: true, + _id: true, + }, + }, + title: "Monitors Affected", + fieldType: FieldType.Element, + getElement: (item: ScheduledMaintenance): ReactElement => { + return <MonitorsElement monitors={item.monitors || []} />; + }, + }, + { + field: { + statusPages: { + name: true, + _id: true, + }, + }, + title: "Shown on Status Pages", + fieldType: FieldType.Element, + getElement: (item: ScheduledMaintenance): ReactElement => { + return ( + <StatusPagesElement statusPages={item.statusPages || []} /> + ); + }, + }, + { + field: { + startsAt: true, + }, + title: "Starts At", + fieldType: FieldType.DateTime, + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + fieldType: FieldType.DateTime, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + fieldType: FieldType.DateTime, + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedOnEventCreated: true, + }, + title: "Notify Status Page Subscribers", + fieldType: FieldType.Boolean, + getElement: (item: ScheduledMaintenance): ReactElement => { + return ( + <div> + <div className=""> + <CheckboxViewer + isChecked={ + item[ + "shouldStatusPageSubscribersBeNotifiedOnEventCreated" + ] as boolean + } + text={ + item[ + "shouldStatusPageSubscribersBeNotifiedOnEventCreated" + ] + ? "Event Created: Notify Subscribers" + : "Event Created: Do Not Notify Subscribers" + } + />{" "} + </div> + <div className=""> + <CheckboxViewer + isChecked={ + item[ + "shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing" + ] as boolean + } + text={ + item[ + "shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing" + ] + ? "Event Ongoing: Notify Subscribers" + : "Event Ongoing: Do Not Notify Subscribers" + } + />{" "} + </div> + <div className=""> + <CheckboxViewer + isChecked={ + item[ + "shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded" + ] as boolean + } + text={ + item[ + "shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded" + ] + ? "Event Ended: Notify Subscribers" + : "Event Ended: Do Not Notify Subscribers" + } + />{" "} + </div> + </div> + ); + }, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: ScheduledMaintenance): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + _id: true, + }, + title: "Change State to Ongoing", + fieldType: FieldType.Element, + getElement: ( + _item: ScheduledMaintenance, + onBeforeFetchData: JSONObject | undefined, + fetchItems: VoidFunction | undefined, + ): ReactElement => { + return ( + <ChangeScheduledMaintenanceState + scheduledMaintenanceId={modelId} + scheduledMaintenanceTimeline={ + onBeforeFetchData + ? (onBeforeFetchData["data"] as Array<BaseModel>) + : [] + } + stateType={StateType.Ongoing} + onActionComplete={() => { + fetchItems && fetchItems(); + }} + /> + ); + }, + }, + { + field: { + _id: true, + }, + title: "Change State to Completed", + fieldType: FieldType.Element, + getElement: ( + _item: ScheduledMaintenance, + onBeforeFetchData: JSONObject | undefined, + fetchItems: VoidFunction | undefined, + ): ReactElement => { + return ( + <ChangeScheduledMaintenanceState + scheduledMaintenanceId={modelId} + scheduledMaintenanceTimeline={ + onBeforeFetchData + ? (onBeforeFetchData["data"] as Array<BaseModel>) + : [] + } + stateType={StateType.Completed} + onActionComplete={() => { + fetchItems && fetchItems(); + }} + /> + ); + }, + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail - name="Event Description" - cardProps={{ - title: 'Event Description', - description: - 'Description for this event. This is visible on Status Page and is in markdown format.', - }} - editButtonText="Edit Event Description" - isEditable={true} - formFields={[ - { - field: { - description: true, - }, - title: 'Description', + <CardModelDetail + name="Event Description" + cardProps={{ + title: "Event Description", + description: + "Description for this event. This is visible on Status Page and is in markdown format.", + }} + editButtonText="Edit Event Description" + isEditable={true} + formFields={[ + { + field: { + description: true, + }, + title: "Description", - fieldType: FormFieldSchemaType.Markdown, - required: true, - placeholder: 'Description', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: ScheduledMaintenance, - id: 'model-detail-event-description', - fields: [ - { - field: { - description: true, - }, - title: 'Description', - fieldType: FieldType.Markdown, - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + fieldType: FormFieldSchemaType.Markdown, + required: true, + placeholder: "Description", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: ScheduledMaintenance, + id: "model-detail-event-description", + fields: [ + { + field: { + description: true, + }, + title: "Description", + fieldType: FieldType.Markdown, + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default ScheduledMaintenanceView; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/InternalNote.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/InternalNote.tsx index 9db1016f5a..9a80c34dbe 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/InternalNote.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/InternalNote.tsx @@ -1,351 +1,337 @@ -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import { ShowAs } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import AlignItem from 'CommonUI/src/Types/AlignItem'; -import API from 'CommonUI/src/Utils/API/API'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ScheduledMaintenanceInternalNote from 'Model/Models/ScheduledMaintenanceInternalNote'; -import ScheduledMaintenanceNoteTemplate from 'Model/Models/ScheduledMaintenanceNoteTemplate'; -import User from 'Model/Models/User'; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import { ShowAs } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import AlignItem from "CommonUI/src/Types/AlignItem"; +import API from "CommonUI/src/Utils/API/API"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ScheduledMaintenanceInternalNote from "Model/Models/ScheduledMaintenanceInternalNote"; +import ScheduledMaintenanceNoteTemplate from "Model/Models/ScheduledMaintenanceNoteTemplate"; +import User from "Model/Models/User"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const ScheduledMaintenanceDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [ - scheduledMaintenanceNoteTemplates, - setScheduledMaintenanceNoteTemplates, - ] = useState<Array<ScheduledMaintenanceNoteTemplate>>([]); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); - const [ - showScheduledMaintenanceNoteTemplateModal, - setShowScheduledMaintenanceNoteTemplateModal, - ] = useState<boolean>(false); - const [ - initialValuesForScheduledMaintenance, - setInitialValuesForScheduledMaintenance, - ] = useState<JSONObject>({}); + const [ + scheduledMaintenanceNoteTemplates, + setScheduledMaintenanceNoteTemplates, + ] = useState<Array<ScheduledMaintenanceNoteTemplate>>([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [ + showScheduledMaintenanceNoteTemplateModal, + setShowScheduledMaintenanceNoteTemplateModal, + ] = useState<boolean>(false); + const [ + initialValuesForScheduledMaintenance, + setInitialValuesForScheduledMaintenance, + ] = useState<JSONObject>({}); - const fetchScheduledMaintenanceNoteTemplate: ( - id: ObjectID - ) => Promise<void> = async (id: ObjectID): Promise<void> => { - setError(''); - setIsLoading(true); + const fetchScheduledMaintenanceNoteTemplate: ( + id: ObjectID, + ) => Promise<void> = async (id: ObjectID): Promise<void> => { + setError(""); + setIsLoading(true); - try { - //fetch scheduledMaintenance template + try { + //fetch scheduledMaintenance template - const scheduledMaintenanceNoteTemplate: ScheduledMaintenanceNoteTemplate | null = - await ModelAPI.getItem<ScheduledMaintenanceNoteTemplate>({ - modelType: ScheduledMaintenanceNoteTemplate, - id, - select: { - note: true, - }, - }); + const scheduledMaintenanceNoteTemplate: ScheduledMaintenanceNoteTemplate | null = + await ModelAPI.getItem<ScheduledMaintenanceNoteTemplate>({ + modelType: ScheduledMaintenanceNoteTemplate, + id, + select: { + note: true, + }, + }); - if (scheduledMaintenanceNoteTemplate) { - const initialValue: JSONObject = { - ...BaseModel.toJSONObject( - scheduledMaintenanceNoteTemplate, - ScheduledMaintenanceNoteTemplate - ), - }; - - setInitialValuesForScheduledMaintenance(initialValue); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - setShowScheduledMaintenanceNoteTemplateModal(false); - }; - - const fetchScheduledMaintenanceNoteTemplates: () => Promise<void> = - async (): Promise<void> => { - setError(''); - setIsLoading(true); - setInitialValuesForScheduledMaintenance({}); - - try { - const listResult: ListResult<ScheduledMaintenanceNoteTemplate> = - await ModelAPI.getList<ScheduledMaintenanceNoteTemplate>({ - modelType: ScheduledMaintenanceNoteTemplate, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - templateName: true, - _id: true, - }, - sort: {}, - }); - - setScheduledMaintenanceNoteTemplates(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); + if (scheduledMaintenanceNoteTemplate) { + const initialValue: JSONObject = { + ...BaseModel.toJSONObject( + scheduledMaintenanceNoteTemplate, + ScheduledMaintenanceNoteTemplate, + ), }; - return ( - <Fragment> - <ModelTable<ScheduledMaintenanceInternalNote> - modelType={ScheduledMaintenanceInternalNote} - id="table-scheduled-maintenance-internal-note" - name="Scheduled Maintenance Events > Internal Note" - isDeleteable={true} - isCreateable={true} - isEditable={true} - isViewable={false} - showViewIdButton={true} - createEditModalWidth={ModalWidth.Large} - query={{ - scheduledMaintenanceId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - showCreateForm={ - Object.keys(initialValuesForScheduledMaintenance).length > 0 - } - createInitialValues={initialValuesForScheduledMaintenance} - onBeforeCreate={( - item: ScheduledMaintenanceInternalNote - ): Promise<ScheduledMaintenanceInternalNote> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.scheduledMaintenanceId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Private Notes', - buttons: [ - { - title: 'Create from Template', - icon: IconProp.Template, - buttonStyle: ButtonStyleType.OUTLINE, - onClick: async (): Promise<void> => { - setShowScheduledMaintenanceNoteTemplateModal( - true - ); - await fetchScheduledMaintenanceNoteTemplates(); - }, - }, - ], - description: - 'Here are private notes for this scheduled maintenance.', - }} - noItemsMessage={ - 'No private notes created for this scheduled maintenance so far.' - } - formFields={[ - { - field: { - note: true, - }, - title: 'Private Scheduled Maintenance Note', - fieldType: FormFieldSchemaType.Markdown, - required: true, - description: - 'Add a private note to this scheduled maintenance here. This is in Markdown.', - }, - ]} - showRefreshButton={true} - showAs={ShowAs.List} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - createdByUser: true, - }, - type: FieldType.Entity, - title: 'Created By', - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - note: true, - }, - type: FieldType.Text, - title: 'Note', - }, - { - field: { - createdAt: true, - }, - type: FieldType.Date, - title: 'Created At', - }, - ]} - columns={[ - { - field: { - createdByUser: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: '', + setInitialValuesForScheduledMaintenance(initialValue); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - type: FieldType.Entity, + setIsLoading(false); + setShowScheduledMaintenanceNoteTemplateModal(false); + }; - getElement: ( - item: ScheduledMaintenanceInternalNote - ): ReactElement => { - return ( - <UserElement - user={item['createdByUser']} - suffix={'wrote'} - usernameClassName={ - 'text-base text-gray-900' - } - suffixClassName={ - 'text-base text-gray-500 mt-1' - } - /> - ); - }, - }, - { - field: { - createdAt: true, - }, + const fetchScheduledMaintenanceNoteTemplates: () => Promise<void> = + async (): Promise<void> => { + setError(""); + setIsLoading(true); + setInitialValuesForScheduledMaintenance({}); - alignItem: AlignItem.Right, - title: '', - type: FieldType.DateTime, - contentClassName: - 'mt-1 whitespace-nowrap text-sm text-gray-600 sm:mt-0 sm:ml-3 text-right', - }, - { - field: { - note: true, - }, + try { + const listResult: ListResult<ScheduledMaintenanceNoteTemplate> = + await ModelAPI.getList<ScheduledMaintenanceNoteTemplate>({ + modelType: ScheduledMaintenanceNoteTemplate, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + templateName: true, + _id: true, + }, + sort: {}, + }); - title: '', - type: FieldType.Markdown, - contentClassName: - '-mt-3 space-y-6 text-sm text-gray-800', - colSpan: 2, - }, - ]} - /> + setScheduledMaintenanceNoteTemplates(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - {scheduledMaintenanceNoteTemplates.length === 0 && - showScheduledMaintenanceNoteTemplateModal && - !isLoading ? ( - <ConfirmModal - title={`No ScheduledMaintenance Note Templates`} - description={`No scheduled maintenance note templates have been created yet. You can create these in Project Settings > Scheduled Maintenance > Note Templates.`} - submitButtonText={'Close'} - onSubmit={() => { - return setShowScheduledMaintenanceNoteTemplateModal( - false - ); - }} + setIsLoading(false); + }; + + return ( + <Fragment> + <ModelTable<ScheduledMaintenanceInternalNote> + modelType={ScheduledMaintenanceInternalNote} + id="table-scheduled-maintenance-internal-note" + name="Scheduled Maintenance Events > Internal Note" + isDeleteable={true} + isCreateable={true} + isEditable={true} + isViewable={false} + showViewIdButton={true} + createEditModalWidth={ModalWidth.Large} + query={{ + scheduledMaintenanceId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + showCreateForm={ + Object.keys(initialValuesForScheduledMaintenance).length > 0 + } + createInitialValues={initialValuesForScheduledMaintenance} + onBeforeCreate={( + item: ScheduledMaintenanceInternalNote, + ): Promise<ScheduledMaintenanceInternalNote> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.scheduledMaintenanceId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Private Notes", + buttons: [ + { + title: "Create from Template", + icon: IconProp.Template, + buttonStyle: ButtonStyleType.OUTLINE, + onClick: async (): Promise<void> => { + setShowScheduledMaintenanceNoteTemplateModal(true); + await fetchScheduledMaintenanceNoteTemplates(); + }, + }, + ], + description: "Here are private notes for this scheduled maintenance.", + }} + noItemsMessage={ + "No private notes created for this scheduled maintenance so far." + } + formFields={[ + { + field: { + note: true, + }, + title: "Private Scheduled Maintenance Note", + fieldType: FormFieldSchemaType.Markdown, + required: true, + description: + "Add a private note to this scheduled maintenance here. This is in Markdown.", + }, + ]} + showRefreshButton={true} + showAs={ShowAs.List} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + createdByUser: true, + }, + type: FieldType.Entity, + title: "Created By", + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + note: true, + }, + type: FieldType.Text, + title: "Note", + }, + { + field: { + createdAt: true, + }, + type: FieldType.Date, + title: "Created At", + }, + ]} + columns={[ + { + field: { + createdByUser: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "", + + type: FieldType.Entity, + + getElement: ( + item: ScheduledMaintenanceInternalNote, + ): ReactElement => { + return ( + <UserElement + user={item["createdByUser"]} + suffix={"wrote"} + usernameClassName={"text-base text-gray-900"} + suffixClassName={"text-base text-gray-500 mt-1"} /> - ) : ( - <></> - )} + ); + }, + }, + { + field: { + createdAt: true, + }, - {error ? ( - <ConfirmModal - title={`Error`} - description={`${error}`} - submitButtonText={'Close'} - onSubmit={() => { - return setError(''); - }} - /> - ) : ( - <></> - )} + alignItem: AlignItem.Right, + title: "", + type: FieldType.DateTime, + contentClassName: + "mt-1 whitespace-nowrap text-sm text-gray-600 sm:mt-0 sm:ml-3 text-right", + }, + { + field: { + note: true, + }, - {showScheduledMaintenanceNoteTemplateModal && - scheduledMaintenanceNoteTemplates.length > 0 ? ( - <BasicFormModal<JSONObject> - title="Create Note from Template" - isLoading={isLoading} - submitButtonText="Create from Template" - onClose={() => { - setShowScheduledMaintenanceNoteTemplateModal(false); - setIsLoading(false); - }} - onSubmit={async (data: JSONObject) => { - await fetchScheduledMaintenanceNoteTemplate( - data[ - 'scheduledMaintenanceNoteTemplateId' - ] as ObjectID - ); - }} - formProps={{ - initialValues: {}, - fields: [ - { - field: { - scheduledMaintenanceNoteTemplateId: true, - }, - title: 'Select Note Template', - description: - 'Select a template to create a note from.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEntityArray( - { - array: scheduledMaintenanceNoteTemplates, - labelField: 'templateName', - valueField: '_id', - } - ), - required: true, - placeholder: 'Select Template', - }, - ], - }} - /> - ) : ( - <> </> - )} - </Fragment> - ); + title: "", + type: FieldType.Markdown, + contentClassName: "-mt-3 space-y-6 text-sm text-gray-800", + colSpan: 2, + }, + ]} + /> + + {scheduledMaintenanceNoteTemplates.length === 0 && + showScheduledMaintenanceNoteTemplateModal && + !isLoading ? ( + <ConfirmModal + title={`No ScheduledMaintenance Note Templates`} + description={`No scheduled maintenance note templates have been created yet. You can create these in Project Settings > Scheduled Maintenance > Note Templates.`} + submitButtonText={"Close"} + onSubmit={() => { + return setShowScheduledMaintenanceNoteTemplateModal(false); + }} + /> + ) : ( + <></> + )} + + {error ? ( + <ConfirmModal + title={`Error`} + description={`${error}`} + submitButtonText={"Close"} + onSubmit={() => { + return setError(""); + }} + /> + ) : ( + <></> + )} + + {showScheduledMaintenanceNoteTemplateModal && + scheduledMaintenanceNoteTemplates.length > 0 ? ( + <BasicFormModal<JSONObject> + title="Create Note from Template" + isLoading={isLoading} + submitButtonText="Create from Template" + onClose={() => { + setShowScheduledMaintenanceNoteTemplateModal(false); + setIsLoading(false); + }} + onSubmit={async (data: JSONObject) => { + await fetchScheduledMaintenanceNoteTemplate( + data["scheduledMaintenanceNoteTemplateId"] as ObjectID, + ); + }} + formProps={{ + initialValues: {}, + fields: [ + { + field: { + scheduledMaintenanceNoteTemplateId: true, + }, + title: "Select Note Template", + description: "Select a template to create a note from.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: DropdownUtil.getDropdownOptionsFromEntityArray( + { + array: scheduledMaintenanceNoteTemplates, + labelField: "templateName", + valueField: "_id", + }, + ), + required: true, + placeholder: "Select Template", + }, + ], + }} + /> + ) : ( + <> </> + )} + </Fragment> + ); }; export default ScheduledMaintenanceDelete; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Layout.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Layout.tsx index 3b26136ade..db311e6ba6 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Layout.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Layout.tsx @@ -1,32 +1,32 @@ -import { getScheduleMaintenanceBreadcrumbs } from '../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getScheduleMaintenanceBreadcrumbs } from "../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const ScheduledMaintenanceViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="Scheduled Event" - modelType={ScheduledMaintenance} - modelId={modelId} - modelNameField="title" - breadcrumbLinks={getScheduleMaintenanceBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <ModelPage + title="Scheduled Event" + modelType={ScheduledMaintenance} + modelId={modelId} + modelNameField="title" + breadcrumbLinks={getScheduleMaintenanceBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default ScheduledMaintenanceViewLayout; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Owners.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Owners.tsx index 9c2e983982..8b99e3ee4d 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Owners.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/Owners.tsx @@ -1,235 +1,230 @@ -import TeamElement from '../../../Components/Team/Team'; -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ScheduledMaintenanceOwnerTeam from 'Model/Models/ScheduledMaintenanceOwnerTeam'; -import ScheduledMaintenanceOwnerUser from 'Model/Models/ScheduledMaintenanceOwnerUser'; -import Team from 'Model/Models/Team'; -import User from 'Model/Models/User'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import TeamElement from "../../../Components/Team/Team"; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ScheduledMaintenanceOwnerTeam from "Model/Models/ScheduledMaintenanceOwnerTeam"; +import ScheduledMaintenanceOwnerUser from "Model/Models/ScheduledMaintenanceOwnerUser"; +import Team from "Model/Models/Team"; +import User from "Model/Models/User"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ScheduledMaintenanceOwners: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<ScheduledMaintenanceOwnerTeam> - modelType={ScheduledMaintenanceOwnerTeam} - id="table-scheduledMaintenance-owner-team" - name="ScheduledMaintenance > Owner Team" - singularName="Team" - isDeleteable={true} - createVerb={'Add'} - isCreateable={true} - isViewable={false} - showViewIdButton={true} - query={{ - scheduledMaintenanceId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: ScheduledMaintenanceOwnerTeam - ): Promise<ScheduledMaintenanceOwnerTeam> => { - item.scheduledMaintenanceId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Teams)', - description: - 'Here is list of teams that own this scheduled maintenance event. They will be alerted when this scheduled maintenance event is created or updated.', - }} - noItemsMessage={ - 'No teams associated with this scheduled maintenance event so far.' - } - formFields={[ - { - field: { - team: true, - }, - title: 'Team', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select Team', - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - team: true, - }, - type: FieldType.Entity, - title: 'Team', - filterEntityType: Team, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - team: { - name: true, - }, - }, - title: 'Team', - type: FieldType.Entity, + return ( + <Fragment> + <ModelTable<ScheduledMaintenanceOwnerTeam> + modelType={ScheduledMaintenanceOwnerTeam} + id="table-scheduledMaintenance-owner-team" + name="ScheduledMaintenance > Owner Team" + singularName="Team" + isDeleteable={true} + createVerb={"Add"} + isCreateable={true} + isViewable={false} + showViewIdButton={true} + query={{ + scheduledMaintenanceId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: ScheduledMaintenanceOwnerTeam, + ): Promise<ScheduledMaintenanceOwnerTeam> => { + item.scheduledMaintenanceId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Teams)", + description: + "Here is list of teams that own this scheduled maintenance event. They will be alerted when this scheduled maintenance event is created or updated.", + }} + noItemsMessage={ + "No teams associated with this scheduled maintenance event so far." + } + formFields={[ + { + field: { + team: true, + }, + title: "Team", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select Team", + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + team: true, + }, + type: FieldType.Entity, + title: "Team", + filterEntityType: Team, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + team: { + name: true, + }, + }, + title: "Team", + type: FieldType.Entity, - getElement: ( - item: ScheduledMaintenanceOwnerTeam - ): ReactElement => { - if (!item['team']) { - throw new BadDataException('Team not found'); - } + getElement: (item: ScheduledMaintenanceOwnerTeam): ReactElement => { + if (!item["team"]) { + throw new BadDataException("Team not found"); + } - return <TeamElement team={item['team'] as Team} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> + return <TeamElement team={item["team"] as Team} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> - <ModelTable<ScheduledMaintenanceOwnerUser> - modelType={ScheduledMaintenanceOwnerUser} - id="table-scheduledMaintenance-owner-team" - name="ScheduledMaintenance > Owner Team" - isDeleteable={true} - singularName="User" - isCreateable={true} - isViewable={false} - showViewIdButton={true} - createVerb={'Add'} - query={{ - scheduledMaintenanceId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: ScheduledMaintenanceOwnerUser - ): Promise<ScheduledMaintenanceOwnerUser> => { - item.scheduledMaintenanceId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Users)', - description: - 'Here is list of users that own this scheduled maintenance event. They will be alerted when this scheduled maintenance event is created or updated.', - }} - noItemsMessage={ - 'No users associated with this scheduled maintenance event so far.' - } - formFields={[ - { - field: { - user: true, - }, - title: 'User', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select User', - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - user: true, - }, - title: 'User', - type: FieldType.Entity, - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - user: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: 'User', - type: FieldType.Entity, + <ModelTable<ScheduledMaintenanceOwnerUser> + modelType={ScheduledMaintenanceOwnerUser} + id="table-scheduledMaintenance-owner-team" + name="ScheduledMaintenance > Owner Team" + isDeleteable={true} + singularName="User" + isCreateable={true} + isViewable={false} + showViewIdButton={true} + createVerb={"Add"} + query={{ + scheduledMaintenanceId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: ScheduledMaintenanceOwnerUser, + ): Promise<ScheduledMaintenanceOwnerUser> => { + item.scheduledMaintenanceId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Users)", + description: + "Here is list of users that own this scheduled maintenance event. They will be alerted when this scheduled maintenance event is created or updated.", + }} + noItemsMessage={ + "No users associated with this scheduled maintenance event so far." + } + formFields={[ + { + field: { + user: true, + }, + title: "User", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select User", + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + user: true, + }, + title: "User", + type: FieldType.Entity, + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + user: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "User", + type: FieldType.Entity, - getElement: ( - item: ScheduledMaintenanceOwnerUser - ): ReactElement => { - if (!item['user']) { - throw new BadDataException('User not found'); - } + getElement: (item: ScheduledMaintenanceOwnerUser): ReactElement => { + if (!item["user"]) { + throw new BadDataException("User not found"); + } - return <UserElement user={item['user'] as User} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + return <UserElement user={item["user"] as User} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default ScheduledMaintenanceOwners; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/PublicNote.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/PublicNote.tsx index 77e11fe471..0cf44e2b12 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/PublicNote.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/PublicNote.tsx @@ -1,411 +1,393 @@ -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import CheckboxViewer from 'CommonUI/src/Components/Checkbox/CheckboxViewer'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import { ShowAs } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import AlignItem from 'CommonUI/src/Types/AlignItem'; -import API from 'CommonUI/src/Utils/API/API'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ScheduledMaintenanceNoteTemplate from 'Model/Models/ScheduledMaintenanceNoteTemplate'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import User from 'Model/Models/User'; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import CheckboxViewer from "CommonUI/src/Components/Checkbox/CheckboxViewer"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import { ShowAs } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import AlignItem from "CommonUI/src/Types/AlignItem"; +import API from "CommonUI/src/Utils/API/API"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ScheduledMaintenanceNoteTemplate from "Model/Models/ScheduledMaintenanceNoteTemplate"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import User from "Model/Models/User"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const PublicNote: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [ - scheduledMaintenanceNoteTemplates, - setScheduledMaintenanceNoteTemplates, - ] = useState<Array<ScheduledMaintenanceNoteTemplate>>([]); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); - const [ - showScheduledMaintenanceNoteTemplateModal, - setShowScheduledMaintenanceNoteTemplateModal, - ] = useState<boolean>(false); - const [ - initialValuesForScheduledMaintenance, - setInitialValuesForScheduledMaintenance, - ] = useState<JSONObject>({}); + const [ + scheduledMaintenanceNoteTemplates, + setScheduledMaintenanceNoteTemplates, + ] = useState<Array<ScheduledMaintenanceNoteTemplate>>([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); + const [ + showScheduledMaintenanceNoteTemplateModal, + setShowScheduledMaintenanceNoteTemplateModal, + ] = useState<boolean>(false); + const [ + initialValuesForScheduledMaintenance, + setInitialValuesForScheduledMaintenance, + ] = useState<JSONObject>({}); - const fetchScheduledMaintenanceNoteTemplate: ( - id: ObjectID - ) => Promise<void> = async (id: ObjectID): Promise<void> => { - setError(''); - setIsLoading(true); + const fetchScheduledMaintenanceNoteTemplate: ( + id: ObjectID, + ) => Promise<void> = async (id: ObjectID): Promise<void> => { + setError(""); + setIsLoading(true); - try { - //fetch scheduledMaintenance template + try { + //fetch scheduledMaintenance template - const scheduledMaintenanceNoteTemplate: ScheduledMaintenanceNoteTemplate | null = - await ModelAPI.getItem<ScheduledMaintenanceNoteTemplate>({ - modelType: ScheduledMaintenanceNoteTemplate, - id, - select: { - note: true, - }, - }); + const scheduledMaintenanceNoteTemplate: ScheduledMaintenanceNoteTemplate | null = + await ModelAPI.getItem<ScheduledMaintenanceNoteTemplate>({ + modelType: ScheduledMaintenanceNoteTemplate, + id, + select: { + note: true, + }, + }); - if (scheduledMaintenanceNoteTemplate) { - const initialValue: JSONObject = { - ...BaseModel.toJSONObject( - scheduledMaintenanceNoteTemplate, - ScheduledMaintenanceNoteTemplate - ), - }; - - setInitialValuesForScheduledMaintenance(initialValue); - } - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - setShowScheduledMaintenanceNoteTemplateModal(false); - }; - - const fetchScheduledMaintenanceNoteTemplates: () => Promise<void> = - async (): Promise<void> => { - setError(''); - setIsLoading(true); - setInitialValuesForScheduledMaintenance({}); - - try { - const listResult: ListResult<ScheduledMaintenanceNoteTemplate> = - await ModelAPI.getList<ScheduledMaintenanceNoteTemplate>({ - modelType: ScheduledMaintenanceNoteTemplate, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - templateName: true, - _id: true, - }, - sort: {}, - }); - - setScheduledMaintenanceNoteTemplates(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); + if (scheduledMaintenanceNoteTemplate) { + const initialValue: JSONObject = { + ...BaseModel.toJSONObject( + scheduledMaintenanceNoteTemplate, + ScheduledMaintenanceNoteTemplate, + ), }; - return ( - <Fragment> - <ModelTable<ScheduledMaintenancePublicNote> - modelType={ScheduledMaintenancePublicNote} - id="table-scheduled-maintenance-internal-note" - name="Scheduled Maintenance Events > Public Notes" - isDeleteable={true} - createEditModalWidth={ModalWidth.Large} - isCreateable={true} - isEditable={true} - showViewIdButton={true} - showCreateForm={ - Object.keys(initialValuesForScheduledMaintenance).length > 0 - } - createInitialValues={initialValuesForScheduledMaintenance} - isViewable={false} - query={{ - scheduledMaintenanceId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: ScheduledMaintenancePublicNote - ): Promise<ScheduledMaintenancePublicNote> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); + setInitialValuesForScheduledMaintenance(initialValue); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + setShowScheduledMaintenanceNoteTemplateModal(false); + }; + + const fetchScheduledMaintenanceNoteTemplates: () => Promise<void> = + async (): Promise<void> => { + setError(""); + setIsLoading(true); + setInitialValuesForScheduledMaintenance({}); + + try { + const listResult: ListResult<ScheduledMaintenanceNoteTemplate> = + await ModelAPI.getList<ScheduledMaintenanceNoteTemplate>({ + modelType: ScheduledMaintenanceNoteTemplate, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + templateName: true, + _id: true, + }, + sort: {}, + }); + + setScheduledMaintenanceNoteTemplates(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + return ( + <Fragment> + <ModelTable<ScheduledMaintenancePublicNote> + modelType={ScheduledMaintenancePublicNote} + id="table-scheduled-maintenance-internal-note" + name="Scheduled Maintenance Events > Public Notes" + isDeleteable={true} + createEditModalWidth={ModalWidth.Large} + isCreateable={true} + isEditable={true} + showViewIdButton={true} + showCreateForm={ + Object.keys(initialValuesForScheduledMaintenance).length > 0 + } + createInitialValues={initialValuesForScheduledMaintenance} + isViewable={false} + query={{ + scheduledMaintenanceId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: ScheduledMaintenancePublicNote, + ): Promise<ScheduledMaintenancePublicNote> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.scheduledMaintenanceId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Public Notes", + buttons: [ + { + title: "Create from Template", + icon: IconProp.Template, + buttonStyle: ButtonStyleType.OUTLINE, + onClick: async (): Promise<void> => { + setShowScheduledMaintenanceNoteTemplateModal(true); + await fetchScheduledMaintenanceNoteTemplates(); + }, + }, + ], + description: + "Here are public notes for this scheduled maintenance. This will show up on the status page.", + }} + noItemsMessage={ + "No public notes created for this scheduled maintenance so far." + } + formFields={[ + { + field: { + note: true, + }, + title: "Public Scheduled Maintenance Note", + fieldType: FormFieldSchemaType.Markdown, + required: true, + description: + "This note is visible on your Status Page. This is in Markdown.", + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true, + }, + + title: "Notify Status Page Subscribers", + stepId: "more", + description: "Should status page subscribers be notified?", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + }, + { + field: { + postedAt: true, + }, + title: "Posted At", + fieldType: FormFieldSchemaType.DateTime, + required: true, + description: + "This is the date and time this note was posted. This is in " + + OneUptimeDate.getCurrentTimezoneString() + + ".", + defaultValue: OneUptimeDate.getCurrentDate(), + }, + ]} + showRefreshButton={true} + showAs={ShowAs.List} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + createdByUser: true, + }, + type: FieldType.Entity, + title: "Created By", + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + note: true, + }, + type: FieldType.Text, + title: "Note", + }, + { + field: { + createdAt: true, + }, + type: FieldType.Date, + title: "Created At", + }, + ]} + columns={[ + { + field: { + createdByUser: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "", + + type: FieldType.Entity, + + getElement: ( + item: ScheduledMaintenancePublicNote, + ): ReactElement => { + return ( + <UserElement + user={item["createdByUser"]} + suffix={"wrote"} + usernameClassName={"text-base text-gray-900"} + suffixClassName={"text-base text-gray-500 mt-1"} + /> + ); + }, + }, + { + field: { + postedAt: true, + }, + + alignItem: AlignItem.Right, + title: "", + type: FieldType.DateTime, + contentClassName: + "mt-1 whitespace-nowrap text-sm text-gray-600 sm:mt-0 sm:ml-3 text-right", + }, + { + field: { + note: true, + }, + + title: "", + type: FieldType.Markdown, + contentClassName: "-mt-3 space-y-6 text-sm text-gray-800", + colSpan: 2, + }, + { + field: { + shouldStatusPageSubscribersBeNotifiedOnNoteCreated: true, + }, + title: "", + type: FieldType.Boolean, + colSpan: 2, + getElement: ( + item: ScheduledMaintenancePublicNote, + ): ReactElement => { + return ( + <div className="-mt-5"> + <CheckboxViewer + isChecked={ + item[ + "shouldStatusPageSubscribersBeNotifiedOnNoteCreated" + ] as boolean } - item.scheduledMaintenanceId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Public Notes', - buttons: [ - { - title: 'Create from Template', - icon: IconProp.Template, - buttonStyle: ButtonStyleType.OUTLINE, - onClick: async (): Promise<void> => { - setShowScheduledMaintenanceNoteTemplateModal( - true - ); - await fetchScheduledMaintenanceNoteTemplates(); - }, - }, - ], - description: - 'Here are public notes for this scheduled maintenance. This will show up on the status page.', - }} - noItemsMessage={ - 'No public notes created for this scheduled maintenance so far.' - } - formFields={[ - { - field: { - note: true, - }, - title: 'Public Scheduled Maintenance Note', - fieldType: FormFieldSchemaType.Markdown, - required: true, - description: - 'This note is visible on your Status Page. This is in Markdown.', - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedOnNoteCreated: - true, - }, + text={ + item["shouldStatusPageSubscribersBeNotifiedOnNoteCreated"] + ? "Status Page Subscribers Notified" + : "Status Page Subscribers Not Notified" + } + />{" "} + </div> + ); + }, + }, + ]} + /> - title: 'Notify Status Page Subscribers', - stepId: 'more', - description: - 'Should status page subscribers be notified?', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - }, - { - field: { - postedAt: true, - }, - title: 'Posted At', - fieldType: FormFieldSchemaType.DateTime, - required: true, - description: - 'This is the date and time this note was posted. This is in ' + - OneUptimeDate.getCurrentTimezoneString() + - '.', - defaultValue: OneUptimeDate.getCurrentDate(), - }, - ]} - showRefreshButton={true} - showAs={ShowAs.List} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - createdByUser: true, - }, - type: FieldType.Entity, - title: 'Created By', - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - note: true, - }, - type: FieldType.Text, - title: 'Note', - }, - { - field: { - createdAt: true, - }, - type: FieldType.Date, - title: 'Created At', - }, - ]} - columns={[ - { - field: { - createdByUser: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: '', + {scheduledMaintenanceNoteTemplates.length === 0 && + showScheduledMaintenanceNoteTemplateModal && + !isLoading ? ( + <ConfirmModal + title={`No ScheduledMaintenance Note Templates`} + description={`No scheduled maintenance note templates have been created yet. You can create these in Project Settings > Scheduled Maintenance > Note Templates.`} + submitButtonText={"Close"} + onSubmit={() => { + return setShowScheduledMaintenanceNoteTemplateModal(false); + }} + /> + ) : ( + <></> + )} - type: FieldType.Entity, + {error ? ( + <ConfirmModal + title={`Error`} + description={`${error}`} + submitButtonText={"Close"} + onSubmit={() => { + return setError(""); + }} + /> + ) : ( + <></> + )} - getElement: ( - item: ScheduledMaintenancePublicNote - ): ReactElement => { - return ( - <UserElement - user={item['createdByUser']} - suffix={'wrote'} - usernameClassName={ - 'text-base text-gray-900' - } - suffixClassName={ - 'text-base text-gray-500 mt-1' - } - /> - ); - }, - }, - { - field: { - postedAt: true, - }, - - alignItem: AlignItem.Right, - title: '', - type: FieldType.DateTime, - contentClassName: - 'mt-1 whitespace-nowrap text-sm text-gray-600 sm:mt-0 sm:ml-3 text-right', - }, - { - field: { - note: true, - }, - - title: '', - type: FieldType.Markdown, - contentClassName: - '-mt-3 space-y-6 text-sm text-gray-800', - colSpan: 2, - }, - { - field: { - shouldStatusPageSubscribersBeNotifiedOnNoteCreated: - true, - }, - title: '', - type: FieldType.Boolean, - colSpan: 2, - getElement: ( - item: ScheduledMaintenancePublicNote - ): ReactElement => { - return ( - <div className="-mt-5"> - <CheckboxViewer - isChecked={ - item[ - 'shouldStatusPageSubscribersBeNotifiedOnNoteCreated' - ] as boolean - } - text={ - item[ - 'shouldStatusPageSubscribersBeNotifiedOnNoteCreated' - ] - ? 'Status Page Subscribers Notified' - : 'Status Page Subscribers Not Notified' - } - />{' '} - </div> - ); - }, - }, - ]} - /> - - {scheduledMaintenanceNoteTemplates.length === 0 && - showScheduledMaintenanceNoteTemplateModal && - !isLoading ? ( - <ConfirmModal - title={`No ScheduledMaintenance Note Templates`} - description={`No scheduled maintenance note templates have been created yet. You can create these in Project Settings > Scheduled Maintenance > Note Templates.`} - submitButtonText={'Close'} - onSubmit={() => { - return setShowScheduledMaintenanceNoteTemplateModal( - false - ); - }} - /> - ) : ( - <></> - )} - - {error ? ( - <ConfirmModal - title={`Error`} - description={`${error}`} - submitButtonText={'Close'} - onSubmit={() => { - return setError(''); - }} - /> - ) : ( - <></> - )} - - {showScheduledMaintenanceNoteTemplateModal && - scheduledMaintenanceNoteTemplates.length > 0 ? ( - <BasicFormModal<JSONObject> - title="Create Note from Template" - isLoading={isLoading} - submitButtonText="Create from Template" - onClose={() => { - setShowScheduledMaintenanceNoteTemplateModal(false); - setIsLoading(false); - }} - onSubmit={async (data: JSONObject) => { - await fetchScheduledMaintenanceNoteTemplate( - data[ - 'scheduledMaintenanceNoteTemplateId' - ] as ObjectID - ); - }} - formProps={{ - initialValues: {}, - fields: [ - { - field: { - scheduledMaintenanceNoteTemplateId: true, - }, - title: 'Select Note Template', - description: - 'Select a template to create a note from.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEntityArray( - { - array: scheduledMaintenanceNoteTemplates, - labelField: 'templateName', - valueField: '_id', - } - ), - required: true, - placeholder: 'Select Template', - }, - ], - }} - /> - ) : ( - <> </> - )} - </Fragment> - ); + {showScheduledMaintenanceNoteTemplateModal && + scheduledMaintenanceNoteTemplates.length > 0 ? ( + <BasicFormModal<JSONObject> + title="Create Note from Template" + isLoading={isLoading} + submitButtonText="Create from Template" + onClose={() => { + setShowScheduledMaintenanceNoteTemplateModal(false); + setIsLoading(false); + }} + onSubmit={async (data: JSONObject) => { + await fetchScheduledMaintenanceNoteTemplate( + data["scheduledMaintenanceNoteTemplateId"] as ObjectID, + ); + }} + formProps={{ + initialValues: {}, + fields: [ + { + field: { + scheduledMaintenanceNoteTemplateId: true, + }, + title: "Select Note Template", + description: "Select a template to create a note from.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: DropdownUtil.getDropdownOptionsFromEntityArray( + { + array: scheduledMaintenanceNoteTemplates, + labelField: "templateName", + valueField: "_id", + }, + ), + required: true, + placeholder: "Select Template", + }, + ], + }} + /> + ) : ( + <> </> + )} + </Fragment> + ); }; export default PublicNote; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/SideMenu.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/SideMenu.tsx index 30beabedef..f63fb6debb 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/SideMenu.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/SideMenu.tsx @@ -1,119 +1,108 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_VIEW - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> - <SideMenuItem - link={{ - title: 'Owners', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Team} - /> - <SideMenuItem - link={{ - title: 'State Timeline', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap - .SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.List} - /> - </SideMenuSection> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> + <SideMenuItem + link={{ + title: "Owners", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Team} + /> + <SideMenuItem + link={{ + title: "State Timeline", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE + ] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.List} + /> + </SideMenuSection> - <SideMenuSection title="Scheduled Maintenance Notes"> - <SideMenuItem - link={{ - title: 'Private Notes', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Lock} - /> - <SideMenuItem - link={{ - title: 'Public Notes', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Public} - /> - </SideMenuSection> + <SideMenuSection title="Scheduled Maintenance Notes"> + <SideMenuItem + link={{ + title: "Private Notes", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Lock} + /> + <SideMenuItem + link={{ + title: "Public Notes", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Public} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.TableCells} - /> + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS + ] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.TableCells} + /> - <SideMenuItem - link={{ - title: 'Delete Event', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuItem + link={{ + title: "Delete Event", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/StateTimeline.tsx b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/StateTimeline.tsx index f3bdabdcef..17615165c3 100644 --- a/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/StateTimeline.tsx +++ b/Dashboard/src/Pages/ScheduledMaintenanceEvents/View/StateTimeline.tsx @@ -1,197 +1,189 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import { Black } from 'Common/Types/BrandColors'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import { Black } from "Common/Types/BrandColors"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ScheduledMaintenanceDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<ScheduledMaintenanceStateTimeline> - modelType={ScheduledMaintenanceStateTimeline} - id="table-scheduledMaintenance-status-timeline" - name="Scheduled Maintenance Events > State Timeline" - isDeleteable={true} - isCreateable={true} - showViewIdButton={true} - isViewable={false} - query={{ - scheduledMaintenanceId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: ScheduledMaintenanceStateTimeline - ): Promise<ScheduledMaintenanceStateTimeline> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.scheduledMaintenanceId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Status Timeline', - description: - 'Here is the status timeline for this Scheduled Maintenance', - }} - noItemsMessage={ - 'No status timeline created for this Scheduled Maintenance so far.' - } - formFields={[ - { - field: { - scheduledMaintenanceState: true, - }, - title: 'Scheduled Maintenance Status', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Scheduled Maintenance Status', - dropdownModal: { - type: ScheduledMaintenanceState, - labelField: 'name', - valueField: '_id', - }, - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, + return ( + <Fragment> + <ModelTable<ScheduledMaintenanceStateTimeline> + modelType={ScheduledMaintenanceStateTimeline} + id="table-scheduledMaintenance-status-timeline" + name="Scheduled Maintenance Events > State Timeline" + isDeleteable={true} + isCreateable={true} + showViewIdButton={true} + isViewable={false} + query={{ + scheduledMaintenanceId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: ScheduledMaintenanceStateTimeline, + ): Promise<ScheduledMaintenanceStateTimeline> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.scheduledMaintenanceId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Status Timeline", + description: + "Here is the status timeline for this Scheduled Maintenance", + }} + noItemsMessage={ + "No status timeline created for this Scheduled Maintenance so far." + } + formFields={[ + { + field: { + scheduledMaintenanceState: true, + }, + title: "Scheduled Maintenance Status", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Scheduled Maintenance Status", + dropdownModal: { + type: ScheduledMaintenanceState, + labelField: "name", + valueField: "_id", + }, + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, - title: 'Notify Status Page Subscribers', - description: - 'Should status page subscribers be notified?', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - scheduledMaintenanceState: { - name: true, - }, - }, - title: 'Scheduled Maintenance Status', - type: FieldType.Text, - }, - { - field: { - createdAt: true, - }, - title: 'Starts At', - type: FieldType.DateTime, - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - type: FieldType.DateTime, - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, - title: 'Subscribers Notified', - type: FieldType.Boolean, - }, - ]} - columns={[ - { - field: { - scheduledMaintenanceState: { - name: true, - color: true, - }, - }, - title: 'Scheduled Maintenance Status', - type: FieldType.Text, + title: "Notify Status Page Subscribers", + description: "Should status page subscribers be notified?", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + scheduledMaintenanceState: { + name: true, + }, + }, + title: "Scheduled Maintenance Status", + type: FieldType.Text, + }, + { + field: { + createdAt: true, + }, + title: "Starts At", + type: FieldType.DateTime, + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + type: FieldType.DateTime, + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, + title: "Subscribers Notified", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + scheduledMaintenanceState: { + name: true, + color: true, + }, + }, + title: "Scheduled Maintenance Status", + type: FieldType.Text, - getElement: ( - item: ScheduledMaintenanceStateTimeline - ): ReactElement => { - if (!item['scheduledMaintenanceState']) { - throw new BadDataException( - 'Scheduled Maintenance Status not found' - ); - } + getElement: ( + item: ScheduledMaintenanceStateTimeline, + ): ReactElement => { + if (!item["scheduledMaintenanceState"]) { + throw new BadDataException( + "Scheduled Maintenance Status not found", + ); + } - return ( - <Pill - color={ - item.scheduledMaintenanceState.color || - Black - } - text={ - item.scheduledMaintenanceState.name || - 'Unknown' - } - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Starts At', - type: FieldType.DateTime, - }, - { - field: { - endsAt: true, - }, - title: 'Ends At', - type: FieldType.DateTime, - noValueMessage: 'Currently Active', - }, - { - field: { - endsAt: true, - }, - title: 'Duration', - type: FieldType.Text, - getElement: ( - item: ScheduledMaintenanceStateTimeline - ): ReactElement => { - return ( - <p> - {OneUptimeDate.differenceBetweenTwoDatesAsFromattedString( - item['createdAt'] as Date, - (item['endsAt'] as Date) || - OneUptimeDate.getCurrentDate() - )} - </p> - ); - }, - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, - title: 'Subscribers Notified', - type: FieldType.Boolean, - }, - ]} - /> - </Fragment> - ); + return ( + <Pill + color={item.scheduledMaintenanceState.color || Black} + text={item.scheduledMaintenanceState.name || "Unknown"} + /> + ); + }, + }, + { + field: { + createdAt: true, + }, + title: "Starts At", + type: FieldType.DateTime, + }, + { + field: { + endsAt: true, + }, + title: "Ends At", + type: FieldType.DateTime, + noValueMessage: "Currently Active", + }, + { + field: { + endsAt: true, + }, + title: "Duration", + type: FieldType.Text, + getElement: ( + item: ScheduledMaintenanceStateTimeline, + ): ReactElement => { + return ( + <p> + {OneUptimeDate.differenceBetweenTwoDatesAsFromattedString( + item["createdAt"] as Date, + (item["endsAt"] as Date) || OneUptimeDate.getCurrentDate(), + )} + </p> + ); + }, + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, + title: "Subscribers Notified", + type: FieldType.Boolean, + }, + ]} + /> + </Fragment> + ); }; export default ScheduledMaintenanceDelete; diff --git a/Dashboard/src/Pages/ServiceCatalog/ServiceCatalog.tsx b/Dashboard/src/Pages/ServiceCatalog/ServiceCatalog.tsx index 4afea16209..c6b5d1eaab 100644 --- a/Dashboard/src/Pages/ServiceCatalog/ServiceCatalog.tsx +++ b/Dashboard/src/Pages/ServiceCatalog/ServiceCatalog.tsx @@ -1,186 +1,176 @@ -import LabelsElement from '../../Components/Label/Labels'; -import ServiceCatalogElement from '../../Components/ServiceCatalog/ServiceElement'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ServiceLanguage from 'Common/Types/ServiceCatalog/ServiceLanguage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Page from 'CommonUI/src/Components/Page/Page'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import ServiceCatalog from 'Model/Models/ServiceCatalog'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import ServiceCatalogElement from "../../Components/ServiceCatalog/ServiceElement"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ServiceLanguage from "Common/Types/ServiceCatalog/ServiceLanguage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Page from "CommonUI/src/Components/Page/Page"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import ServiceCatalog from "Model/Models/ServiceCatalog"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceCatalogPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'Service Catalog'} - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Service Catalog', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGES] as Route - ), - }, - ]} - > - <ModelTable<ServiceCatalog> - modelType={ServiceCatalog} - id="service-catalog-table" - isDeleteable={false} - isEditable={false} - isCreateable={true} - name="Service Catalog" - isViewable={true} - cardProps={{ - title: 'Service Catalog', - description: - 'List and manage services for this project here.', - }} - showViewIdButton={true} - noItemsMessage={'No services found.'} - selectMoreFields={{ - serviceColor: true, - }} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Service Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - serviceLanguage: true, - }, - title: 'Service Language / Framework', - description: - 'The language or framework used to build this service.', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Service Language', - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - ServiceLanguage - ), - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Element, - getElement: (service: ServiceCatalog): ReactElement => { - return ( - <Fragment> - <ServiceCatalogElement - serviceCatalog={service} - /> - </Fragment> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - serviceLanguage: true, - }, - title: 'Service Language / Framework', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, + return ( + <Page + title={"Service Catalog"} + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Service Catalog", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGES] as Route, + ), + }, + ]} + > + <ModelTable<ServiceCatalog> + modelType={ServiceCatalog} + id="service-catalog-table" + isDeleteable={false} + isEditable={false} + isCreateable={true} + name="Service Catalog" + isViewable={true} + cardProps={{ + title: "Service Catalog", + description: "List and manage services for this project here.", + }} + showViewIdButton={true} + noItemsMessage={"No services found."} + selectMoreFields={{ + serviceColor: true, + }} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Service Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + serviceLanguage: true, + }, + title: "Service Language / Framework", + description: + "The language or framework used to build this service.", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Service Language", + dropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(ServiceLanguage), + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Element, + getElement: (service: ServiceCatalog): ReactElement => { + return ( + <Fragment> + <ServiceCatalogElement serviceCatalog={service} /> + </Fragment> + ); + }, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + serviceLanguage: true, + }, + title: "Service Language / Framework", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, - getElement: (item: ServiceCatalog): ReactElement => { - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> - </Page> - ); + getElement: (item: ServiceCatalog): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + </Page> + ); }; export default ServiceCatalogPage; diff --git a/Dashboard/src/Pages/ServiceCatalog/View/Delete.tsx b/Dashboard/src/Pages/ServiceCatalog/View/Delete.tsx index eb43ec602c..c80309ba40 100644 --- a/Dashboard/src/Pages/ServiceCatalog/View/Delete.tsx +++ b/Dashboard/src/Pages/ServiceCatalog/View/Delete.tsx @@ -1,31 +1,29 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ServiceCatalog from 'Model/Models/ServiceCatalog'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ServiceCatalog from "Model/Models/ServiceCatalog"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceCatalogDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={ServiceCatalog} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.SERVICE_CATALOG] as Route - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={ServiceCatalog} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate(RouteMap[PageMap.SERVICE_CATALOG] as Route); + }} + /> + </Fragment> + ); }; export default ServiceCatalogDelete; diff --git a/Dashboard/src/Pages/ServiceCatalog/View/Index.tsx b/Dashboard/src/Pages/ServiceCatalog/View/Index.tsx index cb23c16a19..a88f5f5df1 100644 --- a/Dashboard/src/Pages/ServiceCatalog/View/Index.tsx +++ b/Dashboard/src/Pages/ServiceCatalog/View/Index.tsx @@ -1,153 +1,145 @@ -import LabelsElement from '../../../Components/Label/Labels'; -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import ServiceLanguage from 'Common/Types/ServiceCatalog/ServiceLanguage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import ServiceCatalog from 'Model/Models/ServiceCatalog'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../../Components/Label/Labels"; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import ServiceLanguage from "Common/Types/ServiceCatalog/ServiceLanguage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import ServiceCatalog from "Model/Models/ServiceCatalog"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - {/* ServiceCatalog View */} - <CardModelDetail<ServiceCatalog> - name="Service > Service Details" - cardProps={{ - title: 'Service Details', - description: 'Here are more details for this service.', - }} - formSteps={[ - { - title: 'Service Info', - id: 'service-info', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - stepId: 'service-info', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Service Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'service-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - serviceLanguage: true, - }, - stepId: 'service-info', - title: 'Service Language / Framework', - description: - 'The language or framework used to build this service.', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Service Language', - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - ServiceLanguage - ), - }, - { - field: { - labels: true, - }, - title: 'Labels ', - stepId: 'labels', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: ServiceCatalog, - id: 'model-detail-service-catalog', - fields: [ - { - field: { - _id: true, - }, - title: 'Service ID', - }, - { - field: { - name: true, - }, - title: 'Service Name', - }, - { - field: { - serviceLanguage: true, - }, - title: 'Service Language / Framework', - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: ( - item: ServiceCatalog - ): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + return ( + <Fragment> + {/* ServiceCatalog View */} + <CardModelDetail<ServiceCatalog> + name="Service > Service Details" + cardProps={{ + title: "Service Details", + description: "Here are more details for this service.", + }} + formSteps={[ + { + title: "Service Info", + id: "service-info", + }, + { + title: "Labels", + id: "labels", + }, + ]} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + stepId: "service-info", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Service Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "service-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + serviceLanguage: true, + }, + stepId: "service-info", + title: "Service Language / Framework", + description: + "The language or framework used to build this service.", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Service Language", + dropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(ServiceLanguage), + }, + { + field: { + labels: true, + }, + title: "Labels ", + stepId: "labels", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: ServiceCatalog, + id: "model-detail-service-catalog", + fields: [ + { + field: { + _id: true, + }, + title: "Service ID", + }, + { + field: { + name: true, + }, + title: "Service Name", + }, + { + field: { + serviceLanguage: true, + }, + title: "Service Language / Framework", + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: ServiceCatalog): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default StatusPageView; diff --git a/Dashboard/src/Pages/ServiceCatalog/View/Layout.tsx b/Dashboard/src/Pages/ServiceCatalog/View/Layout.tsx index d1bab94e97..e05a157e02 100644 --- a/Dashboard/src/Pages/ServiceCatalog/View/Layout.tsx +++ b/Dashboard/src/Pages/ServiceCatalog/View/Layout.tsx @@ -1,32 +1,32 @@ -import { getServiceCatalogBreadcrumbs } from '../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ServiceCatalog from 'Model/Models/ServiceCatalog'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getServiceCatalogBreadcrumbs } from "../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ServiceCatalog from "Model/Models/ServiceCatalog"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const ServiceCatalogViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="Service" - modelType={ServiceCatalog} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={getServiceCatalogBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <ModelPage + title="Service" + modelType={ServiceCatalog} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={getServiceCatalogBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default ServiceCatalogViewLayout; diff --git a/Dashboard/src/Pages/ServiceCatalog/View/Owners.tsx b/Dashboard/src/Pages/ServiceCatalog/View/Owners.tsx index f61cb81f55..33db866a39 100644 --- a/Dashboard/src/Pages/ServiceCatalog/View/Owners.tsx +++ b/Dashboard/src/Pages/ServiceCatalog/View/Owners.tsx @@ -1,231 +1,226 @@ -import TeamElement from '../../../Components/Team/Team'; -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ServiceCatalogOwnerTeam from 'Model/Models/ServiceCatalogOwnerTeam'; -import ServiceCatalogOwnerUser from 'Model/Models/ServiceCatalogOwnerUser'; -import Team from 'Model/Models/Team'; -import User from 'Model/Models/User'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import TeamElement from "../../../Components/Team/Team"; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ServiceCatalogOwnerTeam from "Model/Models/ServiceCatalogOwnerTeam"; +import ServiceCatalogOwnerUser from "Model/Models/ServiceCatalogOwnerUser"; +import Team from "Model/Models/Team"; +import User from "Model/Models/User"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceCatalogOwners: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<ServiceCatalogOwnerTeam> - modelType={ServiceCatalogOwnerTeam} - id="table-ServiceCatalog-owner-team" - name="ServiceCatalog > Owner Team" - singularName="Team" - isDeleteable={true} - createVerb={'Add'} - isCreateable={true} - isViewable={false} - showViewIdButton={true} - query={{ - serviceCatalogId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: ServiceCatalogOwnerTeam - ): Promise<ServiceCatalogOwnerTeam> => { - item.serviceCatalogId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Teams)', - description: - 'Here is list of teams that own this service. They will be alerted when this service is created or updated.', - }} - noItemsMessage={'No teams associated with this service so far.'} - formFields={[ - { - field: { - team: true, - }, - title: 'Team', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select Team', - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - team: true, - }, - type: FieldType.Entity, - title: 'Team', - filterEntityType: Team, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - team: { - name: true, - }, - }, - title: 'Team', - type: FieldType.Entity, + return ( + <Fragment> + <ModelTable<ServiceCatalogOwnerTeam> + modelType={ServiceCatalogOwnerTeam} + id="table-ServiceCatalog-owner-team" + name="ServiceCatalog > Owner Team" + singularName="Team" + isDeleteable={true} + createVerb={"Add"} + isCreateable={true} + isViewable={false} + showViewIdButton={true} + query={{ + serviceCatalogId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: ServiceCatalogOwnerTeam, + ): Promise<ServiceCatalogOwnerTeam> => { + item.serviceCatalogId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Teams)", + description: + "Here is list of teams that own this service. They will be alerted when this service is created or updated.", + }} + noItemsMessage={"No teams associated with this service so far."} + formFields={[ + { + field: { + team: true, + }, + title: "Team", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select Team", + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + team: true, + }, + type: FieldType.Entity, + title: "Team", + filterEntityType: Team, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + team: { + name: true, + }, + }, + title: "Team", + type: FieldType.Entity, - getElement: ( - item: ServiceCatalogOwnerTeam - ): ReactElement => { - if (!item['team']) { - throw new BadDataException('Team not found'); - } + getElement: (item: ServiceCatalogOwnerTeam): ReactElement => { + if (!item["team"]) { + throw new BadDataException("Team not found"); + } - return <TeamElement team={item['team'] as Team} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> + return <TeamElement team={item["team"] as Team} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> - <ModelTable<ServiceCatalogOwnerUser> - modelType={ServiceCatalogOwnerUser} - id="table-ServiceCatalog-owner-team" - name="ServiceCatalog > Owner Team" - isDeleteable={true} - singularName="User" - isCreateable={true} - isViewable={false} - showViewIdButton={true} - createVerb={'Add'} - query={{ - serviceCatalogId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: ServiceCatalogOwnerUser - ): Promise<ServiceCatalogOwnerUser> => { - item.serviceCatalogId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Users)', - description: - 'Here is list of users that own this service. They will be alerted when this service is created or updated.', - }} - noItemsMessage={'No users associated with this service so far.'} - formFields={[ - { - field: { - user: true, - }, - title: 'User', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select User', - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - user: true, - }, - title: 'User', - type: FieldType.Entity, - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - user: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: 'User', - type: FieldType.Entity, + <ModelTable<ServiceCatalogOwnerUser> + modelType={ServiceCatalogOwnerUser} + id="table-ServiceCatalog-owner-team" + name="ServiceCatalog > Owner Team" + isDeleteable={true} + singularName="User" + isCreateable={true} + isViewable={false} + showViewIdButton={true} + createVerb={"Add"} + query={{ + serviceCatalogId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: ServiceCatalogOwnerUser, + ): Promise<ServiceCatalogOwnerUser> => { + item.serviceCatalogId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Users)", + description: + "Here is list of users that own this service. They will be alerted when this service is created or updated.", + }} + noItemsMessage={"No users associated with this service so far."} + formFields={[ + { + field: { + user: true, + }, + title: "User", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select User", + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + user: true, + }, + title: "User", + type: FieldType.Entity, + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + user: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "User", + type: FieldType.Entity, - getElement: ( - item: ServiceCatalogOwnerUser - ): ReactElement => { - if (!item['user']) { - throw new BadDataException('User not found'); - } + getElement: (item: ServiceCatalogOwnerUser): ReactElement => { + if (!item["user"]) { + throw new BadDataException("User not found"); + } - return <UserElement user={item['user'] as User} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + return <UserElement user={item["user"] as User} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default ServiceCatalogOwners; diff --git a/Dashboard/src/Pages/ServiceCatalog/View/Settings.tsx b/Dashboard/src/Pages/ServiceCatalog/View/Settings.tsx index 8d588affdb..f8248472a9 100644 --- a/Dashboard/src/Pages/ServiceCatalog/View/Settings.tsx +++ b/Dashboard/src/Pages/ServiceCatalog/View/Settings.tsx @@ -1,57 +1,57 @@ -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ServiceCatalog from 'Model/Models/ServiceCatalog'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ServiceCatalog from "Model/Models/ServiceCatalog"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CardModelDetail - name="Service Settings" - cardProps={{ - title: 'Service Settings', - description: 'Configure settings for your service.', - }} - isEditable={true} - editButtonText="Edit Settings" - formFields={[ - { - field: { - serviceColor: true, - }, - title: 'Service Color', - description: 'Choose a color for your service.', - fieldType: FormFieldSchemaType.Color, - required: true, - placeholder: '15', - }, - ]} - modelDetailProps={{ - modelType: ServiceCatalog, - id: 'model-detail-project', - fields: [ - { - field: { - serviceColor: true, - }, - title: 'Service Color', - description: 'Color for your service.', - fieldType: FieldType.Color, - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + return ( + <Fragment> + <CardModelDetail + name="Service Settings" + cardProps={{ + title: "Service Settings", + description: "Configure settings for your service.", + }} + isEditable={true} + editButtonText="Edit Settings" + formFields={[ + { + field: { + serviceColor: true, + }, + title: "Service Color", + description: "Choose a color for your service.", + fieldType: FormFieldSchemaType.Color, + required: true, + placeholder: "15", + }, + ]} + modelDetailProps={{ + modelType: ServiceCatalog, + id: "model-detail-project", + fields: [ + { + field: { + serviceColor: true, + }, + title: "Service Color", + description: "Color for your service.", + fieldType: FieldType.Color, + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default ServiceDelete; diff --git a/Dashboard/src/Pages/ServiceCatalog/View/SideMenu.tsx b/Dashboard/src/Pages/ServiceCatalog/View/SideMenu.tsx index 82bd3ff10f..79588b236e 100644 --- a/Dashboard/src/Pages/ServiceCatalog/View/SideMenu.tsx +++ b/Dashboard/src/Pages/ServiceCatalog/View/SideMenu.tsx @@ -1,77 +1,71 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SERVICE_CATALOG_VIEW] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SERVICE_CATALOG_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> - <SideMenuItem - link={{ - title: 'Owners', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SERVICE_CATALOG_VIEW_OWNERS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Team} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Owners", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SERVICE_CATALOG_VIEW_OWNERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Team} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Settings', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SERVICE_CATALOG_VIEW_SETTINGS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Settings} - /> - <SideMenuItem - link={{ - title: 'Delete Service', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SERVICE_CATALOG_VIEW_DELETE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SERVICE_CATALOG_VIEW_SETTINGS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Settings} + /> + <SideMenuItem + link={{ + title: "Delete Service", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SERVICE_CATALOG_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Settings/APIKeyView.tsx b/Dashboard/src/Pages/Settings/APIKeyView.tsx index f37f1082ac..ad086ccac7 100644 --- a/Dashboard/src/Pages/Settings/APIKeyView.tsx +++ b/Dashboard/src/Pages/Settings/APIKeyView.tsx @@ -1,389 +1,363 @@ -import LabelsElement from '../../Components/Label/Labels'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission, { PermissionHelper } from 'Common/Types/Permission'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import { FormProps } from 'CommonUI/src/Components/Forms/BasicForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import ResetObjectID from 'CommonUI/src/Components/ResetObjectID/ResetObjectID'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import PermissionUtil from 'CommonUI/src/Utils/Permission'; -import ApiKey from 'Model/Models/ApiKey'; -import ApiKeyPermission from 'Model/Models/ApiKeyPermission'; -import Label from 'Model/Models/Label'; -import TeamPermission from 'Model/Models/TeamPermission'; +import LabelsElement from "../../Components/Label/Labels"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Permission, { PermissionHelper } from "Common/Types/Permission"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import { FormProps } from "CommonUI/src/Components/Forms/BasicForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import ResetObjectID from "CommonUI/src/Components/ResetObjectID/ResetObjectID"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import PermissionUtil from "CommonUI/src/Utils/Permission"; +import ApiKey from "Model/Models/ApiKey"; +import ApiKeyPermission from "Model/Models/ApiKeyPermission"; +import Label from "Model/Models/Label"; +import TeamPermission from "Model/Models/TeamPermission"; import React, { - Fragment, - FunctionComponent, - MutableRefObject, - ReactElement, -} from 'react'; + Fragment, + FunctionComponent, + MutableRefObject, + ReactElement, +} from "react"; export enum PermissionType { - AllowPermissions = 'AllowPermissions', - BlockPermissions = 'BlockPermissions', + AllowPermissions = "AllowPermissions", + BlockPermissions = "BlockPermissions", } const APIKeyView: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - const [refresher, setRefresher] = React.useState<boolean>(false); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const [refresher, setRefresher] = React.useState<boolean>(false); - type GetPermissionTable = (data: { - permissionType: PermissionType; - }) => ReactElement; + type GetPermissionTable = (data: { + permissionType: PermissionType; + }) => ReactElement; - const getPermissionTable: GetPermissionTable = (data: { - permissionType: PermissionType; - }): ReactElement => { - const { permissionType } = data; + const getPermissionTable: GetPermissionTable = (data: { + permissionType: PermissionType; + }): ReactElement => { + const { permissionType } = data; - const formRef: MutableRefObject< - FormProps<FormValues<ApiKeyPermission>> - > = React.useRef< - FormProps<FormValues<ApiKeyPermission>> - >() as MutableRefObject<FormProps<FormValues<ApiKeyPermission>>>; + const formRef: MutableRefObject<FormProps<FormValues<ApiKeyPermission>>> = + React.useRef< + FormProps<FormValues<ApiKeyPermission>> + >() as MutableRefObject<FormProps<FormValues<ApiKeyPermission>>>; - let tableTitle: string = 'Allow Permissions'; + let tableTitle: string = "Allow Permissions"; - if (permissionType === PermissionType.BlockPermissions) { - tableTitle = 'Block Permissions'; - } + if (permissionType === PermissionType.BlockPermissions) { + tableTitle = "Block Permissions"; + } - let tableDescription: string = - 'Here you can manage allow permissions for this API Key.'; + let tableDescription: string = + "Here you can manage allow permissions for this API Key."; - if (permissionType === PermissionType.BlockPermissions) { - tableDescription = - 'Here you can manage block permissions for this API Key. This will override any allow permissions set for this API Key.'; - } + if (permissionType === PermissionType.BlockPermissions) { + tableDescription = + "Here you can manage block permissions for this API Key. This will override any allow permissions set for this API Key."; + } - { - /* API Key Permisison Table */ - } - - return ( - <ModelTable<ApiKeyPermission> - modelType={ApiKeyPermission} - id="api-key-permission-table" - isDeleteable={true} - name="Settings > API Key > Permissions" - query={{ - apiKeyId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - isBlockPermission: - permissionType === PermissionType.BlockPermissions, - }} - onBeforeCreate={( - item: ApiKeyPermission - ): Promise<ApiKeyPermission> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - - item.apiKeyId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - item.isBlockPermission = - permissionType === PermissionType.BlockPermissions; - return Promise.resolve(item); - }} - isEditable={true} - isCreateable={true} - isViewable={false} - cardProps={{ - title: tableTitle, - description: tableDescription, - }} - noItemsMessage={ - 'No permisisons created for this API Key so far.' - } - createEditFromRef={formRef} - formFields={[ - { - field: { - permission: true, - }, - onChange: async (_value: any) => { - await formRef.current.setFieldValue( - 'labels', - [], - true - ); - }, - title: 'Permission', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Permission', - dropdownOptions: - PermissionUtil.projectPermissionsAsDropdownOptions(), - }, - { - field: { - labels: true, - }, - title: 'Restrict to Labels', - description: - 'If you want to restrict this permission to specific labels, you can select them here. This is an optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - showIf: ( - values: FormValues<TeamPermission> - ): boolean => { - if (!values['permission']) { - return false; - } - - if ( - values['permission'] && - !PermissionHelper.isAccessControlPermission( - values['permission'] as Permission - ) - ) { - return false; - } - - return true; - }, - required: false, - placeholder: 'Labels', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - permission: true, - }, - title: 'Permission', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - }, - }, - title: 'Restrict to Labels', - type: FieldType.EntityArray, - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - permission: true, - }, - title: 'Permission', - type: FieldType.Text, - - getElement: (item: ApiKeyPermission): ReactElement => { - return ( - <p> - {PermissionHelper.getTitle( - item['permission'] as Permission - )} - </p> - ); - }, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Restrict to Labels', - type: FieldType.EntityArray, - - getElement: (item: ApiKeyPermission): ReactElement => { - if ( - item && - item['permission'] && - !PermissionHelper.isAccessControlPermission( - item['permission'] as Permission - ) - ) { - return ( - <p> - Restriction by labels cannot be applied - to this permission. - </p> - ); - } - - if ( - !item['labels'] || - item['labels'].length === 0 - ) { - return ( - <p> - No restrictions has been applied to this - permission. - </p> - ); - } - - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> - ); - }; + { + /* API Key Permisison Table */ + } return ( - <Fragment> - {/* API Key View */} - <CardModelDetail<ApiKey> - name="API Key Details" - cardProps={{ - title: 'API Key Details', - description: 'Here are more details for this API Key.', - }} - refresher={refresher} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'API Key Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'API Key Description', - }, - { - field: { - expiresAt: true, - }, - title: 'Expires', - fieldType: FormFieldSchemaType.Date, - required: true, - placeholder: 'Expires at', - validation: { - dateShouldBeInTheFuture: true, - }, - }, - ]} - modelDetailProps={{ - modelType: ApiKey, - id: 'model-detail-api-key', - fields: [ - { - field: { - name: true, - }, - title: 'Name', - }, - { - field: { - description: true, - }, - title: 'Description', - }, - { - field: { - expiresAt: true, - }, - title: 'Expires', - fieldType: FieldType.Date, - }, - { - field: { - apiKey: true, - }, - title: 'API Key', - fieldType: FieldType.HiddenText, - opts: { - isCopyable: true, - }, - }, - ], - modelId: modelId, - }} - /> + <ModelTable<ApiKeyPermission> + modelType={ApiKeyPermission} + id="api-key-permission-table" + isDeleteable={true} + name="Settings > API Key > Permissions" + query={{ + apiKeyId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + isBlockPermission: permissionType === PermissionType.BlockPermissions, + }} + onBeforeCreate={(item: ApiKeyPermission): Promise<ApiKeyPermission> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } - <ResetObjectID<ApiKey> - modelType={ApiKey} - fieldName={'apiKey'} - title={'Reset API Key'} - description={'Reset the API Key to a new value.'} - modelId={modelId} - onUpdateComplete={() => { - setRefresher(!refresher); - }} - /> + item.apiKeyId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + item.isBlockPermission = + permissionType === PermissionType.BlockPermissions; + return Promise.resolve(item); + }} + isEditable={true} + isCreateable={true} + isViewable={false} + cardProps={{ + title: tableTitle, + description: tableDescription, + }} + noItemsMessage={"No permisisons created for this API Key so far."} + createEditFromRef={formRef} + formFields={[ + { + field: { + permission: true, + }, + onChange: async (_value: any) => { + await formRef.current.setFieldValue("labels", [], true); + }, + title: "Permission", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Permission", + dropdownOptions: + PermissionUtil.projectPermissionsAsDropdownOptions(), + }, + { + field: { + labels: true, + }, + title: "Restrict to Labels", + description: + "If you want to restrict this permission to specific labels, you can select them here. This is an optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + showIf: (values: FormValues<TeamPermission>): boolean => { + if (!values["permission"]) { + return false; + } - <Banner - openInNewTab={true} - title="Questions about Permissions?" - description="Watch this 5 minute video to learn how permissions work in OneUptime." - link={URL.fromString('https://youtu.be/TzmaTe4sbCI')} - /> + if ( + values["permission"] && + !PermissionHelper.isAccessControlPermission( + values["permission"] as Permission, + ) + ) { + return false; + } - {/* Allow Permissions */} - {getPermissionTable({ - permissionType: PermissionType.AllowPermissions, - })} + return true; + }, + required: false, + placeholder: "Labels", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + permission: true, + }, + title: "Permission", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + }, + }, + title: "Restrict to Labels", + type: FieldType.EntityArray, + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + permission: true, + }, + title: "Permission", + type: FieldType.Text, - {/* Block Permissions */} - {getPermissionTable({ - permissionType: PermissionType.BlockPermissions, - })} + getElement: (item: ApiKeyPermission): ReactElement => { + return ( + <p> + {PermissionHelper.getTitle(item["permission"] as Permission)} + </p> + ); + }, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Restrict to Labels", + type: FieldType.EntityArray, - {/* Delete API Key */} + getElement: (item: ApiKeyPermission): ReactElement => { + if ( + item && + item["permission"] && + !PermissionHelper.isAccessControlPermission( + item["permission"] as Permission, + ) + ) { + return ( + <p> + Restriction by labels cannot be applied to this permission. + </p> + ); + } - <ModelDelete - modelType={ApiKey} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.SETTINGS_APIKEYS] as Route - ); - }} - /> - </Fragment> + if (!item["labels"] || item["labels"].length === 0) { + return ( + <p>No restrictions has been applied to this permission.</p> + ); + } + + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> ); + }; + + return ( + <Fragment> + {/* API Key View */} + <CardModelDetail<ApiKey> + name="API Key Details" + cardProps={{ + title: "API Key Details", + description: "Here are more details for this API Key.", + }} + refresher={refresher} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "API Key Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "API Key Description", + }, + { + field: { + expiresAt: true, + }, + title: "Expires", + fieldType: FormFieldSchemaType.Date, + required: true, + placeholder: "Expires at", + validation: { + dateShouldBeInTheFuture: true, + }, + }, + ]} + modelDetailProps={{ + modelType: ApiKey, + id: "model-detail-api-key", + fields: [ + { + field: { + name: true, + }, + title: "Name", + }, + { + field: { + description: true, + }, + title: "Description", + }, + { + field: { + expiresAt: true, + }, + title: "Expires", + fieldType: FieldType.Date, + }, + { + field: { + apiKey: true, + }, + title: "API Key", + fieldType: FieldType.HiddenText, + opts: { + isCopyable: true, + }, + }, + ], + modelId: modelId, + }} + /> + + <ResetObjectID<ApiKey> + modelType={ApiKey} + fieldName={"apiKey"} + title={"Reset API Key"} + description={"Reset the API Key to a new value."} + modelId={modelId} + onUpdateComplete={() => { + setRefresher(!refresher); + }} + /> + + <Banner + openInNewTab={true} + title="Questions about Permissions?" + description="Watch this 5 minute video to learn how permissions work in OneUptime." + link={URL.fromString("https://youtu.be/TzmaTe4sbCI")} + /> + + {/* Allow Permissions */} + {getPermissionTable({ + permissionType: PermissionType.AllowPermissions, + })} + + {/* Block Permissions */} + {getPermissionTable({ + permissionType: PermissionType.BlockPermissions, + })} + + {/* Delete API Key */} + + <ModelDelete + modelType={ApiKey} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate(RouteMap[PageMap.SETTINGS_APIKEYS] as Route); + }} + /> + </Fragment> + ); }; export default APIKeyView; diff --git a/Dashboard/src/Pages/Settings/APIKeys.tsx b/Dashboard/src/Pages/Settings/APIKeys.tsx index 1932b35466..f2239ea28c 100644 --- a/Dashboard/src/Pages/Settings/APIKeys.tsx +++ b/Dashboard/src/Pages/Settings/APIKeys.tsx @@ -1,121 +1,121 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ApiKey from 'Model/Models/ApiKey'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ApiKey from "Model/Models/ApiKey"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const APIKeys: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<ApiKey> - modelType={ApiKey} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - id="api-keys-table" - name="Settings > API Keys" - isDeleteable={false} - isEditable={false} - showViewIdButton={false} - isCreateable={true} - isViewable={true} - cardProps={{ - title: 'API Keys', - description: - 'Everything you can do on the dashboard can also be done via the OneUptime API- use it to automate repetitive work or integrate with other platforms.', - }} - noItemsMessage={'No API Keys found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'API Key Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'API Key Description', - }, - { - field: { - expiresAt: true, - }, - title: 'Expires', - fieldType: FormFieldSchemaType.Date, - required: true, - placeholder: 'Expires at', - validation: { - dateShouldBeInTheFuture: true, - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - name: true, - }, - type: FieldType.Text, - title: 'Name', - }, - { - field: { - description: true, - }, - type: FieldType.Text, - title: 'Description', - }, - { - field: { - expiresAt: true, - }, - type: FieldType.Date, - title: 'Expires', - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - expiresAt: true, - }, - title: 'Expires', - type: FieldType.Date, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<ApiKey> + modelType={ApiKey} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + id="api-keys-table" + name="Settings > API Keys" + isDeleteable={false} + isEditable={false} + showViewIdButton={false} + isCreateable={true} + isViewable={true} + cardProps={{ + title: "API Keys", + description: + "Everything you can do on the dashboard can also be done via the OneUptime API- use it to automate repetitive work or integrate with other platforms.", + }} + noItemsMessage={"No API Keys found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "API Key Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "API Key Description", + }, + { + field: { + expiresAt: true, + }, + title: "Expires", + fieldType: FormFieldSchemaType.Date, + required: true, + placeholder: "Expires at", + validation: { + dateShouldBeInTheFuture: true, + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + name: true, + }, + type: FieldType.Text, + title: "Name", + }, + { + field: { + description: true, + }, + type: FieldType.Text, + title: "Description", + }, + { + field: { + expiresAt: true, + }, + type: FieldType.Date, + title: "Expires", + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + expiresAt: true, + }, + title: "Expires", + type: FieldType.Date, + }, + ]} + /> + </Fragment> + ); }; export default APIKeys; diff --git a/Dashboard/src/Pages/Settings/Base/CustomFieldsPageBase.tsx b/Dashboard/src/Pages/Settings/Base/CustomFieldsPageBase.tsx index cbe1de46d7..805e646f21 100644 --- a/Dashboard/src/Pages/Settings/Base/CustomFieldsPageBase.tsx +++ b/Dashboard/src/Pages/Settings/Base/CustomFieldsPageBase.tsx @@ -1,146 +1,145 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import CustomFieldType from 'Common/Types/CustomField/CustomFieldType'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentCustomField from 'Model/Models/IncidentCustomField'; -import MonitorCustomField from 'Model/Models/MonitorCustomField'; -import OnCallDutyPolicyCustomField from 'Model/Models/OnCallDutyPolicyCustomField'; -import ScheduledMaintenanceCustomField from 'Model/Models/ScheduledMaintenanceCustomField'; -import StatusPageCustomField from 'Model/Models/StatusPageCustomField'; -import React, { Fragment, ReactElement } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import CustomFieldType from "Common/Types/CustomField/CustomFieldType"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentCustomField from "Model/Models/IncidentCustomField"; +import MonitorCustomField from "Model/Models/MonitorCustomField"; +import OnCallDutyPolicyCustomField from "Model/Models/OnCallDutyPolicyCustomField"; +import ScheduledMaintenanceCustomField from "Model/Models/ScheduledMaintenanceCustomField"; +import StatusPageCustomField from "Model/Models/StatusPageCustomField"; +import React, { Fragment, ReactElement } from "react"; export type CustomFieldsBaseModels = - | MonitorCustomField - | StatusPageCustomField - | IncidentCustomField - | ScheduledMaintenanceCustomField - | OnCallDutyPolicyCustomField; + | MonitorCustomField + | StatusPageCustomField + | IncidentCustomField + | ScheduledMaintenanceCustomField + | OnCallDutyPolicyCustomField; export interface ComponentProps<CustomFieldsBaseModels> - extends PageComponentProps { - title: string; - modelType: { new (): CustomFieldsBaseModels }; + extends PageComponentProps { + title: string; + modelType: { new (): CustomFieldsBaseModels }; } const CustomFieldsPageBase: ( - props: ComponentProps<CustomFieldsBaseModels> + props: ComponentProps<CustomFieldsBaseModels>, ) => ReactElement = ( - props: ComponentProps<CustomFieldsBaseModels> + props: ComponentProps<CustomFieldsBaseModels>, ): ReactElement => { - return ( - <Fragment> - <ModelTable<CustomFieldsBaseModels> - modelType={props.modelType} - query={{ - projectId: DashboardNavigation.getProjectId()!, - }} - showViewIdButton={true} - id="custom-fields-table" - name={'Settings > ' + props.title} - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: props.title, - description: - 'Custom fields help you add new fields to your resources in OneUptime.', - }} - noItemsMessage={'No custom fields found.'} - viewPageRoute={Navigation.getCurrentRoute()} - formFields={[ - { - field: { - name: true, - }, - title: 'Field Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'internal-service', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Field Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: - 'This label is for all the internal services.', - }, - { - field: { - type: true, - }, - title: 'Field Type', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Please select field type.', - dropdownOptions: Object.keys(CustomFieldType).map( - (item: string) => { - return { - label: item, - value: item, - }; - } - ), - }, - ]} - showRefreshButton={true} - filters={[ - { - field: { - name: true, - }, - title: 'Field Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Field Description', - type: FieldType.Text, - }, - { - field: { - type: true, - }, - title: 'Field Type', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Field Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Field Description', - type: FieldType.Text, - }, - { - field: { - type: true, - }, - title: 'Field Type', - type: FieldType.Text, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<CustomFieldsBaseModels> + modelType={props.modelType} + query={{ + projectId: DashboardNavigation.getProjectId()!, + }} + showViewIdButton={true} + id="custom-fields-table" + name={"Settings > " + props.title} + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: props.title, + description: + "Custom fields help you add new fields to your resources in OneUptime.", + }} + noItemsMessage={"No custom fields found."} + viewPageRoute={Navigation.getCurrentRoute()} + formFields={[ + { + field: { + name: true, + }, + title: "Field Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "internal-service", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Field Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "This label is for all the internal services.", + }, + { + field: { + type: true, + }, + title: "Field Type", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Please select field type.", + dropdownOptions: Object.keys(CustomFieldType).map( + (item: string) => { + return { + label: item, + value: item, + }; + }, + ), + }, + ]} + showRefreshButton={true} + filters={[ + { + field: { + name: true, + }, + title: "Field Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Field Description", + type: FieldType.Text, + }, + { + field: { + type: true, + }, + title: "Field Type", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Field Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Field Description", + type: FieldType.Text, + }, + { + field: { + type: true, + }, + title: "Field Type", + type: FieldType.Text, + }, + ]} + /> + </Fragment> + ); }; export default CustomFieldsPageBase; diff --git a/Dashboard/src/Pages/Settings/Billing.tsx b/Dashboard/src/Pages/Settings/Billing.tsx index dd092bcaa4..74d544b63c 100644 --- a/Dashboard/src/Pages/Settings/Billing.tsx +++ b/Dashboard/src/Pages/Settings/Billing.tsx @@ -1,630 +1,560 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import CheckoutForm from './BillingPaymentMethodForm'; -import { Elements } from '@stripe/react-stripe-js'; -import { Stripe, loadStripe } from '@stripe/stripe-js'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import Text from 'Common/Types/Text'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ButtonType from 'CommonUI/src/Components/Button/ButtonTypes'; -import Card from 'CommonUI/src/Components/Card/Card'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Icon from 'CommonUI/src/Components/Icon/Icon'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import Modal from 'CommonUI/src/Components/Modal/Modal'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import { RadioButton } from 'CommonUI/src/Components/RadioButtons/GroupRadioButtons'; -import Toggle from 'CommonUI/src/Components/Toggle/Toggle'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import CheckoutForm from "./BillingPaymentMethodForm"; +import { Elements } from "@stripe/react-stripe-js"; +import { Stripe, loadStripe } from "@stripe/stripe-js"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import Text from "Common/Types/Text"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ButtonType from "CommonUI/src/Components/Button/ButtonTypes"; +import Card from "CommonUI/src/Components/Card/Card"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Icon from "CommonUI/src/Components/Icon/Icon"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import Modal from "CommonUI/src/Components/Modal/Modal"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons"; +import Toggle from "CommonUI/src/Components/Toggle/Toggle"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; import { - APP_API_URL, - BILLING_ENABLED, - BILLING_PUBLIC_KEY, - getAllEnvVars, -} from 'CommonUI/src/Config'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import BaseAPI from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import BillingPaymentMethod from 'Model/Models/BillingPaymentMethod'; -import Project from 'Model/Models/Project'; -import Reseller from 'Model/Models/Reseller'; -import ResellerPlan from 'Model/Models/ResellerPlan'; + APP_API_URL, + BILLING_ENABLED, + BILLING_PUBLIC_KEY, + getAllEnvVars, +} from "CommonUI/src/Config"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import BaseAPI from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import BillingPaymentMethod from "Model/Models/BillingPaymentMethod"; +import Project from "Model/Models/Project"; +import Reseller from "Model/Models/Reseller"; +import ResellerPlan from "Model/Models/ResellerPlan"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useRef, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + Fragment, + FunctionComponent, + ReactElement, + useRef, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps extends PageComponentProps {} const Settings: FunctionComponent<ComponentProps> = ( - _props: ComponentProps + _props: ComponentProps, ): ReactElement => { - const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] = - useState<boolean>(true); - const [showPaymentMethodModal, setShowPaymentMethodModal] = - useState<boolean>(false); - const [isModalLoading, setIsModalLoading] = useState<boolean>(false); - const [isModalSubmitButtonLoading, setIsModalSubmitButtonLoading] = - useState<boolean>(false); - const [modalError, setModalError] = useState<string | null>(null); - const [setupIntent, setSetupIntent] = useState<string>(''); - const [stripe, setStripe] = useState<Stripe | null>(null); + const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] = + useState<boolean>(true); + const [showPaymentMethodModal, setShowPaymentMethodModal] = + useState<boolean>(false); + const [isModalLoading, setIsModalLoading] = useState<boolean>(false); + const [isModalSubmitButtonLoading, setIsModalSubmitButtonLoading] = + useState<boolean>(false); + const [modalError, setModalError] = useState<string | null>(null); + const [setupIntent, setSetupIntent] = useState<string>(""); + const [stripe, setStripe] = useState<Stripe | null>(null); - const [isLoading, setIsLoading] = useState<boolean>(true); + const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); + const [error, setError] = useState<string | null>(null); - const [reseller, setReseller] = useState<Reseller | null>(null); + const [reseller, setReseller] = useState<Reseller | null>(null); - const [resellerPlan, setResellerPlan] = useState<ResellerPlan | null>(null); + const [resellerPlan, setResellerPlan] = useState<ResellerPlan | null>(null); - const formRef: any = useRef<any>(null); + const formRef: any = useRef<any>(null); - useAsyncEffect(async () => { - setIsModalLoading(true); - setStripe(await loadStripe(BILLING_PUBLIC_KEY)); - setIsModalLoading(false); + useAsyncEffect(async () => { + setIsModalLoading(true); + setStripe(await loadStripe(BILLING_PUBLIC_KEY)); + setIsModalLoading(false); - setIsLoading(true); + setIsLoading(true); - try { - const project: Project | null = await ModelAPI.getItem<Project>({ - modelType: Project, - id: DashboardNavigation.getProjectId()!, - select: { - reseller: { - name: true, - description: true, - _id: true, - changePlanLink: true, - }, - resellerPlan: { - name: true, - description: true, - _id: true, - monitorLimit: true, - teamMemberLimit: true, - planType: true, - otherFeatures: true, - }, - }, - }); + try { + const project: Project | null = await ModelAPI.getItem<Project>({ + modelType: Project, + id: DashboardNavigation.getProjectId()!, + select: { + reseller: { + name: true, + description: true, + _id: true, + changePlanLink: true, + }, + resellerPlan: { + name: true, + description: true, + _id: true, + monitorLimit: true, + teamMemberLimit: true, + planType: true, + otherFeatures: true, + }, + }, + }); - if (project?.reseller) { - setReseller(project.reseller); - } + if (project?.reseller) { + setReseller(project.reseller); + } - if (project?.resellerPlan) { - setResellerPlan(project.resellerPlan); - } - } catch (err) { - setError(BaseAPI.getFriendlyMessage(err)); - } + if (project?.resellerPlan) { + setResellerPlan(project.resellerPlan); + } + } catch (err) { + setError(BaseAPI.getFriendlyMessage(err)); + } - setIsLoading(false); - }, []); + setIsLoading(false); + }, []); - const fetchSetupIntent: PromiseVoidFunction = async (): Promise<void> => { - try { - setIsModalLoading(true); + const fetchSetupIntent: PromiseVoidFunction = async (): Promise<void> => { + try { + setIsModalLoading(true); - const response: HTTPResponse<JSONObject> = - await BaseAPI.post<JSONObject>( - URL.fromString(APP_API_URL.toString()).addRoute( - `/billing-payment-methods/setup` - ), - {}, - ModelAPI.getCommonHeaders() - ); - const data: JSONObject = response.data; + const response: HTTPResponse<JSONObject> = await BaseAPI.post<JSONObject>( + URL.fromString(APP_API_URL.toString()).addRoute( + `/billing-payment-methods/setup`, + ), + {}, + ModelAPI.getCommonHeaders(), + ); + const data: JSONObject = response.data; - setSetupIntent(data['setupIntent'] as string); - setIsModalLoading(false); - } catch (err) { - setModalError(BaseAPI.getFriendlyMessage(err)); - setIsModalLoading(false); - } - }; + setSetupIntent(data["setupIntent"] as string); + setIsModalLoading(false); + } catch (err) { + setModalError(BaseAPI.getFriendlyMessage(err)); + setIsModalLoading(false); + } + }; - const getFooter: GetReactElementFunction = (): ReactElement => { - if (!BILLING_ENABLED) { - return <></>; - } - - return ( - <Toggle - title="Yearly Plan" - value={isSubscriptionPlanYearly} - description="(Save 20%)" - onChange={(value: boolean) => { - setIsSubscriptionPlanYearly(value); - }} - /> - ); - }; + const getFooter: GetReactElementFunction = (): ReactElement => { + if (!BILLING_ENABLED) { + return <></>; + } return ( - <Fragment> - {isLoading ? <PageLoader isVisible={true} /> : <></>} - - {error ? <ErrorMessage error={error} /> : <></>} - - {!isLoading && !error ? ( - <div> - {!reseller && ( - <CardModelDetail<Project> - name="Plan Details" - cardProps={{ - title: 'Current Plan', - description: - 'Here is the plan this project is subscribed to.', - }} - isEditable={true} - editButtonText={'Change Plan'} - formFields={[ - { - field: { - paymentProviderPlanId: true, - }, - validation: { - minLength: 6, - }, - fieldType: FormFieldSchemaType.RadioButton, - radioButtonOptions: - SubscriptionPlan.getSubscriptionPlans( - getAllEnvVars() - ).map( - ( - plan: SubscriptionPlan - ): RadioButton => { - let description: string = - plan.isCustomPricing() - ? `Our sales team will contact you soon.` - : `Billed ${ - isSubscriptionPlanYearly - ? 'yearly' - : 'monthly' - }. ${ - plan.getTrialPeriod() > - 0 - ? `Free ${plan.getTrialPeriod()} days trial.` - : '' - }`; - - if ( - isSubscriptionPlanYearly && - plan.getYearlySubscriptionAmountInUSD() === - 0 - ) { - description = - 'This plan is free, forever. '; - } - - if ( - !isSubscriptionPlanYearly && - plan.getMonthlySubscriptionAmountInUSD() === - 0 - ) { - description = - 'This plan is free, forever. '; - } - - return { - value: isSubscriptionPlanYearly - ? plan.getYearlyPlanId() - : plan.getMonthlyPlanId(), - title: plan.getName(), - description: description, - sideTitle: - plan.isCustomPricing() - ? 'Custom Price' - : isSubscriptionPlanYearly - ? '$' + - plan - .getYearlySubscriptionAmountInUSD() - .toString() + - '/mo billed yearly' - : '$' + - plan - .getMonthlySubscriptionAmountInUSD() - .toString(), - sideDescription: - plan.isCustomPricing() - ? '' - : isSubscriptionPlanYearly - ? `~ $${ - plan.getYearlySubscriptionAmountInUSD() * - 12 - } per user / year` - : `/month per user`, - }; - } - ), - title: 'Please select a plan.', - required: true, - footerElement: getFooter(), - }, - ]} - modelDetailProps={{ - modelType: Project, - id: 'model-detail-project', - fields: [ - { - field: { - paymentProviderPlanId: true, - }, - title: 'Current Plan', - getElement: ( - item: Project - ): ReactElement => { - const plan: - | SubscriptionPlan - | undefined = SubscriptionPlan.getSubscriptionPlanById( - item[ - 'paymentProviderPlanId' - ] as string, - getAllEnvVars() - ); - - if (!plan) { - return ( - <p> - No plan selected for - this project - </p> - ); - } - - const isYearlyPlan: boolean = - SubscriptionPlan.isYearlyPlan( - item[ - 'paymentProviderPlanId' - ] as string, - getAllEnvVars() - ); - - let description: string = - plan.isCustomPricing() - ? `Custom Pricing based on your needs. Our sales team will contact you shortly.` - : `$${ - isYearlyPlan - ? plan.getYearlySubscriptionAmountInUSD() - : plan.getMonthlySubscriptionAmountInUSD() - } / month per user. Billed ${ - isYearlyPlan - ? 'yearly' - : 'monthly' - }.`; - - if ( - isYearlyPlan && - plan.getYearlySubscriptionAmountInUSD() === - 0 - ) { - description = - 'This plan is free, forever. '; - } - - if ( - !isYearlyPlan && - plan.getMonthlySubscriptionAmountInUSD() === - 0 - ) { - description = - 'This plan is free, forever. '; - } - - return ( - <div> - <div className="bold"> - {plan.getName()} - </div> - <div>{description}</div> - </div> - ); - }, - }, - { - field: { - paymentProviderSubscriptionSeats: - true, - }, - title: 'Seats', - description: - 'These are current users in this project. To change this you need to add or remove them.', - getElement: ( - item: Project - ): ReactElement => { - return ( - <div> - <div className="bold"> - { - item[ - 'paymentProviderSubscriptionSeats' - ] - }{' '} - users in this project. - </div> - </div> - ); - }, - }, - ], - modelId: DashboardNavigation.getProjectId()!, - }} - /> - )} - - {reseller && ( - <Card - title={`You have purchased this plan from ${reseller.name}`} - description={`If you would like to change the plan, please contact ${reseller.name} at ${reseller.description}`} - buttons={ - reseller.changePlanLink - ? [ - { - title: `Change Plan`, - onClick: () => { - Navigation.navigate( - reseller.changePlanLink! - ); - }, - icon: IconProp.Edit, - }, - ] - : [] - } - > - <div className="space-y-2"> - <div className="text-sm font-medium text-gray-500"> - The plan you purchased from {reseller.name}{' '} - is {resellerPlan?.name} - </div> - <div> - <span className="text-sm font-medium text-gray-500 mt-10"> - With the following features: - </span> - - <ul className="space-y-1 mt-2"> - <li className="text-sm font-medium text-gray-500"> - {' '} - <span className="text-gray-700 flex"> - <Icon - icon={IconProp.CheckCircle} - className="h-5 w-5 mr-1" - />{' '} - {resellerPlan?.monitorLimit}{' '} - Monitors - </span> - </li> - <li className="text-sm font-medium text-gray-500"> - {' '} - <span className="text-gray-700 flex"> - <Icon - icon={IconProp.CheckCircle} - className="h-5 w-5 mr-1" - />{' '} - {resellerPlan?.teamMemberLimit}{' '} - Team Members - </span> - </li> - - {resellerPlan?.otherFeatures ? ( - resellerPlan.otherFeatures - .split(',') - .map( - ( - item: string, - i: number - ) => { - return ( - <li - key={i} - className="text-sm font-medium text-gray-500" - > - {' '} - <span className="text-gray-700 flex"> - <Icon - icon={ - IconProp.CheckCircle - } - className="h-5 w-5 mr-1" - />{' '} - {item} - </span> - </li> - ); - } - ) - ) : ( - <></> - )} - </ul> - </div> - </div> - </Card> - )} - - <ModelTable<BillingPaymentMethod> - modelType={BillingPaymentMethod} - id="payment-methods-table" - isDeleteable={true} - isEditable={false} - isCreateable={false} - isViewable={false} - name="Settings > Billing > Add Payment Method" - cardProps={{ - buttons: [ - { - title: 'Add Payment Method', - icon: IconProp.Add, - onClick: async () => { - setShowPaymentMethodModal(true); - await fetchSetupIntent(); - }, - buttonStyle: ButtonStyleType.NORMAL, - }, - ], - title: 'Payment Methods', - description: - 'Here is a list of payment methods attached to this project.', - }} - noItemsMessage={'No payment methods found.'} - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - }} - showRefreshButton={true} - filters={[ - { - field: { - type: true, - }, - title: 'Payment Method Type', - type: FieldType.Text, - }, - { - field: { - last4Digits: true, - }, - title: 'Number', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - type: true, - }, - title: 'Payment Method Type', - type: FieldType.Text, - - getElement: (item: BillingPaymentMethod) => { - return ( - <span>{`${Text.uppercaseFirstLetter( - item['type'] as string - )}`}</span> - ); - }, - }, - { - field: { - last4Digits: true, - }, - title: 'Number', - type: FieldType.Text, - - getElement: (item: BillingPaymentMethod) => { - return ( - <span>{`*****${item['last4Digits']}`}</span> - ); - }, - }, - ]} - /> - - {showPaymentMethodModal ? ( - <Modal - title={`Add Payment Method`} - onSubmit={async () => { - setIsModalSubmitButtonLoading(true); - formRef.current.click(); - }} - isLoading={isModalSubmitButtonLoading} - onClose={() => { - setShowPaymentMethodModal(false); - }} - submitButtonText={`Save`} - error={modalError || ''} - isBodyLoading={isModalLoading} - submitButtonType={ButtonType.Submit} - > - {setupIntent && !modalError && stripe ? ( - <Elements - stripe={stripe} - options={{ - // passing the client secret obtained in step 3 - clientSecret: setupIntent, - }} - > - <CheckoutForm - onSuccess={() => { - setIsModalSubmitButtonLoading( - false - ); - }} - onError={(errorMessage: string) => { - setModalError(errorMessage); - setIsModalSubmitButtonLoading( - false - ); - }} - formRef={formRef} - /> - </Elements> - ) : ( - <></> - )} - {!modalError && !setupIntent && !stripe ? ( - <p>Loading...</p> - ) : ( - <></> - )} - </Modal> - ) : ( - <></> - )} - - {!reseller && ( - <Card - title={`Cancel Plan`} - description={`If you would like to cancel the plan, you need to delete the project.`} - buttons={[ - { - title: `Delete Project`, - buttonStyle: ButtonStyleType.DANGER, - onClick: () => { - Navigation.navigate( - RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_DANGERZONE - ] as Route - ) - ); - }, - icon: IconProp.Close, - }, - ]} - /> - )} - - {reseller && ( - <Card - title={`Cancel Plan`} - description={`If you would like to cancel the plan or delete the project, please contact ${reseller.name} at ${reseller.description}`} - /> - )} - </div> - ) : ( - <></> - )} - </Fragment> + <Toggle + title="Yearly Plan" + value={isSubscriptionPlanYearly} + description="(Save 20%)" + onChange={(value: boolean) => { + setIsSubscriptionPlanYearly(value); + }} + /> ); + }; + + return ( + <Fragment> + {isLoading ? <PageLoader isVisible={true} /> : <></>} + + {error ? <ErrorMessage error={error} /> : <></>} + + {!isLoading && !error ? ( + <div> + {!reseller && ( + <CardModelDetail<Project> + name="Plan Details" + cardProps={{ + title: "Current Plan", + description: "Here is the plan this project is subscribed to.", + }} + isEditable={true} + editButtonText={"Change Plan"} + formFields={[ + { + field: { + paymentProviderPlanId: true, + }, + validation: { + minLength: 6, + }, + fieldType: FormFieldSchemaType.RadioButton, + radioButtonOptions: SubscriptionPlan.getSubscriptionPlans( + getAllEnvVars(), + ).map((plan: SubscriptionPlan): RadioButton => { + let description: string = plan.isCustomPricing() + ? `Our sales team will contact you soon.` + : `Billed ${ + isSubscriptionPlanYearly ? "yearly" : "monthly" + }. ${ + plan.getTrialPeriod() > 0 + ? `Free ${plan.getTrialPeriod()} days trial.` + : "" + }`; + + if ( + isSubscriptionPlanYearly && + plan.getYearlySubscriptionAmountInUSD() === 0 + ) { + description = "This plan is free, forever. "; + } + + if ( + !isSubscriptionPlanYearly && + plan.getMonthlySubscriptionAmountInUSD() === 0 + ) { + description = "This plan is free, forever. "; + } + + return { + value: isSubscriptionPlanYearly + ? plan.getYearlyPlanId() + : plan.getMonthlyPlanId(), + title: plan.getName(), + description: description, + sideTitle: plan.isCustomPricing() + ? "Custom Price" + : isSubscriptionPlanYearly + ? "$" + + plan.getYearlySubscriptionAmountInUSD().toString() + + "/mo billed yearly" + : "$" + + plan.getMonthlySubscriptionAmountInUSD().toString(), + sideDescription: plan.isCustomPricing() + ? "" + : isSubscriptionPlanYearly + ? `~ $${ + plan.getYearlySubscriptionAmountInUSD() * 12 + } per user / year` + : `/month per user`, + }; + }), + title: "Please select a plan.", + required: true, + footerElement: getFooter(), + }, + ]} + modelDetailProps={{ + modelType: Project, + id: "model-detail-project", + fields: [ + { + field: { + paymentProviderPlanId: true, + }, + title: "Current Plan", + getElement: (item: Project): ReactElement => { + const plan: SubscriptionPlan | undefined = + SubscriptionPlan.getSubscriptionPlanById( + item["paymentProviderPlanId"] as string, + getAllEnvVars(), + ); + + if (!plan) { + return <p>No plan selected for this project</p>; + } + + const isYearlyPlan: boolean = + SubscriptionPlan.isYearlyPlan( + item["paymentProviderPlanId"] as string, + getAllEnvVars(), + ); + + let description: string = plan.isCustomPricing() + ? `Custom Pricing based on your needs. Our sales team will contact you shortly.` + : `$${ + isYearlyPlan + ? plan.getYearlySubscriptionAmountInUSD() + : plan.getMonthlySubscriptionAmountInUSD() + } / month per user. Billed ${ + isYearlyPlan ? "yearly" : "monthly" + }.`; + + if ( + isYearlyPlan && + plan.getYearlySubscriptionAmountInUSD() === 0 + ) { + description = "This plan is free, forever. "; + } + + if ( + !isYearlyPlan && + plan.getMonthlySubscriptionAmountInUSD() === 0 + ) { + description = "This plan is free, forever. "; + } + + return ( + <div> + <div className="bold">{plan.getName()}</div> + <div>{description}</div> + </div> + ); + }, + }, + { + field: { + paymentProviderSubscriptionSeats: true, + }, + title: "Seats", + description: + "These are current users in this project. To change this you need to add or remove them.", + getElement: (item: Project): ReactElement => { + return ( + <div> + <div className="bold"> + {item["paymentProviderSubscriptionSeats"]} users in + this project. + </div> + </div> + ); + }, + }, + ], + modelId: DashboardNavigation.getProjectId()!, + }} + /> + )} + + {reseller && ( + <Card + title={`You have purchased this plan from ${reseller.name}`} + description={`If you would like to change the plan, please contact ${reseller.name} at ${reseller.description}`} + buttons={ + reseller.changePlanLink + ? [ + { + title: `Change Plan`, + onClick: () => { + Navigation.navigate(reseller.changePlanLink!); + }, + icon: IconProp.Edit, + }, + ] + : [] + } + > + <div className="space-y-2"> + <div className="text-sm font-medium text-gray-500"> + The plan you purchased from {reseller.name} is{" "} + {resellerPlan?.name} + </div> + <div> + <span className="text-sm font-medium text-gray-500 mt-10"> + With the following features: + </span> + + <ul className="space-y-1 mt-2"> + <li className="text-sm font-medium text-gray-500"> + {" "} + <span className="text-gray-700 flex"> + <Icon + icon={IconProp.CheckCircle} + className="h-5 w-5 mr-1" + />{" "} + {resellerPlan?.monitorLimit} Monitors + </span> + </li> + <li className="text-sm font-medium text-gray-500"> + {" "} + <span className="text-gray-700 flex"> + <Icon + icon={IconProp.CheckCircle} + className="h-5 w-5 mr-1" + />{" "} + {resellerPlan?.teamMemberLimit} Team Members + </span> + </li> + + {resellerPlan?.otherFeatures ? ( + resellerPlan.otherFeatures + .split(",") + .map((item: string, i: number) => { + return ( + <li + key={i} + className="text-sm font-medium text-gray-500" + > + {" "} + <span className="text-gray-700 flex"> + <Icon + icon={IconProp.CheckCircle} + className="h-5 w-5 mr-1" + />{" "} + {item} + </span> + </li> + ); + }) + ) : ( + <></> + )} + </ul> + </div> + </div> + </Card> + )} + + <ModelTable<BillingPaymentMethod> + modelType={BillingPaymentMethod} + id="payment-methods-table" + isDeleteable={true} + isEditable={false} + isCreateable={false} + isViewable={false} + name="Settings > Billing > Add Payment Method" + cardProps={{ + buttons: [ + { + title: "Add Payment Method", + icon: IconProp.Add, + onClick: async () => { + setShowPaymentMethodModal(true); + await fetchSetupIntent(); + }, + buttonStyle: ButtonStyleType.NORMAL, + }, + ], + title: "Payment Methods", + description: + "Here is a list of payment methods attached to this project.", + }} + noItemsMessage={"No payment methods found."} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + showRefreshButton={true} + filters={[ + { + field: { + type: true, + }, + title: "Payment Method Type", + type: FieldType.Text, + }, + { + field: { + last4Digits: true, + }, + title: "Number", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + type: true, + }, + title: "Payment Method Type", + type: FieldType.Text, + + getElement: (item: BillingPaymentMethod) => { + return ( + <span>{`${Text.uppercaseFirstLetter( + item["type"] as string, + )}`}</span> + ); + }, + }, + { + field: { + last4Digits: true, + }, + title: "Number", + type: FieldType.Text, + + getElement: (item: BillingPaymentMethod) => { + return <span>{`*****${item["last4Digits"]}`}</span>; + }, + }, + ]} + /> + + {showPaymentMethodModal ? ( + <Modal + title={`Add Payment Method`} + onSubmit={async () => { + setIsModalSubmitButtonLoading(true); + formRef.current.click(); + }} + isLoading={isModalSubmitButtonLoading} + onClose={() => { + setShowPaymentMethodModal(false); + }} + submitButtonText={`Save`} + error={modalError || ""} + isBodyLoading={isModalLoading} + submitButtonType={ButtonType.Submit} + > + {setupIntent && !modalError && stripe ? ( + <Elements + stripe={stripe} + options={{ + // passing the client secret obtained in step 3 + clientSecret: setupIntent, + }} + > + <CheckoutForm + onSuccess={() => { + setIsModalSubmitButtonLoading(false); + }} + onError={(errorMessage: string) => { + setModalError(errorMessage); + setIsModalSubmitButtonLoading(false); + }} + formRef={formRef} + /> + </Elements> + ) : ( + <></> + )} + {!modalError && !setupIntent && !stripe ? ( + <p>Loading...</p> + ) : ( + <></> + )} + </Modal> + ) : ( + <></> + )} + + {!reseller && ( + <Card + title={`Cancel Plan`} + description={`If you would like to cancel the plan, you need to delete the project.`} + buttons={[ + { + title: `Delete Project`, + buttonStyle: ButtonStyleType.DANGER, + onClick: () => { + Navigation.navigate( + RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_DANGERZONE] as Route, + ), + ); + }, + icon: IconProp.Close, + }, + ]} + /> + )} + + {reseller && ( + <Card + title={`Cancel Plan`} + description={`If you would like to cancel the plan or delete the project, please contact ${reseller.name} at ${reseller.description}`} + /> + )} + </div> + ) : ( + <></> + )} + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/Settings/BillingPaymentMethodForm.tsx b/Dashboard/src/Pages/Settings/BillingPaymentMethodForm.tsx index e833ba5a17..80bdf9deda 100644 --- a/Dashboard/src/Pages/Settings/BillingPaymentMethodForm.tsx +++ b/Dashboard/src/Pages/Settings/BillingPaymentMethodForm.tsx @@ -1,71 +1,69 @@ import { - PaymentElement, - useElements, - useStripe, -} from '@stripe/react-stripe-js'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement, Ref } from 'react'; + PaymentElement, + useElements, + useStripe, +} from "@stripe/react-stripe-js"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement, Ref } from "react"; export interface ComponentProps { - onError: (error: string) => void; - onSuccess: () => void; - formRef: Ref<any>; + onError: (error: string) => void; + onSuccess: () => void; + formRef: Ref<any>; } const CheckoutForm: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const stripe: any = useStripe(); - const elements: any = useElements(); + const stripe: any = useStripe(); + const elements: any = useElements(); - type SubmitFormFunction = (event: Event) => Promise<void>; + type SubmitFormFunction = (event: Event) => Promise<void>; - const submitForm: SubmitFormFunction = async ( - event: Event - ): Promise<void> => { - event.preventDefault(); - // We don't want to let default form submission happen here, - // which would refresh the page. - event.preventDefault(); + const submitForm: SubmitFormFunction = async ( + event: Event, + ): Promise<void> => { + event.preventDefault(); + // We don't want to let default form submission happen here, + // which would refresh the page. + event.preventDefault(); - if (!stripe || !elements) { - // Stripe.js has not yet loaded. - // Make sure to disable form submission until Stripe.js has loaded. - return; - } + if (!stripe || !elements) { + // Stripe.js has not yet loaded. + // Make sure to disable form submission until Stripe.js has loaded. + return; + } - const { error } = await stripe.confirmSetup({ - //`Elements` instance that was used to create the Payment Element - elements, - confirmParams: { - return_url: Navigation.getCurrentURL() - .removeQueryString() - .toString(), - }, - }); + const { error } = await stripe.confirmSetup({ + //`Elements` instance that was used to create the Payment Element + elements, + confirmParams: { + return_url: Navigation.getCurrentURL().removeQueryString().toString(), + }, + }); - if (error) { - // This point will only be reached if there is an immediate error when - // confirming the payment. Show error to your customer (for example, payment - // details incomplete) + if (error) { + // This point will only be reached if there is an immediate error when + // confirming the payment. Show error to your customer (for example, payment + // details incomplete) - props.onError( - error.message?.toString() || - 'Unknown error with your payemnt provider.' - ); - } else { - // Your customer will be redirected to your `return_url`. For some payment - // methods like iDEAL, your customer will be redirected to an indeterminate - // site first to authorize the payment, then redirected to the `return_url`. - props.onSuccess(); - } - }; + props.onError( + error.message?.toString() || + "Unknown error with your payemnt provider.", + ); + } else { + // Your customer will be redirected to your `return_url`. For some payment + // methods like iDEAL, your customer will be redirected to an indeterminate + // site first to authorize the payment, then redirected to the `return_url`. + props.onSuccess(); + } + }; - return ( - <div ref={props.formRef} onClick={submitForm as any}> - <PaymentElement /> - </div> - ); + return ( + <div ref={props.formRef} onClick={submitForm as any}> + <PaymentElement /> + </div> + ); }; export default CheckoutForm; diff --git a/Dashboard/src/Pages/Settings/CallLog.tsx b/Dashboard/src/Pages/Settings/CallLog.tsx index 15fd45f54d..f1797a4c59 100644 --- a/Dashboard/src/Pages/Settings/CallLog.tsx +++ b/Dashboard/src/Pages/Settings/CallLog.tsx @@ -1,230 +1,221 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import { Green, Red } from 'Common/Types/BrandColors'; -import CallStatus from 'Common/Types/Call/CallStatus'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import Filter from 'CommonUI/src/Components/ModelFilter/Filter'; -import Column from 'CommonUI/src/Components/ModelTable/Column'; -import Columns from 'CommonUI/src/Components/ModelTable/Columns'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import SimpleLogViewer from 'CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { BILLING_ENABLED } from 'CommonUI/src/Config'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import CallLog from 'Model/Models/CallLog'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import { Green, Red } from "Common/Types/BrandColors"; +import CallStatus from "Common/Types/Call/CallStatus"; +import IconProp from "Common/Types/Icon/IconProp"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import Filter from "CommonUI/src/Components/ModelFilter/Filter"; +import Column from "CommonUI/src/Components/ModelTable/Column"; +import Columns from "CommonUI/src/Components/ModelTable/Columns"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import SimpleLogViewer from "CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { BILLING_ENABLED } from "CommonUI/src/Config"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import CallLog from "Model/Models/CallLog"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const CallLogs: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [showViewCallTextModal, setShowViewCallTextModal] = - useState<boolean>(false); - const [callText, setCallText] = useState<string>(''); - const [callModelTitle, setCallModalTitle] = useState<string>(''); + const [showViewCallTextModal, setShowViewCallTextModal] = + useState<boolean>(false); + const [callText, setCallText] = useState<string>(""); + const [callModelTitle, setCallModalTitle] = useState<string>(""); - const filters: Array<Filter<CallLog>> = [ - { - field: { - _id: true, + const filters: Array<Filter<CallLog>> = [ + { + field: { + _id: true, + }, + title: "Log ID", + type: FieldType.ObjectID, + }, + { + field: { + fromNumber: true, + }, + + title: "From Number", + type: FieldType.Phone, + }, + { + field: { + toNumber: true, + }, + + title: "To Number", + type: FieldType.Phone, + }, + { + field: { + createdAt: true, + }, + title: "Sent at", + type: FieldType.Date, + }, + + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Dropdown, + filterDropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(CallStatus), + }, + ]; + + const modelTableColumns: Columns<CallLog> = [ + { + field: { + _id: true, + }, + title: "Log ID", + type: FieldType.ObjectID, + }, + { + field: { + fromNumber: true, + }, + + title: "From Number", + type: FieldType.Phone, + }, + { + field: { + toNumber: true, + }, + + title: "To Number", + type: FieldType.Phone, + }, + { + field: { + createdAt: true, + }, + title: "Sent at", + type: FieldType.DateTime, + }, + + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Text, + getElement: (item: CallLog): ReactElement => { + if (item["status"]) { + return ( + <Pill + isMinimal={false} + color={item["status"] === CallStatus.Success ? Green : Red} + text={item["status"] as string} + /> + ); + } + + return <></>; + }, + }, + ]; + + if (BILLING_ENABLED) { + modelTableColumns.push({ + field: { + callCostInUSDCents: true, + }, + title: "Call Cost", + type: FieldType.USDCents, + } as Column<CallLog>); + } + + return ( + <Fragment> + <> + <ModelTable<CallLog> + modelType={CallLog} + id="call-logs-table" + isDeleteable={false} + isEditable={false} + isCreateable={false} + name="Call Logs" + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + selectMoreFields={{ + callData: true, + statusMessage: true, + }} + actionButtons={[ + { + title: "View Call Text", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.List, + onClick: async ( + item: CallLog, + onCompleteAction: VoidFunction, + ) => { + setCallText(JSON.stringify(item["callData"]) as string); + + setCallModalTitle("Call Text"); + setShowViewCallTextModal(true); + + onCompleteAction(); + }, }, - title: 'Log ID', - type: FieldType.ObjectID, - }, - { - field: { - fromNumber: true, + { + title: "View Status Message", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.Error, + onClick: async ( + item: CallLog, + onCompleteAction: VoidFunction, + ) => { + setCallText(item["statusMessage"] as string); + + setCallModalTitle("Status Message"); + setShowViewCallTextModal(true); + + onCompleteAction(); + }, }, + ]} + isViewable={false} + cardProps={{ + title: "Call Logs", + description: + "Logs of all the Call sent by this project in the last 30 days.", + }} + noItemsMessage={ + "Looks like no Call is sent by this project in the last 30 days." + } + showRefreshButton={true} + filters={filters} + columns={modelTableColumns} + /> - title: 'From Number', - type: FieldType.Phone, - }, - { - field: { - toNumber: true, - }, - - title: 'To Number', - type: FieldType.Phone, - }, - { - field: { - createdAt: true, - }, - title: 'Sent at', - type: FieldType.Date, - }, - - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Dropdown, - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum(CallStatus), - }, - ]; - - const modelTableColumns: Columns<CallLog> = [ - { - field: { - _id: true, - }, - title: 'Log ID', - type: FieldType.ObjectID, - }, - { - field: { - fromNumber: true, - }, - - title: 'From Number', - type: FieldType.Phone, - }, - { - field: { - toNumber: true, - }, - - title: 'To Number', - type: FieldType.Phone, - }, - { - field: { - createdAt: true, - }, - title: 'Sent at', - type: FieldType.DateTime, - }, - - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Text, - getElement: (item: CallLog): ReactElement => { - if (item['status']) { - return ( - <Pill - isMinimal={false} - color={ - item['status'] === CallStatus.Success - ? Green - : Red - } - text={item['status'] as string} - /> - ); - } - - return <></>; - }, - }, - ]; - - if (BILLING_ENABLED) { - modelTableColumns.push({ - field: { - callCostInUSDCents: true, - }, - title: 'Call Cost', - type: FieldType.USDCents, - } as Column<CallLog>); - } - - return ( - <Fragment> - <> - <ModelTable<CallLog> - modelType={CallLog} - id="call-logs-table" - isDeleteable={false} - isEditable={false} - isCreateable={false} - name="Call Logs" - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - }} - selectMoreFields={{ - callData: true, - statusMessage: true, - }} - actionButtons={[ - { - title: 'View Call Text', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.List, - onClick: async ( - item: CallLog, - onCompleteAction: VoidFunction - ) => { - setCallText( - JSON.stringify(item['callData']) as string - ); - - setCallModalTitle('Call Text'); - setShowViewCallTextModal(true); - - onCompleteAction(); - }, - }, - { - title: 'View Status Message', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.Error, - onClick: async ( - item: CallLog, - onCompleteAction: VoidFunction - ) => { - setCallText(item['statusMessage'] as string); - - setCallModalTitle('Status Message'); - setShowViewCallTextModal(true); - - onCompleteAction(); - }, - }, - ]} - isViewable={false} - cardProps={{ - title: 'Call Logs', - description: - 'Logs of all the Call sent by this project in the last 30 days.', - }} - noItemsMessage={ - 'Looks like no Call is sent by this project in the last 30 days.' - } - showRefreshButton={true} - filters={filters} - columns={modelTableColumns} - /> - - {showViewCallTextModal && ( - <ConfirmModal - title={callModelTitle} - description={ - <SimpleLogViewer>{callText}</SimpleLogViewer> - } - onSubmit={() => { - setShowViewCallTextModal(false); - }} - submitButtonText="Close" - submitButtonType={ButtonStyleType.NORMAL} - /> - )} - </> - </Fragment> - ); + {showViewCallTextModal && ( + <ConfirmModal + title={callModelTitle} + description={<SimpleLogViewer>{callText}</SimpleLogViewer>} + onSubmit={() => { + setShowViewCallTextModal(false); + }} + submitButtonText="Close" + submitButtonType={ButtonStyleType.NORMAL} + /> + )} + </> + </Fragment> + ); }; export default CallLogs; diff --git a/Dashboard/src/Pages/Settings/DangerZone.tsx b/Dashboard/src/Pages/Settings/DangerZone.tsx index 358f7a5714..6a0289b817 100644 --- a/Dashboard/src/Pages/Settings/DangerZone.tsx +++ b/Dashboard/src/Pages/Settings/DangerZone.tsx @@ -1,71 +1,67 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import DashboardSideMenu from './SideMenu'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Page from 'CommonUI/src/Components/Page/Page'; -import PermissionUtil from 'CommonUI/src/Utils/Permission'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Project from 'Model/Models/Project'; -import React, { FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import DashboardSideMenu from "./SideMenu"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import Alert, { AlertType } from "CommonUI/src/Components/Alerts/Alert"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Page from "CommonUI/src/Components/Page/Page"; +import PermissionUtil from "CommonUI/src/Utils/Permission"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Project from "Model/Models/Project"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps extends PageComponentProps { - onProjectDeleted: () => void; + onProjectDeleted: () => void; } const Settings: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Page - title={'Project Settings'} - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Settings', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS] as Route - ), - }, - { - title: 'Danger Zone', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_DANGERZONE] as Route - ), - }, - ]} - sideMenu={<DashboardSideMenu />} - > - <Alert - type={AlertType.DANGER} - strongTitle="DANGER ZONE" - title="Deleting your project will delete it permanently and there is no way to recover. " - /> + return ( + <Page + title={"Project Settings"} + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS] as Route, + ), + }, + { + title: "Danger Zone", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_DANGERZONE] as Route, + ), + }, + ]} + sideMenu={<DashboardSideMenu />} + > + <Alert + type={AlertType.DANGER} + strongTitle="DANGER ZONE" + title="Deleting your project will delete it permanently and there is no way to recover. " + /> - <ModelDelete - modelType={Project} - modelId={ - new ObjectID( - DashboardNavigation.getProjectId()?.toString() || '' - ) - } - onDeleteSuccess={() => { - ProjectUtil.clearCurrentProject(); - PermissionUtil.clearProjectPermissions(); - props.onProjectDeleted(); - }} - /> - </Page> - ); + <ModelDelete + modelType={Project} + modelId={ + new ObjectID(DashboardNavigation.getProjectId()?.toString() || "") + } + onDeleteSuccess={() => { + ProjectUtil.clearCurrentProject(); + PermissionUtil.clearProjectPermissions(); + props.onProjectDeleted(); + }} + /> + </Page> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/Settings/Domains.tsx b/Dashboard/src/Pages/Settings/Domains.tsx index 75f9340a32..e9f9d78e6b 100644 --- a/Dashboard/src/Pages/Settings/Domains.tsx +++ b/Dashboard/src/Pages/Settings/Domains.tsx @@ -1,217 +1,213 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Domain from 'Model/Models/Domain'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Domain from "Model/Models/Domain"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const Domains: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [showVerificationModal, setShowVerificationModal] = - useState<boolean>(false); - const [error, setError] = useState<string>(''); - const [currentVerificationDomain, setCurrentVerificationDomain] = - useState<Domain | null>(null); - const [refreshToggle, setRefreshToggle] = useState<boolean>(false); - const [isVerificationLoading, setIsVerificationLoading] = - useState<boolean>(false); + const [showVerificationModal, setShowVerificationModal] = + useState<boolean>(false); + const [error, setError] = useState<string>(""); + const [currentVerificationDomain, setCurrentVerificationDomain] = + useState<Domain | null>(null); + const [refreshToggle, setRefreshToggle] = useState<boolean>(false); + const [isVerificationLoading, setIsVerificationLoading] = + useState<boolean>(false); - useEffect(() => { - setError(''); - }, [showVerificationModal]); + useEffect(() => { + setError(""); + }, [showVerificationModal]); - return ( - <Fragment> - <ModelTable<Domain> - modelType={Domain} - showViewIdButton={true} - name="Settings > Domain" - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - id="domains-table" - isDeleteable={true} - isEditable={false} - isCreateable={true} - cardProps={{ - title: 'Domains', - description: - 'Please list the domains you own here. This will help you to connect them to Status Page.', - }} - refreshToggle={refreshToggle} - noItemsMessage={'No domains found.'} - viewPageRoute={Navigation.getCurrentRoute()} - actionButtons={[ - { - title: 'Verify Domain', - buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, - icon: IconProp.Check, - isVisible: (item: Domain): boolean => { - if (item['isVerified']) { - return false; - } + return ( + <Fragment> + <ModelTable<Domain> + modelType={Domain} + showViewIdButton={true} + name="Settings > Domain" + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + id="domains-table" + isDeleteable={true} + isEditable={false} + isCreateable={true} + cardProps={{ + title: "Domains", + description: + "Please list the domains you own here. This will help you to connect them to Status Page.", + }} + refreshToggle={refreshToggle} + noItemsMessage={"No domains found."} + viewPageRoute={Navigation.getCurrentRoute()} + actionButtons={[ + { + title: "Verify Domain", + buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, + icon: IconProp.Check, + isVisible: (item: Domain): boolean => { + if (item["isVerified"]) { + return false; + } - return true; - }, - onClick: async ( - item: Domain, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentVerificationDomain(item); - setShowVerificationModal(true); + return true; + }, + onClick: async ( + item: Domain, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentVerificationDomain(item); + setShowVerificationModal(true); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - formFields={[ - { - field: { - domain: true, - }, - title: 'Domain', - fieldType: FormFieldSchemaType.Domain, - required: true, - placeholder: 'acme-inc.com', - validation: { - minLength: 2, - }, - }, - ]} - selectMoreFields={{ - domainVerificationText: true, - }} - showRefreshButton={true} - filters={[ - { - field: { - domain: true, - }, - type: FieldType.Text, - title: 'Domain', - }, - { - field: { - isVerified: true, - }, - title: 'Verified', - type: FieldType.Boolean, - }, - ]} - columns={[ - { - field: { - domain: true, - }, - title: 'Domain', - type: FieldType.Text, - }, - { - field: { - isVerified: true, - }, - title: 'Verified', - type: FieldType.Boolean, - }, - ]} - /> - {showVerificationModal && currentVerificationDomain ? ( - <ConfirmModal - title={`Verify ${currentVerificationDomain['domain']}`} - error={error} - description={ - <div> - <span> - Please add TXT record to your domain. Details of - the TXT records are: - </span> - <br /> - <br /> - <span> - <b>Record Type: </b> TXT - </span> - <br /> - <span> - <b>Name: </b> @ or{' '} - {currentVerificationDomain[ - 'domain' - ]?.toString()} - </span> - <br /> - <span> - <b>Content: </b> - {(currentVerificationDomain[ - 'domainVerificationText' - ] as string) || ''} - </span> - <br /> - <br /> - <span> - Please note: Some domain changes might take 72 - hours to propagate. - </span> - </div> - } - submitButtonText={'Verify Domain'} - onClose={() => { - setShowVerificationModal(false); - setError(''); - }} - isLoading={isVerificationLoading} - onSubmit={async () => { - try { - setIsVerificationLoading(true); - setError(''); - // verify domain. - await ModelAPI.updateById({ - modelType: Domain, - id: new ObjectID( - currentVerificationDomain['_id'] - ? currentVerificationDomain[ - '_id' - ].toString() - : '' - ), - data: { - isVerified: true, - }, - }); - setIsVerificationLoading(false); - setShowVerificationModal(false); - setRefreshToggle(!refreshToggle); - } catch (err) { - setError(API.getFriendlyMessage(err)); - setIsVerificationLoading(false); - } - }} - /> - ) : ( - <></> - )} - </Fragment> - ); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + formFields={[ + { + field: { + domain: true, + }, + title: "Domain", + fieldType: FormFieldSchemaType.Domain, + required: true, + placeholder: "acme-inc.com", + validation: { + minLength: 2, + }, + }, + ]} + selectMoreFields={{ + domainVerificationText: true, + }} + showRefreshButton={true} + filters={[ + { + field: { + domain: true, + }, + type: FieldType.Text, + title: "Domain", + }, + { + field: { + isVerified: true, + }, + title: "Verified", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + domain: true, + }, + title: "Domain", + type: FieldType.Text, + }, + { + field: { + isVerified: true, + }, + title: "Verified", + type: FieldType.Boolean, + }, + ]} + /> + {showVerificationModal && currentVerificationDomain ? ( + <ConfirmModal + title={`Verify ${currentVerificationDomain["domain"]}`} + error={error} + description={ + <div> + <span> + Please add TXT record to your domain. Details of the TXT records + are: + </span> + <br /> + <br /> + <span> + <b>Record Type: </b> TXT + </span> + <br /> + <span> + <b>Name: </b> @ or{" "} + {currentVerificationDomain["domain"]?.toString()} + </span> + <br /> + <span> + <b>Content: </b> + {(currentVerificationDomain[ + "domainVerificationText" + ] as string) || ""} + </span> + <br /> + <br /> + <span> + Please note: Some domain changes might take 72 hours to + propagate. + </span> + </div> + } + submitButtonText={"Verify Domain"} + onClose={() => { + setShowVerificationModal(false); + setError(""); + }} + isLoading={isVerificationLoading} + onSubmit={async () => { + try { + setIsVerificationLoading(true); + setError(""); + // verify domain. + await ModelAPI.updateById({ + modelType: Domain, + id: new ObjectID( + currentVerificationDomain["_id"] + ? currentVerificationDomain["_id"].toString() + : "", + ), + data: { + isVerified: true, + }, + }); + setIsVerificationLoading(false); + setShowVerificationModal(false); + setRefreshToggle(!refreshToggle); + } catch (err) { + setError(API.getFriendlyMessage(err)); + setIsVerificationLoading(false); + } + }} + /> + ) : ( + <></> + )} + </Fragment> + ); }; export default Domains; diff --git a/Dashboard/src/Pages/Settings/EmailLog.tsx b/Dashboard/src/Pages/Settings/EmailLog.tsx index 50dff62a3f..aca9b47c4e 100644 --- a/Dashboard/src/Pages/Settings/EmailLog.tsx +++ b/Dashboard/src/Pages/Settings/EmailLog.tsx @@ -1,224 +1,217 @@ -import CustomSMTPElement from '../../Components/CustomSMTP/CustomSMTPView'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import { Green, Red } from 'Common/Types/BrandColors'; -import IconProp from 'Common/Types/Icon/IconProp'; -import EmailStatus from 'Common/Types/Mail/MailStatus'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import Filter from 'CommonUI/src/Components/ModelFilter/Filter'; -import Columns from 'CommonUI/src/Components/ModelTable/Columns'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import EmailLog from 'Model/Models/EmailLog'; -import ProjectSmtpConfig from 'Model/Models/ProjectSmtpConfig'; +import CustomSMTPElement from "../../Components/CustomSMTP/CustomSMTPView"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import { Green, Red } from "Common/Types/BrandColors"; +import IconProp from "Common/Types/Icon/IconProp"; +import EmailStatus from "Common/Types/Mail/MailStatus"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import Filter from "CommonUI/src/Components/ModelFilter/Filter"; +import Columns from "CommonUI/src/Components/ModelTable/Columns"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import EmailLog from "Model/Models/EmailLog"; +import ProjectSmtpConfig from "Model/Models/ProjectSmtpConfig"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const EmailLogs: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [showViewEmailTextModal, setShowViewEmailTextModal] = - useState<boolean>(false); - const [EmailText, setEmailText] = useState<string>(''); - const [EmailModelTitle, setEmailModalTitle] = useState<string>(''); + const [showViewEmailTextModal, setShowViewEmailTextModal] = + useState<boolean>(false); + const [EmailText, setEmailText] = useState<string>(""); + const [EmailModelTitle, setEmailModalTitle] = useState<string>(""); - const filters: Array<Filter<EmailLog>> = [ - { - field: { - _id: true, - }, - title: 'Log ID', - type: FieldType.ObjectID, + const filters: Array<Filter<EmailLog>> = [ + { + field: { + _id: true, + }, + title: "Log ID", + type: FieldType.ObjectID, + }, + { + field: { + fromEmail: true, + }, + title: "From Email", + type: FieldType.Email, + }, + { + field: { + toEmail: true, + }, + title: "To Email", + type: FieldType.Email, + }, + { + field: { + createdAt: true, + }, + title: "Sent at", + type: FieldType.Date, + }, + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Dropdown, + filterDropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(EmailStatus), + }, + ]; + + const modelTableColumns: Columns<EmailLog> = [ + { + field: { + projectSmtpConfig: { + name: true, }, - { - field: { - fromEmail: true, - }, - title: 'From Email', - type: FieldType.Email, - }, - { - field: { - toEmail: true, - }, - title: 'To Email', - type: FieldType.Email, - }, - { - field: { - createdAt: true, - }, - title: 'Sent at', - type: FieldType.Date, - }, - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Dropdown, - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum(EmailStatus), - }, - ]; + }, + title: "SMTP Server", + type: FieldType.Element, + getElement: (item: EmailLog): ReactElement => { + return ( + <CustomSMTPElement + smtp={item["projectSmtpConfig"] as ProjectSmtpConfig} + /> + ); + }, + }, + { + field: { + fromEmail: true, + }, - const modelTableColumns: Columns<EmailLog> = [ - { - field: { - projectSmtpConfig: { - name: true, - }, + title: "From Email", + type: FieldType.Email, + }, + + { + field: { + toEmail: true, + }, + + title: "To Email", + type: FieldType.Email, + }, + { + field: { + createdAt: true, + }, + title: "Sent at", + type: FieldType.DateTime, + }, + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Text, + getElement: (item: EmailLog): ReactElement => { + if (item["status"]) { + return ( + <Pill + isMinimal={false} + color={item["status"] === EmailStatus.Success ? Green : Red} + text={item["status"] as string} + /> + ); + } + + return <></>; + }, + }, + ]; + + return ( + <Fragment> + <> + <ModelTable<EmailLog> + modelType={EmailLog} + id="Email-logs-table" + isDeleteable={false} + isEditable={false} + isCreateable={false} + showViewIdButton={true} + name="Email Logs" + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + selectMoreFields={{ + subject: true, + statusMessage: true, + }} + actionButtons={[ + { + title: "View Subject", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.List, + onClick: async ( + item: EmailLog, + onCompleteAction: VoidFunction, + ) => { + setEmailText(JSON.stringify(item["subject"]) as string); + + setEmailModalTitle("Subject of Email Message"); + setShowViewEmailTextModal(true); + + onCompleteAction(); + }, }, - title: 'SMTP Server', - type: FieldType.Element, - getElement: (item: EmailLog): ReactElement => { - return ( - <CustomSMTPElement - smtp={item['projectSmtpConfig'] as ProjectSmtpConfig} - /> - ); - }, - }, - { - field: { - fromEmail: true, + { + title: "View Status Message", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.Error, + onClick: async ( + item: EmailLog, + onCompleteAction: VoidFunction, + ) => { + setEmailText(item["statusMessage"] as string); + + setEmailModalTitle("Status Message"); + setShowViewEmailTextModal(true); + + onCompleteAction(); + }, }, + ]} + filters={filters} + isViewable={false} + cardProps={{ + title: "Email Logs", + description: + "Logs of all the emails sent by this project in the last 30 days.", + }} + noItemsMessage={ + "Looks like no email is sent by this project in the last 30 days." + } + showRefreshButton={true} + columns={modelTableColumns} + /> - title: 'From Email', - type: FieldType.Email, - }, - - { - field: { - toEmail: true, - }, - - title: 'To Email', - type: FieldType.Email, - }, - { - field: { - createdAt: true, - }, - title: 'Sent at', - type: FieldType.DateTime, - }, - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Text, - getElement: (item: EmailLog): ReactElement => { - if (item['status']) { - return ( - <Pill - isMinimal={false} - color={ - item['status'] === EmailStatus.Success - ? Green - : Red - } - text={item['status'] as string} - /> - ); - } - - return <></>; - }, - }, - ]; - - return ( - <Fragment> - <> - <ModelTable<EmailLog> - modelType={EmailLog} - id="Email-logs-table" - isDeleteable={false} - isEditable={false} - isCreateable={false} - showViewIdButton={true} - name="Email Logs" - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - }} - selectMoreFields={{ - subject: true, - statusMessage: true, - }} - actionButtons={[ - { - title: 'View Subject', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.List, - onClick: async ( - item: EmailLog, - onCompleteAction: VoidFunction - ) => { - setEmailText( - JSON.stringify(item['subject']) as string - ); - - setEmailModalTitle('Subject of Email Message'); - setShowViewEmailTextModal(true); - - onCompleteAction(); - }, - }, - { - title: 'View Status Message', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.Error, - onClick: async ( - item: EmailLog, - onCompleteAction: VoidFunction - ) => { - setEmailText(item['statusMessage'] as string); - - setEmailModalTitle('Status Message'); - setShowViewEmailTextModal(true); - - onCompleteAction(); - }, - }, - ]} - filters={filters} - isViewable={false} - cardProps={{ - title: 'Email Logs', - description: - 'Logs of all the emails sent by this project in the last 30 days.', - }} - noItemsMessage={ - 'Looks like no email is sent by this project in the last 30 days.' - } - showRefreshButton={true} - columns={modelTableColumns} - /> - - {showViewEmailTextModal && ( - <ConfirmModal - title={EmailModelTitle} - description={EmailText} - onSubmit={() => { - setShowViewEmailTextModal(false); - }} - submitButtonText="Close" - submitButtonType={ButtonStyleType.NORMAL} - /> - )} - </> - </Fragment> - ); + {showViewEmailTextModal && ( + <ConfirmModal + title={EmailModelTitle} + description={EmailText} + onSubmit={() => { + setShowViewEmailTextModal(false); + }} + submitButtonText="Close" + submitButtonType={ButtonStyleType.NORMAL} + /> + )} + </> + </Fragment> + ); }; export default EmailLogs; diff --git a/Dashboard/src/Pages/Settings/FeatureFlags.tsx b/Dashboard/src/Pages/Settings/FeatureFlags.tsx index 84c63d96f0..feff013e7e 100644 --- a/Dashboard/src/Pages/Settings/FeatureFlags.tsx +++ b/Dashboard/src/Pages/Settings/FeatureFlags.tsx @@ -1,62 +1,62 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Project from 'Model/Models/Project'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Project from "Model/Models/Project"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - {/* Project Settings View */} - <CardModelDetail - name="Feature Flags" - cardProps={{ - title: 'Feature Flags', - description: - 'Feature flags allow you to toggle features on and off for your project.', - }} - isEditable={true} - editButtonText="Edit Feature Flags" - formFields={[ - { - field: { - isFeatureFlagMonitorGroupsEnabled: true, - }, - title: 'Enable Monitor Groups', - fieldType: FormFieldSchemaType.Toggle, - required: false, - description: - 'Monitor Groups allow you to group monitors together and view them as a group and allows you to add these to your status page.', - }, - ]} - onSaveSuccess={() => { - Navigation.reload(); - }} - modelDetailProps={{ - modelType: Project, - id: 'model-detail-project', - fields: [ - { - field: { - isFeatureFlagMonitorGroupsEnabled: true, - }, - fieldType: FieldType.Boolean, - title: 'Monitor Groups Enabled', - description: - 'Monitor Groups allow you to group monitors together and view them as a group and allows you to add these to your status page.', - placeholder: 'No', - }, - ], - modelId: DashboardNavigation.getProjectId()!, - }} - /> - </Fragment> - ); + return ( + <Fragment> + {/* Project Settings View */} + <CardModelDetail + name="Feature Flags" + cardProps={{ + title: "Feature Flags", + description: + "Feature flags allow you to toggle features on and off for your project.", + }} + isEditable={true} + editButtonText="Edit Feature Flags" + formFields={[ + { + field: { + isFeatureFlagMonitorGroupsEnabled: true, + }, + title: "Enable Monitor Groups", + fieldType: FormFieldSchemaType.Toggle, + required: false, + description: + "Monitor Groups allow you to group monitors together and view them as a group and allows you to add these to your status page.", + }, + ]} + onSaveSuccess={() => { + Navigation.reload(); + }} + modelDetailProps={{ + modelType: Project, + id: "model-detail-project", + fields: [ + { + field: { + isFeatureFlagMonitorGroupsEnabled: true, + }, + fieldType: FieldType.Boolean, + title: "Monitor Groups Enabled", + description: + "Monitor Groups allow you to group monitors together and view them as a group and allows you to add these to your status page.", + placeholder: "No", + }, + ], + modelId: DashboardNavigation.getProjectId()!, + }} + /> + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/Settings/IncidentCustomFields.tsx b/Dashboard/src/Pages/Settings/IncidentCustomFields.tsx index ee16131920..2631cd6335 100644 --- a/Dashboard/src/Pages/Settings/IncidentCustomFields.tsx +++ b/Dashboard/src/Pages/Settings/IncidentCustomFields.tsx @@ -1,18 +1,18 @@ -import PageComponentProps from '../PageComponentProps'; -import CustomFieldsPageBase from './Base/CustomFieldsPageBase'; -import IncidentCustomField from 'Model/Models/IncidentCustomField'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import CustomFieldsPageBase from "./Base/CustomFieldsPageBase"; +import IncidentCustomField from "Model/Models/IncidentCustomField"; +import React, { FunctionComponent, ReactElement } from "react"; const IncidentCustomFields: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <CustomFieldsPageBase - {...props} - title="Incident Custom Fields" - modelType={IncidentCustomField} - /> - ); + return ( + <CustomFieldsPageBase + {...props} + title="Incident Custom Fields" + modelType={IncidentCustomField} + /> + ); }; export default IncidentCustomFields; diff --git a/Dashboard/src/Pages/Settings/IncidentNoteTemplateView.tsx b/Dashboard/src/Pages/Settings/IncidentNoteTemplateView.tsx index 53908ead70..0857b52aad 100644 --- a/Dashboard/src/Pages/Settings/IncidentNoteTemplateView.tsx +++ b/Dashboard/src/Pages/Settings/IncidentNoteTemplateView.tsx @@ -1,141 +1,138 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentNoteTemplate from 'Model/Models/IncidentNoteTemplate'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentNoteTemplate from "Model/Models/IncidentNoteTemplate"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const TeamView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - {/* Incident View */} - <CardModelDetail - name="Basic Details" - cardProps={{ - title: 'Basic Details', - description: - 'Here are more details for this incident template.', - }} - isEditable={true} - editButtonText="Edit Details" - formFields={[ - { - field: { - templateName: true, - }, - title: 'Template Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Template Name', - validation: { - minLength: 2, - }, - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Template Description', - validation: { - minLength: 2, - }, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: IncidentNoteTemplate, - id: 'model-detail-incidents', - fields: [ - { - field: { - _id: true, - }, - title: 'Incident Note Template ID', - fieldType: FieldType.ObjectID, - }, - { - field: { - templateName: true, - }, - title: 'Template Name', - fieldType: FieldType.Text, - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - fieldType: FieldType.Text, - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + {/* Incident View */} + <CardModelDetail + name="Basic Details" + cardProps={{ + title: "Basic Details", + description: "Here are more details for this incident template.", + }} + isEditable={true} + editButtonText="Edit Details" + formFields={[ + { + field: { + templateName: true, + }, + title: "Template Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Template Name", + validation: { + minLength: 2, + }, + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Template Description", + validation: { + minLength: 2, + }, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: IncidentNoteTemplate, + id: "model-detail-incidents", + fields: [ + { + field: { + _id: true, + }, + title: "Incident Note Template ID", + fieldType: FieldType.ObjectID, + }, + { + field: { + templateName: true, + }, + title: "Template Name", + fieldType: FieldType.Text, + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + fieldType: FieldType.Text, + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail - name="Note Template" - editButtonText="Edit Note Template" - cardProps={{ - title: 'Note Template', - description: 'Here is the note template.', - }} - isEditable={true} - formFields={[ - { - field: { - note: true, - }, - title: 'Note', - fieldType: FormFieldSchemaType.Markdown, - required: true, - validation: { - minLength: 2, - }, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: IncidentNoteTemplate, - id: 'model-detail-incidents', - fields: [ - { - field: { - note: true, - }, - title: 'Note Template', - fieldType: FieldType.Markdown, - }, - ], - modelId: modelId, - }} - /> + <CardModelDetail + name="Note Template" + editButtonText="Edit Note Template" + cardProps={{ + title: "Note Template", + description: "Here is the note template.", + }} + isEditable={true} + formFields={[ + { + field: { + note: true, + }, + title: "Note", + fieldType: FormFieldSchemaType.Markdown, + required: true, + validation: { + minLength: 2, + }, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: IncidentNoteTemplate, + id: "model-detail-incidents", + fields: [ + { + field: { + note: true, + }, + title: "Note Template", + fieldType: FieldType.Markdown, + }, + ], + modelId: modelId, + }} + /> - <ModelDelete - modelType={IncidentNoteTemplate} - modelId={Navigation.getLastParamAsObjectID()} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[ - PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES - ] as Route - ); - }} - /> - </Fragment> - ); + <ModelDelete + modelType={IncidentNoteTemplate} + modelId={Navigation.getLastParamAsObjectID()} + onDeleteSuccess={() => { + Navigation.navigate( + RouteMap[PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES] as Route, + ); + }} + /> + </Fragment> + ); }; export default TeamView; diff --git a/Dashboard/src/Pages/Settings/IncidentNoteTemplates.tsx b/Dashboard/src/Pages/Settings/IncidentNoteTemplates.tsx index 9376065547..ed362905ad 100644 --- a/Dashboard/src/Pages/Settings/IncidentNoteTemplates.tsx +++ b/Dashboard/src/Pages/Settings/IncidentNoteTemplates.tsx @@ -1,122 +1,122 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import IncidentNoteTemplate from 'Model/Models/IncidentNoteTemplate'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import IncidentNoteTemplate from "Model/Models/IncidentNoteTemplate"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const IncidentNoteTemplates: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<IncidentNoteTemplate> - modelType={IncidentNoteTemplate} - id="incident-templates-table" - name="Settings > Incident Templates" - isDeleteable={false} - isEditable={false} - isCreateable={true} - isViewable={true} - cardProps={{ - title: 'Public or Private Note Templates for Incidents', - description: - 'Here is a list of all the public and private note templates for incidents.', - }} - noItemsMessage={'No note templates found.'} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - showViewIdButton={true} - formSteps={[ - { - title: 'Template Info', - id: 'template-info', - }, - { - title: 'Note Details', - id: 'note-details', - }, - ]} - formFields={[ - { - field: { - templateName: true, - }, - title: 'Template Name', - fieldType: FormFieldSchemaType.Text, - stepId: 'template-info', - required: true, - placeholder: 'Template Name', - validation: { - minLength: 2, - }, - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - fieldType: FormFieldSchemaType.LongText, - stepId: 'template-info', - required: true, - placeholder: 'Template Description', - validation: { - minLength: 2, - }, - }, - { - field: { - note: true, - }, - title: 'Public or Private note template.', - fieldType: FormFieldSchemaType.Markdown, - stepId: 'note-details', - required: true, - validation: { - minLength: 2, - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} - filters={[ - { - field: { - templateName: true, - }, - type: FieldType.Text, - title: 'Template Name', - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - templateName: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - templateDescription: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<IncidentNoteTemplate> + modelType={IncidentNoteTemplate} + id="incident-templates-table" + name="Settings > Incident Templates" + isDeleteable={false} + isEditable={false} + isCreateable={true} + isViewable={true} + cardProps={{ + title: "Public or Private Note Templates for Incidents", + description: + "Here is a list of all the public and private note templates for incidents.", + }} + noItemsMessage={"No note templates found."} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + showViewIdButton={true} + formSteps={[ + { + title: "Template Info", + id: "template-info", + }, + { + title: "Note Details", + id: "note-details", + }, + ]} + formFields={[ + { + field: { + templateName: true, + }, + title: "Template Name", + fieldType: FormFieldSchemaType.Text, + stepId: "template-info", + required: true, + placeholder: "Template Name", + validation: { + minLength: 2, + }, + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + fieldType: FormFieldSchemaType.LongText, + stepId: "template-info", + required: true, + placeholder: "Template Description", + validation: { + minLength: 2, + }, + }, + { + field: { + note: true, + }, + title: "Public or Private note template.", + fieldType: FormFieldSchemaType.Markdown, + stepId: "note-details", + required: true, + validation: { + minLength: 2, + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} + filters={[ + { + field: { + templateName: true, + }, + type: FieldType.Text, + title: "Template Name", + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + templateName: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + templateDescription: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + /> + </Fragment> + ); }; export default IncidentNoteTemplates; diff --git a/Dashboard/src/Pages/Settings/IncidentSeverity.tsx b/Dashboard/src/Pages/Settings/IncidentSeverity.tsx index b5bfb4cf8d..7339066407 100644 --- a/Dashboard/src/Pages/Settings/IncidentSeverity.tsx +++ b/Dashboard/src/Pages/Settings/IncidentSeverity.tsx @@ -1,122 +1,121 @@ -import PageComponentProps from '../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import Color from 'Common/Types/Color'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import { ShowAs } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import Color from "Common/Types/Color"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import { ShowAs } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const IncidentSeverityPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<IncidentSeverity> - modelType={IncidentSeverity} - id="incident-state-table" - name="Settings > Incident Severity" - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Incident Severity', - description: - 'Alerts and incidents will be categorised according to their severity level using the following classifications: ', - }} - sortBy="order" - sortOrder={SortOrder.Ascending} - selectMoreFields={{ - color: true, - order: true, - }} - filters={[]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - getElement: (item: IncidentSeverity): ReactElement => { - return ( - <Pill - isMinimal={true} - color={item['color'] as Color} - text={item['name'] as string} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, + return ( + <Fragment> + <ModelTable<IncidentSeverity> + modelType={IncidentSeverity} + id="incident-state-table" + name="Settings > Incident Severity" + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Incident Severity", + description: + "Alerts and incidents will be categorised according to their severity level using the following classifications: ", + }} + sortBy="order" + sortOrder={SortOrder.Ascending} + selectMoreFields={{ + color: true, + order: true, + }} + filters={[]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + getElement: (item: IncidentSeverity): ReactElement => { + return ( + <Pill + isMinimal={true} + color={item["color"] as Color} + text={item["name"] as string} + /> + ); + }, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, - getElement: (item: IncidentSeverity): ReactElement => { - return ( - <div> - <p>{`${item['description']}`}</p> - <p className="text-xs text-gray-400"> - ID: {`${item['_id']}`} - </p> - </div> - ); - }, - }, - ]} - noItemsMessage={'No incident severity found.'} - viewPageRoute={Navigation.getCurrentRoute()} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Investigating', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: - 'This incident state happens when the incident is investigated', - }, - { - field: { - color: true, - }, - title: 'Color', - fieldType: FormFieldSchemaType.Color, - required: true, - placeholder: - 'Please select color for this incident state.', - }, - ]} - showRefreshButton={true} - showAs={ShowAs.OrderedStatesList} - orderedStatesListProps={{ - titleField: 'name', - descriptionField: 'description', - orderField: 'order', - shouldAddItemInTheEnd: true, - }} - /> - </Fragment> - ); + getElement: (item: IncidentSeverity): ReactElement => { + return ( + <div> + <p>{`${item["description"]}`}</p> + <p className="text-xs text-gray-400"> + ID: {`${item["_id"]}`} + </p> + </div> + ); + }, + }, + ]} + noItemsMessage={"No incident severity found."} + viewPageRoute={Navigation.getCurrentRoute()} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Investigating", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: + "This incident state happens when the incident is investigated", + }, + { + field: { + color: true, + }, + title: "Color", + fieldType: FormFieldSchemaType.Color, + required: true, + placeholder: "Please select color for this incident state.", + }, + ]} + showRefreshButton={true} + showAs={ShowAs.OrderedStatesList} + orderedStatesListProps={{ + titleField: "name", + descriptionField: "description", + orderField: "order", + shouldAddItemInTheEnd: true, + }} + /> + </Fragment> + ); }; export default IncidentSeverityPage; diff --git a/Dashboard/src/Pages/Settings/IncidentState.tsx b/Dashboard/src/Pages/Settings/IncidentState.tsx index ea0216b4c9..7ff92b3946 100644 --- a/Dashboard/src/Pages/Settings/IncidentState.tsx +++ b/Dashboard/src/Pages/Settings/IncidentState.tsx @@ -1,149 +1,146 @@ -import PageComponentProps from '../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import Color from 'Common/Types/Color'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import { ShowAs } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentState from 'Model/Models/IncidentState'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import Color from "Common/Types/Color"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import { ShowAs } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentState from "Model/Models/IncidentState"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const IncidentsPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<IncidentState> - modelType={IncidentState} - id="incident-state-table" - name="Settings > Incident State" - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Incident State', - description: - 'Incidents have multiple states like - created, acknowledged and resolved. You can more states help you manage incidents here.', - }} - sortBy="order" - sortOrder={SortOrder.Ascending} - onBeforeDelete={( - item: IncidentState - ): Promise<IncidentState> => { - if (item.isCreatedState) { - throw new BadDataException( - 'This incident cannot be deleted because its the created incident state of for this project. Created, Acknowledged, Resolved incident states cannot be deleted.' - ); - } + return ( + <Fragment> + <ModelTable<IncidentState> + modelType={IncidentState} + id="incident-state-table" + name="Settings > Incident State" + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Incident State", + description: + "Incidents have multiple states like - created, acknowledged and resolved. You can more states help you manage incidents here.", + }} + sortBy="order" + sortOrder={SortOrder.Ascending} + onBeforeDelete={(item: IncidentState): Promise<IncidentState> => { + if (item.isCreatedState) { + throw new BadDataException( + "This incident cannot be deleted because its the created incident state of for this project. Created, Acknowledged, Resolved incident states cannot be deleted.", + ); + } - if (item.isAcknowledgedState) { - throw new BadDataException( - 'This incident cannot be deleted because its the acknowledged incident state of for this project. Created, Acknowledged, Resolved incident states cannot be deleted.' - ); - } + if (item.isAcknowledgedState) { + throw new BadDataException( + "This incident cannot be deleted because its the acknowledged incident state of for this project. Created, Acknowledged, Resolved incident states cannot be deleted.", + ); + } - if (item.isResolvedState) { - throw new BadDataException( - 'This incident cannot be deleted because its the resolved incident state of for this project. Created, Acknowledged, Resolved incident states cannot be deleted.' - ); - } + if (item.isResolvedState) { + throw new BadDataException( + "This incident cannot be deleted because its the resolved incident state of for this project. Created, Acknowledged, Resolved incident states cannot be deleted.", + ); + } - return Promise.resolve(item); - }} - selectMoreFields={{ - color: true, - isCreatedState: true, - isAcknowledgedState: true, - isResolvedState: true, - order: true, - }} - filters={[]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - getElement: (item: IncidentState): ReactElement => { - return ( - <Pill - isMinimal={true} - color={item['color'] as Color} - text={item['name'] as string} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, + return Promise.resolve(item); + }} + selectMoreFields={{ + color: true, + isCreatedState: true, + isAcknowledgedState: true, + isResolvedState: true, + order: true, + }} + filters={[]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + getElement: (item: IncidentState): ReactElement => { + return ( + <Pill + isMinimal={true} + color={item["color"] as Color} + text={item["name"] as string} + /> + ); + }, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, - getElement: (item: IncidentState): ReactElement => { - return ( - <div> - <p>{`${item['description']}`}</p> - <p className="text-xs text-gray-400"> - ID: {`${item['_id']}`} - </p> - </div> - ); - }, - }, - ]} - noItemsMessage={'No incident state found.'} - viewPageRoute={Navigation.getCurrentRoute()} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Investigating', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: - 'This incident state happens when the incident is investigated', - }, - { - field: { - color: true, - }, - title: 'Color', - fieldType: FormFieldSchemaType.Color, - required: true, - placeholder: - 'Please select color for this incident state.', - }, - ]} - showRefreshButton={true} - showAs={ShowAs.OrderedStatesList} - orderedStatesListProps={{ - titleField: 'name', - descriptionField: 'description', - orderField: 'order', - shouldAddItemInTheEnd: true, - }} - /> - </Fragment> - ); + getElement: (item: IncidentState): ReactElement => { + return ( + <div> + <p>{`${item["description"]}`}</p> + <p className="text-xs text-gray-400"> + ID: {`${item["_id"]}`} + </p> + </div> + ); + }, + }, + ]} + noItemsMessage={"No incident state found."} + viewPageRoute={Navigation.getCurrentRoute()} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Investigating", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: + "This incident state happens when the incident is investigated", + }, + { + field: { + color: true, + }, + title: "Color", + fieldType: FormFieldSchemaType.Color, + required: true, + placeholder: "Please select color for this incident state.", + }, + ]} + showRefreshButton={true} + showAs={ShowAs.OrderedStatesList} + orderedStatesListProps={{ + titleField: "name", + descriptionField: "description", + orderField: "order", + shouldAddItemInTheEnd: true, + }} + /> + </Fragment> + ); }; export default IncidentsPage; diff --git a/Dashboard/src/Pages/Settings/IncidentTemplates.tsx b/Dashboard/src/Pages/Settings/IncidentTemplates.tsx index 1d0ececee2..9ef0efac9a 100644 --- a/Dashboard/src/Pages/Settings/IncidentTemplates.tsx +++ b/Dashboard/src/Pages/Settings/IncidentTemplates.tsx @@ -1,278 +1,277 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import ProjectUser from '../../Utils/ProjectUser'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import IncidentTemplate from 'Model/Models/IncidentTemplate'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import Team from 'Model/Models/Team'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import ProjectUser from "../../Utils/ProjectUser"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import IncidentTemplate from "Model/Models/IncidentTemplate"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import Team from "Model/Models/Team"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const IncidentTemplates: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<IncidentTemplate> - modelType={IncidentTemplate} - id="incident-templates-table" - name="Settings > Incident Templates" - isDeleteable={false} - isEditable={false} - isCreateable={true} - isViewable={true} - cardProps={{ - title: 'Incident Templates', - description: - 'Here is a list of all the incident templates in this project.', - }} - noItemsMessage={'No incident templates found.'} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - showViewIdButton={true} - formSteps={[ - { - title: 'Template Info', - id: 'template-info', - }, - { - title: 'Incident Details', - id: 'incident-details', - }, - { - title: 'Resources Affected', - id: 'resources-affected', - }, - { - title: 'On-Call', - id: 'on-call', - }, - { - title: 'Owners', - id: 'owners', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - formFields={[ - { - field: { - templateName: true, - }, - title: 'Template Name', - fieldType: FormFieldSchemaType.Text, - stepId: 'template-info', - required: true, - placeholder: 'Template Name', - validation: { - minLength: 2, - }, - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - fieldType: FormFieldSchemaType.LongText, - stepId: 'template-info', - required: true, - placeholder: 'Template Description', - validation: { - minLength: 2, - }, - }, - { - field: { - title: true, - }, - title: 'Title', - fieldType: FormFieldSchemaType.Text, - stepId: 'incident-details', - required: true, - placeholder: 'Incident Title', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - stepId: 'incident-details', - fieldType: FormFieldSchemaType.Markdown, - required: true, - }, - { - field: { - incidentSeverity: true, - }, - title: 'Incident Severity', - stepId: 'incident-details', - description: 'What type of incident is this?', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: IncidentSeverity, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Incident Severity', - }, - { - field: { - monitors: true, - }, - title: 'Monitors affected', - stepId: 'resources-affected', - description: - 'Select monitors affected by this incident.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Monitor, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Monitors affected', - }, - { - field: { - onCallDutyPolicies: true, - }, - title: 'On-Call Policy', - stepId: 'on-call', - description: - 'Select on-call duty policy to execute when this incident is created.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: OnCallDutyPolicy, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select on-call policies', - }, - { - field: { - changeMonitorStatusTo: true, - }, - title: 'Change Monitor Status to ', - stepId: 'resources-affected', - description: - 'This will change the status of all the monitors attached to this incident.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: MonitorStatus, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Monitor Status', - }, - { - overrideField: { - ownerTeams: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Owner - Teams', - stepId: 'owners', - description: - 'Select which teams own this incident. They will be notified when the incident is created or updated.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select Teams', - overrideFieldKey: 'ownerTeams', - }, - { - overrideField: { - ownerUsers: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Owner - Users', - stepId: 'owners', - description: - 'Select which users own this incident. They will be notified when the incident is created or updated.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - required: false, - placeholder: 'Select Users', - overrideFieldKey: 'ownerUsers', - }, - { - field: { - labels: true, - }, + return ( + <Fragment> + <ModelTable<IncidentTemplate> + modelType={IncidentTemplate} + id="incident-templates-table" + name="Settings > Incident Templates" + isDeleteable={false} + isEditable={false} + isCreateable={true} + isViewable={true} + cardProps={{ + title: "Incident Templates", + description: + "Here is a list of all the incident templates in this project.", + }} + noItemsMessage={"No incident templates found."} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + showViewIdButton={true} + formSteps={[ + { + title: "Template Info", + id: "template-info", + }, + { + title: "Incident Details", + id: "incident-details", + }, + { + title: "Resources Affected", + id: "resources-affected", + }, + { + title: "On-Call", + id: "on-call", + }, + { + title: "Owners", + id: "owners", + }, + { + title: "Labels", + id: "labels", + }, + ]} + formFields={[ + { + field: { + templateName: true, + }, + title: "Template Name", + fieldType: FormFieldSchemaType.Text, + stepId: "template-info", + required: true, + placeholder: "Template Name", + validation: { + minLength: 2, + }, + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + fieldType: FormFieldSchemaType.LongText, + stepId: "template-info", + required: true, + placeholder: "Template Description", + validation: { + minLength: 2, + }, + }, + { + field: { + title: true, + }, + title: "Title", + fieldType: FormFieldSchemaType.Text, + stepId: "incident-details", + required: true, + placeholder: "Incident Title", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + stepId: "incident-details", + fieldType: FormFieldSchemaType.Markdown, + required: true, + }, + { + field: { + incidentSeverity: true, + }, + title: "Incident Severity", + stepId: "incident-details", + description: "What type of incident is this?", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: IncidentSeverity, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Incident Severity", + }, + { + field: { + monitors: true, + }, + title: "Monitors affected", + stepId: "resources-affected", + description: "Select monitors affected by this incident.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Monitor, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Monitors affected", + }, + { + field: { + onCallDutyPolicies: true, + }, + title: "On-Call Policy", + stepId: "on-call", + description: + "Select on-call duty policy to execute when this incident is created.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: OnCallDutyPolicy, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select on-call policies", + }, + { + field: { + changeMonitorStatusTo: true, + }, + title: "Change Monitor Status to ", + stepId: "resources-affected", + description: + "This will change the status of all the monitors attached to this incident.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: MonitorStatus, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Monitor Status", + }, + { + overrideField: { + ownerTeams: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Owner - Teams", + stepId: "owners", + description: + "Select which teams own this incident. They will be notified when the incident is created or updated.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select Teams", + overrideFieldKey: "ownerTeams", + }, + { + overrideField: { + ownerUsers: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Owner - Users", + stepId: "owners", + description: + "Select which users own this incident. They will be notified when the incident is created or updated.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + required: false, + placeholder: "Select Users", + overrideFieldKey: "ownerUsers", + }, + { + field: { + labels: true, + }, - title: 'Labels ', - stepId: 'labels', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - showRefreshButton={true} - viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} - filters={[ - { - field: { - templateName: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - templateDescription: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - templateName: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - templateDescription: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - /> - </Fragment> - ); + title: "Labels ", + stepId: "labels", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + showRefreshButton={true} + viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} + filters={[ + { + field: { + templateName: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + templateDescription: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + templateName: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + templateDescription: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + /> + </Fragment> + ); }; export default IncidentTemplates; diff --git a/Dashboard/src/Pages/Settings/IncidentTemplatesView.tsx b/Dashboard/src/Pages/Settings/IncidentTemplatesView.tsx index fc85ceccef..88d93db4c9 100644 --- a/Dashboard/src/Pages/Settings/IncidentTemplatesView.tsx +++ b/Dashboard/src/Pages/Settings/IncidentTemplatesView.tsx @@ -1,532 +1,503 @@ -import LabelsElement from '../../Components/Label/Labels'; -import MonitorsElement from '../../Components/Monitor/Monitors'; -import OnCallDutyPoliciesView from '../../Components/OnCallPolicy/OnCallPolicies'; -import TeamElement from '../../Components/Team/Team'; -import UserElement from '../../Components/User/User'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import ProjectUser from '../../Utils/ProjectUser'; -import RouteMap from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import { Black } from 'Common/Types/BrandColors'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import IncidentTemplate from 'Model/Models/IncidentTemplate'; -import IncidentTemplateOwnerTeam from 'Model/Models/IncidentTemplateOwnerTeam'; -import IncidentTemplateOwnerUser from 'Model/Models/IncidentTemplateOwnerUser'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import Team from 'Model/Models/Team'; -import User from 'Model/Models/User'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import MonitorsElement from "../../Components/Monitor/Monitors"; +import OnCallDutyPoliciesView from "../../Components/OnCallPolicy/OnCallPolicies"; +import TeamElement from "../../Components/Team/Team"; +import UserElement from "../../Components/User/User"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import ProjectUser from "../../Utils/ProjectUser"; +import RouteMap from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import { Black } from "Common/Types/BrandColors"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import IncidentTemplate from "Model/Models/IncidentTemplate"; +import IncidentTemplateOwnerTeam from "Model/Models/IncidentTemplateOwnerTeam"; +import IncidentTemplateOwnerUser from "Model/Models/IncidentTemplateOwnerUser"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import Team from "Model/Models/Team"; +import User from "Model/Models/User"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const TeamView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - {/* Incident View */} - <CardModelDetail<IncidentTemplate> - name="Incident Template Details" - cardProps={{ - title: 'Incident Template Details', - description: - 'Here are more details for this incident template.', - }} - isEditable={true} - formSteps={[ - { - title: 'Template Info', - id: 'template-info', - }, - { - title: 'Incident Details', - id: 'incident-details', - }, - { - title: 'Resources Affected', - id: 'resources-affected', - }, - { - title: 'On-Call', - id: 'on-call', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - formFields={[ - { - field: { - templateName: true, - }, - title: 'Template Name', - fieldType: FormFieldSchemaType.Text, - stepId: 'template-info', - required: true, - placeholder: 'Template Name', - validation: { - minLength: 2, - }, - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - fieldType: FormFieldSchemaType.Text, - stepId: 'template-info', - required: true, - placeholder: 'Template Description', - validation: { - minLength: 2, - }, - }, - { - field: { - title: true, - }, - title: 'Title', - fieldType: FormFieldSchemaType.Text, - stepId: 'incident-details', - required: true, - placeholder: 'Incident Title', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - stepId: 'incident-details', - fieldType: FormFieldSchemaType.Markdown, - required: true, - }, - { - field: { - incidentSeverity: true, - }, - title: 'Incident Severity', - stepId: 'incident-details', - description: 'What type of incident is this?', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: IncidentSeverity, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Incident Severity', - }, - { - field: { - monitors: true, - }, - title: 'Monitors affected', - stepId: 'resources-affected', - description: - 'Select monitors affected by this incident.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Monitor, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Monitors affected', - }, - { - field: { - onCallDutyPolicies: true, - }, - title: 'On-Call Policy', - stepId: 'on-call', - description: - 'Select on-call duty policy to execute when this incident is created.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: OnCallDutyPolicy, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Select on-call policies', - }, - { - field: { - changeMonitorStatusTo: true, - }, - title: 'Change Monitor Status to ', - stepId: 'resources-affected', - description: - 'This will change the status of all the monitors attached to this incident.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: MonitorStatus, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Monitor Status', - }, - { - field: { - labels: true, - }, + return ( + <Fragment> + {/* Incident View */} + <CardModelDetail<IncidentTemplate> + name="Incident Template Details" + cardProps={{ + title: "Incident Template Details", + description: "Here are more details for this incident template.", + }} + isEditable={true} + formSteps={[ + { + title: "Template Info", + id: "template-info", + }, + { + title: "Incident Details", + id: "incident-details", + }, + { + title: "Resources Affected", + id: "resources-affected", + }, + { + title: "On-Call", + id: "on-call", + }, + { + title: "Labels", + id: "labels", + }, + ]} + formFields={[ + { + field: { + templateName: true, + }, + title: "Template Name", + fieldType: FormFieldSchemaType.Text, + stepId: "template-info", + required: true, + placeholder: "Template Name", + validation: { + minLength: 2, + }, + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + fieldType: FormFieldSchemaType.Text, + stepId: "template-info", + required: true, + placeholder: "Template Description", + validation: { + minLength: 2, + }, + }, + { + field: { + title: true, + }, + title: "Title", + fieldType: FormFieldSchemaType.Text, + stepId: "incident-details", + required: true, + placeholder: "Incident Title", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + stepId: "incident-details", + fieldType: FormFieldSchemaType.Markdown, + required: true, + }, + { + field: { + incidentSeverity: true, + }, + title: "Incident Severity", + stepId: "incident-details", + description: "What type of incident is this?", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: IncidentSeverity, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Incident Severity", + }, + { + field: { + monitors: true, + }, + title: "Monitors affected", + stepId: "resources-affected", + description: "Select monitors affected by this incident.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Monitor, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Monitors affected", + }, + { + field: { + onCallDutyPolicies: true, + }, + title: "On-Call Policy", + stepId: "on-call", + description: + "Select on-call duty policy to execute when this incident is created.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: OnCallDutyPolicy, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Select on-call policies", + }, + { + field: { + changeMonitorStatusTo: true, + }, + title: "Change Monitor Status to ", + stepId: "resources-affected", + description: + "This will change the status of all the monitors attached to this incident.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: MonitorStatus, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Monitor Status", + }, + { + field: { + labels: true, + }, - title: 'Labels ', - stepId: 'labels', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: IncidentTemplate, - id: 'model-detail-incidents', - fields: [ - { - field: { - _id: true, - }, - title: 'Incident Template ID', - fieldType: FieldType.ObjectID, - }, - { - field: { - templateName: true, - }, - title: 'Template Name', - fieldType: FieldType.Text, - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - fieldType: FieldType.Text, - }, - { - field: { - title: true, - }, - title: 'Incident Title', - fieldType: FieldType.Text, - }, + title: "Labels ", + stepId: "labels", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: IncidentTemplate, + id: "model-detail-incidents", + fields: [ + { + field: { + _id: true, + }, + title: "Incident Template ID", + fieldType: FieldType.ObjectID, + }, + { + field: { + templateName: true, + }, + title: "Template Name", + fieldType: FieldType.Text, + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + fieldType: FieldType.Text, + }, + { + field: { + title: true, + }, + title: "Incident Title", + fieldType: FieldType.Text, + }, - { - field: { - incidentSeverity: { - color: true, - name: true, - }, - }, - title: 'Incident Severity', - fieldType: FieldType.Entity, - getElement: ( - item: IncidentTemplate - ): ReactElement => { - if (!item['incidentSeverity']) { - return <p>No incident severity.</p>; - } - - return ( - <Pill - color={ - item.incidentSeverity.color || Black - } - text={ - item.incidentSeverity.name || - 'Unknown' - } - /> - ); - }, - }, - { - field: { - monitors: { - name: true, - _id: true, - }, - }, - title: 'Monitors Affected', - fieldType: FieldType.Element, - getElement: ( - item: IncidentTemplate - ): ReactElement => { - return ( - <MonitorsElement - monitors={item['monitors'] || []} - /> - ); - }, - }, - { - field: { - onCallDutyPolicies: { - name: true, - _id: true, - }, - }, - title: 'On-Call Duty Policies', - fieldType: FieldType.Element, - getElement: ( - item: IncidentTemplate - ): ReactElement => { - return ( - <OnCallDutyPoliciesView - onCallPolicies={ - item['onCallDutyPolicies'] || [] - } - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - fieldType: FieldType.DateTime, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: ( - item: IncidentTemplate - ): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - ], - modelId: modelId, - }} - /> - - <ModelTable<IncidentTemplateOwnerTeam> - modelType={IncidentTemplateOwnerTeam} - id="table-incident-owner-team" - name="Incident Template > Owner Team" - singularName="Team" - isDeleteable={true} - createVerb={'Add'} - isCreateable={true} - isViewable={false} - showViewIdButton={true} - query={{ - incidentTemplateId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: IncidentTemplateOwnerTeam - ): Promise<IncidentTemplateOwnerTeam> => { - item.incidentTemplateId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Teams)', - description: - 'These are the list of teams that will be added to the incident by default when its created.', - }} - noItemsMessage={ - 'No teams associated with this incident template so far.' + { + field: { + incidentSeverity: { + color: true, + name: true, + }, + }, + title: "Incident Severity", + fieldType: FieldType.Entity, + getElement: (item: IncidentTemplate): ReactElement => { + if (!item["incidentSeverity"]) { + return <p>No incident severity.</p>; } - formFields={[ - { - field: { - team: true, - }, - title: 'Team', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select Team', - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - team: { - name: true, - }, - }, - title: 'Team', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - team: { - name: true, - }, - }, - title: 'Team', - type: FieldType.Entity, - getElement: ( - item: IncidentTemplateOwnerTeam - ): ReactElement => { - if (!item['team']) { - throw new BadDataException('Team not found'); - } + return ( + <Pill + color={item.incidentSeverity.color || Black} + text={item.incidentSeverity.name || "Unknown"} + /> + ); + }, + }, + { + field: { + monitors: { + name: true, + _id: true, + }, + }, + title: "Monitors Affected", + fieldType: FieldType.Element, + getElement: (item: IncidentTemplate): ReactElement => { + return <MonitorsElement monitors={item["monitors"] || []} />; + }, + }, + { + field: { + onCallDutyPolicies: { + name: true, + _id: true, + }, + }, + title: "On-Call Duty Policies", + fieldType: FieldType.Element, + getElement: (item: IncidentTemplate): ReactElement => { + return ( + <OnCallDutyPoliciesView + onCallPolicies={item["onCallDutyPolicies"] || []} + /> + ); + }, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + fieldType: FieldType.DateTime, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: IncidentTemplate): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ], + modelId: modelId, + }} + /> - return <TeamElement team={item['team'] as Team} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> + <ModelTable<IncidentTemplateOwnerTeam> + modelType={IncidentTemplateOwnerTeam} + id="table-incident-owner-team" + name="Incident Template > Owner Team" + singularName="Team" + isDeleteable={true} + createVerb={"Add"} + isCreateable={true} + isViewable={false} + showViewIdButton={true} + query={{ + incidentTemplateId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: IncidentTemplateOwnerTeam, + ): Promise<IncidentTemplateOwnerTeam> => { + item.incidentTemplateId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Teams)", + description: + "These are the list of teams that will be added to the incident by default when its created.", + }} + noItemsMessage={ + "No teams associated with this incident template so far." + } + formFields={[ + { + field: { + team: true, + }, + title: "Team", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select Team", + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + team: { + name: true, + }, + }, + title: "Team", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + team: { + name: true, + }, + }, + title: "Team", + type: FieldType.Entity, - <ModelTable<IncidentTemplateOwnerUser> - modelType={IncidentTemplateOwnerUser} - id="table-incident-owner-team" - name="Incident > Owner Team" - isDeleteable={true} - singularName="User" - isCreateable={true} - isViewable={false} - showViewIdButton={true} - createVerb={'Add'} - query={{ - incidentTemplateId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: IncidentTemplateOwnerUser - ): Promise<IncidentTemplateOwnerUser> => { - item.incidentTemplateId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Users)', - description: - 'These are the list of users that will be added to the incident by default when its created.', - }} - noItemsMessage={ - 'No users associated with this incident template so far.' - } - formFields={[ - { - field: { - user: true, - }, - title: 'User', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select User', - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - user: { - name: true, - email: true, - }, - }, - title: 'User', - type: FieldType.Entity, - }, - ]} - columns={[ - { - field: { - user: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: 'User', - type: FieldType.Entity, - getElement: ( - item: IncidentTemplateOwnerUser - ): ReactElement => { - if (!item['user']) { - throw new BadDataException('User not found'); - } + getElement: (item: IncidentTemplateOwnerTeam): ReactElement => { + if (!item["team"]) { + throw new BadDataException("Team not found"); + } - return <UserElement user={item['user'] as User} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> + return <TeamElement team={item["team"] as Team} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> - <ModelDelete - modelType={IncidentTemplate} - modelId={Navigation.getLastParamAsObjectID()} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.SETTINGS_INCIDENT_TEMPLATES] as Route - ); - }} - /> - </Fragment> - ); + <ModelTable<IncidentTemplateOwnerUser> + modelType={IncidentTemplateOwnerUser} + id="table-incident-owner-team" + name="Incident > Owner Team" + isDeleteable={true} + singularName="User" + isCreateable={true} + isViewable={false} + showViewIdButton={true} + createVerb={"Add"} + query={{ + incidentTemplateId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: IncidentTemplateOwnerUser, + ): Promise<IncidentTemplateOwnerUser> => { + item.incidentTemplateId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Users)", + description: + "These are the list of users that will be added to the incident by default when its created.", + }} + noItemsMessage={ + "No users associated with this incident template so far." + } + formFields={[ + { + field: { + user: true, + }, + title: "User", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select User", + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + user: { + name: true, + email: true, + }, + }, + title: "User", + type: FieldType.Entity, + }, + ]} + columns={[ + { + field: { + user: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "User", + type: FieldType.Entity, + getElement: (item: IncidentTemplateOwnerUser): ReactElement => { + if (!item["user"]) { + throw new BadDataException("User not found"); + } + + return <UserElement user={item["user"] as User} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> + + <ModelDelete + modelType={IncidentTemplate} + modelId={Navigation.getLastParamAsObjectID()} + onDeleteSuccess={() => { + Navigation.navigate( + RouteMap[PageMap.SETTINGS_INCIDENT_TEMPLATES] as Route, + ); + }} + /> + </Fragment> + ); }; export default TeamView; diff --git a/Dashboard/src/Pages/Settings/Invoices.tsx b/Dashboard/src/Pages/Settings/Invoices.tsx index 1d37ab3482..ccb157815c 100644 --- a/Dashboard/src/Pages/Settings/Invoices.tsx +++ b/Dashboard/src/Pages/Settings/Invoices.tsx @@ -1,267 +1,247 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import { Green, Yellow } from 'Common/Types/BrandColors'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import Text from 'Common/Types/Text'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { APP_API_URL } from 'CommonUI/src/Config'; -import BaseAPI from 'CommonUI/src/Utils/API/API'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import BillingInvoice, { InvoiceStatus } from 'Model/Models/BillingInvoice'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import { Green, Yellow } from "Common/Types/BrandColors"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import Text from "Common/Types/Text"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { APP_API_URL } from "CommonUI/src/Config"; +import BaseAPI from "CommonUI/src/Utils/API/API"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import BillingInvoice, { InvoiceStatus } from "Model/Models/BillingInvoice"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; export interface ComponentProps extends PageComponentProps {} const Settings: FunctionComponent<ComponentProps> = ( - _props: ComponentProps + _props: ComponentProps, ): ReactElement => { - const [error, setError] = useState<string | null>(null); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string | null>(null); + const [isLoading, setIsLoading] = useState<boolean>(false); - type PayInvoiceFunction = ( - customerId: string, - invoiceId: string - ) => Promise<void>; + type PayInvoiceFunction = ( + customerId: string, + invoiceId: string, + ) => Promise<void>; - const payInvoice: PayInvoiceFunction = async ( - customerId: string, - invoiceId: string - ): Promise<void> => { - try { - setIsLoading(true); + const payInvoice: PayInvoiceFunction = async ( + customerId: string, + invoiceId: string, + ): Promise<void> => { + try { + setIsLoading(true); - const result: HTTPResponse<JSONObject> = - await BaseAPI.post<JSONObject>( - URL.fromString(APP_API_URL.toString()).addRoute( - `/billing-invoices/pay` - ), - { - data: { - paymentProviderInvoiceId: invoiceId, - paymentProviderCustomerId: customerId, - }, - }, - ModelAPI.getCommonHeaders() + const result: HTTPResponse<JSONObject> = await BaseAPI.post<JSONObject>( + URL.fromString(APP_API_URL.toString()).addRoute( + `/billing-invoices/pay`, + ), + { + data: { + paymentProviderInvoiceId: invoiceId, + paymentProviderCustomerId: customerId, + }, + }, + ModelAPI.getCommonHeaders(), + ); + + if (result.isFailure()) { + throw result; + } + + Navigation.reload(); + } catch (err) { + setError(BaseAPI.getFriendlyMessage(err)); + setIsLoading(false); + } + }; + + return ( + <Fragment> + {isLoading ? <ComponentLoader /> : <></>} + + {!isLoading ? ( + <ModelTable<BillingInvoice> + modelType={BillingInvoice} + id="invoices-table" + isDeleteable={false} + name="Settings > Billing > Invoices" + isEditable={false} + isCreateable={false} + isViewable={false} + cardProps={{ + title: "Invoices", + description: "Here is a list of invoices for this project.", + }} + noItemsMessage={"No invoices so far."} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + showRefreshButton={true} + selectMoreFields={{ + currencyCode: true, + paymentProviderCustomerId: true, + }} + filters={[ + { + field: { + paymentProviderInvoiceId: true, + }, + title: "Invoice ID", + type: FieldType.Text, + }, + { + field: { + amount: true, + }, + title: "Amount", + type: FieldType.Text, + }, + { + field: { + status: true, + }, + title: "Invoice Status", + type: FieldType.Dropdown, + filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum( + InvoiceStatus, + ).map((option: DropdownOption) => { + return { + value: option.value, + label: Text.uppercaseFirstLetter( + (option.value as string) || "Undefined", + ), + }; + }), + }, + ]} + columns={[ + { + field: { + paymentProviderInvoiceId: true, + }, + title: "Invoice ID", + type: FieldType.Text, + }, + { + field: { + amount: true, + }, + title: "Amount", + type: FieldType.Text, + + getElement: (item: BillingInvoice) => { + return ( + <span>{`${(item["amount"] as number) / 100} ${item[ + "currencyCode" + ] + ?.toString() + .toUpperCase()}`}</span> ); + }, + }, + { + field: { + status: true, + }, + title: "Invoice Status", + type: FieldType.Text, - if (result.isFailure()) { - throw result; - } + getElement: (item: BillingInvoice) => { + if (item["status"] === InvoiceStatus.Paid) { + return ( + <Pill + text={Text.uppercaseFirstLetter(item["status"] as string)} + color={Green} + /> + ); + } + return ( + <Pill + text={Text.uppercaseFirstLetter(item["status"] as string)} + color={Yellow} + /> + ); + }, + }, + { + field: { + downloadableLink: true, + }, + title: "Actions", + type: FieldType.Text, - Navigation.reload(); - } catch (err) { - setError(BaseAPI.getFriendlyMessage(err)); - setIsLoading(false); - } - }; + getElement: (item: BillingInvoice) => { + return ( + <div> + {item["downloadableLink"] ? ( + <Button + icon={IconProp.Download} + onClick={() => { + Navigation.navigate(item["downloadableLink"] as URL); + }} + title="Download" + /> + ) : ( + <></> + )} - return ( - <Fragment> - {isLoading ? <ComponentLoader /> : <></>} + {item["status"] !== InvoiceStatus.Paid && + item["status"] !== InvoiceStatus.Draft && + item["status"] !== InvoiceStatus.Void && + item["status"] !== InvoiceStatus.Deleted ? ( + <Button + icon={IconProp.Billing} + onClick={async () => { + await payInvoice( + item["paymentProviderCustomerId"] as string, + item["paymentProviderInvoiceId"] as string, + ); + }} + title="Pay Invoice" + /> + ) : ( + <></> + )} + </div> + ); + }, + }, + ]} + /> + ) : ( + <></> + )} - {!isLoading ? ( - <ModelTable<BillingInvoice> - modelType={BillingInvoice} - id="invoices-table" - isDeleteable={false} - name="Settings > Billing > Invoices" - isEditable={false} - isCreateable={false} - isViewable={false} - cardProps={{ - title: 'Invoices', - description: - 'Here is a list of invoices for this project.', - }} - noItemsMessage={'No invoices so far.'} - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - }} - showRefreshButton={true} - selectMoreFields={{ - currencyCode: true, - paymentProviderCustomerId: true, - }} - filters={[ - { - field: { - paymentProviderInvoiceId: true, - }, - title: 'Invoice ID', - type: FieldType.Text, - }, - { - field: { - amount: true, - }, - title: 'Amount', - type: FieldType.Text, - }, - { - field: { - status: true, - }, - title: 'Invoice Status', - type: FieldType.Dropdown, - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - InvoiceStatus - ).map((option: DropdownOption) => { - return { - value: option.value, - label: Text.uppercaseFirstLetter( - (option.value as string) || - 'Undefined' - ), - }; - }), - }, - ]} - columns={[ - { - field: { - paymentProviderInvoiceId: true, - }, - title: 'Invoice ID', - type: FieldType.Text, - }, - { - field: { - amount: true, - }, - title: 'Amount', - type: FieldType.Text, - - getElement: (item: BillingInvoice) => { - return ( - <span>{`${ - (item['amount'] as number) / 100 - } ${item['currencyCode'] - ?.toString() - .toUpperCase()}`}</span> - ); - }, - }, - { - field: { - status: true, - }, - title: 'Invoice Status', - type: FieldType.Text, - - getElement: (item: BillingInvoice) => { - if (item['status'] === InvoiceStatus.Paid) { - return ( - <Pill - text={Text.uppercaseFirstLetter( - item['status'] as string - )} - color={Green} - /> - ); - } - return ( - <Pill - text={Text.uppercaseFirstLetter( - item['status'] as string - )} - color={Yellow} - /> - ); - }, - }, - { - field: { - downloadableLink: true, - }, - title: 'Actions', - type: FieldType.Text, - - getElement: (item: BillingInvoice) => { - return ( - <div> - {item['downloadableLink'] ? ( - <Button - icon={IconProp.Download} - onClick={() => { - Navigation.navigate( - item[ - 'downloadableLink' - ] as URL - ); - }} - title="Download" - /> - ) : ( - <></> - )} - - {item['status'] !== - InvoiceStatus.Paid && - item['status'] !== - InvoiceStatus.Draft && - item['status'] !== InvoiceStatus.Void && - item['status'] !== - InvoiceStatus.Deleted ? ( - <Button - icon={IconProp.Billing} - onClick={async () => { - await payInvoice( - item[ - 'paymentProviderCustomerId' - ] as string, - item[ - 'paymentProviderInvoiceId' - ] as string - ); - }} - title="Pay Invoice" - /> - ) : ( - <></> - )} - </div> - ); - }, - }, - ]} - /> - ) : ( - <></> - )} - - {error ? ( - <ConfirmModal - title={`Something is not quite right...`} - description={`${error}`} - submitButtonText={'Close'} - onSubmit={() => { - setError(''); - }} - submitButtonType={ButtonStyleType.NORMAL} - /> - ) : ( - <></> - )} - </Fragment> - ); + {error ? ( + <ConfirmModal + title={`Something is not quite right...`} + description={`${error}`} + submitButtonText={"Close"} + onSubmit={() => { + setError(""); + }} + submitButtonType={ButtonStyleType.NORMAL} + /> + ) : ( + <></> + )} + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/Settings/Labels.tsx b/Dashboard/src/Pages/Settings/Labels.tsx index 3dc200c733..f846b32063 100644 --- a/Dashboard/src/Pages/Settings/Labels.tsx +++ b/Dashboard/src/Pages/Settings/Labels.tsx @@ -1,118 +1,117 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import Color from 'Common/Types/Color'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import Color from "Common/Types/Color"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Labels: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<Label> - modelType={Label} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - id="labels-table" - name="Settings > Labels" - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Labels', - description: - 'Labels help you categorize resources in your project and give granular permissions to access those resources to team members.', - }} - noItemsMessage={'No labels found.'} - viewPageRoute={Navigation.getCurrentRoute()} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'internal-service', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: - 'This label is for all the internal services.', - }, - { - field: { - color: true, - }, - title: 'Label Color', - fieldType: FormFieldSchemaType.Color, - required: true, - placeholder: 'Please select color for this label.', - }, - ]} - showRefreshButton={true} - selectMoreFields={{ - color: true, - }} - showViewIdButton={true} - filters={[ - { - field: { - name: true, - }, - type: FieldType.Text, - title: 'Name', - }, - { - field: { - description: true, - }, - type: FieldType.Text, - title: 'Description', - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, + return ( + <Fragment> + <ModelTable<Label> + modelType={Label} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + id="labels-table" + name="Settings > Labels" + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Labels", + description: + "Labels help you categorize resources in your project and give granular permissions to access those resources to team members.", + }} + noItemsMessage={"No labels found."} + viewPageRoute={Navigation.getCurrentRoute()} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "internal-service", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "This label is for all the internal services.", + }, + { + field: { + color: true, + }, + title: "Label Color", + fieldType: FormFieldSchemaType.Color, + required: true, + placeholder: "Please select color for this label.", + }, + ]} + showRefreshButton={true} + selectMoreFields={{ + color: true, + }} + showViewIdButton={true} + filters={[ + { + field: { + name: true, + }, + type: FieldType.Text, + title: "Name", + }, + { + field: { + description: true, + }, + type: FieldType.Text, + title: "Description", + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, - getElement: (item: Label): ReactElement => { - return ( - <Pill - color={item['color'] as Color} - text={item['name'] as string} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - /> - </Fragment> - ); + getElement: (item: Label): ReactElement => { + return ( + <Pill + color={item["color"] as Color} + text={item["name"] as string} + /> + ); + }, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + /> + </Fragment> + ); }; export default Labels; diff --git a/Dashboard/src/Pages/Settings/Layout.tsx b/Dashboard/src/Pages/Settings/Layout.tsx index 6e5a94dac7..9af0e30cb6 100644 --- a/Dashboard/src/Pages/Settings/Layout.tsx +++ b/Dashboard/src/Pages/Settings/Layout.tsx @@ -1,25 +1,25 @@ -import { getSettingsBreadcrumbs } from '../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import DashboardSideMenu from './SideMenu'; -import Page from 'CommonUI/src/Components/Page/Page'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; +import { getSettingsBreadcrumbs } from "../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import DashboardSideMenu from "./SideMenu"; +import Page from "CommonUI/src/Components/Page/Page"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet } from "react-router-dom"; const SettingsLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <Page - title={'Project Settings'} - breadcrumbLinks={getSettingsBreadcrumbs(path)} - sideMenu={<DashboardSideMenu />} - > - <Outlet /> - </Page> - ); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <Page + title={"Project Settings"} + breadcrumbLinks={getSettingsBreadcrumbs(path)} + sideMenu={<DashboardSideMenu />} + > + <Outlet /> + </Page> + ); }; export default SettingsLayout; diff --git a/Dashboard/src/Pages/Settings/MonitorCustomFields.tsx b/Dashboard/src/Pages/Settings/MonitorCustomFields.tsx index 3a08ad2b02..ff8ff7688b 100644 --- a/Dashboard/src/Pages/Settings/MonitorCustomFields.tsx +++ b/Dashboard/src/Pages/Settings/MonitorCustomFields.tsx @@ -1,18 +1,18 @@ -import PageComponentProps from '../PageComponentProps'; -import CustomFieldsPageBase from './Base/CustomFieldsPageBase'; -import MonitorCustomField from 'Model/Models/MonitorCustomField'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import CustomFieldsPageBase from "./Base/CustomFieldsPageBase"; +import MonitorCustomField from "Model/Models/MonitorCustomField"; +import React, { FunctionComponent, ReactElement } from "react"; const MonitorCustomFields: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <CustomFieldsPageBase - {...props} - title="Monitor Custom Fields" - modelType={MonitorCustomField} - /> - ); + return ( + <CustomFieldsPageBase + {...props} + title="Monitor Custom Fields" + modelType={MonitorCustomField} + /> + ); }; export default MonitorCustomFields; diff --git a/Dashboard/src/Pages/Settings/MonitorSecrets.tsx b/Dashboard/src/Pages/Settings/MonitorSecrets.tsx index 69dc1879c3..f9467b58b4 100644 --- a/Dashboard/src/Pages/Settings/MonitorSecrets.tsx +++ b/Dashboard/src/Pages/Settings/MonitorSecrets.tsx @@ -1,250 +1,240 @@ -import MonitorsElement from '../../Components/Monitor/Monitors'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import URL from 'Common/Types/API/URL'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { ErrorFunction } from 'Common/Types/FunctionTypes'; -import { JSONObject } from 'Common/Types/JSON'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; -import MonitorSecret from 'Model/Models/MonitorSecret'; +import MonitorsElement from "../../Components/Monitor/Monitors"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import URL from "Common/Types/API/URL"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { ErrorFunction } from "Common/Types/FunctionTypes"; +import { JSONObject } from "Common/Types/JSON"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; +import MonitorSecret from "Model/Models/MonitorSecret"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const MonitorSecrets: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [currentlyEditingItem, setCurrentlyEditingItem] = - useState<MonitorSecret | null>(null); + const [currentlyEditingItem, setCurrentlyEditingItem] = + useState<MonitorSecret | null>(null); - const [isLoading, setIsLoading] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); - return ( - <Fragment> - <Banner - openInNewTab={true} - title="How to use Monitor Secrets?" - description="Learn how to use monitor secrets to store sensitive information like API keys, passwords, etc. that can be shared with monitors." - link={URL.fromString( - 'https://www.youtube.com/watch?v=V5eIpd_IPlU' - )} - /> - <ModelTable<MonitorSecret> - modelType={MonitorSecret} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - id="monitor-secret-table" - name="Settings > Monitor Secret" - isDeleteable={true} - isEditable={true} - isCreateable={true} - actionButtons={[ - { - title: 'Update Secret Value', - buttonStyleType: ButtonStyleType.OUTLINE, - onClick: async ( - item: MonitorSecret, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentlyEditingItem(item); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - cardProps={{ - title: 'Monitor Secrets', - description: - 'Monitor secrets are used to store sensitive information like API keys, passwords, etc. that can be shared with monitors.', - }} - noItemsMessage={ - 'No monitor secret found. Click on the "Create" button to add a new monitor secret.' - } - viewPageRoute={Navigation.getCurrentRoute()} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Secret Name', - validation: { - minLength: 2, - noSpaces: true, - noNumbers: true, - noSpecialCharacters: true, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Secret Description', - }, - { - field: { - secretValue: true, - }, - title: 'Secret Value', - doNotShowWhenEditing: true, // Do not show this field when editing - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: - 'Secret Value (eg: API Key, Password, etc.)', - }, - { - field: { - monitors: true, - }, - title: 'Monitors which have access to this secret', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Monitor, - labelField: 'name', - valueField: '_id', - }, - required: true, - description: - 'Whcih monitors should have access to this secret?', - placeholder: 'Select monitors', - }, - ]} - sortBy="name" - sortOrder={SortOrder.Ascending} - showRefreshButton={true} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - monitors: true, - }, - title: 'Monitors which have access to this secret', - type: FieldType.EntityArray, + return ( + <Fragment> + <Banner + openInNewTab={true} + title="How to use Monitor Secrets?" + description="Learn how to use monitor secrets to store sensitive information like API keys, passwords, etc. that can be shared with monitors." + link={URL.fromString("https://www.youtube.com/watch?v=V5eIpd_IPlU")} + /> + <ModelTable<MonitorSecret> + modelType={MonitorSecret} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + id="monitor-secret-table" + name="Settings > Monitor Secret" + isDeleteable={true} + isEditable={true} + isCreateable={true} + actionButtons={[ + { + title: "Update Secret Value", + buttonStyleType: ButtonStyleType.OUTLINE, + onClick: async ( + item: MonitorSecret, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentlyEditingItem(item); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + cardProps={{ + title: "Monitor Secrets", + description: + "Monitor secrets are used to store sensitive information like API keys, passwords, etc. that can be shared with monitors.", + }} + noItemsMessage={ + 'No monitor secret found. Click on the "Create" button to add a new monitor secret.' + } + viewPageRoute={Navigation.getCurrentRoute()} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Secret Name", + validation: { + minLength: 2, + noSpaces: true, + noNumbers: true, + noSpecialCharacters: true, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Secret Description", + }, + { + field: { + secretValue: true, + }, + title: "Secret Value", + doNotShowWhenEditing: true, // Do not show this field when editing + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Secret Value (eg: API Key, Password, etc.)", + }, + { + field: { + monitors: true, + }, + title: "Monitors which have access to this secret", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Monitor, + labelField: "name", + valueField: "_id", + }, + required: true, + description: "Whcih monitors should have access to this secret?", + placeholder: "Select monitors", + }, + ]} + sortBy="name" + sortOrder={SortOrder.Ascending} + showRefreshButton={true} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + monitors: true, + }, + title: "Monitors which have access to this secret", + type: FieldType.EntityArray, - filterEntityType: Monitor, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - monitors: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Monitors which have access to this secret', - type: FieldType.EntityArray, + filterEntityType: Monitor, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + monitors: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Monitors which have access to this secret", + type: FieldType.EntityArray, - getElement: (item: MonitorSecret): ReactElement => { - return ( - <MonitorsElement - monitors={item['monitors'] || []} - /> - ); - }, - }, - ]} - /> + getElement: (item: MonitorSecret): ReactElement => { + return <MonitorsElement monitors={item["monitors"] || []} />; + }, + }, + ]} + /> - {currentlyEditingItem && ( - <BasicFormModal - title={'Update Secret Value'} - isLoading={isLoading} - onClose={() => { - setIsLoading(false); - return setCurrentlyEditingItem(null); - }} - onSubmit={async (data: JSONObject) => { - try { - setIsLoading(true); + {currentlyEditingItem && ( + <BasicFormModal + title={"Update Secret Value"} + isLoading={isLoading} + onClose={() => { + setIsLoading(false); + return setCurrentlyEditingItem(null); + }} + onSubmit={async (data: JSONObject) => { + try { + setIsLoading(true); - await ModelAPI.updateById<MonitorSecret>({ - modelType: MonitorSecret, - id: currentlyEditingItem.id!, - data: { - secretValue: data['secretValue'], - }, - }); + await ModelAPI.updateById<MonitorSecret>({ + modelType: MonitorSecret, + id: currentlyEditingItem.id!, + data: { + secretValue: data["secretValue"], + }, + }); - setCurrentlyEditingItem(null); - } catch (err) { - // do nothing - } + setCurrentlyEditingItem(null); + } catch (err) { + // do nothing + } - setIsLoading(false); - }} - formProps={{ - initialValues: {}, - fields: [ - { - field: { - secretValue: true, - }, - title: 'Secret Value', - description: - 'This value will be encrypted and stored securely. Once saved, this value cannot be retrieved.', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: - 'Secret Value (eg: API Key, Password, etc.)', - }, - ], - }} - /> - )} - </Fragment> - ); + setIsLoading(false); + }} + formProps={{ + initialValues: {}, + fields: [ + { + field: { + secretValue: true, + }, + title: "Secret Value", + description: + "This value will be encrypted and stored securely. Once saved, this value cannot be retrieved.", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Secret Value (eg: API Key, Password, etc.)", + }, + ], + }} + /> + )} + </Fragment> + ); }; export default MonitorSecrets; diff --git a/Dashboard/src/Pages/Settings/MonitorStatus.tsx b/Dashboard/src/Pages/Settings/MonitorStatus.tsx index 5d046dc91b..5e4857b462 100644 --- a/Dashboard/src/Pages/Settings/MonitorStatus.tsx +++ b/Dashboard/src/Pages/Settings/MonitorStatus.tsx @@ -1,156 +1,151 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import Color from 'Common/Types/Color'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import { ShowAs } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import StatusBubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import Color from "Common/Types/Color"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import { ShowAs } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import StatusBubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Monitors: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<MonitorStatus> - modelType={MonitorStatus} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - id="monitor-status-table" - name="Settings > Monitor Status" - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Monitor Status', - description: - 'Define different status types (eg: Operational, Degraded, Down) here.', - }} - noItemsMessage={'No monitor status found.'} - orderedStatesListProps={{ - titleField: 'name', - descriptionField: 'description', - orderField: 'priority', - }} - showAs={ShowAs.OrderedStatesList} - onBeforeDelete={( - item: MonitorStatus - ): Promise<MonitorStatus> => { - if (item.isOperationalState) { - throw new BadDataException( - 'This monitor status cannot be deleted because its the operational state of monitors. Operational status or Offline Status cannot be deleted.' - ); - } + return ( + <Fragment> + <ModelTable<MonitorStatus> + modelType={MonitorStatus} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + id="monitor-status-table" + name="Settings > Monitor Status" + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Monitor Status", + description: + "Define different status types (eg: Operational, Degraded, Down) here.", + }} + noItemsMessage={"No monitor status found."} + orderedStatesListProps={{ + titleField: "name", + descriptionField: "description", + orderField: "priority", + }} + showAs={ShowAs.OrderedStatesList} + onBeforeDelete={(item: MonitorStatus): Promise<MonitorStatus> => { + if (item.isOperationalState) { + throw new BadDataException( + "This monitor status cannot be deleted because its the operational state of monitors. Operational status or Offline Status cannot be deleted.", + ); + } - if (item.isOfflineState) { - throw new BadDataException( - 'This monitor status cannot be deleted because its the offline state of monitors. Operational status or Offline Status cannot be deleted.' - ); - } + if (item.isOfflineState) { + throw new BadDataException( + "This monitor status cannot be deleted because its the offline state of monitors. Operational status or Offline Status cannot be deleted.", + ); + } - return Promise.resolve(item); - }} - viewPageRoute={Navigation.getCurrentRoute()} - onBeforeCreate={( - item: MonitorStatus - ): Promise<MonitorStatus> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } + return Promise.resolve(item); + }} + viewPageRoute={Navigation.getCurrentRoute()} + onBeforeCreate={(item: MonitorStatus): Promise<MonitorStatus> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Operational', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Monitors are up and operating normally.', - }, - { - field: { - color: true, - }, - title: 'Monitor Status Color', - fieldType: FormFieldSchemaType.Color, - required: true, - placeholder: - 'Please select color for this monitor status.', - }, - ]} - sortBy="priority" - sortOrder={SortOrder.Ascending} - showRefreshButton={true} - selectMoreFields={{ - color: true, - isOperationalState: true, - isOfflineState: true, - priority: true, - }} - filters={[]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Operational", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Monitors are up and operating normally.", + }, + { + field: { + color: true, + }, + title: "Monitor Status Color", + fieldType: FormFieldSchemaType.Color, + required: true, + placeholder: "Please select color for this monitor status.", + }, + ]} + sortBy="priority" + sortOrder={SortOrder.Ascending} + showRefreshButton={true} + selectMoreFields={{ + color: true, + isOperationalState: true, + isOfflineState: true, + priority: true, + }} + filters={[]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, - getElement: (item: MonitorStatus): ReactElement => { - return ( - <StatusBubble - color={item['color'] as Color} - text={item['name'] as string} - shouldAnimate={false} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, + getElement: (item: MonitorStatus): ReactElement => { + return ( + <StatusBubble + color={item["color"] as Color} + text={item["name"] as string} + shouldAnimate={false} + /> + ); + }, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, - getElement: (item: MonitorStatus): ReactElement => { - return ( - <div> - <p>{`${item['description']}`}</p> - <p className="text-xs text-gray-400"> - ID: {`${item['_id']}`} - </p> - </div> - ); - }, - }, - ]} - /> - </Fragment> - ); + getElement: (item: MonitorStatus): ReactElement => { + return ( + <div> + <p>{`${item["description"]}`}</p> + <p className="text-xs text-gray-400"> + ID: {`${item["_id"]}`} + </p> + </div> + ); + }, + }, + ]} + /> + </Fragment> + ); }; export default Monitors; diff --git a/Dashboard/src/Pages/Settings/NotificationSettings.tsx b/Dashboard/src/Pages/Settings/NotificationSettings.tsx index 73ebe522d4..96525b05f2 100644 --- a/Dashboard/src/Pages/Settings/NotificationSettings.tsx +++ b/Dashboard/src/Pages/Settings/NotificationSettings.tsx @@ -1,377 +1,370 @@ -import CustomCallSMSTable from '../../Components/CallSMS/CallSMSConfigTable'; -import CustomSMTPTable from '../../Components/CustomSMTP/CustomSMTPTable'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { APP_API_URL, BILLING_ENABLED } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Project from 'Model/Models/Project'; +import CustomCallSMSTable from "../../Components/CallSMS/CallSMSConfigTable"; +import CustomSMTPTable from "../../Components/CustomSMTP/CustomSMTPTable"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import BasicFormModal from "CommonUI/src/Components/FormModal/BasicFormModal"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { APP_API_URL, BILLING_ENABLED } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Project from "Model/Models/Project"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [showRechargeBalanceModal, setShowRechargeBalanceModal] = - useState<boolean>(false); - const [isRechargeBalanceLoading, setIsRechargeBalanceLoading] = - useState<boolean>(false); - const [rechargeBalanceError, setRechargeBalanceError] = useState< - string | null - >(null); + const [showRechargeBalanceModal, setShowRechargeBalanceModal] = + useState<boolean>(false); + const [isRechargeBalanceLoading, setIsRechargeBalanceLoading] = + useState<boolean>(false); + const [rechargeBalanceError, setRechargeBalanceError] = useState< + string | null + >(null); - return ( - <Fragment> - {/* API Key View */} - {BILLING_ENABLED ? ( - <CardModelDetail - name="Current Balance" - cardProps={{ - title: 'Current Balance', - description: - 'Here is your current call and SMS balance for this project.', - buttons: [ - { - title: 'Recharge Balance', - icon: IconProp.Add, - onClick: () => { - setShowRechargeBalanceModal(true); - setRechargeBalanceError(null); - setIsRechargeBalanceLoading(false); - }, - }, - ], - }} - isEditable={false} - modelDetailProps={{ - modelType: Project, - id: 'current-balance', - fields: [ - { - field: { - smsOrCallCurrentBalanceInUSDCents: true, - }, - fieldType: FieldType.USDCents, - title: 'SMS or Call Current Balance', - description: - 'This is your current balance for SMS or Call. It is in USD. ', - placeholder: '0 USD', - }, - ], - modelId: DashboardNavigation.getProjectId()!, - }} - /> - ) : ( - <></> - )} + return ( + <Fragment> + {/* API Key View */} + {BILLING_ENABLED ? ( + <CardModelDetail + name="Current Balance" + cardProps={{ + title: "Current Balance", + description: + "Here is your current call and SMS balance for this project.", + buttons: [ + { + title: "Recharge Balance", + icon: IconProp.Add, + onClick: () => { + setShowRechargeBalanceModal(true); + setRechargeBalanceError(null); + setIsRechargeBalanceLoading(false); + }, + }, + ], + }} + isEditable={false} + modelDetailProps={{ + modelType: Project, + id: "current-balance", + fields: [ + { + field: { + smsOrCallCurrentBalanceInUSDCents: true, + }, + fieldType: FieldType.USDCents, + title: "SMS or Call Current Balance", + description: + "This is your current balance for SMS or Call. It is in USD. ", + placeholder: "0 USD", + }, + ], + modelId: DashboardNavigation.getProjectId()!, + }} + /> + ) : ( + <></> + )} - <CardModelDetail - name="Enable Notifications" - cardProps={{ - title: 'Enable Notifications', - description: - 'Enable Call and SMS notifications for this project.', - }} - isEditable={true} - editButtonText="Edit Notification Settings" - formFields={[ - { - field: { - enableCallNotifications: true, - }, - title: 'Enable Call Notifications', - description: - 'Enable Call notifications for this project. This will be used for alerting users by phone call.', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - { - field: { - enableSmsNotifications: true, - }, - title: 'Enable SMS Notifications', - description: - 'Enable SMS notifications for this project. This will be used for alerting users by sending an SMS.', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - ]} - modelDetailProps={{ - modelType: Project, - id: 'notifications', - fields: [ - { - field: { - enableCallNotifications: true, - }, - fieldType: FieldType.Boolean, - title: 'Enable Call Notifications', - placeholder: 'Not Enabled', - description: - 'Enable Call notifications for this project. This will be used for alerting users by phone call.', - }, - { - field: { - enableSmsNotifications: true, - }, - fieldType: FieldType.Boolean, - title: 'Enable SMS Notifications', - placeholder: 'Not Enabled', - description: - 'Enable SMS notifications for this project. This will be used for alerting users by SMS.', - }, - ], - modelId: DashboardNavigation.getProjectId()!, - }} - /> + <CardModelDetail + name="Enable Notifications" + cardProps={{ + title: "Enable Notifications", + description: "Enable Call and SMS notifications for this project.", + }} + isEditable={true} + editButtonText="Edit Notification Settings" + formFields={[ + { + field: { + enableCallNotifications: true, + }, + title: "Enable Call Notifications", + description: + "Enable Call notifications for this project. This will be used for alerting users by phone call.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + { + field: { + enableSmsNotifications: true, + }, + title: "Enable SMS Notifications", + description: + "Enable SMS notifications for this project. This will be used for alerting users by sending an SMS.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + ]} + modelDetailProps={{ + modelType: Project, + id: "notifications", + fields: [ + { + field: { + enableCallNotifications: true, + }, + fieldType: FieldType.Boolean, + title: "Enable Call Notifications", + placeholder: "Not Enabled", + description: + "Enable Call notifications for this project. This will be used for alerting users by phone call.", + }, + { + field: { + enableSmsNotifications: true, + }, + fieldType: FieldType.Boolean, + title: "Enable SMS Notifications", + placeholder: "Not Enabled", + description: + "Enable SMS notifications for this project. This will be used for alerting users by SMS.", + }, + ], + modelId: DashboardNavigation.getProjectId()!, + }} + /> - {BILLING_ENABLED ? ( - <CardModelDetail - name="Auto Recharge" - cardProps={{ - title: 'Auto Recharge', - description: - 'Enable Auto Recharge for call and SMS balance. This will make sure you always have enough balance for sending SMS or making calls.', - }} - isEditable={true} - editButtonText="Edit Auto Recharge" - formFields={[ - { - field: { - enableAutoRechargeSmsOrCallBalance: true, - }, - title: 'Enable Auto Recharge', - description: - 'Enable Auto Recharge. This will be used for sending an SMS or Call.', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - { - field: { - autoRechargeSmsOrCallByBalanceInUSD: true, - }, - title: 'Auto Recharge Balance by (in USD)', - description: - 'Amount of balance to be recharged when the balance is low. It is in USD. ', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: [ - { - value: 10, - label: '10 USD', - }, - { - value: 20, - label: '20 USD', - }, - { - value: 25, - label: '25 USD', - }, - { - value: 50, - label: '50 USD', - }, - { - value: 75, - label: '75 USD', - }, - { - value: 100, - label: '100 USD', - }, - { - value: 200, - label: '200 USD', - }, - { - value: 500, - label: '500 USD', - }, - { - value: 500, - label: '500 USD', - }, - { - value: 1000, - label: '1000 USD', - }, - ], - required: true, - }, - { - field: { - autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: - true, - }, - title: 'Auto Recharge when balance falls to (in USD)', - description: - 'Trigger auto recharge when balance falls to this amount. It is in USD. ', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: [ - { - value: 10, - label: '10 USD', - }, - { - value: 20, - label: '20 USD', - }, - { - value: 25, - label: '25 USD', - }, - { - value: 50, - label: '50 USD', - }, - { - value: 75, - label: '75 USD', - }, - { - value: 100, - label: '100 USD', - }, - { - value: 200, - label: '200 USD', - }, - { - value: 500, - label: '500 USD', - }, - { - value: 500, - label: '500 USD', - }, - { - value: 1000, - label: '1000 USD', - }, - ], - required: true, - }, - ]} - modelDetailProps={{ - modelType: Project, - id: 'notifications', - fields: [ - { - field: { - enableAutoRechargeSmsOrCallBalance: true, - }, - fieldType: FieldType.Boolean, - title: 'Auto Recharge Balance by (in USD)', - description: - 'Amount of balance to be recharged when the balance is low. It is in USD. ', - placeholder: 'Not Enabled', - }, - { - field: { - autoRechargeSmsOrCallByBalanceInUSD: true, - }, - fieldType: FieldType.Text, - title: 'Auto Recharge by (in USD)', - placeholder: '0 USD', - }, - { - field: { - autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: - true, - }, - fieldType: FieldType.Text, - title: 'Trigger auto recharge if balance falls below (in USD)', - placeholder: '0 USD', - }, - ], - modelId: DashboardNavigation.getProjectId()!, - }} - /> - ) : ( - <></> - )} + {BILLING_ENABLED ? ( + <CardModelDetail + name="Auto Recharge" + cardProps={{ + title: "Auto Recharge", + description: + "Enable Auto Recharge for call and SMS balance. This will make sure you always have enough balance for sending SMS or making calls.", + }} + isEditable={true} + editButtonText="Edit Auto Recharge" + formFields={[ + { + field: { + enableAutoRechargeSmsOrCallBalance: true, + }, + title: "Enable Auto Recharge", + description: + "Enable Auto Recharge. This will be used for sending an SMS or Call.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + { + field: { + autoRechargeSmsOrCallByBalanceInUSD: true, + }, + title: "Auto Recharge Balance by (in USD)", + description: + "Amount of balance to be recharged when the balance is low. It is in USD. ", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: [ + { + value: 10, + label: "10 USD", + }, + { + value: 20, + label: "20 USD", + }, + { + value: 25, + label: "25 USD", + }, + { + value: 50, + label: "50 USD", + }, + { + value: 75, + label: "75 USD", + }, + { + value: 100, + label: "100 USD", + }, + { + value: 200, + label: "200 USD", + }, + { + value: 500, + label: "500 USD", + }, + { + value: 500, + label: "500 USD", + }, + { + value: 1000, + label: "1000 USD", + }, + ], + required: true, + }, + { + field: { + autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: true, + }, + title: "Auto Recharge when balance falls to (in USD)", + description: + "Trigger auto recharge when balance falls to this amount. It is in USD. ", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: [ + { + value: 10, + label: "10 USD", + }, + { + value: 20, + label: "20 USD", + }, + { + value: 25, + label: "25 USD", + }, + { + value: 50, + label: "50 USD", + }, + { + value: 75, + label: "75 USD", + }, + { + value: 100, + label: "100 USD", + }, + { + value: 200, + label: "200 USD", + }, + { + value: 500, + label: "500 USD", + }, + { + value: 500, + label: "500 USD", + }, + { + value: 1000, + label: "1000 USD", + }, + ], + required: true, + }, + ]} + modelDetailProps={{ + modelType: Project, + id: "notifications", + fields: [ + { + field: { + enableAutoRechargeSmsOrCallBalance: true, + }, + fieldType: FieldType.Boolean, + title: "Auto Recharge Balance by (in USD)", + description: + "Amount of balance to be recharged when the balance is low. It is in USD. ", + placeholder: "Not Enabled", + }, + { + field: { + autoRechargeSmsOrCallByBalanceInUSD: true, + }, + fieldType: FieldType.Text, + title: "Auto Recharge by (in USD)", + placeholder: "0 USD", + }, + { + field: { + autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD: true, + }, + fieldType: FieldType.Text, + title: "Trigger auto recharge if balance falls below (in USD)", + placeholder: "0 USD", + }, + ], + modelId: DashboardNavigation.getProjectId()!, + }} + /> + ) : ( + <></> + )} - {showRechargeBalanceModal ? ( - <BasicFormModal - title={'Recharge Balance'} - onClose={() => { - setShowRechargeBalanceModal(false); - }} - isLoading={isRechargeBalanceLoading} - submitButtonText={'Recharge'} - onSubmit={async (item: JSONObject) => { - setIsRechargeBalanceLoading(true); - try { - const response: - | HTTPResponse<JSONObject> - | HTTPErrorResponse = await API.post( - URL.fromString(APP_API_URL.toString()).addRoute( - '/notification/recharge' - ), - { - amount: item['amount'], - projectId: - DashboardNavigation.getProjectId()?.toString(), - } - ); + {showRechargeBalanceModal ? ( + <BasicFormModal + title={"Recharge Balance"} + onClose={() => { + setShowRechargeBalanceModal(false); + }} + isLoading={isRechargeBalanceLoading} + submitButtonText={"Recharge"} + onSubmit={async (item: JSONObject) => { + setIsRechargeBalanceLoading(true); + try { + const response: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.post( + URL.fromString(APP_API_URL.toString()).addRoute( + "/notification/recharge", + ), + { + amount: item["amount"], + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + ); - if (response.isFailure()) { - setRechargeBalanceError( - API.getFriendlyMessage(response) - ); - setIsRechargeBalanceLoading(false); - } else { - setIsRechargeBalanceLoading(false); - setShowRechargeBalanceModal(false); - Navigation.reload(); - } - } catch (e) { - setRechargeBalanceError(API.getFriendlyMessage(e)); - setIsRechargeBalanceLoading(false); - } - }} - formProps={{ - name: 'Recharge Balance', - error: rechargeBalanceError || '', - fields: [ - { - title: 'Amount (in USD)', - description: `Please enter the amount to recharge. It is in USD.`, - field: { - amount: true, - }, - placeholder: '100', - required: true, - validation: { - minValue: 20, - maxValue: 1000, - }, - fieldType: FormFieldSchemaType.Number, - }, - ], - }} - /> - ) : ( - <></> - )} + if (response.isFailure()) { + setRechargeBalanceError(API.getFriendlyMessage(response)); + setIsRechargeBalanceLoading(false); + } else { + setIsRechargeBalanceLoading(false); + setShowRechargeBalanceModal(false); + Navigation.reload(); + } + } catch (e) { + setRechargeBalanceError(API.getFriendlyMessage(e)); + setIsRechargeBalanceLoading(false); + } + }} + formProps={{ + name: "Recharge Balance", + error: rechargeBalanceError || "", + fields: [ + { + title: "Amount (in USD)", + description: `Please enter the amount to recharge. It is in USD.`, + field: { + amount: true, + }, + placeholder: "100", + required: true, + validation: { + minValue: 20, + maxValue: 1000, + }, + fieldType: FormFieldSchemaType.Number, + }, + ], + }} + /> + ) : ( + <></> + )} - <CustomSMTPTable /> - <CustomCallSMSTable /> - </Fragment> - ); + <CustomSMTPTable /> + <CustomCallSMSTable /> + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/Settings/OnCallDutyPolicyCustomFields.tsx b/Dashboard/src/Pages/Settings/OnCallDutyPolicyCustomFields.tsx index 52ed9d27fb..a70b948eaf 100644 --- a/Dashboard/src/Pages/Settings/OnCallDutyPolicyCustomFields.tsx +++ b/Dashboard/src/Pages/Settings/OnCallDutyPolicyCustomFields.tsx @@ -1,18 +1,18 @@ -import PageComponentProps from '../PageComponentProps'; -import CustomFieldsPageBase from './Base/CustomFieldsPageBase'; -import OnCallDutyPolicyCustomField from 'Model/Models/OnCallDutyPolicyCustomField'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import CustomFieldsPageBase from "./Base/CustomFieldsPageBase"; +import OnCallDutyPolicyCustomField from "Model/Models/OnCallDutyPolicyCustomField"; +import React, { FunctionComponent, ReactElement } from "react"; const OnCallDutyPolicyCustomFields: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <CustomFieldsPageBase - {...props} - title="On-Call Policy Custom Fields" - modelType={OnCallDutyPolicyCustomField} - /> - ); + return ( + <CustomFieldsPageBase + {...props} + title="On-Call Policy Custom Fields" + modelType={OnCallDutyPolicyCustomField} + /> + ); }; export default OnCallDutyPolicyCustomFields; diff --git a/Dashboard/src/Pages/Settings/Probes.tsx b/Dashboard/src/Pages/Settings/Probes.tsx index bb5c0b5438..2d53a04ce8 100644 --- a/Dashboard/src/Pages/Settings/Probes.tsx +++ b/Dashboard/src/Pages/Settings/Probes.tsx @@ -1,322 +1,316 @@ -import ProbeStatusElement from '../../Components/Probe/ProbeStatus'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { Green, Red } from 'Common/Types/BrandColors'; -import OneUptimeDate from 'Common/Types/Date'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import ProbeElement from 'CommonUI/src/Components/Probe/Probe'; -import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { APP_API_URL } from 'CommonUI/src/Config'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Probe from 'Model/Models/Probe'; +import ProbeStatusElement from "../../Components/Probe/ProbeStatus"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { Green, Red } from "Common/Types/BrandColors"; +import OneUptimeDate from "Common/Types/Date"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import ProbeElement from "CommonUI/src/Components/Probe/Probe"; +import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { APP_API_URL } from "CommonUI/src/Config"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Probe from "Model/Models/Probe"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const ProbePage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [showKeyModal, setShowKeyModal] = useState<boolean>(false); + const [showKeyModal, setShowKeyModal] = useState<boolean>(false); - const [currentProbe, setCurrentProbe] = useState<Probe | null>(null); + const [currentProbe, setCurrentProbe] = useState<Probe | null>(null); - return ( - <Fragment> - <> - <ModelTable<Probe> - modelType={Probe} - id="probes-table" - name="Settings > Global Probes" - isDeleteable={false} - isEditable={false} - isCreateable={false} - cardProps={{ - title: 'Global Probes', - description: - 'Global Probes help you monitor external resources from different locations around the world.', - }} - fetchRequestOptions={{ - overrideRequestUrl: URL.fromString( - APP_API_URL.toString() - ).addRoute('/probe/global-probes'), - }} - noItemsMessage={'No probes found.'} - showRefreshButton={true} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, + return ( + <Fragment> + <> + <ModelTable<Probe> + modelType={Probe} + id="probes-table" + name="Settings > Global Probes" + isDeleteable={false} + isEditable={false} + isCreateable={false} + cardProps={{ + title: "Global Probes", + description: + "Global Probes help you monitor external resources from different locations around the world.", + }} + fetchRequestOptions={{ + overrideRequestUrl: URL.fromString(APP_API_URL.toString()).addRoute( + "/probe/global-probes", + ), + }} + noItemsMessage={"No probes found."} + showRefreshButton={true} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, - getElement: (item: Probe): ReactElement => { - return <ProbeElement probe={item} />; - }, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - lastAlive: true, - }, - title: 'Probe Status', - type: FieldType.Text, + getElement: (item: Probe): ReactElement => { + return <ProbeElement probe={item} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + lastAlive: true, + }, + title: "Probe Status", + type: FieldType.Text, - getElement: (item: Probe): ReactElement => { - return <ProbeStatusElement probe={item} />; - }, - }, - ]} - /> + getElement: (item: Probe): ReactElement => { + return <ProbeStatusElement probe={item} />; + }, + }, + ]} + /> - <Banner - openInNewTab={true} - title="Need help with setting up Custom Probes?" - description="Here is a guide which will help you get set up" - link={Route.fromString('/docs/probe/custom-probe')} - /> + <Banner + openInNewTab={true} + title="Need help with setting up Custom Probes?" + description="Here is a guide which will help you get set up" + link={Route.fromString("/docs/probe/custom-probe")} + /> - <ModelTable<Probe> - modelType={Probe} - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - }} - id="probes-table" - name="Settings > Probes" - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Custom Probes', - description: - 'Custom Probes help you monitor internal resources that is behind your firewall.', - }} - selectMoreFields={{ - key: true, - iconFileId: true, - }} - noItemsMessage={'No probes found.'} - viewPageRoute={Navigation.getCurrentRoute()} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'internal-probe', - validation: { - minLength: 2, - }, - }, + <ModelTable<Probe> + modelType={Probe} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + id="probes-table" + name="Settings > Probes" + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Custom Probes", + description: + "Custom Probes help you monitor internal resources that is behind your firewall.", + }} + selectMoreFields={{ + key: true, + iconFileId: true, + }} + noItemsMessage={"No probes found."} + viewPageRoute={Navigation.getCurrentRoute()} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "internal-probe", + validation: { + minLength: 2, + }, + }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: - 'This probe is to monitor all the internal services.', - }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: + "This probe is to monitor all the internal services.", + }, - { - field: { - iconFile: true, - }, - title: 'Probe Logo', - fieldType: FormFieldSchemaType.ImageFile, - required: false, - placeholder: 'Upload logo', - }, - { - field: { - shouldAutoEnableProbeOnNewMonitors: true, - }, - title: 'Enable monitoring automatically on new monitors', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - ]} - showRefreshButton={true} - actionButtons={[ - { - title: 'Show ID and Key', - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - item: Probe, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setCurrentProbe(item); - setShowKeyModal(true); + { + field: { + iconFile: true, + }, + title: "Probe Logo", + fieldType: FormFieldSchemaType.ImageFile, + required: false, + placeholder: "Upload logo", + }, + { + field: { + shouldAutoEnableProbeOnNewMonitors: true, + }, + title: "Enable monitoring automatically on new monitors", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + ]} + showRefreshButton={true} + actionButtons={[ + { + title: "Show ID and Key", + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async ( + item: Probe, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setCurrentProbe(item); + setShowKeyModal(true); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - shouldAutoEnableProbeOnNewMonitors: true, - }, - title: 'Enable Monitoring by Default', - type: FieldType.Boolean, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + shouldAutoEnableProbeOnNewMonitors: true, + }, + title: "Enable Monitoring by Default", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, - getElement: (item: Probe): ReactElement => { - return <ProbeElement probe={item} />; - }, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - shouldAutoEnableProbeOnNewMonitors: true, - }, - title: 'Enable Monitoring by Default', - type: FieldType.Boolean, - }, - { - field: { - lastAlive: true, - }, - title: 'Status', - type: FieldType.Text, + getElement: (item: Probe): ReactElement => { + return <ProbeElement probe={item} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + shouldAutoEnableProbeOnNewMonitors: true, + }, + title: "Enable Monitoring by Default", + type: FieldType.Boolean, + }, + { + field: { + lastAlive: true, + }, + title: "Status", + type: FieldType.Text, - getElement: (item: Probe): ReactElement => { - if ( - item && - item['lastAlive'] && - OneUptimeDate.getNumberOfMinutesBetweenDates( - item['lastAlive'], - OneUptimeDate.getCurrentDate() - ) < 5 - ) { - return ( - <Statusbubble - text={'Connected'} - color={Green} - shouldAnimate={true} - /> - ); - } - - return ( - <Statusbubble - text={'Disconnected'} - color={Red} - shouldAnimate={false} - /> - ); - }, - }, - ]} - /> - - {showKeyModal && currentProbe ? ( - <ConfirmModal - title={`Probe Key`} - description={ - <div> - <span> - Here is your probe key. Please keep this a - secret. - </span> - <br /> - <br /> - <span> - <b>Probe ID: </b>{' '} - {currentProbe['_id']?.toString()} - </span> - <br /> - <br /> - <span> - <b>Probe Key: </b>{' '} - {currentProbe['key']?.toString()} - </span> - </div> - } - submitButtonText={'Close'} - submitButtonType={ButtonStyleType.NORMAL} - onSubmit={async () => { - setShowKeyModal(false); - }} + getElement: (item: Probe): ReactElement => { + if ( + item && + item["lastAlive"] && + OneUptimeDate.getNumberOfMinutesBetweenDates( + item["lastAlive"], + OneUptimeDate.getCurrentDate(), + ) < 5 + ) { + return ( + <Statusbubble + text={"Connected"} + color={Green} + shouldAnimate={true} /> - ) : ( - <></> - )} - </> - </Fragment> - ); + ); + } + + return ( + <Statusbubble + text={"Disconnected"} + color={Red} + shouldAnimate={false} + /> + ); + }, + }, + ]} + /> + + {showKeyModal && currentProbe ? ( + <ConfirmModal + title={`Probe Key`} + description={ + <div> + <span>Here is your probe key. Please keep this a secret.</span> + <br /> + <br /> + <span> + <b>Probe ID: </b> {currentProbe["_id"]?.toString()} + </span> + <br /> + <br /> + <span> + <b>Probe Key: </b> {currentProbe["key"]?.toString()} + </span> + </div> + } + submitButtonText={"Close"} + submitButtonType={ButtonStyleType.NORMAL} + onSubmit={async () => { + setShowKeyModal(false); + }} + /> + ) : ( + <></> + )} + </> + </Fragment> + ); }; export default ProbePage; diff --git a/Dashboard/src/Pages/Settings/ProjectSettings.tsx b/Dashboard/src/Pages/Settings/ProjectSettings.tsx index aee95e4776..6bcc49b42a 100644 --- a/Dashboard/src/Pages/Settings/ProjectSettings.tsx +++ b/Dashboard/src/Pages/Settings/ProjectSettings.tsx @@ -1,63 +1,63 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Project from 'Model/Models/Project'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Project from "Model/Models/Project"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - {/* Project Settings View */} - <CardModelDetail - name="Project Details" - cardProps={{ - title: 'Project Details', - description: 'Here are more details for this Project.', - }} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Project Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Project Name', - validation: { - minLength: 2, - }, - }, - ]} - onSaveSuccess={() => { - Navigation.reload(); - }} - modelDetailProps={{ - modelType: Project, - id: 'model-detail-project', - fields: [ - { - field: { - _id: true, - }, - title: 'Project ID', - }, - { - field: { - name: true, - }, - title: 'Project Name', - }, - ], - modelId: DashboardNavigation.getProjectId()!, - }} - /> - </Fragment> - ); + return ( + <Fragment> + {/* Project Settings View */} + <CardModelDetail + name="Project Details" + cardProps={{ + title: "Project Details", + description: "Here are more details for this Project.", + }} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + title: "Project Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Project Name", + validation: { + minLength: 2, + }, + }, + ]} + onSaveSuccess={() => { + Navigation.reload(); + }} + modelDetailProps={{ + modelType: Project, + id: "model-detail-project", + fields: [ + { + field: { + _id: true, + }, + title: "Project ID", + }, + { + field: { + name: true, + }, + title: "Project Name", + }, + ], + modelId: DashboardNavigation.getProjectId()!, + }} + /> + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/Settings/SSO.tsx b/Dashboard/src/Pages/Settings/SSO.tsx index 2698360bf3..b48589004a 100644 --- a/Dashboard/src/Pages/Settings/SSO.tsx +++ b/Dashboard/src/Pages/Settings/SSO.tsx @@ -1,389 +1,371 @@ -import TeamsElement from '../../Components/Team/TeamsElement'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import URL from 'Common/Types/API/URL'; -import DigestMethod from 'Common/Types/SSO/DigestMethod'; -import SignatureMethod from 'Common/Types/SSO/SignatureMethod'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Card from 'CommonUI/src/Components/Card/Card'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Link from 'CommonUI/src/Components/Link/Link'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; +import TeamsElement from "../../Components/Team/TeamsElement"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import URL from "Common/Types/API/URL"; +import DigestMethod from "Common/Types/SSO/DigestMethod"; +import SignatureMethod from "Common/Types/SSO/SignatureMethod"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Card from "CommonUI/src/Components/Card/Card"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Link from "CommonUI/src/Components/Link/Link"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; import { - DASHBOARD_URL, - HOST, - HTTP_PROTOCOL, - IDENTITY_URL, -} from 'CommonUI/src/Config'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Project from 'Model/Models/Project'; -import ProjectSSO from 'Model/Models/ProjectSso'; -import Team from 'Model/Models/Team'; + DASHBOARD_URL, + HOST, + HTTP_PROTOCOL, + IDENTITY_URL, +} from "CommonUI/src/Config"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Project from "Model/Models/Project"; +import ProjectSSO from "Model/Models/ProjectSso"; +import Team from "Model/Models/Team"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const SSOPage: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const [showSingleSignOnUrlId, setShowSingleSignOnUrlId] = - useState<string>(''); - return ( - <Fragment> - <> - <Banner - openInNewTab={true} - title="Need help with configuring SSO?" - description="Watch this 10 minute video which will help you get set up" - link={URL.fromString('https://youtu.be/tq4WRgxbIwk')} - /> + const [showSingleSignOnUrlId, setShowSingleSignOnUrlId] = + useState<string>(""); + return ( + <Fragment> + <> + <Banner + openInNewTab={true} + title="Need help with configuring SSO?" + description="Watch this 10 minute video which will help you get set up" + link={URL.fromString("https://youtu.be/tq4WRgxbIwk")} + /> - <ModelTable<ProjectSSO> - modelType={ProjectSSO} - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - }} - id="sso-table" - name="Settings > Project SSO" - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Single Sign On (SSO)', - description: - 'Single sign-on is an authentication scheme that allows a user to log in with a single ID to any of several related, yet independent, software systems.', - }} - formSteps={[ - { - title: 'Baisc Info', - id: 'basic', - }, - { - title: 'Sign On', - id: 'sign-on', - }, - { - title: 'Certificate', - id: 'certificate', - }, - { - title: 'More', - id: 'more', - }, - ]} - noItemsMessage={'No SSO configuration found.'} - viewPageRoute={Navigation.getCurrentRoute()} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - description: 'Friendly name to help you remember.', - placeholder: 'Okta', - validation: { - minLength: 2, - }, - stepId: 'basic', - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - description: - 'Friendly description to help you remember.', - placeholder: 'Sign in with Okta', - validation: { - minLength: 2, - }, - stepId: 'basic', - }, - { - field: { - signOnURL: true, - }, - title: 'Sign On URL', - fieldType: FormFieldSchemaType.URL, - required: true, - description: - 'Members will be forwarded here when signing in to your organization', - placeholder: - 'https://yourapp.example.com/apps/appId', - stepId: 'sign-on', - }, - { - field: { - issuerURL: true, - }, - title: 'Issuer', - description: - 'Typically a unique URL generated by your SAML identity provider', - fieldType: FormFieldSchemaType.URL, - required: true, - placeholder: 'https://example.com', - stepId: 'sign-on', - }, - { - field: { - publicCertificate: true, - }, - title: 'Public Certificate', - description: 'Paste in your x509 certificate here.', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Paste in your x509 certificate here.', - stepId: 'certificate', - }, - { - field: { - signatureMethod: true, - }, - title: 'Signature Method', - description: - 'If you do not know what this is, please leave this to RSA-SHA256', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - SignatureMethod - ), - required: true, - placeholder: 'RSA-SHA256', - stepId: 'certificate', - }, - { - field: { - digestMethod: true, - }, - title: 'Digest Method', - description: - 'If you do not know what this is, please leave this to SHA256', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - DigestMethod - ), - required: true, - placeholder: 'SHA256', - stepId: 'certificate', - }, - { - field: { - isEnabled: true, - }, - description: - 'You can test this first, before enabling it. To test, please save the config.', - title: 'Enabled', - fieldType: FormFieldSchemaType.Toggle, - stepId: 'more', - }, - { - field: { - teams: true, - }, - title: 'Teams', - description: - 'Add users to these teams when they sign up', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - required: true, - placeholder: 'Select Teams', - stepId: 'more', - }, - ]} - showRefreshButton={true} - actionButtons={[ - { - title: 'View SSO Config', - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - item: ProjectSSO, - onCompleteAction: VoidFunction - ) => { - setShowSingleSignOnUrlId( - (item['_id'] as string) || '' - ); - onCompleteAction(); - }, - }, - ]} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - isEnabled: true, - }, - title: 'Enabled', - type: FieldType.Boolean, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - teams: { - name: true, - _id: true, - projectId: true, - }, - }, - title: 'Add User to Team', - type: FieldType.Text, - getElement: (item: ProjectSSO): ReactElement => { - return ( - <TeamsElement teams={item['teams'] || []} /> - ); - }, - }, - { - field: { - isEnabled: true, - }, - title: 'Enabled', - type: FieldType.Boolean, - }, - ]} - /> + <ModelTable<ProjectSSO> + modelType={ProjectSSO} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + id="sso-table" + name="Settings > Project SSO" + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Single Sign On (SSO)", + description: + "Single sign-on is an authentication scheme that allows a user to log in with a single ID to any of several related, yet independent, software systems.", + }} + formSteps={[ + { + title: "Baisc Info", + id: "basic", + }, + { + title: "Sign On", + id: "sign-on", + }, + { + title: "Certificate", + id: "certificate", + }, + { + title: "More", + id: "more", + }, + ]} + noItemsMessage={"No SSO configuration found."} + viewPageRoute={Navigation.getCurrentRoute()} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + description: "Friendly name to help you remember.", + placeholder: "Okta", + validation: { + minLength: 2, + }, + stepId: "basic", + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + description: "Friendly description to help you remember.", + placeholder: "Sign in with Okta", + validation: { + minLength: 2, + }, + stepId: "basic", + }, + { + field: { + signOnURL: true, + }, + title: "Sign On URL", + fieldType: FormFieldSchemaType.URL, + required: true, + description: + "Members will be forwarded here when signing in to your organization", + placeholder: "https://yourapp.example.com/apps/appId", + stepId: "sign-on", + }, + { + field: { + issuerURL: true, + }, + title: "Issuer", + description: + "Typically a unique URL generated by your SAML identity provider", + fieldType: FormFieldSchemaType.URL, + required: true, + placeholder: "https://example.com", + stepId: "sign-on", + }, + { + field: { + publicCertificate: true, + }, + title: "Public Certificate", + description: "Paste in your x509 certificate here.", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Paste in your x509 certificate here.", + stepId: "certificate", + }, + { + field: { + signatureMethod: true, + }, + title: "Signature Method", + description: + "If you do not know what this is, please leave this to RSA-SHA256", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(SignatureMethod), + required: true, + placeholder: "RSA-SHA256", + stepId: "certificate", + }, + { + field: { + digestMethod: true, + }, + title: "Digest Method", + description: + "If you do not know what this is, please leave this to SHA256", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(DigestMethod), + required: true, + placeholder: "SHA256", + stepId: "certificate", + }, + { + field: { + isEnabled: true, + }, + description: + "You can test this first, before enabling it. To test, please save the config.", + title: "Enabled", + fieldType: FormFieldSchemaType.Toggle, + stepId: "more", + }, + { + field: { + teams: true, + }, + title: "Teams", + description: "Add users to these teams when they sign up", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + required: true, + placeholder: "Select Teams", + stepId: "more", + }, + ]} + showRefreshButton={true} + actionButtons={[ + { + title: "View SSO Config", + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async ( + item: ProjectSSO, + onCompleteAction: VoidFunction, + ) => { + setShowSingleSignOnUrlId((item["_id"] as string) || ""); + onCompleteAction(); + }, + }, + ]} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + isEnabled: true, + }, + title: "Enabled", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + teams: { + name: true, + _id: true, + projectId: true, + }, + }, + title: "Add User to Team", + type: FieldType.Text, + getElement: (item: ProjectSSO): ReactElement => { + return <TeamsElement teams={item["teams"] || []} />; + }, + }, + { + field: { + isEnabled: true, + }, + title: "Enabled", + type: FieldType.Boolean, + }, + ]} + /> - <Card - title={`Test Single Sign On (SSO)`} - description={ - <span> - Here's a link which will help you test SSO - integration before you force it on your - organization:{' '} - <Link - openInNewTab={true} - to={URL.fromString( - `${DASHBOARD_URL.toString()}/${DashboardNavigation.getProjectId()?.toString()}/sso` - )} - > - <span>{`${DASHBOARD_URL.toString()}/${DashboardNavigation.getProjectId()?.toString()}/sso`}</span> - </Link> - </span> - } - /> - - {/* API Key View */} - <CardModelDetail - name="SSO Settings" - editButtonText={'Edit Settings'} - cardProps={{ - title: 'SSO Settings', - description: 'Configure settings for SSO.', - }} - isEditable={true} - formFields={[ - { - field: { - requireSsoForLogin: true, - }, - title: 'Force SSO for Login', - description: - 'Please test SSO before you you enable this feature. If SSO is not tested properly then you will be locked out of the project.', - fieldType: FormFieldSchemaType.Toggle, - }, - ]} - modelDetailProps={{ - modelType: Project, - id: 'sso-settings', - fields: [ - { - field: { - requireSsoForLogin: true, - }, - fieldType: FieldType.Boolean, - title: 'Force SSO for Login', - description: - 'Please test SSO before you enable this feature. If SSO is not tested properly then you will be locked out of the project.', - }, - ], - modelId: DashboardNavigation.getProjectId()!, - }} - /> - - {showSingleSignOnUrlId && ( - <ConfirmModal - title={`SSO Configuration`} - description={ - <div> - <div> - <div className="font-semibold"> - Identifier (Entity ID):{' '} - </div> - - <div>{`${HTTP_PROTOCOL}${HOST}/${props.currentProject?._id}/${showSingleSignOnUrlId}`}</div> - <br /> - </div> - <div> - <div className="font-semibold"> - Reply URL (Assertion Consumer Service - URL): - </div> - <div> - {`${URL.fromString( - IDENTITY_URL.toString() - ).addRoute( - `/idp-login/${props.currentProject?._id}/${showSingleSignOnUrlId}` - )}`} - </div> - <br /> - </div> - </div> - } - submitButtonText={'Close'} - onSubmit={() => { - setShowSingleSignOnUrlId(''); - }} - submitButtonType={ButtonStyleType.NORMAL} - /> + <Card + title={`Test Single Sign On (SSO)`} + description={ + <span> + Here's a link which will help you test SSO integration before + you force it on your organization:{" "} + <Link + openInNewTab={true} + to={URL.fromString( + `${DASHBOARD_URL.toString()}/${DashboardNavigation.getProjectId()?.toString()}/sso`, )} - </> - </Fragment> - ); + > + <span>{`${DASHBOARD_URL.toString()}/${DashboardNavigation.getProjectId()?.toString()}/sso`}</span> + </Link> + </span> + } + /> + + {/* API Key View */} + <CardModelDetail + name="SSO Settings" + editButtonText={"Edit Settings"} + cardProps={{ + title: "SSO Settings", + description: "Configure settings for SSO.", + }} + isEditable={true} + formFields={[ + { + field: { + requireSsoForLogin: true, + }, + title: "Force SSO for Login", + description: + "Please test SSO before you you enable this feature. If SSO is not tested properly then you will be locked out of the project.", + fieldType: FormFieldSchemaType.Toggle, + }, + ]} + modelDetailProps={{ + modelType: Project, + id: "sso-settings", + fields: [ + { + field: { + requireSsoForLogin: true, + }, + fieldType: FieldType.Boolean, + title: "Force SSO for Login", + description: + "Please test SSO before you enable this feature. If SSO is not tested properly then you will be locked out of the project.", + }, + ], + modelId: DashboardNavigation.getProjectId()!, + }} + /> + + {showSingleSignOnUrlId && ( + <ConfirmModal + title={`SSO Configuration`} + description={ + <div> + <div> + <div className="font-semibold">Identifier (Entity ID): </div> + + <div>{`${HTTP_PROTOCOL}${HOST}/${props.currentProject?._id}/${showSingleSignOnUrlId}`}</div> + <br /> + </div> + <div> + <div className="font-semibold"> + Reply URL (Assertion Consumer Service URL): + </div> + <div> + {`${URL.fromString(IDENTITY_URL.toString()).addRoute( + `/idp-login/${props.currentProject?._id}/${showSingleSignOnUrlId}`, + )}`} + </div> + <br /> + </div> + </div> + } + submitButtonText={"Close"} + onSubmit={() => { + setShowSingleSignOnUrlId(""); + }} + submitButtonType={ButtonStyleType.NORMAL} + /> + )} + </> + </Fragment> + ); }; export default SSOPage; diff --git a/Dashboard/src/Pages/Settings/ScheduledMaintenanceCusomFields.tsx b/Dashboard/src/Pages/Settings/ScheduledMaintenanceCusomFields.tsx index 2d592e2c1c..1a3b018025 100644 --- a/Dashboard/src/Pages/Settings/ScheduledMaintenanceCusomFields.tsx +++ b/Dashboard/src/Pages/Settings/ScheduledMaintenanceCusomFields.tsx @@ -1,18 +1,18 @@ -import PageComponentProps from '../PageComponentProps'; -import CustomFieldsPageBase from './Base/CustomFieldsPageBase'; -import ScheduledMaintenanceCustomField from 'Model/Models/ScheduledMaintenanceCustomField'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import CustomFieldsPageBase from "./Base/CustomFieldsPageBase"; +import ScheduledMaintenanceCustomField from "Model/Models/ScheduledMaintenanceCustomField"; +import React, { FunctionComponent, ReactElement } from "react"; const ScheduledMaintenanceCustomFields: FunctionComponent< - PageComponentProps + PageComponentProps > = (props: PageComponentProps): ReactElement => { - return ( - <CustomFieldsPageBase - {...props} - title="Scheduled Maintenance Custom Fields" - modelType={ScheduledMaintenanceCustomField} - /> - ); + return ( + <CustomFieldsPageBase + {...props} + title="Scheduled Maintenance Custom Fields" + modelType={ScheduledMaintenanceCustomField} + /> + ); }; export default ScheduledMaintenanceCustomFields; diff --git a/Dashboard/src/Pages/Settings/ScheduledMaintenanceNoteTemplateView.tsx b/Dashboard/src/Pages/Settings/ScheduledMaintenanceNoteTemplateView.tsx index ff578ac483..10b4c8ef64 100644 --- a/Dashboard/src/Pages/Settings/ScheduledMaintenanceNoteTemplateView.tsx +++ b/Dashboard/src/Pages/Settings/ScheduledMaintenanceNoteTemplateView.tsx @@ -1,141 +1,139 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ScheduledMaintenanceNoteTemplate from 'Model/Models/ScheduledMaintenanceNoteTemplate'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ScheduledMaintenanceNoteTemplate from "Model/Models/ScheduledMaintenanceNoteTemplate"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const TeamView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - {/* ScheduledMaintenance View */} - <CardModelDetail - name="Basic Details" - cardProps={{ - title: 'Basic Details', - description: - 'Here are more details for this scheduled maintenance template.', - }} - isEditable={true} - editButtonText="Edit Details" - formFields={[ - { - field: { - templateName: true, - }, - title: 'Template Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Template Name', - validation: { - minLength: 2, - }, - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Template Description', - validation: { - minLength: 2, - }, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: ScheduledMaintenanceNoteTemplate, - id: 'model-detail-scheduled-maintenances', - fields: [ - { - field: { - _id: true, - }, - title: 'Scheduled Maintenance Note Template ID', - fieldType: FieldType.ObjectID, - }, - { - field: { - templateName: true, - }, - title: 'Template Name', - fieldType: FieldType.Text, - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - fieldType: FieldType.Text, - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + {/* ScheduledMaintenance View */} + <CardModelDetail + name="Basic Details" + cardProps={{ + title: "Basic Details", + description: + "Here are more details for this scheduled maintenance template.", + }} + isEditable={true} + editButtonText="Edit Details" + formFields={[ + { + field: { + templateName: true, + }, + title: "Template Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Template Name", + validation: { + minLength: 2, + }, + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Template Description", + validation: { + minLength: 2, + }, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: ScheduledMaintenanceNoteTemplate, + id: "model-detail-scheduled-maintenances", + fields: [ + { + field: { + _id: true, + }, + title: "Scheduled Maintenance Note Template ID", + fieldType: FieldType.ObjectID, + }, + { + field: { + templateName: true, + }, + title: "Template Name", + fieldType: FieldType.Text, + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + fieldType: FieldType.Text, + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail - name="Note Template" - editButtonText="Edit Note Template" - cardProps={{ - title: 'Note Template', - description: 'Here is the note template.', - }} - isEditable={true} - formFields={[ - { - field: { - note: true, - }, - title: 'Note', - fieldType: FormFieldSchemaType.Markdown, - required: true, - validation: { - minLength: 2, - }, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: ScheduledMaintenanceNoteTemplate, - id: 'model-detail-scheduled-maintenances', - fields: [ - { - field: { - note: true, - }, - title: 'Note Template', - fieldType: FieldType.Markdown, - }, - ], - modelId: modelId, - }} - /> + <CardModelDetail + name="Note Template" + editButtonText="Edit Note Template" + cardProps={{ + title: "Note Template", + description: "Here is the note template.", + }} + isEditable={true} + formFields={[ + { + field: { + note: true, + }, + title: "Note", + fieldType: FormFieldSchemaType.Markdown, + required: true, + validation: { + minLength: 2, + }, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: ScheduledMaintenanceNoteTemplate, + id: "model-detail-scheduled-maintenances", + fields: [ + { + field: { + note: true, + }, + title: "Note Template", + fieldType: FieldType.Markdown, + }, + ], + modelId: modelId, + }} + /> - <ModelDelete - modelType={ScheduledMaintenanceNoteTemplate} - modelId={Navigation.getLastParamAsObjectID()} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[ - PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES - ] as Route - ); - }} - /> - </Fragment> - ); + <ModelDelete + modelType={ScheduledMaintenanceNoteTemplate} + modelId={Navigation.getLastParamAsObjectID()} + onDeleteSuccess={() => { + Navigation.navigate( + RouteMap[PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES] as Route, + ); + }} + /> + </Fragment> + ); }; export default TeamView; diff --git a/Dashboard/src/Pages/Settings/ScheduledMaintenanceNoteTemplates.tsx b/Dashboard/src/Pages/Settings/ScheduledMaintenanceNoteTemplates.tsx index dc1c89e8e4..9ad246cab1 100644 --- a/Dashboard/src/Pages/Settings/ScheduledMaintenanceNoteTemplates.tsx +++ b/Dashboard/src/Pages/Settings/ScheduledMaintenanceNoteTemplates.tsx @@ -1,122 +1,123 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import ScheduledMaintenanceNoteTemplate from 'Model/Models/ScheduledMaintenanceNoteTemplate'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import ScheduledMaintenanceNoteTemplate from "Model/Models/ScheduledMaintenanceNoteTemplate"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ScheduledMaintenanceNoteTemplates: FunctionComponent< - PageComponentProps + PageComponentProps > = (props: PageComponentProps): ReactElement => { - return ( - <Fragment> - <ModelTable<ScheduledMaintenanceNoteTemplate> - modelType={ScheduledMaintenanceNoteTemplate} - id="incident-templates-table" - name="Settings > Scheduled Maintenance Templates" - isDeleteable={false} - isEditable={false} - isCreateable={true} - isViewable={true} - cardProps={{ - title: 'Public or Private Note Templates for Scheduled Maintenance Events', - description: - 'Here is a list of all the public and private note templates for scheduled maintenance.', - }} - noItemsMessage={'No note templates found.'} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - showViewIdButton={true} - formSteps={[ - { - title: 'Template Info', - id: 'template-info', - }, - { - title: 'Note Details', - id: 'note-details', - }, - ]} - formFields={[ - { - field: { - templateName: true, - }, - title: 'Template Name', - fieldType: FormFieldSchemaType.Text, - stepId: 'template-info', - required: true, - placeholder: 'Template Name', - validation: { - minLength: 2, - }, - }, - { - field: { - templateDescription: true, - }, - title: 'Template Description', - fieldType: FormFieldSchemaType.LongText, - stepId: 'template-info', - required: true, - placeholder: 'Template Description', - validation: { - minLength: 2, - }, - }, - { - field: { - note: true, - }, - title: 'Public or Private note template.', - fieldType: FormFieldSchemaType.Markdown, - stepId: 'note-details', - required: true, - validation: { - minLength: 2, - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} - filters={[ - { - field: { - templateName: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - templateDescription: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - templateName: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - templateDescription: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<ScheduledMaintenanceNoteTemplate> + modelType={ScheduledMaintenanceNoteTemplate} + id="incident-templates-table" + name="Settings > Scheduled Maintenance Templates" + isDeleteable={false} + isEditable={false} + isCreateable={true} + isViewable={true} + cardProps={{ + title: + "Public or Private Note Templates for Scheduled Maintenance Events", + description: + "Here is a list of all the public and private note templates for scheduled maintenance.", + }} + noItemsMessage={"No note templates found."} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + showViewIdButton={true} + formSteps={[ + { + title: "Template Info", + id: "template-info", + }, + { + title: "Note Details", + id: "note-details", + }, + ]} + formFields={[ + { + field: { + templateName: true, + }, + title: "Template Name", + fieldType: FormFieldSchemaType.Text, + stepId: "template-info", + required: true, + placeholder: "Template Name", + validation: { + minLength: 2, + }, + }, + { + field: { + templateDescription: true, + }, + title: "Template Description", + fieldType: FormFieldSchemaType.LongText, + stepId: "template-info", + required: true, + placeholder: "Template Description", + validation: { + minLength: 2, + }, + }, + { + field: { + note: true, + }, + title: "Public or Private note template.", + fieldType: FormFieldSchemaType.Markdown, + stepId: "note-details", + required: true, + validation: { + minLength: 2, + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} + filters={[ + { + field: { + templateName: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + templateDescription: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + templateName: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + templateDescription: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + /> + </Fragment> + ); }; export default ScheduledMaintenanceNoteTemplates; diff --git a/Dashboard/src/Pages/Settings/ScheduledMaintenanceState.tsx b/Dashboard/src/Pages/Settings/ScheduledMaintenanceState.tsx index c3d4dc8513..7270375c46 100644 --- a/Dashboard/src/Pages/Settings/ScheduledMaintenanceState.tsx +++ b/Dashboard/src/Pages/Settings/ScheduledMaintenanceState.tsx @@ -1,159 +1,155 @@ -import PageComponentProps from '../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import Color from 'Common/Types/Color'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import { ShowAs } from 'CommonUI/src/Components/ModelTable/BaseModelTable'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import Color from "Common/Types/Color"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import { ShowAs } from "CommonUI/src/Components/ModelTable/BaseModelTable"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ScheduledMaintenancesPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<ScheduledMaintenanceState> - modelType={ScheduledMaintenanceState} - id="ScheduledMaintenance-state-table" - name="Settings > Scheduled Maintenance State" - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Scheduled Maintenance State', - description: - 'Scheduled Maintenance events have multiple states like - scheduled, ongoing and completed. You can more states help you manage Scheduled Maintenance events here.', - }} - sortBy="order" - sortOrder={SortOrder.Ascending} - onBeforeDelete={( - item: ScheduledMaintenanceState - ): Promise<ScheduledMaintenanceState> => { - if (item.isScheduledState) { - throw new BadDataException( - 'This Scheduled Maintenance cannot be deleted because its the scheduled state of for this project. Scheduled, Ongoing, Ended, Completed states cannot be deleted.' - ); - } + return ( + <Fragment> + <ModelTable<ScheduledMaintenanceState> + modelType={ScheduledMaintenanceState} + id="ScheduledMaintenance-state-table" + name="Settings > Scheduled Maintenance State" + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Scheduled Maintenance State", + description: + "Scheduled Maintenance events have multiple states like - scheduled, ongoing and completed. You can more states help you manage Scheduled Maintenance events here.", + }} + sortBy="order" + sortOrder={SortOrder.Ascending} + onBeforeDelete={( + item: ScheduledMaintenanceState, + ): Promise<ScheduledMaintenanceState> => { + if (item.isScheduledState) { + throw new BadDataException( + "This Scheduled Maintenance cannot be deleted because its the scheduled state of for this project. Scheduled, Ongoing, Ended, Completed states cannot be deleted.", + ); + } - if (item.isOngoingState) { - throw new BadDataException( - 'This Scheduled Maintenance cannot be deleted because its the ongoing state of for this project. Scheduled, Ongoing, Ended, Completed states cannot be deleted.' - ); - } + if (item.isOngoingState) { + throw new BadDataException( + "This Scheduled Maintenance cannot be deleted because its the ongoing state of for this project. Scheduled, Ongoing, Ended, Completed states cannot be deleted.", + ); + } - if (item.isResolvedState) { - throw new BadDataException( - 'This Scheduled Maintenance cannot be deleted because its the resolved state of for this project. Scheduled, Ongoing, Ended, Completed states cannot be deleted.' - ); - } + if (item.isResolvedState) { + throw new BadDataException( + "This Scheduled Maintenance cannot be deleted because its the resolved state of for this project. Scheduled, Ongoing, Ended, Completed states cannot be deleted.", + ); + } - if (item.isEndedState) { - throw new BadDataException( - 'This Scheduled Maintenance cannot be deleted because its the ended state of for this project. Scheduled, Ongoing, Ended, Completed states cannot be deleted.' - ); - } + if (item.isEndedState) { + throw new BadDataException( + "This Scheduled Maintenance cannot be deleted because its the ended state of for this project. Scheduled, Ongoing, Ended, Completed states cannot be deleted.", + ); + } - return Promise.resolve(item); - }} - selectMoreFields={{ - color: true, - isScheduledState: true, - isOngoingState: true, - isResolvedState: true, - isEndedState: true, - order: true, - }} - filters={[]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - getElement: ( - item: ScheduledMaintenanceState - ): ReactElement => { - return ( - <Pill - color={item['color'] as Color} - text={item['name'] as string} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, + return Promise.resolve(item); + }} + selectMoreFields={{ + color: true, + isScheduledState: true, + isOngoingState: true, + isResolvedState: true, + isEndedState: true, + order: true, + }} + filters={[]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + getElement: (item: ScheduledMaintenanceState): ReactElement => { + return ( + <Pill + color={item["color"] as Color} + text={item["name"] as string} + /> + ); + }, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, - getElement: ( - item: ScheduledMaintenanceState - ): ReactElement => { - return ( - <div> - <p>{`${item['description']}`}</p> - <p className="text-xs text-gray-400"> - ID: {`${item['_id']}`} - </p> - </div> - ); - }, - }, - ]} - noItemsMessage={'No Scheduled Maintenance state found.'} - viewPageRoute={Navigation.getCurrentRoute()} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Monitoring', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: - 'This Scheduled Maintenance state happens when the event is been monitored', - }, - { - field: { - color: true, - }, - title: 'Color', - fieldType: FormFieldSchemaType.Color, - required: true, - placeholder: - 'Please select color for this Scheduled Maintenance state.', - }, - ]} - showRefreshButton={true} - showAs={ShowAs.OrderedStatesList} - orderedStatesListProps={{ - titleField: 'name', - descriptionField: 'description', - orderField: 'order', - shouldAddItemInTheEnd: true, - }} - /> - </Fragment> - ); + getElement: (item: ScheduledMaintenanceState): ReactElement => { + return ( + <div> + <p>{`${item["description"]}`}</p> + <p className="text-xs text-gray-400"> + ID: {`${item["_id"]}`} + </p> + </div> + ); + }, + }, + ]} + noItemsMessage={"No Scheduled Maintenance state found."} + viewPageRoute={Navigation.getCurrentRoute()} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Monitoring", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: + "This Scheduled Maintenance state happens when the event is been monitored", + }, + { + field: { + color: true, + }, + title: "Color", + fieldType: FormFieldSchemaType.Color, + required: true, + placeholder: + "Please select color for this Scheduled Maintenance state.", + }, + ]} + showRefreshButton={true} + showAs={ShowAs.OrderedStatesList} + orderedStatesListProps={{ + titleField: "name", + descriptionField: "description", + orderField: "order", + shouldAddItemInTheEnd: true, + }} + /> + </Fragment> + ); }; export default ScheduledMaintenancesPage; diff --git a/Dashboard/src/Pages/Settings/SideMenu.tsx b/Dashboard/src/Pages/Settings/SideMenu.tsx index eb4fd12ca4..7b8fab6347 100644 --- a/Dashboard/src/Pages/Settings/SideMenu.tsx +++ b/Dashboard/src/Pages/Settings/SideMenu.tsx @@ -1,354 +1,331 @@ -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 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import { BILLING_ENABLED } from 'CommonUI/src/Config'; -import React, { ReactElement } from 'react'; +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 "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import { BILLING_ENABLED } from "CommonUI/src/Config"; +import React, { ReactElement } from "react"; const DashboardSideMenu: () => JSX.Element = (): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS] as Route - ), - }} - icon={IconProp.Folder} - /> - <SideMenuItem - link={{ - title: 'Labels', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_LABELS] as Route - ), - }} - icon={IconProp.Label} - /> - </SideMenuSection> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Project", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS] as Route, + ), + }} + icon={IconProp.Folder} + /> + <SideMenuItem + link={{ + title: "Labels", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_LABELS] as Route, + ), + }} + icon={IconProp.Label} + /> + </SideMenuSection> - <SideMenuSection title="Monitors"> - <SideMenuItem - link={{ - title: 'Monitor Status', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_MONITORS_STATUS] as Route - ), - }} - icon={IconProp.AltGlobe} - /> - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS - ] as Route - ), - }} - icon={IconProp.TableCells} - /> + <SideMenuSection title="Monitors"> + <SideMenuItem + link={{ + title: "Monitor Status", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_MONITORS_STATUS] as Route, + ), + }} + icon={IconProp.AltGlobe} + /> + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS] as Route, + ), + }} + icon={IconProp.TableCells} + /> - <SideMenuItem - link={{ - title: 'Secrets', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_MONITOR_SECRETS] as Route - ), - }} - icon={IconProp.Lock} - /> - </SideMenuSection> - <SideMenuSection title="Status Pages"> - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS - ] as Route - ), - }} - icon={IconProp.TableCells} - /> - </SideMenuSection> - <SideMenuSection title="On-Call Policy"> - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap - .SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS - ] as Route - ), - }} - icon={IconProp.TableCells} - /> - </SideMenuSection> - <SideMenuSection title="Incidents"> - <SideMenuItem - link={{ - title: 'Incident State', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_INCIDENTS_STATE] as Route - ), - }} - icon={IconProp.ArrowCircleRight} - /> - <SideMenuItem - link={{ - title: 'Incident Severity', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_INCIDENTS_SEVERITY - ] as Route - ), - }} - icon={IconProp.Alert} - /> - <SideMenuItem - link={{ - title: 'Incident Templates', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_INCIDENT_TEMPLATES - ] as Route - ), - }} - icon={IconProp.Template} - /> - <SideMenuItem - link={{ - title: 'Note Templates', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES - ] as Route - ), - }} - icon={IconProp.Pencil} - /> - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS - ] as Route - ), - }} - icon={IconProp.TableCells} - /> - {/* <SideMenuItem + <SideMenuItem + link={{ + title: "Secrets", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_MONITOR_SECRETS] as Route, + ), + }} + icon={IconProp.Lock} + /> + </SideMenuSection> + <SideMenuSection title="Status Pages"> + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS] as Route, + ), + }} + icon={IconProp.TableCells} + /> + </SideMenuSection> + <SideMenuSection title="On-Call Policy"> + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS + ] as Route, + ), + }} + icon={IconProp.TableCells} + /> + </SideMenuSection> + <SideMenuSection title="Incidents"> + <SideMenuItem + link={{ + title: "Incident State", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_INCIDENTS_STATE] as Route, + ), + }} + icon={IconProp.ArrowCircleRight} + /> + <SideMenuItem + link={{ + title: "Incident Severity", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_INCIDENTS_SEVERITY] as Route, + ), + }} + icon={IconProp.Alert} + /> + <SideMenuItem + link={{ + title: "Incident Templates", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_INCIDENT_TEMPLATES] as Route, + ), + }} + icon={IconProp.Template} + /> + <SideMenuItem + link={{ + title: "Note Templates", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES] as Route, + ), + }} + icon={IconProp.Pencil} + /> + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS] as Route, + ), + }} + icon={IconProp.TableCells} + /> + {/* <SideMenuItem link={{ title: 'Incident Templates', to: new Route('/:projectSlug/home'), }} icon={IconProp.TextFile} /> */} - </SideMenuSection> - <SideMenuSection title="Scheduled Maintenance"> - <SideMenuItem - link={{ - title: 'Event State', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE - ] as Route - ), - }} - icon={IconProp.Clock} - /> - <SideMenuItem - link={{ - title: 'Note Templates', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap - .SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES - ] as Route - ), - }} - icon={IconProp.Pencil} - /> - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap - .SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS - ] as Route - ), - }} - icon={IconProp.TableCells} - /> - </SideMenuSection> - <SideMenuSection title="Team"> - <SideMenuItem - link={{ - title: 'Teams and Members', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_TEAMS] as Route - ), - }} - icon={IconProp.Team} - /> - </SideMenuSection> - <SideMenuSection title="Notifications"> - <SideMenuItem - link={{ - title: 'Notification Settings', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_NOTIFICATION_SETTINGS - ] as Route - ), - }} - icon={IconProp.Settings} - /> - <SideMenuItem - link={{ - title: 'SMS Logs', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_SMS_LOGS] as Route - ), - }} - icon={IconProp.SMS} - /> - <SideMenuItem - link={{ - title: 'Call Logs', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_CALL_LOGS] as Route - ), - }} - icon={IconProp.Call} - /> - <SideMenuItem - link={{ - title: 'Email Logs', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_EMAIL_LOGS] as Route - ), - }} - icon={IconProp.Email} - /> - </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Probes', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_PROBES] as Route - ), - }} - icon={IconProp.Signal} - /> - <SideMenuItem - link={{ - title: 'Domains', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_DOMAINS] as Route - ), - }} - icon={IconProp.Globe} - /> - <SideMenuItem - link={{ - title: 'API Keys', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_APIKEYS] as Route - ), - }} - icon={IconProp.Terminal} - /> - <SideMenuItem - link={{ - title: 'Feature Flags', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_FEATURE_FLAGS] as Route - ), - }} - icon={IconProp.Flag} - /> + </SideMenuSection> + <SideMenuSection title="Scheduled Maintenance"> + <SideMenuItem + link={{ + title: "Event State", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE] as Route, + ), + }} + icon={IconProp.Clock} + /> + <SideMenuItem + link={{ + title: "Note Templates", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES + ] as Route, + ), + }} + icon={IconProp.Pencil} + /> + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS + ] as Route, + ), + }} + icon={IconProp.TableCells} + /> + </SideMenuSection> + <SideMenuSection title="Team"> + <SideMenuItem + link={{ + title: "Teams and Members", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_TEAMS] as Route, + ), + }} + icon={IconProp.Team} + /> + </SideMenuSection> + <SideMenuSection title="Notifications"> + <SideMenuItem + link={{ + title: "Notification Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_NOTIFICATION_SETTINGS] as Route, + ), + }} + icon={IconProp.Settings} + /> + <SideMenuItem + link={{ + title: "SMS Logs", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_SMS_LOGS] as Route, + ), + }} + icon={IconProp.SMS} + /> + <SideMenuItem + link={{ + title: "Call Logs", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_CALL_LOGS] as Route, + ), + }} + icon={IconProp.Call} + /> + <SideMenuItem + link={{ + title: "Email Logs", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_EMAIL_LOGS] as Route, + ), + }} + icon={IconProp.Email} + /> + </SideMenuSection> + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Probes", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_PROBES] as Route, + ), + }} + icon={IconProp.Signal} + /> + <SideMenuItem + link={{ + title: "Domains", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_DOMAINS] as Route, + ), + }} + icon={IconProp.Globe} + /> + <SideMenuItem + link={{ + title: "API Keys", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_APIKEYS] as Route, + ), + }} + icon={IconProp.Terminal} + /> + <SideMenuItem + link={{ + title: "Feature Flags", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_FEATURE_FLAGS] as Route, + ), + }} + icon={IconProp.Flag} + /> - {/* <SideMenuItem + {/* <SideMenuItem link={{ title: 'SMS & Call Provider', to: new Route('/:projectSlug/home'), }} icon={IconProp.Call} /> */} - </SideMenuSection> - {BILLING_ENABLED ? ( - <SideMenuSection title="Billing and Invoices"> - <SideMenuItem - link={{ - title: 'Billing', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_BILLING] as Route - ), - }} - icon={IconProp.Billing} - /> - <SideMenuItem - link={{ - title: 'Usage History', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_USAGE_HISTORY - ] as Route - ), - }} - icon={IconProp.ChartBar} - /> - <SideMenuItem - link={{ - title: 'Invoices', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.SETTINGS_BILLING_INVOICES - ] as Route - ), - }} - icon={IconProp.TextFile} - /> - </SideMenuSection> - ) : ( - <></> - )} - <SideMenuSection title="Authentication Security"> - <SideMenuItem - link={{ - title: 'SSO', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_SSO] as Route - ), - }} - icon={IconProp.Lock} - /> - </SideMenuSection> - <SideMenuSection title="Danger Zone"> - <SideMenuItem - link={{ - title: 'Danger Zone', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.SETTINGS_DANGERZONE] as Route - ), - }} - icon={IconProp.Error} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + </SideMenuSection> + {BILLING_ENABLED ? ( + <SideMenuSection title="Billing and Invoices"> + <SideMenuItem + link={{ + title: "Billing", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_BILLING] as Route, + ), + }} + icon={IconProp.Billing} + /> + <SideMenuItem + link={{ + title: "Usage History", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_USAGE_HISTORY] as Route, + ), + }} + icon={IconProp.ChartBar} + /> + <SideMenuItem + link={{ + title: "Invoices", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_BILLING_INVOICES] as Route, + ), + }} + icon={IconProp.TextFile} + /> + </SideMenuSection> + ) : ( + <></> + )} + <SideMenuSection title="Authentication Security"> + <SideMenuItem + link={{ + title: "SSO", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_SSO] as Route, + ), + }} + icon={IconProp.Lock} + /> + </SideMenuSection> + <SideMenuSection title="Danger Zone"> + <SideMenuItem + link={{ + title: "Danger Zone", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.SETTINGS_DANGERZONE] as Route, + ), + }} + icon={IconProp.Error} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Settings/SmsLog.tsx b/Dashboard/src/Pages/Settings/SmsLog.tsx index f5c5b7a0ea..3944b90c48 100644 --- a/Dashboard/src/Pages/Settings/SmsLog.tsx +++ b/Dashboard/src/Pages/Settings/SmsLog.tsx @@ -1,223 +1,211 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import { Green, Red } from 'Common/Types/BrandColors'; -import IconProp from 'Common/Types/Icon/IconProp'; -import SmsStatus from 'Common/Types/SmsStatus'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import Filter from 'CommonUI/src/Components/ModelFilter/Filter'; -import Column from 'CommonUI/src/Components/ModelTable/Column'; -import Columns from 'CommonUI/src/Components/ModelTable/Columns'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { BILLING_ENABLED } from 'CommonUI/src/Config'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import SmsLog from 'Model/Models/SmsLog'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import { Green, Red } from "Common/Types/BrandColors"; +import IconProp from "Common/Types/Icon/IconProp"; +import SmsStatus from "Common/Types/SmsStatus"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import Filter from "CommonUI/src/Components/ModelFilter/Filter"; +import Column from "CommonUI/src/Components/ModelTable/Column"; +import Columns from "CommonUI/src/Components/ModelTable/Columns"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { BILLING_ENABLED } from "CommonUI/src/Config"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import SmsLog from "Model/Models/SmsLog"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const SMSLogs: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [showViewSmsTextModal, setShowViewSmsTextModal] = - useState<boolean>(false); - const [smsText, setSmsText] = useState<string>(''); - const [smsModelTitle, setSmsModalTitle] = useState<string>(''); + const [showViewSmsTextModal, setShowViewSmsTextModal] = + useState<boolean>(false); + const [smsText, setSmsText] = useState<string>(""); + const [smsModelTitle, setSmsModalTitle] = useState<string>(""); - const filters: Array<Filter<SmsLog>> = [ - { - field: { - _id: true, + const filters: Array<Filter<SmsLog>> = [ + { + field: { + _id: true, + }, + title: "Log ID", + type: FieldType.ObjectID, + }, + + { + field: { + fromNumber: true, + }, + title: "From Number", + type: FieldType.Phone, + }, + { + field: { + toNumber: true, + }, + title: "To Number", + type: FieldType.Phone, + }, + { + field: { + createdAt: true, + }, + title: "Sent at", + type: FieldType.Date, + }, + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Dropdown, + filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum(SmsStatus), + }, + ]; + + const modelTableColumns: Columns<SmsLog> = [ + { + field: { + _id: true, + }, + title: "Log ID", + type: FieldType.Text, + }, + { + field: { + fromNumber: true, + }, + + title: "From Number", + type: FieldType.Phone, + }, + { + field: { + toNumber: true, + }, + + title: "To Number", + type: FieldType.Phone, + }, + { + field: { + createdAt: true, + }, + title: "Sent at", + type: FieldType.DateTime, + }, + + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Text, + getElement: (item: SmsLog): ReactElement => { + if (item["status"]) { + return ( + <Pill + isMinimal={false} + color={item["status"] === SmsStatus.Success ? Green : Red} + text={item["status"] as string} + /> + ); + } + + return <></>; + }, + }, + ]; + + if (BILLING_ENABLED) { + modelTableColumns.push({ + field: { + smsCostInUSDCents: true, + }, + title: "SMS Cost", + type: FieldType.USDCents, + } as Column<SmsLog>); + } + + return ( + <Fragment> + <> + <ModelTable<SmsLog> + modelType={SmsLog} + id="sms-logs-table" + isDeleteable={false} + isEditable={false} + isCreateable={false} + name="SMS Logs" + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + selectMoreFields={{ + smsText: true, + statusMessage: true, + }} + filters={filters} + actionButtons={[ + { + title: "View SMS Text", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.List, + onClick: async (item: SmsLog, onCompleteAction: VoidFunction) => { + setSmsText(item["smsText"] as string); + + setSmsModalTitle("SMS Text"); + setShowViewSmsTextModal(true); + + onCompleteAction(); + }, }, - title: 'Log ID', - type: FieldType.ObjectID, - }, + { + title: "View Status Message", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.Error, + onClick: async (item: SmsLog, onCompleteAction: VoidFunction) => { + setSmsText(item["statusMessage"] as string); - { - field: { - fromNumber: true, + setSmsModalTitle("Status Message"); + setShowViewSmsTextModal(true); + + onCompleteAction(); + }, }, - title: 'From Number', - type: FieldType.Phone, - }, - { - field: { - toNumber: true, - }, - title: 'To Number', - type: FieldType.Phone, - }, - { - field: { - createdAt: true, - }, - title: 'Sent at', - type: FieldType.Date, - }, - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Dropdown, - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum(SmsStatus), - }, - ]; + ]} + isViewable={false} + cardProps={{ + title: "SMS Logs", + description: + "Logs of all the SMS sent by this project in the last 30 days.", + }} + noItemsMessage={ + "Looks like no SMS is sent by this project in the last 30 days." + } + showRefreshButton={true} + columns={modelTableColumns} + /> - const modelTableColumns: Columns<SmsLog> = [ - { - field: { - _id: true, - }, - title: 'Log ID', - type: FieldType.Text, - }, - { - field: { - fromNumber: true, - }, - - title: 'From Number', - type: FieldType.Phone, - }, - { - field: { - toNumber: true, - }, - - title: 'To Number', - type: FieldType.Phone, - }, - { - field: { - createdAt: true, - }, - title: 'Sent at', - type: FieldType.DateTime, - }, - - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Text, - getElement: (item: SmsLog): ReactElement => { - if (item['status']) { - return ( - <Pill - isMinimal={false} - color={ - item['status'] === SmsStatus.Success - ? Green - : Red - } - text={item['status'] as string} - /> - ); - } - - return <></>; - }, - }, - ]; - - if (BILLING_ENABLED) { - modelTableColumns.push({ - field: { - smsCostInUSDCents: true, - }, - title: 'SMS Cost', - type: FieldType.USDCents, - } as Column<SmsLog>); - } - - return ( - <Fragment> - <> - <ModelTable<SmsLog> - modelType={SmsLog} - id="sms-logs-table" - isDeleteable={false} - isEditable={false} - isCreateable={false} - name="SMS Logs" - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - }} - selectMoreFields={{ - smsText: true, - statusMessage: true, - }} - filters={filters} - actionButtons={[ - { - title: 'View SMS Text', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.List, - onClick: async ( - item: SmsLog, - onCompleteAction: VoidFunction - ) => { - setSmsText(item['smsText'] as string); - - setSmsModalTitle('SMS Text'); - setShowViewSmsTextModal(true); - - onCompleteAction(); - }, - }, - { - title: 'View Status Message', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.Error, - onClick: async ( - item: SmsLog, - onCompleteAction: VoidFunction - ) => { - setSmsText(item['statusMessage'] as string); - - setSmsModalTitle('Status Message'); - setShowViewSmsTextModal(true); - - onCompleteAction(); - }, - }, - ]} - isViewable={false} - cardProps={{ - title: 'SMS Logs', - description: - 'Logs of all the SMS sent by this project in the last 30 days.', - }} - noItemsMessage={ - 'Looks like no SMS is sent by this project in the last 30 days.' - } - showRefreshButton={true} - columns={modelTableColumns} - /> - - {showViewSmsTextModal && ( - <ConfirmModal - title={smsModelTitle} - description={smsText} - onSubmit={() => { - setShowViewSmsTextModal(false); - }} - submitButtonText="Close" - submitButtonType={ButtonStyleType.NORMAL} - /> - )} - </> - </Fragment> - ); + {showViewSmsTextModal && ( + <ConfirmModal + title={smsModelTitle} + description={smsText} + onSubmit={() => { + setShowViewSmsTextModal(false); + }} + submitButtonText="Close" + submitButtonType={ButtonStyleType.NORMAL} + /> + )} + </> + </Fragment> + ); }; export default SMSLogs; diff --git a/Dashboard/src/Pages/Settings/StatusPageCustomFields.tsx b/Dashboard/src/Pages/Settings/StatusPageCustomFields.tsx index 20419e5737..cf3d81a3d2 100644 --- a/Dashboard/src/Pages/Settings/StatusPageCustomFields.tsx +++ b/Dashboard/src/Pages/Settings/StatusPageCustomFields.tsx @@ -1,18 +1,18 @@ -import PageComponentProps from '../PageComponentProps'; -import CustomFieldsPageBase from './Base/CustomFieldsPageBase'; -import StatusPageCustomField from 'Model/Models/StatusPageCustomField'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../PageComponentProps"; +import CustomFieldsPageBase from "./Base/CustomFieldsPageBase"; +import StatusPageCustomField from "Model/Models/StatusPageCustomField"; +import React, { FunctionComponent, ReactElement } from "react"; const StatusPageCustomFields: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <CustomFieldsPageBase - {...props} - title="Status Page Custom Fields" - modelType={StatusPageCustomField} - /> - ); + return ( + <CustomFieldsPageBase + {...props} + title="Status Page Custom Fields" + modelType={StatusPageCustomField} + /> + ); }; export default StatusPageCustomFields; diff --git a/Dashboard/src/Pages/Settings/TeamView.tsx b/Dashboard/src/Pages/Settings/TeamView.tsx index f47cca1ea7..41c1916214 100644 --- a/Dashboard/src/Pages/Settings/TeamView.tsx +++ b/Dashboard/src/Pages/Settings/TeamView.tsx @@ -1,455 +1,429 @@ -import LabelsElement from '../../Components/Label/Labels'; -import UserElement from '../../Components/User/User'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import ProjectUser from '../../Utils/ProjectUser'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { Green, Yellow } from 'Common/Types/BrandColors'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission, { PermissionHelper } from 'Common/Types/Permission'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import { FormProps } from 'CommonUI/src/Components/Forms/BasicForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import PermissionUtil from 'CommonUI/src/Utils/Permission'; -import Label from 'Model/Models/Label'; -import Team from 'Model/Models/Team'; -import TeamMember from 'Model/Models/TeamMember'; -import TeamPermission from 'Model/Models/TeamPermission'; -import User from 'Model/Models/User'; +import LabelsElement from "../../Components/Label/Labels"; +import UserElement from "../../Components/User/User"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import ProjectUser from "../../Utils/ProjectUser"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { Green, Yellow } from "Common/Types/BrandColors"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import Permission, { PermissionHelper } from "Common/Types/Permission"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import { FormProps } from "CommonUI/src/Components/Forms/BasicForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import PermissionUtil from "CommonUI/src/Utils/Permission"; +import Label from "Model/Models/Label"; +import Team from "Model/Models/Team"; +import TeamMember from "Model/Models/TeamMember"; +import TeamPermission from "Model/Models/TeamPermission"; +import User from "Model/Models/User"; import React, { - Fragment, - FunctionComponent, - MutableRefObject, - ReactElement, -} from 'react'; + Fragment, + FunctionComponent, + MutableRefObject, + ReactElement, +} from "react"; export enum PermissionType { - AllowPermissions = 'AllowPermissions', - BlockPermissions = 'BlockPermissions', + AllowPermissions = "AllowPermissions", + BlockPermissions = "BlockPermissions", } const TeamView: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - type GetTeamPermissionTable = (data: { - permissionType: PermissionType; - }) => ReactElement; + type GetTeamPermissionTable = (data: { + permissionType: PermissionType; + }) => ReactElement; - const getTeamPermissionTable: GetTeamPermissionTable = (data: { - permissionType: PermissionType; - }) => { - const { permissionType } = data; + const getTeamPermissionTable: GetTeamPermissionTable = (data: { + permissionType: PermissionType; + }) => { + const { permissionType } = data; - const formRef: MutableRefObject<FormProps<FormValues<TeamPermission>>> = - React.useRef< - FormProps<FormValues<TeamPermission>> - >() as MutableRefObject<FormProps<FormValues<TeamPermission>>>; + const formRef: MutableRefObject<FormProps<FormValues<TeamPermission>>> = + React.useRef<FormProps<FormValues<TeamPermission>>>() as MutableRefObject< + FormProps<FormValues<TeamPermission>> + >; - let tableTitle: string = 'Allow Permissions'; + let tableTitle: string = "Allow Permissions"; - if (permissionType === PermissionType.BlockPermissions) { - tableTitle = 'Block Permissions'; - } + if (permissionType === PermissionType.BlockPermissions) { + tableTitle = "Block Permissions"; + } - let tableDescription: string = - 'Here you can manage allow permissions for this team.'; + let tableDescription: string = + "Here you can manage allow permissions for this team."; - if (permissionType === PermissionType.BlockPermissions) { - tableDescription = - 'Here you can manage block permissions for this team. This will override any allow permissions set for this team.'; - } - - return ( - <ModelTable<TeamPermission> - modelType={TeamPermission} - id={'table-team-permission-' + permissionType} - isDeleteable={true} - isEditable={true} - isCreateable={true} - name={'Settings > Team > Permissions-' + permissionType} - isViewable={false} - createEditFromRef={formRef} - query={{ - teamId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - isBlockPermission: - permissionType === PermissionType.BlockPermissions, - }} - onBeforeCreate={( - item: TeamPermission - ): Promise<TeamPermission> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.teamId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - item.isBlockPermission = - permissionType === PermissionType.BlockPermissions; - return Promise.resolve(item); - }} - cardProps={{ - title: tableTitle, - description: tableDescription, - }} - noItemsMessage={'No permisisons created for this team so far.'} - formFields={[ - { - field: { - permission: true, - }, - onChange: async (_value: any): Promise<void> => { - await formRef.current.setFieldValue( - 'labels', - [], - true - ); - }, - title: 'Permission', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Permission', - dropdownOptions: - PermissionUtil.projectPermissionsAsDropdownOptions(), - }, - { - field: { - labels: true, - }, - title: 'Restrict to Labels', - description: - 'If you want to restrict this permission to specific labels, you can select them here. This is an optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - showIf: ( - values: FormValues<TeamPermission> - ): boolean => { - if (!values['permission']) { - return false; - } - - if ( - values['permission'] && - !PermissionHelper.isAccessControlPermission( - values['permission'] as Permission - ) - ) { - return false; - } - - return true; - }, - required: false, - placeholder: 'Labels', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - permission: true, - }, - type: FieldType.Text, - title: 'Permission', - }, - { - field: { - labels: { - name: true, - }, - }, - type: FieldType.EntityArray, - title: 'Restrict to Labels', - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - permission: true, - }, - title: 'Permission', - type: FieldType.Text, - - getElement: (item: TeamPermission): ReactElement => { - return ( - <p> - {PermissionHelper.getTitle( - item['permission'] as Permission - )} - </p> - ); - }, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Restrict to Labels', - type: FieldType.EntityArray, - - getElement: (item: TeamPermission): ReactElement => { - if ( - item && - item['permission'] && - !PermissionHelper.isAccessControlPermission( - item['permission'] as Permission - ) - ) { - return ( - <p> - Restriction by labels cannot be applied - to this permission. - </p> - ); - } - - if ( - !item['labels'] || - item['labels'].length === 0 - ) { - return ( - <p> - No restrictions has been applied to this - permission. - </p> - ); - } - - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> - ); - }; + if (permissionType === PermissionType.BlockPermissions) { + tableDescription = + "Here you can manage block permissions for this team. This will override any allow permissions set for this team."; + } return ( - <Fragment> - {/* API Key View */} - <CardModelDetail - name="Team Details" - cardProps={{ - title: 'Team Details', - description: 'Here are more details for this team.', - }} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Team Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Team Description', - }, - ]} - modelDetailProps={{ - modelType: Team, - id: 'model-detail-team', - fields: [ - { - field: { - _id: true, - }, - title: 'Team ID', - }, - { - field: { - name: true, - }, - title: 'Name', - }, - { - field: { - description: true, - }, - title: 'Description', - }, - ], - modelId: Navigation.getLastParamAsObjectID(), - }} - /> + <ModelTable<TeamPermission> + modelType={TeamPermission} + id={"table-team-permission-" + permissionType} + isDeleteable={true} + isEditable={true} + isCreateable={true} + name={"Settings > Team > Permissions-" + permissionType} + isViewable={false} + createEditFromRef={formRef} + query={{ + teamId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + isBlockPermission: permissionType === PermissionType.BlockPermissions, + }} + onBeforeCreate={(item: TeamPermission): Promise<TeamPermission> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.teamId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + item.isBlockPermission = + permissionType === PermissionType.BlockPermissions; + return Promise.resolve(item); + }} + cardProps={{ + title: tableTitle, + description: tableDescription, + }} + noItemsMessage={"No permisisons created for this team so far."} + formFields={[ + { + field: { + permission: true, + }, + onChange: async (_value: any): Promise<void> => { + await formRef.current.setFieldValue("labels", [], true); + }, + title: "Permission", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Permission", + dropdownOptions: + PermissionUtil.projectPermissionsAsDropdownOptions(), + }, + { + field: { + labels: true, + }, + title: "Restrict to Labels", + description: + "If you want to restrict this permission to specific labels, you can select them here. This is an optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + showIf: (values: FormValues<TeamPermission>): boolean => { + if (!values["permission"]) { + return false; + } - {/* Team Members Table */} + if ( + values["permission"] && + !PermissionHelper.isAccessControlPermission( + values["permission"] as Permission, + ) + ) { + return false; + } - <ModelTable<TeamMember> - modelType={TeamMember} - id="table-team-member" - isDeleteable={true} - name="Settings > Team > Member" - createVerb={'Invite'} - isCreateable={true} - isViewable={false} - query={{ - teamId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={(item: TeamMember): Promise<TeamPermission> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.teamId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Team Members', - description: - 'See a list of members or invite them to this team. ', - }} - noItemsMessage={'No members found for this team.'} - formFields={[ - { - field: { - user: true, - }, - title: 'User Email', - description: - 'Please enter the email of the user you would like to invite. We will send them an email to let them know they have been invited to this team.', - fieldType: FormFieldSchemaType.Email, - required: true, - placeholder: 'member@company.com', - overrideFieldKey: 'email', - }, - ]} - showRefreshButton={true} - deleteButtonText="Remove Member" - viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} - filters={[ - { - field: { - user: true, - }, - type: FieldType.Entity, - title: 'User', - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - hasAcceptedInvitation: true, - }, - type: FieldType.Boolean, - title: 'Accepted Invite', - }, - ]} - columns={[ - { - field: { - user: { - name: true, - email: true, - }, - }, - title: 'User', - type: FieldType.Text, - getElement: (item: TeamMember): ReactElement => { - if (item['user']) { - return <UserElement user={item['user']} />; - } + return true; + }, + required: false, + placeholder: "Labels", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + permission: true, + }, + type: FieldType.Text, + title: "Permission", + }, + { + field: { + labels: { + name: true, + }, + }, + type: FieldType.EntityArray, + title: "Restrict to Labels", + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + permission: true, + }, + title: "Permission", + type: FieldType.Text, - return <></>; - }, - }, - { - field: { - hasAcceptedInvitation: true, - }, - title: 'Status', - type: FieldType.Boolean, - getElement: (item: TeamMember): ReactElement => { - if (item['hasAcceptedInvitation']) { - return <Pill text="Member" color={Green} />; - } - return ( - <Pill text="Invitation Sent" color={Yellow} /> - ); - }, - }, - ]} - /> + getElement: (item: TeamPermission): ReactElement => { + return ( + <p> + {PermissionHelper.getTitle(item["permission"] as Permission)} + </p> + ); + }, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Restrict to Labels", + type: FieldType.EntityArray, - <Banner - openInNewTab={true} - title="Questions about Team Permissions?" - description="Watch this 5 minute video to learn how team permissions work in OneUptime." - link={URL.fromString('https://youtu.be/TzmaTe4sbCI')} - /> + getElement: (item: TeamPermission): ReactElement => { + if ( + item && + item["permission"] && + !PermissionHelper.isAccessControlPermission( + item["permission"] as Permission, + ) + ) { + return ( + <p> + Restriction by labels cannot be applied to this permission. + </p> + ); + } - {/* Team Permisison Table */} - {getTeamPermissionTable({ - permissionType: PermissionType.AllowPermissions, - })} + if (!item["labels"] || item["labels"].length === 0) { + return ( + <p>No restrictions has been applied to this permission.</p> + ); + } - {/* Team Block Permisison Table */} - {getTeamPermissionTable({ - permissionType: PermissionType.BlockPermissions, - })} - - {/* Delete Team */} - <ModelDelete - modelType={Team} - modelId={Navigation.getLastParamAsObjectID()} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.SETTINGS_TEAMS] as Route - ); - }} - /> - </Fragment> + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> ); + }; + + return ( + <Fragment> + {/* API Key View */} + <CardModelDetail + name="Team Details" + cardProps={{ + title: "Team Details", + description: "Here are more details for this team.", + }} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Team Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Team Description", + }, + ]} + modelDetailProps={{ + modelType: Team, + id: "model-detail-team", + fields: [ + { + field: { + _id: true, + }, + title: "Team ID", + }, + { + field: { + name: true, + }, + title: "Name", + }, + { + field: { + description: true, + }, + title: "Description", + }, + ], + modelId: Navigation.getLastParamAsObjectID(), + }} + /> + + {/* Team Members Table */} + + <ModelTable<TeamMember> + modelType={TeamMember} + id="table-team-member" + isDeleteable={true} + name="Settings > Team > Member" + createVerb={"Invite"} + isCreateable={true} + isViewable={false} + query={{ + teamId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={(item: TeamMember): Promise<TeamPermission> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.teamId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Team Members", + description: "See a list of members or invite them to this team. ", + }} + noItemsMessage={"No members found for this team."} + formFields={[ + { + field: { + user: true, + }, + title: "User Email", + description: + "Please enter the email of the user you would like to invite. We will send them an email to let them know they have been invited to this team.", + fieldType: FormFieldSchemaType.Email, + required: true, + placeholder: "member@company.com", + overrideFieldKey: "email", + }, + ]} + showRefreshButton={true} + deleteButtonText="Remove Member" + viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} + filters={[ + { + field: { + user: true, + }, + type: FieldType.Entity, + title: "User", + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + hasAcceptedInvitation: true, + }, + type: FieldType.Boolean, + title: "Accepted Invite", + }, + ]} + columns={[ + { + field: { + user: { + name: true, + email: true, + }, + }, + title: "User", + type: FieldType.Text, + getElement: (item: TeamMember): ReactElement => { + if (item["user"]) { + return <UserElement user={item["user"]} />; + } + + return <></>; + }, + }, + { + field: { + hasAcceptedInvitation: true, + }, + title: "Status", + type: FieldType.Boolean, + getElement: (item: TeamMember): ReactElement => { + if (item["hasAcceptedInvitation"]) { + return <Pill text="Member" color={Green} />; + } + return <Pill text="Invitation Sent" color={Yellow} />; + }, + }, + ]} + /> + + <Banner + openInNewTab={true} + title="Questions about Team Permissions?" + description="Watch this 5 minute video to learn how team permissions work in OneUptime." + link={URL.fromString("https://youtu.be/TzmaTe4sbCI")} + /> + + {/* Team Permisison Table */} + {getTeamPermissionTable({ + permissionType: PermissionType.AllowPermissions, + })} + + {/* Team Block Permisison Table */} + {getTeamPermissionTable({ + permissionType: PermissionType.BlockPermissions, + })} + + {/* Delete Team */} + <ModelDelete + modelType={Team} + modelId={Navigation.getLastParamAsObjectID()} + onDeleteSuccess={() => { + Navigation.navigate(RouteMap[PageMap.SETTINGS_TEAMS] as Route); + }} + /> + </Fragment> + ); }; export default TeamView; diff --git a/Dashboard/src/Pages/Settings/Teams.tsx b/Dashboard/src/Pages/Settings/Teams.tsx index 74be7aaabd..e185e6e722 100644 --- a/Dashboard/src/Pages/Settings/Teams.tsx +++ b/Dashboard/src/Pages/Settings/Teams.tsx @@ -1,95 +1,94 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Team from 'Model/Models/Team'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Team from "Model/Models/Team"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Teams: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<Team> - modelType={Team} - id="teams-table" - name="Settings > Teams" - isDeleteable={false} - isEditable={false} - isCreateable={true} - isViewable={true} - cardProps={{ - title: 'Teams', - description: - 'Here is a list of all the teams in this project.', - }} - noItemsMessage={'No teams found.'} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - showViewIdButton={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Team Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Team Description', - }, - ]} - showRefreshButton={true} - viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<Team> + modelType={Team} + id="teams-table" + name="Settings > Teams" + isDeleteable={false} + isEditable={false} + isCreateable={true} + isViewable={true} + cardProps={{ + title: "Teams", + description: "Here is a list of all the teams in this project.", + }} + noItemsMessage={"No teams found."} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + showViewIdButton={true} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Team Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Team Description", + }, + ]} + showRefreshButton={true} + viewPageRoute={RouteUtil.populateRouteParams(props.pageRoute)} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + ]} + /> + </Fragment> + ); }; export default Teams; diff --git a/Dashboard/src/Pages/Settings/UsageHistory.tsx b/Dashboard/src/Pages/Settings/UsageHistory.tsx index 49c9d52fb6..fc6e6a40f6 100644 --- a/Dashboard/src/Pages/Settings/UsageHistory.tsx +++ b/Dashboard/src/Pages/Settings/UsageHistory.tsx @@ -1,185 +1,175 @@ -import TelemetryServiceElement from '../../Components/TelemetryService/TelemetryServiceElement'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import Currency from 'Common/Types/Currency'; -import Decimal from 'Common/Types/Decimal'; -import DiskSize from 'Common/Types/DiskSize'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import TelemetryService from 'Model/Models/TelemetryService'; -import TelemetryUsageBilling from 'Model/Models/TelemetryUsageBilling'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import TelemetryServiceElement from "../../Components/TelemetryService/TelemetryServiceElement"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import Currency from "Common/Types/Currency"; +import Decimal from "Common/Types/Decimal"; +import DiskSize from "Common/Types/DiskSize"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import TelemetryService from "Model/Models/TelemetryService"; +import TelemetryUsageBilling from "Model/Models/TelemetryUsageBilling"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; export interface ComponentProps extends PageComponentProps {} const Settings: FunctionComponent<ComponentProps> = ( - _props: ComponentProps + _props: ComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<TelemetryUsageBilling> - modelType={TelemetryUsageBilling} - id="usage-history-table" - isDeleteable={false} - name="Settings > Billing > Usage History" - isEditable={false} - isCreateable={false} - isViewable={false} - cardProps={{ - title: 'Telemetry Usage History', - description: - 'Here is the telemetry usage history for this project. Please refer to the pricing page for more details.', - }} - noItemsMessage={ - 'No usage history found. Maybe you have not used Telemetry features yet?' - } - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - showRefreshButton={true} - filters={[ - { - field: { - productType: true, - }, - title: 'Product', - type: FieldType.Dropdown, - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - ProductType - ).filter((option: DropdownOption) => { - return ( - option.value !== - ProductType.ActiveMonitoring - ); - }), // Remove Active Monitoring from the dropdown - }, - { - field: { - createdAt: true, - }, - title: 'Day', - type: FieldType.Date, - }, - { - field: { - telemetryService: true, - }, - title: 'Telemetry Service', - type: FieldType.Entity, - filterEntityType: TelemetryService, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - productType: true, - }, - title: 'Product', - type: FieldType.Text, - }, - { - field: { - createdAt: true, - }, - title: 'Day', - type: FieldType.Date, - }, - { - field: { - dataIngestedInGB: true, - }, - title: 'Data Ingested (in GB)', - type: FieldType.Text, - getElement: (item: TelemetryUsageBilling) => { - return ( - <div>{`${DiskSize.convertToDecimalPlaces( - (item['dataIngestedInGB'] as Decimal) - .value as number - )} GB`}</div> - ); - }, - }, - { - field: { - telemetryService: { - name: true, - _id: true, - }, - }, - title: 'Telemetry Service', - type: FieldType.Element, - getElement: (item: TelemetryUsageBilling) => { - return ( - <TelemetryServiceElement - telemetryService={ - item[ - 'telemetryService' - ] as TelemetryService - } - /> - ); - }, - }, - { - field: { - retainTelemetryDataForDays: true, - }, - title: 'Data Retention (in Days)', - type: FieldType.Text, - getElement: (item: TelemetryUsageBilling) => { - return ( - <div>{`${item[ - 'retainTelemetryDataForDays' - ]?.toString()} Days`}</div> - ); - }, - }, - { - field: { - dataIngestedInGB: true, - }, - title: 'Data Ingested (in GB)', - type: FieldType.Text, - getElement: (item: TelemetryUsageBilling) => { - return ( - <div>{`${DiskSize.convertToDecimalPlaces( - (item['dataIngestedInGB'] as Decimal) - .value as number - )} GB`}</div> - ); - }, - }, - { - field: { - totalCostInUSD: true, - }, - title: 'Total Cost', - type: FieldType.Text, - getElement: (item: TelemetryUsageBilling) => { - return ( - <div>{`${Currency.convertToDecimalPlaces( - (item['totalCostInUSD'] as Decimal) - .value as number - )} USD`}</div> - ); - }, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<TelemetryUsageBilling> + modelType={TelemetryUsageBilling} + id="usage-history-table" + isDeleteable={false} + name="Settings > Billing > Usage History" + isEditable={false} + isCreateable={false} + isViewable={false} + cardProps={{ + title: "Telemetry Usage History", + description: + "Here is the telemetry usage history for this project. Please refer to the pricing page for more details.", + }} + noItemsMessage={ + "No usage history found. Maybe you have not used Telemetry features yet?" + } + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + showRefreshButton={true} + filters={[ + { + field: { + productType: true, + }, + title: "Product", + type: FieldType.Dropdown, + filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum( + ProductType, + ).filter((option: DropdownOption) => { + return option.value !== ProductType.ActiveMonitoring; + }), // Remove Active Monitoring from the dropdown + }, + { + field: { + createdAt: true, + }, + title: "Day", + type: FieldType.Date, + }, + { + field: { + telemetryService: true, + }, + title: "Telemetry Service", + type: FieldType.Entity, + filterEntityType: TelemetryService, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + productType: true, + }, + title: "Product", + type: FieldType.Text, + }, + { + field: { + createdAt: true, + }, + title: "Day", + type: FieldType.Date, + }, + { + field: { + dataIngestedInGB: true, + }, + title: "Data Ingested (in GB)", + type: FieldType.Text, + getElement: (item: TelemetryUsageBilling) => { + return ( + <div>{`${DiskSize.convertToDecimalPlaces( + (item["dataIngestedInGB"] as Decimal).value as number, + )} GB`}</div> + ); + }, + }, + { + field: { + telemetryService: { + name: true, + _id: true, + }, + }, + title: "Telemetry Service", + type: FieldType.Element, + getElement: (item: TelemetryUsageBilling) => { + return ( + <TelemetryServiceElement + telemetryService={ + item["telemetryService"] as TelemetryService + } + /> + ); + }, + }, + { + field: { + retainTelemetryDataForDays: true, + }, + title: "Data Retention (in Days)", + type: FieldType.Text, + getElement: (item: TelemetryUsageBilling) => { + return ( + <div>{`${item[ + "retainTelemetryDataForDays" + ]?.toString()} Days`}</div> + ); + }, + }, + { + field: { + dataIngestedInGB: true, + }, + title: "Data Ingested (in GB)", + type: FieldType.Text, + getElement: (item: TelemetryUsageBilling) => { + return ( + <div>{`${DiskSize.convertToDecimalPlaces( + (item["dataIngestedInGB"] as Decimal).value as number, + )} GB`}</div> + ); + }, + }, + { + field: { + totalCostInUSD: true, + }, + title: "Total Cost", + type: FieldType.Text, + getElement: (item: TelemetryUsageBilling) => { + return ( + <div>{`${Currency.convertToDecimalPlaces( + (item["totalCostInUSD"] as Decimal).value as number, + )} USD`}</div> + ); + }, + }, + ]} + /> + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/StatusPages/StatusPages.tsx b/Dashboard/src/Pages/StatusPages/StatusPages.tsx index 1e5e7c31ec..ace2b1a9db 100644 --- a/Dashboard/src/Pages/StatusPages/StatusPages.tsx +++ b/Dashboard/src/Pages/StatusPages/StatusPages.tsx @@ -1,157 +1,151 @@ -import LabelsElement from '../../Components/Label/Labels'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Page from 'CommonUI/src/Components/Page/Page'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Page from "CommonUI/src/Components/Page/Page"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import StatusPage from "Model/Models/StatusPage"; +import React, { FunctionComponent, ReactElement } from "react"; const StatusPages: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Page - title={'Status Pages'} - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Status Pages', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGES] as Route - ), - }, - ]} - > - <Banner - openInNewTab={true} - title="Need a demo of status pages?" - description="Watch this video which will help you build status page for your company in under 12 minutes." - link={URL.fromString('https://youtu.be/F6BNipy5VCk')} - /> - <ModelTable<StatusPage> - modelType={StatusPage} - id="status-page-table" - isDeleteable={false} - isEditable={false} - isCreateable={true} - name="Status Pages" - isViewable={true} - cardProps={{ - title: 'Status Pages', - description: - 'Here is a list of status pages for this project.', - }} - showViewIdButton={true} - noItemsMessage={'No status pages found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Status Page Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, + return ( + <Page + title={"Status Pages"} + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Status Pages", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGES] as Route, + ), + }, + ]} + > + <Banner + openInNewTab={true} + title="Need a demo of status pages?" + description="Watch this video which will help you build status page for your company in under 12 minutes." + link={URL.fromString("https://youtu.be/F6BNipy5VCk")} + /> + <ModelTable<StatusPage> + modelType={StatusPage} + id="status-page-table" + isDeleteable={false} + isEditable={false} + isCreateable={true} + name="Status Pages" + isViewable={true} + cardProps={{ + title: "Status Pages", + description: "Here is a list of status pages for this project.", + }} + showViewIdButton={true} + noItemsMessage={"No status pages found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Status Page Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, - getElement: (item: StatusPage): ReactElement => { - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> - </Page> - ); + getElement: (item: StatusPage): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + </Page> + ); }; export default StatusPages; diff --git a/Dashboard/src/Pages/StatusPages/View/AdvancedOptions.tsx b/Dashboard/src/Pages/StatusPages/View/AdvancedOptions.tsx index 1646260df2..5c1c8c3864 100644 --- a/Dashboard/src/Pages/StatusPages/View/AdvancedOptions.tsx +++ b/Dashboard/src/Pages/StatusPages/View/AdvancedOptions.tsx @@ -1,62 +1,59 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import React, { FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <ModelPage - title="Status Page" - modelType={StatusPage} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route, - { modelId } - ), - }, - { - title: 'Status Pages', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGES] as Route, - { modelId } - ), - }, - { - title: 'View Status Page', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGE_VIEW] as Route, - { modelId } - ), - }, - { - title: 'Advanced Settings', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS - ] as Route, - { modelId } - ), - }, - ]} - sideMenu={<SideMenu modelId={modelId} />} - > - <div></div> - </ModelPage> - ); + return ( + <ModelPage + title="Status Page" + modelType={StatusPage} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route, { + modelId, + }), + }, + { + title: "Status Pages", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGES] as Route, + { modelId }, + ), + }, + { + title: "View Status Page", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW] as Route, + { modelId }, + ), + }, + { + title: "Advanced Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS] as Route, + { modelId }, + ), + }, + ]} + sideMenu={<SideMenu modelId={modelId} />} + > + <div></div> + </ModelPage> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/Announcements.tsx b/Dashboard/src/Pages/StatusPages/View/Announcements.tsx index 12a9f9bdc3..b8d30cfd42 100644 --- a/Dashboard/src/Pages/StatusPages/View/Announcements.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Announcements.tsx @@ -1,190 +1,188 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageAnnouncement from 'Model/Models/StatusPageAnnouncement'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageAnnouncement from "Model/Models/StatusPageAnnouncement"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const statusPage: StatusPage = new StatusPage(); - statusPage.id = modelId; + const statusPage: StatusPage = new StatusPage(); + statusPage.id = modelId; - return ( - <Fragment> - <ModelTable<StatusPageAnnouncement> - modelType={StatusPageAnnouncement} - id="table-status-page-note" - isDeleteable={true} - isCreateable={true} - showViewIdButton={true} - isEditable={true} - name="Status Page > Announcements" - isViewable={false} - query={{ - statusPages: [statusPage], - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: StatusPageAnnouncement - ): Promise<StatusPageAnnouncement> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } + return ( + <Fragment> + <ModelTable<StatusPageAnnouncement> + modelType={StatusPageAnnouncement} + id="table-status-page-note" + isDeleteable={true} + isCreateable={true} + showViewIdButton={true} + isEditable={true} + name="Status Page > Announcements" + isViewable={false} + query={{ + statusPages: [statusPage], + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: StatusPageAnnouncement, + ): Promise<StatusPageAnnouncement> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } - const statusPage: StatusPage = new StatusPage(); - statusPage.id = modelId; + const statusPage: StatusPage = new StatusPage(); + statusPage.id = modelId; - item.statusPages = [statusPage]; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Announcements', - description: - 'Here are announcements for this status page. This will show up on the status page.', - }} - noItemsMessage={'No announcements found.'} - formSteps={[ - { - title: 'Basic', - id: 'basic', - }, - { - title: 'More', - id: 'more', - }, - ]} - formFields={[ - { - field: { - title: true, - }, - title: 'Announcement Title', - stepId: 'basic', - description: 'Title of announcement', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Title', - }, - { - field: { - description: true, - }, - title: 'Description', - stepId: 'basic', - fieldType: FormFieldSchemaType.Markdown, - required: true, - description: - 'Add an announcement note. This is in Markdown.', - }, - { - field: { - showAnnouncementAt: true, - }, - stepId: 'more', - title: 'Start Showing Announcement At', - fieldType: FormFieldSchemaType.DateTime, - required: true, - placeholder: 'Pick Date and Time', - }, - { - field: { - endAnnouncementAt: true, - }, - stepId: 'more', - title: 'End Showing Announcement At', - fieldType: FormFieldSchemaType.DateTime, - required: true, - placeholder: 'Pick Date and Time', - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, + item.statusPages = [statusPage]; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Announcements", + description: + "Here are announcements for this status page. This will show up on the status page.", + }} + noItemsMessage={"No announcements found."} + formSteps={[ + { + title: "Basic", + id: "basic", + }, + { + title: "More", + id: "more", + }, + ]} + formFields={[ + { + field: { + title: true, + }, + title: "Announcement Title", + stepId: "basic", + description: "Title of announcement", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Title", + }, + { + field: { + description: true, + }, + title: "Description", + stepId: "basic", + fieldType: FormFieldSchemaType.Markdown, + required: true, + description: "Add an announcement note. This is in Markdown.", + }, + { + field: { + showAnnouncementAt: true, + }, + stepId: "more", + title: "Start Showing Announcement At", + fieldType: FormFieldSchemaType.DateTime, + required: true, + placeholder: "Pick Date and Time", + }, + { + field: { + endAnnouncementAt: true, + }, + stepId: "more", + title: "End Showing Announcement At", + fieldType: FormFieldSchemaType.DateTime, + required: true, + placeholder: "Pick Date and Time", + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, - title: 'Notify Status Page Subscribers', - stepId: 'more', - description: - 'Should status page subscribers be notified?', - fieldType: FormFieldSchemaType.Checkbox, - defaultValue: true, - required: false, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - showAnnouncementAt: true, - }, - title: 'Show Announcement At', - type: FieldType.Date, - }, - { - field: { - endAnnouncementAt: true, - }, - title: 'End Announcement At', - type: FieldType.Date, - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, - title: 'Subscribers Notified', - type: FieldType.Boolean, - }, - ]} - columns={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - showAnnouncementAt: true, - }, - title: 'Show Announcement At', - type: FieldType.DateTime, - }, - { - field: { - endAnnouncementAt: true, - }, - title: 'End Announcement At', - type: FieldType.DateTime, - }, - { - field: { - shouldStatusPageSubscribersBeNotified: true, - }, - title: 'Subscribers Notified', - type: FieldType.Boolean, - }, - ]} - /> - </Fragment> - ); + title: "Notify Status Page Subscribers", + stepId: "more", + description: "Should status page subscribers be notified?", + fieldType: FormFieldSchemaType.Checkbox, + defaultValue: true, + required: false, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + showAnnouncementAt: true, + }, + title: "Show Announcement At", + type: FieldType.Date, + }, + { + field: { + endAnnouncementAt: true, + }, + title: "End Announcement At", + type: FieldType.Date, + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, + title: "Subscribers Notified", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + showAnnouncementAt: true, + }, + title: "Show Announcement At", + type: FieldType.DateTime, + }, + { + field: { + endAnnouncementAt: true, + }, + title: "End Announcement At", + type: FieldType.DateTime, + }, + { + field: { + shouldStatusPageSubscribersBeNotified: true, + }, + title: "Subscribers Notified", + type: FieldType.Boolean, + }, + ]} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/AuthenticationSettings.tsx b/Dashboard/src/Pages/StatusPages/View/AuthenticationSettings.tsx index f3de6ff542..b0c795ab17 100644 --- a/Dashboard/src/Pages/StatusPages/View/AuthenticationSettings.tsx +++ b/Dashboard/src/Pages/StatusPages/View/AuthenticationSettings.tsx @@ -1,57 +1,56 @@ -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CardModelDetail<StatusPage> - name="Status Page > Authentication Settings" - cardProps={{ - title: 'Authentication Settings', - description: - 'Authentication settings for this status page.', - }} - editButtonText="Edit Settings" - isEditable={true} - formFields={[ - { - field: { - isPublicStatusPage: true, - }, - title: 'Is Visible to Public', - fieldType: FormFieldSchemaType.Toggle, - required: false, - placeholder: 'Is this status page visible to public', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - isPublicStatusPage: true, - }, - fieldType: FieldType.Boolean, - title: 'Is Visible to Public', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + return ( + <Fragment> + <CardModelDetail<StatusPage> + name="Status Page > Authentication Settings" + cardProps={{ + title: "Authentication Settings", + description: "Authentication settings for this status page.", + }} + editButtonText="Edit Settings" + isEditable={true} + formFields={[ + { + field: { + isPublicStatusPage: true, + }, + title: "Is Visible to Public", + fieldType: FormFieldSchemaType.Toggle, + required: false, + placeholder: "Is this status page visible to public", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + isPublicStatusPage: true, + }, + fieldType: FieldType.Boolean, + title: "Is Visible to Public", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/Branding.tsx b/Dashboard/src/Pages/StatusPages/View/Branding.tsx index a4279affe3..c46f48321c 100644 --- a/Dashboard/src/Pages/StatusPages/View/Branding.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Branding.tsx @@ -1,114 +1,114 @@ -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CardModelDetail<StatusPage> - name="Status Page > Branding > Title and Description" - cardProps={{ - title: 'Title and Description', - description: 'This will also be used for SEO.', - }} - editButtonText={'Edit'} - isEditable={true} - formFields={[ - { - field: { - pageTitle: true, - }, - title: 'Page Title', - fieldType: FormFieldSchemaType.Text, - required: false, - placeholder: 'Please enter page title here.', - }, - { - field: { - pageDescription: true, - }, - title: 'Page Description', - fieldType: FormFieldSchemaType.LongText, - required: false, - placeholder: 'Please enter page description here.', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - pageTitle: true, - }, - fieldType: FieldType.Text, - title: 'Page Title', - placeholder: 'No page title entered so far.', - }, - { - field: { - pageDescription: true, - }, - fieldType: FieldType.Text, - title: 'Page Description', - placeholder: 'No page description entered so far.', - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + <CardModelDetail<StatusPage> + name="Status Page > Branding > Title and Description" + cardProps={{ + title: "Title and Description", + description: "This will also be used for SEO.", + }} + editButtonText={"Edit"} + isEditable={true} + formFields={[ + { + field: { + pageTitle: true, + }, + title: "Page Title", + fieldType: FormFieldSchemaType.Text, + required: false, + placeholder: "Please enter page title here.", + }, + { + field: { + pageDescription: true, + }, + title: "Page Description", + fieldType: FormFieldSchemaType.LongText, + required: false, + placeholder: "Please enter page description here.", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + pageTitle: true, + }, + fieldType: FieldType.Text, + title: "Page Title", + placeholder: "No page title entered so far.", + }, + { + field: { + pageDescription: true, + }, + fieldType: FieldType.Text, + title: "Page Description", + placeholder: "No page description entered so far.", + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail<StatusPage> - name="Status Page > Branding > Favicon" - cardProps={{ - title: 'Favicon', - description: 'Favicon will be used for SEO.', - }} - isEditable={true} - editButtonText={'Edit Favicon'} - formFields={[ - { - field: { - faviconFile: true, - }, - title: 'Favicon', - fieldType: FormFieldSchemaType.ImageFile, - required: false, - placeholder: 'Upload Favicon.', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - faviconFile: { - file: true, - type: true, - }, - }, - fieldType: FieldType.ImageFile, - title: 'Favicon', - placeholder: 'No favicon uploaded.', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + <CardModelDetail<StatusPage> + name="Status Page > Branding > Favicon" + cardProps={{ + title: "Favicon", + description: "Favicon will be used for SEO.", + }} + isEditable={true} + editButtonText={"Edit Favicon"} + formFields={[ + { + field: { + faviconFile: true, + }, + title: "Favicon", + fieldType: FormFieldSchemaType.ImageFile, + required: false, + placeholder: "Upload Favicon.", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + faviconFile: { + file: true, + type: true, + }, + }, + fieldType: FieldType.ImageFile, + title: "Favicon", + placeholder: "No favicon uploaded.", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/CustomFields.tsx b/Dashboard/src/Pages/StatusPages/View/CustomFields.tsx index 41c56bc3cf..c66a6648ed 100644 --- a/Dashboard/src/Pages/StatusPages/View/CustomFields.tsx +++ b/Dashboard/src/Pages/StatusPages/View/CustomFields.tsx @@ -1,30 +1,30 @@ -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import CustomFieldsDetail from 'CommonUI/src/Components/CustomFields/CustomFieldsDetail'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageCustomField from 'Model/Models/StatusPageCustomField'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import CustomFieldsDetail from "CommonUI/src/Components/CustomFields/CustomFieldsDetail"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageCustomField from "Model/Models/StatusPageCustomField"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageCustomFields: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CustomFieldsDetail - title="Status Page Custom Fields" - description="Custom fields help you add new fields to your resources in OneUptime." - modelType={StatusPage} - customFieldType={StatusPageCustomField} - name="Status Page Custom Fields" - projectId={ProjectUtil.getCurrentProject()!.id!} - modelId={modelId} - /> - </Fragment> - ); + return ( + <Fragment> + <CustomFieldsDetail + title="Status Page Custom Fields" + description="Custom fields help you add new fields to your resources in OneUptime." + modelType={StatusPage} + customFieldType={StatusPageCustomField} + name="Status Page Custom Fields" + projectId={ProjectUtil.getCurrentProject()!.id!} + modelId={modelId} + /> + </Fragment> + ); }; export default StatusPageCustomFields; diff --git a/Dashboard/src/Pages/StatusPages/View/CustomHtmlCss.tsx b/Dashboard/src/Pages/StatusPages/View/CustomHtmlCss.tsx index f634d781c2..5ce67ce01b 100644 --- a/Dashboard/src/Pages/StatusPages/View/CustomHtmlCss.tsx +++ b/Dashboard/src/Pages/StatusPages/View/CustomHtmlCss.tsx @@ -1,176 +1,174 @@ -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - {/* StatusPage View */} - <CardModelDetail<StatusPage> - name="Status Page > Branding > Header HTML" - cardProps={{ - title: 'Header HTML', - description: - 'You can include header HTML to your status page.', - }} - isEditable={true} - formFields={[ - { - field: { - headerHTML: true, - }, - title: 'Header HTML', - fieldType: FormFieldSchemaType.HTML, - required: false, - placeholder: 'Insert Custom HTML here.', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - headerHTML: true, - }, - fieldType: FieldType.HTML, - title: 'Header HTML', - placeholder: - 'No Header HTML found. Please edit this Status Page to add some.', - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + {/* StatusPage View */} + <CardModelDetail<StatusPage> + name="Status Page > Branding > Header HTML" + cardProps={{ + title: "Header HTML", + description: "You can include header HTML to your status page.", + }} + isEditable={true} + formFields={[ + { + field: { + headerHTML: true, + }, + title: "Header HTML", + fieldType: FormFieldSchemaType.HTML, + required: false, + placeholder: "Insert Custom HTML here.", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + headerHTML: true, + }, + fieldType: FieldType.HTML, + title: "Header HTML", + placeholder: + "No Header HTML found. Please edit this Status Page to add some.", + }, + ], + modelId: modelId, + }} + /> - {/* StatusPage View */} - <CardModelDetail<StatusPage> - name="Status Page > Branding > Footer HTML" - cardProps={{ - title: 'Footer HTML', - description: - 'You can include footer HTML to your status page.', - }} - isEditable={true} - formFields={[ - { - field: { - footerHTML: true, - }, - title: 'Footer HTML', - fieldType: FormFieldSchemaType.HTML, - required: false, - placeholder: 'Insert Custom HTML here.', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - footerHTML: true, - }, - fieldType: FieldType.HTML, - title: 'Footer HTML', - placeholder: - 'No Footer HTML found. Please edit this Status Page to add some.', - }, - ], - modelId: modelId, - }} - /> + {/* StatusPage View */} + <CardModelDetail<StatusPage> + name="Status Page > Branding > Footer HTML" + cardProps={{ + title: "Footer HTML", + description: "You can include footer HTML to your status page.", + }} + isEditable={true} + formFields={[ + { + field: { + footerHTML: true, + }, + title: "Footer HTML", + fieldType: FormFieldSchemaType.HTML, + required: false, + placeholder: "Insert Custom HTML here.", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + footerHTML: true, + }, + fieldType: FieldType.HTML, + title: "Footer HTML", + placeholder: + "No Footer HTML found. Please edit this Status Page to add some.", + }, + ], + modelId: modelId, + }} + /> - {/* StatusPage View */} - <CardModelDetail<StatusPage> - name="Status Page > Branding > Custom CSS" - cardProps={{ - title: 'Custom CSS', - description: - 'You can include custom CSS classes to your status page.', - }} - isEditable={true} - formFields={[ - { - field: { - customCSS: true, - }, - title: 'Custom CSS', - fieldType: FormFieldSchemaType.CSS, - required: false, - placeholder: 'Insert Custom CSS here.', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - customCSS: true, - }, - fieldType: FieldType.CSS, - title: 'Custom CSS', - placeholder: - 'No Custom CSS found. Please edit this Status Page to add some.', - }, - ], - modelId: modelId, - }} - /> + {/* StatusPage View */} + <CardModelDetail<StatusPage> + name="Status Page > Branding > Custom CSS" + cardProps={{ + title: "Custom CSS", + description: + "You can include custom CSS classes to your status page.", + }} + isEditable={true} + formFields={[ + { + field: { + customCSS: true, + }, + title: "Custom CSS", + fieldType: FormFieldSchemaType.CSS, + required: false, + placeholder: "Insert Custom CSS here.", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + customCSS: true, + }, + fieldType: FieldType.CSS, + title: "Custom CSS", + placeholder: + "No Custom CSS found. Please edit this Status Page to add some.", + }, + ], + modelId: modelId, + }} + /> - {/* StatusPage View */} - <CardModelDetail<StatusPage> - name="Status Page > Branding > Custom JavaScript" - cardProps={{ - title: 'Custom JavaScript', - description: - 'You can include custom JavaScript classes to your status page.', - }} - isEditable={true} - formFields={[ - { - field: { - customJavaScript: true, - }, - title: 'Custom JavaScript', - fieldType: FormFieldSchemaType.JavaScript, - required: false, - placeholder: 'Insert Custom JavaScript here.', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - customJavaScript: true, - }, - fieldType: FieldType.JavaScript, - title: 'Custom JavaScript', - placeholder: - 'No Custom JavaScript found. Please edit this Status Page to add some.', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + {/* StatusPage View */} + <CardModelDetail<StatusPage> + name="Status Page > Branding > Custom JavaScript" + cardProps={{ + title: "Custom JavaScript", + description: + "You can include custom JavaScript classes to your status page.", + }} + isEditable={true} + formFields={[ + { + field: { + customJavaScript: true, + }, + title: "Custom JavaScript", + fieldType: FormFieldSchemaType.JavaScript, + required: false, + placeholder: "Insert Custom JavaScript here.", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + customJavaScript: true, + }, + fieldType: FieldType.JavaScript, + title: "Custom JavaScript", + placeholder: + "No Custom JavaScript found. Please edit this Status Page to add some.", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/Delete.tsx b/Dashboard/src/Pages/StatusPages/View/Delete.tsx index 6018d947ed..88ce9db796 100644 --- a/Dashboard/src/Pages/StatusPages/View/Delete.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Delete.tsx @@ -1,31 +1,29 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={StatusPage} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.STATUS_PAGES] as Route - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={StatusPage} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate(RouteMap[PageMap.STATUS_PAGES] as Route); + }} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/Domains.tsx b/Dashboard/src/Pages/StatusPages/View/Domains.tsx index ff91232ff8..f3c5a557f3 100644 --- a/Dashboard/src/Pages/StatusPages/View/Domains.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Domains.tsx @@ -1,419 +1,389 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { APP_API_URL, StatusPageCNameRecord } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Domain from 'Model/Models/Domain'; -import StatusPageDomain from 'Model/Models/StatusPageDomain'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { APP_API_URL, StatusPageCNameRecord } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Domain from "Model/Models/Domain"; +import StatusPageDomain from "Model/Models/StatusPageDomain"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [refreshToggle, setRefreshToggle] = useState<boolean>(false); + const [refreshToggle, setRefreshToggle] = useState<boolean>(false); - const [showCnameModal, setShowCnameModal] = useState<boolean>(false); + const [showCnameModal, setShowCnameModal] = useState<boolean>(false); - const [selectedStatusPageDomain, setSelectedStatusPageDomain] = - useState<StatusPageDomain | null>(null); + const [selectedStatusPageDomain, setSelectedStatusPageDomain] = + useState<StatusPageDomain | null>(null); - const [verifyCnameLoading, setVerifyCnameLoading] = - useState<boolean>(false); + const [verifyCnameLoading, setVerifyCnameLoading] = useState<boolean>(false); - const [orderSslLoading, setOrderSslLoading] = useState<boolean>(false); + const [orderSslLoading, setOrderSslLoading] = useState<boolean>(false); - const [error, setError] = useState<string>(''); + const [error, setError] = useState<string>(""); - const [showOrderSSLModal, setShowOrderSSLModal] = useState<boolean>(false); + const [showOrderSSLModal, setShowOrderSSLModal] = useState<boolean>(false); - return ( - <Fragment> - <> - <ModelTable<StatusPageDomain> - modelType={StatusPageDomain} - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - statusPageId: modelId, - }} - name="Status Page > Domains" - id="domains-table" - isDeleteable={true} - isCreateable={true} - cardProps={{ - title: 'Custom Domains', - description: `Important: Please add ${StatusPageCNameRecord} as your CNAME for these domains for this to work.`, - }} - refreshToggle={refreshToggle} - onBeforeCreate={( - item: StatusPageDomain - ): Promise<StatusPageDomain> => { - if ( - !props.currentProject || - !props.currentProject._id - ) { - throw new BadDataException( - 'Project ID cannot be null' - ); - } - item.statusPageId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - actionButtons={[ - { - title: 'Add CNAME', - buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, - icon: IconProp.Check, - isVisible: (item: StatusPageDomain): boolean => { - if (item['isCnameVerified']) { - return false; - } + return ( + <Fragment> + <> + <ModelTable<StatusPageDomain> + modelType={StatusPageDomain} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + statusPageId: modelId, + }} + name="Status Page > Domains" + id="domains-table" + isDeleteable={true} + isCreateable={true} + cardProps={{ + title: "Custom Domains", + description: `Important: Please add ${StatusPageCNameRecord} as your CNAME for these domains for this to work.`, + }} + refreshToggle={refreshToggle} + onBeforeCreate={( + item: StatusPageDomain, + ): Promise<StatusPageDomain> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + actionButtons={[ + { + title: "Add CNAME", + buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, + icon: IconProp.Check, + isVisible: (item: StatusPageDomain): boolean => { + if (item["isCnameVerified"]) { + return false; + } - return true; - }, - onClick: async ( - item: StatusPageDomain, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setShowCnameModal(true); - setSelectedStatusPageDomain(item); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - { - title: 'Order Free SSL', - buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, - icon: IconProp.Check, - isVisible: (item: StatusPageDomain): boolean => { - if ( - item['isCnameVerified'] && - !item.isSslOrdered - ) { - return true; - } + return true; + }, + onClick: async ( + item: StatusPageDomain, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setShowCnameModal(true); + setSelectedStatusPageDomain(item); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + { + title: "Order Free SSL", + buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE, + icon: IconProp.Check, + isVisible: (item: StatusPageDomain): boolean => { + if (item["isCnameVerified"] && !item.isSslOrdered) { + return true; + } - return false; - }, - onClick: async ( - item: StatusPageDomain, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setShowOrderSSLModal(true); - setSelectedStatusPageDomain(item); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - setSelectedStatusPageDomain(null); - onError(err as Error); - } - }, - }, - ]} - noItemsMessage={'No custom domains found.'} - viewPageRoute={Navigation.getCurrentRoute()} - selectMoreFields={{ - isSslOrdered: true, - isSslProvisioned: true, - isCnameVerified: true, - }} - formFields={[ - { - field: { - subdomain: true, - }, - title: 'Subdomain', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'status', - validation: { - minLength: 2, - }, - }, - { - field: { - domain: true, - }, - title: 'Domain', - description: - 'Please select a verified domain from this list. If you do not see any domains in this list, please head over to More -> Project Settings -> Custom Domains to add one.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: Domain, - labelField: 'domain', - valueField: '_id', - }, - required: true, - placeholder: 'Select domain', - }, - ]} - showRefreshButton={true} - filters={[ - { - field: { - fullDomain: true, - }, - title: 'Domain', - type: FieldType.Text, - }, - { - field: {}, - title: 'CNAME Valid', - type: FieldType.Boolean, - }, - { - field: {}, - title: 'SSL Provisioned', - type: FieldType.Boolean, - }, - ]} - columns={[ - { - field: { - fullDomain: true, - }, - title: 'Domain', - type: FieldType.Text, - }, - { - field: { - isCnameVerified: true, - }, - title: 'Status', - type: FieldType.Element, + return false; + }, + onClick: async ( + item: StatusPageDomain, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setShowOrderSSLModal(true); + setSelectedStatusPageDomain(item); + onCompleteAction(); + } catch (err) { + onCompleteAction(); + setSelectedStatusPageDomain(null); + onError(err as Error); + } + }, + }, + ]} + noItemsMessage={"No custom domains found."} + viewPageRoute={Navigation.getCurrentRoute()} + selectMoreFields={{ + isSslOrdered: true, + isSslProvisioned: true, + isCnameVerified: true, + }} + formFields={[ + { + field: { + subdomain: true, + }, + title: "Subdomain", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "status", + validation: { + minLength: 2, + }, + }, + { + field: { + domain: true, + }, + title: "Domain", + description: + "Please select a verified domain from this list. If you do not see any domains in this list, please head over to More -> Project Settings -> Custom Domains to add one.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: Domain, + labelField: "domain", + valueField: "_id", + }, + required: true, + placeholder: "Select domain", + }, + ]} + showRefreshButton={true} + filters={[ + { + field: { + fullDomain: true, + }, + title: "Domain", + type: FieldType.Text, + }, + { + field: {}, + title: "CNAME Valid", + type: FieldType.Boolean, + }, + { + field: {}, + title: "SSL Provisioned", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + fullDomain: true, + }, + title: "Domain", + type: FieldType.Text, + }, + { + field: { + isCnameVerified: true, + }, + title: "Status", + type: FieldType.Element, - getElement: ( - item: StatusPageDomain - ): ReactElement => { - if (!item.isCnameVerified) { - return ( - <span> - <span className="font-semibold"> - Action Required: - </span>{' '} - Please add your CNAME record. - </span> - ); - } + getElement: (item: StatusPageDomain): ReactElement => { + if (!item.isCnameVerified) { + return ( + <span> + <span className="font-semibold">Action Required:</span>{" "} + Please add your CNAME record. + </span> + ); + } - if (!item.isSslOrdered) { - return ( - <span> - <span className="font-semibold"> - Action Required: - </span>{' '} - Please order SSL certificate. - </span> - ); - } + if (!item.isSslOrdered) { + return ( + <span> + <span className="font-semibold">Action Required:</span>{" "} + Please order SSL certificate. + </span> + ); + } - if (!item.isSslProvisioned) { - return ( - <span> - No action is required. This SSL - certificate will be provisioned in 1 - hour. If this does not happen. - Please contact support. - </span> - ); - } + if (!item.isSslProvisioned) { + return ( + <span> + No action is required. This SSL certificate will be + provisioned in 1 hour. If this does not happen. Please + contact support. + </span> + ); + } - return ( - <span> - Certificate Provisioned. We will - automatiucally renew this certificate. - No action required.{' '} - </span> - ); - }, - }, - ]} - /> + return ( + <span> + Certificate Provisioned. We will automatiucally renew this + certificate. No action required.{" "} + </span> + ); + }, + }, + ]} + /> - {selectedStatusPageDomain?.fullDomain && showCnameModal && ( - <ConfirmModal - title={`Add CNAME`} - description={ - StatusPageCNameRecord ? ( - <div> - <span> - Please add CNAME record to your domain. - Details of the CNAME records are: - </span> - <br /> - <br /> - <span> - <b>Record Type: </b> CNAME - </span> - <br /> - <span> - <b>Name: </b> - {selectedStatusPageDomain?.fullDomain} - </span> - <br /> - <span> - <b>Content: </b> - {StatusPageCNameRecord} - </span> - <br /> - <br /> - <span> - Once you have done this, it should take - 24 hours to automatically verify. - </span> - </div> - ) : ( - <div> - <span> - Custom Domains not enabled for this - OneUptime installation. Please contact - your server admin to enable this - feature. - </span> - </div> - ) - } - submitButtonText={'Verify CNAME'} - onClose={() => { - setShowCnameModal(false); - setError(''); - return setSelectedStatusPageDomain(null); - }} - isLoading={verifyCnameLoading} - error={error} - onSubmit={async () => { - try { - setVerifyCnameLoading(true); - setError(''); + {selectedStatusPageDomain?.fullDomain && showCnameModal && ( + <ConfirmModal + title={`Add CNAME`} + description={ + StatusPageCNameRecord ? ( + <div> + <span> + Please add CNAME record to your domain. Details of the CNAME + records are: + </span> + <br /> + <br /> + <span> + <b>Record Type: </b> CNAME + </span> + <br /> + <span> + <b>Name: </b> + {selectedStatusPageDomain?.fullDomain} + </span> + <br /> + <span> + <b>Content: </b> + {StatusPageCNameRecord} + </span> + <br /> + <br /> + <span> + Once you have done this, it should take 24 hours to + automatically verify. + </span> + </div> + ) : ( + <div> + <span> + Custom Domains not enabled for this OneUptime installation. + Please contact your server admin to enable this feature. + </span> + </div> + ) + } + submitButtonText={"Verify CNAME"} + onClose={() => { + setShowCnameModal(false); + setError(""); + return setSelectedStatusPageDomain(null); + }} + isLoading={verifyCnameLoading} + error={error} + onSubmit={async () => { + try { + setVerifyCnameLoading(true); + setError(""); - const response: - | HTTPResponse<JSONObject> - | HTTPErrorResponse = await API.get<JSONObject>( - URL.fromString( - APP_API_URL.toString() - ).addRoute( - `/${ - new StatusPageDomain().crudApiPath - }/verify-cname/${selectedStatusPageDomain?.id?.toString()}` - ), - {}, - ModelAPI.getCommonHeaders() - ); + const response: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.get<JSONObject>( + URL.fromString(APP_API_URL.toString()).addRoute( + `/${ + new StatusPageDomain().crudApiPath + }/verify-cname/${selectedStatusPageDomain?.id?.toString()}`, + ), + {}, + ModelAPI.getCommonHeaders(), + ); - if (response.isFailure()) { - throw response; - } + if (response.isFailure()) { + throw response; + } - setShowCnameModal(false); - setRefreshToggle(!refreshToggle); - setSelectedStatusPageDomain(null); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + setShowCnameModal(false); + setRefreshToggle(!refreshToggle); + setSelectedStatusPageDomain(null); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - setVerifyCnameLoading(false); - }} - /> - )} + setVerifyCnameLoading(false); + }} + /> + )} - {showOrderSSLModal && selectedStatusPageDomain && ( - <ConfirmModal - title={`Order Free SSL Certificate for this Status Page`} - description={ - StatusPageCNameRecord ? ( - <div> - Please click on the button below to order - SSL for this domain. We will use LetsEncrypt - to order a certificate. This process is - secure and completely free. The certificate - takes 3 hours to provision after its been - ordered. - </div> - ) : ( - <div> - <span> - Custom Domains not enabled for this - OneUptime installation. Please contact - your server admin to enable this - feature. - </span> - </div> - ) - } - submitButtonText={'Order Free SSL'} - onClose={() => { - setShowOrderSSLModal(false); - setError(''); - return setSelectedStatusPageDomain(null); - }} - isLoading={orderSslLoading} - error={error} - onSubmit={async () => { - try { - setOrderSslLoading(true); - setError(''); + {showOrderSSLModal && selectedStatusPageDomain && ( + <ConfirmModal + title={`Order Free SSL Certificate for this Status Page`} + description={ + StatusPageCNameRecord ? ( + <div> + Please click on the button below to order SSL for this domain. + We will use LetsEncrypt to order a certificate. This process + is secure and completely free. The certificate takes 3 hours + to provision after its been ordered. + </div> + ) : ( + <div> + <span> + Custom Domains not enabled for this OneUptime installation. + Please contact your server admin to enable this feature. + </span> + </div> + ) + } + submitButtonText={"Order Free SSL"} + onClose={() => { + setShowOrderSSLModal(false); + setError(""); + return setSelectedStatusPageDomain(null); + }} + isLoading={orderSslLoading} + error={error} + onSubmit={async () => { + try { + setOrderSslLoading(true); + setError(""); - const response: - | HTTPResponse<JSONObject> - | HTTPErrorResponse = await API.get<JSONObject>( - URL.fromString( - APP_API_URL.toString() - ).addRoute( - `/${ - new StatusPageDomain().crudApiPath - }/order-ssl/${selectedStatusPageDomain?.id?.toString()}` - ), - {}, - ModelAPI.getCommonHeaders() - ); + const response: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.get<JSONObject>( + URL.fromString(APP_API_URL.toString()).addRoute( + `/${ + new StatusPageDomain().crudApiPath + }/order-ssl/${selectedStatusPageDomain?.id?.toString()}`, + ), + {}, + ModelAPI.getCommonHeaders(), + ); - if (response.isFailure()) { - throw response; - } + if (response.isFailure()) { + throw response; + } - setShowOrderSSLModal(false); - setRefreshToggle(!refreshToggle); - setSelectedStatusPageDomain(null); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + setShowOrderSSLModal(false); + setRefreshToggle(!refreshToggle); + setSelectedStatusPageDomain(null); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } - setOrderSslLoading(false); - }} - /> - )} - </> - </Fragment> - ); + setOrderSslLoading(false); + }} + /> + )} + </> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/EmailSubscribers.tsx b/Dashboard/src/Pages/StatusPages/View/EmailSubscribers.tsx index 25927eeff8..5f40b8b500 100644 --- a/Dashboard/src/Pages/StatusPages/View/EmailSubscribers.tsx +++ b/Dashboard/src/Pages/StatusPages/View/EmailSubscribers.tsx @@ -1,306 +1,283 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import NotNull from 'Common/Types/BaseDatabase/NotNull'; -import { Green, Red } from 'Common/Types/BrandColors'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert'; -import { CategoryCheckboxOptionsAndCategories } from 'CommonUI/src/Components/CategoryCheckbox/Index'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { ModelField } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import SubscriberUtil from 'CommonUI/src/Utils/StatusPage'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import NotNull from "Common/Types/BaseDatabase/NotNull"; +import { Green, Red } from "Common/Types/BrandColors"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import Alert, { AlertType } from "CommonUI/src/Components/Alerts/Alert"; +import { CategoryCheckboxOptionsAndCategories } from "CommonUI/src/Components/CategoryCheckbox/Index"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { ModelField } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import SubscriberUtil from "CommonUI/src/Utils/StatusPage"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [ - allowSubscribersToChooseResources, - setAllowSubscribersToChooseResources, - ] = React.useState<boolean>(false); - const [isEmailSubscribersEnabled, setIsEmailSubscribersEnabled] = - React.useState<boolean>(false); - const [isLoading, setIsLoading] = React.useState<boolean>(false); - const [error, setError] = React.useState<string>(''); - const [ - categoryCheckboxOptionsAndCategories, - setCategoryCheckboxOptionsAndCategories, - ] = useState<CategoryCheckboxOptionsAndCategories>({ - categories: [], - options: [], - }); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const [ + allowSubscribersToChooseResources, + setAllowSubscribersToChooseResources, + ] = React.useState<boolean>(false); + const [isEmailSubscribersEnabled, setIsEmailSubscribersEnabled] = + React.useState<boolean>(false); + const [isLoading, setIsLoading] = React.useState<boolean>(false); + const [error, setError] = React.useState<string>(""); + const [ + categoryCheckboxOptionsAndCategories, + setCategoryCheckboxOptionsAndCategories, + ] = useState<CategoryCheckboxOptionsAndCategories>({ + categories: [], + options: [], + }); - const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = - async (): Promise<void> => { - const result: CategoryCheckboxOptionsAndCategories = - await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources( - modelId - ); + const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = + async (): Promise<void> => { + const result: CategoryCheckboxOptionsAndCategories = + await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources(modelId); - setCategoryCheckboxOptionsAndCategories(result); - }; - - const fetchStatusPage: PromiseVoidFunction = async (): Promise<void> => { - try { - setIsLoading(true); - - const statusPage: StatusPage | null = await ModelAPI.getItem({ - modelType: StatusPage, - id: modelId, - select: { - allowSubscribersToChooseResources: true, - enableEmailSubscribers: true, - }, - }); - - if (statusPage && statusPage.allowSubscribersToChooseResources) { - setAllowSubscribersToChooseResources( - statusPage.allowSubscribersToChooseResources - ); - await fetchCheckboxOptionsAndCategories(); - } - - if (statusPage && statusPage.enableEmailSubscribers) { - setIsEmailSubscribersEnabled(statusPage.enableEmailSubscribers); - } - - setIsLoading(false); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); + setCategoryCheckboxOptionsAndCategories(result); }; - useEffect(() => { - fetchStatusPage().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); - }); - }, []); + const fetchStatusPage: PromiseVoidFunction = async (): Promise<void> => { + try { + setIsLoading(true); - const [formFields, setFormFields] = React.useState< - Array<ModelField<StatusPageSubscriber>> - >([]); + const statusPage: StatusPage | null = await ModelAPI.getItem({ + modelType: StatusPage, + id: modelId, + select: { + allowSubscribersToChooseResources: true, + enableEmailSubscribers: true, + }, + }); - useEffect(() => { - if (isLoading) { - return; // don't do anything if loading - } + if (statusPage && statusPage.allowSubscribersToChooseResources) { + setAllowSubscribersToChooseResources( + statusPage.allowSubscribersToChooseResources, + ); + await fetchCheckboxOptionsAndCategories(); + } - const formFields: Array<ModelField<StatusPageSubscriber>> = [ - { + if (statusPage && statusPage.enableEmailSubscribers) { + setIsEmailSubscribersEnabled(statusPage.enableEmailSubscribers); + } + + setIsLoading(false); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchStatusPage().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, []); + + const [formFields, setFormFields] = React.useState< + Array<ModelField<StatusPageSubscriber>> + >([]); + + useEffect(() => { + if (isLoading) { + return; // don't do anything if loading + } + + const formFields: Array<ModelField<StatusPageSubscriber>> = [ + { + field: { + subscriberEmail: true, + }, + title: "Email", + description: "Status page updates will be sent to this email.", + fieldType: FormFieldSchemaType.Email, + required: true, + placeholder: "subscriber@company.com", + }, + { + field: { + sendYouHaveSubscribedMessage: true, + }, + title: "Send Subscription Email", + description: + 'Send "You have subscribed to this status page" email to this subscriber?', + fieldType: FormFieldSchemaType.Toggle, + required: false, + doNotShowWhenEditing: true, + }, + { + field: { + isUnsubscribed: true, + }, + title: "Unsubscribe", + description: "Unsubscribe this email from the status page.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + doNotShowWhenCreating: true, + }, + ]; + + if (allowSubscribersToChooseResources) { + formFields.push({ + field: { + isSubscribedToAllResources: true, + }, + title: "Subscribe to All Resources", + description: "Send notifications for all resources.", + fieldType: FormFieldSchemaType.Checkbox, + required: false, + defaultValue: true, + }); + + formFields.push({ + field: { + statusPageResources: true, + }, + title: "Select Resources to Subscribe", + description: "Please select the resources you want to subscribe to.", + fieldType: FormFieldSchemaType.CategoryCheckbox, + required: false, + categoryCheckboxProps: categoryCheckboxOptionsAndCategories, + showIf: (model: FormValues<StatusPageSubscriber>) => { + return !model || !model.isSubscribedToAllResources; + }, + }); + } + + setFormFields(formFields); + }, [isLoading]); + + return ( + <Fragment> + {isLoading ? <PageLoader isVisible={true} /> : <></>} + + {error ? <ErrorMessage error={error} /> : <></>} + + {!error && !isLoading ? ( + <> + {!isEmailSubscribersEnabled && ( + <Alert + type={AlertType.DANGER} + title="Email subscribers are not enabled for this status page. Please enable it in Subscriber Settings" + /> + )} + <ModelTable<StatusPageSubscriber> + modelType={StatusPageSubscriber} + id="table-subscriber" + name="Status Page > Email Subscribers" + isDeleteable={true} + showViewIdButton={true} + isCreateable={true} + isEditable={true} + isViewable={false} + selectMoreFields={{ + subscriberPhone: true, + }} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + subscriberEmail: new NotNull(), + }} + onBeforeCreate={( + item: StatusPageSubscriber, + ): Promise<StatusPageSubscriber> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Email Subscribers", + description: + "Here are the list of subscribers who have subscribed to the status page.", + }} + noItemsMessage={"No subscribers found."} + formFields={formFields} + showRefreshButton={true} + filters={[ + { field: { - subscriberEmail: true, + subscriberEmail: true, }, - title: 'Email', - description: 'Status page updates will be sent to this email.', - fieldType: FormFieldSchemaType.Email, - required: true, - placeholder: 'subscriber@company.com', - }, - { + title: "Email", + type: FieldType.Text, + }, + { field: { - sendYouHaveSubscribedMessage: true, + isUnsubscribed: true, }, - title: 'Send Subscription Email', - description: - 'Send "You have subscribed to this status page" email to this subscriber?', - fieldType: FormFieldSchemaType.Toggle, - required: false, - doNotShowWhenEditing: true, - }, - { + title: "Unsubscribed", + type: FieldType.Boolean, + }, + { field: { - isUnsubscribed: true, + createdAt: true, }, - title: 'Unsubscribe', - description: 'Unsubscribe this email from the status page.', - fieldType: FormFieldSchemaType.Toggle, - required: false, - doNotShowWhenCreating: true, - }, - ]; - - if (allowSubscribersToChooseResources) { - formFields.push({ + title: "Subscribed At", + type: FieldType.Date, + }, + ]} + viewPageRoute={Navigation.getCurrentRoute()} + columns={[ + { field: { - isSubscribedToAllResources: true, + subscriberEmail: true, }, - title: 'Subscribe to All Resources', - description: 'Send notifications for all resources.', - fieldType: FormFieldSchemaType.Checkbox, - required: false, - defaultValue: true, - }); - - formFields.push({ + title: "Email", + type: FieldType.Email, + }, + { field: { - statusPageResources: true, + isUnsubscribed: true, }, - title: 'Select Resources to Subscribe', - description: - 'Please select the resources you want to subscribe to.', - fieldType: FormFieldSchemaType.CategoryCheckbox, - required: false, - categoryCheckboxProps: categoryCheckboxOptionsAndCategories, - showIf: (model: FormValues<StatusPageSubscriber>) => { - return !model || !model.isSubscribedToAllResources; + title: "Status", + type: FieldType.Text, + getElement: (item: StatusPageSubscriber): ReactElement => { + if (item["isUnsubscribed"]) { + return <Pill color={Red} text={"Unsubscribed"} />; + } + return <Pill color={Green} text={"Subscribed"} />; }, - }); - } - - setFormFields(formFields); - }, [isLoading]); - - return ( - <Fragment> - {isLoading ? <PageLoader isVisible={true} /> : <></>} - - {error ? <ErrorMessage error={error} /> : <></>} - - {!error && !isLoading ? ( - <> - {!isEmailSubscribersEnabled && ( - <Alert - type={AlertType.DANGER} - title="Email subscribers are not enabled for this status page. Please enable it in Subscriber Settings" - /> - )} - <ModelTable<StatusPageSubscriber> - modelType={StatusPageSubscriber} - id="table-subscriber" - name="Status Page > Email Subscribers" - isDeleteable={true} - showViewIdButton={true} - isCreateable={true} - isEditable={true} - isViewable={false} - selectMoreFields={{ - subscriberPhone: true, - }} - query={{ - statusPageId: modelId, - projectId: - DashboardNavigation.getProjectId()?.toString(), - subscriberEmail: new NotNull(), - }} - onBeforeCreate={( - item: StatusPageSubscriber - ): Promise<StatusPageSubscriber> => { - if ( - !props.currentProject || - !props.currentProject._id - ) { - throw new BadDataException( - 'Project ID cannot be null' - ); - } - - item.statusPageId = modelId; - item.projectId = new ObjectID( - props.currentProject._id - ); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Email Subscribers', - description: - 'Here are the list of subscribers who have subscribed to the status page.', - }} - noItemsMessage={'No subscribers found.'} - formFields={formFields} - showRefreshButton={true} - filters={[ - { - field: { - subscriberEmail: true, - }, - title: 'Email', - type: FieldType.Text, - }, - { - field: { - isUnsubscribed: true, - }, - title: 'Unsubscribed', - type: FieldType.Boolean, - }, - { - field: { - createdAt: true, - }, - title: 'Subscribed At', - type: FieldType.Date, - }, - ]} - viewPageRoute={Navigation.getCurrentRoute()} - columns={[ - { - field: { - subscriberEmail: true, - }, - title: 'Email', - type: FieldType.Email, - }, - { - field: { - isUnsubscribed: true, - }, - title: 'Status', - type: FieldType.Text, - getElement: ( - item: StatusPageSubscriber - ): ReactElement => { - if (item['isUnsubscribed']) { - return ( - <Pill - color={Red} - text={'Unsubscribed'} - /> - ); - } - return ( - <Pill - color={Green} - text={'Subscribed'} - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Subscribed At', - type: FieldType.DateTime, - }, - ]} - /> - </> - ) : ( - <></> - )} - </Fragment> - ); + }, + { + field: { + createdAt: true, + }, + title: "Subscribed At", + type: FieldType.DateTime, + }, + ]} + /> + </> + ) : ( + <></> + )} + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/Embedded.tsx b/Dashboard/src/Pages/StatusPages/View/Embedded.tsx index 6018d947ed..88ce9db796 100644 --- a/Dashboard/src/Pages/StatusPages/View/Embedded.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Embedded.tsx @@ -1,31 +1,29 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={StatusPage} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.STATUS_PAGES] as Route - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={StatusPage} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate(RouteMap[PageMap.STATUS_PAGES] as Route); + }} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/FooterStyle.tsx b/Dashboard/src/Pages/StatusPages/View/FooterStyle.tsx index e6457fbc0c..ef49e3771f 100644 --- a/Dashboard/src/Pages/StatusPages/View/FooterStyle.tsx +++ b/Dashboard/src/Pages/StatusPages/View/FooterStyle.tsx @@ -1,149 +1,149 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageFooterLink from 'Model/Models/StatusPageFooterLink'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageFooterLink from "Model/Models/StatusPageFooterLink"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CardModelDetail<StatusPage> - name="Status Page > Branding > Copyright" - cardProps={{ - title: 'Copyright Info', - description: 'Copyright info for your status page', - }} - isEditable={true} - editButtonText={'Edit Copyright'} - formFields={[ - { - field: { - copyrightText: true, - }, - title: 'Copyright Info', - fieldType: FormFieldSchemaType.Text, - required: false, - placeholder: 'Acme, Inc.', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - copyrightText: true, - }, - fieldType: FieldType.Text, - title: 'Copyright Info', - placeholder: 'No copyright info entered so far.', - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + <CardModelDetail<StatusPage> + name="Status Page > Branding > Copyright" + cardProps={{ + title: "Copyright Info", + description: "Copyright info for your status page", + }} + isEditable={true} + editButtonText={"Edit Copyright"} + formFields={[ + { + field: { + copyrightText: true, + }, + title: "Copyright Info", + fieldType: FormFieldSchemaType.Text, + required: false, + placeholder: "Acme, Inc.", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + copyrightText: true, + }, + fieldType: FieldType.Text, + title: "Copyright Info", + placeholder: "No copyright info entered so far.", + }, + ], + modelId: modelId, + }} + /> - <ModelTable<StatusPageFooterLink> - modelType={StatusPageFooterLink} - id="status-page-Footer-link" - isDeleteable={true} - name="Status Page > Footer Links" - sortBy="order" - sortOrder={SortOrder.Ascending} - isCreateable={true} - isViewable={false} - isEditable={true} - query={{ - statusPageId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - enableDragAndDrop={true} - dragDropIndexField="order" - onBeforeCreate={( - item: StatusPageFooterLink - ): Promise<StatusPageFooterLink> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.statusPageId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Footer Links', - description: 'Footer Links for your status page', - }} - noItemsMessage={'No status footer link for this status page.'} - formFields={[ - { - field: { - title: true, - }, - title: 'Title', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Title', - }, - { - field: { - link: true, - }, - title: 'Link', - fieldType: FormFieldSchemaType.URL, - required: true, - placeholder: 'https://link.com', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - link: true, - }, - title: 'Link', - type: FieldType.URL, - }, - ]} - columns={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - link: true, - }, - title: 'Link', - type: FieldType.URL, - }, - ]} - /> - </Fragment> - ); + <ModelTable<StatusPageFooterLink> + modelType={StatusPageFooterLink} + id="status-page-Footer-link" + isDeleteable={true} + name="Status Page > Footer Links" + sortBy="order" + sortOrder={SortOrder.Ascending} + isCreateable={true} + isViewable={false} + isEditable={true} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + enableDragAndDrop={true} + dragDropIndexField="order" + onBeforeCreate={( + item: StatusPageFooterLink, + ): Promise<StatusPageFooterLink> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Footer Links", + description: "Footer Links for your status page", + }} + noItemsMessage={"No status footer link for this status page."} + formFields={[ + { + field: { + title: true, + }, + title: "Title", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Title", + }, + { + field: { + link: true, + }, + title: "Link", + fieldType: FormFieldSchemaType.URL, + required: true, + placeholder: "https://link.com", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + link: true, + }, + title: "Link", + type: FieldType.URL, + }, + ]} + columns={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + link: true, + }, + title: "Link", + type: FieldType.URL, + }, + ]} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/Groups.tsx b/Dashboard/src/Pages/StatusPages/View/Groups.tsx index c41e483e96..1c00e0706c 100644 --- a/Dashboard/src/Pages/StatusPages/View/Groups.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Groups.tsx @@ -1,121 +1,117 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPageGroup from 'Model/Models/StatusPageGroup'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPageGroup from "Model/Models/StatusPageGroup"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<StatusPageGroup> - modelType={StatusPageGroup} - id="status-page-group" - name="Status Page > Groups" - isDeleteable={true} - sortBy="order" - showViewIdButton={true} - sortOrder={SortOrder.Ascending} - isCreateable={true} - isViewable={false} - isEditable={true} - query={{ - statusPageId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - enableDragAndDrop={true} - dragDropIndexField="order" - onBeforeCreate={( - item: StatusPageGroup - ): Promise<StatusPageGroup> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.statusPageId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Resource Groups', - description: - 'Here are different groups for your status page resources.', - }} - noItemsMessage={ - 'No status page group created for this status page.' - } - formFields={[ - { - field: { - name: true, - }, - title: 'Group Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Resource Group Name', - }, - { - field: { - description: true, - }, - title: 'Group Description', - fieldType: FormFieldSchemaType.Markdown, - required: false, - }, - { - field: { - isExpandedByDefault: true, - }, - title: 'Expand on Status Page by Default', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - name: true, - }, - title: 'Resource Group Name', - type: FieldType.Text, - }, - { - field: { - isExpandedByDefault: true, - }, - title: 'Expanded on Status Page by Default', - type: FieldType.Boolean, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Resource Group Name', - type: FieldType.Text, - }, - { - field: { - isExpandedByDefault: true, - }, - title: 'Expanded on Status Page by Default', - type: FieldType.Boolean, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<StatusPageGroup> + modelType={StatusPageGroup} + id="status-page-group" + name="Status Page > Groups" + isDeleteable={true} + sortBy="order" + showViewIdButton={true} + sortOrder={SortOrder.Ascending} + isCreateable={true} + isViewable={false} + isEditable={true} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + enableDragAndDrop={true} + dragDropIndexField="order" + onBeforeCreate={(item: StatusPageGroup): Promise<StatusPageGroup> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Resource Groups", + description: + "Here are different groups for your status page resources.", + }} + noItemsMessage={"No status page group created for this status page."} + formFields={[ + { + field: { + name: true, + }, + title: "Group Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Resource Group Name", + }, + { + field: { + description: true, + }, + title: "Group Description", + fieldType: FormFieldSchemaType.Markdown, + required: false, + }, + { + field: { + isExpandedByDefault: true, + }, + title: "Expand on Status Page by Default", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + name: true, + }, + title: "Resource Group Name", + type: FieldType.Text, + }, + { + field: { + isExpandedByDefault: true, + }, + title: "Expanded on Status Page by Default", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Resource Group Name", + type: FieldType.Text, + }, + { + field: { + isExpandedByDefault: true, + }, + title: "Expanded on Status Page by Default", + type: FieldType.Boolean, + }, + ]} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/HeaderStyle.tsx b/Dashboard/src/Pages/StatusPages/View/HeaderStyle.tsx index 6dad40675e..9535fefd3b 100644 --- a/Dashboard/src/Pages/StatusPages/View/HeaderStyle.tsx +++ b/Dashboard/src/Pages/StatusPages/View/HeaderStyle.tsx @@ -1,172 +1,172 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageHeaderLink from 'Model/Models/StatusPageHeaderLink'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageHeaderLink from "Model/Models/StatusPageHeaderLink"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CardModelDetail<StatusPage> - name="Status Page > Branding > Header Style" - cardProps={{ - title: 'Logo, Cover and Favicon', - description: 'These will show up on your status page.', - }} - isEditable={true} - editButtonText={'Edit Images'} - formFields={[ - { - field: { - logoFile: true, - }, - title: 'Logo', - fieldType: FormFieldSchemaType.ImageFile, - required: false, - placeholder: 'Upload logo', - }, - { - field: { - coverImageFile: true, - }, - title: 'Cover', - fieldType: FormFieldSchemaType.ImageFile, - required: false, - placeholder: 'Upload cover image', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - logoFile: { - file: true, - type: true, - }, - }, - fieldType: FieldType.ImageFile, - title: 'Logo', - placeholder: 'No logo uploaded.', - }, - { - field: { - coverImageFile: { - file: true, - type: true, - }, - }, - fieldType: FieldType.ImageFile, - title: 'Cover Image', - placeholder: 'No cover uploaded.', - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + <CardModelDetail<StatusPage> + name="Status Page > Branding > Header Style" + cardProps={{ + title: "Logo, Cover and Favicon", + description: "These will show up on your status page.", + }} + isEditable={true} + editButtonText={"Edit Images"} + formFields={[ + { + field: { + logoFile: true, + }, + title: "Logo", + fieldType: FormFieldSchemaType.ImageFile, + required: false, + placeholder: "Upload logo", + }, + { + field: { + coverImageFile: true, + }, + title: "Cover", + fieldType: FormFieldSchemaType.ImageFile, + required: false, + placeholder: "Upload cover image", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + logoFile: { + file: true, + type: true, + }, + }, + fieldType: FieldType.ImageFile, + title: "Logo", + placeholder: "No logo uploaded.", + }, + { + field: { + coverImageFile: { + file: true, + type: true, + }, + }, + fieldType: FieldType.ImageFile, + title: "Cover Image", + placeholder: "No cover uploaded.", + }, + ], + modelId: modelId, + }} + /> - <ModelTable<StatusPageHeaderLink> - modelType={StatusPageHeaderLink} - id="status-page-header-link" - name="Status Page > Header Links" - isDeleteable={true} - sortBy="order" - sortOrder={SortOrder.Ascending} - isCreateable={true} - isEditable={true} - isViewable={false} - query={{ - statusPageId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - enableDragAndDrop={true} - dragDropIndexField="order" - onBeforeCreate={( - item: StatusPageHeaderLink - ): Promise<StatusPageHeaderLink> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.statusPageId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Header Links', - description: 'Header Links for your status page', - }} - noItemsMessage={'No status header link for this status page.'} - formFields={[ - { - field: { - title: true, - }, - title: 'Title', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Title', - }, - { - field: { - link: true, - }, - title: 'Link', - fieldType: FormFieldSchemaType.URL, - required: true, - placeholder: 'https://link.com', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - link: true, - }, - title: 'Link', - type: FieldType.URL, - }, - ]} - columns={[ - { - field: { - title: true, - }, - title: 'Title', - type: FieldType.Text, - }, - { - field: { - link: true, - }, - title: 'Link', - type: FieldType.URL, - }, - ]} - /> - </Fragment> - ); + <ModelTable<StatusPageHeaderLink> + modelType={StatusPageHeaderLink} + id="status-page-header-link" + name="Status Page > Header Links" + isDeleteable={true} + sortBy="order" + sortOrder={SortOrder.Ascending} + isCreateable={true} + isEditable={true} + isViewable={false} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + enableDragAndDrop={true} + dragDropIndexField="order" + onBeforeCreate={( + item: StatusPageHeaderLink, + ): Promise<StatusPageHeaderLink> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Header Links", + description: "Header Links for your status page", + }} + noItemsMessage={"No status header link for this status page."} + formFields={[ + { + field: { + title: true, + }, + title: "Title", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Title", + }, + { + field: { + link: true, + }, + title: "Link", + fieldType: FormFieldSchemaType.URL, + required: true, + placeholder: "https://link.com", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + link: true, + }, + title: "Link", + type: FieldType.URL, + }, + ]} + columns={[ + { + field: { + title: true, + }, + title: "Title", + type: FieldType.Text, + }, + { + field: { + link: true, + }, + title: "Link", + type: FieldType.URL, + }, + ]} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/Index.tsx b/Dashboard/src/Pages/StatusPages/View/Index.tsx index 90f391ef4b..e5af76cfa3 100644 --- a/Dashboard/src/Pages/StatusPages/View/Index.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Index.tsx @@ -1,130 +1,126 @@ -import LabelsElement from '../../../Components/Label/Labels'; -import PageComponentProps from '../../PageComponentProps'; -import StatusPagePreviewLink from './StatusPagePreviewLink'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../../Components/Label/Labels"; +import PageComponentProps from "../../PageComponentProps"; +import StatusPagePreviewLink from "./StatusPagePreviewLink"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import StatusPage from "Model/Models/StatusPage"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - return ( - <Fragment> - <StatusPagePreviewLink modelId={modelId} /> + return ( + <Fragment> + <StatusPagePreviewLink modelId={modelId} /> - {/* StatusPage View */} - <CardModelDetail<StatusPage> - name="Status Page > Status Page Details" - cardProps={{ - title: 'Status Page Details', - description: 'Here are more details for this status page.', - }} - formSteps={[ - { - title: 'Status Page Info', - id: 'status-page-info', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - stepId: 'status-page-info', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Status Page Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'status-page-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - labels: true, - }, - title: 'Labels ', - stepId: 'labels', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - _id: true, - }, - title: 'Status Page ID', - }, - { - field: { - name: true, - }, - title: 'Status Page Name', - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: (item: StatusPage): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + {/* StatusPage View */} + <CardModelDetail<StatusPage> + name="Status Page > Status Page Details" + cardProps={{ + title: "Status Page Details", + description: "Here are more details for this status page.", + }} + formSteps={[ + { + title: "Status Page Info", + id: "status-page-info", + }, + { + title: "Labels", + id: "labels", + }, + ]} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + stepId: "status-page-info", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Status Page Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "status-page-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + labels: true, + }, + title: "Labels ", + stepId: "labels", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + _id: true, + }, + title: "Status Page ID", + }, + { + field: { + name: true, + }, + title: "Status Page Name", + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: StatusPage): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default StatusPageView; diff --git a/Dashboard/src/Pages/StatusPages/View/Layout.tsx b/Dashboard/src/Pages/StatusPages/View/Layout.tsx index 6813c2f5cf..b28f78c91b 100644 --- a/Dashboard/src/Pages/StatusPages/View/Layout.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Layout.tsx @@ -1,32 +1,32 @@ -import { getStatusPagesBreadcrumbs } from '../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getStatusPagesBreadcrumbs } from "../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const StatusPageViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="Status Page" - modelType={StatusPage} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={getStatusPagesBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <ModelPage + title="Status Page" + modelType={StatusPage} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={getStatusPagesBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default StatusPageViewLayout; diff --git a/Dashboard/src/Pages/StatusPages/View/NavBarStyle.tsx b/Dashboard/src/Pages/StatusPages/View/NavBarStyle.tsx index 496abdff08..c84a981823 100644 --- a/Dashboard/src/Pages/StatusPages/View/NavBarStyle.tsx +++ b/Dashboard/src/Pages/StatusPages/View/NavBarStyle.tsx @@ -1,14 +1,14 @@ -import PageComponentProps from '../../PageComponentProps'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <></> - </Fragment> - ); + return ( + <Fragment> + <></> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/OverviewPageBranding.tsx b/Dashboard/src/Pages/StatusPages/View/OverviewPageBranding.tsx index da70b5e943..833d474580 100644 --- a/Dashboard/src/Pages/StatusPages/View/OverviewPageBranding.tsx +++ b/Dashboard/src/Pages/StatusPages/View/OverviewPageBranding.tsx @@ -1,251 +1,250 @@ -import MonitorStatuesElement from '../../../Components/MonitorStatus/MonitorStatusesElement'; -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageHistoryChartBarColorRule from 'Model/Models/StatusPageHistoryChartBarColorRule'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import MonitorStatuesElement from "../../../Components/MonitorStatus/MonitorStatusesElement"; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageHistoryChartBarColorRule from "Model/Models/StatusPageHistoryChartBarColorRule"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CardModelDetail<StatusPage> - name="Status Page > Branding > Overview Page" - cardProps={{ - title: 'Overview Page', - description: - 'Essential branding elements for overview page.', - }} - isEditable={true} - editButtonText={'Edit Branding'} - formFields={[ - { - field: { - overviewPageDescription: true, - }, - title: 'Overview Page Description.', - fieldType: FormFieldSchemaType.Markdown, - required: false, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'overview-page-description', - fields: [ - { - field: { - overviewPageDescription: true, - }, - fieldType: FieldType.Markdown, - title: 'Overview Page Description', - placeholder: 'No description set.', - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + <CardModelDetail<StatusPage> + name="Status Page > Branding > Overview Page" + cardProps={{ + title: "Overview Page", + description: "Essential branding elements for overview page.", + }} + isEditable={true} + editButtonText={"Edit Branding"} + formFields={[ + { + field: { + overviewPageDescription: true, + }, + title: "Overview Page Description.", + fieldType: FormFieldSchemaType.Markdown, + required: false, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "overview-page-description", + fields: [ + { + field: { + overviewPageDescription: true, + }, + fieldType: FieldType.Markdown, + title: "Overview Page Description", + placeholder: "No description set.", + }, + ], + modelId: modelId, + }} + /> - <ModelTable<StatusPageHistoryChartBarColorRule> - modelType={StatusPageHistoryChartBarColorRule} - id={`status-page-history-chart-bar-color-rules`} - isDeleteable={true} - name="Status Page > Branding > History Chart Bar Color Rules" - sortBy="order" - showViewIdButton={true} - sortOrder={SortOrder.Ascending} - isCreateable={true} - isViewable={false} - isEditable={true} - query={{ - statusPageId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - enableDragAndDrop={true} - dragDropIndexField="order" - singularName="Rule" - pluralName="Rules" - onBeforeCreate={( - item: StatusPageHistoryChartBarColorRule - ): Promise<StatusPageHistoryChartBarColorRule> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } + <ModelTable<StatusPageHistoryChartBarColorRule> + modelType={StatusPageHistoryChartBarColorRule} + id={`status-page-history-chart-bar-color-rules`} + isDeleteable={true} + name="Status Page > Branding > History Chart Bar Color Rules" + sortBy="order" + showViewIdButton={true} + sortOrder={SortOrder.Ascending} + isCreateable={true} + isViewable={false} + isEditable={true} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + enableDragAndDrop={true} + dragDropIndexField="order" + singularName="Rule" + pluralName="Rules" + onBeforeCreate={( + item: StatusPageHistoryChartBarColorRule, + ): Promise<StatusPageHistoryChartBarColorRule> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } - item.statusPageId = modelId; - item.projectId = new ObjectID(props.currentProject._id); + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: `Rules for Bar Colors of History Chart`, - description: 'Rules for history chart bar colors.', - }} - noItemsMessage={ - 'No history chart bar color rules have been set. By default the lowest monitor state color of that particular day will be used.' + return Promise.resolve(item); + }} + cardProps={{ + title: `Rules for Bar Colors of History Chart`, + description: "Rules for history chart bar colors.", + }} + noItemsMessage={ + "No history chart bar color rules have been set. By default the lowest monitor state color of that particular day will be used." + } + formFields={[ + { + field: { + uptimePercentGreaterThanOrEqualTo: true, + }, + title: "When uptime % is greater than or equal to", + description: + "This rule will be applied when uptime is greater than or equal to this value.", + fieldType: FormFieldSchemaType.Number, + validation: { + minValue: 0, + maxValue: 100, + }, + required: true, + placeholder: "90", + }, + { + field: { + barColor: true, + }, + title: "Then, use this bar color", + fieldType: FormFieldSchemaType.Color, + required: true, + placeholder: "No color set", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[]} + columns={[ + { + field: { + uptimePercentGreaterThanOrEqualTo: true, + }, + title: "When Uptime Percent >=", + type: FieldType.Percent, + }, + { + field: { + barColor: true, + }, + title: "Then, Bar Color is", + type: FieldType.Color, + }, + ]} + /> + + <CardModelDetail<StatusPage> + name="Status Page > Branding > Downtime Monitor Statuses" + cardProps={{ + title: "Downtime Monitor Statuses", + description: + "These monitor statuses are be considered as down when we calculate uptime %.", + }} + isEditable={true} + editButtonText={"Edit Statuses"} + formFields={[ + { + field: { + downtimeMonitorStatuses: true, + }, + title: "These monitor statuses are considered as down", + description: + "These monitor statuses are be considered as down when we calculate uptime %.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: MonitorStatus, + labelField: "name", + valueField: "_id", + }, + required: true, + placeholder: "Select monitor statuses", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "default-bar-color", + fields: [ + { + field: { + downtimeMonitorStatuses: { + _id: true, + name: true, + color: true, + }, + }, + title: "Downtime Monitor Statuses", + description: + "These monitor statuses are be considered as down when we calculate uptime %", + fieldType: FieldType.EntityArray, + getElement: (item: StatusPage): ReactElement => { + if (item["downtimeMonitorStatuses"]) { + return ( + <MonitorStatuesElement + monitorStatuses={ + (item[ + "downtimeMonitorStatuses" + ] as Array<MonitorStatus>) || [] + } + /> + ); } - formFields={[ - { - field: { - uptimePercentGreaterThanOrEqualTo: true, - }, - title: 'When uptime % is greater than or equal to', - description: - 'This rule will be applied when uptime is greater than or equal to this value.', - fieldType: FormFieldSchemaType.Number, - validation: { - minValue: 0, - maxValue: 100, - }, - required: true, - placeholder: '90', - }, - { - field: { - barColor: true, - }, - title: 'Then, use this bar color', - fieldType: FormFieldSchemaType.Color, - required: true, - placeholder: 'No color set', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[]} - columns={[ - { - field: { - uptimePercentGreaterThanOrEqualTo: true, - }, - title: 'When Uptime Percent >=', - type: FieldType.Percent, - }, - { - field: { - barColor: true, - }, - title: 'Then, Bar Color is', - type: FieldType.Color, - }, - ]} - /> - <CardModelDetail<StatusPage> - name="Status Page > Branding > Downtime Monitor Statuses" - cardProps={{ - title: 'Downtime Monitor Statuses', - description: - 'These monitor statuses are be considered as down when we calculate uptime %.', - }} - isEditable={true} - editButtonText={'Edit Statuses'} - formFields={[ - { - field: { - downtimeMonitorStatuses: true, - }, - title: 'These monitor statuses are considered as down', - description: - 'These monitor statuses are be considered as down when we calculate uptime %.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: MonitorStatus, - labelField: 'name', - valueField: '_id', - }, - required: true, - placeholder: 'Select monitor statuses', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'default-bar-color', - fields: [ - { - field: { - downtimeMonitorStatuses: { - _id: true, - name: true, - color: true, - }, - }, - title: 'Downtime Monitor Statuses', - description: - 'These monitor statuses are be considered as down when we calculate uptime %', - fieldType: FieldType.EntityArray, - getElement: (item: StatusPage): ReactElement => { - if (item['downtimeMonitorStatuses']) { - return ( - <MonitorStatuesElement - monitorStatuses={ - (item[ - 'downtimeMonitorStatuses' - ] as Array<MonitorStatus>) || [] - } - /> - ); - } + return <></>; + }, + }, + ], + modelId: modelId, + }} + /> - return <></>; - }, - }, - ], - modelId: modelId, - }} - /> - - <CardModelDetail<StatusPage> - name="Status Page > Branding > Default Bar Color" - cardProps={{ - title: 'Default Bar Color of the History Chart', - description: - 'Bar color will be used for history chart when no data is set.', - }} - isEditable={true} - editButtonText={'Edit Default Bar Color'} - formFields={[ - { - field: { - defaultBarColor: true, - }, - title: 'Default Bar Color', - fieldType: FormFieldSchemaType.Color, - required: true, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'default-bar-color', - fields: [ - { - field: { - defaultBarColor: true, - }, - fieldType: FieldType.Color, - title: 'Default Bar Color', - placeholder: 'No color set.', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + <CardModelDetail<StatusPage> + name="Status Page > Branding > Default Bar Color" + cardProps={{ + title: "Default Bar Color of the History Chart", + description: + "Bar color will be used for history chart when no data is set.", + }} + isEditable={true} + editButtonText={"Edit Default Bar Color"} + formFields={[ + { + field: { + defaultBarColor: true, + }, + title: "Default Bar Color", + fieldType: FormFieldSchemaType.Color, + required: true, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "default-bar-color", + fields: [ + { + field: { + defaultBarColor: true, + }, + fieldType: FieldType.Color, + title: "Default Bar Color", + placeholder: "No color set.", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/Owners.tsx b/Dashboard/src/Pages/StatusPages/View/Owners.tsx index 29f6a6a6a9..742ab77426 100644 --- a/Dashboard/src/Pages/StatusPages/View/Owners.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Owners.tsx @@ -1,235 +1,226 @@ -import TeamElement from '../../../Components/Team/Team'; -import UserElement from '../../../Components/User/User'; -import DashboardNavigation from '../../../Utils/Navigation'; -import ProjectUser from '../../../Utils/ProjectUser'; -import PageComponentProps from '../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPageOwnerTeam from 'Model/Models/StatusPageOwnerTeam'; -import StatusPageOwnerUser from 'Model/Models/StatusPageOwnerUser'; -import Team from 'Model/Models/Team'; -import User from 'Model/Models/User'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import TeamElement from "../../../Components/Team/Team"; +import UserElement from "../../../Components/User/User"; +import DashboardNavigation from "../../../Utils/Navigation"; +import ProjectUser from "../../../Utils/ProjectUser"; +import PageComponentProps from "../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPageOwnerTeam from "Model/Models/StatusPageOwnerTeam"; +import StatusPageOwnerUser from "Model/Models/StatusPageOwnerUser"; +import Team from "Model/Models/Team"; +import User from "Model/Models/User"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageOwners: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<StatusPageOwnerTeam> - modelType={StatusPageOwnerTeam} - id="table-statusPage-owner-team" - name="StatusPage > Owner Team" - singularName="Team" - isDeleteable={true} - createVerb={'Add'} - isCreateable={true} - isViewable={false} - showViewIdButton={true} - query={{ - statusPageId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: StatusPageOwnerTeam - ): Promise<StatusPageOwnerTeam> => { - item.statusPageId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Teams)', - description: - 'Here is list of teams that own this status page. They will be alerted when this status page is created or updated.', - }} - noItemsMessage={ - 'No teams associated with this status page so far.' - } - formFields={[ - { - field: { - team: true, - }, - title: 'Team', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select Team', - dropdownModal: { - type: Team, - labelField: 'name', - valueField: '_id', - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - team: true, - }, - type: FieldType.Entity, - title: 'Team', - filterEntityType: Team, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - team: { - name: true, - }, - }, - title: 'Team', - type: FieldType.Entity, + return ( + <Fragment> + <ModelTable<StatusPageOwnerTeam> + modelType={StatusPageOwnerTeam} + id="table-statusPage-owner-team" + name="StatusPage > Owner Team" + singularName="Team" + isDeleteable={true} + createVerb={"Add"} + isCreateable={true} + isViewable={false} + showViewIdButton={true} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: StatusPageOwnerTeam, + ): Promise<StatusPageOwnerTeam> => { + item.statusPageId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Teams)", + description: + "Here is list of teams that own this status page. They will be alerted when this status page is created or updated.", + }} + noItemsMessage={"No teams associated with this status page so far."} + formFields={[ + { + field: { + team: true, + }, + title: "Team", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select Team", + dropdownModal: { + type: Team, + labelField: "name", + valueField: "_id", + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + team: true, + }, + type: FieldType.Entity, + title: "Team", + filterEntityType: Team, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + team: { + name: true, + }, + }, + title: "Team", + type: FieldType.Entity, - getElement: ( - item: StatusPageOwnerTeam - ): ReactElement => { - if (!item['team']) { - throw new BadDataException('Team not found'); - } + getElement: (item: StatusPageOwnerTeam): ReactElement => { + if (!item["team"]) { + throw new BadDataException("Team not found"); + } - return <TeamElement team={item['team'] as Team} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> + return <TeamElement team={item["team"] as Team} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> - <ModelTable<StatusPageOwnerUser> - modelType={StatusPageOwnerUser} - id="table-statusPage-owner-team" - name="StatusPage > Owner Team" - isDeleteable={true} - singularName="User" - isCreateable={true} - isViewable={false} - showViewIdButton={true} - createVerb={'Add'} - query={{ - statusPageId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: StatusPageOwnerUser - ): Promise<StatusPageOwnerUser> => { - item.statusPageId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Owners (Users)', - description: - 'Here is list of users that own this status page. They will be alerted when this status page is created or updated.', - }} - noItemsMessage={ - 'No users associated with this status page so far.' - } - formFields={[ - { - field: { - user: true, - }, - title: 'User', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select User', - fetchDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - user: true, - }, - title: 'User', - type: FieldType.Entity, - filterEntityType: User, - fetchFilterDropdownOptions: async () => { - return await ProjectUser.fetchProjectUsersAsDropdownOptions( - DashboardNavigation.getProjectId()! - ); - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - user: { - name: true, - email: true, - profilePictureId: true, - }, - }, - title: 'User', - type: FieldType.Entity, + <ModelTable<StatusPageOwnerUser> + modelType={StatusPageOwnerUser} + id="table-statusPage-owner-team" + name="StatusPage > Owner Team" + isDeleteable={true} + singularName="User" + isCreateable={true} + isViewable={false} + showViewIdButton={true} + createVerb={"Add"} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: StatusPageOwnerUser, + ): Promise<StatusPageOwnerUser> => { + item.statusPageId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Owners (Users)", + description: + "Here is list of users that own this status page. They will be alerted when this status page is created or updated.", + }} + noItemsMessage={"No users associated with this status page so far."} + formFields={[ + { + field: { + user: true, + }, + title: "User", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select User", + fetchDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + user: true, + }, + title: "User", + type: FieldType.Entity, + filterEntityType: User, + fetchFilterDropdownOptions: async () => { + return await ProjectUser.fetchProjectUsersAsDropdownOptions( + DashboardNavigation.getProjectId()!, + ); + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + user: { + name: true, + email: true, + profilePictureId: true, + }, + }, + title: "User", + type: FieldType.Entity, - getElement: ( - item: StatusPageOwnerUser - ): ReactElement => { - if (!item['user']) { - throw new BadDataException('User not found'); - } + getElement: (item: StatusPageOwnerUser): ReactElement => { + if (!item["user"]) { + throw new BadDataException("User not found"); + } - return <UserElement user={item['user'] as User} />; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Owner since', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + return <UserElement user={item["user"] as User} />; + }, + }, + { + field: { + createdAt: true, + }, + title: "Owner since", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default StatusPageOwners; diff --git a/Dashboard/src/Pages/StatusPages/View/PrivateUser.tsx b/Dashboard/src/Pages/StatusPages/View/PrivateUser.tsx index dbf9f28a52..77f5a2d13e 100644 --- a/Dashboard/src/Pages/StatusPages/View/PrivateUser.tsx +++ b/Dashboard/src/Pages/StatusPages/View/PrivateUser.tsx @@ -1,100 +1,93 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import { Green, Yellow } from 'Common/Types/BrandColors'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import { Green, Yellow } from "Common/Types/BrandColors"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<StatusPagePrivateUser> - modelType={StatusPagePrivateUser} - id="status-page-group" - name="Status Page > Private Users" - isDeleteable={true} - showViewIdButton={true} - isCreateable={true} - isViewable={false} - query={{ - statusPageId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: StatusPagePrivateUser - ): Promise<StatusPagePrivateUser> => { - item.statusPageId = modelId; - item.projectId = DashboardNavigation.getProjectId()!; - return Promise.resolve(item); - }} - cardProps={{ - title: 'Private Users', - description: - 'Here are a list of private users for this status page.', - }} - noItemsMessage={ - 'No private users created for this status page.' - } - formFields={[ - { - field: { - email: true, - }, - title: 'Email', - fieldType: FormFieldSchemaType.Email, - required: true, - placeholder: 'user@company.com', - }, - ]} - showRefreshButton={true} - filters={[ - { - field: { - email: true, - }, - title: 'Email', - type: FieldType.Email, - }, - ]} - columns={[ - { - field: { - email: true, - }, - title: 'Email', - type: FieldType.Email, - }, - { - field: { - password: true, - }, - title: 'Status', - type: FieldType.Password, + return ( + <Fragment> + <ModelTable<StatusPagePrivateUser> + modelType={StatusPagePrivateUser} + id="status-page-group" + name="Status Page > Private Users" + isDeleteable={true} + showViewIdButton={true} + isCreateable={true} + isViewable={false} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={( + item: StatusPagePrivateUser, + ): Promise<StatusPagePrivateUser> => { + item.statusPageId = modelId; + item.projectId = DashboardNavigation.getProjectId()!; + return Promise.resolve(item); + }} + cardProps={{ + title: "Private Users", + description: "Here are a list of private users for this status page.", + }} + noItemsMessage={"No private users created for this status page."} + formFields={[ + { + field: { + email: true, + }, + title: "Email", + fieldType: FormFieldSchemaType.Email, + required: true, + placeholder: "user@company.com", + }, + ]} + showRefreshButton={true} + filters={[ + { + field: { + email: true, + }, + title: "Email", + type: FieldType.Email, + }, + ]} + columns={[ + { + field: { + email: true, + }, + title: "Email", + type: FieldType.Email, + }, + { + field: { + password: true, + }, + title: "Status", + type: FieldType.Password, - getElement: ( - item: StatusPagePrivateUser - ): ReactElement => { - if (item['password']) { - return ( - <Pill color={Green} text={'Signed up'} /> - ); - } - return <Pill color={Yellow} text={'Invite Sent'} />; - }, - }, - ]} - /> - </Fragment> - ); + getElement: (item: StatusPagePrivateUser): ReactElement => { + if (item["password"]) { + return <Pill color={Green} text={"Signed up"} />; + } + return <Pill color={Yellow} text={"Invite Sent"} />; + }, + }, + ]} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/Resources.tsx b/Dashboard/src/Pages/StatusPages/View/Resources.tsx index 48bb7a6f4f..a0e2fb03e6 100644 --- a/Dashboard/src/Pages/StatusPages/View/Resources.tsx +++ b/Dashboard/src/Pages/StatusPages/View/Resources.tsx @@ -1,433 +1,425 @@ -import MonitorElement from '../../../Components/Monitor/Monitor'; -import MonitorGroupElement from '../../../Components/MonitorGroup/MonitorGroupElement'; -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { ModelField } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import Link from 'CommonUI/src/Components/Link/Link'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import API from 'CommonUI/src/Utils/API/API'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Monitor from 'Model/Models/Monitor'; -import MonitorGroup from 'Model/Models/MonitorGroup'; -import StatusPageGroup from 'Model/Models/StatusPageGroup'; +import MonitorElement from "../../../Components/Monitor/Monitor"; +import MonitorGroupElement from "../../../Components/MonitorGroup/MonitorGroupElement"; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { ModelField } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import Link from "CommonUI/src/Components/Link/Link"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import API from "CommonUI/src/Utils/API/API"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Monitor from "Model/Models/Monitor"; +import MonitorGroup from "Model/Models/MonitorGroup"; +import StatusPageGroup from "Model/Models/StatusPageGroup"; import StatusPageResource, { - UptimePrecision, -} from 'Model/Models/StatusPageResource'; + UptimePrecision, +} from "Model/Models/StatusPageResource"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [groups, setGroups] = useState<Array<StatusPageGroup>>([]); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string>(''); + const [groups, setGroups] = useState<Array<StatusPageGroup>>([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string>(""); - const [addMonitorGroup, setAddMonitorGroup] = useState<boolean>(false); + const [addMonitorGroup, setAddMonitorGroup] = useState<boolean>(false); - const fetchGroups: PromiseVoidFunction = async (): Promise<void> => { - setError(''); - setIsLoading(true); + const fetchGroups: PromiseVoidFunction = async (): Promise<void> => { + setError(""); + setIsLoading(true); - try { - const listResult: ListResult<StatusPageGroup> = - await ModelAPI.getList<StatusPageGroup>({ - modelType: StatusPageGroup, - query: { - statusPageId: modelId, - projectId: props.currentProject?.id, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - _id: true, - }, - sort: { - order: SortOrder.Ascending, - }, - requestOptions: {}, - }); - - setGroups(listResult.data); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); - }; - - useEffect(() => { - fetchGroups().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); + try { + const listResult: ListResult<StatusPageGroup> = + await ModelAPI.getList<StatusPageGroup>({ + modelType: StatusPageGroup, + query: { + statusPageId: modelId, + projectId: props.currentProject?.id, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + _id: true, + }, + sort: { + order: SortOrder.Ascending, + }, + requestOptions: {}, }); - }, []); - const getFooterForMonitor: GetReactElementFunction = (): ReactElement => { - if (props.currentProject?.isFeatureFlagMonitorGroupsEnabled) { - if (!addMonitorGroup) { - return ( - <Link - onClick={() => { - setAddMonitorGroup(true); - }} - className="mt-1 text-sm text-gray-500 underline" - > - <div> - <p> Add a Monitor Group instead. </p> - </div> - </Link> - ); - } - return ( - <Link - onClick={() => { - setAddMonitorGroup(false); - }} - className="mt-1 text-sm text-gray-500 underline" - > - <div> - <p> Add a Monitor instead. </p> - </div> - </Link> - ); - } - - return <></>; - }; - - let formFields: Array<ModelField<StatusPageResource>> = [ - { - field: { - monitor: true, - }, - title: 'Monitor', - description: - 'Select monitor that will be shown on the status page.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: Monitor, - labelField: 'name', - valueField: '_id', - }, - required: true, - placeholder: 'Select Monitor', - stepId: 'monitor-details', - footerElement: getFooterForMonitor(), - }, - ]; - - if (addMonitorGroup) { - formFields = [ - { - field: { - monitorGroup: true, - }, - title: 'Monitor Group', - description: - 'Select monitor group that will be shown on the status page.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: MonitorGroup, - labelField: 'name', - valueField: '_id', - }, - required: true, - placeholder: 'Select Monitor Group', - stepId: 'monitor-details', - footerElement: getFooterForMonitor(), - }, - ]; + setGroups(listResult.data); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - formFields = formFields.concat([ - { - field: { - displayName: true, - }, - title: 'Display Name', - description: - 'This will be the name that will be shown on the status page', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Display Name', - stepId: 'monitor-details', - }, - { - field: { - displayDescription: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.Markdown, - required: false, - placeholder: '', - stepId: 'monitor-details', - }, - { - field: { - displayTooltip: true, - }, - title: 'Tooltip ', - fieldType: FormFieldSchemaType.LongText, - required: false, - description: - 'This will show up as tooltip beside the resource on your status page.', - placeholder: 'Tooltip', - stepId: 'advanced', - }, - { - field: { - showCurrentStatus: true, - }, - title: 'Show Current Resource Status', - fieldType: FormFieldSchemaType.Toggle, - required: false, - defaultValue: true, - description: - 'Current Resource Status will be shown beside this resource on your status page.', - stepId: 'advanced', - }, - { - field: { - showUptimePercent: true, - }, - title: 'Show Uptime %', - fieldType: FormFieldSchemaType.Toggle, - required: false, - defaultValue: false, - description: - 'Show uptime percentage for the past 90 days beside this resource on your status page.', - stepId: 'advanced', - }, - { - field: { - uptimePercentPrecision: true, - }, - stepId: 'advanced', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum(UptimePrecision), - showIf: (item: FormValues<StatusPageResource>): boolean => { - return Boolean(item.showUptimePercent); - }, - title: 'Select Uptime Precision', - defaultValue: UptimePrecision.ONE_DECIMAL, - required: true, - }, - { - field: { - showStatusHistoryChart: true, - }, - title: 'Show Status History Chart', - fieldType: FormFieldSchemaType.Toggle, - required: false, - description: 'Show resource status history for the past 90 days. ', - defaultValue: true, - stepId: 'advanced', - }, - ]); + setIsLoading(false); + }; - type GetModelTableFunction = ( - statusPageGroupId: ObjectID | null, - statusPageGroupName: string | null - ) => ReactElement; + useEffect(() => { + fetchGroups().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, []); - const getModelTable: GetModelTableFunction = ( - statusPageGroupId: ObjectID | null, - statusPageGroupName: string | null - ): ReactElement => { + const getFooterForMonitor: GetReactElementFunction = (): ReactElement => { + if (props.currentProject?.isFeatureFlagMonitorGroupsEnabled) { + if (!addMonitorGroup) { return ( - <ModelTable<StatusPageResource> - modelType={StatusPageResource} - id={`status-page-group-${statusPageGroupId?.toString() || ''}`} - isDeleteable={true} - name="Status Page > Resources" - sortBy="order" - showViewIdButton={true} - sortOrder={SortOrder.Ascending} - isCreateable={true} - isViewable={false} - isEditable={true} - query={{ - statusPageId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - statusPageGroupId: statusPageGroupId, - }} - enableDragAndDrop={true} - dragDropIndexField="order" - onBeforeCreate={( - item: StatusPageResource - ): Promise<StatusPageResource> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } - item.statusPageId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - - if (statusPageGroupId) { - item.statusPageGroupId = statusPageGroupId; - } - - return Promise.resolve(item); - }} - cardProps={{ - title: `${ - statusPageGroupName - ? statusPageGroupName + ' - ' - : groups.length > 0 - ? 'Uncategorized - ' - : '' - }Status Page Resources`, - description: 'Resources that will be shown on the page', - }} - noItemsMessage={ - 'No status page resources created for this status page.' - } - formSteps={[ - { - title: 'Monitor Details', - id: 'monitor-details', - }, - { - title: 'Advanced', - id: 'advanced', - }, - ]} - formFields={formFields} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - selectMoreFields={{ - monitorGroup: { - name: true, - _id: true, - projectId: true, - }, - }} - filters={[ - { - field: { - monitor: { - name: true, - }, - }, - title: 'Monitor', - type: FieldType.Entity, - filterEntityType: Monitor, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - { - field: { - displayName: true, - }, - title: 'Display Name', - type: FieldType.Text, - }, - ]} - columns={[ - { - field: { - monitor: { - name: true, - _id: true, - projectId: true, - }, - }, - title: props.currentProject - ?.isFeatureFlagMonitorGroupsEnabled - ? 'Resource' - : 'Monitor', - type: FieldType.Entity, - - getElement: ( - item: StatusPageResource - ): ReactElement => { - if (item['monitor']) { - return ( - <MonitorElement - monitor={item['monitor']} - showIcon={ - props.currentProject - ?.isFeatureFlagMonitorGroupsEnabled || - false - } - /> - ); - } - - if (item['monitorGroup']) { - return ( - <MonitorGroupElement - monitorGroup={item['monitorGroup']} - showIcon={ - props.currentProject - ?.isFeatureFlagMonitorGroupsEnabled || - false - } - /> - ); - } - - return <></>; - }, - }, - { - field: { - displayName: true, - }, - title: 'Display Name', - type: FieldType.Text, - }, - ]} - /> + <Link + onClick={() => { + setAddMonitorGroup(true); + }} + className="mt-1 text-sm text-gray-500 underline" + > + <div> + <p> Add a Monitor Group instead. </p> + </div> + </Link> ); - }; + } + return ( + <Link + onClick={() => { + setAddMonitorGroup(false); + }} + className="mt-1 text-sm text-gray-500 underline" + > + <div> + <p> Add a Monitor instead. </p> + </div> + </Link> + ); + } + return <></>; + }; + + let formFields: Array<ModelField<StatusPageResource>> = [ + { + field: { + monitor: true, + }, + title: "Monitor", + description: "Select monitor that will be shown on the status page.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: Monitor, + labelField: "name", + valueField: "_id", + }, + required: true, + placeholder: "Select Monitor", + stepId: "monitor-details", + footerElement: getFooterForMonitor(), + }, + ]; + + if (addMonitorGroup) { + formFields = [ + { + field: { + monitorGroup: true, + }, + title: "Monitor Group", + description: + "Select monitor group that will be shown on the status page.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: MonitorGroup, + labelField: "name", + valueField: "_id", + }, + required: true, + placeholder: "Select Monitor Group", + stepId: "monitor-details", + footerElement: getFooterForMonitor(), + }, + ]; + } + + formFields = formFields.concat([ + { + field: { + displayName: true, + }, + title: "Display Name", + description: + "This will be the name that will be shown on the status page", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Display Name", + stepId: "monitor-details", + }, + { + field: { + displayDescription: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.Markdown, + required: false, + placeholder: "", + stepId: "monitor-details", + }, + { + field: { + displayTooltip: true, + }, + title: "Tooltip ", + fieldType: FormFieldSchemaType.LongText, + required: false, + description: + "This will show up as tooltip beside the resource on your status page.", + placeholder: "Tooltip", + stepId: "advanced", + }, + { + field: { + showCurrentStatus: true, + }, + title: "Show Current Resource Status", + fieldType: FormFieldSchemaType.Toggle, + required: false, + defaultValue: true, + description: + "Current Resource Status will be shown beside this resource on your status page.", + stepId: "advanced", + }, + { + field: { + showUptimePercent: true, + }, + title: "Show Uptime %", + fieldType: FormFieldSchemaType.Toggle, + required: false, + defaultValue: false, + description: + "Show uptime percentage for the past 90 days beside this resource on your status page.", + stepId: "advanced", + }, + { + field: { + uptimePercentPrecision: true, + }, + stepId: "advanced", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: DropdownUtil.getDropdownOptionsFromEnum(UptimePrecision), + showIf: (item: FormValues<StatusPageResource>): boolean => { + return Boolean(item.showUptimePercent); + }, + title: "Select Uptime Precision", + defaultValue: UptimePrecision.ONE_DECIMAL, + required: true, + }, + { + field: { + showStatusHistoryChart: true, + }, + title: "Show Status History Chart", + fieldType: FormFieldSchemaType.Toggle, + required: false, + description: "Show resource status history for the past 90 days. ", + defaultValue: true, + stepId: "advanced", + }, + ]); + + type GetModelTableFunction = ( + statusPageGroupId: ObjectID | null, + statusPageGroupName: string | null, + ) => ReactElement; + + const getModelTable: GetModelTableFunction = ( + statusPageGroupId: ObjectID | null, + statusPageGroupName: string | null, + ): ReactElement => { return ( - <Fragment> - <> - {isLoading ? <ComponentLoader /> : <></>} + <ModelTable<StatusPageResource> + modelType={StatusPageResource} + id={`status-page-group-${statusPageGroupId?.toString() || ""}`} + isDeleteable={true} + name="Status Page > Resources" + sortBy="order" + showViewIdButton={true} + sortOrder={SortOrder.Ascending} + isCreateable={true} + isViewable={false} + isEditable={true} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + statusPageGroupId: statusPageGroupId, + }} + enableDragAndDrop={true} + dragDropIndexField="order" + onBeforeCreate={( + item: StatusPageResource, + ): Promise<StatusPageResource> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); - {error ? <ErrorMessage error={error} /> : <></>} + if (statusPageGroupId) { + item.statusPageGroupId = statusPageGroupId; + } - {!isLoading && !error ? getModelTable(null, null) : <></>} + return Promise.resolve(item); + }} + cardProps={{ + title: `${ + statusPageGroupName + ? statusPageGroupName + " - " + : groups.length > 0 + ? "Uncategorized - " + : "" + }Status Page Resources`, + description: "Resources that will be shown on the page", + }} + noItemsMessage={ + "No status page resources created for this status page." + } + formSteps={[ + { + title: "Monitor Details", + id: "monitor-details", + }, + { + title: "Advanced", + id: "advanced", + }, + ]} + formFields={formFields} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + selectMoreFields={{ + monitorGroup: { + name: true, + _id: true, + projectId: true, + }, + }} + filters={[ + { + field: { + monitor: { + name: true, + }, + }, + title: "Monitor", + type: FieldType.Entity, + filterEntityType: Monitor, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + { + field: { + displayName: true, + }, + title: "Display Name", + type: FieldType.Text, + }, + ]} + columns={[ + { + field: { + monitor: { + name: true, + _id: true, + projectId: true, + }, + }, + title: props.currentProject?.isFeatureFlagMonitorGroupsEnabled + ? "Resource" + : "Monitor", + type: FieldType.Entity, - {!isLoading && !error && groups && groups.length > 0 ? ( - groups.map((group: StatusPageGroup) => { - return getModelTable(group.id, group.name || null); - }) - ) : ( - <></> - )} - </> - </Fragment> + getElement: (item: StatusPageResource): ReactElement => { + if (item["monitor"]) { + return ( + <MonitorElement + monitor={item["monitor"]} + showIcon={ + props.currentProject?.isFeatureFlagMonitorGroupsEnabled || + false + } + /> + ); + } + + if (item["monitorGroup"]) { + return ( + <MonitorGroupElement + monitorGroup={item["monitorGroup"]} + showIcon={ + props.currentProject?.isFeatureFlagMonitorGroupsEnabled || + false + } + /> + ); + } + + return <></>; + }, + }, + { + field: { + displayName: true, + }, + title: "Display Name", + type: FieldType.Text, + }, + ]} + /> ); + }; + + return ( + <Fragment> + <> + {isLoading ? <ComponentLoader /> : <></>} + + {error ? <ErrorMessage error={error} /> : <></>} + + {!isLoading && !error ? getModelTable(null, null) : <></>} + + {!isLoading && !error && groups && groups.length > 0 ? ( + groups.map((group: StatusPageGroup) => { + return getModelTable(group.id, group.name || null); + }) + ) : ( + <></> + )} + </> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/SMSSubscribers.tsx b/Dashboard/src/Pages/StatusPages/View/SMSSubscribers.tsx index 411c9f2c4c..0c2b72c003 100644 --- a/Dashboard/src/Pages/StatusPages/View/SMSSubscribers.tsx +++ b/Dashboard/src/Pages/StatusPages/View/SMSSubscribers.tsx @@ -1,308 +1,283 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import NotNull from 'Common/Types/BaseDatabase/NotNull'; -import { Green, Red } from 'Common/Types/BrandColors'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert'; -import { CategoryCheckboxOptionsAndCategories } from 'CommonUI/src/Components/CategoryCheckbox/Index'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { ModelField } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import SubscriberUtil from 'CommonUI/src/Utils/StatusPage'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import NotNull from "Common/Types/BaseDatabase/NotNull"; +import { Green, Red } from "Common/Types/BrandColors"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import Alert, { AlertType } from "CommonUI/src/Components/Alerts/Alert"; +import { CategoryCheckboxOptionsAndCategories } from "CommonUI/src/Components/CategoryCheckbox/Index"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { ModelField } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import SubscriberUtil from "CommonUI/src/Utils/StatusPage"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [ - allowSubscribersToChooseResources, - setAllowSubscribersToChooseResources, - ] = React.useState<boolean>(false); - const [isSMSSubscribersEnabled, setIsSMSSubscribersEnabled] = - React.useState<boolean>(false); - const [isLoading, setIsLoading] = React.useState<boolean>(false); - const [error, setError] = React.useState<string>(''); - const [ - categoryCheckboxOptionsAndCategories, - setCategoryCheckboxOptionsAndCategories, - ] = useState<CategoryCheckboxOptionsAndCategories>({ - categories: [], - options: [], - }); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const [ + allowSubscribersToChooseResources, + setAllowSubscribersToChooseResources, + ] = React.useState<boolean>(false); + const [isSMSSubscribersEnabled, setIsSMSSubscribersEnabled] = + React.useState<boolean>(false); + const [isLoading, setIsLoading] = React.useState<boolean>(false); + const [error, setError] = React.useState<string>(""); + const [ + categoryCheckboxOptionsAndCategories, + setCategoryCheckboxOptionsAndCategories, + ] = useState<CategoryCheckboxOptionsAndCategories>({ + categories: [], + options: [], + }); - const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = - async (): Promise<void> => { - const result: CategoryCheckboxOptionsAndCategories = - await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources( - modelId - ); + const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = + async (): Promise<void> => { + const result: CategoryCheckboxOptionsAndCategories = + await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources(modelId); - setCategoryCheckboxOptionsAndCategories(result); - }; - - const fetchStatusPage: PromiseVoidFunction = async (): Promise<void> => { - try { - setIsLoading(true); - - const statusPage: StatusPage | null = await ModelAPI.getItem({ - modelType: StatusPage, - id: modelId, - select: { - allowSubscribersToChooseResources: true, - enableSmsSubscribers: true, - }, - }); - - if (statusPage && statusPage.allowSubscribersToChooseResources) { - setAllowSubscribersToChooseResources( - statusPage.allowSubscribersToChooseResources - ); - await fetchCheckboxOptionsAndCategories(); - } - - if (statusPage && statusPage.enableSmsSubscribers) { - setIsSMSSubscribersEnabled(statusPage.enableSmsSubscribers); - } - - setIsLoading(false); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - - setIsLoading(false); + setCategoryCheckboxOptionsAndCategories(result); }; - useEffect(() => { - fetchStatusPage().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); - }); - }, []); + const fetchStatusPage: PromiseVoidFunction = async (): Promise<void> => { + try { + setIsLoading(true); - const [formFields, setFormFields] = React.useState< - Array<ModelField<StatusPageSubscriber>> - >([]); + const statusPage: StatusPage | null = await ModelAPI.getItem({ + modelType: StatusPage, + id: modelId, + select: { + allowSubscribersToChooseResources: true, + enableSmsSubscribers: true, + }, + }); - useEffect(() => { - if (isLoading) { - return; // don't do anything if loading - } + if (statusPage && statusPage.allowSubscribersToChooseResources) { + setAllowSubscribersToChooseResources( + statusPage.allowSubscribersToChooseResources, + ); + await fetchCheckboxOptionsAndCategories(); + } - const formFields: Array<ModelField<StatusPageSubscriber>> = [ - { + if (statusPage && statusPage.enableSmsSubscribers) { + setIsSMSSubscribersEnabled(statusPage.enableSmsSubscribers); + } + + setIsLoading(false); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchStatusPage().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, []); + + const [formFields, setFormFields] = React.useState< + Array<ModelField<StatusPageSubscriber>> + >([]); + + useEffect(() => { + if (isLoading) { + return; // don't do anything if loading + } + + const formFields: Array<ModelField<StatusPageSubscriber>> = [ + { + field: { + subscriberPhone: true, + }, + title: "Phone Number", + description: "Status page updates will be sent to this phone number.", + fieldType: FormFieldSchemaType.Phone, + required: true, + placeholder: "+11234567890", + }, + { + field: { + sendYouHaveSubscribedMessage: true, + }, + title: "Send Subscription SMS", + description: + 'Send "You have subscribed to this status page" SMS to this subscriber?', + fieldType: FormFieldSchemaType.Toggle, + required: false, + doNotShowWhenEditing: true, + }, + { + field: { + isUnsubscribed: true, + }, + title: "Unsubscribe", + description: "Unsubscribe this phone number from the status page.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + doNotShowWhenCreating: true, + }, + ]; + + if (allowSubscribersToChooseResources) { + formFields.push({ + field: { + isSubscribedToAllResources: true, + }, + title: "Subscribe to All Resources", + description: "Send notifications for all resources.", + fieldType: FormFieldSchemaType.Checkbox, + required: false, + defaultValue: true, + }); + + formFields.push({ + field: { + statusPageResources: true, + }, + title: "Select Resources to Subscribe", + description: "Please select the resources you want to subscribe to.", + fieldType: FormFieldSchemaType.CategoryCheckbox, + required: false, + categoryCheckboxProps: categoryCheckboxOptionsAndCategories, + showIf: (model: FormValues<StatusPageSubscriber>) => { + return !model || !model.isSubscribedToAllResources; + }, + }); + } + + setFormFields(formFields); + }, [isLoading]); + + return ( + <Fragment> + {isLoading ? <PageLoader isVisible={true} /> : <></>} + + {error ? <ErrorMessage error={error} /> : <></>} + + {!error && !isLoading ? ( + <> + {!isSMSSubscribersEnabled && ( + <Alert + type={AlertType.DANGER} + title="SMS subscribers are not enabled for this status page. Please enable it in Subscriber Settings" + /> + )} + <ModelTable<StatusPageSubscriber> + modelType={StatusPageSubscriber} + id="table-subscriber" + name="Status Page > SMS Subscribers" + isDeleteable={true} + showViewIdButton={true} + isCreateable={true} + isEditable={true} + isViewable={false} + selectMoreFields={{ + subscriberPhone: true, + }} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + subscriberPhone: new NotNull(), + }} + onBeforeCreate={( + item: StatusPageSubscriber, + ): Promise<StatusPageSubscriber> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } + + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "SMS Subscribers", + description: + "Here are the list of subscribers who have subscribed to the status page.", + }} + noItemsMessage={"No subscribers found."} + formFields={formFields} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { field: { - subscriberPhone: true, + subscriberPhone: true, }, - title: 'Phone Number', - description: - 'Status page updates will be sent to this phone number.', - fieldType: FormFieldSchemaType.Phone, - required: true, - placeholder: '+11234567890', - }, - { + title: "Phone Number", + type: FieldType.Phone, + }, + { field: { - sendYouHaveSubscribedMessage: true, + isUnsubscribed: true, }, - title: 'Send Subscription SMS', - description: - 'Send "You have subscribed to this status page" SMS to this subscriber?', - fieldType: FormFieldSchemaType.Toggle, - required: false, - doNotShowWhenEditing: true, - }, - { + title: "Is Unsubscribed", + type: FieldType.Boolean, + }, + { field: { - isUnsubscribed: true, + createdAt: true, }, - title: 'Unsubscribe', - description: - 'Unsubscribe this phone number from the status page.', - fieldType: FormFieldSchemaType.Toggle, - required: false, - doNotShowWhenCreating: true, - }, - ]; - - if (allowSubscribersToChooseResources) { - formFields.push({ + title: "Subscribed At", + type: FieldType.DateTime, + }, + ]} + columns={[ + { field: { - isSubscribedToAllResources: true, + subscriberPhone: true, }, - title: 'Subscribe to All Resources', - description: 'Send notifications for all resources.', - fieldType: FormFieldSchemaType.Checkbox, - required: false, - defaultValue: true, - }); - - formFields.push({ + title: "SMS", + type: FieldType.Phone, + }, + { field: { - statusPageResources: true, + isUnsubscribed: true, }, - title: 'Select Resources to Subscribe', - description: - 'Please select the resources you want to subscribe to.', - fieldType: FormFieldSchemaType.CategoryCheckbox, - required: false, - categoryCheckboxProps: categoryCheckboxOptionsAndCategories, - showIf: (model: FormValues<StatusPageSubscriber>) => { - return !model || !model.isSubscribedToAllResources; + title: "Status", + type: FieldType.Text, + getElement: (item: StatusPageSubscriber): ReactElement => { + if (item["isUnsubscribed"]) { + return <Pill color={Red} text={"Unsubscribed"} />; + } + return <Pill color={Green} text={"Subscribed"} />; }, - }); - } - - setFormFields(formFields); - }, [isLoading]); - - return ( - <Fragment> - {isLoading ? <PageLoader isVisible={true} /> : <></>} - - {error ? <ErrorMessage error={error} /> : <></>} - - {!error && !isLoading ? ( - <> - {!isSMSSubscribersEnabled && ( - <Alert - type={AlertType.DANGER} - title="SMS subscribers are not enabled for this status page. Please enable it in Subscriber Settings" - /> - )} - <ModelTable<StatusPageSubscriber> - modelType={StatusPageSubscriber} - id="table-subscriber" - name="Status Page > SMS Subscribers" - isDeleteable={true} - showViewIdButton={true} - isCreateable={true} - isEditable={true} - isViewable={false} - selectMoreFields={{ - subscriberPhone: true, - }} - query={{ - statusPageId: modelId, - projectId: - DashboardNavigation.getProjectId()?.toString(), - subscriberPhone: new NotNull(), - }} - onBeforeCreate={( - item: StatusPageSubscriber - ): Promise<StatusPageSubscriber> => { - if ( - !props.currentProject || - !props.currentProject._id - ) { - throw new BadDataException( - 'Project ID cannot be null' - ); - } - - item.statusPageId = modelId; - item.projectId = new ObjectID( - props.currentProject._id - ); - return Promise.resolve(item); - }} - cardProps={{ - title: 'SMS Subscribers', - description: - 'Here are the list of subscribers who have subscribed to the status page.', - }} - noItemsMessage={'No subscribers found.'} - formFields={formFields} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - subscriberPhone: true, - }, - title: 'Phone Number', - type: FieldType.Phone, - }, - { - field: { - isUnsubscribed: true, - }, - title: 'Is Unsubscribed', - type: FieldType.Boolean, - }, - { - field: { - createdAt: true, - }, - title: 'Subscribed At', - type: FieldType.DateTime, - }, - ]} - columns={[ - { - field: { - subscriberPhone: true, - }, - title: 'SMS', - type: FieldType.Phone, - }, - { - field: { - isUnsubscribed: true, - }, - title: 'Status', - type: FieldType.Text, - getElement: ( - item: StatusPageSubscriber - ): ReactElement => { - if (item['isUnsubscribed']) { - return ( - <Pill - color={Red} - text={'Unsubscribed'} - /> - ); - } - return ( - <Pill - color={Green} - text={'Subscribed'} - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Subscribed At', - type: FieldType.Date, - }, - ]} - /> - </> - ) : ( - <></> - )} - </Fragment> - ); + }, + { + field: { + createdAt: true, + }, + title: "Subscribed At", + type: FieldType.Date, + }, + ]} + /> + </> + ) : ( + <></> + )} + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/SSO.tsx b/Dashboard/src/Pages/StatusPages/View/SSO.tsx index 97e57862a1..f91ff67fd1 100644 --- a/Dashboard/src/Pages/StatusPages/View/SSO.tsx +++ b/Dashboard/src/Pages/StatusPages/View/SSO.tsx @@ -1,378 +1,356 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { VoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import DigestMethod from 'Common/Types/SSO/DigestMethod'; -import SignatureMethod from 'Common/Types/SSO/SignatureMethod'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Card from 'CommonUI/src/Components/Card/Card'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Link from 'CommonUI/src/Components/Link/Link'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { VoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import DigestMethod from "Common/Types/SSO/DigestMethod"; +import SignatureMethod from "Common/Types/SSO/SignatureMethod"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Card from "CommonUI/src/Components/Card/Card"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Link from "CommonUI/src/Components/Link/Link"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; import { - HOST, - HTTP_PROTOCOL, - IDENTITY_URL, - STATUS_PAGE_URL, -} from 'CommonUI/src/Config'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageSSO from 'Model/Models/StatusPageSso'; + HOST, + HTTP_PROTOCOL, + IDENTITY_URL, + STATUS_PAGE_URL, +} from "CommonUI/src/Config"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageSSO from "Model/Models/StatusPageSso"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const SSOPage: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [showSingleSignOnUrlId, setShowSingleSignOnUrlId] = - useState<string>(''); - return ( - <Fragment> - <> - <Banner - openInNewTab={true} - title="Need help with configuring SSO?" - description="Watch this 10 minute video which will help you get set up" - link={URL.fromString('https://youtu.be/F_h74p38SU0')} - /> + const [showSingleSignOnUrlId, setShowSingleSignOnUrlId] = + useState<string>(""); + return ( + <Fragment> + <> + <Banner + openInNewTab={true} + title="Need help with configuring SSO?" + description="Watch this 10 minute video which will help you get set up" + link={URL.fromString("https://youtu.be/F_h74p38SU0")} + /> - <ModelTable<StatusPageSSO> - modelType={StatusPageSSO} - query={{ - projectId: - DashboardNavigation.getProjectId()?.toString(), - statusPageId: modelId.toString(), - }} - onBeforeCreate={( - item: StatusPageSSO - ): Promise<StatusPageSSO> => { - if ( - !props.currentProject || - !props.currentProject._id - ) { - throw new BadDataException( - 'Project ID cannot be null' - ); - } + <ModelTable<StatusPageSSO> + modelType={StatusPageSSO} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + statusPageId: modelId.toString(), + }} + onBeforeCreate={(item: StatusPageSSO): Promise<StatusPageSSO> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } - item.statusPageId = modelId; - item.projectId = new ObjectID(props.currentProject._id); + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - id="sso-table" - name="Status Pages > Status Page View > Project SSO" - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: 'Single Sign On (SSO)', - description: - 'Single sign-on is an authentication scheme that allows a user to log in with a single ID to any of several related, yet independent, software systems.', - }} - noItemsMessage={'No SSO configuration found.'} - viewPageRoute={Navigation.getCurrentRoute()} - formSteps={[ - { - title: 'Baisc Info', - id: 'basic', - }, - { - title: 'Sign On', - id: 'sign-on', - }, - { - title: 'Certificate', - id: 'certificate', - }, - { - title: 'More', - id: 'more', - }, - ]} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - description: 'Friendly name to help you remember.', - placeholder: 'Okta', - stepId: 'basic', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - stepId: 'basic', - description: - 'Friendly description to help you remember.', - placeholder: 'Sign in with Okta', - validation: { - minLength: 2, - }, - }, - { - field: { - signOnURL: true, - }, - title: 'Sign On URL', - fieldType: FormFieldSchemaType.URL, - required: true, - description: - 'Members will be forwarded here when signing in to your organization', - placeholder: - 'https://yourapp.example.com/apps/appId', - stepId: 'sign-on', - }, - { - field: { - issuerURL: true, - }, - title: 'Issuer', - description: - 'Typically a unique URL generated by your SAML identity provider', - fieldType: FormFieldSchemaType.URL, - required: true, - placeholder: 'https://example.com', - stepId: 'sign-on', - }, - { - field: { - publicCertificate: true, - }, - title: 'Public Certificate', - description: 'Paste in your x509 certificate here.', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Paste in your x509 certificate here.', - stepId: 'certificate', - }, - { - field: { - signatureMethod: true, - }, - title: 'Signature Method', - description: - 'If you do not know what this is, please leave this to RSA-SHA256', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - SignatureMethod - ), - required: true, - placeholder: 'RSA-SHA256', - stepId: 'certificate', - }, - { - field: { - digestMethod: true, - }, - title: 'Digest Method', - description: - 'If you do not know what this is, please leave this to SHA256', - fieldType: FormFieldSchemaType.Dropdown, - dropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - DigestMethod - ), - required: true, - placeholder: 'SHA256', - stepId: 'certificate', - }, - { - field: { - isEnabled: true, - }, - description: - 'You can test this first, before enabling it. To test, please save the config.', - title: 'Enabled', - fieldType: FormFieldSchemaType.Toggle, - stepId: 'more', - }, - ]} - showRefreshButton={true} - actionButtons={[ - { - title: 'View SSO Config', - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - item: StatusPageSSO, - onCompleteAction: VoidFunction - ) => { - setShowSingleSignOnUrlId( - (item['_id'] as string) || '' - ); - onCompleteAction(); - }, - }, - ]} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - isEnabled: true, - }, - title: 'Enabled', - type: FieldType.Boolean, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, + return Promise.resolve(item); + }} + id="sso-table" + name="Status Pages > Status Page View > Project SSO" + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: "Single Sign On (SSO)", + description: + "Single sign-on is an authentication scheme that allows a user to log in with a single ID to any of several related, yet independent, software systems.", + }} + noItemsMessage={"No SSO configuration found."} + viewPageRoute={Navigation.getCurrentRoute()} + formSteps={[ + { + title: "Baisc Info", + id: "basic", + }, + { + title: "Sign On", + id: "sign-on", + }, + { + title: "Certificate", + id: "certificate", + }, + { + title: "More", + id: "more", + }, + ]} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + description: "Friendly name to help you remember.", + placeholder: "Okta", + stepId: "basic", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + stepId: "basic", + description: "Friendly description to help you remember.", + placeholder: "Sign in with Okta", + validation: { + minLength: 2, + }, + }, + { + field: { + signOnURL: true, + }, + title: "Sign On URL", + fieldType: FormFieldSchemaType.URL, + required: true, + description: + "Members will be forwarded here when signing in to your organization", + placeholder: "https://yourapp.example.com/apps/appId", + stepId: "sign-on", + }, + { + field: { + issuerURL: true, + }, + title: "Issuer", + description: + "Typically a unique URL generated by your SAML identity provider", + fieldType: FormFieldSchemaType.URL, + required: true, + placeholder: "https://example.com", + stepId: "sign-on", + }, + { + field: { + publicCertificate: true, + }, + title: "Public Certificate", + description: "Paste in your x509 certificate here.", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Paste in your x509 certificate here.", + stepId: "certificate", + }, + { + field: { + signatureMethod: true, + }, + title: "Signature Method", + description: + "If you do not know what this is, please leave this to RSA-SHA256", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(SignatureMethod), + required: true, + placeholder: "RSA-SHA256", + stepId: "certificate", + }, + { + field: { + digestMethod: true, + }, + title: "Digest Method", + description: + "If you do not know what this is, please leave this to SHA256", + fieldType: FormFieldSchemaType.Dropdown, + dropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(DigestMethod), + required: true, + placeholder: "SHA256", + stepId: "certificate", + }, + { + field: { + isEnabled: true, + }, + description: + "You can test this first, before enabling it. To test, please save the config.", + title: "Enabled", + fieldType: FormFieldSchemaType.Toggle, + stepId: "more", + }, + ]} + showRefreshButton={true} + actionButtons={[ + { + title: "View SSO Config", + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async ( + item: StatusPageSSO, + onCompleteAction: VoidFunction, + ) => { + setShowSingleSignOnUrlId((item["_id"] as string) || ""); + onCompleteAction(); + }, + }, + ]} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + isEnabled: true, + }, + title: "Enabled", + type: FieldType.Boolean, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, - { - field: { - isEnabled: true, - }, - title: 'Enabled', - type: FieldType.Boolean, - }, - ]} - /> + { + field: { + isEnabled: true, + }, + title: "Enabled", + type: FieldType.Boolean, + }, + ]} + /> - <Card - title={`Test Single Sign On (SSO)`} - description={ - <span> - Here's a link which will help you test SSO - integration before you force it on your - organization:{' '} - <Link - openInNewTab={true} - to={URL.fromString( - `${STATUS_PAGE_URL.toString()}/${modelId}/sso` - )} - > - <span>{`${STATUS_PAGE_URL.toString()}/${modelId}/sso`}</span> - </Link> - </span> - } - /> - - {/* API Key View */} - <CardModelDetail - name="SSO Settings" - editButtonText={'Edit Settings'} - cardProps={{ - title: 'SSO Settings', - description: 'Configure settings for SSO.', - }} - isEditable={true} - formFields={[ - { - field: { - requireSsoForLogin: true, - }, - title: 'Force SSO for Login', - description: - 'Please test SSO before you you enable this feature. If SSO is not tested properly then you will be locked out of the project.', - fieldType: FormFieldSchemaType.Toggle, - }, - ]} - modelDetailProps={{ - modelType: StatusPage, - id: 'sso-settings', - fields: [ - { - field: { - requireSsoForLogin: true, - }, - fieldType: FieldType.Boolean, - title: 'Force SSO for Login', - description: - 'Please test SSO before you enable this feature. If SSO is not tested properly then you will be locked out of the status page.', - }, - ], - modelId: modelId, - }} - /> - - {showSingleSignOnUrlId && ( - <ConfirmModal - title={`SSO Configuration`} - description={ - <div> - <div> - <div className="font-semibold"> - Identifier (Entity ID): - </div> - - <div>{`${HTTP_PROTOCOL}${HOST}/${modelId.toString()}/${showSingleSignOnUrlId}`}</div> - <br /> - </div> - <div> - <div className="font-semibold"> - Reply URL (Assertion Consumer Service - URL): - </div> - <div> - {`${URL.fromString( - IDENTITY_URL.toString() - ).addRoute( - `/status-page-idp-login/${modelId.toString()}/${showSingleSignOnUrlId}` - )}`} - </div> - <br /> - </div> - </div> - } - submitButtonText={'Close'} - onSubmit={() => { - setShowSingleSignOnUrlId(''); - }} - submitButtonType={ButtonStyleType.NORMAL} - /> + <Card + title={`Test Single Sign On (SSO)`} + description={ + <span> + Here's a link which will help you test SSO integration before + you force it on your organization:{" "} + <Link + openInNewTab={true} + to={URL.fromString( + `${STATUS_PAGE_URL.toString()}/${modelId}/sso`, )} - </> - </Fragment> - ); + > + <span>{`${STATUS_PAGE_URL.toString()}/${modelId}/sso`}</span> + </Link> + </span> + } + /> + + {/* API Key View */} + <CardModelDetail + name="SSO Settings" + editButtonText={"Edit Settings"} + cardProps={{ + title: "SSO Settings", + description: "Configure settings for SSO.", + }} + isEditable={true} + formFields={[ + { + field: { + requireSsoForLogin: true, + }, + title: "Force SSO for Login", + description: + "Please test SSO before you you enable this feature. If SSO is not tested properly then you will be locked out of the project.", + fieldType: FormFieldSchemaType.Toggle, + }, + ]} + modelDetailProps={{ + modelType: StatusPage, + id: "sso-settings", + fields: [ + { + field: { + requireSsoForLogin: true, + }, + fieldType: FieldType.Boolean, + title: "Force SSO for Login", + description: + "Please test SSO before you enable this feature. If SSO is not tested properly then you will be locked out of the status page.", + }, + ], + modelId: modelId, + }} + /> + + {showSingleSignOnUrlId && ( + <ConfirmModal + title={`SSO Configuration`} + description={ + <div> + <div> + <div className="font-semibold">Identifier (Entity ID):</div> + + <div>{`${HTTP_PROTOCOL}${HOST}/${modelId.toString()}/${showSingleSignOnUrlId}`}</div> + <br /> + </div> + <div> + <div className="font-semibold"> + Reply URL (Assertion Consumer Service URL): + </div> + <div> + {`${URL.fromString(IDENTITY_URL.toString()).addRoute( + `/status-page-idp-login/${modelId.toString()}/${showSingleSignOnUrlId}`, + )}`} + </div> + <br /> + </div> + </div> + } + submitButtonText={"Close"} + onSubmit={() => { + setShowSingleSignOnUrlId(""); + }} + submitButtonType={ButtonStyleType.NORMAL} + /> + )} + </> + </Fragment> + ); }; export default SSOPage; diff --git a/Dashboard/src/Pages/StatusPages/View/SideMenu.tsx b/Dashboard/src/Pages/StatusPages/View/SideMenu.tsx index 7c158f1e49..508eaaa222 100644 --- a/Dashboard/src/Pages/StatusPages/View/SideMenu.tsx +++ b/Dashboard/src/Pages/StatusPages/View/SideMenu.tsx @@ -1,116 +1,108 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Project from 'Model/Models/Project'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Project from "Model/Models/Project"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const project: Project | null = ProjectUtil.getCurrentProject(); + const project: Project | null = ProjectUtil.getCurrentProject(); - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGE_VIEW] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> - <SideMenuItem - link={{ - title: 'Announcements', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Announcement} - /> - <SideMenuItem - link={{ - title: 'Owners', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGE_VIEW_OWNERS] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Team} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Announcements", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Announcement} + /> + <SideMenuItem + link={{ + title: "Owners", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_OWNERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Team} + /> + </SideMenuSection> - <SideMenuSection title="Resources"> - <SideMenuItem - link={{ - title: project?.isFeatureFlagMonitorGroupsEnabled - ? 'Resources' - : 'Monitors', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_RESOURCES - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.AltGlobe} - /> - <SideMenuItem - link={{ - title: 'Groups', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGE_VIEW_GROUPS] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Folder} - /> - </SideMenuSection> + <SideMenuSection title="Resources"> + <SideMenuItem + link={{ + title: project?.isFeatureFlagMonitorGroupsEnabled + ? "Resources" + : "Monitors", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_RESOURCES] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.AltGlobe} + /> + <SideMenuItem + link={{ + title: "Groups", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_GROUPS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Folder} + /> + </SideMenuSection> - <SideMenuSection title="Subscribers"> - <SideMenuItem - link={{ - title: 'Email Subscribers', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Email} - /> - <SideMenuItem - link={{ - title: 'SMS Subscribers', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.SMS} - /> + <SideMenuSection title="Subscribers"> + <SideMenuItem + link={{ + title: "Email Subscribers", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Email} + /> + <SideMenuItem + link={{ + title: "SMS Subscribers", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.SMS} + /> - {/* <SideMenuItem + {/* <SideMenuItem link={{ title: 'Webhook Subscribers', to: RouteUtil.populateRouteParams( @@ -123,178 +115,162 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = ( icon={IconProp.Webhook} /> */} - <SideMenuItem - link={{ - title: 'Subscriber Settings', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Settings} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Subscriber Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Settings} + /> + </SideMenuSection> - <SideMenuSection title="Branding"> - <SideMenuItem - link={{ - title: 'Essential Branding', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_BRANDING - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Image} - /> + <SideMenuSection title="Branding"> + <SideMenuItem + link={{ + title: "Essential Branding", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_BRANDING] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Image} + /> - <SideMenuItem - link={{ - title: 'HTML, CSS & JavaScript', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Code} - /> + <SideMenuItem + link={{ + title: "HTML, CSS & JavaScript", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Code} + /> - <SideMenuItem - link={{ - title: 'Custom Domains', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGE_VIEW_DOMAINS] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Globe} - /> + <SideMenuItem + link={{ + title: "Custom Domains", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_DOMAINS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Globe} + /> - <SideMenuItem - link={{ - title: 'Header', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_HEADER_STYLE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.ArrowCircleUp} - /> + <SideMenuItem + link={{ + title: "Header", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_HEADER_STYLE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.ArrowCircleUp} + /> - <SideMenuItem - link={{ - title: 'Footer', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.ArrowCircleDown} - /> + <SideMenuItem + link={{ + title: "Footer", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.ArrowCircleDown} + /> - <SideMenuItem - link={{ - title: 'Overview Page', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.CheckCircle} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Overview Page", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING + ] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.CheckCircle} + /> + </SideMenuSection> - <SideMenuSection title="Authentication Security"> - <SideMenuItem - link={{ - title: 'Private Users', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.User} - /> + <SideMenuSection title="Authentication Security"> + <SideMenuItem + link={{ + title: "Private Users", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.User} + /> - <SideMenuItem - link={{ - title: 'SSO', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGE_VIEW_SSO] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Lock} - /> + <SideMenuItem + link={{ + title: "SSO", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_SSO] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Lock} + /> - <SideMenuItem - link={{ - title: 'Authentication Settings', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Settings} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Authentication Settings", + to: RouteUtil.populateRouteParams( + RouteMap[ + PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS + ] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Settings} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Custom Fields', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.TableCells} - /> + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Custom Fields", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.TableCells} + /> - <SideMenuItem - link={{ - title: 'Advanced Settings', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.STATUS_PAGE_VIEW_SETTINGS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Settings} - /> + <SideMenuItem + link={{ + title: "Advanced Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_SETTINGS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Settings} + /> - <SideMenuItem - link={{ - title: 'Delete Status Page', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.STATUS_PAGE_VIEW_DELETE] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuItem + link={{ + title: "Delete Status Page", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.STATUS_PAGE_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/StatusPages/View/StatusPagePreviewLink.tsx b/Dashboard/src/Pages/StatusPages/View/StatusPagePreviewLink.tsx index 6b50d00d48..c948d8c751 100644 --- a/Dashboard/src/Pages/StatusPages/View/StatusPagePreviewLink.tsx +++ b/Dashboard/src/Pages/StatusPages/View/StatusPagePreviewLink.tsx @@ -1,39 +1,37 @@ -import URL from 'Common/Types/API/URL'; -import ObjectID from 'Common/Types/ObjectID'; -import Card from 'CommonUI/src/Components/Card/Card'; -import Link from 'CommonUI/src/Components/Link/Link'; -import { STATUS_PAGE_URL } from 'CommonUI/src/Config'; -import React, { FunctionComponent, ReactElement } from 'react'; +import URL from "Common/Types/API/URL"; +import ObjectID from "Common/Types/ObjectID"; +import Card from "CommonUI/src/Components/Card/Card"; +import Link from "CommonUI/src/Components/Link/Link"; +import { STATUS_PAGE_URL } from "CommonUI/src/Config"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const StatusPagePreviewLink: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <> - <Card - title={`Status Page Preview URL`} - description={ - <span> - Here's a link to preview your status page:{' '} - <Link - openInNewTab={true} - to={URL.fromString( - `${STATUS_PAGE_URL.toString()}/${props.modelId}` - )} - > - <span>{`${STATUS_PAGE_URL.toString()}/${ - props.modelId - }`}</span> - </Link> - </span> - } - /> - </> - ); + return ( + <> + <Card + title={`Status Page Preview URL`} + description={ + <span> + Here's a link to preview your status page:{" "} + <Link + openInNewTab={true} + to={URL.fromString( + `${STATUS_PAGE_URL.toString()}/${props.modelId}`, + )} + > + <span>{`${STATUS_PAGE_URL.toString()}/${props.modelId}`}</span> + </Link> + </span> + } + /> + </> + ); }; export default StatusPagePreviewLink; diff --git a/Dashboard/src/Pages/StatusPages/View/StatusPageSettings.tsx b/Dashboard/src/Pages/StatusPages/View/StatusPageSettings.tsx index a44c147e4b..2d6ae1a59d 100644 --- a/Dashboard/src/Pages/StatusPages/View/StatusPageSettings.tsx +++ b/Dashboard/src/Pages/StatusPages/View/StatusPageSettings.tsx @@ -1,197 +1,196 @@ -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPage from "Model/Models/StatusPage"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CardModelDetail<StatusPage> - name="Status Page > Settings" - cardProps={{ - title: 'Incident Settings', - description: 'Incident Settings for Status Page', - }} - editButtonText="Edit Settings" - isEditable={true} - formFields={[ - { - field: { - showIncidentHistoryInDays: true, - }, - title: 'Show Incident History (in days)', - fieldType: FormFieldSchemaType.Number, - required: true, - placeholder: '14', - }, - { - field: { - showIncidentLabelsOnStatusPage: true, - }, - title: 'Show Incident Labels', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - showIncidentHistoryInDays: true, - }, - fieldType: FieldType.Number, - title: 'Show Incident History (in days)', - }, - { - field: { - showIncidentLabelsOnStatusPage: true, - }, - fieldType: FieldType.Boolean, - title: 'Show Incident Labels', - placeholder: 'No', - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + <CardModelDetail<StatusPage> + name="Status Page > Settings" + cardProps={{ + title: "Incident Settings", + description: "Incident Settings for Status Page", + }} + editButtonText="Edit Settings" + isEditable={true} + formFields={[ + { + field: { + showIncidentHistoryInDays: true, + }, + title: "Show Incident History (in days)", + fieldType: FormFieldSchemaType.Number, + required: true, + placeholder: "14", + }, + { + field: { + showIncidentLabelsOnStatusPage: true, + }, + title: "Show Incident Labels", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + showIncidentHistoryInDays: true, + }, + fieldType: FieldType.Number, + title: "Show Incident History (in days)", + }, + { + field: { + showIncidentLabelsOnStatusPage: true, + }, + fieldType: FieldType.Boolean, + title: "Show Incident Labels", + placeholder: "No", + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail<StatusPage> - name="Status Page > Settings" - cardProps={{ - title: 'Announcement Settings', - description: 'Announcement Settings for Status Page', - }} - editButtonText="Edit Settings" - isEditable={true} - formFields={[ - { - field: { - showAnnouncementHistoryInDays: true, - }, - title: 'Show Announcement History (in days)', - fieldType: FormFieldSchemaType.Number, - required: true, - placeholder: '14', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - showAnnouncementHistoryInDays: true, - }, - fieldType: FieldType.Number, - title: 'Show Announcement History (in days)', - }, - ], - modelId: modelId, - }} - /> + <CardModelDetail<StatusPage> + name="Status Page > Settings" + cardProps={{ + title: "Announcement Settings", + description: "Announcement Settings for Status Page", + }} + editButtonText="Edit Settings" + isEditable={true} + formFields={[ + { + field: { + showAnnouncementHistoryInDays: true, + }, + title: "Show Announcement History (in days)", + fieldType: FormFieldSchemaType.Number, + required: true, + placeholder: "14", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + showAnnouncementHistoryInDays: true, + }, + fieldType: FieldType.Number, + title: "Show Announcement History (in days)", + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail<StatusPage> - name="Status Page > Settings" - cardProps={{ - title: 'Scheduled Event Settings', - description: 'Scheduled Event Settings for Status Page', - }} - editButtonText="Edit Settings" - isEditable={true} - formFields={[ - { - field: { - showScheduledEventHistoryInDays: true, - }, - title: 'Show Scheduled Event History (in days)', - fieldType: FormFieldSchemaType.Number, - required: true, - placeholder: '14', - }, - { - field: { - showScheduledEventLabelsOnStatusPage: true, - }, - title: 'Show Event Labels', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - showScheduledEventHistoryInDays: true, - }, - fieldType: FieldType.Number, - title: 'Show Scheduled Event History (in days)', - }, - { - field: { - showScheduledEventLabelsOnStatusPage: true, - }, - fieldType: FieldType.Boolean, - title: 'Show Event Labels', - placeholder: 'No', - }, - ], - modelId: modelId, - }} - /> + <CardModelDetail<StatusPage> + name="Status Page > Settings" + cardProps={{ + title: "Scheduled Event Settings", + description: "Scheduled Event Settings for Status Page", + }} + editButtonText="Edit Settings" + isEditable={true} + formFields={[ + { + field: { + showScheduledEventHistoryInDays: true, + }, + title: "Show Scheduled Event History (in days)", + fieldType: FormFieldSchemaType.Number, + required: true, + placeholder: "14", + }, + { + field: { + showScheduledEventLabelsOnStatusPage: true, + }, + title: "Show Event Labels", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + showScheduledEventHistoryInDays: true, + }, + fieldType: FieldType.Number, + title: "Show Scheduled Event History (in days)", + }, + { + field: { + showScheduledEventLabelsOnStatusPage: true, + }, + fieldType: FieldType.Boolean, + title: "Show Event Labels", + placeholder: "No", + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail<StatusPage> - name="Status Page > Settings" - cardProps={{ - title: 'Powered By OneUptime Branding', - description: - 'Show or hide the Powered By OneUptime Branding', - }} - editButtonText="Edit Settings" - isEditable={true} - formFields={[ - { - field: { - hidePoweredByOneUptimeBranding: true, - }, - title: 'Hide Powered By OneUptime Branding', - fieldType: FormFieldSchemaType.Toggle, - required: false, - placeholder: 'No', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - hidePoweredByOneUptimeBranding: true, - }, - fieldType: FieldType.Boolean, - title: 'Hide Powered By OneUptime Branding', - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + <CardModelDetail<StatusPage> + name="Status Page > Settings" + cardProps={{ + title: "Powered By OneUptime Branding", + description: "Show or hide the Powered By OneUptime Branding", + }} + editButtonText="Edit Settings" + isEditable={true} + formFields={[ + { + field: { + hidePoweredByOneUptimeBranding: true, + }, + title: "Hide Powered By OneUptime Branding", + fieldType: FormFieldSchemaType.Toggle, + required: false, + placeholder: "No", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + hidePoweredByOneUptimeBranding: true, + }, + fieldType: FieldType.Boolean, + title: "Hide Powered By OneUptime Branding", + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/SubscriberSettings.tsx b/Dashboard/src/Pages/StatusPages/View/SubscriberSettings.tsx index a3ae4e8139..5210b7b974 100644 --- a/Dashboard/src/Pages/StatusPages/View/SubscriberSettings.tsx +++ b/Dashboard/src/Pages/StatusPages/View/SubscriberSettings.tsx @@ -1,238 +1,229 @@ -import ProjectCallSMSConfigElement from '../../../Components/ProjectCallSMSConfig/ProjectCallSMSConfig'; -import ProjectSMTPConfig from '../../../Components/ProjectSMTPConfig/ProjectSMTPConfig'; -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import PlaceholderText from 'CommonUI/src/Components/Detail/PlaceholderText'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig'; -import ProjectSmtpConfig from 'Model/Models/ProjectSmtpConfig'; -import StatusPage from 'Model/Models/StatusPage'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import ProjectCallSMSConfigElement from "../../../Components/ProjectCallSMSConfig/ProjectCallSMSConfig"; +import ProjectSMTPConfig from "../../../Components/ProjectSMTPConfig/ProjectSMTPConfig"; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import PlaceholderText from "CommonUI/src/Components/Detail/PlaceholderText"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig"; +import ProjectSmtpConfig from "Model/Models/ProjectSmtpConfig"; +import StatusPage from "Model/Models/StatusPage"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CardModelDetail<StatusPage> - name="Status Page > Branding > Subscriber" - cardProps={{ - title: 'Subscriber Settings', - description: 'Subscriber settings for this status page.', - }} - isEditable={true} - formFields={[ - { - field: { - enableEmailSubscribers: true, - }, - title: 'Enable Email Subscribers', - fieldType: FormFieldSchemaType.Toggle, - required: false, - placeholder: - 'Can email subscribers subscribe to this status page?', - }, - { - field: { - enableSmsSubscribers: true, - }, - title: 'Enable SMS Subscribers', - fieldType: FormFieldSchemaType.Toggle, - required: false, - placeholder: - 'Can SMS subscribers subscribe to this status page?', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - enableEmailSubscribers: true, - }, - fieldType: FieldType.Boolean, - title: 'Enable Email Subscribers', - }, - { - field: { - enableSmsSubscribers: true, - }, - fieldType: FieldType.Boolean, - title: 'Enable SMS Subscribers', - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + <CardModelDetail<StatusPage> + name="Status Page > Branding > Subscriber" + cardProps={{ + title: "Subscriber Settings", + description: "Subscriber settings for this status page.", + }} + isEditable={true} + formFields={[ + { + field: { + enableEmailSubscribers: true, + }, + title: "Enable Email Subscribers", + fieldType: FormFieldSchemaType.Toggle, + required: false, + placeholder: "Can email subscribers subscribe to this status page?", + }, + { + field: { + enableSmsSubscribers: true, + }, + title: "Enable SMS Subscribers", + fieldType: FormFieldSchemaType.Toggle, + required: false, + placeholder: "Can SMS subscribers subscribe to this status page?", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + enableEmailSubscribers: true, + }, + fieldType: FieldType.Boolean, + title: "Enable Email Subscribers", + }, + { + field: { + enableSmsSubscribers: true, + }, + fieldType: FieldType.Boolean, + title: "Enable SMS Subscribers", + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail<StatusPage> - name="Status Page > Branding > Subscriber > Advanced" - cardProps={{ - title: 'Advanced Subscriber Settings', - description: - 'Advanced subscriber settings for this status page.', - }} - isEditable={true} - formFields={[ - { - field: { - allowSubscribersToChooseResources: true, - }, - title: 'Allow Subscribers to Choose Resources', - fieldType: FormFieldSchemaType.Toggle, - required: false, - placeholder: - 'Can subscribers choose which resources they want to subscribe to?', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - allowSubscribersToChooseResources: true, - }, - fieldType: FieldType.Boolean, - title: 'Allow Subscribers to Choose Resources', - description: - 'Can subscribers choose which resources they want to subscribe to?', - }, - ], - modelId: modelId, - }} - /> + <CardModelDetail<StatusPage> + name="Status Page > Branding > Subscriber > Advanced" + cardProps={{ + title: "Advanced Subscriber Settings", + description: "Advanced subscriber settings for this status page.", + }} + isEditable={true} + formFields={[ + { + field: { + allowSubscribersToChooseResources: true, + }, + title: "Allow Subscribers to Choose Resources", + fieldType: FormFieldSchemaType.Toggle, + required: false, + placeholder: + "Can subscribers choose which resources they want to subscribe to?", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + allowSubscribersToChooseResources: true, + }, + fieldType: FieldType.Boolean, + title: "Allow Subscribers to Choose Resources", + description: + "Can subscribers choose which resources they want to subscribe to?", + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail<StatusPage> - name="Status Page > Email > Subscriber" - cardProps={{ - title: 'Custom SMTP', - description: - 'Custom SMTP settings for this status page. This will be used to send emails to subscribers.', - }} - editButtonText={'Edit SMTP'} - isEditable={true} - formFields={[ - { - field: { - smtpConfig: true, - }, - title: 'Custom SMTP Config', - description: - 'Select SMTP Config to use for this status page to send email to subscribers. You can add SMTP Config in Project Settings > Notification Settings > Custom SMTP.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: ProjectSmtpConfig, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'SMTP Config', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-status-page', - fields: [ - { - field: { - smtpConfig: { - name: true, - }, - }, - title: 'Custom SMTP Config', - fieldType: FieldType.Element, - getElement: (item: StatusPage): ReactElement => { - if (item['smtpConfig']) { - return ( - <ProjectSMTPConfig - smtpConfig={item['smtpConfig']} - /> - ); - } - return ( - <PlaceholderText - text="No Custom SMTP Config selected so far + <CardModelDetail<StatusPage> + name="Status Page > Email > Subscriber" + cardProps={{ + title: "Custom SMTP", + description: + "Custom SMTP settings for this status page. This will be used to send emails to subscribers.", + }} + editButtonText={"Edit SMTP"} + isEditable={true} + formFields={[ + { + field: { + smtpConfig: true, + }, + title: "Custom SMTP Config", + description: + "Select SMTP Config to use for this status page to send email to subscribers. You can add SMTP Config in Project Settings > Notification Settings > Custom SMTP.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: ProjectSmtpConfig, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "SMTP Config", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-status-page", + fields: [ + { + field: { + smtpConfig: { + name: true, + }, + }, + title: "Custom SMTP Config", + fieldType: FieldType.Element, + getElement: (item: StatusPage): ReactElement => { + if (item["smtpConfig"]) { + return <ProjectSMTPConfig smtpConfig={item["smtpConfig"]} />; + } + return ( + <PlaceholderText + text="No Custom SMTP Config selected so far for this status page." - /> - ); - }, - }, - ], - modelId: modelId, - }} - /> + /> + ); + }, + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail<StatusPage> - name="Status Page > Call and SMS > Subscriber" - cardProps={{ - title: 'Twilio Config', - description: - 'Twilio Config settings for this status page. This will be used to send SMS to subscribers.', - }} - editButtonText={'Edit Twilio Config'} - isEditable={true} - formFields={[ - { - field: { - callSmsConfig: true, - }, - title: 'Twilio Config', - description: - 'Select Twilio Config to use for this status page to send SMS to subscribers. You can add Twilio Config in Project Settings > Notification Settings > Twilio Config.', - fieldType: FormFieldSchemaType.Dropdown, - dropdownModal: { - type: ProjectCallSMSConfig, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Twilio Config', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 1, - modelType: StatusPage, - id: 'model-detail-call-config', - fields: [ - { - field: { - callSmsConfig: { - name: true, - }, - }, - title: 'Twilio Config', - fieldType: FieldType.Element, - getElement: (item: StatusPage): ReactElement => { - if (item['callSmsConfig']) { - return ( - <ProjectCallSMSConfigElement - callSmsConfig={ - item['callSmsConfig'] - } - /> - ); - } - return ( - <PlaceholderText text="No Twilio Config selected so far." /> - ); - }, - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + <CardModelDetail<StatusPage> + name="Status Page > Call and SMS > Subscriber" + cardProps={{ + title: "Twilio Config", + description: + "Twilio Config settings for this status page. This will be used to send SMS to subscribers.", + }} + editButtonText={"Edit Twilio Config"} + isEditable={true} + formFields={[ + { + field: { + callSmsConfig: true, + }, + title: "Twilio Config", + description: + "Select Twilio Config to use for this status page to send SMS to subscribers. You can add Twilio Config in Project Settings > Notification Settings > Twilio Config.", + fieldType: FormFieldSchemaType.Dropdown, + dropdownModal: { + type: ProjectCallSMSConfig, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Twilio Config", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 1, + modelType: StatusPage, + id: "model-detail-call-config", + fields: [ + { + field: { + callSmsConfig: { + name: true, + }, + }, + title: "Twilio Config", + fieldType: FieldType.Element, + getElement: (item: StatusPage): ReactElement => { + if (item["callSmsConfig"]) { + return ( + <ProjectCallSMSConfigElement + callSmsConfig={item["callSmsConfig"]} + /> + ); + } + return ( + <PlaceholderText text="No Twilio Config selected so far." /> + ); + }, + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/StatusPages/View/WebhookSubscribers.tsx b/Dashboard/src/Pages/StatusPages/View/WebhookSubscribers.tsx index 554e1c115c..3529c756f6 100644 --- a/Dashboard/src/Pages/StatusPages/View/WebhookSubscribers.tsx +++ b/Dashboard/src/Pages/StatusPages/View/WebhookSubscribers.tsx @@ -1,102 +1,101 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import NotNull from 'Common/Types/BaseDatabase/NotNull'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import NotNull from "Common/Types/BaseDatabase/NotNull"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const StatusPageDelete: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<StatusPageSubscriber> - modelType={StatusPageSubscriber} - name="Status Page > Webhook Subscribers" - id="table-subscriber" - isDeleteable={true} - isCreateable={true} - isEditable={false} - isViewable={false} - query={{ - statusPageId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - subscriberWebhook: new NotNull(), - }} - onBeforeCreate={( - item: StatusPageSubscriber - ): Promise<StatusPageSubscriber> => { - if (!props.currentProject || !props.currentProject._id) { - throw new BadDataException('Project ID cannot be null'); - } + return ( + <Fragment> + <ModelTable<StatusPageSubscriber> + modelType={StatusPageSubscriber} + name="Status Page > Webhook Subscribers" + id="table-subscriber" + isDeleteable={true} + isCreateable={true} + isEditable={false} + isViewable={false} + query={{ + statusPageId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + subscriberWebhook: new NotNull(), + }} + onBeforeCreate={( + item: StatusPageSubscriber, + ): Promise<StatusPageSubscriber> => { + if (!props.currentProject || !props.currentProject._id) { + throw new BadDataException("Project ID cannot be null"); + } - item.statusPageId = modelId; - item.projectId = new ObjectID(props.currentProject._id); - return Promise.resolve(item); - }} - cardProps={{ - title: 'Webhook Subscribers', - description: - 'Here are the list of subscribers who have subscribed to the status page.', - }} - noItemsMessage={'No subscribers found.'} - formFields={[ - { - field: { - subscriberWebhook: true, - }, - title: 'Webhook URL', - description: - 'A POST request will be sent to this webhook.', - fieldType: FormFieldSchemaType.URL, - required: true, - placeholder: 'URL', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - subscriberWebhook: true, - }, - title: 'Webhook URL', - type: FieldType.URL, - }, - { - field: { - createdAt: true, - }, - title: 'Subscribed At', - type: FieldType.DateTime, - }, - ]} - columns={[ - { - field: { - subscriberWebhook: true, - }, - title: 'Webhook URL', - type: FieldType.URL, - }, - { - field: { - createdAt: true, - }, - title: 'Subscribed At', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + item.statusPageId = modelId; + item.projectId = new ObjectID(props.currentProject._id); + return Promise.resolve(item); + }} + cardProps={{ + title: "Webhook Subscribers", + description: + "Here are the list of subscribers who have subscribed to the status page.", + }} + noItemsMessage={"No subscribers found."} + formFields={[ + { + field: { + subscriberWebhook: true, + }, + title: "Webhook URL", + description: "A POST request will be sent to this webhook.", + fieldType: FormFieldSchemaType.URL, + required: true, + placeholder: "URL", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + subscriberWebhook: true, + }, + title: "Webhook URL", + type: FieldType.URL, + }, + { + field: { + createdAt: true, + }, + title: "Subscribed At", + type: FieldType.DateTime, + }, + ]} + columns={[ + { + field: { + subscriberWebhook: true, + }, + title: "Webhook URL", + type: FieldType.URL, + }, + { + field: { + createdAt: true, + }, + title: "Subscribed At", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default StatusPageDelete; diff --git a/Dashboard/src/Pages/Telemetry/Services.tsx b/Dashboard/src/Pages/Telemetry/Services.tsx index 0e3f563749..35c4dc99b9 100644 --- a/Dashboard/src/Pages/Telemetry/Services.tsx +++ b/Dashboard/src/Pages/Telemetry/Services.tsx @@ -1,182 +1,173 @@ -import LabelsElement from '../../Components/Label/Labels'; -import TelemetryServiceElement from '../../Components/TelemetryService/TelemetryServiceElement'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import SideMenu from './SideMenu'; -import Route from 'Common/Types/API/Route'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Page from 'CommonUI/src/Components/Page/Page'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import TelemetryService from 'Model/Models/TelemetryService'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import TelemetryServiceElement from "../../Components/TelemetryService/TelemetryServiceElement"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import SideMenu from "./SideMenu"; +import Route from "Common/Types/API/Route"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Page from "CommonUI/src/Components/Page/Page"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import TelemetryService from "Model/Models/TelemetryService"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Services: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const disableTelemetryForThisProject: boolean = - props.currentProject?.reseller?.enableTelemetryFeatures === false; - - if (disableTelemetryForThisProject) { - return ( - <ErrorMessage error="Looks like you have bought this plan from a reseller. It did not include telemetry features in your plan. Telemetry features are disabled for this project." /> - ); - } + const disableTelemetryForThisProject: boolean = + props.currentProject?.reseller?.enableTelemetryFeatures === false; + if (disableTelemetryForThisProject) { return ( - <Page - title={'Telemetry'} - breadcrumbLinks={[ - { - title: 'Project', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.HOME] as Route - ), - }, - { - title: 'Telemetry', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.TELEMETRY] as Route - ), - }, - { - title: 'Services', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.TELEMETRY_SERVICES] as Route - ), - }, - ]} - sideMenu={<SideMenu />} - > - <ModelTable<TelemetryService> - modelType={TelemetryService} - id="services-table" - isDeleteable={false} - isEditable={false} - isCreateable={true} - name="Services" - isViewable={true} - cardProps={{ - title: 'Services', - description: 'Here is a list of services for this project.', - }} - showViewIdButton={true} - noItemsMessage={'No services found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Service Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Service Description', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - }, - ]} - selectMoreFields={{ - serviceColor: true, - }} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Element, - getElement: ( - service: TelemetryService - ): ReactElement => { - return ( - <Fragment> - <TelemetryServiceElement - telemetryService={service} - /> - </Fragment> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - - getElement: (item: TelemetryService): ReactElement => { - return ( - <LabelsElement labels={item['labels'] || []} /> - ); - }, - }, - ]} - /> - </Page> + <ErrorMessage error="Looks like you have bought this plan from a reseller. It did not include telemetry features in your plan. Telemetry features are disabled for this project." /> ); + } + + return ( + <Page + title={"Telemetry"} + breadcrumbLinks={[ + { + title: "Project", + to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route), + }, + { + title: "Telemetry", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY] as Route, + ), + }, + { + title: "Services", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES] as Route, + ), + }, + ]} + sideMenu={<SideMenu />} + > + <ModelTable<TelemetryService> + modelType={TelemetryService} + id="services-table" + isDeleteable={false} + isEditable={false} + isCreateable={true} + name="Services" + isViewable={true} + cardProps={{ + title: "Services", + description: "Here is a list of services for this project.", + }} + showViewIdButton={true} + noItemsMessage={"No services found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Service Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Service Description", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + }, + ]} + selectMoreFields={{ + serviceColor: true, + }} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Element, + getElement: (service: TelemetryService): ReactElement => { + return ( + <Fragment> + <TelemetryServiceElement telemetryService={service} /> + </Fragment> + ); + }, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + + getElement: (item: TelemetryService): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + </Page> + ); }; export default Services; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Dashboard/Index.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Dashboard/Index.tsx index f9c0e3312d..c064bde316 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Dashboard/Index.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Dashboard/Index.tsx @@ -1,15 +1,15 @@ -import PageComponentProps from '../../../../PageComponentProps'; -import ComingSoon from 'CommonUI/src/Components/ComingSoon/ComingSoon'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../../../PageComponentProps"; +import ComingSoon from "CommonUI/src/Components/ComingSoon/ComingSoon"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ComingSoon /> - </Fragment> - ); + return ( + <Fragment> + <ComingSoon /> + </Fragment> + ); }; export default ServiceDelete; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Delete.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Delete.tsx index 1411040913..0a8adc7aa6 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Delete.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Delete.tsx @@ -1,31 +1,29 @@ -import PageMap from '../../../../Utils/PageMap'; -import RouteMap from '../../../../Utils/RouteMap'; -import PageComponentProps from '../../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import TelemetryService from 'Model/Models/TelemetryService'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../../Utils/PageMap"; +import RouteMap from "../../../../Utils/RouteMap"; +import PageComponentProps from "../../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import TelemetryService from "Model/Models/TelemetryService"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={TelemetryService} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteMap[PageMap.TELEMETRY_SERVICES] as Route - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={TelemetryService} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate(RouteMap[PageMap.TELEMETRY_SERVICES] as Route); + }} + /> + </Fragment> + ); }; export default ServiceDelete; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Documentation.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Documentation.tsx index 91c483c95b..da9d93ca47 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Documentation.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Documentation.tsx @@ -1,15 +1,15 @@ -import TelemetryDocumentation from '../../../../Components/Telemetry/Documentation'; -import PageComponentProps from '../../../PageComponentProps'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import TelemetryDocumentation from "../../../../Components/Telemetry/Documentation"; +import PageComponentProps from "../../../PageComponentProps"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const TelemetryDocumentationPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <TelemetryDocumentation /> - </Fragment> - ); + return ( + <Fragment> + <TelemetryDocumentation /> + </Fragment> + ); }; export default TelemetryDocumentationPage; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Index.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Index.tsx index bed8e982eb..e3b178c886 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Index.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Index.tsx @@ -1,136 +1,130 @@ -import LabelsElement from '../../../../Components/Label/Labels'; -import PageComponentProps from '../../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import TelemetryService from 'Model/Models/TelemetryService'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../../../Components/Label/Labels"; +import PageComponentProps from "../../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import TelemetryService from "Model/Models/TelemetryService"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(0); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(0); - return ( - <Fragment> - {/* Service View */} - <CardModelDetail<TelemetryService> - name="Service Details" - formSteps={[ - { - title: 'Service Info', - id: 'service-info', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - cardProps={{ - title: 'Service Details', - description: 'Here are more details for this service.', - }} - isEditable={true} - formFields={[ - { - field: { - name: true, - }, - stepId: 'service-info', - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Service Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'service-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - labels: true, - }, - stepId: 'labels', - title: 'Labels ', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: TelemetryService, - id: 'model-detail-services', - fields: [ - { - field: { - _id: true, - }, - title: 'Service ID', - }, - { - field: { - name: true, - }, - title: 'Service Name', - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - fieldType: FieldType.Element, - getElement: ( - item: TelemetryService - ): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - { - field: { - description: true, - }, - title: 'Description', - }, - { - field: { - telemetryServiceToken: true, - }, - title: 'Service Token', - fieldType: FieldType.HiddenText, - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + return ( + <Fragment> + {/* Service View */} + <CardModelDetail<TelemetryService> + name="Service Details" + formSteps={[ + { + title: "Service Info", + id: "service-info", + }, + { + title: "Labels", + id: "labels", + }, + ]} + cardProps={{ + title: "Service Details", + description: "Here are more details for this service.", + }} + isEditable={true} + formFields={[ + { + field: { + name: true, + }, + stepId: "service-info", + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Service Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "service-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + labels: true, + }, + stepId: "labels", + title: "Labels ", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: TelemetryService, + id: "model-detail-services", + fields: [ + { + field: { + _id: true, + }, + title: "Service ID", + }, + { + field: { + name: true, + }, + title: "Service Name", + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + fieldType: FieldType.Element, + getElement: (item: TelemetryService): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + { + field: { + description: true, + }, + title: "Description", + }, + { + field: { + telemetryServiceToken: true, + }, + title: "Service Token", + fieldType: FieldType.HiddenText, + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default ServiceDelete; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Layout.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Layout.tsx index 771687f791..45c6e83c46 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Layout.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Layout.tsx @@ -1,32 +1,32 @@ -import { getTelemetryBreadcrumbs } from '../../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../../Utils/RouteMap'; -import PageComponentProps from '../../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import TelemetryService from 'Model/Models/TelemetryService'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getTelemetryBreadcrumbs } from "../../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../../Utils/RouteMap"; +import PageComponentProps from "../../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import TelemetryService from "Model/Models/TelemetryService"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const TelemetryServiceViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="Service" - modelType={TelemetryService} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={getTelemetryBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <ModelPage + title="Service" + modelType={TelemetryService} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={getTelemetryBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default TelemetryServiceViewLayout; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Logs/Index.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Logs/Index.tsx index f7773397d5..16760697a1 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Logs/Index.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Logs/Index.tsx @@ -1,24 +1,24 @@ -import DashboardLogsViewer from '../../../../../Components/Logs/LogsViewer'; -import PageComponentProps from '../../../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardLogsViewer from "../../../../../Components/Logs/LogsViewer"; +import PageComponentProps from "../../../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <DashboardLogsViewer - showFilters={true} - telemetryServiceIds={[modelId]} - enableRealtime={false} - id="logs" - /> - </Fragment> - ); + return ( + <Fragment> + <DashboardLogsViewer + showFilters={true} + telemetryServiceIds={[modelId]} + enableRealtime={false} + id="logs" + /> + </Fragment> + ); }; export default ServiceDelete; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Metrics/Index.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Metrics/Index.tsx index 37fdc9895f..683b545a01 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Metrics/Index.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Metrics/Index.tsx @@ -1,80 +1,80 @@ -import DashboardNavigation from '../../../../../Utils/Navigation'; -import PageComponentProps from '../../../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import ObjectID from 'Common/Types/ObjectID'; -import AnalyticsModelTable from 'CommonUI/src/Components/ModelTable/AnalyticsModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Metric from 'Model/AnalyticsModels/Metric'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../../../../Utils/Navigation"; +import PageComponentProps from "../../../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import ObjectID from "Common/Types/ObjectID"; +import AnalyticsModelTable from "CommonUI/src/Components/ModelTable/AnalyticsModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Metric from "Model/AnalyticsModels/Metric"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <AnalyticsModelTable<Metric> - modelType={Metric} - id="metrics-table" - isDeleteable={false} - isEditable={false} - isCreateable={false} - singularName="Metric" - pluralName="Metrics" - name="Metrics" - isViewable={true} - sortBy="name" - sortOrder={SortOrder.Ascending} - cardProps={{ - title: 'Metrics', - description: - 'Metrics are the individual data points that make up a service. They are the building blocks of a service and represent the work done by a single service.', - }} - groupBy={{ - name: true, - }} - onViewPage={async (_item: Metric) => { - return Promise.resolve(new Route('')); - }} - query={{ - projectId: DashboardNavigation.getProjectId(), - serviceId: modelId, - }} - showViewIdButton={false} - noItemsMessage={'No metrics found for this service.'} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - attributes: true, - }, - type: FieldType.JSON, - title: 'Attributes', - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <AnalyticsModelTable<Metric> + modelType={Metric} + id="metrics-table" + isDeleteable={false} + isEditable={false} + isCreateable={false} + singularName="Metric" + pluralName="Metrics" + name="Metrics" + isViewable={true} + sortBy="name" + sortOrder={SortOrder.Ascending} + cardProps={{ + title: "Metrics", + description: + "Metrics are the individual data points that make up a service. They are the building blocks of a service and represent the work done by a single service.", + }} + groupBy={{ + name: true, + }} + onViewPage={async (_item: Metric) => { + return Promise.resolve(new Route("")); + }} + query={{ + projectId: DashboardNavigation.getProjectId(), + serviceId: modelId, + }} + showViewIdButton={false} + noItemsMessage={"No metrics found for this service."} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + attributes: true, + }, + type: FieldType.JSON, + title: "Attributes", + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + ]} + /> + </Fragment> + ); }; export default ServiceDelete; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Metrics/View/Index.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Metrics/View/Index.tsx index e5b47c2ecc..cc8edf879c 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Metrics/View/Index.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Metrics/View/Index.tsx @@ -1,16 +1,16 @@ -import MetricView from '../../../../../../Components/Metrics/MetricVIew'; -import PageComponentProps from '../../../../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; +import MetricView from "../../../../../../Components/Metrics/MetricVIew"; +import PageComponentProps from "../../../../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; const MetricViewPage: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const metricName: string = Navigation.getLastParamAsString(); - const serviceId: ObjectID = Navigation.getLastParamAsObjectID(2); + const metricName: string = Navigation.getLastParamAsString(); + const serviceId: ObjectID = Navigation.getLastParamAsObjectID(2); - return <MetricView metricName={metricName} serviceId={serviceId} />; + return <MetricView metricName={metricName} serviceId={serviceId} />; }; export default MetricViewPage; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Settings.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Settings.tsx index 9278c88e7a..468c501a50 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Settings.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Settings.tsx @@ -1,108 +1,106 @@ -import PageComponentProps from '../../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import ResetObjectID from 'CommonUI/src/Components/ResetObjectID/ResetObjectID'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import TelemetryService from 'Model/Models/TelemetryService'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageComponentProps from "../../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import ResetObjectID from "CommonUI/src/Components/ResetObjectID/ResetObjectID"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import TelemetryService from "Model/Models/TelemetryService"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const ServiceDelete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <CardModelDetail - name="Data Retention" - cardProps={{ - title: 'Telemetry Data Retention', - description: - 'Configure how long you want to keep your telemetry data - like Logs, Metrics, and Traces.', - }} - isEditable={true} - editButtonText="Edit Data Retention" - formFields={[ - { - field: { - retainTelemetryDataForDays: true, - }, - title: 'Telemetry Data Retention (Days)', - description: - 'How long do you want to keep your telemetry data - like Logs, Metrics, and Traces.', - fieldType: FormFieldSchemaType.Number, - required: true, - placeholder: '15', - }, - ]} - modelDetailProps={{ - modelType: TelemetryService, - id: 'model-detail-project', - fields: [ - { - field: { - retainTelemetryDataForDays: true, - }, - title: 'Telemetry Data Retention (Days)', - description: - 'How long do you want to keep your telemetry data - like Logs, Metrics, and Traces.', - fieldType: FieldType.Number, - }, - ], - modelId: modelId, - }} - /> + return ( + <Fragment> + <CardModelDetail + name="Data Retention" + cardProps={{ + title: "Telemetry Data Retention", + description: + "Configure how long you want to keep your telemetry data - like Logs, Metrics, and Traces.", + }} + isEditable={true} + editButtonText="Edit Data Retention" + formFields={[ + { + field: { + retainTelemetryDataForDays: true, + }, + title: "Telemetry Data Retention (Days)", + description: + "How long do you want to keep your telemetry data - like Logs, Metrics, and Traces.", + fieldType: FormFieldSchemaType.Number, + required: true, + placeholder: "15", + }, + ]} + modelDetailProps={{ + modelType: TelemetryService, + id: "model-detail-project", + fields: [ + { + field: { + retainTelemetryDataForDays: true, + }, + title: "Telemetry Data Retention (Days)", + description: + "How long do you want to keep your telemetry data - like Logs, Metrics, and Traces.", + fieldType: FieldType.Number, + }, + ], + modelId: modelId, + }} + /> - <CardModelDetail - name="Telemetry Service Settings" - cardProps={{ - title: 'Telemetry Service Settings', - description: - 'Configure settings for your telemetry service.', - }} - isEditable={true} - editButtonText="Edit Settings" - formFields={[ - { - field: { - serviceColor: true, - }, - title: 'Service Color', - description: - 'Choose a color for your telemetry service.', - fieldType: FormFieldSchemaType.Color, - required: true, - placeholder: '15', - }, - ]} - modelDetailProps={{ - modelType: TelemetryService, - id: 'model-detail-project', - fields: [ - { - field: { - serviceColor: true, - }, - title: 'Service Color', - description: 'Color for your telemetry service.', - fieldType: FieldType.Color, - }, - ], - modelId: modelId, - }} - /> + <CardModelDetail + name="Telemetry Service Settings" + cardProps={{ + title: "Telemetry Service Settings", + description: "Configure settings for your telemetry service.", + }} + isEditable={true} + editButtonText="Edit Settings" + formFields={[ + { + field: { + serviceColor: true, + }, + title: "Service Color", + description: "Choose a color for your telemetry service.", + fieldType: FormFieldSchemaType.Color, + required: true, + placeholder: "15", + }, + ]} + modelDetailProps={{ + modelType: TelemetryService, + id: "model-detail-project", + fields: [ + { + field: { + serviceColor: true, + }, + title: "Service Color", + description: "Color for your telemetry service.", + fieldType: FieldType.Color, + }, + ], + modelId: modelId, + }} + /> - <ResetObjectID<TelemetryService> - modelType={TelemetryService} - fieldName={'telemetryServiceToken'} - title={'Reset Service Token'} - description={'Reset the service token to a new value.'} - modelId={modelId} - /> - </Fragment> - ); + <ResetObjectID<TelemetryService> + modelType={TelemetryService} + fieldName={"telemetryServiceToken"} + title={"Reset Service Token"} + description={"Reset the service token to a new value."} + modelId={modelId} + /> + </Fragment> + ); }; export default ServiceDelete; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/SideMenu.tsx b/Dashboard/src/Pages/Telemetry/Services/View/SideMenu.tsx index ca22afe9e8..c8441fe798 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/SideMenu.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/SideMenu.tsx @@ -1,126 +1,112 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.TELEMETRY_SERVICES_VIEW] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> - <SideMenuItem - link={{ - title: 'Documentation', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Book} - /> - </SideMenuSection> - <SideMenuSection title="Telemetry"> - <SideMenuItem - link={{ - title: 'Logs', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_LOGS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Logs} - /> - <SideMenuItem - link={{ - title: 'Traces', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_TRACES - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.RectangleStack} - /> - <SideMenuItem - link={{ - title: 'Metrics', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_METRICS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.ChartBar} - /> - <SideMenuItem - link={{ - title: 'Dashboards', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Window} - /> - </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Settings', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Settings} - /> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> + <SideMenuItem + link={{ + title: "Documentation", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Book} + /> + </SideMenuSection> + <SideMenuSection title="Telemetry"> + <SideMenuItem + link={{ + title: "Logs", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_LOGS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Logs} + /> + <SideMenuItem + link={{ + title: "Traces", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_TRACES] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.RectangleStack} + /> + <SideMenuItem + link={{ + title: "Metrics", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_METRICS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.ChartBar} + /> + <SideMenuItem + link={{ + title: "Dashboards", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Window} + /> + </SideMenuSection> + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Settings} + /> - <SideMenuItem - link={{ - title: 'Delete Service', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_DELETE - ] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuItem + link={{ + title: "Delete Service", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Traces/Index.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Traces/Index.tsx index 477c7d3d54..32404e9f0e 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Traces/Index.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Traces/Index.tsx @@ -1,146 +1,145 @@ -import SpanStatusElement from '../../../../../Components/Span/SpanStatusElement'; -import DashboardNavigation from '../../../../../Utils/Navigation'; -import PageComponentProps from '../../../../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import ObjectID from 'Common/Types/ObjectID'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import AnalyticsModelTable from 'CommonUI/src/Components/ModelTable/AnalyticsModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Span, { SpanKind, SpanStatus } from 'Model/AnalyticsModels/Span'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import SpanStatusElement from "../../../../../Components/Span/SpanStatusElement"; +import DashboardNavigation from "../../../../../Utils/Navigation"; +import PageComponentProps from "../../../../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import ObjectID from "Common/Types/ObjectID"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import AnalyticsModelTable from "CommonUI/src/Components/ModelTable/AnalyticsModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Span, { SpanKind, SpanStatus } from "Model/AnalyticsModels/Span"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const TracesList: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const spanKindDropdownOptions: Array<DropdownOption> = - DropdownUtil.getDropdownOptionsFromEnum(SpanKind); + const spanKindDropdownOptions: Array<DropdownOption> = + DropdownUtil.getDropdownOptionsFromEnum(SpanKind); - return ( - <Fragment> - <AnalyticsModelTable<Span> - modelType={Span} - id="traces-table" - isDeleteable={false} - isEditable={false} - isCreateable={false} - singularName="Trace" - pluralName="Traces" - name="Traces" - isViewable={true} - cardProps={{ - title: 'Traces', - description: - 'Traces are the individual spans that make up a request. They are the building blocks of a trace and represent the work done by a single service.', - }} - query={{ - projectId: DashboardNavigation.getProjectId(), - serviceId: modelId, - }} - showViewIdButton={true} - noItemsMessage={'No traces found for this service.'} - showRefreshButton={true} - sortBy="startTime" - sortOrder={SortOrder.Descending} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - traceId: true, - }, - type: FieldType.Text, - title: 'Trace ID', - }, - { - field: { - statusCode: true, - }, - type: FieldType.Dropdown, - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - SpanStatus, - true - ).filter((dropdownOption: DropdownOption) => { - return ( - dropdownOption.label === 'Unset' || - dropdownOption.label === 'Ok' || - dropdownOption.label === 'Error' - ); - }), - title: 'Span Status', - }, - { - field: { - name: true, - }, - type: FieldType.Text, - title: 'Root Span Name', - }, - { - field: { - kind: true, - }, - type: FieldType.Text, - title: 'Root Span Kind', - filterDropdownOptions: spanKindDropdownOptions, - }, - { - field: { - startTime: true, - }, - type: FieldType.Date, - title: 'Seen At', - }, - { - field: { - attributes: true, - }, - type: FieldType.JSON, - title: 'Attributes', - }, - ]} - selectMoreFields={{ - statusCode: true, - }} - columns={[ - { - field: { - traceId: true, - }, - title: 'Span ID', - type: FieldType.Element, - getElement: (span: Span): ReactElement => { - return ( - <Fragment> - <SpanStatusElement - span={span} - title={span.traceId?.toString()} - /> - </Fragment> - ); - }, - }, - { - field: { - name: true, - }, - title: 'Span Name', - type: FieldType.Text, - }, - { - field: { - startTime: true, - }, - title: 'Seen At', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <AnalyticsModelTable<Span> + modelType={Span} + id="traces-table" + isDeleteable={false} + isEditable={false} + isCreateable={false} + singularName="Trace" + pluralName="Traces" + name="Traces" + isViewable={true} + cardProps={{ + title: "Traces", + description: + "Traces are the individual spans that make up a request. They are the building blocks of a trace and represent the work done by a single service.", + }} + query={{ + projectId: DashboardNavigation.getProjectId(), + serviceId: modelId, + }} + showViewIdButton={true} + noItemsMessage={"No traces found for this service."} + showRefreshButton={true} + sortBy="startTime" + sortOrder={SortOrder.Descending} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + traceId: true, + }, + type: FieldType.Text, + title: "Trace ID", + }, + { + field: { + statusCode: true, + }, + type: FieldType.Dropdown, + filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum( + SpanStatus, + true, + ).filter((dropdownOption: DropdownOption) => { + return ( + dropdownOption.label === "Unset" || + dropdownOption.label === "Ok" || + dropdownOption.label === "Error" + ); + }), + title: "Span Status", + }, + { + field: { + name: true, + }, + type: FieldType.Text, + title: "Root Span Name", + }, + { + field: { + kind: true, + }, + type: FieldType.Text, + title: "Root Span Kind", + filterDropdownOptions: spanKindDropdownOptions, + }, + { + field: { + startTime: true, + }, + type: FieldType.Date, + title: "Seen At", + }, + { + field: { + attributes: true, + }, + type: FieldType.JSON, + title: "Attributes", + }, + ]} + selectMoreFields={{ + statusCode: true, + }} + columns={[ + { + field: { + traceId: true, + }, + title: "Span ID", + type: FieldType.Element, + getElement: (span: Span): ReactElement => { + return ( + <Fragment> + <SpanStatusElement + span={span} + title={span.traceId?.toString()} + /> + </Fragment> + ); + }, + }, + { + field: { + name: true, + }, + title: "Span Name", + type: FieldType.Text, + }, + { + field: { + startTime: true, + }, + title: "Seen At", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default TracesList; diff --git a/Dashboard/src/Pages/Telemetry/Services/View/Traces/View/Index.tsx b/Dashboard/src/Pages/Telemetry/Services/View/Traces/View/Index.tsx index 9349546507..f09914d5a5 100644 --- a/Dashboard/src/Pages/Telemetry/Services/View/Traces/View/Index.tsx +++ b/Dashboard/src/Pages/Telemetry/Services/View/Traces/View/Index.tsx @@ -1,552 +1,530 @@ -import DashboardLogsViewer from '../../../../../../Components/Logs/LogsViewer'; -import SpanStatusElement from '../../../../../../Components/Span/SpanStatusElement'; -import SpanViewer from '../../../../../../Components/Span/SpanViewer'; -import TelemetryServiceElement from '../../../../../../Components/TelemetryService/TelemetryServiceElement'; -import DashboardNavigation from '../../../../../../Utils/Navigation'; +import DashboardLogsViewer from "../../../../../../Components/Logs/LogsViewer"; +import SpanStatusElement from "../../../../../../Components/Span/SpanStatusElement"; +import SpanViewer from "../../../../../../Components/Span/SpanViewer"; +import TelemetryServiceElement from "../../../../../../Components/TelemetryService/TelemetryServiceElement"; +import DashboardNavigation from "../../../../../../Utils/Navigation"; import SpanUtil, { - DivisibilityFactor, - IntervalUnit, -} from '../../../../../../Utils/SpanUtil'; -import PageComponentProps from '../../../../../PageComponentProps'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import Color from 'Common/Types/Color'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import Card from 'CommonUI/src/Components/Card/Card'; -import { getRefreshButton } from 'CommonUI/src/Components/Card/CardButtons/Refresh'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { GanttChartBar } from 'CommonUI/src/Components/GanttChart/Bar/Index'; + DivisibilityFactor, + IntervalUnit, +} from "../../../../../../Utils/SpanUtil"; +import PageComponentProps from "../../../../../PageComponentProps"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import Color from "Common/Types/Color"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import Card from "CommonUI/src/Components/Card/Card"; +import { getRefreshButton } from "CommonUI/src/Components/Card/CardButtons/Refresh"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { GanttChartBar } from "CommonUI/src/Components/GanttChart/Bar/Index"; import GanttChart, { - GanttChartProps, -} from 'CommonUI/src/Components/GanttChart/Index'; -import { GanttChartRow } from 'CommonUI/src/Components/GanttChart/Row/Row'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; + GanttChartProps, +} from "CommonUI/src/Components/GanttChart/Index"; +import { GanttChartRow } from "CommonUI/src/Components/GanttChart/Row/Row"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; import SideOver, { - SideOverSize, -} from 'CommonUI/src/Components/SideOver/SideOver'; -import API from 'CommonUI/src/Utils/API/API'; -import AnalyticsModelAPI from 'CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI'; -import ListResult from 'CommonUI/src/Utils/BaseDatabase/ListResult'; -import Select from 'CommonUI/src/Utils/BaseDatabase/Select'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Span from 'Model/AnalyticsModels/Span'; -import TelemetryService from 'Model/Models/TelemetryService'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; + SideOverSize, +} from "CommonUI/src/Components/SideOver/SideOver"; +import API from "CommonUI/src/Utils/API/API"; +import AnalyticsModelAPI from "CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI"; +import ListResult from "CommonUI/src/Utils/BaseDatabase/ListResult"; +import Select from "CommonUI/src/Utils/BaseDatabase/Select"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Span from "Model/AnalyticsModels/Span"; +import TelemetryService from "Model/Models/TelemetryService"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; type BarTooltipFunctionProps = { - span: Span; - timelineStartTimeUnixNano: number; - divisibilityFactor: DivisibilityFactor; + span: Span; + timelineStartTimeUnixNano: number; + divisibilityFactor: DivisibilityFactor; }; type GetBarTooltipFunction = (data: BarTooltipFunctionProps) => ReactElement; const TraceView: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [telemetryServices, setTelemetryServices] = React.useState< - TelemetryService[] - >([]); + const [telemetryServices, setTelemetryServices] = React.useState< + TelemetryService[] + >([]); - const [selectedSpans, setSelectedSpans] = React.useState<string[]>([]); + const [selectedSpans, setSelectedSpans] = React.useState<string[]>([]); - const spanIdFromUrl: ObjectID = Navigation.getLastParamAsObjectID(0); + const spanIdFromUrl: ObjectID = Navigation.getLastParamAsObjectID(0); - const [error, setError] = React.useState<string | null>(null); + const [error, setError] = React.useState<string | null>(null); - const [isLoading, setIsLoading] = React.useState<boolean>(false); + const [isLoading, setIsLoading] = React.useState<boolean>(false); - const [spans, setSpans] = React.useState<Span[]>([]); + const [spans, setSpans] = React.useState<Span[]>([]); - const [traceId, setTraceId] = React.useState<string | null>(null); + const [traceId, setTraceId] = React.useState<string | null>(null); - const [ganttChart, setGanttChart] = React.useState<GanttChartProps | null>( - null - ); + const [ganttChart, setGanttChart] = React.useState<GanttChartProps | null>( + null, + ); - const [divisibilityFactor, setDivisibilityFactor] = - React.useState<DivisibilityFactor>({ - divisibilityFactorNumber: 1000, - intervalUnit: IntervalUnit.Milliseconds, + const [divisibilityFactor, setDivisibilityFactor] = + React.useState<DivisibilityFactor>({ + divisibilityFactorNumber: 1000, + intervalUnit: IntervalUnit.Milliseconds, + }); + + const fetchItems: PromiseVoidFunction = async (): Promise<void> => { + try { + setIsLoading(true); + + // get trace with this id and then get all the parentSpanId with this traceid. + + const telemetryServices: ListResult<TelemetryService> = + await ModelAPI.getList<TelemetryService>({ + query: { + projectId: DashboardNavigation.getProjectId()!, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + modelType: TelemetryService, + sort: { + name: SortOrder.Ascending, + }, + select: { + name: true, + _id: true, + serviceColor: true, + }, }); - const fetchItems: PromiseVoidFunction = async (): Promise<void> => { - try { - setIsLoading(true); + setTelemetryServices(telemetryServices.data); - // get trace with this id and then get all the parentSpanId with this traceid. + const select: Select<Span> = { + startTime: true, + endTime: true, + startTimeUnixNano: true, + endTimeUnixNano: true, + name: true, + traceId: true, + parentSpanId: true, + spanId: true, + kind: true, + serviceId: true, + durationUnixNano: true, + statusCode: true, + }; - const telemetryServices: ListResult<TelemetryService> = - await ModelAPI.getList<TelemetryService>({ - query: { - projectId: DashboardNavigation.getProjectId()!, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - modelType: TelemetryService, - sort: { - name: SortOrder.Ascending, - }, - select: { - name: true, - _id: true, - serviceColor: true, - }, - }); - - setTelemetryServices(telemetryServices.data); - - const select: Select<Span> = { - startTime: true, - endTime: true, - startTimeUnixNano: true, - endTimeUnixNano: true, - name: true, - traceId: true, - parentSpanId: true, - spanId: true, - kind: true, - serviceId: true, - durationUnixNano: true, - statusCode: true, - }; - - const spanFromUrl: Span | null = - await AnalyticsModelAPI.getItem<Span>({ - id: spanIdFromUrl, - modelType: Span, - select: select, - }); - - if (spanFromUrl === null) { - setError('Span not found'); - setIsLoading(false); - return; - } - - // now get all the spans with the traceId - - const traceId: string = spanFromUrl.traceId!; - - setTraceId(traceId); - - const allSpans: ListResult<Span> = - await AnalyticsModelAPI.getList<Span>({ - modelType: Span, - select: select, - query: { - traceId: traceId, - }, - sort: { - startTimeUnixNano: SortOrder.Ascending, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - }); - - const spans: Span[] = [...allSpans.data]; - - setSpans(spans); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + const spanFromUrl: Span | null = await AnalyticsModelAPI.getItem<Span>({ + id: spanIdFromUrl, + modelType: Span, + select: select, + }); + if (spanFromUrl === null) { + setError("Span not found"); setIsLoading(false); - }; + return; + } - const getBarTooltip: GetBarTooltipFunction = ( - data: BarTooltipFunctionProps - ): ReactElement => { - const { span, timelineStartTimeUnixNano, divisibilityFactor } = data; + // now get all the spans with the traceId - return ( - <div className="px-1 min-w-56 cursor-default"> - <div className="bar-tooltip-title text-sm text-gray-700 font-medium my-2"> - {span.name} - </div> - <div className="bar-tooltip-description text-gray-600 text-xs space-y-1.5 my-2"> - <div className=""> - <div className="font-semibold">Span ID:</div>{' '} - <div>{span.spanId?.toString()}</div> - </div> - <div className=""> - <div className="font-semibold">Span Status:</div>{' '} - <div> - <SpanStatusElement - span={span} - title={ - 'Status: ' + - SpanUtil.getSpanStatusCodeFriendlyName( - span.statusCode! - ) - } - titleClassName="mt-0.5" - />{' '} - </div> - </div> - <div className=""> - <div className="font-semibold">Seen at:</div>{' '} - <div> - {OneUptimeDate.getDateAsFormattedString( - span.startTime! - )} - </div> - </div> - <div className=""> - <div className="font-semibold">Start:</div>{' '} - <div> - {SpanUtil.getSpanStartsAtAsString({ - timelineStartTimeUnixNano, - divisibilityFactor: divisibilityFactor, - spanStartTimeUnixNano: span.startTimeUnixNano!, - })} - </div> - </div> - <div className=""> - <div className="font-semibold">End:</div>{' '} - <div> - {SpanUtil.getSpanEndsAtAsString({ - timelineStartTimeUnixNano, - divisibilityFactor: divisibilityFactor, - spanEndTimeUnixNano: span.startTimeUnixNano!, - })} - </div> - </div> - <div className=""> - <div className="font-semibold">Duration:</div>{' '} - <div> - {SpanUtil.getSpanDurationAsString({ - spanDurationInUnixNano: span.durationUnixNano!, - divisibilityFactor: divisibilityFactor, - })} - </div> - </div> - <div className=""> - <div className="font-semibold">Span Kind:</div>{' '} - <div> - {SpanUtil.getSpanKindFriendlyName(span.kind!)} - </div> - </div> - </div> - </div> - ); - }; + const traceId: string = spanFromUrl.traceId!; - type SpanToBarFunctionProps = { - span: Span; - timelineStartTimeUnixNano: number; - divisibilityFactor: DivisibilityFactor; - }; + setTraceId(traceId); - type SpanToBarFunction = (data: SpanToBarFunctionProps) => GanttChartBar; + const allSpans: ListResult<Span> = await AnalyticsModelAPI.getList<Span>({ + modelType: Span, + select: select, + query: { + traceId: traceId, + }, + sort: { + startTimeUnixNano: SortOrder.Ascending, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + }); - const spanToBar: SpanToBarFunction = ( - data: SpanToBarFunctionProps - ): GanttChartBar => { - const { span, timelineStartTimeUnixNano, divisibilityFactor } = data; + const spans: Span[] = [...allSpans.data]; - const spanColor: { - barColor: Color; - } = SpanUtil.getGanttChartBarColor({ - span: span, - telemetryServices: telemetryServices, - }); - - return { - id: span.spanId!, - label: ( - <div className="mt-0.5"> - <SpanStatusElement - span={span} - title={ - 'Status: ' + - SpanUtil.getSpanStatusCodeFriendlyName( - span.statusCode! - ) - } - /> - </div> - ), - barColor: spanColor.barColor, - barTimelineStart: - (span.startTimeUnixNano! - timelineStartTimeUnixNano) / - divisibilityFactor.divisibilityFactorNumber, - barTimelineEnd: - (span.endTimeUnixNano! - timelineStartTimeUnixNano) / - divisibilityFactor.divisibilityFactorNumber, - rowId: span.spanId!, - tooltip: getBarTooltip({ - span, - timelineStartTimeUnixNano, - divisibilityFactor: divisibilityFactor, - }), - }; - }; - - type GetBarsFunctionProps = { - rootSpan: Span; - allSpans: Span[]; - timelineStartTimeUnixNano: number; - divisibilityFactor: DivisibilityFactor; - }; - - type GetRowDescriptionFunction = (data: { - telemetryService: TelemetryService; - span: Span; - }) => ReactElement; - - const getRowDescription: GetRowDescriptionFunction = (data: { - telemetryService: TelemetryService; - span: Span; - }): ReactElement => { - const { telemetryService } = data; - - return ( - <div className="flex space-x-5"> - <TelemetryServiceElement - telemetryService={telemetryService} - telemetryServiceNameClassName="mt-0.5" - /> - </div> - ); - }; - - type GetRowsFunction = (data: GetBarsFunctionProps) => Array<GanttChartRow>; - - const getRows: GetRowsFunction = ( - data: GetBarsFunctionProps - ): Array<GanttChartRow> => { - const { - rootSpan, - allSpans, - timelineStartTimeUnixNano, - divisibilityFactor, - } = data; - - if (!rootSpan) { - return []; - } - - const telemetryService: TelemetryService | undefined = - telemetryServices.find((service: TelemetryService) => { - return ( - service._id?.toString() === rootSpan.serviceId?.toString() - ); - }); - - const rootRow: GanttChartRow = { - rowInfo: { - title: <div>{rootSpan.name!}</div>, - description: telemetryService ? ( - getRowDescription({ - telemetryService, - span: rootSpan, - }) - ) : ( - <></> - ), - id: ObjectID.generate().toString(), - }, - bars: [ - spanToBar({ - span: rootSpan, - timelineStartTimeUnixNano, - divisibilityFactor, - }), - ], - childRows: [], - }; - - const currentSpan: Span = rootSpan; - - const currentSpanId: string | undefined = currentSpan.spanId; - - const childSpans: Array<Span> = allSpans.filter((span: Span) => { - return span.parentSpanId?.toString() === currentSpanId?.toString(); - }); - - for (const span of childSpans) { - const childRows: Array<GanttChartRow> | null = getRows({ - rootSpan: span, - allSpans, - timelineStartTimeUnixNano, - divisibilityFactor, - }); - - for (const row of childRows) { - rootRow.childRows.push(row); - } - } - - return [rootRow]; - }; - - React.useEffect(() => { - fetchItems().catch((err: Error) => { - setError(API.getFriendlyMessage(err)); - }); - }, []); - - React.useEffect(() => { - // convert spans to gantt chart - - if (spans.length === 0) { - return; - } - - let timelineStartTimeUnixNano: number = spans[0]!.startTimeUnixNano!; - - let timelineEndTimeUnixNano: number = - spans[spans.length - 1]!.endTimeUnixNano!; - - for (const span of spans) { - if (span.startTimeUnixNano! < timelineStartTimeUnixNano) { - timelineStartTimeUnixNano = span.startTimeUnixNano!; - } - - if (span.endTimeUnixNano! > timelineEndTimeUnixNano) { - timelineEndTimeUnixNano = span.endTimeUnixNano!; - } - } - - const startTimeline: number = 0; - - const divisibilityFactor: DivisibilityFactor = - SpanUtil.getDivisibilityFactor( - timelineEndTimeUnixNano - timelineStartTimeUnixNano - ); - - setDivisibilityFactor(divisibilityFactor); - - const divisibilityFactorNumber: number = - divisibilityFactor.divisibilityFactorNumber; - - const endTimeline: number = - (timelineEndTimeUnixNano - timelineStartTimeUnixNano) / - divisibilityFactorNumber; // divide by 1000 to convert from nanoseconds to ms - - const intervalTemp: number = Math.round(endTimeline / 100) * 10; - - const numberOfDigitsInIntervalTemp: number = - intervalTemp.toString().length; - - const interval: number = Math.pow(10, numberOfDigitsInIntervalTemp); - - const ganttChart: GanttChartProps = { - id: 'chart', - selectedBarIds: selectedSpans, - rows: getRows({ - rootSpan: spans[0]!, - allSpans: spans, - timelineStartTimeUnixNano, - divisibilityFactor, - }), - onBarSelectChange(barIds: Array<string>) { - setSelectedSpans(barIds); - }, - timeline: { - start: startTimeline, - end: Math.ceil(endTimeline / interval) * interval, - interval: interval, - intervalUnit: divisibilityFactor.intervalUnit, - }, - }; - - setGanttChart(ganttChart); - }, [spans, selectedSpans]); - - if (isLoading) { - return <PageLoader isVisible={true} />; + setSpans(spans); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (error) { - return <ErrorMessage error={error} />; - } + setIsLoading(false); + }; + + const getBarTooltip: GetBarTooltipFunction = ( + data: BarTooltipFunctionProps, + ): ReactElement => { + const { span, timelineStartTimeUnixNano, divisibilityFactor } = data; return ( - <Fragment> - <Card - title={'Traces'} - description={'Traces for the request operation.'} - buttons={[ - { - ...getRefreshButton(), - className: 'py-0 pr-0 pl-1 mt-1', - onClick: async () => { - await fetchItems(); - }, - disabled: isLoading, - }, - ]} - > - <div className="overflow-x-auto"> - {ganttChart ? ( - <GanttChart chart={ganttChart} /> - ) : ( - <ErrorMessage error={'No spans found'} /> - )} - </div> - </Card> - - {traceId && ( - <DashboardLogsViewer - id={'traces-logs-viewer'} - noLogsMessage="No logs found for this trace." - traceIds={[traceId]} - enableRealtime={false} - /> - )} - - {selectedSpans.length > 0 && ( - <SideOver - title="View Span" - description="View the span details." - onClose={() => { - setSelectedSpans([]); - }} - size={SideOverSize.Large} - > - <SpanViewer - id={'span-viewer'} - openTelemetrySpanId={selectedSpans[0] as string} - traceStartTimeInUnixNano={spans[0]!.startTimeUnixNano!} - onClose={() => { - setSelectedSpans([]); - }} - telemetryService={ - telemetryServices.find( - (service: TelemetryService) => { - const selectedSpan: Span | undefined = - spans.find((span: Span) => { - return ( - span.spanId?.toString() === - selectedSpans[0]! - ); - }); - - if (!selectedSpan) { - throw new BadDataException( - 'Selected span not found' - ); // this should never happen - } - - return ( - service._id?.toString() === - selectedSpan.serviceId?.toString() - ); - } - )! - } - divisibilityFactor={divisibilityFactor} - /> - </SideOver> - )} - </Fragment> + <div className="px-1 min-w-56 cursor-default"> + <div className="bar-tooltip-title text-sm text-gray-700 font-medium my-2"> + {span.name} + </div> + <div className="bar-tooltip-description text-gray-600 text-xs space-y-1.5 my-2"> + <div className=""> + <div className="font-semibold">Span ID:</div>{" "} + <div>{span.spanId?.toString()}</div> + </div> + <div className=""> + <div className="font-semibold">Span Status:</div>{" "} + <div> + <SpanStatusElement + span={span} + title={ + "Status: " + + SpanUtil.getSpanStatusCodeFriendlyName(span.statusCode!) + } + titleClassName="mt-0.5" + />{" "} + </div> + </div> + <div className=""> + <div className="font-semibold">Seen at:</div>{" "} + <div>{OneUptimeDate.getDateAsFormattedString(span.startTime!)}</div> + </div> + <div className=""> + <div className="font-semibold">Start:</div>{" "} + <div> + {SpanUtil.getSpanStartsAtAsString({ + timelineStartTimeUnixNano, + divisibilityFactor: divisibilityFactor, + spanStartTimeUnixNano: span.startTimeUnixNano!, + })} + </div> + </div> + <div className=""> + <div className="font-semibold">End:</div>{" "} + <div> + {SpanUtil.getSpanEndsAtAsString({ + timelineStartTimeUnixNano, + divisibilityFactor: divisibilityFactor, + spanEndTimeUnixNano: span.startTimeUnixNano!, + })} + </div> + </div> + <div className=""> + <div className="font-semibold">Duration:</div>{" "} + <div> + {SpanUtil.getSpanDurationAsString({ + spanDurationInUnixNano: span.durationUnixNano!, + divisibilityFactor: divisibilityFactor, + })} + </div> + </div> + <div className=""> + <div className="font-semibold">Span Kind:</div>{" "} + <div>{SpanUtil.getSpanKindFriendlyName(span.kind!)}</div> + </div> + </div> + </div> ); + }; + + type SpanToBarFunctionProps = { + span: Span; + timelineStartTimeUnixNano: number; + divisibilityFactor: DivisibilityFactor; + }; + + type SpanToBarFunction = (data: SpanToBarFunctionProps) => GanttChartBar; + + const spanToBar: SpanToBarFunction = ( + data: SpanToBarFunctionProps, + ): GanttChartBar => { + const { span, timelineStartTimeUnixNano, divisibilityFactor } = data; + + const spanColor: { + barColor: Color; + } = SpanUtil.getGanttChartBarColor({ + span: span, + telemetryServices: telemetryServices, + }); + + return { + id: span.spanId!, + label: ( + <div className="mt-0.5"> + <SpanStatusElement + span={span} + title={ + "Status: " + + SpanUtil.getSpanStatusCodeFriendlyName(span.statusCode!) + } + /> + </div> + ), + barColor: spanColor.barColor, + barTimelineStart: + (span.startTimeUnixNano! - timelineStartTimeUnixNano) / + divisibilityFactor.divisibilityFactorNumber, + barTimelineEnd: + (span.endTimeUnixNano! - timelineStartTimeUnixNano) / + divisibilityFactor.divisibilityFactorNumber, + rowId: span.spanId!, + tooltip: getBarTooltip({ + span, + timelineStartTimeUnixNano, + divisibilityFactor: divisibilityFactor, + }), + }; + }; + + type GetBarsFunctionProps = { + rootSpan: Span; + allSpans: Span[]; + timelineStartTimeUnixNano: number; + divisibilityFactor: DivisibilityFactor; + }; + + type GetRowDescriptionFunction = (data: { + telemetryService: TelemetryService; + span: Span; + }) => ReactElement; + + const getRowDescription: GetRowDescriptionFunction = (data: { + telemetryService: TelemetryService; + span: Span; + }): ReactElement => { + const { telemetryService } = data; + + return ( + <div className="flex space-x-5"> + <TelemetryServiceElement + telemetryService={telemetryService} + telemetryServiceNameClassName="mt-0.5" + /> + </div> + ); + }; + + type GetRowsFunction = (data: GetBarsFunctionProps) => Array<GanttChartRow>; + + const getRows: GetRowsFunction = ( + data: GetBarsFunctionProps, + ): Array<GanttChartRow> => { + const { + rootSpan, + allSpans, + timelineStartTimeUnixNano, + divisibilityFactor, + } = data; + + if (!rootSpan) { + return []; + } + + const telemetryService: TelemetryService | undefined = + telemetryServices.find((service: TelemetryService) => { + return service._id?.toString() === rootSpan.serviceId?.toString(); + }); + + const rootRow: GanttChartRow = { + rowInfo: { + title: <div>{rootSpan.name!}</div>, + description: telemetryService ? ( + getRowDescription({ + telemetryService, + span: rootSpan, + }) + ) : ( + <></> + ), + id: ObjectID.generate().toString(), + }, + bars: [ + spanToBar({ + span: rootSpan, + timelineStartTimeUnixNano, + divisibilityFactor, + }), + ], + childRows: [], + }; + + const currentSpan: Span = rootSpan; + + const currentSpanId: string | undefined = currentSpan.spanId; + + const childSpans: Array<Span> = allSpans.filter((span: Span) => { + return span.parentSpanId?.toString() === currentSpanId?.toString(); + }); + + for (const span of childSpans) { + const childRows: Array<GanttChartRow> | null = getRows({ + rootSpan: span, + allSpans, + timelineStartTimeUnixNano, + divisibilityFactor, + }); + + for (const row of childRows) { + rootRow.childRows.push(row); + } + } + + return [rootRow]; + }; + + React.useEffect(() => { + fetchItems().catch((err: Error) => { + setError(API.getFriendlyMessage(err)); + }); + }, []); + + React.useEffect(() => { + // convert spans to gantt chart + + if (spans.length === 0) { + return; + } + + let timelineStartTimeUnixNano: number = spans[0]!.startTimeUnixNano!; + + let timelineEndTimeUnixNano: number = + spans[spans.length - 1]!.endTimeUnixNano!; + + for (const span of spans) { + if (span.startTimeUnixNano! < timelineStartTimeUnixNano) { + timelineStartTimeUnixNano = span.startTimeUnixNano!; + } + + if (span.endTimeUnixNano! > timelineEndTimeUnixNano) { + timelineEndTimeUnixNano = span.endTimeUnixNano!; + } + } + + const startTimeline: number = 0; + + const divisibilityFactor: DivisibilityFactor = + SpanUtil.getDivisibilityFactor( + timelineEndTimeUnixNano - timelineStartTimeUnixNano, + ); + + setDivisibilityFactor(divisibilityFactor); + + const divisibilityFactorNumber: number = + divisibilityFactor.divisibilityFactorNumber; + + const endTimeline: number = + (timelineEndTimeUnixNano - timelineStartTimeUnixNano) / + divisibilityFactorNumber; // divide by 1000 to convert from nanoseconds to ms + + const intervalTemp: number = Math.round(endTimeline / 100) * 10; + + const numberOfDigitsInIntervalTemp: number = intervalTemp.toString().length; + + const interval: number = Math.pow(10, numberOfDigitsInIntervalTemp); + + const ganttChart: GanttChartProps = { + id: "chart", + selectedBarIds: selectedSpans, + rows: getRows({ + rootSpan: spans[0]!, + allSpans: spans, + timelineStartTimeUnixNano, + divisibilityFactor, + }), + onBarSelectChange(barIds: Array<string>) { + setSelectedSpans(barIds); + }, + timeline: { + start: startTimeline, + end: Math.ceil(endTimeline / interval) * interval, + interval: interval, + intervalUnit: divisibilityFactor.intervalUnit, + }, + }; + + setGanttChart(ganttChart); + }, [spans, selectedSpans]); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <Fragment> + <Card + title={"Traces"} + description={"Traces for the request operation."} + buttons={[ + { + ...getRefreshButton(), + className: "py-0 pr-0 pl-1 mt-1", + onClick: async () => { + await fetchItems(); + }, + disabled: isLoading, + }, + ]} + > + <div className="overflow-x-auto"> + {ganttChart ? ( + <GanttChart chart={ganttChart} /> + ) : ( + <ErrorMessage error={"No spans found"} /> + )} + </div> + </Card> + + {traceId && ( + <DashboardLogsViewer + id={"traces-logs-viewer"} + noLogsMessage="No logs found for this trace." + traceIds={[traceId]} + enableRealtime={false} + /> + )} + + {selectedSpans.length > 0 && ( + <SideOver + title="View Span" + description="View the span details." + onClose={() => { + setSelectedSpans([]); + }} + size={SideOverSize.Large} + > + <SpanViewer + id={"span-viewer"} + openTelemetrySpanId={selectedSpans[0] as string} + traceStartTimeInUnixNano={spans[0]!.startTimeUnixNano!} + onClose={() => { + setSelectedSpans([]); + }} + telemetryService={ + telemetryServices.find((service: TelemetryService) => { + const selectedSpan: Span | undefined = spans.find( + (span: Span) => { + return span.spanId?.toString() === selectedSpans[0]!; + }, + ); + + if (!selectedSpan) { + throw new BadDataException("Selected span not found"); // this should never happen + } + + return ( + service._id?.toString() === selectedSpan.serviceId?.toString() + ); + })! + } + divisibilityFactor={divisibilityFactor} + /> + </SideOver> + )} + </Fragment> + ); }; export default TraceView; diff --git a/Dashboard/src/Pages/Telemetry/SideMenu.tsx b/Dashboard/src/Pages/Telemetry/SideMenu.tsx index 8ac000dca8..4240da8cd8 100644 --- a/Dashboard/src/Pages/Telemetry/SideMenu.tsx +++ b/Dashboard/src/Pages/Telemetry/SideMenu.tsx @@ -1,28 +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 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; const DashboardSideMenu: FunctionComponent = (): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Services', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.TELEMETRY_SERVICES] as Route - ), - }} - icon={IconProp.SquareStack} - /> - </SideMenuSection> - </SideMenu> - ); + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Services", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.TELEMETRY_SERVICES] as Route, + ), + }} + icon={IconProp.SquareStack} + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/UserSettings/Layout.tsx b/Dashboard/src/Pages/UserSettings/Layout.tsx index 027c16d7aa..e57fc34306 100644 --- a/Dashboard/src/Pages/UserSettings/Layout.tsx +++ b/Dashboard/src/Pages/UserSettings/Layout.tsx @@ -1,25 +1,25 @@ -import { getUserSettingsBreadcrumbs } from '../../Utils/Breadcrumbs/UserSettingsBreadcrumbs'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import DashboardSideMenu from './SideMenu'; -import Page from 'CommonUI/src/Components/Page/Page'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; +import { getUserSettingsBreadcrumbs } from "../../Utils/Breadcrumbs/UserSettingsBreadcrumbs"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import DashboardSideMenu from "./SideMenu"; +import Page from "CommonUI/src/Components/Page/Page"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet } from "react-router-dom"; const UserSettingsLayout: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <Page - title={'User Settings for Project ' + props.currentProject?.name} - breadcrumbLinks={getUserSettingsBreadcrumbs(path)} - sideMenu={<DashboardSideMenu />} - > - <Outlet /> - </Page> - ); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <Page + title={"User Settings for Project " + props.currentProject?.name} + breadcrumbLinks={getUserSettingsBreadcrumbs(path)} + sideMenu={<DashboardSideMenu />} + > + <Outlet /> + </Page> + ); }; export default UserSettingsLayout; diff --git a/Dashboard/src/Pages/UserSettings/NotificationMethods.tsx b/Dashboard/src/Pages/UserSettings/NotificationMethods.tsx index 6ae959b38e..8f4cf3ab72 100644 --- a/Dashboard/src/Pages/UserSettings/NotificationMethods.tsx +++ b/Dashboard/src/Pages/UserSettings/NotificationMethods.tsx @@ -1,19 +1,19 @@ -import UserCall from '../../Components/NotificationMethods/Call'; -import UserEmail from '../../Components/NotificationMethods/Email'; -import UserSMS from '../../Components/NotificationMethods/SMS'; -import PageComponentProps from '../PageComponentProps'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import UserCall from "../../Components/NotificationMethods/Call"; +import UserEmail from "../../Components/NotificationMethods/Email"; +import UserSMS from "../../Components/NotificationMethods/SMS"; +import PageComponentProps from "../PageComponentProps"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <UserEmail /> - <UserSMS /> - <UserCall /> - </Fragment> - ); + return ( + <Fragment> + <UserEmail /> + <UserSMS /> + <UserCall /> + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/UserSettings/NotificationSettings.tsx b/Dashboard/src/Pages/UserSettings/NotificationSettings.tsx index 9f2bb3e4af..596d047a0c 100644 --- a/Dashboard/src/Pages/UserSettings/NotificationSettings.tsx +++ b/Dashboard/src/Pages/UserSettings/NotificationSettings.tsx @@ -1,196 +1,193 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import User from 'CommonUI/src/Utils/User'; -import UserNotificationSetting from 'Model/Models/UserNotificationSetting'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import User from "CommonUI/src/Utils/User"; +import UserNotificationSetting from "Model/Models/UserNotificationSetting"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - type GetModelTableFunctionProps = { - eventOptions: Array<NotificationSettingEventType>; - title: string; - description: string; - }; + type GetModelTableFunctionProps = { + eventOptions: Array<NotificationSettingEventType>; + title: string; + description: string; + }; - type GetModelTableFuncitonType = ( - options: GetModelTableFunctionProps - ) => ReactElement; - - const getModelTable: GetModelTableFuncitonType = ( - options: GetModelTableFunctionProps - ): ReactElement => { - return ( - <ModelTable<UserNotificationSetting> - modelType={UserNotificationSetting} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - userId: User.getUserId().toString(), - eventType: options.eventOptions, - }} - onBeforeCreate={( - model: UserNotificationSetting - ): Promise<UserNotificationSetting> => { - model.projectId = DashboardNavigation.getProjectId()!; - model.userId = User.getUserId(); - return Promise.resolve(model); - }} - createVerb={'Add'} - id="notification-settings" - name={`User Settings > Notification Rules > ${options.title}`} - isDeleteable={true} - isEditable={true} - isCreateable={true} - cardProps={{ - title: options.title, - description: options.description, - }} - noItemsMessage={ - 'No notification settings found. Please add one to receive notifications.' - } - formFields={[ - { - field: { - eventType: true, - }, - title: 'Event Type', - description: 'Select the event type.', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Select an event type', - dropdownOptions: - DropdownUtil.getDropdownOptionsFromArray( - options.eventOptions - ), - }, - { - field: { - alertByEmail: true, - }, - title: 'Alert By Email', - description: - 'Select if you want to be alerted by email.', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - { - field: { - alertBySMS: true, - }, - title: 'Alert By SMS', - description: 'Select if you want to be alerted by SMS.', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - { - field: { - alertByCall: true, - }, - title: 'Alert By Call', - description: - 'Select if you want to be alerted by call.', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - ]} - showRefreshButton={true} - filters={[]} - columns={[ - { - field: { - eventType: true, - }, - title: 'Event Type', - type: FieldType.Text, - }, - { - field: { - alertByEmail: true, - }, - title: 'Email Alerts', - type: FieldType.Boolean, - }, - { - field: { - alertBySMS: true, - }, - title: 'SMS Alerts', - type: FieldType.Boolean, - }, - { - field: { - alertByCall: true, - }, - title: 'Call Alerts', - type: FieldType.Boolean, - }, - ]} - /> - ); - }; + type GetModelTableFuncitonType = ( + options: GetModelTableFunctionProps, + ) => ReactElement; + const getModelTable: GetModelTableFuncitonType = ( + options: GetModelTableFunctionProps, + ): ReactElement => { return ( - <Fragment> - <div> - {getModelTable({ - eventOptions: [ - NotificationSettingEventType.SEND_INCIDENT_NOTE_POSTED_OWNER_NOTIFICATION, - NotificationSettingEventType.SEND_INCIDENT_OWNER_ADDED_NOTIFICATION, - NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION, - NotificationSettingEventType.SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION, - ], - title: 'Incident Notifications', - description: - 'Here are the list of notification methods we will use when an event happens on an incident.', - })} - </div> - - <div> - {getModelTable({ - eventOptions: [ - NotificationSettingEventType.SEND_MONITOR_OWNER_ADDED_NOTIFICATION, - NotificationSettingEventType.SEND_MONITOR_CREATED_OWNER_NOTIFICATION, - NotificationSettingEventType.SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION, - ], - title: 'Monitor Notifications', - description: - 'Here are the list of notification methods we will use when an event happens on a monitor.', - })} - </div> - - <div> - {getModelTable({ - eventOptions: [ - NotificationSettingEventType.SEND_STATUS_PAGE_CREATED_OWNER_NOTIFICATION, - NotificationSettingEventType.SEND_STATUS_PAGE_OWNER_ADDED_NOTIFICATION, - NotificationSettingEventType.SEND_STATUS_PAGE_ANNOUNCEMENT_CREATED_OWNER_NOTIFICATION, - ], - title: 'Status Page Notifications', - description: - 'Here are the list of notification methods we will use when an event happens on a status page.', - })} - </div> - - <div> - {getModelTable({ - eventOptions: [ - NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_NOTE_POSTED_OWNER_NOTIFICATION, - NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_OWNER_ADDED_NOTIFICATION, - NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_CREATED_OWNER_NOTIFICATION, - NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_STATE_CHANGED_OWNER_NOTIFICATION, - ], - title: 'Scheduled Maintenance Notifications', - description: - 'Here are the list of notification methods we will use when an event happens on an incident.', - })} - </div> - </Fragment> + <ModelTable<UserNotificationSetting> + modelType={UserNotificationSetting} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + userId: User.getUserId().toString(), + eventType: options.eventOptions, + }} + onBeforeCreate={( + model: UserNotificationSetting, + ): Promise<UserNotificationSetting> => { + model.projectId = DashboardNavigation.getProjectId()!; + model.userId = User.getUserId(); + return Promise.resolve(model); + }} + createVerb={"Add"} + id="notification-settings" + name={`User Settings > Notification Rules > ${options.title}`} + isDeleteable={true} + isEditable={true} + isCreateable={true} + cardProps={{ + title: options.title, + description: options.description, + }} + noItemsMessage={ + "No notification settings found. Please add one to receive notifications." + } + formFields={[ + { + field: { + eventType: true, + }, + title: "Event Type", + description: "Select the event type.", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Select an event type", + dropdownOptions: DropdownUtil.getDropdownOptionsFromArray( + options.eventOptions, + ), + }, + { + field: { + alertByEmail: true, + }, + title: "Alert By Email", + description: "Select if you want to be alerted by email.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + { + field: { + alertBySMS: true, + }, + title: "Alert By SMS", + description: "Select if you want to be alerted by SMS.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + { + field: { + alertByCall: true, + }, + title: "Alert By Call", + description: "Select if you want to be alerted by call.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + ]} + showRefreshButton={true} + filters={[]} + columns={[ + { + field: { + eventType: true, + }, + title: "Event Type", + type: FieldType.Text, + }, + { + field: { + alertByEmail: true, + }, + title: "Email Alerts", + type: FieldType.Boolean, + }, + { + field: { + alertBySMS: true, + }, + title: "SMS Alerts", + type: FieldType.Boolean, + }, + { + field: { + alertByCall: true, + }, + title: "Call Alerts", + type: FieldType.Boolean, + }, + ]} + /> ); + }; + + return ( + <Fragment> + <div> + {getModelTable({ + eventOptions: [ + NotificationSettingEventType.SEND_INCIDENT_NOTE_POSTED_OWNER_NOTIFICATION, + NotificationSettingEventType.SEND_INCIDENT_OWNER_ADDED_NOTIFICATION, + NotificationSettingEventType.SEND_INCIDENT_CREATED_OWNER_NOTIFICATION, + NotificationSettingEventType.SEND_INCIDENT_STATE_CHANGED_OWNER_NOTIFICATION, + ], + title: "Incident Notifications", + description: + "Here are the list of notification methods we will use when an event happens on an incident.", + })} + </div> + + <div> + {getModelTable({ + eventOptions: [ + NotificationSettingEventType.SEND_MONITOR_OWNER_ADDED_NOTIFICATION, + NotificationSettingEventType.SEND_MONITOR_CREATED_OWNER_NOTIFICATION, + NotificationSettingEventType.SEND_MONITOR_STATUS_CHANGED_OWNER_NOTIFICATION, + ], + title: "Monitor Notifications", + description: + "Here are the list of notification methods we will use when an event happens on a monitor.", + })} + </div> + + <div> + {getModelTable({ + eventOptions: [ + NotificationSettingEventType.SEND_STATUS_PAGE_CREATED_OWNER_NOTIFICATION, + NotificationSettingEventType.SEND_STATUS_PAGE_OWNER_ADDED_NOTIFICATION, + NotificationSettingEventType.SEND_STATUS_PAGE_ANNOUNCEMENT_CREATED_OWNER_NOTIFICATION, + ], + title: "Status Page Notifications", + description: + "Here are the list of notification methods we will use when an event happens on a status page.", + })} + </div> + + <div> + {getModelTable({ + eventOptions: [ + NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_NOTE_POSTED_OWNER_NOTIFICATION, + NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_OWNER_ADDED_NOTIFICATION, + NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_CREATED_OWNER_NOTIFICATION, + NotificationSettingEventType.SEND_SCHEDULED_MAINTENANCE_STATE_CHANGED_OWNER_NOTIFICATION, + ], + title: "Scheduled Maintenance Notifications", + description: + "Here are the list of notification methods we will use when an event happens on an incident.", + })} + </div> + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/UserSettings/OnCallLogs.tsx b/Dashboard/src/Pages/UserSettings/OnCallLogs.tsx index bc8abb1801..3a2f9053ba 100644 --- a/Dashboard/src/Pages/UserSettings/OnCallLogs.tsx +++ b/Dashboard/src/Pages/UserSettings/OnCallLogs.tsx @@ -1,280 +1,259 @@ -import EscalationRuleView from '../../Components/OnCallPolicy/EscalationRule/EscalationRule'; -import OnCallDutyPolicyView from '../../Components/OnCallPolicy/OnCallPolicy'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import { Green, Red, Yellow } from 'Common/Types/BrandColors'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import UserNotificationExecutionStatus from 'Common/Types/UserNotification/UserNotificationExecutionStatus'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import User from 'CommonUI/src/Utils/User'; -import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy'; -import OnCallDutyPolicyEscalationRule from 'Model/Models/OnCallDutyPolicyEscalationRule'; -import UserOnCallLog from 'Model/Models/UserOnCallLog'; +import EscalationRuleView from "../../Components/OnCallPolicy/EscalationRule/EscalationRule"; +import OnCallDutyPolicyView from "../../Components/OnCallPolicy/OnCallPolicy"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import { Green, Red, Yellow } from "Common/Types/BrandColors"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import UserNotificationExecutionStatus from "Common/Types/UserNotification/UserNotificationExecutionStatus"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import User from "CommonUI/src/Utils/User"; +import OnCallDutyPolicy from "Model/Models/OnCallDutyPolicy"; +import OnCallDutyPolicyEscalationRule from "Model/Models/OnCallDutyPolicyEscalationRule"; +import UserOnCallLog from "Model/Models/UserOnCallLog"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [showViewStatusMessageModal, setShowViewStatusMessageModal] = - useState<boolean>(false); - const [statusMessage, setStatusMessage] = useState<string>(''); + const [showViewStatusMessageModal, setShowViewStatusMessageModal] = + useState<boolean>(false); + const [statusMessage, setStatusMessage] = useState<string>(""); - return ( - <Fragment> - <ModelTable<UserOnCallLog> - modelType={UserOnCallLog} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - userId: User.getUserId()?.toString(), - }} - id="notification-logs-table" - name="User Settings > Notification Logs" - isDeleteable={false} - isEditable={false} - isCreateable={false} - cardProps={{ - title: 'Notification Logs', - description: - 'Here are all the notification logs. This will help you to debug any notification issues that you may face.', - }} - selectMoreFields={{ - statusMessage: true, - }} - noItemsMessage={'No notifications sent out so far.'} - viewPageRoute={Navigation.getCurrentRoute()} - showRefreshButton={true} - showViewIdButton={true} - isViewable={true} - actionButtons={[ - { - title: 'View Status Message', - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - item: UserOnCallLog, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setStatusMessage( - item['statusMessage'] as string - ); - setShowViewStatusMessageModal(true); + return ( + <Fragment> + <ModelTable<UserOnCallLog> + modelType={UserOnCallLog} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + userId: User.getUserId()?.toString(), + }} + id="notification-logs-table" + name="User Settings > Notification Logs" + isDeleteable={false} + isEditable={false} + isCreateable={false} + cardProps={{ + title: "Notification Logs", + description: + "Here are all the notification logs. This will help you to debug any notification issues that you may face.", + }} + selectMoreFields={{ + statusMessage: true, + }} + noItemsMessage={"No notifications sent out so far."} + viewPageRoute={Navigation.getCurrentRoute()} + showRefreshButton={true} + showViewIdButton={true} + isViewable={true} + actionButtons={[ + { + title: "View Status Message", + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async ( + item: UserOnCallLog, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setStatusMessage(item["statusMessage"] as string); + setShowViewStatusMessageModal(true); - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - viewButtonText={'View Timeline'} - filters={[ - { - field: { - onCallDutyPolicy: { - name: true, - }, - }, - filterEntityType: OnCallDutyPolicy, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - title: 'On-Call Policy', - type: FieldType.Entity, - }, - { - field: { - onCallDutyPolicyEscalationRule: { - name: true, - }, - }, - filterEntityType: OnCallDutyPolicyEscalationRule, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - title: 'Escalation Rule', - type: FieldType.Entity, - }, - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Dropdown, - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - UserNotificationExecutionStatus - ), - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - onCallDutyPolicy: { - name: true, - }, - }, - title: 'On-Call Policy', - type: FieldType.Element, + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + viewButtonText={"View Timeline"} + filters={[ + { + field: { + onCallDutyPolicy: { + name: true, + }, + }, + filterEntityType: OnCallDutyPolicy, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + title: "On-Call Policy", + type: FieldType.Entity, + }, + { + field: { + onCallDutyPolicyEscalationRule: { + name: true, + }, + }, + filterEntityType: OnCallDutyPolicyEscalationRule, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + title: "Escalation Rule", + type: FieldType.Entity, + }, + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Dropdown, + filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum( + UserNotificationExecutionStatus, + ), + }, + { + field: { + createdAt: true, + }, + title: "Created At", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + onCallDutyPolicy: { + name: true, + }, + }, + title: "On-Call Policy", + type: FieldType.Element, - getElement: (item: UserOnCallLog): ReactElement => { - if (item['onCallDutyPolicy']) { - return ( - <OnCallDutyPolicyView - onCallPolicy={ - item[ - 'onCallDutyPolicy' - ] as OnCallDutyPolicy - } - /> - ); - } - return <p>No on-call policy.</p>; - }, - }, - { - field: { - onCallDutyPolicyEscalationRule: { - name: true, - }, - }, - title: 'Escalation Rule', - type: FieldType.Element, + getElement: (item: UserOnCallLog): ReactElement => { + if (item["onCallDutyPolicy"]) { + return ( + <OnCallDutyPolicyView + onCallPolicy={item["onCallDutyPolicy"] as OnCallDutyPolicy} + /> + ); + } + return <p>No on-call policy.</p>; + }, + }, + { + field: { + onCallDutyPolicyEscalationRule: { + name: true, + }, + }, + title: "Escalation Rule", + type: FieldType.Element, - getElement: (item: UserOnCallLog): ReactElement => { - if (item['onCallDutyPolicyEscalationRule']) { - return ( - <EscalationRuleView - escalationRule={ - item[ - 'onCallDutyPolicyEscalationRule' - ] as OnCallDutyPolicyEscalationRule - } - /> - ); - } - return <p>No escalation rule.</p>; - }, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - type: FieldType.DateTime, - }, - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Element, + getElement: (item: UserOnCallLog): ReactElement => { + if (item["onCallDutyPolicyEscalationRule"]) { + return ( + <EscalationRuleView + escalationRule={ + item[ + "onCallDutyPolicyEscalationRule" + ] as OnCallDutyPolicyEscalationRule + } + /> + ); + } + return <p>No escalation rule.</p>; + }, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + type: FieldType.DateTime, + }, + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Element, - getElement: (item: UserOnCallLog): ReactElement => { - if ( - item['status'] === - UserNotificationExecutionStatus.Completed - ) { - return ( - <Pill - color={Green} - text={ - UserNotificationExecutionStatus.Completed - } - /> - ); - } else if ( - item['status'] === - UserNotificationExecutionStatus.Started - ) { - return ( - <Pill - color={Yellow} - text={ - UserNotificationExecutionStatus.Started - } - /> - ); - } else if ( - item['status'] === - UserNotificationExecutionStatus.Scheduled - ) { - return ( - <Pill - color={Yellow} - text={ - UserNotificationExecutionStatus.Scheduled - } - /> - ); - } else if ( - item['status'] === - UserNotificationExecutionStatus.Executing - ) { - return ( - <Pill - color={Yellow} - text={ - UserNotificationExecutionStatus.Executing - } - /> - ); - } + getElement: (item: UserOnCallLog): ReactElement => { + if ( + item["status"] === UserNotificationExecutionStatus.Completed + ) { + return ( + <Pill + color={Green} + text={UserNotificationExecutionStatus.Completed} + /> + ); + } else if ( + item["status"] === UserNotificationExecutionStatus.Started + ) { + return ( + <Pill + color={Yellow} + text={UserNotificationExecutionStatus.Started} + /> + ); + } else if ( + item["status"] === UserNotificationExecutionStatus.Scheduled + ) { + return ( + <Pill + color={Yellow} + text={UserNotificationExecutionStatus.Scheduled} + /> + ); + } else if ( + item["status"] === UserNotificationExecutionStatus.Executing + ) { + return ( + <Pill + color={Yellow} + text={UserNotificationExecutionStatus.Executing} + /> + ); + } - return ( - <Pill - color={Red} - text={UserNotificationExecutionStatus.Error} - /> - ); - }, - }, - ]} - /> - - {showViewStatusMessageModal ? ( - <ConfirmModal - title={'Status Message'} - description={statusMessage} - submitButtonText={'Close'} - onSubmit={async () => { - setShowViewStatusMessageModal(false); - }} + return ( + <Pill + color={Red} + text={UserNotificationExecutionStatus.Error} /> - ) : ( - <></> - )} - </Fragment> - ); + ); + }, + }, + ]} + /> + + {showViewStatusMessageModal ? ( + <ConfirmModal + title={"Status Message"} + description={statusMessage} + submitButtonText={"Close"} + onSubmit={async () => { + setShowViewStatusMessageModal(false); + }} + /> + ) : ( + <></> + )} + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/UserSettings/OnCallLogsTimeline.tsx b/Dashboard/src/Pages/UserSettings/OnCallLogsTimeline.tsx index 77606062ed..8485317b89 100644 --- a/Dashboard/src/Pages/UserSettings/OnCallLogsTimeline.tsx +++ b/Dashboard/src/Pages/UserSettings/OnCallLogsTimeline.tsx @@ -1,222 +1,191 @@ -import NotificationMethodView from '../../Components/NotificationMethods/NotificationMethod'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import { Green, Red, Yellow } from 'Common/Types/BrandColors'; -import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import UserNotificationStatus from 'Common/Types/UserNotification/UserNotificationStatus'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import Pill from 'CommonUI/src/Components/Pill/Pill'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import User from 'CommonUI/src/Utils/User'; -import UserOnCallLogTimeline from 'Model/Models/UserOnCallLogTimeline'; +import NotificationMethodView from "../../Components/NotificationMethods/NotificationMethod"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import { Green, Red, Yellow } from "Common/Types/BrandColors"; +import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import UserNotificationStatus from "Common/Types/UserNotification/UserNotificationStatus"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import Pill from "CommonUI/src/Components/Pill/Pill"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import User from "CommonUI/src/Utils/User"; +import UserOnCallLogTimeline from "Model/Models/UserOnCallLogTimeline"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(); - const [showViewStatusMessageModal, setShowViewStatusMessageModal] = - useState<boolean>(false); - const [statusMessage, setStatusMessage] = useState<string>(''); - - const getModelTable: GetReactElementFunction = (): ReactElement => { - return ( - <ModelTable<UserOnCallLogTimeline> - modelType={UserOnCallLogTimeline} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - userNotificationLogId: modelId.toString(), - userId: User.getUserId()?.toString(), - }} - id="notification-logs-timeline-table" - name="User Settings > Notification Logs > Timeline" - isDeleteable={false} - isEditable={false} - isCreateable={false} - cardProps={{ - title: 'Notification Timeline', - description: - 'Here are all the timeline events. This will help you to debug any notification issues that you may face.', - }} - selectMoreFields={{ - statusMessage: true, - userEmail: { - email: true, - }, - userSms: { - phone: true, - }, - }} - noItemsMessage={'No notifications sent out so far.'} - showRefreshButton={true} - showViewIdButton={true} - actionButtons={[ - { - title: 'View Status Message', - buttonStyleType: ButtonStyleType.NORMAL, - onClick: async ( - item: UserOnCallLogTimeline, - onCompleteAction: VoidFunction, - onError: ErrorFunction - ) => { - try { - setStatusMessage( - item['statusMessage'] as string - ); - setShowViewStatusMessageModal(true); - - onCompleteAction(); - } catch (err) { - onCompleteAction(); - onError(err as Error); - } - }, - }, - ]} - filters={[ - { - field: { - createdAt: true, - }, - title: 'Notification Sent At', - type: FieldType.DateTime, - }, - - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Element, - - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - UserNotificationStatus - ), - }, - ]} - columns={[ - { - field: { - userCall: { - phone: true, - }, - }, - title: 'Notification Method', - type: FieldType.Element, - getElement: ( - item: UserOnCallLogTimeline - ): ReactElement => { - return ( - <NotificationMethodView - item={item} - modelType={UserOnCallLogTimeline} - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Notification Sent At', - type: FieldType.DateTime, - }, - { - field: { - status: true, - }, - title: 'Status', - type: FieldType.Element, - - getElement: ( - item: UserOnCallLogTimeline - ): ReactElement => { - if ( - item['status'] === UserNotificationStatus.Sent - ) { - return ( - <Pill - color={Green} - text={UserNotificationStatus.Sent} - /> - ); - } else if ( - item['status'] === - UserNotificationStatus.Acknowledged - ) { - return ( - <Pill - color={Green} - text={ - UserNotificationStatus.Acknowledged - } - /> - ); - } else if ( - item['status'] === UserNotificationStatus.Error - ) { - return ( - <Pill - color={Yellow} - text={UserNotificationStatus.Error} - /> - ); - } else if ( - item['status'] === - UserNotificationStatus.Skipped - ) { - return ( - <Pill - color={Yellow} - text={UserNotificationStatus.Skipped} - /> - ); - } - - return ( - <Pill - color={Red} - text={UserNotificationStatus.Error} - /> - ); - }, - }, - ]} - /> - ); - }; + const [showViewStatusMessageModal, setShowViewStatusMessageModal] = + useState<boolean>(false); + const [statusMessage, setStatusMessage] = useState<string>(""); + const getModelTable: GetReactElementFunction = (): ReactElement => { return ( - <Fragment> - {getModelTable()} + <ModelTable<UserOnCallLogTimeline> + modelType={UserOnCallLogTimeline} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + userNotificationLogId: modelId.toString(), + userId: User.getUserId()?.toString(), + }} + id="notification-logs-timeline-table" + name="User Settings > Notification Logs > Timeline" + isDeleteable={false} + isEditable={false} + isCreateable={false} + cardProps={{ + title: "Notification Timeline", + description: + "Here are all the timeline events. This will help you to debug any notification issues that you may face.", + }} + selectMoreFields={{ + statusMessage: true, + userEmail: { + email: true, + }, + userSms: { + phone: true, + }, + }} + noItemsMessage={"No notifications sent out so far."} + showRefreshButton={true} + showViewIdButton={true} + actionButtons={[ + { + title: "View Status Message", + buttonStyleType: ButtonStyleType.NORMAL, + onClick: async ( + item: UserOnCallLogTimeline, + onCompleteAction: VoidFunction, + onError: ErrorFunction, + ) => { + try { + setStatusMessage(item["statusMessage"] as string); + setShowViewStatusMessageModal(true); - {showViewStatusMessageModal ? ( - <ConfirmModal - title={'Status Message'} - description={statusMessage} - submitButtonText={'Close'} - onSubmit={async () => { - setShowViewStatusMessageModal(false); - }} + onCompleteAction(); + } catch (err) { + onCompleteAction(); + onError(err as Error); + } + }, + }, + ]} + filters={[ + { + field: { + createdAt: true, + }, + title: "Notification Sent At", + type: FieldType.DateTime, + }, + + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Element, + + filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum( + UserNotificationStatus, + ), + }, + ]} + columns={[ + { + field: { + userCall: { + phone: true, + }, + }, + title: "Notification Method", + type: FieldType.Element, + getElement: (item: UserOnCallLogTimeline): ReactElement => { + return ( + <NotificationMethodView + item={item} + modelType={UserOnCallLogTimeline} /> - ) : ( - <></> - )} - </Fragment> + ); + }, + }, + { + field: { + createdAt: true, + }, + title: "Notification Sent At", + type: FieldType.DateTime, + }, + { + field: { + status: true, + }, + title: "Status", + type: FieldType.Element, + + getElement: (item: UserOnCallLogTimeline): ReactElement => { + if (item["status"] === UserNotificationStatus.Sent) { + return ( + <Pill color={Green} text={UserNotificationStatus.Sent} /> + ); + } else if ( + item["status"] === UserNotificationStatus.Acknowledged + ) { + return ( + <Pill + color={Green} + text={UserNotificationStatus.Acknowledged} + /> + ); + } else if (item["status"] === UserNotificationStatus.Error) { + return ( + <Pill color={Yellow} text={UserNotificationStatus.Error} /> + ); + } else if (item["status"] === UserNotificationStatus.Skipped) { + return ( + <Pill color={Yellow} text={UserNotificationStatus.Skipped} /> + ); + } + + return <Pill color={Red} text={UserNotificationStatus.Error} />; + }, + }, + ]} + /> ); + }; + + return ( + <Fragment> + {getModelTable()} + + {showViewStatusMessageModal ? ( + <ConfirmModal + title={"Status Message"} + description={statusMessage} + submitButtonText={"Close"} + onSubmit={async () => { + setShowViewStatusMessageModal(false); + }} + /> + ) : ( + <></> + )} + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/UserSettings/OnCallRules.tsx b/Dashboard/src/Pages/UserSettings/OnCallRules.tsx index 55cca2dc68..7925e966b8 100644 --- a/Dashboard/src/Pages/UserSettings/OnCallRules.tsx +++ b/Dashboard/src/Pages/UserSettings/OnCallRules.tsx @@ -1,386 +1,366 @@ -import NotificationMethodView from '../../Components/NotificationMethods/NotificationMethod'; -import NotifyAfterDropdownOptions from '../../Components/NotificationRule/NotifyAfterMinutesDropdownOptions'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { JSONObject } from 'Common/Types/JSON'; -import NotificationRuleType from 'Common/Types/NotificationRule/NotificationRuleType'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import User from 'CommonUI/src/Utils/User'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import UserCall from 'Model/Models/UserCall'; -import UserEmail from 'Model/Models/UserEmail'; -import UserNotificationRule from 'Model/Models/UserNotificationRule'; -import UserSMS from 'Model/Models/UserSMS'; +import NotificationMethodView from "../../Components/NotificationMethods/NotificationMethod"; +import NotifyAfterDropdownOptions from "../../Components/NotificationRule/NotifyAfterMinutesDropdownOptions"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { JSONObject } from "Common/Types/JSON"; +import NotificationRuleType from "Common/Types/NotificationRule/NotificationRuleType"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import User from "CommonUI/src/Utils/User"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import UserCall from "Model/Models/UserCall"; +import UserEmail from "Model/Models/UserEmail"; +import UserNotificationRule from "Model/Models/UserNotificationRule"; +import UserSMS from "Model/Models/UserSMS"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [error, setError] = useState<string>(''); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [incidentSeverities, setIncidentSeverities] = useState< - Array<IncidentSeverity> - >([]); - const [userEmails, setUserEmails] = useState<Array<UserEmail>>([]); - const [userSMSs, setUserSMSs] = useState<Array<UserSMS>>([]); - const [userCalls, setUserCalls] = useState<Array<UserCall>>([]); - const [ - notificationMethodsDropdownOptions, - setNotificationMethodsDropdownOptions, - ] = useState<Array<DropdownOption>>([]); + const [error, setError] = useState<string>(""); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [incidentSeverities, setIncidentSeverities] = useState< + Array<IncidentSeverity> + >([]); + const [userEmails, setUserEmails] = useState<Array<UserEmail>>([]); + const [userSMSs, setUserSMSs] = useState<Array<UserSMS>>([]); + const [userCalls, setUserCalls] = useState<Array<UserCall>>([]); + const [ + notificationMethodsDropdownOptions, + setNotificationMethodsDropdownOptions, + ] = useState<Array<DropdownOption>>([]); - type GetTableFunctionProps = { - incidentSeverity?: IncidentSeverity; - ruleType: NotificationRuleType; - title: string; - description: string; - }; + type GetTableFunctionProps = { + incidentSeverity?: IncidentSeverity; + ruleType: NotificationRuleType; + title: string; + description: string; + }; - type GetTableFunction = (props: GetTableFunctionProps) => ReactElement; + type GetTableFunction = (props: GetTableFunctionProps) => ReactElement; - const getModelTable: GetTableFunction = ( - options: GetTableFunctionProps - ): ReactElement => { - return ( - <ModelTable<UserNotificationRule> - modelType={UserNotificationRule} - query={{ - projectId: DashboardNavigation.getProjectId()?.toString(), - userId: User.getUserId().toString(), - ruleType: options.ruleType, - incidentSeverityId: - options.incidentSeverity?.id?.toString() || undefined, - }} - onBeforeCreate={( - model: UserNotificationRule, - miscDataProps: JSONObject - ): Promise<UserNotificationRule> => { - model.projectId = DashboardNavigation.getProjectId()!; - model.userId = User.getUserId(); - model.ruleType = options.ruleType; - if (options.incidentSeverity?.id) { - model.incidentSeverityId = options.incidentSeverity?.id; - } + const getModelTable: GetTableFunction = ( + options: GetTableFunctionProps, + ): ReactElement => { + return ( + <ModelTable<UserNotificationRule> + modelType={UserNotificationRule} + query={{ + projectId: DashboardNavigation.getProjectId()?.toString(), + userId: User.getUserId().toString(), + ruleType: options.ruleType, + incidentSeverityId: + options.incidentSeverity?.id?.toString() || undefined, + }} + onBeforeCreate={( + model: UserNotificationRule, + miscDataProps: JSONObject, + ): Promise<UserNotificationRule> => { + model.projectId = DashboardNavigation.getProjectId()!; + model.userId = User.getUserId(); + model.ruleType = options.ruleType; + if (options.incidentSeverity?.id) { + model.incidentSeverityId = options.incidentSeverity?.id; + } - if (miscDataProps['notificationMethod']) { - const userEmail: UserEmail | undefined = - userEmails.find((userEmail: UserEmail) => { - return ( - userEmail.id!.toString() === - miscDataProps[ - 'notificationMethod' - ]?.toString() - ); - }); + if (miscDataProps["notificationMethod"]) { + const userEmail: UserEmail | undefined = userEmails.find( + (userEmail: UserEmail) => { + return ( + userEmail.id!.toString() === + miscDataProps["notificationMethod"]?.toString() + ); + }, + ); - if (userEmail) { - model.userEmailId = userEmail.id!; - } + if (userEmail) { + model.userEmailId = userEmail.id!; + } - const userSMS: UserSMS | undefined = userSMSs.find( - (userSMS: UserSMS) => { - return ( - userSMS.id!.toString() === - miscDataProps[ - 'notificationMethod' - ]?.toString() - ); - } - ); + const userSMS: UserSMS | undefined = userSMSs.find( + (userSMS: UserSMS) => { + return ( + userSMS.id!.toString() === + miscDataProps["notificationMethod"]?.toString() + ); + }, + ); - if (userSMS) { - model.userSmsId = userSMS.id!; - } + if (userSMS) { + model.userSmsId = userSMS.id!; + } - const userCall: UserCall | undefined = userCalls.find( - (userCall: UserCall) => { - return ( - userCall.id!.toString() === - miscDataProps[ - 'notificationMethod' - ]?.toString() - ); - } - ); + const userCall: UserCall | undefined = userCalls.find( + (userCall: UserCall) => { + return ( + userCall.id!.toString() === + miscDataProps["notificationMethod"]?.toString() + ); + }, + ); - if (userCall) { - model.userCallId = userCall.id!; - } - } + if (userCall) { + model.userCallId = userCall.id!; + } + } - return Promise.resolve(model); - }} - sortOrder={SortOrder.Ascending} - sortBy="notifyAfterMinutes" - createVerb={'Add'} - id="notification-rules" - name={`User Settings > Notification Rules > ${ - options.incidentSeverity?.name || options.ruleType - }`} - isDeleteable={true} - isEditable={false} - isCreateable={true} - cardProps={{ - title: options.title, - description: options.description, - }} - noItemsMessage={ - 'No notification rules found. Please add one to receive notifications.' - } - formFields={[ - { - overrideField: { - notificationMethod: true, - }, - showEvenIfPermissionDoesNotExist: true, - overrideFieldKey: 'notificationMethod', - title: 'Notification Method', - description: 'How do you want to be notified?', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Notification Method', - dropdownOptions: notificationMethodsDropdownOptions, - }, - { - field: { - notifyAfterMinutes: true, - }, - title: 'Notify me after', - fieldType: FormFieldSchemaType.Dropdown, - required: true, - placeholder: 'Immediately', - dropdownOptions: NotifyAfterDropdownOptions, - }, - ]} - showRefreshButton={true} - selectMoreFields={{ - userEmail: { - email: true, - }, - userSms: { - phone: true, - }, - }} - filters={[]} - columns={[ - { - field: { - userCall: { - phone: true, - }, - }, - title: 'Notification Method', - type: FieldType.Text, - getElement: ( - item: UserNotificationRule - ): ReactElement => { - return ( - <NotificationMethodView - item={item} - modelType={UserNotificationRule} - /> - ); - }, - }, - { - field: { - notifyAfterMinutes: true, - }, - title: 'Notify After', - type: FieldType.Text, - getElement: ( - item: UserNotificationRule - ): ReactElement => { - return ( - <div> - {item['notifyAfterMinutes'] === 0 && ( - <p>Immediately</p> - )} - {(item['notifyAfterMinutes'] as number) > - 0 && ( - <p> - { - item[ - 'notifyAfterMinutes' - ] as number - }{' '} - minutes - </p> - )} - </div> - ); - }, - }, - ]} - /> - ); - }; + return Promise.resolve(model); + }} + sortOrder={SortOrder.Ascending} + sortBy="notifyAfterMinutes" + createVerb={"Add"} + id="notification-rules" + name={`User Settings > Notification Rules > ${ + options.incidentSeverity?.name || options.ruleType + }`} + isDeleteable={true} + isEditable={false} + isCreateable={true} + cardProps={{ + title: options.title, + description: options.description, + }} + noItemsMessage={ + "No notification rules found. Please add one to receive notifications." + } + formFields={[ + { + overrideField: { + notificationMethod: true, + }, + showEvenIfPermissionDoesNotExist: true, + overrideFieldKey: "notificationMethod", + title: "Notification Method", + description: "How do you want to be notified?", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Notification Method", + dropdownOptions: notificationMethodsDropdownOptions, + }, + { + field: { + notifyAfterMinutes: true, + }, + title: "Notify me after", + fieldType: FormFieldSchemaType.Dropdown, + required: true, + placeholder: "Immediately", + dropdownOptions: NotifyAfterDropdownOptions, + }, + ]} + showRefreshButton={true} + selectMoreFields={{ + userEmail: { + email: true, + }, + userSms: { + phone: true, + }, + }} + filters={[]} + columns={[ + { + field: { + userCall: { + phone: true, + }, + }, + title: "Notification Method", + type: FieldType.Text, + getElement: (item: UserNotificationRule): ReactElement => { + return ( + <NotificationMethodView + item={item} + modelType={UserNotificationRule} + /> + ); + }, + }, + { + field: { + notifyAfterMinutes: true, + }, + title: "Notify After", + type: FieldType.Text, + getElement: (item: UserNotificationRule): ReactElement => { + return ( + <div> + {item["notifyAfterMinutes"] === 0 && <p>Immediately</p>} + {(item["notifyAfterMinutes"] as number) > 0 && ( + <p>{item["notifyAfterMinutes"] as number} minutes</p> + )} + </div> + ); + }, + }, + ]} + /> + ); + }; - const init: PromiseVoidFunction = async (): Promise<void> => { - // Ping an API here. - setError(''); - setIsLoading(true); + const init: PromiseVoidFunction = async (): Promise<void> => { + // Ping an API here. + setError(""); + setIsLoading(true); - try { - const incidentSeverities: ListResult<IncidentSeverity> = - await ModelAPI.getList({ - modelType: IncidentSeverity, - query: { - projectId: DashboardNavigation.getProjectId(), - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - }, - sort: {}, - }); + try { + const incidentSeverities: ListResult<IncidentSeverity> = + await ModelAPI.getList({ + modelType: IncidentSeverity, + query: { + projectId: DashboardNavigation.getProjectId(), + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + }, + sort: {}, + }); - const userEmails: ListResult<UserEmail> = await ModelAPI.getList({ - modelType: UserEmail, - query: { - projectId: DashboardNavigation.getProjectId(), - userId: User.getUserId(), - isVerified: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - email: true, - }, - sort: {}, - }); + const userEmails: ListResult<UserEmail> = await ModelAPI.getList({ + modelType: UserEmail, + query: { + projectId: DashboardNavigation.getProjectId(), + userId: User.getUserId(), + isVerified: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + email: true, + }, + sort: {}, + }); - setUserEmails(userEmails.data); + setUserEmails(userEmails.data); - const userSMSes: ListResult<UserSMS> = await ModelAPI.getList({ - modelType: UserSMS, - query: { - projectId: DashboardNavigation.getProjectId(), - userId: User.getUserId(), - isVerified: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - phone: true, - }, - sort: {}, - }); + const userSMSes: ListResult<UserSMS> = await ModelAPI.getList({ + modelType: UserSMS, + query: { + projectId: DashboardNavigation.getProjectId(), + userId: User.getUserId(), + isVerified: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + phone: true, + }, + sort: {}, + }); - setUserSMSs(userSMSes.data); + setUserSMSs(userSMSes.data); - const userCalls: ListResult<UserCall> = await ModelAPI.getList({ - modelType: UserCall, - query: { - projectId: DashboardNavigation.getProjectId(), - userId: User.getUserId(), - isVerified: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - phone: true, - }, - sort: {}, - }); + const userCalls: ListResult<UserCall> = await ModelAPI.getList({ + modelType: UserCall, + query: { + projectId: DashboardNavigation.getProjectId(), + userId: User.getUserId(), + isVerified: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + phone: true, + }, + sort: {}, + }); - setUserCalls(userCalls.data); + setUserCalls(userCalls.data); - setIncidentSeverities(incidentSeverities.data); + setIncidentSeverities(incidentSeverities.data); - const dropdownOptions: Array<DropdownOption> = [ - ...userCalls.data, - ...userEmails.data, - ...userSMSes.data, - ].map((model: BaseModel) => { - const isUserCall: boolean = model instanceof UserCall; - const isUserSms: boolean = model instanceof UserSMS; + const dropdownOptions: Array<DropdownOption> = [ + ...userCalls.data, + ...userEmails.data, + ...userSMSes.data, + ].map((model: BaseModel) => { + const isUserCall: boolean = model instanceof UserCall; + const isUserSms: boolean = model instanceof UserSMS; - const option: DropdownOption = { - label: model.getColumnValue('phone') - ? (model.getColumnValue('phone')?.toString() as string) - : (model.getColumnValue('email')?.toString() as string), - value: model.id!.toString(), - }; + const option: DropdownOption = { + label: model.getColumnValue("phone") + ? (model.getColumnValue("phone")?.toString() as string) + : (model.getColumnValue("email")?.toString() as string), + value: model.id!.toString(), + }; - if (isUserCall) { - option.label = 'Call: ' + option.label; - } else if (isUserSms) { - option.label = 'SMS: ' + option.label; - } else { - option.label = 'Email: ' + option.label; - } - - return option; - }); - - setNotificationMethodsDropdownOptions(dropdownOptions); - } catch (err) { - setError(API.getFriendlyMessage(err)); + if (isUserCall) { + option.label = "Call: " + option.label; + } else if (isUserSms) { + option.label = "SMS: " + option.label; + } else { + option.label = "Email: " + option.label; } - setIsLoading(false); - }; + return option; + }); - useEffect(() => { - init().catch((err: Error) => { - setError(err.toString()); - }); - }, []); - - if (isLoading) { - return <PageLoader isVisible={true} />; + setNotificationMethodsDropdownOptions(dropdownOptions); + } catch (err) { + setError(API.getFriendlyMessage(err)); } - if (error) { - return <ErrorMessage error={error} />; - } + setIsLoading(false); + }; - return ( - <Fragment> - <div> - {incidentSeverities.map( - (incidentSeverity: IncidentSeverity, i: number) => { - return ( - <div key={i}> - {getModelTable({ - incidentSeverity: incidentSeverity, - ruleType: - NotificationRuleType.ON_CALL_INCIDENT_CREATED, - title: - 'When I am on call and ' + - incidentSeverity.name + - ' is assigned to me...', - description: - 'Here are the rules when you are on call and ' + - incidentSeverity.name + - ' is assigned to you.', - })} - </div> - ); - } - )} - </div> + useEffect(() => { + init().catch((err: Error) => { + setError(err.toString()); + }); + }, []); - {/* <div> + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <Fragment> + <div> + {incidentSeverities.map( + (incidentSeverity: IncidentSeverity, i: number) => { + return ( + <div key={i}> + {getModelTable({ + incidentSeverity: incidentSeverity, + ruleType: NotificationRuleType.ON_CALL_INCIDENT_CREATED, + title: + "When I am on call and " + + incidentSeverity.name + + " is assigned to me...", + description: + "Here are the rules when you are on call and " + + incidentSeverity.name + + " is assigned to you.", + })} + </div> + ); + }, + )} + </div> + + {/* <div> {getModelTable({ incidentSeverity: undefined, ruleType: NotificationRuleType.WHEN_USER_GOES_ON_CALL, @@ -399,8 +379,8 @@ const Settings: FunctionComponent<PageComponentProps> = ( 'Here are the rules to notify you when you go off call.', })} </div> */} - </Fragment> - ); + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/UserSettings/SideMenu.tsx b/Dashboard/src/Pages/UserSettings/SideMenu.tsx index b34535937e..7aecc9fcc5 100644 --- a/Dashboard/src/Pages/UserSettings/SideMenu.tsx +++ b/Dashboard/src/Pages/UserSettings/SideMenu.tsx @@ -1,82 +1,74 @@ -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 Link from 'Common/Types/Link'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { ReactElement } from 'react'; +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 Link from "Common/Types/Link"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { ReactElement } from "react"; const DashboardSideMenu: () => ReactElement = (): ReactElement => { - let subItemMenuLink: Link | undefined = undefined; + let subItemMenuLink: Link | undefined = undefined; - if ( - Navigation.isOnThisPage( - RouteMap[PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE]! - ) - ) { - subItemMenuLink = { - title: 'Timeline', - to: Navigation.getCurrentRoute(), - }; - } + if ( + Navigation.isOnThisPage( + RouteMap[PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE]!, + ) + ) { + subItemMenuLink = { + title: "Timeline", + to: Navigation.getCurrentRoute(), + }; + } - return ( - <SideMenu> - <SideMenuSection title="Alerts & Notifications"> - <SideMenuItem - link={{ - title: 'Notification Methods', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.USER_SETTINGS_NOTIFICATION_METHODS - ] as Route - ), - }} - icon={IconProp.Bell} - /> - <SideMenuItem - link={{ - title: 'Notification Settings', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS - ] as Route - ), - }} - icon={IconProp.Settings} - /> - <SideMenuItem - link={{ - title: 'On-Call Rules', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.USER_SETTINGS_ON_CALL_RULES - ] as Route - ), - }} - icon={IconProp.Call} - /> - </SideMenuSection> - <SideMenuSection title="Logs"> - <SideMenuItem - link={{ - title: 'On-Call Logs', - to: RouteUtil.populateRouteParams( - RouteMap[ - PageMap.USER_SETTINGS_ON_CALL_LOGS - ] as Route - ), - }} - icon={IconProp.Logs} - subItemIcon={IconProp.Clock} - subItemLink={subItemMenuLink} - /> - </SideMenuSection> - </SideMenu> - ); + return ( + <SideMenu> + <SideMenuSection title="Alerts & Notifications"> + <SideMenuItem + link={{ + title: "Notification Methods", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_SETTINGS_NOTIFICATION_METHODS] as Route, + ), + }} + icon={IconProp.Bell} + /> + <SideMenuItem + link={{ + title: "Notification Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS] as Route, + ), + }} + icon={IconProp.Settings} + /> + <SideMenuItem + link={{ + title: "On-Call Rules", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_SETTINGS_ON_CALL_RULES] as Route, + ), + }} + icon={IconProp.Call} + /> + </SideMenuSection> + <SideMenuSection title="Logs"> + <SideMenuItem + link={{ + title: "On-Call Logs", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.USER_SETTINGS_ON_CALL_LOGS] as Route, + ), + }} + icon={IconProp.Logs} + subItemIcon={IconProp.Clock} + subItemLink={subItemMenuLink} + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Workflow/Layout.tsx b/Dashboard/src/Pages/Workflow/Layout.tsx index eaa877ea43..6cc304777d 100644 --- a/Dashboard/src/Pages/Workflow/Layout.tsx +++ b/Dashboard/src/Pages/Workflow/Layout.tsx @@ -1,25 +1,25 @@ -import { getWorkflowsBreadcrumbs } from '../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../Utils/RouteMap'; -import PageComponentProps from '../PageComponentProps'; -import WorkflowSideMenu from './SideMenu'; -import Page from 'CommonUI/src/Components/Page/Page'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; +import { getWorkflowsBreadcrumbs } from "../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../Utils/RouteMap"; +import PageComponentProps from "../PageComponentProps"; +import WorkflowSideMenu from "./SideMenu"; +import Page from "CommonUI/src/Components/Page/Page"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet } from "react-router-dom"; const WorkflowsLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <Page - title={'Workflows'} - breadcrumbLinks={getWorkflowsBreadcrumbs(path)} - sideMenu={<WorkflowSideMenu />} - > - <Outlet /> - </Page> - ); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <Page + title={"Workflows"} + breadcrumbLinks={getWorkflowsBreadcrumbs(path)} + sideMenu={<WorkflowSideMenu />} + > + <Outlet /> + </Page> + ); }; export default WorkflowsLayout; diff --git a/Dashboard/src/Pages/Workflow/Logs.tsx b/Dashboard/src/Pages/Workflow/Logs.tsx index 0dc81c6b05..5809b65157 100644 --- a/Dashboard/src/Pages/Workflow/Logs.tsx +++ b/Dashboard/src/Pages/Workflow/Logs.tsx @@ -1,202 +1,190 @@ -import WorkflowElement from '../../Components/Workflow/WorkflowElement'; -import PageComponentProps from '../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import WorkflowStatus from 'Common/Types/Workflow/WorkflowStatus'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Modal, { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import SimpleLogViewer from 'CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import WorkflowStatusElement from 'CommonUI/src/Components/Workflow/WorkflowStatus'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import WorkflowLog from 'Model/Models/WorkflowLog'; +import WorkflowElement from "../../Components/Workflow/WorkflowElement"; +import PageComponentProps from "../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import WorkflowStatus from "Common/Types/Workflow/WorkflowStatus"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Modal, { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import SimpleLogViewer from "CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import WorkflowStatusElement from "CommonUI/src/Components/Workflow/WorkflowStatus"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import WorkflowLog from "Model/Models/WorkflowLog"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const Workflows: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); - const [logs, setLogs] = useState<string>(''); + const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); + const [logs, setLogs] = useState<string>(""); - return ( - <Fragment> - <> - <ModelTable<WorkflowLog> - modelType={WorkflowLog} - id="workflow-logs-table" - isDeleteable={false} - actionButtons={[ - { - title: 'View Logs', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.List, - onClick: async ( - item: WorkflowLog, - onCompleteAction: VoidFunction - ) => { - setLogs(item['logs'] as string); - setShowViewLogsModal(true); + return ( + <Fragment> + <> + <ModelTable<WorkflowLog> + modelType={WorkflowLog} + id="workflow-logs-table" + isDeleteable={false} + actionButtons={[ + { + title: "View Logs", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.List, + onClick: async ( + item: WorkflowLog, + onCompleteAction: VoidFunction, + ) => { + setLogs(item["logs"] as string); + setShowViewLogsModal(true); - onCompleteAction(); - }, - }, - ]} - isEditable={false} - isCreateable={false} - name="Workflow Logs" - isViewable={false} - selectMoreFields={{ - logs: true, - }} - cardProps={{ - title: 'Workflow Logs', - description: - 'List of logs in the last 30 days for all your workflows', - }} - noItemsMessage={ - 'Looks like no workflow ran so far in the last 30 days.' - } - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - workflow: { - name: true, - }, - }, - title: 'Workflow Name', - type: FieldType.Text, - }, - { - field: { - workflowStatus: true, - }, - title: 'Workflow Status', - type: FieldType.Dropdown, - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - WorkflowStatus - ), - }, - { - field: { - createdAt: true, - }, - title: 'Scheduled At', - type: FieldType.Date, - }, - { - field: { - startedAt: true, - }, - title: 'Started At', - type: FieldType.Date, - }, - { - field: { - completedAt: true, - }, - title: 'Completed At', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - workflow: { - name: true, - }, - }, - title: 'Workflow Name', - type: FieldType.Text, + onCompleteAction(); + }, + }, + ]} + isEditable={false} + isCreateable={false} + name="Workflow Logs" + isViewable={false} + selectMoreFields={{ + logs: true, + }} + cardProps={{ + title: "Workflow Logs", + description: + "List of logs in the last 30 days for all your workflows", + }} + noItemsMessage={ + "Looks like no workflow ran so far in the last 30 days." + } + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + workflow: { + name: true, + }, + }, + title: "Workflow Name", + type: FieldType.Text, + }, + { + field: { + workflowStatus: true, + }, + title: "Workflow Status", + type: FieldType.Dropdown, + filterDropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(WorkflowStatus), + }, + { + field: { + createdAt: true, + }, + title: "Scheduled At", + type: FieldType.Date, + }, + { + field: { + startedAt: true, + }, + title: "Started At", + type: FieldType.Date, + }, + { + field: { + completedAt: true, + }, + title: "Completed At", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + workflow: { + name: true, + }, + }, + title: "Workflow Name", + type: FieldType.Text, - getElement: (item: WorkflowLog): ReactElement => { - return ( - <WorkflowElement - workflow={item.workflow!} - /> - ); - }, - }, - { - field: { - workflowStatus: true, - }, + getElement: (item: WorkflowLog): ReactElement => { + return <WorkflowElement workflow={item.workflow!} />; + }, + }, + { + field: { + workflowStatus: true, + }, - title: 'Workflow Status', - type: FieldType.Text, - getElement: (item: WorkflowLog): ReactElement => { - if (!item['workflowStatus']) { - throw new BadDataException( - 'Workflow Status not found' - ); - } + title: "Workflow Status", + type: FieldType.Text, + getElement: (item: WorkflowLog): ReactElement => { + if (!item["workflowStatus"]) { + throw new BadDataException("Workflow Status not found"); + } - return ( - <WorkflowStatusElement - status={ - item[ - 'workflowStatus' - ] as WorkflowStatus - } - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Scheduled At', - type: FieldType.DateTime, - }, - { - field: { - startedAt: true, - }, - title: 'Started At', - type: FieldType.DateTime, - }, - { - field: { - completedAt: true, - }, - title: 'Completed At', - type: FieldType.DateTime, - }, - ]} - /> + return ( + <WorkflowStatusElement + status={item["workflowStatus"] as WorkflowStatus} + /> + ); + }, + }, + { + field: { + createdAt: true, + }, + title: "Scheduled At", + type: FieldType.DateTime, + }, + { + field: { + startedAt: true, + }, + title: "Started At", + type: FieldType.DateTime, + }, + { + field: { + completedAt: true, + }, + title: "Completed At", + type: FieldType.DateTime, + }, + ]} + /> - {showViewLogsModal && ( - <Modal - title={'Workflow Logs'} - description="Here are the logs for this workflow" - isLoading={false} - modalWidth={ModalWidth.Large} - onSubmit={() => { - setShowViewLogsModal(false); - }} - submitButtonText={'Close'} - submitButtonStyleType={ButtonStyleType.NORMAL} - > - <SimpleLogViewer> - {logs.split('\n').map((log: string, i: number) => { - return <div key={i}>{log}</div>; - })} - </SimpleLogViewer> - </Modal> - )} - </> - </Fragment> - ); + {showViewLogsModal && ( + <Modal + title={"Workflow Logs"} + description="Here are the logs for this workflow" + isLoading={false} + modalWidth={ModalWidth.Large} + onSubmit={() => { + setShowViewLogsModal(false); + }} + submitButtonText={"Close"} + submitButtonStyleType={ButtonStyleType.NORMAL} + > + <SimpleLogViewer> + {logs.split("\n").map((log: string, i: number) => { + return <div key={i}>{log}</div>; + })} + </SimpleLogViewer> + </Modal> + )} + </> + </Fragment> + ); }; export default Workflows; diff --git a/Dashboard/src/Pages/Workflow/SideMenu.tsx b/Dashboard/src/Pages/Workflow/SideMenu.tsx index 7824e823fe..840abc7184 100644 --- a/Dashboard/src/Pages/Workflow/SideMenu.tsx +++ b/Dashboard/src/Pages/Workflow/SideMenu.tsx @@ -1,44 +1,44 @@ -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 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import React, { ReactElement } from 'react'; +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 "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import React, { ReactElement } from "react"; const DashboardSideMenu: () => ReactElement = (): ReactElement => { - return ( - <SideMenu> - <SideMenuItem - link={{ - title: 'Workflows', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOWS] as Route - ), - }} - icon={IconProp.Workflow} - /> - <SideMenuItem - link={{ - title: 'Global Variables', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOWS_VARIABLES] as Route - ), - }} - icon={IconProp.Variable} - /> + return ( + <SideMenu> + <SideMenuItem + link={{ + title: "Workflows", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOWS] as Route, + ), + }} + icon={IconProp.Workflow} + /> + <SideMenuItem + link={{ + title: "Global Variables", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOWS_VARIABLES] as Route, + ), + }} + icon={IconProp.Variable} + /> - <SideMenuItem - link={{ - title: 'Runs & Logs', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOWS_LOGS] as Route - ), - }} - icon={IconProp.Logs} - /> - </SideMenu> - ); + <SideMenuItem + link={{ + title: "Runs & Logs", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOWS_LOGS] as Route, + ), + }} + icon={IconProp.Logs} + /> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Workflow/Variable.tsx b/Dashboard/src/Pages/Workflow/Variable.tsx index 132a0cd5dc..0aee5d64ef 100644 --- a/Dashboard/src/Pages/Workflow/Variable.tsx +++ b/Dashboard/src/Pages/Workflow/Variable.tsx @@ -1,144 +1,144 @@ -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import WorkflowVariable from 'Model/Models/WorkflowVariable'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import WorkflowVariable from "Model/Models/WorkflowVariable"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Workflows: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Fragment> - <ModelTable<WorkflowVariable> - modelType={WorkflowVariable} - id="status-page-table" - isDeleteable={true} - isEditable={false} - isCreateable={true} - name="Workflows" - isViewable={false} - cardProps={{ - title: 'Global Variables', - description: - 'Here is a list of global secrets and variables for this project.', - }} - query={{ - workflowId: null, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - noItemsMessage={'No global variables found.'} - showViewIdButton={true} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Workflow Name', - validation: { - minLength: 2, - noSpaces: true, - noSpecialCharacters: true, - noNumbers: true, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - isSecret: true, - }, - title: 'Secret', - description: - 'Is this variable secret or secure? Should this be encrypted in the Database?', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - { - field: { - content: true, - }, - title: 'Content', - description: 'Enter the content of the variable', - fieldType: FormFieldSchemaType.LongText, - required: true, - }, - ]} - showRefreshButton={true} - filters={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - isSecret: true, - }, - title: 'Secret', - type: FieldType.Boolean, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - isSecret: true, - }, - title: 'Secret', - type: FieldType.Boolean, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<WorkflowVariable> + modelType={WorkflowVariable} + id="status-page-table" + isDeleteable={true} + isEditable={false} + isCreateable={true} + name="Workflows" + isViewable={false} + cardProps={{ + title: "Global Variables", + description: + "Here is a list of global secrets and variables for this project.", + }} + query={{ + workflowId: null, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + noItemsMessage={"No global variables found."} + showViewIdButton={true} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Workflow Name", + validation: { + minLength: 2, + noSpaces: true, + noSpecialCharacters: true, + noNumbers: true, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + isSecret: true, + }, + title: "Secret", + description: + "Is this variable secret or secure? Should this be encrypted in the Database?", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + { + field: { + content: true, + }, + title: "Content", + description: "Enter the content of the variable", + fieldType: FormFieldSchemaType.LongText, + required: true, + }, + ]} + showRefreshButton={true} + filters={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + isSecret: true, + }, + title: "Secret", + type: FieldType.Boolean, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + isSecret: true, + }, + title: "Secret", + type: FieldType.Boolean, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default Workflows; diff --git a/Dashboard/src/Pages/Workflow/View/Builder.tsx b/Dashboard/src/Pages/Workflow/View/Builder.tsx index 7d1b037c47..4a83bf3a82 100644 --- a/Dashboard/src/Pages/Workflow/View/Builder.tsx +++ b/Dashboard/src/Pages/Workflow/View/Builder.tsx @@ -1,382 +1,362 @@ -import PageComponentProps from '../../PageComponentProps'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; +import PageComponentProps from "../../PageComponentProps"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; import ComponentMetadata, { - ComponentCategory, - ComponentType, - NodeDataProp, - NodeType, -} from 'Common/Types/Workflow/Component'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Card from 'CommonUI/src/Components/Card/Card'; -import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader'; -import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal'; -import { loadComponentsAndCategories } from 'CommonUI/src/Components/Workflow/Utils'; + ComponentCategory, + ComponentType, + NodeDataProp, + NodeType, +} from "Common/Types/Workflow/Component"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Card from "CommonUI/src/Components/Card/Card"; +import ComponentLoader from "CommonUI/src/Components/ComponentLoader/ComponentLoader"; +import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal"; +import { loadComponentsAndCategories } from "CommonUI/src/Components/Workflow/Utils"; import Workflow, { - getEdgeDefaultProps, - getPlaceholderTriggerNode, -} from 'CommonUI/src/Components/Workflow/Workflow'; -import { WORKFLOW_URL } from 'CommonUI/src/Config'; -import API from 'CommonUI/src/Utils/API/API'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import WorkflowModel from 'Model/Models/Workflow'; + getEdgeDefaultProps, + getPlaceholderTriggerNode, +} from "CommonUI/src/Components/Workflow/Workflow"; +import { WORKFLOW_URL } from "CommonUI/src/Config"; +import API from "CommonUI/src/Utils/API/API"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import WorkflowModel from "Model/Models/Workflow"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; -import { Edge, Node } from 'reactflow'; -import { useAsyncEffect } from 'use-async-effect'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; +import { Edge, Node } from "reactflow"; +import { useAsyncEffect } from "use-async-effect"; const Delete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [saveStatus, setSaveStatus] = useState<string>(''); - const [saveTimeout, setSaveTimeout] = useState<ReturnType< - typeof setTimeout - > | null>(null); - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [nodes, setNodes] = useState<Array<Node>>([]); - const [edges, setEdges] = useState<Array<Edge>>([]); - const [error, setError] = useState<string>(''); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [saveStatus, setSaveStatus] = useState<string>(""); + const [saveTimeout, setSaveTimeout] = useState<ReturnType< + typeof setTimeout + > | null>(null); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const [nodes, setNodes] = useState<Array<Node>>([]); + const [edges, setEdges] = useState<Array<Edge>>([]); + const [error, setError] = useState<string>(""); - const [showRunSuccessConfirmation, setShowRunSuccessConfirmation] = - useState<boolean>(false); + const [showRunSuccessConfirmation, setShowRunSuccessConfirmation] = + useState<boolean>(false); - const [showComponentPickerModal, setShowComponentPickerModal] = - useState<boolean>(false); + const [showComponentPickerModal, setShowComponentPickerModal] = + useState<boolean>(false); - const [showRunModal, setShowRunModal] = useState<boolean>(false); + const [showRunModal, setShowRunModal] = useState<boolean>(false); - const loadGraph: PromiseVoidFunction = async (): Promise<void> => { - try { - setIsLoading(true); - const workflow: WorkflowModel | null = await ModelAPI.getItem({ - modelType: WorkflowModel, - id: modelId, - select: { - graph: true, - }, - requestOptions: {}, - }); + const loadGraph: PromiseVoidFunction = async (): Promise<void> => { + try { + setIsLoading(true); + const workflow: WorkflowModel | null = await ModelAPI.getItem({ + modelType: WorkflowModel, + id: modelId, + select: { + graph: true, + }, + requestOptions: {}, + }); - if (workflow) { - const allComponents: { - components: Array<ComponentMetadata>; - categories: Array<ComponentCategory>; - } = loadComponentsAndCategories(); + if (workflow) { + const allComponents: { + components: Array<ComponentMetadata>; + categories: Array<ComponentCategory>; + } = loadComponentsAndCategories(); - if (workflow.graph && (workflow.graph as JSONObject)['nodes']) { - if ( - ((workflow.graph as any)['nodes'] as Array<Node>) - .length === 0 - ) { - // add a placeholder trigger node. - setNodes([getPlaceholderTriggerNode()]); - } else { - let nodes: Array<Node> = (workflow.graph as any)[ - 'nodes' - ] as Array<Node>; + if (workflow.graph && (workflow.graph as JSONObject)["nodes"]) { + if (((workflow.graph as any)["nodes"] as Array<Node>).length === 0) { + // add a placeholder trigger node. + setNodes([getPlaceholderTriggerNode()]); + } else { + let nodes: Array<Node> = (workflow.graph as any)[ + "nodes" + ] as Array<Node>; - // Fill nodes. + // Fill nodes. - for (let i: number = 0; i < nodes.length; i++) { - if (!nodes[i]) { - continue; - } + for (let i: number = 0; i < nodes.length; i++) { + if (!nodes[i]) { + continue; + } - if ( - nodes[i]?.data.nodeType === - NodeType.PlaceholderNode - ) { - nodes[i] = { - ...nodes[i], - ...getPlaceholderTriggerNode(), - }; - continue; - } + if (nodes[i]?.data.nodeType === NodeType.PlaceholderNode) { + nodes[i] = { + ...nodes[i], + ...getPlaceholderTriggerNode(), + }; + continue; + } - let componentMetdata: - | ComponentMetadata - | undefined = undefined; + let componentMetdata: ComponentMetadata | undefined = undefined; - for (const component of allComponents.components) { - if ( - component.id === nodes[i]?.data.metadataId - ) { - componentMetdata = component; - } - } - - if (!componentMetdata) { - throw new BadDataException( - 'Component Metadata not found for node ' + - nodes[i]?.data.metadataId - ); - } - - nodes[i]!.data.metadata = { - ...componentMetdata, - }; - } - - // see if it has the trigger node. - - if ( - !nodes.find((node: Node) => { - return ( - node.data.nodeType === - NodeType.PlaceholderNode || - node.data.componentType === - ComponentType.Trigger - ); - }) - ) { - nodes = [...nodes, getPlaceholderTriggerNode()]; - } - - setNodes(nodes); - } - } else { - // add a placeholder trigger node. - setNodes([getPlaceholderTriggerNode()]); + for (const component of allComponents.components) { + if (component.id === nodes[i]?.data.metadataId) { + componentMetdata = component; } + } - if (workflow.graph && (workflow.graph as JSONObject)['edges']) { - const edges: Array<Edge> = (workflow.graph as any)[ - 'edges' - ] as Array<Edge>; + if (!componentMetdata) { + throw new BadDataException( + "Component Metadata not found for node " + + nodes[i]?.data.metadataId, + ); + } - for (let i: number = 0; i < edges.length; i++) { - if (!edges[i]) { - continue; - } - - edges[i] = { - ...edges[i], - ...getEdgeDefaultProps(false), - } as Edge; - } - - setEdges(edges); - } else { - setEdges([]); - } - } else { - setError('Workflow not found'); + nodes[i]!.data.metadata = { + ...componentMetdata, + }; } - } catch (err) { - setError(API.getFriendlyMessage(err)); + + // see if it has the trigger node. + + if ( + !nodes.find((node: Node) => { + return ( + node.data.nodeType === NodeType.PlaceholderNode || + node.data.componentType === ComponentType.Trigger + ); + }) + ) { + nodes = [...nodes, getPlaceholderTriggerNode()]; + } + + setNodes(nodes); + } + } else { + // add a placeholder trigger node. + setNodes([getPlaceholderTriggerNode()]); } - setIsLoading(false); - }; + if (workflow.graph && (workflow.graph as JSONObject)["edges"]) { + const edges: Array<Edge> = (workflow.graph as any)[ + "edges" + ] as Array<Edge>; - type SaveGraphFunction = ( - nodes: Array<Node>, - edges: Array<Edge> - ) => Promise<void>; + for (let i: number = 0; i < edges.length; i++) { + if (!edges[i]) { + continue; + } - const saveGraph: SaveGraphFunction = async ( - nodes: Array<Node>, - edges: Array<Edge> - ): Promise<void> => { - setSaveStatus('Saving...'); + edges[i] = { + ...edges[i], + ...getEdgeDefaultProps(false), + } as Edge; + } + + setEdges(edges); + } else { + setEdges([]); + } + } else { + setError("Workflow not found"); + } + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + type SaveGraphFunction = ( + nodes: Array<Node>, + edges: Array<Edge>, + ) => Promise<void>; + + const saveGraph: SaveGraphFunction = async ( + nodes: Array<Node>, + edges: Array<Edge>, + ): Promise<void> => { + setSaveStatus("Saving..."); + + if (saveTimeout) { + clearTimeout(saveTimeout); + setSaveTimeout(null); + } + + setSaveTimeout( + setTimeout(async () => { + try { + const graph: any = JSONFunctions.parse( + JSON.stringify({ nodes, edges }), + ); // deep copy + + // clean up. + + if (graph["nodes"]) { + for ( + let i: number = 0; + i < (graph["nodes"] as Array<Node>).length; + i++ + ) { + (graph["nodes"] as Array<Node>)[i] = { + ...((graph["nodes"] as Array<Node>)[i] as Node), + }; + + delete ((graph["nodes"] as Array<Node>)[i] as Node).data.metadata; + } + } + + if (graph["edges"]) { + for ( + let i: number = 0; + i < (graph["edges"] as Array<Edge>).length; + i++ + ) { + (graph["edges"] as Array<Edge>)[i] = { + ...((graph["edges"] as Array<Edge>)[i] as Edge), + }; + + delete ((graph["edges"] as Array<Edge>)[i] as Edge).type; + delete ((graph["edges"] as Array<Edge>)[i] as Edge).style; + delete ((graph["edges"] as Array<Edge>)[i] as Edge).markerEnd; + } + } + + await ModelAPI.updateById({ + modelType: WorkflowModel, + id: modelId, + data: { + graph, + }, + }); + + setSaveStatus("Changes Saved."); + } catch (err) { + setError(API.getFriendlyMessage(err)); + + setSaveStatus("Save Error."); + } if (saveTimeout) { - clearTimeout(saveTimeout); - setSaveTimeout(null); + clearTimeout(saveTimeout); + setSaveTimeout(null); } - - setSaveTimeout( - setTimeout(async () => { - try { - const graph: any = JSONFunctions.parse( - JSON.stringify({ nodes, edges }) - ); // deep copy - - // clean up. - - if (graph['nodes']) { - for ( - let i: number = 0; - i < (graph['nodes'] as Array<Node>).length; - i++ - ) { - (graph['nodes'] as Array<Node>)[i] = { - ...((graph['nodes'] as Array<Node>)[i] as Node), - }; - - delete ((graph['nodes'] as Array<Node>)[i] as Node) - .data.metadata; - } - } - - if (graph['edges']) { - for ( - let i: number = 0; - i < (graph['edges'] as Array<Edge>).length; - i++ - ) { - (graph['edges'] as Array<Edge>)[i] = { - ...((graph['edges'] as Array<Edge>)[i] as Edge), - }; - - delete ((graph['edges'] as Array<Edge>)[i] as Edge) - .type; - delete ((graph['edges'] as Array<Edge>)[i] as Edge) - .style; - delete ((graph['edges'] as Array<Edge>)[i] as Edge) - .markerEnd; - } - } - - await ModelAPI.updateById({ - modelType: WorkflowModel, - id: modelId, - data: { - graph, - }, - }); - - setSaveStatus('Changes Saved.'); - } catch (err) { - setError(API.getFriendlyMessage(err)); - - setSaveStatus('Save Error.'); - } - - if (saveTimeout) { - clearTimeout(saveTimeout); - setSaveTimeout(null); - } - }, 1000) - ); - }; - - useAsyncEffect(async () => { - await loadGraph(); - }, []); - - return ( - <Fragment> - <> - <Banner - openInNewTab={true} - title="Need help with building workflows?" - description="Watch this 10 minute video which will help you connect Slack with OneUptime using workflows" - link={URL.fromString('https://youtu.be/k1-reCQTZnM')} - /> - <Card - title={'Workflow Builder'} - description={'Workflow builder for OneUptime'} - rightElement={ - <div className="flex"> - <p className="text-sm text-gray-400 mr-3 mt-2"> - {saveStatus} - </p> - <div> - <Button - title="Add Component" - icon={IconProp.Add} - onClick={() => { - setShowComponentPickerModal(true); - }} - /> - </div> - <div> - <Button - title="Run Workflow Manually" - icon={IconProp.Play} - onClick={() => { - setShowRunModal(true); - }} - /> - </div> - </div> - } - > - {isLoading ? <ComponentLoader /> : <></>} - - {!isLoading ? ( - <Workflow - workflowId={modelId} - showComponentsPickerModal={showComponentPickerModal} - onComponentPickerModalUpdate={(value: boolean) => { - setShowComponentPickerModal(value); - }} - initialNodes={nodes} - onRunModalUpdate={(value: boolean) => { - setShowRunModal(value); - }} - showRunModal={showRunModal} - initialEdges={edges} - onWorkflowUpdated={async ( - nodes: Array<Node>, - edges: Array<Edge> - ) => { - setNodes(nodes); - setEdges(edges); - await saveGraph(nodes, edges); - }} - onRun={async (component: NodeDataProp) => { - try { - await API.post( - URL.fromString( - WORKFLOW_URL.toString() - ).addRoute( - '/manual/run/' + modelId.toString() - ), - { - data: component.returnValues, - } - ); - - setShowRunSuccessConfirmation(true); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } - }} - /> - ) : ( - <></> - )} - </Card> - {error && ( - <ConfirmModal - title={`Error`} - description={`${error}`} - submitButtonText={'Close'} - onSubmit={() => { - setError(''); - }} - submitButtonType={ButtonStyleType.NORMAL} - /> - )} - - {showRunSuccessConfirmation && ( - <ConfirmModal - title={`Workflow scheduled to execute`} - description={`This workflow is scheduled to execute soon. You can see the status of the run in the Runs and Logs section.`} - submitButtonText={'Close'} - onSubmit={() => { - setShowRunSuccessConfirmation(false); - }} - submitButtonType={ButtonStyleType.NORMAL} - /> - )} - </> - </Fragment> + }, 1000), ); + }; + + useAsyncEffect(async () => { + await loadGraph(); + }, []); + + return ( + <Fragment> + <> + <Banner + openInNewTab={true} + title="Need help with building workflows?" + description="Watch this 10 minute video which will help you connect Slack with OneUptime using workflows" + link={URL.fromString("https://youtu.be/k1-reCQTZnM")} + /> + <Card + title={"Workflow Builder"} + description={"Workflow builder for OneUptime"} + rightElement={ + <div className="flex"> + <p className="text-sm text-gray-400 mr-3 mt-2">{saveStatus}</p> + <div> + <Button + title="Add Component" + icon={IconProp.Add} + onClick={() => { + setShowComponentPickerModal(true); + }} + /> + </div> + <div> + <Button + title="Run Workflow Manually" + icon={IconProp.Play} + onClick={() => { + setShowRunModal(true); + }} + /> + </div> + </div> + } + > + {isLoading ? <ComponentLoader /> : <></>} + + {!isLoading ? ( + <Workflow + workflowId={modelId} + showComponentsPickerModal={showComponentPickerModal} + onComponentPickerModalUpdate={(value: boolean) => { + setShowComponentPickerModal(value); + }} + initialNodes={nodes} + onRunModalUpdate={(value: boolean) => { + setShowRunModal(value); + }} + showRunModal={showRunModal} + initialEdges={edges} + onWorkflowUpdated={async ( + nodes: Array<Node>, + edges: Array<Edge>, + ) => { + setNodes(nodes); + setEdges(edges); + await saveGraph(nodes, edges); + }} + onRun={async (component: NodeDataProp) => { + try { + await API.post( + URL.fromString(WORKFLOW_URL.toString()).addRoute( + "/manual/run/" + modelId.toString(), + ), + { + data: component.returnValues, + }, + ); + + setShowRunSuccessConfirmation(true); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + }} + /> + ) : ( + <></> + )} + </Card> + {error && ( + <ConfirmModal + title={`Error`} + description={`${error}`} + submitButtonText={"Close"} + onSubmit={() => { + setError(""); + }} + submitButtonType={ButtonStyleType.NORMAL} + /> + )} + + {showRunSuccessConfirmation && ( + <ConfirmModal + title={`Workflow scheduled to execute`} + description={`This workflow is scheduled to execute soon. You can see the status of the run in the Runs and Logs section.`} + submitButtonText={"Close"} + onSubmit={() => { + setShowRunSuccessConfirmation(false); + }} + submitButtonType={ButtonStyleType.NORMAL} + /> + )} + </> + </Fragment> + ); }; export default Delete; diff --git a/Dashboard/src/Pages/Workflow/View/Delete.tsx b/Dashboard/src/Pages/Workflow/View/Delete.tsx index 1339bb8f9d..6ba2822a71 100644 --- a/Dashboard/src/Pages/Workflow/View/Delete.tsx +++ b/Dashboard/src/Pages/Workflow/View/Delete.tsx @@ -1,34 +1,34 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Workflow from 'Model/Models/Workflow'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import ModelDelete from "CommonUI/src/Components/ModelDelete/ModelDelete"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Workflow from "Model/Models/Workflow"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Delete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelDelete - modelType={Workflow} - modelId={modelId} - onDeleteSuccess={() => { - Navigation.navigate( - RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOWS] as Route, - { modelId } - ) - ); - }} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelDelete + modelType={Workflow} + modelId={modelId} + onDeleteSuccess={() => { + Navigation.navigate( + RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOWS] as Route, + { modelId }, + ), + ); + }} + /> + </Fragment> + ); }; export default Delete; diff --git a/Dashboard/src/Pages/Workflow/View/Index.tsx b/Dashboard/src/Pages/Workflow/View/Index.tsx index 62d077b580..58a9bf19d6 100644 --- a/Dashboard/src/Pages/Workflow/View/Index.tsx +++ b/Dashboard/src/Pages/Workflow/View/Index.tsx @@ -1,140 +1,136 @@ -import LabelsElement from '../../../Components/Label/Labels'; -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import Workflow from 'Model/Models/Workflow'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../../Components/Label/Labels"; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import Workflow from "Model/Models/Workflow"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Delete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(0); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(0); - return ( - <Fragment> - <CardModelDetail<Workflow> - name="Workflow > Workflow Details" - cardProps={{ - title: 'Workflow Details', - description: 'Here are more details for this workflow.', - }} - isEditable={true} - formSteps={[ - { - title: 'Workflow Info', - id: 'workflow-info', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - formFields={[ - { - field: { - name: true, - }, - stepId: 'workflow-info', - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Status Page Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'workflow-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - isEnabled: true, - }, - title: 'Enabled', - stepId: 'workflow-info', - fieldType: FormFieldSchemaType.Toggle, - }, - { - field: { - labels: true, - }, - stepId: 'labels', - title: 'Labels ', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - modelDetailProps={{ - showDetailsInNumberOfColumns: 2, - modelType: Workflow, - id: 'model-detail-workflow', - fields: [ - { - field: { - _id: true, - }, - title: 'Workflow ID', - }, - { - field: { - name: true, - }, - title: 'Name', - }, - { - field: { - description: true, - }, - title: 'Description', - }, - { - field: { - isEnabled: true, - }, - title: 'Enabled', - fieldType: FieldType.Boolean, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - getElement: (item: Workflow): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - ], - modelId: modelId, - }} - /> - </Fragment> - ); + return ( + <Fragment> + <CardModelDetail<Workflow> + name="Workflow > Workflow Details" + cardProps={{ + title: "Workflow Details", + description: "Here are more details for this workflow.", + }} + isEditable={true} + formSteps={[ + { + title: "Workflow Info", + id: "workflow-info", + }, + { + title: "Labels", + id: "labels", + }, + ]} + formFields={[ + { + field: { + name: true, + }, + stepId: "workflow-info", + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Status Page Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "workflow-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + isEnabled: true, + }, + title: "Enabled", + stepId: "workflow-info", + fieldType: FormFieldSchemaType.Toggle, + }, + { + field: { + labels: true, + }, + stepId: "labels", + title: "Labels ", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + modelDetailProps={{ + showDetailsInNumberOfColumns: 2, + modelType: Workflow, + id: "model-detail-workflow", + fields: [ + { + field: { + _id: true, + }, + title: "Workflow ID", + }, + { + field: { + name: true, + }, + title: "Name", + }, + { + field: { + description: true, + }, + title: "Description", + }, + { + field: { + isEnabled: true, + }, + title: "Enabled", + fieldType: FieldType.Boolean, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + getElement: (item: Workflow): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ], + modelId: modelId, + }} + /> + </Fragment> + ); }; export default Delete; diff --git a/Dashboard/src/Pages/Workflow/View/Layout.tsx b/Dashboard/src/Pages/Workflow/View/Layout.tsx index 74399eac0a..4557cce91a 100644 --- a/Dashboard/src/Pages/Workflow/View/Layout.tsx +++ b/Dashboard/src/Pages/Workflow/View/Layout.tsx @@ -1,32 +1,32 @@ -import { getWorkflowsBreadcrumbs } from '../../../Utils/Breadcrumbs'; -import { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import SideMenu from './SideMenu'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelPage from 'CommonUI/src/Components/Page/ModelPage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Workflow from 'Model/Models/Workflow'; -import React, { FunctionComponent, ReactElement } from 'react'; -import { Outlet, useParams } from 'react-router-dom'; +import { getWorkflowsBreadcrumbs } from "../../../Utils/Breadcrumbs"; +import { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import SideMenu from "./SideMenu"; +import ObjectID from "Common/Types/ObjectID"; +import ModelPage from "CommonUI/src/Components/Page/ModelPage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Workflow from "Model/Models/Workflow"; +import React, { FunctionComponent, ReactElement } from "react"; +import { Outlet, useParams } from "react-router-dom"; const WorkflowViewLayout: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const { id } = useParams(); - const modelId: ObjectID = new ObjectID(id || ''); - const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); - return ( - <ModelPage - title="Workflow" - modelType={Workflow} - modelId={modelId} - modelNameField="name" - breadcrumbLinks={getWorkflowsBreadcrumbs(path)} - sideMenu={<SideMenu modelId={modelId} />} - > - <Outlet /> - </ModelPage> - ); + const { id } = useParams(); + const modelId: ObjectID = new ObjectID(id || ""); + const path: string = Navigation.getRoutePath(RouteUtil.getRoutes()); + return ( + <ModelPage + title="Workflow" + modelType={Workflow} + modelId={modelId} + modelNameField="name" + breadcrumbLinks={getWorkflowsBreadcrumbs(path)} + sideMenu={<SideMenu modelId={modelId} />} + > + <Outlet /> + </ModelPage> + ); }; export default WorkflowViewLayout; diff --git a/Dashboard/src/Pages/Workflow/View/Logs.tsx b/Dashboard/src/Pages/Workflow/View/Logs.tsx index 1a2abfc176..d06d8d4edb 100644 --- a/Dashboard/src/Pages/Workflow/View/Logs.tsx +++ b/Dashboard/src/Pages/Workflow/View/Logs.tsx @@ -1,198 +1,188 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import WorkflowStatus from 'Common/Types/Workflow/WorkflowStatus'; -import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button'; -import Modal, { ModalWidth } from 'CommonUI/src/Components/Modal/Modal'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import SimpleLogViewer from 'CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import WorkflowStatusElement from 'CommonUI/src/Components/Workflow/WorkflowStatus'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import WorkflowLogs from 'Model/Models/WorkflowLog'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import WorkflowStatus from "Common/Types/Workflow/WorkflowStatus"; +import { ButtonStyleType } from "CommonUI/src/Components/Button/Button"; +import Modal, { ModalWidth } from "CommonUI/src/Components/Modal/Modal"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import SimpleLogViewer from "CommonUI/src/Components/SimpleLogViewer/SimpleLogViewer"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import WorkflowStatusElement from "CommonUI/src/Components/Workflow/WorkflowStatus"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import WorkflowLogs from "Model/Models/WorkflowLog"; import React, { - Fragment, - FunctionComponent, - ReactElement, - useState, -} from 'react'; + Fragment, + FunctionComponent, + ReactElement, + useState, +} from "react"; const Delete: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); - const [logs, setLogs] = useState<string>(''); + const [showViewLogsModal, setShowViewLogsModal] = useState<boolean>(false); + const [logs, setLogs] = useState<string>(""); - return ( - <Fragment> - <> - <ModelTable<WorkflowLogs> - modelType={WorkflowLogs} - id="workflow-logs-table" - isDeleteable={false} - isEditable={false} - isCreateable={false} - name="Workflow Logs" - query={{ - workflowId: modelId, - projectId: - DashboardNavigation.getProjectId()?.toString(), - }} - selectMoreFields={{ - logs: true, - }} - actionButtons={[ - { - title: 'View Logs', - buttonStyleType: ButtonStyleType.NORMAL, - icon: IconProp.List, - onClick: async ( - item: WorkflowLogs, - onCompleteAction: VoidFunction - ) => { - setLogs(item['logs'] as string); - setShowViewLogsModal(true); + return ( + <Fragment> + <> + <ModelTable<WorkflowLogs> + modelType={WorkflowLogs} + id="workflow-logs-table" + isDeleteable={false} + isEditable={false} + isCreateable={false} + name="Workflow Logs" + query={{ + workflowId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + selectMoreFields={{ + logs: true, + }} + actionButtons={[ + { + title: "View Logs", + buttonStyleType: ButtonStyleType.NORMAL, + icon: IconProp.List, + onClick: async ( + item: WorkflowLogs, + onCompleteAction: VoidFunction, + ) => { + setLogs(item["logs"] as string); + setShowViewLogsModal(true); - onCompleteAction(); - }, - }, - ]} - isViewable={false} - cardProps={{ - title: 'Workflow Logs', - description: - 'List of logs in the last 30 days for this workflow', - }} - noItemsMessage={ - 'Looks like this workflow did not run so far in the last 30 days.' - } - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - field: { - _id: true, - }, - title: 'Run ID', - type: FieldType.ObjectID, - }, - { - field: { - workflowStatus: true, - }, - title: 'Workflow Status', - type: FieldType.Dropdown, - filterDropdownOptions: - DropdownUtil.getDropdownOptionsFromEnum( - WorkflowStatus - ), - }, - { - field: { - createdAt: true, - }, - title: 'Scheduled At', - type: FieldType.Date, - }, - { - field: { - startedAt: true, - }, - title: 'Started At', - type: FieldType.Date, - }, - { - field: { - completedAt: true, - }, - title: 'Completed At', - type: FieldType.Date, - }, - ]} - columns={[ - { - field: { - _id: true, - }, - title: 'Run ID', - type: FieldType.Text, - }, - { - field: { - workflowStatus: true, - }, + onCompleteAction(); + }, + }, + ]} + isViewable={false} + cardProps={{ + title: "Workflow Logs", + description: "List of logs in the last 30 days for this workflow", + }} + noItemsMessage={ + "Looks like this workflow did not run so far in the last 30 days." + } + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + field: { + _id: true, + }, + title: "Run ID", + type: FieldType.ObjectID, + }, + { + field: { + workflowStatus: true, + }, + title: "Workflow Status", + type: FieldType.Dropdown, + filterDropdownOptions: + DropdownUtil.getDropdownOptionsFromEnum(WorkflowStatus), + }, + { + field: { + createdAt: true, + }, + title: "Scheduled At", + type: FieldType.Date, + }, + { + field: { + startedAt: true, + }, + title: "Started At", + type: FieldType.Date, + }, + { + field: { + completedAt: true, + }, + title: "Completed At", + type: FieldType.Date, + }, + ]} + columns={[ + { + field: { + _id: true, + }, + title: "Run ID", + type: FieldType.Text, + }, + { + field: { + workflowStatus: true, + }, - title: 'Workflow Status', - type: FieldType.Text, - getElement: (item: WorkflowLogs): ReactElement => { - if (!item['workflowStatus']) { - throw new BadDataException( - 'Workflow Status not found' - ); - } + title: "Workflow Status", + type: FieldType.Text, + getElement: (item: WorkflowLogs): ReactElement => { + if (!item["workflowStatus"]) { + throw new BadDataException("Workflow Status not found"); + } - return ( - <WorkflowStatusElement - status={ - item[ - 'workflowStatus' - ] as WorkflowStatus - } - /> - ); - }, - }, - { - field: { - createdAt: true, - }, - title: 'Scheduled At', - type: FieldType.DateTime, - }, - { - field: { - startedAt: true, - }, - title: 'Started At', - type: FieldType.DateTime, - }, - { - field: { - completedAt: true, - }, - title: 'Completed At', - type: FieldType.DateTime, - }, - ]} - /> + return ( + <WorkflowStatusElement + status={item["workflowStatus"] as WorkflowStatus} + /> + ); + }, + }, + { + field: { + createdAt: true, + }, + title: "Scheduled At", + type: FieldType.DateTime, + }, + { + field: { + startedAt: true, + }, + title: "Started At", + type: FieldType.DateTime, + }, + { + field: { + completedAt: true, + }, + title: "Completed At", + type: FieldType.DateTime, + }, + ]} + /> - {showViewLogsModal && ( - <Modal - title={'Workflow Logs'} - description="Here are the logs for this workflow" - isLoading={false} - modalWidth={ModalWidth.Large} - onSubmit={() => { - setShowViewLogsModal(false); - }} - submitButtonText={'Close'} - submitButtonStyleType={ButtonStyleType.NORMAL} - > - <SimpleLogViewer> - {logs.split('\n').map((log: string, i: number) => { - return <div key={i}>{log}</div>; - })} - </SimpleLogViewer> - </Modal> - )} - </> - </Fragment> - ); + {showViewLogsModal && ( + <Modal + title={"Workflow Logs"} + description="Here are the logs for this workflow" + isLoading={false} + modalWidth={ModalWidth.Large} + onSubmit={() => { + setShowViewLogsModal(false); + }} + submitButtonText={"Close"} + submitButtonStyleType={ButtonStyleType.NORMAL} + > + <SimpleLogViewer> + {logs.split("\n").map((log: string, i: number) => { + return <div key={i}>{log}</div>; + })} + </SimpleLogViewer> + </Modal> + )} + </> + </Fragment> + ); }; export default Delete; diff --git a/Dashboard/src/Pages/Workflow/View/Settings.tsx b/Dashboard/src/Pages/Workflow/View/Settings.tsx index 00ff50b4f3..0ec20a0f92 100644 --- a/Dashboard/src/Pages/Workflow/View/Settings.tsx +++ b/Dashboard/src/Pages/Workflow/View/Settings.tsx @@ -1,50 +1,50 @@ -import PageMap from '../../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../../Utils/RouteMap'; -import PageComponentProps from '../../PageComponentProps'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import DuplicateModel from 'CommonUI/src/Components/DuplicateModel/DuplicateModel'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Workflow from 'Model/Models/Workflow'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import PageMap from "../../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../../Utils/RouteMap"; +import PageComponentProps from "../../PageComponentProps"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import DuplicateModel from "CommonUI/src/Components/DuplicateModel/DuplicateModel"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Workflow from "Model/Models/Workflow"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Settings: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <DuplicateModel - modelId={modelId} - modelType={Workflow} - fieldsToDuplicate={{ - description: true, - graph: true, - isEnabled: true, - labels: true, - }} - navigateToOnSuccess={RouteUtil.populateRouteParams( - new Route(RouteMap[PageMap.WORKFLOWS]?.toString()) - )} - fieldsToChange={[ - { - field: { - name: true, - }, - title: 'New Workflow Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'New Workflow Name', - validation: { - minLength: 2, - }, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <DuplicateModel + modelId={modelId} + modelType={Workflow} + fieldsToDuplicate={{ + description: true, + graph: true, + isEnabled: true, + labels: true, + }} + navigateToOnSuccess={RouteUtil.populateRouteParams( + new Route(RouteMap[PageMap.WORKFLOWS]?.toString()), + )} + fieldsToChange={[ + { + field: { + name: true, + }, + title: "New Workflow Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "New Workflow Name", + validation: { + minLength: 2, + }, + }, + ]} + /> + </Fragment> + ); }; export default Settings; diff --git a/Dashboard/src/Pages/Workflow/View/SideMenu.tsx b/Dashboard/src/Pages/Workflow/View/SideMenu.tsx index 74f169f9bf..2096272619 100644 --- a/Dashboard/src/Pages/Workflow/View/SideMenu.tsx +++ b/Dashboard/src/Pages/Workflow/View/SideMenu.tsx @@ -1,93 +1,93 @@ -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 ObjectID from 'Common/Types/ObjectID'; -import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 ObjectID from "Common/Types/ObjectID"; +import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - modelId: ObjectID; + modelId: ObjectID; } const DashboardSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - <SideMenuSection title="Basic"> - <SideMenuItem - link={{ - title: 'Overview', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOW_VIEW] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Info} - /> - <SideMenuItem - link={{ - title: 'Builder', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOW_BUILDER] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Workflow} - /> + return ( + <SideMenu> + <SideMenuSection title="Basic"> + <SideMenuItem + link={{ + title: "Overview", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOW_VIEW] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Info} + /> + <SideMenuItem + link={{ + title: "Builder", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOW_BUILDER] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Workflow} + /> - <SideMenuItem - link={{ - title: 'Workflow Variables', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOW_VARIABLES] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Variable} - /> - </SideMenuSection> + <SideMenuItem + link={{ + title: "Workflow Variables", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOW_VARIABLES] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Variable} + /> + </SideMenuSection> - <SideMenuSection title="Advanced"> - <SideMenuItem - link={{ - title: 'Runs & Logs', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOW_LOGS] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Logs} - /> + <SideMenuSection title="Advanced"> + <SideMenuItem + link={{ + title: "Runs & Logs", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOW_LOGS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Logs} + /> - <SideMenuItem - link={{ - title: 'Settings', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOW_VIEW_SETTINGS] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Settings} - /> + <SideMenuItem + link={{ + title: "Settings", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOW_VIEW_SETTINGS] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Settings} + /> - <SideMenuItem - link={{ - title: 'Delete Workflow', - to: RouteUtil.populateRouteParams( - RouteMap[PageMap.WORKFLOW_DELETE] as Route, - { modelId: props.modelId } - ), - }} - icon={IconProp.Trash} - className="danger-on-hover" - /> - </SideMenuSection> - </SideMenu> - ); + <SideMenuItem + link={{ + title: "Delete Workflow", + to: RouteUtil.populateRouteParams( + RouteMap[PageMap.WORKFLOW_DELETE] as Route, + { modelId: props.modelId }, + ), + }} + icon={IconProp.Trash} + className="danger-on-hover" + /> + </SideMenuSection> + </SideMenu> + ); }; export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Workflow/View/Variable.tsx b/Dashboard/src/Pages/Workflow/View/Variable.tsx index 0e9cf446cd..bd6eeedbd1 100644 --- a/Dashboard/src/Pages/Workflow/View/Variable.tsx +++ b/Dashboard/src/Pages/Workflow/View/Variable.tsx @@ -1,154 +1,152 @@ -import DashboardNavigation from '../../../Utils/Navigation'; -import PageComponentProps from '../../PageComponentProps'; -import ObjectID from 'Common/Types/ObjectID'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import WorkflowVariable from 'Model/Models/WorkflowVariable'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import DashboardNavigation from "../../../Utils/Navigation"; +import PageComponentProps from "../../PageComponentProps"; +import ObjectID from "Common/Types/ObjectID"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import WorkflowVariable from "Model/Models/WorkflowVariable"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Workflows: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); + const modelId: ObjectID = Navigation.getLastParamAsObjectID(1); - return ( - <Fragment> - <ModelTable<WorkflowVariable> - modelType={WorkflowVariable} - id="status-page-table" - isDeleteable={true} - isEditable={false} - isCreateable={true} - showViewIdButton={true} - name="Workflows" - isViewable={false} - cardProps={{ - title: 'Workflow Variables', - description: - 'Here is a list of workflow secrets and variables for this specific workflow.', - }} - query={{ - workflowId: modelId, - projectId: DashboardNavigation.getProjectId()?.toString(), - }} - onBeforeCreate={( - item: WorkflowVariable - ): Promise<WorkflowVariable> => { - item.workflowId = modelId; - return Promise.resolve(item); - }} - noItemsMessage={'No workflow variables found.'} - formFields={[ - { - field: { - name: true, - }, - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Workflow Name', - validation: { - minLength: 2, - noSpaces: true, - noSpecialCharacters: true, - noNumbers: true, - }, - }, - { - field: { - description: true, - }, - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - isSecret: true, - }, - title: 'Secret', - description: - 'Is this variable secret or secure? Should this be encrypted in the Database?', - fieldType: FormFieldSchemaType.Toggle, - required: false, - }, - { - field: { - content: true, - }, - title: 'Content', - description: 'Enter the content of the variable', - fieldType: FormFieldSchemaType.LongText, - required: true, - }, - ]} - showRefreshButton={true} - filters={[ - { - title: 'Name', - type: FieldType.Text, - field: { - name: true, - }, - }, - { - title: 'Description', - type: FieldType.Text, - field: { - description: true, - }, - }, - { - title: 'Secret', - type: FieldType.Boolean, - field: { - isSecret: true, - }, - }, - { - title: 'Created At', - type: FieldType.Date, - field: { - createdAt: true, - }, - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - isSecret: true, - }, - title: 'Secret', - type: FieldType.Boolean, - }, - { - field: { - createdAt: true, - }, - title: 'Created At', - type: FieldType.DateTime, - }, - ]} - /> - </Fragment> - ); + return ( + <Fragment> + <ModelTable<WorkflowVariable> + modelType={WorkflowVariable} + id="status-page-table" + isDeleteable={true} + isEditable={false} + isCreateable={true} + showViewIdButton={true} + name="Workflows" + isViewable={false} + cardProps={{ + title: "Workflow Variables", + description: + "Here is a list of workflow secrets and variables for this specific workflow.", + }} + query={{ + workflowId: modelId, + projectId: DashboardNavigation.getProjectId()?.toString(), + }} + onBeforeCreate={(item: WorkflowVariable): Promise<WorkflowVariable> => { + item.workflowId = modelId; + return Promise.resolve(item); + }} + noItemsMessage={"No workflow variables found."} + formFields={[ + { + field: { + name: true, + }, + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Workflow Name", + validation: { + minLength: 2, + noSpaces: true, + noSpecialCharacters: true, + noNumbers: true, + }, + }, + { + field: { + description: true, + }, + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + isSecret: true, + }, + title: "Secret", + description: + "Is this variable secret or secure? Should this be encrypted in the Database?", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }, + { + field: { + content: true, + }, + title: "Content", + description: "Enter the content of the variable", + fieldType: FormFieldSchemaType.LongText, + required: true, + }, + ]} + showRefreshButton={true} + filters={[ + { + title: "Name", + type: FieldType.Text, + field: { + name: true, + }, + }, + { + title: "Description", + type: FieldType.Text, + field: { + description: true, + }, + }, + { + title: "Secret", + type: FieldType.Boolean, + field: { + isSecret: true, + }, + }, + { + title: "Created At", + type: FieldType.Date, + field: { + createdAt: true, + }, + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + isSecret: true, + }, + title: "Secret", + type: FieldType.Boolean, + }, + { + field: { + createdAt: true, + }, + title: "Created At", + type: FieldType.DateTime, + }, + ]} + /> + </Fragment> + ); }; export default Workflows; diff --git a/Dashboard/src/Pages/Workflow/Workflows.tsx b/Dashboard/src/Pages/Workflow/Workflows.tsx index 0f919447b3..88a8049c07 100644 --- a/Dashboard/src/Pages/Workflow/Workflows.tsx +++ b/Dashboard/src/Pages/Workflow/Workflows.tsx @@ -1,224 +1,216 @@ -import LabelsElement from '../../Components/Label/Labels'; -import DashboardNavigation from '../../Utils/Navigation'; -import PageComponentProps from '../PageComponentProps'; -import URL from 'Common/Types/API/URL'; -import InBetween from 'Common/Types/BaseDatabase/InBetween'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import OneUptimeDate from 'Common/Types/Date'; -import WorkflowPlan from 'Common/Types/Workflow/WorkflowPlan'; -import Banner from 'CommonUI/src/Components/Banner/Banner'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import ModelProgress from 'CommonUI/src/Components/ModelProgress/ModelProgress'; -import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable'; -import FieldType from 'CommonUI/src/Components/Types/FieldType'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Label from 'Model/Models/Label'; -import Workflow from 'Model/Models/Workflow'; -import WorkflowLog from 'Model/Models/WorkflowLog'; -import React, { Fragment, FunctionComponent, ReactElement } from 'react'; +import LabelsElement from "../../Components/Label/Labels"; +import DashboardNavigation from "../../Utils/Navigation"; +import PageComponentProps from "../PageComponentProps"; +import URL from "Common/Types/API/URL"; +import InBetween from "Common/Types/BaseDatabase/InBetween"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import OneUptimeDate from "Common/Types/Date"; +import WorkflowPlan from "Common/Types/Workflow/WorkflowPlan"; +import Banner from "CommonUI/src/Components/Banner/Banner"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import ModelProgress from "CommonUI/src/Components/ModelProgress/ModelProgress"; +import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable"; +import FieldType from "CommonUI/src/Components/Types/FieldType"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Label from "Model/Models/Label"; +import Workflow from "Model/Models/Workflow"; +import WorkflowLog from "Model/Models/WorkflowLog"; +import React, { Fragment, FunctionComponent, ReactElement } from "react"; const Workflows: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - const startDate: Date = OneUptimeDate.getSomeDaysAgo(30); - const endDate: Date = OneUptimeDate.getCurrentDate(); - const plan: PlanSelect | null = ProjectUtil.getCurrentPlan(); + const startDate: Date = OneUptimeDate.getSomeDaysAgo(30); + const endDate: Date = OneUptimeDate.getCurrentDate(); + const plan: PlanSelect | null = ProjectUtil.getCurrentPlan(); - return ( - <Fragment> - <> - <Banner - openInNewTab={true} - title="Need a demo of workflows?" - description="Watch this 10 minute video which will help you connect Slack with OneUptime using workflows" - link={URL.fromString('https://youtu.be/z-b7_KQcUDY')} - /> + return ( + <Fragment> + <> + <Banner + openInNewTab={true} + title="Need a demo of workflows?" + description="Watch this 10 minute video which will help you connect Slack with OneUptime using workflows" + link={URL.fromString("https://youtu.be/z-b7_KQcUDY")} + /> - {plan && - (plan === PlanSelect.Growth || - plan === PlanSelect.Scale) && ( - <ModelProgress<WorkflowLog> - totalCount={WorkflowPlan[plan]} - modelType={WorkflowLog} - countQuery={{ - createdAt: new InBetween(startDate, endDate), - }} - title="Workflow Runs" - description={ - 'Workflow runs in the last 30 days. Your current plan is ' + - plan + - '. It currently supports ' + - WorkflowPlan[plan] + - ' runs in the last 30 days.' - } - /> - )} + {plan && (plan === PlanSelect.Growth || plan === PlanSelect.Scale) && ( + <ModelProgress<WorkflowLog> + totalCount={WorkflowPlan[plan]} + modelType={WorkflowLog} + countQuery={{ + createdAt: new InBetween(startDate, endDate), + }} + title="Workflow Runs" + description={ + "Workflow runs in the last 30 days. Your current plan is " + + plan + + ". It currently supports " + + WorkflowPlan[plan] + + " runs in the last 30 days." + } + /> + )} - <ModelTable<Workflow> - modelType={Workflow} - id="status-page-table" - isDeleteable={false} - isEditable={false} - isCreateable={true} - name="Workflows" - isViewable={true} - showViewIdButton={true} - cardProps={{ - title: 'Workflows', - description: - 'Here is a list of workflows for this project.', - }} - noItemsMessage={'No workflows found.'} - formSteps={[ - { - title: 'Workflow Info', - id: 'workflow-info', - }, - { - title: 'Labels', - id: 'labels', - }, - ]} - formFields={[ - { - field: { - name: true, - }, - stepId: 'workflow-info', - title: 'Name', - fieldType: FormFieldSchemaType.Text, - required: true, - placeholder: 'Workflow Name', - validation: { - minLength: 2, - }, - }, - { - field: { - description: true, - }, - stepId: 'workflow-info', - title: 'Description', - fieldType: FormFieldSchemaType.LongText, - required: true, - placeholder: 'Description', - }, - { - field: { - isEnabled: true, - }, - stepId: 'workflow-info', - title: 'Enabled', - fieldType: FormFieldSchemaType.Toggle, - }, - { - field: { - labels: true, - }, - stepId: 'labels', - title: 'Labels ', - description: - 'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.', - fieldType: FormFieldSchemaType.MultiSelectDropdown, - dropdownModal: { - type: Label, - labelField: 'name', - valueField: '_id', - }, - required: false, - placeholder: 'Labels', - }, - ]} - showRefreshButton={true} - viewPageRoute={Navigation.getCurrentRoute()} - filters={[ - { - title: 'Name', - type: FieldType.Text, - field: { - name: true, - }, - }, - { - title: 'Description', - type: FieldType.Text, - field: { - description: true, - }, - }, - { - title: 'Enabled', - type: FieldType.Boolean, - field: { - isEnabled: true, - }, - }, - { - title: 'Labels', - type: FieldType.EntityArray, - field: { - labels: { - name: true, - color: true, - }, - }, - filterEntityType: Label, - filterQuery: { - projectId: - DashboardNavigation.getProjectId()?.toString(), - }, - filterDropdownField: { - label: 'name', - value: '_id', - }, - filterDropdownOptions: [], - }, - ]} - columns={[ - { - field: { - name: true, - }, - title: 'Name', - type: FieldType.Text, - }, - { - field: { - description: true, - }, - title: 'Description', - type: FieldType.Text, - }, - { - field: { - isEnabled: true, - }, - title: 'Enabled', - type: FieldType.Boolean, - }, - { - field: { - labels: { - name: true, - color: true, - }, - }, - title: 'Labels', - type: FieldType.EntityArray, - getElement: (item: Workflow): ReactElement => { - return ( - <LabelsElement - labels={item['labels'] || []} - /> - ); - }, - }, - ]} - /> - </> - </Fragment> - ); + <ModelTable<Workflow> + modelType={Workflow} + id="status-page-table" + isDeleteable={false} + isEditable={false} + isCreateable={true} + name="Workflows" + isViewable={true} + showViewIdButton={true} + cardProps={{ + title: "Workflows", + description: "Here is a list of workflows for this project.", + }} + noItemsMessage={"No workflows found."} + formSteps={[ + { + title: "Workflow Info", + id: "workflow-info", + }, + { + title: "Labels", + id: "labels", + }, + ]} + formFields={[ + { + field: { + name: true, + }, + stepId: "workflow-info", + title: "Name", + fieldType: FormFieldSchemaType.Text, + required: true, + placeholder: "Workflow Name", + validation: { + minLength: 2, + }, + }, + { + field: { + description: true, + }, + stepId: "workflow-info", + title: "Description", + fieldType: FormFieldSchemaType.LongText, + required: true, + placeholder: "Description", + }, + { + field: { + isEnabled: true, + }, + stepId: "workflow-info", + title: "Enabled", + fieldType: FormFieldSchemaType.Toggle, + }, + { + field: { + labels: true, + }, + stepId: "labels", + title: "Labels ", + description: + "Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.", + fieldType: FormFieldSchemaType.MultiSelectDropdown, + dropdownModal: { + type: Label, + labelField: "name", + valueField: "_id", + }, + required: false, + placeholder: "Labels", + }, + ]} + showRefreshButton={true} + viewPageRoute={Navigation.getCurrentRoute()} + filters={[ + { + title: "Name", + type: FieldType.Text, + field: { + name: true, + }, + }, + { + title: "Description", + type: FieldType.Text, + field: { + description: true, + }, + }, + { + title: "Enabled", + type: FieldType.Boolean, + field: { + isEnabled: true, + }, + }, + { + title: "Labels", + type: FieldType.EntityArray, + field: { + labels: { + name: true, + color: true, + }, + }, + filterEntityType: Label, + filterQuery: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + filterDropdownField: { + label: "name", + value: "_id", + }, + filterDropdownOptions: [], + }, + ]} + columns={[ + { + field: { + name: true, + }, + title: "Name", + type: FieldType.Text, + }, + { + field: { + description: true, + }, + title: "Description", + type: FieldType.Text, + }, + { + field: { + isEnabled: true, + }, + title: "Enabled", + type: FieldType.Boolean, + }, + { + field: { + labels: { + name: true, + color: true, + }, + }, + title: "Labels", + type: FieldType.EntityArray, + getElement: (item: Workflow): ReactElement => { + return <LabelsElement labels={item["labels"] || []} />; + }, + }, + ]} + /> + </> + </Fragment> + ); }; export default Workflows; diff --git a/Dashboard/src/Routes/AICopilotRoutes.tsx b/Dashboard/src/Routes/AICopilotRoutes.tsx index 145b07eb16..0a06ab8d5f 100644 --- a/Dashboard/src/Routes/AICopilotRoutes.tsx +++ b/Dashboard/src/Routes/AICopilotRoutes.tsx @@ -1,152 +1,145 @@ -import Loader from '../Components/Loader/Loader'; -import CodeRepositoryViewLayout from '../Pages/AICopilot/CodeRepository/View/Layout'; -import ComponentProps from '../Pages/PageComponentProps'; -import PageMap from '../Utils/PageMap'; +import Loader from "../Components/Loader/Loader"; +import CodeRepositoryViewLayout from "../Pages/AICopilot/CodeRepository/View/Layout"; +import ComponentProps from "../Pages/PageComponentProps"; +import PageMap from "../Utils/PageMap"; import RouteMap, { - CodeRepositoryRoutePath, - RouteUtil, -} from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; + CodeRepositoryRoutePath, + RouteUtil, +} from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Pages const AiCopilot: LazyExoticComponent<FunctionComponent<ComponentProps>> = lazy( - () => { - return import('../Pages/AICopilot/Index'); - } + () => { + return import("../Pages/AICopilot/Index"); + }, ); const CodeRepositoryView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/AICopilot/CodeRepository/View/Index'); + return import("../Pages/AICopilot/CodeRepository/View/Index"); }); const CodeRepositoryViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/AICopilot/CodeRepository/View/Delete'); + return import("../Pages/AICopilot/CodeRepository/View/Delete"); }); const CodeRepositoryViewSettings: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/AICopilot/CodeRepository/View/Settings'); + return import("../Pages/AICopilot/CodeRepository/View/Settings"); }); const CodeRepositoryViewServices: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/AICopilot/CodeRepository/View/Services'); + return import("../Pages/AICopilot/CodeRepository/View/Services"); }); const CodeRepositoryRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute - path={CodeRepositoryRoutePath[PageMap.AI_COPILOT] || ''} - element={ - <Suspense fallback={Loader}> - <AiCopilot - {...props} - pageRoute={RouteMap[PageMap.AI_COPILOT] as Route} - /> - </Suspense> - } + return ( + <Routes> + <PageRoute + path={CodeRepositoryRoutePath[PageMap.AI_COPILOT] || ""} + element={ + <Suspense fallback={Loader}> + <AiCopilot + {...props} + pageRoute={RouteMap[PageMap.AI_COPILOT] as Route} /> + </Suspense> + } + /> - <PageRoute - path={ - CodeRepositoryRoutePath[ - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW - ] || '' + <PageRoute + path={ + CodeRepositoryRoutePath[PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW] || "" + } + element={<CodeRepositoryViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <CodeRepositoryView + {...props} + pageRoute={ + RouteMap[PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW] as Route } - element={<CodeRepositoryViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <CodeRepositoryView - {...props} - pageRoute={ - RouteMap[ - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW - ] as Route - } - /> - </Suspense> - } - /> + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <CodeRepositoryViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap - .AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE, + )} + element={ + <Suspense fallback={Loader}> + <CodeRepositoryViewDelete + {...props} + pageRoute={ + RouteMap[ + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS - )} - element={ - <Suspense fallback={Loader}> - <CodeRepositoryViewSettings - {...props} - pageRoute={ - RouteMap[ - PageMap - .AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS, + )} + element={ + <Suspense fallback={Loader}> + <CodeRepositoryViewSettings + {...props} + pageRoute={ + RouteMap[ + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES - )} - element={ - <Suspense fallback={Loader}> - <CodeRepositoryViewServices - {...props} - pageRoute={ - RouteMap[ - PageMap - .AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES, + )} + element={ + <Suspense fallback={Loader}> + <CodeRepositoryViewServices + {...props} + pageRoute={ + RouteMap[ + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES + ] as Route + } + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default CodeRepositoryRoutes; diff --git a/Dashboard/src/Routes/IncidentsRoutes.tsx b/Dashboard/src/Routes/IncidentsRoutes.tsx index 81fd7e3ddd..19123fd160 100644 --- a/Dashboard/src/Routes/IncidentsRoutes.tsx +++ b/Dashboard/src/Routes/IncidentsRoutes.tsx @@ -1,228 +1,192 @@ -import Loader from '../Components/Loader/Loader'; -import Layout from '../Pages/Incidents/Layout'; -import IncidentViewLayout from '../Pages/Incidents/View/Layout'; -import ComponentProps from '../Pages/PageComponentProps'; -import PageMap from '../Utils/PageMap'; -import RouteMap, { IncidentsRoutePath, RouteUtil } from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; +import Loader from "../Components/Loader/Loader"; +import Layout from "../Pages/Incidents/Layout"; +import IncidentViewLayout from "../Pages/Incidents/View/Layout"; +import ComponentProps from "../Pages/PageComponentProps"; +import PageMap from "../Utils/PageMap"; +import RouteMap, { IncidentsRoutePath, RouteUtil } from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Pages const Incidents: LazyExoticComponent<FunctionComponent<ComponentProps>> = lazy( - () => { - return import('../Pages/Incidents/Incidents'); - } + () => { + return import("../Pages/Incidents/Incidents"); + }, ); const IncidentView: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Incidents/View/Index'); - }); + lazy(() => { + return import("../Pages/Incidents/View/Index"); + }); const IncidentViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Incidents/View/Delete'); + return import("../Pages/Incidents/View/Delete"); }); const IncidentViewStateTimeline: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Incidents/View/StateTimeline'); + return import("../Pages/Incidents/View/StateTimeline"); }); const IncidentInternalNote: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Incidents/View/InternalNote'); + return import("../Pages/Incidents/View/InternalNote"); }); const IncidentPublicNote: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Incidents/View/PublicNote'); + return import("../Pages/Incidents/View/PublicNote"); }); const UnresolvedIncidents: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Incidents/Unresolved'); + return import("../Pages/Incidents/Unresolved"); }); const IncidentViewCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Incidents/View/CustomFields'); + return import("../Pages/Incidents/View/CustomFields"); }); const IncidentViewOwner: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Incidents/View/Owners'); + return import("../Pages/Incidents/View/Owners"); }); const IncidentsRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ) => { - return ( - <Routes> - <PageRoute path="/" element={<Layout {...props} />}> - <PageRoute - path={IncidentsRoutePath[PageMap.INCIDENTS] || ''} - element={ - <Suspense fallback={Loader}> - <Incidents - {...props} - pageRoute={RouteMap[PageMap.INCIDENTS] as Route} - /> - </Suspense> - } - /> + return ( + <Routes> + <PageRoute path="/" element={<Layout {...props} />}> + <PageRoute + path={IncidentsRoutePath[PageMap.INCIDENTS] || ""} + element={ + <Suspense fallback={Loader}> + <Incidents + {...props} + pageRoute={RouteMap[PageMap.INCIDENTS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={ - IncidentsRoutePath[PageMap.UNRESOLVED_INCIDENTS] || '' - } - element={ - <Suspense fallback={Loader}> - <UnresolvedIncidents - {...props} - pageRoute={ - RouteMap[ - PageMap.UNRESOLVED_INCIDENTS - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> + <PageRoute + path={IncidentsRoutePath[PageMap.UNRESOLVED_INCIDENTS] || ""} + element={ + <Suspense fallback={Loader}> + <UnresolvedIncidents + {...props} + pageRoute={RouteMap[PageMap.UNRESOLVED_INCIDENTS] as Route} + /> + </Suspense> + } + /> + </PageRoute> - <PageRoute - path={IncidentsRoutePath[PageMap.INCIDENT_VIEW] || ''} - element={<IncidentViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <IncidentView - {...props} - pageRoute={ - RouteMap[PageMap.INCIDENT_VIEW] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.INCIDENT_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <IncidentViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap.INCIDENT_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={IncidentsRoutePath[PageMap.INCIDENT_VIEW] || ""} + element={<IncidentViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <IncidentView + {...props} + pageRoute={RouteMap[PageMap.INCIDENT_VIEW] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.INCIDENT_VIEW_DELETE)} + element={ + <Suspense fallback={Loader}> + <IncidentViewDelete + {...props} + pageRoute={RouteMap[PageMap.INCIDENT_VIEW_DELETE] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.INCIDENT_VIEW_STATE_TIMELINE - )} - element={ - <Suspense fallback={Loader}> - <IncidentViewStateTimeline - {...props} - pageRoute={ - RouteMap[ - PageMap.INCIDENT_VIEW_STATE_TIMELINE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.INCIDENT_VIEW_STATE_TIMELINE, + )} + element={ + <Suspense fallback={Loader}> + <IncidentViewStateTimeline + {...props} + pageRoute={ + RouteMap[PageMap.INCIDENT_VIEW_STATE_TIMELINE] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.INCIDENT_INTERNAL_NOTE - )} - element={ - <Suspense fallback={Loader}> - <IncidentInternalNote - {...props} - pageRoute={ - RouteMap[ - PageMap.INCIDENT_INTERNAL_NOTE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.INCIDENT_INTERNAL_NOTE)} + element={ + <Suspense fallback={Loader}> + <IncidentInternalNote + {...props} + pageRoute={RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.INCIDENT_VIEW_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <IncidentViewCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap.INCIDENT_VIEW_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.INCIDENT_VIEW_CUSTOM_FIELDS, + )} + element={ + <Suspense fallback={Loader}> + <IncidentViewCustomFields + {...props} + pageRoute={ + RouteMap[PageMap.INCIDENT_VIEW_CUSTOM_FIELDS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.INCIDENT_PUBLIC_NOTE - )} - element={ - <Suspense fallback={Loader}> - <IncidentPublicNote - {...props} - pageRoute={ - RouteMap[ - PageMap.INCIDENT_PUBLIC_NOTE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.INCIDENT_PUBLIC_NOTE)} + element={ + <Suspense fallback={Loader}> + <IncidentPublicNote + {...props} + pageRoute={RouteMap[PageMap.INCIDENT_PUBLIC_NOTE] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.INCIDENT_VIEW_OWNERS - )} - element={ - <Suspense fallback={Loader}> - <IncidentViewOwner - {...props} - pageRoute={ - RouteMap[ - PageMap.INCIDENT_VIEW_OWNERS - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.INCIDENT_VIEW_OWNERS)} + element={ + <Suspense fallback={Loader}> + <IncidentViewOwner + {...props} + pageRoute={RouteMap[PageMap.INCIDENT_VIEW_OWNERS] as Route} + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default IncidentsRoutes; diff --git a/Dashboard/src/Routes/InitRoutes.tsx b/Dashboard/src/Routes/InitRoutes.tsx index 6c9e49ff1c..481d39c81d 100644 --- a/Dashboard/src/Routes/InitRoutes.tsx +++ b/Dashboard/src/Routes/InitRoutes.tsx @@ -1,50 +1,50 @@ -import Loader from '../Components/Loader/Loader'; -import { RoutesProps } from '../Types/RoutesProps'; -import PageMap from '../Utils/PageMap'; -import RouteMap from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; -import React, { FunctionComponent, ReactElement, Suspense, lazy } from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; +import Loader from "../Components/Loader/Loader"; +import { RoutesProps } from "../Types/RoutesProps"; +import PageMap from "../Utils/PageMap"; +import RouteMap from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; +import React, { FunctionComponent, ReactElement, Suspense, lazy } from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; const Init: any = lazy(() => { - return import('../Pages/Init/Init'); + return import("../Pages/Init/Init"); }); const InitRoutes: FunctionComponent<RoutesProps> = ( - props: RoutesProps + props: RoutesProps, ): ReactElement => { - const { projects, isLoading, ...rest } = props; - return ( - <Routes> - <PageRoute - path={RouteMap[PageMap.INIT]?.toString() || ''} - element={ - <Suspense fallback={Loader}> - <Init - {...rest} - pageRoute={RouteMap[PageMap.INIT] as Route} - projects={projects} - isLoadingProjects={isLoading} - /> - </Suspense> - } + const { projects, isLoading, ...rest } = props; + return ( + <Routes> + <PageRoute + path={RouteMap[PageMap.INIT]?.toString() || ""} + element={ + <Suspense fallback={Loader}> + <Init + {...rest} + pageRoute={RouteMap[PageMap.INIT] as Route} + projects={projects} + isLoadingProjects={isLoading} /> + </Suspense> + } + /> - <PageRoute - path={RouteMap[PageMap.INIT_PROJECT]?.toString() || ''} - element={ - <Suspense fallback={Loader}> - <Init - {...rest} - pageRoute={RouteMap[PageMap.INIT_PROJECT] as Route} - projects={projects} - isLoadingProjects={isLoading} - /> - </Suspense> - } + <PageRoute + path={RouteMap[PageMap.INIT_PROJECT]?.toString() || ""} + element={ + <Suspense fallback={Loader}> + <Init + {...rest} + pageRoute={RouteMap[PageMap.INIT_PROJECT] as Route} + projects={projects} + isLoadingProjects={isLoading} /> - </Routes> - ); + </Suspense> + } + /> + </Routes> + ); }; export default InitRoutes; diff --git a/Dashboard/src/Routes/MonitorGroupRoutes.tsx b/Dashboard/src/Routes/MonitorGroupRoutes.tsx index 3add1cb159..8c4185dafc 100644 --- a/Dashboard/src/Routes/MonitorGroupRoutes.tsx +++ b/Dashboard/src/Routes/MonitorGroupRoutes.tsx @@ -1,160 +1,138 @@ -import Loader from '../Components/Loader/Loader'; -import MonitorGroupViewLayout from '../Pages/MonitorGroup/View/Layout'; -import ComponentProps from '../Pages/PageComponentProps'; -import PageMap from '../Utils/PageMap'; -import RouteMap, { MonitorGroupRoutePath, RouteUtil } from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; +import Loader from "../Components/Loader/Loader"; +import MonitorGroupViewLayout from "../Pages/MonitorGroup/View/Layout"; +import ComponentProps from "../Pages/PageComponentProps"; +import PageMap from "../Utils/PageMap"; +import RouteMap, { MonitorGroupRoutePath, RouteUtil } from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Pages const MonitorGroups: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/MonitorGroup/MonitorGroups'); - }); + lazy(() => { + return import("../Pages/MonitorGroup/MonitorGroups"); + }); const MonitorGroupView: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/MonitorGroup/View/Index'); - }); + lazy(() => { + return import("../Pages/MonitorGroup/View/Index"); + }); const MonitorGroupViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/MonitorGroup/View/Delete'); + return import("../Pages/MonitorGroup/View/Delete"); }); const MonitorGroupViewMonitors: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/MonitorGroup/View/Monitors'); + return import("../Pages/MonitorGroup/View/Monitors"); }); const MonitorGroupViewIncidents: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/MonitorGroup/View/Incidents'); + return import("../Pages/MonitorGroup/View/Incidents"); }); const MonitorGroupViewOwners: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/MonitorGroup/View/Owners'); + return import("../Pages/MonitorGroup/View/Owners"); }); const MonitorGroupRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute - path={MonitorGroupRoutePath[PageMap.MONITOR_GROUPS] || ''} - element={ - <Suspense fallback={Loader}> - <MonitorGroups - {...props} - pageRoute={ - RouteMap[PageMap.MONITOR_GROUPS] as Route - } - /> - </Suspense> - } + return ( + <Routes> + <PageRoute + path={MonitorGroupRoutePath[PageMap.MONITOR_GROUPS] || ""} + element={ + <Suspense fallback={Loader}> + <MonitorGroups + {...props} + pageRoute={RouteMap[PageMap.MONITOR_GROUPS] as Route} /> + </Suspense> + } + /> - <PageRoute - path={MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW] || ''} - element={<MonitorGroupViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <MonitorGroupView - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_GROUP_VIEW - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_GROUP_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <MonitorGroupViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_GROUP_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW] || ""} + element={<MonitorGroupViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <MonitorGroupView + {...props} + pageRoute={RouteMap[PageMap.MONITOR_GROUP_VIEW] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_GROUP_VIEW_DELETE)} + element={ + <Suspense fallback={Loader}> + <MonitorGroupViewDelete + {...props} + pageRoute={RouteMap[PageMap.MONITOR_GROUP_VIEW_DELETE] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_GROUP_VIEW_MONITORS - )} - element={ - <Suspense fallback={Loader}> - <MonitorGroupViewMonitors - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_GROUP_VIEW_MONITORS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.MONITOR_GROUP_VIEW_MONITORS, + )} + element={ + <Suspense fallback={Loader}> + <MonitorGroupViewMonitors + {...props} + pageRoute={ + RouteMap[PageMap.MONITOR_GROUP_VIEW_MONITORS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_GROUP_VIEW_INCIDENTS - )} - element={ - <Suspense fallback={Loader}> - <MonitorGroupViewIncidents - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_GROUP_VIEW_INCIDENTS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.MONITOR_GROUP_VIEW_INCIDENTS, + )} + element={ + <Suspense fallback={Loader}> + <MonitorGroupViewIncidents + {...props} + pageRoute={ + RouteMap[PageMap.MONITOR_GROUP_VIEW_INCIDENTS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_GROUP_VIEW_OWNERS - )} - element={ - <Suspense fallback={Loader}> - <MonitorGroupViewOwners - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_GROUP_VIEW_OWNERS - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_GROUP_VIEW_OWNERS)} + element={ + <Suspense fallback={Loader}> + <MonitorGroupViewOwners + {...props} + pageRoute={RouteMap[PageMap.MONITOR_GROUP_VIEW_OWNERS] as Route} + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default MonitorGroupRoutes; diff --git a/Dashboard/src/Routes/MonitorsRoutes.tsx b/Dashboard/src/Routes/MonitorsRoutes.tsx index 4aa8dcbbbc..b9eb531f1a 100644 --- a/Dashboard/src/Routes/MonitorsRoutes.tsx +++ b/Dashboard/src/Routes/MonitorsRoutes.tsx @@ -1,333 +1,271 @@ -import Loader from '../Components/Loader/Loader'; -import MonitorLayout from '../Pages/Monitor/Layout'; -import MonitorViewLayout from '../Pages/Monitor/View/Layout'; -import ComponentProps from '../Pages/PageComponentProps'; -import PageMap from '../Utils/PageMap'; -import RouteMap, { MonitorsRoutePath, RouteUtil } from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; +import Loader from "../Components/Loader/Loader"; +import MonitorLayout from "../Pages/Monitor/Layout"; +import MonitorViewLayout from "../Pages/Monitor/View/Layout"; +import ComponentProps from "../Pages/PageComponentProps"; +import PageMap from "../Utils/PageMap"; +import RouteMap, { MonitorsRoutePath, RouteUtil } from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Pages const MonitorPage: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Monitor/Monitors'); - }); + lazy(() => { + return import("../Pages/Monitor/Monitors"); + }); const MonitorView: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Monitor/View/Index'); - }); + lazy(() => { + return import("../Pages/Monitor/View/Index"); + }); const MonitorViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Monitor/View/Delete'); + return import("../Pages/Monitor/View/Delete"); }); const MonitorViewCriteria: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Monitor/View/Criteria'); + return import("../Pages/Monitor/View/Criteria"); }); const MonitorViewStatusTimeline: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Monitor/View/StatusTimeline'); + return import("../Pages/Monitor/View/StatusTimeline"); }); const MonitorIncidents: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Monitor/View/Incidents'); - }); + lazy(() => { + return import("../Pages/Monitor/View/Incidents"); + }); const MonitorInoperational: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Monitor/NotOperationalMonitors'); + return import("../Pages/Monitor/NotOperationalMonitors"); }); const MonitorDisabled: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Monitor/DisabledMonitors'); - }); + lazy(() => { + return import("../Pages/Monitor/DisabledMonitors"); + }); const MonitorViewCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Monitor/View/CustomFields'); + return import("../Pages/Monitor/View/CustomFields"); }); const MonitorViewInterval: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Monitor/View/Interval'); + return import("../Pages/Monitor/View/Interval"); }); const MonitorViewDocumentation: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Monitor/View/Documentation'); + return import("../Pages/Monitor/View/Documentation"); }); const MonitorViewProbes: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Monitor/View/Probes'); + return import("../Pages/Monitor/View/Probes"); }); const MonitorViewOwner: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Monitor/View/Owners'); - }); + lazy(() => { + return import("../Pages/Monitor/View/Owners"); + }); const MonitorViewSettings: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Monitor/View/Settings'); + return import("../Pages/Monitor/View/Settings"); }); const MonitorRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute path="/" element={<MonitorLayout {...props} />}> - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <MonitorPage - {...props} - pageRoute={RouteMap[PageMap.MONITORS] as Route} - /> - </Suspense> - } - /> - <PageRoute - path={MonitorsRoutePath[PageMap.MONITORS_DISABLED] || ''} - element={ - <Suspense fallback={Loader}> - <MonitorDisabled - {...props} - pageRoute={ - RouteMap[PageMap.MONITORS_DISABLED] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={ - MonitorsRoutePath[PageMap.MONITORS_INOPERATIONAL] || '' - } - element={ - <Suspense fallback={Loader}> - <MonitorInoperational - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITORS_INOPERATIONAL - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> + return ( + <Routes> + <PageRoute path="/" element={<MonitorLayout {...props} />}> + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <MonitorPage + {...props} + pageRoute={RouteMap[PageMap.MONITORS] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={MonitorsRoutePath[PageMap.MONITORS_DISABLED] || ""} + element={ + <Suspense fallback={Loader}> + <MonitorDisabled + {...props} + pageRoute={RouteMap[PageMap.MONITORS_DISABLED] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={MonitorsRoutePath[PageMap.MONITORS_INOPERATIONAL] || ""} + element={ + <Suspense fallback={Loader}> + <MonitorInoperational + {...props} + pageRoute={RouteMap[PageMap.MONITORS_INOPERATIONAL] as Route} + /> + </Suspense> + } + /> + </PageRoute> - <PageRoute - path={MonitorsRoutePath[PageMap.MONITOR_VIEW] || ''} - element={<MonitorViewLayout />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <MonitorView - {...props} - pageRoute={ - RouteMap[PageMap.MONITOR_VIEW] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_SETTINGS - )} - element={ - <Suspense fallback={Loader}> - <MonitorViewSettings - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_SETTINGS - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_OWNERS - )} - element={ - <Suspense fallback={Loader}> - <MonitorViewOwner - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_OWNERS - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_CRITERIA - )} - element={ - <Suspense fallback={Loader}> - <MonitorViewCriteria - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_CRITERIA - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={MonitorsRoutePath[PageMap.MONITOR_VIEW] || ""} + element={<MonitorViewLayout />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <MonitorView + {...props} + pageRoute={RouteMap[PageMap.MONITOR_VIEW] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_VIEW_SETTINGS)} + element={ + <Suspense fallback={Loader}> + <MonitorViewSettings + {...props} + pageRoute={RouteMap[PageMap.MONITOR_VIEW_SETTINGS] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_VIEW_OWNERS)} + element={ + <Suspense fallback={Loader}> + <MonitorViewOwner + {...props} + pageRoute={RouteMap[PageMap.MONITOR_VIEW_OWNERS] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_VIEW_CRITERIA)} + element={ + <Suspense fallback={Loader}> + <MonitorViewCriteria + {...props} + pageRoute={RouteMap[PageMap.MONITOR_VIEW_CRITERIA] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_INTERVAL - )} - element={ - <Suspense fallback={Loader}> - <MonitorViewInterval - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_INTERVAL - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_VIEW_INTERVAL)} + element={ + <Suspense fallback={Loader}> + <MonitorViewInterval + {...props} + pageRoute={RouteMap[PageMap.MONITOR_VIEW_INTERVAL] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_DOCUMENTATION - )} - element={ - <Suspense fallback={Loader}> - <MonitorViewDocumentation - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_DOCUMENTATION - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_VIEW_DOCUMENTATION)} + element={ + <Suspense fallback={Loader}> + <MonitorViewDocumentation + {...props} + pageRoute={ + RouteMap[PageMap.MONITOR_VIEW_DOCUMENTATION] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_STATUS_TIMELINE - )} - element={ - <Suspense fallback={Loader}> - <MonitorViewStatusTimeline - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_STATUS_TIMELINE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.MONITOR_VIEW_STATUS_TIMELINE, + )} + element={ + <Suspense fallback={Loader}> + <MonitorViewStatusTimeline + {...props} + pageRoute={ + RouteMap[PageMap.MONITOR_VIEW_STATUS_TIMELINE] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_INCIDENTS - )} - element={ - <Suspense fallback={Loader}> - <MonitorIncidents - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_INCIDENTS - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <MonitorViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_VIEW_INCIDENTS)} + element={ + <Suspense fallback={Loader}> + <MonitorIncidents + {...props} + pageRoute={RouteMap[PageMap.MONITOR_VIEW_INCIDENTS] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_VIEW_DELETE)} + element={ + <Suspense fallback={Loader}> + <MonitorViewDelete + {...props} + pageRoute={RouteMap[PageMap.MONITOR_VIEW_DELETE] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <MonitorViewCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_VIEW_CUSTOM_FIELDS)} + element={ + <Suspense fallback={Loader}> + <MonitorViewCustomFields + {...props} + pageRoute={ + RouteMap[PageMap.MONITOR_VIEW_CUSTOM_FIELDS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.MONITOR_VIEW_PROBES - )} - element={ - <Suspense fallback={Loader}> - <MonitorViewProbes - {...props} - pageRoute={ - RouteMap[ - PageMap.MONITOR_VIEW_PROBES - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.MONITOR_VIEW_PROBES)} + element={ + <Suspense fallback={Loader}> + <MonitorViewProbes + {...props} + pageRoute={RouteMap[PageMap.MONITOR_VIEW_PROBES] as Route} + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default MonitorRoutes; diff --git a/Dashboard/src/Routes/OnCallDutyRoutes.tsx b/Dashboard/src/Routes/OnCallDutyRoutes.tsx index 4d6887d4a2..730113a600 100644 --- a/Dashboard/src/Routes/OnCallDutyRoutes.tsx +++ b/Dashboard/src/Routes/OnCallDutyRoutes.tsx @@ -1,364 +1,316 @@ -import Loader from '../Components/Loader/Loader'; -import OnCallDutyLayout from '../Pages/OnCallDuty/Layout'; -import OnCallDutyPolicyViewLayout from '../Pages/OnCallDuty/OnCallDutyPolicy/Layout'; -import OnCallDutyScheduleViewLayout from '../Pages/OnCallDuty/OnCallDutySchedule/Layout'; -import ComponentProps from '../Pages/PageComponentProps'; -import PageMap from '../Utils/PageMap'; -import RouteMap, { OnCallDutyRoutePath, RouteUtil } from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; +import Loader from "../Components/Loader/Loader"; +import OnCallDutyLayout from "../Pages/OnCallDuty/Layout"; +import OnCallDutyPolicyViewLayout from "../Pages/OnCallDuty/OnCallDutyPolicy/Layout"; +import OnCallDutyScheduleViewLayout from "../Pages/OnCallDuty/OnCallDutySchedule/Layout"; +import ComponentProps from "../Pages/PageComponentProps"; +import PageMap from "../Utils/PageMap"; +import RouteMap, { OnCallDutyRoutePath, RouteUtil } from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Polcies const OnCallDutyPoliciesPage: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutyPolicies'); + return import("../Pages/OnCallDuty/OnCallDutyPolicies"); }); const OnCallDutyExecutionLogs: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutyExecutionLogs'); + return import("../Pages/OnCallDuty/OnCallDutyExecutionLogs"); }); const OnCallDutyPolicyExecutionLogTimeline: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutyExecutionLogView'); + return import("../Pages/OnCallDuty/OnCallDutyExecutionLogView"); }); const OnCallDutyPolicyView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutyPolicy/Index'); + return import("../Pages/OnCallDuty/OnCallDutyPolicy/Index"); }); const OnCallDutyPolicyViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutyPolicy/Delete'); + return import("../Pages/OnCallDuty/OnCallDutyPolicy/Delete"); }); const OnCallDutyPolicyViewLogs: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogs'); + return import("../Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogs"); }); const OnCallDutyPolicyViewLogsView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogView'); + return import("../Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogView"); }); const OnCallDutyPolicyViewEscalation: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutyPolicy/Escalation'); + return import("../Pages/OnCallDuty/OnCallDutyPolicy/Escalation"); }); const OnCallDutyPolicyViewCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutyPolicy/CustomFields'); + return import("../Pages/OnCallDuty/OnCallDutyPolicy/CustomFields"); }); // Schedules const OnCallDutySchedules: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutySchedules'); + return import("../Pages/OnCallDuty/OnCallDutySchedules"); }); const OnCallDutyScheduleView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutySchedule/Index'); + return import("../Pages/OnCallDuty/OnCallDutySchedule/Index"); }); const OnCallDutyScheduleViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutySchedule/Delete'); + return import("../Pages/OnCallDuty/OnCallDutySchedule/Delete"); }); const OnCallDutyScheduleViewLayers: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/OnCallDuty/OnCallDutySchedule/Layers'); + return import("../Pages/OnCallDuty/OnCallDutySchedule/Layers"); }); const OnCallDutyRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute element={<OnCallDutyLayout {...props} />}> - <PageRoute - path={OnCallDutyRoutePath[PageMap.ON_CALL_DUTY] || ''} - element={ - <Suspense fallback={Loader}> - <OnCallDutyPoliciesPage - {...props} - pageRoute={ - RouteMap[PageMap.ON_CALL_DUTY] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICIES] || '' - } - element={ - <Suspense fallback={Loader}> - <OnCallDutyPoliciesPage - {...props} - pageRoute={ - RouteMap[ - PageMap.ON_CALL_DUTY_POLICIES - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={ - OnCallDutyRoutePath[ - PageMap.ON_CALL_DUTY_EXECUTION_LOGS - ] || '' - } - element={ - <Suspense fallback={Loader}> - <OnCallDutyExecutionLogs - {...props} - pageRoute={ - RouteMap[ - PageMap.ON_CALL_DUTY_EXECUTION_LOGS - ] as Route - } - /> - </Suspense> - } - /> - - <PageRoute - path={ - OnCallDutyRoutePath[ - PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE - ] || '' - } - element={ - <Suspense fallback={Loader}> - <OnCallDutyPolicyExecutionLogTimeline - {...props} - pageRoute={ - RouteMap[ - PageMap - .ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE - ] as Route - } - /> - </Suspense> - } - /> - - <PageRoute - path={ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULES] || - '' - } - element={ - <Suspense fallback={Loader}> - <OnCallDutySchedules - {...props} - pageRoute={ - RouteMap[ - PageMap.ON_CALL_DUTY_SCHEDULES - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - - <PageRoute - path={ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW] || '' + return ( + <Routes> + <PageRoute element={<OnCallDutyLayout {...props} />}> + <PageRoute + path={OnCallDutyRoutePath[PageMap.ON_CALL_DUTY] || ""} + element={ + <Suspense fallback={Loader}> + <OnCallDutyPoliciesPage + {...props} + pageRoute={RouteMap[PageMap.ON_CALL_DUTY] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICIES] || ""} + element={ + <Suspense fallback={Loader}> + <OnCallDutyPoliciesPage + {...props} + pageRoute={RouteMap[PageMap.ON_CALL_DUTY_POLICIES] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_EXECUTION_LOGS] || ""} + element={ + <Suspense fallback={Loader}> + <OnCallDutyExecutionLogs + {...props} + pageRoute={ + RouteMap[PageMap.ON_CALL_DUTY_EXECUTION_LOGS] as Route } - element={<OnCallDutyPolicyViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <OnCallDutyPolicyView - {...props} - pageRoute={ - RouteMap[ - PageMap.ON_CALL_DUTY_POLICY_VIEW - ] as Route - } - /> - </Suspense> - } - /> + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <OnCallDutyPolicyViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> - - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION - )} - element={ - <Suspense fallback={Loader}> - <OnCallDutyPolicyViewEscalation - {...props} - pageRoute={ - RouteMap[ - PageMap - .ON_CALL_DUTY_POLICY_VIEW_ESCALATION - ] as Route - } - /> - </Suspense> - } - /> - - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <OnCallDutyPolicyViewCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap - .ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> - - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS - )} - element={ - <Suspense fallback={Loader}> - <OnCallDutyPolicyViewLogs - {...props} - pageRoute={ - RouteMap[ - PageMap - .ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS - ] as Route - } - /> - </Suspense> - } - /> - - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW, - 2 - )} - element={ - <OnCallDutyPolicyViewLogsView - {...props} - pageRoute={ - RouteMap[ - PageMap - .ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW - ] as Route - } - /> - } - /> - </PageRoute> - <PageRoute - path={ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW] || - '' + <PageRoute + path={ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE] || + "" + } + element={ + <Suspense fallback={Loader}> + <OnCallDutyPolicyExecutionLogTimeline + {...props} + pageRoute={ + RouteMap[ + PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE + ] as Route } - element={<OnCallDutyScheduleViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <OnCallDutyScheduleView - {...props} - pageRoute={ - RouteMap[ - PageMap.ON_CALL_DUTY_SCHEDULE_VIEW - ] as Route - } - /> - </Suspense> - } - /> + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <OnCallDutyScheduleViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap - .ON_CALL_DUTY_SCHEDULE_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULES] || ""} + element={ + <Suspense fallback={Loader}> + <OnCallDutySchedules + {...props} + pageRoute={RouteMap[PageMap.ON_CALL_DUTY_SCHEDULES] as Route} + /> + </Suspense> + } + /> + </PageRoute> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS - )} - element={ - <Suspense fallback={Loader}> - <OnCallDutyScheduleViewLayers - {...props} - pageRoute={ - RouteMap[ - PageMap - .ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW] || ""} + element={<OnCallDutyPolicyViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <OnCallDutyPolicyView + {...props} + pageRoute={RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route} + /> + </Suspense> + } + /> + + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE, + )} + element={ + <Suspense fallback={Loader}> + <OnCallDutyPolicyViewDelete + {...props} + pageRoute={ + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE] as Route + } + /> + </Suspense> + } + /> + + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION, + )} + element={ + <Suspense fallback={Loader}> + <OnCallDutyPolicyViewEscalation + {...props} + pageRoute={ + RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION] as Route + } + /> + </Suspense> + } + /> + + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS, + )} + element={ + <Suspense fallback={Loader}> + <OnCallDutyPolicyViewCustomFields + {...props} + pageRoute={ + RouteMap[ + PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS + ] as Route + } + /> + </Suspense> + } + /> + + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS, + )} + element={ + <Suspense fallback={Loader}> + <OnCallDutyPolicyViewLogs + {...props} + pageRoute={ + RouteMap[ + PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS + ] as Route + } + /> + </Suspense> + } + /> + + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW, + 2, + )} + element={ + <OnCallDutyPolicyViewLogsView + {...props} + pageRoute={ + RouteMap[ + PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW + ] as Route + } + /> + } + /> + </PageRoute> + <PageRoute + path={OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW] || ""} + element={<OnCallDutyScheduleViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <OnCallDutyScheduleView + {...props} + pageRoute={ + RouteMap[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW] as Route + } + /> + </Suspense> + } + /> + + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE, + )} + element={ + <Suspense fallback={Loader}> + <OnCallDutyScheduleViewDelete + {...props} + pageRoute={ + RouteMap[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE] as Route + } + /> + </Suspense> + } + /> + + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS, + )} + element={ + <Suspense fallback={Loader}> + <OnCallDutyScheduleViewLayers + {...props} + pageRoute={ + RouteMap[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS] as Route + } + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default OnCallDutyRoutes; diff --git a/Dashboard/src/Routes/ScheduleMaintenaceEventsRoutes.tsx b/Dashboard/src/Routes/ScheduleMaintenaceEventsRoutes.tsx index 3defbe01f2..7160389836 100644 --- a/Dashboard/src/Routes/ScheduleMaintenaceEventsRoutes.tsx +++ b/Dashboard/src/Routes/ScheduleMaintenaceEventsRoutes.tsx @@ -1,256 +1,234 @@ -import Loader from '../Components/Loader/Loader'; -import ComponentProps from '../Pages/PageComponentProps'; -import ScheduledMaintenancesLaoyut from '../Pages/ScheduledMaintenanceEvents/Layout'; -import ScheduledMaintenanceViewLayout from '../Pages/ScheduledMaintenanceEvents/View/Layout'; -import PageMap from '../Utils/PageMap'; +import Loader from "../Components/Loader/Loader"; +import ComponentProps from "../Pages/PageComponentProps"; +import ScheduledMaintenancesLaoyut from "../Pages/ScheduledMaintenanceEvents/Layout"; +import ScheduledMaintenanceViewLayout from "../Pages/ScheduledMaintenanceEvents/View/Layout"; +import PageMap from "../Utils/PageMap"; import RouteMap, { - RouteUtil, - ScheduledMaintenanceEventsRoutePath, -} from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; + RouteUtil, + ScheduledMaintenanceEventsRoutePath, +} from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Pages const ScheduledMaintenanceEvents: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import( - '../Pages/ScheduledMaintenanceEvents/ScheduledMaintenanceEvents' - ); + return import( + "../Pages/ScheduledMaintenanceEvents/ScheduledMaintenanceEvents" + ); }); const ScheduledMaintenanceEventView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ScheduledMaintenanceEvents/View/Index'); + return import("../Pages/ScheduledMaintenanceEvents/View/Index"); }); const ScheduledMaintenanceEventViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ScheduledMaintenanceEvents/View/Delete'); + return import("../Pages/ScheduledMaintenanceEvents/View/Delete"); }); const ScheduledMaintenanceEventViewOwner: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ScheduledMaintenanceEvents/View/Owners'); + return import("../Pages/ScheduledMaintenanceEvents/View/Owners"); }); const ScheduledMaintenanceEventViewStateTimeline: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ScheduledMaintenanceEvents/View/StateTimeline'); + return import("../Pages/ScheduledMaintenanceEvents/View/StateTimeline"); }); const ScheduledMaintenanceEventInternalNote: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ScheduledMaintenanceEvents/View/InternalNote'); + return import("../Pages/ScheduledMaintenanceEvents/View/InternalNote"); }); const ScheduledMaintenanceEventPublicNote: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ScheduledMaintenanceEvents/View/PublicNote'); + return import("../Pages/ScheduledMaintenanceEvents/View/PublicNote"); }); const OngoingScheduledMaintenanceEvents: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ScheduledMaintenanceEvents/Ongoing'); + return import("../Pages/ScheduledMaintenanceEvents/Ongoing"); }); const ScheduledMaintenanceEventsViewCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ScheduledMaintenanceEvents/View/CustomFields'); + return import("../Pages/ScheduledMaintenanceEvents/View/CustomFields"); }); const ScheduledMaintenanceEventsRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute - path="/" - element={<ScheduledMaintenancesLaoyut {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceEvents - {...props} - pageRoute={ - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_EVENTS - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={ - ScheduledMaintenanceEventsRoutePath[ - PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS - ] || '' - } - element={ - <Suspense fallback={Loader}> - <OngoingScheduledMaintenanceEvents - {...props} - pageRoute={ - RouteMap[ - PageMap - .ONGOING_SCHEDULED_MAINTENANCE_EVENTS - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - - <PageRoute - path={ - ScheduledMaintenanceEventsRoutePath[ - PageMap.SCHEDULED_MAINTENANCE_VIEW - ] || '' + return ( + <Routes> + <PageRoute path="/" element={<ScheduledMaintenancesLaoyut {...props} />}> + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceEvents + {...props} + pageRoute={ + RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route } - element={<ScheduledMaintenanceViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceEventView - {...props} - pageRoute={ - RouteMap[ - PageMap.SCHEDULED_MAINTENANCE_VIEW - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceEventsViewCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap - .SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceEventViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap - .SCHEDULED_MAINTENANCE_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> + /> + </Suspense> + } + /> + <PageRoute + path={ + ScheduledMaintenanceEventsRoutePath[ + PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS + ] || "" + } + element={ + <Suspense fallback={Loader}> + <OngoingScheduledMaintenanceEvents + {...props} + pageRoute={ + RouteMap[ + PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS + ] as Route + } + /> + </Suspense> + } + /> + </PageRoute> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS - )} - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceEventViewOwner - {...props} - pageRoute={ - RouteMap[ - PageMap - .SCHEDULED_MAINTENANCE_VIEW_OWNERS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={ + ScheduledMaintenanceEventsRoutePath[ + PageMap.SCHEDULED_MAINTENANCE_VIEW + ] || "" + } + element={<ScheduledMaintenanceViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceEventView + {...props} + pageRoute={ + RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW] as Route + } + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS, + )} + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceEventsViewCustomFields + {...props} + pageRoute={ + RouteMap[ + PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS + ] as Route + } + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE, + )} + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceEventViewDelete + {...props} + pageRoute={ + RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE - )} - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceEventViewStateTimeline - {...props} - pageRoute={ - RouteMap[ - PageMap - .SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS, + )} + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceEventViewOwner + {...props} + pageRoute={ + RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE - )} - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceEventInternalNote - {...props} - pageRoute={ - RouteMap[ - PageMap - .SCHEDULED_MAINTENANCE_INTERNAL_NOTE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE, + )} + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceEventViewStateTimeline + {...props} + pageRoute={ + RouteMap[ + PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE - )} - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceEventPublicNote - {...props} - pageRoute={ - RouteMap[ - PageMap - .SCHEDULED_MAINTENANCE_PUBLIC_NOTE - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE, + )} + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceEventInternalNote + {...props} + pageRoute={ + RouteMap[PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE] as Route + } + /> + </Suspense> + } + /> + + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE, + )} + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceEventPublicNote + {...props} + pageRoute={ + RouteMap[PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE] as Route + } + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default ScheduledMaintenanceEventsRoutes; diff --git a/Dashboard/src/Routes/ServiceCatalogRoutes.tsx b/Dashboard/src/Routes/ServiceCatalogRoutes.tsx index 2944ecb043..5171074f17 100644 --- a/Dashboard/src/Routes/ServiceCatalogRoutes.tsx +++ b/Dashboard/src/Routes/ServiceCatalogRoutes.tsx @@ -1,146 +1,132 @@ -import Loader from '../Components/Loader/Loader'; -import ComponentProps from '../Pages/PageComponentProps'; -import StatusPageViewLayout from '../Pages/ServiceCatalog/View/Layout'; -import PageMap from '../Utils/PageMap'; +import Loader from "../Components/Loader/Loader"; +import ComponentProps from "../Pages/PageComponentProps"; +import StatusPageViewLayout from "../Pages/ServiceCatalog/View/Layout"; +import PageMap from "../Utils/PageMap"; import RouteMap, { - RouteUtil, - ServiceCatalogRoutePath, -} from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; + RouteUtil, + ServiceCatalogRoutePath, +} from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Pages const ServiceCatalog: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/ServiceCatalog/ServiceCatalog'); - }); + lazy(() => { + return import("../Pages/ServiceCatalog/ServiceCatalog"); + }); const ServiceCatalogView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ServiceCatalog/View/Index'); + return import("../Pages/ServiceCatalog/View/Index"); }); const ServiceCatalogViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ServiceCatalog/View/Delete'); + return import("../Pages/ServiceCatalog/View/Delete"); }); const ServiceCatalogViewSettings: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ServiceCatalog/View/Settings'); + return import("../Pages/ServiceCatalog/View/Settings"); }); const ServiceCatalogViewOwners: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/ServiceCatalog/View/Owners'); + return import("../Pages/ServiceCatalog/View/Owners"); }); const ServiceCatalogRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute - path={ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG] || ''} - element={ - <Suspense fallback={Loader}> - <ServiceCatalog - {...props} - pageRoute={ - RouteMap[PageMap.SERVICE_CATALOG] as Route - } - /> - </Suspense> - } + return ( + <Routes> + <PageRoute + path={ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG] || ""} + element={ + <Suspense fallback={Loader}> + <ServiceCatalog + {...props} + pageRoute={RouteMap[PageMap.SERVICE_CATALOG] as Route} /> + </Suspense> + } + /> - <PageRoute - path={ - ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW] || '' + <PageRoute + path={ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW] || ""} + element={<StatusPageViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <ServiceCatalogView + {...props} + pageRoute={RouteMap[PageMap.SERVICE_CATALOG_VIEW] as Route} + /> + </Suspense> + } + /> + + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SERVICE_CATALOG_VIEW_DELETE, + )} + element={ + <Suspense fallback={Loader}> + <ServiceCatalogViewDelete + {...props} + pageRoute={ + RouteMap[PageMap.SERVICE_CATALOG_VIEW_DELETE] as Route } - element={<StatusPageViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <ServiceCatalogView - {...props} - pageRoute={ - RouteMap[ - PageMap.SERVICE_CATALOG_VIEW - ] as Route - } - /> - </Suspense> - } - /> + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SERVICE_CATALOG_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <ServiceCatalogViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap.SERVICE_CATALOG_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SERVICE_CATALOG_VIEW_SETTINGS, + )} + element={ + <Suspense fallback={Loader}> + <ServiceCatalogViewSettings + {...props} + pageRoute={ + RouteMap[PageMap.SERVICE_CATALOG_VIEW_SETTINGS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SERVICE_CATALOG_VIEW_SETTINGS - )} - element={ - <Suspense fallback={Loader}> - <ServiceCatalogViewSettings - {...props} - pageRoute={ - RouteMap[ - PageMap.SERVICE_CATALOG_VIEW_SETTINGS - ] as Route - } - /> - </Suspense> - } - /> - - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SERVICE_CATALOG_VIEW_OWNERS - )} - element={ - <Suspense fallback={Loader}> - <ServiceCatalogViewOwners - {...props} - pageRoute={ - RouteMap[ - PageMap.SERVICE_CATALOG_VIEW_OWNERS - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SERVICE_CATALOG_VIEW_OWNERS, + )} + element={ + <Suspense fallback={Loader}> + <ServiceCatalogViewOwners + {...props} + pageRoute={ + RouteMap[PageMap.SERVICE_CATALOG_VIEW_OWNERS] as Route + } + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default ServiceCatalogRoutes; diff --git a/Dashboard/src/Routes/SettingsRoutes.tsx b/Dashboard/src/Routes/SettingsRoutes.tsx index c4d04891cf..ccda1e8fef 100644 --- a/Dashboard/src/Routes/SettingsRoutes.tsx +++ b/Dashboard/src/Routes/SettingsRoutes.tsx @@ -1,755 +1,652 @@ -import Loader from '../Components/Loader/Loader'; -import ComponentProps from '../Pages/PageComponentProps'; -import SettingsLayout from '../Pages/Settings/Layout'; -import PageMap from '../Utils/PageMap'; -import RouteMap, { RouteUtil, SettingsRoutePath } from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; +import Loader from "../Components/Loader/Loader"; +import ComponentProps from "../Pages/PageComponentProps"; +import SettingsLayout from "../Pages/Settings/Layout"; +import PageMap from "../Utils/PageMap"; +import RouteMap, { RouteUtil, SettingsRoutePath } from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Pages const ProjectSettings: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/ProjectSettings'); - }); + lazy(() => { + return import("../Pages/Settings/ProjectSettings"); + }); const SettingsApiKeys: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/APIKeys'); - }); + lazy(() => { + return import("../Pages/Settings/APIKeys"); + }); const SettingsApiKeyView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/APIKeyView'); + return import("../Pages/Settings/APIKeyView"); }); const SettingLabels: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/Labels'); - }); + lazy(() => { + return import("../Pages/Settings/Labels"); + }); const SettingProbes: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/Probes'); - }); + lazy(() => { + return import("../Pages/Settings/Probes"); + }); const SettingFeatureFlags: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/FeatureFlags'); + return import("../Pages/Settings/FeatureFlags"); }); const SettingsTeams: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/Teams'); - }); + lazy(() => { + return import("../Pages/Settings/Teams"); + }); const SettingsTeamView: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/TeamView'); - }); + lazy(() => { + return import("../Pages/Settings/TeamView"); + }); const SettingsMonitors: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/MonitorStatus'); - }); + lazy(() => { + return import("../Pages/Settings/MonitorStatus"); + }); const SettingsIncidents: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/IncidentState'); + return import("../Pages/Settings/IncidentState"); }); const SettingsScheduledMaintenanceState: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/ScheduledMaintenanceState'); + return import("../Pages/Settings/ScheduledMaintenanceState"); }); const SettingsDomains: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/Domains'); - }); + lazy(() => { + return import("../Pages/Settings/Domains"); + }); const SettingsIncidentSeverity: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/IncidentSeverity'); + return import("../Pages/Settings/IncidentSeverity"); }); const SettingsBilling: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/Billing'); - }); + lazy(() => { + return import("../Pages/Settings/Billing"); + }); const SettingsSSO: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/SSO'); - }); + lazy(() => { + return import("../Pages/Settings/SSO"); + }); const SettingsSmsLog: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/SmsLog'); - }); + lazy(() => { + return import("../Pages/Settings/SmsLog"); + }); const SettingsCallLog: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/CallLog'); - }); + lazy(() => { + return import("../Pages/Settings/CallLog"); + }); const SettingsEmailLog: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/EmailLog'); - }); + lazy(() => { + return import("../Pages/Settings/EmailLog"); + }); const SettingsNotifications: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/NotificationSettings'); + return import("../Pages/Settings/NotificationSettings"); }); const SettingsInvoices: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/Invoices'); - }); + lazy(() => { + return import("../Pages/Settings/Invoices"); + }); const MonitorCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/MonitorCustomFields'); + return import("../Pages/Settings/MonitorCustomFields"); }); const MonitorSecrets: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Settings/MonitorSecrets'); - }); + lazy(() => { + return import("../Pages/Settings/MonitorSecrets"); + }); const StatusPageCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/StatusPageCustomFields'); + return import("../Pages/Settings/StatusPageCustomFields"); }); const IncidentCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/IncidentCustomFields'); + return import("../Pages/Settings/IncidentCustomFields"); }); const OnCallDutyPolicyCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/OnCallDutyPolicyCustomFields'); + return import("../Pages/Settings/OnCallDutyPolicyCustomFields"); }); const ScheduledMaintenanceCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/ScheduledMaintenanceCusomFields'); + return import("../Pages/Settings/ScheduledMaintenanceCusomFields"); }); const IncidentTemplates: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/IncidentTemplates'); + return import("../Pages/Settings/IncidentTemplates"); }); const IncidentTemplatesView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/IncidentTemplatesView'); + return import("../Pages/Settings/IncidentTemplatesView"); }); const IncidentNoteTemplates: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/IncidentNoteTemplates'); + return import("../Pages/Settings/IncidentNoteTemplates"); }); const IncidentNoteTemplateView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/IncidentNoteTemplateView'); + return import("../Pages/Settings/IncidentNoteTemplateView"); }); const ScheduledMaintenanceNoteTemplates: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/ScheduledMaintenanceNoteTemplates'); + return import("../Pages/Settings/ScheduledMaintenanceNoteTemplates"); }); const ScheduledMaintenanceNoteTemplateView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/ScheduledMaintenanceNoteTemplateView'); + return import("../Pages/Settings/ScheduledMaintenanceNoteTemplateView"); }); const SettingsUsageHistory: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Settings/UsageHistory'); + return import("../Pages/Settings/UsageHistory"); }); const SettingsRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute - path={SettingsRoutePath[PageMap.SETTINGS] || ''} - element={<SettingsLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <ProjectSettings - {...props} - pageRoute={RouteMap[PageMap.SETTINGS] as Route} - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_SMS_LOGS - )} - element={ - <Suspense fallback={Loader}> - <SettingsSmsLog - {...props} - pageRoute={ - RouteMap[PageMap.SETTINGS_SMS_LOGS] as Route - } - /> - </Suspense> - } - /> + return ( + <Routes> + <PageRoute + path={SettingsRoutePath[PageMap.SETTINGS] || ""} + element={<SettingsLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <ProjectSettings + {...props} + pageRoute={RouteMap[PageMap.SETTINGS] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_SMS_LOGS)} + element={ + <Suspense fallback={Loader}> + <SettingsSmsLog + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_SMS_LOGS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_INCIDENT_TEMPLATES - )} - element={ - <Suspense fallback={Loader}> - <IncidentTemplates - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_INCIDENT_TEMPLATES - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_INCIDENT_TEMPLATES, + )} + element={ + <Suspense fallback={Loader}> + <IncidentTemplates + {...props} + pageRoute={ + RouteMap[PageMap.SETTINGS_INCIDENT_TEMPLATES] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW, - 2 - )} - element={ - <Suspense fallback={Loader}> - <IncidentTemplatesView - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW, + 2, + )} + element={ + <Suspense fallback={Loader}> + <IncidentTemplatesView + {...props} + pageRoute={ + RouteMap[PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_USAGE_HISTORY - )} - element={ - <Suspense fallback={Loader}> - <SettingsUsageHistory - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_USAGE_HISTORY - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_USAGE_HISTORY)} + element={ + <Suspense fallback={Loader}> + <SettingsUsageHistory + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_USAGE_HISTORY] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_FEATURE_FLAGS - )} - element={ - <Suspense fallback={Loader}> - <SettingFeatureFlags - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_FEATURE_FLAGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_FEATURE_FLAGS)} + element={ + <Suspense fallback={Loader}> + <SettingFeatureFlags + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_FEATURE_FLAGS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES - )} - element={ - <Suspense fallback={Loader}> - <IncidentNoteTemplates - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES, + )} + element={ + <Suspense fallback={Loader}> + <IncidentNoteTemplates + {...props} + pageRoute={ + RouteMap[PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW, - 2 - )} - element={ - <Suspense fallback={Loader}> - <IncidentNoteTemplateView - {...props} - pageRoute={ - RouteMap[ - PageMap - .SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW, + 2, + )} + element={ + <Suspense fallback={Loader}> + <IncidentNoteTemplateView + {...props} + pageRoute={ + RouteMap[ + PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES - )} - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceNoteTemplates - {...props} - pageRoute={ - RouteMap[ - PageMap - .SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES, + )} + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceNoteTemplates + {...props} + pageRoute={ + RouteMap[ + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW, - 2 - )} - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceNoteTemplateView - {...props} - pageRoute={ - RouteMap[ - PageMap - .SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW, + 2, + )} + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceNoteTemplateView + {...props} + pageRoute={ + RouteMap[ + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_CALL_LOGS - )} - element={ - <Suspense fallback={Loader}> - <SettingsCallLog - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_CALL_LOGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_CALL_LOGS)} + element={ + <Suspense fallback={Loader}> + <SettingsCallLog + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_CALL_LOGS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_EMAIL_LOGS - )} - element={ - <Suspense fallback={Loader}> - <SettingsEmailLog - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_EMAIL_LOGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_EMAIL_LOGS)} + element={ + <Suspense fallback={Loader}> + <SettingsEmailLog + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_EMAIL_LOGS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_NOTIFICATION_SETTINGS - )} - element={ - <Suspense fallback={Loader}> - <SettingsNotifications - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_NOTIFICATION_SETTINGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_NOTIFICATION_SETTINGS, + )} + element={ + <Suspense fallback={Loader}> + <SettingsNotifications + {...props} + pageRoute={ + RouteMap[PageMap.SETTINGS_NOTIFICATION_SETTINGS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_MONITORS_STATUS - )} - element={ - <Suspense fallback={Loader}> - <SettingsMonitors - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_MONITORS_STATUS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_MONITORS_STATUS)} + element={ + <Suspense fallback={Loader}> + <SettingsMonitors + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_MONITORS_STATUS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_INCIDENTS_STATE - )} - element={ - <Suspense fallback={Loader}> - <SettingsIncidents - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_INCIDENTS_STATE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_INCIDENTS_STATE)} + element={ + <Suspense fallback={Loader}> + <SettingsIncidents + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_INCIDENTS_STATE] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE - )} - element={ - <Suspense fallback={Loader}> - <SettingsScheduledMaintenanceState - {...props} - pageRoute={ - RouteMap[ - PageMap - .SETTINGS_SCHEDULED_MAINTENANCE_STATE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE, + )} + element={ + <Suspense fallback={Loader}> + <SettingsScheduledMaintenanceState + {...props} + pageRoute={ + RouteMap[ + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_SSO)} - element={ - <Suspense fallback={Loader}> - <SettingsSSO - {...props} - pageRoute={ - RouteMap[PageMap.SETTINGS_SSO] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_SSO)} + element={ + <Suspense fallback={Loader}> + <SettingsSSO + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_SSO] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_INCIDENTS_SEVERITY - )} - element={ - <Suspense fallback={Loader}> - <SettingsIncidentSeverity - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_INCIDENTS_SEVERITY - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_INCIDENTS_SEVERITY, + )} + element={ + <Suspense fallback={Loader}> + <SettingsIncidentSeverity + {...props} + pageRoute={ + RouteMap[PageMap.SETTINGS_INCIDENTS_SEVERITY] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_DOMAINS)} - element={ - <Suspense fallback={Loader}> - <SettingsDomains - {...props} - pageRoute={ - RouteMap[PageMap.SETTINGS_DOMAINS] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_DOMAINS)} + element={ + <Suspense fallback={Loader}> + <SettingsDomains + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_DOMAINS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_APIKEYS)} - element={ - <Suspense fallback={Loader}> - <SettingsApiKeys - {...props} - pageRoute={ - RouteMap[PageMap.SETTINGS_APIKEYS] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_APIKEYS)} + element={ + <Suspense fallback={Loader}> + <SettingsApiKeys + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_APIKEYS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_APIKEY_VIEW, - 2 - )} - element={ - <Suspense fallback={Loader}> - <SettingsApiKeyView - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_APIKEY_VIEW - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_APIKEY_VIEW, 2)} + element={ + <Suspense fallback={Loader}> + <SettingsApiKeyView + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_APIKEY_VIEW] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <MonitorCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS, + )} + element={ + <Suspense fallback={Loader}> + <MonitorCustomFields + {...props} + pageRoute={ + RouteMap[PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_MONITOR_SECRETS - )} - element={ - <Suspense fallback={Loader}> - <MonitorSecrets - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_MONITOR_SECRETS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_MONITOR_SECRETS)} + element={ + <Suspense fallback={Loader}> + <MonitorSecrets + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_MONITOR_SECRETS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <StatusPageCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap - .SETTINGS_STATUS_PAGE_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPageCustomFields + {...props} + pageRoute={ + RouteMap[PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <ScheduledMaintenanceCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap - .SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS, + )} + element={ + <Suspense fallback={Loader}> + <ScheduledMaintenanceCustomFields + {...props} + pageRoute={ + RouteMap[ + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <IncidentCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS, + )} + element={ + <Suspense fallback={Loader}> + <IncidentCustomFields + {...props} + pageRoute={ + RouteMap[PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <OnCallDutyPolicyCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap - .SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS, + )} + element={ + <Suspense fallback={Loader}> + <OnCallDutyPolicyCustomFields + {...props} + pageRoute={ + RouteMap[ + PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_BILLING)} - element={ - <Suspense fallback={Loader}> - <SettingsBilling - {...props} - pageRoute={ - RouteMap[PageMap.SETTINGS_BILLING] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_BILLING)} + element={ + <Suspense fallback={Loader}> + <SettingsBilling + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_BILLING] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_BILLING_INVOICES - )} - element={ - <Suspense fallback={Loader}> - <SettingsInvoices - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_BILLING_INVOICES - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_BILLING_INVOICES)} + element={ + <Suspense fallback={Loader}> + <SettingsInvoices + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_BILLING_INVOICES] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_LABELS)} - element={ - <Suspense fallback={Loader}> - <SettingLabels - {...props} - pageRoute={ - RouteMap[PageMap.SETTINGS_LABELS] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_LABELS)} + element={ + <Suspense fallback={Loader}> + <SettingLabels + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_LABELS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_PROBES)} - element={ - <Suspense fallback={Loader}> - <SettingProbes - {...props} - pageRoute={ - RouteMap[PageMap.SETTINGS_PROBES] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_PROBES)} + element={ + <Suspense fallback={Loader}> + <SettingProbes + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_PROBES] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_TEAMS)} - element={ - <Suspense fallback={Loader}> - <SettingsTeams - {...props} - pageRoute={ - RouteMap[PageMap.SETTINGS_TEAMS] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_TEAMS)} + element={ + <Suspense fallback={Loader}> + <SettingsTeams + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_TEAMS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.SETTINGS_TEAM_VIEW, - 2 - )} - element={ - <Suspense fallback={Loader}> - <SettingsTeamView - {...props} - pageRoute={ - RouteMap[ - PageMap.SETTINGS_TEAM_VIEW - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.SETTINGS_TEAM_VIEW, 2)} + element={ + <Suspense fallback={Loader}> + <SettingsTeamView + {...props} + pageRoute={RouteMap[PageMap.SETTINGS_TEAM_VIEW] as Route} + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default SettingsRoutes; diff --git a/Dashboard/src/Routes/StatusPagesRoutes.tsx b/Dashboard/src/Routes/StatusPagesRoutes.tsx index fab3cf950c..06aca1811c 100644 --- a/Dashboard/src/Routes/StatusPagesRoutes.tsx +++ b/Dashboard/src/Routes/StatusPagesRoutes.tsx @@ -1,596 +1,515 @@ -import Loader from '../Components/Loader/Loader'; -import ComponentProps from '../Pages/PageComponentProps'; -import StatusPageViewLayout from '../Pages/StatusPages/View/Layout'; -import PageMap from '../Utils/PageMap'; -import RouteMap, { RouteUtil, StatusPagesRoutePath } from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; +import Loader from "../Components/Loader/Loader"; +import ComponentProps from "../Pages/PageComponentProps"; +import StatusPageViewLayout from "../Pages/StatusPages/View/Layout"; +import PageMap from "../Utils/PageMap"; +import RouteMap, { RouteUtil, StatusPagesRoutePath } from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Pages const StatusPages: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/StatusPages/StatusPages'); - }); + lazy(() => { + return import("../Pages/StatusPages/StatusPages"); + }); const StatusPagesView: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/StatusPages/View/Index'); - }); + lazy(() => { + return import("../Pages/StatusPages/View/Index"); + }); const StatusPagesViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/Delete'); + return import("../Pages/StatusPages/View/Delete"); }); const StatusPagesViewBranding: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/Branding'); + return import("../Pages/StatusPages/View/Branding"); }); const StatusPagesViewEmailSubscribers: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/EmailSubscribers'); + return import("../Pages/StatusPages/View/EmailSubscribers"); }); const StatusPagesViewSMSSubscribers: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/SMSSubscribers'); + return import("../Pages/StatusPages/View/SMSSubscribers"); }); const StatusPagesViewWebhookSubscribers: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/WebhookSubscribers'); + return import("../Pages/StatusPages/View/WebhookSubscribers"); }); const StatusPagesViewEmbedded: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/Embedded'); + return import("../Pages/StatusPages/View/Embedded"); }); const StatusPagesViewDomains: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/Domains'); + return import("../Pages/StatusPages/View/Domains"); }); const StatusPagesViewResources: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/Resources'); + return import("../Pages/StatusPages/View/Resources"); }); const StatusPagesViewAnnouncement: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/Announcements'); + return import("../Pages/StatusPages/View/Announcements"); }); const StatusPagesViewAdvancedOptions: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/AdvancedOptions'); + return import("../Pages/StatusPages/View/AdvancedOptions"); }); const StatusPagesViewCustomHtmlCss: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/CustomHtmlCss'); + return import("../Pages/StatusPages/View/CustomHtmlCss"); }); const StatusPagesViewHeaderStyle: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/HeaderStyle'); + return import("../Pages/StatusPages/View/HeaderStyle"); }); const StatusPagesViewFooterStyle: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/FooterStyle'); + return import("../Pages/StatusPages/View/FooterStyle"); }); const StatusPagesViewNavBarStyle: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/NavBarStyle'); + return import("../Pages/StatusPages/View/NavBarStyle"); }); const StatusPagesViewGroups: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/Groups'); + return import("../Pages/StatusPages/View/Groups"); }); const StatusPageViewSubscriberSettings: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/SubscriberSettings'); + return import("../Pages/StatusPages/View/SubscriberSettings"); }); const StatusPageViewCustomFields: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/CustomFields'); + return import("../Pages/StatusPages/View/CustomFields"); }); const StatusPageViewSSO: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/SSO'); + return import("../Pages/StatusPages/View/SSO"); }); const StatusPageViewPrivateUser: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/PrivateUser'); + return import("../Pages/StatusPages/View/PrivateUser"); }); const StatusPageViewOwners: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/Owners'); + return import("../Pages/StatusPages/View/Owners"); }); const StatusPageViewAuthenticationSettings: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/AuthenticationSettings'); + return import("../Pages/StatusPages/View/AuthenticationSettings"); }); const StatusPageViewSettings: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/StatusPageSettings'); + return import("../Pages/StatusPages/View/StatusPageSettings"); }); const StatusPagesViewOverviewPageBranding: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/StatusPages/View/OverviewPageBranding'); + return import("../Pages/StatusPages/View/OverviewPageBranding"); }); const StatusPagesRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute - path={StatusPagesRoutePath[PageMap.STATUS_PAGES] || ''} - element={ - <Suspense fallback={Loader}> - <StatusPages - {...props} - pageRoute={RouteMap[PageMap.STATUS_PAGES] as Route} - /> - </Suspense> - } + return ( + <Routes> + <PageRoute + path={StatusPagesRoutePath[PageMap.STATUS_PAGES] || ""} + element={ + <Suspense fallback={Loader}> + <StatusPages + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGES] as Route} /> + </Suspense> + } + /> - <PageRoute - path={StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW] || ''} - element={<StatusPageViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <StatusPagesView - {...props} - pageRoute={ - RouteMap[PageMap.STATUS_PAGE_VIEW] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS - )} - element={ - <Suspense fallback={Loader}> - <StatusPageViewSubscriberSettings - {...props} - pageRoute={ - RouteMap[PageMap.STATUS_PAGES] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_BRANDING - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewBranding - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_BRANDING - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW] || ""} + element={<StatusPageViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <StatusPagesView + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGE_VIEW] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPageViewSubscriberSettings + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGES] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.STATUS_PAGE_VIEW_DELETE)} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewDelete + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGE_VIEW_DELETE] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.STATUS_PAGE_VIEW_BRANDING)} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewBranding + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGE_VIEW_BRANDING] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewCustomHtmlCss - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewCustomHtmlCss + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewAdvancedOptions - {...props} - pageRoute={ - RouteMap[ - PageMap - .STATUS_PAGE_VIEW_ADVANCED_OPTIONS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewAdvancedOptions + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS - )} - element={ - <Suspense fallback={Loader}> - <StatusPageViewCustomFields - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPageViewCustomFields + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_OWNERS - )} - element={ - <Suspense fallback={Loader}> - <StatusPageViewOwners - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_OWNERS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.STATUS_PAGE_VIEW_OWNERS)} + element={ + <Suspense fallback={Loader}> + <StatusPageViewOwners + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGE_VIEW_OWNERS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_SSO - )} - element={ - <Suspense fallback={Loader}> - <StatusPageViewSSO - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_SSO - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.STATUS_PAGE_VIEW_SSO)} + element={ + <Suspense fallback={Loader}> + <StatusPageViewSSO + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGE_VIEW_SSO] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewEmailSubscribers - {...props} - pageRoute={ - RouteMap[ - PageMap - .STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewEmailSubscribers + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS - )} - element={ - <Suspense fallback={Loader}> - <StatusPageViewAuthenticationSettings - {...props} - pageRoute={ - RouteMap[ - PageMap - .STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPageViewAuthenticationSettings + {...props} + pageRoute={ + RouteMap[ + PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_SETTINGS - )} - element={ - <Suspense fallback={Loader}> - <StatusPageViewSettings - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_SETTINGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.STATUS_PAGE_VIEW_SETTINGS)} + element={ + <Suspense fallback={Loader}> + <StatusPageViewSettings + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGE_VIEW_SETTINGS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS - )} - element={ - <Suspense fallback={Loader}> - <StatusPageViewPrivateUser - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPageViewPrivateUser + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewSMSSubscribers - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewSMSSubscribers + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_HEADER_STYLE - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewHeaderStyle - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_HEADER_STYLE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_HEADER_STYLE, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewHeaderStyle + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_HEADER_STYLE] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewFooterStyle - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewFooterStyle + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewOverviewPageBranding - {...props} - pageRoute={ - RouteMap[ - PageMap - .STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewOverviewPageBranding + {...props} + pageRoute={ + RouteMap[ + PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewNavBarStyle - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewNavBarStyle + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewWebhookSubscribers - {...props} - pageRoute={ - RouteMap[ - PageMap - .STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewWebhookSubscribers + {...props} + pageRoute={ + RouteMap[ + PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS + ] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_EMBEDDED - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewEmbedded - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_EMBEDDED - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.STATUS_PAGE_VIEW_EMBEDDED)} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewEmbedded + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGE_VIEW_EMBEDDED] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_RESOURCES - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewResources - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_RESOURCES - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.STATUS_PAGE_VIEW_RESOURCES)} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewResources + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_RESOURCES] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_DOMAINS - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewDomains - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_DOMAINS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.STATUS_PAGE_VIEW_DOMAINS)} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewDomains + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGE_VIEW_DOMAINS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_GROUPS - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewGroups - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_GROUPS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.STATUS_PAGE_VIEW_GROUPS)} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewGroups + {...props} + pageRoute={RouteMap[PageMap.STATUS_PAGE_VIEW_GROUPS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS - )} - element={ - <Suspense fallback={Loader}> - <StatusPagesViewAnnouncement - {...props} - pageRoute={ - RouteMap[ - PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS, + )} + element={ + <Suspense fallback={Loader}> + <StatusPagesViewAnnouncement + {...props} + pageRoute={ + RouteMap[PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS] as Route + } + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default StatusPagesRoutes; diff --git a/Dashboard/src/Routes/TelemetryRoutes.tsx b/Dashboard/src/Routes/TelemetryRoutes.tsx index a876cd6ccf..d58645b6cf 100644 --- a/Dashboard/src/Routes/TelemetryRoutes.tsx +++ b/Dashboard/src/Routes/TelemetryRoutes.tsx @@ -1,299 +1,275 @@ -import Loader from '../Components/Loader/Loader'; -import ComponentProps from '../Pages/PageComponentProps'; -import TelemetryServiceViewLayout from '../Pages/Telemetry/Services/View/Layout'; -import PageMap from '../Utils/PageMap'; -import RouteMap, { RouteUtil, TelemetryRouthPath } from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; +import Loader from "../Components/Loader/Loader"; +import ComponentProps from "../Pages/PageComponentProps"; +import TelemetryServiceViewLayout from "../Pages/Telemetry/Services/View/Layout"; +import PageMap from "../Utils/PageMap"; +import RouteMap, { RouteUtil, TelemetryRouthPath } from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Lazy Pages const TelemetryServices: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services'); + return import("../Pages/Telemetry/Services"); }); const TelemetryServiceView: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Index'); + return import("../Pages/Telemetry/Services/View/Index"); }); const TelemetryServiceViewDelete: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Delete'); + return import("../Pages/Telemetry/Services/View/Delete"); }); const TelemetryServiceViewLogs: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Logs/Index'); + return import("../Pages/Telemetry/Services/View/Logs/Index"); }); const TelemetryServiceViewTraces: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Traces/Index'); + return import("../Pages/Telemetry/Services/View/Traces/Index"); }); const TelemetryServiceViewTrace: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Traces/View/Index'); + return import("../Pages/Telemetry/Services/View/Traces/View/Index"); }); const TelemetryServiceViewMetric: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Metrics/View/Index'); + return import("../Pages/Telemetry/Services/View/Metrics/View/Index"); }); const TelemetryServiceViewMetrics: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Metrics/Index'); + return import("../Pages/Telemetry/Services/View/Metrics/Index"); }); const TelemetryServiceViewDashboard: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Dashboard/Index'); + return import("../Pages/Telemetry/Services/View/Dashboard/Index"); }); const TelemetryServicesViewSettings: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Settings'); + return import("../Pages/Telemetry/Services/View/Settings"); }); const TelemetryServicesViewDocumentation: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Telemetry/Services/View/Documentation'); + return import("../Pages/Telemetry/Services/View/Documentation"); }); const TelemetryRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <TelemetryServices - {...props} - pageRoute={RouteMap[PageMap.TELEMETRY] as Route} - /> - </Suspense> - } + return ( + <Routes> + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <TelemetryServices + {...props} + pageRoute={RouteMap[PageMap.TELEMETRY] as Route} /> + </Suspense> + } + /> - <PageRoute - path={TelemetryRouthPath[PageMap.TELEMETRY_SERVICES] || ''} - element={ - <Suspense fallback={Loader}> - <TelemetryServices - {...props} - pageRoute={ - RouteMap[PageMap.TELEMETRY_SERVICES] as Route - } - /> - </Suspense> - } + <PageRoute + path={TelemetryRouthPath[PageMap.TELEMETRY_SERVICES] || ""} + element={ + <Suspense fallback={Loader}> + <TelemetryServices + {...props} + pageRoute={RouteMap[PageMap.TELEMETRY_SERVICES] as Route} /> + </Suspense> + } + /> - <PageRoute - path={TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW] || ''} - element={<TelemetryServiceViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <TelemetryServiceView - {...props} - pageRoute={ - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.TELEMETRY_SERVICES_VIEW_DELETE - )} - element={ - <Suspense fallback={Loader}> - <TelemetryServiceViewDelete - {...props} - pageRoute={ - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_DELETE - ] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.TELEMETRY_SERVICES_VIEW_LOGS - )} - element={ - <Suspense fallback={Loader}> - <TelemetryServiceViewLogs - {...props} - pageRoute={ - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_LOGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW] || ""} + element={<TelemetryServiceViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <TelemetryServiceView + {...props} + pageRoute={RouteMap[PageMap.TELEMETRY_SERVICES_VIEW] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.TELEMETRY_SERVICES_VIEW_DELETE, + )} + element={ + <Suspense fallback={Loader}> + <TelemetryServiceViewDelete + {...props} + pageRoute={ + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_DELETE] as Route + } + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.TELEMETRY_SERVICES_VIEW_LOGS, + )} + element={ + <Suspense fallback={Loader}> + <TelemetryServiceViewLogs + {...props} + pageRoute={ + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_LOGS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.TELEMETRY_SERVICES_VIEW_TRACE, - 2 - )} - element={ - <Suspense fallback={Loader}> - <TelemetryServiceViewTrace - {...props} - pageRoute={ - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_TRACE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.TELEMETRY_SERVICES_VIEW_TRACE, + 2, + )} + element={ + <Suspense fallback={Loader}> + <TelemetryServiceViewTrace + {...props} + pageRoute={ + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_TRACE] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.TELEMETRY_SERVICES_VIEW_TRACES - )} - element={ - <Suspense fallback={Loader}> - <TelemetryServiceViewTraces - {...props} - pageRoute={ - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_TRACES - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.TELEMETRY_SERVICES_VIEW_TRACES, + )} + element={ + <Suspense fallback={Loader}> + <TelemetryServiceViewTraces + {...props} + pageRoute={ + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_TRACES] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.TELEMETRY_SERVICES_VIEW_METRIC, - 2 - )} - element={ - <Suspense fallback={Loader}> - <TelemetryServiceViewMetric - {...props} - pageRoute={ - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_METRIC - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.TELEMETRY_SERVICES_VIEW_METRIC, + 2, + )} + element={ + <Suspense fallback={Loader}> + <TelemetryServiceViewMetric + {...props} + pageRoute={ + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_METRIC] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.TELEMETRY_SERVICES_VIEW_METRICS - )} - element={ - <Suspense fallback={Loader}> - <TelemetryServiceViewMetrics - {...props} - pageRoute={ - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_METRICS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.TELEMETRY_SERVICES_VIEW_METRICS, + )} + element={ + <Suspense fallback={Loader}> + <TelemetryServiceViewMetrics + {...props} + pageRoute={ + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_METRICS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS - )} - element={ - <Suspense fallback={Loader}> - <TelemetryServiceViewDashboard - {...props} - pageRoute={ - RouteMap[ - PageMap - .TELEMETRY_SERVICES_VIEW_DASHBOARDS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS, + )} + element={ + <Suspense fallback={Loader}> + <TelemetryServiceViewDashboard + {...props} + pageRoute={ + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS - )} - element={ - <Suspense fallback={Loader}> - <TelemetryServicesViewSettings - {...props} - pageRoute={ - RouteMap[ - PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS, + )} + element={ + <Suspense fallback={Loader}> + <TelemetryServicesViewSettings + {...props} + pageRoute={ + RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION - )} - element={ - <Suspense fallback={Loader}> - <TelemetryServicesViewDocumentation - {...props} - pageRoute={ - RouteMap[ - PageMap - .TELEMETRY_SERVICES_VIEW_DOCUMENTATION - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey( + PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION, + )} + element={ + <Suspense fallback={Loader}> + <TelemetryServicesViewDocumentation + {...props} + pageRoute={ + RouteMap[ + PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION + ] as Route + } + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default TelemetryRoutes; diff --git a/Dashboard/src/Routes/UserSettingsRoutes.tsx b/Dashboard/src/Routes/UserSettingsRoutes.tsx index a38ed9ba32..20cc7fc72a 100644 --- a/Dashboard/src/Routes/UserSettingsRoutes.tsx +++ b/Dashboard/src/Routes/UserSettingsRoutes.tsx @@ -1,169 +1,147 @@ -import Loader from '../Components/Loader/Loader'; -import ComponentProps from '../Pages/PageComponentProps'; -import UserSettingsLayout from '../Pages/UserSettings/Layout'; -import PageMap from '../Utils/PageMap'; -import RouteMap, { UserSettingsRoutePath } from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; +import Loader from "../Components/Loader/Loader"; +import ComponentProps from "../Pages/PageComponentProps"; +import UserSettingsLayout from "../Pages/UserSettings/Layout"; +import PageMap from "../Utils/PageMap"; +import RouteMap, { UserSettingsRoutePath } from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Pages const UserSettingsNotificationMethods: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/UserSettings/NotificationMethods'); + return import("../Pages/UserSettings/NotificationMethods"); }); const UserSettingsNotificationRules: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/UserSettings/OnCallRules'); + return import("../Pages/UserSettings/OnCallRules"); }); const UserSettingsNotificationLogs: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/UserSettings/OnCallLogs'); + return import("../Pages/UserSettings/OnCallLogs"); }); const UserSettingsNotificationLogsTimeline: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/UserSettings/OnCallLogsTimeline'); + return import("../Pages/UserSettings/OnCallLogsTimeline"); }); const UserSettingsNotiifcationSetting: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/UserSettings/NotificationSettings'); + return import("../Pages/UserSettings/NotificationSettings"); }); const UserSettingsRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute element={<UserSettingsLayout {...props} />}> - <PageRoute - path={UserSettingsRoutePath[PageMap.USER_SETTINGS] || ''} - element={ - <Suspense fallback={Loader}> - <UserSettingsNotificationMethods - {...props} - pageRoute={ - RouteMap[PageMap.USER_SETTINGS] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={ - UserSettingsRoutePath[ - PageMap.USER_SETTINGS_ON_CALL_LOGS - ] || '' - } - element={ - <Suspense fallback={Loader}> - <UserSettingsNotificationLogs - {...props} - pageRoute={ - RouteMap[ - PageMap.USER_SETTINGS_ON_CALL_LOGS - ] as Route - } - /> - </Suspense> - } - /> + return ( + <Routes> + <PageRoute element={<UserSettingsLayout {...props} />}> + <PageRoute + path={UserSettingsRoutePath[PageMap.USER_SETTINGS] || ""} + element={ + <Suspense fallback={Loader}> + <UserSettingsNotificationMethods + {...props} + pageRoute={RouteMap[PageMap.USER_SETTINGS] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={UserSettingsRoutePath[PageMap.USER_SETTINGS_ON_CALL_LOGS] || ""} + element={ + <Suspense fallback={Loader}> + <UserSettingsNotificationLogs + {...props} + pageRoute={ + RouteMap[PageMap.USER_SETTINGS_ON_CALL_LOGS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={ - UserSettingsRoutePath[ - PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE - ] || '' - } - element={ - <Suspense fallback={Loader}> - <UserSettingsNotificationLogsTimeline - {...props} - pageRoute={ - RouteMap[ - PageMap - .USER_SETTINGS_ON_CALL_LOGS_TIMELINE - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={ + UserSettingsRoutePath[ + PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE + ] || "" + } + element={ + <Suspense fallback={Loader}> + <UserSettingsNotificationLogsTimeline + {...props} + pageRoute={ + RouteMap[PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={ - UserSettingsRoutePath[ - PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS - ] || '' - } - element={ - <Suspense fallback={Loader}> - <UserSettingsNotiifcationSetting - {...props} - pageRoute={ - RouteMap[ - PageMap - .USER_SETTINGS_NOTIFICATION_SETTINGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={ + UserSettingsRoutePath[ + PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS + ] || "" + } + element={ + <Suspense fallback={Loader}> + <UserSettingsNotiifcationSetting + {...props} + pageRoute={ + RouteMap[PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={ - UserSettingsRoutePath[ - PageMap.USER_SETTINGS_NOTIFICATION_METHODS - ] || '' - } - element={ - <Suspense fallback={Loader}> - <UserSettingsNotificationMethods - {...props} - pageRoute={ - RouteMap[ - PageMap - .USER_SETTINGS_NOTIFICATION_METHODS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={ + UserSettingsRoutePath[PageMap.USER_SETTINGS_NOTIFICATION_METHODS] || + "" + } + element={ + <Suspense fallback={Loader}> + <UserSettingsNotificationMethods + {...props} + pageRoute={ + RouteMap[PageMap.USER_SETTINGS_NOTIFICATION_METHODS] as Route + } + /> + </Suspense> + } + /> - <PageRoute - path={ - UserSettingsRoutePath[ - PageMap.USER_SETTINGS_ON_CALL_RULES - ] || '' - } - element={ - <Suspense fallback={Loader}> - <UserSettingsNotificationRules - {...props} - pageRoute={ - RouteMap[ - PageMap.USER_SETTINGS_ON_CALL_RULES - ] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={ + UserSettingsRoutePath[PageMap.USER_SETTINGS_ON_CALL_RULES] || "" + } + element={ + <Suspense fallback={Loader}> + <UserSettingsNotificationRules + {...props} + pageRoute={ + RouteMap[PageMap.USER_SETTINGS_ON_CALL_RULES] as Route + } + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default UserSettingsRoutes; diff --git a/Dashboard/src/Routes/WorkflowRoutes.tsx b/Dashboard/src/Routes/WorkflowRoutes.tsx index b26582cd57..0b4b71bd8d 100644 --- a/Dashboard/src/Routes/WorkflowRoutes.tsx +++ b/Dashboard/src/Routes/WorkflowRoutes.tsx @@ -1,206 +1,180 @@ -import Loader from '../Components/Loader/Loader'; -import ComponentProps from '../Pages/PageComponentProps'; -import WorkflowsLayout from '../Pages/Workflow/Layout'; -import WorkflowViewLayout from '../Pages/Workflow/View/Layout'; -import PageMap from '../Utils/PageMap'; -import RouteMap, { RouteUtil, WorkflowRoutePath } from '../Utils/RouteMap'; -import Route from 'Common/Types/API/Route'; +import Loader from "../Components/Loader/Loader"; +import ComponentProps from "../Pages/PageComponentProps"; +import WorkflowsLayout from "../Pages/Workflow/Layout"; +import WorkflowViewLayout from "../Pages/Workflow/View/Layout"; +import PageMap from "../Utils/PageMap"; +import RouteMap, { RouteUtil, WorkflowRoutePath } from "../Utils/RouteMap"; +import Route from "Common/Types/API/Route"; import React, { - FunctionComponent, - LazyExoticComponent, - ReactElement, - Suspense, - lazy, -} from 'react'; -import { Route as PageRoute, Routes } from 'react-router-dom'; + FunctionComponent, + LazyExoticComponent, + ReactElement, + Suspense, + lazy, +} from "react"; +import { Route as PageRoute, Routes } from "react-router-dom"; // Lazy Pages const Workflows: LazyExoticComponent<FunctionComponent<ComponentProps>> = lazy( - () => { - return import('../Pages/Workflow/Workflows'); - } + () => { + return import("../Pages/Workflow/Workflows"); + }, ); const WorkflowsVariables: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Workflow/Variable'); + return import("../Pages/Workflow/Variable"); }); const WorkflowsLogs: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Workflow/Logs'); - }); + lazy(() => { + return import("../Pages/Workflow/Logs"); + }); const WorkflowLogs: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Workflow/View/Logs'); - }); + lazy(() => { + return import("../Pages/Workflow/View/Logs"); + }); const WorkflowDelete: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Workflow/View/Delete'); - }); + lazy(() => { + return import("../Pages/Workflow/View/Delete"); + }); const WorkflowBuilder: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Workflow/View/Builder'); - }); + lazy(() => { + return import("../Pages/Workflow/View/Builder"); + }); const WorkflowOverview: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Workflow/View/Index'); - }); + lazy(() => { + return import("../Pages/Workflow/View/Index"); + }); const WorkflowVariables: LazyExoticComponent< - FunctionComponent<ComponentProps> + FunctionComponent<ComponentProps> > = lazy(() => { - return import('../Pages/Workflow/View/Variable'); + return import("../Pages/Workflow/View/Variable"); }); const WorkflowSettings: LazyExoticComponent<FunctionComponent<ComponentProps>> = - lazy(() => { - return import('../Pages/Workflow/View/Settings'); - }); + lazy(() => { + return import("../Pages/Workflow/View/Settings"); + }); const WorkflowRoutes: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <Routes> - <PageRoute path="/" element={<WorkflowsLayout {...props} />}> - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <Workflows - {...props} - pageRoute={RouteMap[PageMap.WORKFLOWS] as Route} - /> - </Suspense> - } - /> + return ( + <Routes> + <PageRoute path="/" element={<WorkflowsLayout {...props} />}> + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <Workflows + {...props} + pageRoute={RouteMap[PageMap.WORKFLOWS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={WorkflowRoutePath[PageMap.WORKFLOWS_VARIABLES] || ''} - element={ - <Suspense fallback={Loader}> - <WorkflowsVariables - {...props} - pageRoute={ - RouteMap[ - PageMap.WORKFLOWS_VARIABLES - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={WorkflowRoutePath[PageMap.WORKFLOWS_VARIABLES] || ""} + element={ + <Suspense fallback={Loader}> + <WorkflowsVariables + {...props} + pageRoute={RouteMap[PageMap.WORKFLOWS_VARIABLES] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={WorkflowRoutePath[PageMap.WORKFLOWS_LOGS] || ''} - element={ - <Suspense fallback={Loader}> - <WorkflowsLogs - {...props} - pageRoute={ - RouteMap[PageMap.WORKFLOWS_LOGS] as Route - } - /> - </Suspense> - } - /> - </PageRoute> + <PageRoute + path={WorkflowRoutePath[PageMap.WORKFLOWS_LOGS] || ""} + element={ + <Suspense fallback={Loader}> + <WorkflowsLogs + {...props} + pageRoute={RouteMap[PageMap.WORKFLOWS_LOGS] as Route} + /> + </Suspense> + } + /> + </PageRoute> - <PageRoute - path={WorkflowRoutePath[PageMap.WORKFLOW_VIEW] || ''} - element={<WorkflowViewLayout {...props} />} - > - <PageRoute - index - element={ - <Suspense fallback={Loader}> - <WorkflowOverview - {...props} - pageRoute={ - RouteMap[PageMap.WORKFLOW_VIEW] as Route - } - /> - </Suspense> - } - /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.WORKFLOW_VIEW_SETTINGS - )} - element={ - <Suspense fallback={Loader}> - <WorkflowSettings - {...props} - pageRoute={ - RouteMap[ - PageMap.WORKFLOW_VIEW_SETTINGS - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={WorkflowRoutePath[PageMap.WORKFLOW_VIEW] || ""} + element={<WorkflowViewLayout {...props} />} + > + <PageRoute + index + element={ + <Suspense fallback={Loader}> + <WorkflowOverview + {...props} + pageRoute={RouteMap[PageMap.WORKFLOW_VIEW] as Route} + /> + </Suspense> + } + /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.WORKFLOW_VIEW_SETTINGS)} + element={ + <Suspense fallback={Loader}> + <WorkflowSettings + {...props} + pageRoute={RouteMap[PageMap.WORKFLOW_VIEW_SETTINGS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey( - PageMap.WORKFLOW_VARIABLES - )} - element={ - <Suspense fallback={Loader}> - <WorkflowVariables - {...props} - pageRoute={ - RouteMap[ - PageMap.WORKFLOW_VARIABLES - ] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.WORKFLOW_VARIABLES)} + element={ + <Suspense fallback={Loader}> + <WorkflowVariables + {...props} + pageRoute={RouteMap[PageMap.WORKFLOW_VARIABLES] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.WORKFLOW_BUILDER)} - element={ - <Suspense fallback={Loader}> - <WorkflowBuilder - {...props} - pageRoute={ - RouteMap[PageMap.WORKFLOW_BUILDER] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.WORKFLOW_BUILDER)} + element={ + <Suspense fallback={Loader}> + <WorkflowBuilder + {...props} + pageRoute={RouteMap[PageMap.WORKFLOW_BUILDER] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.WORKFLOW_LOGS)} - element={ - <Suspense fallback={Loader}> - <WorkflowLogs - {...props} - pageRoute={ - RouteMap[PageMap.WORKFLOW_LOGS] as Route - } - /> - </Suspense> - } - /> + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.WORKFLOW_LOGS)} + element={ + <Suspense fallback={Loader}> + <WorkflowLogs + {...props} + pageRoute={RouteMap[PageMap.WORKFLOW_LOGS] as Route} + /> + </Suspense> + } + /> - <PageRoute - path={RouteUtil.getLastPathForKey(PageMap.WORKFLOW_DELETE)} - element={ - <Suspense fallback={Loader}> - <WorkflowDelete - {...props} - pageRoute={ - RouteMap[PageMap.WORKFLOW_DELETE] as Route - } - /> - </Suspense> - } - /> - </PageRoute> - </Routes> - ); + <PageRoute + path={RouteUtil.getLastPathForKey(PageMap.WORKFLOW_DELETE)} + element={ + <Suspense fallback={Loader}> + <WorkflowDelete + {...props} + pageRoute={RouteMap[PageMap.WORKFLOW_DELETE] as Route} + /> + </Suspense> + } + /> + </PageRoute> + </Routes> + ); }; export default WorkflowRoutes; diff --git a/Dashboard/src/Types/RoutesProps.tsx b/Dashboard/src/Types/RoutesProps.tsx index bbf077e5bd..68a9764bcb 100644 --- a/Dashboard/src/Types/RoutesProps.tsx +++ b/Dashboard/src/Types/RoutesProps.tsx @@ -1,7 +1,7 @@ -import ComponentProps from '../Pages/PageComponentProps'; -import Project from 'Model/Models/Project'; +import ComponentProps from "../Pages/PageComponentProps"; +import Project from "Model/Models/Project"; export type RoutesProps = { - projects: Array<Project>; - isLoading: boolean; + projects: Array<Project>; + isLoading: boolean; } & ComponentProps; diff --git a/Dashboard/src/Utils/BillingProvider.ts b/Dashboard/src/Utils/BillingProvider.ts index 0fdeca060f..fe39871981 100644 --- a/Dashboard/src/Utils/BillingProvider.ts +++ b/Dashboard/src/Utils/BillingProvider.ts @@ -1,10 +1,10 @@ -import { BILLING_PUBLIC_KEY } from 'CommonUI/src/Config'; -import Stripe from 'stripe'; +import { BILLING_PUBLIC_KEY } from "CommonUI/src/Config"; +import Stripe from "stripe"; export class BillingProvider { - public static getBillingProvider(): Stripe { - return new Stripe(BILLING_PUBLIC_KEY, { - apiVersion: '2022-11-15', - }); - } + public static getBillingProvider(): Stripe { + return new Stripe(BILLING_PUBLIC_KEY, { + apiVersion: "2022-11-15", + }); + } } diff --git a/Dashboard/src/Utils/Breadcrumbs/AICopilotBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/AICopilotBreadcrumbs.ts index 432d9321db..424bec09f1 100644 --- a/Dashboard/src/Utils/Breadcrumbs/AICopilotBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/AICopilotBreadcrumbs.ts @@ -1,33 +1,29 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getCodeRepositoryBreadcrumbs( - path: string + path: string, ): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles( - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW, - ['Project', 'AI Copilot', 'View Git Repository'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE, - [ - 'Project', - 'AI Copilot', - 'View Git Repository', - 'Delete Repository', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS, - ['Project', 'AI Copilot', 'View Git Repository', 'Settings'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES, - ['Project', 'AI Copilot', 'View Git Repository', 'Services'] - ), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW, [ + "Project", + "AI Copilot", + "View Git Repository", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE, + ["Project", "AI Copilot", "View Git Repository", "Delete Repository"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS, + ["Project", "AI Copilot", "View Git Repository", "Settings"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES, + ["Project", "AI Copilot", "View Git Repository", "Services"], + ), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/Helper.ts b/Dashboard/src/Utils/Breadcrumbs/Helper.ts index 3a0c5f6c0b..0adfd9a90a 100644 --- a/Dashboard/src/Utils/Breadcrumbs/Helper.ts +++ b/Dashboard/src/Utils/Breadcrumbs/Helper.ts @@ -1,29 +1,29 @@ -import { RouteUtil } from '../RouteMap'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; -import Navigation from 'CommonUI/src/Utils/Navigation'; +import { RouteUtil } from "../RouteMap"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; +import Navigation from "CommonUI/src/Utils/Navigation"; export function BuildBreadcrumbLinks( - key: string, - breadcrumpLinks: Link[] + key: string, + breadcrumpLinks: Link[], ): Dictionary<Link[]> { - return { - [RouteUtil.getRouteString(key)]: breadcrumpLinks, - }; + return { + [RouteUtil.getRouteString(key)]: breadcrumpLinks, + }; } export function BuildBreadcrumbLinksByTitles( - key: string, - titles: Array<string> + key: string, + titles: Array<string>, ): Dictionary<Link[]> { - return { - [RouteUtil.getRouteString(key)]: titles.map( - (title: string, index: number) => { - return { - title, - to: Navigation.getBreadcrumbRoute(index + 1), - }; - } - ), - }; + return { + [RouteUtil.getRouteString(key)]: titles.map( + (title: string, index: number) => { + return { + title, + to: Navigation.getBreadcrumbRoute(index + 1), + }; + }, + ), + }; } diff --git a/Dashboard/src/Utils/Breadcrumbs/IncidentBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/IncidentBreadcrumbs.ts index a86562bea0..19cade5eb4 100644 --- a/Dashboard/src/Utils/Breadcrumbs/IncidentBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/IncidentBreadcrumbs.ts @@ -1,60 +1,60 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getIncidentsBreadcrumbs(path: string): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENTS, [ - 'Project', - 'Incidents', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.UNRESOLVED_INCIDENTS, [ - 'Project', - 'Incidents', - 'Active Incidents', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW, [ - 'Project', - 'Incidents', - 'View Incident', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_STATE_TIMELINE, [ - 'Project', - 'Incidents', - 'View Incident', - 'State Timeline', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_OWNERS, [ - 'Project', - 'Incidents', - 'View Incident', - 'Owners', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_INTERNAL_NOTE, [ - 'Project', - 'Incidents', - 'View Incident', - 'Private Notes', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_PUBLIC_NOTE, [ - 'Project', - 'Incidents', - 'View Incident', - 'Public Notes', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_CUSTOM_FIELDS, [ - 'Project', - 'Incidents', - 'View Incident', - 'Custom Fields', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_DELETE, [ - 'Project', - 'Incidents', - 'View Incident', - 'Delete Incident', - ]), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENTS, [ + "Project", + "Incidents", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.UNRESOLVED_INCIDENTS, [ + "Project", + "Incidents", + "Active Incidents", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW, [ + "Project", + "Incidents", + "View Incident", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_STATE_TIMELINE, [ + "Project", + "Incidents", + "View Incident", + "State Timeline", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_OWNERS, [ + "Project", + "Incidents", + "View Incident", + "Owners", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_INTERNAL_NOTE, [ + "Project", + "Incidents", + "View Incident", + "Private Notes", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_PUBLIC_NOTE, [ + "Project", + "Incidents", + "View Incident", + "Public Notes", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_CUSTOM_FIELDS, [ + "Project", + "Incidents", + "View Incident", + "Custom Fields", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.INCIDENT_VIEW_DELETE, [ + "Project", + "Incidents", + "View Incident", + "Delete Incident", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/MonitorBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/MonitorBreadcrumbs.ts index d182e5fec8..0c23370d6f 100644 --- a/Dashboard/src/Utils/Breadcrumbs/MonitorBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/MonitorBreadcrumbs.ts @@ -1,83 +1,80 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getMonitorBreadcrumbs(path: string): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.MONITORS, [ - 'Project', - 'Monitors', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITORS_INOPERATIONAL, [ - 'Project', - 'Monitors', - 'Inoperational', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITORS_DISABLED, [ - 'Project', - 'Monitors', - 'Disabled', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW, [ - 'Project', - 'Monitors', - 'View Monitor', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_OWNERS, [ - 'Project', - 'Monitors', - 'View Monitor', - 'Owners', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_CRITERIA, [ - 'Project', - 'Monitors', - 'View Monitor', - 'Criteria', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_INTERVAL, [ - 'Project', - 'Monitors', - 'View Monitor', - 'Interval', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_STATUS_TIMELINE, [ - 'Project', - 'Monitors', - 'View Monitor', - 'Status Timeline', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_INCIDENTS, [ - 'Project', - 'Monitors', - 'View Monitor', - 'Incidents', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_PROBES, [ - 'Project', - 'Monitors', - 'View Monitor', - 'Probes', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_CUSTOM_FIELDS, [ - 'Project', - 'Monitors', - 'View Monitor', - 'Custom Fields', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_SETTINGS, [ - 'Project', - 'Monitors', - 'View Monitor', - 'Settings', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_DELETE, [ - 'Project', - 'Monitors', - 'View Monitor', - 'Delete Monitor', - ]), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.MONITORS, ["Project", "Monitors"]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITORS_INOPERATIONAL, [ + "Project", + "Monitors", + "Inoperational", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITORS_DISABLED, [ + "Project", + "Monitors", + "Disabled", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW, [ + "Project", + "Monitors", + "View Monitor", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_OWNERS, [ + "Project", + "Monitors", + "View Monitor", + "Owners", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_CRITERIA, [ + "Project", + "Monitors", + "View Monitor", + "Criteria", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_INTERVAL, [ + "Project", + "Monitors", + "View Monitor", + "Interval", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_STATUS_TIMELINE, [ + "Project", + "Monitors", + "View Monitor", + "Status Timeline", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_INCIDENTS, [ + "Project", + "Monitors", + "View Monitor", + "Incidents", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_PROBES, [ + "Project", + "Monitors", + "View Monitor", + "Probes", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_CUSTOM_FIELDS, [ + "Project", + "Monitors", + "View Monitor", + "Custom Fields", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_SETTINGS, [ + "Project", + "Monitors", + "View Monitor", + "Settings", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_VIEW_DELETE, [ + "Project", + "Monitors", + "View Monitor", + "Delete Monitor", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/MonitorGroupBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/MonitorGroupBreadcrumbs.ts index 0e3e4d3617..b3aa228789 100644 --- a/Dashboard/src/Utils/Breadcrumbs/MonitorGroupBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/MonitorGroupBreadcrumbs.ts @@ -1,41 +1,41 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getMonitorGroupBreadcrumbs( - path: string + path: string, ): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW, [ - 'Project', - 'Monitor Groups', - 'View Monitor Group', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW_OWNERS, [ - 'Project', - 'Monitor Groups', - 'View Monitor Group', - 'Owners', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW_MONITORS, [ - 'Project', - 'Monitor Groups', - 'View Monitor Group', - 'Monitors', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW_INCIDENTS, [ - 'Project', - 'Monitor Groups', - 'View Monitor Group', - 'Incidents', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW_DELETE, [ - 'Project', - 'Monitor Groups', - 'View Monitor Group', - 'Delete Monitor Group', - ]), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW, [ + "Project", + "Monitor Groups", + "View Monitor Group", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW_OWNERS, [ + "Project", + "Monitor Groups", + "View Monitor Group", + "Owners", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW_MONITORS, [ + "Project", + "Monitor Groups", + "View Monitor Group", + "Monitors", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW_INCIDENTS, [ + "Project", + "Monitor Groups", + "View Monitor Group", + "Incidents", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.MONITOR_GROUP_VIEW_DELETE, [ + "Project", + "Monitor Groups", + "View Monitor Group", + "Delete Monitor Group", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/OnCallDutyBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/OnCallDutyBreadcrumbs.ts index 847a77928c..d00f1975ac 100644 --- a/Dashboard/src/Utils/Breadcrumbs/OnCallDutyBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/OnCallDutyBreadcrumbs.ts @@ -1,98 +1,86 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getOnCallDutyBreadcrumbs( - path: string + path: string, ): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY, [ - 'Project', - 'On-Call Duty', - 'Policies', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_POLICIES, [ - 'Project', - 'On-Call Duty', - 'Policies', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_SCHEDULES, [ - 'Project', - 'On-Call Duty', - 'Schedules', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_EXECUTION_LOGS, [ - 'Project', - 'On-Call Duty', - 'Execution Logs', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE, - ['Project', 'On-Call Duty', 'Execution Logs', 'Timeline'] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_POLICY_VIEW, [ - 'Project', - 'On-Call Duty', - 'View On-Call Policy', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION, - [ - 'Project', - 'On-Call Duty', - 'View On-Call Policy', - 'Escalation Rules', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS, - ['Project', 'On-Call Duty', 'View On-Call Policy', 'Logs'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW, - ['Project', 'On-Call Duty', 'View On-Call Policy', 'Timeline'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS, - ['Project', 'On-Call Duty', 'View On-Call Policy', 'Custom Fields'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE, - [ - 'Project', - 'On-Call Duty', - 'View On-Call Policy', - 'Delete On-Call Policy', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE, - [ - 'Project', - 'On-Call Duty', - 'View On-Call Policy', - 'Delete On-Call Policy', - ] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_SCHEDULE_VIEW, [ - 'Project', - 'On-Call Duty', - 'View On-Call Schedule', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS, - ['Project', 'On-Call Duty', 'View On-Call Schedule', 'Layers'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE, - [ - 'Project', - 'On-Call Duty', - 'View On-Call Schedule', - 'Delete On-Call Schedule', - ] - ), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY, [ + "Project", + "On-Call Duty", + "Policies", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_POLICIES, [ + "Project", + "On-Call Duty", + "Policies", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_SCHEDULES, [ + "Project", + "On-Call Duty", + "Schedules", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_EXECUTION_LOGS, [ + "Project", + "On-Call Duty", + "Execution Logs", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE, + ["Project", "On-Call Duty", "Execution Logs", "Timeline"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_POLICY_VIEW, [ + "Project", + "On-Call Duty", + "View On-Call Policy", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION, + ["Project", "On-Call Duty", "View On-Call Policy", "Escalation Rules"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS, + ["Project", "On-Call Duty", "View On-Call Policy", "Logs"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW, + ["Project", "On-Call Duty", "View On-Call Policy", "Timeline"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS, + ["Project", "On-Call Duty", "View On-Call Policy", "Custom Fields"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE, [ + "Project", + "On-Call Duty", + "View On-Call Policy", + "Delete On-Call Policy", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE, [ + "Project", + "On-Call Duty", + "View On-Call Policy", + "Delete On-Call Policy", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_SCHEDULE_VIEW, [ + "Project", + "On-Call Duty", + "View On-Call Schedule", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS, [ + "Project", + "On-Call Duty", + "View On-Call Schedule", + "Layers", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE, [ + "Project", + "On-Call Duty", + "View On-Call Schedule", + "Delete On-Call Schedule", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/ScheduledMaintenanceBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/ScheduledMaintenanceBreadcrumbs.ts index 97f42bb2e3..44ddfdd0e0 100644 --- a/Dashboard/src/Utils/Breadcrumbs/ScheduledMaintenanceBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/ScheduledMaintenanceBreadcrumbs.ts @@ -1,83 +1,74 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getScheduleMaintenanceBreadcrumbs( - path: string + path: string, ): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.SCHEDULED_MAINTENANCE_EVENTS, [ - 'Project', - 'Scheduled Maintenance Events', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS, - [ - 'Project', - 'Scheduled Maintenance Events', - 'Ongoing Scheduled Maintenance', - ] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.SCHEDULED_MAINTENANCE_VIEW, [ - 'Project', - 'Scheduled Maintenance Events', - 'View Scheduled Maintenance Event', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS, - [ - 'Project', - 'Scheduled Maintenance Events', - 'View Scheduled Maintenance Event', - 'Owners', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE, - [ - 'Project', - 'Scheduled Maintenance Events', - 'View Scheduled Maintenance Event', - 'Status Timeline', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE, - [ - 'Project', - 'Scheduled Maintenance Events', - 'View Scheduled Maintenance Event', - 'Private Notes', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE, - [ - 'Project', - 'Scheduled Maintenance Events', - 'View Scheduled Maintenance Event', - 'Public Notes', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS, - [ - 'Project', - 'Scheduled Maintenance Events', - 'View Scheduled Maintenance Event', - 'Custom Fields', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE, - [ - 'Project', - 'Scheduled Maintenance Events', - 'View Scheduled Maintenance Event', - 'Delete Scheduled Maintenance', - ] - ), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.SCHEDULED_MAINTENANCE_EVENTS, [ + "Project", + "Scheduled Maintenance Events", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS, + [ + "Project", + "Scheduled Maintenance Events", + "Ongoing Scheduled Maintenance", + ], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.SCHEDULED_MAINTENANCE_VIEW, [ + "Project", + "Scheduled Maintenance Events", + "View Scheduled Maintenance Event", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS, [ + "Project", + "Scheduled Maintenance Events", + "View Scheduled Maintenance Event", + "Owners", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE, + [ + "Project", + "Scheduled Maintenance Events", + "View Scheduled Maintenance Event", + "Status Timeline", + ], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE, + [ + "Project", + "Scheduled Maintenance Events", + "View Scheduled Maintenance Event", + "Private Notes", + ], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE, [ + "Project", + "Scheduled Maintenance Events", + "View Scheduled Maintenance Event", + "Public Notes", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS, + [ + "Project", + "Scheduled Maintenance Events", + "View Scheduled Maintenance Event", + "Custom Fields", + ], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE, [ + "Project", + "Scheduled Maintenance Events", + "View Scheduled Maintenance Event", + "Delete Scheduled Maintenance", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/ServiceCatalogBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/ServiceCatalogBreadcrumbs.ts index 020f096145..348eb90e6d 100644 --- a/Dashboard/src/Utils/Breadcrumbs/ServiceCatalogBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/ServiceCatalogBreadcrumbs.ts @@ -1,35 +1,35 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getServiceCatalogBreadcrumbs( - path: string + path: string, ): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW, [ - 'Project', - 'Service Catalog', - 'View Service', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_OWNERS, [ - 'Project', - 'Service Catalog', - 'View Service', - 'Owners', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_DELETE, [ - 'Project', - 'Service Catalog', - 'View Service', - 'Delete Service', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_SETTINGS, [ - 'Project', - 'Service Catalog', - 'View Service', - 'Settings', - ]), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW, [ + "Project", + "Service Catalog", + "View Service", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_OWNERS, [ + "Project", + "Service Catalog", + "View Service", + "Owners", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_DELETE, [ + "Project", + "Service Catalog", + "View Service", + "Delete Service", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_SETTINGS, [ + "Project", + "Service Catalog", + "View Service", + "Settings", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/SettingsBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/SettingsBreadcrumbs.ts index 16ceb7adc8..67877250c9 100644 --- a/Dashboard/src/Utils/Breadcrumbs/SettingsBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/SettingsBreadcrumbs.ts @@ -1,174 +1,180 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getSettingsBreadcrumbs(path: string): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS, [ - 'Project', - 'Project Settings', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_LABELS, [ - 'Project', - 'Settings', - 'Labels', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_MONITORS_STATUS, [ - 'Project', - 'Settings', - 'Monitor Status', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS, - ['Project', 'Settings', 'Monitor Custom Fields'] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_MONITOR_SECRETS, [ - 'Project', - 'Settings', - 'Monitor Secrets', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS, - ['Project', 'Settings', 'Status Page Custom Fields'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS, - ['Project', 'Settings', 'On-Call Puty Custom Fields'] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_INCIDENTS_STATE, [ - 'Project', - 'Settings', - 'Incident State', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_INCIDENTS_SEVERITY, [ - 'Project', - 'Settings', - 'Incident Severity', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_INCIDENT_TEMPLATES, [ - 'Project', - 'Settings', - 'Incident Templates', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW, - ['Project', 'Settings', 'Incident Templates', 'View Template'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES, - ['Project', 'Settings', 'Incident Note Templates'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW, - ['Project', 'Settings', 'Incident Note Templates', 'View Template'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS, - ['Project', 'Settings', 'Incident Custom Fields'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE, - ['Project', 'Settings', 'Scheduled Maintenance State'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES, - ['Project', 'Settings', 'Scheduled Maintenance Note Templates'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW, - [ - 'Project', - 'Settings', - 'Scheduled Maintenance Note Templates', - 'View Template', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS, - ['Project', 'Settings', 'Scheduled Maintenance Custom Fields'] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_TEAMS, [ - 'Project', - 'Settings', - 'Teams', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_TEAM_VIEW, [ - 'Project', - 'Settings', - 'Teams', - 'View Team', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.SETTINGS_NOTIFICATION_SETTINGS, - ['Project', 'Settings', 'Notification Settings'] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_SMS_LOGS, [ - 'Project', - 'Settings', - 'SMS Logs', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_CALL_LOGS, [ - 'Project', - 'Settings', - 'Call Logs', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_EMAIL_LOGS, [ - 'Project', - 'Settings', - 'Email Logs', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_PROBES, [ - 'Project', - 'Settings', - 'Probes', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_DOMAINS, [ - 'Project', - 'Settings', - 'Domains', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_APIKEYS, [ - 'Project', - 'Settings', - 'API Keys', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_APIKEY_VIEW, [ - 'Project', - 'Settings', - 'API Keys', - 'View API Key', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_FEATURE_FLAGS, [ - 'Project', - 'Settings', - 'Feature Flags', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_BILLING, [ - 'Project', - 'Settings', - 'Billing', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_USAGE_HISTORY, [ - 'Project', - 'Settings', - 'Usage History', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_BILLING_INVOICES, [ - 'Project', - 'Settings', - 'Invoices', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_SSO, [ - 'Project', - 'Settings', - 'SSO', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_DANGERZONE, [ - 'Project', - 'Settings', - 'Danger Zone', - ]), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS, [ + "Project", + "Project Settings", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_LABELS, [ + "Project", + "Settings", + "Labels", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_MONITORS_STATUS, [ + "Project", + "Settings", + "Monitor Status", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS, [ + "Project", + "Settings", + "Monitor Custom Fields", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_MONITOR_SECRETS, [ + "Project", + "Settings", + "Monitor Secrets", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS, + ["Project", "Settings", "Status Page Custom Fields"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS, + ["Project", "Settings", "On-Call Puty Custom Fields"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_INCIDENTS_STATE, [ + "Project", + "Settings", + "Incident State", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_INCIDENTS_SEVERITY, [ + "Project", + "Settings", + "Incident Severity", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_INCIDENT_TEMPLATES, [ + "Project", + "Settings", + "Incident Templates", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW, [ + "Project", + "Settings", + "Incident Templates", + "View Template", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES, [ + "Project", + "Settings", + "Incident Note Templates", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW, + ["Project", "Settings", "Incident Note Templates", "View Template"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS, [ + "Project", + "Settings", + "Incident Custom Fields", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE, + ["Project", "Settings", "Scheduled Maintenance State"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES, + ["Project", "Settings", "Scheduled Maintenance Note Templates"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW, + [ + "Project", + "Settings", + "Scheduled Maintenance Note Templates", + "View Template", + ], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS, + ["Project", "Settings", "Scheduled Maintenance Custom Fields"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_TEAMS, [ + "Project", + "Settings", + "Teams", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_TEAM_VIEW, [ + "Project", + "Settings", + "Teams", + "View Team", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_NOTIFICATION_SETTINGS, [ + "Project", + "Settings", + "Notification Settings", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_SMS_LOGS, [ + "Project", + "Settings", + "SMS Logs", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_CALL_LOGS, [ + "Project", + "Settings", + "Call Logs", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_EMAIL_LOGS, [ + "Project", + "Settings", + "Email Logs", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_PROBES, [ + "Project", + "Settings", + "Probes", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_DOMAINS, [ + "Project", + "Settings", + "Domains", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_APIKEYS, [ + "Project", + "Settings", + "API Keys", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_APIKEY_VIEW, [ + "Project", + "Settings", + "API Keys", + "View API Key", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_FEATURE_FLAGS, [ + "Project", + "Settings", + "Feature Flags", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_BILLING, [ + "Project", + "Settings", + "Billing", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_USAGE_HISTORY, [ + "Project", + "Settings", + "Usage History", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_BILLING_INVOICES, [ + "Project", + "Settings", + "Invoices", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_SSO, [ + "Project", + "Settings", + "SSO", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.SETTINGS_DANGERZONE, [ + "Project", + "Settings", + "Danger Zone", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/StatusPagesBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/StatusPagesBreadcrumbs.ts index a54f15e975..b55b422ca0 100644 --- a/Dashboard/src/Utils/Breadcrumbs/StatusPagesBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/StatusPagesBreadcrumbs.ts @@ -1,148 +1,138 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getStatusPagesBreadcrumbs( - path: string + path: string, ): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW, [ - 'Project', - 'Status Pages', - 'View Status Page', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS, - ['Project', 'Status Pages', 'View Status Page', 'Announcements'] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_OWNERS, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Owners', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_RESOURCES, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Resources', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_GROUPS, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Resource Groups', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS, - ['Project', 'Status Pages', 'View Status Page', 'Email Subscribers'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS, - ['Project', 'Status Pages', 'View Status Page', 'SMS Subscribers'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS, - [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Webhook Subscribers', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS, - [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Subscriber Settings', - ] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_BRANDING, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Essential Branding', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS, - [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Custom HTML, CSS & JavaScript', - ] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_DOMAINS, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Domains', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_HEADER_STYLE, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Header', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Footer', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING, - [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Overview Page Branding', - ] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Navbar', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS, - ['Project', 'Status Pages', 'View Status Page', 'Private Users'] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_SSO, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'SSO', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS, - [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Authentication Settings', - ] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS, - ['Project', 'Status Pages', 'View Status Page', 'Custom Fields'] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_SETTINGS, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Settings', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_DELETE, [ - 'Project', - 'Status Pages', - 'View Status Page', - 'Delete Status Page', - ]), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW, [ + "Project", + "Status Pages", + "View Status Page", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS, [ + "Project", + "Status Pages", + "View Status Page", + "Announcements", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_OWNERS, [ + "Project", + "Status Pages", + "View Status Page", + "Owners", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_RESOURCES, [ + "Project", + "Status Pages", + "View Status Page", + "Resources", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_GROUPS, [ + "Project", + "Status Pages", + "View Status Page", + "Resource Groups", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS, + ["Project", "Status Pages", "View Status Page", "Email Subscribers"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS, [ + "Project", + "Status Pages", + "View Status Page", + "SMS Subscribers", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS, + ["Project", "Status Pages", "View Status Page", "Webhook Subscribers"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS, + ["Project", "Status Pages", "View Status Page", "Subscriber Settings"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_BRANDING, [ + "Project", + "Status Pages", + "View Status Page", + "Essential Branding", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS, [ + "Project", + "Status Pages", + "View Status Page", + "Custom HTML, CSS & JavaScript", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_DOMAINS, [ + "Project", + "Status Pages", + "View Status Page", + "Domains", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_HEADER_STYLE, [ + "Project", + "Status Pages", + "View Status Page", + "Header", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE, [ + "Project", + "Status Pages", + "View Status Page", + "Footer", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING, + ["Project", "Status Pages", "View Status Page", "Overview Page Branding"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE, [ + "Project", + "Status Pages", + "View Status Page", + "Navbar", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS, [ + "Project", + "Status Pages", + "View Status Page", + "Private Users", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_SSO, [ + "Project", + "Status Pages", + "View Status Page", + "SSO", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS, + [ + "Project", + "Status Pages", + "View Status Page", + "Authentication Settings", + ], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS, [ + "Project", + "Status Pages", + "View Status Page", + "Custom Fields", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_SETTINGS, [ + "Project", + "Status Pages", + "View Status Page", + "Settings", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.STATUS_PAGE_VIEW_DELETE, [ + "Project", + "Status Pages", + "View Status Page", + "Delete Status Page", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/TelemetryBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/TelemetryBreadcrumbs.ts index aaa330c031..3de29bcec9 100644 --- a/Dashboard/src/Utils/Breadcrumbs/TelemetryBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/TelemetryBreadcrumbs.ts @@ -1,60 +1,60 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getTelemetryBreadcrumbs(path: string): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.TELEMETRY_SERVICES_VIEW, [ - 'Project', - 'Telemetry', - 'Services', - 'View Service', - 'Overview', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION, - [ - 'Project', - 'Telemetry', - 'Services', - 'View Service', - 'Documentation', - ] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.TELEMETRY_SERVICES_VIEW_LOGS, [ - 'Project', - 'Telemetry', - 'Services', - 'View Service', - 'Logs', - ]), - ...BuildBreadcrumbLinksByTitles( - PageMap.TELEMETRY_SERVICES_VIEW_METRICS, - ['Project', 'Telemetry', 'Services', 'View Service', 'Metrics'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.TELEMETRY_SERVICES_VIEW_TRACES, - ['Project', 'Telemetry', 'Services', 'View Service', 'Traces'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS, - ['Project', 'Telemetry', 'Services', 'View Service', 'Dashboards'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS, - ['Project', 'Telemetry', 'Services', 'View Service', 'Settings'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.TELEMETRY_SERVICES_VIEW_DELETE, - [ - 'Project', - 'Telemetry', - 'Services', - 'View Service', - 'Delete Service', - ] - ), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.TELEMETRY_SERVICES_VIEW, [ + "Project", + "Telemetry", + "Services", + "View Service", + "Overview", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION, + ["Project", "Telemetry", "Services", "View Service", "Documentation"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.TELEMETRY_SERVICES_VIEW_LOGS, [ + "Project", + "Telemetry", + "Services", + "View Service", + "Logs", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.TELEMETRY_SERVICES_VIEW_METRICS, [ + "Project", + "Telemetry", + "Services", + "View Service", + "Metrics", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.TELEMETRY_SERVICES_VIEW_TRACES, [ + "Project", + "Telemetry", + "Services", + "View Service", + "Traces", + ]), + ...BuildBreadcrumbLinksByTitles( + PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS, + ["Project", "Telemetry", "Services", "View Service", "Dashboards"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS, [ + "Project", + "Telemetry", + "Services", + "View Service", + "Settings", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.TELEMETRY_SERVICES_VIEW_DELETE, [ + "Project", + "Telemetry", + "Services", + "View Service", + "Delete Service", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/UserSettingsBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/UserSettingsBreadcrumbs.ts index ee51a65ad1..3a4356cb26 100644 --- a/Dashboard/src/Utils/Breadcrumbs/UserSettingsBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/UserSettingsBreadcrumbs.ts @@ -1,30 +1,30 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getUserSettingsBreadcrumbs( - path: string + path: string, ): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles( - PageMap.USER_SETTINGS_NOTIFICATION_METHODS, - ['Project', 'User Settings', 'Notification Methods'] - ), - ...BuildBreadcrumbLinksByTitles( - PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS, - ['Project', 'User Settings', 'Notification Settings'] - ), - ...BuildBreadcrumbLinksByTitles(PageMap.USER_SETTINGS_ON_CALL_RULES, [ - 'Project', - 'User Settings', - 'Notification Rules', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.USER_SETTINGS_ON_CALL_LOGS, [ - 'Project', - 'User Settings', - 'Notification Logs', - ]), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles( + PageMap.USER_SETTINGS_NOTIFICATION_METHODS, + ["Project", "User Settings", "Notification Methods"], + ), + ...BuildBreadcrumbLinksByTitles( + PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS, + ["Project", "User Settings", "Notification Settings"], + ), + ...BuildBreadcrumbLinksByTitles(PageMap.USER_SETTINGS_ON_CALL_RULES, [ + "Project", + "User Settings", + "Notification Rules", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.USER_SETTINGS_ON_CALL_LOGS, [ + "Project", + "User Settings", + "Notification Logs", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/WorkflowsBreadcrumbs.ts b/Dashboard/src/Utils/Breadcrumbs/WorkflowsBreadcrumbs.ts index 3ded495139..4212d5d4cc 100644 --- a/Dashboard/src/Utils/Breadcrumbs/WorkflowsBreadcrumbs.ts +++ b/Dashboard/src/Utils/Breadcrumbs/WorkflowsBreadcrumbs.ts @@ -1,59 +1,59 @@ -import PageMap from '../PageMap'; -import { BuildBreadcrumbLinksByTitles } from './Helper'; -import Dictionary from 'Common/Types/Dictionary'; -import Link from 'Common/Types/Link'; +import PageMap from "../PageMap"; +import { BuildBreadcrumbLinksByTitles } from "./Helper"; +import Dictionary from "Common/Types/Dictionary"; +import Link from "Common/Types/Link"; export function getWorkflowsBreadcrumbs(path: string): Array<Link> | undefined { - const breadcrumpLinksMap: Dictionary<Link[]> = { - ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOWS, [ - 'Project', - 'Workflows', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOWS_VARIABLES, [ - 'Project', - 'Workflows', - 'Variables', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOWS_LOGS, [ - 'Project', - 'Workflows', - 'Logs', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_VIEW, [ - 'Project', - 'Workflows', - 'View Workflow', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_BUILDER, [ - 'Project', - 'Workflows', - 'View Workflow', - 'Builder', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_VARIABLES, [ - 'Project', - 'Workflows', - 'View Workflow', - 'Variables', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_LOGS, [ - 'Project', - 'Workflows', - 'View Workflow', - 'Logs', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_VIEW_SETTINGS, [ - 'Project', - 'Workflows', - 'View Workflow', - 'Settings', - ]), - ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_DELETE, [ - 'Project', - 'Workflows', - 'View Workflow', - 'Delete Workflow', - ]), - }; - return breadcrumpLinksMap[path]; + const breadcrumpLinksMap: Dictionary<Link[]> = { + ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOWS, [ + "Project", + "Workflows", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOWS_VARIABLES, [ + "Project", + "Workflows", + "Variables", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOWS_LOGS, [ + "Project", + "Workflows", + "Logs", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_VIEW, [ + "Project", + "Workflows", + "View Workflow", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_BUILDER, [ + "Project", + "Workflows", + "View Workflow", + "Builder", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_VARIABLES, [ + "Project", + "Workflows", + "View Workflow", + "Variables", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_LOGS, [ + "Project", + "Workflows", + "View Workflow", + "Logs", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_VIEW_SETTINGS, [ + "Project", + "Workflows", + "View Workflow", + "Settings", + ]), + ...BuildBreadcrumbLinksByTitles(PageMap.WORKFLOW_DELETE, [ + "Project", + "Workflows", + "View Workflow", + "Delete Workflow", + ]), + }; + return breadcrumpLinksMap[path]; } diff --git a/Dashboard/src/Utils/Breadcrumbs/index.ts b/Dashboard/src/Utils/Breadcrumbs/index.ts index ac3f71e3a2..288b0061bc 100644 --- a/Dashboard/src/Utils/Breadcrumbs/index.ts +++ b/Dashboard/src/Utils/Breadcrumbs/index.ts @@ -1,11 +1,11 @@ -export * from './MonitorBreadcrumbs'; -export * from './MonitorBreadcrumbs'; -export * from './ScheduledMaintenanceBreadcrumbs'; -export * from './StatusPagesBreadcrumbs'; -export * from './WorkflowsBreadcrumbs'; -export * from './OnCallDutyBreadcrumbs'; -export * from './TelemetryBreadcrumbs'; -export * from './SettingsBreadcrumbs'; -export * from './MonitorGroupBreadcrumbs'; -export * from './ServiceCatalogBreadcrumbs'; -export * from './AICopilotBreadcrumbs'; +export * from "./MonitorBreadcrumbs"; +export * from "./MonitorBreadcrumbs"; +export * from "./ScheduledMaintenanceBreadcrumbs"; +export * from "./StatusPagesBreadcrumbs"; +export * from "./WorkflowsBreadcrumbs"; +export * from "./OnCallDutyBreadcrumbs"; +export * from "./TelemetryBreadcrumbs"; +export * from "./SettingsBreadcrumbs"; +export * from "./MonitorGroupBreadcrumbs"; +export * from "./ServiceCatalogBreadcrumbs"; +export * from "./AICopilotBreadcrumbs"; diff --git a/Dashboard/src/Utils/EventName.tsx b/Dashboard/src/Utils/EventName.tsx index 791f613ec8..2289dddbd1 100644 --- a/Dashboard/src/Utils/EventName.tsx +++ b/Dashboard/src/Utils/EventName.tsx @@ -1,7 +1,7 @@ enum EventName { - ACTIVE_INCIDENTS_COUNT_REFRESH = 'ACTIVE_INCIDENTS_COUNT_REFRESH', - PROJECT_INVITATIONS_REFRESH = 'PROJECT_INVITATIONS_REFRESH', - SET_NEW_PROFILE_PICTURE = 'SET_NEW_PROFILE_PICTURE', + ACTIVE_INCIDENTS_COUNT_REFRESH = "ACTIVE_INCIDENTS_COUNT_REFRESH", + PROJECT_INVITATIONS_REFRESH = "PROJECT_INVITATIONS_REFRESH", + SET_NEW_PROFILE_PICTURE = "SET_NEW_PROFILE_PICTURE", } export default EventName; diff --git a/Dashboard/src/Utils/Form/Monitor/CriteriaFilter.ts b/Dashboard/src/Utils/Form/Monitor/CriteriaFilter.ts index 70f1746b25..9d7bc90b2c 100644 --- a/Dashboard/src/Utils/Form/Monitor/CriteriaFilter.ts +++ b/Dashboard/src/Utils/Form/Monitor/CriteriaFilter.ts @@ -1,563 +1,530 @@ import { - CheckOn, - CriteriaFilter, - EvaluateOverTimeMinutes, - EvaluateOverTimeType, - FilterCondition, - FilterType, -} from 'Common/Types/Monitor/CriteriaFilter'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import BrowserType from 'Common/Types/Monitor/SyntheticMonitors/BrowserType'; -import ScreenSizeType from 'Common/Types/Monitor/SyntheticMonitors/ScreenSizeType'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import DropdownUtil from 'CommonUI/src/Utils/Dropdown'; + CheckOn, + CriteriaFilter, + EvaluateOverTimeMinutes, + EvaluateOverTimeType, + FilterCondition, + FilterType, +} from "Common/Types/Monitor/CriteriaFilter"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import BrowserType from "Common/Types/Monitor/SyntheticMonitors/BrowserType"; +import ScreenSizeType from "Common/Types/Monitor/SyntheticMonitors/ScreenSizeType"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import DropdownUtil from "CommonUI/src/Utils/Dropdown"; export default class CriteriaFilterUtil { - public static getEvaluateOverTimeMinutesOptions(): Array<DropdownOption> { - const keys: Array<string> = Object.keys(EvaluateOverTimeMinutes); - return keys.map((key: string) => { - return { - label: `${(EvaluateOverTimeMinutes as any)[ - key - ].toString()} Minutes`, - value: (EvaluateOverTimeMinutes as any)[key]!.toString(), - }; - }); + public static getEvaluateOverTimeMinutesOptions(): Array<DropdownOption> { + const keys: Array<string> = Object.keys(EvaluateOverTimeMinutes); + return keys.map((key: string) => { + return { + label: `${(EvaluateOverTimeMinutes as any)[key].toString()} Minutes`, + value: (EvaluateOverTimeMinutes as any)[key]!.toString(), + }; + }); + } + + public static translateFilterToText( + criteriaFilter: CriteriaFilter, + filterCondition?: FilterCondition | undefined, + ): string { + let text: string = "Check if "; + + // template: the maximum percentage of disk usage on /dev/sda in the past three minutes exceeds 21%. + + const isPercentage: boolean = + criteriaFilter?.checkOn === CheckOn.CPUUsagePercent || + criteriaFilter?.checkOn === CheckOn.DiskUsagePercent || + criteriaFilter?.checkOn === CheckOn.MemoryUsagePercent; + + const isMilliseconds: boolean = + criteriaFilter?.checkOn === CheckOn.ResponseTime; + + // check evaluation over time values. + if ( + criteriaFilter?.eveluateOverTime && + criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType + ) { + if ( + criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === + EvaluateOverTimeType.AllValues + ) { + text += `all `; + } else if ( + criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === + EvaluateOverTimeType.AnyValue + ) { + text += `any `; + } else if ( + criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === + EvaluateOverTimeType.Average + ) { + text += `average ${isPercentage ? "percentage " : ""}value`; + } else if ( + criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === + EvaluateOverTimeType.MaximumValue + ) { + text += `maximum ${isPercentage ? "percentage " : ""}value `; + } else if ( + criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === + EvaluateOverTimeType.MunimumValue + ) { + text += `minimum ${isPercentage ? "percentage " : ""}value `; + } else if ( + criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === + EvaluateOverTimeType.Sum + ) { + text += `sum of all ${isPercentage ? "percentage " : ""}values `; + } } - public static translateFilterToText( - criteriaFilter: CriteriaFilter, - filterCondition?: FilterCondition | undefined - ): string { - let text: string = 'Check if '; + if (criteriaFilter?.checkOn === CheckOn.JavaScriptExpression) { + text += + "JavaScript expression " + + criteriaFilter?.value + + " - evaluates to true."; + } else if (criteriaFilter?.checkOn === CheckOn.IsOnline) { + if (criteriaFilter?.filterType === FilterType.True) { + text += "the resource is online "; + } else { + text += "the resource is offline "; + } + } else { + text += criteriaFilter?.checkOn.toString().toLowerCase() + " "; - // template: the maximum percentage of disk usage on /dev/sda in the past three minutes exceeds 21%. + if (criteriaFilter?.serverMonitorOptions?.diskPath) { + text += "on " + criteriaFilter?.serverMonitorOptions?.diskPath + " "; + } - const isPercentage: boolean = - criteriaFilter?.checkOn === CheckOn.CPUUsagePercent || - criteriaFilter?.checkOn === CheckOn.DiskUsagePercent || - criteriaFilter?.checkOn === CheckOn.MemoryUsagePercent; + // add minutes if evaluate over time is true + if ( + criteriaFilter?.eveluateOverTime && + criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes + ) { + text += + "in the past " + + criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes + + " minutes "; + } - const isMilliseconds: boolean = - criteriaFilter?.checkOn === CheckOn.ResponseTime; + // ADD FILTER TYPE - like greater than, less than, etc - // check evaluation over time values. - if ( - criteriaFilter?.eveluateOverTime && - criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType - ) { - if ( - criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === - EvaluateOverTimeType.AllValues - ) { - text += `all `; - } else if ( - criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === - EvaluateOverTimeType.AnyValue - ) { - text += `any `; - } else if ( - criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === - EvaluateOverTimeType.Average - ) { - text += `average ${isPercentage ? 'percentage ' : ''}value`; - } else if ( - criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === - EvaluateOverTimeType.MaximumValue - ) { - text += `maximum ${isPercentage ? 'percentage ' : ''}value `; - } else if ( - criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === - EvaluateOverTimeType.MunimumValue - ) { - text += `minimum ${isPercentage ? 'percentage ' : ''}value `; - } else if ( - criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType === - EvaluateOverTimeType.Sum - ) { - text += `sum of all ${ - isPercentage ? 'percentage ' : '' - }values `; - } - } - - if (criteriaFilter?.checkOn === CheckOn.JavaScriptExpression) { - text += - 'JavaScript expression ' + - criteriaFilter?.value + - ' - evaluates to true.'; - } else if (criteriaFilter?.checkOn === CheckOn.IsOnline) { - if (criteriaFilter?.filterType === FilterType.True) { - text += 'the resource is online '; - } else { - text += 'the resource is offline '; - } + if (criteriaFilter?.filterType) { + if (criteriaFilter?.filterType.toLowerCase().includes("contains")) { + text += criteriaFilter?.filterType.toString().toLowerCase() + " "; } else { - text += criteriaFilter?.checkOn.toString().toLowerCase() + ' '; - - if (criteriaFilter?.serverMonitorOptions?.diskPath) { - text += - 'on ' + - criteriaFilter?.serverMonitorOptions?.diskPath + - ' '; - } - - // add minutes if evaluate over time is true - if ( - criteriaFilter?.eveluateOverTime && - criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes - ) { - text += - 'in the past ' + - criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes + - ' minutes '; - } - - // ADD FILTER TYPE - like greater than, less than, etc - - if (criteriaFilter?.filterType) { - if ( - criteriaFilter?.filterType - .toLowerCase() - .includes('contains') - ) { - text += - criteriaFilter?.filterType.toString().toLowerCase() + - ' '; - } else { - text += - 'is ' + - criteriaFilter?.filterType.toString().toLowerCase() + - ' '; - } - } - - /// FINALLY ADD THE VALUE - - if (criteriaFilter?.value !== undefined) { - text += `${criteriaFilter?.value.toString()}${ - isPercentage ? '%' : '' - }${isMilliseconds ? 'ms' : ''} `; - } + text += + "is " + criteriaFilter?.filterType.toString().toLowerCase() + " "; } + } - if (filterCondition === FilterCondition.All) { - text += 'and,'; - } + /// FINALLY ADD THE VALUE - if (filterCondition === FilterCondition.Any) { - text += 'or,'; - } - - return text; + if (criteriaFilter?.value !== undefined) { + text += `${criteriaFilter?.value.toString()}${ + isPercentage ? "%" : "" + }${isMilliseconds ? "ms" : ""} `; + } } - public static getCheckOnOptionsByMonitorType( - monitorType: MonitorType - ): Array<DropdownOption> { - let options: Array<DropdownOption> = - DropdownUtil.getDropdownOptionsFromEnum(CheckOn); - - if ( - monitorType === MonitorType.Ping || - monitorType === MonitorType.IP || - monitorType === MonitorType.Port - ) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === CheckOn.IsOnline || - i.value === CheckOn.ResponseTime - ); - }); - } - - if (monitorType === MonitorType.Server) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === CheckOn.IsOnline || - i.value === CheckOn.DiskUsagePercent || - i.value === CheckOn.CPUUsagePercent || - i.value === CheckOn.MemoryUsagePercent || - i.value === CheckOn.ServerProcessCommand || - i.value === CheckOn.ServerProcessName || - i.value === CheckOn.ServerProcessPID - ); - }); - } - - if (monitorType === MonitorType.CustomJavaScriptCode) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === CheckOn.Error || - i.value === CheckOn.ResultValue || - i.value === CheckOn.ExecutionTime - ); - }); - } - - if (monitorType === MonitorType.SyntheticMonitor) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === CheckOn.Error || - i.value === CheckOn.ResultValue || - i.value === CheckOn.ExecutionTime || - i.value === CheckOn.BrowserType || - i.value === CheckOn.ScreenSizeType - ); - }); - } - - if (monitorType === MonitorType.SSLCertificate) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === CheckOn.IsValidCertificate || - i.value === CheckOn.IsSelfSignedCertificate || - i.value === CheckOn.IsExpiredCertificate || - i.value === CheckOn.IsNotAValidCertificate || - i.value === CheckOn.ExpiresInDays || - i.value === CheckOn.ExpiresInHours - ); - }); - } - - if (monitorType === MonitorType.IncomingRequest) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === CheckOn.IncomingRequest || - i.value === CheckOn.RequestBody || - i.value === CheckOn.RequestHeader || - i.value === CheckOn.RequestHeaderValue || - i.value === CheckOn.JavaScriptExpression - ); - }); - } - - if ( - monitorType === MonitorType.Website || - monitorType === MonitorType.API - ) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === CheckOn.IsOnline || - i.value === CheckOn.ResponseTime || - i.value === CheckOn.ResponseBody || - i.value === CheckOn.ResponseHeader || - i.value === CheckOn.ResponseHeaderValue || - i.value === CheckOn.ResponseStatusCode || - i.value === CheckOn.JavaScriptExpression - ); - }); - } - - return options; + if (filterCondition === FilterCondition.All) { + text += "and,"; } - public static getFilterTypeOptionsByCheckOn( - checkOn: CheckOn - ): Array<DropdownOption> { - let options: Array<DropdownOption> = - DropdownUtil.getDropdownOptionsFromEnum(FilterType); - - if (!checkOn) { - return []; - } - - if ( - checkOn === CheckOn.ResponseTime || - checkOn === CheckOn.ExecutionTime - ) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.GreaterThan || - i.value === FilterType.LessThan || - i.value === FilterType.LessThanOrEqualTo || - i.value === FilterType.GreaterThanOrEqualTo - ); - }); - } - - if ( - checkOn === CheckOn.CPUUsagePercent || - checkOn === CheckOn.DiskUsagePercent || - checkOn === CheckOn.MemoryUsagePercent - ) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.GreaterThan || - i.value === FilterType.LessThan || - i.value === FilterType.LessThanOrEqualTo || - i.value === FilterType.GreaterThanOrEqualTo - ); - }); - } - - if ( - checkOn === CheckOn.ServerProcessPID || - checkOn === CheckOn.ServerProcessCommand || - checkOn === CheckOn.ServerProcessName - ) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.IsExecuting || - i.value === FilterType.IsNotExecuting - ); - }); - } - - if (checkOn === CheckOn.IncomingRequest) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.NotRecievedInMinutes || - i.value === FilterType.RecievedInMinutes - ); - }); - } - - if (checkOn === CheckOn.IsOnline) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.True || i.value === FilterType.False - ); - }); - } - - if ( - checkOn === CheckOn.ResponseBody || - checkOn === CheckOn.ResponseHeader || - checkOn === CheckOn.ResponseHeaderValue || - checkOn === CheckOn.RequestBody || - checkOn === CheckOn.RequestHeader || - checkOn === CheckOn.RequestHeaderValue - ) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.Contains || - i.value === FilterType.NotContains - ); - }); - } - - if (checkOn === CheckOn.ResultValue) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.Contains || - i.value === FilterType.NotContains || - i.value === FilterType.EqualTo || - i.value === FilterType.NotEqualTo || - i.value === FilterType.IsEmpty || - i.value === FilterType.IsNotEmpty || - i.value === FilterType.GreaterThan || - i.value === FilterType.LessThan || - i.value === FilterType.LessThanOrEqualTo || - i.value === FilterType.GreaterThanOrEqualTo - ); - }); - } - - if ( - checkOn === CheckOn.BrowserType || - checkOn === CheckOn.ScreenSizeType - ) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.EqualTo || - i.value === FilterType.NotEqualTo - ); - }); - } - - if (checkOn === CheckOn.Error) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.Contains || - i.value === FilterType.NotContains || - i.value === FilterType.EqualTo || - i.value === FilterType.NotEqualTo || - i.value === FilterType.IsEmpty || - i.value === FilterType.IsNotEmpty - ); - }); - } - - if (checkOn === CheckOn.JavaScriptExpression) { - options = options.filter((i: DropdownOption) => { - return i.value === FilterType.EvaluatesToTrue; - }); - } - - if (checkOn === CheckOn.ResponseStatusCode) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.GreaterThan || - i.value === FilterType.LessThan || - i.value === FilterType.LessThanOrEqualTo || - i.value === FilterType.GreaterThanOrEqualTo || - i.value === FilterType.EqualTo || - i.value === FilterType.NotEqualTo - ); - }); - } - - if ( - checkOn === CheckOn.IsValidCertificate || - checkOn === CheckOn.IsSelfSignedCertificate || - checkOn === CheckOn.IsExpiredCertificate || - checkOn === CheckOn.IsNotAValidCertificate - ) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.True || i.value === FilterType.False - ); - }); - } - - if ( - checkOn === CheckOn.ExpiresInDays || - checkOn === CheckOn.ExpiresInHours - ) { - options = options.filter((i: DropdownOption) => { - return ( - i.value === FilterType.GreaterThan || - i.value === FilterType.LessThan || - i.value === FilterType.LessThanOrEqualTo || - i.value === FilterType.GreaterThanOrEqualTo - ); - }); - } - - return options; + if (filterCondition === FilterCondition.Any) { + text += "or,"; } - public static hasValueField(data: { - checkOn: CheckOn; - filterType: FilterType | undefined; - }): boolean { - const { checkOn } = data; + return text; + } - if (checkOn === CheckOn.IsOnline) { - return false; - } + public static getCheckOnOptionsByMonitorType( + monitorType: MonitorType, + ): Array<DropdownOption> { + let options: Array<DropdownOption> = + DropdownUtil.getDropdownOptionsFromEnum(CheckOn); - if ( - checkOn === CheckOn.IsValidCertificate || - checkOn === CheckOn.IsSelfSignedCertificate || - checkOn === CheckOn.IsExpiredCertificate || - checkOn === CheckOn.IsNotAValidCertificate - ) { - return false; - } - - if ( - FilterType.IsEmpty === data.filterType || - FilterType.IsNotEmpty === data.filterType || - FilterType.True === data.filterType || - FilterType.False === data.filterType - ) { - return false; - } - - return true; + if ( + monitorType === MonitorType.Ping || + monitorType === MonitorType.IP || + monitorType === MonitorType.Port + ) { + options = options.filter((i: DropdownOption) => { + return i.value === CheckOn.IsOnline || i.value === CheckOn.ResponseTime; + }); } - public static isDropdownValueField(data: { - checkOn?: CheckOn | undefined; - }): boolean { - const { checkOn } = data; - - if ( - checkOn === CheckOn.ScreenSizeType || - checkOn === CheckOn.BrowserType - ) { - return true; - } - - return false; + if (monitorType === MonitorType.Server) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === CheckOn.IsOnline || + i.value === CheckOn.DiskUsagePercent || + i.value === CheckOn.CPUUsagePercent || + i.value === CheckOn.MemoryUsagePercent || + i.value === CheckOn.ServerProcessCommand || + i.value === CheckOn.ServerProcessName || + i.value === CheckOn.ServerProcessPID + ); + }); } - public static getDropdownOptionsByCheckOn(data: { - checkOn: CheckOn; - }): Array<DropdownOption> { - const { checkOn } = data; - - if (checkOn === CheckOn.ScreenSizeType) { - return DropdownUtil.getDropdownOptionsFromEnum(ScreenSizeType); - } - - if (checkOn === CheckOn.BrowserType) { - return DropdownUtil.getDropdownOptionsFromEnum(BrowserType); - } - - return []; + if (monitorType === MonitorType.CustomJavaScriptCode) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === CheckOn.Error || + i.value === CheckOn.ResultValue || + i.value === CheckOn.ExecutionTime + ); + }); } - public static getFilterTypePlaceholderValueByCheckOn(data: { - monitorType: MonitorType; - checkOn: CheckOn; - }): string { - const { monitorType, checkOn } = data; - - if (!checkOn) { - return ''; - } - - if (checkOn === CheckOn.ResponseTime) { - return '5000'; - } - - if (checkOn === CheckOn.ServerProcessPID) { - return '1234'; - } - - if (checkOn === CheckOn.ServerProcessCommand) { - return 'node index.js'; - } - - if (checkOn === CheckOn.ServerProcessName) { - return 'node'; - } - - if ( - checkOn === CheckOn.CPUUsagePercent || - checkOn === CheckOn.DiskUsagePercent || - checkOn === CheckOn.MemoryUsagePercent - ) { - return '65'; - } - - if (checkOn === CheckOn.IncomingRequest) { - return '5'; - } - - if ( - checkOn === CheckOn.ResponseBody || - checkOn === CheckOn.ResponseHeader || - checkOn === CheckOn.ResponseHeaderValue || - checkOn === CheckOn.RequestBody || - checkOn === CheckOn.RequestHeader || - checkOn === CheckOn.RequestHeaderValue - ) { - return 'Some Text'; - } - - if (checkOn === CheckOn.JavaScriptExpression) { - if (monitorType === MonitorType.IncomingRequest) { - return '{{requestBody.result}} === true'; - } - return '{{responseBody.result}} === true'; - } - - if (checkOn === CheckOn.ResponseStatusCode) { - return '200'; - } - - if (checkOn === CheckOn.ExpiresInDays) { - return '30'; - } - - if (checkOn === CheckOn.ExpiresInHours) { - return '24'; - } - - return ''; + if (monitorType === MonitorType.SyntheticMonitor) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === CheckOn.Error || + i.value === CheckOn.ResultValue || + i.value === CheckOn.ExecutionTime || + i.value === CheckOn.BrowserType || + i.value === CheckOn.ScreenSizeType + ); + }); } + + if (monitorType === MonitorType.SSLCertificate) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === CheckOn.IsValidCertificate || + i.value === CheckOn.IsSelfSignedCertificate || + i.value === CheckOn.IsExpiredCertificate || + i.value === CheckOn.IsNotAValidCertificate || + i.value === CheckOn.ExpiresInDays || + i.value === CheckOn.ExpiresInHours + ); + }); + } + + if (monitorType === MonitorType.IncomingRequest) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === CheckOn.IncomingRequest || + i.value === CheckOn.RequestBody || + i.value === CheckOn.RequestHeader || + i.value === CheckOn.RequestHeaderValue || + i.value === CheckOn.JavaScriptExpression + ); + }); + } + + if ( + monitorType === MonitorType.Website || + monitorType === MonitorType.API + ) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === CheckOn.IsOnline || + i.value === CheckOn.ResponseTime || + i.value === CheckOn.ResponseBody || + i.value === CheckOn.ResponseHeader || + i.value === CheckOn.ResponseHeaderValue || + i.value === CheckOn.ResponseStatusCode || + i.value === CheckOn.JavaScriptExpression + ); + }); + } + + return options; + } + + public static getFilterTypeOptionsByCheckOn( + checkOn: CheckOn, + ): Array<DropdownOption> { + let options: Array<DropdownOption> = + DropdownUtil.getDropdownOptionsFromEnum(FilterType); + + if (!checkOn) { + return []; + } + + if (checkOn === CheckOn.ResponseTime || checkOn === CheckOn.ExecutionTime) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.GreaterThan || + i.value === FilterType.LessThan || + i.value === FilterType.LessThanOrEqualTo || + i.value === FilterType.GreaterThanOrEqualTo + ); + }); + } + + if ( + checkOn === CheckOn.CPUUsagePercent || + checkOn === CheckOn.DiskUsagePercent || + checkOn === CheckOn.MemoryUsagePercent + ) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.GreaterThan || + i.value === FilterType.LessThan || + i.value === FilterType.LessThanOrEqualTo || + i.value === FilterType.GreaterThanOrEqualTo + ); + }); + } + + if ( + checkOn === CheckOn.ServerProcessPID || + checkOn === CheckOn.ServerProcessCommand || + checkOn === CheckOn.ServerProcessName + ) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.IsExecuting || + i.value === FilterType.IsNotExecuting + ); + }); + } + + if (checkOn === CheckOn.IncomingRequest) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.NotRecievedInMinutes || + i.value === FilterType.RecievedInMinutes + ); + }); + } + + if (checkOn === CheckOn.IsOnline) { + options = options.filter((i: DropdownOption) => { + return i.value === FilterType.True || i.value === FilterType.False; + }); + } + + if ( + checkOn === CheckOn.ResponseBody || + checkOn === CheckOn.ResponseHeader || + checkOn === CheckOn.ResponseHeaderValue || + checkOn === CheckOn.RequestBody || + checkOn === CheckOn.RequestHeader || + checkOn === CheckOn.RequestHeaderValue + ) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.Contains || i.value === FilterType.NotContains + ); + }); + } + + if (checkOn === CheckOn.ResultValue) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.Contains || + i.value === FilterType.NotContains || + i.value === FilterType.EqualTo || + i.value === FilterType.NotEqualTo || + i.value === FilterType.IsEmpty || + i.value === FilterType.IsNotEmpty || + i.value === FilterType.GreaterThan || + i.value === FilterType.LessThan || + i.value === FilterType.LessThanOrEqualTo || + i.value === FilterType.GreaterThanOrEqualTo + ); + }); + } + + if (checkOn === CheckOn.BrowserType || checkOn === CheckOn.ScreenSizeType) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.EqualTo || i.value === FilterType.NotEqualTo + ); + }); + } + + if (checkOn === CheckOn.Error) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.Contains || + i.value === FilterType.NotContains || + i.value === FilterType.EqualTo || + i.value === FilterType.NotEqualTo || + i.value === FilterType.IsEmpty || + i.value === FilterType.IsNotEmpty + ); + }); + } + + if (checkOn === CheckOn.JavaScriptExpression) { + options = options.filter((i: DropdownOption) => { + return i.value === FilterType.EvaluatesToTrue; + }); + } + + if (checkOn === CheckOn.ResponseStatusCode) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.GreaterThan || + i.value === FilterType.LessThan || + i.value === FilterType.LessThanOrEqualTo || + i.value === FilterType.GreaterThanOrEqualTo || + i.value === FilterType.EqualTo || + i.value === FilterType.NotEqualTo + ); + }); + } + + if ( + checkOn === CheckOn.IsValidCertificate || + checkOn === CheckOn.IsSelfSignedCertificate || + checkOn === CheckOn.IsExpiredCertificate || + checkOn === CheckOn.IsNotAValidCertificate + ) { + options = options.filter((i: DropdownOption) => { + return i.value === FilterType.True || i.value === FilterType.False; + }); + } + + if ( + checkOn === CheckOn.ExpiresInDays || + checkOn === CheckOn.ExpiresInHours + ) { + options = options.filter((i: DropdownOption) => { + return ( + i.value === FilterType.GreaterThan || + i.value === FilterType.LessThan || + i.value === FilterType.LessThanOrEqualTo || + i.value === FilterType.GreaterThanOrEqualTo + ); + }); + } + + return options; + } + + public static hasValueField(data: { + checkOn: CheckOn; + filterType: FilterType | undefined; + }): boolean { + const { checkOn } = data; + + if (checkOn === CheckOn.IsOnline) { + return false; + } + + if ( + checkOn === CheckOn.IsValidCertificate || + checkOn === CheckOn.IsSelfSignedCertificate || + checkOn === CheckOn.IsExpiredCertificate || + checkOn === CheckOn.IsNotAValidCertificate + ) { + return false; + } + + if ( + FilterType.IsEmpty === data.filterType || + FilterType.IsNotEmpty === data.filterType || + FilterType.True === data.filterType || + FilterType.False === data.filterType + ) { + return false; + } + + return true; + } + + public static isDropdownValueField(data: { + checkOn?: CheckOn | undefined; + }): boolean { + const { checkOn } = data; + + if (checkOn === CheckOn.ScreenSizeType || checkOn === CheckOn.BrowserType) { + return true; + } + + return false; + } + + public static getDropdownOptionsByCheckOn(data: { + checkOn: CheckOn; + }): Array<DropdownOption> { + const { checkOn } = data; + + if (checkOn === CheckOn.ScreenSizeType) { + return DropdownUtil.getDropdownOptionsFromEnum(ScreenSizeType); + } + + if (checkOn === CheckOn.BrowserType) { + return DropdownUtil.getDropdownOptionsFromEnum(BrowserType); + } + + return []; + } + + public static getFilterTypePlaceholderValueByCheckOn(data: { + monitorType: MonitorType; + checkOn: CheckOn; + }): string { + const { monitorType, checkOn } = data; + + if (!checkOn) { + return ""; + } + + if (checkOn === CheckOn.ResponseTime) { + return "5000"; + } + + if (checkOn === CheckOn.ServerProcessPID) { + return "1234"; + } + + if (checkOn === CheckOn.ServerProcessCommand) { + return "node index.js"; + } + + if (checkOn === CheckOn.ServerProcessName) { + return "node"; + } + + if ( + checkOn === CheckOn.CPUUsagePercent || + checkOn === CheckOn.DiskUsagePercent || + checkOn === CheckOn.MemoryUsagePercent + ) { + return "65"; + } + + if (checkOn === CheckOn.IncomingRequest) { + return "5"; + } + + if ( + checkOn === CheckOn.ResponseBody || + checkOn === CheckOn.ResponseHeader || + checkOn === CheckOn.ResponseHeaderValue || + checkOn === CheckOn.RequestBody || + checkOn === CheckOn.RequestHeader || + checkOn === CheckOn.RequestHeaderValue + ) { + return "Some Text"; + } + + if (checkOn === CheckOn.JavaScriptExpression) { + if (monitorType === MonitorType.IncomingRequest) { + return "{{requestBody.result}} === true"; + } + return "{{responseBody.result}} === true"; + } + + if (checkOn === CheckOn.ResponseStatusCode) { + return "200"; + } + + if (checkOn === CheckOn.ExpiresInDays) { + return "30"; + } + + if (checkOn === CheckOn.ExpiresInHours) { + return "24"; + } + + return ""; + } } diff --git a/Dashboard/src/Utils/IncidentState.ts b/Dashboard/src/Utils/IncidentState.ts index b6e8fdf54a..c5f5d8bcb7 100644 --- a/Dashboard/src/Utils/IncidentState.ts +++ b/Dashboard/src/Utils/IncidentState.ts @@ -1,41 +1,41 @@ -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import ObjectID from 'Common/Types/ObjectID'; -import ListResult from 'CommonUI/src/Utils/BaseDatabase/ListResult'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import IncidentState from 'Model/Models/IncidentState'; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import ObjectID from "Common/Types/ObjectID"; +import ListResult from "CommonUI/src/Utils/BaseDatabase/ListResult"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import IncidentState from "Model/Models/IncidentState"; export default class IncidentStateUtil { - public static async getUnresolvedIncidentStates( - projectId: ObjectID - ): Promise<IncidentState[]> { - const incidentStates: ListResult<IncidentState> = - await ModelAPI.getList<IncidentState>({ - modelType: IncidentState, - query: { - projectId: projectId, - }, - skip: 0, - limit: LIMIT_PER_PROJECT, - sort: { - order: SortOrder.Ascending, - }, - select: { - _id: true, - isResolvedState: true, - }, - }); + public static async getUnresolvedIncidentStates( + projectId: ObjectID, + ): Promise<IncidentState[]> { + const incidentStates: ListResult<IncidentState> = + await ModelAPI.getList<IncidentState>({ + modelType: IncidentState, + query: { + projectId: projectId, + }, + skip: 0, + limit: LIMIT_PER_PROJECT, + sort: { + order: SortOrder.Ascending, + }, + select: { + _id: true, + isResolvedState: true, + }, + }); - const unresolvedIncidentStates: Array<IncidentState> = []; + const unresolvedIncidentStates: Array<IncidentState> = []; - for (const state of incidentStates.data) { - if (!state.isResolvedState) { - unresolvedIncidentStates.push(state); - } else { - break; // everything after resolved state is resolved - } - } - - return unresolvedIncidentStates; + for (const state of incidentStates.data) { + if (!state.isResolvedState) { + unresolvedIncidentStates.push(state); + } else { + break; // everything after resolved state is resolved + } } + + return unresolvedIncidentStates; + } } diff --git a/Dashboard/src/Utils/MonitorIntervalDropdownOptions.ts b/Dashboard/src/Utils/MonitorIntervalDropdownOptions.ts index 8b66cd3ba5..86df4c31e4 100644 --- a/Dashboard/src/Utils/MonitorIntervalDropdownOptions.ts +++ b/Dashboard/src/Utils/MonitorIntervalDropdownOptions.ts @@ -1,38 +1,38 @@ -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; const MonitoringInterval: Array<DropdownOption> = [ - { - value: '* * * * *', - label: 'Every Minute', - }, - { - value: '*/5 * * * *', - label: 'Every 5 Minutes', - }, - { - value: '*/10 * * * *', - label: 'Every 10 Minutes', - }, - { - value: '*/15 * * * *', - label: 'Every 15 Minutes', - }, - { - value: '*/30 * * * *', - label: 'Every 30 Minutes', - }, - { - value: '0 * * * *', - label: 'Every Hour', - }, - { - value: '0 0 * * *', - label: 'Every Day', - }, - { - value: '0 0 * * 0', - label: 'Every Week', - }, + { + value: "* * * * *", + label: "Every Minute", + }, + { + value: "*/5 * * * *", + label: "Every 5 Minutes", + }, + { + value: "*/10 * * * *", + label: "Every 10 Minutes", + }, + { + value: "*/15 * * * *", + label: "Every 15 Minutes", + }, + { + value: "*/30 * * * *", + label: "Every 30 Minutes", + }, + { + value: "0 * * * *", + label: "Every Hour", + }, + { + value: "0 0 * * *", + label: "Every Day", + }, + { + value: "0 0 * * 0", + label: "Every Week", + }, ]; export default MonitoringInterval; diff --git a/Dashboard/src/Utils/MonitorType.ts b/Dashboard/src/Utils/MonitorType.ts index b09e1fc679..9afe2c4ddf 100644 --- a/Dashboard/src/Utils/MonitorType.ts +++ b/Dashboard/src/Utils/MonitorType.ts @@ -1,19 +1,19 @@ import { - MonitorTypeHelper, - MonitorTypeProps, -} from 'Common/Types/Monitor/MonitorType'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; + MonitorTypeHelper, + MonitorTypeProps, +} from "Common/Types/Monitor/MonitorType"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; export default class MonitorTypeUtil { - public static monitorTypesAsDropdownOptions(): Array<DropdownOption> { - const monitorTypes: Array<MonitorTypeProps> = - MonitorTypeHelper.getAllMonitorTypeProps(); + public static monitorTypesAsDropdownOptions(): Array<DropdownOption> { + const monitorTypes: Array<MonitorTypeProps> = + MonitorTypeHelper.getAllMonitorTypeProps(); - return monitorTypes.map((props: MonitorTypeProps) => { - return { - value: props.monitorType, - label: props.title, - }; - }); - } + return monitorTypes.map((props: MonitorTypeProps) => { + return { + value: props.monitorType, + label: props.title, + }; + }); + } } diff --git a/Dashboard/src/Utils/Navigation.ts b/Dashboard/src/Utils/Navigation.ts index 8cea7f0377..9a4c76cc34 100644 --- a/Dashboard/src/Utils/Navigation.ts +++ b/Dashboard/src/Utils/Navigation.ts @@ -1,12 +1,12 @@ -import ObjectID from 'Common/Types/ObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; +import ObjectID from "Common/Types/ObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; export default class DashboardNavigation { - public static getProjectId(): ObjectID | null { - const projectId: string | undefined = Navigation.getFirstParam(2); - if (projectId) { - return new ObjectID(projectId); - } - return null; + public static getProjectId(): ObjectID | null { + const projectId: string | undefined = Navigation.getFirstParam(2); + if (projectId) { + return new ObjectID(projectId); } + return null; + } } diff --git a/Dashboard/src/Utils/PageMap.ts b/Dashboard/src/Utils/PageMap.ts index 21737cc00d..83748ee671 100644 --- a/Dashboard/src/Utils/PageMap.ts +++ b/Dashboard/src/Utils/PageMap.ts @@ -1,254 +1,254 @@ enum PageMap { - INIT = 'INIT', - INIT_PROJECT = 'INIT_PROJECT', + INIT = "INIT", + INIT_PROJECT = "INIT_PROJECT", - // ONBOARDING - WELCOME = 'WELCOME', + // ONBOARDING + WELCOME = "WELCOME", - //SSO - PROJECT_SSO = 'PROJECT_SSO', + //SSO + PROJECT_SSO = "PROJECT_SSO", - // Telemetry - TELEMETRY_ROOT = 'TELEMETRY_ROOT', - TELEMETRY = 'TELEMETRY', - TELEMETRY_SERVICES = 'TELEMETRY_SERVICES', - TELEMETRY_SERVICES_VIEW = 'TELEMETRY_SERVICES_VIEW', - TELEMETRY_SERVICES_VIEW_DELETE = 'TELEMETRY_SERVICES_VIEW_DELETE', - TELEMETRY_SERVICES_VIEW_SETTINGS = 'TELEMETRY_SERVICES_VIEW_SETTINGS', + // Telemetry + TELEMETRY_ROOT = "TELEMETRY_ROOT", + TELEMETRY = "TELEMETRY", + TELEMETRY_SERVICES = "TELEMETRY_SERVICES", + TELEMETRY_SERVICES_VIEW = "TELEMETRY_SERVICES_VIEW", + TELEMETRY_SERVICES_VIEW_DELETE = "TELEMETRY_SERVICES_VIEW_DELETE", + TELEMETRY_SERVICES_VIEW_SETTINGS = "TELEMETRY_SERVICES_VIEW_SETTINGS", - // Telemetry - LOGS - TELEMETRY_SERVICES_VIEW_LOGS = 'TELEMETRY_SERVICE_VIEW_LOGS', + // Telemetry - LOGS + TELEMETRY_SERVICES_VIEW_LOGS = "TELEMETRY_SERVICE_VIEW_LOGS", - // Traces - TELEMETRY_SERVICES_VIEW_TRACES = 'TELEMETRY_SERVICE_VIEW_TRACES', - TELEMETRY_SERVICES_VIEW_TRACE = 'TELEMETRY_SERVICE_VIEW_TRACE', + // Traces + TELEMETRY_SERVICES_VIEW_TRACES = "TELEMETRY_SERVICE_VIEW_TRACES", + TELEMETRY_SERVICES_VIEW_TRACE = "TELEMETRY_SERVICE_VIEW_TRACE", - // Metrics - TELEMETRY_SERVICES_VIEW_METRICS = 'TELEMETRY_SERVICE_VIEW_METRICS', - TELEMETRY_SERVICES_VIEW_METRIC = 'TELEMETRY_SERVICE_VIEW_METRIC', + // Metrics + TELEMETRY_SERVICES_VIEW_METRICS = "TELEMETRY_SERVICE_VIEW_METRICS", + TELEMETRY_SERVICES_VIEW_METRIC = "TELEMETRY_SERVICE_VIEW_METRIC", - // Docuemntation - TELEMETRY_SERVICES_VIEW_DOCUMENTATION = 'TELEMETRY_SERVICES_VIEW_DOCUMENTATION', + // Docuemntation + TELEMETRY_SERVICES_VIEW_DOCUMENTATION = "TELEMETRY_SERVICES_VIEW_DOCUMENTATION", - // Dashboard - TELEMETRY_SERVICES_VIEW_DASHBOARDS = 'TELEMETRY_SERVICES_VIEW_DASHBOARDS', + // Dashboard + TELEMETRY_SERVICES_VIEW_DASHBOARDS = "TELEMETRY_SERVICES_VIEW_DASHBOARDS", - HOME = 'HOME', - HOME_NOT_OPERATIONAL_MONITORS = 'HOME_NOT_OPERATIONAL_MONITORS', - HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS = 'HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS', + HOME = "HOME", + HOME_NOT_OPERATIONAL_MONITORS = "HOME_NOT_OPERATIONAL_MONITORS", + HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS = "HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS", - INCIDENTS_ROOT = 'INCIDENTS_ROOT', - INCIDENTS = 'INCIDENTS', - UNRESOLVED_INCIDENTS = 'UNRESOLVED_INCIDENTS', - INCIDENT_VIEW = 'INCIDENT_VIEW', - INCIDENT_VIEW_DELETE = 'INCIDENT_VIEW_DELETE', - INCIDENT_VIEW_STATE_TIMELINE = 'INCIDENT_VIEW_STATE_TIMELINE', - INCIDENT_INTERNAL_NOTE = 'INCIDENT_INTERNAL_NOTE', - INCIDENT_PUBLIC_NOTE = 'INCIDENT_PUBLIC_NOTE', - INCIDENT_VIEW_CUSTOM_FIELDS = 'INCIDENT_VIEW_CUSTOM_FIELDS', - INCIDENT_VIEW_OWNERS = 'INCIDENT_VIEW_OWNERS', + INCIDENTS_ROOT = "INCIDENTS_ROOT", + INCIDENTS = "INCIDENTS", + UNRESOLVED_INCIDENTS = "UNRESOLVED_INCIDENTS", + INCIDENT_VIEW = "INCIDENT_VIEW", + INCIDENT_VIEW_DELETE = "INCIDENT_VIEW_DELETE", + INCIDENT_VIEW_STATE_TIMELINE = "INCIDENT_VIEW_STATE_TIMELINE", + INCIDENT_INTERNAL_NOTE = "INCIDENT_INTERNAL_NOTE", + INCIDENT_PUBLIC_NOTE = "INCIDENT_PUBLIC_NOTE", + INCIDENT_VIEW_CUSTOM_FIELDS = "INCIDENT_VIEW_CUSTOM_FIELDS", + INCIDENT_VIEW_OWNERS = "INCIDENT_VIEW_OWNERS", - SCHEDULED_MAINTENANCE_EVENTS_ROOT = 'SCHEDULED_MAINTENANCE_EVENTS_ROOT', - SCHEDULED_MAINTENANCE_EVENTS = 'SCHEDULED_MAINTENANCE_EVENTS', - ONGOING_SCHEDULED_MAINTENANCE_EVENTS = 'ONGOING_SCHEDULED_MAINTENANCE_EVENTS', - SCHEDULED_MAINTENANCE_VIEW = 'SCHEDULED_MAINTENANCE_VIEW', - SCHEDULED_MAINTENANCE_VIEW_DELETE = 'SCHEDULED_MAINTENANCE_VIEW_DELETE', - SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE = 'SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE', - SCHEDULED_MAINTENANCE_INTERNAL_NOTE = 'SCHEDULED_MAINTENANCE_INTERNAL_NOTE', - SCHEDULED_MAINTENANCE_PUBLIC_NOTE = 'SCHEDULED_MAINTENANCE_PUBLIC_NOTE', - SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS = 'SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS', - SCHEDULED_MAINTENANCE_VIEW_OWNERS = 'SCHEDULED_MAINTENANCE_VIEW_OWNERS', + SCHEDULED_MAINTENANCE_EVENTS_ROOT = "SCHEDULED_MAINTENANCE_EVENTS_ROOT", + SCHEDULED_MAINTENANCE_EVENTS = "SCHEDULED_MAINTENANCE_EVENTS", + ONGOING_SCHEDULED_MAINTENANCE_EVENTS = "ONGOING_SCHEDULED_MAINTENANCE_EVENTS", + SCHEDULED_MAINTENANCE_VIEW = "SCHEDULED_MAINTENANCE_VIEW", + SCHEDULED_MAINTENANCE_VIEW_DELETE = "SCHEDULED_MAINTENANCE_VIEW_DELETE", + SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE = "SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE", + SCHEDULED_MAINTENANCE_INTERNAL_NOTE = "SCHEDULED_MAINTENANCE_INTERNAL_NOTE", + SCHEDULED_MAINTENANCE_PUBLIC_NOTE = "SCHEDULED_MAINTENANCE_PUBLIC_NOTE", + SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS = "SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS", + SCHEDULED_MAINTENANCE_VIEW_OWNERS = "SCHEDULED_MAINTENANCE_VIEW_OWNERS", - MONITORS = 'MONITORS', - MONITORS_ROOT = 'MONITORS_ROOT', - MONITORS_INOPERATIONAL = 'MONITORS_INOPERATIONAL', - MONITORS_DISABLED = 'MONITORS_DISABLED', - MONITOR_VIEW = 'MONITOR_VIEW', - MONITOR_VIEW_CRITERIA = 'MONITOR_VIEW_CRITERIA', - MONITOR_VIEW_DELETE = 'MONITOR_VIEW_DELETE', - MONITOR_VIEW_STATUS_TIMELINE = 'MONITOR_VIEW_STATUS_TIMELINE', - MONITOR_VIEW_INCIDENTS = 'MONITOR_VIEW_INCIDENTS', - MONITOR_VIEW_CUSTOM_FIELDS = 'MONITOR_VIEW_CUSTOM_FIELDS', - MONITOR_VIEW_INTERVAL = 'MONITOR_VIEW_INTERVAL', - MONITOR_VIEW_PROBES = 'MONITOR_VIEW_PROBES', - MONITOR_VIEW_DOCUMENTATION = 'MONITOR_VIEW_DOCUMENTATION', - MONITOR_VIEW_OWNERS = 'MONITOR_VIEW_OWNERS', - MONITOR_VIEW_SETTINGS = 'MONITOR_VIEW_SETTINGS', + MONITORS = "MONITORS", + MONITORS_ROOT = "MONITORS_ROOT", + MONITORS_INOPERATIONAL = "MONITORS_INOPERATIONAL", + MONITORS_DISABLED = "MONITORS_DISABLED", + MONITOR_VIEW = "MONITOR_VIEW", + MONITOR_VIEW_CRITERIA = "MONITOR_VIEW_CRITERIA", + MONITOR_VIEW_DELETE = "MONITOR_VIEW_DELETE", + MONITOR_VIEW_STATUS_TIMELINE = "MONITOR_VIEW_STATUS_TIMELINE", + MONITOR_VIEW_INCIDENTS = "MONITOR_VIEW_INCIDENTS", + MONITOR_VIEW_CUSTOM_FIELDS = "MONITOR_VIEW_CUSTOM_FIELDS", + MONITOR_VIEW_INTERVAL = "MONITOR_VIEW_INTERVAL", + MONITOR_VIEW_PROBES = "MONITOR_VIEW_PROBES", + MONITOR_VIEW_DOCUMENTATION = "MONITOR_VIEW_DOCUMENTATION", + MONITOR_VIEW_OWNERS = "MONITOR_VIEW_OWNERS", + MONITOR_VIEW_SETTINGS = "MONITOR_VIEW_SETTINGS", - MONITOR_GROUPS_ROOT = 'MONITOR_GROUPS_ROOT', - MONITOR_GROUPS = 'MONITOR_GROUPS', - MONITOR_GROUP_VIEW = 'MONITOR_GROUP_VIEW', - MONITOR_GROUP_VIEW_DELETE = 'MONITOR_GROUP_VIEW_DELETE', - MONITOR_GROUP_VIEW_MONITORS = 'MONITOR_GROUP_VIEW_MONITORS', - MONITOR_GROUP_VIEW_OWNERS = 'MONITOR_GROUP_VIEW_OWNERS', - MONITOR_GROUP_VIEW_INCIDENTS = 'MONITOR_GROUP_VIEW_INCIDENTS', + MONITOR_GROUPS_ROOT = "MONITOR_GROUPS_ROOT", + MONITOR_GROUPS = "MONITOR_GROUPS", + MONITOR_GROUP_VIEW = "MONITOR_GROUP_VIEW", + MONITOR_GROUP_VIEW_DELETE = "MONITOR_GROUP_VIEW_DELETE", + MONITOR_GROUP_VIEW_MONITORS = "MONITOR_GROUP_VIEW_MONITORS", + MONITOR_GROUP_VIEW_OWNERS = "MONITOR_GROUP_VIEW_OWNERS", + MONITOR_GROUP_VIEW_INCIDENTS = "MONITOR_GROUP_VIEW_INCIDENTS", - SERVICE_CATALOG_ROOT = 'SERVICE_CATALOG_ROOT', - SERVICE_CATALOG = 'SERVICE_CATALOG', - SERVICE_CATALOG_VIEW = 'SERVICE_CATALOG_VIEW', - SERVICE_CATALOG_VIEW_DELETE = 'SERVICE_CATALOG_VIEW_DELETE', - SERVICE_CATALOG_VIEW_SETTINGS = 'SERVICE_CATALOG_VIEW_SETTINGS', - SERVICE_CATALOG_VIEW_OWNERS = 'SERVICE_CATALOG_VIEW_OWNERS', + SERVICE_CATALOG_ROOT = "SERVICE_CATALOG_ROOT", + SERVICE_CATALOG = "SERVICE_CATALOG", + SERVICE_CATALOG_VIEW = "SERVICE_CATALOG_VIEW", + SERVICE_CATALOG_VIEW_DELETE = "SERVICE_CATALOG_VIEW_DELETE", + SERVICE_CATALOG_VIEW_SETTINGS = "SERVICE_CATALOG_VIEW_SETTINGS", + SERVICE_CATALOG_VIEW_OWNERS = "SERVICE_CATALOG_VIEW_OWNERS", - AI_COPILOT_ROOT = 'AI_COPILOT_ROOT', - AI_COPILOT = 'AI_COPILOT', - AI_COPILOT_CODE_REPOSITORY = 'AI_COPILOT_CODE_REPOSITORY', - AI_COPILOT_CODE_REPOSITORY_VIEW = 'AI_COPILOT_CODE_REPOSITORY_VIEW', - AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE = 'AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE', - AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS = 'AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS', - AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES = 'AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES', + AI_COPILOT_ROOT = "AI_COPILOT_ROOT", + AI_COPILOT = "AI_COPILOT", + AI_COPILOT_CODE_REPOSITORY = "AI_COPILOT_CODE_REPOSITORY", + AI_COPILOT_CODE_REPOSITORY_VIEW = "AI_COPILOT_CODE_REPOSITORY_VIEW", + AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE = "AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE", + AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS = "AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS", + AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES = "AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES", - STATUS_PAGES_ROOT = 'STATUS_PAGES_ROOT', - STATUS_PAGES = 'STATUS_PAGES', - STATUS_PAGE_VIEW = 'STATUS_PAGE_VIEW', - STATUS_PAGE_VIEW_BRANDING = 'STATUS_PAGE_VIEW_BRADNING', - STATUS_PAGE_VIEW_HEADER_STYLE = 'STATUS_PAGE_VIEW_HEADER_STYLE', - STATUS_PAGE_VIEW_FOOTER_STYLE = 'STATUS_PAGE_VIEW_FOOTER_STYLE', - STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING = 'STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING', - STATUS_PAGE_VIEW_NAVBAR_STYLE = 'STATUS_PAGE_VIEW_NAVBAR_STYLE', - STATUS_PAGE_VIEW_DELETE = 'STATUS_PAGE_VIEW_DELETE', - STATUS_PAGE_VIEW_DOMAINS = 'STATUS_PAGE_VIEW_DOMAINS', - STATUS_PAGE_VIEW_EMBEDDED = 'STATUS_PAGE_VIEW_EMBEDDED', - STATUS_PAGE_VIEW_ANNOUNCEMENTS = 'STATUS_PAGE_VIEW_ANNOUNCEMENTS', - STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS = 'STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS', - STATUS_PAGE_VIEW_SMS_SUBSCRIBERS = 'STATUS_PAGE_VIEW_SMS_SUBSCRIBERS', - STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS = 'STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS', - STATUS_PAGE_VIEW_RESOURCES = 'STATUS_PAGE_VIEW_RESOURCES', - STATUS_PAGE_VIEW_ADVANCED_OPTIONS = 'STATUS_PAGE_VIEW_ADVANCED_OPTIONS', - STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS = 'STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS', - STATUS_PAGE_VIEW_CUSTOM_HTML_CSS = 'STATUS_PAGE_VIEW_CUSTOM_HTML_CSS', - STATUS_PAGE_VIEW_GROUPS = 'STATUS_PAGE_VIEW_GROUPS', - STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS = 'STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS', - STATUS_PAGE_VIEW_PRIVATE_USERS = 'STATUS_PAGE_VIEW_PRIVATE_USERS', - STATUS_PAGE_VIEW_CUSTOM_FIELDS = 'STATUS_PAGE_VIEW_CUSTOM_FIELDS', - STATUS_PAGE_VIEW_SSO = 'STATUS_PAGE_VIEW_SSO', - STATUS_PAGE_VIEW_OWNERS = 'STATUS_PAGE_VIEW_OWNERS', - STATUS_PAGE_VIEW_SETTINGS = 'STATUS_PAGE_VIEW_SETTINGS', + STATUS_PAGES_ROOT = "STATUS_PAGES_ROOT", + STATUS_PAGES = "STATUS_PAGES", + STATUS_PAGE_VIEW = "STATUS_PAGE_VIEW", + STATUS_PAGE_VIEW_BRANDING = "STATUS_PAGE_VIEW_BRADNING", + STATUS_PAGE_VIEW_HEADER_STYLE = "STATUS_PAGE_VIEW_HEADER_STYLE", + STATUS_PAGE_VIEW_FOOTER_STYLE = "STATUS_PAGE_VIEW_FOOTER_STYLE", + STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING = "STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING", + STATUS_PAGE_VIEW_NAVBAR_STYLE = "STATUS_PAGE_VIEW_NAVBAR_STYLE", + STATUS_PAGE_VIEW_DELETE = "STATUS_PAGE_VIEW_DELETE", + STATUS_PAGE_VIEW_DOMAINS = "STATUS_PAGE_VIEW_DOMAINS", + STATUS_PAGE_VIEW_EMBEDDED = "STATUS_PAGE_VIEW_EMBEDDED", + STATUS_PAGE_VIEW_ANNOUNCEMENTS = "STATUS_PAGE_VIEW_ANNOUNCEMENTS", + STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS = "STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS", + STATUS_PAGE_VIEW_SMS_SUBSCRIBERS = "STATUS_PAGE_VIEW_SMS_SUBSCRIBERS", + STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS = "STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS", + STATUS_PAGE_VIEW_RESOURCES = "STATUS_PAGE_VIEW_RESOURCES", + STATUS_PAGE_VIEW_ADVANCED_OPTIONS = "STATUS_PAGE_VIEW_ADVANCED_OPTIONS", + STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS = "STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS", + STATUS_PAGE_VIEW_CUSTOM_HTML_CSS = "STATUS_PAGE_VIEW_CUSTOM_HTML_CSS", + STATUS_PAGE_VIEW_GROUPS = "STATUS_PAGE_VIEW_GROUPS", + STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS = "STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS", + STATUS_PAGE_VIEW_PRIVATE_USERS = "STATUS_PAGE_VIEW_PRIVATE_USERS", + STATUS_PAGE_VIEW_CUSTOM_FIELDS = "STATUS_PAGE_VIEW_CUSTOM_FIELDS", + STATUS_PAGE_VIEW_SSO = "STATUS_PAGE_VIEW_SSO", + STATUS_PAGE_VIEW_OWNERS = "STATUS_PAGE_VIEW_OWNERS", + STATUS_PAGE_VIEW_SETTINGS = "STATUS_PAGE_VIEW_SETTINGS", - LOGS = 'LOGS', + LOGS = "LOGS", - ON_CALL_DUTY_ROOT = 'ON_CALL_DUTY_ROOT', - ON_CALL_DUTY = 'ON_CALL_DUTY', - ON_CALL_DUTY_POLICIES = 'ON_CALL_DUTY_POLICIES', - ON_CALL_DUTY_EXECUTION_LOGS = 'ON_CALL_DUTY_EXECUTION_LOGS', - ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE = 'ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE', - ON_CALL_DUTY_POLICY_VIEW = 'ON_CALL_DUTY_POLICY_VIEW', - ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS = 'ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS', - ON_CALL_DUTY_POLICY_VIEW_DELETE = 'ON_CALL_DUTY_POLICY_VIEW_DELETE', - ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS = 'ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS', - ON_CALL_DUTY_POLICY_VIEW_ESCALATION = 'ON_CALL_DUTY_POLICY_VIEW_ESCALATION', - ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW = 'ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW', + ON_CALL_DUTY_ROOT = "ON_CALL_DUTY_ROOT", + ON_CALL_DUTY = "ON_CALL_DUTY", + ON_CALL_DUTY_POLICIES = "ON_CALL_DUTY_POLICIES", + ON_CALL_DUTY_EXECUTION_LOGS = "ON_CALL_DUTY_EXECUTION_LOGS", + ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE = "ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE", + ON_CALL_DUTY_POLICY_VIEW = "ON_CALL_DUTY_POLICY_VIEW", + ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS = "ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS", + ON_CALL_DUTY_POLICY_VIEW_DELETE = "ON_CALL_DUTY_POLICY_VIEW_DELETE", + ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS = "ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS", + ON_CALL_DUTY_POLICY_VIEW_ESCALATION = "ON_CALL_DUTY_POLICY_VIEW_ESCALATION", + ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW = "ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW", - ON_CALL_DUTY_SCHEDULES = 'ON_CALL_DUTY_SCHEDULES', - ON_CALL_DUTY_SCHEDULE_VIEW = 'ON_CALL_DUTY_SCHEDULE_VIEW', - ON_CALL_DUTY_SCHEDULE_VIEW_DELETE = 'ON_CALL_DUTY_SCHEDULE_VIEW_DELETE', - ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS = 'ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS', + ON_CALL_DUTY_SCHEDULES = "ON_CALL_DUTY_SCHEDULES", + ON_CALL_DUTY_SCHEDULE_VIEW = "ON_CALL_DUTY_SCHEDULE_VIEW", + ON_CALL_DUTY_SCHEDULE_VIEW_DELETE = "ON_CALL_DUTY_SCHEDULE_VIEW_DELETE", + ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS = "ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS", - AUTOMATION_SCRIPTS = 'AUTOMATION_SCRIPTS', - REPORTS = 'REPORTS', - ERROR_TRACKER = 'ERROR_TRACKER', + AUTOMATION_SCRIPTS = "AUTOMATION_SCRIPTS", + REPORTS = "REPORTS", + ERROR_TRACKER = "ERROR_TRACKER", - // User Settings. - USER_SETTINGS_ROOT = 'USER_SETTINGS_ROOT', - USER_SETTINGS = 'USER_SETTINGS', - USER_SETTINGS_NOTIFICATION_METHODS = 'USER_SETTINGS_NOTIFICATION_METHODS', - USER_SETTINGS_ON_CALL_RULES = 'USER_SETTINGS_ON_CALL_RULES', - USER_SETTINGS_ON_CALL_LOGS = 'USER_SETTINGS_ON_CALL_LOGS', - USER_SETTINGS_ON_CALL_LOGS_TIMELINE = 'USER_SETTINGS_ON_CALL_LOGS_TIMELINE', - USER_SETTINGS_NOTIFICATION_SETTINGS = 'USER_SETTINGS_NOTIFICATION_SETTINGS', + // User Settings. + USER_SETTINGS_ROOT = "USER_SETTINGS_ROOT", + USER_SETTINGS = "USER_SETTINGS", + USER_SETTINGS_NOTIFICATION_METHODS = "USER_SETTINGS_NOTIFICATION_METHODS", + USER_SETTINGS_ON_CALL_RULES = "USER_SETTINGS_ON_CALL_RULES", + USER_SETTINGS_ON_CALL_LOGS = "USER_SETTINGS_ON_CALL_LOGS", + USER_SETTINGS_ON_CALL_LOGS_TIMELINE = "USER_SETTINGS_ON_CALL_LOGS_TIMELINE", + USER_SETTINGS_NOTIFICATION_SETTINGS = "USER_SETTINGS_NOTIFICATION_SETTINGS", - // Settings Routes - SETTINGS_ROOT = 'SETTINGS_ROOT', - SETTINGS = 'SETTINGS', - SETTINGS_DANGERZONE = 'SETTINGS_DANGERZONE', + // Settings Routes + SETTINGS_ROOT = "SETTINGS_ROOT", + SETTINGS = "SETTINGS", + SETTINGS_DANGERZONE = "SETTINGS_DANGERZONE", - // API Keys. - SETTINGS_APIKEYS = 'SETTINGS_APIKEYS', - SETTINGS_APIKEY_VIEW = 'SETTINGS_APIKEY_VIEW', + // API Keys. + SETTINGS_APIKEYS = "SETTINGS_APIKEYS", + SETTINGS_APIKEY_VIEW = "SETTINGS_APIKEY_VIEW", - // Team - SETTINGS_TEAMS = 'SETTINGS_TEAMS', - SETTINGS_TEAM_VIEW = 'SETTINGS_TEAM_VIEW', + // Team + SETTINGS_TEAMS = "SETTINGS_TEAMS", + SETTINGS_TEAM_VIEW = "SETTINGS_TEAM_VIEW", - // Resource settings. - SETTINGS_INCIDENTS_STATE = 'SETTINGS_INCIDENTS_STATE', - SETTINGS_INCIDENTS_SEVERITY = 'SETTINGS_INCIDENTS_SEVERITY', - SETTINGS_INCIDENT_CUSTOM_FIELDS = 'SETTINGS_INCIDENT_CUSTOM_FIELDS', + // Resource settings. + SETTINGS_INCIDENTS_STATE = "SETTINGS_INCIDENTS_STATE", + SETTINGS_INCIDENTS_SEVERITY = "SETTINGS_INCIDENTS_SEVERITY", + SETTINGS_INCIDENT_CUSTOM_FIELDS = "SETTINGS_INCIDENT_CUSTOM_FIELDS", - // Status Page - SETTINGS_STATUS_PAGE_CUSTOM_FIELDS = 'SETTINGS_STATUS_PAGE_CUSTOM_FIELDS', + // Status Page + SETTINGS_STATUS_PAGE_CUSTOM_FIELDS = "SETTINGS_STATUS_PAGE_CUSTOM_FIELDS", - // monitors - SETTINGS_MONITORS_STATUS = 'SETTINGS_MONITORS_STATUS', - SETTINGS_MONITOR_CUSTOM_FIELDS = 'SETTINGS_MONITOR_CUSTOM_FIELDS', - SETTINGS_MONITOR_SECRETS = 'SETTINGS_MONITOR_SECRETS', + // monitors + SETTINGS_MONITORS_STATUS = "SETTINGS_MONITORS_STATUS", + SETTINGS_MONITOR_CUSTOM_FIELDS = "SETTINGS_MONITOR_CUSTOM_FIELDS", + SETTINGS_MONITOR_SECRETS = "SETTINGS_MONITOR_SECRETS", - // incident templates - SETTINGS_INCIDENT_TEMPLATES = 'SETTINGS_INCIDENT_TEMPLATES', - SETTINGS_INCIDENT_TEMPLATES_VIEW = 'SETTINGS_INCIDENT_TEMPLATES_VIEW', + // incident templates + SETTINGS_INCIDENT_TEMPLATES = "SETTINGS_INCIDENT_TEMPLATES", + SETTINGS_INCIDENT_TEMPLATES_VIEW = "SETTINGS_INCIDENT_TEMPLATES_VIEW", - // incident note templates - SETTINGS_INCIDENT_NOTE_TEMPLATES = 'SETTINGS_INCIDENT_NOTE_TEMPLATES', - SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW = 'SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW', + // incident note templates + SETTINGS_INCIDENT_NOTE_TEMPLATES = "SETTINGS_INCIDENT_NOTE_TEMPLATES", + SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW = "SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW", - // incident note templates - SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES = 'SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES', - SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW = 'SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW', + // incident note templates + SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES = "SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES", + SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW = "SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW", - // on-call duty - SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS = 'SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS', + // on-call duty + SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS = "SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS", - // Scheduled Events - SETTINGS_SCHEDULED_MAINTENANCE_STATE = 'SETTINGS_SCHEDULED_MAINTENANCE_STATE', - SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS = 'SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS', + // Scheduled Events + SETTINGS_SCHEDULED_MAINTENANCE_STATE = "SETTINGS_SCHEDULED_MAINTENANCE_STATE", + SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS = "SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS", - // Labels. - SETTINGS_LABELS = 'SETTINGS_LABELS', + // Labels. + SETTINGS_LABELS = "SETTINGS_LABELS", - // Probes. - SETTINGS_PROBES = 'SETTINGS_PROBES', + // Probes. + SETTINGS_PROBES = "SETTINGS_PROBES", - // SSO. - SETTINGS_SSO = 'SETTINGS_SSO', + // SSO. + SETTINGS_SSO = "SETTINGS_SSO", - // Domains + // Domains - SETTINGS_DOMAINS = 'SETTINGS_DOMAINS', + SETTINGS_DOMAINS = "SETTINGS_DOMAINS", - // BILLING + // BILLING - SETTINGS_BILLING = 'SETTINGS_BILLING', - SETTINGS_BILLING_INVOICES = 'SETTINGS_BILLING_INVOICES', - SETTINGS_USAGE_HISTORY = 'SETTINGS_USAGE_HISTORY', + SETTINGS_BILLING = "SETTINGS_BILLING", + SETTINGS_BILLING_INVOICES = "SETTINGS_BILLING_INVOICES", + SETTINGS_USAGE_HISTORY = "SETTINGS_USAGE_HISTORY", - // Featur Flags - SETTINGS_FEATURE_FLAGS = 'SETTINGS_FEATURE_FLAGS', + // Featur Flags + SETTINGS_FEATURE_FLAGS = "SETTINGS_FEATURE_FLAGS", - // Misc - LOGOUT = 'LOGOUT', - USER_PROFILE_OVERVIEW = 'USER_PROFILE_OVERVIEW', - USER_PROFILE_PASSWORD = 'USER_PROFILE_PASSWORD', - USER_PROFILE_PICTURE = 'USER_PROFILE_PICTURE', - NEW_INCIDENTS = 'NEW_INCIDENTS', - PROJECT_INVITATIONS = 'PROJECT_INVITATIONS', + // Misc + LOGOUT = "LOGOUT", + USER_PROFILE_OVERVIEW = "USER_PROFILE_OVERVIEW", + USER_PROFILE_PASSWORD = "USER_PROFILE_PASSWORD", + USER_PROFILE_PICTURE = "USER_PROFILE_PICTURE", + NEW_INCIDENTS = "NEW_INCIDENTS", + PROJECT_INVITATIONS = "PROJECT_INVITATIONS", - // WORKFLOW - WORKFLOWS_ROOT = 'WORKFLOWS_ROOT', - WORKFLOWS = 'WORKFLOWS', - WORKFLOWS_LOGS = 'WORKFLOWS_LOGS', - WORKFLOWS_VARIABLES = 'WORKFLOWS_VARIABLES', - WORKFLOW_VIEW = 'WORKFLOW_VIEW', - WORKFLOW_DELETE = 'WORKFLOW_DELETE', - WORKFLOW_BUILDER = 'WORKFLOW_BUILDER', - WORKFLOW_LOGS = 'WORKFLOW_LOGS', - WORKFLOW_VARIABLES = 'WORKFLOW_VARIABLES', - WORKFLOW_VIEW_SETTINGS = 'WORKFLOW_VIEW_SETTINGS', + // WORKFLOW + WORKFLOWS_ROOT = "WORKFLOWS_ROOT", + WORKFLOWS = "WORKFLOWS", + WORKFLOWS_LOGS = "WORKFLOWS_LOGS", + WORKFLOWS_VARIABLES = "WORKFLOWS_VARIABLES", + WORKFLOW_VIEW = "WORKFLOW_VIEW", + WORKFLOW_DELETE = "WORKFLOW_DELETE", + WORKFLOW_BUILDER = "WORKFLOW_BUILDER", + WORKFLOW_LOGS = "WORKFLOW_LOGS", + WORKFLOW_VARIABLES = "WORKFLOW_VARIABLES", + WORKFLOW_VIEW_SETTINGS = "WORKFLOW_VIEW_SETTINGS", - // SMS and Call - SETTINGS_NOTIFICATION_SETTINGS = 'SETTINGS_NOTIFICATION_SETTINGS', - SETTINGS_SMS_LOGS = 'SETTINGS_SMS_LOGS', - SETTINGS_EMAIL_LOGS = 'SETTINGS_EMAIL_LOGS', - SETTINGS_CALL_LOGS = 'SETTINGS_CALL_LOGS', + // SMS and Call + SETTINGS_NOTIFICATION_SETTINGS = "SETTINGS_NOTIFICATION_SETTINGS", + SETTINGS_SMS_LOGS = "SETTINGS_SMS_LOGS", + SETTINGS_EMAIL_LOGS = "SETTINGS_EMAIL_LOGS", + SETTINGS_CALL_LOGS = "SETTINGS_CALL_LOGS", } export default PageMap; diff --git a/Dashboard/src/Utils/Probe.ts b/Dashboard/src/Utils/Probe.ts index 0620db3df8..0e695c2745 100644 --- a/Dashboard/src/Utils/Probe.ts +++ b/Dashboard/src/Utils/Probe.ts @@ -1,44 +1,44 @@ -import DashboardNavigation from './Navigation'; -import URL from 'Common/Types/API/URL'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import { APP_API_URL } from 'CommonUI/src/Config'; -import ListResult from 'CommonUI/src/Utils/BaseDatabase/ListResult'; -import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import Probe from 'Model/Models/Probe'; +import DashboardNavigation from "./Navigation"; +import URL from "Common/Types/API/URL"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import { APP_API_URL } from "CommonUI/src/Config"; +import ListResult from "CommonUI/src/Utils/BaseDatabase/ListResult"; +import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import Probe from "Model/Models/Probe"; export default class ProbeUtil { - public static async getAllProbes(): Promise<Array<Probe>> { - const projectProbeList: ListResult<Probe> = await ModelAPI.getList({ - modelType: Probe, - query: { - projectId: DashboardNavigation.getProjectId()?.toString(), - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - _id: true, - }, - sort: {}, - }); + public static async getAllProbes(): Promise<Array<Probe>> { + const projectProbeList: ListResult<Probe> = await ModelAPI.getList({ + modelType: Probe, + query: { + projectId: DashboardNavigation.getProjectId()?.toString(), + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + _id: true, + }, + sort: {}, + }); - const globalProbeList: ListResult<Probe> = await ModelAPI.getList({ - modelType: Probe, - query: {}, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - name: true, - _id: true, - }, - sort: {}, - requestOptions: { - overrideRequestUrl: URL.fromString( - APP_API_URL.toString() - ).addRoute('/probe/global-probes'), - }, - }); + const globalProbeList: ListResult<Probe> = await ModelAPI.getList({ + modelType: Probe, + query: {}, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + name: true, + _id: true, + }, + sort: {}, + requestOptions: { + overrideRequestUrl: URL.fromString(APP_API_URL.toString()).addRoute( + "/probe/global-probes", + ), + }, + }); - return [...projectProbeList.data, ...globalProbeList.data]; - } + return [...projectProbeList.data, ...globalProbeList.data]; + } } diff --git a/Dashboard/src/Utils/ProjectUser.ts b/Dashboard/src/Utils/ProjectUser.ts index 858ba736a1..2344788273 100644 --- a/Dashboard/src/Utils/ProjectUser.ts +++ b/Dashboard/src/Utils/ProjectUser.ts @@ -1,38 +1,38 @@ -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import ObjectID from 'Common/Types/ObjectID'; -import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown'; -import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI'; -import TeamMember from 'Model/Models/TeamMember'; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import ObjectID from "Common/Types/ObjectID"; +import { DropdownOption } from "CommonUI/src/Components/Dropdown/Dropdown"; +import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI"; +import TeamMember from "Model/Models/TeamMember"; export default class ProjectUser { - public static async fetchProjectUsersAsDropdownOptions( - projectId: ObjectID - ): Promise<Array<DropdownOption>> { - const teamMembers: ListResult<TeamMember> = await ModelAPI.getList({ - modelType: TeamMember, - query: { projectId }, - limit: LIMIT_PER_PROJECT, - skip: 0, - select: { - _id: true, - user: { - _id: true, - name: true, - email: true, - }, - }, - sort: {}, - requestOptions: {}, - }); + public static async fetchProjectUsersAsDropdownOptions( + projectId: ObjectID, + ): Promise<Array<DropdownOption>> { + const teamMembers: ListResult<TeamMember> = await ModelAPI.getList({ + modelType: TeamMember, + query: { projectId }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + _id: true, + user: { + _id: true, + name: true, + email: true, + }, + }, + sort: {}, + requestOptions: {}, + }); - return teamMembers.data.map((teamMember: TeamMember) => { - return { - value: teamMember.user?._id as string, - label: - teamMember.user?.name?.toString() || - teamMember.user?.email?.toString() || - '', - }; - }); - } + return teamMembers.data.map((teamMember: TeamMember) => { + return { + value: teamMember.user?._id as string, + label: + teamMember.user?.name?.toString() || + teamMember.user?.email?.toString() || + "", + }; + }); + } } diff --git a/Dashboard/src/Utils/RouteMap.ts b/Dashboard/src/Utils/RouteMap.ts index ee74fd6fab..615c1203fb 100644 --- a/Dashboard/src/Utils/RouteMap.ts +++ b/Dashboard/src/Utils/RouteMap.ts @@ -1,1270 +1,1243 @@ -import PageMap from './PageMap'; -import RouteParams from './RouteParams'; -import Route from 'Common/Types/API/Route'; -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; -import ProjectUtil from 'CommonUI/src/Utils/Project'; -import Project from 'Model/Models/Project'; +import PageMap from "./PageMap"; +import RouteParams from "./RouteParams"; +import Route from "Common/Types/API/Route"; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; +import ProjectUtil from "CommonUI/src/Utils/Project"; +import Project from "Model/Models/Project"; export const MonitorsRoutePath: Dictionary<string> = { - [PageMap.MONITORS_INOPERATIONAL]: 'inoperational', - [PageMap.MONITORS_DISABLED]: 'disabled', + [PageMap.MONITORS_INOPERATIONAL]: "inoperational", + [PageMap.MONITORS_DISABLED]: "disabled", - [PageMap.MONITOR_VIEW]: `${RouteParams.ModelID}`, - [PageMap.MONITOR_VIEW_INTERVAL]: `${RouteParams.ModelID}/interval`, - [PageMap.MONITOR_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, - [PageMap.MONITOR_VIEW_STATUS_TIMELINE]: `${RouteParams.ModelID}/status-timeline`, - [PageMap.MONITOR_VIEW_INCIDENTS]: `${RouteParams.ModelID}/incidents`, - [PageMap.MONITOR_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`, - [PageMap.MONITOR_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, - [PageMap.MONITOR_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, - [PageMap.MONITOR_VIEW_CRITERIA]: `${RouteParams.ModelID}/criteria`, - [PageMap.MONITOR_VIEW_PROBES]: `${RouteParams.ModelID}/probes`, - [PageMap.MONITOR_VIEW_DOCUMENTATION]: `${RouteParams.ModelID}/documentation`, + [PageMap.MONITOR_VIEW]: `${RouteParams.ModelID}`, + [PageMap.MONITOR_VIEW_INTERVAL]: `${RouteParams.ModelID}/interval`, + [PageMap.MONITOR_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, + [PageMap.MONITOR_VIEW_STATUS_TIMELINE]: `${RouteParams.ModelID}/status-timeline`, + [PageMap.MONITOR_VIEW_INCIDENTS]: `${RouteParams.ModelID}/incidents`, + [PageMap.MONITOR_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`, + [PageMap.MONITOR_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, + [PageMap.MONITOR_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, + [PageMap.MONITOR_VIEW_CRITERIA]: `${RouteParams.ModelID}/criteria`, + [PageMap.MONITOR_VIEW_PROBES]: `${RouteParams.ModelID}/probes`, + [PageMap.MONITOR_VIEW_DOCUMENTATION]: `${RouteParams.ModelID}/documentation`, }; export const ServiceCatalogRoutePath: Dictionary<string> = { - [PageMap.SERVICE_CATALOG_VIEW]: `${RouteParams.ModelID}`, - [PageMap.SERVICE_CATALOG_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, - [PageMap.SERVICE_CATALOG_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, - [PageMap.SERVICE_CATALOG_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, + [PageMap.SERVICE_CATALOG_VIEW]: `${RouteParams.ModelID}`, + [PageMap.SERVICE_CATALOG_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, + [PageMap.SERVICE_CATALOG_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, + [PageMap.SERVICE_CATALOG_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, }; export const CodeRepositoryRoutePath: Dictionary<string> = { - [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW]: `code-repository/${RouteParams.ModelID}`, - [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE]: `code-repository/${RouteParams.ModelID}/delete`, - [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS]: `code-repository/${RouteParams.ModelID}/settings`, - [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES]: `code-repository/${RouteParams.ModelID}/services`, + [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW]: `code-repository/${RouteParams.ModelID}`, + [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE]: `code-repository/${RouteParams.ModelID}/delete`, + [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS]: `code-repository/${RouteParams.ModelID}/settings`, + [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES]: `code-repository/${RouteParams.ModelID}/services`, }; export const WorkflowRoutePath: Dictionary<string> = { - [PageMap.WORKFLOWS_LOGS]: 'logs', - [PageMap.WORKFLOWS_VARIABLES]: 'variables', - [PageMap.WORKFLOW_VARIABLES]: `${RouteParams.ModelID}/variables`, - [PageMap.WORKFLOW_BUILDER]: `${RouteParams.ModelID}/builder`, - [PageMap.WORKFLOW_VIEW]: `${RouteParams.ModelID}`, - [PageMap.WORKFLOW_LOGS]: `${RouteParams.ModelID}/logs`, - [PageMap.WORKFLOW_DELETE]: `${RouteParams.ModelID}/delete`, - [PageMap.WORKFLOW_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, + [PageMap.WORKFLOWS_LOGS]: "logs", + [PageMap.WORKFLOWS_VARIABLES]: "variables", + [PageMap.WORKFLOW_VARIABLES]: `${RouteParams.ModelID}/variables`, + [PageMap.WORKFLOW_BUILDER]: `${RouteParams.ModelID}/builder`, + [PageMap.WORKFLOW_VIEW]: `${RouteParams.ModelID}`, + [PageMap.WORKFLOW_LOGS]: `${RouteParams.ModelID}/logs`, + [PageMap.WORKFLOW_DELETE]: `${RouteParams.ModelID}/delete`, + [PageMap.WORKFLOW_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, }; export const TelemetryRouthPath: Dictionary<string> = { - [PageMap.TELEMETRY_SERVICES_VIEW]: `${RouteParams.ModelID}`, - [PageMap.TELEMETRY_SERVICES_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, - [PageMap.TELEMETRY_SERVICES_VIEW_LOGS]: `${RouteParams.ModelID}/logs`, - [PageMap.TELEMETRY_SERVICES_VIEW_TRACES]: `${RouteParams.ModelID}/traces`, - [PageMap.TELEMETRY_SERVICES_VIEW_TRACE]: `${RouteParams.ModelID}/traces/${RouteParams.SubModelID}`, - [PageMap.TELEMETRY_SERVICES_VIEW_METRICS]: `${RouteParams.ModelID}/metrics`, - [PageMap.TELEMETRY_SERVICES_VIEW_METRIC]: `${RouteParams.ModelID}/metrics/${RouteParams.SubModelID}`, - [PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS]: `${RouteParams.ModelID}/dashboards`, - [PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, - [PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION]: `${RouteParams.ModelID}/documentation`, + [PageMap.TELEMETRY_SERVICES_VIEW]: `${RouteParams.ModelID}`, + [PageMap.TELEMETRY_SERVICES_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, + [PageMap.TELEMETRY_SERVICES_VIEW_LOGS]: `${RouteParams.ModelID}/logs`, + [PageMap.TELEMETRY_SERVICES_VIEW_TRACES]: `${RouteParams.ModelID}/traces`, + [PageMap.TELEMETRY_SERVICES_VIEW_TRACE]: `${RouteParams.ModelID}/traces/${RouteParams.SubModelID}`, + [PageMap.TELEMETRY_SERVICES_VIEW_METRICS]: `${RouteParams.ModelID}/metrics`, + [PageMap.TELEMETRY_SERVICES_VIEW_METRIC]: `${RouteParams.ModelID}/metrics/${RouteParams.SubModelID}`, + [PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS]: `${RouteParams.ModelID}/dashboards`, + [PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, + [PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION]: `${RouteParams.ModelID}/documentation`, }; export const StatusPagesRoutePath: Dictionary<string> = { - [PageMap.STATUS_PAGE_VIEW]: `${RouteParams.ModelID}`, - [PageMap.STATUS_PAGE_VIEW_BRANDING]: `${RouteParams.ModelID}/branding`, - [PageMap.STATUS_PAGE_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, - [PageMap.STATUS_PAGE_VIEW_GROUPS]: `${RouteParams.ModelID}/groups`, - [PageMap.STATUS_PAGE_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, - [PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`, - [PageMap.STATUS_PAGE_VIEW_DOMAINS]: `${RouteParams.ModelID}/domains`, - [PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS]: `${RouteParams.ModelID}/email-subscribers`, - [PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS]: `${RouteParams.ModelID}/sms-subscribers`, - [PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS]: `${RouteParams.ModelID}/webhook-subscribers`, - [PageMap.STATUS_PAGE_VIEW_HEADER_STYLE]: `${RouteParams.ModelID}/header-style`, - [PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE]: `${RouteParams.ModelID}/footer-style`, - [PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING]: `${RouteParams.ModelID}/overview-page-branding`, - [PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS]: `${RouteParams.ModelID}/private-users`, - [PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE]: `${RouteParams.ModelID}/navbar-style`, - [PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS]: `${RouteParams.ModelID}/announcements`, - [PageMap.STATUS_PAGE_VIEW_EMBEDDED]: `${RouteParams.ModelID}/embedded`, - [PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS]: `${RouteParams.ModelID}/subscriber-settings`, - [PageMap.STATUS_PAGE_VIEW_SSO]: `${RouteParams.ModelID}/sso`, - [PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS]: `${RouteParams.ModelID}/custom-code`, - [PageMap.STATUS_PAGE_VIEW_RESOURCES]: `${RouteParams.ModelID}/resources`, - [PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS]: `${RouteParams.ModelID}/advanced-options`, - [PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS]: `${RouteParams.ModelID}/authentication-settings`, - [PageMap.STATUS_PAGE_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, + [PageMap.STATUS_PAGE_VIEW]: `${RouteParams.ModelID}`, + [PageMap.STATUS_PAGE_VIEW_BRANDING]: `${RouteParams.ModelID}/branding`, + [PageMap.STATUS_PAGE_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, + [PageMap.STATUS_PAGE_VIEW_GROUPS]: `${RouteParams.ModelID}/groups`, + [PageMap.STATUS_PAGE_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, + [PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`, + [PageMap.STATUS_PAGE_VIEW_DOMAINS]: `${RouteParams.ModelID}/domains`, + [PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS]: `${RouteParams.ModelID}/email-subscribers`, + [PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS]: `${RouteParams.ModelID}/sms-subscribers`, + [PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS]: `${RouteParams.ModelID}/webhook-subscribers`, + [PageMap.STATUS_PAGE_VIEW_HEADER_STYLE]: `${RouteParams.ModelID}/header-style`, + [PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE]: `${RouteParams.ModelID}/footer-style`, + [PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING]: `${RouteParams.ModelID}/overview-page-branding`, + [PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS]: `${RouteParams.ModelID}/private-users`, + [PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE]: `${RouteParams.ModelID}/navbar-style`, + [PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS]: `${RouteParams.ModelID}/announcements`, + [PageMap.STATUS_PAGE_VIEW_EMBEDDED]: `${RouteParams.ModelID}/embedded`, + [PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS]: `${RouteParams.ModelID}/subscriber-settings`, + [PageMap.STATUS_PAGE_VIEW_SSO]: `${RouteParams.ModelID}/sso`, + [PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS]: `${RouteParams.ModelID}/custom-code`, + [PageMap.STATUS_PAGE_VIEW_RESOURCES]: `${RouteParams.ModelID}/resources`, + [PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS]: `${RouteParams.ModelID}/advanced-options`, + [PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS]: `${RouteParams.ModelID}/authentication-settings`, + [PageMap.STATUS_PAGE_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`, }; export const IncidentsRoutePath: Dictionary<string> = { - [PageMap.UNRESOLVED_INCIDENTS]: 'unresolved', - [PageMap.INCIDENT_VIEW]: `${RouteParams.ModelID}`, - [PageMap.INCIDENT_VIEW_STATE_TIMELINE]: `${RouteParams.ModelID}/state-timeline`, - [PageMap.INCIDENT_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, - [PageMap.INCIDENT_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, - [PageMap.INCIDENT_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`, - [PageMap.INCIDENT_INTERNAL_NOTE]: `${RouteParams.ModelID}/internal-notes`, - [PageMap.INCIDENT_PUBLIC_NOTE]: `${RouteParams.ModelID}/public-notes`, + [PageMap.UNRESOLVED_INCIDENTS]: "unresolved", + [PageMap.INCIDENT_VIEW]: `${RouteParams.ModelID}`, + [PageMap.INCIDENT_VIEW_STATE_TIMELINE]: `${RouteParams.ModelID}/state-timeline`, + [PageMap.INCIDENT_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, + [PageMap.INCIDENT_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, + [PageMap.INCIDENT_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`, + [PageMap.INCIDENT_INTERNAL_NOTE]: `${RouteParams.ModelID}/internal-notes`, + [PageMap.INCIDENT_PUBLIC_NOTE]: `${RouteParams.ModelID}/public-notes`, }; export const ScheduledMaintenanceEventsRoutePath: Dictionary<string> = { - [PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS]: 'ongoing', - [PageMap.SCHEDULED_MAINTENANCE_VIEW]: `${RouteParams.ModelID}`, - [PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, - [PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE]: `${RouteParams.ModelID}/state-timeline`, - [PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, - [PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE]: `${RouteParams.ModelID}/internal-notes`, - [PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE]: `${RouteParams.ModelID}/public-notes`, - [PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`, + [PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS]: "ongoing", + [PageMap.SCHEDULED_MAINTENANCE_VIEW]: `${RouteParams.ModelID}`, + [PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, + [PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE]: `${RouteParams.ModelID}/state-timeline`, + [PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, + [PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE]: `${RouteParams.ModelID}/internal-notes`, + [PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE]: `${RouteParams.ModelID}/public-notes`, + [PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS]: `${RouteParams.ModelID}/custom-fields`, }; export const SettingsRoutePath: Dictionary<string> = { - [PageMap.SETTINGS_DANGERZONE]: 'danger-zone', - [PageMap.SETTINGS_NOTIFICATION_SETTINGS]: 'notification-settings', - [PageMap.SETTINGS_SMS_LOGS]: 'sms-logs', - [PageMap.SETTINGS_EMAIL_LOGS]: 'email-logs', - [PageMap.SETTINGS_CALL_LOGS]: 'call-logs', - [PageMap.SETTINGS_APIKEYS]: `api-keys`, - [PageMap.SETTINGS_APIKEY_VIEW]: `api-keys/${RouteParams.ModelID}`, - [PageMap.SETTINGS_MONITORS_STATUS]: 'monitors-status', - [PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS]: 'monitor-custom-fields', - [PageMap.SETTINGS_MONITOR_SECRETS]: 'monitor-secrets', - [PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS]: 'incident-custom-fields', - [PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS]: - 'on-call-duty-policy-custom-fields', - [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS]: - 'scheduled-maintenance-custom-fields', - [PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS]: 'status-page-custom-fields', - [PageMap.SETTINGS_INCIDENTS_STATE]: 'incidents-state', - [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE]: - 'scheduled-maintenance-state', - [PageMap.SETTINGS_INCIDENTS_SEVERITY]: 'incidents-severity', - [PageMap.SETTINGS_DOMAINS]: 'domains', - [PageMap.SETTINGS_FEATURE_FLAGS]: 'feature-flags', - [PageMap.SETTINGS_SSO]: 'sso', - [PageMap.SETTINGS_TEAMS]: 'teams', - [PageMap.SETTINGS_INCIDENT_TEMPLATES]: 'incident-templates', - [PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW]: `incident-templates/${RouteParams.ModelID}`, - [PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES]: 'incident-note-templates', - [PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW]: `incident-note-templates/${RouteParams.ModelID}`, - [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES]: - 'scheduled-maintenance-note-templates', - [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW]: `scheduled-maintenance-note-templates/${RouteParams.ModelID}`, - [PageMap.SETTINGS_BILLING]: 'billing', - [PageMap.SETTINGS_BILLING_INVOICES]: 'invoices', - [PageMap.SETTINGS_USAGE_HISTORY]: 'usage-history', - [PageMap.SETTINGS_TEAM_VIEW]: `teams/${RouteParams.ModelID}`, - [PageMap.SETTINGS_LABELS]: 'labels', - [PageMap.SETTINGS_PROBES]: 'probes', + [PageMap.SETTINGS_DANGERZONE]: "danger-zone", + [PageMap.SETTINGS_NOTIFICATION_SETTINGS]: "notification-settings", + [PageMap.SETTINGS_SMS_LOGS]: "sms-logs", + [PageMap.SETTINGS_EMAIL_LOGS]: "email-logs", + [PageMap.SETTINGS_CALL_LOGS]: "call-logs", + [PageMap.SETTINGS_APIKEYS]: `api-keys`, + [PageMap.SETTINGS_APIKEY_VIEW]: `api-keys/${RouteParams.ModelID}`, + [PageMap.SETTINGS_MONITORS_STATUS]: "monitors-status", + [PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS]: "monitor-custom-fields", + [PageMap.SETTINGS_MONITOR_SECRETS]: "monitor-secrets", + [PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS]: "incident-custom-fields", + [PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS]: + "on-call-duty-policy-custom-fields", + [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS]: + "scheduled-maintenance-custom-fields", + [PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS]: "status-page-custom-fields", + [PageMap.SETTINGS_INCIDENTS_STATE]: "incidents-state", + [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE]: "scheduled-maintenance-state", + [PageMap.SETTINGS_INCIDENTS_SEVERITY]: "incidents-severity", + [PageMap.SETTINGS_DOMAINS]: "domains", + [PageMap.SETTINGS_FEATURE_FLAGS]: "feature-flags", + [PageMap.SETTINGS_SSO]: "sso", + [PageMap.SETTINGS_TEAMS]: "teams", + [PageMap.SETTINGS_INCIDENT_TEMPLATES]: "incident-templates", + [PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW]: `incident-templates/${RouteParams.ModelID}`, + [PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES]: "incident-note-templates", + [PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW]: `incident-note-templates/${RouteParams.ModelID}`, + [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES]: + "scheduled-maintenance-note-templates", + [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW]: `scheduled-maintenance-note-templates/${RouteParams.ModelID}`, + [PageMap.SETTINGS_BILLING]: "billing", + [PageMap.SETTINGS_BILLING_INVOICES]: "invoices", + [PageMap.SETTINGS_USAGE_HISTORY]: "usage-history", + [PageMap.SETTINGS_TEAM_VIEW]: `teams/${RouteParams.ModelID}`, + [PageMap.SETTINGS_LABELS]: "labels", + [PageMap.SETTINGS_PROBES]: "probes", }; export const OnCallDutyRoutePath: Dictionary<string> = { - [PageMap.ON_CALL_DUTY_SCHEDULES]: 'schedules', - [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW]: `schedules/${RouteParams.ModelID}`, - [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE]: `schedules/${RouteParams.ModelID}/delete`, - [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS]: `schedules/${RouteParams.ModelID}/layers`, - [PageMap.ON_CALL_DUTY_POLICIES]: 'policies', - [PageMap.ON_CALL_DUTY_POLICY_VIEW]: `policies/${RouteParams.ModelID}`, - [PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE]: `policies/${RouteParams.ModelID}/delete`, - [PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS]: `policies/${RouteParams.ModelID}/execution-logs`, - [PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS]: `policies/${RouteParams.ModelID}/custom-fields`, - [PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW]: `policies/${RouteParams.ModelID}/execution-logs/${RouteParams.SubModelID}`, - [PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION]: `policies/${RouteParams.ModelID}/escalation`, - [PageMap.ON_CALL_DUTY_EXECUTION_LOGS]: 'execution-logs', - [PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE]: `execution-logs/${RouteParams.ModelID}`, + [PageMap.ON_CALL_DUTY_SCHEDULES]: "schedules", + [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW]: `schedules/${RouteParams.ModelID}`, + [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE]: `schedules/${RouteParams.ModelID}/delete`, + [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS]: `schedules/${RouteParams.ModelID}/layers`, + [PageMap.ON_CALL_DUTY_POLICIES]: "policies", + [PageMap.ON_CALL_DUTY_POLICY_VIEW]: `policies/${RouteParams.ModelID}`, + [PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE]: `policies/${RouteParams.ModelID}/delete`, + [PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS]: `policies/${RouteParams.ModelID}/execution-logs`, + [PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS]: `policies/${RouteParams.ModelID}/custom-fields`, + [PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW]: `policies/${RouteParams.ModelID}/execution-logs/${RouteParams.SubModelID}`, + [PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION]: `policies/${RouteParams.ModelID}/escalation`, + [PageMap.ON_CALL_DUTY_EXECUTION_LOGS]: "execution-logs", + [PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE]: `execution-logs/${RouteParams.ModelID}`, }; export const MonitorGroupRoutePath: Dictionary<string> = { - [PageMap.MONITOR_GROUP_VIEW]: `${RouteParams.ModelID}`, - [PageMap.MONITOR_GROUP_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, - [PageMap.MONITOR_GROUP_VIEW_INCIDENTS]: `${RouteParams.ModelID}/incidents`, - [PageMap.MONITOR_GROUP_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, - [PageMap.MONITOR_GROUP_VIEW_MONITORS]: `${RouteParams.ModelID}/monitors`, + [PageMap.MONITOR_GROUP_VIEW]: `${RouteParams.ModelID}`, + [PageMap.MONITOR_GROUP_VIEW_OWNERS]: `${RouteParams.ModelID}/owners`, + [PageMap.MONITOR_GROUP_VIEW_INCIDENTS]: `${RouteParams.ModelID}/incidents`, + [PageMap.MONITOR_GROUP_VIEW_DELETE]: `${RouteParams.ModelID}/delete`, + [PageMap.MONITOR_GROUP_VIEW_MONITORS]: `${RouteParams.ModelID}/monitors`, }; export const UserSettingsRoutePath: Dictionary<string> = { - [PageMap.USER_SETTINGS]: 'notification-methods', - [PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS]: 'notification-settings', - [PageMap.USER_SETTINGS_NOTIFICATION_METHODS]: 'notification-methods', - [PageMap.USER_SETTINGS_ON_CALL_RULES]: 'on-call-rules', - [PageMap.USER_SETTINGS_ON_CALL_LOGS]: 'on-call-logs', - [PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE]: `on-call-logs/${RouteParams.ModelID}`, + [PageMap.USER_SETTINGS]: "notification-methods", + [PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS]: "notification-settings", + [PageMap.USER_SETTINGS_NOTIFICATION_METHODS]: "notification-methods", + [PageMap.USER_SETTINGS_ON_CALL_RULES]: "on-call-rules", + [PageMap.USER_SETTINGS_ON_CALL_LOGS]: "on-call-logs", + [PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE]: `on-call-logs/${RouteParams.ModelID}`, }; const RouteMap: Dictionary<Route> = { - [PageMap.INIT]: new Route(`/dashboard`), - - [PageMap.WELCOME]: new Route(`/dashboard/welcome`), - - [PageMap.PROJECT_SSO]: new Route(`/dashboard/${RouteParams.ProjectID}/sso`), - - [PageMap.INIT_PROJECT]: new Route(`/dashboard/${RouteParams.ProjectID}`), - - [PageMap.HOME]: new Route(`/dashboard/${RouteParams.ProjectID}/home/`), - - [PageMap.HOME_NOT_OPERATIONAL_MONITORS]: new Route( - `/dashboard/${RouteParams.ProjectID}/home/monitors-inoperational` - ), - - [PageMap.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/home/scheduled-maintenance-ongoing` - ), - - [PageMap.MONITORS_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/*` - ), - [PageMap.MONITORS]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors` - ), - - [PageMap.MONITORS_INOPERATIONAL]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITORS_INOPERATIONAL] - }` - ), - - [PageMap.MONITORS_DISABLED]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITORS_DISABLED] - }` - ), - - [PageMap.MONITOR_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW] - }` - ), - - [PageMap.MONITOR_VIEW_INTERVAL]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_INTERVAL] - }` - ), - - [PageMap.MONITOR_VIEW_DOCUMENTATION]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_DOCUMENTATION] - }` - ), - - [PageMap.MONITOR_VIEW_OWNERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_OWNERS] - }` - ), - - [PageMap.MONITOR_VIEW_STATUS_TIMELINE]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_STATUS_TIMELINE] - }` - ), - - [PageMap.MONITOR_VIEW_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_CUSTOM_FIELDS] - }` - ), - - [PageMap.MONITOR_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_DELETE] - }` - ), - - [PageMap.MONITOR_VIEW_INCIDENTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_INCIDENTS] - }` - ), - - [PageMap.MONITOR_VIEW_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_SETTINGS] - }` - ), - - [PageMap.MONITOR_VIEW_PROBES]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_PROBES] - }` - ), - - [PageMap.MONITOR_VIEW_CRITERIA]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitors/${ - MonitorsRoutePath[PageMap.MONITOR_VIEW_CRITERIA] - }` - ), - - [PageMap.INCIDENTS_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents/*` - ), - - [PageMap.INCIDENTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents` - ), - - [PageMap.UNRESOLVED_INCIDENTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents/${ - IncidentsRoutePath[PageMap.UNRESOLVED_INCIDENTS] - }` - ), - - [PageMap.USER_PROFILE_OVERVIEW]: new Route( - `/dashboard/user-profile/overview` - ), - [PageMap.USER_PROFILE_PASSWORD]: new Route( - `/dashboard/user-profile/password-management` - ), - [PageMap.USER_PROFILE_PICTURE]: new Route( - `/dashboard/user-profile/profile-picture` - ), - - [PageMap.NEW_INCIDENTS]: new Route(`/dashboard/new-incidents`), - - [PageMap.PROJECT_INVITATIONS]: new Route(`/dashboard/project-invitations`), - - [PageMap.INCIDENT_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents/${ - IncidentsRoutePath[PageMap.INCIDENT_VIEW] - }` - ), - - [PageMap.INCIDENT_VIEW_STATE_TIMELINE]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents/${ - IncidentsRoutePath[PageMap.INCIDENT_VIEW_STATE_TIMELINE] - }` - ), - - [PageMap.INCIDENT_VIEW_OWNERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents/${ - IncidentsRoutePath[PageMap.INCIDENT_VIEW_OWNERS] - }` - ), - - [PageMap.INCIDENT_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents/${ - IncidentsRoutePath[PageMap.INCIDENT_VIEW_DELETE] - }` - ), - - [PageMap.INCIDENT_VIEW_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents/${ - IncidentsRoutePath[PageMap.INCIDENT_VIEW_CUSTOM_FIELDS] - }` - ), - - [PageMap.INCIDENT_INTERNAL_NOTE]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents/${ - IncidentsRoutePath[PageMap.INCIDENT_INTERNAL_NOTE] - }` - ), - - [PageMap.INCIDENT_PUBLIC_NOTE]: new Route( - `/dashboard/${RouteParams.ProjectID}/incidents/${ - IncidentsRoutePath[PageMap.INCIDENT_PUBLIC_NOTE] - }` - ), - - [PageMap.SCHEDULED_MAINTENANCE_EVENTS_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/*` - ), - - [PageMap.SCHEDULED_MAINTENANCE_EVENTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events` - ), - - [PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ - ScheduledMaintenanceEventsRoutePath[ - PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS - ] - }` - ), - - [PageMap.SCHEDULED_MAINTENANCE_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ - ScheduledMaintenanceEventsRoutePath[ - PageMap.SCHEDULED_MAINTENANCE_VIEW - ] - }` - ), - - [PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ - ScheduledMaintenanceEventsRoutePath[ - PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS - ] - }` - ), - - [PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ - ScheduledMaintenanceEventsRoutePath[ - PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE - ] - }` - ), - - [PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ - ScheduledMaintenanceEventsRoutePath[ - PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE - ] - }` - ), - - [PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ - ScheduledMaintenanceEventsRoutePath[ - PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE - ] - }` - ), - - [PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ - ScheduledMaintenanceEventsRoutePath[ - PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS - ] - }` - ), - - [PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE]: new Route( - `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ - ScheduledMaintenanceEventsRoutePath[ - PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE - ] - }` - ), - - [PageMap.AI_COPILOT_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/ai-copilot/*` - ), - - [PageMap.AI_COPILOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/ai-copilot` - ), - - [PageMap.AI_COPILOT_CODE_REPOSITORY]: new Route( - `/dashboard/${RouteParams.ProjectID}/ai-copilot/code-repository` - ), - - [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/ai-copilot/${ - CodeRepositoryRoutePath[PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW] - }` - ), - - [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/ai-copilot/${ - CodeRepositoryRoutePath[ - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE - ] - }` - ), - - [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/ai-copilot/${ - CodeRepositoryRoutePath[ - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS - ] - }` - ), - - [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES]: new Route( - `/dashboard/${RouteParams.ProjectID}/ai-copilot/${ - CodeRepositoryRoutePath[ - PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES - ] - }` - ), - - [PageMap.SERVICE_CATALOG_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/service-catalog/*` - ), - - [PageMap.SERVICE_CATALOG]: new Route( - `/dashboard/${RouteParams.ProjectID}/service-catalog` - ), - - [PageMap.SERVICE_CATALOG_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/service-catalog/${ - ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW] - }` - ), - - [PageMap.SERVICE_CATALOG_VIEW_OWNERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/service-catalog/${ - ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_OWNERS] - }` - ), - - [PageMap.SERVICE_CATALOG_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/service-catalog/${ - ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_DELETE] - }` - ), - - [PageMap.SERVICE_CATALOG_VIEW_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/service-catalog/${ - ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_SETTINGS] - }` - ), - - [PageMap.STATUS_PAGES_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/*` - ), - - [PageMap.STATUS_PAGES]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages` - ), - - [PageMap.STATUS_PAGE_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_BRANDING]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_BRANDING] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_OWNERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_OWNERS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_GROUPS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_GROUPS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_DELETE] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_DOMAINS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_DOMAINS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_HEADER_STYLE]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_HEADER_STYLE] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[ - PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING - ] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_EMBEDDED]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_EMBEDDED] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_SSO]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_SSO] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_RESOURCES]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_RESOURCES] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[ - PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS - ] - }` - ), - - [PageMap.STATUS_PAGE_VIEW_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/status-pages/${ - StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_SETTINGS] - }` - ), - - [PageMap.LOGS]: new Route(`/dashboard/${RouteParams.ProjectID}/logs/`), - - [PageMap.AUTOMATION_SCRIPTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/automation-scripts/` - ), - - [PageMap.ON_CALL_DUTY_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/*` - ), - - [PageMap.ON_CALL_DUTY]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/policies` - ), - - [PageMap.ON_CALL_DUTY_SCHEDULES]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULES] - }` - ), - - [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW] - }` - ), - - [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE] - }` - ), - - [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS] - }` - ), - - [PageMap.ON_CALL_DUTY_POLICIES]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/policies` - ), - - [PageMap.ON_CALL_DUTY_EXECUTION_LOGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_EXECUTION_LOGS] - }` - ), - - [PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE] - }` - ), - - [PageMap.ON_CALL_DUTY_POLICY_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW] - }` - ), - - [PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE] - }` - ), - - [PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS] - }` - ), - - [PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS] - }` - ), - - [PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[ - PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW - ] - }` - ), - - [PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION]: new Route( - `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ - OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION] - }` - ), - - [PageMap.REPORTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/reports/` - ), - - [PageMap.ERROR_TRACKER]: new Route( - `/dashboard/${RouteParams.ProjectID}/error-tracker/` - ), - - [PageMap.TELEMETRY_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/*` - ), - - [PageMap.TELEMETRY]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services` - ), - - [PageMap.TELEMETRY_SERVICES]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services` - ), - - [PageMap.TELEMETRY_SERVICES_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW] - }` - ), - - [PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION] - }` - ), - - [PageMap.TELEMETRY_SERVICES_VIEW_TRACE]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_TRACE] - }` - ), - - [PageMap.TELEMETRY_SERVICES_VIEW_METRIC]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_METRIC] - }` - ), - - [PageMap.TELEMETRY_SERVICES_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_DELETE] - }` - ), - - [PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS] - }` - ), - - //TELEMETRY_SERVICE_VIEW_LOGS - [PageMap.TELEMETRY_SERVICES_VIEW_LOGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_LOGS] - }` - ), - - //TELEMETRY_SERVICE_VIEW_TRACES - [PageMap.TELEMETRY_SERVICES_VIEW_TRACES]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_TRACES] - }` - ), - - // Metrics - [PageMap.TELEMETRY_SERVICES_VIEW_METRICS]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_METRICS] - }` - ), - - // Dashboard - [PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ - TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS] - }` - ), - - // User Settings Routes - [PageMap.USER_SETTINGS_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/user-settings/*` - ), - - [PageMap.USER_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/user-settings/${ - UserSettingsRoutePath[PageMap.USER_SETTINGS] - }` - ), - - [PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/user-settings/${ - UserSettingsRoutePath[PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS] - }` - ), - - [PageMap.USER_SETTINGS_NOTIFICATION_METHODS]: new Route( - `/dashboard/${RouteParams.ProjectID}/user-settings/${ - UserSettingsRoutePath[PageMap.USER_SETTINGS_NOTIFICATION_METHODS] - }` - ), - - [PageMap.USER_SETTINGS_ON_CALL_RULES]: new Route( - `/dashboard/${RouteParams.ProjectID}/user-settings/${ - UserSettingsRoutePath[PageMap.USER_SETTINGS_ON_CALL_RULES] - }` - ), - - [PageMap.USER_SETTINGS_ON_CALL_LOGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/user-settings/${ - UserSettingsRoutePath[PageMap.USER_SETTINGS_ON_CALL_LOGS] - }` - ), - - [PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE]: new Route( - `/dashboard/${RouteParams.ProjectID}/user-settings/${ - UserSettingsRoutePath[PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE] - }` - ), - - // Settings Routes - [PageMap.SETTINGS_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/*` - ), - - [PageMap.SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/` - ), - [PageMap.SETTINGS_DANGERZONE]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_DANGERZONE] - }` - ), - - [PageMap.SETTINGS_NOTIFICATION_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_NOTIFICATION_SETTINGS] - }` - ), - - [PageMap.SETTINGS_SMS_LOGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_SMS_LOGS] - }` - ), - - [PageMap.SETTINGS_EMAIL_LOGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_EMAIL_LOGS] - }` - ), - - [PageMap.SETTINGS_CALL_LOGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_CALL_LOGS] - }` - ), - - [PageMap.SETTINGS_APIKEYS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_APIKEYS] - }` - ), - - [PageMap.SETTINGS_APIKEY_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_APIKEY_VIEW] - }` - ), - - [PageMap.SETTINGS_MONITORS_STATUS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_MONITORS_STATUS] - }` - ), - - [PageMap.SETTINGS_INCIDENTS_STATE]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_INCIDENTS_STATE] - }` - ), - - [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE] - }` - ), - - [PageMap.SETTINGS_INCIDENTS_SEVERITY]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_INCIDENTS_SEVERITY] - }` - ), - - [PageMap.SETTINGS_DOMAINS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_DOMAINS] - }` - ), - - [PageMap.SETTINGS_FEATURE_FLAGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_FEATURE_FLAGS] - }` - ), - - [PageMap.SETTINGS_SSO]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_SSO] - }` - ), - - [PageMap.SETTINGS_TEAMS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_TEAMS] - }` - ), - - [PageMap.SETTINGS_INCIDENT_TEMPLATES]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_INCIDENT_TEMPLATES] - }` - ), - - [PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW] - }` - ), - - [PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES] - }` - ), - - [PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW] - }` - ), - - [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[ - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES - ] - }` - ), - - [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[ - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW - ] - }` - ), - [PageMap.SETTINGS_BILLING]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_BILLING] - }` - ), - - [PageMap.SETTINGS_BILLING_INVOICES]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_BILLING_INVOICES] - }` - ), - - [PageMap.SETTINGS_USAGE_HISTORY]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_USAGE_HISTORY] - }` - ), - - [PageMap.SETTINGS_TEAM_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_TEAM_VIEW] - }` - ), - - // labels. - [PageMap.SETTINGS_LABELS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_LABELS] - }` - ), - - // Probes. - [PageMap.SETTINGS_PROBES]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_PROBES] - }` - ), - - // workflows. - [PageMap.WORKFLOWS_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows/*` - ), - [PageMap.WORKFLOWS]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows` - ), - - [PageMap.WORKFLOWS_LOGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows/${ - WorkflowRoutePath[PageMap.WORKFLOWS_LOGS] - }` - ), - - [PageMap.WORKFLOWS_VARIABLES]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows/${ - WorkflowRoutePath[PageMap.WORKFLOWS_VARIABLES] - }` - ), - - [PageMap.WORKFLOW_VARIABLES]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows/${ - WorkflowRoutePath[PageMap.WORKFLOW_VARIABLES] - }` - ), - - [PageMap.WORKFLOW_BUILDER]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows/${ - WorkflowRoutePath[PageMap.WORKFLOW_BUILDER] - }` - ), - - [PageMap.WORKFLOW_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows/${ - WorkflowRoutePath[PageMap.WORKFLOW_VIEW] - }` - ), - - [PageMap.WORKFLOW_LOGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows/${ - WorkflowRoutePath[PageMap.WORKFLOW_LOGS] - }` - ), - - [PageMap.WORKFLOW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows/${ - WorkflowRoutePath[PageMap.WORKFLOW_DELETE] - }` - ), - - [PageMap.WORKFLOW_VIEW_SETTINGS]: new Route( - `/dashboard/${RouteParams.ProjectID}/workflows/${ - WorkflowRoutePath[PageMap.WORKFLOW_VIEW_SETTINGS] - }` - ), - - /// custom fields settings. - - [PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS] - }` - ), - - [PageMap.SETTINGS_MONITOR_SECRETS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_MONITOR_SECRETS] - }` - ), - - [PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[ - PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS - ] - }` - ), - - [PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS] - }` - ), - - [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[ - PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS - ] - }` - ), - [PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS]: new Route( - `/dashboard/${RouteParams.ProjectID}/settings/${ - SettingsRoutePath[PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS] - }` - ), - - // logout. - [PageMap.LOGOUT]: new Route(`/dashboard/logout`), - - [PageMap.MONITOR_GROUPS_ROOT]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitor-groups/*` - ), - - [PageMap.MONITOR_GROUPS]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitor-groups` - ), - - [PageMap.MONITOR_GROUP_VIEW]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ - MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW] - }` - ), - - [PageMap.MONITOR_GROUP_VIEW_DELETE]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ - MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW_DELETE] - }` - ), - - [PageMap.MONITOR_GROUP_VIEW_MONITORS]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ - MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW_MONITORS] - }` - ), - - [PageMap.MONITOR_GROUP_VIEW_OWNERS]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ - MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW_OWNERS] - }` - ), - - [PageMap.MONITOR_GROUP_VIEW_INCIDENTS]: new Route( - `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ - MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW_INCIDENTS] - }` - ), + [PageMap.INIT]: new Route(`/dashboard`), + + [PageMap.WELCOME]: new Route(`/dashboard/welcome`), + + [PageMap.PROJECT_SSO]: new Route(`/dashboard/${RouteParams.ProjectID}/sso`), + + [PageMap.INIT_PROJECT]: new Route(`/dashboard/${RouteParams.ProjectID}`), + + [PageMap.HOME]: new Route(`/dashboard/${RouteParams.ProjectID}/home/`), + + [PageMap.HOME_NOT_OPERATIONAL_MONITORS]: new Route( + `/dashboard/${RouteParams.ProjectID}/home/monitors-inoperational`, + ), + + [PageMap.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS]: new Route( + `/dashboard/${RouteParams.ProjectID}/home/scheduled-maintenance-ongoing`, + ), + + [PageMap.MONITORS_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/*`, + ), + [PageMap.MONITORS]: new Route(`/dashboard/${RouteParams.ProjectID}/monitors`), + + [PageMap.MONITORS_INOPERATIONAL]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITORS_INOPERATIONAL] + }`, + ), + + [PageMap.MONITORS_DISABLED]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITORS_DISABLED] + }`, + ), + + [PageMap.MONITOR_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW] + }`, + ), + + [PageMap.MONITOR_VIEW_INTERVAL]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_INTERVAL] + }`, + ), + + [PageMap.MONITOR_VIEW_DOCUMENTATION]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_DOCUMENTATION] + }`, + ), + + [PageMap.MONITOR_VIEW_OWNERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_OWNERS] + }`, + ), + + [PageMap.MONITOR_VIEW_STATUS_TIMELINE]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_STATUS_TIMELINE] + }`, + ), + + [PageMap.MONITOR_VIEW_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_CUSTOM_FIELDS] + }`, + ), + + [PageMap.MONITOR_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_DELETE] + }`, + ), + + [PageMap.MONITOR_VIEW_INCIDENTS]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_INCIDENTS] + }`, + ), + + [PageMap.MONITOR_VIEW_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_SETTINGS] + }`, + ), + + [PageMap.MONITOR_VIEW_PROBES]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_PROBES] + }`, + ), + + [PageMap.MONITOR_VIEW_CRITERIA]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITOR_VIEW_CRITERIA] + }`, + ), + + [PageMap.INCIDENTS_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents/*`, + ), + + [PageMap.INCIDENTS]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents`, + ), + + [PageMap.UNRESOLVED_INCIDENTS]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents/${ + IncidentsRoutePath[PageMap.UNRESOLVED_INCIDENTS] + }`, + ), + + [PageMap.USER_PROFILE_OVERVIEW]: new Route( + `/dashboard/user-profile/overview`, + ), + [PageMap.USER_PROFILE_PASSWORD]: new Route( + `/dashboard/user-profile/password-management`, + ), + [PageMap.USER_PROFILE_PICTURE]: new Route( + `/dashboard/user-profile/profile-picture`, + ), + + [PageMap.NEW_INCIDENTS]: new Route(`/dashboard/new-incidents`), + + [PageMap.PROJECT_INVITATIONS]: new Route(`/dashboard/project-invitations`), + + [PageMap.INCIDENT_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents/${ + IncidentsRoutePath[PageMap.INCIDENT_VIEW] + }`, + ), + + [PageMap.INCIDENT_VIEW_STATE_TIMELINE]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents/${ + IncidentsRoutePath[PageMap.INCIDENT_VIEW_STATE_TIMELINE] + }`, + ), + + [PageMap.INCIDENT_VIEW_OWNERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents/${ + IncidentsRoutePath[PageMap.INCIDENT_VIEW_OWNERS] + }`, + ), + + [PageMap.INCIDENT_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents/${ + IncidentsRoutePath[PageMap.INCIDENT_VIEW_DELETE] + }`, + ), + + [PageMap.INCIDENT_VIEW_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents/${ + IncidentsRoutePath[PageMap.INCIDENT_VIEW_CUSTOM_FIELDS] + }`, + ), + + [PageMap.INCIDENT_INTERNAL_NOTE]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents/${ + IncidentsRoutePath[PageMap.INCIDENT_INTERNAL_NOTE] + }`, + ), + + [PageMap.INCIDENT_PUBLIC_NOTE]: new Route( + `/dashboard/${RouteParams.ProjectID}/incidents/${ + IncidentsRoutePath[PageMap.INCIDENT_PUBLIC_NOTE] + }`, + ), + + [PageMap.SCHEDULED_MAINTENANCE_EVENTS_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/*`, + ), + + [PageMap.SCHEDULED_MAINTENANCE_EVENTS]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events`, + ), + + [PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ + ScheduledMaintenanceEventsRoutePath[ + PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS + ] + }`, + ), + + [PageMap.SCHEDULED_MAINTENANCE_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ + ScheduledMaintenanceEventsRoutePath[PageMap.SCHEDULED_MAINTENANCE_VIEW] + }`, + ), + + [PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ + ScheduledMaintenanceEventsRoutePath[ + PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS + ] + }`, + ), + + [PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ + ScheduledMaintenanceEventsRoutePath[ + PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE + ] + }`, + ), + + [PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ + ScheduledMaintenanceEventsRoutePath[ + PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE + ] + }`, + ), + + [PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ + ScheduledMaintenanceEventsRoutePath[ + PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE + ] + }`, + ), + + [PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ + ScheduledMaintenanceEventsRoutePath[ + PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS + ] + }`, + ), + + [PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE]: new Route( + `/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${ + ScheduledMaintenanceEventsRoutePath[ + PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE + ] + }`, + ), + + [PageMap.AI_COPILOT_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/ai-copilot/*`, + ), + + [PageMap.AI_COPILOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/ai-copilot`, + ), + + [PageMap.AI_COPILOT_CODE_REPOSITORY]: new Route( + `/dashboard/${RouteParams.ProjectID}/ai-copilot/code-repository`, + ), + + [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/ai-copilot/${ + CodeRepositoryRoutePath[PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW] + }`, + ), + + [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/ai-copilot/${ + CodeRepositoryRoutePath[PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_DELETE] + }`, + ), + + [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/ai-copilot/${ + CodeRepositoryRoutePath[PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SETTINGS] + }`, + ), + + [PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES]: new Route( + `/dashboard/${RouteParams.ProjectID}/ai-copilot/${ + CodeRepositoryRoutePath[PageMap.AI_COPILOT_CODE_REPOSITORY_VIEW_SERVICES] + }`, + ), + + [PageMap.SERVICE_CATALOG_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/service-catalog/*`, + ), + + [PageMap.SERVICE_CATALOG]: new Route( + `/dashboard/${RouteParams.ProjectID}/service-catalog`, + ), + + [PageMap.SERVICE_CATALOG_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/service-catalog/${ + ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW] + }`, + ), + + [PageMap.SERVICE_CATALOG_VIEW_OWNERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/service-catalog/${ + ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_OWNERS] + }`, + ), + + [PageMap.SERVICE_CATALOG_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/service-catalog/${ + ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_DELETE] + }`, + ), + + [PageMap.SERVICE_CATALOG_VIEW_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/service-catalog/${ + ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_SETTINGS] + }`, + ), + + [PageMap.STATUS_PAGES_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/*`, + ), + + [PageMap.STATUS_PAGES]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages`, + ), + + [PageMap.STATUS_PAGE_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_BRANDING]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_BRANDING] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_OWNERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_OWNERS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_GROUPS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_GROUPS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_DELETE] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_CUSTOM_FIELDS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_DOMAINS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_DOMAINS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_EMAIL_SUBSCRIBERS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_SMS_SUBSCRIBERS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_WEBHOOK_SUBSCRIBERS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_HEADER_STYLE]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_HEADER_STYLE] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_FOOTER_STYLE] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_OVERVIEW_PAGE_BRANDING] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_PRIVATE_USERS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_NAVBAR_STYLE] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_ANNOUNCEMENTS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_EMBEDDED]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_EMBEDDED] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_SUBSCRIBER_SETTINGS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_SSO]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_SSO] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_CUSTOM_HTML_CSS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_RESOURCES]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_RESOURCES] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_ADVANCED_OPTIONS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_AUTHENTICATION_SETTINGS] + }`, + ), + + [PageMap.STATUS_PAGE_VIEW_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/status-pages/${ + StatusPagesRoutePath[PageMap.STATUS_PAGE_VIEW_SETTINGS] + }`, + ), + + [PageMap.LOGS]: new Route(`/dashboard/${RouteParams.ProjectID}/logs/`), + + [PageMap.AUTOMATION_SCRIPTS]: new Route( + `/dashboard/${RouteParams.ProjectID}/automation-scripts/`, + ), + + [PageMap.ON_CALL_DUTY_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/*`, + ), + + [PageMap.ON_CALL_DUTY]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/policies`, + ), + + [PageMap.ON_CALL_DUTY_SCHEDULES]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULES] + }`, + ), + + [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW] + }`, + ), + + [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_DELETE] + }`, + ), + + [PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_SCHEDULE_VIEW_LAYERS] + }`, + ), + + [PageMap.ON_CALL_DUTY_POLICIES]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/policies`, + ), + + [PageMap.ON_CALL_DUTY_EXECUTION_LOGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_EXECUTION_LOGS] + }`, + ), + + [PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_EXECUTION_LOGS_TIMELINE] + }`, + ), + + [PageMap.ON_CALL_DUTY_POLICY_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW] + }`, + ), + + [PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE] + }`, + ), + + [PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS] + }`, + ), + + [PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS] + }`, + ), + + [PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW] + }`, + ), + + [PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION]: new Route( + `/dashboard/${RouteParams.ProjectID}/on-call-duty/${ + OnCallDutyRoutePath[PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION] + }`, + ), + + [PageMap.REPORTS]: new Route(`/dashboard/${RouteParams.ProjectID}/reports/`), + + [PageMap.ERROR_TRACKER]: new Route( + `/dashboard/${RouteParams.ProjectID}/error-tracker/`, + ), + + [PageMap.TELEMETRY_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/*`, + ), + + [PageMap.TELEMETRY]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services`, + ), + + [PageMap.TELEMETRY_SERVICES]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services`, + ), + + [PageMap.TELEMETRY_SERVICES_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW] + }`, + ), + + [PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION] + }`, + ), + + [PageMap.TELEMETRY_SERVICES_VIEW_TRACE]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_TRACE] + }`, + ), + + [PageMap.TELEMETRY_SERVICES_VIEW_METRIC]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_METRIC] + }`, + ), + + [PageMap.TELEMETRY_SERVICES_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_DELETE] + }`, + ), + + [PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_SETTINGS] + }`, + ), + + //TELEMETRY_SERVICE_VIEW_LOGS + [PageMap.TELEMETRY_SERVICES_VIEW_LOGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_LOGS] + }`, + ), + + //TELEMETRY_SERVICE_VIEW_TRACES + [PageMap.TELEMETRY_SERVICES_VIEW_TRACES]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_TRACES] + }`, + ), + + // Metrics + [PageMap.TELEMETRY_SERVICES_VIEW_METRICS]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_METRICS] + }`, + ), + + // Dashboard + [PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/telemetry/services/${ + TelemetryRouthPath[PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS] + }`, + ), + + // User Settings Routes + [PageMap.USER_SETTINGS_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/user-settings/*`, + ), + + [PageMap.USER_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/user-settings/${ + UserSettingsRoutePath[PageMap.USER_SETTINGS] + }`, + ), + + [PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/user-settings/${ + UserSettingsRoutePath[PageMap.USER_SETTINGS_NOTIFICATION_SETTINGS] + }`, + ), + + [PageMap.USER_SETTINGS_NOTIFICATION_METHODS]: new Route( + `/dashboard/${RouteParams.ProjectID}/user-settings/${ + UserSettingsRoutePath[PageMap.USER_SETTINGS_NOTIFICATION_METHODS] + }`, + ), + + [PageMap.USER_SETTINGS_ON_CALL_RULES]: new Route( + `/dashboard/${RouteParams.ProjectID}/user-settings/${ + UserSettingsRoutePath[PageMap.USER_SETTINGS_ON_CALL_RULES] + }`, + ), + + [PageMap.USER_SETTINGS_ON_CALL_LOGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/user-settings/${ + UserSettingsRoutePath[PageMap.USER_SETTINGS_ON_CALL_LOGS] + }`, + ), + + [PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE]: new Route( + `/dashboard/${RouteParams.ProjectID}/user-settings/${ + UserSettingsRoutePath[PageMap.USER_SETTINGS_ON_CALL_LOGS_TIMELINE] + }`, + ), + + // Settings Routes + [PageMap.SETTINGS_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/*`, + ), + + [PageMap.SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/`, + ), + [PageMap.SETTINGS_DANGERZONE]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_DANGERZONE] + }`, + ), + + [PageMap.SETTINGS_NOTIFICATION_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_NOTIFICATION_SETTINGS] + }`, + ), + + [PageMap.SETTINGS_SMS_LOGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_SMS_LOGS] + }`, + ), + + [PageMap.SETTINGS_EMAIL_LOGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_EMAIL_LOGS] + }`, + ), + + [PageMap.SETTINGS_CALL_LOGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_CALL_LOGS] + }`, + ), + + [PageMap.SETTINGS_APIKEYS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_APIKEYS] + }`, + ), + + [PageMap.SETTINGS_APIKEY_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_APIKEY_VIEW] + }`, + ), + + [PageMap.SETTINGS_MONITORS_STATUS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_MONITORS_STATUS] + }`, + ), + + [PageMap.SETTINGS_INCIDENTS_STATE]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_INCIDENTS_STATE] + }`, + ), + + [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE] + }`, + ), + + [PageMap.SETTINGS_INCIDENTS_SEVERITY]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_INCIDENTS_SEVERITY] + }`, + ), + + [PageMap.SETTINGS_DOMAINS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_DOMAINS] + }`, + ), + + [PageMap.SETTINGS_FEATURE_FLAGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_FEATURE_FLAGS] + }`, + ), + + [PageMap.SETTINGS_SSO]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_SSO] + }`, + ), + + [PageMap.SETTINGS_TEAMS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_TEAMS] + }`, + ), + + [PageMap.SETTINGS_INCIDENT_TEMPLATES]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_INCIDENT_TEMPLATES] + }`, + ), + + [PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_INCIDENT_TEMPLATES_VIEW] + }`, + ), + + [PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES] + }`, + ), + + [PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_INCIDENT_NOTE_TEMPLATES_VIEW] + }`, + ), + + [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES] + }`, + ), + + [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[ + PageMap.SETTINGS_SCHEDULED_MAINTENANCE_NOTE_TEMPLATES_VIEW + ] + }`, + ), + [PageMap.SETTINGS_BILLING]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_BILLING] + }`, + ), + + [PageMap.SETTINGS_BILLING_INVOICES]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_BILLING_INVOICES] + }`, + ), + + [PageMap.SETTINGS_USAGE_HISTORY]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_USAGE_HISTORY] + }`, + ), + + [PageMap.SETTINGS_TEAM_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_TEAM_VIEW] + }`, + ), + + // labels. + [PageMap.SETTINGS_LABELS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_LABELS] + }`, + ), + + // Probes. + [PageMap.SETTINGS_PROBES]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_PROBES] + }`, + ), + + // workflows. + [PageMap.WORKFLOWS_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows/*`, + ), + [PageMap.WORKFLOWS]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows`, + ), + + [PageMap.WORKFLOWS_LOGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows/${ + WorkflowRoutePath[PageMap.WORKFLOWS_LOGS] + }`, + ), + + [PageMap.WORKFLOWS_VARIABLES]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows/${ + WorkflowRoutePath[PageMap.WORKFLOWS_VARIABLES] + }`, + ), + + [PageMap.WORKFLOW_VARIABLES]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows/${ + WorkflowRoutePath[PageMap.WORKFLOW_VARIABLES] + }`, + ), + + [PageMap.WORKFLOW_BUILDER]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows/${ + WorkflowRoutePath[PageMap.WORKFLOW_BUILDER] + }`, + ), + + [PageMap.WORKFLOW_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows/${ + WorkflowRoutePath[PageMap.WORKFLOW_VIEW] + }`, + ), + + [PageMap.WORKFLOW_LOGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows/${ + WorkflowRoutePath[PageMap.WORKFLOW_LOGS] + }`, + ), + + [PageMap.WORKFLOW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows/${ + WorkflowRoutePath[PageMap.WORKFLOW_DELETE] + }`, + ), + + [PageMap.WORKFLOW_VIEW_SETTINGS]: new Route( + `/dashboard/${RouteParams.ProjectID}/workflows/${ + WorkflowRoutePath[PageMap.WORKFLOW_VIEW_SETTINGS] + }`, + ), + + /// custom fields settings. + + [PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_MONITOR_CUSTOM_FIELDS] + }`, + ), + + [PageMap.SETTINGS_MONITOR_SECRETS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_MONITOR_SECRETS] + }`, + ), + + [PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS] + }`, + ), + + [PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_INCIDENT_CUSTOM_FIELDS] + }`, + ), + + [PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_SCHEDULED_MAINTENANCE_CUSTOM_FIELDS] + }`, + ), + [PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS]: new Route( + `/dashboard/${RouteParams.ProjectID}/settings/${ + SettingsRoutePath[PageMap.SETTINGS_STATUS_PAGE_CUSTOM_FIELDS] + }`, + ), + + // logout. + [PageMap.LOGOUT]: new Route(`/dashboard/logout`), + + [PageMap.MONITOR_GROUPS_ROOT]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitor-groups/*`, + ), + + [PageMap.MONITOR_GROUPS]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitor-groups`, + ), + + [PageMap.MONITOR_GROUP_VIEW]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ + MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW] + }`, + ), + + [PageMap.MONITOR_GROUP_VIEW_DELETE]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ + MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW_DELETE] + }`, + ), + + [PageMap.MONITOR_GROUP_VIEW_MONITORS]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ + MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW_MONITORS] + }`, + ), + + [PageMap.MONITOR_GROUP_VIEW_OWNERS]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ + MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW_OWNERS] + }`, + ), + + [PageMap.MONITOR_GROUP_VIEW_INCIDENTS]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitor-groups/${ + MonitorGroupRoutePath[PageMap.MONITOR_GROUP_VIEW_INCIDENTS] + }`, + ), }; export class RouteUtil { - public static isGlobalRoute(route: Route): boolean { - if ( - route.toString() === - RouteMap[PageMap.USER_PROFILE_OVERVIEW]?.toString() || - route.toString() === - RouteMap[PageMap.USER_PROFILE_PASSWORD]?.toString() || - route.toString() === - RouteMap[PageMap.USER_PROFILE_PICTURE]?.toString() || - route.toString() === - RouteMap[PageMap.PROJECT_INVITATIONS]?.toString() || - route.toString() === RouteMap[PageMap.NEW_INCIDENTS]?.toString() - ) { - return true; - } - return false; + public static isGlobalRoute(route: Route): boolean { + if ( + route.toString() === + RouteMap[PageMap.USER_PROFILE_OVERVIEW]?.toString() || + route.toString() === + RouteMap[PageMap.USER_PROFILE_PASSWORD]?.toString() || + route.toString() === RouteMap[PageMap.USER_PROFILE_PICTURE]?.toString() || + route.toString() === RouteMap[PageMap.PROJECT_INVITATIONS]?.toString() || + route.toString() === RouteMap[PageMap.NEW_INCIDENTS]?.toString() + ) { + return true; + } + return false; + } + + public static populateRouteParams( + route: Route, + props?: { + modelId?: ObjectID; + subModelId?: ObjectID; + }, + ): Route { + // populate projectid + const project: Project | null = ProjectUtil.getCurrentProject(); + const tempRoute: Route = new Route(route.toString()); + + if (project && project._id) { + route = tempRoute.addRouteParam(RouteParams.ProjectID, project._id); } - public static populateRouteParams( - route: Route, - props?: { - modelId?: ObjectID; - subModelId?: ObjectID; - } - ): Route { - // populate projectid - const project: Project | null = ProjectUtil.getCurrentProject(); - const tempRoute: Route = new Route(route.toString()); - - if (project && project._id) { - route = tempRoute.addRouteParam(RouteParams.ProjectID, project._id); - } - - if (props && props.modelId) { - route = tempRoute.addRouteParam( - RouteParams.ModelID, - props.modelId.toString() - ); - } - - if (props && props.subModelId) { - route = tempRoute.addRouteParam( - RouteParams.SubModelID, - props.subModelId.toString() - ); - } - - return tempRoute; + if (props && props.modelId) { + route = tempRoute.addRouteParam( + RouteParams.ModelID, + props.modelId.toString(), + ); } - public static getRoutes(): Array<{ path: string }> { - return Object.values(RouteMap).map((route: Route) => { - return { - path: route.toString(), - }; - }); + if (props && props.subModelId) { + route = tempRoute.addRouteParam( + RouteParams.SubModelID, + props.subModelId.toString(), + ); } - public static getRouteString(key: string): string { - return RouteMap[key]?.toString() || ''; - } + return tempRoute; + } - public static getLastPath(path: string): string { - const paths: string[] = path.split('/'); - return paths[paths.length - 1] || ''; - } + public static getRoutes(): Array<{ path: string }> { + return Object.values(RouteMap).map((route: Route) => { + return { + path: route.toString(), + }; + }); + } - public static getLastPathForKey(key: string, count: number = 1): string { - const routePath: string = RouteMap[key]?.toString() || ''; - const paths: string[] = routePath.split('/'); - if (count === 1) { - return paths[paths.length - 1] || ''; - } - return paths.splice(paths.length - count, count).join('/'); + public static getRouteString(key: string): string { + return RouteMap[key]?.toString() || ""; + } + + public static getLastPath(path: string): string { + const paths: string[] = path.split("/"); + return paths[paths.length - 1] || ""; + } + + public static getLastPathForKey(key: string, count: number = 1): string { + const routePath: string = RouteMap[key]?.toString() || ""; + const paths: string[] = routePath.split("/"); + if (count === 1) { + return paths[paths.length - 1] || ""; } + return paths.splice(paths.length - count, count).join("/"); + } } export default RouteMap; diff --git a/Dashboard/src/Utils/RouteParams.ts b/Dashboard/src/Utils/RouteParams.ts index ddd75d2e2d..db11605ecf 100644 --- a/Dashboard/src/Utils/RouteParams.ts +++ b/Dashboard/src/Utils/RouteParams.ts @@ -1,7 +1,7 @@ enum RouteParams { - ProjectID = ':projectId', - ModelID = ':id', - SubModelID = ':subModelId', + ProjectID = ":projectId", + ModelID = ":id", + SubModelID = ":subModelId", } export default RouteParams; diff --git a/Dashboard/src/Utils/SpanUtil.ts b/Dashboard/src/Utils/SpanUtil.ts index 1969bd7137..c0012b22da 100644 --- a/Dashboard/src/Utils/SpanUtil.ts +++ b/Dashboard/src/Utils/SpanUtil.ts @@ -1,166 +1,163 @@ -import { Black } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import Span, { SpanKind, SpanStatus } from 'Model/AnalyticsModels/Span'; -import TelemetryService from 'Model/Models/TelemetryService'; +import { Black } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import Span, { SpanKind, SpanStatus } from "Model/AnalyticsModels/Span"; +import TelemetryService from "Model/Models/TelemetryService"; export enum IntervalUnit { - Nanoseconds = 'ns', - Microseconds = 'μs', - Milliseconds = 'ms', - Seconds = 's', + Nanoseconds = "ns", + Microseconds = "μs", + Milliseconds = "ms", + Seconds = "s", } export interface DivisibilityFactor { - divisibilityFactorNumber: number; - intervalUnit: IntervalUnit; + divisibilityFactorNumber: number; + intervalUnit: IntervalUnit; } export default class SpanUtil { - public static getSpanDurationAsString(data: { - divisibilityFactor: DivisibilityFactor; - spanDurationInUnixNano: number; - }): string { - const { divisibilityFactor, spanDurationInUnixNano } = data; + public static getSpanDurationAsString(data: { + divisibilityFactor: DivisibilityFactor; + spanDurationInUnixNano: number; + }): string { + const { divisibilityFactor, spanDurationInUnixNano } = data; - return `${Math.round( - spanDurationInUnixNano / divisibilityFactor.divisibilityFactorNumber - )} ${divisibilityFactor.intervalUnit}`; + return `${Math.round( + spanDurationInUnixNano / divisibilityFactor.divisibilityFactorNumber, + )} ${divisibilityFactor.intervalUnit}`; + } + + public static getSpanStartsAtAsString(data: { + divisibilityFactor: DivisibilityFactor; + timelineStartTimeUnixNano: number; + spanStartTimeUnixNano: number; + }): string { + const { + divisibilityFactor, + timelineStartTimeUnixNano, + spanStartTimeUnixNano, + } = data; + + return `${Math.round( + (spanStartTimeUnixNano! - timelineStartTimeUnixNano) / + divisibilityFactor.divisibilityFactorNumber, + )} ${divisibilityFactor.intervalUnit}`; + } + + public static getSpanEventTimeAsString(data: { + divisibilityFactor: DivisibilityFactor; + timelineStartTimeUnixNano: number; + spanEventTimeUnixNano: number; + }): string { + const { + divisibilityFactor, + timelineStartTimeUnixNano, + spanEventTimeUnixNano, + } = data; + + return `${Math.round( + (spanEventTimeUnixNano! - timelineStartTimeUnixNano) / + divisibilityFactor.divisibilityFactorNumber, + )} ${divisibilityFactor.intervalUnit}`; + } + + public static getSpanEndsAtAsString(data: { + divisibilityFactor: DivisibilityFactor; + timelineStartTimeUnixNano: number; + spanEndTimeUnixNano: number; + }): string { + const { + divisibilityFactor, + timelineStartTimeUnixNano, + spanEndTimeUnixNano, + } = data; + + return `${Math.round( + (spanEndTimeUnixNano! - timelineStartTimeUnixNano) / + divisibilityFactor.divisibilityFactorNumber, + )} ${divisibilityFactor.intervalUnit}`; + } + + public static getSpanKindFriendlyName(spanKind: SpanKind): string { + let spanKindText: string = "Internal"; // by default spans are always internal + + if (spanKind === SpanKind.Client) { + spanKindText = "Client"; + } else if (spanKind === SpanKind.Server) { + spanKindText = "Server"; + } else if (spanKind === SpanKind.Producer) { + spanKindText = "Producer"; + } else if (spanKind === SpanKind.Consumer) { + spanKindText = "Consumer"; + } else { + spanKindText = "Internal"; } - public static getSpanStartsAtAsString(data: { - divisibilityFactor: DivisibilityFactor; - timelineStartTimeUnixNano: number; - spanStartTimeUnixNano: number; - }): string { - const { - divisibilityFactor, - timelineStartTimeUnixNano, - spanStartTimeUnixNano, - } = data; + return spanKindText; + } - return `${Math.round( - (spanStartTimeUnixNano! - timelineStartTimeUnixNano) / - divisibilityFactor.divisibilityFactorNumber - )} ${divisibilityFactor.intervalUnit}`; + public static getDivisibilityFactor( + totalTimelineTimeInUnixNano: number, + ): DivisibilityFactor { + let intervalUnit: IntervalUnit = IntervalUnit.Milliseconds; + let divisibilityFactorNumber: number = 1000; // default is in milliseconds + + if (totalTimelineTimeInUnixNano < 1000) { + intervalUnit = IntervalUnit.Nanoseconds; + divisibilityFactorNumber = 1; // this is in nanoseconds + } else if (totalTimelineTimeInUnixNano < 1000000) { + intervalUnit = IntervalUnit.Microseconds; + divisibilityFactorNumber = 1000; // this is in microseconds + } else if (totalTimelineTimeInUnixNano < 1000000000) { + intervalUnit = IntervalUnit.Milliseconds; + divisibilityFactorNumber = 1000000; // this is in microseconds + } else if (totalTimelineTimeInUnixNano < 1000000000000) { + intervalUnit = IntervalUnit.Seconds; + divisibilityFactorNumber = 1000000000; // this is in seconds } - public static getSpanEventTimeAsString(data: { - divisibilityFactor: DivisibilityFactor; - timelineStartTimeUnixNano: number; - spanEventTimeUnixNano: number; - }): string { - const { - divisibilityFactor, - timelineStartTimeUnixNano, - spanEventTimeUnixNano, - } = data; + return { + divisibilityFactorNumber: divisibilityFactorNumber, + intervalUnit: intervalUnit, + }; + } - return `${Math.round( - (spanEventTimeUnixNano! - timelineStartTimeUnixNano) / - divisibilityFactor.divisibilityFactorNumber - )} ${divisibilityFactor.intervalUnit}`; + public static getSpanStatusCodeFriendlyName(statusCode: SpanStatus): string { + let statusCodeText: string = "Unset"; // by default spans are always unset + + if (statusCode === SpanStatus.Ok) { + statusCodeText = "Ok"; + } else if (statusCode === SpanStatus.Error) { + statusCodeText = "Error"; + } else { + statusCodeText = "Unset"; } - public static getSpanEndsAtAsString(data: { - divisibilityFactor: DivisibilityFactor; - timelineStartTimeUnixNano: number; - spanEndTimeUnixNano: number; - }): string { - const { - divisibilityFactor, - timelineStartTimeUnixNano, - spanEndTimeUnixNano, - } = data; + return statusCodeText; + } - return `${Math.round( - (spanEndTimeUnixNano! - timelineStartTimeUnixNano) / - divisibilityFactor.divisibilityFactorNumber - )} ${divisibilityFactor.intervalUnit}`; + public static getGanttChartBarColor(data: { + span: Span; + telemetryServices: Array<TelemetryService>; + }): { + barColor: Color; + } { + const service: TelemetryService | undefined = data.telemetryServices.find( + (service: TelemetryService) => { + return service.id?.toString() === data.span.serviceId?.toString(); + }, + ); + + if (!service || !service.serviceColor) { + return { + barColor: Black, + }; } - public static getSpanKindFriendlyName(spanKind: SpanKind): string { - let spanKindText: string = 'Internal'; // by default spans are always internal + const barColor: Color = service.serviceColor; - if (spanKind === SpanKind.Client) { - spanKindText = 'Client'; - } else if (spanKind === SpanKind.Server) { - spanKindText = 'Server'; - } else if (spanKind === SpanKind.Producer) { - spanKindText = 'Producer'; - } else if (spanKind === SpanKind.Consumer) { - spanKindText = 'Consumer'; - } else { - spanKindText = 'Internal'; - } - - return spanKindText; - } - - public static getDivisibilityFactor( - totalTimelineTimeInUnixNano: number - ): DivisibilityFactor { - let intervalUnit: IntervalUnit = IntervalUnit.Milliseconds; - let divisibilityFactorNumber: number = 1000; // default is in milliseconds - - if (totalTimelineTimeInUnixNano < 1000) { - intervalUnit = IntervalUnit.Nanoseconds; - divisibilityFactorNumber = 1; // this is in nanoseconds - } else if (totalTimelineTimeInUnixNano < 1000000) { - intervalUnit = IntervalUnit.Microseconds; - divisibilityFactorNumber = 1000; // this is in microseconds - } else if (totalTimelineTimeInUnixNano < 1000000000) { - intervalUnit = IntervalUnit.Milliseconds; - divisibilityFactorNumber = 1000000; // this is in microseconds - } else if (totalTimelineTimeInUnixNano < 1000000000000) { - intervalUnit = IntervalUnit.Seconds; - divisibilityFactorNumber = 1000000000; // this is in seconds - } - - return { - divisibilityFactorNumber: divisibilityFactorNumber, - intervalUnit: intervalUnit, - }; - } - - public static getSpanStatusCodeFriendlyName( - statusCode: SpanStatus - ): string { - let statusCodeText: string = 'Unset'; // by default spans are always unset - - if (statusCode === SpanStatus.Ok) { - statusCodeText = 'Ok'; - } else if (statusCode === SpanStatus.Error) { - statusCodeText = 'Error'; - } else { - statusCodeText = 'Unset'; - } - - return statusCodeText; - } - - public static getGanttChartBarColor(data: { - span: Span; - telemetryServices: Array<TelemetryService>; - }): { - barColor: Color; - } { - const service: TelemetryService | undefined = - data.telemetryServices.find((service: TelemetryService) => { - return ( - service.id?.toString() === data.span.serviceId?.toString() - ); - }); - - if (!service || !service.serviceColor) { - return { - barColor: Black, - }; - } - - const barColor: Color = service.serviceColor; - - return { - barColor, - }; - } + return { + barColor, + }; + } } diff --git a/Dashboard/webpack.config.js b/Dashboard/webpack.config.js index ab44560db9..d0df423153 100644 --- a/Dashboard/webpack.config.js +++ b/Dashboard/webpack.config.js @@ -1,85 +1,87 @@ -require('ts-loader'); -require('file-loader'); -require('style-loader'); -require('css-loader'); -require('sass-loader'); +require("ts-loader"); +require("file-loader"); +require("style-loader"); +require("css-loader"); +require("sass-loader"); const path = require("path"); const webpack = require("webpack"); -const dotenv = require('dotenv'); -const express = require('express'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const dotenv = require("dotenv"); +const express = require("express"); +const BundleAnalyzerPlugin = + require("webpack-bundle-analyzer").BundleAnalyzerPlugin; const readEnvFile = (pathToFile) => { + const parsed = dotenv.config({ path: pathToFile }).parsed; - const parsed = dotenv.config({ path: pathToFile }).parsed; + const env = {}; - const env = { - }; + for (const key in parsed) { + env[key] = JSON.stringify(parsed[key]); + } - for (const key in parsed) { - env[key] = JSON.stringify(parsed[key]); - } - - return env; -} + return env; +}; module.exports = { - entry: "./src/Index.tsx", - mode: "development", - output: { - filename: "bundle.js", - path: path.resolve(__dirname, "public", "dist"), - publicPath: "/dashboard/dist/", + entry: "./src/Index.tsx", + mode: "development", + output: { + filename: "bundle.js", + path: path.resolve(__dirname, "public", "dist"), + publicPath: "/dashboard/dist/", + }, + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"], + alias: { + react: path.resolve("./node_modules/react"), }, - resolve: { - extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'], - alias: { - react: path.resolve('./node_modules/react'), - } - }, - externals: { - 'react-native-sqlite-storage': 'react-native-sqlite-storage' - }, - plugins: [ - new webpack.DefinePlugin({ - 'process': { - 'env': { - ...readEnvFile('/usr/src/app/dev-env/.env') - } - } - }), - process.env.analyze === 'true' ? new BundleAnalyzerPlugin() : () => {}, - ], - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - loader: 'ts-loader', - }, - { - test: /\.s[ac]ss$/i, - use: ['style-loader', 'css-loader', "sass-loader"] - }, - { - test: /\.css$/i, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.(jpe?g|png|gif|svg)$/i, - loader: 'file-loader' - } - ], - }, - devServer: { - historyApiFallback: true, - devMiddleware: { - writeToDisk: true, + }, + externals: { + "react-native-sqlite-storage": "react-native-sqlite-storage", + }, + plugins: [ + new webpack.DefinePlugin({ + process: { + env: { + ...readEnvFile("/usr/src/app/dev-env/.env"), }, - allowedHosts: "all", - setupMiddlewares: (middlewares, devServer) => { - devServer.app.use('/dashboard/assets', express.static(path.resolve(__dirname, 'public', 'assets'))); - return middlewares; - } + }, + }), + process.env.analyze === "true" ? new BundleAnalyzerPlugin() : () => {}, + ], + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + loader: "ts-loader", + }, + { + test: /\.s[ac]ss$/i, + use: ["style-loader", "css-loader", "sass-loader"], + }, + { + test: /\.css$/i, + use: ["style-loader", "css-loader"], + }, + { + test: /\.(jpe?g|png|gif|svg)$/i, + loader: "file-loader", + }, + ], + }, + devServer: { + historyApiFallback: true, + devMiddleware: { + writeToDisk: true, }, - devtool: 'eval-source-map', -} \ No newline at end of file + allowedHosts: "all", + setupMiddlewares: (middlewares, devServer) => { + devServer.app.use( + "/dashboard/assets", + express.static(path.resolve(__dirname, "public", "assets")), + ); + return middlewares; + }, + }, + devtool: "eval-source-map", +}; diff --git a/E2E/Config.ts b/E2E/Config.ts index cca4d4496e..785998b110 100644 --- a/E2E/Config.ts +++ b/E2E/Config.ts @@ -1,28 +1,28 @@ -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; type GetEnvFunction = (key: string) => string; export const env: GetEnvFunction = (key: string): string => { - return (process.env[key] as string) || ''; + return (process.env[key] as string) || ""; }; -export const HOST: string = env('HOST') || 'localhost'; +export const HOST: string = env("HOST") || "localhost"; export const HTTP_PROTOCOL: Protocol = - env('HTTP_PROTOCOL') === 'https' ? Protocol.HTTPS : Protocol.HTTP; + env("HTTP_PROTOCOL") === "https" ? Protocol.HTTPS : Protocol.HTTP; export const BASE_URL: URL = URL.fromString(`${HTTP_PROTOCOL}${HOST}`); export const IS_USER_REGISTERED: boolean = - env('E2E_TEST_IS_USER_REGISTERED') === 'true'; + env("E2E_TEST_IS_USER_REGISTERED") === "true"; export const REGISTERED_USER_EMAIL: string = - env('E2E_TEST_REGISTERED_USER_EMAIL') || ''; + env("E2E_TEST_REGISTERED_USER_EMAIL") || ""; export const REGISTERED_USER_PASSWORD: string = - env('E2E_TEST_REGISTERED_USER_PASSWORD') || ''; + env("E2E_TEST_REGISTERED_USER_PASSWORD") || ""; -export const IS_BILLING_ENABLED: boolean = env('BILLING_ENABLED') === 'true'; +export const IS_BILLING_ENABLED: boolean = env("BILLING_ENABLED") === "true"; -export const STATUS_PAGE_URL: URL | null = env('E2E_TEST_STATUS_PAGE_URL') - ? URL.fromString(env('E2E_TEST_STATUS_PAGE_URL')) - : null; +export const STATUS_PAGE_URL: URL | null = env("E2E_TEST_STATUS_PAGE_URL") + ? URL.fromString(env("E2E_TEST_STATUS_PAGE_URL")) + : null; diff --git a/E2E/Tests/Accounts/Login.spec.ts b/E2E/Tests/Accounts/Login.spec.ts index ad042103d3..79d055c5cb 100644 --- a/E2E/Tests/Accounts/Login.spec.ts +++ b/E2E/Tests/Accounts/Login.spec.ts @@ -1,52 +1,50 @@ import { - BASE_URL, - IS_USER_REGISTERED, - REGISTERED_USER_EMAIL, - REGISTERED_USER_PASSWORD, -} from '../../Config'; -import { Page, expect, test } from '@playwright/test'; -import URL from 'Common/Types/API/URL'; + BASE_URL, + IS_USER_REGISTERED, + REGISTERED_USER_EMAIL, + REGISTERED_USER_PASSWORD, +} from "../../Config"; +import { Page, expect, test } from "@playwright/test"; +import URL from "Common/Types/API/URL"; -test.describe('Login', () => { - test('should be able to login', async ({ page }: { page: Page }) => { - if ( - !IS_USER_REGISTERED || - !REGISTERED_USER_EMAIL || - !REGISTERED_USER_PASSWORD - ) { - // pass this test if the user is not registered - return; - } +test.describe("Login", () => { + test("should be able to login", async ({ page }: { page: Page }) => { + if ( + !IS_USER_REGISTERED || + !REGISTERED_USER_EMAIL || + !REGISTERED_USER_PASSWORD + ) { + // pass this test if the user is not registered + return; + } - // go to login page - await page.goto( - URL.fromString(BASE_URL.toString()) - .addRoute('/accounts/') - .toString() - ); + // go to login page + await page.goto( + URL.fromString(BASE_URL.toString()).addRoute("/accounts/").toString(), + ); - await page.locator('input[type="email"]').click(); - await page - .locator('input[type="email"]') - .fill(REGISTERED_USER_EMAIL.toString()); - await page.locator('input[type="email"]').press('Tab'); - await page - .locator('input[type="password"]') - .fill(REGISTERED_USER_PASSWORD.toString()); - await page.locator('input[type="password"]').press('Enter'); + await page.locator('input[type="email"]').click(); + await page + .locator('input[type="email"]') + .fill(REGISTERED_USER_EMAIL.toString()); + await page.locator('input[type="email"]').press("Tab"); + await page + .locator('input[type="password"]') + .fill(REGISTERED_USER_PASSWORD.toString()); + await page.locator('input[type="password"]').press("Enter"); - // wait for navigation with base url - await page.waitForURL( - URL.fromString(BASE_URL.toString()) - .addRoute('/dashboard/welcome') - .toString() - ); - expect(page.url()).toBe( - URL.fromString(BASE_URL.toString()) - .addRoute('/dashboard/welcome') - .toString() - ); + // wait for navigation with base url + await page.waitForURL( + URL.fromString(BASE_URL.toString()) + .addRoute("/dashboard/welcome") + .toString(), + ); + expect(page.url()).toBe( + URL.fromString(BASE_URL.toString()) + .addRoute("/dashboard/welcome") + .toString(), + ); - await page.getByTestId('create-new-project-button').click(); - }); + await page.getByTestId("create-new-project-button").click(); + }); }); diff --git a/E2E/Tests/Accounts/Register.spec.ts b/E2E/Tests/Accounts/Register.spec.ts index 0bd9cc8bf4..1ad9fd02c5 100644 --- a/E2E/Tests/Accounts/Register.spec.ts +++ b/E2E/Tests/Accounts/Register.spec.ts @@ -1,50 +1,50 @@ -import { BASE_URL, IS_BILLING_ENABLED, IS_USER_REGISTERED } from '../../Config'; -import { Page, expect, test } from '@playwright/test'; -import URL from 'Common/Types/API/URL'; -import Faker from 'Common/Utils/Faker'; +import { BASE_URL, IS_BILLING_ENABLED, IS_USER_REGISTERED } from "../../Config"; +import { Page, expect, test } from "@playwright/test"; +import URL from "Common/Types/API/URL"; +import Faker from "Common/Utils/Faker"; -test.describe('Account Registration', () => { - test('should register a new account', async ({ page }: { page: Page }) => { - if (IS_USER_REGISTERED) { - // pass this test if user is already registered - return; - } +test.describe("Account Registration", () => { + test("should register a new account", async ({ page }: { page: Page }) => { + if (IS_USER_REGISTERED) { + // pass this test if user is already registered + return; + } - await page.goto( - URL.fromString(BASE_URL.toString()) - .addRoute('/accounts/register') - .toString() - ); - await page.getByTestId('email').click(); - await page.getByTestId('email').fill(Faker.generateEmail().toString()); - await page.getByTestId('email').press('Tab'); - await page.getByTestId('name').fill('sample'); - await page.getByTestId('name').press('Tab'); + await page.goto( + URL.fromString(BASE_URL.toString()) + .addRoute("/accounts/register") + .toString(), + ); + await page.getByTestId("email").click(); + await page.getByTestId("email").fill(Faker.generateEmail().toString()); + await page.getByTestId("email").press("Tab"); + await page.getByTestId("name").fill("sample"); + await page.getByTestId("name").press("Tab"); - if (IS_BILLING_ENABLED) { - await page.getByTestId('companyName').fill('sample'); - await page.getByTestId('companyName').press('Tab'); - await page.getByTestId('companyPhoneNumber').fill('+15853641376'); - await page.getByTestId('companyPhoneNumber').press('Tab'); - } + if (IS_BILLING_ENABLED) { + await page.getByTestId("companyName").fill("sample"); + await page.getByTestId("companyName").press("Tab"); + await page.getByTestId("companyPhoneNumber").fill("+15853641376"); + await page.getByTestId("companyPhoneNumber").press("Tab"); + } - await page.getByTestId('password').fill('sample'); - await page.getByTestId('password').press('Tab'); - await page.getByTestId('confirmPassword').fill('sample'); - await page.getByTestId('Sign Up').click(); + await page.getByTestId("password").fill("sample"); + await page.getByTestId("password").press("Tab"); + await page.getByTestId("confirmPassword").fill("sample"); + await page.getByTestId("Sign Up").click(); - // wait for navigation with base url - await page.waitForURL( - URL.fromString(BASE_URL.toString()) - .addRoute('/dashboard/welcome') - .toString() - ); - expect(page.url()).toBe( - URL.fromString(BASE_URL.toString()) - .addRoute('/dashboard/welcome') - .toString() - ); + // wait for navigation with base url + await page.waitForURL( + URL.fromString(BASE_URL.toString()) + .addRoute("/dashboard/welcome") + .toString(), + ); + expect(page.url()).toBe( + URL.fromString(BASE_URL.toString()) + .addRoute("/dashboard/welcome") + .toString(), + ); - await page.getByTestId('create-new-project-button').click(); - }); + await page.getByTestId("create-new-project-button").click(); + }); }); diff --git a/E2E/Tests/App/StatusCheck.spec.ts b/E2E/Tests/App/StatusCheck.spec.ts index 0c453eb03a..58c42d0be9 100644 --- a/E2E/Tests/App/StatusCheck.spec.ts +++ b/E2E/Tests/App/StatusCheck.spec.ts @@ -1,35 +1,33 @@ -import { BASE_URL } from '../../Config'; -import { Page, expect, test } from '@playwright/test'; -import URL from 'Common/Types/API/URL'; +import { BASE_URL } from "../../Config"; +import { Page, expect, test } from "@playwright/test"; +import URL from "Common/Types/API/URL"; -test.describe('check live and health check of the app', () => { - test('check if app status is ok', async ({ page }: { page: Page }) => { - await page.goto( - `${URL.fromString(BASE_URL.toString()) - .addRoute('/status') - .toString()}` - ); - const content: string = await page.content(); - expect(content).toContain('{"status":"ok"}'); - }); +test.describe("check live and health check of the app", () => { + test("check if app status is ok", async ({ page }: { page: Page }) => { + await page.goto( + `${URL.fromString(BASE_URL.toString()).addRoute("/status").toString()}`, + ); + const content: string = await page.content(); + expect(content).toContain('{"status":"ok"}'); + }); - test('check if app is ready', async ({ page }: { page: Page }) => { - await page.goto( - `${URL.fromString(BASE_URL.toString()) - .addRoute('/status/ready') - .toString()}` - ); - const content: string = await page.content(); - expect(content).toContain('{"status":"ok"}'); - }); + test("check if app is ready", async ({ page }: { page: Page }) => { + await page.goto( + `${URL.fromString(BASE_URL.toString()) + .addRoute("/status/ready") + .toString()}`, + ); + const content: string = await page.content(); + expect(content).toContain('{"status":"ok"}'); + }); - test('check if app is live', async ({ page }: { page: Page }) => { - await page.goto( - `${URL.fromString(BASE_URL.toString()) - .addRoute('/status/live') - .toString()}` - ); - const content: string = await page.content(); - expect(content).toContain('{"status":"ok"}'); - }); + test("check if app is live", async ({ page }: { page: Page }) => { + await page.goto( + `${URL.fromString(BASE_URL.toString()) + .addRoute("/status/live") + .toString()}`, + ); + const content: string = await page.content(); + expect(content).toContain('{"status":"ok"}'); + }); }); diff --git a/E2E/Tests/Home/Landing.spec.ts b/E2E/Tests/Home/Landing.spec.ts index 0cb7792785..f28595c335 100644 --- a/E2E/Tests/Home/Landing.spec.ts +++ b/E2E/Tests/Home/Landing.spec.ts @@ -1,39 +1,37 @@ -import { BASE_URL, IS_BILLING_ENABLED } from '../../Config'; -import { Page, expect, test } from '@playwright/test'; -import URL from 'Common/Types/API/URL'; +import { BASE_URL, IS_BILLING_ENABLED } from "../../Config"; +import { Page, expect, test } from "@playwright/test"; +import URL from "Common/Types/API/URL"; test.beforeEach(async ({ page }: { page: Page }) => { + if (!IS_BILLING_ENABLED) { + return; + } + + await page.goto(URL.fromString(BASE_URL.toString()).toString()); +}); +test.describe("check if pages loades with its title", () => { + test("has title", async ({ page }: { page: Page }) => { if (!IS_BILLING_ENABLED) { - return; + return; } - await page.goto(URL.fromString(BASE_URL.toString()).toString()); -}); -test.describe('check if pages loades with its title', () => { - test('has title', async ({ page }: { page: Page }) => { - if (!IS_BILLING_ENABLED) { - return; - } - - await expect(page).toHaveTitle( - /OneUptime | One Complete SRE and DevOps platform./ - ); - }); - test('oneUptime link navigate to homepage', async ({ - page, - }: { - page: Page; - }) => { - if (!IS_BILLING_ENABLED) { - return; - } - - await page - .getByRole('link', { name: 'OneUptime', exact: true }) - .click(); - - await expect(page).toHaveURL( - URL.fromString(BASE_URL.toString()).toString() - ); - }); + await expect(page).toHaveTitle( + /OneUptime | One Complete SRE and DevOps platform./, + ); + }); + test("oneUptime link navigate to homepage", async ({ + page, + }: { + page: Page; + }) => { + if (!IS_BILLING_ENABLED) { + return; + } + + await page.getByRole("link", { name: "OneUptime", exact: true }).click(); + + await expect(page).toHaveURL( + URL.fromString(BASE_URL.toString()).toString(), + ); + }); }); diff --git a/E2E/Tests/Home/Navigation.spec.ts b/E2E/Tests/Home/Navigation.spec.ts index 044060936a..35b4efe949 100644 --- a/E2E/Tests/Home/Navigation.spec.ts +++ b/E2E/Tests/Home/Navigation.spec.ts @@ -1,72 +1,66 @@ -import { BASE_URL, IS_BILLING_ENABLED } from '../../Config'; -import { Page, expect, test } from '@playwright/test'; -import URL from 'Common/Types/API/URL'; +import { BASE_URL, IS_BILLING_ENABLED } from "../../Config"; +import { Page, expect, test } from "@playwright/test"; +import URL from "Common/Types/API/URL"; test.beforeEach(async ({ page }: { page: Page }) => { + if (!IS_BILLING_ENABLED) { + return; + } + + await page.goto(URL.fromString(BASE_URL.toString()).toString()); +}); + +test.describe("navigation bar", () => { + test("product page", async ({ page }: { page: Page }) => { if (!IS_BILLING_ENABLED) { - return; + return; } + await page.getByRole("button", { name: "Products" }).click(); + await page.getByRole("button", { name: "Products" }).hover(); + await expect(page.getByRole("button", { name: "Products" })).toHaveText( + /Products/, + ); + await expect(page.getByRole("button", { name: "Products" })).toBeVisible(); + await expect( + page.getByRole("button", { name: "Products" }), + ).toBeInViewport(); + }); - await page.goto(URL.fromString(BASE_URL.toString()).toString()); -}); - -test.describe('navigation bar', () => { - test('product page', async ({ page }: { page: Page }) => { - if (!IS_BILLING_ENABLED) { - return; - } - await page.getByRole('button', { name: 'Products' }).click(); - await page.getByRole('button', { name: 'Products' }).hover(); - await expect(page.getByRole('button', { name: 'Products' })).toHaveText( - /Products/ - ); - await expect( - page.getByRole('button', { name: 'Products' }) - ).toBeVisible(); - await expect( - page.getByRole('button', { name: 'Products' }) - ).toBeInViewport(); - }); - - test('pricing page', async ({ page }: { page: Page }) => { - if (!IS_BILLING_ENABLED) { - return; - } - await page.getByRole('link', { name: 'Pricing' }).click(); - await page.getByRole('link', { name: 'Pricing' }).hover(); - await expect(page.getByRole('link', { name: 'Pricing' })).toHaveText( - /Pricing/ - ); - await expect(page.getByRole('link', { name: 'Pricing' })).toBeVisible(); - await expect( - page.getByRole('link', { name: 'Pricing' }) - ).toBeInViewport(); - await expect(page).toHaveURL(/.*pricing/); - }); - - test('Enterprise', async ({ page }: { page: Page }) => { - if (!IS_BILLING_ENABLED) { - return; - } - await page.getByRole('link', { name: 'Enterprise' }).click(); - await page.getByRole('link', { name: 'Enterprise' }).hover(); - await expect( - page.getByRole('link', { name: 'Enterprise' }) - ).toBeVisible(); - await expect( - page.getByRole('link', { name: 'Enterprise' }) - ).toBeInViewport(); - await expect(page.getByRole('link', { name: 'Enterprise' })).toHaveText( - /Enterprise/ - ); - await expect(page).toHaveURL(/.*enterprise\/overview/); - }); - - test('Request Demo', async ({ page }: { page: Page }) => { - if (!IS_BILLING_ENABLED) { - return; - } - await page.getByTestId('request-demo-desktop-link').click(); - await expect(page).toHaveURL(/.*enterprise\/demo/); - }); + test("pricing page", async ({ page }: { page: Page }) => { + if (!IS_BILLING_ENABLED) { + return; + } + await page.getByRole("link", { name: "Pricing" }).click(); + await page.getByRole("link", { name: "Pricing" }).hover(); + await expect(page.getByRole("link", { name: "Pricing" })).toHaveText( + /Pricing/, + ); + await expect(page.getByRole("link", { name: "Pricing" })).toBeVisible(); + await expect(page.getByRole("link", { name: "Pricing" })).toBeInViewport(); + await expect(page).toHaveURL(/.*pricing/); + }); + + test("Enterprise", async ({ page }: { page: Page }) => { + if (!IS_BILLING_ENABLED) { + return; + } + await page.getByRole("link", { name: "Enterprise" }).click(); + await page.getByRole("link", { name: "Enterprise" }).hover(); + await expect(page.getByRole("link", { name: "Enterprise" })).toBeVisible(); + await expect( + page.getByRole("link", { name: "Enterprise" }), + ).toBeInViewport(); + await expect(page.getByRole("link", { name: "Enterprise" })).toHaveText( + /Enterprise/, + ); + await expect(page).toHaveURL(/.*enterprise\/overview/); + }); + + test("Request Demo", async ({ page }: { page: Page }) => { + if (!IS_BILLING_ENABLED) { + return; + } + await page.getByTestId("request-demo-desktop-link").click(); + await expect(page).toHaveURL(/.*enterprise\/demo/); + }); }); diff --git a/E2E/Tests/Home/SignIn.spec.ts b/E2E/Tests/Home/SignIn.spec.ts index d444a66e84..8680e4b8b3 100644 --- a/E2E/Tests/Home/SignIn.spec.ts +++ b/E2E/Tests/Home/SignIn.spec.ts @@ -1,17 +1,17 @@ -import { BASE_URL, IS_BILLING_ENABLED } from '../../Config'; -import { Page, expect, test } from '@playwright/test'; -import URL from 'Common/Types/API/URL'; +import { BASE_URL, IS_BILLING_ENABLED } from "../../Config"; +import { Page, expect, test } from "@playwright/test"; +import URL from "Common/Types/API/URL"; test.beforeEach(async ({ page }: { page: Page }) => { - if (!IS_BILLING_ENABLED) { - return; - } - await page.goto(URL.fromString(BASE_URL.toString()).toString()); + if (!IS_BILLING_ENABLED) { + return; + } + await page.goto(URL.fromString(BASE_URL.toString()).toString()); }); -test('sign in button ', async ({ page }: { page: Page }) => { - if (!IS_BILLING_ENABLED) { - return; - } - await page.getByRole('link', { name: 'Sign in' }).click(); - await expect(page).toHaveURL(/.*accounts/); +test("sign in button ", async ({ page }: { page: Page }) => { + if (!IS_BILLING_ENABLED) { + return; + } + await page.getByRole("link", { name: "Sign in" }).click(); + await expect(page).toHaveURL(/.*accounts/); }); diff --git a/E2E/Tests/Home/SignUp.spec.ts b/E2E/Tests/Home/SignUp.spec.ts index e04148e7a0..4fddea5109 100644 --- a/E2E/Tests/Home/SignUp.spec.ts +++ b/E2E/Tests/Home/SignUp.spec.ts @@ -1,21 +1,21 @@ -import { BASE_URL, IS_BILLING_ENABLED } from '../../Config'; -import { Page, expect, test } from '@playwright/test'; -import URL from 'Common/Types/API/URL'; +import { BASE_URL, IS_BILLING_ENABLED } from "../../Config"; +import { Page, expect, test } from "@playwright/test"; +import URL from "Common/Types/API/URL"; test.beforeEach(async ({ page }: { page: Page }) => { - if (!IS_BILLING_ENABLED) { - return; - } - await page.goto(URL.fromString(BASE_URL.toString()).toString()); + if (!IS_BILLING_ENABLED) { + return; + } + await page.goto(URL.fromString(BASE_URL.toString()).toString()); }); -test('sign up button', async ({ page }: { page: Page }) => { - if (!IS_BILLING_ENABLED) { - return; - } - await page.getByTestId('Sign-up').click(); - await expect(page).toHaveURL( - URL.fromString(BASE_URL.toString()) - .addRoute('/accounts/register') - .toString() - ); +test("sign up button", async ({ page }: { page: Page }) => { + if (!IS_BILLING_ENABLED) { + return; + } + await page.getByTestId("Sign-up").click(); + await expect(page).toHaveURL( + URL.fromString(BASE_URL.toString()) + .addRoute("/accounts/register") + .toString(), + ); }); diff --git a/E2E/Tests/Ingestor/StatusCheck.spec.ts b/E2E/Tests/Ingestor/StatusCheck.spec.ts index 2618b38ec6..ed5f6b4a25 100644 --- a/E2E/Tests/Ingestor/StatusCheck.spec.ts +++ b/E2E/Tests/Ingestor/StatusCheck.spec.ts @@ -1,35 +1,35 @@ -import { BASE_URL } from '../../Config'; -import { Page, expect, test } from '@playwright/test'; -import URL from 'Common/Types/API/URL'; +import { BASE_URL } from "../../Config"; +import { Page, expect, test } from "@playwright/test"; +import URL from "Common/Types/API/URL"; -test.describe('check live and health check of the app', () => { - test('check if app status is ok', async ({ page }: { page: Page }) => { - await page.goto( - `${URL.fromString(BASE_URL.toString()) - .addRoute('/ingestor/status') - .toString()}` - ); - const content: string = await page.content(); - expect(content).toContain('{"status":"ok"}'); - }); +test.describe("check live and health check of the app", () => { + test("check if app status is ok", async ({ page }: { page: Page }) => { + await page.goto( + `${URL.fromString(BASE_URL.toString()) + .addRoute("/ingestor/status") + .toString()}`, + ); + const content: string = await page.content(); + expect(content).toContain('{"status":"ok"}'); + }); - test('check if app is ready', async ({ page }: { page: Page }) => { - await page.goto( - `${URL.fromString(BASE_URL.toString()) - .addRoute('/ingestor/status/ready') - .toString()}` - ); - const content: string = await page.content(); - expect(content).toContain('{"status":"ok"}'); - }); + test("check if app is ready", async ({ page }: { page: Page }) => { + await page.goto( + `${URL.fromString(BASE_URL.toString()) + .addRoute("/ingestor/status/ready") + .toString()}`, + ); + const content: string = await page.content(); + expect(content).toContain('{"status":"ok"}'); + }); - test('check if app is live', async ({ page }: { page: Page }) => { - await page.goto( - `${URL.fromString(BASE_URL.toString()) - .addRoute('/ingestor/status/live') - .toString()}` - ); - const content: string = await page.content(); - expect(content).toContain('{"status":"ok"}'); - }); + test("check if app is live", async ({ page }: { page: Page }) => { + await page.goto( + `${URL.fromString(BASE_URL.toString()) + .addRoute("/ingestor/status/live") + .toString()}`, + ); + const content: string = await page.content(); + expect(content).toContain('{"status":"ok"}'); + }); }); diff --git a/E2E/Tests/StatusPage/Basic.spec.ts b/E2E/Tests/StatusPage/Basic.spec.ts index a4f40cb5b9..beed466fc3 100644 --- a/E2E/Tests/StatusPage/Basic.spec.ts +++ b/E2E/Tests/StatusPage/Basic.spec.ts @@ -1,22 +1,22 @@ -import { STATUS_PAGE_URL } from '../../Config'; -import { Page, test } from '@playwright/test'; -import URL from 'Common/Types/API/URL'; +import { STATUS_PAGE_URL } from "../../Config"; +import { Page, test } from "@playwright/test"; +import URL from "Common/Types/API/URL"; -test.describe('Basic Status Page', () => { - test('should be able to load status page properly', async ({ - page, - }: { - page: Page; - }) => { - if (!STATUS_PAGE_URL) { - // pass this test if the user is not registered - return; - } +test.describe("Basic Status Page", () => { + test("should be able to load status page properly", async ({ + page, + }: { + page: Page; + }) => { + if (!STATUS_PAGE_URL) { + // pass this test if the user is not registered + return; + } - // go to login page - await page.goto(URL.fromString(STATUS_PAGE_URL.toString()).toString()); + // go to login page + await page.goto(URL.fromString(STATUS_PAGE_URL.toString()).toString()); - // check if data-testid is present with status-page-overview - await page.waitForSelector('[data-testid="status-page-overview"]'); // page loaded properly. - }); + // check if data-testid is present with status-page-overview + await page.waitForSelector('[data-testid="status-page-overview"]'); // page loaded properly. + }); }); diff --git a/E2E/playwright.config.ts b/E2E/playwright.config.ts index df8af2ab71..6d8d232d42 100644 --- a/E2E/playwright.config.ts +++ b/E2E/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices } from "@playwright/test"; /** * Read environment variables from file. @@ -10,76 +10,76 @@ import { defineConfig, devices } from '@playwright/test'; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './Tests', - /* Maximum time one test can run for. */ - timeout: 120 * 1000, - // globalTimeout: 600 * 1000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, - }, - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: Boolean(process.env['CI']), - /* Retry on CI only */ - retries: process.env['CI'] ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: 1, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', + testDir: "./Tests", + /* Maximum time one test can run for. */ + timeout: 120 * 1000, + // globalTimeout: 600 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: Boolean(process.env["CI"]), + /* Retry on CI only */ + retries: process.env["CI"] ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on', + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { channel: 'chrome' }, - // }, - ], - - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - // outputDir: 'test-results/', - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // port: 3000, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { channel: 'chrome' }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, }); diff --git a/Examples/fluentd/index.js b/Examples/fluentd/index.js index b88b86c4ca..0bf549ebef 100644 --- a/Examples/fluentd/index.js +++ b/Examples/fluentd/index.js @@ -1,27 +1,27 @@ -// This app will log all the logs to the docker-container fluentd that's running in development. +// This app will log all the logs to the docker-container fluentd that's running in development. // You can find the details of the docker container in this file: /docker-compose.dev.yml -// This docker container is not run in production because there is no need to, customers will run fluentd on their own side in production. +// This docker container is not run in production because there is no need to, customers will run fluentd on their own side in production. -const express = require('express'); -const FluentClient = require('@fluent-org/logger').FluentClient; +const express = require("express"); +const FluentClient = require("@fluent-org/logger").FluentClient; const app = express(); // The 2nd argument can be omitted. Here is a default value for options. -const logger = new FluentClient('fluentd.test', { +const logger = new FluentClient("fluentd.test", { socket: { - host: 'localhost', + host: "localhost", port: 24224, timeout: 3000, // 3 seconds - } + }, }); -app.get('/', function(request, response) { - logger.emit('follow', {from: 'userA', to: 'userB'}); - response.send('Hello World!'); +app.get("/", function (request, response) { + logger.emit("follow", { from: "userA", to: "userB" }); + response.send("Hello World!"); }); const port = 7856; -app.listen(port, function() { +app.listen(port, function () { console.log("Listening on " + port); -}); \ No newline at end of file +}); diff --git a/Haraka/plugins/email_parser.js b/Haraka/plugins/email_parser.js index 2bea75127a..498802c341 100644 --- a/Haraka/plugins/email_parser.js +++ b/Haraka/plugins/email_parser.js @@ -22,9 +22,8 @@ // next(); // }; - exports.hook_rcpt = function (next, connection, params) { - var rcpt = params[0]; - this.loginfo("Got recipient: " + rcpt); - next(); -} \ No newline at end of file + var rcpt = params[0]; + this.loginfo("Got recipient: " + rcpt); + next(); +}; diff --git a/Ingestor/API/Alive.ts b/Ingestor/API/Alive.ts index 6fe919a683..f5ebe1a8dd 100644 --- a/Ingestor/API/Alive.ts +++ b/Ingestor/API/Alive.ts @@ -1,30 +1,30 @@ -import ProbeAuthorization from '../Middleware/ProbeAuthorization'; +import ProbeAuthorization from "../Middleware/ProbeAuthorization"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import Response from 'CommonServer/Utils/Response'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import Response from "CommonServer/Utils/Response"; const router: ExpressRouter = Express.getRouter(); router.post( - '/alive', - ProbeAuthorization.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - // middleware marks the probe as alive. - // so we don't need to do anything here. - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); - } + "/alive", + ProbeAuthorization.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + // middleware marks the probe as alive. + // so we don't need to do anything here. + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/Ingestor/API/FluentIngest.ts b/Ingestor/API/FluentIngest.ts index 182e538348..bf25b37227 100644 --- a/Ingestor/API/FluentIngest.ts +++ b/Ingestor/API/FluentIngest.ts @@ -1,89 +1,88 @@ import TelemetryIngest, { - TelemetryRequest, -} from '../Middleware/TelemetryIngest'; -import OneUptimeDate from 'Common/Types/Date'; -import { JSONObject } from 'Common/Types/JSON'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import LogService from 'CommonServer/Services/LogService'; + TelemetryRequest, +} from "../Middleware/TelemetryIngest"; +import OneUptimeDate from "Common/Types/Date"; +import { JSONObject } from "Common/Types/JSON"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import LogService from "CommonServer/Services/LogService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import Log, { LogSeverity } from 'Model/AnalyticsModels/Log'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import Log, { LogSeverity } from "Model/AnalyticsModels/Log"; export class FluentRequestMiddleware { - public static async getProductType( - req: ExpressRequest, - _res: ExpressResponse, - next: NextFunction - ): Promise<void> { - try { - (req as TelemetryRequest).productType = ProductType.Logs; - return next(); - } catch (err) { - return next(err); - } + public static async getProductType( + req: ExpressRequest, + _res: ExpressResponse, + next: NextFunction, + ): Promise<void> { + try { + (req as TelemetryRequest).productType = ProductType.Logs; + return next(); + } catch (err) { + return next(err); } + } } const router: ExpressRouter = Express.getRouter(); router.post( - '/fluentd/v1/logs', - FluentRequestMiddleware.getProductType, - TelemetryIngest.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - logger.debug('Fluent Ingestor API called'); + "/fluentd/v1/logs", + FluentRequestMiddleware.getProductType, + TelemetryIngest.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + logger.debug("Fluent Ingestor API called"); - const dbLogs: Array<Log> = []; + const dbLogs: Array<Log> = []; - const logItems: Array<JSONObject | string> = req.body as Array< - JSONObject | string - >; + const logItems: Array<JSONObject | string> = req.body as Array< + JSONObject | string + >; - for (let logItem of logItems) { - const dbLog: Log = new Log(); + for (let logItem of logItems) { + const dbLog: Log = new Log(); - dbLog.projectId = (req as TelemetryRequest).projectId; - dbLog.serviceId = (req as TelemetryRequest).serviceId; - dbLog.severityNumber = 0; - const currentTimeAndDate: Date = OneUptimeDate.getCurrentDate(); - dbLog.timeUnixNano = - OneUptimeDate.toUnixNano(currentTimeAndDate); - dbLog.time = currentTimeAndDate; + dbLog.projectId = (req as TelemetryRequest).projectId; + dbLog.serviceId = (req as TelemetryRequest).serviceId; + dbLog.severityNumber = 0; + const currentTimeAndDate: Date = OneUptimeDate.getCurrentDate(); + dbLog.timeUnixNano = OneUptimeDate.toUnixNano(currentTimeAndDate); + dbLog.time = currentTimeAndDate; - dbLog.severityText = LogSeverity.Unspecified; + dbLog.severityText = LogSeverity.Unspecified; - if (typeof logItem !== 'string') { - logItem = JSON.stringify(logItem); - } - - dbLog.body = logItem as string; - - dbLogs.push(dbLog); - } - - await LogService.createMany({ - items: dbLogs, - props: { - isRoot: true, - }, - }); - - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); + if (typeof logItem !== "string") { + logItem = JSON.stringify(logItem); } + + dbLog.body = logItem as string; + + dbLogs.push(dbLog); + } + + await LogService.createMany({ + items: dbLogs, + props: { + isRoot: true, + }, + }); + + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/Ingestor/API/IncomingRequest.ts b/Ingestor/API/IncomingRequest.ts index 6a27006688..371ebfc890 100644 --- a/Ingestor/API/IncomingRequest.ts +++ b/Ingestor/API/IncomingRequest.ts @@ -1,114 +1,110 @@ -import HTTPMethod from 'Common/Types/API/HTTPMethod'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ObjectID from 'Common/Types/ObjectID'; -import MonitorService from 'CommonServer/Services/MonitorService'; +import HTTPMethod from "Common/Types/API/HTTPMethod"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ObjectID from "Common/Types/ObjectID"; +import MonitorService from "CommonServer/Services/MonitorService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, - RequestHandler, -} from 'CommonServer/Utils/Express'; -import ProbeMonitorResponseService from 'CommonServer/Utils/Probe/ProbeMonitorResponse'; -import Response from 'CommonServer/Utils/Response'; -import Monitor from 'Model/Models/Monitor'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, + RequestHandler, +} from "CommonServer/Utils/Express"; +import ProbeMonitorResponseService from "CommonServer/Utils/Probe/ProbeMonitorResponse"; +import Response from "CommonServer/Utils/Response"; +import Monitor from "Model/Models/Monitor"; const router: ExpressRouter = Express.getRouter(); const processIncomingRequest: RequestHandler = async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, ): Promise<void> => { - try { - const requestHeaders: Dictionary<string> = - req.headers as Dictionary<string>; - const requestBody: string | JSONObject = req.body as - | string - | JSONObject; + try { + const requestHeaders: Dictionary<string> = + req.headers as Dictionary<string>; + const requestBody: string | JSONObject = req.body as string | JSONObject; - const monitorSecretKeyAsString: string | undefined = - req.params['secretkey']; + const monitorSecretKeyAsString: string | undefined = + req.params["secretkey"]; - if (!monitorSecretKeyAsString) { - throw new BadDataException('Invalid Secret Key'); - } - - const isGetRequest: boolean = req.method === 'GET'; - const isPostRequest: boolean = req.method === 'POST'; - - let httpMethod: HTTPMethod = HTTPMethod.GET; - - if (isGetRequest) { - httpMethod = HTTPMethod.GET; - } - - if (isPostRequest) { - httpMethod = HTTPMethod.POST; - } - - const monitor: Monitor | null = await MonitorService.findOneBy({ - query: { - incomingRequestSecretKey: new ObjectID( - monitorSecretKeyAsString - ), - monitorType: MonitorType.IncomingRequest, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); - - if (!monitor || !monitor._id) { - throw new BadDataException('Monitor not found'); - } - - const incomingRequest: IncomingMonitorRequest = { - monitorId: new ObjectID(monitor._id), - requestHeaders: requestHeaders, - requestBody: requestBody, - incomingRequestReceivedAt: OneUptimeDate.getCurrentDate(), - onlyCheckForIncomingRequestReceivedAt: false, - requestMethod: httpMethod, - }; - - // process probe response here. - await ProbeMonitorResponseService.processProbeResponse(incomingRequest); - - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); + if (!monitorSecretKeyAsString) { + throw new BadDataException("Invalid Secret Key"); } + + const isGetRequest: boolean = req.method === "GET"; + const isPostRequest: boolean = req.method === "POST"; + + let httpMethod: HTTPMethod = HTTPMethod.GET; + + if (isGetRequest) { + httpMethod = HTTPMethod.GET; + } + + if (isPostRequest) { + httpMethod = HTTPMethod.POST; + } + + const monitor: Monitor | null = await MonitorService.findOneBy({ + query: { + incomingRequestSecretKey: new ObjectID(monitorSecretKeyAsString), + monitorType: MonitorType.IncomingRequest, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); + + if (!monitor || !monitor._id) { + throw new BadDataException("Monitor not found"); + } + + const incomingRequest: IncomingMonitorRequest = { + monitorId: new ObjectID(monitor._id), + requestHeaders: requestHeaders, + requestBody: requestBody, + incomingRequestReceivedAt: OneUptimeDate.getCurrentDate(), + onlyCheckForIncomingRequestReceivedAt: false, + requestMethod: httpMethod, + }; + + // process probe response here. + await ProbeMonitorResponseService.processProbeResponse(incomingRequest); + + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); + } }; router.post( - '/incoming-request/:secretkey', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - processIncomingRequest(req, res, next); - } + "/incoming-request/:secretkey", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + processIncomingRequest(req, res, next); + }, ); router.get( - '/incoming-request/:secretkey', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - processIncomingRequest(req, res, next); - } + "/incoming-request/:secretkey", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + processIncomingRequest(req, res, next); + }, ); export default router; diff --git a/Ingestor/API/Monitor.ts b/Ingestor/API/Monitor.ts index b0644dca40..07536a26f5 100644 --- a/Ingestor/API/Monitor.ts +++ b/Ingestor/API/Monitor.ts @@ -1,340 +1,330 @@ -import ProbeAuthorization from '../Middleware/ProbeAuthorization'; -import { ProbeExpressRequest } from '../Types/Request'; -import MonitorUtil from '../Utils/Monitor'; -import BaseModel from 'Common/Models/BaseModel'; -import SortOrder from 'Common/Types/BaseDatabase/SortOrder'; -import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus'; -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Semaphore from 'CommonServer/Infrastructure/Semaphore'; -import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization'; -import MonitorProbeService from 'CommonServer/Services/MonitorProbeService'; -import Query from 'CommonServer/Types/Database/Query'; -import QueryHelper from 'CommonServer/Types/Database/QueryHelper'; -import CronTab from 'CommonServer/Utils/CronTab'; +import ProbeAuthorization from "../Middleware/ProbeAuthorization"; +import { ProbeExpressRequest } from "../Types/Request"; +import MonitorUtil from "../Utils/Monitor"; +import BaseModel from "Common/Models/BaseModel"; +import SortOrder from "Common/Types/BaseDatabase/SortOrder"; +import SubscriptionStatus from "Common/Types/Billing/SubscriptionStatus"; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Semaphore from "CommonServer/Infrastructure/Semaphore"; +import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization"; +import MonitorProbeService from "CommonServer/Services/MonitorProbeService"; +import Query from "CommonServer/Types/Database/Query"; +import QueryHelper from "CommonServer/Types/Database/QueryHelper"; +import CronTab from "CommonServer/Utils/CronTab"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, - OneUptimeRequest, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import Monitor from 'Model/Models/Monitor'; -import MonitorProbe from 'Model/Models/MonitorProbe'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, + OneUptimeRequest, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import Monitor from "Model/Models/Monitor"; +import MonitorProbe from "Model/Models/MonitorProbe"; const router: ExpressRouter = Express.getRouter(); type GetMonitorFetchQueryFunction = (probeId: ObjectID) => Query<MonitorProbe>; const getMonitorFetchQuery: GetMonitorFetchQueryFunction = ( - probeId: ObjectID + probeId: ObjectID, ): Query<MonitorProbe> => { - const monitorFetchQuery: Query<MonitorProbe> = { - probeId: probeId, - isEnabled: true, - nextPingAt: QueryHelper.lessThanEqualToOrNull( - OneUptimeDate.getCurrentDate() - ), - monitor: { - disableActiveMonitoring: false, // do not fetch if disabled is true. - disableActiveMonitoringBecauseOfManualIncident: false, - disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: false, - }, + const monitorFetchQuery: Query<MonitorProbe> = { + probeId: probeId, + isEnabled: true, + nextPingAt: QueryHelper.lessThanEqualToOrNull( + OneUptimeDate.getCurrentDate(), + ), + monitor: { + disableActiveMonitoring: false, // do not fetch if disabled is true. + disableActiveMonitoringBecauseOfManualIncident: false, + disableActiveMonitoringBecauseOfScheduledMaintenanceEvent: false, + }, - project: { - // get only active projects - paymentProviderSubscriptionStatus: QueryHelper.equalToOrNull([ - SubscriptionStatus.Active, - SubscriptionStatus.Trialing, - ]), - paymentProviderMeteredSubscriptionStatus: QueryHelper.equalToOrNull( - [SubscriptionStatus.Active, SubscriptionStatus.Trialing] - ), - }, - }; + project: { + // get only active projects + paymentProviderSubscriptionStatus: QueryHelper.equalToOrNull([ + SubscriptionStatus.Active, + SubscriptionStatus.Trialing, + ]), + paymentProviderMeteredSubscriptionStatus: QueryHelper.equalToOrNull([ + SubscriptionStatus.Active, + SubscriptionStatus.Trialing, + ]), + }, + }; - return monitorFetchQuery; + return monitorFetchQuery; }; router.get( - '/monitor/pending-list/:probeId', - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - if (!req.params['probeId']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Probe not found') - ); - } + "/monitor/pending-list/:probeId", + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + if (!req.params["probeId"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Probe not found"), + ); + } - //get list of monitors to be monitored - const monitorProbes: Array<MonitorProbe> = - await MonitorProbeService.findBy({ - query: getMonitorFetchQuery( - new ObjectID(req.params['probeId']) - ), - sort: { - nextPingAt: SortOrder.Ascending, - }, - select: { - nextPingAt: true, - probeId: true, - monitorId: true, - monitor: { - monitorSteps: true, - monitorType: true, - monitoringInterval: true, - }, - }, - skip: 0, - limit: LIMIT_MAX, - props: { - isRoot: true, - }, - }); + //get list of monitors to be monitored + const monitorProbes: Array<MonitorProbe> = + await MonitorProbeService.findBy({ + query: getMonitorFetchQuery(new ObjectID(req.params["probeId"])), + sort: { + nextPingAt: SortOrder.Ascending, + }, + select: { + nextPingAt: true, + probeId: true, + monitorId: true, + monitor: { + monitorSteps: true, + monitorType: true, + monitoringInterval: true, + }, + }, + skip: 0, + limit: LIMIT_MAX, + props: { + isRoot: true, + }, + }); - const monitors: Array<Monitor> = monitorProbes - .map((monitorProbe: MonitorProbe) => { - return monitorProbe.monitor!; - }) - .filter((monitor: Monitor) => { - return Boolean(monitor._id); - }); + const monitors: Array<Monitor> = monitorProbes + .map((monitorProbe: MonitorProbe) => { + return monitorProbe.monitor!; + }) + .filter((monitor: Monitor) => { + return Boolean(monitor._id); + }); - // return the list of monitors to be monitored + // return the list of monitors to be monitored - return Response.sendEntityArrayResponse( - req, - res, - monitors, - new PositiveNumber(monitors.length), - Monitor - ); - } catch (err) { - return next(err); - } + return Response.sendEntityArrayResponse( + req, + res, + monitors, + new PositiveNumber(monitors.length), + Monitor, + ); + } catch (err) { + return next(err); } + }, ); // This API returns the count of the monitor waiting to be monitored. router.get( - '/monitor/pending-count/:probeId', - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - if (!req.params['probeId']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Probe not found') - ); - } + "/monitor/pending-count/:probeId", + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + if (!req.params["probeId"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Probe not found"), + ); + } - //get list of monitors to be monitored - const monitorProbesCount: PositiveNumber = - await MonitorProbeService.countBy({ - query: getMonitorFetchQuery( - new ObjectID(req.params['probeId']) - ), - props: { - isRoot: true, - }, - }); + //get list of monitors to be monitored + const monitorProbesCount: PositiveNumber = + await MonitorProbeService.countBy({ + query: getMonitorFetchQuery(new ObjectID(req.params["probeId"])), + props: { + isRoot: true, + }, + }); - //get list of monitors to be monitored - const firstMonitorToBeFetched: MonitorProbe | null = - await MonitorProbeService.findOneBy({ - query: getMonitorFetchQuery( - new ObjectID(req.params['probeId']) - ), - select: { - nextPingAt: true, - monitorId: true, - }, - sort: { - nextPingAt: SortOrder.Ascending, - }, - props: { - isRoot: true, - }, - }); + //get list of monitors to be monitored + const firstMonitorToBeFetched: MonitorProbe | null = + await MonitorProbeService.findOneBy({ + query: getMonitorFetchQuery(new ObjectID(req.params["probeId"])), + select: { + nextPingAt: true, + monitorId: true, + }, + sort: { + nextPingAt: SortOrder.Ascending, + }, + props: { + isRoot: true, + }, + }); - return Response.sendJsonObjectResponse(req, res, { - firstMonitorToBeFetched: firstMonitorToBeFetched - ? BaseModel.toJSONObject( - firstMonitorToBeFetched, - MonitorProbe - ) - : null, - count: monitorProbesCount.toNumber(), - nextPingAt: firstMonitorToBeFetched?.nextPingAt, - friendlyNextPingAt: firstMonitorToBeFetched?.nextPingAt - ? OneUptimeDate.getDateAsFormattedStringInMultipleTimezones( - firstMonitorToBeFetched?.nextPingAt - ) - : '', - }); - } catch (err) { - return next(err); - } + return Response.sendJsonObjectResponse(req, res, { + firstMonitorToBeFetched: firstMonitorToBeFetched + ? BaseModel.toJSONObject(firstMonitorToBeFetched, MonitorProbe) + : null, + count: monitorProbesCount.toNumber(), + nextPingAt: firstMonitorToBeFetched?.nextPingAt, + friendlyNextPingAt: firstMonitorToBeFetched?.nextPingAt + ? OneUptimeDate.getDateAsFormattedStringInMultipleTimezones( + firstMonitorToBeFetched?.nextPingAt, + ) + : "", + }); + } catch (err) { + return next(err); } + }, ); router.post( - '/monitor/list', - ProbeAuthorization.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - let mutexId: ObjectID | null = null; + "/monitor/list", + ProbeAuthorization.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + let mutexId: ObjectID | null = null; + + try { + const data: JSONObject = req.body; + const limit: number = (data["limit"] as number) || 100; + + if ( + !(req as ProbeExpressRequest).probe || + !(req as ProbeExpressRequest).probe?.id + ) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Probe not found"), + ); + } + + const probeId: ObjectID = (req as ProbeExpressRequest).probe!.id!; + + if (!probeId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Probe not found"), + ); + } + + mutexId = await Semaphore.lock({ + key: probeId.toString(), + }); + + //get list of monitors to be monitored + const monitorProbes: Array<MonitorProbe> = + await MonitorProbeService.findBy({ + query: getMonitorFetchQuery((req as OneUptimeRequest).probe!.id!), + sort: { + nextPingAt: SortOrder.Ascending, + }, + skip: 0, + limit: limit, + select: { + nextPingAt: true, + probeId: true, + monitorId: true, + monitor: { + monitorSteps: true, + monitorType: true, + monitoringInterval: true, + }, + }, + props: { + isRoot: true, + }, + }); + + // update the lastMonitoredAt field of the monitors + + for (const monitorProbe of monitorProbes) { + if (!monitorProbe.monitor) { + continue; + } + + let nextPing: Date = OneUptimeDate.addRemoveMinutes( + OneUptimeDate.getCurrentDate(), + 1, + ); try { - const data: JSONObject = req.body; - const limit: number = (data['limit'] as number) || 100; - - if ( - !(req as ProbeExpressRequest).probe || - !(req as ProbeExpressRequest).probe?.id - ) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Probe not found') - ); - } - - const probeId: ObjectID = (req as ProbeExpressRequest).probe!.id!; - - if (!probeId) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Probe not found') - ); - } - - mutexId = await Semaphore.lock({ - key: probeId.toString(), - }); - - //get list of monitors to be monitored - const monitorProbes: Array<MonitorProbe> = - await MonitorProbeService.findBy({ - query: getMonitorFetchQuery( - (req as OneUptimeRequest).probe!.id! - ), - sort: { - nextPingAt: SortOrder.Ascending, - }, - skip: 0, - limit: limit, - select: { - nextPingAt: true, - probeId: true, - monitorId: true, - monitor: { - monitorSteps: true, - monitorType: true, - monitoringInterval: true, - }, - }, - props: { - isRoot: true, - }, - }); - - // update the lastMonitoredAt field of the monitors - - for (const monitorProbe of monitorProbes) { - if (!monitorProbe.monitor) { - continue; - } - - let nextPing: Date = OneUptimeDate.addRemoveMinutes( - OneUptimeDate.getCurrentDate(), - 1 - ); - - try { - nextPing = CronTab.getNextExecutionTime( - monitorProbe?.monitor?.monitoringInterval as string - ); - } catch (err) { - logger.error(err); - } - - await MonitorProbeService.updateOneById({ - id: monitorProbe.id!, - data: { - lastPingAt: OneUptimeDate.getCurrentDate(), - nextPingAt: nextPing, - }, - props: { - isRoot: true, - }, - }); - } - - await Semaphore.release(mutexId); - - const monitors: Array<Monitor> = monitorProbes - .map((monitorProbe: MonitorProbe) => { - return monitorProbe.monitor!; - }) - .filter((monitor: Monitor) => { - return Boolean(monitor._id); - }); - - // check if the monitor needs secrets to be filled. - - const monitorsWithSecretPopulated: Array<Monitor> = []; - - for (const monitor of monitors) { - const monitorWithSecrets: Monitor = - await MonitorUtil.populateSecrets(monitor); - - monitorsWithSecretPopulated.push(monitorWithSecrets); - } - - // return the list of monitors to be monitored - - return Response.sendEntityArrayResponse( - req, - res, - monitorsWithSecretPopulated, - new PositiveNumber(monitorsWithSecretPopulated.length), - Monitor - ); + nextPing = CronTab.getNextExecutionTime( + monitorProbe?.monitor?.monitoringInterval as string, + ); } catch (err) { - try { - if (mutexId) { - await Semaphore.release(mutexId); - } - } catch (err) { - logger.error(err); - } - - return next(err); + logger.error(err); } + + await MonitorProbeService.updateOneById({ + id: monitorProbe.id!, + data: { + lastPingAt: OneUptimeDate.getCurrentDate(), + nextPingAt: nextPing, + }, + props: { + isRoot: true, + }, + }); + } + + await Semaphore.release(mutexId); + + const monitors: Array<Monitor> = monitorProbes + .map((monitorProbe: MonitorProbe) => { + return monitorProbe.monitor!; + }) + .filter((monitor: Monitor) => { + return Boolean(monitor._id); + }); + + // check if the monitor needs secrets to be filled. + + const monitorsWithSecretPopulated: Array<Monitor> = []; + + for (const monitor of monitors) { + const monitorWithSecrets: Monitor = + await MonitorUtil.populateSecrets(monitor); + + monitorsWithSecretPopulated.push(monitorWithSecrets); + } + + // return the list of monitors to be monitored + + return Response.sendEntityArrayResponse( + req, + res, + monitorsWithSecretPopulated, + new PositiveNumber(monitorsWithSecretPopulated.length), + Monitor, + ); + } catch (err) { + try { + if (mutexId) { + await Semaphore.release(mutexId); + } + } catch (err) { + logger.error(err); + } + + return next(err); } + }, ); export default router; diff --git a/Ingestor/API/OTelIngest.ts b/Ingestor/API/OTelIngest.ts index 53a9ae09ef..5753fe915b 100644 --- a/Ingestor/API/OTelIngest.ts +++ b/Ingestor/API/OTelIngest.ts @@ -1,49 +1,49 @@ import TelemetryIngest, { - TelemetryRequest, -} from '../Middleware/TelemetryIngest'; + TelemetryRequest, +} from "../Middleware/TelemetryIngest"; import OTelIngestService, { - OtelAggregationTemporality, -} from '../Service/OTelIngest'; -import OneUptimeDate from 'Common/Types/Date'; -import BadRequestException from 'Common/Types/Exception/BadRequestException'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import Text from 'Common/Types/Text'; -import LogService from 'CommonServer/Services/LogService'; -import MetricService from 'CommonServer/Services/MetricService'; -import SpanService from 'CommonServer/Services/SpanService'; + OtelAggregationTemporality, +} from "../Service/OTelIngest"; +import OneUptimeDate from "Common/Types/Date"; +import BadRequestException from "Common/Types/Exception/BadRequestException"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import Text from "Common/Types/Text"; +import LogService from "CommonServer/Services/LogService"; +import MetricService from "CommonServer/Services/MetricService"; +import SpanService from "CommonServer/Services/SpanService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import Log, { LogSeverity } from 'Model/AnalyticsModels/Log'; -import Metric, { MetricPointType } from 'Model/AnalyticsModels/Metric'; -import Span, { SpanKind, SpanStatus } from 'Model/AnalyticsModels/Span'; -import protobuf from 'protobufjs'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import Log, { LogSeverity } from "Model/AnalyticsModels/Log"; +import Metric, { MetricPointType } from "Model/AnalyticsModels/Metric"; +import Span, { SpanKind, SpanStatus } from "Model/AnalyticsModels/Span"; +import protobuf from "protobufjs"; // Load proto file for OTel // Create a root namespace const LogsProto: protobuf.Root = protobuf.loadSync( - '/usr/src/app/ProtoFiles/OTel/v1/logs.proto' + "/usr/src/app/ProtoFiles/OTel/v1/logs.proto", ); const TracesProto: protobuf.Root = protobuf.loadSync( - '/usr/src/app/ProtoFiles/OTel/v1/traces.proto' + "/usr/src/app/ProtoFiles/OTel/v1/traces.proto", ); const MetricsProto: protobuf.Root = protobuf.loadSync( - '/usr/src/app/ProtoFiles/OTel/v1/metrics.proto' + "/usr/src/app/ProtoFiles/OTel/v1/metrics.proto", ); // Lookup the message type -const LogsData: protobuf.Type = LogsProto.lookupType('LogsData'); -const TracesData: protobuf.Type = TracesProto.lookupType('TracesData'); -const MetricsData: protobuf.Type = MetricsProto.lookupType('MetricsData'); +const LogsData: protobuf.Type = LogsProto.lookupType("LogsData"); +const TracesData: protobuf.Type = TracesProto.lookupType("TracesData"); +const MetricsData: protobuf.Type = MetricsProto.lookupType("MetricsData"); const router: ExpressRouter = Express.getRouter(); @@ -54,532 +54,469 @@ const router: ExpressRouter = Express.getRouter(); */ class OpenTelemetryRequestMiddleware { - public static async getProductType( - req: ExpressRequest, - _res: ExpressResponse, - next: NextFunction - ): Promise<void> { - try { - let productType: ProductType; + public static async getProductType( + req: ExpressRequest, + _res: ExpressResponse, + next: NextFunction, + ): Promise<void> { + try { + let productType: ProductType; - const isProtobuf: boolean = req.body instanceof Uint8Array; + const isProtobuf: boolean = req.body instanceof Uint8Array; - if (req.url.includes('/otlp/v1/traces')) { - if (isProtobuf) { - req.body = TracesData.decode(req.body); - } - productType = ProductType.Traces; - } else if (req.url.includes('/otlp/v1/logs')) { - if (isProtobuf) { - req.body = LogsData.decode(req.body); - } - productType = ProductType.Logs; - } else if (req.url.includes('/otlp/v1/metrics')) { - if (isProtobuf) { - req.body = MetricsData.decode(req.body); - } - productType = ProductType.Metrics; - } else { - throw new BadRequestException('Invalid URL: ' + req.baseUrl); - } - - (req as TelemetryRequest).productType = productType; - next(); - } catch (err) { - return next(err); + if (req.url.includes("/otlp/v1/traces")) { + if (isProtobuf) { + req.body = TracesData.decode(req.body); } + productType = ProductType.Traces; + } else if (req.url.includes("/otlp/v1/logs")) { + if (isProtobuf) { + req.body = LogsData.decode(req.body); + } + productType = ProductType.Logs; + } else if (req.url.includes("/otlp/v1/metrics")) { + if (isProtobuf) { + req.body = MetricsData.decode(req.body); + } + productType = ProductType.Metrics; + } else { + throw new BadRequestException("Invalid URL: " + req.baseUrl); + } + + (req as TelemetryRequest).productType = productType; + next(); + } catch (err) { + return next(err); } + } } router.post( - '/otlp/v1/traces', - OpenTelemetryRequestMiddleware.getProductType, - TelemetryIngest.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { + "/otlp/v1/traces", + OpenTelemetryRequestMiddleware.getProductType, + TelemetryIngest.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + if ( + !(req as TelemetryRequest).projectId || + !(req as TelemetryRequest).serviceId + ) { + throw new BadRequestException( + "Invalid request - projectId or serviceId not found in request.", + ); + } + + const traceData: JSONObject = req.body.toJSON + ? req.body.toJSON() + : req.body; + const resourceSpans: JSONArray = traceData["resourceSpans"] as JSONArray; + + const dbSpans: Array<Span> = []; + + for (const resourceSpan of resourceSpans) { + const scopeSpans: JSONArray = resourceSpan["scopeSpans"] as JSONArray; + + for (const scopeSpan of scopeSpans) { + const spans: JSONArray = scopeSpan["spans"] as JSONArray; + + for (const span of spans) { + const dbSpan: Span = new Span(); + + // attrbibutes + const attributesObject: JSONObject = {}; + if ( - !(req as TelemetryRequest).projectId || - !(req as TelemetryRequest).serviceId + resourceSpan["resource"] && + (resourceSpan["resource"] as JSONObject)["attributes"] && + ( + (resourceSpan["resource"] as JSONObject)[ + "attributes" + ] as JSONArray + ).length > 0 ) { - throw new BadRequestException( - 'Invalid request - projectId or serviceId not found in request.' - ); + attributesObject["resource"] = OTelIngestService.getAttributes( + (resourceSpan["resource"] as JSONObject)[ + "attributes" + ] as JSONArray, + ); } - const traceData: JSONObject = req.body.toJSON - ? req.body.toJSON() - : req.body; - const resourceSpans: JSONArray = traceData[ - 'resourceSpans' - ] as JSONArray; + // scope attributes - const dbSpans: Array<Span> = []; - - for (const resourceSpan of resourceSpans) { - const scopeSpans: JSONArray = resourceSpan[ - 'scopeSpans' - ] as JSONArray; - - for (const scopeSpan of scopeSpans) { - const spans: JSONArray = scopeSpan['spans'] as JSONArray; - - for (const span of spans) { - const dbSpan: Span = new Span(); - - // attrbibutes - const attributesObject: JSONObject = {}; - - if ( - resourceSpan['resource'] && - (resourceSpan['resource'] as JSONObject)[ - 'attributes' - ] && - ( - (resourceSpan['resource'] as JSONObject)[ - 'attributes' - ] as JSONArray - ).length > 0 - ) { - attributesObject['resource'] = - OTelIngestService.getAttributes( - (resourceSpan['resource'] as JSONObject)[ - 'attributes' - ] as JSONArray - ); - } - - // scope attributes - - if ( - scopeSpan['scope'] && - Object.keys(scopeSpan['scope']).length > 0 - ) { - attributesObject['scope'] = scopeSpan[ - 'scope' - ] as JSONObject; - } - - dbSpan.attributes = { - ...attributesObject, - ...OTelIngestService.getAttributes( - span['attributes'] as JSONArray - ), - }; - - dbSpan.projectId = (req as TelemetryRequest).projectId; - dbSpan.serviceId = (req as TelemetryRequest).serviceId; - - dbSpan.spanId = Text.convertBase64ToHex( - span['spanId'] as string - ); - dbSpan.traceId = Text.convertBase64ToHex( - span['traceId'] as string - ); - dbSpan.parentSpanId = Text.convertBase64ToHex( - span['parentSpanId'] as string - ); - dbSpan.startTimeUnixNano = span[ - 'startTimeUnixNano' - ] as number; - dbSpan.endTimeUnixNano = span[ - 'endTimeUnixNano' - ] as number; - - let spanStatusCode: SpanStatus = SpanStatus.Unset; - - if ( - span['status'] && - (span['status'] as JSONObject)?.['code'] && - typeof (span['status'] as JSONObject)?.['code'] === - 'number' - ) { - spanStatusCode = (span['status'] as JSONObject)?.[ - 'code' - ] as number; - } - - if ( - span['status'] && - (span['status'] as JSONObject)?.['code'] && - typeof (span['status'] as JSONObject)?.['code'] === - 'string' - ) { - if ( - (span['status'] as JSONObject)?.['code'] === - 'STATUS_CODE_UNSET' - ) { - spanStatusCode = SpanStatus.Unset; - } else if ( - (span['status'] as JSONObject)?.['code'] === - 'STATUS_CODE_OK' - ) { - spanStatusCode = SpanStatus.Ok; - } else if ( - (span['status'] as JSONObject)?.['code'] === - 'STATUS_CODE_ERROR' - ) { - spanStatusCode = SpanStatus.Error; - } - } - - dbSpan.statusCode = spanStatusCode; - - dbSpan.statusMessage = (span['status'] as JSONObject)?.[ - 'message' - ] as string; - - dbSpan.startTime = OneUptimeDate.fromUnixNano( - span['startTimeUnixNano'] as number - ); - - dbSpan.endTime = OneUptimeDate.fromUnixNano( - span['endTimeUnixNano'] as number - ); - - dbSpan.durationUnixNano = - (span['endTimeUnixNano'] as number) - - (span['startTimeUnixNano'] as number); - - dbSpan.name = span['name'] as string; - dbSpan.kind = - (span['kind'] as SpanKind) || SpanKind.Internal; - - // add events - - if (span['events'] && span['events'] instanceof Array) { - dbSpan.events = []; - - for (const event of span['events'] as JSONArray) { - const eventTimeUnixNano: number = event[ - 'timeUnixNano' - ] as number; - const eventTime: Date = - OneUptimeDate.fromUnixNano( - eventTimeUnixNano - ); - - dbSpan.events.push({ - time: eventTime, - timeUnixNano: eventTimeUnixNano, - name: event['name'] as string, - attributes: OTelIngestService.getAttributes( - event['attributes'] as JSONArray - ), - }); - } - } - - // add links - - if (span['links'] && span['links'] instanceof Array) { - dbSpan.links = []; - - for (const link of span['links'] as JSONArray) { - dbSpan.links.push({ - traceId: Text.convertBase64ToHex( - link['traceId'] as string - ), - spanId: Text.convertBase64ToHex( - link['spanId'] as string - ), - attributes: OTelIngestService.getAttributes( - link['attributes'] as JSONArray - ), - }); - } - } - - dbSpans.push(dbSpan); - } - } + if ( + scopeSpan["scope"] && + Object.keys(scopeSpan["scope"]).length > 0 + ) { + attributesObject["scope"] = scopeSpan["scope"] as JSONObject; } - await SpanService.createMany({ - items: dbSpans, - props: { - isRoot: true, - }, - }); + dbSpan.attributes = { + ...attributesObject, + ...OTelIngestService.getAttributes( + span["attributes"] as JSONArray, + ), + }; - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); + dbSpan.projectId = (req as TelemetryRequest).projectId; + dbSpan.serviceId = (req as TelemetryRequest).serviceId; + + dbSpan.spanId = Text.convertBase64ToHex(span["spanId"] as string); + dbSpan.traceId = Text.convertBase64ToHex(span["traceId"] as string); + dbSpan.parentSpanId = Text.convertBase64ToHex( + span["parentSpanId"] as string, + ); + dbSpan.startTimeUnixNano = span["startTimeUnixNano"] as number; + dbSpan.endTimeUnixNano = span["endTimeUnixNano"] as number; + + let spanStatusCode: SpanStatus = SpanStatus.Unset; + + if ( + span["status"] && + (span["status"] as JSONObject)?.["code"] && + typeof (span["status"] as JSONObject)?.["code"] === "number" + ) { + spanStatusCode = (span["status"] as JSONObject)?.[ + "code" + ] as number; + } + + if ( + span["status"] && + (span["status"] as JSONObject)?.["code"] && + typeof (span["status"] as JSONObject)?.["code"] === "string" + ) { + if ( + (span["status"] as JSONObject)?.["code"] === "STATUS_CODE_UNSET" + ) { + spanStatusCode = SpanStatus.Unset; + } else if ( + (span["status"] as JSONObject)?.["code"] === "STATUS_CODE_OK" + ) { + spanStatusCode = SpanStatus.Ok; + } else if ( + (span["status"] as JSONObject)?.["code"] === "STATUS_CODE_ERROR" + ) { + spanStatusCode = SpanStatus.Error; + } + } + + dbSpan.statusCode = spanStatusCode; + + dbSpan.statusMessage = (span["status"] as JSONObject)?.[ + "message" + ] as string; + + dbSpan.startTime = OneUptimeDate.fromUnixNano( + span["startTimeUnixNano"] as number, + ); + + dbSpan.endTime = OneUptimeDate.fromUnixNano( + span["endTimeUnixNano"] as number, + ); + + dbSpan.durationUnixNano = + (span["endTimeUnixNano"] as number) - + (span["startTimeUnixNano"] as number); + + dbSpan.name = span["name"] as string; + dbSpan.kind = (span["kind"] as SpanKind) || SpanKind.Internal; + + // add events + + if (span["events"] && span["events"] instanceof Array) { + dbSpan.events = []; + + for (const event of span["events"] as JSONArray) { + const eventTimeUnixNano: number = event[ + "timeUnixNano" + ] as number; + const eventTime: Date = + OneUptimeDate.fromUnixNano(eventTimeUnixNano); + + dbSpan.events.push({ + time: eventTime, + timeUnixNano: eventTimeUnixNano, + name: event["name"] as string, + attributes: OTelIngestService.getAttributes( + event["attributes"] as JSONArray, + ), + }); + } + } + + // add links + + if (span["links"] && span["links"] instanceof Array) { + dbSpan.links = []; + + for (const link of span["links"] as JSONArray) { + dbSpan.links.push({ + traceId: Text.convertBase64ToHex(link["traceId"] as string), + spanId: Text.convertBase64ToHex(link["spanId"] as string), + attributes: OTelIngestService.getAttributes( + link["attributes"] as JSONArray, + ), + }); + } + } + + dbSpans.push(dbSpan); + } } + } + + await SpanService.createMany({ + items: dbSpans, + props: { + isRoot: true, + }, + }); + + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); router.post( - '/otlp/v1/metrics', - OpenTelemetryRequestMiddleware.getProductType, - TelemetryIngest.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { + "/otlp/v1/metrics", + OpenTelemetryRequestMiddleware.getProductType, + TelemetryIngest.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + if ( + !(req as TelemetryRequest).projectId || + !(req as TelemetryRequest).serviceId + ) { + throw new BadRequestException( + "Invalid request - projectId or serviceId not found in request.", + ); + } + + req.body = req.body.toJSON ? req.body.toJSON() : req.body; + + const resourceMetrics: JSONArray = req.body[ + "resourceMetrics" + ] as JSONArray; + + const dbMetrics: Array<Metric> = new Array<Metric>(); + + for (const resourceMetric of resourceMetrics) { + const scopeMetrics: JSONArray = resourceMetric[ + "scopeMetrics" + ] as JSONArray; + + for (const scopeMetric of scopeMetrics) { + const metrics: JSONArray = scopeMetric["metrics"] as JSONArray; + + for (const metric of metrics) { + const metricName: string = metric["name"] as string; + const metricDescription: string = metric["description"] as string; + + const metricUnit: string = metric["unit"] as string; + + const dbMetric: Metric = new Metric(); + + dbMetric.projectId = (req as TelemetryRequest).projectId; + dbMetric.serviceId = (req as TelemetryRequest).serviceId; + + dbMetric.name = metricName; + dbMetric.description = metricDescription; + + if (metricUnit) { + dbMetric.unit = metricUnit; + } + + let attributesObject: JSONObject = {}; + if ( - !(req as TelemetryRequest).projectId || - !(req as TelemetryRequest).serviceId + metric["attributes"] && + metric["attributes"] instanceof Array && + metric["attributes"].length > 0 ) { - throw new BadRequestException( - 'Invalid request - projectId or serviceId not found in request.' - ); + attributesObject = { + ...OTelIngestService.getAttributes( + metric["attributes"] as JSONArray, + ), + }; } - req.body = req.body.toJSON ? req.body.toJSON() : req.body; - - const resourceMetrics: JSONArray = req.body[ - 'resourceMetrics' - ] as JSONArray; - - const dbMetrics: Array<Metric> = new Array<Metric>(); - - for (const resourceMetric of resourceMetrics) { - const scopeMetrics: JSONArray = resourceMetric[ - 'scopeMetrics' - ] as JSONArray; - - for (const scopeMetric of scopeMetrics) { - const metrics: JSONArray = scopeMetric[ - 'metrics' - ] as JSONArray; - - for (const metric of metrics) { - const metricName: string = metric['name'] as string; - const metricDescription: string = metric[ - 'description' - ] as string; - - const metricUnit: string = metric['unit'] as string; - - const dbMetric: Metric = new Metric(); - - dbMetric.projectId = ( - req as TelemetryRequest - ).projectId; - dbMetric.serviceId = ( - req as TelemetryRequest - ).serviceId; - - dbMetric.name = metricName; - dbMetric.description = metricDescription; - - if (metricUnit) { - dbMetric.unit = metricUnit; - } - - let attributesObject: JSONObject = {}; - - if ( - metric['attributes'] && - metric['attributes'] instanceof Array && - metric['attributes'].length > 0 - ) { - attributesObject = { - ...OTelIngestService.getAttributes( - metric['attributes'] as JSONArray - ), - }; - } - - if ( - resourceMetric['resource'] && - (resourceMetric['resource'] as JSONObject)[ - 'attributes' - ] && - ((resourceMetric['resource'] as JSONObject)[ - 'attributes' - ] as JSONArray) instanceof Array && - ( - (resourceMetric['resource'] as JSONObject)[ - 'attributes' - ] as JSONArray - ).length > 0 - ) { - attributesObject = { - ...attributesObject, - resource: OTelIngestService.getAttributes( - (resourceMetric['resource'] as JSONObject)[ - 'attributes' - ] as JSONArray - ), - }; - } - - if ( - scopeMetric['scope'] && - Object.keys(scopeMetric['scope']).length > 0 - ) { - attributesObject = { - ...attributesObject, - scope: - (scopeMetric['scope'] as JSONObject) || {}, - }; - } - - // add attributes - dbMetric.attributes = attributesObject; - - if ( - metric['sum'] && - (metric['sum'] as JSONObject)['dataPoints'] && - ( - (metric['sum'] as JSONObject)[ - 'dataPoints' - ] as JSONArray - ).length > 0 - ) { - for (const datapoint of ( - metric['sum'] as JSONObject - )['dataPoints'] as JSONArray) { - const sumMetric: Metric = - OTelIngestService.getMetricFromDatapoint({ - dbMetric: dbMetric, - datapoint: datapoint, - aggregationTemporality: ( - metric['sum'] as JSONObject - )[ - 'aggregationTemporality' - ] as OtelAggregationTemporality, - isMonotonic: ( - metric['sum'] as JSONObject - )['isMonotonic'] as boolean | undefined, - }); - - sumMetric.metricPointType = MetricPointType.Sum; - - dbMetrics.push(sumMetric); - } - } else if ( - metric['gauge'] && - (metric['gauge'] as JSONObject)['dataPoints'] && - ( - (metric['gauge'] as JSONObject)[ - 'dataPoints' - ] as JSONArray - ).length > 0 - ) { - for (const datapoint of ( - metric['gauge'] as JSONObject - )['dataPoints'] as JSONArray) { - const guageMetric: Metric = - OTelIngestService.getMetricFromDatapoint({ - dbMetric: dbMetric, - datapoint: datapoint, - aggregationTemporality: ( - metric['gauge'] as JSONObject - )[ - 'aggregationTemporality' - ] as OtelAggregationTemporality, - isMonotonic: ( - metric['gauge'] as JSONObject - )['isMonotonic'] as boolean | undefined, - }); - - guageMetric.metricPointType = - MetricPointType.Gauge; - - dbMetrics.push(guageMetric); - } - } else if ( - metric['histogram'] && - (metric['histogram'] as JSONObject)['dataPoints'] && - ( - (metric['histogram'] as JSONObject)[ - 'dataPoints' - ] as JSONArray - ).length > 0 - ) { - for (const datapoint of ( - metric['histogram'] as JSONObject - )['dataPoints'] as JSONArray) { - const histogramMetric: Metric = - OTelIngestService.getMetricFromDatapoint({ - dbMetric: dbMetric, - datapoint: datapoint, - aggregationTemporality: ( - metric['histogram'] as JSONObject - )[ - 'aggregationTemporality' - ] as OtelAggregationTemporality, - isMonotonic: ( - metric['histogram'] as JSONObject - )['isMonotonic'] as boolean | undefined, - }); - - histogramMetric.metricPointType = - MetricPointType.Histogram; - - dbMetrics.push(histogramMetric); - } - } else { - logger.warn('Unknown metric type'); - logger.warn(metric); - } - } - } + if ( + resourceMetric["resource"] && + (resourceMetric["resource"] as JSONObject)["attributes"] && + ((resourceMetric["resource"] as JSONObject)[ + "attributes" + ] as JSONArray) instanceof Array && + ( + (resourceMetric["resource"] as JSONObject)[ + "attributes" + ] as JSONArray + ).length > 0 + ) { + attributesObject = { + ...attributesObject, + resource: OTelIngestService.getAttributes( + (resourceMetric["resource"] as JSONObject)[ + "attributes" + ] as JSONArray, + ), + }; } - await MetricService.createMany({ - items: dbMetrics, - props: { - isRoot: true, - }, - }); + if ( + scopeMetric["scope"] && + Object.keys(scopeMetric["scope"]).length > 0 + ) { + attributesObject = { + ...attributesObject, + scope: (scopeMetric["scope"] as JSONObject) || {}, + }; + } - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); + // add attributes + dbMetric.attributes = attributesObject; + + if ( + metric["sum"] && + (metric["sum"] as JSONObject)["dataPoints"] && + ((metric["sum"] as JSONObject)["dataPoints"] as JSONArray) + .length > 0 + ) { + for (const datapoint of (metric["sum"] as JSONObject)[ + "dataPoints" + ] as JSONArray) { + const sumMetric: Metric = + OTelIngestService.getMetricFromDatapoint({ + dbMetric: dbMetric, + datapoint: datapoint, + aggregationTemporality: (metric["sum"] as JSONObject)[ + "aggregationTemporality" + ] as OtelAggregationTemporality, + isMonotonic: (metric["sum"] as JSONObject)[ + "isMonotonic" + ] as boolean | undefined, + }); + + sumMetric.metricPointType = MetricPointType.Sum; + + dbMetrics.push(sumMetric); + } + } else if ( + metric["gauge"] && + (metric["gauge"] as JSONObject)["dataPoints"] && + ((metric["gauge"] as JSONObject)["dataPoints"] as JSONArray) + .length > 0 + ) { + for (const datapoint of (metric["gauge"] as JSONObject)[ + "dataPoints" + ] as JSONArray) { + const guageMetric: Metric = + OTelIngestService.getMetricFromDatapoint({ + dbMetric: dbMetric, + datapoint: datapoint, + aggregationTemporality: (metric["gauge"] as JSONObject)[ + "aggregationTemporality" + ] as OtelAggregationTemporality, + isMonotonic: (metric["gauge"] as JSONObject)[ + "isMonotonic" + ] as boolean | undefined, + }); + + guageMetric.metricPointType = MetricPointType.Gauge; + + dbMetrics.push(guageMetric); + } + } else if ( + metric["histogram"] && + (metric["histogram"] as JSONObject)["dataPoints"] && + ((metric["histogram"] as JSONObject)["dataPoints"] as JSONArray) + .length > 0 + ) { + for (const datapoint of (metric["histogram"] as JSONObject)[ + "dataPoints" + ] as JSONArray) { + const histogramMetric: Metric = + OTelIngestService.getMetricFromDatapoint({ + dbMetric: dbMetric, + datapoint: datapoint, + aggregationTemporality: (metric["histogram"] as JSONObject)[ + "aggregationTemporality" + ] as OtelAggregationTemporality, + isMonotonic: (metric["histogram"] as JSONObject)[ + "isMonotonic" + ] as boolean | undefined, + }); + + histogramMetric.metricPointType = MetricPointType.Histogram; + + dbMetrics.push(histogramMetric); + } + } else { + logger.warn("Unknown metric type"); + logger.warn(metric); + } + } } + } + + await MetricService.createMany({ + items: dbMetrics, + props: { + isRoot: true, + }, + }); + + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); router.post( - '/otlp/v1/logs', - OpenTelemetryRequestMiddleware.getProductType, - TelemetryIngest.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - if ( - !(req as TelemetryRequest).projectId || - !(req as TelemetryRequest).serviceId - ) { - throw new BadRequestException( - 'Invalid request - projectId or serviceId not found in request.' - ); - } + "/otlp/v1/logs", + OpenTelemetryRequestMiddleware.getProductType, + TelemetryIngest.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + if ( + !(req as TelemetryRequest).projectId || + !(req as TelemetryRequest).serviceId + ) { + throw new BadRequestException( + "Invalid request - projectId or serviceId not found in request.", + ); + } - req.body = req.body.toJSON ? req.body.toJSON() : req.body; + req.body = req.body.toJSON ? req.body.toJSON() : req.body; - const resourceLogs: JSONArray = req.body[ - 'resourceLogs' - ] as JSONArray; + const resourceLogs: JSONArray = req.body["resourceLogs"] as JSONArray; - const dbLogs: Array<Log> = []; + const dbLogs: Array<Log> = []; - for (const resourceLog of resourceLogs) { - const scopeLogs: JSONArray = resourceLog[ - 'scopeLogs' - ] as JSONArray; + for (const resourceLog of resourceLogs) { + const scopeLogs: JSONArray = resourceLog["scopeLogs"] as JSONArray; - for (const scopeLog of scopeLogs) { - const logRecords: JSONArray = scopeLog[ - 'logRecords' - ] as JSONArray; + for (const scopeLog of scopeLogs) { + const logRecords: JSONArray = scopeLog["logRecords"] as JSONArray; - for (const log of logRecords) { - const dbLog: Log = new Log(); + for (const log of logRecords) { + const dbLog: Log = new Log(); - /* + /* Example: { @@ -595,157 +532,123 @@ router.post( } */ - //attributes + //attributes - let attributesObject: JSONObject = {}; + let attributesObject: JSONObject = {}; - if ( - resourceLog['resource'] && - (resourceLog['resource'] as JSONObject)[ - 'attributes' - ] && - ( - (resourceLog['resource'] as JSONObject)[ - 'attributes' - ] as JSONArray - ).length > 0 - ) { - attributesObject = { - ...attributesObject, - resource: OTelIngestService.getAttributes( - (resourceLog['resource'] as JSONObject)[ - 'attributes' - ] as JSONArray - ), - }; - } - - if ( - scopeLog['scope'] && - Object.keys(scopeLog['scope']).length > 0 - ) { - attributesObject = { - ...attributesObject, - scope: (scopeLog['scope'] as JSONObject) || {}, - }; - } - - dbLog.attributes = { - ...attributesObject, - ...OTelIngestService.getAttributes( - log['attributes'] as JSONArray - ), - }; - - dbLog.projectId = (req as TelemetryRequest).projectId; - dbLog.serviceId = (req as TelemetryRequest).serviceId; - - dbLog.timeUnixNano = log['timeUnixNano'] as number; - dbLog.time = OneUptimeDate.fromUnixNano( - log['timeUnixNano'] as number - ); - - let logSeverityNumber: number = - (log['severityNumber'] as number) || 0; // 0 is Unspecified by default. - - if (typeof logSeverityNumber === 'string') { - if (logSeverityNumber === 'SEVERITY_NUMBER_TRACE') { - logSeverityNumber = 1; - } else if ( - logSeverityNumber === 'SEVERITY_NUMBER_DEBUG' - ) { - logSeverityNumber = 5; - } else if ( - logSeverityNumber === 'SEVERITY_NUMBER_INFO' - ) { - logSeverityNumber = 9; - } else if ( - logSeverityNumber === 'SEVERITY_NUMBER_WARN' - ) { - logSeverityNumber = 13; - } else if ( - logSeverityNumber === 'SEVERITY_NUMBER_ERROR' - ) { - logSeverityNumber = 17; - } else if ( - logSeverityNumber === 'SEVERITY_NUMBER_FATAL' - ) { - logSeverityNumber = 21; - } else { - logSeverityNumber = parseInt(logSeverityNumber); - } - } - - dbLog.severityNumber = logSeverityNumber; - - let logSeverity: LogSeverity = LogSeverity.Unspecified; - - // these numbers are from the opentelemetry/api-logs package - if (logSeverityNumber < 0 || logSeverityNumber > 24) { - logSeverity = LogSeverity.Unspecified; - logSeverityNumber = 0; - } else if ( - logSeverityNumber >= 1 && - logSeverityNumber <= 4 - ) { - logSeverity = LogSeverity.Trace; - } else if ( - logSeverityNumber >= 5 && - logSeverityNumber <= 8 - ) { - logSeverity = LogSeverity.Debug; - } else if ( - logSeverityNumber >= 9 && - logSeverityNumber <= 12 - ) { - logSeverity = LogSeverity.Information; - } else if ( - logSeverityNumber >= 13 && - logSeverityNumber <= 16 - ) { - logSeverity = LogSeverity.Warning; - } else if ( - logSeverityNumber >= 17 && - logSeverityNumber <= 20 - ) { - logSeverity = LogSeverity.Error; - } else if ( - logSeverityNumber >= 21 && - logSeverityNumber <= 24 - ) { - logSeverity = LogSeverity.Fatal; - } - - dbLog.severityText = logSeverity; - - const logBody: JSONObject = log['body'] as JSONObject; - - dbLog.body = logBody['stringValue'] as string; - - dbLog.traceId = Text.convertBase64ToHex( - log['traceId'] as string - ); - dbLog.spanId = Text.convertBase64ToHex( - log['spanId'] as string - ); - - dbLogs.push(dbLog); - } - } + if ( + resourceLog["resource"] && + (resourceLog["resource"] as JSONObject)["attributes"] && + ( + (resourceLog["resource"] as JSONObject)[ + "attributes" + ] as JSONArray + ).length > 0 + ) { + attributesObject = { + ...attributesObject, + resource: OTelIngestService.getAttributes( + (resourceLog["resource"] as JSONObject)[ + "attributes" + ] as JSONArray, + ), + }; } - await LogService.createMany({ - items: dbLogs, - props: { - isRoot: true, - }, - }); + if ( + scopeLog["scope"] && + Object.keys(scopeLog["scope"]).length > 0 + ) { + attributesObject = { + ...attributesObject, + scope: (scopeLog["scope"] as JSONObject) || {}, + }; + } - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); + dbLog.attributes = { + ...attributesObject, + ...OTelIngestService.getAttributes( + log["attributes"] as JSONArray, + ), + }; + + dbLog.projectId = (req as TelemetryRequest).projectId; + dbLog.serviceId = (req as TelemetryRequest).serviceId; + + dbLog.timeUnixNano = log["timeUnixNano"] as number; + dbLog.time = OneUptimeDate.fromUnixNano( + log["timeUnixNano"] as number, + ); + + let logSeverityNumber: number = + (log["severityNumber"] as number) || 0; // 0 is Unspecified by default. + + if (typeof logSeverityNumber === "string") { + if (logSeverityNumber === "SEVERITY_NUMBER_TRACE") { + logSeverityNumber = 1; + } else if (logSeverityNumber === "SEVERITY_NUMBER_DEBUG") { + logSeverityNumber = 5; + } else if (logSeverityNumber === "SEVERITY_NUMBER_INFO") { + logSeverityNumber = 9; + } else if (logSeverityNumber === "SEVERITY_NUMBER_WARN") { + logSeverityNumber = 13; + } else if (logSeverityNumber === "SEVERITY_NUMBER_ERROR") { + logSeverityNumber = 17; + } else if (logSeverityNumber === "SEVERITY_NUMBER_FATAL") { + logSeverityNumber = 21; + } else { + logSeverityNumber = parseInt(logSeverityNumber); + } + } + + dbLog.severityNumber = logSeverityNumber; + + let logSeverity: LogSeverity = LogSeverity.Unspecified; + + // these numbers are from the opentelemetry/api-logs package + if (logSeverityNumber < 0 || logSeverityNumber > 24) { + logSeverity = LogSeverity.Unspecified; + logSeverityNumber = 0; + } else if (logSeverityNumber >= 1 && logSeverityNumber <= 4) { + logSeverity = LogSeverity.Trace; + } else if (logSeverityNumber >= 5 && logSeverityNumber <= 8) { + logSeverity = LogSeverity.Debug; + } else if (logSeverityNumber >= 9 && logSeverityNumber <= 12) { + logSeverity = LogSeverity.Information; + } else if (logSeverityNumber >= 13 && logSeverityNumber <= 16) { + logSeverity = LogSeverity.Warning; + } else if (logSeverityNumber >= 17 && logSeverityNumber <= 20) { + logSeverity = LogSeverity.Error; + } else if (logSeverityNumber >= 21 && logSeverityNumber <= 24) { + logSeverity = LogSeverity.Fatal; + } + + dbLog.severityText = logSeverity; + + const logBody: JSONObject = log["body"] as JSONObject; + + dbLog.body = logBody["stringValue"] as string; + + dbLog.traceId = Text.convertBase64ToHex(log["traceId"] as string); + dbLog.spanId = Text.convertBase64ToHex(log["spanId"] as string); + + dbLogs.push(dbLog); + } } + } + + await LogService.createMany({ + items: dbLogs, + props: { + isRoot: true, + }, + }); + + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/Ingestor/API/Probe.ts b/Ingestor/API/Probe.ts index c9a72c8218..6b235e28c9 100644 --- a/Ingestor/API/Probe.ts +++ b/Ingestor/API/Probe.ts @@ -1,275 +1,260 @@ -import ProbeAuthorization from '../Middleware/ProbeAuthorization'; -import Email from 'Common/Types/Email'; -import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import ProbeApiIngestResponse from 'Common/Types/Probe/ProbeApiIngestResponse'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import ProbeStatusReport from 'Common/Types/Probe/ProbeStatusReport'; -import { DisableAutomaticIncidentCreation } from 'CommonServer/EnvironmentConfig'; -import GlobalConfigService from 'CommonServer/Services/GlobalConfigService'; -import MailService from 'CommonServer/Services/MailService'; -import ProbeService from 'CommonServer/Services/ProbeService'; -import ProjectService from 'CommonServer/Services/ProjectService'; +import ProbeAuthorization from "../Middleware/ProbeAuthorization"; +import Email from "Common/Types/Email"; +import EmailTemplateType from "Common/Types/Email/EmailTemplateType"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import ProbeApiIngestResponse from "Common/Types/Probe/ProbeApiIngestResponse"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import ProbeStatusReport from "Common/Types/Probe/ProbeStatusReport"; +import { DisableAutomaticIncidentCreation } from "CommonServer/EnvironmentConfig"; +import GlobalConfigService from "CommonServer/Services/GlobalConfigService"; +import MailService from "CommonServer/Services/MailService"; +import ProbeService from "CommonServer/Services/ProbeService"; +import ProjectService from "CommonServer/Services/ProjectService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import ProbeMonitorResponseService from 'CommonServer/Utils/Probe/ProbeMonitorResponse'; -import Response from 'CommonServer/Utils/Response'; -import GlobalConfig from 'Model/Models/GlobalConfig'; -import Probe from 'Model/Models/Probe'; -import User from 'Model/Models/User'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import ProbeMonitorResponseService from "CommonServer/Utils/Probe/ProbeMonitorResponse"; +import Response from "CommonServer/Utils/Response"; +import GlobalConfig from "Model/Models/GlobalConfig"; +import Probe from "Model/Models/Probe"; +import User from "Model/Models/User"; const router: ExpressRouter = Express.getRouter(); router.post( - '/probe/status-report/offline', - ProbeAuthorization.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - const data: JSONObject = req.body; - const statusReport: ProbeStatusReport = JSONFunctions.deserialize( - (data as JSONObject)['statusReport'] as any - ) as any; + "/probe/status-report/offline", + ProbeAuthorization.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + const data: JSONObject = req.body; + const statusReport: ProbeStatusReport = JSONFunctions.deserialize( + (data as JSONObject)["statusReport"] as any, + ) as any; - if (!statusReport) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('StatusReport not found') - ); - } + if (!statusReport) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("StatusReport not found"), + ); + } - // process status report here. + // process status report here. - let isWebsiteCheckOffline: boolean = false; - let isPingCheckOffline: boolean = false; - let isPortCheckOffline: boolean = false; + let isWebsiteCheckOffline: boolean = false; + let isPingCheckOffline: boolean = false; + let isPortCheckOffline: boolean = false; - if (statusReport['isWebsiteCheckOffline']) { - isWebsiteCheckOffline = statusReport[ - 'isWebsiteCheckOffline' - ] as boolean; - } + if (statusReport["isWebsiteCheckOffline"]) { + isWebsiteCheckOffline = statusReport[ + "isWebsiteCheckOffline" + ] as boolean; + } - if (statusReport['isPingCheckOffline']) { - isPingCheckOffline = statusReport[ - 'isPingCheckOffline' - ] as boolean; - } + if (statusReport["isPingCheckOffline"]) { + isPingCheckOffline = statusReport["isPingCheckOffline"] as boolean; + } - if (statusReport['isPortCheckOffline']) { - isPortCheckOffline = statusReport[ - 'isPortCheckOffline' - ] as boolean; - } + if (statusReport["isPortCheckOffline"]) { + isPortCheckOffline = statusReport["isPortCheckOffline"] as boolean; + } - if ( - isWebsiteCheckOffline || - isPingCheckOffline || - isPortCheckOffline - ) { - // email probe owner. - const probeId: ObjectID = new ObjectID( - data['probeId'] as string - ); + if (isWebsiteCheckOffline || isPingCheckOffline || isPortCheckOffline) { + // email probe owner. + const probeId: ObjectID = new ObjectID(data["probeId"] as string); - const probe: Probe | null = await ProbeService.findOneBy({ - query: { - _id: probeId.toString(), - }, - select: { - _id: true, - projectId: true, - name: true, - description: true, - }, - props: { - isRoot: true, - }, - }); + const probe: Probe | null = await ProbeService.findOneBy({ + query: { + _id: probeId.toString(), + }, + select: { + _id: true, + projectId: true, + name: true, + description: true, + }, + props: { + isRoot: true, + }, + }); - if (!probe) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid Probe ID or Probe Key') - ); - } - - // If global probe offline? If yes, then email master-admin. - // If not a global probe then them email project owners. - - const isGlobalProbe: boolean = !probe.projectId; - const emailsToNotify: Email[] = []; - - let emailReason: string = ''; - - if (isGlobalProbe) { - // email master-admin - - const globalConfig: GlobalConfig | null = - await GlobalConfigService.findOneBy({ - query: {}, - select: { - _id: true, - adminNotificationEmail: true, - }, - props: { - isRoot: true, - }, - }); - - if (!globalConfig) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Global config not found') - ); - } - - const adminNotificationEmail: Email | undefined = - globalConfig.adminNotificationEmail; - - if (adminNotificationEmail) { - // email adminNotificationEmail - emailsToNotify.push(adminNotificationEmail); - - emailReason = - 'This email is sent to you becuse you have listed this email as a notification email in the Admin Dashobard. To change this email, please visit the Admin Dashboard > Settings > Email.'; - } - } else { - if (!probe.projectId) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid Project ID') - ); - } - - // email project owners. - const owners: Array<User> = await ProjectService.getOwners( - probe.projectId! - ); - - for (const owner of owners) { - if (owner.email) { - emailsToNotify.push(owner.email); - } - } - - emailReason = - 'This email is sent to you because you are listed as an owner of the project that this probe is associated with. To change this email, please visit the Project Dashboard > Settings > Teams and Members > Owners.'; - } - - let issue: string = ''; - - if (isWebsiteCheckOffline) { - issue += 'This probe cannot reach out to monitor websites.'; - } - - if (isPingCheckOffline) { - issue += - ' This probe cannot reach out to ping other servers / hostnames or IP addresses. '; - } - - if (!isWebsiteCheckOffline && isPingCheckOffline) { - issue += - 'Looks like ICMP is blocked. We will fallback to port monitoring (on default port 80) to monitor the uptime of resources.'; - } - - if (isPortCheckOffline) { - issue += ' This probe cannot reach out to monitor ports.'; - } - - // now send an email to all the emailsToNotify - for (const email of emailsToNotify) { - MailService.sendMail( - { - toEmail: email, - templateType: EmailTemplateType.ProbeOffline, - subject: - 'ACTION REQUIRED: Probe Offline Notification', - vars: { - probeName: probe.name || '', - probeDescription: probe.description || '', - projectId: probe.projectId?.toString() || '', - probeId: probe.id?.toString() || '', - hostname: - statusReport['hostname']?.toString() || '', - emailReason: emailReason, - issue: issue, - }, - }, - { - projectId: probe.projectId, - } - ).catch((err: Error) => { - logger.error(err); - }); - } - } - - return Response.sendJsonObjectResponse(req, res, { - message: 'Status Report received', - }); - } catch (err) { - return next(err); + if (!probe) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid Probe ID or Probe Key"), + ); } + + // If global probe offline? If yes, then email master-admin. + // If not a global probe then them email project owners. + + const isGlobalProbe: boolean = !probe.projectId; + const emailsToNotify: Email[] = []; + + let emailReason: string = ""; + + if (isGlobalProbe) { + // email master-admin + + const globalConfig: GlobalConfig | null = + await GlobalConfigService.findOneBy({ + query: {}, + select: { + _id: true, + adminNotificationEmail: true, + }, + props: { + isRoot: true, + }, + }); + + if (!globalConfig) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Global config not found"), + ); + } + + const adminNotificationEmail: Email | undefined = + globalConfig.adminNotificationEmail; + + if (adminNotificationEmail) { + // email adminNotificationEmail + emailsToNotify.push(adminNotificationEmail); + + emailReason = + "This email is sent to you becuse you have listed this email as a notification email in the Admin Dashobard. To change this email, please visit the Admin Dashboard > Settings > Email."; + } + } else { + if (!probe.projectId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid Project ID"), + ); + } + + // email project owners. + const owners: Array<User> = await ProjectService.getOwners( + probe.projectId!, + ); + + for (const owner of owners) { + if (owner.email) { + emailsToNotify.push(owner.email); + } + } + + emailReason = + "This email is sent to you because you are listed as an owner of the project that this probe is associated with. To change this email, please visit the Project Dashboard > Settings > Teams and Members > Owners."; + } + + let issue: string = ""; + + if (isWebsiteCheckOffline) { + issue += "This probe cannot reach out to monitor websites."; + } + + if (isPingCheckOffline) { + issue += + " This probe cannot reach out to ping other servers / hostnames or IP addresses. "; + } + + if (!isWebsiteCheckOffline && isPingCheckOffline) { + issue += + "Looks like ICMP is blocked. We will fallback to port monitoring (on default port 80) to monitor the uptime of resources."; + } + + if (isPortCheckOffline) { + issue += " This probe cannot reach out to monitor ports."; + } + + // now send an email to all the emailsToNotify + for (const email of emailsToNotify) { + MailService.sendMail( + { + toEmail: email, + templateType: EmailTemplateType.ProbeOffline, + subject: "ACTION REQUIRED: Probe Offline Notification", + vars: { + probeName: probe.name || "", + probeDescription: probe.description || "", + projectId: probe.projectId?.toString() || "", + probeId: probe.id?.toString() || "", + hostname: statusReport["hostname"]?.toString() || "", + emailReason: emailReason, + issue: issue, + }, + }, + { + projectId: probe.projectId, + }, + ).catch((err: Error) => { + logger.error(err); + }); + } + } + + return Response.sendJsonObjectResponse(req, res, { + message: "Status Report received", + }); + } catch (err) { + return next(err); } + }, ); router.post( - '/probe/response/ingest', - ProbeAuthorization.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - if (DisableAutomaticIncidentCreation) { - return Response.sendJsonObjectResponse(req, res, { - message: 'Automatic incident creation is disabled.', - }); - } + "/probe/response/ingest", + ProbeAuthorization.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + if (DisableAutomaticIncidentCreation) { + return Response.sendJsonObjectResponse(req, res, { + message: "Automatic incident creation is disabled.", + }); + } - const probeResponse: ProbeMonitorResponse = - JSONFunctions.deserialize( - req.body['probeMonitorResponse'] - ) as any; + const probeResponse: ProbeMonitorResponse = JSONFunctions.deserialize( + req.body["probeMonitorResponse"], + ) as any; - if (!probeResponse) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('ProbeMonitorResponse not found') - ); - } + if (!probeResponse) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("ProbeMonitorResponse not found"), + ); + } - // process probe response here. - const probeApiIngestResponse: ProbeApiIngestResponse = - await ProbeMonitorResponseService.processProbeResponse( - probeResponse - ); + // process probe response here. + const probeApiIngestResponse: ProbeApiIngestResponse = + await ProbeMonitorResponseService.processProbeResponse(probeResponse); - return Response.sendJsonObjectResponse(req, res, { - probeApiIngestResponse: probeApiIngestResponse, - } as any); - } catch (err) { - return next(err); - } + return Response.sendJsonObjectResponse(req, res, { + probeApiIngestResponse: probeApiIngestResponse, + } as any); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/Ingestor/API/Register.ts b/Ingestor/API/Register.ts index 2c5c949e54..eb1ff2b3b5 100644 --- a/Ingestor/API/Register.ts +++ b/Ingestor/API/Register.ts @@ -1,96 +1,96 @@ -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization'; -import ProbeService from 'CommonServer/Services/ProbeService'; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization"; +import ProbeService from "CommonServer/Services/ProbeService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import Response from 'CommonServer/Utils/Response'; -import Probe from 'Model/Models/Probe'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import Response from "CommonServer/Utils/Response"; +import Probe from "Model/Models/Probe"; const router: ExpressRouter = Express.getRouter(); // Register Global Probe. Custom Probe can be registered via dashboard. router.post( - '/register', - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - const data: JSONObject = req.body; + "/register", + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + const data: JSONObject = req.body; - if (!data['probeKey']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('ProbeId or ProbeKey is missing') - ); - } + if (!data["probeKey"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("ProbeId or ProbeKey is missing"), + ); + } - const probeKey: string = data['probeKey'] as string; + const probeKey: string = data["probeKey"] as string; - const probe: Probe | null = await ProbeService.findOneBy({ - query: { - key: probeKey, - isGlobalProbe: true, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); + const probe: Probe | null = await ProbeService.findOneBy({ + query: { + key: probeKey, + isGlobalProbe: true, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (probe) { - await ProbeService.updateOneById({ - id: probe.id!, - data: { - name: data['probeName'] as string, - description: data['probeDescription'] as string, - lastAlive: OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); + if (probe) { + await ProbeService.updateOneById({ + id: probe.id!, + data: { + name: data["probeName"] as string, + description: data["probeDescription"] as string, + lastAlive: OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); - return Response.sendJsonObjectResponse(req, res, { - _id: probe._id?.toString(), - message: 'Probe already registered', - }); - } + return Response.sendJsonObjectResponse(req, res, { + _id: probe._id?.toString(), + message: "Probe already registered", + }); + } - let newProbe: Probe = new Probe(); - newProbe.isGlobalProbe = true; - newProbe.key = probeKey; - newProbe.name = data['probeName'] as string; - newProbe.description = data['probeDescription'] as string; - newProbe.lastAlive = OneUptimeDate.getCurrentDate(); - newProbe.shouldAutoEnableProbeOnNewMonitors = true; + let newProbe: Probe = new Probe(); + newProbe.isGlobalProbe = true; + newProbe.key = probeKey; + newProbe.name = data["probeName"] as string; + newProbe.description = data["probeDescription"] as string; + newProbe.lastAlive = OneUptimeDate.getCurrentDate(); + newProbe.shouldAutoEnableProbeOnNewMonitors = true; - newProbe = await ProbeService.create({ - data: newProbe, - props: { - isRoot: true, - }, - }); + newProbe = await ProbeService.create({ + data: newProbe, + props: { + isRoot: true, + }, + }); - return Response.sendJsonObjectResponse(req, res, { - _id: newProbe._id?.toString(), - message: 'Probe registered successfully', - }); - } catch (err) { - return next(err); - } + return Response.sendJsonObjectResponse(req, res, { + _id: newProbe._id?.toString(), + message: "Probe registered successfully", + }); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/Ingestor/API/ServerMonitor.ts b/Ingestor/API/ServerMonitor.ts index 30afbe8155..71b0abca3a 100644 --- a/Ingestor/API/ServerMonitor.ts +++ b/Ingestor/API/ServerMonitor.ts @@ -1,129 +1,125 @@ -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ServerMonitorResponse from 'Common/Types/Monitor/ServerMonitor/ServerMonitorResponse'; -import ObjectID from 'Common/Types/ObjectID'; -import ProbeApiIngestResponse from 'Common/Types/Probe/ProbeApiIngestResponse'; -import MonitorService from 'CommonServer/Services/MonitorService'; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ServerMonitorResponse from "Common/Types/Monitor/ServerMonitor/ServerMonitorResponse"; +import ObjectID from "Common/Types/ObjectID"; +import ProbeApiIngestResponse from "Common/Types/Probe/ProbeApiIngestResponse"; +import MonitorService from "CommonServer/Services/MonitorService"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import ProbeMonitorResponseService from 'CommonServer/Utils/Probe/ProbeMonitorResponse'; -import Response from 'CommonServer/Utils/Response'; -import Monitor from 'Model/Models/Monitor'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import ProbeMonitorResponseService from "CommonServer/Utils/Probe/ProbeMonitorResponse"; +import Response from "CommonServer/Utils/Response"; +import Monitor from "Model/Models/Monitor"; const router: ExpressRouter = Express.getRouter(); // an api to see if secret key is valid router.get( - '/server-monitor/secret-key/verify/:secretkey', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - const monitorSecretKeyAsString: string | undefined = - req.params['secretkey']; + "/server-monitor/secret-key/verify/:secretkey", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + const monitorSecretKeyAsString: string | undefined = + req.params["secretkey"]; - if (!monitorSecretKeyAsString) { - throw new BadDataException('Invalid Secret Key'); - } + if (!monitorSecretKeyAsString) { + throw new BadDataException("Invalid Secret Key"); + } - const monitor: Monitor | null = await MonitorService.findOneBy({ - query: { - serverMonitorSecretKey: new ObjectID( - monitorSecretKeyAsString - ), - monitorType: MonitorType.Server, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); + const monitor: Monitor | null = await MonitorService.findOneBy({ + query: { + serverMonitorSecretKey: new ObjectID(monitorSecretKeyAsString), + monitorType: MonitorType.Server, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (!monitor) { - throw new BadDataException('Monitor not found'); - } + if (!monitor) { + throw new BadDataException("Monitor not found"); + } - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); - } + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); router.post( - '/server-monitor/response/ingest/:secretkey', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - const monitorSecretKeyAsString: string | undefined = - req.params['secretkey']; + "/server-monitor/response/ingest/:secretkey", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + const monitorSecretKeyAsString: string | undefined = + req.params["secretkey"]; - if (!monitorSecretKeyAsString) { - throw new BadDataException('Invalid Secret Key'); - } + if (!monitorSecretKeyAsString) { + throw new BadDataException("Invalid Secret Key"); + } - const monitor: Monitor | null = await MonitorService.findOneBy({ - query: { - serverMonitorSecretKey: new ObjectID( - monitorSecretKeyAsString - ), - monitorType: MonitorType.Server, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); + const monitor: Monitor | null = await MonitorService.findOneBy({ + query: { + serverMonitorSecretKey: new ObjectID(monitorSecretKeyAsString), + monitorType: MonitorType.Server, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - if (!monitor) { - throw new BadDataException('Monitor not found'); - } + if (!monitor) { + throw new BadDataException("Monitor not found"); + } - // now process this request. + // now process this request. - const serverMonitorResponse: ServerMonitorResponse = - JSONFunctions.deserialize( - req.body['serverMonitorResponse'] as JSONObject - ) as any; + const serverMonitorResponse: ServerMonitorResponse = + JSONFunctions.deserialize( + req.body["serverMonitorResponse"] as JSONObject, + ) as any; - if (!serverMonitorResponse) { - throw new BadDataException('Invalid Server Monitor Response'); - } + if (!serverMonitorResponse) { + throw new BadDataException("Invalid Server Monitor Response"); + } - if (!monitor.id) { - throw new BadDataException('Monitor id not found'); - } + if (!monitor.id) { + throw new BadDataException("Monitor id not found"); + } - serverMonitorResponse.monitorId = monitor.id; + serverMonitorResponse.monitorId = monitor.id; - // process probe response here. - const probeApiIngestResponse: ProbeApiIngestResponse = - await ProbeMonitorResponseService.processProbeResponse( - serverMonitorResponse - ); + // process probe response here. + const probeApiIngestResponse: ProbeApiIngestResponse = + await ProbeMonitorResponseService.processProbeResponse( + serverMonitorResponse, + ); - return Response.sendJsonObjectResponse(req, res, { - probeApiIngestResponse: probeApiIngestResponse, - } as any); - } catch (err) { - return next(err); - } + return Response.sendJsonObjectResponse(req, res, { + probeApiIngestResponse: probeApiIngestResponse, + } as any); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/Ingestor/Index.ts b/Ingestor/Index.ts index fbd493d38f..133f2e1863 100644 --- a/Ingestor/Index.ts +++ b/Ingestor/Index.ts @@ -1,79 +1,79 @@ -import AliveAPI from './API/Alive'; -import FluentIngestAPI from './API/FluentIngest'; -import IncomingRequestAPI from './API/IncomingRequest'; -import MonitorAPI from './API/Monitor'; -import OTelIngestAPI from './API/OTelIngest'; -import Ingestor from './API/Probe'; -import RegisterAPI from './API/Register'; -import ServerMonitorAPI from './API/ServerMonitor'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { ClickhouseAppInstance } from 'CommonServer/Infrastructure/ClickhouseDatabase'; -import { PostgresAppInstance } from 'CommonServer/Infrastructure/PostgresDatabase'; -import Redis from 'CommonServer/Infrastructure/Redis'; -import InfrastructureStatus from 'CommonServer/Infrastructure/Status'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import Realtime from 'CommonServer/Utils/Realtime'; -import App from 'CommonServer/Utils/StartServer'; -import 'ejs'; +import AliveAPI from "./API/Alive"; +import FluentIngestAPI from "./API/FluentIngest"; +import IncomingRequestAPI from "./API/IncomingRequest"; +import MonitorAPI from "./API/Monitor"; +import OTelIngestAPI from "./API/OTelIngest"; +import Ingestor from "./API/Probe"; +import RegisterAPI from "./API/Register"; +import ServerMonitorAPI from "./API/ServerMonitor"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { ClickhouseAppInstance } from "CommonServer/Infrastructure/ClickhouseDatabase"; +import { PostgresAppInstance } from "CommonServer/Infrastructure/PostgresDatabase"; +import Redis from "CommonServer/Infrastructure/Redis"; +import InfrastructureStatus from "CommonServer/Infrastructure/Status"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import Realtime from "CommonServer/Utils/Realtime"; +import App from "CommonServer/Utils/StartServer"; +import "ejs"; const app: ExpressApplication = Express.getExpressApp(); -const APP_NAME: string = 'ingestor'; +const APP_NAME: string = "ingestor"; -app.use([`/${APP_NAME}`, '/'], AliveAPI); -app.use([`/${APP_NAME}`, '/'], RegisterAPI); -app.use([`/${APP_NAME}`, '/'], MonitorAPI); -app.use([`/${APP_NAME}`, '/'], Ingestor); -app.use([`/${APP_NAME}`, '/'], IncomingRequestAPI); -app.use([`/${APP_NAME}`, '/'], OTelIngestAPI); -app.use([`/${APP_NAME}`, '/'], FluentIngestAPI); -app.use([`/${APP_NAME}`, '/'], ServerMonitorAPI); +app.use([`/${APP_NAME}`, "/"], AliveAPI); +app.use([`/${APP_NAME}`, "/"], RegisterAPI); +app.use([`/${APP_NAME}`, "/"], MonitorAPI); +app.use([`/${APP_NAME}`, "/"], Ingestor); +app.use([`/${APP_NAME}`, "/"], IncomingRequestAPI); +app.use([`/${APP_NAME}`, "/"], OTelIngestAPI); +app.use([`/${APP_NAME}`, "/"], FluentIngestAPI); +app.use([`/${APP_NAME}`, "/"], ServerMonitorAPI); const init: PromiseVoidFunction = async (): Promise<void> => { - try { - const statusCheck: PromiseVoidFunction = async (): Promise<void> => { - return await InfrastructureStatus.checkStatus({ - checkClickhouseStatus: true, - checkPostgresStatus: true, - checkRedisStatus: true, - }); - }; + try { + const statusCheck: PromiseVoidFunction = async (): Promise<void> => { + return await InfrastructureStatus.checkStatus({ + checkClickhouseStatus: true, + checkPostgresStatus: true, + checkRedisStatus: true, + }); + }; - // init the app - await App.init({ - appName: APP_NAME, - statusOptions: { - liveCheck: statusCheck, - readyCheck: statusCheck, - }, - }); + // init the app + await App.init({ + appName: APP_NAME, + statusOptions: { + liveCheck: statusCheck, + readyCheck: statusCheck, + }, + }); - // connect to the database. - await PostgresAppInstance.connect( - PostgresAppInstance.getDatasourceOptions() - ); + // connect to the database. + await PostgresAppInstance.connect( + PostgresAppInstance.getDatasourceOptions(), + ); - // connect redis - await Redis.connect(); + // connect redis + await Redis.connect(); - await ClickhouseAppInstance.connect( - ClickhouseAppInstance.getDatasourceOptions() - ); + await ClickhouseAppInstance.connect( + ClickhouseAppInstance.getDatasourceOptions(), + ); - await Realtime.init(); + await Realtime.init(); - // add default routes - await App.addDefaultRoutes(); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); diff --git a/Ingestor/Middleware/ProbeAuthorization.ts b/Ingestor/Middleware/ProbeAuthorization.ts index 66f50273d9..58d9e2140b 100644 --- a/Ingestor/Middleware/ProbeAuthorization.ts +++ b/Ingestor/Middleware/ProbeAuthorization.ts @@ -1,74 +1,74 @@ -import { ProbeExpressRequest } from '../Types/Request'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import { ClusterKey as ONEUPTIME_SECRET } from 'CommonServer/EnvironmentConfig'; -import ProbeService from 'CommonServer/Services/ProbeService'; -import { ExpressResponse, NextFunction } from 'CommonServer/Utils/Express'; -import Response from 'CommonServer/Utils/Response'; -import Probe from 'Model/Models/Probe'; +import { ProbeExpressRequest } from "../Types/Request"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import { ClusterKey as ONEUPTIME_SECRET } from "CommonServer/EnvironmentConfig"; +import ProbeService from "CommonServer/Services/ProbeService"; +import { ExpressResponse, NextFunction } from "CommonServer/Utils/Express"; +import Response from "CommonServer/Utils/Response"; +import Probe from "Model/Models/Probe"; export default class ProbeAuthorization { - public static getClusterKeyHeaders(): Dictionary<string> { - return { - clusterkey: ONEUPTIME_SECRET.toString(), - }; + public static getClusterKeyHeaders(): Dictionary<string> { + return { + clusterkey: ONEUPTIME_SECRET.toString(), + }; + } + + public static async isAuthorizedServiceMiddleware( + req: ProbeExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> { + const data: JSONObject = req.body; + + if (!data["probeId"] || !data["probeKey"]) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("ProbeId or ProbeKey is missing"), + ); } - public static async isAuthorizedServiceMiddleware( - req: ProbeExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> { - const data: JSONObject = req.body; + const probeId: ObjectID = new ObjectID(data["probeId"] as string); - if (!data['probeId'] || !data['probeKey']) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('ProbeId or ProbeKey is missing') - ); - } + const probeKey: string = data["probeKey"] as string; - const probeId: ObjectID = new ObjectID(data['probeId'] as string); + const probe: Probe | null = await ProbeService.findOneBy({ + query: { + _id: probeId.toString(), + key: probeKey, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); - const probeKey: string = data['probeKey'] as string; - - const probe: Probe | null = await ProbeService.findOneBy({ - query: { - _id: probeId.toString(), - key: probeKey, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - }, - }); - - if (!probe) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Invalid Probe ID or Probe Key') - ); - } - - await ProbeService.updateOneById({ - id: probeId, - data: { - lastAlive: OneUptimeDate.getCurrentDate(), - }, - props: { - isRoot: true, - }, - }); - - req.probe = probe; - - return next(); + if (!probe) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Invalid Probe ID or Probe Key"), + ); } + + await ProbeService.updateOneById({ + id: probeId, + data: { + lastAlive: OneUptimeDate.getCurrentDate(), + }, + props: { + isRoot: true, + }, + }); + + req.probe = probe; + + return next(); + } } diff --git a/Ingestor/Middleware/TelemetryIngest.ts b/Ingestor/Middleware/TelemetryIngest.ts index 94321b32d6..3747db2a6d 100644 --- a/Ingestor/Middleware/TelemetryIngest.ts +++ b/Ingestor/Middleware/TelemetryIngest.ts @@ -1,97 +1,93 @@ -import { ProbeExpressRequest } from '../Types/Request'; -import DiskSize from 'Common/Types/DiskSize'; -import BadRequestException from 'Common/Types/Exception/BadRequestException'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import ObjectID from 'Common/Types/ObjectID'; -import TelemetryServiceService from 'CommonServer/Services/TelemetryServiceService'; -import TelemetryUsageBillingService from 'CommonServer/Services/TelemetryUsageBillingService'; +import { ProbeExpressRequest } from "../Types/Request"; +import DiskSize from "Common/Types/DiskSize"; +import BadRequestException from "Common/Types/Exception/BadRequestException"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import ObjectID from "Common/Types/ObjectID"; +import TelemetryServiceService from "CommonServer/Services/TelemetryServiceService"; +import TelemetryUsageBillingService from "CommonServer/Services/TelemetryUsageBillingService"; import { - ExpressRequest, - ExpressResponse, - NextFunction, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import TelemetryService from 'Model/Models/TelemetryService'; -import { DEFAULT_RETENTION_IN_DAYS } from 'Model/Models/TelemetryUsageBilling'; + ExpressRequest, + ExpressResponse, + NextFunction, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import TelemetryService from "Model/Models/TelemetryService"; +import { DEFAULT_RETENTION_IN_DAYS } from "Model/Models/TelemetryUsageBilling"; export interface TelemetryRequest extends ExpressRequest { - serviceId: ObjectID; // Service ID - projectId: ObjectID; // Project ID - dataRententionInDays: number; // how long the data should be retained. - productType: ProductType; // what is the product type of the request - logs, metrics or traces. + serviceId: ObjectID; // Service ID + projectId: ObjectID; // Project ID + dataRententionInDays: number; // how long the data should be retained. + productType: ProductType; // what is the product type of the request - logs, metrics or traces. } export default class TelemetryIngest { - public static async isAuthorizedServiceMiddleware( - req: ProbeExpressRequest, - _res: ExpressResponse, - next: NextFunction - ): Promise<void> { - try { - // check header. + public static async isAuthorizedServiceMiddleware( + req: ProbeExpressRequest, + _res: ExpressResponse, + next: NextFunction, + ): Promise<void> { + try { + // check header. - const serviceTokenInHeader: string | undefined = req.headers[ - 'x-oneuptime-service-token' - ] as string | undefined; + const serviceTokenInHeader: string | undefined = req.headers[ + "x-oneuptime-service-token" + ] as string | undefined; - if (!serviceTokenInHeader) { - throw new BadRequestException( - 'Missing header: x-oneuptime-service-token' - ); - } + if (!serviceTokenInHeader) { + throw new BadRequestException( + "Missing header: x-oneuptime-service-token", + ); + } - // size of req.body in bytes. - const sizeInBytes: number = Buffer.byteLength( - JSON.stringify(req.body) - ); + // size of req.body in bytes. + const sizeInBytes: number = Buffer.byteLength(JSON.stringify(req.body)); - const sizeToGb: number = DiskSize.byteSizeToGB(sizeInBytes); + const sizeToGb: number = DiskSize.byteSizeToGB(sizeInBytes); - // load from the database and set the cache. - const service: TelemetryService | null = - await TelemetryServiceService.findOneBy({ - query: { - telemetryServiceToken: new ObjectID( - serviceTokenInHeader as string - ), - }, - select: { - _id: true, - projectId: true, - retainTelemetryDataForDays: true, - }, - props: { - isRoot: true, - }, - }); + // load from the database and set the cache. + const service: TelemetryService | null = + await TelemetryServiceService.findOneBy({ + query: { + telemetryServiceToken: new ObjectID(serviceTokenInHeader as string), + }, + select: { + _id: true, + projectId: true, + retainTelemetryDataForDays: true, + }, + props: { + isRoot: true, + }, + }); - if (!service) { - throw new BadRequestException('Invalid service token'); - } + if (!service) { + throw new BadRequestException("Invalid service token"); + } - (req as TelemetryRequest).serviceId = service.id as ObjectID; - (req as TelemetryRequest).projectId = service.projectId as ObjectID; - (req as TelemetryRequest).dataRententionInDays = - service.retainTelemetryDataForDays || DEFAULT_RETENTION_IN_DAYS; + (req as TelemetryRequest).serviceId = service.id as ObjectID; + (req as TelemetryRequest).projectId = service.projectId as ObjectID; + (req as TelemetryRequest).dataRententionInDays = + service.retainTelemetryDataForDays || DEFAULT_RETENTION_IN_DAYS; - (req as TelemetryRequest).serviceId = service.id as ObjectID; - (req as TelemetryRequest).projectId = service.projectId as ObjectID; + (req as TelemetryRequest).serviceId = service.id as ObjectID; + (req as TelemetryRequest).projectId = service.projectId as ObjectID; - // report to Usage Service. - TelemetryUsageBillingService.updateUsageBilling({ - projectId: (req as TelemetryRequest).projectId, - productType: (req as TelemetryRequest).productType, - dataIngestedInGB: sizeToGb, - telemetryServiceId: (req as TelemetryRequest).serviceId, - retentionInDays: (req as TelemetryRequest).dataRententionInDays, - }).catch((err: Error) => { - logger.error('Failed to update usage billing for OTel'); - logger.error(err); - }); + // report to Usage Service. + TelemetryUsageBillingService.updateUsageBilling({ + projectId: (req as TelemetryRequest).projectId, + productType: (req as TelemetryRequest).productType, + dataIngestedInGB: sizeToGb, + telemetryServiceId: (req as TelemetryRequest).serviceId, + retentionInDays: (req as TelemetryRequest).dataRententionInDays, + }).catch((err: Error) => { + logger.error("Failed to update usage billing for OTel"); + logger.error(err); + }); - next(); - } catch (err) { - return next(err); - } + next(); + } catch (err) { + return next(err); } + } } diff --git a/Ingestor/Service/OTelIngest.ts b/Ingestor/Service/OTelIngest.ts index 06c0643205..643c2a4c6e 100644 --- a/Ingestor/Service/OTelIngest.ts +++ b/Ingestor/Service/OTelIngest.ts @@ -1,120 +1,111 @@ -import OneUptimeDate from 'Common/Types/Date'; -import { JSONArray, JSONObject, JSONValue } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import Metric, { AggregationTemporality } from 'Model/AnalyticsModels/Metric'; +import OneUptimeDate from "Common/Types/Date"; +import { JSONArray, JSONObject, JSONValue } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import Metric, { AggregationTemporality } from "Model/AnalyticsModels/Metric"; export enum OtelAggregationTemporality { - Cumulative = 'AGGREGATION_TEMPORALITY_CUMULATIVE', - Delta = 'AGGREGATION_TEMPORALITY_DELTA', + Cumulative = "AGGREGATION_TEMPORALITY_CUMULATIVE", + Delta = "AGGREGATION_TEMPORALITY_DELTA", } export default class OTelIngestService { - public static getAttributes(items: JSONArray): JSONObject { - const finalObj: JSONObject = {}; - // We need to convert this to date. - const attributes: JSONArray = items; + public static getAttributes(items: JSONArray): JSONObject { + const finalObj: JSONObject = {}; + // We need to convert this to date. + const attributes: JSONArray = items; - if (attributes) { - for (const attribute of attributes) { - if (attribute['key'] && typeof attribute['key'] === 'string') { - let value: JSONValue = attribute['value'] as JSONObject; + if (attributes) { + for (const attribute of attributes) { + if (attribute["key"] && typeof attribute["key"] === "string") { + let value: JSONValue = attribute["value"] as JSONObject; - if (value['stringValue']) { - value = value['stringValue'] as string; - } else if (value['intValue']) { - value = value['intValue'] as number; - } + if (value["stringValue"]) { + value = value["stringValue"] as string; + } else if (value["intValue"]) { + value = value["intValue"] as number; + } - finalObj[attribute['key']] = value; - } - } + finalObj[attribute["key"]] = value; } - - return JSONFunctions.flattenObject(finalObj); + } } - public static getMetricFromDatapoint(data: { - dbMetric: Metric; - datapoint: JSONObject; - aggregationTemporality: OtelAggregationTemporality; - isMonotonic: boolean | undefined; - }): Metric { - const { dbMetric, datapoint, aggregationTemporality, isMonotonic } = - data; + return JSONFunctions.flattenObject(finalObj); + } - const newDbMetric: Metric = Metric.fromJSON( - dbMetric.toJSON(), - Metric - ) as Metric; + public static getMetricFromDatapoint(data: { + dbMetric: Metric; + datapoint: JSONObject; + aggregationTemporality: OtelAggregationTemporality; + isMonotonic: boolean | undefined; + }): Metric { + const { dbMetric, datapoint, aggregationTemporality, isMonotonic } = data; - newDbMetric.startTimeUnixNano = datapoint[ - 'startTimeUnixNano' - ] as number; - newDbMetric.startTime = OneUptimeDate.fromUnixNano( - datapoint['startTimeUnixNano'] as number - ); + const newDbMetric: Metric = Metric.fromJSON( + dbMetric.toJSON(), + Metric, + ) as Metric; - newDbMetric.timeUnixNano = datapoint['timeUnixNano'] as number; - newDbMetric.time = OneUptimeDate.fromUnixNano( - datapoint['timeUnixNano'] as number - ); + newDbMetric.startTimeUnixNano = datapoint["startTimeUnixNano"] as number; + newDbMetric.startTime = OneUptimeDate.fromUnixNano( + datapoint["startTimeUnixNano"] as number, + ); - if (Object.keys(datapoint).includes('asInt')) { - newDbMetric.value = datapoint['asInt'] as number; - } else if (Object.keys(datapoint).includes('asDouble')) { - newDbMetric.value = datapoint['asDouble'] as number; - } + newDbMetric.timeUnixNano = datapoint["timeUnixNano"] as number; + newDbMetric.time = OneUptimeDate.fromUnixNano( + datapoint["timeUnixNano"] as number, + ); - newDbMetric.count = datapoint['count'] as number; - newDbMetric.sum = datapoint['sum'] as number; - - newDbMetric.min = datapoint['min'] as number; - newDbMetric.max = datapoint['max'] as number; - - newDbMetric.bucketCounts = datapoint['bucketCounts'] as Array<number>; - newDbMetric.explicitBounds = datapoint[ - 'explicitBounds' - ] as Array<number>; - - // attrbutes - - if (Object.keys(datapoint).includes('attributes')) { - if (!newDbMetric.attributes) { - newDbMetric.attributes = {}; - } - - newDbMetric.attributes = { - ...(newDbMetric.attributes || {}), - ...this.getAttributes(datapoint['attributes'] as JSONArray), - }; - } - - if (newDbMetric.attributes) { - newDbMetric.attributes = JSONFunctions.flattenObject( - newDbMetric.attributes - ); - } - - // aggregationTemporality - - if (aggregationTemporality) { - if ( - aggregationTemporality === OtelAggregationTemporality.Cumulative - ) { - newDbMetric.aggregationTemporality = - AggregationTemporality.Cumulative; - } - - if (aggregationTemporality === OtelAggregationTemporality.Delta) { - newDbMetric.aggregationTemporality = - AggregationTemporality.Delta; - } - } - - if (isMonotonic !== undefined) { - newDbMetric.isMonotonic = isMonotonic; - } - - return newDbMetric; + if (Object.keys(datapoint).includes("asInt")) { + newDbMetric.value = datapoint["asInt"] as number; + } else if (Object.keys(datapoint).includes("asDouble")) { + newDbMetric.value = datapoint["asDouble"] as number; } + + newDbMetric.count = datapoint["count"] as number; + newDbMetric.sum = datapoint["sum"] as number; + + newDbMetric.min = datapoint["min"] as number; + newDbMetric.max = datapoint["max"] as number; + + newDbMetric.bucketCounts = datapoint["bucketCounts"] as Array<number>; + newDbMetric.explicitBounds = datapoint["explicitBounds"] as Array<number>; + + // attrbutes + + if (Object.keys(datapoint).includes("attributes")) { + if (!newDbMetric.attributes) { + newDbMetric.attributes = {}; + } + + newDbMetric.attributes = { + ...(newDbMetric.attributes || {}), + ...this.getAttributes(datapoint["attributes"] as JSONArray), + }; + } + + if (newDbMetric.attributes) { + newDbMetric.attributes = JSONFunctions.flattenObject( + newDbMetric.attributes, + ); + } + + // aggregationTemporality + + if (aggregationTemporality) { + if (aggregationTemporality === OtelAggregationTemporality.Cumulative) { + newDbMetric.aggregationTemporality = AggregationTemporality.Cumulative; + } + + if (aggregationTemporality === OtelAggregationTemporality.Delta) { + newDbMetric.aggregationTemporality = AggregationTemporality.Delta; + } + } + + if (isMonotonic !== undefined) { + newDbMetric.isMonotonic = isMonotonic; + } + + return newDbMetric; + } } diff --git a/Ingestor/Types/Request.ts b/Ingestor/Types/Request.ts index 00f6bd6abd..a565b8c240 100644 --- a/Ingestor/Types/Request.ts +++ b/Ingestor/Types/Request.ts @@ -1,6 +1,6 @@ -import { ExpressRequest } from 'CommonServer/Utils/Express'; -import Probe from 'Model/Models/Probe'; +import { ExpressRequest } from "CommonServer/Utils/Express"; +import Probe from "Model/Models/Probe"; export interface ProbeExpressRequest extends ExpressRequest { - probe?: Probe | undefined; + probe?: Probe | undefined; } diff --git a/Ingestor/Utils/Monitor.ts b/Ingestor/Utils/Monitor.ts index de9d1a3e95..fb25354125 100644 --- a/Ingestor/Utils/Monitor.ts +++ b/Ingestor/Utils/Monitor.ts @@ -1,187 +1,179 @@ -import Hostname from 'Common/Types/API/Hostname'; -import URL from 'Common/Types/API/URL'; -import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; -import Dictionary from 'Common/Types/Dictionary'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IP from 'Common/Types/IP/IP'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import MonitorSecretService from 'CommonServer/Services/MonitorSecretService'; -import VMUtil from 'CommonServer/Utils/VM/VMAPI'; -import Monitor from 'Model/Models/Monitor'; -import MonitorSecret from 'Model/Models/MonitorSecret'; +import Hostname from "Common/Types/API/Hostname"; +import URL from "Common/Types/API/URL"; +import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax"; +import Dictionary from "Common/Types/Dictionary"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IP from "Common/Types/IP/IP"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import MonitorSecretService from "CommonServer/Services/MonitorSecretService"; +import VMUtil from "CommonServer/Utils/VM/VMAPI"; +import Monitor from "Model/Models/Monitor"; +import MonitorSecret from "Model/Models/MonitorSecret"; export default class MonitorUtil { - public static async populateSecrets(monitor: Monitor): Promise<Monitor> { - const isSecretsLoaded: boolean = false; - let monitorSecrets: MonitorSecret[] = []; + public static async populateSecrets(monitor: Monitor): Promise<Monitor> { + const isSecretsLoaded: boolean = false; + let monitorSecrets: MonitorSecret[] = []; - const loadSecrets: PromiseVoidFunction = async (): Promise<void> => { - if (isSecretsLoaded) { - return; - } + const loadSecrets: PromiseVoidFunction = async (): Promise<void> => { + if (isSecretsLoaded) { + return; + } - if (!monitor.id) { - return; - } + if (!monitor.id) { + return; + } - const secrets: Array<MonitorSecret> = - await MonitorSecretService.findBy({ - query: { - monitors: [monitor.id] as any, - }, - select: { - secretValue: true, - name: true, - }, - limit: LIMIT_PER_PROJECT, - skip: 0, - props: { - isRoot: true, - }, - }); + const secrets: Array<MonitorSecret> = await MonitorSecretService.findBy({ + query: { + monitors: [monitor.id] as any, + }, + select: { + secretValue: true, + name: true, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + props: { + isRoot: true, + }, + }); - monitorSecrets = secrets; - }; + monitorSecrets = secrets; + }; - if (!monitor.monitorSteps) { - return monitor; - } - - if (monitor.monitorType === MonitorType.API) { - for (const monitorStep of monitor.monitorSteps?.data - ?.monitorStepsInstanceArray || []) { - if ( - monitorStep.data?.requestHeaders && - this.hasSecrets( - JSONFunctions.toString(monitorStep.data.requestHeaders) - ) - ) { - await loadSecrets(); - - monitorStep.data.requestHeaders = - (await MonitorUtil.fillSecretsInStringOrJSON({ - secrets: monitorSecrets, - populateSecretsIn: monitorStep.data.requestHeaders, - })) as Dictionary<string>; - } else if ( - monitorStep.data?.requestBody && - this.hasSecrets( - JSONFunctions.toString(monitorStep.data.requestBody) - ) - ) { - await loadSecrets(); - - monitorStep.data.requestBody = - (await MonitorUtil.fillSecretsInStringOrJSON({ - secrets: monitorSecrets, - populateSecretsIn: monitorStep.data.requestBody, - })) as string; - } - } - } + if (!monitor.monitorSteps) { + return monitor; + } + if (monitor.monitorType === MonitorType.API) { + for (const monitorStep of monitor.monitorSteps?.data + ?.monitorStepsInstanceArray || []) { if ( - monitor.monitorType === MonitorType.API || - monitor.monitorType === MonitorType.IP || - monitor.monitorType === MonitorType.Ping || - monitor.monitorType === MonitorType.Port || - monitor.monitorType === MonitorType.Website || - monitor.monitorType === MonitorType.SSLCertificate + monitorStep.data?.requestHeaders && + this.hasSecrets( + JSONFunctions.toString(monitorStep.data.requestHeaders), + ) ) { - for (const monitorStep of monitor.monitorSteps?.data - ?.monitorStepsInstanceArray || []) { - if ( - monitorStep.data?.monitorDestination && - this.hasSecrets( - JSONFunctions.toString( - monitorStep.data.monitorDestination - ) - ) - ) { - // replace secret in monitorDestination. - await loadSecrets(); + await loadSecrets(); - monitorStep.data.monitorDestination = - (await MonitorUtil.fillSecretsInStringOrJSON({ - secrets: monitorSecrets, - populateSecretsIn: - monitorStep.data.monitorDestination, - })) as URL | Hostname | IP; - } - } + monitorStep.data.requestHeaders = + (await MonitorUtil.fillSecretsInStringOrJSON({ + secrets: monitorSecrets, + populateSecretsIn: monitorStep.data.requestHeaders, + })) as Dictionary<string>; + } else if ( + monitorStep.data?.requestBody && + this.hasSecrets(JSONFunctions.toString(monitorStep.data.requestBody)) + ) { + await loadSecrets(); + + monitorStep.data.requestBody = + (await MonitorUtil.fillSecretsInStringOrJSON({ + secrets: monitorSecrets, + populateSecretsIn: monitorStep.data.requestBody, + })) as string; } + } + } + if ( + monitor.monitorType === MonitorType.API || + monitor.monitorType === MonitorType.IP || + monitor.monitorType === MonitorType.Ping || + monitor.monitorType === MonitorType.Port || + monitor.monitorType === MonitorType.Website || + monitor.monitorType === MonitorType.SSLCertificate + ) { + for (const monitorStep of monitor.monitorSteps?.data + ?.monitorStepsInstanceArray || []) { if ( - monitor.monitorType === MonitorType.SyntheticMonitor || - monitor.monitorType === MonitorType.CustomJavaScriptCode + monitorStep.data?.monitorDestination && + this.hasSecrets( + JSONFunctions.toString(monitorStep.data.monitorDestination), + ) ) { - for (const monitorStep of monitor.monitorSteps?.data - ?.monitorStepsInstanceArray || []) { - if ( - monitorStep.data?.customCode && - this.hasSecrets( - JSONFunctions.toString(monitorStep.data.customCode) - ) - ) { - // replace secret in script - await loadSecrets(); + // replace secret in monitorDestination. + await loadSecrets(); - monitorStep.data.customCode = - (await MonitorUtil.fillSecretsInStringOrJSON({ - secrets: monitorSecrets, - populateSecretsIn: monitorStep.data.customCode, - })) as string; - } - } + monitorStep.data.monitorDestination = + (await MonitorUtil.fillSecretsInStringOrJSON({ + secrets: monitorSecrets, + populateSecretsIn: monitorStep.data.monitorDestination, + })) as URL | Hostname | IP; } - - return monitor; + } } - private static hasSecrets(prepopulatedString: string): boolean { - return prepopulatedString.includes('monitorSecrets.'); + if ( + monitor.monitorType === MonitorType.SyntheticMonitor || + monitor.monitorType === MonitorType.CustomJavaScriptCode + ) { + for (const monitorStep of monitor.monitorSteps?.data + ?.monitorStepsInstanceArray || []) { + if ( + monitorStep.data?.customCode && + this.hasSecrets(JSONFunctions.toString(monitorStep.data.customCode)) + ) { + // replace secret in script + await loadSecrets(); + + monitorStep.data.customCode = + (await MonitorUtil.fillSecretsInStringOrJSON({ + secrets: monitorSecrets, + populateSecretsIn: monitorStep.data.customCode, + })) as string; + } + } } - private static async fillSecretsInStringOrJSON(data: { - secrets: MonitorSecret[]; - populateSecretsIn: string | JSONObject | URL | Hostname | IP; - }): Promise<string | JSONObject | URL | Hostname | IP> { - // get all secrets for this monitor. + return monitor; + } - const secrets: MonitorSecret[] = data.secrets; + private static hasSecrets(prepopulatedString: string): boolean { + return prepopulatedString.includes("monitorSecrets."); + } - if (secrets.length === 0) { - return data.populateSecretsIn; - } + private static async fillSecretsInStringOrJSON(data: { + secrets: MonitorSecret[]; + populateSecretsIn: string | JSONObject | URL | Hostname | IP; + }): Promise<string | JSONObject | URL | Hostname | IP> { + // get all secrets for this monitor. - // replace all secrets in the populateSecretsIn + const secrets: MonitorSecret[] = data.secrets; - const storageMap: JSONObject = { - monitorSecrets: {}, - }; - - for (const monitorSecret of secrets) { - if (!monitorSecret.name) { - continue; - } - - if (!monitorSecret.secretValue) { - continue; - } - - (storageMap['monitorSecrets'] as JSONObject)[ - monitorSecret.name as string - ] = monitorSecret.secretValue; - } - - const isValueJSON: boolean = typeof data.populateSecretsIn === 'object'; - - return VMUtil.replaceValueInPlace( - storageMap, - data.populateSecretsIn as string, - isValueJSON - ); + if (secrets.length === 0) { + return data.populateSecretsIn; } + + // replace all secrets in the populateSecretsIn + + const storageMap: JSONObject = { + monitorSecrets: {}, + }; + + for (const monitorSecret of secrets) { + if (!monitorSecret.name) { + continue; + } + + if (!monitorSecret.secretValue) { + continue; + } + + (storageMap["monitorSecrets"] as JSONObject)[ + monitorSecret.name as string + ] = monitorSecret.secretValue; + } + + const isValueJSON: boolean = typeof data.populateSecretsIn === "object"; + + return VMUtil.replaceValueInPlace( + storageMap, + data.populateSecretsIn as string, + isValueJSON, + ); + } } diff --git a/IsolatedVM/API/VM.ts b/IsolatedVM/API/VM.ts index d79e1590d2..e7f482135a 100644 --- a/IsolatedVM/API/VM.ts +++ b/IsolatedVM/API/VM.ts @@ -1,74 +1,74 @@ -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ReturnResult from 'Common/Types/IsolatedVM/ReturnResult'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization'; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ReturnResult from "Common/Types/IsolatedVM/ReturnResult"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; -import VMRunner from 'CommonServer/Utils/VM/VMRunner'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; +import VMRunner from "CommonServer/Utils/VM/VMRunner"; const router: ExpressRouter = Express.getRouter(); router.post( - '/run-code', - ClusterKeyAuthorization.isAuthorizedServiceMiddleware, - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - if (!req.body.code) { - return Response.sendErrorResponse( - req, - res, - new BadDataException('Code is missing') - ); - } + "/run-code", + ClusterKeyAuthorization.isAuthorizedServiceMiddleware, + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + if (!req.body.code) { + return Response.sendErrorResponse( + req, + res, + new BadDataException("Code is missing"), + ); + } - logger.debug('Running code in sandbox'); - logger.debug(req.body.code); + logger.debug("Running code in sandbox"); + logger.debug(req.body.code); - let result: ReturnResult | null = null; + let result: ReturnResult | null = null; - try { - result = await VMRunner.runCodeInSandbox({ - code: req.body.code, - options: { - timeout: req.body?.['options']?.['timeout'] || 5000, - args: req.body?.['options']?.['args'] || {}, - }, - }); - } catch (err) { - logger.error(err); - throw new BadDataException((err as Error).message); - } + try { + result = await VMRunner.runCodeInSandbox({ + code: req.body.code, + options: { + timeout: req.body?.["options"]?.["timeout"] || 5000, + args: req.body?.["options"]?.["args"] || {}, + }, + }); + } catch (err) { + logger.error(err); + throw new BadDataException((err as Error).message); + } - logger.debug('Code execution completed'); - logger.debug(result.returnValue); + logger.debug("Code execution completed"); + logger.debug(result.returnValue); - logger.debug('Code Logs '); - logger.debug(result.logMessages); + logger.debug("Code Logs "); + logger.debug(result.logMessages); - if (typeof result.returnValue === 'object') { - result.returnValue = JSONFunctions.removeCircularReferences( - result.returnValue - ); - } + if (typeof result.returnValue === "object") { + result.returnValue = JSONFunctions.removeCircularReferences( + result.returnValue, + ); + } - return Response.sendJsonObjectResponse(req, res, { - returnValue: result.returnValue, - logMessages: result.logMessages, - }); - } catch (err) { - return next(err); - } + return Response.sendJsonObjectResponse(req, res, { + returnValue: result.returnValue, + logMessages: result.logMessages, + }); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/IsolatedVM/Index.ts b/IsolatedVM/Index.ts index b58c549ea9..faef3c71d4 100644 --- a/IsolatedVM/Index.ts +++ b/IsolatedVM/Index.ts @@ -1,41 +1,41 @@ -import VmAPI from './API/VM'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import App from 'CommonServer/Utils/StartServer'; -import process from 'process'; +import VmAPI from "./API/VM"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import App from "CommonServer/Utils/StartServer"; +import process from "process"; -const APP_NAME: string = 'isolated-vm'; +const APP_NAME: string = "isolated-vm"; const app: ExpressApplication = Express.getExpressApp(); -app.use([`/${APP_NAME}`, '/'], VmAPI); +app.use([`/${APP_NAME}`, "/"], VmAPI); const init: PromiseVoidFunction = async (): Promise<void> => { - try { - // init the app - await App.init({ - appName: APP_NAME, - port: undefined, - isFrontendApp: false, - statusOptions: { - liveCheck: async () => {}, - readyCheck: async () => {}, - }, - }); - logger.debug('App Init Success'); + try { + // init the app + await App.init({ + appName: APP_NAME, + port: undefined, + isFrontendApp: false, + statusOptions: { + liveCheck: async () => {}, + readyCheck: async () => {}, + }, + }); + logger.debug("App Init Success"); - // add default routes - await App.addDefaultRoutes(); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); diff --git a/Model/AnalyticsModels/Index.ts b/Model/AnalyticsModels/Index.ts index 7393666818..fe1a7a3acc 100644 --- a/Model/AnalyticsModels/Index.ts +++ b/Model/AnalyticsModels/Index.ts @@ -1,14 +1,14 @@ -import Log from './Log'; -import Metric from './Metric'; -import MonitorMetricsByMinute from './MonitorMetricsByMinute'; -import Span from './Span'; -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; +import Log from "./Log"; +import Metric from "./Metric"; +import MonitorMetricsByMinute from "./MonitorMetricsByMinute"; +import Span from "./Span"; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; const AnalyticsModels: Array<typeof AnalyticsBaseModel> = [ - Log, - Span, - Metric, - MonitorMetricsByMinute, + Log, + Span, + Metric, + MonitorMetricsByMinute, ]; export default AnalyticsModels; diff --git a/Model/AnalyticsModels/Log.ts b/Model/AnalyticsModels/Log.ts index 07d99010a5..ab6f3d35e4 100644 --- a/Model/AnalyticsModels/Log.ts +++ b/Model/AnalyticsModels/Log.ts @@ -1,373 +1,373 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AnalyticsTableEngine from 'Common/Types/AnalyticsDatabase/AnalyticsTableEngine'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import Route from "Common/Types/API/Route"; +import AnalyticsTableEngine from "Common/Types/AnalyticsDatabase/AnalyticsTableEngine"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; export enum LogSeverity { - Unspecified = 'Unspecified', - Information = 'Information', - Warning = 'Warning', - Error = 'Error', - Trace = 'Trace', - Debug = 'Debug', - Fatal = 'Fatal', + Unspecified = "Unspecified", + Information = "Information", + Warning = "Warning", + Error = "Error", + Trace = "Trace", + Debug = "Debug", + Fatal = "Fatal", } export default class Log extends AnalyticsBaseModel { - public constructor() { - super({ - tableName: 'Log', - tableEngine: AnalyticsTableEngine.MergeTree, - singularName: 'Log', - enableRealtimeEventsOn: { - create: true, - }, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryServiceLog, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteTelemetryServiceLog, - ], - }, - pluralName: 'Logs', - crudApiPath: new Route('/logs'), - tableColumns: [ - new AnalyticsTableColumn({ - key: 'projectId', - title: 'Project ID', - description: 'ID of project', - required: true, - type: TableColumnType.ObjectID, - isTenantId: true, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'serviceId', - title: 'Service ID', - description: 'ID of the Service which created the log', - required: true, - type: TableColumnType.ObjectID, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'time', - title: 'Time', - description: 'When was the log created?', - required: true, - type: TableColumnType.Date, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'timeUnixNano', - title: 'Time (in Unix Nano)', - description: 'When was the log created?', - required: true, - type: TableColumnType.LongNumber, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'severityText', - title: 'Severity Text', - description: 'Log Severity Text', - required: true, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'severityNumber', - title: 'Severity Number', - description: 'Log Severity Number', - required: true, - type: TableColumnType.Number, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'attributes', - title: 'Attributes', - description: 'Attributes', - required: true, - defaultValue: {}, - type: TableColumnType.JSON, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'traceId', - title: 'Trace ID', - description: 'ID of the trace', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'spanId', - title: 'Span ID', - description: 'ID of the span', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'body', - title: 'Log Body', - description: 'Body of the Log', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), + public constructor() { + super({ + tableName: "Log", + tableEngine: AnalyticsTableEngine.MergeTree, + singularName: "Log", + enableRealtimeEventsOn: { + create: true, + }, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryServiceLog, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteTelemetryServiceLog, + ], + }, + pluralName: "Logs", + crudApiPath: new Route("/logs"), + tableColumns: [ + new AnalyticsTableColumn({ + key: "projectId", + title: "Project ID", + description: "ID of project", + required: true, + type: TableColumnType.ObjectID, + isTenantId: true, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, ], - primaryKeys: ['projectId', 'serviceId', 'time'], - }); - } + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get projectId(): ObjectID | undefined { - return this.getColumnValue('projectId') as ObjectID | undefined; - } + new AnalyticsTableColumn({ + key: "serviceId", + title: "Service ID", + description: "ID of the Service which created the log", + required: true, + type: TableColumnType.ObjectID, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set projectId(v: ObjectID | undefined) { - this.setColumnValue('projectId', v); - } + new AnalyticsTableColumn({ + key: "time", + title: "Time", + description: "When was the log created?", + required: true, + type: TableColumnType.Date, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get serviceId(): ObjectID | undefined { - return this.getColumnValue('serviceId') as ObjectID | undefined; - } + new AnalyticsTableColumn({ + key: "timeUnixNano", + title: "Time (in Unix Nano)", + description: "When was the log created?", + required: true, + type: TableColumnType.LongNumber, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set serviceId(v: ObjectID | undefined) { - this.setColumnValue('serviceId', v); - } + new AnalyticsTableColumn({ + key: "severityText", + title: "Severity Text", + description: "Log Severity Text", + required: true, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set body(v: string | undefined) { - this.setColumnValue('body', v); - } + new AnalyticsTableColumn({ + key: "severityNumber", + title: "Severity Number", + description: "Log Severity Number", + required: true, + type: TableColumnType.Number, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get body(): string | undefined { - return this.getColumnValue('body'); - } + new AnalyticsTableColumn({ + key: "attributes", + title: "Attributes", + description: "Attributes", + required: true, + defaultValue: {}, + type: TableColumnType.JSON, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get time(): Date | undefined { - return this.getColumnValue('time') as Date | undefined; - } + new AnalyticsTableColumn({ + key: "traceId", + title: "Trace ID", + description: "ID of the trace", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set time(v: Date | undefined) { - this.setColumnValue('time', v); - } + new AnalyticsTableColumn({ + key: "spanId", + title: "Span ID", + description: "ID of the span", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get timeUnixNano(): number | undefined { - return this.getColumnValue('timeUnixNano') as number | undefined; - } + new AnalyticsTableColumn({ + key: "body", + title: "Log Body", + description: "Body of the Log", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), + ], + primaryKeys: ["projectId", "serviceId", "time"], + }); + } - public set timeUnixNano(v: number | undefined) { - this.setColumnValue('timeUnixNano', v); - } + public get projectId(): ObjectID | undefined { + return this.getColumnValue("projectId") as ObjectID | undefined; + } - public get severityText(): LogSeverity | undefined { - return this.getColumnValue('severityText') as LogSeverity | undefined; - } + public set projectId(v: ObjectID | undefined) { + this.setColumnValue("projectId", v); + } - public set severityText(v: LogSeverity | undefined) { - this.setColumnValue('severityText', v); - } + public get serviceId(): ObjectID | undefined { + return this.getColumnValue("serviceId") as ObjectID | undefined; + } - public get severityNumber(): number | undefined { - return this.getColumnValue('severityNumber') as number | undefined; - } + public set serviceId(v: ObjectID | undefined) { + this.setColumnValue("serviceId", v); + } - public set severityNumber(v: number | undefined) { - this.setColumnValue('severityNumber', v); - } + public set body(v: string | undefined) { + this.setColumnValue("body", v); + } - public get attributes(): JSONObject | undefined { - return this.getColumnValue('attributes') as JSONObject | undefined; - } + public get body(): string | undefined { + return this.getColumnValue("body"); + } - public set attributes(v: JSONObject | undefined) { - this.setColumnValue('attributes', v); - } + public get time(): Date | undefined { + return this.getColumnValue("time") as Date | undefined; + } - public get traceId(): string | undefined { - return this.getColumnValue('traceId') as string | undefined; - } + public set time(v: Date | undefined) { + this.setColumnValue("time", v); + } - public set traceId(v: string | undefined) { - this.setColumnValue('traceId', v); - } + public get timeUnixNano(): number | undefined { + return this.getColumnValue("timeUnixNano") as number | undefined; + } - public get spanId(): string | undefined { - return this.getColumnValue('spanId') as string | undefined; - } + public set timeUnixNano(v: number | undefined) { + this.setColumnValue("timeUnixNano", v); + } - public set spanId(v: string | undefined) { - this.setColumnValue('spanId', v); - } + public get severityText(): LogSeverity | undefined { + return this.getColumnValue("severityText") as LogSeverity | undefined; + } + + public set severityText(v: LogSeverity | undefined) { + this.setColumnValue("severityText", v); + } + + public get severityNumber(): number | undefined { + return this.getColumnValue("severityNumber") as number | undefined; + } + + public set severityNumber(v: number | undefined) { + this.setColumnValue("severityNumber", v); + } + + public get attributes(): JSONObject | undefined { + return this.getColumnValue("attributes") as JSONObject | undefined; + } + + public set attributes(v: JSONObject | undefined) { + this.setColumnValue("attributes", v); + } + + public get traceId(): string | undefined { + return this.getColumnValue("traceId") as string | undefined; + } + + public set traceId(v: string | undefined) { + this.setColumnValue("traceId", v); + } + + public get spanId(): string | undefined { + return this.getColumnValue("spanId") as string | undefined; + } + + public set spanId(v: string | undefined) { + this.setColumnValue("spanId", v); + } } diff --git a/Model/AnalyticsModels/Metric.ts b/Model/AnalyticsModels/Metric.ts index d58c719ffe..4f3e880cc1 100644 --- a/Model/AnalyticsModels/Metric.ts +++ b/Model/AnalyticsModels/Metric.ts @@ -1,694 +1,692 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AnalyticsTableEngine from 'Common/Types/AnalyticsDatabase/AnalyticsTableEngine'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import Route from "Common/Types/API/Route"; +import AnalyticsTableEngine from "Common/Types/AnalyticsDatabase/AnalyticsTableEngine"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; export enum AggregationTemporality { - Delta = 'Delta', - Cumulative = 'Cumulative', + Delta = "Delta", + Cumulative = "Cumulative", } export enum MetricPointType { - Sum = 'Sum', - Gauge = 'Gauge', - Histogram = 'Histogram', - ExponentialHistogram = 'ExponentialHistogram', + Sum = "Sum", + Gauge = "Gauge", + Histogram = "Histogram", + ExponentialHistogram = "ExponentialHistogram", } export default class Metric extends AnalyticsBaseModel { - public constructor() { - super({ - tableName: 'Metric', - tableEngine: AnalyticsTableEngine.MergeTree, - singularName: 'Metric', - pluralName: 'Metrics', - crudApiPath: new Route('/metrics'), - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryServiceTraces, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteTelemetryServiceTraces, - ], - }, - tableColumns: [ - new AnalyticsTableColumn({ - key: 'projectId', - title: 'Project ID', - description: 'ID of project', - required: true, - type: TableColumnType.ObjectID, - isTenantId: true, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'serviceId', - title: 'Service ID', - description: 'ID of the Service which created the log', - required: true, - type: TableColumnType.ObjectID, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - // add name and description - - new AnalyticsTableColumn({ - key: 'name', - title: 'Name', - description: 'Name of the Metric', - required: true, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'description', - title: 'Description', - description: 'Description of the Metric', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'unit', - title: 'Unit', - description: 'Unit of the Metric', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'aggregationTemporality', - title: 'Aggregation Temporality', - description: 'Aggregation Temporality of this Metric', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'metricPointType', - title: 'Metric Point Type', - description: 'Metric Point Type of this Metric', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - // this is end time. - new AnalyticsTableColumn({ - key: 'time', - title: 'Time', - description: 'When did the Metric happen?', - required: true, - type: TableColumnType.Date, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'startTime', - title: 'Start Time', - description: 'When did the Metric happen?', - required: false, - type: TableColumnType.Date, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - // end time. - new AnalyticsTableColumn({ - key: 'timeUnixNano', - title: 'Time (in Unix Nano)', - description: 'When did the Metric happen?', - required: true, - type: TableColumnType.LongNumber, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'startTimeUnixNano', - title: 'Start Time (in Unix Nano)', - description: 'When did the Metric happen?', - required: false, - type: TableColumnType.LongNumber, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'attributes', - title: 'Attributes', - description: 'Attributes', - required: true, - type: TableColumnType.JSON, - defaultValue: {}, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'isMonotonic', - title: 'Is Monotonic', - description: 'Is Monotonic', - required: false, - type: TableColumnType.Boolean, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'count', - title: 'Count', - description: 'Count', - required: false, - type: TableColumnType.Number, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'sum', - title: 'Sum', - description: 'Sum', - required: false, - type: TableColumnType.Decimal, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'value', - title: 'Value', - description: 'Value', - required: false, - type: TableColumnType.Decimal, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'min', - title: 'Min', - description: 'Min', - required: false, - type: TableColumnType.Decimal, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'max', - title: 'Max', - description: 'Max', - required: false, - type: TableColumnType.Decimal, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'bucketCounts', - title: 'Bucket Counts', - description: 'Bucket Counts', - required: true, - defaultValue: [], - type: TableColumnType.ArrayNumber, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'explicitBounds', - title: 'Explicit Bonds', - description: 'Explicit Bonds', - required: true, - defaultValue: [], - type: TableColumnType.ArrayNumber, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceLog, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceLog, - ], - update: [], - }, - }), + public constructor() { + super({ + tableName: "Metric", + tableEngine: AnalyticsTableEngine.MergeTree, + singularName: "Metric", + pluralName: "Metrics", + crudApiPath: new Route("/metrics"), + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryServiceTraces, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteTelemetryServiceTraces, + ], + }, + tableColumns: [ + new AnalyticsTableColumn({ + key: "projectId", + title: "Project ID", + description: "ID of project", + required: true, + type: TableColumnType.ObjectID, + isTenantId: true, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, ], - primaryKeys: ['projectId', 'serviceId', 'time'], - }); - } + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get projectId(): ObjectID | undefined { - return this.getColumnValue('projectId') as ObjectID | undefined; - } + new AnalyticsTableColumn({ + key: "serviceId", + title: "Service ID", + description: "ID of the Service which created the log", + required: true, + type: TableColumnType.ObjectID, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set projectId(v: ObjectID | undefined) { - this.setColumnValue('projectId', v); - } + // add name and description - public get serviceId(): ObjectID | undefined { - return this.getColumnValue('serviceId') as ObjectID | undefined; - } + new AnalyticsTableColumn({ + key: "name", + title: "Name", + description: "Name of the Metric", + required: true, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get name(): string | undefined { - return this.getColumnValue('name') as string | undefined; - } + new AnalyticsTableColumn({ + key: "description", + title: "Description", + description: "Description of the Metric", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set name(v: string | undefined) { - this.setColumnValue('name', v); - } + new AnalyticsTableColumn({ + key: "unit", + title: "Unit", + description: "Unit of the Metric", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get aggregationTemporality(): AggregationTemporality | undefined { - return this.getColumnValue('aggregationTemporality') as - | AggregationTemporality - | undefined; - } + new AnalyticsTableColumn({ + key: "aggregationTemporality", + title: "Aggregation Temporality", + description: "Aggregation Temporality of this Metric", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set aggregationTemporality(v: AggregationTemporality | undefined) { - this.setColumnValue('aggregationTemporality', v); - } + new AnalyticsTableColumn({ + key: "metricPointType", + title: "Metric Point Type", + description: "Metric Point Type of this Metric", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get metricPointType(): MetricPointType | undefined { - return this.getColumnValue('metricPointType') as - | MetricPointType - | undefined; - } + // this is end time. + new AnalyticsTableColumn({ + key: "time", + title: "Time", + description: "When did the Metric happen?", + required: true, + type: TableColumnType.Date, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set metricPointType(v: MetricPointType | undefined) { - this.setColumnValue('metricPointType', v); - } + new AnalyticsTableColumn({ + key: "startTime", + title: "Start Time", + description: "When did the Metric happen?", + required: false, + type: TableColumnType.Date, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get description(): string | undefined { - return this.getColumnValue('description') as string | undefined; - } + // end time. + new AnalyticsTableColumn({ + key: "timeUnixNano", + title: "Time (in Unix Nano)", + description: "When did the Metric happen?", + required: true, + type: TableColumnType.LongNumber, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set description(v: string | undefined) { - this.setColumnValue('description', v); - } + new AnalyticsTableColumn({ + key: "startTimeUnixNano", + title: "Start Time (in Unix Nano)", + description: "When did the Metric happen?", + required: false, + type: TableColumnType.LongNumber, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get unit(): string | undefined { - return this.getColumnValue('unit') as string | undefined; - } + new AnalyticsTableColumn({ + key: "attributes", + title: "Attributes", + description: "Attributes", + required: true, + type: TableColumnType.JSON, + defaultValue: {}, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set unit(v: string | undefined) { - this.setColumnValue('unit', v); - } + new AnalyticsTableColumn({ + key: "isMonotonic", + title: "Is Monotonic", + description: "Is Monotonic", + required: false, + type: TableColumnType.Boolean, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get isMonotonic(): boolean | undefined { - return this.getColumnValue('isMonotonic') as boolean | undefined; - } + new AnalyticsTableColumn({ + key: "count", + title: "Count", + description: "Count", + required: false, + type: TableColumnType.Number, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set isMonotonic(v: boolean | undefined) { - this.setColumnValue('isMonotonic', v); - } + new AnalyticsTableColumn({ + key: "sum", + title: "Sum", + description: "Sum", + required: false, + type: TableColumnType.Decimal, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set serviceId(v: ObjectID | undefined) { - this.setColumnValue('serviceId', v); - } + new AnalyticsTableColumn({ + key: "value", + title: "Value", + description: "Value", + required: false, + type: TableColumnType.Decimal, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get time(): Date | undefined { - return this.getColumnValue('time') as Date | undefined; - } + new AnalyticsTableColumn({ + key: "min", + title: "Min", + description: "Min", + required: false, + type: TableColumnType.Decimal, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set time(v: Date | undefined) { - this.setColumnValue('time', v); - } + new AnalyticsTableColumn({ + key: "max", + title: "Max", + description: "Max", + required: false, + type: TableColumnType.Decimal, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public get attributes(): JSONObject | undefined { - return this.getColumnValue('attributes') as JSONObject | undefined; - } + new AnalyticsTableColumn({ + key: "bucketCounts", + title: "Bucket Counts", + description: "Bucket Counts", + required: true, + defaultValue: [], + type: TableColumnType.ArrayNumber, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), - public set attributes(v: JSONObject | undefined) { - this.setColumnValue('attributes', v); - } + new AnalyticsTableColumn({ + key: "explicitBounds", + title: "Explicit Bonds", + description: "Explicit Bonds", + required: true, + defaultValue: [], + type: TableColumnType.ArrayNumber, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceLog, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceLog, + ], + update: [], + }, + }), + ], + primaryKeys: ["projectId", "serviceId", "time"], + }); + } - public get startTime(): Date | undefined { - return this.getColumnValue('startTime') as Date | undefined; - } + public get projectId(): ObjectID | undefined { + return this.getColumnValue("projectId") as ObjectID | undefined; + } - public set startTime(v: Date | undefined) { - this.setColumnValue('startTime', v); - } + public set projectId(v: ObjectID | undefined) { + this.setColumnValue("projectId", v); + } - public get startTimeUnixNano(): number | undefined { - return this.getColumnValue('startTimeUnixNano') as number | undefined; - } + public get serviceId(): ObjectID | undefined { + return this.getColumnValue("serviceId") as ObjectID | undefined; + } - public set startTimeUnixNano(v: number | undefined) { - this.setColumnValue('startTimeUnixNano', v); - } + public get name(): string | undefined { + return this.getColumnValue("name") as string | undefined; + } - public get timeUnixNano(): number | undefined { - return this.getColumnValue('timeUnixNano') as number | undefined; - } + public set name(v: string | undefined) { + this.setColumnValue("name", v); + } - public set timeUnixNano(v: number | undefined) { - this.setColumnValue('timeUnixNano', v); - } + public get aggregationTemporality(): AggregationTemporality | undefined { + return this.getColumnValue("aggregationTemporality") as + | AggregationTemporality + | undefined; + } - public get count(): number | undefined { - return this.getColumnValue('count') as number | undefined; - } + public set aggregationTemporality(v: AggregationTemporality | undefined) { + this.setColumnValue("aggregationTemporality", v); + } - public set count(v: number | undefined) { - this.setColumnValue('count', v); - } + public get metricPointType(): MetricPointType | undefined { + return this.getColumnValue("metricPointType") as + | MetricPointType + | undefined; + } - public get sum(): number | undefined { - return this.getColumnValue('sum') as number | undefined; - } + public set metricPointType(v: MetricPointType | undefined) { + this.setColumnValue("metricPointType", v); + } - public set sum(v: number | undefined) { - this.setColumnValue('sum', v); - } + public get description(): string | undefined { + return this.getColumnValue("description") as string | undefined; + } - public get value(): number | undefined { - return this.getColumnValue('value') as number | undefined; - } + public set description(v: string | undefined) { + this.setColumnValue("description", v); + } - public set value(v: number | undefined) { - this.setColumnValue('value', v); - } + public get unit(): string | undefined { + return this.getColumnValue("unit") as string | undefined; + } - public get min(): number | undefined { - return this.getColumnValue('min') as number | undefined; - } + public set unit(v: string | undefined) { + this.setColumnValue("unit", v); + } - public set min(v: number | undefined) { - this.setColumnValue('min', v); - } + public get isMonotonic(): boolean | undefined { + return this.getColumnValue("isMonotonic") as boolean | undefined; + } - public get max(): number | undefined { - return this.getColumnValue('max') as number | undefined; - } + public set isMonotonic(v: boolean | undefined) { + this.setColumnValue("isMonotonic", v); + } - public set max(v: number | undefined) { - this.setColumnValue('max', v); - } + public set serviceId(v: ObjectID | undefined) { + this.setColumnValue("serviceId", v); + } - public get bucketCounts(): Array<number> | undefined { - return this.getColumnValue('bucketCounts') as Array<number> | undefined; - } + public get time(): Date | undefined { + return this.getColumnValue("time") as Date | undefined; + } - public set bucketCounts(v: Array<number> | undefined) { - this.setColumnValue('bucketCounts', v); - } + public set time(v: Date | undefined) { + this.setColumnValue("time", v); + } - public get explicitBounds(): Array<number> | undefined { - return this.getColumnValue('explicitBounds') as - | Array<number> - | undefined; - } + public get attributes(): JSONObject | undefined { + return this.getColumnValue("attributes") as JSONObject | undefined; + } - public set explicitBounds(v: Array<number> | undefined) { - this.setColumnValue('explicitBounds', v); - } + public set attributes(v: JSONObject | undefined) { + this.setColumnValue("attributes", v); + } + + public get startTime(): Date | undefined { + return this.getColumnValue("startTime") as Date | undefined; + } + + public set startTime(v: Date | undefined) { + this.setColumnValue("startTime", v); + } + + public get startTimeUnixNano(): number | undefined { + return this.getColumnValue("startTimeUnixNano") as number | undefined; + } + + public set startTimeUnixNano(v: number | undefined) { + this.setColumnValue("startTimeUnixNano", v); + } + + public get timeUnixNano(): number | undefined { + return this.getColumnValue("timeUnixNano") as number | undefined; + } + + public set timeUnixNano(v: number | undefined) { + this.setColumnValue("timeUnixNano", v); + } + + public get count(): number | undefined { + return this.getColumnValue("count") as number | undefined; + } + + public set count(v: number | undefined) { + this.setColumnValue("count", v); + } + + public get sum(): number | undefined { + return this.getColumnValue("sum") as number | undefined; + } + + public set sum(v: number | undefined) { + this.setColumnValue("sum", v); + } + + public get value(): number | undefined { + return this.getColumnValue("value") as number | undefined; + } + + public set value(v: number | undefined) { + this.setColumnValue("value", v); + } + + public get min(): number | undefined { + return this.getColumnValue("min") as number | undefined; + } + + public set min(v: number | undefined) { + this.setColumnValue("min", v); + } + + public get max(): number | undefined { + return this.getColumnValue("max") as number | undefined; + } + + public set max(v: number | undefined) { + this.setColumnValue("max", v); + } + + public get bucketCounts(): Array<number> | undefined { + return this.getColumnValue("bucketCounts") as Array<number> | undefined; + } + + public set bucketCounts(v: Array<number> | undefined) { + this.setColumnValue("bucketCounts", v); + } + + public get explicitBounds(): Array<number> | undefined { + return this.getColumnValue("explicitBounds") as Array<number> | undefined; + } + + public set explicitBounds(v: Array<number> | undefined) { + this.setColumnValue("explicitBounds", v); + } } diff --git a/Model/AnalyticsModels/MonitorMetricsByMinute.ts b/Model/AnalyticsModels/MonitorMetricsByMinute.ts index 71da2897a8..e7e34011f7 100644 --- a/Model/AnalyticsModels/MonitorMetricsByMinute.ts +++ b/Model/AnalyticsModels/MonitorMetricsByMinute.ts @@ -1,177 +1,176 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AnalyticsTableEngine from 'Common/Types/AnalyticsDatabase/AnalyticsTableEngine'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import BrowserType from 'Common/Types/BrowserType'; -import { JSONObject } from 'Common/Types/JSON'; -import { CheckOn } from 'Common/Types/Monitor/CriteriaFilter'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import ScreenSizeType from 'Common/Types/ScreenSizeType'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import Route from "Common/Types/API/Route"; +import AnalyticsTableEngine from "Common/Types/AnalyticsDatabase/AnalyticsTableEngine"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import BrowserType from "Common/Types/BrowserType"; +import { JSONObject } from "Common/Types/JSON"; +import { CheckOn } from "Common/Types/Monitor/CriteriaFilter"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import ScreenSizeType from "Common/Types/ScreenSizeType"; export interface MonitorMetricsMiscData { - diskPath?: string; - probeId?: string; - browserType?: BrowserType; - screenSizeType?: ScreenSizeType; + diskPath?: string; + probeId?: string; + browserType?: BrowserType; + screenSizeType?: ScreenSizeType; } export default class MonitorMetricsByMinute extends AnalyticsBaseModel { - public constructor() { - super({ - tableName: 'MonitorMetricsByMinute', - tableEngine: AnalyticsTableEngine.MergeTree, - singularName: 'Monitor Metrics By Minute', - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - create: [], - update: [], - delete: [], - }, - pluralName: 'Monitor Metrics By Minutes', - crudApiPath: new Route('/monitor-metrics-by-minute'), - tableColumns: [ - new AnalyticsTableColumn({ - key: 'projectId', - title: 'Project ID', - description: 'ID of project', - required: true, - type: TableColumnType.ObjectID, - isTenantId: true, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - create: [], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'monitorId', - title: 'Monitor ID', - description: - 'ID of the Monitor which this metric belongs to', - required: true, - type: TableColumnType.ObjectID, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - create: [], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'metricType', - title: 'Metric Type', - description: 'Type of metric', - required: true, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - create: [], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'metricValue', - title: 'Metric Value', - description: 'Value of the metric', - required: true, - type: TableColumnType.Number, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - create: [], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'miscData', - title: 'Misc Data', - description: 'Misc data for the metric (if any)', - required: false, - type: TableColumnType.JSON, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - create: [], - update: [], - }, - }), + public constructor() { + super({ + tableName: "MonitorMetricsByMinute", + tableEngine: AnalyticsTableEngine.MergeTree, + singularName: "Monitor Metrics By Minute", + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + create: [], + update: [], + delete: [], + }, + pluralName: "Monitor Metrics By Minutes", + crudApiPath: new Route("/monitor-metrics-by-minute"), + tableColumns: [ + new AnalyticsTableColumn({ + key: "projectId", + title: "Project ID", + description: "ID of project", + required: true, + type: TableColumnType.ObjectID, + isTenantId: true, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, ], - primaryKeys: ['projectId', 'monitorId', 'createdAt'], - }); - } + create: [], + update: [], + }, + }), - public get projectId(): ObjectID | undefined { - return this.getColumnValue('projectId') as ObjectID | undefined; - } + new AnalyticsTableColumn({ + key: "monitorId", + title: "Monitor ID", + description: "ID of the Monitor which this metric belongs to", + required: true, + type: TableColumnType.ObjectID, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + create: [], + update: [], + }, + }), - public set projectId(v: ObjectID | undefined) { - this.setColumnValue('projectId', v); - } + new AnalyticsTableColumn({ + key: "metricType", + title: "Metric Type", + description: "Type of metric", + required: true, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + create: [], + update: [], + }, + }), - public get monitorId(): ObjectID | undefined { - return this.getColumnValue('monitorId') as ObjectID | undefined; - } + new AnalyticsTableColumn({ + key: "metricValue", + title: "Metric Value", + description: "Value of the metric", + required: true, + type: TableColumnType.Number, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + create: [], + update: [], + }, + }), - public set monitorId(v: ObjectID | undefined) { - this.setColumnValue('monitorId', v); - } + new AnalyticsTableColumn({ + key: "miscData", + title: "Misc Data", + description: "Misc data for the metric (if any)", + required: false, + type: TableColumnType.JSON, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + create: [], + update: [], + }, + }), + ], + primaryKeys: ["projectId", "monitorId", "createdAt"], + }); + } - public get metricType(): CheckOn | undefined { - return this.getColumnValue('metricType') as CheckOn | undefined; - } + public get projectId(): ObjectID | undefined { + return this.getColumnValue("projectId") as ObjectID | undefined; + } - public set metricType(v: CheckOn | undefined) { - this.setColumnValue('metricType', v); - } + public set projectId(v: ObjectID | undefined) { + this.setColumnValue("projectId", v); + } - public get metricValue(): number | undefined { - return this.getColumnValue('metricValue') as number | undefined; - } + public get monitorId(): ObjectID | undefined { + return this.getColumnValue("monitorId") as ObjectID | undefined; + } - public set metricValue(v: number | undefined) { - this.setColumnValue('metricValue', v); - } + public set monitorId(v: ObjectID | undefined) { + this.setColumnValue("monitorId", v); + } - public get miscData(): MonitorMetricsMiscData | undefined { - return this.getColumnValue('miscData') as - | MonitorMetricsMiscData - | undefined; - } + public get metricType(): CheckOn | undefined { + return this.getColumnValue("metricType") as CheckOn | undefined; + } - public set miscData(v: MonitorMetricsMiscData | undefined) { - this.setColumnValue('miscData', v as JSONObject); - } + public set metricType(v: CheckOn | undefined) { + this.setColumnValue("metricType", v); + } + + public get metricValue(): number | undefined { + return this.getColumnValue("metricValue") as number | undefined; + } + + public set metricValue(v: number | undefined) { + this.setColumnValue("metricValue", v); + } + + public get miscData(): MonitorMetricsMiscData | undefined { + return this.getColumnValue("miscData") as + | MonitorMetricsMiscData + | undefined; + } + + public set miscData(v: MonitorMetricsMiscData | undefined) { + this.setColumnValue("miscData", v as JSONObject); + } } diff --git a/Model/AnalyticsModels/NestedModels/KeyValueNestedModel.ts b/Model/AnalyticsModels/NestedModels/KeyValueNestedModel.ts index c6b9f667fb..aafec6bada 100644 --- a/Model/AnalyticsModels/NestedModels/KeyValueNestedModel.ts +++ b/Model/AnalyticsModels/NestedModels/KeyValueNestedModel.ts @@ -1,59 +1,59 @@ -import NestedModel from 'Common/AnalyticsModels/NestedModel'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; +import NestedModel from "Common/AnalyticsModels/NestedModel"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; export default class KeyValueNestedModel extends NestedModel { - public constructor() { - super({ - tableColumns: [ - new AnalyticsTableColumn({ - key: 'key', - title: 'Key', - description: 'Key of the attribute', - required: true, - type: TableColumnType.Text, - }), + public constructor() { + super({ + tableColumns: [ + new AnalyticsTableColumn({ + key: "key", + title: "Key", + description: "Key of the attribute", + required: true, + type: TableColumnType.Text, + }), - new AnalyticsTableColumn({ - key: 'stringValue', - title: 'String Value', - description: 'Key of the attribute', - required: false, - type: TableColumnType.Text, - }), + new AnalyticsTableColumn({ + key: "stringValue", + title: "String Value", + description: "Key of the attribute", + required: false, + type: TableColumnType.Text, + }), - new AnalyticsTableColumn({ - key: 'numberValue', - title: 'Number Value', - description: 'Value of the attribute', - required: false, - type: TableColumnType.Number, - }), - ], - }); - } + new AnalyticsTableColumn({ + key: "numberValue", + title: "Number Value", + description: "Value of the attribute", + required: false, + type: TableColumnType.Number, + }), + ], + }); + } - public get key(): string | undefined { - return this.getColumnValue('key'); - } + public get key(): string | undefined { + return this.getColumnValue("key"); + } - public set key(v: string | undefined) { - this.setColumnValue('key', v); - } + public set key(v: string | undefined) { + this.setColumnValue("key", v); + } - public get stringValue(): string | undefined { - return this.getColumnValue('stringValue'); - } + public get stringValue(): string | undefined { + return this.getColumnValue("stringValue"); + } - public set stringValue(v: string | undefined) { - this.setColumnValue('stringValue', v); - } + public set stringValue(v: string | undefined) { + this.setColumnValue("stringValue", v); + } - public get numberValue(): number | undefined { - return this.getColumnValue('numberValue'); - } + public get numberValue(): number | undefined { + return this.getColumnValue("numberValue"); + } - public set numberValue(v: number | undefined) { - this.setColumnValue('numberValue', v); - } + public set numberValue(v: number | undefined) { + this.setColumnValue("numberValue", v); + } } diff --git a/Model/AnalyticsModels/Span.ts b/Model/AnalyticsModels/Span.ts index 9fcbd51847..98da134a7b 100644 --- a/Model/AnalyticsModels/Span.ts +++ b/Model/AnalyticsModels/Span.ts @@ -1,648 +1,648 @@ -import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AnalyticsTableEngine from 'Common/Types/AnalyticsDatabase/AnalyticsTableEngine'; -import AnalyticsTableColumn from 'Common/Types/AnalyticsDatabase/TableColumn'; -import TableColumnType from 'Common/Types/AnalyticsDatabase/TableColumnType'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel"; +import Route from "Common/Types/API/Route"; +import AnalyticsTableEngine from "Common/Types/AnalyticsDatabase/AnalyticsTableEngine"; +import AnalyticsTableColumn from "Common/Types/AnalyticsDatabase/TableColumn"; +import TableColumnType from "Common/Types/AnalyticsDatabase/TableColumnType"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; export enum SpanKind { - Server = 'SPAN_KIND_SERVER', - Client = 'SPAN_KIND_CLIENT', - Producer = 'SPAN_KIND_PRODUCER', - Consumer = 'SPAN_KIND_CONSUMER', - Internal = 'SPAN_KIND_INTERNAL', + Server = "SPAN_KIND_SERVER", + Client = "SPAN_KIND_CLIENT", + Producer = "SPAN_KIND_PRODUCER", + Consumer = "SPAN_KIND_CONSUMER", + Internal = "SPAN_KIND_INTERNAL", } export enum SpanEventType { - Exception = 'Exception', - Event = 'Event', + Exception = "Exception", + Event = "Event", } export enum SpanStatus { - Unset = 0, - Ok = 1, - Error = 2, + Unset = 0, + Ok = 1, + Error = 2, } export interface SpanEvent { - name: string; - time: Date; - timeUnixNano: number; - attributes: JSONObject; + name: string; + time: Date; + timeUnixNano: number; + attributes: JSONObject; } export interface SpanLink { - traceId: string; - spanId: string; - attributes?: JSONObject; + traceId: string; + spanId: string; + attributes?: JSONObject; } export default class Span extends AnalyticsBaseModel { - public constructor() { - super({ - tableName: 'Span', - tableEngine: AnalyticsTableEngine.MergeTree, - singularName: 'Span', - pluralName: 'Spans', - crudApiPath: new Route('/span'), - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryServiceTraces, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteTelemetryServiceTraces, - ], - }, - tableColumns: [ - new AnalyticsTableColumn({ - key: 'projectId', - title: 'Project ID', - description: 'ID of project', - required: true, - type: TableColumnType.ObjectID, - isTenantId: true, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'serviceId', - title: 'Service ID', - description: 'ID of the Service which created the log', - required: true, - type: TableColumnType.ObjectID, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'startTime', - title: 'Start Time', - description: 'When did the span start?', - required: true, - type: TableColumnType.Date, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'endTime', - title: 'End Time', - description: 'When did the span end?', - required: true, - type: TableColumnType.Date, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'startTimeUnixNano', - title: 'Start Time in Unix Nano', - description: 'When did the span start?', - required: true, - type: TableColumnType.LongNumber, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'durationUnixNano', - title: 'Duration in Unix Nano', - description: 'How long did the span last?', - required: true, - type: TableColumnType.LongNumber, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'endTimeUnixNano', - title: 'End Time', - description: 'When did the span end?', - required: true, - type: TableColumnType.LongNumber, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'traceId', - title: 'Trace ID', - description: 'ID of the trace', - required: true, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'spanId', - title: 'Span ID', - description: 'ID of the span', - required: true, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'parentSpanId', - title: 'Parent Span ID', - description: 'ID of the parent span', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'traceState', - title: 'Trace State', - description: 'Trace State', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'attributes', - title: 'Attributes', - description: 'Attributes', - required: true, - defaultValue: {}, - type: TableColumnType.JSON, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'events', - title: 'Events', - description: 'Span Events', - required: true, - defaultValue: [], - type: TableColumnType.JSONArray, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'links', - title: 'Links', - description: 'Span Links', - required: true, - defaultValue: {}, - type: TableColumnType.JSON, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'statusCode', - title: 'Status Code', - description: 'Status Code', - required: false, - type: TableColumnType.Number, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'statusMessage', - title: 'Status Message', - description: 'Status Message', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'name', - title: 'Name', - description: 'Name of the span', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), - - new AnalyticsTableColumn({ - key: 'kind', - title: 'Kind', - description: 'Kind of the span', - required: false, - type: TableColumnType.Text, - accessControl: { - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadTelemetryServiceTraces, - ], - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryServiceTraces, - ], - update: [], - }, - }), + public constructor() { + super({ + tableName: "Span", + tableEngine: AnalyticsTableEngine.MergeTree, + singularName: "Span", + pluralName: "Spans", + crudApiPath: new Route("/span"), + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryServiceTraces, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteTelemetryServiceTraces, + ], + }, + tableColumns: [ + new AnalyticsTableColumn({ + key: "projectId", + title: "Project ID", + description: "ID of project", + required: true, + type: TableColumnType.ObjectID, + isTenantId: true, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, ], - primaryKeys: [ - 'projectId', - 'serviceId', - 'traceId', - 'startTime', - 'endTime', + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, ], - }); - } + update: [], + }, + }), - public get startTimeUnixNano(): number | undefined { - return this.getColumnValue('startTimeUnixNano') as number | undefined; - } + new AnalyticsTableColumn({ + key: "serviceId", + title: "Service ID", + description: "ID of the Service which created the log", + required: true, + type: TableColumnType.ObjectID, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public set startTimeUnixNano(v: number | undefined) { - this.setColumnValue('startTimeUnixNano', v); - } + new AnalyticsTableColumn({ + key: "startTime", + title: "Start Time", + description: "When did the span start?", + required: true, + type: TableColumnType.Date, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public get durationUnixNano(): number | undefined { - return this.getColumnValue('durationUnixNano') as number | undefined; - } + new AnalyticsTableColumn({ + key: "endTime", + title: "End Time", + description: "When did the span end?", + required: true, + type: TableColumnType.Date, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public set durationUnixNano(v: number | undefined) { - this.setColumnValue('durationUnixNano', v); - } + new AnalyticsTableColumn({ + key: "startTimeUnixNano", + title: "Start Time in Unix Nano", + description: "When did the span start?", + required: true, + type: TableColumnType.LongNumber, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public get endTimeUnixNano(): number | undefined { - return this.getColumnValue('endTimeUnixNano') as number | undefined; - } + new AnalyticsTableColumn({ + key: "durationUnixNano", + title: "Duration in Unix Nano", + description: "How long did the span last?", + required: true, + type: TableColumnType.LongNumber, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public set endTimeUnixNano(v: number | undefined) { - this.setColumnValue('endTimeUnixNano', v); - } + new AnalyticsTableColumn({ + key: "endTimeUnixNano", + title: "End Time", + description: "When did the span end?", + required: true, + type: TableColumnType.LongNumber, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public get name(): string | undefined { - return this.getColumnValue('name') as string | undefined; - } + new AnalyticsTableColumn({ + key: "traceId", + title: "Trace ID", + description: "ID of the trace", + required: true, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public set name(v: string | undefined) { - this.setColumnValue('name', v); - } + new AnalyticsTableColumn({ + key: "spanId", + title: "Span ID", + description: "ID of the span", + required: true, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public get kind(): SpanKind | undefined { - return this.getColumnValue('kind') as SpanKind | undefined; - } + new AnalyticsTableColumn({ + key: "parentSpanId", + title: "Parent Span ID", + description: "ID of the parent span", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public set kind(v: SpanKind | undefined) { - this.setColumnValue('kind', v); - } + new AnalyticsTableColumn({ + key: "traceState", + title: "Trace State", + description: "Trace State", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public get projectId(): ObjectID | undefined { - return this.getColumnValue('projectId') as ObjectID | undefined; - } + new AnalyticsTableColumn({ + key: "attributes", + title: "Attributes", + description: "Attributes", + required: true, + defaultValue: {}, + type: TableColumnType.JSON, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public set projectId(v: ObjectID | undefined) { - this.setColumnValue('projectId', v); - } + new AnalyticsTableColumn({ + key: "events", + title: "Events", + description: "Span Events", + required: true, + defaultValue: [], + type: TableColumnType.JSONArray, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public get serviceId(): ObjectID | undefined { - return this.getColumnValue('serviceId') as ObjectID | undefined; - } + new AnalyticsTableColumn({ + key: "links", + title: "Links", + description: "Span Links", + required: true, + defaultValue: {}, + type: TableColumnType.JSON, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public set serviceId(v: ObjectID | undefined) { - this.setColumnValue('serviceId', v); - } + new AnalyticsTableColumn({ + key: "statusCode", + title: "Status Code", + description: "Status Code", + required: false, + type: TableColumnType.Number, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public get startTime(): Date | undefined { - return this.getColumnValue('startTime') as Date | undefined; - } + new AnalyticsTableColumn({ + key: "statusMessage", + title: "Status Message", + description: "Status Message", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public set startTime(v: Date | undefined) { - this.setColumnValue('startTime', v); - } + new AnalyticsTableColumn({ + key: "name", + title: "Name", + description: "Name of the span", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), - public get endTime(): Date | undefined { - return this.getColumnValue('endTime') as Date | undefined; - } + new AnalyticsTableColumn({ + key: "kind", + title: "Kind", + description: "Kind of the span", + required: false, + type: TableColumnType.Text, + accessControl: { + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadTelemetryServiceTraces, + ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryServiceTraces, + ], + update: [], + }, + }), + ], + primaryKeys: [ + "projectId", + "serviceId", + "traceId", + "startTime", + "endTime", + ], + }); + } - public set endTime(v: Date | undefined) { - this.setColumnValue('endTime', v); - } + public get startTimeUnixNano(): number | undefined { + return this.getColumnValue("startTimeUnixNano") as number | undefined; + } - public get traceId(): string | undefined { - return this.getColumnValue('traceId') as string | undefined; - } + public set startTimeUnixNano(v: number | undefined) { + this.setColumnValue("startTimeUnixNano", v); + } - public set traceId(v: string | undefined) { - this.setColumnValue('traceId', v); - } + public get durationUnixNano(): number | undefined { + return this.getColumnValue("durationUnixNano") as number | undefined; + } - public get spanId(): string | undefined { - return this.getColumnValue('spanId') as string | undefined; - } + public set durationUnixNano(v: number | undefined) { + this.setColumnValue("durationUnixNano", v); + } - public set spanId(v: string | undefined) { - this.setColumnValue('spanId', v); - } + public get endTimeUnixNano(): number | undefined { + return this.getColumnValue("endTimeUnixNano") as number | undefined; + } - public get parentSpanId(): string | undefined { - return this.getColumnValue('parentSpanId') as string | undefined; - } + public set endTimeUnixNano(v: number | undefined) { + this.setColumnValue("endTimeUnixNano", v); + } - public set parentSpanId(v: string | undefined) { - this.setColumnValue('parentSpanId', v); - } + public get name(): string | undefined { + return this.getColumnValue("name") as string | undefined; + } - public get traceState(): string | undefined { - return this.getColumnValue('traceState') as string | undefined; - } + public set name(v: string | undefined) { + this.setColumnValue("name", v); + } - public set traceState(v: string | undefined) { - this.setColumnValue('traceState', v); - } + public get kind(): SpanKind | undefined { + return this.getColumnValue("kind") as SpanKind | undefined; + } - public get attributes(): JSONObject | undefined { - return this.getColumnValue('attributes') as JSONObject | undefined; - } + public set kind(v: SpanKind | undefined) { + this.setColumnValue("kind", v); + } - public set attributes(v: JSONObject | undefined) { - this.setColumnValue('attributes', v); - } + public get projectId(): ObjectID | undefined { + return this.getColumnValue("projectId") as ObjectID | undefined; + } - public get events(): Array<SpanEvent> | undefined { - return this.getColumnValue('events') as Array<SpanEvent> | undefined; - } + public set projectId(v: ObjectID | undefined) { + this.setColumnValue("projectId", v); + } - public set events(v: Array<SpanEvent> | undefined) { - this.setColumnValue('events', v as Array<JSONObject> | undefined); - } + public get serviceId(): ObjectID | undefined { + return this.getColumnValue("serviceId") as ObjectID | undefined; + } - public get links(): Array<SpanLink> | undefined { - return this.getColumnValue('links') as Array<SpanLink> | undefined; - } + public set serviceId(v: ObjectID | undefined) { + this.setColumnValue("serviceId", v); + } - public set links(v: Array<SpanLink> | undefined) { - this.setColumnValue('links', v as Array<JSONObject> | undefined); - } + public get startTime(): Date | undefined { + return this.getColumnValue("startTime") as Date | undefined; + } - public get statusCode(): SpanStatus | undefined { - return this.getColumnValue('statusCode') as SpanStatus | undefined; - } + public set startTime(v: Date | undefined) { + this.setColumnValue("startTime", v); + } - public set statusCode(v: SpanStatus | undefined) { - this.setColumnValue('statusCode', v); - } + public get endTime(): Date | undefined { + return this.getColumnValue("endTime") as Date | undefined; + } - public get statusMessage(): string | undefined { - return this.getColumnValue('statusMessage') as string | undefined; - } + public set endTime(v: Date | undefined) { + this.setColumnValue("endTime", v); + } - public set statusMessage(v: string | undefined) { - this.setColumnValue('statusMessage', v); - } + public get traceId(): string | undefined { + return this.getColumnValue("traceId") as string | undefined; + } + + public set traceId(v: string | undefined) { + this.setColumnValue("traceId", v); + } + + public get spanId(): string | undefined { + return this.getColumnValue("spanId") as string | undefined; + } + + public set spanId(v: string | undefined) { + this.setColumnValue("spanId", v); + } + + public get parentSpanId(): string | undefined { + return this.getColumnValue("parentSpanId") as string | undefined; + } + + public set parentSpanId(v: string | undefined) { + this.setColumnValue("parentSpanId", v); + } + + public get traceState(): string | undefined { + return this.getColumnValue("traceState") as string | undefined; + } + + public set traceState(v: string | undefined) { + this.setColumnValue("traceState", v); + } + + public get attributes(): JSONObject | undefined { + return this.getColumnValue("attributes") as JSONObject | undefined; + } + + public set attributes(v: JSONObject | undefined) { + this.setColumnValue("attributes", v); + } + + public get events(): Array<SpanEvent> | undefined { + return this.getColumnValue("events") as Array<SpanEvent> | undefined; + } + + public set events(v: Array<SpanEvent> | undefined) { + this.setColumnValue("events", v as Array<JSONObject> | undefined); + } + + public get links(): Array<SpanLink> | undefined { + return this.getColumnValue("links") as Array<SpanLink> | undefined; + } + + public set links(v: Array<SpanLink> | undefined) { + this.setColumnValue("links", v as Array<JSONObject> | undefined); + } + + public get statusCode(): SpanStatus | undefined { + return this.getColumnValue("statusCode") as SpanStatus | undefined; + } + + public set statusCode(v: SpanStatus | undefined) { + this.setColumnValue("statusCode", v); + } + + public get statusMessage(): string | undefined { + return this.getColumnValue("statusMessage") as string | undefined; + } + + public set statusMessage(v: string | undefined) { + this.setColumnValue("statusMessage", v); + } } diff --git a/Model/Models/AcmeCertificate.ts b/Model/Models/AcmeCertificate.ts index 2a86624662..707e88233c 100644 --- a/Model/Models/AcmeCertificate.ts +++ b/Model/Models/AcmeCertificate.ts @@ -1,142 +1,142 @@ -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableAccessControl({ - create: [], - read: [], - delete: [], - update: [], + create: [], + read: [], + delete: [], + update: [], }) @TableMetadata({ - tableName: 'AcmeCertificate', - singularName: 'Acme Certificate', - pluralName: 'Acme Certificate', - icon: IconProp.Lock, - tableDescription: 'Lets Encrypt Certificates', + tableName: "AcmeCertificate", + singularName: "Acme Certificate", + pluralName: "Acme Certificate", + icon: IconProp.Lock, + tableDescription: "Lets Encrypt Certificates", }) @Entity({ - name: 'AcmeCertificate', + name: "AcmeCertificate", }) export default class AcmeCertificate extends BaseModel { - @Index() - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: false, - }) - public domain?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public domain?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.VeryLongText }) - @Column({ - type: ColumnType.VeryLongText, - nullable: false, - unique: false, - }) - public certificate?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.VeryLongText }) + @Column({ + type: ColumnType.VeryLongText, + nullable: false, + unique: false, + }) + public certificate?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.VeryLongText }) - @Column({ - type: ColumnType.VeryLongText, - nullable: false, - unique: false, - }) - public certificateKey?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.VeryLongText }) + @Column({ + type: ColumnType.VeryLongText, + nullable: false, + unique: false, + }) + public certificateKey?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: false, - unique: false, - }) - public issuedAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: false, + unique: false, + }) + public issuedAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: false, - unique: false, - }) - public expiresAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: false, + unique: false, + }) + public expiresAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/AcmeChallenge.ts b/Model/Models/AcmeChallenge.ts index 5cdc1fabbc..855403127e 100644 --- a/Model/Models/AcmeChallenge.ts +++ b/Model/Models/AcmeChallenge.ts @@ -1,118 +1,118 @@ -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableAccessControl({ - create: [], - read: [], - delete: [], - update: [], + create: [], + read: [], + delete: [], + update: [], }) @TableMetadata({ - tableName: 'AcmeChallenge', - singularName: 'Acme Challenge', - pluralName: 'Acme Challenges', - icon: IconProp.Lock, - tableDescription: 'HTTP Challege for Lets Encrypt Certificates', + tableName: "AcmeChallenge", + singularName: "Acme Challenge", + pluralName: "Acme Challenges", + icon: IconProp.Lock, + tableDescription: "HTTP Challege for Lets Encrypt Certificates", }) @Entity({ - name: 'AcmeChallenge', + name: "AcmeChallenge", }) export default class AcmeChallenge extends BaseModel { - @Index() - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: false, - unique: false, - }) - public domain?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: false, + unique: false, + }) + public domain?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: false, - unique: false, - }) - public token?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: false, + unique: false, + }) + public token?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - nullable: false, - unique: false, - }) - public challenge?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + nullable: false, + unique: false, + }) + public challenge?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/ApiKey.ts b/Model/Models/ApiKey.ts index e51e02a9a3..c9a21975fc 100644 --- a/Model/Models/ApiKey.ts +++ b/Model/Models/ApiKey.ts @@ -1,388 +1,386 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @EnableDocumentation() -@TenantColumn('projectId') -@CrudApiEndpoint(new Route('/api-key')) -@SlugifyColumn('name', 'slug') +@TenantColumn("projectId") +@CrudApiEndpoint(new Route("/api-key")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'ApiKey', + name: "ApiKey", }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteProjectApiKey, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKeyPermissions, - Permission.EditProjectApiKey, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteProjectApiKey, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKeyPermissions, + Permission.EditProjectApiKey, + ], }) @TableMetadata({ - tableName: 'ApiKey', - singularName: 'API Key', - pluralName: 'API Keys', - icon: IconProp.Code, - tableDescription: 'Manage API Keys for your project', + tableName: "ApiKey", + singularName: "API Key", + pluralName: "API Keys", + icon: IconProp.Code, + tableDescription: "Manage API Keys for your project", }) export default class ApiKey extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKey, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name of this object', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKey, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name of this object", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKey, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKey, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKey, - ], - }) - @TableColumn({ - title: 'Expires At', - type: TableColumnType.Date, - required: true, - description: 'Date and Time when this API Key expires.', - }) - @Column({ - type: ColumnType.Date, - nullable: false, - }) - public expiresAt?: Date = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKey, + ], + }) + @TableColumn({ + title: "Expires At", + type: TableColumnType.Date, + required: true, + description: "Date and Time when this API Key expires.", + }) + @Column({ + type: ColumnType.Date, + nullable: false, + }) + public expiresAt?: Date = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKey, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - isDefaultValueColumn: false, - title: 'API Key', - description: 'Secret API Key', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public apiKey?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKey, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + isDefaultValueColumn: false, + title: "API Key", + description: "Secret API Key", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public apiKey?: ObjectID = undefined; } diff --git a/Model/Models/ApiKeyPermission.ts b/Model/Models/ApiKeyPermission.ts index 795244be6b..6178cff2ec 100644 --- a/Model/Models/ApiKeyPermission.ts +++ b/Model/Models/ApiKeyPermission.ts @@ -1,438 +1,434 @@ -import ApiKey from './ApiKey'; -import Label from './Label'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import ApiKey from "./ApiKey"; +import Label from "./Label"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @EnableDocumentation() @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - Permission.EditProjectApiKeyPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteProjectApiKey, - Permission.EditProjectApiKeyPermissions, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKeyPermissions, - Permission.EditProjectApiKey, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + Permission.EditProjectApiKeyPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteProjectApiKey, + Permission.EditProjectApiKeyPermissions, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKeyPermissions, + Permission.EditProjectApiKey, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@TenantColumn('projectId') -@CrudApiEndpoint(new Route('/api-key-permission')) +@TenantColumn("projectId") +@CrudApiEndpoint(new Route("/api-key-permission")) @Entity({ - name: 'ApiKeyPermission', + name: "ApiKeyPermission", }) @TableMetadata({ - tableName: 'ApiKeyPermission', - singularName: 'API Key Permission', - pluralName: 'API Key Permissions', - icon: IconProp.Lock, - tableDescription: 'Permissions for your API Keys', + tableName: "ApiKeyPermission", + singularName: "API Key Permission", + pluralName: "API Key Permissions", + icon: IconProp.Lock, + tableDescription: "Permissions for your API Keys", }) export default class APIKeyPermission extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - Permission.EditProjectApiKeyPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKeyPermissions, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'apiKeyId', - type: TableColumnType.Entity, - modelType: ApiKey, - title: 'Api Key', - description: - 'Relation to API Key resource in which this object belongs', - }) - @ManyToOne( - () => { - return ApiKey; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'apiKeyId' }) - public apiKey?: ApiKey = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + Permission.EditProjectApiKeyPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKeyPermissions, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "apiKeyId", + type: TableColumnType.Entity, + modelType: ApiKey, + title: "Api Key", + description: "Relation to API Key resource in which this object belongs", + }) + @ManyToOne( + () => { + return ApiKey; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "apiKeyId" }) + public apiKey?: ApiKey = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - Permission.EditProjectApiKeyPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'API Key ID', - description: 'ID of API Key resource in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public apiKeyId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + Permission.EditProjectApiKeyPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "API Key ID", + description: "ID of API Key resource in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public apiKeyId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - Permission.EditProjectApiKeyPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + Permission.EditProjectApiKeyPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - Permission.EditProjectApiKeyPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + Permission.EditProjectApiKeyPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [], + }) + @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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - Permission.EditProjectApiKeyPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKeyPermissions, - Permission.EditProjectApiKey, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.Permission, - title: 'Permission', - description: - 'Permission. You can find list of permissions on the Permissions page.', - }) - @UniqueColumnBy('apiKeyId') - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public permission?: Permission = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + Permission.EditProjectApiKeyPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKeyPermissions, + Permission.EditProjectApiKey, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.Permission, + title: "Permission", + description: + "Permission. You can find list of permissions on the Permissions page.", + }) + @UniqueColumnBy("apiKeyId") + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public permission?: Permission = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - Permission.EditProjectApiKeyPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKeyPermissions, - Permission.EditProjectApiKey, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this permission is scoped at.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'ApiKeyPermissionLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'apiKeyPermissionId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + Permission.EditProjectApiKeyPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKeyPermissions, + Permission.EditProjectApiKey, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: "Relation to Labels Array where this permission is scoped at.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "ApiKeyPermissionLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "apiKeyPermissionId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectApiKey, - Permission.EditProjectApiKeyPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectApiKey, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectApiKeyPermissions, - Permission.EditProjectApiKey, - ], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isBlockPermission?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectApiKey, + Permission.EditProjectApiKeyPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectApiKey, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectApiKeyPermissions, + Permission.EditProjectApiKey, + ], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isBlockPermission?: boolean = undefined; } diff --git a/Model/Models/BillingInvoice.ts b/Model/Models/BillingInvoice.ts index 78a5227507..dbd078f101 100644 --- a/Model/Models/BillingInvoice.ts +++ b/Model/Models/BillingInvoice.ts @@ -1,341 +1,339 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import AllowAccessIfSubscriptionIsUnpaid from "Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; export enum InvoiceStatus { - Paid = 'paid', - Draft = 'draft', - Void = 'void', - Uncollectible = 'uncollectible', - Deleted = 'deleted', - Open = 'open', - Undefined = '', + Paid = "paid", + Draft = "draft", + Void = "void", + Uncollectible = "uncollectible", + Deleted = "deleted", + Open = "open", + Undefined = "", } @AllowAccessIfSubscriptionIsUnpaid() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - delete: [], - update: [], + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/billing-invoices')) +@CrudApiEndpoint(new Route("/billing-invoices")) @TableMetadata({ - tableName: 'BillingInvoice', - singularName: 'Invoice', - pluralName: 'Invoices', - icon: IconProp.Invoice, - tableDescription: 'Manage invoices for your project', + tableName: "BillingInvoice", + singularName: "Invoice", + pluralName: "Invoices", + icon: IconProp.Invoice, + tableDescription: "Manage invoices for your project", }) @Entity({ - name: 'BillingInvoice', + name: "BillingInvoice", }) export default class BillingInvoice extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - 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.ReadInvoices, + ], + 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.ReadInvoices, - ], - 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.ReadInvoices, + ], + 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.ReadInvoices, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - update: [], - }) - @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: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + 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; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.Number }) - @Column({ - type: ColumnType.Decimal, - nullable: false, - unique: false, - }) - public amount?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.Number }) + @Column({ + type: ColumnType.Decimal, + nullable: false, + unique: false, + }) + public amount?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: false, - }) - public currencyCode?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public currencyCode?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.LongURL }) - @Column({ - type: ColumnType.LongURL, - nullable: false, - unique: false, - transformer: URL.getDatabaseTransformer(), - }) - public downloadableLink?: URL = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.LongURL }) + @Column({ + type: ColumnType.LongURL, + nullable: false, + unique: false, + transformer: URL.getDatabaseTransformer(), + }) + public downloadableLink?: URL = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: false, - }) - public status?: InvoiceStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public status?: InvoiceStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: false, - }) - public paymentProviderCustomerId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public paymentProviderCustomerId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public paymentProviderSubscriptionId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public paymentProviderSubscriptionId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadInvoices, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: false, - }) - public paymentProviderInvoiceId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadInvoices, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public paymentProviderInvoiceId?: string = undefined; } diff --git a/Model/Models/BillingPaymentMethod.ts b/Model/Models/BillingPaymentMethod.ts index 6c9c49554b..e810ef308e 100644 --- a/Model/Models/BillingPaymentMethod.ts +++ b/Model/Models/BillingPaymentMethod.ts @@ -1,347 +1,317 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import AllowAccessIfSubscriptionIsUnpaid from "Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @AllowAccessIfSubscriptionIsUnpaid() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod], - read: [ - Permission.ProjectOwner, - Permission.ProjectUser, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - delete: [Permission.ProjectOwner, Permission.DeleteBillingPaymentMethod], - update: [], + create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod], + read: [ + Permission.ProjectOwner, + Permission.ProjectUser, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + delete: [Permission.ProjectOwner, Permission.DeleteBillingPaymentMethod], + update: [], }) -@CrudApiEndpoint(new Route('/billing-payment-methods')) +@CrudApiEndpoint(new Route("/billing-payment-methods")) @TableMetadata({ - tableName: 'BillingPaymentMethod', - singularName: 'Payment Method', - pluralName: 'Payment Methods', - icon: IconProp.Billing, - tableDescription: - 'Manage billing payment methods like visa and master card for your project', + tableName: "BillingPaymentMethod", + singularName: "Payment Method", + pluralName: "Payment Methods", + icon: IconProp.Billing, + tableDescription: + "Manage billing payment methods like visa and master card for your project", }) @Entity({ - name: 'BillingPaymentMethod', + name: "BillingPaymentMethod", }) export default class BillingPaymentMethod extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, + @ColumnAccessControl({ + create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod], + read: [ + Permission.ProjectOwner, + Permission.ProjectUser, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + 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; - Permission.CreateBillingPaymentMethod, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectUser, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - 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.ProjectOwner, Permission.CreateBillingPaymentMethod], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + 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.ProjectOwner, + @ColumnAccessControl({ + create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - Permission.CreateBillingPaymentMethod, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - 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.ProjectOwner, Permission.CreateBillingPaymentMethod], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + update: [], + }) + @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.ProjectOwner, + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - Permission.CreateBillingPaymentMethod, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, + @ColumnAccessControl({ + create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public type?: string = undefined; - Permission.CreateBillingPaymentMethod, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - update: [], - }) - @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: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public paymentProviderPaymentMethodId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public paymentProviderCustomerId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - 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; + @ColumnAccessControl({ + create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: false, + }) + public last4Digits?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - - Permission.CreateBillingPaymentMethod, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: false, - }) - public type?: string = undefined; - - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: false, - }) - public paymentProviderPaymentMethodId?: string = undefined; - - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: false, - }) - public paymentProviderCustomerId?: string = undefined; - - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - - Permission.CreateBillingPaymentMethod, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: false, - }) - public last4Digits?: string = undefined; - - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - - Permission.CreateBillingPaymentMethod, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.ProjectMember, - Permission.ReadBillingPaymentMethod, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - nullable: true, - unique: false, - }) - public isDefault?: boolean = undefined; + @ColumnAccessControl({ + create: [Permission.ProjectOwner, Permission.CreateBillingPaymentMethod], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.ProjectMember, + Permission.ReadBillingPaymentMethod, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + nullable: true, + unique: false, + }) + public isDefault?: boolean = undefined; } diff --git a/Model/Models/CallLog.ts b/Model/Models/CallLog.ts index 2a13e91ab9..ee7aac190d 100644 --- a/Model/Models/CallLog.ts +++ b/Model/Models/CallLog.ts @@ -1,305 +1,303 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import CallStatus from 'Common/Types/Call/CallStatus'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Phone from 'Common/Types/Phone'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import CallStatus from "Common/Types/Call/CallStatus"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Phone from "Common/Types/Phone"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCallLog, - ], - delete: [], - update: [], + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCallLog, + ], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/call-log')) +@CrudApiEndpoint(new Route("/call-log")) @Entity({ - name: 'CallLog', + name: "CallLog", }) @EnableWorkflow({ - create: true, - delete: false, - update: false, - read: true, + create: true, + delete: false, + update: false, + read: true, }) @TableMetadata({ - tableName: 'CallLog', - singularName: 'Call Log', - pluralName: 'Call Logs', - icon: IconProp.Call, - tableDescription: - 'Logs of all the Call sent out to all users and subscribers for this project.', + tableName: "CallLog", + singularName: "Call Log", + pluralName: "Call Logs", + icon: IconProp.Call, + tableDescription: + "Logs of all the Call sent out to all users and subscribers for this project.", }) export default class CallLog extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCallLog, - ], - 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.ReadCallLog, + ], + 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.ReadCallLog, - ], - 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.ReadCallLog, + ], + 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.ReadCallLog, - ], - update: [], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.Phone, - title: 'To Number', - description: 'Phone Number Call was sent to', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.Phone, - length: ColumnLength.Phone, - transformer: Phone.getDatabaseTransformer(), - }) - public toNumber?: Phone = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCallLog, + ], + update: [], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.Phone, + title: "To Number", + description: "Phone Number Call was sent to", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.Phone, + length: ColumnLength.Phone, + transformer: Phone.getDatabaseTransformer(), + }) + public toNumber?: Phone = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCallLog, - ], - update: [], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.Phone, - title: 'From Number', - description: 'Phone Number Call was sent from', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.Phone, - length: ColumnLength.Phone, - transformer: Phone.getDatabaseTransformer(), - }) - public fromNumber?: Phone = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCallLog, + ], + update: [], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.Phone, + title: "From Number", + description: "Phone Number Call was sent from", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.Phone, + length: ColumnLength.Phone, + transformer: Phone.getDatabaseTransformer(), + }) + public fromNumber?: Phone = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCallLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.JSON, - title: 'Call Data', - description: 'Content of the data that was sent in the call', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.JSON, - }) - public callData?: JSON = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCallLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.JSON, + title: "Call Data", + description: "Content of the data that was sent in the call", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.JSON, + }) + public callData?: JSON = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCallLog, - ], - 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.ReadCallLog, + ], + 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.ReadCallLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Status of the Call', - description: 'Status of the Call sent', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public status?: CallStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCallLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Status of the Call", + description: "Status of the Call sent", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public status?: CallStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCallLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.Number, - title: 'Call Cost', - description: 'Call Cost in USD Cents', - canReadOnRelationQuery: false, - isDefaultValueColumn: true, - }) - @Column({ - nullable: false, - default: 0, - type: ColumnType.Number, - }) - public callCostInUSDCents?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCallLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.Number, + title: "Call Cost", + description: "Call Cost in USD Cents", + canReadOnRelationQuery: false, + isDefaultValueColumn: true, + }) + @Column({ + nullable: false, + default: 0, + type: ColumnType.Number, + }) + public callCostInUSDCents?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/CodeRepository.ts b/Model/Models/CodeRepository.ts index e1f590d197..c2a434d81a 100644 --- a/Model/Models/CodeRepository.ts +++ b/Model/Models/CodeRepository.ts @@ -1,608 +1,606 @@ -import Label from './Label'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import CodeRepositoryType from 'Common/Types/CodeRepository/CodeRepositoryType'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Label from "./Label"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import CodeRepositoryType from "Common/Types/CodeRepository/CodeRepositoryType"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; -@AccessControlColumn('labels') +@AccessControlColumn("labels") @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteCodeRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditCodeRepository, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteCodeRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditCodeRepository, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/code-repository')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/code-repository")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'CodeRepository', - singularName: 'Git Repository', - pluralName: 'Git Repositories', - icon: IconProp.SquareStack, - tableDescription: - 'A Git Repository is a place where you can store your code and collaborate with others. You can connect your Git Repository to OneUptime and we will improve your code automatically for you.', + tableName: "CodeRepository", + singularName: "Git Repository", + pluralName: "Git Repositories", + icon: IconProp.SquareStack, + tableDescription: + "A Git Repository is a place where you can store your code and collaborate with others. You can connect your Git Repository to OneUptime and we will improve your code automatically for you.", }) @Entity({ - name: 'CodeRepository', + name: "CodeRepository", }) export default class CodeRepository extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditCodeRepository, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditCodeRepository, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditCodeRepository, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditCodeRepository, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditCodeRepository, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'CodeRepositoryLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'codeRepositoryId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditCodeRepository, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "CodeRepositoryLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "codeRepositoryId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditCodeRepository, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - isDefaultValueColumn: false, - title: 'Secret Token', - description: - 'Secret Token for this code repository. This is used to connect this code repository to OneUptime.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public secretToken?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditCodeRepository, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + isDefaultValueColumn: false, + title: "Secret Token", + description: + "Secret Token for this code repository. This is used to connect this code repository to OneUptime.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public secretToken?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditCodeRepository, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Main Branch Name', - description: 'Name of the main branch of this repository', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - default: 'master', - }) - public mainBranchName?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditCodeRepository, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Main Branch Name", + description: "Name of the main branch of this repository", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + default: "master", + }) + public mainBranchName?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditCodeRepository, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Repository Hosted At', - description: - 'Where is this repository hosted at? GitHub, GitLab, Bitbucket, etc.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - default: CodeRepositoryType.GitHub, - }) - public repositoryHostedAt?: CodeRepositoryType = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditCodeRepository, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Repository Hosted At", + description: + "Where is this repository hosted at? GitHub, GitLab, Bitbucket, etc.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + default: CodeRepositoryType.GitHub, + }) + public repositoryHostedAt?: CodeRepositoryType = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditCodeRepository, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Organization Name', - description: - 'Name of the organization where this repo belongs. Eg: Your GitHub Organization Name', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public organizationName?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditCodeRepository, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Organization Name", + description: + "Name of the organization where this repo belongs. Eg: Your GitHub Organization Name", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public organizationName?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateCodeRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadCodeRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditCodeRepository, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Repository Name', - description: 'Name of the repository. Eg: Your GitHub Repository Name', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public repositoryName?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateCodeRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadCodeRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditCodeRepository, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Repository Name", + description: "Name of the repository. Eg: Your GitHub Repository Name", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public repositoryName?: string = undefined; } diff --git a/Model/Models/CopilotEvent.ts b/Model/Models/CopilotEvent.ts index e105f5d02d..adf52273d6 100644 --- a/Model/Models/CopilotEvent.ts +++ b/Model/Models/CopilotEvent.ts @@ -1,512 +1,509 @@ -import CodeRepository from './CodeRepository'; -import Project from './Project'; -import ServiceCatalog from './ServiceCatalog'; -import ServiceRepository from './ServiceRepository'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import CopilotEventStatus from 'Common/Types/Copilot/CopilotEventStatus'; -import CopilotEventType from 'Common/Types/Copilot/CopilotEventType'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import CodeRepository from "./CodeRepository"; +import Project from "./Project"; +import ServiceCatalog from "./ServiceCatalog"; +import ServiceRepository from "./ServiceRepository"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import CopilotEventStatus from "Common/Types/Copilot/CopilotEventStatus"; +import CopilotEventType from "Common/Types/Copilot/CopilotEventType"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@CanAccessIfCanReadOn('codeRepository') +@CanAccessIfCanReadOn("codeRepository") @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - delete: [], - update: [], + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + delete: [], + update: [], }) @EnableWorkflow({ - create: true, - delete: false, - update: true, - read: false, + create: true, + delete: false, + update: true, + read: false, }) -@CrudApiEndpoint(new Route('/copilot-event')) +@CrudApiEndpoint(new Route("/copilot-event")) @TableMetadata({ - tableName: 'CopilotEvent', - singularName: 'Copilot Event', - pluralName: 'Copilot Events', - icon: IconProp.Bolt, - tableDescription: 'Copilot Event Resource', + tableName: "CopilotEvent", + singularName: "Copilot Event", + pluralName: "Copilot Events", + icon: IconProp.Bolt, + tableDescription: "Copilot Event Resource", }) @Entity({ - name: 'CopilotEvent', + name: "CopilotEvent", }) export default class CopilotEvent extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - 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.ReadCopilotEvent, + ], + 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.ReadCopilotEvent, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'codeRepositoryId', - type: TableColumnType.Entity, - modelType: CodeRepository, - title: 'Code Repository', - description: - 'Relation to CodeRepository Resource in which this object belongs', - }) - @ManyToOne( - () => { - return CodeRepository; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'codeRepositoryId' }) - public codeRepository?: CodeRepository = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "codeRepositoryId", + type: TableColumnType.Entity, + modelType: CodeRepository, + title: "Code Repository", + description: + "Relation to CodeRepository Resource in which this object belongs", + }) + @ManyToOne( + () => { + return CodeRepository; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "codeRepositoryId" }) + public codeRepository?: CodeRepository = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Code Repository ID', - description: - 'ID of your OneUptime Code Repository in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public codeRepositoryId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Code Repository ID", + description: + "ID of your OneUptime Code Repository in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public codeRepositoryId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @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: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + 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; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.LongText, - title: 'File Path in Code Repository', - required: true, - description: - 'File Path in Code Repository where this event was triggered', - }) - @Column({ - type: ColumnType.LongText, - nullable: false, - }) - public filePath?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.LongText, + title: "File Path in Code Repository", + required: true, + description: "File Path in Code Repository where this event was triggered", + }) + @Column({ + type: ColumnType.LongText, + nullable: false, + }) + public filePath?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.LongText, - title: 'Commit Hash', - description: - 'Commit Hash of the commit for this file in Code Repository where this event was triggered', - }) - @Column({ - type: ColumnType.LongText, - nullable: false, - }) - public commitHash?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.LongText, + title: "Commit Hash", + description: + "Commit Hash of the commit for this file in Code Repository where this event was triggered", + }) + @Column({ + type: ColumnType.LongText, + nullable: false, + }) + public commitHash?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Copilot Event Type', - description: - 'Type of Copilot Event that was triggered for this file in Code Repository', - }) - @Column({ - type: ColumnType.ShortText, - nullable: false, - }) - public copilotEventType?: CopilotEventType = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Copilot Event Type", + description: + "Type of Copilot Event that was triggered for this file in Code Repository", + }) + @Column({ + type: ColumnType.ShortText, + nullable: false, + }) + public copilotEventType?: CopilotEventType = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'serviceCatalogId', - type: TableColumnType.Entity, - modelType: ServiceCatalog, - title: 'Service Catalog', - description: - 'Relation to Service Catalog Resource in which this object belongs', - }) - @ManyToOne( - () => { - return ServiceCatalog; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'serviceCatalogId' }) - public serviceCatalog?: ServiceCatalog = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "serviceCatalogId", + type: TableColumnType.Entity, + modelType: ServiceCatalog, + title: "Service Catalog", + description: + "Relation to Service Catalog Resource in which this object belongs", + }) + @ManyToOne( + () => { + return ServiceCatalog; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "serviceCatalogId" }) + public serviceCatalog?: ServiceCatalog = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Service Catalog ID', - description: - 'ID of your OneUptime ServiceCatalog in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public serviceCatalogId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Service Catalog ID", + description: + "ID of your OneUptime ServiceCatalog in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public serviceCatalogId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'serviceRepositoryId', - type: TableColumnType.Entity, - modelType: ServiceRepository, - title: 'Service Repository', - description: - 'Relation to Service Repository Resource in which this object belongs', - }) - @ManyToOne( - () => { - return ServiceRepository; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'serviceRepositoryId' }) - public serviceRepository?: ServiceRepository = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "serviceRepositoryId", + type: TableColumnType.Entity, + modelType: ServiceRepository, + title: "Service Repository", + description: + "Relation to Service Repository Resource in which this object belongs", + }) + @ManyToOne( + () => { + return ServiceRepository; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "serviceRepositoryId" }) + public serviceRepository?: ServiceRepository = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Service Repository ID', - description: - 'ID of your OneUptime Service Repository in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public serviceRepositoryId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Service Repository ID", + description: + "ID of your OneUptime Service Repository in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public serviceRepositoryId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - required: false, - isDefaultValueColumn: false, - title: 'Pull Request ID', - description: - 'ID of Pull Request in the repository where this event was executed and then PR was created.', - }) - @Column({ - type: ColumnType.ShortText, - nullable: true, - }) - public pullRequestId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + required: false, + isDefaultValueColumn: false, + title: "Pull Request ID", + description: + "ID of Pull Request in the repository where this event was executed and then PR was created.", + }) + @Column({ + type: ColumnType.ShortText, + nullable: true, + }) + public pullRequestId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadCopilotEvent, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Copilot Event Status', - description: - 'Status of Copilot Event that was triggered for this file in Code Repository', - }) - @Column({ - type: ColumnType.ShortText, - nullable: false, - }) - public copilotEventStatus?: CopilotEventStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadCopilotEvent, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Copilot Event Status", + description: + "Status of Copilot Event that was triggered for this file in Code Repository", + }) + @Column({ + type: ColumnType.ShortText, + nullable: false, + }) + public copilotEventStatus?: CopilotEventStatus = undefined; } diff --git a/Model/Models/DataMigration.ts b/Model/Models/DataMigration.ts index 9df97bc1d0..f2e0ba4f06 100644 --- a/Model/Models/DataMigration.ts +++ b/Model/Models/DataMigration.ts @@ -1,183 +1,183 @@ -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; @TableAccessControl({ - create: [], - read: [], - delete: [], - update: [], + create: [], + read: [], + delete: [], + update: [], }) @TableMetadata({ - tableName: 'DataMigration', - singularName: 'Data Migration', - pluralName: 'Data Migrations', - icon: IconProp.Database, - tableDescription: 'Log of all the database migrations executed.', + tableName: "DataMigration", + singularName: "Data Migration", + pluralName: "Data Migrations", + icon: IconProp.Database, + tableDescription: "Log of all the database migrations executed.", }) @Entity({ - name: 'DataMigrations', + name: "DataMigrations", }) export default class DataMigration extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Name of the migration.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Name of the migration.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.Boolean, - title: 'Executed', - description: 'Did the migration execute successfully?', - }) - @Column({ - nullable: false, - type: ColumnType.Boolean, - default: true, - }) - public executed?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.Boolean, + title: "Executed", + description: "Did the migration execute successfully?", + }) + @Column({ + nullable: false, + type: ColumnType.Boolean, + default: true, + }) + public executed?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.Date, - title: 'Executed At', - description: 'When was the migration executed?', - }) - @Column({ - nullable: false, - type: ColumnType.Date, - }) - public executedAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.Date, + title: "Executed At", + description: "When was the migration executed?", + }) + @Column({ + nullable: false, + type: ColumnType.Date, + }) + public executedAt?: Date = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @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: [], + read: [], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/Domain.ts b/Model/Models/Domain.ts index 2e455bd936..8d1330f6f3 100644 --- a/Model/Models/Domain.ts +++ b/Model/Models/Domain.ts @@ -1,356 +1,354 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import DomainType from 'Common/Types/Domain'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import DomainType from "Common/Types/Domain"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteProjectDomain, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectDomain, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteProjectDomain, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectDomain, + ], }) -@CrudApiEndpoint(new Route('/domain')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/domain")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'Domain', - singularName: 'Domain', - pluralName: 'Domains', - icon: IconProp.Globe, - tableDescription: 'Manage Custom Domains for your project', + tableName: "Domain", + singularName: "Domain", + pluralName: "Domains", + icon: IconProp.Globe, + tableDescription: "Manage Custom Domains for your project", }) @Entity({ - name: 'Domain', + name: "Domain", }) export default class Domain extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectDomain, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.Domain, - canReadOnRelationQuery: true, - title: 'Domain', - description: 'Domain - acmeinc.com for example.', - }) - @Column({ - nullable: false, - type: ColumnType.Domain, - length: ColumnLength.Domain, - transformer: DomainType.getDatabaseTransformer(), - }) - @UniqueColumnBy('projectId') - public domain?: DomainType = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectDomain, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.Domain, + canReadOnRelationQuery: true, + title: "Domain", + description: "Domain - acmeinc.com for example.", + }) + @Column({ + nullable: false, + type: ColumnType.Domain, + length: ColumnLength.Domain, + transformer: DomainType.getDatabaseTransformer(), + }) + @UniqueColumnBy("projectId") + public domain?: DomainType = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + 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; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectDomain, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Verified', - description: 'Is this domain verified?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isVerified?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectDomain, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Verified", + description: "Is this domain verified?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isVerified?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectDomain, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Domain Verification Text', - description: - 'Verification text that you need to add to your domains TXT record to veify the domain.', - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: false, - unique: true, - }) - public domainVerificationText?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectDomain, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Domain Verification Text", + description: + "Verification text that you need to add to your domains TXT record to veify the domain.", + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: false, + unique: true, + }) + public domainVerificationText?: string = undefined; } diff --git a/Model/Models/EmailLog.ts b/Model/Models/EmailLog.ts index eb78684689..7f8601dc62 100644 --- a/Model/Models/EmailLog.ts +++ b/Model/Models/EmailLog.ts @@ -1,340 +1,337 @@ -import Project from './Project'; -import ProjectSmtpConfig from './ProjectSmtpConfig'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import Email from 'Common/Types/Email'; -import IconProp from 'Common/Types/Icon/IconProp'; -import MailStatus from 'Common/Types/Mail/MailStatus'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import ProjectSmtpConfig from "./ProjectSmtpConfig"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import Email from "Common/Types/Email"; +import IconProp from "Common/Types/Icon/IconProp"; +import MailStatus from "Common/Types/Mail/MailStatus"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadEmailLog, - ], - delete: [], - update: [], + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadEmailLog, + ], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/email-log')) +@CrudApiEndpoint(new Route("/email-log")) @Entity({ - name: 'EmailLog', + name: "EmailLog", }) @EnableWorkflow({ - create: true, - delete: false, - update: false, - read: true, + create: true, + delete: false, + update: false, + read: true, }) @TableMetadata({ - tableName: 'EmailLog', - singularName: 'Email Log', - pluralName: 'Email Logs', - icon: IconProp.Email, - tableDescription: - 'Logs of all the Email sent out to all users and subscribers for this project.', + tableName: "EmailLog", + singularName: "Email Log", + pluralName: "Email Logs", + icon: IconProp.Email, + tableDescription: + "Logs of all the Email sent out to all users and subscribers for this project.", }) export default class EmailLog extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadEmailLog, - ], - 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.ReadEmailLog, + ], + 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.ReadEmailLog, - ], - 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.ReadEmailLog, + ], + 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.ReadEmailLog, - ], - update: [], - }) - @Index() - @TableColumn({ - required: false, - type: TableColumnType.Email, - title: 'To Email', - description: 'Email address where the mail was sent', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: true, - type: ColumnType.Email, - length: ColumnLength.Email, - transformer: Email.getDatabaseTransformer(), - }) - public toEmail?: Email = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadEmailLog, + ], + update: [], + }) + @Index() + @TableColumn({ + required: false, + type: TableColumnType.Email, + title: "To Email", + description: "Email address where the mail was sent", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: true, + type: ColumnType.Email, + length: ColumnLength.Email, + transformer: Email.getDatabaseTransformer(), + }) + public toEmail?: Email = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadEmailLog, - ], - update: [], - }) - @Index() - @TableColumn({ - required: false, - type: TableColumnType.Email, - title: 'From Email', - description: 'Email address where the mail was sent from', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: true, - type: ColumnType.Email, - length: ColumnLength.Email, - transformer: Email.getDatabaseTransformer(), - }) - public fromEmail?: Email = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadEmailLog, + ], + update: [], + }) + @Index() + @TableColumn({ + required: false, + type: TableColumnType.Email, + title: "From Email", + description: "Email address where the mail was sent from", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: true, + type: ColumnType.Email, + length: ColumnLength.Email, + transformer: Email.getDatabaseTransformer(), + }) + public fromEmail?: Email = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadEmailLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - title: 'Email Subject', - description: 'Subject of the email sent', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public subject?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadEmailLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + title: "Email Subject", + description: "Subject of the email sent", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public subject?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadEmailLog, - ], - 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.ReadEmailLog, + ], + 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.ReadEmailLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Status of the SMS', - description: 'Status of the SMS sent', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public status?: MailStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadEmailLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Status of the SMS", + description: "Status of the SMS sent", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public status?: MailStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadEmailLog, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'projectSmtpConfigId', - type: TableColumnType.Entity, - modelType: ProjectSmtpConfig, - title: 'Project Smtp Config Id', - description: - 'Relation to ProjectSmtpConfig resource in which this object belongs', - }) - @ManyToOne( - () => { - return ProjectSmtpConfig; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectSmtpConfigId' }) - public projectSmtpConfig?: ProjectSmtpConfig = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadEmailLog, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "projectSmtpConfigId", + type: TableColumnType.Entity, + modelType: ProjectSmtpConfig, + title: "Project Smtp Config Id", + description: + "Relation to ProjectSmtpConfig resource in which this object belongs", + }) + @ManyToOne( + () => { + return ProjectSmtpConfig; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectSmtpConfigId" }) + public projectSmtpConfig?: ProjectSmtpConfig = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadEmailLog, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Project Smtp Config ID', - description: - 'ID of your Project Smtp Config in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public projectSmtpConfigId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadEmailLog, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Project Smtp Config ID", + description: "ID of your Project Smtp Config in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public projectSmtpConfigId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/EmailVerificationToken.ts b/Model/Models/EmailVerificationToken.ts index 5e20a977c9..984cb69d2a 100755 --- a/Model/Models/EmailVerificationToken.ts +++ b/Model/Models/EmailVerificationToken.ts @@ -1,163 +1,163 @@ -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import Email from 'Common/Types/Email'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import Email from "Common/Types/Email"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@CrudApiEndpoint(new Route('/email-verification-token')) +@CrudApiEndpoint(new Route("/email-verification-token")) @Entity({ - name: 'EmailVerificationToken', + name: "EmailVerificationToken", }) @TableMetadata({ - tableName: 'EmailVerificationToken', - singularName: 'Email Verification Token', - pluralName: 'Email Verification Tokens', - icon: IconProp.Email, - tableDescription: 'Email verification token is stored here', + tableName: "EmailVerificationToken", + singularName: "Email Verification Token", + pluralName: "Email Verification Tokens", + icon: IconProp.Email, + tableDescription: "Email verification token is stored here", }) @TableAccessControl({ - create: [], - read: [], - delete: [], - update: [], + create: [], + read: [], + delete: [], + update: [], }) export default class EmailVerificationToken extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - required: true, - type: TableColumnType.Entity, - modelType: User, - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + required: true, + type: TableColumnType.Entity, + modelType: User, + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ required: true, type: TableColumnType.ObjectID }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ required: true, type: TableColumnType.ObjectID }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Email }) - @Column({ - type: ColumnType.Email, - length: ColumnLength.Email, - nullable: false, - transformer: Email.getDatabaseTransformer(), - }) - public email?: Email = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Email }) + @Column({ + type: ColumnType.Email, + length: ColumnLength.Email, + nullable: false, + transformer: Email.getDatabaseTransformer(), + }) + public email?: Email = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.ObjectID, - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - unique: true, + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.ObjectID, + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + unique: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public token?: ObjectID = undefined; + transformer: ObjectID.getDatabaseTransformer(), + }) + public token?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ required: true, type: TableColumnType.Date }) - @Column({ - nullable: false, - type: ColumnType.Date, - }) - public expires?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ required: true, type: TableColumnType.Date }) + @Column({ + nullable: false, + type: ColumnType.Date, + }) + public expires?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/File.ts b/Model/Models/File.ts index 48e7ffd192..8c23c7f5a1 100644 --- a/Model/Models/File.ts +++ b/Model/Models/File.ts @@ -1,27 +1,27 @@ -import FileModel from 'Common/Models/FileModel'; -import Route from 'Common/Types/API/Route'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Permission from 'Common/Types/Permission'; -import { Entity } from 'typeorm'; +import FileModel from "Common/Models/FileModel"; +import Route from "Common/Types/API/Route"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import Permission from "Common/Types/Permission"; +import { Entity } from "typeorm"; @TableMetadata({ - tableName: 'File', - singularName: 'File', - pluralName: 'Files', - icon: IconProp.File, - tableDescription: 'BLOB or File storage', + tableName: "File", + singularName: "File", + pluralName: "Files", + icon: IconProp.File, + tableDescription: "BLOB or File storage", }) @Entity({ - name: 'File', + name: "File", }) -@CrudApiEndpoint(new Route('/file')) +@CrudApiEndpoint(new Route("/file")) @TableAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - delete: [], - update: [], + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + delete: [], + update: [], }) export default class File extends FileModel {} diff --git a/Model/Models/GlobalConfig.ts b/Model/Models/GlobalConfig.ts index b0483797d5..9c0679a1ac 100644 --- a/Model/Models/GlobalConfig.ts +++ b/Model/Models/GlobalConfig.ts @@ -1,371 +1,371 @@ -import GlobalConfigModel from 'Common/Models/GlobalConfig'; -import Hostname from 'Common/Types/API/Hostname'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import Email from 'Common/Types/Email'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Phone from 'Common/Types/Phone'; -import Port from 'Common/Types/Port'; -import { Column, Entity } from 'typeorm'; +import GlobalConfigModel from "Common/Models/GlobalConfig"; +import Hostname from "Common/Types/API/Hostname"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import Email from "Common/Types/Email"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Phone from "Common/Types/Phone"; +import Port from "Common/Types/Port"; +import { Column, Entity } from "typeorm"; export enum EmailServerType { - Internal = 'Internal', - Sendgrid = 'Sendgrid', - CustomSMTP = 'Custom SMTP', + Internal = "Internal", + Sendgrid = "Sendgrid", + CustomSMTP = "Custom SMTP", } @TableMetadata({ - tableName: 'GlobalConfig', - singularName: 'Global Config', - pluralName: 'Global Configs', - icon: IconProp.Settings, - tableDescription: 'Settings for OneUptime Server', + tableName: "GlobalConfig", + singularName: "Global Config", + pluralName: "Global Configs", + icon: IconProp.Settings, + tableDescription: "Settings for OneUptime Server", }) @Entity({ - name: 'GlobalConfig', + name: "GlobalConfig", }) -@CrudApiEndpoint(new Route('/global-config')) +@CrudApiEndpoint(new Route("/global-config")) @TableAccessControl({ - create: [], - read: [], - delete: [], - update: [], + create: [], + read: [], + delete: [], + update: [], }) export default class GlobalConfig extends GlobalConfigModel { - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.Boolean, - title: 'Disable Signup', - description: 'Should we disable new user sign up to this server?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: true, - default: false, - unique: true, - }) - public disableSignup?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.Boolean, + title: "Disable Signup", + description: "Should we disable new user sign up to this server?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: true, + default: false, + unique: true, + }) + public disableSignup?: boolean = undefined; - // SMTP Settings. + // SMTP Settings. - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.Boolean, - title: 'Is SMTP Secure', - description: 'Is this SMTP server hosted with SSL/TLS?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: true, - unique: true, - }) - public isSMTPSecure?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.Boolean, + title: "Is SMTP Secure", + description: "Is this SMTP server hosted with SSL/TLS?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: true, + unique: true, + }) + public isSMTPSecure?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'SMTP Username', - description: 'Username for your SMTP Server', - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: true, - }) - public smtpUsername?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "SMTP Username", + description: "Username for your SMTP Server", + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: true, + }) + public smtpUsername?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'SMTP Password', - description: 'Password for your SMTP Server', - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: true, - }) - public smtpPassword?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "SMTP Password", + description: "Password for your SMTP Server", + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: true, + }) + public smtpPassword?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.Number, - title: 'SMTP Port', - description: 'Port for your SMTP Server', - }) - @Column({ - type: ColumnType.Number, - nullable: true, - unique: true, - transformer: Port.getDatabaseTransformer(), - }) - public smtpPort?: Port = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.Number, + title: "SMTP Port", + description: "Port for your SMTP Server", + }) + @Column({ + type: ColumnType.Number, + nullable: true, + unique: true, + transformer: Port.getDatabaseTransformer(), + }) + public smtpPort?: Port = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'SMTP Host', - description: 'Host for your SMTP Server', - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: true, - transformer: Hostname.getDatabaseTransformer(), - }) - public smtpHost?: Hostname = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "SMTP Host", + description: "Host for your SMTP Server", + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: true, + transformer: Hostname.getDatabaseTransformer(), + }) + public smtpHost?: Hostname = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.Email, - title: 'SMTP From Email', - description: 'Which email should we send mail from?', - }) - @Column({ - type: ColumnType.Email, - length: ColumnLength.Email, - nullable: true, - unique: true, - transformer: Email.getDatabaseTransformer(), - }) - public smtpFromEmail?: Email = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.Email, + title: "SMTP From Email", + description: "Which email should we send mail from?", + }) + @Column({ + type: ColumnType.Email, + length: ColumnLength.Email, + nullable: true, + unique: true, + transformer: Email.getDatabaseTransformer(), + }) + public smtpFromEmail?: Email = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'SMTP From Name', - description: 'Which name should we send emails from?', - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: true, - }) - public smtpFromName?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "SMTP From Name", + description: "Which name should we send emails from?", + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: true, + }) + public smtpFromName?: string = undefined; - // Twilio config. + // Twilio config. - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Twilio Account SID', - description: 'Account SID for your Twilio Account', - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: true, - }) - public twilioAccountSID?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Twilio Account SID", + description: "Account SID for your Twilio Account", + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: true, + }) + public twilioAccountSID?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Twilio Auth Token', - description: 'Auth Token for your Twilio Account', - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: true, - }) - public twilioAuthToken?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Twilio Auth Token", + description: "Auth Token for your Twilio Account", + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: true, + }) + public twilioAuthToken?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.Phone, - title: 'Twilio Phone Number', - description: 'Phone Number for your Twilio account', - }) - @Column({ - type: ColumnType.Phone, - length: ColumnLength.Phone, - nullable: true, - unique: true, - transformer: Phone.getDatabaseTransformer(), - }) - public twilioPhoneNumber?: Phone = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.Phone, + title: "Twilio Phone Number", + description: "Phone Number for your Twilio account", + }) + @Column({ + type: ColumnType.Phone, + length: ColumnLength.Phone, + nullable: true, + unique: true, + transformer: Phone.getDatabaseTransformer(), + }) + public twilioPhoneNumber?: Phone = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Email Server Type', - description: 'Email Server Type', - }) - @Column({ - type: ColumnType.ShortText, - nullable: true, - unique: true, - }) - public emailServerType?: EmailServerType = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Email Server Type", + description: "Email Server Type", + }) + @Column({ + type: ColumnType.ShortText, + nullable: true, + unique: true, + }) + public emailServerType?: EmailServerType = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Sendgrid API Key', - description: 'Sendgrid API Key', - }) - @Column({ - type: ColumnType.ShortText, - nullable: true, - unique: true, - }) - public sendgridApiKey?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Sendgrid API Key", + description: "Sendgrid API Key", + }) + @Column({ + type: ColumnType.ShortText, + nullable: true, + unique: true, + }) + public sendgridApiKey?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.Email, - title: 'Sendgrid From Email', - description: 'Sendgrid From Email', - }) - @Column({ - type: ColumnType.Email, - nullable: true, - unique: true, - }) - public sendgridFromEmail?: Email = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.Email, + title: "Sendgrid From Email", + description: "Sendgrid From Email", + }) + @Column({ + type: ColumnType.Email, + nullable: true, + unique: true, + }) + public sendgridFromEmail?: Email = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Sendgrid From Name', - description: 'Sendgrid From Name', - }) - @Column({ - type: ColumnType.ShortText, - nullable: true, - unique: true, - }) - public sendgridFromName?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Sendgrid From Name", + description: "Sendgrid From Name", + }) + @Column({ + type: ColumnType.ShortText, + nullable: true, + unique: true, + }) + public sendgridFromName?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.Boolean, - title: 'Is Master API Key Enabled', - description: 'Is Master API Key Enabled?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: true, - unique: true, - default: false, - }) - public isMasterApiKeyEnabled?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.Boolean, + title: "Is Master API Key Enabled", + description: "Is Master API Key Enabled?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: true, + unique: true, + default: false, + }) + public isMasterApiKeyEnabled?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Master API Key', - description: - 'This API key has root access to all the resources in all the projects on OneUptime.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - unique: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public masterApiKey?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Master API Key", + description: + "This API key has root access to all the resources in all the projects on OneUptime.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + unique: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public masterApiKey?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - type: TableColumnType.Email, - title: 'Admin Notification Email', - description: - 'Email to send admin notifications to (when probes are offline, etc.)', - }) - @Column({ - type: ColumnType.Email, - length: ColumnLength.Email, - nullable: true, - unique: true, - transformer: Email.getDatabaseTransformer(), - }) - public adminNotificationEmail?: Email = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + type: TableColumnType.Email, + title: "Admin Notification Email", + description: + "Email to send admin notifications to (when probes are offline, etc.)", + }) + @Column({ + type: ColumnType.Email, + length: ColumnLength.Email, + nullable: true, + unique: true, + transformer: Email.getDatabaseTransformer(), + }) + public adminNotificationEmail?: Email = undefined; } diff --git a/Model/Models/GreenlockCertificate.ts b/Model/Models/GreenlockCertificate.ts index 1887d670a0..bad525de32 100644 --- a/Model/Models/GreenlockCertificate.ts +++ b/Model/Models/GreenlockCertificate.ts @@ -1,117 +1,117 @@ -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableAccessControl({ - create: [], - read: [], - delete: [], - update: [], + create: [], + read: [], + delete: [], + update: [], }) @TableMetadata({ - tableName: 'GreenlockCertificate', - singularName: 'Greenlock Certificate', - pluralName: 'Greenlock Certificate', - icon: IconProp.Lock, - tableDescription: 'Lets Encrypt Certificates', + tableName: "GreenlockCertificate", + singularName: "Greenlock Certificate", + pluralName: "Greenlock Certificate", + icon: IconProp.Lock, + tableDescription: "Lets Encrypt Certificates", }) @Entity({ - name: 'GreenlockCertificate', + name: "GreenlockCertificate", }) export default class GreenlockCertificate extends BaseModel { - @Index() - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: false, - unique: false, - }) - public key?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: false, + unique: false, + }) + public key?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.VeryLongText }) - @Column({ - type: ColumnType.VeryLongText, - nullable: false, - unique: false, - }) - public blob?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.VeryLongText }) + @Column({ + type: ColumnType.VeryLongText, + nullable: false, + unique: false, + }) + public blob?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - unique: false, - }) - public isKeyPair?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + unique: false, + }) + public isKeyPair?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/GreenlockChallenge.ts b/Model/Models/GreenlockChallenge.ts index c538692906..ca23a0f998 100644 --- a/Model/Models/GreenlockChallenge.ts +++ b/Model/Models/GreenlockChallenge.ts @@ -1,118 +1,118 @@ -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableAccessControl({ - create: [], - read: [], - delete: [], - update: [], + create: [], + read: [], + delete: [], + update: [], }) @TableMetadata({ - tableName: 'GreenlockChallenge', - singularName: 'Greenlock Challenge', - pluralName: 'Greenlock Challenges', - icon: IconProp.Lock, - tableDescription: 'HTTP Challege for Lets Encrypt Certificates', + tableName: "GreenlockChallenge", + singularName: "Greenlock Challenge", + pluralName: "Greenlock Challenges", + icon: IconProp.Lock, + tableDescription: "HTTP Challege for Lets Encrypt Certificates", }) @Entity({ - name: 'GreenlockChallenge', + name: "GreenlockChallenge", }) export default class GreenlockChallenge extends BaseModel { - @Index() - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: false, - unique: false, - }) - public key?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: false, + unique: false, + }) + public key?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: false, - unique: false, - }) - public token?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: false, + unique: false, + }) + public token?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - nullable: false, - unique: false, - }) - public challenge?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + nullable: false, + unique: false, + }) + public challenge?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/Incident.ts b/Model/Models/Incident.ts index 19ad077d49..0dc1b52efe 100644 --- a/Model/Models/Incident.ts +++ b/Model/Models/Incident.ts @@ -1,1019 +1,1018 @@ -import IncidentSeverity from './IncidentSeverity'; -import IncidentState from './IncidentState'; -import Label from './Label'; -import Monitor from './Monitor'; -import MonitorStatus from './MonitorStatus'; -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import Probe from './Probe'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import MultiTenentQueryAllowed from 'Common/Types/Database/MultiTenentQueryAllowed'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import IncidentSeverity from "./IncidentSeverity"; +import IncidentState from "./IncidentState"; +import Label from "./Label"; +import Monitor from "./Monitor"; +import MonitorStatus from "./MonitorStatus"; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import Probe from "./Probe"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import MultiTenentQueryAllowed from "Common/Types/Database/MultiTenentQueryAllowed"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() -@AccessControlColumn('labels') +@AccessControlColumn("labels") @MultiTenentQueryAllowed(true) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], }) -@CrudApiEndpoint(new Route('/incident')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/incident")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'Incident', + name: "Incident", }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) @TableMetadata({ - tableName: 'Incident', - singularName: 'Incident', - pluralName: 'Incidents', - icon: IconProp.Alert, - tableDescription: 'Manage incidents for your project', + tableName: "Incident", + singularName: "Incident", + pluralName: "Incidents", + icon: IconProp.Alert, + tableDescription: "Manage incidents for your project", }) export default class Incident extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.LongText, - canReadOnRelationQuery: true, - title: 'Title', - description: 'Title of this incident', - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public title?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.LongText, + canReadOnRelationQuery: true, + title: "Title", + description: "Title of this incident", + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public title?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.Markdown, - title: 'Description', - description: - 'Short description of this incident. This is in markdown and will be visible on the status page.', - }) - @Column({ - nullable: true, - type: ColumnType.Markdown, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.Markdown, + title: "Description", + description: + "Short description of this incident. This is in markdown and will be visible on the status page.", + }) + @Column({ + nullable: true, + type: ColumnType.Markdown, + }) + public description?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Monitor, - title: 'Monitors', - description: 'List of monitors affected by this incident', - }) - @ManyToMany( - () => { - return Monitor; - }, - { eager: false } - ) - @JoinTable({ - name: 'IncidentMonitor', - inverseJoinColumn: { - name: 'monitorId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'incidentId', - referencedColumnName: '_id', - }, - }) - public monitors?: Array<Monitor> = undefined; // monitors affected by this incident. + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Monitor, + title: "Monitors", + description: "List of monitors affected by this incident", + }) + @ManyToMany( + () => { + return Monitor; + }, + { eager: false }, + ) + @JoinTable({ + name: "IncidentMonitor", + inverseJoinColumn: { + name: "monitorId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "incidentId", + referencedColumnName: "_id", + }, + }) + public monitors?: Array<Monitor> = undefined; // monitors affected by this incident. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Monitor, - title: 'On-Call Duty Policies', - description: 'List of on-call duty policy affected by this incident.', - }) - @ManyToMany( - () => { - return OnCallDutyPolicy; - }, - { eager: false } - ) - @JoinTable({ - name: 'IncidentOnCallDutyPolicy', - inverseJoinColumn: { - name: 'monitorId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'onCallDutyPolicyId', - referencedColumnName: '_id', - }, - }) - public onCallDutyPolicies?: Array<OnCallDutyPolicy> = undefined; // monitors affected by this incident. + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Monitor, + title: "On-Call Duty Policies", + description: "List of on-call duty policy affected by this incident.", + }) + @ManyToMany( + () => { + return OnCallDutyPolicy; + }, + { eager: false }, + ) + @JoinTable({ + name: "IncidentOnCallDutyPolicy", + inverseJoinColumn: { + name: "monitorId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "onCallDutyPolicyId", + referencedColumnName: "_id", + }, + }) + public onCallDutyPolicies?: Array<OnCallDutyPolicy> = undefined; // monitors affected by this incident. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'IncidentLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'incidentId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "IncidentLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "incidentId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'currentIncidentStateId', - type: TableColumnType.Entity, - modelType: IncidentState, - title: 'Current Incident State', - description: - 'Current state of this incident. Is the incident acknowledged? or resolved?. This is related to Incident State', - }) - @ManyToOne( - () => { - return IncidentState; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'currentIncidentStateId' }) - public currentIncidentState?: IncidentState = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "currentIncidentStateId", + type: TableColumnType.Entity, + modelType: IncidentState, + title: "Current Incident State", + description: + "Current state of this incident. Is the incident acknowledged? or resolved?. This is related to Incident State", + }) + @ManyToOne( + () => { + return IncidentState; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "currentIncidentStateId" }) + public currentIncidentState?: IncidentState = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Current Incident State ID', - description: 'Current Incident State ID', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public currentIncidentStateId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Current Incident State ID", + description: "Current Incident State ID", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public currentIncidentStateId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentSeverityId', - type: TableColumnType.Entity, - modelType: IncidentSeverity, - title: 'Incident Severity', - description: - 'How severe is this incident. Is it critical? or a minor incident?. This is related to Incident Severity.', - }) - @ManyToOne( - () => { - return IncidentSeverity; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentSeverityId' }) - public incidentSeverity?: IncidentSeverity = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentSeverityId", + type: TableColumnType.Entity, + modelType: IncidentSeverity, + title: "Incident Severity", + description: + "How severe is this incident. Is it critical? or a minor incident?. This is related to Incident Severity.", + }) + @ManyToOne( + () => { + return IncidentSeverity; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentSeverityId" }) + public incidentSeverity?: IncidentSeverity = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Incident Severity ID', - description: 'Incident Severity ID', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentSeverityId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Incident Severity ID", + description: "Incident Severity ID", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentSeverityId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'changeMonitorStatusToId', - type: TableColumnType.Entity, - modelType: IncidentState, - title: 'Change Monitor Status To', - description: - 'Relation to Monitor Status Object. All monitors connected to this incident will be changed to this status when the incident is created.', - }) - @ManyToOne( - () => { - return MonitorStatus; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'changeMonitorStatusToId' }) - public changeMonitorStatusTo?: MonitorStatus = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "changeMonitorStatusToId", + type: TableColumnType.Entity, + modelType: IncidentState, + title: "Change Monitor Status To", + description: + "Relation to Monitor Status Object. All monitors connected to this incident will be changed to this status when the incident is created.", + }) + @ManyToOne( + () => { + return MonitorStatus; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "changeMonitorStatusToId" }) + public changeMonitorStatusTo?: MonitorStatus = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - title: 'Change Monitor Status To ID', - description: - 'Relation to Monitor Status Object ID. All monitors connected to this incident will be changed to this status when the incident is created.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public changeMonitorStatusToId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + title: "Change Monitor Status To ID", + description: + "Relation to Monitor Status Object ID. All monitors connected to this incident will be changed to this status when the incident is created.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public changeMonitorStatusToId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Are subscribers notified?', - description: 'Are subscribers notified about this incident?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isStatusPageSubscribersNotifiedOnIncidentCreated?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Are subscribers notified?", + description: "Are subscribers notified about this incident?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isStatusPageSubscribersNotifiedOnIncidentCreated?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should subscribers be notified?', - description: 'Should subscribers be notified about this incident?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public shouldStatusPageSubscribersBeNotifiedOnIncidentCreated?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should subscribers be notified?", + description: "Should subscribers be notified about this incident?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public shouldStatusPageSubscribersBeNotifiedOnIncidentCreated?: boolean = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectIncident, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - title: 'Custom Fields', - description: 'Custom Fields on this resource.', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public customFields?: JSONObject = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectIncident, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + title: "Custom Fields", + description: "Custom Fields on this resource.", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public customFields?: JSONObject = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified Of Resource Creation?', - description: 'Are owners notified of when this resource is created?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotifiedOfResourceCreation?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified Of Resource Creation?", + description: "Are owners notified of when this resource is created?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotifiedOfResourceCreation?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectIncident, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Markdown, - required: false, - isDefaultValueColumn: false, - title: 'Root Cause', - description: 'What is the root cause of this incident?', - }) - @Column({ - type: ColumnType.Markdown, - nullable: true, - }) - public rootCause?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectIncident, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Markdown, + required: false, + isDefaultValueColumn: false, + title: "Root Cause", + description: "What is the root cause of this incident?", + }) + @Column({ + type: ColumnType.Markdown, + nullable: true, + }) + public rootCause?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - unique: false, - }) - public createdStateLog?: JSONObject = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + unique: false, + }) + public createdStateLog?: JSONObject = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.LongText, - required: false, - isDefaultValueColumn: false, - title: 'Created Criteria ID', - description: - 'If this incident was created by a Probe, this is the ID of the criteria that created it.', - }) - @Column({ - type: ColumnType.LongText, - nullable: true, - }) - public createdCriteriaId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.LongText, + required: false, + isDefaultValueColumn: false, + title: "Created Criteria ID", + description: + "If this incident was created by a Probe, this is the ID of the criteria that created it.", + }) + @Column({ + type: ColumnType.LongText, + nullable: true, + }) + public createdCriteriaId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.LongText, - required: false, - isDefaultValueColumn: false, - title: 'Created Incident Template ID', - description: - 'If this incident was created by a Probe, this is the ID of the incident template that was used for creation.', - }) - @Column({ - type: ColumnType.LongText, - nullable: true, - }) - public createdIncidentTemplateId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.LongText, + required: false, + isDefaultValueColumn: false, + title: "Created Incident Template ID", + description: + "If this incident was created by a Probe, this is the ID of the incident template that was used for creation.", + }) + @Column({ + type: ColumnType.LongText, + nullable: true, + }) + public createdIncidentTemplateId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'createdByProbeId', - type: TableColumnType.Entity, - modelType: Probe, - title: 'Created By Probe', - description: - 'If this incident was created by a Probe, this is the probe that created it.', - }) - @ManyToOne( - () => { - return Probe; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByProbeId' }) - public createdByProbe?: Probe = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "createdByProbeId", + type: TableColumnType.Entity, + modelType: Probe, + title: "Created By Probe", + description: + "If this incident was created by a Probe, this is the probe that created it.", + }) + @ManyToOne( + () => { + return Probe; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByProbeId" }) + public createdByProbe?: Probe = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Created By Probe ID', - description: - 'If this incident was created by a Probe, this is the ID of the probe that created it.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public createdByProbeId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Created By Probe ID", + description: + "If this incident was created by a Probe, this is the ID of the probe that created it.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public createdByProbeId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectIncident, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Is created automatically?', - description: - 'Is this incident created by OneUptime Probe or Workers automatically (and not created manually by a user)?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isCreatedAutomatically?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectIncident, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Is created automatically?", + description: + "Is this incident created by OneUptime Probe or Workers automatically (and not created manually by a user)?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isCreatedAutomatically?: boolean = undefined; } diff --git a/Model/Models/IncidentCustomField.ts b/Model/Models/IncidentCustomField.ts index ab40704fae..3f7c7b6a29 100644 --- a/Model/Models/IncidentCustomField.ts +++ b/Model/Models/IncidentCustomField.ts @@ -1,342 +1,340 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import CustomFieldType from 'Common/Types/CustomField/CustomFieldType'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import CustomFieldType from "Common/Types/CustomField/CustomFieldType"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateIncidentCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteIncidentCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditIncidentCustomField, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteIncidentCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentCustomField, + ], }) -@CrudApiEndpoint(new Route('/incident-custom-field')) +@CrudApiEndpoint(new Route("/incident-custom-field")) @TableMetadata({ - tableName: 'IncidentCustomField', - singularName: 'Incident Custom Field', - pluralName: 'Incident Custom Fields', - icon: IconProp.TableCells, - tableDescription: 'Manage custom fields for your incident.', + tableName: "IncidentCustomField", + singularName: "Incident Custom Field", + pluralName: "Incident Custom Fields", + icon: IconProp.TableCells, + tableDescription: "Manage custom fields for your incident.", }) @Entity({ - name: 'IncidentCustomField', + name: "IncidentCustomField", }) export default class IncidentCustomField extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateIncidentCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateIncidentCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateIncidentCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditIncidentCustomField, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentCustomField, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateIncidentCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditIncidentCustomField, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: - 'Friendly description of this custom field that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentCustomField, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: + "Friendly description of this custom field that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateIncidentCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.CustomFieldType, - title: 'Custom Field Type', - description: 'Is this field Text, Number or Boolean?', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public type?: CustomFieldType = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.CustomFieldType, + title: "Custom Field Type", + description: "Is this field Text, Number or Boolean?", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public type?: CustomFieldType = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateIncidentCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateIncidentCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentCustomField, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentCustomField, + ], + 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; } diff --git a/Model/Models/IncidentInternalNote.ts b/Model/Models/IncidentInternalNote.ts index a7c9e7f36f..aaeca19170 100644 --- a/Model/Models/IncidentInternalNote.ts +++ b/Model/Models/IncidentInternalNote.ts @@ -1,373 +1,371 @@ -import Incident from './Incident'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Incident from "./Incident"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('incident') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("incident") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentInternalNote, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentInternalNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentInternalNote, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentInternalNote, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentInternalNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentInternalNote, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/incident-internal-note')) +@CrudApiEndpoint(new Route("/incident-internal-note")) @Entity({ - name: 'IncidentInternalNote', + name: "IncidentInternalNote", }) @TableMetadata({ - tableName: 'IncidentInternalNote', - singularName: 'Incident Internal Note', - pluralName: 'Incident Internal Notes', - icon: IconProp.Lock, - tableDescription: 'Manage internal notes for your incident', + tableName: "IncidentInternalNote", + singularName: "Incident Internal Note", + pluralName: "Incident Internal Notes", + icon: IconProp.Lock, + tableDescription: "Manage internal notes for your incident", }) export default class IncidentInternalNote extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentInternalNote, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentInternalNote, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentInternalNote, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentInternalNote, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentInternalNote, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentId', - type: TableColumnType.Entity, - modelType: Incident, - title: 'Incident', - description: 'Relation to Incident in which this resource belongs', - }) - @ManyToOne( - () => { - return Incident; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentId' }) - public incident?: Incident = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentInternalNote, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentId", + type: TableColumnType.Entity, + modelType: Incident, + title: "Incident", + description: "Relation to Incident in which this resource belongs", + }) + @ManyToOne( + () => { + return Incident; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentId" }) + public incident?: Incident = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentInternalNote, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Incident ID', - description: 'Relation to Incident ID in which this resource belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentInternalNote, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Incident ID", + description: "Relation to Incident ID in which this resource belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentInternalNote, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentInternalNote, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentInternalNote, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentInternalNote, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentInternalNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentInternalNote, - ], - }) - @TableColumn({ - type: TableColumnType.Markdown, - title: 'Note', - description: 'Notes in markdown', - }) - @Column({ - type: ColumnType.Markdown, - nullable: false, - unique: false, - }) - public note?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentInternalNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentInternalNote, + ], + }) + @TableColumn({ + type: TableColumnType.Markdown, + title: "Note", + description: "Notes in markdown", + }) + @Column({ + type: ColumnType.Markdown, + nullable: false, + unique: false, + }) + public note?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentInternalNote, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentInternalNote, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/IncidentNoteTemplate.ts b/Model/Models/IncidentNoteTemplate.ts index 5846d30b90..652845cc61 100644 --- a/Model/Models/IncidentNoteTemplate.ts +++ b/Model/Models/IncidentNoteTemplate.ts @@ -1,346 +1,344 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentNoteTemplate, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentNoteTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentNoteTemplate, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentNoteTemplate, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentNoteTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentNoteTemplate, + ], }) -@CrudApiEndpoint(new Route('/incident-note-template')) +@CrudApiEndpoint(new Route("/incident-note-template")) @Entity({ - name: 'IncidentNoteTemplate', + name: "IncidentNoteTemplate", }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) @TableMetadata({ - tableName: 'IncidentNoteTemplate', - singularName: 'Incident Note Template', - pluralName: 'Incident Note Templates', - icon: IconProp.Alert, - tableDescription: 'Manage incident note templates for your project', + tableName: "IncidentNoteTemplate", + singularName: "Incident Note Template", + pluralName: "Incident Note Templates", + icon: IconProp.Alert, + tableDescription: "Manage incident note templates for your project", }) export default class IncidentNoteTemplate extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentNoteTemplate, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentNoteTemplate, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentNoteTemplate, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentNoteTemplate, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentNoteTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentNoteTemplate, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.Markdown, - title: 'Note', - description: - 'Note template for public or private notes. This is in markdown.', - }) - @Column({ - type: ColumnType.Markdown, - nullable: false, - unique: false, - }) - public note?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentNoteTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentNoteTemplate, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.Markdown, + title: "Note", + description: + "Note template for public or private notes. This is in markdown.", + }) + @Column({ + type: ColumnType.Markdown, + nullable: false, + unique: false, + }) + public note?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentNoteTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentNoteTemplate, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Name of the Incident Template', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public templateName?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentNoteTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentNoteTemplate, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Name of the Incident Template", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public templateName?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentNoteTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentNoteTemplate, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - canReadOnRelationQuery: true, - title: 'Template Description', - description: 'Description of the Incident Template', - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public templateDescription?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentNoteTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentNoteTemplate, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + canReadOnRelationQuery: true, + title: "Template Description", + description: "Description of the Incident Template", + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public templateDescription?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentNoteTemplate, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentNoteTemplate, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentNoteTemplate, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentNoteTemplate, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/IncidentOwnerTeam.ts b/Model/Models/IncidentOwnerTeam.ts index 42fc63f168..53741208e3 100644 --- a/Model/Models/IncidentOwnerTeam.ts +++ b/Model/Models/IncidentOwnerTeam.ts @@ -1,420 +1,416 @@ -import Incident from './Incident'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Incident from "./Incident"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentOwnerTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentOwnerTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentOwnerTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentOwnerTeam, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/incident-owner-team')) +@CrudApiEndpoint(new Route("/incident-owner-team")) @TableMetadata({ - tableName: 'IncidentOwnerTeam', - singularName: 'Incident Team Owner', - pluralName: 'Incident Team Owners', - icon: IconProp.Signal, - tableDescription: 'Add teams as owners to your incidents.', + tableName: "IncidentOwnerTeam", + singularName: "Incident Team Owner", + pluralName: "Incident Team Owners", + icon: IconProp.Signal, + tableDescription: "Add teams as owners to your incidents.", }) @Entity({ - name: 'IncidentOwnerTeam', + name: "IncidentOwnerTeam", }) export default class IncidentOwnerTeam extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: - 'Team that is the owner. All users in this team will receive notifications. ', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: + "Team that is the owner. All users in this team will receive notifications. ", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Team ID', - description: 'ID of your OneUptime Team in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Team ID", + description: "ID of your OneUptime Team in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentId', - type: TableColumnType.Entity, - modelType: Incident, - title: 'Incident', - description: - 'Relation to Incident Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Incident; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentId' }) - public incident?: Incident = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentId", + type: TableColumnType.Entity, + modelType: Incident, + title: "Incident", + description: "Relation to Incident Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Incident; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentId" }) + public incident?: Incident = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Incident ID', - description: - 'ID of your OneUptime Incident in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Incident ID", + description: "ID of your OneUptime Incident in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/IncidentOwnerUser.ts b/Model/Models/IncidentOwnerUser.ts index 987e159ebb..e725037691 100644 --- a/Model/Models/IncidentOwnerUser.ts +++ b/Model/Models/IncidentOwnerUser.ts @@ -1,419 +1,415 @@ -import Incident from './Incident'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Incident from "./Incident"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentOwnerUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentOwnerUser, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentOwnerUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentOwnerUser, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/incident-owner-user')) +@CrudApiEndpoint(new Route("/incident-owner-user")) @TableMetadata({ - tableName: 'IncidentOwnerUser', - singularName: 'Incident User Owner', - pluralName: 'Incident User Owners', - icon: IconProp.Signal, - tableDescription: 'Add users as owners to your incidents.', + tableName: "IncidentOwnerUser", + singularName: "Incident User Owner", + pluralName: "Incident User Owners", + icon: IconProp.Signal, + tableDescription: "Add users as owners to your incidents.", }) @Entity({ - name: 'IncidentOwnerUser', + name: "IncidentOwnerUser", }) export default class IncidentOwnerUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: - 'User that is the owner. This user will receive notifications. ', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: + "User that is the owner. This user will receive notifications. ", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'User ID', - description: 'ID of your OneUptime User in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "User ID", + description: "ID of your OneUptime User in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentId', - type: TableColumnType.Entity, - modelType: Incident, - title: 'Incident', - description: - 'Relation to Incident Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Incident; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentId' }) - public incident?: Incident = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentId", + type: TableColumnType.Entity, + modelType: Incident, + title: "Incident", + description: "Relation to Incident Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Incident; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentId" }) + public incident?: Incident = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Incident ID', - description: - 'ID of your OneUptime Incident in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Incident ID", + description: "ID of your OneUptime Incident in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/IncidentPublicNote.ts b/Model/Models/IncidentPublicNote.ts index 4a5adc8d8c..05f46727be 100644 --- a/Model/Models/IncidentPublicNote.ts +++ b/Model/Models/IncidentPublicNote.ts @@ -1,454 +1,453 @@ -import Incident from './Incident'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Incident from "./Incident"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('incident') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("incident") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentPublicNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentPublicNote, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentPublicNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentPublicNote, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/incident-public-note')) +@CrudApiEndpoint(new Route("/incident-public-note")) @Entity({ - name: 'IncidentPublicNote', + name: "IncidentPublicNote", }) @TableMetadata({ - tableName: 'IncidentPublicNote', - singularName: 'Incident Public Note', - pluralName: 'Incident Public Notes', - icon: IconProp.Team, - tableDescription: 'Manage public notes for your incident', + tableName: "IncidentPublicNote", + singularName: "Incident Public Note", + pluralName: "Incident Public Notes", + icon: IconProp.Team, + tableDescription: "Manage public notes for your incident", }) export default class IncidentPublicNote extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentId', - type: TableColumnType.Entity, - modelType: Incident, - title: 'Incident', - description: 'Relation to Incident in which this resource belongs', - }) - @ManyToOne( - () => { - return Incident; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentId' }) - public incident?: Incident = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentId", + type: TableColumnType.Entity, + modelType: Incident, + title: "Incident", + description: "Relation to Incident in which this resource belongs", + }) + @ManyToOne( + () => { + return Incident; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentId" }) + public incident?: Incident = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Incident ID', - description: 'Relation to Incident ID in which this resource belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Incident ID", + description: "Relation to Incident ID in which this resource belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentPublicNote, - ], - }) - @TableColumn({ - type: TableColumnType.Markdown, - title: 'Note', - description: 'Notes in markdown', - }) - @Column({ - type: ColumnType.Markdown, - nullable: false, - unique: false, - }) - public note?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentPublicNote, + ], + }) + @TableColumn({ + type: TableColumnType.Markdown, + title: "Note", + description: "Notes in markdown", + }) + @Column({ + type: ColumnType.Markdown, + nullable: false, + unique: false, + }) + public note?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Are subscribers notified?', - description: 'Are subscribers notified about this note?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isStatusPageSubscribersNotifiedOnNoteCreated?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Are subscribers notified?", + description: "Are subscribers notified about this note?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isStatusPageSubscribersNotifiedOnNoteCreated?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should subscribers be notified?', - description: 'Should subscribers be notified about this note?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public shouldStatusPageSubscribersBeNotifiedOnNoteCreated?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should subscribers be notified?", + description: "Should subscribers be notified about this note?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public shouldStatusPageSubscribersBeNotifiedOnNoteCreated?: boolean = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentPublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentPublicNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentPublicNote, - ], - }) - @TableColumn({ - title: 'Note Posted At', - description: 'Date and time when the note was posted', - type: TableColumnType.Date, - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public postedAt?: Date = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentPublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentPublicNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentPublicNote, + ], + }) + @TableColumn({ + title: "Note Posted At", + description: "Date and time when the note was posted", + type: TableColumnType.Date, + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public postedAt?: Date = undefined; } diff --git a/Model/Models/IncidentSeverity.ts b/Model/Models/IncidentSeverity.ts index 035e6f3ec9..7df8e3eebb 100644 --- a/Model/Models/IncidentSeverity.ts +++ b/Model/Models/IncidentSeverity.ts @@ -1,428 +1,426 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import Color from 'Common/Types/Color'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import Color from "Common/Types/Color"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentSeverity, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentSeverity, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentSeverity, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentSeverity, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentSeverity, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentSeverity, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/incident-severity')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/incident-severity")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'IncidentSeverity', - singularName: 'Incident Severity', - pluralName: 'Incident Severities', - icon: IconProp.Alert, - tableDescription: - 'Manage incident severity for your project (Created, Acknowledged for example). Add / edit or remove severities.', + tableName: "IncidentSeverity", + singularName: "Incident Severity", + pluralName: "Incident Severities", + icon: IconProp.Alert, + tableDescription: + "Manage incident severity for your project (Created, Acknowledged for example). Add / edit or remove severities.", }) @Entity({ - name: 'IncidentSeverity', + name: "IncidentSeverity", }) export default class IncidentSeverity extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentSeverity, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentSeverity, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentSeverity, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentSeverity, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentSeverity, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentSeverity, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentSeverity, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentSeverity, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentSeverity, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentSeverity, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentSeverity, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentSeverity, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentSeverity, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentSeverity, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentSeverity, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentSeverity, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentSeverity, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentSeverity, - ], - }) - @TableColumn({ - title: 'Color', - required: true, - unique: false, - type: TableColumnType.Color, - canReadOnRelationQuery: true, - description: 'Color of this resource in Hex (#32a852 for example)', - }) - @Column({ - type: ColumnType.Color, - length: ColumnLength.Color, - unique: false, - nullable: false, - transformer: Color.getDatabaseTransformer(), - }) - public color?: Color = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentSeverity, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentSeverity, + ], + }) + @TableColumn({ + title: "Color", + required: true, + unique: false, + type: TableColumnType.Color, + canReadOnRelationQuery: true, + description: "Color of this resource in Hex (#32a852 for example)", + }) + @Column({ + type: ColumnType.Color, + length: ColumnLength.Color, + unique: false, + nullable: false, + transformer: Color.getDatabaseTransformer(), + }) + public color?: Color = undefined; - @UniqueColumnBy('projectId') - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentSeverity, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentSeverity, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentSeverity, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.SmallNumber, - title: 'Order', - description: 'Order / Priority of this resource', - }) - @Column({ - type: ColumnType.SmallNumber, - }) - public order?: number = undefined; + @UniqueColumnBy("projectId") + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentSeverity, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentSeverity, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentSeverity, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.SmallNumber, + title: "Order", + description: "Order / Priority of this resource", + }) + @Column({ + type: ColumnType.SmallNumber, + }) + public order?: number = undefined; } diff --git a/Model/Models/IncidentState.ts b/Model/Models/IncidentState.ts index 457108c124..3dc828d7aa 100644 --- a/Model/Models/IncidentState.ts +++ b/Model/Models/IncidentState.ts @@ -1,528 +1,526 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import Color from 'Common/Types/Color'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import Color from "Common/Types/Color"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentState, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentState, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/incident-state')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/incident-state")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'IncidentState', - singularName: 'Incident State', - pluralName: 'Incident States', - icon: IconProp.ArrowCircleRight, - tableDescription: - 'Manage incident states for your project (Created, Acknowledged for example). Add / edit or remove states.', + tableName: "IncidentState", + singularName: "Incident State", + pluralName: "Incident States", + icon: IconProp.ArrowCircleRight, + tableDescription: + "Manage incident states for your project (Created, Acknowledged for example). Add / edit or remove states.", }) @Entity({ - name: 'IncidentState', + name: "IncidentState", }) export default class IncidentState extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentState, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentState, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentState, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentState, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentState, - ], - }) - @TableColumn({ - title: 'Color', - required: true, - unique: false, - type: TableColumnType.Color, - canReadOnRelationQuery: true, - description: 'Color of this resource in Hex (#32a852 for example)', - }) - @Column({ - type: ColumnType.Color, - length: ColumnLength.Color, - unique: false, - nullable: false, - transformer: Color.getDatabaseTransformer(), - }) - public color?: Color = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentState, + ], + }) + @TableColumn({ + title: "Color", + required: true, + unique: false, + type: TableColumnType.Color, + canReadOnRelationQuery: true, + description: "Color of this resource in Hex (#32a852 for example)", + }) + @Column({ + type: ColumnType.Color, + length: ColumnLength.Color, + unique: false, + nullable: false, + transformer: Color.getDatabaseTransformer(), + }) + public color?: Color = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentState, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Is Created State', - description: 'Is it the created state of the incident?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isCreatedState?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentState, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Is Created State", + description: "Is it the created state of the incident?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isCreatedState?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentState, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Is Acknowledged State', - description: 'Is it the acknowledged state of the incident?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isAcknowledgedState?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentState, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Is Acknowledged State", + description: "Is it the acknowledged state of the incident?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isAcknowledgedState?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentState, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Is Resolved State', - description: 'Is it the resolved state of the incident?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isResolvedState?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentState, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Is Resolved State", + description: "Is it the resolved state of the incident?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isResolvedState?: boolean = undefined; - @UniqueColumnBy('projectId') - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentState, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.SmallNumber, - canReadOnRelationQuery: true, - title: 'Order', - description: 'Order / Priority of this resource', - }) - @Column({ - type: ColumnType.SmallNumber, - }) - public order?: number = undefined; + @UniqueColumnBy("projectId") + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentState, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.SmallNumber, + canReadOnRelationQuery: true, + title: "Order", + description: "Order / Priority of this resource", + }) + @Column({ + type: ColumnType.SmallNumber, + }) + public order?: number = undefined; } diff --git a/Model/Models/IncidentStateTimeline.ts b/Model/Models/IncidentStateTimeline.ts index 652e619277..a2472e7b66 100644 --- a/Model/Models/IncidentStateTimeline.ts +++ b/Model/Models/IncidentStateTimeline.ts @@ -1,582 +1,579 @@ -import Incident from './Incident'; -import IncidentState from './IncidentState'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Incident from "./Incident"; +import IncidentState from "./IncidentState"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('incident') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("incident") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentStateTimeline, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentStateTimeline, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentStateTimeline, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentStateTimeline, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/incident-state-timeline')) +@CrudApiEndpoint(new Route("/incident-state-timeline")) @Entity({ - name: 'IncidentStateTimeline', + name: "IncidentStateTimeline", }) @TableMetadata({ - tableName: 'IncidentStateTimeline', - singularName: 'Incident State Timeline', - pluralName: 'Incident State Timelines', - icon: IconProp.List, - tableDescription: - 'Change state of the incidents (Created to Acknowledged for example)', + tableName: "IncidentStateTimeline", + singularName: "Incident State Timeline", + pluralName: "Incident State Timelines", + icon: IconProp.List, + tableDescription: + "Change state of the incidents (Created to Acknowledged for example)", }) export default class IncidentStateTimeline extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentId', - type: TableColumnType.Entity, - modelType: Incident, - title: 'Incident', - description: 'Relation to Incident in which this resource belongs', - }) - @ManyToOne( - () => { - return Incident; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentId' }) - public incident?: Incident = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentId", + type: TableColumnType.Entity, + modelType: Incident, + title: "Incident", + description: "Relation to Incident in which this resource belongs", + }) + @ManyToOne( + () => { + return Incident; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentId" }) + public incident?: Incident = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Incident ID', - description: 'Relation to Incident ID in which this resource belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Incident ID", + description: "Relation to Incident ID in which this resource belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentStateTimeline, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentStateId', - type: TableColumnType.Entity, - modelType: IncidentState, - title: 'Incident State', - description: - 'Incident State Relation. Which incident state does this incident change to?', - }) - @ManyToOne( - () => { - return IncidentState; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentStateId' }) - public incidentState?: IncidentState = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentStateTimeline, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentStateId", + type: TableColumnType.Entity, + modelType: IncidentState, + title: "Incident State", + description: + "Incident State Relation. Which incident state does this incident change to?", + }) + @ManyToOne( + () => { + return IncidentState; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentStateId" }) + public incidentState?: IncidentState = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentStateTimeline, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Incident State ID', - description: - 'Incident State ID Relation. Which incident state does this incident change to?', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentStateId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentStateTimeline, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Incident State ID", + description: + "Incident State ID Relation. Which incident state does this incident change to?", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentStateId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Are subscribers notified?', - description: - 'Are subscribers notified about this incident state change?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isStatusPageSubscribersNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Are subscribers notified?", + description: "Are subscribers notified about this incident state change?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isStatusPageSubscribersNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should subscribers be notified?', - description: 'Should subscribers be notified about this state change?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public shouldStatusPageSubscribersBeNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should subscribers be notified?", + description: "Should subscribers be notified about this state change?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public shouldStatusPageSubscribersBeNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of state change?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of state change?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - unique: false, - }) - public stateChangeLog?: JSONObject = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + unique: false, + }) + public stateChangeLog?: JSONObject = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Markdown, - required: false, - isDefaultValueColumn: false, - title: 'Root Cause', - description: 'What is the root cause of this status change?', - }) - @Column({ - type: ColumnType.Markdown, - nullable: true, - }) - public rootCause?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Markdown, + required: false, + isDefaultValueColumn: false, + title: "Root Cause", + description: "What is the root cause of this status change?", + }) + @Column({ + type: ColumnType.Markdown, + nullable: true, + }) + public rootCause?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Date, - title: 'Ends At', - description: 'When did this status change end?', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public endsAt?: Date = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Date, + title: "Ends At", + description: "When did this status change end?", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public endsAt?: Date = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentStateTimeline, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Date, - title: 'Starts At', - description: 'When did this status change start?', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public startsAt?: Date = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentStateTimeline, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Date, + title: "Starts At", + description: "When did this status change start?", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public startsAt?: Date = undefined; } diff --git a/Model/Models/IncidentTemplate.ts b/Model/Models/IncidentTemplate.ts index acd3887197..2b56a01c0f 100644 --- a/Model/Models/IncidentTemplate.ts +++ b/Model/Models/IncidentTemplate.ts @@ -1,748 +1,746 @@ -import IncidentSeverity from './IncidentSeverity'; -import IncidentState from './IncidentState'; -import Label from './Label'; -import Monitor from './Monitor'; -import MonitorStatus from './MonitorStatus'; -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import IncidentSeverity from "./IncidentSeverity"; +import IncidentState from "./IncidentState"; +import Label from "./Label"; +import Monitor from "./Monitor"; +import MonitorStatus from "./MonitorStatus"; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() -@AccessControlColumn('labels') -@TenantColumn('projectId') +@AccessControlColumn("labels") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], }) -@CrudApiEndpoint(new Route('/incident-templates')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/incident-templates")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'IncidentTemplate', + name: "IncidentTemplate", }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) @TableMetadata({ - tableName: 'IncidentTemplate', - singularName: 'Incident Template', - pluralName: 'Incident Templates', - icon: IconProp.Alert, - tableDescription: 'Manage incident templates for your project', + tableName: "IncidentTemplate", + singularName: "Incident Template", + pluralName: "Incident Templates", + icon: IconProp.Alert, + tableDescription: "Manage incident templates for your project", }) export default class IncidentTemplate extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.LongText, - canReadOnRelationQuery: true, - title: 'Title', - description: 'Title of this incident', - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public title?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.LongText, + canReadOnRelationQuery: true, + title: "Title", + description: "Title of this incident", + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public title?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Name of the Incident Template', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public templateName?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Name of the Incident Template", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public templateName?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - canReadOnRelationQuery: true, - title: 'Template Description', - description: 'Description of the Incident Template', - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public templateDescription?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + canReadOnRelationQuery: true, + title: "Template Description", + description: "Description of the Incident Template", + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public templateDescription?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.Markdown, - title: 'Description', - description: - 'Short description of this incident. This is in markdown and will be visible on the status page.', - }) - @Column({ - nullable: true, - type: ColumnType.Markdown, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.Markdown, + title: "Description", + description: + "Short description of this incident. This is in markdown and will be visible on the status page.", + }) + @Column({ + nullable: true, + type: ColumnType.Markdown, + }) + public description?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Monitor, - title: 'Monitors', - description: 'List of monitors affected by this incident', - }) - @ManyToMany( - () => { - return Monitor; - }, - { eager: false } - ) - @JoinTable({ - name: 'IncidentTemplateMonitor', - inverseJoinColumn: { - name: 'monitorId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'incidentTemplateId', - referencedColumnName: '_id', - }, - }) - public monitors?: Array<Monitor> = undefined; // monitors affected by this incident. + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Monitor, + title: "Monitors", + description: "List of monitors affected by this incident", + }) + @ManyToMany( + () => { + return Monitor; + }, + { eager: false }, + ) + @JoinTable({ + name: "IncidentTemplateMonitor", + inverseJoinColumn: { + name: "monitorId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "incidentTemplateId", + referencedColumnName: "_id", + }, + }) + public monitors?: Array<Monitor> = undefined; // monitors affected by this incident. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Monitor, - title: 'On-Call Duty Policies', - description: 'List of on-call duty policy affected by this incident.', - }) - @ManyToMany( - () => { - return OnCallDutyPolicy; - }, - { eager: false } - ) - @JoinTable({ - name: 'IncidentTemplateOnCallDutyPolicy', - inverseJoinColumn: { - name: 'onCallDutyPolicyId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'incidentTemplateId', - referencedColumnName: '_id', - }, - }) - public onCallDutyPolicies?: Array<OnCallDutyPolicy> = undefined; // monitors affected by this incident. + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Monitor, + title: "On-Call Duty Policies", + description: "List of on-call duty policy affected by this incident.", + }) + @ManyToMany( + () => { + return OnCallDutyPolicy; + }, + { eager: false }, + ) + @JoinTable({ + name: "IncidentTemplateOnCallDutyPolicy", + inverseJoinColumn: { + name: "onCallDutyPolicyId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "incidentTemplateId", + referencedColumnName: "_id", + }, + }) + public onCallDutyPolicies?: Array<OnCallDutyPolicy> = undefined; // monitors affected by this incident. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'IncidentTemplateLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'incidentTemplateId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "IncidentTemplateLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "incidentTemplateId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentSeverityId', - type: TableColumnType.Entity, - modelType: IncidentSeverity, - title: 'Incident Severity', - description: - 'How severe is this incident. Is it critical? or a minor incident?. This is related to Incident Severity.', - }) - @ManyToOne( - () => { - return IncidentSeverity; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentSeverityId' }) - public incidentSeverity?: IncidentSeverity = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentSeverityId", + type: TableColumnType.Entity, + modelType: IncidentSeverity, + title: "Incident Severity", + description: + "How severe is this incident. Is it critical? or a minor incident?. This is related to Incident Severity.", + }) + @ManyToOne( + () => { + return IncidentSeverity; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentSeverityId" }) + public incidentSeverity?: IncidentSeverity = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - title: 'Incident Severity ID', - description: 'Incident Severity ID', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentSeverityId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + title: "Incident Severity ID", + description: "Incident Severity ID", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentSeverityId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'changeMonitorStatusToId', - type: TableColumnType.Entity, - modelType: IncidentState, - title: 'Change Monitor Status To', - description: - 'Relation to Monitor Status Object. All monitors connected to this incident will be changed to this status when the incident is created.', - }) - @ManyToOne( - () => { - return MonitorStatus; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'changeMonitorStatusToId' }) - public changeMonitorStatusTo?: MonitorStatus = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "changeMonitorStatusToId", + type: TableColumnType.Entity, + modelType: IncidentState, + title: "Change Monitor Status To", + description: + "Relation to Monitor Status Object. All monitors connected to this incident will be changed to this status when the incident is created.", + }) + @ManyToOne( + () => { + return MonitorStatus; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "changeMonitorStatusToId" }) + public changeMonitorStatusTo?: MonitorStatus = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - title: 'Change Monitor Status To ID', - description: - 'Relation to Monitor Status Object ID. All monitors connected to this incident will be changed to this status when the incident is created.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public changeMonitorStatusToId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + title: "Change Monitor Status To ID", + description: + "Relation to Monitor Status Object ID. All monitors connected to this incident will be changed to this status when the incident is created.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public changeMonitorStatusToId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplate, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - title: 'Custom Fields', - description: 'Custom Fields on this resource.', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public customFields?: JSONObject = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplate, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + title: "Custom Fields", + description: "Custom Fields on this resource.", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public customFields?: JSONObject = undefined; } diff --git a/Model/Models/IncidentTemplateOwnerTeam.ts b/Model/Models/IncidentTemplateOwnerTeam.ts index 451ff63797..41bfe64f70 100644 --- a/Model/Models/IncidentTemplateOwnerTeam.ts +++ b/Model/Models/IncidentTemplateOwnerTeam.ts @@ -1,420 +1,418 @@ -import IncidentTemplate from './IncidentTemplate'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import IncidentTemplate from "./IncidentTemplate"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentTemplateOwnerTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplateOwnerTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentTemplateOwnerTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplateOwnerTeam, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/incident-template-owner-team')) +@CrudApiEndpoint(new Route("/incident-template-owner-team")) @TableMetadata({ - tableName: 'IncidentTemplateOwnerTeam', - singularName: 'Incident Template Team Owner', - pluralName: 'Incident Template Team Owners', - icon: IconProp.Signal, - tableDescription: 'Add teams as owners to your incidents.', + tableName: "IncidentTemplateOwnerTeam", + singularName: "Incident Template Team Owner", + pluralName: "Incident Template Team Owners", + icon: IconProp.Signal, + tableDescription: "Add teams as owners to your incidents.", }) @Entity({ - name: 'IncidentTemplateOwnerTeam', + name: "IncidentTemplateOwnerTeam", }) export default class IncidentTemplateOwnerTeam extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: - 'Team that is the owner. All users in this team will receive notifications. ', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: + "Team that is the owner. All users in this team will receive notifications. ", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Team ID', - description: 'ID of your OneUptime Team in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Team ID", + description: "ID of your OneUptime Team in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentTemplateId', - type: TableColumnType.Entity, - modelType: IncidentTemplate, - title: 'IncidentTemplate', - description: - 'Relation to IncidentTemplate Resource in which this object belongs', - }) - @ManyToOne( - () => { - return IncidentTemplate; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentTemplateId' }) - public incidentTemplate?: IncidentTemplate = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentTemplateId", + type: TableColumnType.Entity, + modelType: IncidentTemplate, + title: "IncidentTemplate", + description: + "Relation to IncidentTemplate Resource in which this object belongs", + }) + @ManyToOne( + () => { + return IncidentTemplate; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentTemplateId" }) + public incidentTemplate?: IncidentTemplate = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'IncidentTemplate ID', - description: - 'ID of your OneUptime IncidentTemplate in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentTemplateId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "IncidentTemplate ID", + description: + "ID of your OneUptime IncidentTemplate in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentTemplateId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/IncidentTemplateOwnerUser.ts b/Model/Models/IncidentTemplateOwnerUser.ts index bbc083349c..d90822aec0 100644 --- a/Model/Models/IncidentTemplateOwnerUser.ts +++ b/Model/Models/IncidentTemplateOwnerUser.ts @@ -1,431 +1,429 @@ -import IncidentTemplate from './IncidentTemplate'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import IncidentTemplate from "./IncidentTemplate"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteIncidentTemplateOwnerUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditIncidentTemplateOwnerUser, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteIncidentTemplateOwnerUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditIncidentTemplateOwnerUser, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/incident-template-owner-user')) +@CrudApiEndpoint(new Route("/incident-template-owner-user")) @TableMetadata({ - tableName: 'IncidentTemplateOwnerUser', - singularName: 'Incident Template User Owner', - pluralName: 'Incident Template User Owners', - icon: IconProp.Signal, - tableDescription: 'Add users as owners to your incident templates.', + tableName: "IncidentTemplateOwnerUser", + singularName: "Incident Template User Owner", + pluralName: "Incident Template User Owners", + icon: IconProp.Signal, + tableDescription: "Add users as owners to your incident templates.", }) @Entity({ - name: 'IncidentTemplateOwnerUser', + name: "IncidentTemplateOwnerUser", }) export default class IncidentTemplateOwnerUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: - 'User that is the owner. This user will receive notifications. ', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: + "User that is the owner. This user will receive notifications. ", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'User ID', - description: 'ID of your OneUptime User in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "User ID", + description: "ID of your OneUptime User in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentTemplateId', - type: TableColumnType.Entity, - modelType: IncidentTemplate, - title: 'IncidentTemplate', - description: - 'Relation to Incident Template Resource in which this object belongs', - }) - @ManyToOne( - () => { - return IncidentTemplate; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentTemplateId' }) - public incidentTemplate?: IncidentTemplate = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentTemplateId", + type: TableColumnType.Entity, + modelType: IncidentTemplate, + title: "IncidentTemplate", + description: + "Relation to Incident Template Resource in which this object belongs", + }) + @ManyToOne( + () => { + return IncidentTemplate; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentTemplateId" }) + public incidentTemplate?: IncidentTemplate = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'IncidentTemplate ID', - description: - 'ID of your OneUptime IncidentTemplate in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentTemplateId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "IncidentTemplate ID", + description: + "ID of your OneUptime IncidentTemplate in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentTemplateId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateIncidentTemplateOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadIncidentTemplateOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentTemplateOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadIncidentTemplateOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/Index.ts b/Model/Models/Index.ts index 676dcfeff3..b27ff8c899 100644 --- a/Model/Models/Index.ts +++ b/Model/Models/Index.ts @@ -1,271 +1,271 @@ -import AcmeCertificate from './AcmeCertificate'; -import AcmeChallenge from './AcmeChallenge'; +import AcmeCertificate from "./AcmeCertificate"; +import AcmeChallenge from "./AcmeChallenge"; // API Keys -import ApiKey from './ApiKey'; -import ApiKeyPermission from './ApiKeyPermission'; -import BillingInvoice from './BillingInvoice'; -import BillingPaymentMethods from './BillingPaymentMethod'; -import CallLog from './CallLog'; -import CodeRepository from './CodeRepository'; -import CopilotEvent from './CopilotEvent'; +import ApiKey from "./ApiKey"; +import ApiKeyPermission from "./ApiKeyPermission"; +import BillingInvoice from "./BillingInvoice"; +import BillingPaymentMethods from "./BillingPaymentMethod"; +import CallLog from "./CallLog"; +import CodeRepository from "./CodeRepository"; +import CopilotEvent from "./CopilotEvent"; // Date migration -import DataMigration from './DataMigration'; -import Domain from './Domain'; -import EmailLog from './EmailLog'; -import EmailVerificationToken from './EmailVerificationToken'; -import File from './File'; -import GlobalConfig from './GlobalConfig'; -import GreenlockCertificate from './GreenlockCertificate'; +import DataMigration from "./DataMigration"; +import Domain from "./Domain"; +import EmailLog from "./EmailLog"; +import EmailVerificationToken from "./EmailVerificationToken"; +import File from "./File"; +import GlobalConfig from "./GlobalConfig"; +import GreenlockCertificate from "./GreenlockCertificate"; // Greenlock -import GreenlockChallenge from './GreenlockChallenge'; +import GreenlockChallenge from "./GreenlockChallenge"; // Incidents -import Incident from './Incident'; -import IncidentCustomField from './IncidentCustomField'; -import IncidentInternalNote from './IncidentInternalNote'; -import IncidentNoteTemplate from './IncidentNoteTemplate'; -import IncidentOwnerTeam from './IncidentOwnerTeam'; -import IncidentOwnerUser from './IncidentOwnerUser'; -import IncidentPublicNote from './IncidentPublicNote'; -import IncidentSeverity from './IncidentSeverity'; -import IncidentState from './IncidentState'; -import IncidentStateTimeline from './IncidentStateTimeline'; -import IncidentTemplate from './IncidentTemplate'; -import IncidentTemplateOwnerTeam from './IncidentTemplateOwnerTeam'; -import IncidentTemplateOwnerUser from './IncidentTemplateOwnerUser'; +import Incident from "./Incident"; +import IncidentCustomField from "./IncidentCustomField"; +import IncidentInternalNote from "./IncidentInternalNote"; +import IncidentNoteTemplate from "./IncidentNoteTemplate"; +import IncidentOwnerTeam from "./IncidentOwnerTeam"; +import IncidentOwnerUser from "./IncidentOwnerUser"; +import IncidentPublicNote from "./IncidentPublicNote"; +import IncidentSeverity from "./IncidentSeverity"; +import IncidentState from "./IncidentState"; +import IncidentStateTimeline from "./IncidentStateTimeline"; +import IncidentTemplate from "./IncidentTemplate"; +import IncidentTemplateOwnerTeam from "./IncidentTemplateOwnerTeam"; +import IncidentTemplateOwnerUser from "./IncidentTemplateOwnerUser"; //Labels. -import Label from './Label'; +import Label from "./Label"; // Monitors -import Monitor from './Monitor'; -import MonitorCustomField from './MonitorCustomField'; +import Monitor from "./Monitor"; +import MonitorCustomField from "./MonitorCustomField"; // Monitor Groups -import MonitorGroup from './MonitorGroup'; -import MonitorGroupOwnerTeam from './MonitorGroupOwnerTeam'; -import MonitorGroupOwnerUser from './MonitorGroupOwnerUser'; -import MonitorGroupResource from './MonitorGroupResource'; -import MonitorOwnerTeam from './MonitorOwnerTeam'; -import MonitorOwnerUser from './MonitorOwnerUser'; -import MonitorProbe from './MonitorProbe'; -import MonitorSecret from './MonitorSecret'; -import MonitorStatus from './MonitorStatus'; -import MonitorStatusTimeline from './MonitorStatusTimeline'; +import MonitorGroup from "./MonitorGroup"; +import MonitorGroupOwnerTeam from "./MonitorGroupOwnerTeam"; +import MonitorGroupOwnerUser from "./MonitorGroupOwnerUser"; +import MonitorGroupResource from "./MonitorGroupResource"; +import MonitorOwnerTeam from "./MonitorOwnerTeam"; +import MonitorOwnerUser from "./MonitorOwnerUser"; +import MonitorProbe from "./MonitorProbe"; +import MonitorSecret from "./MonitorSecret"; +import MonitorStatus from "./MonitorStatus"; +import MonitorStatusTimeline from "./MonitorStatusTimeline"; // On-Call Duty -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import OnCallDutyPolicyCustomField from './OnCallDutyPolicyCustomField'; -import OnCallDutyPolicyEscalationRule from './OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicyEscalationRuleSchedule from './OnCallDutyPolicyEscalationRuleSchedule'; -import OnCallDutyPolicyEscalationRuleTeam from './OnCallDutyPolicyEscalationRuleTeam'; -import OnCallDutyPolicyEscalationRuleUser from './OnCallDutyPolicyEscalationRuleUser'; -import OnCallDutyPolicyExecutionLog from './OnCallDutyPolicyExecutionLog'; -import OnCallDutyPolicyExecutionLogTimeline from './OnCallDutyPolicyExecutionLogTimeline'; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import OnCallDutyPolicyCustomField from "./OnCallDutyPolicyCustomField"; +import OnCallDutyPolicyEscalationRule from "./OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicyEscalationRuleSchedule from "./OnCallDutyPolicyEscalationRuleSchedule"; +import OnCallDutyPolicyEscalationRuleTeam from "./OnCallDutyPolicyEscalationRuleTeam"; +import OnCallDutyPolicyEscalationRuleUser from "./OnCallDutyPolicyEscalationRuleUser"; +import OnCallDutyPolicyExecutionLog from "./OnCallDutyPolicyExecutionLog"; +import OnCallDutyPolicyExecutionLogTimeline from "./OnCallDutyPolicyExecutionLogTimeline"; // On call duty policy schedule -import OnCallDutyPolicySchedule from './OnCallDutyPolicySchedule'; -import OnCallDutyPolicyScheduleLayer from './OnCallDutyPolicyScheduleLayer'; -import OnCallDutyPolicyScheduleLayerUser from './OnCallDutyPolicyScheduleLayerUser'; -import Probe from './Probe'; -import Project from './Project'; -import ProjectCallSMSConfig from './ProjectCallSMSConfig'; +import OnCallDutyPolicySchedule from "./OnCallDutyPolicySchedule"; +import OnCallDutyPolicyScheduleLayer from "./OnCallDutyPolicyScheduleLayer"; +import OnCallDutyPolicyScheduleLayerUser from "./OnCallDutyPolicyScheduleLayerUser"; +import Probe from "./Probe"; +import Project from "./Project"; +import ProjectCallSMSConfig from "./ProjectCallSMSConfig"; // Project SMTP Config. -import ProjectSmtpConfig from './ProjectSmtpConfig'; +import ProjectSmtpConfig from "./ProjectSmtpConfig"; //SSO -import ProjectSSO from './ProjectSso'; -import PromoCode from './PromoCode'; -import Reseller from './Reseller'; -import ResellerPlan from './ResellerPlan'; +import ProjectSSO from "./ProjectSso"; +import PromoCode from "./PromoCode"; +import Reseller from "./Reseller"; +import ResellerPlan from "./ResellerPlan"; // ScheduledMaintenances -import ScheduledMaintenance from './ScheduledMaintenance'; -import ScheduledMaintenanceCustomField from './ScheduledMaintenanceCustomField'; -import ScheduledMaintenanceInternalNote from './ScheduledMaintenanceInternalNote'; -import ScheduledMaintenanceNoteTemplate from './ScheduledMaintenanceNoteTemplate'; -import ScheduledMaintenanceOwnerTeam from './ScheduledMaintenanceOwnerTeam'; -import ScheduledMaintenanceOwnerUser from './ScheduledMaintenanceOwnerUser'; -import ScheduledMaintenancePublicNote from './ScheduledMaintenancePublicNote'; -import ScheduledMaintenanceState from './ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from './ScheduledMaintenanceStateTimeline'; -import ServiceCatalog from './ServiceCatalog'; -import ServiceCatalogOwnerTeam from './ServiceCatalogOwnerTeam'; -import ServiceCatalogOwnerUser from './ServiceCatalogOwnerUser'; -import ServiceRepository from './ServiceRepository'; +import ScheduledMaintenance from "./ScheduledMaintenance"; +import ScheduledMaintenanceCustomField from "./ScheduledMaintenanceCustomField"; +import ScheduledMaintenanceInternalNote from "./ScheduledMaintenanceInternalNote"; +import ScheduledMaintenanceNoteTemplate from "./ScheduledMaintenanceNoteTemplate"; +import ScheduledMaintenanceOwnerTeam from "./ScheduledMaintenanceOwnerTeam"; +import ScheduledMaintenanceOwnerUser from "./ScheduledMaintenanceOwnerUser"; +import ScheduledMaintenancePublicNote from "./ScheduledMaintenancePublicNote"; +import ScheduledMaintenanceState from "./ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "./ScheduledMaintenanceStateTimeline"; +import ServiceCatalog from "./ServiceCatalog"; +import ServiceCatalogOwnerTeam from "./ServiceCatalogOwnerTeam"; +import ServiceCatalogOwnerUser from "./ServiceCatalogOwnerUser"; +import ServiceRepository from "./ServiceRepository"; // Short link. -import ShortLink from './ShortLink'; +import ShortLink from "./ShortLink"; // SMS -import SmsLog from './SmsLog'; +import SmsLog from "./SmsLog"; // Status Page -import StatusPage from './StatusPage'; -import StatusPageAnnouncement from './StatusPageAnnouncement'; -import StatusPageCustomField from './StatusPageCustomField'; -import StatusPageDomain from './StatusPageDomain'; -import StatusPageFooterLink from './StatusPageFooterLink'; -import StatusPageGroup from './StatusPageGroup'; -import StatusPageHeaderLink from './StatusPageHeaderLink'; -import StatusPageHistoryChartBarColorRule from './StatusPageHistoryChartBarColorRule'; -import StatusPageOwnerTeam from './StatusPageOwnerTeam'; -import StatusPageOwnerUser from './StatusPageOwnerUser'; -import StatusPagePrivateUser from './StatusPagePrivateUser'; -import StatusPageResource from './StatusPageResource'; -import StatusPageSSO from './StatusPageSso'; -import StatusPageSubscriber from './StatusPageSubscriber'; +import StatusPage from "./StatusPage"; +import StatusPageAnnouncement from "./StatusPageAnnouncement"; +import StatusPageCustomField from "./StatusPageCustomField"; +import StatusPageDomain from "./StatusPageDomain"; +import StatusPageFooterLink from "./StatusPageFooterLink"; +import StatusPageGroup from "./StatusPageGroup"; +import StatusPageHeaderLink from "./StatusPageHeaderLink"; +import StatusPageHistoryChartBarColorRule from "./StatusPageHistoryChartBarColorRule"; +import StatusPageOwnerTeam from "./StatusPageOwnerTeam"; +import StatusPageOwnerUser from "./StatusPageOwnerUser"; +import StatusPagePrivateUser from "./StatusPagePrivateUser"; +import StatusPageResource from "./StatusPageResource"; +import StatusPageSSO from "./StatusPageSso"; +import StatusPageSubscriber from "./StatusPageSubscriber"; // Team -import Team from './Team'; -import TeamMember from './TeamMember'; -import TeamPermission from './TeamPermission'; -import TelemetryService from './TelemetryService'; -import UsageBilling from './TelemetryUsageBilling'; -import User from './User'; -import UserCall from './UserCall'; +import Team from "./Team"; +import TeamMember from "./TeamMember"; +import TeamPermission from "./TeamPermission"; +import TelemetryService from "./TelemetryService"; +import UsageBilling from "./TelemetryUsageBilling"; +import User from "./User"; +import UserCall from "./UserCall"; // Notification Methods -import UserEmail from './UserEmail'; +import UserEmail from "./UserEmail"; // User Notification Rules -import UserNotificationRule from './UserNotificationRule'; -import UserNotificationSetting from './UserNotificationSetting'; -import UserOnCallLog from './UserOnCallLog'; -import UserOnCallLogTimeline from './UserOnCallLogTimeline'; -import UserSms from './UserSMS'; +import UserNotificationRule from "./UserNotificationRule"; +import UserNotificationSetting from "./UserNotificationSetting"; +import UserOnCallLog from "./UserOnCallLog"; +import UserOnCallLogTimeline from "./UserOnCallLogTimeline"; +import UserSms from "./UserSMS"; // Workflows. -import Workflow from './Workflow'; -import WorkflowLog from './WorkflowLog'; -import WorkflowVariables from './WorkflowVariable'; +import Workflow from "./Workflow"; +import WorkflowLog from "./WorkflowLog"; +import WorkflowVariables from "./WorkflowVariable"; export default [ - User, - Probe, - Project, - EmailVerificationToken, - Team, - TeamMember, - TeamPermission, - ApiKey, - Label, - ApiKeyPermission, - ProjectSmtpConfig, - StatusPage, + User, + Probe, + Project, + EmailVerificationToken, + Team, + TeamMember, + TeamPermission, + ApiKey, + Label, + ApiKeyPermission, + ProjectSmtpConfig, + StatusPage, - OnCallDutyPolicy, - OnCallDutyPolicyCustomField, - OnCallDutyPolicyEscalationRule, - OnCallDutyPolicyEscalationRuleTeam, - OnCallDutyPolicyEscalationRuleUser, - OnCallDutyPolicyExecutionLog, - OnCallDutyPolicyExecutionLogTimeline, + OnCallDutyPolicy, + OnCallDutyPolicyCustomField, + OnCallDutyPolicyEscalationRule, + OnCallDutyPolicyEscalationRuleTeam, + OnCallDutyPolicyEscalationRuleUser, + OnCallDutyPolicyExecutionLog, + OnCallDutyPolicyExecutionLogTimeline, - Monitor, - MonitorSecret, - MonitorStatus, - MonitorCustomField, + Monitor, + MonitorSecret, + MonitorStatus, + MonitorCustomField, - IncidentState, - Incident, - IncidentCustomField, - IncidentStateTimeline, - MonitorStatusTimeline, - IncidentPublicNote, - IncidentInternalNote, - File, - Domain, + IncidentState, + Incident, + IncidentCustomField, + IncidentStateTimeline, + MonitorStatusTimeline, + IncidentPublicNote, + IncidentInternalNote, + File, + Domain, - StatusPageGroup, - StatusPageDomain, - StatusPageCustomField, - StatusPageResource, - IncidentSeverity, - StatusPageAnnouncement, - StatusPageSubscriber, - StatusPageFooterLink, - StatusPageHeaderLink, - StatusPagePrivateUser, - StatusPageHistoryChartBarColorRule, + StatusPageGroup, + StatusPageDomain, + StatusPageCustomField, + StatusPageResource, + IncidentSeverity, + StatusPageAnnouncement, + StatusPageSubscriber, + StatusPageFooterLink, + StatusPageHeaderLink, + StatusPagePrivateUser, + StatusPageHistoryChartBarColorRule, - ScheduledMaintenanceState, - ScheduledMaintenance, - ScheduledMaintenanceStateTimeline, - ScheduledMaintenancePublicNote, - ScheduledMaintenanceInternalNote, - ScheduledMaintenanceCustomField, + ScheduledMaintenanceState, + ScheduledMaintenance, + ScheduledMaintenanceStateTimeline, + ScheduledMaintenancePublicNote, + ScheduledMaintenanceInternalNote, + ScheduledMaintenanceCustomField, - BillingPaymentMethods, - BillingInvoice, + BillingPaymentMethods, + BillingInvoice, - GreenlockChallenge, - GreenlockCertificate, + GreenlockChallenge, + GreenlockCertificate, - Workflow, - WorkflowVariables, - WorkflowLog, + Workflow, + WorkflowVariables, + WorkflowLog, - ProjectSSO, - StatusPageSSO, + ProjectSSO, + StatusPageSSO, - MonitorProbe, + MonitorProbe, - MonitorOwnerTeam, - MonitorOwnerUser, + MonitorOwnerTeam, + MonitorOwnerUser, - IncidentOwnerTeam, - IncidentOwnerUser, + IncidentOwnerTeam, + IncidentOwnerUser, - ScheduledMaintenanceOwnerTeam, - ScheduledMaintenanceOwnerUser, + ScheduledMaintenanceOwnerTeam, + ScheduledMaintenanceOwnerUser, - StatusPageOwnerTeam, - StatusPageOwnerUser, + StatusPageOwnerTeam, + StatusPageOwnerUser, - SmsLog, - CallLog, - EmailLog, + SmsLog, + CallLog, + EmailLog, - UserEmail, - UserSms, - UserCall, + UserEmail, + UserSms, + UserCall, - UserNotificationRule, - UserOnCallLog, - UserOnCallLogTimeline, - UserNotificationSetting, + UserNotificationRule, + UserOnCallLog, + UserOnCallLogTimeline, + UserNotificationSetting, - DataMigration, + DataMigration, - ShortLink, + ShortLink, - IncidentTemplate, - IncidentTemplateOwnerTeam, - IncidentTemplateOwnerUser, + IncidentTemplate, + IncidentTemplateOwnerTeam, + IncidentTemplateOwnerUser, - IncidentNoteTemplate, + IncidentNoteTemplate, - ScheduledMaintenanceNoteTemplate, + ScheduledMaintenanceNoteTemplate, - Reseller, - ResellerPlan, + Reseller, + ResellerPlan, - PromoCode, + PromoCode, - GlobalConfig, + GlobalConfig, - MonitorGroup, - MonitorGroupOwnerTeam, - MonitorGroupOwnerUser, - MonitorGroupResource, + MonitorGroup, + MonitorGroupOwnerTeam, + MonitorGroupOwnerUser, + MonitorGroupResource, - TelemetryService, + TelemetryService, - OnCallDutyPolicySchedule, - OnCallDutyPolicyScheduleLayer, - OnCallDutyPolicyScheduleLayerUser, + OnCallDutyPolicySchedule, + OnCallDutyPolicyScheduleLayer, + OnCallDutyPolicyScheduleLayerUser, - OnCallDutyPolicyEscalationRuleSchedule, + OnCallDutyPolicyEscalationRuleSchedule, - UsageBilling, + UsageBilling, - ProjectCallSMSConfig, + ProjectCallSMSConfig, - AcmeCertificate, + AcmeCertificate, - AcmeChallenge, + AcmeChallenge, - ServiceCatalog, - ServiceCatalogOwnerTeam, - ServiceCatalogOwnerUser, + ServiceCatalog, + ServiceCatalogOwnerTeam, + ServiceCatalogOwnerUser, - CodeRepository, - CopilotEvent, - ServiceRepository, + CodeRepository, + CopilotEvent, + ServiceRepository, ]; diff --git a/Model/Models/Label.ts b/Model/Models/Label.ts index a389d779ee..950dc99eb7 100644 --- a/Model/Models/Label.ts +++ b/Model/Models/Label.ts @@ -1,383 +1,381 @@ -import Project from './Project'; -import User from './User'; -import AccessControlModel from 'Common/Models/AccessControlModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import Color from 'Common/Types/Color'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import AccessControlModel from "Common/Models/AccessControlModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import Color from "Common/Types/Color"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteProjectLabel, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectLabel, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteProjectLabel, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectLabel, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/label')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/label")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'Label', - singularName: 'Label', - pluralName: 'Labels', - icon: IconProp.Label, - tableDescription: - 'Organize resources for your project by using labels / tags.', + tableName: "Label", + singularName: "Label", + pluralName: "Labels", + icon: IconProp.Label, + tableDescription: + "Organize resources for your project by using labels / tags.", }) @Entity({ - name: 'Label', + name: "Label", }) export default class Label extends AccessControlModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectLabel, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public override name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectLabel, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public override name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectLabel, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectLabel, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectLabel, - ], - }) - @TableColumn({ - title: 'Color', - required: true, - unique: false, - type: TableColumnType.Color, - canReadOnRelationQuery: true, - description: 'Color of this resource in Hex (#32a852 for example)', - }) - @Column({ - type: ColumnType.Color, - length: ColumnLength.Color, - unique: false, - nullable: false, - transformer: Color.getDatabaseTransformer(), - }) - public color?: Color = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectLabel, + ], + }) + @TableColumn({ + title: "Color", + required: true, + unique: false, + type: TableColumnType.Color, + canReadOnRelationQuery: true, + description: "Color of this resource in Hex (#32a852 for example)", + }) + @Column({ + type: ColumnType.Color, + length: ColumnLength.Color, + unique: false, + nullable: false, + transformer: Color.getDatabaseTransformer(), + }) + public color?: Color = undefined; } diff --git a/Model/Models/Monitor.ts b/Model/Models/Monitor.ts index 16b4ed679a..2eb4189124 100644 --- a/Model/Models/Monitor.ts +++ b/Model/Models/Monitor.ts @@ -1,929 +1,926 @@ -import Label from './Label'; -import MonitorStatus from './MonitorStatus'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import IncomingMonitorRequest from 'Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest'; -import MonitorSteps from 'Common/Types/Monitor/MonitorSteps'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import ServerMonitorResponse from 'Common/Types/Monitor/ServerMonitor/ServerMonitorResponse'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Label from "./Label"; +import MonitorStatus from "./MonitorStatus"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest"; +import MonitorSteps from "Common/Types/Monitor/MonitorSteps"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import ServerMonitorResponse from "Common/Types/Monitor/ServerMonitor/ServerMonitorResponse"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() -@AccessControlColumn('labels') -@TenantColumn('projectId') +@AccessControlColumn("labels") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitor, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitor, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/monitor")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'Monitor', + name: "Monitor", }) @TableMetadata({ - tableName: 'Monitor', - singularName: 'Monitor', - pluralName: 'Monitors', - icon: IconProp.AltGlobe, - tableDescription: - 'Monitor is anything that monitors your API, Websites, IP, Network or more. You can also create static monitor that does not monitor anything.', + tableName: "Monitor", + singularName: "Monitor", + pluralName: "Monitors", + icon: IconProp.AltGlobe, + tableDescription: + "Monitor is anything that monitors your API, Websites, IP, Network or more. You can also create static monitor that does not monitor anything.", }) export default class Monitor extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitor, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name for this monitor', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitor, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name for this monitor", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitor, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitor, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitor, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'MonitorLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'monitorId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitor, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "MonitorLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "monitorId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.MonitorType, - title: 'Monitor Type', - description: 'What is the type of this monitor? Website? API? etc.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public monitorType?: MonitorType = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.MonitorType, + title: "Monitor Type", + description: "What is the type of this monitor? Website? API? etc.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public monitorType?: MonitorType = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitor, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'currentMonitorStatusId', - type: TableColumnType.Entity, - modelType: MonitorStatus, - title: 'Current Monitor Status', - description: 'Whats the current status of this monitor?', - }) - @ManyToOne( - () => { - return MonitorStatus; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'currentMonitorStatusId' }) - public currentMonitorStatus?: MonitorStatus = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitor, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "currentMonitorStatusId", + type: TableColumnType.Entity, + modelType: MonitorStatus, + title: "Current Monitor Status", + description: "Whats the current status of this monitor?", + }) + @ManyToOne( + () => { + return MonitorStatus; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "currentMonitorStatusId" }) + public currentMonitorStatus?: MonitorStatus = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitor, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Current Monitor Status ID', - description: 'Whats the current status ID of this monitor?', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public currentMonitorStatusId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitor, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Current Monitor Status ID", + description: "Whats the current status ID of this monitor?", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public currentMonitorStatusId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitor, - ], - }) - @TableColumn({ - type: TableColumnType.JSON, - required: false, - title: 'Monitor Steps', - description: 'What would you like to monitor and what is the criteria?', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - transformer: MonitorSteps.getDatabaseTransformer(), - }) - public monitorSteps?: MonitorSteps = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitor, + ], + }) + @TableColumn({ + type: TableColumnType.JSON, + required: false, + title: "Monitor Steps", + description: "What would you like to monitor and what is the criteria?", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + transformer: MonitorSteps.getDatabaseTransformer(), + }) + public monitorSteps?: MonitorSteps = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitor, - ], - }) - @Index() - @TableColumn({ - required: false, - type: TableColumnType.ShortText, - title: 'Monitoring Interval', - description: - 'How often would you like OneUptime to monitor this resource?', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public monitoringInterval?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitor, + ], + }) + @Index() + @TableColumn({ + required: false, + type: TableColumnType.ShortText, + title: "Monitoring Interval", + description: "How often would you like OneUptime to monitor this resource?", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public monitoringInterval?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitor, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - title: 'Custom Fields', - description: 'Custom Fields on this resource.', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public customFields?: JSONObject = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitor, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + title: "Custom Fields", + description: "Custom Fields on this resource.", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public customFields?: JSONObject = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified Of Resource Creation?', - description: 'Are owners notified of when this resource is created?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotifiedOfResourceCreation?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified Of Resource Creation?", + description: "Are owners notified of when this resource is created?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotifiedOfResourceCreation?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Disable Monitoring', - description: 'Disable active monitoring for this resource?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public disableActiveMonitoring?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Disable Monitoring", + description: "Disable active monitoring for this resource?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public disableActiveMonitoring?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.Date, - required: false, - isDefaultValueColumn: false, - title: 'Incoming Request Received At', - description: - 'This field is for Incoming Request monitor only. When was the last time we received a request?', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - }) - public incomingRequestReceivedAt?: Date = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.Date, + required: false, + isDefaultValueColumn: false, + title: "Incoming Request Received At", + description: + "This field is for Incoming Request monitor only. When was the last time we received a request?", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + }) + public incomingRequestReceivedAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Disable Monitoring because of Ongoing Scheduled Maintenance Event', - description: - 'Disable Monitoring because of Ongoing Scheduled Maintenance Event', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public disableActiveMonitoringBecauseOfScheduledMaintenanceEvent?: boolean = - undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Disable Monitoring because of Ongoing Scheduled Maintenance Event", + description: + "Disable Monitoring because of Ongoing Scheduled Maintenance Event", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public disableActiveMonitoringBecauseOfScheduledMaintenanceEvent?: boolean = + undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Disable Monitoring because of Manual Incident', - description: - 'Disable Monitoring because of Incident which is creeated manually by user.', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public disableActiveMonitoringBecauseOfManualIncident?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Disable Monitoring because of Manual Incident", + description: + "Disable Monitoring because of Incident which is creeated manually by user.", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public disableActiveMonitoringBecauseOfManualIncident?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.Date, - required: false, - isDefaultValueColumn: false, - title: 'Server Monitor Request Received At', - description: - 'This field is for Server Monitor only. When was the last time we received a request?', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - }) - public serverMonitorRequestReceivedAt?: Date = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.Date, + required: false, + isDefaultValueColumn: false, + title: "Server Monitor Request Received At", + description: + "This field is for Server Monitor only. When was the last time we received a request?", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + }) + public serverMonitorRequestReceivedAt?: Date = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - isDefaultValueColumn: false, - title: 'Server Monitor Secret Key', - description: - 'This field is for Server Monitor only. Secret Key to authenticate the request.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public serverMonitorSecretKey?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + isDefaultValueColumn: false, + title: "Server Monitor Secret Key", + description: + "This field is for Server Monitor only. Secret Key to authenticate the request.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public serverMonitorSecretKey?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - isDefaultValueColumn: false, - title: 'Incoming Request Secret Key', - description: - 'This field is for Incoming Request Monitor only. Secret Key to authenticate the request.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incomingRequestSecretKey?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + isDefaultValueColumn: false, + title: "Incoming Request Secret Key", + description: + "This field is for Incoming Request Monitor only. Secret Key to authenticate the request.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incomingRequestSecretKey?: ObjectID = undefined; - // this is for incoming request monitor. + // this is for incoming request monitor. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.JSON, - required: false, - title: 'Incoming Monitor Request', - description: 'Incoming Monitor Request for Incoming Request Monitor', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public incomingMonitorRequest?: IncomingMonitorRequest = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.JSON, + required: false, + title: "Incoming Monitor Request", + description: "Incoming Monitor Request for Incoming Request Monitor", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public incomingMonitorRequest?: IncomingMonitorRequest = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitor, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitor, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.JSON, - required: false, - title: 'Server Monitor Response', - description: 'Server Monitor Response for Server Monitor', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public serverMonitorResponse?: ServerMonitorResponse = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitor, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitor, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.JSON, + required: false, + title: "Server Monitor Response", + description: "Server Monitor Response for Server Monitor", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public serverMonitorResponse?: ServerMonitorResponse = undefined; } diff --git a/Model/Models/MonitorCustomField.ts b/Model/Models/MonitorCustomField.ts index 876748e565..d6d19ff0c6 100644 --- a/Model/Models/MonitorCustomField.ts +++ b/Model/Models/MonitorCustomField.ts @@ -1,342 +1,340 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import CustomFieldType from 'Common/Types/CustomField/CustomFieldType'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import CustomFieldType from "Common/Types/CustomField/CustomFieldType"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteMonitorCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditMonitorCustomField, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteMonitorCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditMonitorCustomField, + ], }) -@CrudApiEndpoint(new Route('/monitor-custom-field')) +@CrudApiEndpoint(new Route("/monitor-custom-field")) @TableMetadata({ - tableName: 'MonitorCustomField', - singularName: 'Monitor Custom Field', - pluralName: 'Monitor Custom Fields', - icon: IconProp.TableCells, - tableDescription: 'Manage custom fields for your monitor', + tableName: "MonitorCustomField", + singularName: "Monitor Custom Field", + pluralName: "Monitor Custom Fields", + icon: IconProp.TableCells, + tableDescription: "Manage custom fields for your monitor", }) @Entity({ - name: 'MonitorCustomField', + name: "MonitorCustomField", }) export default class MonitorCustomField extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditMonitorCustomField, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditMonitorCustomField, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditMonitorCustomField, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: - 'Friendly description of this custom field that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditMonitorCustomField, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: + "Friendly description of this custom field that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.CustomFieldType, - title: 'Custom Field Type', - description: 'Is this field Text, Number or Boolean?', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public type?: CustomFieldType = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.CustomFieldType, + title: "Custom Field Type", + description: "Is this field Text, Number or Boolean?", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public type?: CustomFieldType = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorCustomField, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorCustomField, + ], + 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; } diff --git a/Model/Models/MonitorGroup.ts b/Model/Models/MonitorGroup.ts index 84dfaaa6d0..8d000a9e72 100644 --- a/Model/Models/MonitorGroup.ts +++ b/Model/Models/MonitorGroup.ts @@ -1,412 +1,410 @@ -import Label from './Label'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Label from "./Label"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Scale, - read: PlanSelect.Scale, - update: PlanSelect.Scale, - delete: PlanSelect.Scale, + create: PlanSelect.Scale, + read: PlanSelect.Scale, + update: PlanSelect.Scale, + delete: PlanSelect.Scale, }) -@AccessControlColumn('labels') -@TenantColumn('projectId') +@AccessControlColumn("labels") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroup, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteMonitorGroup, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorGroup, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroup, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteMonitorGroup, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorGroup, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-group')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/monitor-group")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'MonitorGroup', + name: "MonitorGroup", }) @TableMetadata({ - tableName: 'MonitorGroup', - singularName: 'Monitor Group', - pluralName: 'Monitor Groups', - icon: IconProp.Folder, - tableDescription: - 'Monitor Groups are a way to organize your monitors into groups. You can create as many groups as you want and add as many monitors as you want to each group.', + tableName: "MonitorGroup", + singularName: "Monitor Group", + pluralName: "Monitor Groups", + icon: IconProp.Folder, + tableDescription: + "Monitor Groups are a way to organize your monitors into groups. You can create as many groups as you want and add as many monitors as you want to each group.", }) export default class MonitorGroup extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroup, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroup, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroup, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroup, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroup, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorGroup, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name for this monitor group', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroup, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorGroup, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name for this monitor group", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroup, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorGroup, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroup, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorGroup, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroup, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroup, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroup, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroup, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroup, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroup, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroup, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorGroup, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'MonitorGroupLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'monitorGroupId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroup, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorGroup, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "MonitorGroupLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "monitorGroupId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; } diff --git a/Model/Models/MonitorGroupOwnerTeam.ts b/Model/Models/MonitorGroupOwnerTeam.ts index c8b02eb7a0..c85ef4eabb 100644 --- a/Model/Models/MonitorGroupOwnerTeam.ts +++ b/Model/Models/MonitorGroupOwnerTeam.ts @@ -1,428 +1,426 @@ -import MonitorGroup from './MonitorGroup'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import MonitorGroup from "./MonitorGroup"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteMonitorGroupOwnerTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorGroupOwnerTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteMonitorGroupOwnerTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorGroupOwnerTeam, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-group-owner-team')) +@CrudApiEndpoint(new Route("/monitor-group-owner-team")) @TableMetadata({ - tableName: 'MonitorGroupOwnerTeam', - singularName: 'Monitor Group Team Owner', - pluralName: 'Monitor Group Team Owners', - icon: IconProp.Signal, - tableDescription: 'Add teams as owners to your monitor groups.', + tableName: "MonitorGroupOwnerTeam", + singularName: "Monitor Group Team Owner", + pluralName: "Monitor Group Team Owners", + icon: IconProp.Signal, + tableDescription: "Add teams as owners to your monitor groups.", }) @Entity({ - name: 'MonitorGroupOwnerTeam', + name: "MonitorGroupOwnerTeam", }) export default class MonitorGroupOwnerTeam extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: - 'Team that is the owner. All users in this team will receive notifications. ', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: + "Team that is the owner. All users in this team will receive notifications. ", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Team ID', - description: 'ID of your OneUptime Team in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Team ID", + description: "ID of your OneUptime Team in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorGroupId', - type: TableColumnType.Entity, - modelType: MonitorGroup, - title: 'MonitorGroup', - description: - 'Relation to MonitorGroup Resource in which this object belongs', - }) - @ManyToOne( - () => { - return MonitorGroup; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorGroupId' }) - public monitorGroup?: MonitorGroup = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorGroupId", + type: TableColumnType.Entity, + modelType: MonitorGroup, + title: "MonitorGroup", + description: + "Relation to MonitorGroup Resource in which this object belongs", + }) + @ManyToOne( + () => { + return MonitorGroup; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorGroupId" }) + public monitorGroup?: MonitorGroup = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'MonitorGroup ID', - description: - 'ID of your OneUptime MonitorGroup in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorGroupId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "MonitorGroup ID", + description: + "ID of your OneUptime MonitorGroup in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorGroupId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/MonitorGroupOwnerUser.ts b/Model/Models/MonitorGroupOwnerUser.ts index 813625c3ea..ef8169f91b 100644 --- a/Model/Models/MonitorGroupOwnerUser.ts +++ b/Model/Models/MonitorGroupOwnerUser.ts @@ -1,427 +1,425 @@ -import MonitorGroup from './MonitorGroup'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import MonitorGroup from "./MonitorGroup"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteMonitorGroupOwnerUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorGroupOwnerUser, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteMonitorGroupOwnerUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorGroupOwnerUser, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-group-owner-user')) +@CrudApiEndpoint(new Route("/monitor-group-owner-user")) @TableMetadata({ - tableName: 'MonitorGroupOwnerUser', - singularName: 'Monitor Group User Owner', - pluralName: 'Monitor Group User Owners', - icon: IconProp.Signal, - tableDescription: 'Add users as owners to your monitor group.', + tableName: "MonitorGroupOwnerUser", + singularName: "Monitor Group User Owner", + pluralName: "Monitor Group User Owners", + icon: IconProp.Signal, + tableDescription: "Add users as owners to your monitor group.", }) @Entity({ - name: 'MonitorGroupOwnerUser', + name: "MonitorGroupOwnerUser", }) export default class MonitorGroupOwnerUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: - 'User that is the owner. This user will receive notifications. ', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: + "User that is the owner. This user will receive notifications. ", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'User ID', - description: 'ID of your OneUptime User in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "User ID", + description: "ID of your OneUptime User in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorGroupId', - type: TableColumnType.Entity, - modelType: MonitorGroup, - title: 'MonitorGroup', - description: - 'Relation to MonitorGroup Resource in which this object belongs', - }) - @ManyToOne( - () => { - return MonitorGroup; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorGroupId' }) - public monitorGroup?: MonitorGroup = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorGroupId", + type: TableColumnType.Entity, + modelType: MonitorGroup, + title: "MonitorGroup", + description: + "Relation to MonitorGroup Resource in which this object belongs", + }) + @ManyToOne( + () => { + return MonitorGroup; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorGroupId" }) + public monitorGroup?: MonitorGroup = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'MonitorGroup ID', - description: - 'ID of your OneUptime MonitorGroup in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorGroupId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "MonitorGroup ID", + description: + "ID of your OneUptime MonitorGroup in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorGroupId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/MonitorGroupResource.ts b/Model/Models/MonitorGroupResource.ts index 75e4838902..a39f672fcb 100644 --- a/Model/Models/MonitorGroupResource.ts +++ b/Model/Models/MonitorGroupResource.ts @@ -1,391 +1,386 @@ -import Monitor from './Monitor'; -import MonitorGroup from './MonitorGroup'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Monitor from "./Monitor"; +import MonitorGroup from "./MonitorGroup"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('monitorGroup') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("monitorGroup") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupResource, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteMonitorGroupResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorGroupResource, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupResource, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteMonitorGroupResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorGroupResource, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-group-resource')) +@CrudApiEndpoint(new Route("/monitor-group-resource")) @TableMetadata({ - tableName: 'MonitorGroupResource', - singularName: 'Monitor Group Resource', - pluralName: 'Monitor Group Resources', - icon: IconProp.AltGlobe, - tableDescription: 'Add monitors to your monitor group', + tableName: "MonitorGroupResource", + singularName: "Monitor Group Resource", + pluralName: "Monitor Group Resources", + icon: IconProp.AltGlobe, + tableDescription: "Add monitors to your monitor group", }) @Entity({ - name: 'MonitorGroupResource', + name: "MonitorGroupResource", }) export default class MonitorGroupResource extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupResource, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupResource, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupResource, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupResource, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupResource, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorGroupId', - type: TableColumnType.Entity, - modelType: MonitorGroup, - title: 'Monitor Group', - description: - 'Relation to Monitor Group Resource in which this object belongs', - }) - @ManyToOne( - () => { - return MonitorGroup; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorGroupId' }) - public monitorGroup?: MonitorGroup = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupResource, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorGroupId", + type: TableColumnType.Entity, + modelType: MonitorGroup, + title: "Monitor Group", + description: + "Relation to Monitor Group Resource in which this object belongs", + }) + @ManyToOne( + () => { + return MonitorGroup; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorGroupId" }) + public monitorGroup?: MonitorGroup = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupResource, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Monitor Group ID', - description: - 'ID of your Monitor Group resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorGroupId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupResource, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Monitor Group ID", + description: "ID of your Monitor Group resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorGroupId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorGroupResource, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorId', - type: TableColumnType.Entity, - modelType: Monitor, - title: 'Monitor', - description: - 'Relation to Monitor Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Monitor; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorId' }) - public monitor?: Monitor = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorGroupResource, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorId", + type: TableColumnType.Entity, + modelType: Monitor, + title: "Monitor", + description: "Relation to Monitor Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Monitor; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorId" }) + public monitor?: Monitor = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorGroupResource, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Monitor ID', - description: - 'Relation to Monitor ID Resource in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorGroupResource, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Monitor ID", + description: "Relation to Monitor ID Resource in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupResource, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupResource, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorGroupResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorGroupResource, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorGroupResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorGroupResource, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/MonitorOwnerTeam.ts b/Model/Models/MonitorOwnerTeam.ts index 472b2d4746..d3753812de 100644 --- a/Model/Models/MonitorOwnerTeam.ts +++ b/Model/Models/MonitorOwnerTeam.ts @@ -1,428 +1,424 @@ -import Monitor from './Monitor'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Monitor from "./Monitor"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteMonitorOwnerTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorOwnerTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteMonitorOwnerTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorOwnerTeam, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-owner-team')) +@CrudApiEndpoint(new Route("/monitor-owner-team")) @TableMetadata({ - tableName: 'MonitorOwnerTeam', - singularName: 'Monitor Team Owner', - pluralName: 'Monitor Team Owners', - icon: IconProp.Signal, - tableDescription: 'Add teams as owners to your monitors.', + tableName: "MonitorOwnerTeam", + singularName: "Monitor Team Owner", + pluralName: "Monitor Team Owners", + icon: IconProp.Signal, + tableDescription: "Add teams as owners to your monitors.", }) @Entity({ - name: 'MonitorOwnerTeam', + name: "MonitorOwnerTeam", }) export default class MonitorOwnerTeam extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: - 'Team that is the owner. All users in this team will receive notifications. ', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: + "Team that is the owner. All users in this team will receive notifications. ", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Team ID', - description: 'ID of your OneUptime Team in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Team ID", + description: "ID of your OneUptime Team in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorId', - type: TableColumnType.Entity, - modelType: Monitor, - title: 'Monitor', - description: - 'Relation to Monitor Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Monitor; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorId' }) - public monitor?: Monitor = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorId", + type: TableColumnType.Entity, + modelType: Monitor, + title: "Monitor", + description: "Relation to Monitor Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Monitor; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorId" }) + public monitor?: Monitor = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Monitor ID', - description: - 'ID of your OneUptime Monitor in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Monitor ID", + description: "ID of your OneUptime Monitor in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/MonitorOwnerUser.ts b/Model/Models/MonitorOwnerUser.ts index da406d3cc2..5df7e8476f 100644 --- a/Model/Models/MonitorOwnerUser.ts +++ b/Model/Models/MonitorOwnerUser.ts @@ -1,427 +1,423 @@ -import Monitor from './Monitor'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Monitor from "./Monitor"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteMonitorOwnerUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorOwnerUser, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteMonitorOwnerUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorOwnerUser, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-owner-user')) +@CrudApiEndpoint(new Route("/monitor-owner-user")) @TableMetadata({ - tableName: 'MonitorOwnerUser', - singularName: 'Monitor User Owner', - pluralName: 'Monitor User Owners', - icon: IconProp.Signal, - tableDescription: 'Add users as owners to your monitors.', + tableName: "MonitorOwnerUser", + singularName: "Monitor User Owner", + pluralName: "Monitor User Owners", + icon: IconProp.Signal, + tableDescription: "Add users as owners to your monitors.", }) @Entity({ - name: 'MonitorOwnerUser', + name: "MonitorOwnerUser", }) export default class MonitorOwnerUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: - 'User that is the owner. This user will receive notifications. ', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: + "User that is the owner. This user will receive notifications. ", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'User ID', - description: 'ID of your OneUptime User in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "User ID", + description: "ID of your OneUptime User in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorId', - type: TableColumnType.Entity, - modelType: Monitor, - title: 'Monitor', - description: - 'Relation to Monitor Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Monitor; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorId' }) - public monitor?: Monitor = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorId", + type: TableColumnType.Entity, + modelType: Monitor, + title: "Monitor", + description: "Relation to Monitor Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Monitor; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorId" }) + public monitor?: Monitor = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Monitor ID', - description: - 'ID of your OneUptime Monitor in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Monitor ID", + description: "ID of your OneUptime Monitor in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/MonitorProbe.ts b/Model/Models/MonitorProbe.ts index 6137edf27d..6f0afbcb73 100644 --- a/Model/Models/MonitorProbe.ts +++ b/Model/Models/MonitorProbe.ts @@ -1,484 +1,480 @@ -import Monitor from './Monitor'; -import Probe from './Probe'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import Dictionary from 'Common/Types/Dictionary'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Monitor from "./Monitor"; +import Probe from "./Probe"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import Dictionary from "Common/Types/Dictionary"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; export type MonitorStepProbeResponse = Dictionary<ProbeMonitorResponse>; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteMonitorProbe, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditMonitorProbe, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteMonitorProbe, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditMonitorProbe, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-probe')) +@CrudApiEndpoint(new Route("/monitor-probe")) @TableMetadata({ - tableName: 'MonitorProbe', - singularName: 'Monitor Probe', - pluralName: 'Monitor Probes', - icon: IconProp.Signal, - tableDescription: - 'Add probes to monitor your resource from multiple locations around the world.', + tableName: "MonitorProbe", + singularName: "Monitor Probe", + pluralName: "Monitor Probes", + icon: IconProp.Signal, + tableDescription: + "Add probes to monitor your resource from multiple locations around the world.", }) @Entity({ - name: 'MonitorProbe', + name: "MonitorProbe", }) export default class MonitorProbe extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'probeId', - type: TableColumnType.Entity, - modelType: Probe, - title: 'Probe', - description: 'Relation to Probe Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Probe; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'probeId' }) - public probe?: Probe = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "probeId", + type: TableColumnType.Entity, + modelType: Probe, + title: "Probe", + description: "Relation to Probe Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Probe; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "probeId" }) + public probe?: Probe = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Probe ID', - description: 'ID of your OneUptime Probe in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public probeId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Probe ID", + description: "ID of your OneUptime Probe in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public probeId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorId', - type: TableColumnType.Entity, - modelType: Monitor, - title: 'Monitor', - description: - 'Relation to Monitor Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Monitor; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorId' }) - public monitor?: Monitor = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorId", + type: TableColumnType.Entity, + modelType: Monitor, + title: "Monitor", + description: "Relation to Monitor Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Monitor; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorId" }) + public monitor?: Monitor = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Monitor ID', - description: - 'ID of your OneUptime Monitor in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Monitor ID", + description: "ID of your OneUptime Monitor in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public lastPingAt?: Date = undefined; + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public lastPingAt?: Date = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public nextPingAt?: Date = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public nextPingAt?: Date = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorProbe, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: true, - }) - public isEnabled?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorProbe, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: true, + }) + public isEnabled?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorProbe, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - unique: false, - }) - public lastMonitoringLog?: MonitorStepProbeResponse = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorProbe, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + unique: false, + }) + public lastMonitoringLog?: MonitorStepProbeResponse = undefined; } diff --git a/Model/Models/MonitorSecret.ts b/Model/Models/MonitorSecret.ts index 07bde274be..4e652da9bb 100644 --- a/Model/Models/MonitorSecret.ts +++ b/Model/Models/MonitorSecret.ts @@ -1,405 +1,403 @@ -import Monitor from './Monitor'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Monitor from "./Monitor"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorSecret, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorSecret, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteMonitorSecret, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditMonitorSecret, - ], + Permission.ReadMonitorSecret, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteMonitorSecret, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditMonitorSecret, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-secret')) +@CrudApiEndpoint(new Route("/monitor-secret")) @TableMetadata({ - tableName: 'MonitorSecret', - singularName: 'Monitor Secret', - pluralName: 'Monitor Secrets', - icon: IconProp.Lock, - tableDescription: - 'Monitor Secret is a secret variable that can be used in monitors. For example you can store auth tokens, passwords, etc. in Monitor Secret and use them in your monitors. Monitor Secret is encrypted and only accessible by the probe.', + tableName: "MonitorSecret", + singularName: "Monitor Secret", + pluralName: "Monitor Secrets", + icon: IconProp.Lock, + tableDescription: + "Monitor Secret is a secret variable that can be used in monitors. For example you can store auth tokens, passwords, etc. in Monitor Secret and use them in your monitors. Monitor Secret is encrypted and only accessible by the probe.", }) @Entity({ - name: 'MonitorSecret', + name: "MonitorSecret", }) export default class MonitorSecret extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorSecret, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorSecret, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - 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; + Permission.ReadMonitorSecret, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorSecret, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorSecret, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - 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; + Permission.ReadMonitorSecret, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.CreateMonitorSecret, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + Permission.CreateMonitorSecret, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + Permission.ReadMonitorSecret, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.EditMonitorSecret, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + Permission.EditMonitorSecret, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.CreateMonitorSecret, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + Permission.CreateMonitorSecret, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + Permission.ReadMonitorSecret, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.EditMonitorSecret, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + Permission.EditMonitorSecret, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorSecret, - ], - read: [], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditMonitorSecret, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - encrypted: true, - title: 'Secret Value', - description: - 'Secret value that you want to store in this object. This value will be encrypted and only accessible by the probe.', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public secretValue?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorSecret, + ], + read: [], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditMonitorSecret, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + encrypted: true, + title: "Secret Value", + description: + "Secret value that you want to store in this object. This value will be encrypted and only accessible by the probe.", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public secretValue?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditMonitorSecret, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Monitor, - title: 'Monitors', - description: 'List of monitors that can access this secret', - }) - @ManyToMany( - () => { - return Monitor; - }, - { eager: false } - ) - @JoinTable({ - name: 'MonitorSecretMonitor', - inverseJoinColumn: { - name: 'monitorId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'monitorSecretId', - referencedColumnName: '_id', - }, - }) - public monitors?: Array<Monitor> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadMonitorSecret, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadMonitorSecret, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditMonitorSecret, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Monitor, + title: "Monitors", + description: "List of monitors that can access this secret", + }) + @ManyToMany( + () => { + return Monitor; + }, + { eager: false }, + ) + @JoinTable({ + name: "MonitorSecretMonitor", + inverseJoinColumn: { + name: "monitorId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "monitorSecretId", + referencedColumnName: "_id", + }, + }) + public monitors?: Array<Monitor> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorSecret, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorSecret, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + Permission.ReadMonitorSecret, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateMonitorSecret, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateMonitorSecret, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - update: [], - }) - @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; + Permission.ReadMonitorSecret, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + Permission.ReadMonitorSecret, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadMonitorSecret, - ], - 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; + Permission.ReadMonitorSecret, + ], + 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; } diff --git a/Model/Models/MonitorStatus.ts b/Model/Models/MonitorStatus.ts index ffb5c0a99b..ae48f51925 100644 --- a/Model/Models/MonitorStatus.ts +++ b/Model/Models/MonitorStatus.ts @@ -1,495 +1,493 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import Color from 'Common/Types/Color'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import Color from "Common/Types/Color"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectMonitorStatus, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitorStatus, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectMonitorStatus, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitorStatus, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-status')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/monitor-status")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'MonitorStatus', - singularName: 'Monitor Status', - pluralName: 'Monitor Statuses', - icon: IconProp.AltGlobe, - tableDescription: - 'Manage monitor status in your project. Monitor Status are Operational, Degraded and Offline for example. Add custom status like Monitoring or more.', + tableName: "MonitorStatus", + singularName: "Monitor Status", + pluralName: "Monitor Statuses", + icon: IconProp.AltGlobe, + tableDescription: + "Manage monitor status in your project. Monitor Status are Operational, Degraded and Offline for example. Add custom status like Monitoring or more.", }) @Entity({ - name: 'MonitorStatus', + name: "MonitorStatus", }) export default class MonitorStatus extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitorStatus, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitorStatus, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitorStatus, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitorStatus, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitorStatus, - ], - }) - @TableColumn({ - title: 'Color', - required: true, - unique: false, - type: TableColumnType.Color, - canReadOnRelationQuery: true, - description: 'Color of this resource in Hex (#32a852 for example)', - }) - @Column({ - type: ColumnType.Color, - length: ColumnLength.Color, - unique: false, - nullable: false, - transformer: Color.getDatabaseTransformer(), - }) - public color?: Color = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitorStatus, + ], + }) + @TableColumn({ + title: "Color", + required: true, + unique: false, + type: TableColumnType.Color, + canReadOnRelationQuery: true, + description: "Color of this resource in Hex (#32a852 for example)", + }) + @Column({ + type: ColumnType.Color, + length: ColumnLength.Color, + unique: false, + nullable: false, + transformer: Color.getDatabaseTransformer(), + }) + public color?: Color = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitorStatus, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Is Operational State', - description: 'Is this monitor in operational state?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isOperationalState?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitorStatus, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Is Operational State", + description: "Is this monitor in operational state?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isOperationalState?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitorStatus, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Is Offline State', - description: 'Is this monitor in offline state?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isOfflineState?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitorStatus, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Is Offline State", + description: "Is this monitor in offline state?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isOfflineState?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectMonitorStatus, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectMonitorStatus, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectMonitorStatus, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Number, - canReadOnRelationQuery: true, - title: 'Order', - description: - 'Order / Priority of this status. For example: Operational has priority 1, Degraded has 2, Offline has 3. Lower priority would mean bad state of the resource. ', - }) - @Column({ - type: ColumnType.Number, - }) - public priority?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectMonitorStatus, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectMonitorStatus, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectMonitorStatus, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Number, + canReadOnRelationQuery: true, + title: "Order", + description: + "Order / Priority of this status. For example: Operational has priority 1, Degraded has 2, Offline has 3. Lower priority would mean bad state of the resource. ", + }) + @Column({ + type: ColumnType.Number, + }) + public priority?: number = undefined; } diff --git a/Model/Models/MonitorStatusTimeline.ts b/Model/Models/MonitorStatusTimeline.ts index 7e56100c08..0f3edd1152 100644 --- a/Model/Models/MonitorStatusTimeline.ts +++ b/Model/Models/MonitorStatusTimeline.ts @@ -1,531 +1,527 @@ -import Monitor from './Monitor'; -import MonitorStatus from './MonitorStatus'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Monitor from "./Monitor"; +import MonitorStatus from "./MonitorStatus"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('monitor') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("monitor") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteMonitorStatusTimeline, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorStatusTimeline, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteMonitorStatusTimeline, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorStatusTimeline, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/monitor-status-timeline')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/monitor-status-timeline")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'MonitorStatusTimeline', + name: "MonitorStatusTimeline", }) @TableMetadata({ - tableName: 'MonitorStatusTimeline', - singularName: 'Monitor Status Event', - pluralName: 'Monitor Status Events', - icon: IconProp.List, - tableDescription: - 'Change state of the monitor (Operational to Offline for example)', + tableName: "MonitorStatusTimeline", + singularName: "Monitor Status Event", + pluralName: "Monitor Status Events", + icon: IconProp.List, + tableDescription: + "Change state of the monitor (Operational to Offline for example)", }) export default class MonitorStatusTimeline extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorId', - type: TableColumnType.Entity, - modelType: Monitor, - title: 'Monitor', - description: - 'Relation to Monitor Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Monitor; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorId' }) - public monitor?: Monitor = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorId", + type: TableColumnType.Entity, + modelType: Monitor, + title: "Monitor", + description: "Relation to Monitor Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Monitor; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorId" }) + public monitor?: Monitor = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Monitor ID', - description: - 'Relation to Monitor ID Resource in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Monitor ID", + description: "Relation to Monitor ID Resource in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorStatusTimeline, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorStatusId', - type: TableColumnType.Entity, - modelType: MonitorStatus, - title: 'Monitor Status', - description: - 'Relation to Monitor Status Resource in which this object belongs', - }) - @ManyToOne( - () => { - return MonitorStatus; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorStatusId' }) - public monitorStatus?: MonitorStatus = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorStatusTimeline, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorStatusId", + type: TableColumnType.Entity, + modelType: MonitorStatus, + title: "Monitor Status", + description: + "Relation to Monitor Status Resource in which this object belongs", + }) + @ManyToOne( + () => { + return MonitorStatus; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorStatusId" }) + public monitorStatus?: MonitorStatus = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditMonitorStatusTimeline, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Monitor Status ID', - description: - 'Relation to Monitor Status ID Resource in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorStatusId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditMonitorStatusTimeline, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Monitor Status ID", + description: + "Relation to Monitor Status ID Resource in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorStatusId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of status change?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of status change?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - unique: false, - }) - public statusChangeLog?: JSONObject = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + unique: false, + }) + public statusChangeLog?: JSONObject = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Markdown, - required: false, - isDefaultValueColumn: false, - title: 'Root Cause', - description: 'What is the root cause of this status change?', - }) - @Column({ - type: ColumnType.Markdown, - nullable: true, - }) - public rootCause?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Markdown, + required: false, + isDefaultValueColumn: false, + title: "Root Cause", + description: "What is the root cause of this status change?", + }) + @Column({ + type: ColumnType.Markdown, + nullable: true, + }) + public rootCause?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Date, - title: 'Ends At', - description: 'When did this status change end?', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public endsAt?: Date = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Date, + title: "Ends At", + description: "When did this status change end?", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public endsAt?: Date = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateMonitorStatusTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadMonitorStatusTimeline, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Date, - title: 'Starts At', - description: 'When did this status change start?', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public startsAt?: Date = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateMonitorStatusTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadMonitorStatusTimeline, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Date, + title: "Starts At", + description: "When did this status change start?", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public startsAt?: Date = undefined; } diff --git a/Model/Models/OnCallDutyPolicy.ts b/Model/Models/OnCallDutyPolicy.ts index c016995942..05d80a4349 100644 --- a/Model/Models/OnCallDutyPolicy.ts +++ b/Model/Models/OnCallDutyPolicy.ts @@ -1,501 +1,499 @@ -import Label from './Label'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Label from "./Label"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() -@AccessControlColumn('labels') -@TenantColumn('projectId') +@AccessControlColumn("labels") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectOnCallDutyPolicy, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicy, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectOnCallDutyPolicy, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicy, + ], }) -@CrudApiEndpoint(new Route('/on-call-duty-policy')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/on-call-duty-policy")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'OnCallDutyPolicy', + name: "OnCallDutyPolicy", }) @TableMetadata({ - tableName: 'OnCallDutyPolicy', - singularName: 'On-Call Policy', - pluralName: 'On-Call Duty Policies', - icon: IconProp.Call, - tableDescription: - 'Manage on-call duty, schedules and roster for your project', + tableName: "OnCallDutyPolicy", + singularName: "On-Call Policy", + pluralName: "On-Call Duty Policies", + icon: IconProp.Call, + tableDescription: + "Manage on-call duty, schedules and roster for your project", }) export default class OnCallDutyPolicy extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicy, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name of this object', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicy, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name of this object", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicy, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'OnCallDutyPolicyLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'onCallDutyPolicyId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicy, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "OnCallDutyPolicyLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "onCallDutyPolicyId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicy, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicy, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicy, - ], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Repeat Policy If No One Acknowledges', - description: 'Repeat the policy if no one acknowledges the alert', - }) - @Column({ - nullable: false, - default: false, - type: ColumnType.Boolean, - }) - public repeatPolicyIfNoOneAcknowledges?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicy, + ], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Repeat Policy If No One Acknowledges", + description: "Repeat the policy if no one acknowledges the alert", + }) + @Column({ + nullable: false, + default: false, + type: ColumnType.Boolean, + }) + public repeatPolicyIfNoOneAcknowledges?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicy, - ], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Repeat Policy Times If No One Acknowledges', - description: - 'Repeat the policy X number of times if no one acknowledges the alert', - }) - @Column({ - nullable: false, - default: 0, - type: ColumnType.Number, - }) - public repeatPolicyIfNoOneAcknowledgesNoOfTimes?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicy, + ], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Repeat Policy Times If No One Acknowledges", + description: + "Repeat the policy X number of times if no one acknowledges the alert", + }) + @Column({ + nullable: false, + default: 0, + type: ColumnType.Number, + }) + public repeatPolicyIfNoOneAcknowledgesNoOfTimes?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicy, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicy, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicy, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - title: 'Custom Fields', - description: 'Custom Fields on this resource.', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public customFields?: JSONObject = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicy, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicy, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicy, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + title: "Custom Fields", + description: "Custom Fields on this resource.", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public customFields?: JSONObject = undefined; } diff --git a/Model/Models/OnCallDutyPolicyCustomField.ts b/Model/Models/OnCallDutyPolicyCustomField.ts index 89be1e09b9..e62b8f90bd 100644 --- a/Model/Models/OnCallDutyPolicyCustomField.ts +++ b/Model/Models/OnCallDutyPolicyCustomField.ts @@ -1,342 +1,340 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import CustomFieldType from 'Common/Types/CustomField/CustomFieldType'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import CustomFieldType from "Common/Types/CustomField/CustomFieldType"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateOnCallDutyPolicyCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteOnCallDutyPolicyCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditOnCallDutyPolicyCustomField, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateOnCallDutyPolicyCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteOnCallDutyPolicyCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditOnCallDutyPolicyCustomField, + ], }) -@CrudApiEndpoint(new Route('/on-call-duty-policy-custom-field')) +@CrudApiEndpoint(new Route("/on-call-duty-policy-custom-field")) @TableMetadata({ - tableName: 'OnCallDutyPolicyCustomField', - singularName: 'On-Call Policy Custom Field', - pluralName: 'On-Call Policy Custom Fields', - icon: IconProp.TableCells, - tableDescription: 'Manage custom fields for your on-call policy', + tableName: "OnCallDutyPolicyCustomField", + singularName: "On-Call Policy Custom Field", + pluralName: "On-Call Policy Custom Fields", + icon: IconProp.TableCells, + tableDescription: "Manage custom fields for your on-call policy", }) @Entity({ - name: 'OnCallDutyPolicyCustomField', + name: "OnCallDutyPolicyCustomField", }) export default class OnCallDutyPolicyCustomField extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateOnCallDutyPolicyCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateOnCallDutyPolicyCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateOnCallDutyPolicyCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateOnCallDutyPolicyCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateOnCallDutyPolicyCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditOnCallDutyPolicyCustomField, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateOnCallDutyPolicyCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditOnCallDutyPolicyCustomField, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateOnCallDutyPolicyCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditOnCallDutyPolicyCustomField, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: - 'Friendly description of this custom field that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateOnCallDutyPolicyCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditOnCallDutyPolicyCustomField, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: + "Friendly description of this custom field that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateOnCallDutyPolicyCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.CustomFieldType, - title: 'Custom Field Type', - description: 'Is this field Text, Number or Boolean?', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public type?: CustomFieldType = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateOnCallDutyPolicyCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.CustomFieldType, + title: "Custom Field Type", + description: "Is this field Text, Number or Boolean?", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public type?: CustomFieldType = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateOnCallDutyPolicyCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateOnCallDutyPolicyCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateOnCallDutyPolicyCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateOnCallDutyPolicyCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyCustomField, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyCustomField, + ], + 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; } diff --git a/Model/Models/OnCallDutyPolicyEscalationRule.ts b/Model/Models/OnCallDutyPolicyEscalationRule.ts index cc08c2d0b6..e338a0574d 100644 --- a/Model/Models/OnCallDutyPolicyEscalationRule.ts +++ b/Model/Models/OnCallDutyPolicyEscalationRule.ts @@ -1,441 +1,439 @@ -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectOnCallDutyPolicyEscalationRule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicyEscalationRule, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectOnCallDutyPolicyEscalationRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicyEscalationRule, + ], }) -@CrudApiEndpoint(new Route('/on-call-duty-policy-esclation-rule')) +@CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule")) @Entity({ - name: 'OnCallDutyPolicyEscalationRule', + name: "OnCallDutyPolicyEscalationRule", }) @TableMetadata({ - tableName: 'OnCallDutyPolicyEscalationRule', - singularName: 'Escalation Rule', - pluralName: 'Escalation Rules', - icon: IconProp.Call, - tableDescription: - 'Manage on-call duty escalation rule for the on-call policy.', + tableName: "OnCallDutyPolicyEscalationRule", + singularName: "Escalation Rule", + pluralName: "Escalation Rules", + icon: IconProp.Call, + tableDescription: + "Manage on-call duty escalation rule for the on-call policy.", }) export default class OnCallDutyPolicyEscalationRule extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicy, - title: 'On-Call Policy', - description: - 'Relation to On-Call Policy where this escalation rule belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicy; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyId' }) - public onCallDutyPolicy?: OnCallDutyPolicy = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicy, + title: "On-Call Policy", + description: + "Relation to On-Call Policy where this escalation rule belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicy; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyId" }) + public onCallDutyPolicy?: OnCallDutyPolicy = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy ID', - description: - 'ID of your On-Call Policy where this escalation rule belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy ID", + description: + "ID of your On-Call Policy where this escalation rule belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicyEscalationRule, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name of this object', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicyEscalationRule, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name of this object", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicyEscalationRule, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicyEscalationRule, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicyEscalationRule, - ], - }) - @Index() - @TableColumn({ - required: false, - type: TableColumnType.Number, - title: 'Escalate After (in minutes)', - description: - 'How long should we wait before we execute the next escalation rule?', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: true, - type: ColumnType.Number, - }) - public escalateAfterInMinutes?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicyEscalationRule, + ], + }) + @Index() + @TableColumn({ + required: false, + type: TableColumnType.Number, + title: "Escalate After (in minutes)", + description: + "How long should we wait before we execute the next escalation rule?", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: true, + type: ColumnType.Number, + }) + public escalateAfterInMinutes?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicyEscalationRule, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Number, - title: 'Order', - description: 'Order of this rule', - }) - @Column({ - type: ColumnType.Number, - }) - public order?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicyEscalationRule, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Number, + title: "Order", + description: "Order of this rule", + }) + @Column({ + type: ColumnType.Number, + }) + public order?: number = undefined; } diff --git a/Model/Models/OnCallDutyPolicyEscalationRuleSchedule.ts b/Model/Models/OnCallDutyPolicyEscalationRuleSchedule.ts index a605696aae..dd02215e25 100644 --- a/Model/Models/OnCallDutyPolicyEscalationRuleSchedule.ts +++ b/Model/Models/OnCallDutyPolicyEscalationRuleSchedule.ts @@ -1,441 +1,439 @@ -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import OnCallDutyPolicyEscalationRule from './OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicySchedule from './OnCallDutyPolicySchedule'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import OnCallDutyPolicyEscalationRule from "./OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicySchedule from "./OnCallDutyPolicySchedule"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicyEscalationRuleSchedule, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicyEscalationRuleSchedule, + ], }) -@CrudApiEndpoint(new Route('/on-call-duty-policy-esclation-rule-schedule')) +@CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule-schedule")) @Entity({ - name: 'OnCallDutyPolicyEscalationRuleSchedule', + name: "OnCallDutyPolicyEscalationRuleSchedule", }) @TableMetadata({ - tableName: 'OnCallDutyPolicyEscalationRuleSchedule', - singularName: 'On-Call Duty Escalation Rule Schedule', - pluralName: 'On-Call Duty Escalation Rule Schedules', - icon: IconProp.Calendar, - tableDescription: - 'Manage schedules for on-call duty policy escalation rules.', + tableName: "OnCallDutyPolicyEscalationRuleSchedule", + singularName: "On-Call Duty Escalation Rule Schedule", + pluralName: "On-Call Duty Escalation Rule Schedules", + icon: IconProp.Calendar, + tableDescription: + "Manage schedules for on-call duty policy escalation rules.", }) export default class OnCallDutyPolicyEscalationRuleSchedule extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicy, - title: 'On-Call Policy', - description: - 'Relation to On-Call Policy where this escalation rule belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicy; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyId' }) - public onCallDutyPolicy?: OnCallDutyPolicy = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicy, + title: "On-Call Policy", + description: + "Relation to On-Call Policy where this escalation rule belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicy; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyId" }) + public onCallDutyPolicy?: OnCallDutyPolicy = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy ID', - description: - 'ID of your On-Call Policy where this escalation rule belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy ID", + description: + "ID of your On-Call Policy where this escalation rule belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyScheduleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicySchedule, - title: 'On Call Policy Schedule', - description: - 'Relation to On Call Policy Schedule who is in this escalation rule.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicySchedule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyScheduleId' }) - public onCallDutyPolicySchedule?: OnCallDutyPolicySchedule = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyScheduleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicySchedule, + title: "On Call Policy Schedule", + description: + "Relation to On Call Policy Schedule who is in this escalation rule.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicySchedule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyScheduleId" }) + public onCallDutyPolicySchedule?: OnCallDutyPolicySchedule = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'On Call Duty Policy Schedule ID', - description: - 'ID of the on call schedule which is in this escalation rule.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyScheduleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "On Call Duty Policy Schedule ID", + description: "ID of the on call schedule which is in this escalation rule.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyScheduleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyEscalationRuleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyEscalationRule, - title: 'Escalation Rule', - description: - 'Relation to On-Call Policy Escalation Rule where this user belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyEscalationRule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyEscalationRuleId' }) - public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyEscalationRuleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyEscalationRule, + title: "Escalation Rule", + description: + "Relation to On-Call Policy Escalation Rule where this user belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyEscalationRule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyEscalationRuleId" }) + public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Escalation Rule ID', - description: - 'ID of your On-Call Policy Escalation Rule where this user belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Escalation Rule ID", + description: + "ID of your On-Call Policy Escalation Rule where this user belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleSchedule, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/OnCallDutyPolicyEscalationRuleTeam.ts b/Model/Models/OnCallDutyPolicyEscalationRuleTeam.ts index 71a1dcf080..eee2a7ab37 100644 --- a/Model/Models/OnCallDutyPolicyEscalationRuleTeam.ts +++ b/Model/Models/OnCallDutyPolicyEscalationRuleTeam.ts @@ -1,439 +1,438 @@ -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import OnCallDutyPolicyEscalationRule from './OnCallDutyPolicyEscalationRule'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import OnCallDutyPolicyEscalationRule from "./OnCallDutyPolicyEscalationRule"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectOnCallDutyPolicyEscalationRuleTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicyEscalationRuleTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectOnCallDutyPolicyEscalationRuleTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicyEscalationRuleTeam, + ], }) -@CrudApiEndpoint(new Route('/on-call-duty-policy-esclation-rule-team')) +@CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule-team")) @Entity({ - name: 'OnCallDutyPolicyEscalationRuleTeam', + name: "OnCallDutyPolicyEscalationRuleTeam", }) @TableMetadata({ - tableName: 'OnCallDutyPolicyEscalationRuleTeam', - singularName: 'On-Call Duty Escalation Rule', - pluralName: 'On-Call Duty Escalation Rules', - icon: IconProp.Call, - tableDescription: - 'Manage on-call duty escalation rule for the on-call policy.', + tableName: "OnCallDutyPolicyEscalationRuleTeam", + singularName: "On-Call Duty Escalation Rule", + pluralName: "On-Call Duty Escalation Rules", + icon: IconProp.Call, + tableDescription: + "Manage on-call duty escalation rule for the on-call policy.", }) export default class OnCallDutyPolicyEscalationRuleTeam extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicy, - title: 'On-Call Policy', - description: - 'Relation to On-Call Policy where this escalation rule belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicy; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyId' }) - public onCallDutyPolicy?: OnCallDutyPolicy = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicy, + title: "On-Call Policy", + description: + "Relation to On-Call Policy where this escalation rule belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicy; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyId" }) + public onCallDutyPolicy?: OnCallDutyPolicy = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy ID', - description: - 'ID of your On-Call Policy where this escalation rule belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy ID", + description: + "ID of your On-Call Policy where this escalation rule belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: 'Relation to Team who is in this escalation rule.', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: "Relation to Team who is in this escalation rule.", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Team ID', - description: 'ID of the team who is in this escalation rule.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Team ID", + description: "ID of the team who is in this escalation rule.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyEscalationRuleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyEscalationRule, - title: 'Escalation Rule', - description: - 'Relation to On-Call Policy Escalation Rule where this user belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyEscalationRule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyEscalationRuleId' }) - public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyEscalationRuleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyEscalationRule, + title: "Escalation Rule", + description: + "Relation to On-Call Policy Escalation Rule where this user belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyEscalationRule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyEscalationRuleId" }) + public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Escalation Rule ID', - description: - 'ID of your On-Call Policy Escalation Rule where this user belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Escalation Rule ID", + description: + "ID of your On-Call Policy Escalation Rule where this user belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleTeam, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/OnCallDutyPolicyEscalationRuleUser.ts b/Model/Models/OnCallDutyPolicyEscalationRuleUser.ts index 1f2489cbcf..4dcbe033cb 100644 --- a/Model/Models/OnCallDutyPolicyEscalationRuleUser.ts +++ b/Model/Models/OnCallDutyPolicyEscalationRuleUser.ts @@ -1,438 +1,437 @@ -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import OnCallDutyPolicyEscalationRule from './OnCallDutyPolicyEscalationRule'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import OnCallDutyPolicyEscalationRule from "./OnCallDutyPolicyEscalationRule"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectOnCallDutyPolicyEscalationRuleUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicyEscalationRuleUser, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectOnCallDutyPolicyEscalationRuleUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicyEscalationRuleUser, + ], }) -@CrudApiEndpoint(new Route('/on-call-duty-policy-esclation-rule-user')) +@CrudApiEndpoint(new Route("/on-call-duty-policy-esclation-rule-user")) @Entity({ - name: 'OnCallDutyPolicyEscalationRuleUser', + name: "OnCallDutyPolicyEscalationRuleUser", }) @TableMetadata({ - tableName: 'OnCallDutyPolicyEscalationRuleUser', - singularName: 'On-Call Duty Escalation Rule', - pluralName: 'On-Call Duty Esdcalation Rules', - icon: IconProp.Call, - tableDescription: - 'Manage on-call duty escalation rule for the on-call policy.', + tableName: "OnCallDutyPolicyEscalationRuleUser", + singularName: "On-Call Duty Escalation Rule", + pluralName: "On-Call Duty Esdcalation Rules", + icon: IconProp.Call, + tableDescription: + "Manage on-call duty escalation rule for the on-call policy.", }) export default class OnCallDutyPolicyEscalationRuleUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicy, - title: 'On-Call Policy', - description: - 'Relation to On-Call Policy where this escalation rule belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicy; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyId' }) - public onCallDutyPolicy?: OnCallDutyPolicy = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicy, + title: "On-Call Policy", + description: + "Relation to On-Call Policy where this escalation rule belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicy; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyId" }) + public onCallDutyPolicy?: OnCallDutyPolicy = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy ID', - description: - 'ID of your On-Call Policy where this escalation rule belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy ID", + description: + "ID of your On-Call Policy where this escalation rule belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyEscalationRuleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyEscalationRule, - title: 'Escalation Rule', - description: - 'Relation to On-Call Policy Escalation Rule where this user belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyEscalationRule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyEscalationRuleId' }) - public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyEscalationRuleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyEscalationRule, + title: "Escalation Rule", + description: + "Relation to On-Call Policy Escalation Rule where this user belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyEscalationRule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyEscalationRuleId" }) + public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Escalation Rule ID', - description: - 'ID of your On-Call Policy Escalation Rule where this user belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Escalation Rule ID", + description: + "ID of your On-Call Policy Escalation Rule where this user belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'Relation to User who is in this escalation rule.', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "Relation to User who is in this escalation rule.", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User ID', - description: 'ID of the user who is in this escalation rule.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User ID", + description: "ID of the user who is in this escalation rule.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicyEscalationRuleUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyEscalationRuleUser, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/OnCallDutyPolicyExecutionLog.ts b/Model/Models/OnCallDutyPolicyExecutionLog.ts index 258f4e437c..8245c84a7e 100644 --- a/Model/Models/OnCallDutyPolicyExecutionLog.ts +++ b/Model/Models/OnCallDutyPolicyExecutionLog.ts @@ -1,667 +1,665 @@ -import Incident from './Incident'; -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import OnCallDutyPolicyEscalationRule from './OnCallDutyPolicyEscalationRule'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import OnCallDutyPolicyStatus from 'Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus'; -import Permission from 'Common/Types/Permission'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Incident from "./Incident"; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import OnCallDutyPolicyEscalationRule from "./OnCallDutyPolicyEscalationRule"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import OnCallDutyPolicyStatus from "Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus"; +import Permission from "Common/Types/Permission"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - delete: [], - update: [], + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/on-call-duty-policy-execution-log')) +@CrudApiEndpoint(new Route("/on-call-duty-policy-execution-log")) @Entity({ - name: 'OnCallDutyPolicyExecutionLog', + name: "OnCallDutyPolicyExecutionLog", }) @TableMetadata({ - tableName: 'OnCallDutyPolicyExecutionLog', - singularName: 'On-Call Duty Execution Log', - pluralName: 'On-Call Duty Execution Log', - icon: IconProp.Call, - tableDescription: 'Logs for on-call duty policy execution.', + tableName: "OnCallDutyPolicyExecutionLog", + singularName: "On-Call Duty Execution Log", + pluralName: "On-Call Duty Execution Log", + icon: IconProp.Call, + tableDescription: "Logs for on-call duty policy execution.", }) export default class OnCallDutyPolicyExecutionLog extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - 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.ReadProjectOnCallDutyPolicyExecutionLog, + ], + 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.ReadProjectOnCallDutyPolicyExecutionLog, - ], - 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.ReadProjectOnCallDutyPolicyExecutionLog, + ], + 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.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicy, - title: 'On-Call Policy', - description: - 'Relation to On-Call Policy which belongs to this execution log event.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicy; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyId' }) - public onCallDutyPolicy?: OnCallDutyPolicy = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicy, + title: "On-Call Policy", + description: + "Relation to On-Call Policy which belongs to this execution log event.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicy; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyId" }) + public onCallDutyPolicy?: OnCallDutyPolicy = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy ID', - description: - 'ID of your On-Call Policy which belongs to this execution log event.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy ID", + description: + "ID of your On-Call Policy which belongs to this execution log event.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'triggeredByIncidentId', - type: TableColumnType.Entity, - modelType: Incident, - title: 'Triggered By Incident', - description: - 'Relation to Incident which triggered this on-call duty policy.', - }) - @ManyToOne( - () => { - return Incident; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'triggeredByIncidentId' }) - public triggeredByIncident?: Incident = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "triggeredByIncidentId", + type: TableColumnType.Entity, + modelType: Incident, + title: "Triggered By Incident", + description: + "Relation to Incident which triggered this on-call duty policy.", + }) + @ManyToOne( + () => { + return Incident; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "triggeredByIncidentId" }) + public triggeredByIncident?: Incident = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Triggered By Incident ID', - description: - 'ID of the incident which triggered this on-call escalation policy.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public triggeredByIncidentId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Triggered By Incident ID", + description: + "ID of the incident which triggered this on-call escalation policy.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public triggeredByIncidentId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Status', - description: 'Status of this execution', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public status?: OnCallDutyPolicyStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Status", + description: "Status of this execution", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public status?: OnCallDutyPolicyStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - title: 'Status Message', - description: 'Status message of this execution', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public statusMessage?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + title: "Status Message", + description: "Status message of this execution", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public statusMessage?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Notification Event Type', - description: 'Type of event that triggered this on-call duty policy.', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public userNotificationEventType?: UserNotificationEventType = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Notification Event Type", + description: "Type of event that triggered this on-call duty policy.", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public userNotificationEventType?: UserNotificationEventType = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @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: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + 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; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'acknowledgedByUserId', - type: TableColumnType.Entity, - modelType: User, - title: 'Acknowledged by User', - description: - 'Relation to User who acknowledged this policy execution (if this policy was acknowledged by a User)', - }) - @ManyToOne( - () => { - return User; - }, - { - cascade: false, - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'acknowledgedByUserId' }) - public acknowledgedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "acknowledgedByUserId", + type: TableColumnType.Entity, + modelType: User, + title: "Acknowledged by User", + description: + "Relation to User who acknowledged this policy execution (if this policy was acknowledged by a User)", + }) + @ManyToOne( + () => { + return User; + }, + { + cascade: false, + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "acknowledgedByUserId" }) + public acknowledgedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Deleted by User ID', - description: - 'User ID who acknowledged this object (if this object was acknowledged by a User)', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public acknowledgedByUserId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Deleted by User ID", + description: + "User ID who acknowledged this object (if this object was acknowledged by a User)", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public acknowledgedByUserId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public acknowledgedAt?: Date = undefined; + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public acknowledgedAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'acknowledgedByTeamId', - type: TableColumnType.Entity, - title: 'Acknowledged by Team', - description: - 'Relation to Team who acknowledged this policy execution (if this policy was acknowledged by a Team)', - }) - @ManyToOne( - () => { - return Team; - }, - { - cascade: false, - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'acknowledgedByTeamId' }) - public acknowledgedByTeam?: Team = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "acknowledgedByTeamId", + type: TableColumnType.Entity, + title: "Acknowledged by Team", + description: + "Relation to Team who acknowledged this policy execution (if this policy was acknowledged by a Team)", + }) + @ManyToOne( + () => { + return Team; + }, + { + cascade: false, + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "acknowledgedByTeamId" }) + public acknowledgedByTeam?: Team = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLog, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Acknowledged by Team ID', - description: - 'Team ID who acknowledged this object (if this object was acknowledged by a Team)', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public acknowledgedByTeamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLog, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Acknowledged by Team ID", + description: + "Team ID who acknowledged this object (if this object was acknowledged by a Team)", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public acknowledgedByTeamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - required: false, - type: TableColumnType.Number, - title: 'Executed Escalation Rule Order', - description: 'Which escalation rule was executed?', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: true, - type: ColumnType.Number, - }) - public lastExecutedEscalationRuleOrder?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + required: false, + type: TableColumnType.Number, + title: "Executed Escalation Rule Order", + description: "Which escalation rule was executed?", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: true, + type: ColumnType.Number, + }) + public lastExecutedEscalationRuleOrder?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - required: false, - type: TableColumnType.Date, - title: 'Last Escalation Rule Executed At', - description: 'When was the escalation rule executed?', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: true, - type: ColumnType.Date, - }) - public lastEscalationRuleExecutedAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + required: false, + type: TableColumnType.Date, + title: "Last Escalation Rule Executed At", + description: "When was the escalation rule executed?", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: true, + type: ColumnType.Date, + }) + public lastEscalationRuleExecutedAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'lastExecutedEscalationRuleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyEscalationRule, - title: 'Last Executed Escalation Rule', - description: - 'Relation to On-Call Policy Last Executed Escalation Rule.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyEscalationRule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'lastExecutedEscalationRuleId' }) - public lastExecutedEscalationRule?: OnCallDutyPolicyEscalationRule = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "lastExecutedEscalationRuleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyEscalationRule, + title: "Last Executed Escalation Rule", + description: "Relation to On-Call Policy Last Executed Escalation Rule.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyEscalationRule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "lastExecutedEscalationRuleId" }) + public lastExecutedEscalationRule?: OnCallDutyPolicyEscalationRule = + undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Last Executed Escalation Rule ID', - description: 'ID of your On-Call Policy Last Executed Escalation Rule.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public lastExecutedEscalationRuleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Last Executed Escalation Rule ID", + description: "ID of your On-Call Policy Last Executed Escalation Rule.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public lastExecutedEscalationRuleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Number, - required: false, - canReadOnRelationQuery: true, - title: 'Execute next escalation rule in minutes', - description: - 'How many minutes should we wait before executing the next escalation rule?', - }) - @Column({ - type: ColumnType.Number, - nullable: true, - }) - public executeNextEscalationRuleInMinutes?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Number, + required: false, + canReadOnRelationQuery: true, + title: "Execute next escalation rule in minutes", + description: + "How many minutes should we wait before executing the next escalation rule?", + }) + @Column({ + type: ColumnType.Number, + nullable: true, + }) + public executeNextEscalationRuleInMinutes?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Number, - required: true, - isDefaultValueColumn: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Execution Repeat Count', - description: 'How many times did we execute this on-call policy?', - }) - @Column({ - type: ColumnType.Number, - nullable: false, - default: 1, - }) - public onCallPolicyExecutionRepeatCount?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Number, + required: true, + isDefaultValueColumn: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Execution Repeat Count", + description: "How many times did we execute this on-call policy?", + }) + @Column({ + type: ColumnType.Number, + nullable: false, + default: 1, + }) + public onCallPolicyExecutionRepeatCount?: number = undefined; } diff --git a/Model/Models/OnCallDutyPolicyExecutionLogTimeline.ts b/Model/Models/OnCallDutyPolicyExecutionLogTimeline.ts index a250df36b0..aff7f88390 100644 --- a/Model/Models/OnCallDutyPolicyExecutionLogTimeline.ts +++ b/Model/Models/OnCallDutyPolicyExecutionLogTimeline.ts @@ -1,729 +1,726 @@ -import Incident from './Incident'; -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import OnCallDutyPolicyEscalationRule from './OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicyExecutionLog from './OnCallDutyPolicyExecutionLog'; -import OnCallDutyPolicySchedule from './OnCallDutyPolicySchedule'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import OnCallDutyExecutionLogTimelineStatus from 'Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus'; -import Permission from 'Common/Types/Permission'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Incident from "./Incident"; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import OnCallDutyPolicyEscalationRule from "./OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicyExecutionLog from "./OnCallDutyPolicyExecutionLog"; +import OnCallDutyPolicySchedule from "./OnCallDutyPolicySchedule"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import OnCallDutyExecutionLogTimelineStatus from "Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus"; +import Permission from "Common/Types/Permission"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - delete: [], - update: [], + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/on-call-duty-policy-execution-log-timeline')) +@CrudApiEndpoint(new Route("/on-call-duty-policy-execution-log-timeline")) @Entity({ - name: 'OnCallDutyPolicyExecutionLogTimeline', + name: "OnCallDutyPolicyExecutionLogTimeline", }) @TableMetadata({ - tableName: 'OnCallDutyPolicyExecutionLogTimeline', - singularName: 'On-Call Duty Execution Log Timeline', - pluralName: 'On-Call Duty Execution Log Timeline', - icon: IconProp.Call, - tableDescription: 'Timeline events for on-call duty policy execution log.', + tableName: "OnCallDutyPolicyExecutionLogTimeline", + singularName: "On-Call Duty Execution Log Timeline", + pluralName: "On-Call Duty Execution Log Timeline", + icon: IconProp.Call, + tableDescription: "Timeline events for on-call duty policy execution log.", }) export default class OnCallDutyPolicyExecutionLogTimeline extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - 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.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + 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.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - 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.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + 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.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicy, - title: 'OnCallDutyPolicy', - description: - 'Relation to on-call duty policy Resource in which this object belongs', - }) - @ManyToOne( - () => { - return OnCallDutyPolicy; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyId' }) - public onCallDutyPolicy?: OnCallDutyPolicy = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicy, + title: "OnCallDutyPolicy", + description: + "Relation to on-call duty policy Resource in which this object belongs", + }) + @ManyToOne( + () => { + return OnCallDutyPolicy; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyId" }) + public onCallDutyPolicy?: OnCallDutyPolicy = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'OnCallDutyPolicy ID', - description: - 'ID of your OneUptime on-call duty policy in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "OnCallDutyPolicy ID", + description: + "ID of your OneUptime on-call duty policy in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'triggeredByIncidentId', - type: TableColumnType.Entity, - modelType: Incident, - title: 'Incident', - description: - 'Relation to Incident Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Incident; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'triggeredByIncidentId' }) - public triggeredByIncident?: Incident = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "triggeredByIncidentId", + type: TableColumnType.Entity, + modelType: Incident, + title: "Incident", + description: "Relation to Incident Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Incident; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "triggeredByIncidentId" }) + public triggeredByIncident?: Incident = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Incident ID', - description: - 'ID of your OneUptime Incident in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public triggeredByIncidentId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Incident ID", + description: "ID of your OneUptime Incident in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public triggeredByIncidentId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyExecutionLogId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyExecutionLog, - title: 'On-Call Policy Execution Log', - description: - 'Relation to On-Call Policy Execution Log where this timeline event belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyExecutionLog; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyExecutionLogId' }) - public onCallDutyPolicyExecutionLog?: OnCallDutyPolicyExecutionLog = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyExecutionLogId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyExecutionLog, + title: "On-Call Policy Execution Log", + description: + "Relation to On-Call Policy Execution Log where this timeline event belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyExecutionLog; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyExecutionLogId" }) + public onCallDutyPolicyExecutionLog?: OnCallDutyPolicyExecutionLog = + undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Execution Log ID', - description: - 'ID of your On-Call Policy Execution Log where this timeline event belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyExecutionLogId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Execution Log ID", + description: + "ID of your On-Call Policy Execution Log where this timeline event belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyExecutionLogId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyEscalationRuleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyEscalationRule, - title: 'On-Call Policy Escalation Rule', - description: - 'Relation to On-Call Policy Escalation Rule where this timeline event belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyEscalationRule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyEscalationRuleId' }) - public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyEscalationRuleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyEscalationRule, + title: "On-Call Policy Escalation Rule", + description: + "Relation to On-Call Policy Escalation Rule where this timeline event belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyEscalationRule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyEscalationRuleId" }) + public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = + undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Escalation Rule ID', - description: - 'ID of your On-Call Policy Escalation Rule where this timeline event belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Escalation Rule ID", + description: + "ID of your On-Call Policy Escalation Rule where this timeline event belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'alertSentToUserId', - type: TableColumnType.Entity, - modelType: User, - title: 'Alert Sent To User', - description: 'Relation to User who we sent alert to.', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'alertSentToUserId' }) - public alertSentToUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "alertSentToUserId", + type: TableColumnType.Entity, + modelType: User, + title: "Alert Sent To User", + description: "Relation to User who we sent alert to.", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "alertSentToUserId" }) + public alertSentToUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Notification Event Type', - description: 'Type of event that triggered this on-call duty policy.', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public userNotificationEventType?: UserNotificationEventType = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Notification Event Type", + description: "Type of event that triggered this on-call duty policy.", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public userNotificationEventType?: UserNotificationEventType = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Alert Sent To User ID', - description: 'ID of the user who we sent alert to.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public alertSentToUserId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Alert Sent To User ID", + description: "ID of the user who we sent alert to.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public alertSentToUserId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userBelongsToTeamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'User Belongs To Team', - description: - 'Which team did the user belong to when the alert was sent?', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userBelongsToTeamId' }) - public userBelongsToTeam?: Team = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userBelongsToTeamId", + type: TableColumnType.Entity, + modelType: Team, + title: "User Belongs To Team", + description: "Which team did the user belong to when the alert was sent?", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userBelongsToTeamId" }) + public userBelongsToTeam?: Team = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User Belongs To Team ID', - description: - 'Which team ID did the user belong to when the alert was sent?', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userBelongsToTeamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User Belongs To Team ID", + description: + "Which team ID did the user belong to when the alert was sent?", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userBelongsToTeamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyScheduleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicySchedule, - title: 'User Belongs To Schedule', - description: - 'Which schedule did the user belong to when the alert was sent?', - }) - @ManyToOne( - () => { - return OnCallDutyPolicySchedule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyScheduleId' }) - public onCallDutySchedule?: OnCallDutyPolicySchedule = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyScheduleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicySchedule, + title: "User Belongs To Schedule", + description: + "Which schedule did the user belong to when the alert was sent?", + }) + @ManyToOne( + () => { + return OnCallDutyPolicySchedule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyScheduleId" }) + public onCallDutySchedule?: OnCallDutyPolicySchedule = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User Belongs To Schedule ID', - description: - 'Which schedule ID did the user belong to when the alert was sent?', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyScheduleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User Belongs To Schedule ID", + description: + "Which schedule ID did the user belong to when the alert was sent?", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyScheduleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - title: 'Status Message', - description: 'Status message of this execution timeline event', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public statusMessage?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + title: "Status Message", + description: "Status message of this execution timeline event", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public statusMessage?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Status', - description: 'Status of this execution timeline event', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public status?: OnCallDutyExecutionLogTimelineStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Status", + description: "Status of this execution timeline event", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public status?: OnCallDutyExecutionLogTimelineStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @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: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - nullable: true, - unique: false, - }) - public isAcknowledged?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + nullable: true, + unique: false, + }) + public isAcknowledged?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.Date, - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public acknowledgedAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicyExecutionLogTimeline, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.Date, + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public acknowledgedAt?: Date = 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; + @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; } diff --git a/Model/Models/OnCallDutyPolicySchedule.ts b/Model/Models/OnCallDutyPolicySchedule.ts index 9fa169c026..67d7716b98 100644 --- a/Model/Models/OnCallDutyPolicySchedule.ts +++ b/Model/Models/OnCallDutyPolicySchedule.ts @@ -1,405 +1,403 @@ -import Label from './Label'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Label from "./Label"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@AccessControlColumn('labels') -@TenantColumn('projectId') +@AccessControlColumn("labels") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicySchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicySchedule, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectOnCallDutyPolicySchedule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicySchedule, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicySchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicySchedule, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectOnCallDutyPolicySchedule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicySchedule, + ], }) -@CrudApiEndpoint(new Route('/on-call-duty-policy-schedule')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/on-call-duty-policy-schedule")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'OnCallDutyPolicySchedule', + name: "OnCallDutyPolicySchedule", }) @TableMetadata({ - tableName: 'OnCallDutyPolicySchedule', - singularName: 'On-Call Policy Schedule', - pluralName: 'On-Call Duty Policy Schedules', - icon: IconProp.Calendar, - tableDescription: - 'Manage schedules and rotations for your on-call duty policy.', + tableName: "OnCallDutyPolicySchedule", + singularName: "On-Call Policy Schedule", + pluralName: "On-Call Duty Policy Schedules", + icon: IconProp.Calendar, + tableDescription: + "Manage schedules and rotations for your on-call duty policy.", }) export default class OnCallDutyPolicySchedule extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicySchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicySchedule, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicySchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicySchedule, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicySchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicySchedule, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicySchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicySchedule, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicySchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicySchedule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicySchedule, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name of this object', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicySchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicySchedule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicySchedule, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name of this object", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicySchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicySchedule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicySchedule, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'OnCallDutyPolicyScheduleLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'onCallDutyPolicyScheduleId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicySchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicySchedule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicySchedule, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "OnCallDutyPolicyScheduleLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "onCallDutyPolicyScheduleId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicySchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicySchedule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectOnCallDutyPolicySchedule, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicySchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicySchedule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectOnCallDutyPolicySchedule, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicySchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicySchedule, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicySchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicySchedule, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicySchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicySchedule, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicySchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicySchedule, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectOnCallDutyPolicySchedule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectOnCallDutyPolicySchedule, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectOnCallDutyPolicySchedule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectOnCallDutyPolicySchedule, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/OnCallDutyPolicyScheduleLayer.ts b/Model/Models/OnCallDutyPolicyScheduleLayer.ts index 4fe0005d63..28c300b748 100644 --- a/Model/Models/OnCallDutyPolicyScheduleLayer.ts +++ b/Model/Models/OnCallDutyPolicyScheduleLayer.ts @@ -1,559 +1,557 @@ -import OnCallDutyPolicySchedule from './OnCallDutyPolicySchedule'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import Recurring from 'Common/Types/Events/Recurring'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import RestrictionTimes from 'Common/Types/OnCallDutyPolicy/RestrictionTimes'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import OnCallDutyPolicySchedule from "./OnCallDutyPolicySchedule"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import Recurring from "Common/Types/Events/Recurring"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import RestrictionTimes from "Common/Types/OnCallDutyPolicy/RestrictionTimes"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], }) -@CrudApiEndpoint(new Route('/on-call-duty-schedule-layer')) +@CrudApiEndpoint(new Route("/on-call-duty-schedule-layer")) @TableMetadata({ - tableName: 'OnCallDutyPolicyScheduleLayer', - singularName: 'On-Call Schedule Layer', - pluralName: 'On-Call Schedule Layers', - icon: IconProp.Layers, - tableDescription: 'On-Call Schedule Layers', + tableName: "OnCallDutyPolicyScheduleLayer", + singularName: "On-Call Schedule Layer", + pluralName: "On-Call Schedule Layers", + icon: IconProp.Layers, + tableDescription: "On-Call Schedule Layers", }) @Entity({ - name: 'OnCallDutyPolicyScheduleLayer', + name: "OnCallDutyPolicyScheduleLayer", }) export default class OnCallDutyPolicyScheduleLayer extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyScheduleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicySchedule, - title: 'On-Call Policy Schedule', - description: - 'Relation to On-Call Policy Schedule where this escalation rule belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicySchedule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyScheduleId' }) - public onCallDutyPolicySchedule?: OnCallDutyPolicySchedule = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyScheduleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicySchedule, + title: "On-Call Policy Schedule", + description: + "Relation to On-Call Policy Schedule where this escalation rule belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicySchedule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyScheduleId" }) + public onCallDutyPolicySchedule?: OnCallDutyPolicySchedule = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Schedule ID', - description: - 'ID of your On-Call Policy Schedule where this escalation rule belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyScheduleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Schedule ID", + description: + "ID of your On-Call Policy Schedule where this escalation rule belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyScheduleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Friendly name for this layer', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Friendly name for this layer", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: - 'Description for this layer. This is optional and can be left blank.', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: + "Description for this layer. This is optional and can be left blank.", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Number, - canReadOnRelationQuery: true, - title: 'Order', - description: - 'Order / Priority of this layer. Lower the number, higher the priority.', - }) - @Column({ - type: ColumnType.Number, - }) - public order?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Number, + canReadOnRelationQuery: true, + title: "Order", + description: + "Order / Priority of this layer. Lower the number, higher the priority.", + }) + @Column({ + type: ColumnType.Number, + }) + public order?: number = undefined; - @TableColumn({ - title: 'Start At', - type: TableColumnType.Date, - required: true, - description: 'Start date and time of this layer.', - }) - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @Column({ - nullable: false, - type: ColumnType.Date, - }) - public startsAt?: Date = undefined; + @TableColumn({ + title: "Start At", + type: TableColumnType.Date, + required: true, + description: "Start date and time of this layer.", + }) + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @Column({ + nullable: false, + type: ColumnType.Date, + }) + public startsAt?: Date = undefined; - @TableColumn({ - title: 'Rotation', - type: TableColumnType.JSON, - required: true, - description: - 'How often would you like to hand off the duty to the next user in this layer?', - }) - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @Column({ - nullable: false, - type: ColumnType.JSON, - default: Recurring.getDefault(), - transformer: Recurring.getDatabaseTransformer(), - }) - public rotation?: Recurring = undefined; + @TableColumn({ + title: "Rotation", + type: TableColumnType.JSON, + required: true, + description: + "How often would you like to hand off the duty to the next user in this layer?", + }) + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @Column({ + nullable: false, + type: ColumnType.JSON, + default: Recurring.getDefault(), + transformer: Recurring.getDatabaseTransformer(), + }) + public rotation?: Recurring = undefined; - @TableColumn({ - title: 'Hand Off Time', - type: TableColumnType.Date, - required: true, - description: - 'Hand off time. When would you like to hand off the duty to the next user in this layer?', - }) - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @Column({ - nullable: false, - type: ColumnType.Date, - }) - public handOffTime?: Date = undefined; + @TableColumn({ + title: "Hand Off Time", + type: TableColumnType.Date, + required: true, + description: + "Hand off time. When would you like to hand off the duty to the next user in this layer?", + }) + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @Column({ + nullable: false, + type: ColumnType.Date, + }) + public handOffTime?: Date = undefined; - @TableColumn({ - title: 'Restriction Times', - type: TableColumnType.JSON, - required: true, - description: 'Restrict this layer to these times', - }) - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @Column({ - nullable: false, - type: ColumnType.JSON, - default: RestrictionTimes.getDefault(), - transformer: RestrictionTimes.getDatabaseTransformer(), - }) - public restrictionTimes?: RestrictionTimes = undefined; + @TableColumn({ + title: "Restriction Times", + type: TableColumnType.JSON, + required: true, + description: "Restrict this layer to these times", + }) + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @Column({ + nullable: false, + type: ColumnType.JSON, + default: RestrictionTimes.getDefault(), + transformer: RestrictionTimes.getDatabaseTransformer(), + }) + public restrictionTimes?: RestrictionTimes = undefined; } diff --git a/Model/Models/OnCallDutyPolicyScheduleLayerUser.ts b/Model/Models/OnCallDutyPolicyScheduleLayerUser.ts index 931f4e388d..203614dcee 100644 --- a/Model/Models/OnCallDutyPolicyScheduleLayerUser.ts +++ b/Model/Models/OnCallDutyPolicyScheduleLayerUser.ts @@ -1,497 +1,496 @@ -import OnCallDutyPolicySchedule from './OnCallDutyPolicySchedule'; -import OnCallDutyPolicyScheduleLayer from './OnCallDutyPolicyScheduleLayer'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import OnCallDutyPolicySchedule from "./OnCallDutyPolicySchedule"; +import OnCallDutyPolicyScheduleLayer from "./OnCallDutyPolicyScheduleLayer"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], }) -@CrudApiEndpoint(new Route('/on-call-duty-schedule-layer-user')) +@CrudApiEndpoint(new Route("/on-call-duty-schedule-layer-user")) @TableMetadata({ - tableName: 'OnCallDutyPolicyScheduleLayerUser', - singularName: 'On-Call Schedule Layer User', - pluralName: 'On-Call Schedule Layer Users', - icon: IconProp.Layers, - tableDescription: 'On-Call Schedule Layer Users', + tableName: "OnCallDutyPolicyScheduleLayerUser", + singularName: "On-Call Schedule Layer User", + pluralName: "On-Call Schedule Layer Users", + icon: IconProp.Layers, + tableDescription: "On-Call Schedule Layer Users", }) @Entity({ - name: 'OnCallDutyPolicyScheduleLayerUser', + name: "OnCallDutyPolicyScheduleLayerUser", }) export default class OnCallDutyPolicyScheduleLayerUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyScheduleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicySchedule, - title: 'On-Call Policy Schedule', - description: - 'Relation to On-Call Policy Schedule where this escalation rule belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicySchedule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyScheduleId' }) - public onCallDutyPolicySchedule?: OnCallDutyPolicySchedule = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyScheduleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicySchedule, + title: "On-Call Policy Schedule", + description: + "Relation to On-Call Policy Schedule where this escalation rule belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicySchedule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyScheduleId" }) + public onCallDutyPolicySchedule?: OnCallDutyPolicySchedule = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Schedule ID', - description: - 'ID of your On-Call Policy Schedule where this escalation rule belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyScheduleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Schedule ID", + description: + "ID of your On-Call Policy Schedule where this escalation rule belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyScheduleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyScheduleLayerId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyScheduleLayer, - title: 'On-Call Policy Schedule Layer', - description: - 'Relation to On-Call Policy Schedule Layer where this belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyScheduleLayer; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyScheduleLayerId' }) - public onCallDutyPolicyScheduleLayer?: OnCallDutyPolicyScheduleLayer = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyScheduleLayerId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyScheduleLayer, + title: "On-Call Policy Schedule Layer", + description: + "Relation to On-Call Policy Schedule Layer where this belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyScheduleLayer; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyScheduleLayerId" }) + public onCallDutyPolicyScheduleLayer?: OnCallDutyPolicyScheduleLayer = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Schedule Layer ID', - description: - 'ID of your On-Call Policy Schedule Layer where this escalation rule belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyScheduleLayerId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Schedule Layer ID", + description: + "ID of your On-Call Policy Schedule Layer where this escalation rule belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyScheduleLayerId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Number, - canReadOnRelationQuery: true, - title: 'Order', - description: - 'Order / Priority of this layer. Lower the number, higher the priority.', - }) - @Column({ - type: ColumnType.Number, - }) - public order?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Number, + canReadOnRelationQuery: true, + title: "Order", + description: + "Order / Priority of this layer. Lower the number, higher the priority.", + }) + @Column({ + type: ColumnType.Number, + }) + public order?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'User who belongs to this layer.', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "User who belongs to this layer.", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateOnCallDutyPolicyScheduleLayer, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadOnCallDutyPolicyScheduleLayer, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditOnCallDutyPolicyScheduleLayer, - ], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'User ID', - description: 'ID of User who belongs to this team', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateOnCallDutyPolicyScheduleLayer, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadOnCallDutyPolicyScheduleLayer, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditOnCallDutyPolicyScheduleLayer, + ], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "User ID", + description: "ID of User who belongs to this team", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; } diff --git a/Model/Models/Probe.ts b/Model/Models/Probe.ts index 476d707bac..2b0fee328b 100755 --- a/Model/Models/Probe.ts +++ b/Model/Models/Probe.ts @@ -1,473 +1,473 @@ -import File from './File'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import IsPermissionsIf from 'Common/Types/Database/IsPermissionsIf'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Version from 'Common/Types/Version'; -import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import File from "./File"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import IsPermissionsIf from "Common/Types/Database/IsPermissionsIf"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Version from "Common/Types/Version"; +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) -@IsPermissionsIf(Permission.Public, 'projectId', null) -@TenantColumn('projectId') -@CrudApiEndpoint(new Route('/probe')) -@SlugifyColumn('name', 'slug') +@IsPermissionsIf(Permission.Public, "projectId", null) +@TenantColumn("projectId") +@CrudApiEndpoint(new Route("/probe")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'Probe', + name: "Probe", }) @TableMetadata({ - tableName: 'Probe', - singularName: 'Probe', - pluralName: 'Probes', - icon: IconProp.Settings, - tableDescription: - 'Manages custom probes. Deploy probes anywhere in the world and connect it to your project.', + tableName: "Probe", + singularName: "Probe", + pluralName: "Probes", + icon: IconProp.Settings, + tableDescription: + "Manages custom probes. Deploy probes anywhere in the world and connect it to your project.", }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [Permission.Public], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectProbe, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectProbe, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [Permission.Public], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectProbe, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectProbe, + ], }) export default class Probe extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [Permission.ProjectOwner, Permission.ProjectAdmin], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.ShortText, - }) - @Column({ - type: ColumnType.ShortText, - nullable: false, - unique: true, - }) - public key?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [Permission.ProjectOwner, Permission.ProjectAdmin], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.ShortText, + }) + @Column({ + type: ColumnType.ShortText, + nullable: false, + unique: true, + }) + public key?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [Permission.Public], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectProbe, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.Name, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.Name, - length: ColumnLength.Name, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [Permission.Public], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectProbe, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.Name, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.Name, + length: ColumnLength.Name, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [Permission.Public], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectProbe, - ], - }) - @TableColumn({ required: false, type: TableColumnType.Name }) - @Column({ - nullable: true, - type: ColumnType.Name, - length: ColumnLength.Name, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [Permission.Public], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectProbe, + ], + }) + @TableColumn({ required: false, type: TableColumnType.Name }) + @Column({ + nullable: true, + type: ColumnType.Name, + length: ColumnLength.Name, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.Public], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.Public], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [Permission.Public], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectProbe, - ], - }) - @TableColumn({ required: true, type: TableColumnType.Version }) - @Column({ - nullable: false, - type: ColumnType.Version, - length: ColumnLength.Version, - transformer: Version.getDatabaseTransformer(), - }) - public probeVersion?: Version = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [Permission.Public], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectProbe, + ], + }) + @TableColumn({ required: true, type: TableColumnType.Version }) + @Column({ + nullable: false, + type: ColumnType.Version, + length: ColumnLength.Version, + transformer: Version.getDatabaseTransformer(), + }) + public probeVersion?: Version = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectProbe, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.Date, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: true, - type: ColumnType.Date, - }) - public lastAlive?: Date = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectProbe, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.Date, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: true, + type: ColumnType.Date, + }) + public lastAlive?: Date = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'iconFileId', - type: TableColumnType.Entity, - modelType: File, - title: 'Icon', - description: 'Probe Icon', - }) - @ManyToOne( - () => { - return File; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'delete', - } - ) - @JoinColumn({ name: 'iconFileId' }) - public iconFile?: File = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "iconFileId", + type: TableColumnType.Entity, + modelType: File, + title: "Icon", + description: "Probe Icon", + }) + @ManyToOne( + () => { + return File; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "delete", + }, + ) + @JoinColumn({ name: "iconFileId" }) + public iconFile?: File = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Icon', - description: 'Probe Page Icon File ID', - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public iconFileId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Icon", + description: "Probe Page Icon File ID", + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public iconFileId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [Permission.Public], - update: [], - }) - @TableColumn({ - type: TableColumnType.Entity, - required: false, - modelType: Project, - }) - @ManyToOne( - () => { - return Project; - }, - { - cascade: false, - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [Permission.Public], + update: [], + }) + @TableColumn({ + type: TableColumnType.Entity, + required: false, + modelType: Project, + }) + @ManyToOne( + () => { + return Project; + }, + { + cascade: false, + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [Permission.Public], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public projectId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [Permission.Public], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public projectId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Entity, modelType: User }) - @ManyToOne( - () => { - return User; - }, - { - cascade: false, - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Entity, modelType: User }) + @ManyToOne( + () => { + return User; + }, + { + cascade: false, + eager: false, + nullable: true, + onDelete: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [Permission.ProjectOwner], - update: [], - }) - @TableColumn({ type: TableColumnType.Entity, modelType: User }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [Permission.ProjectOwner], + update: [], + }) + @TableColumn({ type: TableColumnType.Entity, modelType: User }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectProbe, - ], - read: [Permission.ProjectOwner], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectProbe, + ], + read: [Permission.ProjectOwner], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public isGlobalProbe?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isGlobalProbe?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public shouldAutoEnableProbeOnNewMonitors?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public shouldAutoEnableProbeOnNewMonitors?: boolean = undefined; } diff --git a/Model/Models/Project.ts b/Model/Models/Project.ts index 9cf68c4f67..62bce1f94b 100644 --- a/Model/Models/Project.ts +++ b/Model/Models/Project.ts @@ -1,1126 +1,1125 @@ -import Reseller from './Reseller'; -import ResellerPlan from './ResellerPlan'; -import User from './User'; -import TenantModel from 'Common/Models/TenantModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus'; -import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import ColumnBillingAccessControl from 'Common/Types/Database/AccessControl/ColumnBillingAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import MultiTenentQueryAllowed from 'Common/Types/Database/MultiTenentQueryAllowed'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import Email from 'Common/Types/Email'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Name from 'Common/Types/Name'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Phone from 'Common/Types/Phone'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Reseller from "./Reseller"; +import ResellerPlan from "./ResellerPlan"; +import User from "./User"; +import TenantModel from "Common/Models/TenantModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import SubscriptionStatus from "Common/Types/Billing/SubscriptionStatus"; +import AllowAccessIfSubscriptionIsUnpaid from "Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import ColumnBillingAccessControl from "Common/Types/Database/AccessControl/ColumnBillingAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import MultiTenentQueryAllowed from "Common/Types/Database/MultiTenentQueryAllowed"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import Email from "Common/Types/Email"; +import IconProp from "Common/Types/Icon/IconProp"; +import Name from "Common/Types/Name"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Phone from "Common/Types/Phone"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation({ - isMasterAdminApiDocs: true, + isMasterAdminApiDocs: true, }) @AllowAccessIfSubscriptionIsUnpaid() @MultiTenentQueryAllowed(true) @TableAccessControl({ - create: [Permission.User], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - delete: [Permission.ProjectOwner, Permission.DeleteProject], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - Permission.EditProject, - ], + create: [Permission.User], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + delete: [Permission.ProjectOwner, Permission.DeleteProject], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + Permission.EditProject, + ], }) @TableMetadata({ - tableName: 'Project', - singularName: 'Project', - pluralName: 'Projects', - icon: IconProp.Folder, - tableDescription: 'OneUptime Project, and everything happens inside it', + tableName: "Project", + singularName: "Project", + pluralName: "Projects", + icon: IconProp.Folder, + tableDescription: "OneUptime Project, and everything happens inside it", }) -@CrudApiEndpoint(new Route('/project')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/project")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'Project', + name: "Project", }) -@TenantColumn('_id') +@TenantColumn("_id") export default class Model extends TenantModel { - @ColumnAccessControl({ - create: [Permission.User], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ManageProjectBilling, - Permission.EditProject, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name of this object', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ManageProjectBilling, + Permission.EditProject, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name of this object", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [Permission.User], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [Permission.User], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [Permission.ProjectOwner], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public paymentProviderPlanId?: string = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [Permission.ProjectOwner], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public paymentProviderPlanId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public paymentProviderSubscriptionId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public paymentProviderSubscriptionId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public paymentProviderMeteredSubscriptionId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public paymentProviderMeteredSubscriptionId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.Number }) - @Column({ - type: ColumnType.Number, - nullable: true, - unique: false, - }) - public paymentProviderSubscriptionSeats?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.Number }) + @Column({ + type: ColumnType.Number, + nullable: true, + unique: false, + }) + public paymentProviderSubscriptionSeats?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public trialEndsAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public trialEndsAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public paymentProviderCustomerId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public paymentProviderCustomerId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public paymentProviderSubscriptionStatus?: SubscriptionStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public paymentProviderSubscriptionStatus?: SubscriptionStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public paymentProviderMeteredSubscriptionStatus?: SubscriptionStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public paymentProviderMeteredSubscriptionStatus?: SubscriptionStatus = + undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public paymentProviderPromoCode?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public paymentProviderPromoCode?: string = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @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.CurrentUser], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + 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; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.Boolean, - isDefaultValueColumn: true, - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public isBlocked?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.Boolean, + isDefaultValueColumn: true, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isBlocked?: boolean = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ManageProjectBilling, - Permission.EditProject, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.Boolean, - isDefaultValueColumn: true, - title: 'Is Feature Flag Monitor Groups Enabled', - description: 'Is Feature Flag Monitor Groups Enabled', - }) - @Column({ - type: ColumnType.Boolean, - nullable: true, - unique: false, - default: false, - }) - public isFeatureFlagMonitorGroupsEnabled?: boolean = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ManageProjectBilling, + Permission.EditProject, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.Boolean, + isDefaultValueColumn: true, + title: "Is Feature Flag Monitor Groups Enabled", + description: "Is Feature Flag Monitor Groups Enabled", + }) + @Column({ + type: ColumnType.Boolean, + nullable: true, + unique: false, + default: false, + }) + public isFeatureFlagMonitorGroupsEnabled?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.SmallPositiveNumber }) - @Column({ - type: ColumnType.SmallPositiveNumber, - nullable: true, - unique: false, - }) - public unpaidSubscriptionNotificationCount?: PositiveNumber = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.SmallPositiveNumber }) + @Column({ + type: ColumnType.SmallPositiveNumber, + nullable: true, + unique: false, + }) + public unpaidSubscriptionNotificationCount?: PositiveNumber = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public paymentFailedDate?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public paymentFailedDate?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public paymentSuccessDate?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public paymentSuccessDate?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ReadWorkflow, - ], - update: [], - }) - @TableColumn({ type: TableColumnType.Number }) - @Column({ - type: ColumnType.Number, - nullable: true, - unique: false, - }) - public workflowRunsInLast30Days?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ReadWorkflow, + ], + update: [], + }) + @TableColumn({ type: TableColumnType.Number }) + @Column({ + type: ColumnType.Number, + nullable: true, + unique: false, + }) + public workflowRunsInLast30Days?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProject, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.Boolean, - isDefaultValueColumn: true, - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Scale, - create: PlanSelect.Free, - }) - public requireSsoForLogin?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProject, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.Boolean, + isDefaultValueColumn: true, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Scale, + create: PlanSelect.Free, + }) + public requireSsoForLogin?: boolean = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Number }) - @Column({ - type: ColumnType.Number, - nullable: true, - unique: false, - }) - public activeMonitorsLimit?: number = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Number }) + @Column({ + type: ColumnType.Number, + nullable: true, + unique: false, + }) + public activeMonitorsLimit?: number = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Number }) - @Column({ - type: ColumnType.Number, - nullable: true, - unique: false, - }) - public seatLimit?: number = undefined; // this is used for stopping customers from adding more users than their plan allows. For ex: Some enterprise customers have a limit of 100 users. This is used to enforce that limit. + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Number }) + @Column({ + type: ColumnType.Number, + nullable: true, + unique: false, + }) + public seatLimit?: number = undefined; // this is used for stopping customers from adding more users than their plan allows. For ex: Some enterprise customers have a limit of 100 users. This is used to enforce that limit. - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Number }) - @Column({ - type: ColumnType.Number, - nullable: true, - unique: false, - }) - public currentActiveMonitorsCount?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Number }) + @Column({ + type: ColumnType.Number, + nullable: true, + unique: false, + }) + public currentActiveMonitorsCount?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Number, - isDefaultValueColumn: true, - required: true, - title: 'SMS or Call Current Balance', - description: 'Balance in USD for SMS or Call', - }) - @Column({ - type: ColumnType.Number, - nullable: false, - unique: false, - default: 0, - }) - public smsOrCallCurrentBalanceInUSDCents?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Number, + isDefaultValueColumn: true, + required: true, + title: "SMS or Call Current Balance", + description: "Balance in USD for SMS or Call", + }) + @Column({ + type: ColumnType.Number, + nullable: false, + unique: false, + default: 0, + }) + public smsOrCallCurrentBalanceInUSDCents?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - ], - update: [Permission.ProjectOwner, Permission.ManageProjectBilling], - }) - @TableColumn({ - type: TableColumnType.Number, - isDefaultValueColumn: true, - required: true, - title: 'Auto Recharge Amount', - description: 'Auto recharge amount in USD for SMS or Call', - }) - @Column({ - type: ColumnType.Number, - nullable: false, - unique: false, - default: 20, - }) - public autoRechargeSmsOrCallByBalanceInUSD?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + ], + update: [Permission.ProjectOwner, Permission.ManageProjectBilling], + }) + @TableColumn({ + type: TableColumnType.Number, + isDefaultValueColumn: true, + required: true, + title: "Auto Recharge Amount", + description: "Auto recharge amount in USD for SMS or Call", + }) + @Column({ + type: ColumnType.Number, + nullable: false, + unique: false, + default: 20, + }) + public autoRechargeSmsOrCallByBalanceInUSD?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - ], - update: [Permission.ProjectOwner, Permission.ManageProjectBilling], - }) - @TableColumn({ - type: TableColumnType.Number, - isDefaultValueColumn: true, - required: true, - title: 'Auto Recharge when current balance falls to', - description: - 'Auto recharge is triggered when current balance falls to this amount in USD for SMS or Call', - }) - @Column({ - type: ColumnType.Number, - nullable: false, - unique: false, - default: 10, - }) - public autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + ], + update: [Permission.ProjectOwner, Permission.ManageProjectBilling], + }) + @TableColumn({ + type: TableColumnType.Number, + isDefaultValueColumn: true, + required: true, + title: "Auto Recharge when current balance falls to", + description: + "Auto recharge is triggered when current balance falls to this amount in USD for SMS or Call", + }) + @Column({ + type: ColumnType.Number, + nullable: false, + unique: false, + default: 10, + }) + public autoRechargeSmsOrCallWhenCurrentBalanceFallsInUSD?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [Permission.ProjectOwner, Permission.ManageProjectBilling], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Enable SMS Notifications', - description: 'Enable SMS notifications for this project.', - }) - @Column({ - nullable: false, - default: false, - type: ColumnType.Boolean, - }) - public enableSmsNotifications?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [Permission.ProjectOwner, Permission.ManageProjectBilling], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Enable SMS Notifications", + description: "Enable SMS notifications for this project.", + }) + @Column({ + nullable: false, + default: false, + type: ColumnType.Boolean, + }) + public enableSmsNotifications?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [Permission.ProjectOwner, Permission.ManageProjectBilling], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Enable Call Notifications', - description: 'Enable call notifications for this project.', - }) - @Column({ - nullable: false, - default: false, - type: ColumnType.Boolean, - }) - public enableCallNotifications?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [Permission.ProjectOwner, Permission.ManageProjectBilling], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Enable Call Notifications", + description: "Enable call notifications for this project.", + }) + @Column({ + nullable: false, + default: false, + type: ColumnType.Boolean, + }) + public enableCallNotifications?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [Permission.ProjectOwner, Permission.ManageProjectBilling], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Enable auto recharge SMS or Call balance', - description: - 'Enable auto recharge SMS or Call balance for this project.', - }) - @Column({ - nullable: false, - default: false, - type: ColumnType.Boolean, - }) - public enableAutoRechargeSmsOrCallBalance?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [Permission.ProjectOwner, Permission.ManageProjectBilling], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Enable auto recharge SMS or Call balance", + description: "Enable auto recharge SMS or Call balance for this project.", + }) + @Column({ + nullable: false, + default: false, + type: ColumnType.Boolean, + }) + public enableAutoRechargeSmsOrCallBalance?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Low Call and SMS Balance Notification Sent to Owners', - description: 'Low Call and SMS Balance Notification Sent to Owners', - }) - @Column({ - nullable: false, - default: false, - type: ColumnType.Boolean, - }) - public lowCallAndSMSBalanceNotificationSentToOwners?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Low Call and SMS Balance Notification Sent to Owners", + description: "Low Call and SMS Balance Notification Sent to Owners", + }) + @Column({ + nullable: false, + default: false, + type: ColumnType.Boolean, + }) + public lowCallAndSMSBalanceNotificationSentToOwners?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Failed Call and SMS Balance Charge Notification Sent to Owners', - description: - 'Failed Call and SMS Balance Charge Notification Sent to Owners', - }) - @Column({ - nullable: false, - default: false, - type: ColumnType.Boolean, - }) - public failedCallAndSMSBalanceChargeNotificationSentToOwners?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Failed Call and SMS Balance Charge Notification Sent to Owners", + description: + "Failed Call and SMS Balance Charge Notification Sent to Owners", + }) + @Column({ + nullable: false, + default: false, + type: ColumnType.Boolean, + }) + public failedCallAndSMSBalanceChargeNotificationSentToOwners?: boolean = + undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Failed Call and SMS Balance Charge Notification Sent to Owners', - description: - 'Failed Call and SMS Balance Charge Notification Sent to Owners', - }) - @Column({ - nullable: false, - default: false, - type: ColumnType.Boolean, - }) - public notEnabledSmsOrCallNotificationSentToOwners?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Failed Call and SMS Balance Charge Notification Sent to Owners", + description: + "Failed Call and SMS Balance Charge Notification Sent to Owners", + }) + @Column({ + nullable: false, + default: false, + type: ColumnType.Boolean, + }) + public notEnabledSmsOrCallNotificationSentToOwners?: boolean = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.ShortText, - title: 'Plan Name', - description: 'Name of the plan this project is subscribed to.', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public planName?: PlanSelect = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortText, + title: "Plan Name", + description: "Name of the plan this project is subscribed to.", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public planName?: PlanSelect = undefined; - @ColumnAccessControl({ - create: [], - read: [], + @ColumnAccessControl({ + create: [], + read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public lastActive?: Date = undefined; + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public lastActive?: Date = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Phone }) - @Column({ - type: ColumnType.Phone, - length: ColumnLength.Phone, - nullable: true, - unique: false, - transformer: Phone.getDatabaseTransformer(), - }) - public createdOwnerPhone?: Phone = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Phone }) + @Column({ + type: ColumnType.Phone, + length: ColumnLength.Phone, + nullable: true, + unique: false, + transformer: Phone.getDatabaseTransformer(), + }) + public createdOwnerPhone?: Phone = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Email }) - @Column({ - type: ColumnType.Email, - length: ColumnLength.Email, - nullable: true, - unique: false, - transformer: Email.getDatabaseTransformer(), - }) - public createdOwnerEmail?: Email = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Email }) + @Column({ + type: ColumnType.Email, + length: ColumnLength.Email, + nullable: true, + unique: false, + transformer: Email.getDatabaseTransformer(), + }) + public createdOwnerEmail?: Email = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Name }) - @Column({ - type: ColumnType.Name, - length: ColumnLength.Name, - nullable: true, - unique: false, - transformer: Name.getDatabaseTransformer(), - }) - public createdOwnerName?: Name = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.Name }) + @Column({ + type: ColumnType.Name, + length: ColumnLength.Name, + nullable: true, + unique: false, + transformer: Name.getDatabaseTransformer(), + }) + public createdOwnerName?: Name = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmSource?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmSource?: string = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmMedium?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmMedium?: string = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmCampaign?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmCampaign?: string = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmTerm?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmTerm?: string = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmContent?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmContent?: string = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmUrl?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmUrl?: string = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public createdOwnerCompanyName?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public createdOwnerCompanyName?: string = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [Permission.ProjectOwner], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'resellerId', - type: TableColumnType.Entity, - modelType: Reseller, - title: 'Reseller', - description: - 'Relation to Reseller Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Reseller; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'resellerId' }) - public reseller?: Reseller = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [Permission.ProjectOwner], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "resellerId", + type: TableColumnType.Entity, + modelType: Reseller, + title: "Reseller", + description: "Relation to Reseller Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Reseller; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "resellerId" }) + public reseller?: Reseller = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Reseller ID', - description: - 'ID of your OneUptime Reseller in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public resellerId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Reseller ID", + description: "ID of your OneUptime Reseller in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public resellerId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [Permission.ProjectOwner], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'ResellerPlanId', - type: TableColumnType.Entity, - modelType: ResellerPlan, - title: 'ResellerPlan', - description: - 'Relation to ResellerPlan Resource in which this object belongs', - }) - @ManyToOne( - () => { - return ResellerPlan; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'resellerPlanId' }) - public resellerPlan?: ResellerPlan = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [Permission.ProjectOwner], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "ResellerPlanId", + type: TableColumnType.Entity, + modelType: ResellerPlan, + title: "ResellerPlan", + description: + "Relation to ResellerPlan Resource in which this object belongs", + }) + @ManyToOne( + () => { + return ResellerPlan; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "resellerPlanId" }) + public resellerPlan?: ResellerPlan = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [Permission.ProjectOwner], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Reseller Plan ID', - description: - 'ID of your OneUptime Reseller Plan in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public resellerPlanId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [Permission.ProjectOwner], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Reseller Plan ID", + description: + "ID of your OneUptime Reseller Plan in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public resellerPlanId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.ShortText, - title: 'License ID', - description: 'License ID from a OneUptime Reseller', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public resellerLicenseId?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortText, + title: "License ID", + description: "License ID from a OneUptime Reseller", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public resellerLicenseId?: string = undefined; } diff --git a/Model/Models/ProjectCallSMSConfig.ts b/Model/Models/ProjectCallSMSConfig.ts index 36e2082922..f77283a039 100644 --- a/Model/Models/ProjectCallSMSConfig.ts +++ b/Model/Models/ProjectCallSMSConfig.ts @@ -1,432 +1,430 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Phone from 'Common/Types/Phone'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Phone from "Common/Types/Phone"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteProjectCallSMSConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectCallSMSConfig, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteProjectCallSMSConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectCallSMSConfig, + ], }) -@CrudApiEndpoint(new Route('/call-sms-config')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/call-sms-config")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'ProjectCallSMSConfig', - singularName: 'Call and SMS Config', - pluralName: 'Call and SMS Configs', - icon: IconProp.Email, - tableDescription: 'Manage Custom SMTP Servers for your project', + tableName: "ProjectCallSMSConfig", + singularName: "Call and SMS Config", + pluralName: "Call and SMS Configs", + icon: IconProp.Email, + tableDescription: "Manage Custom SMTP Servers for your project", }) @Entity({ - name: 'ProjectCallSMSConfig', + name: "ProjectCallSMSConfig", }) export default class ProjectCallSMSConfig extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectCallSMSConfig, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectCallSMSConfig, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectCallSMSConfig, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectCallSMSConfig, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + 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; - // Twilio config. + // Twilio config. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectCallSMSConfig, - ], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Twilio Account SID', - description: 'Account SID for your Twilio Account', - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: true, - }) - public twilioAccountSID?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectCallSMSConfig, + ], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Twilio Account SID", + description: "Account SID for your Twilio Account", + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: true, + }) + public twilioAccountSID?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectCallSMSConfig, - ], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Twilio Auth Token', - description: 'Auth Token for your Twilio Account', - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: true, - }) - public twilioAuthToken?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectCallSMSConfig, + ], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Twilio Auth Token", + description: "Auth Token for your Twilio Account", + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: true, + }) + public twilioAuthToken?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectCallSMSConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectCallSMSConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectCallSMSConfig, - ], - }) - @TableColumn({ - type: TableColumnType.Phone, - title: 'Twilio Phone Number', - description: 'Phone Number for your Twilio account', - }) - @Column({ - type: ColumnType.Phone, - length: ColumnLength.Phone, - nullable: true, - unique: true, - transformer: Phone.getDatabaseTransformer(), - }) - public twilioPhoneNumber?: Phone = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectCallSMSConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectCallSMSConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectCallSMSConfig, + ], + }) + @TableColumn({ + type: TableColumnType.Phone, + title: "Twilio Phone Number", + description: "Phone Number for your Twilio account", + }) + @Column({ + type: ColumnType.Phone, + length: ColumnLength.Phone, + nullable: true, + unique: true, + transformer: Phone.getDatabaseTransformer(), + }) + public twilioPhoneNumber?: Phone = undefined; } diff --git a/Model/Models/ProjectSmtpConfig.ts b/Model/Models/ProjectSmtpConfig.ts index 56e48b9fba..39ac06e75a 100755 --- a/Model/Models/ProjectSmtpConfig.ts +++ b/Model/Models/ProjectSmtpConfig.ts @@ -1,523 +1,521 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Hostname from 'Common/Types/API/Hostname'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import Email from 'Common/Types/Email'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Port from 'Common/Types/Port'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Hostname from "Common/Types/API/Hostname"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import Email from "Common/Types/Email"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Port from "Common/Types/Port"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], }) -@CrudApiEndpoint(new Route('/smtp-config')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/smtp-config")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'ProjectSMTPConfig', - singularName: 'SMTP Config', - pluralName: 'SMTP Configs', - icon: IconProp.Email, - tableDescription: 'Manage Custom SMTP Servers for your project', + tableName: "ProjectSMTPConfig", + singularName: "SMTP Config", + pluralName: "SMTP Configs", + icon: IconProp.Email, + tableDescription: "Manage Custom SMTP Servers for your project", }) @Entity({ - name: 'ProjectSMTPConfig', + name: "ProjectSMTPConfig", }) export default class ProjectSmtpConfig extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + 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; - // This is not required because some SMTP servers do not require authentication. - // eg: https://learn.microsoft.com/en-us/exchange/mail-flow-best-practices/how-to-set-up-a-multifunction-device-or-application-to-send-email-using-microsoft-365-or-office-365#option-2-send-mail-directly-from-your-printer-or-application-to-microsoft-365-or-office-365-direct-send - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], - }) - @TableColumn({ required: false, type: TableColumnType.ShortText }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public username?: string = undefined; + // This is not required because some SMTP servers do not require authentication. + // eg: https://learn.microsoft.com/en-us/exchange/mail-flow-best-practices/how-to-set-up-a-multifunction-device-or-application-to-send-email-using-microsoft-365-or-office-365#option-2-send-mail-directly-from-your-printer-or-application-to-microsoft-365-or-office-365-direct-send + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], + }) + @TableColumn({ required: false, type: TableColumnType.ShortText }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public username?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], - }) - @TableColumn({ required: false, type: TableColumnType.Password }) - @Column({ - nullable: true, - type: ColumnType.Password, - length: ColumnLength.Password, - }) - public password?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], + }) + @TableColumn({ required: false, type: TableColumnType.Password }) + @Column({ + nullable: true, + type: ColumnType.Password, + length: ColumnLength.Password, + }) + public password?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], - }) - @TableColumn({ required: true, type: TableColumnType.ShortText }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - transformer: Hostname.getDatabaseTransformer(), - }) - public hostname?: Hostname = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], + }) + @TableColumn({ required: true, type: TableColumnType.ShortText }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + transformer: Hostname.getDatabaseTransformer(), + }) + public hostname?: Hostname = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], - }) - @TableColumn({ required: true, type: TableColumnType.Number }) - @Column({ - nullable: false, - type: ColumnType.Number, - transformer: Port.getDatabaseTransformer(), - }) - public port?: Port = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], + }) + @TableColumn({ required: true, type: TableColumnType.Number }) + @Column({ + nullable: false, + type: ColumnType.Number, + transformer: Port.getDatabaseTransformer(), + }) + public port?: Port = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], - }) - @TableColumn({ required: true, type: TableColumnType.Email }) - @Column({ - nullable: false, - type: ColumnType.Email, - length: ColumnLength.Email, - transformer: Email.getDatabaseTransformer(), - }) - public fromEmail?: Email = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], + }) + @TableColumn({ required: true, type: TableColumnType.Email }) + @Column({ + nullable: false, + type: ColumnType.Email, + length: ColumnLength.Email, + transformer: Email.getDatabaseTransformer(), + }) + public fromEmail?: Email = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], - }) - @TableColumn({ required: true, type: TableColumnType.ShortText }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public fromName?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], + }) + @TableColumn({ required: true, type: TableColumnType.ShortText }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public fromName?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSMTPConfig, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSMTPConfig, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSMTPConfig, - ], - }) - @TableColumn({ required: true, type: TableColumnType.Boolean }) - @Column({ - nullable: false, - type: ColumnType.Boolean, - default: true, - }) - public secure?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSMTPConfig, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSMTPConfig, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSMTPConfig, + ], + }) + @TableColumn({ required: true, type: TableColumnType.Boolean }) + @Column({ + nullable: false, + type: ColumnType.Boolean, + default: true, + }) + public secure?: boolean = undefined; } diff --git a/Model/Models/ProjectSso.ts b/Model/Models/ProjectSso.ts index 901e2aa457..c7e64fd87f 100644 --- a/Model/Models/ProjectSso.ts +++ b/Model/Models/ProjectSso.ts @@ -1,584 +1,582 @@ -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import DigestMethod from 'Common/Types/SSO/DigestMethod'; -import SignatureMethod from 'Common/Types/SSO/SignatureMethod'; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import DigestMethod from "Common/Types/SSO/DigestMethod"; +import SignatureMethod from "Common/Types/SSO/SignatureMethod"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Scale, - read: PlanSelect.Scale, - update: PlanSelect.Scale, - delete: PlanSelect.Scale, + create: PlanSelect.Scale, + read: PlanSelect.Scale, + update: PlanSelect.Scale, + delete: PlanSelect.Scale, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectUser, - Permission.UnAuthorizedSsoUser, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteProjectSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectUser, + Permission.UnAuthorizedSsoUser, + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteProjectSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], }) -@CrudApiEndpoint(new Route('/project-sso')) +@CrudApiEndpoint(new Route("/project-sso")) @TableMetadata({ - tableName: 'ProjectSSO', - singularName: 'SSO', - pluralName: 'SSO', - icon: IconProp.Lock, - tableDescription: 'Manage SSO for your project', + tableName: "ProjectSSO", + singularName: "SSO", + pluralName: "SSO", + icon: IconProp.Lock, + tableDescription: "Manage SSO for your project", }) @Entity({ - name: 'ProjectSSO', + name: "ProjectSSO", }) export default class ProjectSSO extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.Public, - Permission.UnAuthorizedSsoUser, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.Public, + Permission.UnAuthorizedSsoUser, + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.Public, - Permission.UnAuthorizedSsoUser, - Permission.ProjectMember, - Permission.ReadProjectSSO, - Permission.ProjectUser, - Permission.UnAuthorizedSsoUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.Public, + Permission.UnAuthorizedSsoUser, + Permission.ProjectMember, + Permission.ReadProjectSSO, + Permission.ProjectUser, + Permission.UnAuthorizedSsoUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.Public, - Permission.UnAuthorizedSsoUser, - Permission.ProjectMember, - Permission.ReadProjectSSO, - Permission.ProjectUser, - Permission.UnAuthorizedSsoUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.Public, + Permission.UnAuthorizedSsoUser, + Permission.ProjectMember, + Permission.ReadProjectSSO, + Permission.ProjectUser, + Permission.UnAuthorizedSsoUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.Public, - Permission.UnAuthorizedSsoUser, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.Public, + Permission.UnAuthorizedSsoUser, + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public signatureMethod?: SignatureMethod = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public signatureMethod?: SignatureMethod = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public digestMethod?: DigestMethod = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public digestMethod?: DigestMethod = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSSO, - Permission.Public, - Permission.ProjectUser, - Permission.UnAuthorizedSsoUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongURL, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.LongURL, - transformer: URL.getDatabaseTransformer(), - }) - @UniqueColumnBy('projectId') - public signOnURL?: URL = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSSO, + Permission.Public, + Permission.ProjectUser, + Permission.UnAuthorizedSsoUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongURL, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.LongURL, + transformer: URL.getDatabaseTransformer(), + }) + @UniqueColumnBy("projectId") + public signOnURL?: URL = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Team, - }) - @ManyToMany( - () => { - return Team; - }, - { eager: false } - ) - @JoinTable({ - name: 'ProjectSsoTeam', - inverseJoinColumn: { - name: 'teamId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'projectSsoId', - referencedColumnName: '_id', - }, - }) - public teams?: Array<Team> = undefined; // teams that teammember should be added to when they sign into SSO for the first time. + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Team, + }) + @ManyToMany( + () => { + return Team; + }, + { eager: false }, + ) + @JoinTable({ + name: "ProjectSsoTeam", + inverseJoinColumn: { + name: "teamId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "projectSsoId", + referencedColumnName: "_id", + }, + }) + public teams?: Array<Team> = undefined; // teams that teammember should be added to when they sign into SSO for the first time. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadProjectSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongURL, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.LongURL, - transformer: URL.getDatabaseTransformer(), - }) - public issuerURL?: URL = undefined; + Permission.ReadProjectSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongURL, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.LongURL, + transformer: URL.getDatabaseTransformer(), + }) + public issuerURL?: URL = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.VeryLongText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.VeryLongText, - }) - public publicCertificate?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.VeryLongText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.VeryLongText, + }) + public publicCertificate?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - update: [], - }) - @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; + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - 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; + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.UnAuthorizedSsoUser, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectSSO, - ], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isEnabled?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.UnAuthorizedSsoUser, + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectSSO, + ], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isEnabled?: boolean = undefined; - // Is this integration tested? - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + // Is this integration tested? + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectSSO, - ], - update: [], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isTested?: boolean = undefined; + Permission.ProjectMember, + Permission.ReadProjectSSO, + ], + update: [], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isTested?: boolean = undefined; } diff --git a/Model/Models/PromoCode.ts b/Model/Models/PromoCode.ts index 1b245e171f..e272529b62 100644 --- a/Model/Models/PromoCode.ts +++ b/Model/Models/PromoCode.ts @@ -1,416 +1,413 @@ -import Project from './Project'; -import Reseller from './Reseller'; -import ResellerPlan from './ResellerPlan'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import Email from 'Common/Types/Email'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import Reseller from "./Reseller"; +import ResellerPlan from "./ResellerPlan"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import Email from "Common/Types/Email"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableAccessControl({ - create: [], - read: [], - delete: [], - update: [], + create: [], + read: [], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/promo-code')) +@CrudApiEndpoint(new Route("/promo-code")) @TableMetadata({ - tableName: 'PromoCode', - singularName: 'Promo Code', - pluralName: 'Promo Codes', - icon: IconProp.Billing, - tableDescription: - 'List of Promo Codes that customers can use in OneUptime.', + tableName: "PromoCode", + singularName: "Promo Code", + pluralName: "Promo Codes", + icon: IconProp.Billing, + tableDescription: "List of Promo Codes that customers can use in OneUptime.", }) @Entity({ - name: 'PromoCode', + name: "PromoCode", }) export default class PromoCode extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Resller ID', - description: 'ID that is shared between resller and OneUptime.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public promoCodeId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Resller ID", + description: "ID that is shared between resller and OneUptime.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public promoCodeId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Plan Type', - description: - 'If this promocode can be used for specific plan, please specify here. If null, it can be used for all the plans', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public planType?: PlanSelect = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Plan Type", + description: + "If this promocode can be used for specific plan, please specify here. If null, it can be used for all the plans", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public planType?: PlanSelect = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'User Email', - description: - 'Which user can redeem this promocode? If no one is specified, anyone can redeem this promocode.', - }) - @Column({ - nullable: true, - type: ColumnType.Email, - length: ColumnLength.Email, - transformer: Email.getDatabaseTransformer(), - }) - public userEmail?: Email = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "User Email", + description: + "Which user can redeem this promocode? If no one is specified, anyone can redeem this promocode.", + }) + @Column({ + nullable: true, + type: ColumnType.Email, + length: ColumnLength.Email, + transformer: Email.getDatabaseTransformer(), + }) + public userEmail?: Email = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectLabel, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectLabel, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectLabel, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectLabel, + ], + 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; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'resellerId', - type: TableColumnType.Entity, - modelType: Reseller, - title: 'Reseller', - description: - 'Relation to Reseller Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Reseller; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'resellerId' }) - public reseller?: Reseller = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "resellerId", + type: TableColumnType.Entity, + modelType: Reseller, + title: "Reseller", + description: "Relation to Reseller Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Reseller; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "resellerId" }) + public reseller?: Reseller = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Reseller ID', - description: - 'ID of your OneUptime Reseller in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public resellerId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Reseller ID", + description: "ID of your OneUptime Reseller in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public resellerId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'ResellerPlanId', - type: TableColumnType.Entity, - modelType: ResellerPlan, - title: 'ResellerPlan', - description: - 'Relation to ResellerPlan Resource in which this object belongs', - }) - @ManyToOne( - () => { - return ResellerPlan; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'resellerPlanId' }) - public resellerPlan?: ResellerPlan = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "ResellerPlanId", + type: TableColumnType.Entity, + modelType: ResellerPlan, + title: "ResellerPlan", + description: + "Relation to ResellerPlan Resource in which this object belongs", + }) + @ManyToOne( + () => { + return ResellerPlan; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "resellerPlanId" }) + public resellerPlan?: ResellerPlan = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Reseller Plan ID', - description: - 'ID of your OneUptime Reseller Plan in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public resellerPlanId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Reseller Plan ID", + description: + "ID of your OneUptime Reseller Plan in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public resellerPlanId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.User], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProject, - Permission.UnAuthorizedSsoUser, - Permission.ProjectUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ManageProjectBilling, - Permission.EditProject, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.ShortText, - title: 'License ID', - description: 'License ID from a OneUptime Reseller', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public resellerLicenseId?: string = undefined; + @ColumnAccessControl({ + create: [Permission.User], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProject, + Permission.UnAuthorizedSsoUser, + Permission.ProjectUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ManageProjectBilling, + Permission.EditProject, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortText, + title: "License ID", + description: "License ID from a OneUptime Reseller", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public resellerLicenseId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isPromoCodeUsed?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isPromoCodeUsed?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ isDefaultValueColumn: false, type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - }) - public promoCodeUsedAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ isDefaultValueColumn: false, type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + }) + public promoCodeUsedAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'projectId', - type: TableColumnType.Entity, - modelType: Project, - title: 'Project', - description: - 'If promo code is used for a specific project, please specify here.', - }) - @ManyToOne( - () => { - return Project; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "projectId", + type: TableColumnType.Entity, + modelType: Project, + title: "Project", + description: + "If promo code is used for a specific project, please specify here.", + }) + @ManyToOne( + () => { + return Project; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Project ID', - description: - 'If promo code is used for a specific project, please specify here.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public projectId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Project ID", + description: + "If promo code is used for a specific project, please specify here.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public projectId?: ObjectID = undefined; } diff --git a/Model/Models/Reseller.ts b/Model/Models/Reseller.ts index 70386f9367..72341a806d 100644 --- a/Model/Models/Reseller.ts +++ b/Model/Models/Reseller.ts @@ -1,279 +1,278 @@ -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; @TableAccessControl({ - create: [], - read: [Permission.ProjectOwner, Permission.Public], - delete: [], - update: [], + create: [], + read: [Permission.ProjectOwner, Permission.Public], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/reseller')) +@CrudApiEndpoint(new Route("/reseller")) @TableMetadata({ - tableName: 'Reseller', - singularName: 'Reseller', - pluralName: 'Resellers', - icon: IconProp.Billing, - tableDescription: - 'List of Resellers that sell OneUptime to their customers', + tableName: "Reseller", + singularName: "Reseller", + pluralName: "Resellers", + icon: IconProp.Billing, + tableDescription: "List of Resellers that sell OneUptime to their customers", }) @Entity({ - name: 'Reseller', + name: "Reseller", }) export default class Reseller extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [Permission.Public], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Resller ID', - description: 'ID that is shared between resller and OneUptime.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public resellerId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.Public], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Resller ID", + description: "ID that is shared between resller and OneUptime.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public resellerId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Name of the reseller', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Name of the reseller", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Description', - description: 'Description of the reseller', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Description", + description: "Description of the reseller", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: false, - title: 'Username', - description: 'Username of the reseller', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public username?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: false, + title: "Username", + description: "Username of the reseller", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public username?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: false, - title: 'Password', - description: 'Password for reseller to login', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public password?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: false, + title: "Password", + description: "Password for reseller to login", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public password?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @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: [], + read: [], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.ShortURL, - canReadOnRelationQuery: true, - title: 'Change Plan Link', - description: 'Reseller Change plan Link', - }) - @Column({ - nullable: true, - type: ColumnType.ShortURL, - length: ColumnLength.ShortURL, - transformer: URL.getDatabaseTransformer(), - }) - public changePlanLink?: URL = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortURL, + canReadOnRelationQuery: true, + title: "Change Plan Link", + description: "Reseller Change plan Link", + }) + @Column({ + nullable: true, + type: ColumnType.ShortURL, + length: ColumnLength.ShortURL, + transformer: URL.getDatabaseTransformer(), + }) + public changePlanLink?: URL = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.Public], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Hide Phone Number on Signup', - description: - 'Should we hide the phone number on sign up form based on reseller request?', - }) - @Column({ - nullable: true, - type: ColumnType.Boolean, - }) - public hidePhoneNumberOnSignup?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.Public], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Hide Phone Number on Signup", + description: + "Should we hide the phone number on sign up form based on reseller request?", + }) + @Column({ + nullable: true, + type: ColumnType.Boolean, + }) + public hidePhoneNumberOnSignup?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.Public], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Enable Telemetry Features', - description: 'Should we enable telemetry features for this reseller?', - }) - @Column({ - nullable: true, - default: false, - type: ColumnType.Boolean, - }) - public enableTelemetryFeatures?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.Public], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Enable Telemetry Features", + description: "Should we enable telemetry features for this reseller?", + }) + @Column({ + nullable: true, + default: false, + type: ColumnType.Boolean, + }) + public enableTelemetryFeatures?: boolean = undefined; } diff --git a/Model/Models/ResellerPlan.ts b/Model/Models/ResellerPlan.ts index ba04dfc112..955045805b 100644 --- a/Model/Models/ResellerPlan.ts +++ b/Model/Models/ResellerPlan.ts @@ -1,309 +1,306 @@ -import Reseller from './Reseller'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Reseller from "./Reseller"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableAccessControl({ - create: [], - read: [Permission.ProjectOwner], - delete: [], - update: [], + create: [], + read: [Permission.ProjectOwner], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/reseller-plan')) +@CrudApiEndpoint(new Route("/reseller-plan")) @TableMetadata({ - tableName: 'Reseller Plan', - singularName: 'Reseller Plan', - pluralName: 'Reseller Plans', - icon: IconProp.Billing, - tableDescription: - 'List of Reseller Plans that reseller use to sell OneUptime.', + tableName: "Reseller Plan", + singularName: "Reseller Plan", + pluralName: "Reseller Plans", + icon: IconProp.Billing, + tableDescription: + "List of Reseller Plans that reseller use to sell OneUptime.", }) @Entity({ - name: 'ResellerPlan', + name: "ResellerPlan", }) export default class ResellerPlan extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'resellerId', - type: TableColumnType.Entity, - modelType: Reseller, - title: 'Reseller', - description: - 'Relation to Reseller Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Reseller; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'resellerId' }) - public reseller?: Reseller = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "resellerId", + type: TableColumnType.Entity, + modelType: Reseller, + title: "Reseller", + description: "Relation to Reseller Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Reseller; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "resellerId" }) + public reseller?: Reseller = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Reseller ID', - description: - 'ID of your OneUptime Reseller in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public resellerId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Reseller ID", + description: "ID of your OneUptime Reseller in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public resellerId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Plan ID', - description: - 'ID of the plan. This is shared by the Reseller and OneUptime.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public planId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Plan ID", + description: + "ID of the plan. This is shared by the Reseller and OneUptime.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public planId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Name of the Reseller Plan', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Name of the Reseller Plan", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Plan Type', - description: - 'Name of the Base Plan that the project should created with.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public planType?: PlanSelect = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Plan Type", + description: "Name of the Base Plan that the project should created with.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public planType?: PlanSelect = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Description', - description: 'Description of the Reseller Plan', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Description", + description: "Description of the Reseller Plan", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.Number, - canReadOnRelationQuery: true, - title: 'Monitor Limit', - description: 'Monitor Limit of the OneUptime Project.', - }) - @Column({ - nullable: true, - type: ColumnType.Number, - }) - public monitorLimit?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.Number, + canReadOnRelationQuery: true, + title: "Monitor Limit", + description: "Monitor Limit of the OneUptime Project.", + }) + @Column({ + nullable: true, + type: ColumnType.Number, + }) + public monitorLimit?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.Number, - canReadOnRelationQuery: true, - title: 'Team Member Limit', - description: 'Team Member Limit of the OneUptime Project.', - }) - @Column({ - nullable: true, - type: ColumnType.Number, - }) - public teamMemberLimit?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.Number, + canReadOnRelationQuery: true, + title: "Team Member Limit", + description: "Team Member Limit of the OneUptime Project.", + }) + @Column({ + nullable: true, + type: ColumnType.Number, + }) + public teamMemberLimit?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @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: [], + read: [], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Other Features', - description: - 'Other Features of this Reseller Plan. This is shown on the dashboard. Its a comma seperated list of features', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public otherFeatures?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Other Features", + description: + "Other Features of this Reseller Plan. This is shown on the dashboard. Its a comma seperated list of features", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public otherFeatures?: string = undefined; } diff --git a/Model/Models/ScheduledMaintenance.ts b/Model/Models/ScheduledMaintenance.ts index 12deb2e722..c7845c6338 100644 --- a/Model/Models/ScheduledMaintenance.ts +++ b/Model/Models/ScheduledMaintenance.ts @@ -1,884 +1,883 @@ -import Label from './Label'; -import Monitor from './Monitor'; -import MonitorStatus from './MonitorStatus'; -import Project from './Project'; -import ScheduledMaintenanceState from './ScheduledMaintenanceState'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import MultiTenentQueryAllowed from 'Common/Types/Database/MultiTenentQueryAllowed'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Label from "./Label"; +import Monitor from "./Monitor"; +import MonitorStatus from "./MonitorStatus"; +import Project from "./Project"; +import ScheduledMaintenanceState from "./ScheduledMaintenanceState"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import MultiTenentQueryAllowed from "Common/Types/Database/MultiTenentQueryAllowed"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() -@AccessControlColumn('labels') +@AccessControlColumn("labels") @MultiTenentQueryAllowed(true) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], }) -@CrudApiEndpoint(new Route('/scheduled-maintenance')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/scheduled-maintenance")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'ScheduledMaintenance', + name: "ScheduledMaintenance", }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) @TableMetadata({ - singularName: 'Scheduled Maintenance Event', - pluralName: 'Scheduled Maintenance Events', - icon: IconProp.Clock, - tableName: 'ScheduledMaintenance', - tableDescription: 'Manage scheduled maintenance event for your project', + singularName: "Scheduled Maintenance Event", + pluralName: "Scheduled Maintenance Events", + icon: IconProp.Clock, + tableName: "ScheduledMaintenance", + tableDescription: "Manage scheduled maintenance event for your project", }) export default class ScheduledMaintenance extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Title', - description: 'Title of this scheduled event.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public title?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Title", + description: "Title of this scheduled event.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public title?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.Markdown, - title: 'Description', - description: - 'Description of this scheduled event that will show up on Status Page. This is in markdown.', - }) - @Column({ - nullable: true, - type: ColumnType.Markdown, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.Markdown, + title: "Description", + description: + "Description of this scheduled event that will show up on Status Page. This is in markdown.", + }) + @Column({ + nullable: true, + type: ColumnType.Markdown, + }) + public description?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Monitor, - title: 'Monitors', - description: 'List of monitors attached to this event', - }) - @ManyToMany( - () => { - return Monitor; - }, - { eager: false } - ) - @JoinTable({ - name: 'ScheduledMaintenanceMonitor', - inverseJoinColumn: { - name: 'monitorId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'scheduledMaintenanceId', - referencedColumnName: '_id', - }, - }) - public monitors?: Array<Monitor> = undefined; // monitors affected by this scheduledMaintenance. + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Monitor, + title: "Monitors", + description: "List of monitors attached to this event", + }) + @ManyToMany( + () => { + return Monitor; + }, + { eager: false }, + ) + @JoinTable({ + name: "ScheduledMaintenanceMonitor", + inverseJoinColumn: { + name: "monitorId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "scheduledMaintenanceId", + referencedColumnName: "_id", + }, + }) + public monitors?: Array<Monitor> = undefined; // monitors affected by this scheduledMaintenance. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: StatusPage, - title: 'Status Pages', - description: 'List of status pages to show this event on', - }) - @ManyToMany( - () => { - return StatusPage; - }, - { eager: false } - ) - @JoinTable({ - name: 'ScheduledMaintenanceStatusPage', - inverseJoinColumn: { - name: 'statusPageId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'scheduledMaintenanceId', - referencedColumnName: '_id', - }, - }) - public statusPages?: Array<StatusPage> = undefined; // visible on which status page? + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: StatusPage, + title: "Status Pages", + description: "List of status pages to show this event on", + }) + @ManyToMany( + () => { + return StatusPage; + }, + { eager: false }, + ) + @JoinTable({ + name: "ScheduledMaintenanceStatusPage", + inverseJoinColumn: { + name: "statusPageId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "scheduledMaintenanceId", + referencedColumnName: "_id", + }, + }) + public statusPages?: Array<StatusPage> = undefined; // visible on which status page? - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'ScheduledMaintenanceLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'scheduledMaintenanceId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "ScheduledMaintenanceLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "scheduledMaintenanceId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'currentScheduledMaintenanceStateId', - type: TableColumnType.Entity, - modelType: ScheduledMaintenanceState, - title: 'Current Scheduled Maintenance State', - description: - 'Relation to Scheduled Maintenance State. The state the event currently is in.', - }) - @ManyToOne( - () => { - return ScheduledMaintenanceState; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'currentScheduledMaintenanceStateId' }) - public currentScheduledMaintenanceState?: ScheduledMaintenanceState = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "currentScheduledMaintenanceStateId", + type: TableColumnType.Entity, + modelType: ScheduledMaintenanceState, + title: "Current Scheduled Maintenance State", + description: + "Relation to Scheduled Maintenance State. The state the event currently is in.", + }) + @ManyToOne( + () => { + return ScheduledMaintenanceState; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "currentScheduledMaintenanceStateId" }) + public currentScheduledMaintenanceState?: ScheduledMaintenanceState = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Current Scheduled Maintenance State ID', - description: - 'Scheduled Maintenance State ID. The state the event currently is in.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public currentScheduledMaintenanceStateId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Current Scheduled Maintenance State ID", + description: + "Scheduled Maintenance State ID. The state the event currently is in.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public currentScheduledMaintenanceStateId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'changeMonitorStatusToId', - type: TableColumnType.Entity, - modelType: ScheduledMaintenanceState, - title: 'Change Monitor Status To', - description: - 'Relation to Monitor Status Object. All monitors connected to this event will be changed to this status when the event is ongoing.', - }) - @ManyToOne( - () => { - return MonitorStatus; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'changeMonitorStatusToId' }) - public changeMonitorStatusTo?: MonitorStatus = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "changeMonitorStatusToId", + type: TableColumnType.Entity, + modelType: ScheduledMaintenanceState, + title: "Change Monitor Status To", + description: + "Relation to Monitor Status Object. All monitors connected to this event will be changed to this status when the event is ongoing.", + }) + @ManyToOne( + () => { + return MonitorStatus; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "changeMonitorStatusToId" }) + public changeMonitorStatusTo?: MonitorStatus = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - title: 'Change Monitor Status To ID', - description: - 'Relation to Monitor Status Object ID. All monitors connected to this incident will be changed to this status when the event is ongoing.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public changeMonitorStatusToId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + title: "Change Monitor Status To ID", + description: + "Relation to Monitor Status Object ID. All monitors connected to this incident will be changed to this status when the event is ongoing.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public changeMonitorStatusToId?: ObjectID = undefined; - @TableColumn({ - title: 'Start At', - type: TableColumnType.Date, - required: true, - description: 'When does this event start?', - }) - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @Column({ - nullable: false, - type: ColumnType.Date, - }) - public startsAt?: Date = undefined; + @TableColumn({ + title: "Start At", + type: TableColumnType.Date, + required: true, + description: "When does this event start?", + }) + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @Column({ + nullable: false, + type: ColumnType.Date, + }) + public startsAt?: Date = undefined; - @TableColumn({ - title: 'End At', - type: TableColumnType.Date, - required: true, - description: 'When does this event end?', - }) - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @Column({ - nullable: false, - type: ColumnType.Date, - }) - public endsAt?: Date = undefined; + @TableColumn({ + title: "End At", + type: TableColumnType.Date, + required: true, + description: "When does this event end?", + }) + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @Column({ + nullable: false, + type: ColumnType.Date, + }) + public endsAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Status Page Subscribers Notified On Event Scheduled', - description: 'Status Page Subscribers Notified On Event Scheduled', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isStatusPageSubscribersNotifiedOnEventScheduled?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Status Page Subscribers Notified On Event Scheduled", + description: "Status Page Subscribers Notified On Event Scheduled", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isStatusPageSubscribersNotifiedOnEventScheduled?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should subscribers be notified when event is created?', - description: - 'Should subscribers be notified about this event creation?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public shouldStatusPageSubscribersBeNotifiedOnEventCreated?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should subscribers be notified when event is created?", + description: "Should subscribers be notified about this event creation?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public shouldStatusPageSubscribersBeNotifiedOnEventCreated?: boolean = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should subscribers be notified when event is changed to ongoing?', - description: - 'Should subscribers be notified about this event event is changed to ongoing?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing?: boolean = - undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should subscribers be notified when event is changed to ongoing?", + description: + "Should subscribers be notified about this event event is changed to ongoing?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public shouldStatusPageSubscribersBeNotifiedWhenEventChangedToOngoing?: boolean = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should subscribers be notified when event is changed to ended?', - description: - 'Should subscribers be notified about this event event is changed to ended?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded?: boolean = - undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should subscribers be notified when event is changed to ended?", + description: + "Should subscribers be notified about this event event is changed to ended?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public shouldStatusPageSubscribersBeNotifiedWhenEventChangedToEnded?: boolean = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectScheduledMaintenance, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - title: 'Custom Fields', - description: 'Custom Fields on this resource.', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public customFields?: JSONObject = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectScheduledMaintenance, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + title: "Custom Fields", + description: "Custom Fields on this resource.", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public customFields?: JSONObject = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectScheduledMaintenance, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectScheduledMaintenance, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified Of Resource Creation?', - description: 'Are owners notified of when this resource is created?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotifiedOfResourceCreation?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectScheduledMaintenance, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectScheduledMaintenance, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified Of Resource Creation?", + description: "Are owners notified of when this resource is created?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotifiedOfResourceCreation?: boolean = undefined; } diff --git a/Model/Models/ScheduledMaintenanceCustomField.ts b/Model/Models/ScheduledMaintenanceCustomField.ts index 049558398b..c7c1132cf7 100644 --- a/Model/Models/ScheduledMaintenanceCustomField.ts +++ b/Model/Models/ScheduledMaintenanceCustomField.ts @@ -1,342 +1,340 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import CustomFieldType from 'Common/Types/CustomField/CustomFieldType'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import CustomFieldType from "Common/Types/CustomField/CustomFieldType"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateScheduledMaintenanceCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteScheduledMaintenanceCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditScheduledMaintenanceCustomField, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateScheduledMaintenanceCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteScheduledMaintenanceCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditScheduledMaintenanceCustomField, + ], }) -@CrudApiEndpoint(new Route('/scheduled-maintenance-custom-field')) +@CrudApiEndpoint(new Route("/scheduled-maintenance-custom-field")) @TableMetadata({ - tableName: 'ScheduledMaintenanceCustomField', - singularName: 'Scheduled Maintenance Custom Field', - pluralName: 'Scheduled Maintenance Custom Fields', - icon: IconProp.TableCells, - tableDescription: 'Manage custom fields for your scheduled event', + tableName: "ScheduledMaintenanceCustomField", + singularName: "Scheduled Maintenance Custom Field", + pluralName: "Scheduled Maintenance Custom Fields", + icon: IconProp.TableCells, + tableDescription: "Manage custom fields for your scheduled event", }) @Entity({ - name: 'ScheduledMaintenanceCustomField', + name: "ScheduledMaintenanceCustomField", }) export default class ScheduledMaintenanceCustomField extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateScheduledMaintenanceCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateScheduledMaintenanceCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateScheduledMaintenanceCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateScheduledMaintenanceCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateScheduledMaintenanceCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditScheduledMaintenanceCustomField, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateScheduledMaintenanceCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditScheduledMaintenanceCustomField, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateScheduledMaintenanceCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditScheduledMaintenanceCustomField, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: - 'Friendly description of this custom field that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateScheduledMaintenanceCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditScheduledMaintenanceCustomField, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: + "Friendly description of this custom field that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateScheduledMaintenanceCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.CustomFieldType, - title: 'Custom Field Type', - description: 'Is this field Text, Number or Boolean?', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public type?: CustomFieldType = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateScheduledMaintenanceCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.CustomFieldType, + title: "Custom Field Type", + description: "Is this field Text, Number or Boolean?", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public type?: CustomFieldType = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateScheduledMaintenanceCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateScheduledMaintenanceCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateScheduledMaintenanceCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateScheduledMaintenanceCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceCustomField, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceCustomField, + ], + 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; } diff --git a/Model/Models/ScheduledMaintenanceInternalNote.ts b/Model/Models/ScheduledMaintenanceInternalNote.ts index 9076b9f712..ba3b971cfb 100644 --- a/Model/Models/ScheduledMaintenanceInternalNote.ts +++ b/Model/Models/ScheduledMaintenanceInternalNote.ts @@ -1,373 +1,371 @@ -import Project from './Project'; -import ScheduledMaintenance from './ScheduledMaintenance'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import ScheduledMaintenance from "./ScheduledMaintenance"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@CanAccessIfCanReadOn('scheduledMaintenance') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("scheduledMaintenance") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceInternalNote, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteScheduledMaintenanceInternalNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceInternalNote, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceInternalNote, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteScheduledMaintenanceInternalNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceInternalNote, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/scheduled-maintenance-internal-note')) +@CrudApiEndpoint(new Route("/scheduled-maintenance-internal-note")) @Entity({ - name: 'ScheduledMaintenanceInternalNote', + name: "ScheduledMaintenanceInternalNote", }) @TableMetadata({ - tableName: 'ScheduledMaintenanceInternalNote', - singularName: 'Scheduled Event Internal Note', - pluralName: 'Scheduled Event Internal Notes', - icon: IconProp.Lock, - tableDescription: - 'Manage internal or postmortem note for your scheduled event', + tableName: "ScheduledMaintenanceInternalNote", + singularName: "Scheduled Event Internal Note", + pluralName: "Scheduled Event Internal Notes", + icon: IconProp.Lock, + tableDescription: + "Manage internal or postmortem note for your scheduled event", }) export default class ScheduledMaintenanceInternalNote extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceInternalNote, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceInternalNote, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceInternalNote, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceInternalNote, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceInternalNote, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'scheduledMaintenanceId', - type: TableColumnType.Entity, - modelType: ScheduledMaintenance, - title: 'Scheduled Maintenance', - description: - 'Relation to Scheduled Maintenance Event this resource belongs to', - }) - @ManyToOne( - () => { - return ScheduledMaintenance; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'scheduledMaintenanceId' }) - public scheduledMaintenance?: ScheduledMaintenance = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceInternalNote, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "scheduledMaintenanceId", + type: TableColumnType.Entity, + modelType: ScheduledMaintenance, + title: "Scheduled Maintenance", + description: + "Relation to Scheduled Maintenance Event this resource belongs to", + }) + @ManyToOne( + () => { + return ScheduledMaintenance; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "scheduledMaintenanceId" }) + public scheduledMaintenance?: ScheduledMaintenance = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceInternalNote, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Scheduled Maintenance ID', - description: 'ID of Scheduled Maintenance this resource belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public scheduledMaintenanceId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceInternalNote, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Scheduled Maintenance ID", + description: "ID of Scheduled Maintenance this resource belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public scheduledMaintenanceId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceInternalNote, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceInternalNote, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceInternalNote, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceInternalNote, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceInternalNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceInternalNote, - ], - }) - @TableColumn({ - type: TableColumnType.Markdown, - title: 'Note', - description: 'Notes in markdown', - }) - @Column({ - type: ColumnType.Markdown, - nullable: false, - unique: false, - }) - public note?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceInternalNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceInternalNote, + ], + }) + @TableColumn({ + type: TableColumnType.Markdown, + title: "Note", + description: "Notes in markdown", + }) + @Column({ + type: ColumnType.Markdown, + nullable: false, + unique: false, + }) + public note?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceInternalNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceInternalNote, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceInternalNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceInternalNote, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/ScheduledMaintenanceNoteTemplate.ts b/Model/Models/ScheduledMaintenanceNoteTemplate.ts index 7559e29996..343d3639ce 100644 --- a/Model/Models/ScheduledMaintenanceNoteTemplate.ts +++ b/Model/Models/ScheduledMaintenanceNoteTemplate.ts @@ -1,347 +1,345 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceNoteTemplate, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteScheduledMaintenanceNoteTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceNoteTemplate, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceNoteTemplate, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteScheduledMaintenanceNoteTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceNoteTemplate, + ], }) -@CrudApiEndpoint(new Route('/scheduled-maintenance-note-template')) +@CrudApiEndpoint(new Route("/scheduled-maintenance-note-template")) @Entity({ - name: 'ScheduledMaintenanceNoteTemplate', + name: "ScheduledMaintenanceNoteTemplate", }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) @TableMetadata({ - tableName: 'ScheduledMaintenanceNoteTemplate', - singularName: 'Scheduled Maintenance Note Template', - pluralName: 'Scheduled Maintenance Note Templates', - icon: IconProp.Alert, - tableDescription: - 'Manage scheduled maintenance note templates for your project', + tableName: "ScheduledMaintenanceNoteTemplate", + singularName: "Scheduled Maintenance Note Template", + pluralName: "Scheduled Maintenance Note Templates", + icon: IconProp.Alert, + tableDescription: + "Manage scheduled maintenance note templates for your project", }) export default class ScheduledMaintenanceNoteTemplate extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceNoteTemplate, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceNoteTemplate, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceNoteTemplate, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceNoteTemplate, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceNoteTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceNoteTemplate, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.Markdown, - title: 'Note', - description: - 'Note template for public or private notes. This is in markdown.', - }) - @Column({ - type: ColumnType.Markdown, - nullable: false, - unique: false, - }) - public note?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceNoteTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceNoteTemplate, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.Markdown, + title: "Note", + description: + "Note template for public or private notes. This is in markdown.", + }) + @Column({ + type: ColumnType.Markdown, + nullable: false, + unique: false, + }) + public note?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceNoteTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceNoteTemplate, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Name of the Incident Template', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public templateName?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceNoteTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceNoteTemplate, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Name of the Incident Template", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public templateName?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceNoteTemplate, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceNoteTemplate, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - canReadOnRelationQuery: true, - title: 'Template Description', - description: 'Description of the Incident Template', - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public templateDescription?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceNoteTemplate, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceNoteTemplate, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + canReadOnRelationQuery: true, + title: "Template Description", + description: "Description of the Incident Template", + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public templateDescription?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceNoteTemplate, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceNoteTemplate, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceNoteTemplate, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceNoteTemplate, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceNoteTemplate, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceNoteTemplate, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/ScheduledMaintenanceOwnerTeam.ts b/Model/Models/ScheduledMaintenanceOwnerTeam.ts index 1251a402e6..8b6011b5fa 100644 --- a/Model/Models/ScheduledMaintenanceOwnerTeam.ts +++ b/Model/Models/ScheduledMaintenanceOwnerTeam.ts @@ -1,421 +1,418 @@ -import Project from './Project'; -import ScheduledMaintenance from './ScheduledMaintenance'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import ScheduledMaintenance from "./ScheduledMaintenance"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteScheduledMaintenanceOwnerTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceOwnerTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteScheduledMaintenanceOwnerTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceOwnerTeam, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/scheduled-maintenance-owner-team')) +@CrudApiEndpoint(new Route("/scheduled-maintenance-owner-team")) @TableMetadata({ - tableName: 'ScheduledMaintenanceOwnerTeam', - singularName: 'Scheduled Maintenance Team Owner', - pluralName: 'Scheduled Maintenance Team Owners', - icon: IconProp.Signal, - tableDescription: - 'Add teams as owners to your Scheduled Maintenance event.', + tableName: "ScheduledMaintenanceOwnerTeam", + singularName: "Scheduled Maintenance Team Owner", + pluralName: "Scheduled Maintenance Team Owners", + icon: IconProp.Signal, + tableDescription: "Add teams as owners to your Scheduled Maintenance event.", }) @Entity({ - name: 'ScheduledMaintenanceOwnerTeam', + name: "ScheduledMaintenanceOwnerTeam", }) export default class ScheduledMaintenanceOwnerTeam extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: - 'Team that is the owner. All users in this team will receive notifications. ', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: + "Team that is the owner. All users in this team will receive notifications. ", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Team ID', - description: 'ID of your OneUptime Team in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Team ID", + description: "ID of your OneUptime Team in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'scheduledMaintenanceId', - type: TableColumnType.Entity, - modelType: ScheduledMaintenance, - title: 'ScheduledMaintenance', - description: - 'Relation to ScheduledMaintenance Resource in which this object belongs', - }) - @ManyToOne( - () => { - return ScheduledMaintenance; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'scheduledMaintenanceId' }) - public scheduledMaintenance?: ScheduledMaintenance = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "scheduledMaintenanceId", + type: TableColumnType.Entity, + modelType: ScheduledMaintenance, + title: "ScheduledMaintenance", + description: + "Relation to ScheduledMaintenance Resource in which this object belongs", + }) + @ManyToOne( + () => { + return ScheduledMaintenance; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "scheduledMaintenanceId" }) + public scheduledMaintenance?: ScheduledMaintenance = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'ScheduledMaintenance ID', - description: - 'ID of your OneUptime ScheduledMaintenance in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public scheduledMaintenanceId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "ScheduledMaintenance ID", + description: + "ID of your OneUptime ScheduledMaintenance in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public scheduledMaintenanceId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/ScheduledMaintenanceOwnerUser.ts b/Model/Models/ScheduledMaintenanceOwnerUser.ts index d5b728a934..23fb56d23e 100644 --- a/Model/Models/ScheduledMaintenanceOwnerUser.ts +++ b/Model/Models/ScheduledMaintenanceOwnerUser.ts @@ -1,420 +1,417 @@ -import Project from './Project'; -import ScheduledMaintenance from './ScheduledMaintenance'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import ScheduledMaintenance from "./ScheduledMaintenance"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteScheduledMaintenanceOwnerUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceOwnerUser, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteScheduledMaintenanceOwnerUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceOwnerUser, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/scheduled-maintenance-owner-user')) +@CrudApiEndpoint(new Route("/scheduled-maintenance-owner-user")) @TableMetadata({ - tableName: 'ScheduledMaintenanceOwnerUser', - singularName: 'Scheduled Maintenance User Owner', - pluralName: 'Scheduled Maintenance User Owners', - icon: IconProp.Signal, - tableDescription: - 'Add users as owners to your scheduled maintenance event.', + tableName: "ScheduledMaintenanceOwnerUser", + singularName: "Scheduled Maintenance User Owner", + pluralName: "Scheduled Maintenance User Owners", + icon: IconProp.Signal, + tableDescription: "Add users as owners to your scheduled maintenance event.", }) @Entity({ - name: 'ScheduledMaintenanceOwnerUser', + name: "ScheduledMaintenanceOwnerUser", }) export default class ScheduledMaintenanceOwnerUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: - 'User that is the owner. This user will receive notifications. ', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: + "User that is the owner. This user will receive notifications. ", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'User ID', - description: 'ID of your OneUptime User in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "User ID", + description: "ID of your OneUptime User in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'scheduledMaintenanceId', - type: TableColumnType.Entity, - modelType: ScheduledMaintenance, - title: 'ScheduledMaintenance', - description: - 'Relation to ScheduledMaintenance Resource in which this object belongs', - }) - @ManyToOne( - () => { - return ScheduledMaintenance; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'scheduledMaintenanceId' }) - public scheduledMaintenance?: ScheduledMaintenance = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "scheduledMaintenanceId", + type: TableColumnType.Entity, + modelType: ScheduledMaintenance, + title: "ScheduledMaintenance", + description: + "Relation to ScheduledMaintenance Resource in which this object belongs", + }) + @ManyToOne( + () => { + return ScheduledMaintenance; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "scheduledMaintenanceId" }) + public scheduledMaintenance?: ScheduledMaintenance = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'ScheduledMaintenance ID', - description: - 'ID of your OneUptime ScheduledMaintenance in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public scheduledMaintenanceId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "ScheduledMaintenance ID", + description: + "ID of your OneUptime ScheduledMaintenance in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public scheduledMaintenanceId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/ScheduledMaintenancePublicNote.ts b/Model/Models/ScheduledMaintenancePublicNote.ts index 3cecc59d75..ff6832e58f 100644 --- a/Model/Models/ScheduledMaintenancePublicNote.ts +++ b/Model/Models/ScheduledMaintenancePublicNote.ts @@ -1,455 +1,454 @@ -import Project from './Project'; -import ScheduledMaintenance from './ScheduledMaintenance'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import ScheduledMaintenance from "./ScheduledMaintenance"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('scheduledMaintenance') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("scheduledMaintenance") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteScheduledMaintenancePublicNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenancePublicNote, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteScheduledMaintenancePublicNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenancePublicNote, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/scheduled-maintenance-public-note')) +@CrudApiEndpoint(new Route("/scheduled-maintenance-public-note")) @Entity({ - name: 'ScheduledMaintenancePublicNote', + name: "ScheduledMaintenancePublicNote", }) @TableMetadata({ - tableName: 'ScheduledMaintenancePublicNote', - singularName: 'Scheduled Event Public Note', - pluralName: 'Scheduled Event Public Notes', - icon: IconProp.Public, - tableDescription: 'Manage public notes for your scheduled event', + tableName: "ScheduledMaintenancePublicNote", + singularName: "Scheduled Event Public Note", + pluralName: "Scheduled Event Public Notes", + icon: IconProp.Public, + tableDescription: "Manage public notes for your scheduled event", }) export default class ScheduledMaintenancePublicNote extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'scheduledMaintenanceId', - type: TableColumnType.Entity, - modelType: ScheduledMaintenance, - title: 'Scheduled Maintenance', - description: - 'Relation to Scheduled Maintenance Event this resource belongs to', - }) - @ManyToOne( - () => { - return ScheduledMaintenance; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'scheduledMaintenanceId' }) - public scheduledMaintenance?: ScheduledMaintenance = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "scheduledMaintenanceId", + type: TableColumnType.Entity, + modelType: ScheduledMaintenance, + title: "Scheduled Maintenance", + description: + "Relation to Scheduled Maintenance Event this resource belongs to", + }) + @ManyToOne( + () => { + return ScheduledMaintenance; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "scheduledMaintenanceId" }) + public scheduledMaintenance?: ScheduledMaintenance = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Scheduled Maintenance ID', - description: 'ID of Scheduled Maintenance this resource belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public scheduledMaintenanceId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Scheduled Maintenance ID", + description: "ID of Scheduled Maintenance this resource belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public scheduledMaintenanceId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenancePublicNote, - ], - }) - @TableColumn({ - type: TableColumnType.Markdown, - title: 'Note', - description: 'Notes in markdown', - }) - @Column({ - type: ColumnType.Markdown, - nullable: false, - unique: false, - }) - public note?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenancePublicNote, + ], + }) + @TableColumn({ + type: TableColumnType.Markdown, + title: "Note", + description: "Notes in markdown", + }) + @Column({ + type: ColumnType.Markdown, + nullable: false, + unique: false, + }) + public note?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Are subscribers notified?', - description: 'Are subscribers notified about this note?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isStatusPageSubscribersNotifiedOnNoteCreated?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Are subscribers notified?", + description: "Are subscribers notified about this note?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isStatusPageSubscribersNotifiedOnNoteCreated?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should subscribers be notified?', - description: 'Should subscribers be notified about this note?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public shouldStatusPageSubscribersBeNotifiedOnNoteCreated?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should subscribers be notified?", + description: "Should subscribers be notified about this note?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public shouldStatusPageSubscribersBeNotifiedOnNoteCreated?: boolean = + undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenancePublicNote, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenancePublicNote, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenancePublicNote, - ], - }) - @TableColumn({ - title: 'Note Posted At', - description: 'Date and time when the note was posted', - type: TableColumnType.Date, - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public postedAt?: Date = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenancePublicNote, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenancePublicNote, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenancePublicNote, + ], + }) + @TableColumn({ + title: "Note Posted At", + description: "Date and time when the note was posted", + type: TableColumnType.Date, + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public postedAt?: Date = undefined; } diff --git a/Model/Models/ScheduledMaintenanceState.ts b/Model/Models/ScheduledMaintenanceState.ts index af1a471019..caaae65055 100644 --- a/Model/Models/ScheduledMaintenanceState.ts +++ b/Model/Models/ScheduledMaintenanceState.ts @@ -1,561 +1,559 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import Color from 'Common/Types/Color'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import Color from "Common/Types/Color"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteScheduledMaintenanceState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceState, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteScheduledMaintenanceState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceState, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/scheduled-maintenance-state')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/scheduled-maintenance-state")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'ScheduledMaintenanceState', - singularName: 'Scheduled Maintenance State', - pluralName: 'Scheduled Maintenance States', - icon: IconProp.ArrowCircleRight, - tableDescription: - 'Manage different scheduled maintenance state to your project (Scheduled, Ongoing, Completed for example)', + tableName: "ScheduledMaintenanceState", + singularName: "Scheduled Maintenance State", + pluralName: "Scheduled Maintenance States", + icon: IconProp.ArrowCircleRight, + tableDescription: + "Manage different scheduled maintenance state to your project (Scheduled, Ongoing, Completed for example)", }) @Entity({ - name: 'ScheduledMaintenanceState', + name: "ScheduledMaintenanceState", }) export default class ScheduledMaintenanceState extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceState, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceState, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceState, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceState, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceState, - ], - }) - @TableColumn({ - title: 'Color', - required: true, - unique: false, - type: TableColumnType.Color, - canReadOnRelationQuery: true, - description: 'Color of this resource in Hex (#32a852 for example)', - }) - @Column({ - type: ColumnType.Color, - length: ColumnLength.Color, - unique: false, - nullable: false, - transformer: Color.getDatabaseTransformer(), - }) - public color?: Color = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceState, + ], + }) + @TableColumn({ + title: "Color", + required: true, + unique: false, + type: TableColumnType.Color, + canReadOnRelationQuery: true, + description: "Color of this resource in Hex (#32a852 for example)", + }) + @Column({ + type: ColumnType.Color, + length: ColumnLength.Color, + unique: false, + nullable: false, + transformer: Color.getDatabaseTransformer(), + }) + public color?: Color = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceState, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Scheduled State', - description: 'Is this state a scheduled state?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isScheduledState?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceState, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Scheduled State", + description: "Is this state a scheduled state?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isScheduledState?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceState, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Ongoing State', - description: 'Is this state a ongoing state?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isOngoingState?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceState, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Ongoing State", + description: "Is this state a ongoing state?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isOngoingState?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceState, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Ended State', - description: 'Is this state a ended state?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isEndedState?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceState, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Ended State", + description: "Is this state a ended state?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isEndedState?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceState, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Resolved State', - description: 'Is this state a resolved state?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isResolvedState?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceState, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Resolved State", + description: "Is this state a resolved state?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isResolvedState?: boolean = undefined; - @UniqueColumnBy('projectId') - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceState, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceState, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceState, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.SmallNumber, - canReadOnRelationQuery: true, - title: 'Order', - description: 'Order / Priority of this resource', - }) - @Column({ - type: ColumnType.SmallNumber, - }) - public order?: number = undefined; + @UniqueColumnBy("projectId") + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceState, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceState, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceState, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.SmallNumber, + canReadOnRelationQuery: true, + title: "Order", + description: "Order / Priority of this resource", + }) + @Column({ + type: ColumnType.SmallNumber, + }) + public order?: number = undefined; } diff --git a/Model/Models/ScheduledMaintenanceStateTimeline.ts b/Model/Models/ScheduledMaintenanceStateTimeline.ts index a9492347e6..839109aaba 100644 --- a/Model/Models/ScheduledMaintenanceStateTimeline.ts +++ b/Model/Models/ScheduledMaintenanceStateTimeline.ts @@ -1,530 +1,527 @@ -import Project from './Project'; -import ScheduledMaintenance from './ScheduledMaintenance'; -import ScheduledMaintenanceState from './ScheduledMaintenanceState'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import ScheduledMaintenance from "./ScheduledMaintenance"; +import ScheduledMaintenanceState from "./ScheduledMaintenanceState"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('scheduledMaintenance') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("scheduledMaintenance") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteScheduledMaintenanceStateTimeline, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceStateTimeline, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteScheduledMaintenanceStateTimeline, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceStateTimeline, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/scheduled-maintenance-state-timeline')) +@CrudApiEndpoint(new Route("/scheduled-maintenance-state-timeline")) @Entity({ - name: 'ScheduledMaintenanceStateTimeline', + name: "ScheduledMaintenanceStateTimeline", }) @TableMetadata({ - tableName: 'ScheduledMaintenanceStateTimeline', - icon: IconProp.List, - singularName: 'Scheduled Maintenance State Timeline', - pluralName: 'Scheduled Maintenance State Timelines', - tableDescription: 'Change state of your scheduled maintenance event.', + tableName: "ScheduledMaintenanceStateTimeline", + icon: IconProp.List, + singularName: "Scheduled Maintenance State Timeline", + pluralName: "Scheduled Maintenance State Timelines", + tableDescription: "Change state of your scheduled maintenance event.", }) export default class ScheduledMaintenanceStateTimeline extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'scheduledMaintenanceId', - type: TableColumnType.Entity, - modelType: ScheduledMaintenance, - title: 'Scheduled Maintenance', - description: - 'Relation to Scheduled Maintenance Event this resource belongs to', - }) - @ManyToOne( - () => { - return ScheduledMaintenance; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'scheduledMaintenanceId' }) - public scheduledMaintenance?: ScheduledMaintenance = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "scheduledMaintenanceId", + type: TableColumnType.Entity, + modelType: ScheduledMaintenance, + title: "Scheduled Maintenance", + description: + "Relation to Scheduled Maintenance Event this resource belongs to", + }) + @ManyToOne( + () => { + return ScheduledMaintenance; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "scheduledMaintenanceId" }) + public scheduledMaintenance?: ScheduledMaintenance = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Scheduled Maintenance ID', - description: 'ID of Scheduled Maintenance this resource belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public scheduledMaintenanceId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Scheduled Maintenance ID", + description: "ID of Scheduled Maintenance this resource belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public scheduledMaintenanceId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceStateTimeline, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'scheduledMaintenanceStateId', - type: TableColumnType.Entity, - modelType: ScheduledMaintenanceState, - title: 'Scheduled Maintenance State', - description: - 'Which state does this event belongs to?. Relation to Scheduled Maintenance State', - }) - @ManyToOne( - () => { - return ScheduledMaintenanceState; - }, - { - eager: false, - nullable: true, - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'scheduledMaintenanceStateId' }) - public scheduledMaintenanceState?: ScheduledMaintenanceState = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceStateTimeline, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "scheduledMaintenanceStateId", + type: TableColumnType.Entity, + modelType: ScheduledMaintenanceState, + title: "Scheduled Maintenance State", + description: + "Which state does this event belongs to?. Relation to Scheduled Maintenance State", + }) + @ManyToOne( + () => { + return ScheduledMaintenanceState; + }, + { + eager: false, + nullable: true, + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "scheduledMaintenanceStateId" }) + public scheduledMaintenanceState?: ScheduledMaintenanceState = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditScheduledMaintenanceStateTimeline, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Scheduled Maintenance State ID', - description: - ' Scheduled Maintenance State ID. Which state does this event belongs to?', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public scheduledMaintenanceStateId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditScheduledMaintenanceStateTimeline, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Scheduled Maintenance State ID", + description: + " Scheduled Maintenance State ID. Which state does this event belongs to?", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public scheduledMaintenanceStateId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Are subscribers notified?', - description: - 'Are subscribers notified about this incident state change?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isStatusPageSubscribersNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Are subscribers notified?", + description: "Are subscribers notified about this incident state change?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isStatusPageSubscribersNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should subscribers be notified?', - description: 'Should subscribers be notified about this state change?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public shouldStatusPageSubscribersBeNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should subscribers be notified?", + description: "Should subscribers be notified about this state change?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public shouldStatusPageSubscribersBeNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of state change?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of state change?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Date, - title: 'Ends At', - description: 'When did this status change end?', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public endsAt?: Date = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Date, + title: "Ends At", + description: "When did this status change end?", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public endsAt?: Date = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateScheduledMaintenanceStateTimeline, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadScheduledMaintenanceStateTimeline, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Date, - title: 'Starts At', - description: 'When did this status change start?', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public startsAt?: Date = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateScheduledMaintenanceStateTimeline, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadScheduledMaintenanceStateTimeline, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Date, + title: "Starts At", + description: "When did this status change start?", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public startsAt?: Date = undefined; } diff --git a/Model/Models/ServiceCatalog.ts b/Model/Models/ServiceCatalog.ts index 4812a5aa8f..6d8488e6af 100644 --- a/Model/Models/ServiceCatalog.ts +++ b/Model/Models/ServiceCatalog.ts @@ -1,498 +1,496 @@ -import Label from './Label'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import Color from 'Common/Types/Color'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import ServiceLanguage from 'Common/Types/ServiceCatalog/ServiceLanguage'; +import Label from "./Label"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import Color from "Common/Types/Color"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import ServiceLanguage from "Common/Types/ServiceCatalog/ServiceLanguage"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; -@AccessControlColumn('labels') +@AccessControlColumn("labels") @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteServiceCatalog, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceCatalog, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteServiceCatalog, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceCatalog, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/service-catalog')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/service-catalog")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'ServiceCatalog', - singularName: 'Service', - pluralName: 'Services', - icon: IconProp.SquareStack, - tableDescription: - 'Service Catalog is a collection of services that you have in your organization. It can be a collection of services that you are monitoring or services that you are providing to your customers. It can be anything that you want to keep track of.', + tableName: "ServiceCatalog", + singularName: "Service", + pluralName: "Services", + icon: IconProp.SquareStack, + tableDescription: + "Service Catalog is a collection of services that you have in your organization. It can be a collection of services that you are monitoring or services that you are providing to your customers. It can be anything that you want to keep track of.", }) @Entity({ - name: 'ServiceCatalog', + name: "ServiceCatalog", }) export default class ServiceCatalog extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceCatalog, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceCatalog, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceCatalog, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceCatalog, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceCatalog, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'ServiceCatalogLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'serviceCatalogId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceCatalog, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "ServiceCatalogLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "serviceCatalogId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceCatalog, - ], - }) - @TableColumn({ - type: TableColumnType.Color, - title: 'Service Color', - description: 'Color for this service', - }) - @Column({ - type: ColumnType.Color, - nullable: true, - unique: false, - transformer: Color.getDatabaseTransformer(), - }) - public serviceColor?: Color = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceCatalog, + ], + }) + @TableColumn({ + type: TableColumnType.Color, + title: "Service Color", + description: "Color for this service", + }) + @Column({ + type: ColumnType.Color, + nullable: true, + unique: false, + transformer: Color.getDatabaseTransformer(), + }) + public serviceColor?: Color = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceCatalog, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceCatalog, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Service Language', - description: 'Language in which this service is written', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public serviceLanguage?: ServiceLanguage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceCatalog, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceCatalog, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Service Language", + description: "Language in which this service is written", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public serviceLanguage?: ServiceLanguage = undefined; } diff --git a/Model/Models/ServiceCatalogOwnerTeam.ts b/Model/Models/ServiceCatalogOwnerTeam.ts index 3e3f81dac4..2625754200 100644 --- a/Model/Models/ServiceCatalogOwnerTeam.ts +++ b/Model/Models/ServiceCatalogOwnerTeam.ts @@ -1,398 +1,396 @@ -import Project from './Project'; -import ServiceCatalog from './ServiceCatalog'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import ServiceCatalog from "./ServiceCatalog"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@CanAccessIfCanReadOn('serviceCatalog') +@CanAccessIfCanReadOn("serviceCatalog") @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteServiceCatalogOwnerTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditServiceCatalogOwnerTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteServiceCatalogOwnerTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditServiceCatalogOwnerTeam, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/service-catalog-owner-team')) +@CrudApiEndpoint(new Route("/service-catalog-owner-team")) @TableMetadata({ - tableName: 'ServiceCatalogOwnerTeam', - singularName: 'Service Catalog Team Owner', - pluralName: 'Service Catalog Team Owners', - icon: IconProp.Signal, - tableDescription: 'Add teams as owners to your Service Catalog.', + tableName: "ServiceCatalogOwnerTeam", + singularName: "Service Catalog Team Owner", + pluralName: "Service Catalog Team Owners", + icon: IconProp.Signal, + tableDescription: "Add teams as owners to your Service Catalog.", }) @Entity({ - name: 'ServiceCatalogOwnerTeam', + name: "ServiceCatalogOwnerTeam", }) export default class ServiceCatalogOwnerTeam extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: - 'Team that is the owner. All users in this team will receive notifications. ', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: + "Team that is the owner. All users in this team will receive notifications. ", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Team ID', - description: 'ID of your OneUptime Team in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Team ID", + description: "ID of your OneUptime Team in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'serviceCatalogId', - type: TableColumnType.Entity, - modelType: ServiceCatalog, - title: 'Service Catalog', - description: - 'Relation to Service Catalog Resource in which this object belongs', - }) - @ManyToOne( - () => { - return ServiceCatalog; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'serviceCatalogId' }) - public serviceCatalog?: ServiceCatalog = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "serviceCatalogId", + type: TableColumnType.Entity, + modelType: ServiceCatalog, + title: "Service Catalog", + description: + "Relation to Service Catalog Resource in which this object belongs", + }) + @ManyToOne( + () => { + return ServiceCatalog; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "serviceCatalogId" }) + public serviceCatalog?: ServiceCatalog = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Service Catalog ID', - description: - 'ID of your OneUptime Service Catalog in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public serviceCatalogId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Service Catalog ID", + description: + "ID of your OneUptime Service Catalog in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public serviceCatalogId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerTeam, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerTeam, + ], + 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; } diff --git a/Model/Models/ServiceCatalogOwnerUser.ts b/Model/Models/ServiceCatalogOwnerUser.ts index 594b821916..bf51b58ff9 100644 --- a/Model/Models/ServiceCatalogOwnerUser.ts +++ b/Model/Models/ServiceCatalogOwnerUser.ts @@ -1,397 +1,395 @@ -import Project from './Project'; -import ServiceCatalog from './ServiceCatalog'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import ServiceCatalog from "./ServiceCatalog"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@CanAccessIfCanReadOn('serviceCatalog') +@CanAccessIfCanReadOn("serviceCatalog") @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteServiceCatalogOwnerUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditServiceCatalogOwnerUser, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteServiceCatalogOwnerUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditServiceCatalogOwnerUser, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/service-catalog-owner-user')) +@CrudApiEndpoint(new Route("/service-catalog-owner-user")) @TableMetadata({ - tableName: 'ServiceCatalogOwnerUser', - singularName: 'Service Catalog User Owner', - pluralName: 'Service Catalog User Owners', - icon: IconProp.Signal, - tableDescription: 'Add users as owners to your Service Catalog.', + tableName: "ServiceCatalogOwnerUser", + singularName: "Service Catalog User Owner", + pluralName: "Service Catalog User Owners", + icon: IconProp.Signal, + tableDescription: "Add users as owners to your Service Catalog.", }) @Entity({ - name: 'ServiceCatalogOwnerUser', + name: "ServiceCatalogOwnerUser", }) export default class ServiceCatalogOwnerUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: - 'User that is the owner. This user will receive notifications. ', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: + "User that is the owner. This user will receive notifications. ", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'User ID', - description: 'ID of your OneUptime User in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "User ID", + description: "ID of your OneUptime User in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'serviceCatalogId', - type: TableColumnType.Entity, - modelType: ServiceCatalog, - title: 'Service Catalog', - description: - 'Relation to Service Catalog Resource in which this object belongs', - }) - @ManyToOne( - () => { - return ServiceCatalog; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'serviceCatalogId' }) - public serviceCatalog?: ServiceCatalog = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "serviceCatalogId", + type: TableColumnType.Entity, + modelType: ServiceCatalog, + title: "Service Catalog", + description: + "Relation to Service Catalog Resource in which this object belongs", + }) + @ManyToOne( + () => { + return ServiceCatalog; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "serviceCatalogId" }) + public serviceCatalog?: ServiceCatalog = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Service Catalog ID', - description: - 'ID of your OneUptime Service Catalog in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public serviceCatalogId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Service Catalog ID", + description: + "ID of your OneUptime Service Catalog in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public serviceCatalogId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceCatalogOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceCatalogOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadServiceCatalogOwnerUser, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadServiceCatalogOwnerUser, + ], + 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; } diff --git a/Model/Models/ServiceRepository.ts b/Model/Models/ServiceRepository.ts index c54518a722..a0578145d0 100644 --- a/Model/Models/ServiceRepository.ts +++ b/Model/Models/ServiceRepository.ts @@ -1,546 +1,542 @@ -import CodeRepository from './CodeRepository'; -import Project from './Project'; -import ServiceCatalog from './ServiceCatalog'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import CodeRepository from "./CodeRepository"; +import Project from "./Project"; +import ServiceCatalog from "./ServiceCatalog"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@CanAccessIfCanReadOn('codeRepositoryId') +@CanAccessIfCanReadOn("codeRepositoryId") @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteServiceRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceRepository, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteServiceRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceRepository, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/servic-repository')) +@CrudApiEndpoint(new Route("/servic-repository")) @TableMetadata({ - tableName: 'ServiceRepository', - singularName: 'Service', - pluralName: 'Services', - icon: IconProp.SquareStack, - tableDescription: - 'Add services to your code repository to categorize and manage them easily. This will help copilot understand and generate code.', + tableName: "ServiceRepository", + singularName: "Service", + pluralName: "Services", + icon: IconProp.SquareStack, + tableDescription: + "Add services to your code repository to categorize and manage them easily. This will help copilot understand and generate code.", }) @Entity({ - name: 'ServiceRepository', + name: "ServiceRepository", }) export default class ServiceRepository extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceRepository, - ], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.LongText, - canReadOnRelationQuery: true, - title: 'Path in Repository', - description: - 'Path in your code repository where this service is located', - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - default: '/', - }) - public servicePathInRepository?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceRepository, + ], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.LongText, + canReadOnRelationQuery: true, + title: "Path in Repository", + description: "Path in your code repository where this service is located", + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + default: "/", + }) + public servicePathInRepository?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceRepository, - ], - }) - @TableColumn({ - required: false, - isDefaultValueColumn: true, - type: TableColumnType.Number, - canReadOnRelationQuery: true, - title: 'Limit Number of Open Pull Requests Count', - description: - 'Limit Number of Open Pull Requests Count for this service', - }) - @Column({ - nullable: true, - type: ColumnType.Number, - default: 3, - }) - public limitNumberOfOpenPullRequestsCount?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceRepository, + ], + }) + @TableColumn({ + required: false, + isDefaultValueColumn: true, + type: TableColumnType.Number, + canReadOnRelationQuery: true, + title: "Limit Number of Open Pull Requests Count", + description: "Limit Number of Open Pull Requests Count for this service", + }) + @Column({ + nullable: true, + type: ColumnType.Number, + default: 3, + }) + public limitNumberOfOpenPullRequestsCount?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceRepository, - ], - }) - @TableColumn({ - required: true, - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Enable Pull Requests', - description: - 'Copilot will automatically improve your code by creating pull requests for this service', - }) - @Column({ - nullable: false, - type: ColumnType.Boolean, - default: true, - }) - public enablePullRequests?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceRepository, + ], + }) + @TableColumn({ + required: true, + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Enable Pull Requests", + description: + "Copilot will automatically improve your code by creating pull requests for this service", + }) + @Column({ + nullable: false, + type: ColumnType.Boolean, + default: true, + }) + public enablePullRequests?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceRepository, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'codeRepositoryId', - type: TableColumnType.Entity, - modelType: CodeRepository, - title: 'Code Repository', - description: - 'Relation to CodeRepository Resource in which this object belongs', - }) - @ManyToOne( - () => { - return CodeRepository; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'codeRepositoryId' }) - public codeRepository?: CodeRepository = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceRepository, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "codeRepositoryId", + type: TableColumnType.Entity, + modelType: CodeRepository, + title: "Code Repository", + description: + "Relation to CodeRepository Resource in which this object belongs", + }) + @ManyToOne( + () => { + return CodeRepository; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "codeRepositoryId" }) + public codeRepository?: CodeRepository = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceRepository, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Code Repository ID', - description: - 'ID of your OneUptime Code Repository in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public codeRepositoryId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceRepository, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Code Repository ID", + description: + "ID of your OneUptime Code Repository in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public codeRepositoryId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceRepository, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'serviceCatalogId', - type: TableColumnType.Entity, - modelType: ServiceCatalog, - title: 'Service Catalog', - description: - 'Relation to Service Catalog Resource in which this object belongs', - }) - @ManyToOne( - () => { - return ServiceCatalog; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'serviceCatalogId' }) - public serviceCatalog?: ServiceCatalog = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceRepository, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "serviceCatalogId", + type: TableColumnType.Entity, + modelType: ServiceCatalog, + title: "Service Catalog", + description: + "Relation to Service Catalog Resource in which this object belongs", + }) + @ManyToOne( + () => { + return ServiceCatalog; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "serviceCatalogId" }) + public serviceCatalog?: ServiceCatalog = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateServiceRepository, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadServiceRepository, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditServiceRepository, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Service Catalog ID', - description: - 'ID of your OneUptime ServiceCatalog in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public serviceCatalogId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateServiceRepository, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadServiceRepository, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditServiceRepository, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Service Catalog ID", + description: + "ID of your OneUptime ServiceCatalog in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public serviceCatalogId?: ObjectID = undefined; } diff --git a/Model/Models/ShortLink.ts b/Model/Models/ShortLink.ts index dec582966a..f9e890fc93 100644 --- a/Model/Models/ShortLink.ts +++ b/Model/Models/ShortLink.ts @@ -1,119 +1,119 @@ -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableAccessControl({ - create: [], - read: [], - delete: [], - update: [], + create: [], + read: [], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/short-link')) +@CrudApiEndpoint(new Route("/short-link")) @Entity({ - name: 'ShortLink', + name: "ShortLink", }) @TableMetadata({ - tableName: 'ShortLink', - singularName: 'Short Link', - pluralName: 'Short Links', - icon: IconProp.Link, - tableDescription: - 'Short links are used to redirect users to a specific long link in OneUptime.', + tableName: "ShortLink", + singularName: "Short Link", + pluralName: "Short Links", + icon: IconProp.Link, + tableDescription: + "Short links are used to redirect users to a specific long link in OneUptime.", }) export default class ShortLink extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Short Link ID', - description: 'Random ID for the short link', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public shortId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Short Link ID", + description: "Random ID for the short link", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public shortId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongURL, - title: 'Long URL', - description: 'Long URL to redirect to', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.LongURL, - transformer: URL.getDatabaseTransformer(), - }) - public link?: URL = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongURL, + title: "Long URL", + description: "Long URL to redirect to", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.LongURL, + transformer: URL.getDatabaseTransformer(), + }) + public link?: URL = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/SmsLog.ts b/Model/Models/SmsLog.ts index f8fa23a13d..1fc069051b 100644 --- a/Model/Models/SmsLog.ts +++ b/Model/Models/SmsLog.ts @@ -1,305 +1,303 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Phone from 'Common/Types/Phone'; -import SmsStatus from 'Common/Types/SmsStatus'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Phone from "Common/Types/Phone"; +import SmsStatus from "Common/Types/SmsStatus"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadSmsLog, - ], - delete: [], - update: [], + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadSmsLog, + ], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/sms-log')) +@CrudApiEndpoint(new Route("/sms-log")) @Entity({ - name: 'SmsLog', + name: "SmsLog", }) @EnableWorkflow({ - create: true, - delete: false, - update: false, - read: true, + create: true, + delete: false, + update: false, + read: true, }) @TableMetadata({ - tableName: 'SmsLog', - singularName: 'SMS Log', - pluralName: 'SMS Logs', - icon: IconProp.SMS, - tableDescription: - 'Logs of all the SMS sent out to all users and subscribers for this project.', + tableName: "SmsLog", + singularName: "SMS Log", + pluralName: "SMS Logs", + icon: IconProp.SMS, + tableDescription: + "Logs of all the SMS sent out to all users and subscribers for this project.", }) export default class SmsLog extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadSmsLog, - ], - 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.ReadSmsLog, + ], + 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.ReadSmsLog, - ], - 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.ReadSmsLog, + ], + 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.ReadSmsLog, - ], - update: [], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.Phone, - title: 'To Number', - description: 'Phone Number SMS was sent to', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.Phone, - length: ColumnLength.Phone, - transformer: Phone.getDatabaseTransformer(), - }) - public toNumber?: Phone = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadSmsLog, + ], + update: [], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.Phone, + title: "To Number", + description: "Phone Number SMS was sent to", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.Phone, + length: ColumnLength.Phone, + transformer: Phone.getDatabaseTransformer(), + }) + public toNumber?: Phone = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadSmsLog, - ], - update: [], - }) - @Index() - @TableColumn({ - required: false, // false because we may not have a from number if you dont have a twilio config. - type: TableColumnType.Phone, - title: 'From Number', - description: 'Phone Number SMS was sent from', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: true, - type: ColumnType.Phone, - length: ColumnLength.Phone, - transformer: Phone.getDatabaseTransformer(), - }) - public fromNumber?: Phone = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadSmsLog, + ], + update: [], + }) + @Index() + @TableColumn({ + required: false, // false because we may not have a from number if you dont have a twilio config. + type: TableColumnType.Phone, + title: "From Number", + description: "Phone Number SMS was sent from", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: true, + type: ColumnType.Phone, + length: ColumnLength.Phone, + transformer: Phone.getDatabaseTransformer(), + }) + public fromNumber?: Phone = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadSmsLog, - ], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.VeryLongText, - title: 'SMS Text', - description: 'Text content of the message', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: true, - type: ColumnType.VeryLongText, - }) - public smsText?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadSmsLog, + ], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.VeryLongText, + title: "SMS Text", + description: "Text content of the message", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: true, + type: ColumnType.VeryLongText, + }) + public smsText?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadSmsLog, - ], - 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.ReadSmsLog, + ], + 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.ReadSmsLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Status of the SMS', - description: 'Status of the SMS sent', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public status?: SmsStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadSmsLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Status of the SMS", + description: "Status of the SMS sent", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public status?: SmsStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadSmsLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.Number, - title: 'SMS Cost', - description: 'SMS Cost in USD Cents', - canReadOnRelationQuery: false, - isDefaultValueColumn: true, - }) - @Column({ - nullable: false, - default: 0, - type: ColumnType.Number, - }) - public smsCostInUSDCents?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadSmsLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.Number, + title: "SMS Cost", + description: "SMS Cost in USD Cents", + canReadOnRelationQuery: false, + isDefaultValueColumn: true, + }) + @Column({ + nullable: false, + default: 0, + type: ColumnType.Number, + }) + public smsCostInUSDCents?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/StatusPage.ts b/Model/Models/StatusPage.ts index 6d3a991e14..da3f5b6051 100755 --- a/Model/Models/StatusPage.ts +++ b/Model/Models/StatusPage.ts @@ -1,1650 +1,1648 @@ -import File from './File'; -import Label from './Label'; -import MonitorStatus from './MonitorStatus'; -import Project from './Project'; -import ProjectCallSMSConfig from './ProjectCallSMSConfig'; -import ProjectSmtpConfig from './ProjectSmtpConfig'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import Color from 'Common/Types/Color'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import ColumnBillingAccessControl from 'Common/Types/Database/AccessControl/ColumnBillingAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import File from "./File"; +import Label from "./Label"; +import MonitorStatus from "./MonitorStatus"; +import Project from "./Project"; +import ProjectCallSMSConfig from "./ProjectCallSMSConfig"; +import ProjectSmtpConfig from "./ProjectSmtpConfig"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import Color from "Common/Types/Color"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import ColumnBillingAccessControl from "Common/Types/Database/AccessControl/ColumnBillingAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() -@AccessControlColumn('labels') -@TenantColumn('projectId') +@AccessControlColumn("labels") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/status-page")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'StatusPage', + name: "StatusPage", }) @TableMetadata({ - tableName: 'StatusPage', - singularName: 'Status Page', - pluralName: 'Status Pages', - icon: IconProp.CheckCircle, - tableDescription: 'Manage status pages for your project.', + tableName: "StatusPage", + singularName: "Status Page", + pluralName: "Status Pages", + icon: IconProp.CheckCircle, + tableDescription: "Manage status pages for your project.", }) export default class StatusPage extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + 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; - @UniqueColumnBy('projectId') - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name of this object', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @UniqueColumnBy("projectId") + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name of this object", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.ShortText, - title: 'Page Title', - description: 'Title of your Status Page. This is used for SEO.', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public pageTitle?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortText, + title: "Page Title", + description: "Title of your Status Page. This is used for SEO.", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public pageTitle?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Page Description', - description: 'Description of your Status Page. This is used for SEO.', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public pageDescription?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Page Description", + description: "Description of your Status Page. This is used for SEO.", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public pageDescription?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'StatusPageLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'statusPageId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "StatusPageLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "statusPageId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - //// Branding Files. + //// Branding Files. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'faviconFileId', - type: TableColumnType.Entity, - modelType: File, - title: 'Favicon', - description: 'Status Page Favicon', - }) - @ManyToOne( - () => { - return File; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'delete', - } - ) - @JoinColumn({ name: 'faviconFileId' }) - public faviconFile?: File = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "faviconFileId", + type: TableColumnType.Entity, + modelType: File, + title: "Favicon", + description: "Status Page Favicon", + }) + @ManyToOne( + () => { + return File; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "delete", + }, + ) + @JoinColumn({ name: "faviconFileId" }) + public faviconFile?: File = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Favicon', - description: 'Status Page Favicon File ID', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public faviconFileId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Favicon", + description: "Status Page Favicon File ID", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public faviconFileId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'logoFileId', - type: TableColumnType.Entity, - modelType: File, - title: 'Logo', - description: 'Status Page Logo', - }) - @ManyToOne( - () => { - return File; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'delete', - } - ) - @JoinColumn({ name: 'logoFileId' }) - public logoFile?: File = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "logoFileId", + type: TableColumnType.Entity, + modelType: File, + title: "Logo", + description: "Status Page Logo", + }) + @ManyToOne( + () => { + return File; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "delete", + }, + ) + @JoinColumn({ name: "logoFileId" }) + public logoFile?: File = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Logo', - description: 'Status Page Logo File ID', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public logoFileId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Logo", + description: "Status Page Logo File ID", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public logoFileId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'coverImageFileId', - type: TableColumnType.Entity, - modelType: File, - title: 'Cover Image', - description: 'Status Page Cover Image', - }) - @ManyToOne( - () => { - return File; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'delete', - } - ) - @JoinColumn({ name: 'coverImageFileId' }) - public coverImageFile?: File = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "coverImageFileId", + type: TableColumnType.Entity, + modelType: File, + title: "Cover Image", + description: "Status Page Cover Image", + }) + @ManyToOne( + () => { + return File; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "delete", + }, + ) + @JoinColumn({ name: "coverImageFileId" }) + public coverImageFile?: File = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Cover Image', - description: 'Status Page Cover Image ID', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public coverImageFileId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Cover Image", + description: "Status Page Cover Image ID", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public coverImageFileId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.HTML, - title: 'Header HTML', - description: 'Status Page Custom HTML Header', - }) - @Column({ - nullable: true, - type: ColumnType.HTML, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Growth, - create: PlanSelect.Free, - }) - public headerHTML?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.HTML, + title: "Header HTML", + description: "Status Page Custom HTML Header", + }) + @Column({ + nullable: true, + type: ColumnType.HTML, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Growth, + create: PlanSelect.Free, + }) + public headerHTML?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.HTML, - title: 'Footer HTML', - description: 'Status Page Custom HTML Footer', - }) - @Column({ - nullable: true, - type: ColumnType.HTML, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Growth, - create: PlanSelect.Free, - }) - public footerHTML?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.HTML, + title: "Footer HTML", + description: "Status Page Custom HTML Footer", + }) + @Column({ + nullable: true, + type: ColumnType.HTML, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Growth, + create: PlanSelect.Free, + }) + public footerHTML?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.CSS, - title: 'CSS', - description: 'Status Page Custom CSS Header', - }) - @Column({ - nullable: true, - type: ColumnType.CSS, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Growth, - create: PlanSelect.Free, - }) - public customCSS?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.CSS, + title: "CSS", + description: "Status Page Custom CSS Header", + }) + @Column({ + nullable: true, + type: ColumnType.CSS, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Growth, + create: PlanSelect.Free, + }) + public customCSS?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.JavaScript, - title: 'JavaScript', - description: - 'Status Page Custom JavaScript. This runs when the status page is loaded.', - }) - @Column({ - nullable: true, - type: ColumnType.JavaScript, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Growth, - create: PlanSelect.Free, - }) - public customJavaScript?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.JavaScript, + title: "JavaScript", + description: + "Status Page Custom JavaScript. This runs when the status page is loaded.", + }) + @Column({ + nullable: true, + type: ColumnType.JavaScript, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Growth, + create: PlanSelect.Free, + }) + public customJavaScript?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Public Status Page', - description: 'Is this status page public?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Growth, - create: PlanSelect.Free, - }) - public isPublicStatusPage?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Public Status Page", + description: "Is this status page public?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Growth, + create: PlanSelect.Free, + }) + public isPublicStatusPage?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Show Incident Labels', - description: 'Show Incident Labels on Status Page?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Growth, - create: PlanSelect.Free, - }) - public showIncidentLabelsOnStatusPage?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Show Incident Labels", + description: "Show Incident Labels on Status Page?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Growth, + create: PlanSelect.Free, + }) + public showIncidentLabelsOnStatusPage?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Show Scheduled Event Labels', - description: 'Show Scheduled Event Labels on Status Page?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Growth, - create: PlanSelect.Free, - }) - public showScheduledEventLabelsOnStatusPage?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Show Scheduled Event Labels", + description: "Show Scheduled Event Labels on Status Page?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Growth, + create: PlanSelect.Free, + }) + public showScheduledEventLabelsOnStatusPage?: boolean = undefined; - // This column is Deprectaed. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Enable Subscribers', - description: 'Can subscribers subscribe to this Status Page?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public enableSubscribers?: boolean = undefined; + // This column is Deprectaed. + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Enable Subscribers", + description: "Can subscribers subscribe to this Status Page?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public enableSubscribers?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Enable Email Subscribers', - description: 'Can email subscribers subscribe to this Status Page?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public enableEmailSubscribers?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Enable Email Subscribers", + description: "Can email subscribers subscribe to this Status Page?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public enableEmailSubscribers?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Allow Subscribers to Choose Resources', - description: 'Can subscribers choose which resources to subscribe to?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Scale, - create: PlanSelect.Free, - }) - public allowSubscribersToChooseResources?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Allow Subscribers to Choose Resources", + description: "Can subscribers choose which resources to subscribe to?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Scale, + create: PlanSelect.Free, + }) + public allowSubscribersToChooseResources?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Enable SMS Subscribers', - description: 'Can SMS subscribers subscribe to this Status Page?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Growth, - create: PlanSelect.Free, - }) - public enableSmsSubscribers?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Enable SMS Subscribers", + description: "Can SMS subscribers subscribe to this Status Page?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Growth, + create: PlanSelect.Free, + }) + public enableSmsSubscribers?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.ShortText, - title: 'Copyright Text', - description: 'Copyright Text', - }) - @Column({ - type: ColumnType.ShortText, - nullable: true, - }) - public copyrightText?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.ShortText, + title: "Copyright Text", + description: "Copyright Text", + }) + @Column({ + type: ColumnType.ShortText, + nullable: true, + }) + public copyrightText?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - title: 'Custom Fields', - description: 'Custom Fields on this resource.', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public customFields?: JSONObject = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + title: "Custom Fields", + description: "Custom Fields on this resource.", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public customFields?: JSONObject = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - Permission.Public, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.Boolean, - isDefaultValueColumn: true, - description: 'Should SSO be required to login to Private Status Page', - title: 'Require SSO', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public requireSsoForLogin?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + Permission.Public, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.Boolean, + isDefaultValueColumn: true, + description: "Should SSO be required to login to Private Status Page", + title: "Require SSO", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public requireSsoForLogin?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'smtpConfigId', - type: TableColumnType.Entity, - modelType: ProjectSmtpConfig, - title: 'SMTP Config', - description: - 'Relation to SMTP Config Resource which is used to send email to subscribers.', - }) - @ManyToOne( - () => { - return ProjectSmtpConfig; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'smtpConfigId' }) - public smtpConfig?: ProjectSmtpConfig = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "smtpConfigId", + type: TableColumnType.Entity, + modelType: ProjectSmtpConfig, + title: "SMTP Config", + description: + "Relation to SMTP Config Resource which is used to send email to subscribers.", + }) + @ManyToOne( + () => { + return ProjectSmtpConfig; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "smtpConfigId" }) + public smtpConfig?: ProjectSmtpConfig = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'SMTP Config ID', - description: - 'ID of your SMTP Config Resource which is used to send email to subscribers.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public smtpConfigId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "SMTP Config ID", + description: + "ID of your SMTP Config Resource which is used to send email to subscribers.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public smtpConfigId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'callSmsConfigId', - type: TableColumnType.Entity, - modelType: ProjectCallSMSConfig, - title: 'Call/SMS Config', - description: - 'Relation to Call/SMS Config Resource which is used to send SMS to subscribers.', - }) - @ManyToOne( - () => { - return ProjectCallSMSConfig; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'callSmsConfigId' }) - public callSmsConfig?: ProjectCallSMSConfig = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "callSmsConfigId", + type: TableColumnType.Entity, + modelType: ProjectCallSMSConfig, + title: "Call/SMS Config", + description: + "Relation to Call/SMS Config Resource which is used to send SMS to subscribers.", + }) + @ManyToOne( + () => { + return ProjectCallSMSConfig; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "callSmsConfigId" }) + public callSmsConfig?: ProjectCallSMSConfig = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Call/SMS Config ID', - description: - 'ID of your Call/SMS Config Resource which is used to send SMS to subscribers.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public callSmsConfigId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Call/SMS Config ID", + description: + "ID of your Call/SMS Config Resource which is used to send SMS to subscribers.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public callSmsConfigId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified Of Resource Creation?', - description: 'Are owners notified of when this resource is created?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotifiedOfResourceCreation?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified Of Resource Creation?", + description: "Are owners notified of when this resource is created?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotifiedOfResourceCreation?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.Number, - required: true, - isDefaultValueColumn: true, - title: 'Show incident history in days', - description: - 'How many days of incident history should be shown on the status page (in days)?', - }) - @Column({ - type: ColumnType.Number, - nullable: false, - default: 14, - }) - public showIncidentHistoryInDays?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.Number, + required: true, + isDefaultValueColumn: true, + title: "Show incident history in days", + description: + "How many days of incident history should be shown on the status page (in days)?", + }) + @Column({ + type: ColumnType.Number, + nullable: false, + default: 14, + }) + public showIncidentHistoryInDays?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.Number, - required: true, - isDefaultValueColumn: true, - title: 'Show announcement history in days', - description: - 'How many days of announcement history should be shown on the status page (in days)?', - }) - @Column({ - type: ColumnType.Number, - nullable: false, - default: 14, - }) - public showAnnouncementHistoryInDays?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.Number, + required: true, + isDefaultValueColumn: true, + title: "Show announcement history in days", + description: + "How many days of announcement history should be shown on the status page (in days)?", + }) + @Column({ + type: ColumnType.Number, + nullable: false, + default: 14, + }) + public showAnnouncementHistoryInDays?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.Number, - required: true, - isDefaultValueColumn: true, - title: 'Show scheduled event history in days', - description: - 'How many days of scheduled event history should be shown on the status page (in days)?', - }) - @Column({ - type: ColumnType.Number, - nullable: false, - default: 14, - }) - public showScheduledEventHistoryInDays?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.Number, + required: true, + isDefaultValueColumn: true, + title: "Show scheduled event history in days", + description: + "How many days of scheduled event history should be shown on the status page (in days)?", + }) + @Column({ + type: ColumnType.Number, + nullable: false, + default: 14, + }) + public showScheduledEventHistoryInDays?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.Markdown, - required: false, - isDefaultValueColumn: false, - title: 'Overview Page Description', - description: - 'Overview Page description for your status page. This is a markdown field.', - }) - @Column({ - type: ColumnType.Markdown, - nullable: true, - }) - public overviewPageDescription?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.Markdown, + required: false, + isDefaultValueColumn: false, + title: "Overview Page Description", + description: + "Overview Page description for your status page. This is a markdown field.", + }) + @Column({ + type: ColumnType.Markdown, + nullable: true, + }) + public overviewPageDescription?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Hide Powered By OneUptime Branding', - description: 'Hide Powered By OneUptime Branding?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - @ColumnBillingAccessControl({ - read: PlanSelect.Free, - update: PlanSelect.Scale, - create: PlanSelect.Free, - }) - public hidePoweredByOneUptimeBranding?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Hide Powered By OneUptime Branding", + description: "Hide Powered By OneUptime Branding?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + @ColumnBillingAccessControl({ + read: PlanSelect.Free, + update: PlanSelect.Scale, + create: PlanSelect.Free, + }) + public hidePoweredByOneUptimeBranding?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - title: 'Default Bar Color', - required: false, - unique: false, - type: TableColumnType.Color, - canReadOnRelationQuery: true, - description: 'Default color of the bar on the overview page', - }) - @Column({ - type: ColumnType.Color, - length: ColumnLength.Color, - unique: false, - nullable: true, - transformer: Color.getDatabaseTransformer(), - }) - public defaultBarColor?: Color = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + title: "Default Bar Color", + required: false, + unique: false, + type: TableColumnType.Color, + canReadOnRelationQuery: true, + description: "Default color of the bar on the overview page", + }) + @Column({ + type: ColumnType.Color, + length: ColumnLength.Color, + unique: false, + nullable: true, + transformer: Color.getDatabaseTransformer(), + }) + public defaultBarColor?: Color = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectStatusPage, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectStatusPage, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectStatusPage, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: MonitorStatus, - title: 'Downtime Monitor Statuses', - description: - 'List of monitors statuses that are considered as "down" for this status page.', - }) - @ManyToMany( - () => { - return MonitorStatus; - }, - { eager: false } - ) - @JoinTable({ - name: 'StatusPageDownMonitorStatus', - inverseJoinColumn: { - name: 'monitorStatusId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'statusPageId', - referencedColumnName: '_id', - }, - }) - public downtimeMonitorStatuses?: Array<MonitorStatus> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectStatusPage, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectStatusPage, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectStatusPage, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: MonitorStatus, + title: "Downtime Monitor Statuses", + description: + 'List of monitors statuses that are considered as "down" for this status page.', + }) + @ManyToMany( + () => { + return MonitorStatus; + }, + { eager: false }, + ) + @JoinTable({ + name: "StatusPageDownMonitorStatus", + inverseJoinColumn: { + name: "monitorStatusId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "statusPageId", + referencedColumnName: "_id", + }, + }) + public downtimeMonitorStatuses?: Array<MonitorStatus> = undefined; } diff --git a/Model/Models/StatusPageAnnouncement.ts b/Model/Models/StatusPageAnnouncement.ts index 4b3cff8555..99a5e68451 100644 --- a/Model/Models/StatusPageAnnouncement.ts +++ b/Model/Models/StatusPageAnnouncement.ts @@ -1,514 +1,512 @@ -import Project from './Project'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') -@CanAccessIfCanReadOn('statusPages') +@TenantColumn("projectId") +@CanAccessIfCanReadOn("statusPages") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteStatusPageAnnouncement, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageAnnouncement, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteStatusPageAnnouncement, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageAnnouncement, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-announcement')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/status-page-announcement")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'StatusPageAnnouncement', - singularName: 'Status Page Announcement', - pluralName: 'Status Page Announcements', - icon: IconProp.Announcement, - tableDescription: 'Manage announcements on your status page', + tableName: "StatusPageAnnouncement", + singularName: "Status Page Announcement", + pluralName: "Status Page Announcements", + icon: IconProp.Announcement, + tableDescription: "Manage announcements on your status page", }) @Entity({ - name: 'StatusPageAnnouncement', + name: "StatusPageAnnouncement", }) export default class StatusPageAnnouncement extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: StatusPage, - title: 'Status Pages', - description: 'Status Pages to show show this announcement on.', - }) - @ManyToMany( - () => { - return StatusPage; - }, - { eager: false } - ) - @JoinTable({ - name: 'AnnouncementStatusPage', - inverseJoinColumn: { - name: 'statusPageId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'announcementId', - referencedColumnName: '_id', - }, - }) - public statusPages?: Array<StatusPage> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: StatusPage, + title: "Status Pages", + description: "Status Pages to show show this announcement on.", + }) + @ManyToMany( + () => { + return StatusPage; + }, + { eager: false }, + ) + @JoinTable({ + name: "AnnouncementStatusPage", + inverseJoinColumn: { + name: "statusPageId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "announcementId", + referencedColumnName: "_id", + }, + }) + public statusPages?: Array<StatusPage> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageAnnouncement, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Title', - description: 'Title of this resource', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public title?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageAnnouncement, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Title", + description: "Title of this resource", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public title?: string = undefined; - @TableColumn({ - title: 'Show At', - type: TableColumnType.Date, - required: true, - description: 'When should this announcement be shown?', - }) - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageAnnouncement, - ], - }) - @Column({ - nullable: false, - type: ColumnType.Date, - }) - public showAnnouncementAt?: Date = undefined; + @TableColumn({ + title: "Show At", + type: TableColumnType.Date, + required: true, + description: "When should this announcement be shown?", + }) + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageAnnouncement, + ], + }) + @Column({ + nullable: false, + type: ColumnType.Date, + }) + public showAnnouncementAt?: Date = undefined; - @TableColumn({ - title: 'End At', - type: TableColumnType.Date, - required: true, - description: 'When should this announcement hidden?', - }) - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageAnnouncement, - ], - }) - @Column({ - nullable: false, - type: ColumnType.Date, - }) - public endAnnouncementAt?: Date = undefined; + @TableColumn({ + title: "End At", + type: TableColumnType.Date, + required: true, + description: "When should this announcement hidden?", + }) + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageAnnouncement, + ], + }) + @Column({ + nullable: false, + type: ColumnType.Date, + }) + public endAnnouncementAt?: Date = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageAnnouncement, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.Markdown, - title: 'Announcement Description', - description: 'Text of the announcement. This is in Markdown.', - }) - @Column({ - nullable: false, - type: ColumnType.Markdown, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageAnnouncement, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.Markdown, + title: "Announcement Description", + description: "Text of the announcement. This is in Markdown.", + }) + @Column({ + nullable: false, + type: ColumnType.Markdown, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isStatusPageSubscribersNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isStatusPageSubscribersNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should subscribers be notified?', - description: 'Should subscribers be notified about this announcement?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public shouldStatusPageSubscribersBeNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should subscribers be notified?", + description: "Should subscribers be notified about this announcement?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public shouldStatusPageSubscribersBeNotified?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageAnnouncement, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageAnnouncement, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this announcement?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageAnnouncement, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageAnnouncement, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this announcement?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/StatusPageCustomField.ts b/Model/Models/StatusPageCustomField.ts index 003c0a10ef..af9e332439 100644 --- a/Model/Models/StatusPageCustomField.ts +++ b/Model/Models/StatusPageCustomField.ts @@ -1,342 +1,340 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import CustomFieldType from 'Common/Types/CustomField/CustomFieldType'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import CustomFieldType from "Common/Types/CustomField/CustomFieldType"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteStatusPageCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageCustomField, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteStatusPageCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageCustomField, + ], }) -@CrudApiEndpoint(new Route('/status-page-custom-field')) +@CrudApiEndpoint(new Route("/status-page-custom-field")) @TableMetadata({ - tableName: 'StatusPageCustomField', - singularName: 'Status Page Custom Field', - pluralName: 'Status Page Custom Fields', - icon: IconProp.TableCells, - tableDescription: 'Manage custom fields on your status page', + tableName: "StatusPageCustomField", + singularName: "Status Page Custom Field", + pluralName: "Status Page Custom Fields", + icon: IconProp.TableCells, + tableDescription: "Manage custom fields on your status page", }) @Entity({ - name: 'StatusPageCustomField', + name: "StatusPageCustomField", }) export default class StatusPageCustomField extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageCustomField, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageCustomField, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageCustomField, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: - 'Friendly description of this custom field that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageCustomField, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: + "Friendly description of this custom field that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.CustomFieldType, - title: 'Custom Field Type', - description: 'Is this field Text, Number or Boolean?', - }) - @Column({ - nullable: true, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public type?: CustomFieldType = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.CustomFieldType, + title: "Custom Field Type", + description: "Is this field Text, Number or Boolean?", + }) + @Column({ + nullable: true, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public type?: CustomFieldType = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageCustomField, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageCustomField, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageCustomField, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageCustomField, + ], + 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; } diff --git a/Model/Models/StatusPageDomain.ts b/Model/Models/StatusPageDomain.ts index 865849c29b..cffab6cdaa 100644 --- a/Model/Models/StatusPageDomain.ts +++ b/Model/Models/StatusPageDomain.ts @@ -1,544 +1,541 @@ -import Domain from './Domain'; -import Project from './Project'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Domain from "./Domain"; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('statusPage') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("statusPage") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteStatusPageDomain, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageDomain, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteStatusPageDomain, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageDomain, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-domain')) +@CrudApiEndpoint(new Route("/status-page-domain")) @TableMetadata({ - tableName: 'StatusPageDomain', - singularName: 'Status Page Domain', - pluralName: 'Status Page Domains', - icon: IconProp.Globe, - tableDescription: 'Manage custom domains for your status page', + tableName: "StatusPageDomain", + singularName: "Status Page Domain", + pluralName: "Status Page Domains", + icon: IconProp.Globe, + tableDescription: "Manage custom domains for your status page", }) @Entity({ - name: 'StatusPageDomain', + name: "StatusPageDomain", }) export default class StatusPageDomain extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'domainId', - type: TableColumnType.Entity, - modelType: Domain, - }) - @ManyToOne( - () => { - return Domain; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'domainId' }) - public domain?: Domain = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "domainId", + type: TableColumnType.Entity, + modelType: Domain, + }) + @ManyToOne( + () => { + return Domain; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "domainId" }) + public domain?: Domain = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @Index() - @TableColumn({ type: TableColumnType.ObjectID, required: true }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public domainId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @Index() + @TableColumn({ type: TableColumnType.ObjectID, required: true }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public domainId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'Status Page', - description: - 'Relation to Status Page Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "Status Page", + description: + "Relation to Status Page Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Status Page ID', - description: - 'ID of your Status Page resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Status Page ID", + description: "ID of your Status Page resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageDomain, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Sumdomain', - description: 'Subdomain of your status page - like (status)', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public subdomain?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageDomain, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Sumdomain", + description: "Subdomain of your status page - like (status)", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public subdomain?: string = undefined; - @UniqueColumnBy('projectId') - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Full Domain', - description: - 'Full domain of your status page (like status.acmeinc.com). This is autogenerated and is derived from subdomain and domain.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public fullDomain?: string = undefined; + @UniqueColumnBy("projectId") + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Full Domain", + description: + "Full domain of your status page (like status.acmeinc.com). This is autogenerated and is derived from subdomain and domain.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public fullDomain?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - // This token is used by the Worker. - // worker pings the status page of customers - eg: status.company.com/verify-token/:id - // and the end point on Status Page project returns 200. - // when that happens the isCnameVerified is set to True and the certificate is added to Greenlock. - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageDomain, - ], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'CNAME Verification Token', - description: 'CNAME Verification Token', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public cnameVerificationToken?: string = undefined; + // This token is used by the Worker. + // worker pings the status page of customers - eg: status.company.com/verify-token/:id + // and the end point on Status Page project returns 200. + // when that happens the isCnameVerified is set to True and the certificate is added to Greenlock. + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageDomain, + ], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "CNAME Verification Token", + description: "CNAME Verification Token", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public cnameVerificationToken?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - title: 'CNAME Verified', - description: 'Is CNAME Verified?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public isCnameVerified?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + title: "CNAME Verified", + description: "Is CNAME Verified?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isCnameVerified?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - title: 'SSL Ordered', - description: 'Is SSL ordered?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public isSslOrdered?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + title: "SSL Ordered", + description: "Is SSL ordered?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isSslOrdered?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageDomain, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - title: 'SSL Provisioned', - description: 'Is SSL provisioned?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public isSslProvisioned?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageDomain, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + title: "SSL Provisioned", + description: "Is SSL provisioned?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isSslProvisioned?: boolean = 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; + @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; } diff --git a/Model/Models/StatusPageFooterLink.ts b/Model/Models/StatusPageFooterLink.ts index b22d48a7f7..ecb3be8ae0 100644 --- a/Model/Models/StatusPageFooterLink.ts +++ b/Model/Models/StatusPageFooterLink.ts @@ -1,424 +1,421 @@ -import Project from './Project'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import TotalItemsBy from 'Common/Types/Database/TotalItemsBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import TotalItemsBy from "Common/Types/Database/TotalItemsBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('statusPage') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("statusPage") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteStatusPageFooterLink, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageFooterLink, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteStatusPageFooterLink, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageFooterLink, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-footer-link')) +@CrudApiEndpoint(new Route("/status-page-footer-link")) @TableMetadata({ - tableName: 'StatusPageFooterLink', - singularName: 'Status Page Footer Link', - pluralName: 'Status Page Footer Links', - icon: IconProp.ArrowCircleDown, - tableDescription: 'Manage footer links on your status page', + tableName: "StatusPageFooterLink", + singularName: "Status Page Footer Link", + pluralName: "Status Page Footer Links", + icon: IconProp.ArrowCircleDown, + tableDescription: "Manage footer links on your status page", }) @Entity({ - name: 'StatusPageFooterLink', + name: "StatusPageFooterLink", }) @TotalItemsBy( - 'statusPageId', - 3, - 'This status page cannot have more than 3 footer links' + "statusPageId", + 3, + "This status page cannot have more than 3 footer links", ) export default class StatusPageFooterLink extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'Status Page', - description: - 'Relation to Status Page Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "Status Page", + description: + "Relation to Status Page Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Status Page ID', - description: - 'ID of your Status Page resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Status Page ID", + description: "ID of your Status Page resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageFooterLink, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Title', - description: 'Title of this resource', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public title?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageFooterLink, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Title", + description: "Title of this resource", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public title?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageFooterLink, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortURL, - title: 'Link', - description: 'URL to a website or any other resource on the internet', - }) - @Column({ - nullable: false, - type: ColumnType.ShortURL, - length: ColumnLength.ShortURL, - transformer: URL.getDatabaseTransformer(), - }) - public link?: URL = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageFooterLink, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortURL, + title: "Link", + description: "URL to a website or any other resource on the internet", + }) + @Column({ + nullable: false, + type: ColumnType.ShortURL, + length: ColumnLength.ShortURL, + transformer: URL.getDatabaseTransformer(), + }) + public link?: URL = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageFooterLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageFooterLink, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageFooterLink, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Number, - title: 'Order', - description: 'Order / Priority of this resource', - }) - @Column({ - type: ColumnType.Number, - }) - public order?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageFooterLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageFooterLink, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageFooterLink, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Number, + title: "Order", + description: "Order / Priority of this resource", + }) + @Column({ + type: ColumnType.Number, + }) + public order?: number = 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; + @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; } diff --git a/Model/Models/StatusPageGroup.ts b/Model/Models/StatusPageGroup.ts index 32f67933b9..42dd2e396d 100644 --- a/Model/Models/StatusPageGroup.ts +++ b/Model/Models/StatusPageGroup.ts @@ -1,482 +1,479 @@ -import Project from './Project'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) -@CanAccessIfCanReadOn('statusPage') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("statusPage") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteStatusPageGroup, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageGroup, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteStatusPageGroup, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageGroup, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-group')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/status-page-group")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'StatusPageGroup', - singularName: 'Status Page Group', - pluralName: 'Status Page Groups', - icon: IconProp.Folder, - tableDescription: - 'Manage groups on your status page and categorize resources like monitors into these groups.', + tableName: "StatusPageGroup", + singularName: "Status Page Group", + pluralName: "Status Page Groups", + icon: IconProp.Folder, + tableDescription: + "Manage groups on your status page and categorize resources like monitors into these groups.", }) @Entity({ - name: 'StatusPageGroup', + name: "StatusPageGroup", }) export default class StatusPageGroup extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'Status Page', - description: - 'Relation to Status Page Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "Status Page", + description: + "Relation to Status Page Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Status Page ID', - description: - 'ID of your Status Page resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Status Page ID", + description: "ID of your Status Page resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageGroup, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Group Name', - description: 'Name of the Group', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('statusPageId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageGroup, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Group Name", + description: "Name of the Group", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("statusPageId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageGroup, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.Markdown, - title: 'Description', - description: - 'Description for this group. This is visible on Status Page. This can be in markdown format.', - }) - @Column({ - nullable: true, - type: ColumnType.Markdown, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageGroup, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.Markdown, + title: "Description", + description: + "Description for this group. This is visible on Status Page. This can be in markdown format.", + }) + @Column({ + nullable: true, + type: ColumnType.Markdown, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + update: [], + }) + @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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageGroup, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Number, - title: 'Order', - description: 'Order / Priority of this resource', - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.Number, - }) - public order?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageGroup, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Number, + title: "Order", + description: "Order / Priority of this resource", + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.Number, + }) + public order?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageGroup, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageGroup, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageGroup, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Expanded by Default', - description: 'Is this group expanded by default on Status Page?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public isExpandedByDefault?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageGroup, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageGroup, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageGroup, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Expanded by Default", + description: "Is this group expanded by default on Status Page?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public isExpandedByDefault?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/StatusPageHeaderLink.ts b/Model/Models/StatusPageHeaderLink.ts index db17c49a9e..a8cfacd335 100644 --- a/Model/Models/StatusPageHeaderLink.ts +++ b/Model/Models/StatusPageHeaderLink.ts @@ -1,424 +1,421 @@ -import Project from './Project'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import TotalItemsBy from 'Common/Types/Database/TotalItemsBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import TotalItemsBy from "Common/Types/Database/TotalItemsBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('statusPage') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("statusPage") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteStatusPageHeaderLink, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageHeaderLink, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteStatusPageHeaderLink, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageHeaderLink, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-header-link')) +@CrudApiEndpoint(new Route("/status-page-header-link")) @TableMetadata({ - tableName: 'StatusPageHeaderLink', - singularName: 'Status Page Header Link', - pluralName: 'Status Page Header Links', - icon: IconProp.ArrowCircleUp, - tableDescription: 'Manage header links on your status page', + tableName: "StatusPageHeaderLink", + singularName: "Status Page Header Link", + pluralName: "Status Page Header Links", + icon: IconProp.ArrowCircleUp, + tableDescription: "Manage header links on your status page", }) @Entity({ - name: 'StatusPageHeaderLink', + name: "StatusPageHeaderLink", }) @TotalItemsBy( - 'statusPageId', - 3, - 'This status page cannot have more than 3 header links' + "statusPageId", + 3, + "This status page cannot have more than 3 header links", ) export default class StatusPageHeaderLink extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'Status Page', - description: - 'Relation to Status Page Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "Status Page", + description: + "Relation to Status Page Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Status Page ID', - description: - 'ID of your Status Page resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Status Page ID", + description: "ID of your Status Page resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageHeaderLink, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Title', - description: 'Title of this resource', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public title?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageHeaderLink, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Title", + description: "Title of this resource", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public title?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageHeaderLink, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortURL, - title: 'Link', - description: 'URL to a website or any other resource on the internet', - }) - @Column({ - nullable: false, - type: ColumnType.ShortURL, - length: ColumnLength.ShortURL, - transformer: URL.getDatabaseTransformer(), - }) - public link?: URL = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageHeaderLink, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortURL, + title: "Link", + description: "URL to a website or any other resource on the internet", + }) + @Column({ + nullable: false, + type: ColumnType.ShortURL, + length: ColumnLength.ShortURL, + transformer: URL.getDatabaseTransformer(), + }) + public link?: URL = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHeaderLink, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHeaderLink, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageHeaderLink, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Number, - title: 'Order', - description: 'Order / Priority of this resource', - }) - @Column({ - type: ColumnType.Number, - }) - public order?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHeaderLink, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHeaderLink, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageHeaderLink, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Number, + title: "Order", + description: "Order / Priority of this resource", + }) + @Column({ + type: ColumnType.Number, + }) + public order?: number = 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; + @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; } diff --git a/Model/Models/StatusPageHistoryChartBarColorRule.ts b/Model/Models/StatusPageHistoryChartBarColorRule.ts index 51a36606b2..edf5e847a1 100644 --- a/Model/Models/StatusPageHistoryChartBarColorRule.ts +++ b/Model/Models/StatusPageHistoryChartBarColorRule.ts @@ -1,419 +1,416 @@ -import MonitorStatus from './MonitorStatus'; -import Project from './Project'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import Color from 'Common/Types/Color'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import MonitorStatus from "./MonitorStatus"; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import Color from "Common/Types/Color"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@CanAccessIfCanReadOn('statusPage') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("statusPage") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteStatusPageHistoryChartBarColorRule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageHistoryChartBarColorRule, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteStatusPageHistoryChartBarColorRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageHistoryChartBarColorRule, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-history-chart-bar-color-rule')) +@CrudApiEndpoint(new Route("/status-page-history-chart-bar-color-rule")) @TableMetadata({ - tableName: 'StatusPageHistoryChartBarColorRule', - singularName: 'Status Page History Chart Bar Color', - pluralName: 'Status Page History Chart Bar Colors', - icon: IconProp.ChartBar, - tableDescription: - 'Modify the colors of the history chart bars on Status Page', + tableName: "StatusPageHistoryChartBarColorRule", + singularName: "Status Page History Chart Bar Color", + pluralName: "Status Page History Chart Bar Colors", + icon: IconProp.ChartBar, + tableDescription: + "Modify the colors of the history chart bars on Status Page", }) @Entity({ - name: 'StatusPageHistoryChartBarColorRule', + name: "StatusPageHistoryChartBarColorRule", }) export default class StatusPageHistoryChartBarColorRule extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'Status Page', - description: - 'Relation to Status Page Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "Status Page", + description: + "Relation to Status Page Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Status Page ID', - description: - 'ID of your Status Page resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Status Page ID", + description: "ID of your Status Page resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageHistoryChartBarColorRule, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.Number, - modelType: MonitorStatus, - title: 'Uptime Percent Greater than or equal to', - description: 'Uptime percent greater than or equal to this value', - }) - @Column({ - nullable: false, - type: ColumnType.Decimal, - }) - public uptimePercentGreaterThanOrEqualTo?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageHistoryChartBarColorRule, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.Number, + modelType: MonitorStatus, + title: "Uptime Percent Greater than or equal to", + description: "Uptime percent greater than or equal to this value", + }) + @Column({ + nullable: false, + type: ColumnType.Decimal, + }) + public uptimePercentGreaterThanOrEqualTo?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageHistoryChartBarColorRule, - ], - }) - @TableColumn({ - title: 'Bar Color', - required: true, - unique: false, - type: TableColumnType.Color, - canReadOnRelationQuery: true, - description: - 'Color of the bar chart when this rule matches (#32a852 for example)', - }) - @Column({ - type: ColumnType.Color, - length: ColumnLength.Color, - unique: false, - nullable: false, - transformer: Color.getDatabaseTransformer(), - }) - public barColor?: Color = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageHistoryChartBarColorRule, + ], + }) + @TableColumn({ + title: "Bar Color", + required: true, + unique: false, + type: TableColumnType.Color, + canReadOnRelationQuery: true, + description: + "Color of the bar chart when this rule matches (#32a852 for example)", + }) + @Column({ + type: ColumnType.Color, + length: ColumnLength.Color, + unique: false, + nullable: false, + transformer: Color.getDatabaseTransformer(), + }) + public barColor?: Color = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + update: [], + }) + @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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageHistoryChartBarColorRule, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageHistoryChartBarColorRule, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageHistoryChartBarColorRule, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Number, - title: 'Order', - description: 'Order / Priority of this resource', - }) - @Column({ - type: ColumnType.Number, - }) - public order?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageHistoryChartBarColorRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageHistoryChartBarColorRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageHistoryChartBarColorRule, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Number, + title: "Order", + description: "Order / Priority of this resource", + }) + @Column({ + type: ColumnType.Number, + }) + public order?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/StatusPageOwnerTeam.ts b/Model/Models/StatusPageOwnerTeam.ts index a33c69027d..073ab39f5f 100644 --- a/Model/Models/StatusPageOwnerTeam.ts +++ b/Model/Models/StatusPageOwnerTeam.ts @@ -1,428 +1,424 @@ -import Project from './Project'; -import StatusPage from './StatusPage'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@CanAccessIfCanReadOn('statusPage') +@CanAccessIfCanReadOn("statusPage") @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteStatusPageOwnerTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageOwnerTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteStatusPageOwnerTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageOwnerTeam, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-owner-team')) +@CrudApiEndpoint(new Route("/status-page-owner-team")) @TableMetadata({ - tableName: 'StatusPageOwnerTeam', - singularName: 'Status Page Team Owner', - pluralName: 'Status Page Team Owners', - icon: IconProp.Signal, - tableDescription: 'Add teams as owners to your Status Page.', + tableName: "StatusPageOwnerTeam", + singularName: "Status Page Team Owner", + pluralName: "Status Page Team Owners", + icon: IconProp.Signal, + tableDescription: "Add teams as owners to your Status Page.", }) @Entity({ - name: 'StatusPageOwnerTeam', + name: "StatusPageOwnerTeam", }) export default class StatusPageOwnerTeam extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: - 'Team that is the owner. All users in this team will receive notifications. ', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: + "Team that is the owner. All users in this team will receive notifications. ", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Team ID', - description: 'ID of your OneUptime Team in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Team ID", + description: "ID of your OneUptime Team in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'StatusPage', - description: - 'Relation to StatusPage Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "StatusPage", + description: "Relation to StatusPage Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'StatusPage ID', - description: - 'ID of your OneUptime StatusPage in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "StatusPage ID", + description: "ID of your OneUptime StatusPage in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/StatusPageOwnerUser.ts b/Model/Models/StatusPageOwnerUser.ts index 1abb4a7f49..fc498b0e90 100644 --- a/Model/Models/StatusPageOwnerUser.ts +++ b/Model/Models/StatusPageOwnerUser.ts @@ -1,427 +1,423 @@ -import Project from './Project'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@CanAccessIfCanReadOn('statusPage') +@CanAccessIfCanReadOn("statusPage") @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Growth, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteStatusPageOwnerUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageOwnerUser, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteStatusPageOwnerUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageOwnerUser, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-owner-user')) +@CrudApiEndpoint(new Route("/status-page-owner-user")) @TableMetadata({ - tableName: 'StatusPageOwnerUser', - singularName: 'Status Page User Owner', - pluralName: 'Status Page User Owners', - icon: IconProp.Signal, - tableDescription: 'Add users as owners to your Status Page.', + tableName: "StatusPageOwnerUser", + singularName: "Status Page User Owner", + pluralName: "Status Page User Owners", + icon: IconProp.Signal, + tableDescription: "Add users as owners to your Status Page.", }) @Entity({ - name: 'StatusPageOwnerUser', + name: "StatusPageOwnerUser", }) export default class StatusPageOwnerUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - 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: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'projectId' }) - public project?: Project = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + 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: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: - 'User that is the owner. This user will receive notifications. ', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: + "User that is the owner. This user will receive notifications. ", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'User ID', - description: 'ID of your OneUptime User in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "User ID", + description: "ID of your OneUptime User in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'StatusPage', - description: - 'Relation to StatusPage Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "StatusPage", + description: "Relation to StatusPage Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'StatusPage ID', - description: - 'ID of your OneUptime StatusPage in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "StatusPage ID", + description: "ID of your OneUptime StatusPage in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageOwnerUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageOwnerUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.Boolean, - required: true, - isDefaultValueColumn: true, - title: 'Are Owners Notified', - description: 'Are owners notified of this resource ownership?', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - default: false, - }) - public isOwnerNotified?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageOwnerUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageOwnerUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.Boolean, + required: true, + isDefaultValueColumn: true, + title: "Are Owners Notified", + description: "Are owners notified of this resource ownership?", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: false, + }) + public isOwnerNotified?: boolean = undefined; } diff --git a/Model/Models/StatusPagePrivateUser.ts b/Model/Models/StatusPagePrivateUser.ts index 90b042de57..7783ac1f8c 100644 --- a/Model/Models/StatusPagePrivateUser.ts +++ b/Model/Models/StatusPagePrivateUser.ts @@ -1,467 +1,464 @@ -import Project from './Project'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import Email from 'Common/Types/Email'; -import HashedString from 'Common/Types/HashedString'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import Email from "Common/Types/Email"; +import HashedString from "Common/Types/HashedString"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@CanAccessIfCanReadOn('statusPage') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("statusPage") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteStatusPagePrivateUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPagePrivateUser, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteStatusPagePrivateUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPagePrivateUser, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-private-user')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/status-page-private-user")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'StatusPagePrivateUser', - singularName: 'Status Page Private User', - pluralName: 'Status Page Private Users', - icon: IconProp.User, - tableDescription: ' Manage private users on your status page', + tableName: "StatusPagePrivateUser", + singularName: "Status Page Private User", + pluralName: "Status Page Private Users", + icon: IconProp.User, + tableDescription: " Manage private users on your status page", }) @Entity({ - name: 'StatusPagePrivateUser', + name: "StatusPagePrivateUser", }) export default class StatusPagePrivateUser extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'Status Page', - description: - 'Relation to Status Page Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "Status Page", + description: + "Relation to Status Page Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Status Page ID', - description: - 'ID of your Status Page resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Status Page ID", + description: "ID of your Status Page resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPagePrivateUser, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.Email, - title: 'Email', - }) - @Column({ - nullable: true, - type: ColumnType.Email, - length: ColumnLength.Email, - transformer: Email.getDatabaseTransformer(), - }) - public email?: Email = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPagePrivateUser, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.Email, + title: "Email", + }) + @Column({ + nullable: true, + type: ColumnType.Email, + length: ColumnLength.Email, + transformer: Email.getDatabaseTransformer(), + }) + public email?: Email = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPagePrivateUser, - ], - }) - @TableColumn({ - title: 'Password', - hashed: true, - type: TableColumnType.HashedString, - }) - @Column({ - type: ColumnType.HashedString, - length: ColumnLength.HashedString, - unique: false, - nullable: true, - transformer: HashedString.getDatabaseTransformer(), - }) - public password?: HashedString = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPagePrivateUser, + ], + }) + @TableColumn({ + title: "Password", + hashed: true, + type: TableColumnType.HashedString, + }) + @Column({ + type: ColumnType.HashedString, + length: ColumnLength.HashedString, + unique: false, + nullable: true, + transformer: HashedString.getDatabaseTransformer(), + }) + public password?: HashedString = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public resetPasswordToken?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public resetPasswordToken?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], + @ColumnAccessControl({ + create: [], + read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public resetPasswordExpires?: Date = undefined; + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public resetPasswordExpires?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [], + @ColumnAccessControl({ + create: [], + read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public lastActive?: Date = undefined; + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public lastActive?: Date = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPagePrivateUser, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPagePrivateUser, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'SSO User', - description: 'Did this user sign up via SSO?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isSsoUser?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPagePrivateUser, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPagePrivateUser, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "SSO User", + description: "Did this user sign up via SSO?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isSsoUser?: boolean = 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; + @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; } diff --git a/Model/Models/StatusPageResource.ts b/Model/Models/StatusPageResource.ts index fe0122fc44..0ed6e626a7 100644 --- a/Model/Models/StatusPageResource.ts +++ b/Model/Models/StatusPageResource.ts @@ -1,816 +1,810 @@ -import Monitor from './Monitor'; -import MonitorGroup from './MonitorGroup'; -import Project from './Project'; -import StatusPage from './StatusPage'; -import StatusPageGroup from './StatusPageGroup'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Monitor from "./Monitor"; +import MonitorGroup from "./MonitorGroup"; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import StatusPageGroup from "./StatusPageGroup"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; export enum UptimePrecision { - NO_DECIMAL = '99% (No Decimal)', - ONE_DECIMAL = '99.9% (One Decimal)', - TWO_DECIMAL = '99.99% (Two Decimal)', - THREE_DECIMAL = '99.999% (Three Decimal)', + NO_DECIMAL = "99% (No Decimal)", + ONE_DECIMAL = "99.9% (One Decimal)", + TWO_DECIMAL = "99.99% (Two Decimal)", + THREE_DECIMAL = "99.999% (Three Decimal)", } @EnableDocumentation() -@CanAccessIfCanReadOn('statusPage') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("statusPage") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/status-page-resource')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/status-page-resource")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'StatusPageResource', - singularName: 'Status Page Resource', - pluralName: 'Status Page Resources', - icon: IconProp.AltGlobe, - tableDescription: 'Add resources like monitors to your status page', + tableName: "StatusPageResource", + singularName: "Status Page Resource", + pluralName: "Status Page Resources", + icon: IconProp.AltGlobe, + tableDescription: "Add resources like monitors to your status page", }) @Entity({ - name: 'StatusPageResource', + name: "StatusPageResource", }) export default class StatusPageResource extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'Status Page', - description: - 'Relation to Status Page Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "Status Page", + description: + "Relation to Status Page Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Status Page ID', - description: - 'ID of your Status Page resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Status Page ID", + description: "ID of your Status Page resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorId', - type: TableColumnType.Entity, - modelType: Monitor, - title: 'Monitor', - description: - 'Relation to Monitor Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Monitor; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorId' }) - public monitor?: Monitor = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorId", + type: TableColumnType.Entity, + modelType: Monitor, + title: "Monitor", + description: "Relation to Monitor Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Monitor; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorId" }) + public monitor?: Monitor = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - title: 'Monitor ID', - description: - 'Relation to Monitor ID Resource in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + title: "Monitor ID", + description: "Relation to Monitor ID Resource in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'monitorGroupId', - type: TableColumnType.Entity, - modelType: MonitorGroup, - title: 'Monitor Group', - description: - 'Relation to Monitor Group Resource in which this object belongs', - }) - @ManyToOne( - () => { - return MonitorGroup; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'monitorGroupId' }) - public monitorGroup?: MonitorGroup = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "monitorGroupId", + type: TableColumnType.Entity, + modelType: MonitorGroup, + title: "Monitor Group", + description: + "Relation to Monitor Group Resource in which this object belongs", + }) + @ManyToOne( + () => { + return MonitorGroup; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "monitorGroupId" }) + public monitorGroup?: MonitorGroup = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - title: 'Monitor Group ID', - description: - 'Relation to Monitor Group ID Resource in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public monitorGroupId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + title: "Monitor Group ID", + description: + "Relation to Monitor Group ID Resource in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public monitorGroupId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageGroupId', - type: TableColumnType.Entity, - modelType: StatusPageGroup, - title: 'Status Page Group', - description: - 'Does this monitor belong to a status page group? If so - which one is it?', - }) - @ManyToOne( - () => { - return StatusPageGroup; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageGroupId' }) - public statusPageGroup?: StatusPageGroup = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageGroupId", + type: TableColumnType.Entity, + modelType: StatusPageGroup, + title: "Status Page Group", + description: + "Does this monitor belong to a status page group? If so - which one is it?", + }) + @ManyToOne( + () => { + return StatusPageGroup; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageGroupId" }) + public statusPageGroup?: StatusPageGroup = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - title: 'Group ID', - description: 'Does this monitor belong to a status page group?', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageGroupId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + title: "Group ID", + description: "Does this monitor belong to a status page group?", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageGroupId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Display Name', - description: 'Display name of the monitor on the Status Page', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public displayName?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Display Name", + description: "Display name of the monitor on the Status Page", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public displayName?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.Markdown, - title: 'Display Description', - description: - 'Display description of the monitor on the Status Page. This is in markdown format.', - }) - @Column({ - nullable: true, - type: ColumnType.Markdown, - }) - public displayDescription?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.Markdown, + title: "Display Description", + description: + "Display description of the monitor on the Status Page. This is in markdown format.", + }) + @Column({ + nullable: true, + type: ColumnType.Markdown, + }) + public displayDescription?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Display Tooltip', - description: 'Tooltip of the monitor on the Status Page', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public displayTooltip?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Display Tooltip", + description: "Tooltip of the monitor on the Status Page", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public displayTooltip?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Show current status', - description: - 'Show current status like offline, operational or degraded.', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public showCurrentStatus?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Show current status", + description: "Show current status like offline, operational or degraded.", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public showCurrentStatus?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Show Uptime Percent', - description: 'Show uptime percent of this monitor for the last 90 days', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public showUptimePercent?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Show Uptime Percent", + description: "Show uptime percent of this monitor for the last 90 days", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public showUptimePercent?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - type: TableColumnType.ShortText, - title: 'Uptime Percent Precision', - required: false, - description: - 'Precision of uptime percent of this monitor for the last 90 days', - }) - @Column({ - type: ColumnType.ShortText, - nullable: true, - }) - public uptimePercentPrecision?: UptimePrecision = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + type: TableColumnType.ShortText, + title: "Uptime Percent Precision", + required: false, + description: + "Precision of uptime percent of this monitor for the last 90 days", + }) + @Column({ + type: ColumnType.ShortText, + nullable: true, + }) + public uptimePercentPrecision?: UptimePrecision = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Show History Chart', - description: 'Show a 90 day uptime history of this monitor', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public showStatusHistoryChart?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Show History Chart", + description: "Show a 90 day uptime history of this monitor", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public showStatusHistoryChart?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [], + }) + @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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageResource, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageResource, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageResource, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - type: TableColumnType.Number, - title: 'Order', - description: 'Order / Priority of this resource', - }) - @Column({ - type: ColumnType.Number, - }) - public order?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageResource, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageResource, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageResource, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + type: TableColumnType.Number, + title: "Order", + description: "Order / Priority of this resource", + }) + @Column({ + type: ColumnType.Number, + }) + public order?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/StatusPageSso.ts b/Model/Models/StatusPageSso.ts index d53973cea3..e43d33879d 100644 --- a/Model/Models/StatusPageSso.ts +++ b/Model/Models/StatusPageSso.ts @@ -1,586 +1,583 @@ -import Project from './Project'; -import StatusPage from './StatusPage'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import DigestMethod from 'Common/Types/SSO/DigestMethod'; -import SignatureMethod from 'Common/Types/SSO/SignatureMethod'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import DigestMethod from "Common/Types/SSO/DigestMethod"; +import SignatureMethod from "Common/Types/SSO/SignatureMethod"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Scale, - read: PlanSelect.Scale, - update: PlanSelect.Scale, - delete: PlanSelect.Scale, + create: PlanSelect.Scale, + read: PlanSelect.Scale, + update: PlanSelect.Scale, + delete: PlanSelect.Scale, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectUser, - Permission.Public, - Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteStatusPageSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageSSO, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectUser, + Permission.Public, + Permission.ProjectAdmin, + Permission.ReadStatusPageSSO, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteStatusPageSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageSSO, + ], }) -@CrudApiEndpoint(new Route('/status-page-sso')) +@CrudApiEndpoint(new Route("/status-page-sso")) @TableMetadata({ - tableName: 'StatusPageSSO', - singularName: 'Status Page SSO', - pluralName: 'Status Page SSO', - icon: IconProp.Lock, - tableDescription: 'Configure Status Page SSO', + tableName: "StatusPageSSO", + singularName: "Status Page SSO", + pluralName: "Status Page SSO", + icon: IconProp.Lock, + tableDescription: "Configure Status Page SSO", }) @Entity({ - name: 'StatusPageSSO', + name: "StatusPageSSO", }) export default class StatusPageSSO extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.Public, - Permission.ReadStatusPageSSO, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.Public, + Permission.ReadStatusPageSSO, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.Public, - Permission.ReadStatusPageSSO, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.Public, + Permission.ReadStatusPageSSO, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSSO, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'Status Page', - description: - 'Relation to Status Page Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSSO, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "Status Page", + description: + "Relation to Status Page Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSSO, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Status Page ID', - description: - 'ID of your Status Page resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSSO, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Status Page ID", + description: "ID of your Status Page resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.Public, - Permission.ReadStatusPageSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.Public, + Permission.ReadStatusPageSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.Public, - Permission.ReadStatusPageSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.Public, + Permission.ReadStatusPageSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public signatureMethod?: SignatureMethod = undefined; + Permission.ReadStatusPageSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public signatureMethod?: SignatureMethod = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public digestMethod?: DigestMethod = undefined; + Permission.ReadStatusPageSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public digestMethod?: DigestMethod = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - Permission.ProjectUser, - Permission.Public, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongURL, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.LongURL, - transformer: URL.getDatabaseTransformer(), - }) - @UniqueColumnBy('projectId') - public signOnURL?: URL = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadStatusPageSSO, + Permission.ProjectUser, + Permission.Public, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongURL, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.LongURL, + transformer: URL.getDatabaseTransformer(), + }) + @UniqueColumnBy("projectId") + public signOnURL?: URL = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongURL, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.LongURL, - transformer: URL.getDatabaseTransformer(), - }) - public issuerURL?: URL = undefined; + Permission.ReadStatusPageSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongURL, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.LongURL, + transformer: URL.getDatabaseTransformer(), + }) + public issuerURL?: URL = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageSSO, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.VeryLongText, - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.VeryLongText, - }) - public publicCertificate?: string = undefined; + Permission.ReadStatusPageSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageSSO, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.VeryLongText, + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.VeryLongText, + }) + public publicCertificate?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + Permission.ReadStatusPageSSO, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - update: [], - }) - @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; + Permission.ReadStatusPageSSO, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + Permission.ReadStatusPageSSO, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - 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; + Permission.ReadStatusPageSSO, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectUser, - Permission.Public, - Permission.ReadStatusPageSSO, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditStatusPageSSO, - ], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isEnabled?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectUser, + Permission.Public, + Permission.ReadStatusPageSSO, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditStatusPageSSO, + ], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isEnabled?: boolean = undefined; - // Is this integration tested? - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateStatusPageSSO, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + // Is this integration tested? + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateStatusPageSSO, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.ReadStatusPageSSO, - ], - update: [], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isTested?: boolean = undefined; + Permission.ReadStatusPageSSO, + ], + update: [], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isTested?: boolean = undefined; } diff --git a/Model/Models/StatusPageSubscriber.ts b/Model/Models/StatusPageSubscriber.ts index 63fdf4267a..d45f815502 100644 --- a/Model/Models/StatusPageSubscriber.ts +++ b/Model/Models/StatusPageSubscriber.ts @@ -1,582 +1,578 @@ -import Project from './Project'; -import StatusPage from './StatusPage'; -import StatusPageResource from './StatusPageResource'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import Email from 'Common/Types/Email'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Phone from 'Common/Types/Phone'; +import Project from "./Project"; +import StatusPage from "./StatusPage"; +import StatusPageResource from "./StatusPageResource"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import CanAccessIfCanReadOn from "Common/Types/Database/CanAccessIfCanReadOn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import Email from "Common/Types/Email"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Phone from "Common/Types/Phone"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CanAccessIfCanReadOn('statusPage') -@TenantColumn('projectId') +@CanAccessIfCanReadOn("statusPage") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - Permission.Public, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteStatusPageSubscriber, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageSubscriber, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + Permission.Public, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteStatusPageSubscriber, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageSubscriber, + ], }) -@CrudApiEndpoint(new Route('/status-page-subscriber')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/status-page-subscriber")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'StatusPageSubscriber', - singularName: 'Status Page Subscriber', - pluralName: 'Status Page Subscribers', - icon: IconProp.Team, - tableDescription: 'Subscriber that subscribed to your status page', + tableName: "StatusPageSubscriber", + singularName: "Status Page Subscriber", + pluralName: "Status Page Subscribers", + icon: IconProp.Team, + tableDescription: "Subscriber that subscribed to your status page", }) @Entity({ - name: 'StatusPageSubscriber', + name: "StatusPageSubscriber", }) export default class StatusPageSubscriber extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'statusPageId', - type: TableColumnType.Entity, - modelType: StatusPage, - title: 'Status Page', - description: - 'Relation to Status Page Resource in which this object belongs', - }) - @ManyToOne( - () => { - return StatusPage; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'statusPageId' }) - public statusPage?: StatusPage = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "statusPageId", + type: TableColumnType.Entity, + modelType: StatusPage, + title: "Status Page", + description: + "Relation to Status Page Resource in which this object belongs", + }) + @ManyToOne( + () => { + return StatusPage; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "statusPageId" }) + public statusPage?: StatusPage = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Status Page ID', - description: - 'ID of your Status Page resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public statusPageId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Status Page ID", + description: "ID of your Status Page resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public statusPageId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - Permission.Public, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageSubscriber, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.Email, - title: 'Email', - description: 'Email address of the subscriber.', - }) - @Column({ - nullable: true, - type: ColumnType.Email, - length: ColumnLength.Email, - transformer: Email.getDatabaseTransformer(), - }) - public subscriberEmail?: Email = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + Permission.Public, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageSubscriber, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.Email, + title: "Email", + description: "Email address of the subscriber.", + }) + @Column({ + nullable: true, + type: ColumnType.Email, + length: ColumnLength.Email, + transformer: Email.getDatabaseTransformer(), + }) + public subscriberEmail?: Email = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - Permission.Public, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageSubscriber, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.Phone, - title: 'Phone', - description: 'Phone number of subscriber', - }) - @Column({ - nullable: true, - type: ColumnType.Phone, - length: ColumnLength.Phone, - transformer: Phone.getDatabaseTransformer(), - }) - public subscriberPhone?: Phone = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + Permission.Public, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageSubscriber, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.Phone, + title: "Phone", + description: "Phone number of subscriber", + }) + @Column({ + nullable: true, + type: ColumnType.Phone, + length: ColumnLength.Phone, + transformer: Phone.getDatabaseTransformer(), + }) + public subscriberPhone?: Phone = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - Permission.Public, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageSubscriber, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.ShortURL, - title: 'Webhook', - description: 'Webhook to ping when events happen on Status Page', - }) - @Column({ - nullable: true, - type: ColumnType.ShortURL, - transformer: URL.getDatabaseTransformer(), - }) - public subscriberWebhook?: URL = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + Permission.Public, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageSubscriber, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.ShortURL, + title: "Webhook", + description: "Webhook to ping when events happen on Status Page", + }) + @Column({ + nullable: true, + type: ColumnType.ShortURL, + transformer: URL.getDatabaseTransformer(), + }) + public subscriberWebhook?: URL = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - Permission.Public, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageSubscriber, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Is Unsubscribed', - description: 'Is Subscriber Unsubscribed?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isUnsubscribed?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + Permission.Public, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageSubscriber, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Is Unsubscribed", + description: "Is Subscriber Unsubscribed?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isUnsubscribed?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - Permission.Public, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Send You Have Subscribed Message', - description: - 'Send You Have Subscribed Message when subscriber is created?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public sendYouHaveSubscribedMessage?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + Permission.Public, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Send You Have Subscribed Message", + description: "Send You Have Subscribed Message when subscriber is created?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public sendYouHaveSubscribedMessage?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - Permission.Public, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageSubscriber, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Is Subscribed to All Resources', - description: - 'Is Subscriber Subscribed to All Resources on this status page?', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public isSubscribedToAllResources?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + Permission.Public, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageSubscriber, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Is Subscribed to All Resources", + description: + "Is Subscriber Subscribed to All Resources on this status page?", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public isSubscribedToAllResources?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateStatusPageSubscriber, - Permission.Public, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadStatusPageSubscriber, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditStatusPageSubscriber, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: StatusPageResource, - title: 'Subscribed to Resources', - description: - 'Relation to Status Page Resources where this subscriber is subscribed to', - }) - @ManyToMany( - () => { - return StatusPageResource; - }, - { eager: false } - ) - @JoinTable({ - name: 'StatusPageSubscriberStatusPageResource', - inverseJoinColumn: { - name: 'statusPageResourceId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'statusPageSubscriberId', - referencedColumnName: '_id', - }, - }) - public statusPageResources?: Array<StatusPageResource> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateStatusPageSubscriber, + Permission.Public, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadStatusPageSubscriber, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditStatusPageSubscriber, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: StatusPageResource, + title: "Subscribed to Resources", + description: + "Relation to Status Page Resources where this subscriber is subscribed to", + }) + @ManyToMany( + () => { + return StatusPageResource; + }, + { eager: false }, + ) + @JoinTable({ + name: "StatusPageSubscriberStatusPageResource", + inverseJoinColumn: { + name: "statusPageResourceId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "statusPageSubscriberId", + referencedColumnName: "_id", + }, + }) + public statusPageResources?: Array<StatusPageResource> = undefined; } diff --git a/Model/Models/Team.ts b/Model/Models/Team.ts index 6b74c195dd..bac35395cb 100644 --- a/Model/Models/Team.ts +++ b/Model/Models/Team.ts @@ -1,441 +1,439 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Scale, - read: PlanSelect.Free, - update: PlanSelect.Growth, - delete: PlanSelect.Free, + create: PlanSelect.Scale, + read: PlanSelect.Free, + update: PlanSelect.Growth, + delete: PlanSelect.Free, }) @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteProjectTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.InviteProjectTeamMembers, - Permission.EditProjectTeamPermissions, - Permission.EditProjectTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteProjectTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.InviteProjectTeamMembers, + Permission.EditProjectTeamPermissions, + Permission.EditProjectTeam, + ], }) -@CrudApiEndpoint(new Route('/team')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/team")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'Team', + name: "Team", }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) @TableMetadata({ - tableName: 'Team', - singularName: 'Team', - pluralName: 'Teams', - icon: IconProp.Team, - tableDescription: - 'Teams lets your organize users of your project into groups and lets you assign different level of permissions.', + tableName: "Team", + singularName: "Team", + pluralName: "Teams", + icon: IconProp.Team, + tableDescription: + "Teams lets your organize users of your project into groups and lets you assign different level of permissions.", }) export default class Team extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectTeam, - ], - }) - @Index() - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Name', - description: 'Any friendly name of this object', - canReadOnRelationQuery: true, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectTeam, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Name", + description: "Any friendly name of this object", + canReadOnRelationQuery: true, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectTeam, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectTeam, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @Index() - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; + @Index() + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectTeam, - Permission.EditProjectTeamPermissions, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Permissions Editable', - description: - 'Can you edit team permissions? Teams auto-created for you are uneditable but you should be able to edit permissions on the team you create', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public isPermissionsEditable?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectTeam, + Permission.EditProjectTeamPermissions, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Permissions Editable", + description: + "Can you edit team permissions? Teams auto-created for you are uneditable but you should be able to edit permissions on the team you create", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public isPermissionsEditable?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectTeam, - Permission.EditProjectTeamPermissions, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Team Deleteable', - description: - 'Can you delete this team? Teams auto-created for you are not deleteable but you should be able to delete permissions on the team you create', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public isTeamDeleteable?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectTeam, + Permission.EditProjectTeamPermissions, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Team Deleteable", + description: + "Can you delete this team? Teams auto-created for you are not deleteable but you should be able to delete permissions on the team you create", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public isTeamDeleteable?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectTeam, - Permission.EditProjectTeamPermissions, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Should have one member?', - description: - 'Can this team have no members? Owner team should have at least 1 member, other teams can have no members', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public shouldHaveAtLeastOneMember?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectTeam, + Permission.EditProjectTeamPermissions, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Should have one member?", + description: + "Can this team have no members? Owner team should have at least 1 member, other teams can have no members", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public shouldHaveAtLeastOneMember?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditProjectTeam, - Permission.EditProjectTeamPermissions, - ], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Team Editable', - description: - 'Can you edit team? Teams auto-created for you are uneditable but you should be able to edit on the team you create', - }) - @Column({ - type: ColumnType.Boolean, - default: true, - }) - public isTeamEditable?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditProjectTeam, + Permission.EditProjectTeamPermissions, + ], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Team Editable", + description: + "Can you edit team? Teams auto-created for you are uneditable but you should be able to edit on the team you create", + }) + @Column({ + type: ColumnType.Boolean, + default: true, + }) + public isTeamEditable?: boolean = undefined; } diff --git a/Model/Models/TeamMember.ts b/Model/Models/TeamMember.ts index f3e33048bc..139e970da9 100644 --- a/Model/Models/TeamMember.ts +++ b/Model/Models/TeamMember.ts @@ -1,424 +1,422 @@ -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import AllowUserQueryWithoutTenant from 'Common/Types/Database/AllowUserQueryWithoutTenant'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import CurrentUserCanAccessRecordBy from 'Common/Types/Database/CurrentUserCanAccessRecordBy'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import MultiTenentQueryAllowed from 'Common/Types/Database/MultiTenentQueryAllowed'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import AllowUserQueryWithoutTenant from "Common/Types/Database/AllowUserQueryWithoutTenant"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import CurrentUserCanAccessRecordBy from "Common/Types/Database/CurrentUserCanAccessRecordBy"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import MultiTenentQueryAllowed from "Common/Types/Database/MultiTenentQueryAllowed"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.InviteProjectTeamMembers, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - Permission.CurrentUser, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.InviteProjectTeamMembers, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + Permission.CurrentUser, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, - Permission.DeleteProjectTeam, - Permission.CurrentUser, - ], - update: [ - Permission.ProjectOwner, - Permission.InviteProjectTeamMembers, - Permission.EditProjectTeam, - Permission.CurrentUser, - ], + Permission.DeleteProjectTeam, + Permission.CurrentUser, + ], + update: [ + Permission.ProjectOwner, + Permission.InviteProjectTeamMembers, + Permission.EditProjectTeam, + Permission.CurrentUser, + ], }) @MultiTenentQueryAllowed(true) @AllowUserQueryWithoutTenant(true) -@CurrentUserCanAccessRecordBy('userId') -@TenantColumn('projectId') -@CrudApiEndpoint(new Route('/team-member')) +@CurrentUserCanAccessRecordBy("userId") +@TenantColumn("projectId") +@CrudApiEndpoint(new Route("/team-member")) @Entity({ - name: 'TeamMember', + name: "TeamMember", }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) @TableMetadata({ - tableName: 'TeamMember', - singularName: 'Team Member', - pluralName: 'Team Members', - icon: IconProp.User, - tableDescription: 'This model connects users and teams', + tableName: "TeamMember", + singularName: "Team Member", + pluralName: "Team Members", + icon: IconProp.User, + tableDescription: "This model connects users and teams", }) export default class TeamMember extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.InviteProjectTeamMembers, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - Permission.CurrentUser, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: 'Team this permission belongs in.', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.InviteProjectTeamMembers, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + Permission.CurrentUser, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: "Team this permission belongs in.", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.InviteProjectTeamMembers, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - Permission.CurrentUser, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Team ID', - description: 'ID of Team this user belongs to.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.InviteProjectTeamMembers, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + Permission.CurrentUser, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Team ID", + description: "ID of Team this user belongs to.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.InviteProjectTeamMembers, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - Permission.CurrentUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.InviteProjectTeamMembers, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + Permission.CurrentUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.InviteProjectTeamMembers, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - Permission.CurrentUser, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.InviteProjectTeamMembers, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + Permission.CurrentUser, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.InviteProjectTeamMembers, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectTeam, - Permission.ProjectMember, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'User who belongs to this team.', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.InviteProjectTeamMembers, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectTeam, + Permission.ProjectMember, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "User who belongs to this team.", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.InviteProjectTeamMembers, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - Permission.CurrentUser, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'User ID', - description: 'ID of User who belongs to this team', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.InviteProjectTeamMembers, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + Permission.CurrentUser, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "User ID", + description: "ID of User who belongs to this team", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @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: [], + read: [], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.InviteProjectTeamMembers, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - Permission.CurrentUser, - ], - update: [Permission.CurrentUser], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - title: 'Has Accepted Invitation', - description: 'Has this team member accepted invitation', - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public hasAcceptedInvitation?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.InviteProjectTeamMembers, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + Permission.CurrentUser, + ], + update: [Permission.CurrentUser], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + title: "Has Accepted Invitation", + description: "Has this team member accepted invitation", + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public hasAcceptedInvitation?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.InviteProjectTeamMembers, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ReadProjectTeam, - ], - update: [Permission.CurrentUser], - }) - @TableColumn({ - required: false, - type: TableColumnType.Date, - title: 'Accepted Invitation At', - description: 'When did this team member accept invitation', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public invitationAcceptedAt?: Date = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.InviteProjectTeamMembers, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ReadProjectTeam, + ], + update: [Permission.CurrentUser], + }) + @TableColumn({ + required: false, + type: TableColumnType.Date, + title: "Accepted Invitation At", + description: "When did this team member accept invitation", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public invitationAcceptedAt?: Date = undefined; } diff --git a/Model/Models/TeamPermission.ts b/Model/Models/TeamPermission.ts index abb7cb4eee..6e918e48c5 100644 --- a/Model/Models/TeamPermission.ts +++ b/Model/Models/TeamPermission.ts @@ -1,443 +1,440 @@ -import Label from './Label'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Label from "./Label"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Scale, - read: PlanSelect.Free, - update: PlanSelect.Scale, - delete: PlanSelect.Free, + create: PlanSelect.Scale, + read: PlanSelect.Free, + update: PlanSelect.Scale, + delete: PlanSelect.Free, }) @EnableDocumentation() @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.EditProjectTeamPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteProjectTeam, - Permission.EditProjectTeamPermissions, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.InviteProjectTeamMembers, - Permission.EditProjectTeamPermissions, - Permission.EditProjectTeam, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.EditProjectTeamPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteProjectTeam, + Permission.EditProjectTeamPermissions, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.InviteProjectTeamMembers, + Permission.EditProjectTeamPermissions, + Permission.EditProjectTeam, + ], }) -@TenantColumn('projectId') -@CrudApiEndpoint(new Route('/team-permission')) +@TenantColumn("projectId") +@CrudApiEndpoint(new Route("/team-permission")) @Entity({ - name: 'TeamPermission', + name: "TeamPermission", }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) @TableMetadata({ - tableName: 'TeamPermission', - singularName: 'Team Permission', - pluralName: 'Team Permissions', - icon: IconProp.Lock, - tableDescription: 'Permissions for your OneUptime team', + tableName: "TeamPermission", + singularName: "Team Permission", + pluralName: "Team Permissions", + icon: IconProp.Lock, + tableDescription: "Permissions for your OneUptime team", }) export default class TeamPermission extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.EditProjectTeamPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.EditProjectTeamPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.EditProjectTeamPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.EditProjectTeamPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.EditProjectTeamPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'teamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Team', - description: 'Team this permission belongs in.', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'teamId' }) - public team?: Team = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.EditProjectTeamPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "teamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Team", + description: "Team this permission belongs in.", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "teamId" }) + public team?: Team = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.EditProjectTeamPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Team ID', - description: 'ID of Team this permission belongs in.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public teamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.EditProjectTeamPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Team ID", + description: "ID of Team this permission belongs in.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public teamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [], + }) + @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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.EditProjectTeamPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.InviteProjectTeamMembers, - Permission.EditProjectTeamPermissions, - Permission.EditProjectTeam, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.Permission, - title: 'Permission', - description: - 'Permission. You can find list of permissions on the Permissions page.', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public permission?: Permission = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.EditProjectTeamPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.InviteProjectTeamMembers, + Permission.EditProjectTeamPermissions, + Permission.EditProjectTeam, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.Permission, + title: "Permission", + description: + "Permission. You can find list of permissions on the Permissions page.", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public permission?: Permission = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.EditProjectTeamPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectTeamPermissions, - Permission.EditProjectTeam, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this permission is scoped at.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'TeamPermissionLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'teamPermissionId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.EditProjectTeamPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectTeamPermissions, + Permission.EditProjectTeam, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: "Relation to Labels Array where this permission is scoped at.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "TeamPermissionLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "teamPermissionId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateProjectTeam, - Permission.EditProjectTeamPermissions, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadProjectTeam, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditProjectTeamPermissions, - Permission.EditProjectTeam, - ], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isBlockPermission?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateProjectTeam, + Permission.EditProjectTeamPermissions, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadProjectTeam, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditProjectTeamPermissions, + Permission.EditProjectTeam, + ], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isBlockPermission?: boolean = undefined; } diff --git a/Model/Models/TelemetryService.ts b/Model/Models/TelemetryService.ts index b0b8aa29e7..b33c56d839 100644 --- a/Model/Models/TelemetryService.ts +++ b/Model/Models/TelemetryService.ts @@ -1,524 +1,521 @@ -import Label from './Label'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import Color from 'Common/Types/Color'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import EnableWorkflow from 'Common/Types/Database/EnableWorkflow'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Label from "./Label"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import Color from "Common/Types/Color"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import EnableWorkflow from "Common/Types/Database/EnableWorkflow"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; -@AccessControlColumn('labels') +@AccessControlColumn("labels") @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.DeleteTelemetryService, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryService, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.DeleteTelemetryService, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryService, + ], }) @EnableWorkflow({ - create: true, - delete: true, - update: true, - read: true, + create: true, + delete: true, + update: true, + read: true, }) -@CrudApiEndpoint(new Route('/telemetry-service')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/telemetry-service")) +@SlugifyColumn("name", "slug") @TableMetadata({ - tableName: 'TelemetryService', - singularName: 'Telemetry Service', - pluralName: 'Telemetry Services', - icon: IconProp.SquareStack, - tableDescription: - 'Telemetry Services are the services that you can use to monitor your services. You can create multiple Telemetry Services and use them to monitor your services.', + tableName: "TelemetryService", + singularName: "Telemetry Service", + pluralName: "Telemetry Services", + icon: IconProp.SquareStack, + tableDescription: + "Telemetry Services are the services that you can use to monitor your services. You can create multiple Telemetry Services and use them to monitor your services.", }) @Entity({ - name: 'TelemetryService', + name: "TelemetryService", }) export default class TelemetryService extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryService, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryService, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryService, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryService, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryService, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'ServiceLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'telemetryServiceId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryService, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "ServiceLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "telemetryServiceId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryService, - ], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - isDefaultValueColumn: false, - title: 'Service Token', - description: 'Service Token for this telemetry service', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public telemetryServiceToken?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryService, + ], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + isDefaultValueColumn: false, + title: "Service Token", + description: "Service Token for this telemetry service", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public telemetryServiceToken?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryService, - ], - }) - @TableColumn({ - type: TableColumnType.Number, - title: 'Retain Telemetry Data For Days', - description: - 'Number of days to retain telemetry data for this service.', - }) - @Column({ - type: ColumnType.Number, - nullable: true, - unique: false, - default: 15, - }) - public retainTelemetryDataForDays?: number = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryService, + ], + }) + @TableColumn({ + type: TableColumnType.Number, + title: "Retain Telemetry Data For Days", + description: "Number of days to retain telemetry data for this service.", + }) + @Column({ + type: ColumnType.Number, + nullable: true, + unique: false, + default: 15, + }) + public retainTelemetryDataForDays?: number = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.CreateTelemetryService, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ProjectMember, - Permission.ReadTelemetryService, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.EditTelemetryService, - ], - }) - @TableColumn({ - type: TableColumnType.Color, - title: 'Service Color', - description: 'Color for this telemetry service', - }) - @Column({ - type: ColumnType.Color, - nullable: true, - unique: false, - transformer: Color.getDatabaseTransformer(), - }) - public serviceColor?: Color = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateTelemetryService, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ProjectMember, + Permission.ReadTelemetryService, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.EditTelemetryService, + ], + }) + @TableColumn({ + type: TableColumnType.Color, + title: "Service Color", + description: "Color for this telemetry service", + }) + @Column({ + type: ColumnType.Color, + nullable: true, + unique: false, + transformer: Color.getDatabaseTransformer(), + }) + public serviceColor?: Color = undefined; } diff --git a/Model/Models/TelemetryUsageBilling.ts b/Model/Models/TelemetryUsageBilling.ts index 19aa6f762d..85d0a72d2e 100644 --- a/Model/Models/TelemetryUsageBilling.ts +++ b/Model/Models/TelemetryUsageBilling.ts @@ -1,418 +1,415 @@ -import Project from './Project'; -import TelemetryService from './TelemetryService'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import Decimal from 'Common/Types/Decimal'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ProductType from 'Common/Types/MeteredPlan/ProductType'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import TelemetryService from "./TelemetryService"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import Decimal from "Common/Types/Decimal"; +import IconProp from "Common/Types/Icon/IconProp"; +import ProductType from "Common/Types/MeteredPlan/ProductType"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; export const DEFAULT_RETENTION_IN_DAYS: number = 15; -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - delete: [], - update: [], + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/telemetry-usage-billing')) +@CrudApiEndpoint(new Route("/telemetry-usage-billing")) @TableMetadata({ - tableName: 'TelemetryUsageBilling', - singularName: 'Telemetry Usage Billing', - pluralName: 'Telemetry Usage Billings', - icon: IconProp.Billing, - tableDescription: - 'Stores historical usage billing data for your telemetry data like Logs, Metrics, and Traces.', + tableName: "TelemetryUsageBilling", + singularName: "Telemetry Usage Billing", + pluralName: "Telemetry Usage Billings", + icon: IconProp.Billing, + tableDescription: + "Stores historical usage billing data for your telemetry data like Logs, Metrics, and Traces.", }) @Entity({ - name: 'TelemetryUsageBilling', + name: "TelemetryUsageBilling", }) export default class TelemetryUsageBilling extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - 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.ManageProjectBilling, + ], + 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.ManageProjectBilling, - ], - 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.ManageProjectBilling, + ], + 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.ManageProjectBilling, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Day', - description: 'Day of the month this usage billing was generated for', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public day?: string = undefined; // this is of format DD-MM-YYYY + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Day", + description: "Day of the month this usage billing was generated for", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public day?: string = undefined; // this is of format DD-MM-YYYY - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Product Type', - description: 'Product Type this usage billing was generated for', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public productType?: ProductType = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Product Type", + description: "Product Type this usage billing was generated for", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public productType?: ProductType = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Number, - title: 'Retain Telemetry Data For Days', - description: - 'Number of days to retain telemetry data for this service.', - }) - @Column({ - type: ColumnType.Number, - nullable: true, - unique: false, - default: DEFAULT_RETENTION_IN_DAYS, - }) - public retainTelemetryDataForDays?: number = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Number, + title: "Retain Telemetry Data For Days", + description: "Number of days to retain telemetry data for this service.", + }) + @Column({ + type: ColumnType.Number, + nullable: true, + unique: false, + default: DEFAULT_RETENTION_IN_DAYS, + }) + public retainTelemetryDataForDays?: number = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.Number, - canReadOnRelationQuery: true, - title: 'Data Ingested (in GB)', - description: 'Data Ingested in GB this usage billing was generated for', - }) - @Column({ - nullable: false, - type: ColumnType.Decimal, - transformer: Decimal.getDatabaseTransformer(), - }) - public dataIngestedInGB?: Decimal = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.Number, + canReadOnRelationQuery: true, + title: "Data Ingested (in GB)", + description: "Data Ingested in GB this usage billing was generated for", + }) + @Column({ + nullable: false, + type: ColumnType.Decimal, + transformer: Decimal.getDatabaseTransformer(), + }) + public dataIngestedInGB?: Decimal = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.Number, - canReadOnRelationQuery: true, - title: 'Total Cost in USD', - description: 'Total Cost in USD this usage billing was generated for', - }) - @Column({ - nullable: false, - type: ColumnType.Decimal, - transformer: Decimal.getDatabaseTransformer(), - }) - public totalCostInUSD?: Decimal = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.Number, + canReadOnRelationQuery: true, + title: "Total Cost in USD", + description: "Total Cost in USD this usage billing was generated for", + }) + @Column({ + nullable: false, + type: ColumnType.Decimal, + transformer: Decimal.getDatabaseTransformer(), + }) + public totalCostInUSD?: Decimal = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - title: 'Reported to Billing Provider', - description: - 'Whether this usage billing was reported to billing provider or not (eg Stripe)', - }) - @Column({ - nullable: false, - type: ColumnType.Boolean, - default: false, - }) - public isReportedToBillingProvider?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + title: "Reported to Billing Provider", + description: + "Whether this usage billing was reported to billing provider or not (eg Stripe)", + }) + @Column({ + nullable: false, + type: ColumnType.Boolean, + default: false, + }) + public isReportedToBillingProvider?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'telemetryServiceId', - type: TableColumnType.Entity, - modelType: TelemetryService, - title: 'Telemetry Service', - description: - 'Relation to Telemetry Service Resource in which this object belongs', - }) - @ManyToOne( - () => { - return TelemetryService; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'telemetryServiceId' }) - public telemetryService?: TelemetryService = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "telemetryServiceId", + type: TableColumnType.Entity, + modelType: TelemetryService, + title: "Telemetry Service", + description: + "Relation to Telemetry Service Resource in which this object belongs", + }) + @ManyToOne( + () => { + return TelemetryService; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "telemetryServiceId" }) + public telemetryService?: TelemetryService = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - title: 'Telemetry Service ID', - description: - 'ID of your Telemetry Service resource where this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public telemetryServiceId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + title: "Telemetry Service ID", + description: + "ID of your Telemetry Service resource where this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public telemetryServiceId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.Date, - canReadOnRelationQuery: true, - title: 'Reported to Billing Provider At', - description: - 'When this usage billing was reported to billing provider or not (eg Stripe)', - }) - @Column({ - nullable: true, - type: ColumnType.Date, - }) - public reportedToBillingProviderAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.Date, + canReadOnRelationQuery: true, + title: "Reported to Billing Provider At", + description: + "When this usage billing was reported to billing provider or not (eg Stripe)", + }) + @Column({ + nullable: true, + type: ColumnType.Date, + }) + public reportedToBillingProviderAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - update: [], - }) - @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: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ManageProjectBilling, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ManageProjectBilling, + ], + 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; } diff --git a/Model/Models/User.ts b/Model/Models/User.ts index f60fd390c9..7f83a418a7 100644 --- a/Model/Models/User.ts +++ b/Model/Models/User.ts @@ -1,706 +1,706 @@ -import File from './File'; -import UserModel from 'Common/Models/UserModel'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import CompanySize from 'Common/Types/Company/CompanySize'; -import JobRole from 'Common/Types/Company/JobRole'; -import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import CurrentUserCanAccessRecordBy from 'Common/Types/Database/CurrentUserCanAccessRecordBy'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import Email from 'Common/Types/Email'; -import HashedString from 'Common/Types/HashedString'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Name from 'Common/Types/Name'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Phone from 'Common/Types/Phone'; -import Timezone from 'Common/Types/Timezone'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import File from "./File"; +import UserModel from "Common/Models/UserModel"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import CompanySize from "Common/Types/Company/CompanySize"; +import JobRole from "Common/Types/Company/JobRole"; +import AllowAccessIfSubscriptionIsUnpaid from "Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import CurrentUserCanAccessRecordBy from "Common/Types/Database/CurrentUserCanAccessRecordBy"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import Email from "Common/Types/Email"; +import HashedString from "Common/Types/HashedString"; +import IconProp from "Common/Types/Icon/IconProp"; +import Name from "Common/Types/Name"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Phone from "Common/Types/Phone"; +import Timezone from "Common/Types/Timezone"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation({ - isMasterAdminApiDocs: true, + isMasterAdminApiDocs: true, }) @AllowAccessIfSubscriptionIsUnpaid() @TableAccessControl({ - create: [Permission.Public], - read: [Permission.CurrentUser], - delete: [Permission.CurrentUser], - update: [Permission.CurrentUser], + create: [Permission.Public], + read: [Permission.CurrentUser], + delete: [Permission.CurrentUser], + update: [Permission.CurrentUser], }) -@CrudApiEndpoint(new Route('/user')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/user")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'User', + name: "User", }) @TableMetadata({ - tableName: 'User', - singularName: 'User', - pluralName: 'Users', - icon: IconProp.User, - tableDescription: 'A signed up or invited OneUptime user.', + tableName: "User", + singularName: "User", + pluralName: "Users", + icon: IconProp.User, + tableDescription: "A signed up or invited OneUptime user.", }) -@CurrentUserCanAccessRecordBy('_id') +@CurrentUserCanAccessRecordBy("_id") class User extends UserModel { - @ColumnAccessControl({ - create: [Permission.Public], - read: [Permission.CurrentUser], - update: [Permission.CurrentUser], - }) - @TableColumn({ type: TableColumnType.Name, canReadOnRelationQuery: true }) - @Column({ - type: ColumnType.Name, - length: ColumnLength.Name, - nullable: true, - unique: false, - transformer: Name.getDatabaseTransformer(), - }) - public name?: Name = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ - title: 'Email', - required: true, - unique: true, - type: TableColumnType.Email, - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.Email, - length: ColumnLength.Email, - unique: true, - nullable: false, - transformer: Email.getDatabaseTransformer(), - }) - public email?: Email = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ type: TableColumnType.Email }) - @Column({ - type: ColumnType.Email, - length: ColumnLength.Email, - unique: false, - nullable: true, - transformer: Email.getDatabaseTransformer(), - }) - public newUnverifiedTemporaryEmail?: string = undefined; - - @Index() - @ColumnAccessControl({ - create: [Permission.User], - read: [], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - unique: true, - }) - public slug?: string = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ - title: 'Password', - hashed: true, - type: TableColumnType.HashedString, - }) - @Column({ - type: ColumnType.HashedString, - length: ColumnLength.HashedString, - unique: false, - nullable: true, - transformer: HashedString.getDatabaseTransformer(), - }) - public password?: HashedString = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - - update: [], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isEmailVerified?: boolean = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public companyName?: string = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public jobRole?: JobRole = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public companySize?: CompanySize = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public referral?: string = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ type: TableColumnType.Phone }) - @Column({ - type: ColumnType.Phone, - length: ColumnLength.Phone, - nullable: true, - unique: false, - transformer: Phone.getDatabaseTransformer(), - }) - public companyPhoneNumber?: Phone = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ - manyToOneRelationColumn: 'profilePictureId', - type: TableColumnType.Entity, - modelType: File, - }) - @ManyToOne( - () => { - return File; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'delete', - } - ) - @JoinColumn({ name: 'profilePictureId' }) - public profilePictureFile?: File = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public profilePictureId?: ObjectID = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.Boolean, - default: false, - nullable: false, - unique: false, - }) - public twoFactorAuthEnabled?: boolean = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public twoFactorSecretCode?: string = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.ShortURL }) - @Column({ - type: ColumnType.ShortURL, - length: ColumnLength.ShortURL, - nullable: true, - unique: false, - transformer: URL.getDatabaseTransformer(), - }) - public twoFactorAuthUrl?: URL = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - - update: [], - }) - @TableColumn({ type: TableColumnType.Array }) - @Column({ - type: ColumnType.Array, - nullable: true, - unique: false, - }) - public backupCodes?: Array<string> = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public jwtRefreshToken?: string = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public paymentProviderCustomerId?: string = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public resetPasswordToken?: string = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public resetPasswordExpires?: Date = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public timezone?: Timezone = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public lastActive?: Date = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.ShortText }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - nullable: true, - unique: false, - }) - public promotionName?: string = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CustomerSupport], - - update: [Permission.CustomerSupport], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public isDisabled?: boolean = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public paymentFailedDate?: Date = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public isMasterAdmin?: boolean = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CustomerSupport], - - update: [Permission.CustomerSupport], - }) - @TableColumn({ - isDefaultValueColumn: true, - required: true, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - nullable: false, - unique: false, - default: false, - }) - public isBlocked?: boolean = undefined; - - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - - update: [Permission.CurrentUser], - }) - @TableColumn({ type: TableColumnType.Phone }) - @Column({ - type: ColumnType.Phone, - length: ColumnLength.Phone, - nullable: true, - unique: false, - }) - public alertPhoneNumber?: Phone = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.OTP }) - @Column({ - type: ColumnType.OTP, - length: ColumnLength.OTP, - nullable: true, - unique: false, - }) - public alertPhoneVerificationCode?: string = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmSource?: string = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmMedium?: string = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmCampaign?: string = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmTerm?: string = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmContent?: string = undefined; - - @ColumnAccessControl({ - create: [Permission.Public], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.LongText }) - @Column({ - type: ColumnType.LongText, - length: ColumnLength.LongText, - nullable: true, - unique: false, - }) - public utmUrl?: string = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public alertPhoneVerificationCodeRequestTime?: Date = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - - update: [], - }) - @TableColumn({ type: TableColumnType.Phone }) - @Column({ - type: ColumnType.Phone, - length: ColumnLength.Phone, - nullable: true, - unique: false, - }) - public tempAlertPhoneNumber?: Phone = undefined; - - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - 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; + @ColumnAccessControl({ + create: [Permission.Public], + read: [Permission.CurrentUser], + update: [Permission.CurrentUser], + }) + @TableColumn({ type: TableColumnType.Name, canReadOnRelationQuery: true }) + @Column({ + type: ColumnType.Name, + length: ColumnLength.Name, + nullable: true, + unique: false, + transformer: Name.getDatabaseTransformer(), + }) + public name?: Name = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ + title: "Email", + required: true, + unique: true, + type: TableColumnType.Email, + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.Email, + length: ColumnLength.Email, + unique: true, + nullable: false, + transformer: Email.getDatabaseTransformer(), + }) + public email?: Email = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ type: TableColumnType.Email }) + @Column({ + type: ColumnType.Email, + length: ColumnLength.Email, + unique: false, + nullable: true, + transformer: Email.getDatabaseTransformer(), + }) + public newUnverifiedTemporaryEmail?: string = undefined; + + @Index() + @ColumnAccessControl({ + create: [Permission.User], + read: [], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + unique: true, + }) + public slug?: string = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ + title: "Password", + hashed: true, + type: TableColumnType.HashedString, + }) + @Column({ + type: ColumnType.HashedString, + length: ColumnLength.HashedString, + unique: false, + nullable: true, + transformer: HashedString.getDatabaseTransformer(), + }) + public password?: HashedString = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + + update: [], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isEmailVerified?: boolean = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public companyName?: string = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public jobRole?: JobRole = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public companySize?: CompanySize = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public referral?: string = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ type: TableColumnType.Phone }) + @Column({ + type: ColumnType.Phone, + length: ColumnLength.Phone, + nullable: true, + unique: false, + transformer: Phone.getDatabaseTransformer(), + }) + public companyPhoneNumber?: Phone = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ + manyToOneRelationColumn: "profilePictureId", + type: TableColumnType.Entity, + modelType: File, + }) + @ManyToOne( + () => { + return File; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "delete", + }, + ) + @JoinColumn({ name: "profilePictureId" }) + public profilePictureFile?: File = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public profilePictureId?: ObjectID = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.Boolean, + default: false, + nullable: false, + unique: false, + }) + public twoFactorAuthEnabled?: boolean = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public twoFactorSecretCode?: string = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.ShortURL }) + @Column({ + type: ColumnType.ShortURL, + length: ColumnLength.ShortURL, + nullable: true, + unique: false, + transformer: URL.getDatabaseTransformer(), + }) + public twoFactorAuthUrl?: URL = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + + update: [], + }) + @TableColumn({ type: TableColumnType.Array }) + @Column({ + type: ColumnType.Array, + nullable: true, + unique: false, + }) + public backupCodes?: Array<string> = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public jwtRefreshToken?: string = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public paymentProviderCustomerId?: string = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public resetPasswordToken?: string = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public resetPasswordExpires?: Date = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public timezone?: Timezone = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public lastActive?: Date = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.ShortText }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + nullable: true, + unique: false, + }) + public promotionName?: string = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CustomerSupport], + + update: [Permission.CustomerSupport], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isDisabled?: boolean = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public paymentFailedDate?: Date = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isMasterAdmin?: boolean = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CustomerSupport], + + update: [Permission.CustomerSupport], + }) + @TableColumn({ + isDefaultValueColumn: true, + required: true, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + unique: false, + default: false, + }) + public isBlocked?: boolean = undefined; + + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + + update: [Permission.CurrentUser], + }) + @TableColumn({ type: TableColumnType.Phone }) + @Column({ + type: ColumnType.Phone, + length: ColumnLength.Phone, + nullable: true, + unique: false, + }) + public alertPhoneNumber?: Phone = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.OTP }) + @Column({ + type: ColumnType.OTP, + length: ColumnLength.OTP, + nullable: true, + unique: false, + }) + public alertPhoneVerificationCode?: string = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmSource?: string = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmMedium?: string = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmCampaign?: string = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmTerm?: string = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmContent?: string = undefined; + + @ColumnAccessControl({ + create: [Permission.Public], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.LongText }) + @Column({ + type: ColumnType.LongText, + length: ColumnLength.LongText, + nullable: true, + unique: false, + }) + public utmUrl?: string = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public alertPhoneVerificationCodeRequestTime?: Date = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + + update: [], + }) + @TableColumn({ type: TableColumnType.Phone }) + @Column({ + type: ColumnType.Phone, + length: ColumnLength.Phone, + nullable: true, + unique: false, + }) + public tempAlertPhoneNumber?: Phone = undefined; + + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; } export default User; diff --git a/Model/Models/UserCall.ts b/Model/Models/UserCall.ts index 875f2de3cf..c34f6f062c 100644 --- a/Model/Models/UserCall.ts +++ b/Model/Models/UserCall.ts @@ -1,287 +1,285 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import CurrentUserCanAccessRecordBy from 'Common/Types/Database/CurrentUserCanAccessRecordBy'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Phone from 'Common/Types/Phone'; -import Text from 'Common/Types/Text'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import AllowAccessIfSubscriptionIsUnpaid from "Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import CurrentUserCanAccessRecordBy from "Common/Types/Database/CurrentUserCanAccessRecordBy"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Phone from "Common/Types/Phone"; +import Text from "Common/Types/Text"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@TenantColumn('projectId') +@TenantColumn("projectId") @AllowAccessIfSubscriptionIsUnpaid() @TableAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - delete: [Permission.CurrentUser], - update: [Permission.CurrentUser], + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + delete: [Permission.CurrentUser], + update: [Permission.CurrentUser], }) -@CrudApiEndpoint(new Route('/user-call')) +@CrudApiEndpoint(new Route("/user-call")) @Entity({ - name: 'UserCall', + name: "UserCall", }) @TableMetadata({ - tableName: 'UserCall', - singularName: 'Phone Number for Calls', - pluralName: 'Phone Numbers for Calls', - icon: IconProp.Call, - tableDescription: 'Phone Number which will be used for call notifications.', + tableName: "UserCall", + singularName: "Phone Number for Calls", + pluralName: "Phone Numbers for Calls", + icon: IconProp.Call, + tableDescription: "Phone Number which will be used for call notifications.", }) -@CurrentUserCanAccessRecordBy('userId') +@CurrentUserCanAccessRecordBy("userId") class UserCall extends BaseModel { - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - title: 'Phone', - required: true, - unique: false, - type: TableColumnType.Phone, - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.Phone, - length: ColumnLength.Phone, - unique: false, - nullable: false, - transformer: Phone.getDatabaseTransformer(), - }) - public phone?: Phone = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + title: "Phone", + required: true, + unique: false, + type: TableColumnType.Phone, + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.Phone, + length: ColumnLength.Phone, + unique: false, + nullable: false, + transformer: Phone.getDatabaseTransformer(), + }) + public phone?: Phone = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'user', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'Relation to User who this email belongs to', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "user", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "Relation to User who this email belongs to", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User ID', - description: 'User ID who this email belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - @Index() - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User ID", + description: "User ID who this email belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + @Index() + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - title: 'Is Verified', - description: 'Is this verified?', - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isVerified?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + title: "Is Verified", + description: "Is this verified?", + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isVerified?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - title: 'Verification Code', - description: 'Temporary Verification Code', - isDefaultValueColumn: true, - required: true, - type: TableColumnType.ShortText, - forceGetDefaultValueOnCreate: () => { - return Text.generateRandomNumber(6); - }, - }) - @Column({ - type: ColumnType.ShortText, - nullable: false, - length: ColumnLength.ShortText, - }) - public verificationCode?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + title: "Verification Code", + description: "Temporary Verification Code", + isDefaultValueColumn: true, + required: true, + type: TableColumnType.ShortText, + forceGetDefaultValueOnCreate: () => { + return Text.generateRandomNumber(6); + }, + }) + @Column({ + type: ColumnType.ShortText, + nullable: false, + length: ColumnLength.ShortText, + }) + public verificationCode?: string = undefined; } export default UserCall; diff --git a/Model/Models/UserEmail.ts b/Model/Models/UserEmail.ts index 60a9260a5a..d868ffaa02 100644 --- a/Model/Models/UserEmail.ts +++ b/Model/Models/UserEmail.ts @@ -1,287 +1,285 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import CurrentUserCanAccessRecordBy from 'Common/Types/Database/CurrentUserCanAccessRecordBy'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import Email from 'Common/Types/Email'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Text from 'Common/Types/Text'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import AllowAccessIfSubscriptionIsUnpaid from "Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import CurrentUserCanAccessRecordBy from "Common/Types/Database/CurrentUserCanAccessRecordBy"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import Email from "Common/Types/Email"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Text from "Common/Types/Text"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@TenantColumn('projectId') +@TenantColumn("projectId") @AllowAccessIfSubscriptionIsUnpaid() @TableAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - delete: [Permission.CurrentUser], - update: [Permission.CurrentUser], + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + delete: [Permission.CurrentUser], + update: [Permission.CurrentUser], }) -@CrudApiEndpoint(new Route('/user-email')) +@CrudApiEndpoint(new Route("/user-email")) @Entity({ - name: 'UserEmail', + name: "UserEmail", }) @TableMetadata({ - tableName: 'UserEmail', - singularName: 'Email for Notifications', - pluralName: 'Emails for Notifications', - icon: IconProp.Email, - tableDescription: 'Emails which will be used for notifications.', + tableName: "UserEmail", + singularName: "Email for Notifications", + pluralName: "Emails for Notifications", + icon: IconProp.Email, + tableDescription: "Emails which will be used for notifications.", }) -@CurrentUserCanAccessRecordBy('userId') +@CurrentUserCanAccessRecordBy("userId") class UserEmail extends BaseModel { - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - title: 'Email', - required: true, - unique: false, - type: TableColumnType.Email, - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.Email, - length: ColumnLength.Email, - unique: false, - nullable: false, - transformer: Email.getDatabaseTransformer(), - }) - public email?: Email = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + title: "Email", + required: true, + unique: false, + type: TableColumnType.Email, + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.Email, + length: ColumnLength.Email, + unique: false, + nullable: false, + transformer: Email.getDatabaseTransformer(), + }) + public email?: Email = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'user', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'Relation to User who this email belongs to', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "user", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "Relation to User who this email belongs to", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User ID', - description: 'User ID who this email belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - @Index() - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User ID", + description: "User ID who this email belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + @Index() + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - title: 'Is Verified', - description: 'Is this verified?', - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isVerified?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + title: "Is Verified", + description: "Is this verified?", + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isVerified?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - title: 'Verification Code', - description: 'Temporary Verification Code', - isDefaultValueColumn: true, - required: true, - type: TableColumnType.ShortText, - forceGetDefaultValueOnCreate: () => { - return Text.generateRandomNumber(6); - }, - }) - @Column({ - type: ColumnType.ShortText, - nullable: false, - length: ColumnLength.ShortText, - }) - public verificationCode?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + title: "Verification Code", + description: "Temporary Verification Code", + isDefaultValueColumn: true, + required: true, + type: TableColumnType.ShortText, + forceGetDefaultValueOnCreate: () => { + return Text.generateRandomNumber(6); + }, + }) + @Column({ + type: ColumnType.ShortText, + nullable: false, + length: ColumnLength.ShortText, + }) + public verificationCode?: string = undefined; } export default UserEmail; diff --git a/Model/Models/UserNotificationRule.ts b/Model/Models/UserNotificationRule.ts index c0ca941617..c905c944e4 100644 --- a/Model/Models/UserNotificationRule.ts +++ b/Model/Models/UserNotificationRule.ts @@ -1,460 +1,455 @@ -import IncidentSeverity from './IncidentSeverity'; -import Project from './Project'; -import User from './User'; -import UserCall from './UserCall'; -import UserEmail from './UserEmail'; -import UserSMS from './UserSMS'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import CurrentUserCanAccessRecordBy from 'Common/Types/Database/CurrentUserCanAccessRecordBy'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import NotificationRuleType from 'Common/Types/NotificationRule/NotificationRuleType'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import IncidentSeverity from "./IncidentSeverity"; +import Project from "./Project"; +import User from "./User"; +import UserCall from "./UserCall"; +import UserEmail from "./UserEmail"; +import UserSMS from "./UserSMS"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import AllowAccessIfSubscriptionIsUnpaid from "Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import CurrentUserCanAccessRecordBy from "Common/Types/Database/CurrentUserCanAccessRecordBy"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import NotificationRuleType from "Common/Types/NotificationRule/NotificationRuleType"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@TenantColumn('projectId') +@TenantColumn("projectId") @AllowAccessIfSubscriptionIsUnpaid() @TableAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - delete: [Permission.CurrentUser], - update: [Permission.CurrentUser], + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + delete: [Permission.CurrentUser], + update: [Permission.CurrentUser], }) -@CrudApiEndpoint(new Route('/user-notification-rule')) +@CrudApiEndpoint(new Route("/user-notification-rule")) @Entity({ - name: 'UserNotificationRule', + name: "UserNotificationRule", }) @TableMetadata({ - tableName: 'UserNotificationRule', - singularName: 'Notification Rule', - pluralName: 'Notification Rules', - icon: IconProp.Email, - tableDescription: 'Rules which will be used to send notifications.', + tableName: "UserNotificationRule", + singularName: "Notification Rule", + pluralName: "Notification Rules", + icon: IconProp.Email, + tableDescription: "Rules which will be used to send notifications.", }) -@CurrentUserCanAccessRecordBy('userId') +@CurrentUserCanAccessRecordBy("userId") class UserNotificationRule extends BaseModel { - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - title: 'Rule Type', - required: true, - unique: false, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - unique: false, - nullable: false, - }) - public ruleType?: NotificationRuleType = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + title: "Rule Type", + required: true, + unique: false, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + unique: false, + nullable: false, + }) + public ruleType?: NotificationRuleType = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'user', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'Relation to User who this email belongs to', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "user", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "Relation to User who this email belongs to", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User ID', - description: 'User ID who this email belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - @Index() - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User ID", + description: "User ID who this email belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + @Index() + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userCallId', - type: TableColumnType.Entity, - modelType: UserCall, - title: 'User Call', - description: - 'Relation to User Call Resource in which this object belongs', - }) - @ManyToOne( - () => { - return UserCall; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userCallId' }) - public userCall?: UserCall = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userCallId", + type: TableColumnType.Entity, + modelType: UserCall, + title: "User Call", + description: "Relation to User Call Resource in which this object belongs", + }) + @ManyToOne( + () => { + return UserCall; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userCallId" }) + public userCall?: UserCall = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'User Call ID', - description: 'ID of User Call in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userCallId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "User Call ID", + description: "ID of User Call in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userCallId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userSmsId', - type: TableColumnType.Entity, - modelType: UserSMS, - title: 'User SMS', - description: - 'Relation to User SMS Resource in which this object belongs', - }) - @ManyToOne( - () => { - return UserSMS; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userSmsId' }) - public userSms?: UserSMS = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userSmsId", + type: TableColumnType.Entity, + modelType: UserSMS, + title: "User SMS", + description: "Relation to User SMS Resource in which this object belongs", + }) + @ManyToOne( + () => { + return UserSMS; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userSmsId" }) + public userSms?: UserSMS = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'User SMS ID', - description: 'ID of User SMS in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userSmsId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "User SMS ID", + description: "ID of User SMS in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userSmsId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userEmailId', - type: TableColumnType.Entity, - modelType: UserEmail, - title: 'User Email', - description: - 'Relation to User Email Resource in which this object belongs', - }) - @ManyToOne( - () => { - return UserEmail; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userEmailId' }) - public userEmail?: UserEmail = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userEmailId", + type: TableColumnType.Entity, + modelType: UserEmail, + title: "User Email", + description: "Relation to User Email Resource in which this object belongs", + }) + @ManyToOne( + () => { + return UserEmail; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userEmailId" }) + public userEmail?: UserEmail = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'User Email ID', - description: 'ID of User Email in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userEmailId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "User Email ID", + description: "ID of User Email in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userEmailId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [Permission.CurrentUser], - }) - @Index() - @TableColumn({ - type: TableColumnType.Number, - required: true, - isDefaultValueColumn: true, - canReadOnRelationQuery: true, - title: 'Notify After Minutes', - description: - 'How long should we wait before sending a notification to the user after the event has occurred?', - }) - @Column({ - type: ColumnType.Number, - nullable: false, - default: 0, - }) - public notifyAfterMinutes?: number = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [Permission.CurrentUser], + }) + @Index() + @TableColumn({ + type: TableColumnType.Number, + required: true, + isDefaultValueColumn: true, + canReadOnRelationQuery: true, + title: "Notify After Minutes", + description: + "How long should we wait before sending a notification to the user after the event has occurred?", + }) + @Column({ + type: ColumnType.Number, + nullable: false, + default: 0, + }) + public notifyAfterMinutes?: number = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'incidentSeverityId', - type: TableColumnType.Entity, - modelType: IncidentSeverity, - title: 'Incident Severity', - description: - 'Relation to Incident Severity Resource in which this object belongs', - }) - @ManyToOne( - () => { - return IncidentSeverity; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'incidentSeverityId' }) - public incidentSeverity?: IncidentSeverity = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentSeverityId", + type: TableColumnType.Entity, + modelType: IncidentSeverity, + title: "Incident Severity", + description: + "Relation to Incident Severity Resource in which this object belongs", + }) + @ManyToOne( + () => { + return IncidentSeverity; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentSeverityId" }) + public incidentSeverity?: IncidentSeverity = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Incident Severity ID', - description: 'ID of Incident Severity in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public incidentSeverityId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Incident Severity ID", + description: "ID of Incident Severity in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentSeverityId?: ObjectID = undefined; } export default UserNotificationRule; diff --git a/Model/Models/UserNotificationSetting.ts b/Model/Models/UserNotificationSetting.ts index 55bcd9ddcd..61fe524525 100644 --- a/Model/Models/UserNotificationSetting.ts +++ b/Model/Models/UserNotificationSetting.ts @@ -1,282 +1,280 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import CurrentUserCanAccessRecordBy from 'Common/Types/Database/CurrentUserCanAccessRecordBy'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import NotificationSettingEventType from 'Common/Types/NotificationSetting/NotificationSettingEventType'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import AllowAccessIfSubscriptionIsUnpaid from "Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import CurrentUserCanAccessRecordBy from "Common/Types/Database/CurrentUserCanAccessRecordBy"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import NotificationSettingEventType from "Common/Types/NotificationSetting/NotificationSettingEventType"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@TenantColumn('projectId') +@TenantColumn("projectId") @AllowAccessIfSubscriptionIsUnpaid() @TableAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - delete: [Permission.CurrentUser], - update: [Permission.CurrentUser], + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + delete: [Permission.CurrentUser], + update: [Permission.CurrentUser], }) -@CrudApiEndpoint(new Route('/user-notification-setting')) +@CrudApiEndpoint(new Route("/user-notification-setting")) @Entity({ - name: 'UserNotificationSetting', + name: "UserNotificationSetting", }) @TableMetadata({ - tableName: 'UserNotificationSetting', - singularName: 'Notification Setting', - pluralName: 'Notification Settings', - icon: IconProp.Bell, - tableDescription: 'Settings which will be used to send notifications.', + tableName: "UserNotificationSetting", + singularName: "Notification Setting", + pluralName: "Notification Settings", + icon: IconProp.Bell, + tableDescription: "Settings which will be used to send notifications.", }) -@CurrentUserCanAccessRecordBy('userId') +@CurrentUserCanAccessRecordBy("userId") class UserNotificationSetting extends BaseModel { - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - update: [Permission.CurrentUser], - }) - @TableColumn({ - title: 'Rule Type', - required: true, - unique: false, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - unique: false, - nullable: false, - }) - public eventType?: NotificationSettingEventType = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [Permission.CurrentUser], + }) + @TableColumn({ + title: "Rule Type", + required: true, + unique: false, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + unique: false, + nullable: false, + }) + public eventType?: NotificationSettingEventType = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'user', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'Relation to User who this email belongs to', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "user", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "Relation to User who this email belongs to", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User ID', - description: 'User ID who this email belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - @Index() - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User ID", + description: "User ID who this email belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + @Index() + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [Permission.CurrentUser], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public alertByEmail?: boolean = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [Permission.CurrentUser], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public alertByEmail?: boolean = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [Permission.CurrentUser], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public alertBySMS?: boolean = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [Permission.CurrentUser], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public alertBySMS?: boolean = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [Permission.CurrentUser], - }) - @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public alertByCall?: boolean = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [Permission.CurrentUser], + }) + @TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public alertByCall?: boolean = undefined; } export default UserNotificationSetting; diff --git a/Model/Models/UserOnCallLog.ts b/Model/Models/UserOnCallLog.ts index 2da4628cc2..89b69fd80c 100644 --- a/Model/Models/UserOnCallLog.ts +++ b/Model/Models/UserOnCallLog.ts @@ -1,702 +1,700 @@ -import Incident from './Incident'; -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import OnCallDutyPolicyEscalationRule from './OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicyExecutionLog from './OnCallDutyPolicyExecutionLog'; -import OnCallDutyPolicyExecutionLogTimeline from './OnCallDutyPolicyExecutionLogTimeline'; -import OnCallDutyPolicySchedule from './OnCallDutyPolicySchedule'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import UserNotificationExecutionStatus from 'Common/Types/UserNotification/UserNotificationExecutionStatus'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Incident from "./Incident"; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import OnCallDutyPolicyEscalationRule from "./OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicyExecutionLog from "./OnCallDutyPolicyExecutionLog"; +import OnCallDutyPolicyExecutionLogTimeline from "./OnCallDutyPolicyExecutionLogTimeline"; +import OnCallDutyPolicySchedule from "./OnCallDutyPolicySchedule"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import UserNotificationExecutionStatus from "Common/Types/UserNotification/UserNotificationExecutionStatus"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @TableAccessControl({ - create: [], - read: [Permission.CurrentUser], - delete: [], - update: [], + create: [], + read: [Permission.CurrentUser], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/user-notification-log')) +@CrudApiEndpoint(new Route("/user-notification-log")) @Entity({ - name: 'UserOnCallLog', + name: "UserOnCallLog", }) @TableMetadata({ - tableName: 'UserOnCallLog', - singularName: 'User Notification Log', - pluralName: 'User Notification Logs', - icon: IconProp.Logs, - tableDescription: 'Log events for user notifications', + tableName: "UserOnCallLog", + singularName: "User Notification Log", + pluralName: "User Notification Logs", + icon: IconProp.Logs, + tableDescription: "Log events for user notifications", }) export default class UserOnCallLog extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'Relation to User who this log belongs to', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: false, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "Relation to User who this log belongs to", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: false, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User ID', - description: 'User ID who this log belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User ID", + description: "User ID who this log belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userBelongsToTeamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Which team did the user belong to when the alert was sent?', - description: - 'Which team did the user belong to when the alert was sent?', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userBelongsToTeamId' }) - public userBelongsToTeam?: Team = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userBelongsToTeamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Which team did the user belong to when the alert was sent?", + description: "Which team did the user belong to when the alert was sent?", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userBelongsToTeamId" }) + public userBelongsToTeam?: Team = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Which team did the user belong to when the alert was sent?', - description: - 'Which team did the user belong to when the alert was sent?', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userBelongsToTeamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Which team did the user belong to when the alert was sent?", + description: "Which team did the user belong to when the alert was sent?", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userBelongsToTeamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - 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.CurrentUser], + 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.CurrentUser], - 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.CurrentUser], + 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.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicy, - title: 'On-Call Policy', - description: - 'Relation to On-Call Policy which belongs to this execution log event.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicy; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyId' }) - public onCallDutyPolicy?: OnCallDutyPolicy = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicy, + title: "On-Call Policy", + description: + "Relation to On-Call Policy which belongs to this execution log event.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicy; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyId" }) + public onCallDutyPolicy?: OnCallDutyPolicy = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy ID', - description: - 'ID of your On-Call Policy which belongs to this execution log event.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy ID", + description: + "ID of your On-Call Policy which belongs to this execution log event.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyExecutionLogId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyExecutionLog, - title: 'On-Call Policy Execution Log', - description: - 'Relation to On-Call Policy Execution Log which belongs to this execution log event.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyExecutionLog; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyExecutionLogId' }) - public onCallDutyPolicyExecutionLog?: OnCallDutyPolicyExecutionLog = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyExecutionLogId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyExecutionLog, + title: "On-Call Policy Execution Log", + description: + "Relation to On-Call Policy Execution Log which belongs to this execution log event.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyExecutionLog; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyExecutionLogId" }) + public onCallDutyPolicyExecutionLog?: OnCallDutyPolicyExecutionLog = + undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Execution Log ID', - description: - 'ID of your On-Call Policy execution log which belongs to this log event.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyExecutionLogId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Execution Log ID", + description: + "ID of your On-Call Policy execution log which belongs to this log event.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyExecutionLogId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyEscalationRuleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyEscalationRule, - title: 'On-Call Policy Escalation Rule', - description: - 'Relation to On-Call Policy Escalation Rule which belongs to this execution log event.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyEscalationRule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyEscalationRuleId' }) - public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyEscalationRuleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyEscalationRule, + title: "On-Call Policy Escalation Rule", + description: + "Relation to On-Call Policy Escalation Rule which belongs to this execution log event.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyEscalationRule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyEscalationRuleId" }) + public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = + undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Escalation Rule ID', - description: - 'ID of your On-Call Policy Escalation Rule which belongs to this log event.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Escalation Rule ID", + description: + "ID of your On-Call Policy Escalation Rule which belongs to this log event.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'triggeredByIncidentId', - type: TableColumnType.Entity, - modelType: Incident, - title: 'Triggered By Incident', - description: - 'Relation to Incident which triggered this on-call duty policy.', - }) - @ManyToOne( - () => { - return Incident; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'triggeredByIncidentId' }) - public triggeredByIncident?: Incident = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "triggeredByIncidentId", + type: TableColumnType.Entity, + modelType: Incident, + title: "Triggered By Incident", + description: + "Relation to Incident which triggered this on-call duty policy.", + }) + @ManyToOne( + () => { + return Incident; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "triggeredByIncidentId" }) + public triggeredByIncident?: Incident = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Triggered By Incident ID', - description: - 'ID of the incident which triggered this on-call escalation policy.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public triggeredByIncidentId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Triggered By Incident ID", + description: + "ID of the incident which triggered this on-call escalation policy.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public triggeredByIncidentId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Status', - description: 'Status of this execution', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public status?: UserNotificationExecutionStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Status", + description: "Status of this execution", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public status?: UserNotificationExecutionStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Notification Event Type', - description: 'Notification Event Type of this execution', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public userNotificationEventType?: UserNotificationEventType = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Notification Event Type", + description: "Notification Event Type of this execution", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public userNotificationEventType?: UserNotificationEventType = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyExecutionLogTimelineId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyExecutionLogTimeline, - title: 'On-Call Policy Execution Log Timeline', - description: - 'Relation to On-Call Policy Execution Log Timeline where this timeline event belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyExecutionLogTimeline; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyExecutionLogTimelineId' }) - public onCallDutyPolicyExecutionLogTimeline?: OnCallDutyPolicyExecutionLogTimeline = - undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyExecutionLogTimelineId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyExecutionLogTimeline, + title: "On-Call Policy Execution Log Timeline", + description: + "Relation to On-Call Policy Execution Log Timeline where this timeline event belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyExecutionLogTimeline; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyExecutionLogTimelineId" }) + public onCallDutyPolicyExecutionLogTimeline?: OnCallDutyPolicyExecutionLogTimeline = + undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Execution Log ID', - description: - 'ID of your On-Call Policy Execution Log where this timeline event belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyExecutionLogTimelineId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Execution Log ID", + description: + "ID of your On-Call Policy Execution Log where this timeline event belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyExecutionLogTimelineId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - title: 'Status Message', - description: 'Status message of this execution', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public statusMessage?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + title: "Status Message", + description: "Status message of this execution", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public statusMessage?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @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: [], + read: [Permission.CurrentUser], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - 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; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + 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; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'acknowledgedByUserId', - type: TableColumnType.Entity, - title: 'Acknowledged by User', - description: - 'Relation to User who acknowledged this policy execution (if this policy was acknowledged by a User)', - }) - @ManyToOne( - () => { - return User; - }, - { - cascade: false, - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'acknowledgedByUserId' }) - public acknowledgedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "acknowledgedByUserId", + type: TableColumnType.Entity, + title: "Acknowledged by User", + description: + "Relation to User who acknowledged this policy execution (if this policy was acknowledged by a User)", + }) + @ManyToOne( + () => { + return User; + }, + { + cascade: false, + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "acknowledgedByUserId" }) + public acknowledgedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Deleted by User ID', - description: - 'User ID who acknowledged this object (if this object was acknowledged by a User)', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public acknowledgedByUserId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Deleted by User ID", + description: + "User ID who acknowledged this object (if this object was acknowledged by a User)", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public acknowledgedByUserId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ type: TableColumnType.Date }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public acknowledgedAt?: Date = undefined; + update: [], + }) + @TableColumn({ type: TableColumnType.Date }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public acknowledgedAt?: Date = undefined; - /** - * - * In the format of { - * [notificationRuleId]: DateOfExecution, - * } - */ + /** + * + * In the format of { + * [notificationRuleId]: DateOfExecution, + * } + */ - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ type: TableColumnType.JSON }) - @Column({ - type: ColumnType.JSON, - nullable: true, - unique: false, - }) - public executedNotificationRules?: JSONObject = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ type: TableColumnType.JSON }) + @Column({ + type: ColumnType.JSON, + nullable: true, + unique: false, + }) + public executedNotificationRules?: JSONObject = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyScheduleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicySchedule, - title: 'On Call Schedule', - description: - 'Which schedule did the user belong to when the alert was sent?', - }) - @ManyToOne( - () => { - return OnCallDutyPolicySchedule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyScheduleId' }) - public onCallDutySchedule?: OnCallDutyPolicySchedule = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyScheduleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicySchedule, + title: "On Call Schedule", + description: + "Which schedule did the user belong to when the alert was sent?", + }) + @ManyToOne( + () => { + return OnCallDutyPolicySchedule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyScheduleId" }) + public onCallDutySchedule?: OnCallDutyPolicySchedule = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'On Call Schedule ID', - description: - 'Which schedule ID did the user belong to when the alert was sent?', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyScheduleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "On Call Schedule ID", + description: + "Which schedule ID did the user belong to when the alert was sent?", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyScheduleId?: ObjectID = undefined; } diff --git a/Model/Models/UserOnCallLogTimeline.ts b/Model/Models/UserOnCallLogTimeline.ts index 7a2fa9348e..7f86e6d591 100644 --- a/Model/Models/UserOnCallLogTimeline.ts +++ b/Model/Models/UserOnCallLogTimeline.ts @@ -1,855 +1,848 @@ -import Incident from './Incident'; -import OnCallDutyPolicy from './OnCallDutyPolicy'; -import OnCallDutyPolicyEscalationRule from './OnCallDutyPolicyEscalationRule'; -import OnCallDutyPolicyExecutionLog from './OnCallDutyPolicyExecutionLog'; -import OnCallDutyPolicyExecutionLogTimeline from './OnCallDutyPolicyExecutionLogTimeline'; -import Project from './Project'; -import Team from './Team'; -import User from './User'; -import UserCall from './UserCall'; -import UserEmail from './UserEmail'; -import UserNotificationRule from './UserNotificationRule'; -import UserOnCallLog from './UserOnCallLog'; -import UserSMS from './UserSMS'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType'; -import UserNotificationStatus from 'Common/Types/UserNotification/UserNotificationStatus'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Incident from "./Incident"; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import OnCallDutyPolicyEscalationRule from "./OnCallDutyPolicyEscalationRule"; +import OnCallDutyPolicyExecutionLog from "./OnCallDutyPolicyExecutionLog"; +import OnCallDutyPolicyExecutionLogTimeline from "./OnCallDutyPolicyExecutionLogTimeline"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import UserCall from "./UserCall"; +import UserEmail from "./UserEmail"; +import UserNotificationRule from "./UserNotificationRule"; +import UserOnCallLog from "./UserOnCallLog"; +import UserSMS from "./UserSMS"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import UserNotificationEventType from "Common/Types/UserNotification/UserNotificationEventType"; +import UserNotificationStatus from "Common/Types/UserNotification/UserNotificationStatus"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [], - read: [Permission.CurrentUser], - delete: [], - update: [], + create: [], + read: [Permission.CurrentUser], + delete: [], + update: [], }) -@CrudApiEndpoint(new Route('/user-notification-log-timeline')) +@CrudApiEndpoint(new Route("/user-notification-log-timeline")) @Entity({ - name: 'UserOnCallLogTimeline', + name: "UserOnCallLogTimeline", }) @TableMetadata({ - tableName: 'UserOnCallLogTimeline', - singularName: 'User On-Call Log Timeline', - pluralName: 'User On-Call Log Timelines', - icon: IconProp.Logs, - tableDescription: 'Timeline events for user on-call log.', + tableName: "UserOnCallLogTimeline", + singularName: "User On-Call Log Timeline", + pluralName: "User On-Call Log Timelines", + icon: IconProp.Logs, + tableDescription: "Timeline events for user on-call log.", }) export default class UserOnCallLogTimeline extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userId', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'Relation to User who this log belongs to', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userId", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "Relation to User who this log belongs to", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User ID', - description: 'User ID who this log belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User ID", + description: "User ID who this log belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - 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.CurrentUser], + 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.CurrentUser], - 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.CurrentUser], + 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.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userNotificationLogId', - type: TableColumnType.Entity, - modelType: UserOnCallLog, - title: 'User Notification Log', - description: - 'Relation to User Notification Log Resource in which this object belongs', - }) - @ManyToOne( - () => { - return UserOnCallLog; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userNotificationLogId' }) - public userOnCallLog?: UserOnCallLog = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userNotificationLogId", + type: TableColumnType.Entity, + modelType: UserOnCallLog, + title: "User Notification Log", + description: + "Relation to User Notification Log Resource in which this object belongs", + }) + @ManyToOne( + () => { + return UserOnCallLog; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userNotificationLogId" }) + public userOnCallLog?: UserOnCallLog = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'User Notification Log ID', - description: - 'ID of your OneUptime User Notification Log in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userNotificationLogId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "User Notification Log ID", + description: + "ID of your OneUptime User Notification Log in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userNotificationLogId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userNotificationRuleId', - type: TableColumnType.Entity, - modelType: UserNotificationRule, - title: 'User Notification Rule', - description: - 'Relation to User Notification Rule Resource in which this object belongs', - }) - @ManyToOne( - () => { - return UserNotificationRule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userNotificationRuleId' }) - public userNotificationRule?: UserNotificationRule = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userNotificationRuleId", + type: TableColumnType.Entity, + modelType: UserNotificationRule, + title: "User Notification Rule", + description: + "Relation to User Notification Rule Resource in which this object belongs", + }) + @ManyToOne( + () => { + return UserNotificationRule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userNotificationRuleId" }) + public userNotificationRule?: UserNotificationRule = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'User Notification Rule ID', - description: - 'ID of your OneUptime User Notification Rule in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userNotificationRuleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "User Notification Rule ID", + description: + "ID of your OneUptime User Notification Rule in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userNotificationRuleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicy, - title: 'OnCallDutyPolicy', - description: - 'Relation to on-call duty policy Resource in which this object belongs', - }) - @ManyToOne( - () => { - return OnCallDutyPolicy; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyId' }) - public onCallDutyPolicy?: OnCallDutyPolicy = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicy, + title: "OnCallDutyPolicy", + description: + "Relation to on-call duty policy Resource in which this object belongs", + }) + @ManyToOne( + () => { + return OnCallDutyPolicy; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyId" }) + public onCallDutyPolicy?: OnCallDutyPolicy = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'OnCallDutyPolicy ID', - description: - 'ID of your OneUptime on-call duty policy in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "OnCallDutyPolicy ID", + description: + "ID of your OneUptime on-call duty policy in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'triggeredByIncidentId', - type: TableColumnType.Entity, - modelType: Incident, - title: 'Incident', - description: - 'Relation to Incident Resource in which this object belongs', - }) - @ManyToOne( - () => { - return Incident; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'triggeredByIncidentId' }) - public triggeredByIncident?: Incident = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "triggeredByIncidentId", + type: TableColumnType.Entity, + modelType: Incident, + title: "Incident", + description: "Relation to Incident Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Incident; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "triggeredByIncidentId" }) + public triggeredByIncident?: Incident = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Incident ID', - description: - 'ID of your OneUptime Incident in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public triggeredByIncidentId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Incident ID", + description: "ID of your OneUptime Incident in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public triggeredByIncidentId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyExecutionLogId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyExecutionLog, - title: 'On-Call Policy Execution Log', - description: - 'Relation to On-Call Policy Execution Log where this timeline event belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyExecutionLog; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyExecutionLogId' }) - public onCallDutyPolicyExecutionLog?: OnCallDutyPolicyExecutionLog = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyExecutionLogId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyExecutionLog, + title: "On-Call Policy Execution Log", + description: + "Relation to On-Call Policy Execution Log where this timeline event belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyExecutionLog; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyExecutionLogId" }) + public onCallDutyPolicyExecutionLog?: OnCallDutyPolicyExecutionLog = + undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Execution Log Timeline ID', - description: - 'ID of your On-Call Policy Execution Log Timeline where this timeline event belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyExecutionLogId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Execution Log Timeline ID", + description: + "ID of your On-Call Policy Execution Log Timeline where this timeline event belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyExecutionLogId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyExecutionLogTimelineId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyExecutionLogTimeline, - title: 'On-Call Policy Execution Log Timeline', - description: - 'Relation to On-Call Policy Execution Log Timeline where this timeline event belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyExecutionLogTimeline; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyExecutionLogTimelineId' }) - public onCallDutyPolicyExecutionLogTimeline?: OnCallDutyPolicyExecutionLogTimeline = - undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyExecutionLogTimelineId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyExecutionLogTimeline, + title: "On-Call Policy Execution Log Timeline", + description: + "Relation to On-Call Policy Execution Log Timeline where this timeline event belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyExecutionLogTimeline; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyExecutionLogTimelineId" }) + public onCallDutyPolicyExecutionLogTimeline?: OnCallDutyPolicyExecutionLogTimeline = + undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Execution Log ID', - description: - 'ID of your On-Call Policy Execution Log where this timeline event belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyExecutionLogTimelineId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Execution Log ID", + description: + "ID of your On-Call Policy Execution Log where this timeline event belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyExecutionLogTimelineId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'onCallDutyPolicyEscalationRuleId', - type: TableColumnType.Entity, - modelType: OnCallDutyPolicyEscalationRule, - title: 'On-Call Policy Escalation Rule', - description: - 'Relation to On-Call Policy Escalation Rule where this timeline event belongs.', - }) - @ManyToOne( - () => { - return OnCallDutyPolicyEscalationRule; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'onCallDutyPolicyEscalationRuleId' }) - public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "onCallDutyPolicyEscalationRuleId", + type: TableColumnType.Entity, + modelType: OnCallDutyPolicyEscalationRule, + title: "On-Call Policy Escalation Rule", + description: + "Relation to On-Call Policy Escalation Rule where this timeline event belongs.", + }) + @ManyToOne( + () => { + return OnCallDutyPolicyEscalationRule; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "onCallDutyPolicyEscalationRuleId" }) + public onCallDutyPolicyEscalationRule?: OnCallDutyPolicyEscalationRule = + undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Notification Event Type', - description: 'Notification Event Type of this execution', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public userNotificationEventType?: UserNotificationEventType = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Notification Event Type", + description: "Notification Event Type of this execution", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public userNotificationEventType?: UserNotificationEventType = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'On-Call Policy Escalation Rule ID', - description: - 'ID of your On-Call Policy Escalation Rule where this timeline event belongs.', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "On-Call Policy Escalation Rule ID", + description: + "ID of your On-Call Policy Escalation Rule where this timeline event belongs.", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public onCallDutyPolicyEscalationRuleId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userBelongsToTeamId', - type: TableColumnType.Entity, - modelType: Team, - title: 'Which team did the user belong to when the alert was sent?', - description: - 'Which team did the user belong to when the alert was sent?', - }) - @ManyToOne( - () => { - return Team; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userBelongsToTeamId' }) - public userBelongsToTeam?: Team = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userBelongsToTeamId", + type: TableColumnType.Entity, + modelType: Team, + title: "Which team did the user belong to when the alert was sent?", + description: "Which team did the user belong to when the alert was sent?", + }) + @ManyToOne( + () => { + return Team; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userBelongsToTeamId" }) + public userBelongsToTeam?: Team = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'Which team did the user belong to when the alert was sent?', - description: - 'Which team did the user belong to when the alert was sent?', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userBelongsToTeamId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "Which team did the user belong to when the alert was sent?", + description: "Which team did the user belong to when the alert was sent?", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userBelongsToTeamId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.LongText, - title: 'Status Message', - description: 'Status message of this execution timeline event', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public statusMessage?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.LongText, + title: "Status Message", + description: "Status message of this execution timeline event", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public statusMessage?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - title: 'Status', - description: 'Status of this execution timeline event', - canReadOnRelationQuery: false, - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - public status?: UserNotificationStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + title: "Status", + description: "Status of this execution timeline event", + canReadOnRelationQuery: false, + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public status?: UserNotificationStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @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: [], + read: [Permission.CurrentUser], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - nullable: true, - unique: false, - }) - public isAcknowledged?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + nullable: true, + unique: false, + }) + public isAcknowledged?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.Date, - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public acknowledgedAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.Date, + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public acknowledgedAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userCallId', - type: TableColumnType.Entity, - modelType: UserCall, - title: 'User Call', - description: - 'Relation to User Call Resource in which this object belongs', - }) - @ManyToOne( - () => { - return UserCall; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userCallId' }) - public userCall?: UserCall = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userCallId", + type: TableColumnType.Entity, + modelType: UserCall, + title: "User Call", + description: "Relation to User Call Resource in which this object belongs", + }) + @ManyToOne( + () => { + return UserCall; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userCallId" }) + public userCall?: UserCall = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'User Call ID', - description: 'ID of User Call in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userCallId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "User Call ID", + description: "ID of User Call in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userCallId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userSmsId', - type: TableColumnType.Entity, - modelType: UserSMS, - title: 'User SMS', - description: - 'Relation to User SMS Resource in which this object belongs', - }) - @ManyToOne( - () => { - return UserSMS; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userSmsId' }) - public userSms?: UserSMS = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userSmsId", + type: TableColumnType.Entity, + modelType: UserSMS, + title: "User SMS", + description: "Relation to User SMS Resource in which this object belongs", + }) + @ManyToOne( + () => { + return UserSMS; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userSmsId" }) + public userSms?: UserSMS = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'User SMS ID', - description: 'ID of User SMS in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userSmsId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "User SMS ID", + description: "ID of User SMS in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userSmsId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'userEmailId', - type: TableColumnType.Entity, - modelType: UserEmail, - title: 'User Email', - description: - 'Relation to User Email Resource in which this object belongs', - }) - @ManyToOne( - () => { - return UserEmail; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userEmailId' }) - public userEmail?: UserEmail = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "userEmailId", + type: TableColumnType.Entity, + modelType: UserEmail, + title: "User Email", + description: "Relation to User Email Resource in which this object belongs", + }) + @ManyToOne( + () => { + return UserEmail; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userEmailId" }) + public userEmail?: UserEmail = undefined; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'User Email ID', - description: 'ID of User Email in which this object belongs', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public userEmailId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "User Email ID", + description: "ID of User Email in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public userEmailId?: ObjectID = 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; + @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; } diff --git a/Model/Models/UserSMS.ts b/Model/Models/UserSMS.ts index 6d6cd51c06..426c2c8f34 100644 --- a/Model/Models/UserSMS.ts +++ b/Model/Models/UserSMS.ts @@ -1,287 +1,285 @@ -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import CurrentUserCanAccessRecordBy from 'Common/Types/Database/CurrentUserCanAccessRecordBy'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import Phone from 'Common/Types/Phone'; -import Text from 'Common/Types/Text'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import AllowAccessIfSubscriptionIsUnpaid from "Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import CurrentUserCanAccessRecordBy from "Common/Types/Database/CurrentUserCanAccessRecordBy"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import Phone from "Common/Types/Phone"; +import Text from "Common/Types/Text"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; -@TenantColumn('projectId') +@TenantColumn("projectId") @AllowAccessIfSubscriptionIsUnpaid() @TableAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - delete: [Permission.CurrentUser], - update: [Permission.CurrentUser], + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + delete: [Permission.CurrentUser], + update: [Permission.CurrentUser], }) -@CrudApiEndpoint(new Route('/user-sms')) +@CrudApiEndpoint(new Route("/user-sms")) @Entity({ - name: 'UserSMS', + name: "UserSMS", }) @TableMetadata({ - tableName: 'UserSMS', - singularName: 'Phone Number for SMS Notifications', - pluralName: 'Phone Numbers for SMS Notifications', - icon: IconProp.SMS, - tableDescription: 'Phone Number which will be used for SMS notifications.', + tableName: "UserSMS", + singularName: "Phone Number for SMS Notifications", + pluralName: "Phone Numbers for SMS Notifications", + icon: IconProp.SMS, + tableDescription: "Phone Number which will be used for SMS notifications.", }) -@CurrentUserCanAccessRecordBy('userId') +@CurrentUserCanAccessRecordBy("userId") class UserSMS extends BaseModel { - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - 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.CurrentUser], + read: [Permission.CurrentUser], + 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.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - title: 'Phone', - required: true, - unique: false, - type: TableColumnType.Phone, - canReadOnRelationQuery: true, - }) - @Column({ - type: ColumnType.Phone, - length: ColumnLength.Phone, - unique: false, - nullable: false, - transformer: Phone.getDatabaseTransformer(), - }) - public phone?: Phone = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + title: "Phone", + required: true, + unique: false, + type: TableColumnType.Phone, + canReadOnRelationQuery: true, + }) + @Column({ + type: ColumnType.Phone, + length: ColumnLength.Phone, + unique: false, + nullable: false, + transformer: Phone.getDatabaseTransformer(), + }) + public phone?: Phone = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'user', - type: TableColumnType.Entity, - modelType: User, - title: 'User', - description: 'Relation to User who this email belongs to', - }) - @ManyToOne( - () => { - return User; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'userId' }) - public user?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "user", + type: TableColumnType.Entity, + modelType: User, + title: "User", + description: "Relation to User who this email belongs to", + }) + @ManyToOne( + () => { + return User; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "userId" }) + public user?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - type: TableColumnType.ObjectID, - title: 'User ID', - description: 'User ID who this email belongs to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - @Index() - public userId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + type: TableColumnType.ObjectID, + title: "User ID", + description: "User ID who this email belongs to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + @Index() + public userId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [Permission.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [Permission.CurrentUser], - read: [Permission.CurrentUser], - update: [], - }) - @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.CurrentUser], + read: [Permission.CurrentUser], + update: [], + }) + @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: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; - @ColumnAccessControl({ - create: [], - read: [Permission.CurrentUser], - update: [], - }) - @TableColumn({ - title: 'Is Verified', - description: 'Is this verified?', - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isVerified?: boolean = undefined; + @ColumnAccessControl({ + create: [], + read: [Permission.CurrentUser], + update: [], + }) + @TableColumn({ + title: "Is Verified", + description: "Is this verified?", + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isVerified?: boolean = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - title: 'Verification Code', - description: 'Temporary Verification Code', - isDefaultValueColumn: true, - required: true, - type: TableColumnType.ShortText, - forceGetDefaultValueOnCreate: () => { - return Text.generateRandomNumber(6); - }, - }) - @Column({ - type: ColumnType.ShortText, - nullable: false, - length: ColumnLength.ShortText, - }) - public verificationCode?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + title: "Verification Code", + description: "Temporary Verification Code", + isDefaultValueColumn: true, + required: true, + type: TableColumnType.ShortText, + forceGetDefaultValueOnCreate: () => { + return Text.generateRandomNumber(6); + }, + }) + @Column({ + type: ColumnType.ShortText, + nullable: false, + length: ColumnLength.ShortText, + }) + public verificationCode?: string = undefined; } export default UserSMS; diff --git a/Model/Models/Workflow.ts b/Model/Models/Workflow.ts index 9e9f5ed7cb..91dd6dc879 100644 --- a/Model/Models/Workflow.ts +++ b/Model/Models/Workflow.ts @@ -1,526 +1,524 @@ -import Label from './Label'; -import Project from './Project'; -import User from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import AccessControlColumn from 'Common/Types/Database/AccessControlColumn'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import SlugifyColumn from 'Common/Types/Database/SlugifyColumn'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; +import Label from "./Label"; +import Project from "./Project"; +import User from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import AccessControlColumn from "Common/Types/Database/AccessControlColumn"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import SlugifyColumn from "Common/Types/Database/SlugifyColumn"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, -} from 'typeorm'; + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@AccessControlColumn('labels') -@TenantColumn('projectId') +@AccessControlColumn("labels") +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteWorkflow, - Permission.ProjectMember, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteWorkflow, - Permission.EditWorkflow, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteWorkflow, + Permission.ProjectMember, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteWorkflow, + Permission.EditWorkflow, + ], }) -@CrudApiEndpoint(new Route('/workflow')) -@SlugifyColumn('name', 'slug') +@CrudApiEndpoint(new Route("/workflow")) +@SlugifyColumn("name", "slug") @Entity({ - name: 'Workflow', + name: "Workflow", }) @TableMetadata({ - tableName: 'Workflow', - singularName: 'Workflow', - pluralName: 'Workflows', - icon: IconProp.Workflow, - tableDescription: - 'Integrate your OneUptime project with rest of your software stack.', + tableName: "Workflow", + singularName: "Workflow", + pluralName: "Workflows", + icon: IconProp.Workflow, + tableDescription: + "Integrate your OneUptime project with rest of your software stack.", }) export default class Workflow extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteWorkflow, - Permission.EditWorkflow, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Any friendly name of this object', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy('projectId') - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteWorkflow, + Permission.EditWorkflow, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Any friendly name of this object", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy("projectId") + public name?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - update: [], - }) - @TableColumn({ - required: true, - unique: true, - type: TableColumnType.Slug, - title: 'Slug', - description: 'Friendly globally unique name for your object', - }) - @Column({ - nullable: false, - type: ColumnType.Slug, - length: ColumnLength.Slug, - }) - public slug?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + update: [], + }) + @TableColumn({ + required: true, + unique: true, + type: TableColumnType.Slug, + title: "Slug", + description: "Friendly globally unique name for your object", + }) + @Column({ + nullable: false, + type: ColumnType.Slug, + length: ColumnLength.Slug, + }) + public slug?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteWorkflow, - Permission.EditWorkflow, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteWorkflow, + Permission.EditWorkflow, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + 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; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteWorkflow, - Permission.EditWorkflow, - ], - }) - @TableColumn({ - isDefaultValueColumn: true, - type: TableColumnType.Boolean, - title: 'Is Enabled', - description: 'Is this workflow enabled?', - }) - @Column({ - type: ColumnType.Boolean, - default: false, - }) - public isEnabled?: boolean = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteWorkflow, + Permission.EditWorkflow, + ], + }) + @TableColumn({ + isDefaultValueColumn: true, + type: TableColumnType.Boolean, + title: "Is Enabled", + description: "Is this workflow enabled?", + }) + @Column({ + type: ColumnType.Boolean, + default: false, + }) + public isEnabled?: boolean = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteWorkflow, - Permission.EditWorkflow, - ], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - title: 'Workflow Graph', - description: - 'Workflow Graph in JSON. Ideally, create this via UI and not via API.', - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public graph?: JSONObject = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteWorkflow, + Permission.EditWorkflow, + ], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + title: "Workflow Graph", + description: + "Workflow Graph in JSON. Ideally, create this via UI and not via API.", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public graph?: JSONObject = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflow, - Permission.ProjectMember, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflow, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteWorkflow, - Permission.EditWorkflow, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.EntityArray, - modelType: Label, - title: 'Labels', - description: - 'Relation to Labels Array where this object is categorized in.', - }) - @ManyToMany( - () => { - return Label; - }, - { eager: false } - ) - @JoinTable({ - name: 'WorkflowLabel', - inverseJoinColumn: { - name: 'labelId', - referencedColumnName: '_id', - }, - joinColumn: { - name: 'workflowId', - referencedColumnName: '_id', - }, - }) - public labels?: Array<Label> = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflow, + Permission.ProjectMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflow, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteWorkflow, + Permission.EditWorkflow, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Labels", + description: + "Relation to Labels Array where this object is categorized in.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "WorkflowLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "workflowId", + referencedColumnName: "_id", + }, + }) + public labels?: Array<Label> = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.ShortText, - }) - @Column({ - type: ColumnType.ShortText, - nullable: true, - length: ColumnLength.ShortText, - }) - public triggerId?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.ShortText, + }) + @Column({ + type: ColumnType.ShortText, + nullable: true, + length: ColumnLength.ShortText, + }) + public triggerId?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.JSON, - }) - @Column({ - type: ColumnType.JSON, - nullable: true, - }) - public triggerArguments?: JSONObject = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.JSON, + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public triggerArguments?: JSONObject = undefined; - // This is a BullMQ job key that is used to schedule job for this workflow. This is used internally to remove existing job. - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - isDefaultValueColumn: false, - required: false, - type: TableColumnType.LongText, - }) - @Column({ - type: ColumnType.LongText, - nullable: true, - }) - public repeatableJobKey?: string = undefined; + // This is a BullMQ job key that is used to schedule job for this workflow. This is used internally to remove existing job. + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + isDefaultValueColumn: false, + required: false, + type: TableColumnType.LongText, + }) + @Column({ + type: ColumnType.LongText, + nullable: true, + }) + public repeatableJobKey?: string = undefined; } diff --git a/Model/Models/WorkflowLog.ts b/Model/Models/WorkflowLog.ts index e00f4d9832..b959ec4a37 100644 --- a/Model/Models/WorkflowLog.ts +++ b/Model/Models/WorkflowLog.ts @@ -1,304 +1,302 @@ -import Project from './Project'; -import User from './User'; -import Workflow from './Workflow'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import WorkflowStatus from 'Common/Types/Workflow/WorkflowStatus'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import Workflow from "./Workflow"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import WorkflowStatus from "Common/Types/Workflow/WorkflowStatus"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowLog, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowLog, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteWorkflowLog, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditWorkflowLog, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowLog, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowLog, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteWorkflowLog, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditWorkflowLog, + ], }) -@CrudApiEndpoint(new Route('/workflow-log')) +@CrudApiEndpoint(new Route("/workflow-log")) @Entity({ - name: 'WorkflowLog', + name: "WorkflowLog", }) @TableMetadata({ - tableName: 'WorkflowLog', - singularName: 'Workflow Log', - pluralName: 'Workflow Logs', - icon: IconProp.Logs, - tableDescription: 'Logs of the workflows executed', + tableName: "WorkflowLog", + singularName: "Workflow Log", + pluralName: "Workflow Logs", + icon: IconProp.Logs, + tableDescription: "Logs of the workflows executed", }) export default class WorkflowLog extends BaseModel { - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowLog, - ], - 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.ReadWorkflowLog, + ], + 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.ReadWorkflowLog, - ], - 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.ReadWorkflowLog, + ], + 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.ReadWorkflowLog, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'workflowId', - type: TableColumnType.Entity, - modelType: Workflow, - title: 'Workflow', - description: 'Workflow this logs belong to', - }) - @ManyToOne( - () => { - return Workflow; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'workflowId' }) - public workflow?: Workflow = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowLog, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "workflowId", + type: TableColumnType.Entity, + modelType: Workflow, + title: "Workflow", + description: "Workflow this logs belong to", + }) + @ManyToOne( + () => { + return Workflow; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "workflowId" }) + public workflow?: Workflow = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowLog, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: true, - canReadOnRelationQuery: true, - title: 'Workflow ID', - description: 'ID of Workflow this logs belong to', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: false, - transformer: ObjectID.getDatabaseTransformer(), - }) - public workflowId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowLog, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Workflow ID", + description: "ID of Workflow this logs belong to", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public workflowId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowLog, - ], - update: [], - }) - @TableColumn({ - required: false, - type: TableColumnType.VeryLongText, - title: 'Logs', - description: 'Logs', - }) - @Column({ - nullable: false, - type: ColumnType.VeryLongText, - }) - public logs?: string = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowLog, + ], + update: [], + }) + @TableColumn({ + required: false, + type: TableColumnType.VeryLongText, + title: "Logs", + description: "Logs", + }) + @Column({ + nullable: false, + type: ColumnType.VeryLongText, + }) + public logs?: string = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowLog, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.WorkflowStatus, - title: 'Workflow Status', - description: 'Status of this workflow', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - }) - public workflowStatus?: WorkflowStatus = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowLog, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.WorkflowStatus, + title: "Workflow Status", + description: "Status of this workflow", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + }) + public workflowStatus?: WorkflowStatus = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowLog, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Date, - title: 'Started At', - description: 'When did this workflow start', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public startedAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowLog, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Date, + title: "Started At", + description: "When did this workflow start", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public startedAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowLog, - ], - update: [], - }) - @TableColumn({ - type: TableColumnType.Date, - title: 'Completed At', - description: 'When did this workflow complete', - }) - @Column({ - type: ColumnType.Date, - nullable: true, - unique: false, - }) - public completedAt?: Date = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowLog, + ], + update: [], + }) + @TableColumn({ + type: TableColumnType.Date, + title: "Completed At", + description: "When did this workflow complete", + }) + @Column({ + type: ColumnType.Date, + nullable: true, + unique: false, + }) + public completedAt?: Date = undefined; - @ColumnAccessControl({ - create: [], - read: [], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + 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; + @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; } diff --git a/Model/Models/WorkflowVariable.ts b/Model/Models/WorkflowVariable.ts index 0eb612afad..af6821eaf3 100644 --- a/Model/Models/WorkflowVariable.ts +++ b/Model/Models/WorkflowVariable.ts @@ -1,430 +1,428 @@ -import Project from './Project'; -import User from './User'; -import Workflow from './Workflow'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan'; -import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl'; -import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl'; -import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl'; -import ColumnLength from 'Common/Types/Database/ColumnLength'; -import ColumnType from 'Common/Types/Database/ColumnType'; -import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint'; -import EnableDocumentation from 'Common/Types/Database/EnableDocumentation'; -import TableColumn from 'Common/Types/Database/TableColumn'; -import TableColumnType from 'Common/Types/Database/TableColumnType'; -import TableMetadata from 'Common/Types/Database/TableMetadata'; -import TenantColumn from 'Common/Types/Database/TenantColumn'; -import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy'; -import IconProp from 'Common/Types/Icon/IconProp'; -import ObjectID from 'Common/Types/ObjectID'; -import Permission from 'Common/Types/Permission'; -import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import Project from "./Project"; +import User from "./User"; +import Workflow from "./Workflow"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import { PlanSelect } from "Common/Types/Billing/SubscriptionPlan"; +import ColumnAccessControl from "Common/Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "Common/Types/Database/AccessControl/TableAccessControl"; +import TableBillingAccessControl from "Common/Types/Database/AccessControl/TableBillingAccessControl"; +import ColumnLength from "Common/Types/Database/ColumnLength"; +import ColumnType from "Common/Types/Database/ColumnType"; +import CrudApiEndpoint from "Common/Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "Common/Types/Database/EnableDocumentation"; +import TableColumn from "Common/Types/Database/TableColumn"; +import TableColumnType from "Common/Types/Database/TableColumnType"; +import TableMetadata from "Common/Types/Database/TableMetadata"; +import TenantColumn from "Common/Types/Database/TenantColumn"; +import UniqueColumnBy from "Common/Types/Database/UniqueColumnBy"; +import IconProp from "Common/Types/Icon/IconProp"; +import ObjectID from "Common/Types/ObjectID"; +import Permission from "Common/Types/Permission"; +import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm"; @EnableDocumentation() @TableBillingAccessControl({ - create: PlanSelect.Growth, - read: PlanSelect.Growth, - update: PlanSelect.Growth, - delete: PlanSelect.Growth, + create: PlanSelect.Growth, + read: PlanSelect.Growth, + update: PlanSelect.Growth, + delete: PlanSelect.Growth, }) -@TenantColumn('projectId') +@TenantColumn("projectId") @TableAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - delete: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.DeleteWorkflowVariable, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditWorkflowVariable, - ], + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteWorkflowVariable, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditWorkflowVariable, + ], }) -@CrudApiEndpoint(new Route('/workflow-variable')) +@CrudApiEndpoint(new Route("/workflow-variable")) @Entity({ - name: 'WorkflowVariable', + name: "WorkflowVariable", }) @TableMetadata({ - tableName: 'WorkflowVariable', - singularName: 'Workflow Variable', - pluralName: 'Workflow Variables', - icon: IconProp.Variable, - tableDescription: - 'Store environment variables or secrets for your workflows.', + tableName: "WorkflowVariable", + singularName: "Workflow Variable", + pluralName: "Workflow Variables", + icon: IconProp.Variable, + tableDescription: + "Store environment variables or secrets for your workflows.", }) export default class WorkflowVariable extends BaseModel { - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - 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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + 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.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'workflowId', - type: TableColumnType.Entity, - modelType: Workflow, - title: 'Workflow', - description: - 'Workflow this variable belong to. If this is null then this variable will be a global variable', - }) - @ManyToOne( - () => { - return Workflow; - }, - { - eager: false, - nullable: true, - onDelete: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'workflowId' }) - public workflow?: Workflow = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "workflowId", + type: TableColumnType.Entity, + modelType: Workflow, + title: "Workflow", + description: + "Workflow this variable belong to. If this is null then this variable will be a global variable", + }) + @ManyToOne( + () => { + return Workflow; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "workflowId" }) + public workflow?: Workflow = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - update: [], - }) - @Index() - @TableColumn({ - type: TableColumnType.ObjectID, - required: false, - canReadOnRelationQuery: true, - title: 'Workflow ID', - description: - 'ID of Workflow this variable belong to. If this is null then this variable will be a global variable', - }) - @Column({ - type: ColumnType.ObjectID, - nullable: true, - transformer: ObjectID.getDatabaseTransformer(), - }) - public workflowId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + canReadOnRelationQuery: true, + title: "Workflow ID", + description: + "ID of Workflow this variable belong to. If this is null then this variable will be a global variable", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public workflowId?: ObjectID = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditWorkflowVariable, - ], - }) - @TableColumn({ - required: true, - type: TableColumnType.ShortText, - canReadOnRelationQuery: true, - title: 'Name', - description: 'Variable Name', - }) - @Column({ - nullable: false, - type: ColumnType.ShortText, - length: ColumnLength.ShortText, - }) - @UniqueColumnBy(['workflowId', 'projectId']) - public name?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditWorkflowVariable, + ], + }) + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Variable Name", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + @UniqueColumnBy(["workflowId", "projectId"]) + public name?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - update: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.EditWorkflowVariable, - ], - }) - @TableColumn({ - required: false, - type: TableColumnType.LongText, - title: 'Description', - description: 'Friendly description that will help you remember', - }) - @Column({ - nullable: true, - type: ColumnType.LongText, - length: ColumnLength.LongText, - }) - public description?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditWorkflowVariable, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Friendly description that will help you remember", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [], - update: [Permission.ProjectOwner, Permission.ProjectAdmin], - }) - @TableColumn({ - required: true, - type: TableColumnType.VeryLongText, - title: 'Content', - description: 'Content of the variable', - }) - @Column({ - nullable: false, - type: ColumnType.VeryLongText, - }) - public content?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [], + update: [Permission.ProjectOwner, Permission.ProjectAdmin], + }) + @TableColumn({ + required: true, + type: TableColumnType.VeryLongText, + title: "Content", + description: "Content of the variable", + }) + @Column({ + nullable: false, + type: ColumnType.VeryLongText, + }) + public content?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - update: [], - }) - @TableColumn({ - required: true, - type: TableColumnType.Boolean, - title: 'Secret', - description: - "Is this variable a secret. If true, then it'll not be in the logs", - }) - @Column({ - nullable: false, - default: false, - type: ColumnType.Boolean, - }) - public isSecret?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + update: [], + }) + @TableColumn({ + required: true, + type: TableColumnType.Boolean, + title: "Secret", + description: + "Is this variable a secret. If true, then it'll not be in the logs", + }) + @Column({ + nullable: false, + default: false, + type: ColumnType.Boolean, + }) + public isSecret?: string = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - update: [], - }) - @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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'createdByUserId' }) - public createdByUser?: User = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + update: [], + }) + @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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "createdByUserId" }) + public createdByUser?: User = undefined; - @ColumnAccessControl({ - create: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.CreateWorkflowVariable, - ], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - update: [], - }) - @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.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateWorkflowVariable, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + update: [], + }) + @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: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - update: [], - }) - @TableColumn({ - manyToOneRelationColumn: 'deletedByUserId', - type: TableColumnType.Entity, - title: 'Deleted by 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: 'CASCADE', - orphanedRowAction: 'nullify', - } - ) - @JoinColumn({ name: 'deletedByUserId' }) - public deletedByUser?: User = undefined; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "deletedByUserId", + type: TableColumnType.Entity, + title: "Deleted by 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: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "deletedByUserId" }) + public deletedByUser?: User = undefined; - @ColumnAccessControl({ - create: [], - read: [ - Permission.ProjectOwner, - Permission.ProjectAdmin, - Permission.ProjectMember, - Permission.ReadWorkflowVariable, - ], - 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; + @ColumnAccessControl({ + create: [], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadWorkflowVariable, + ], + 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; } diff --git a/Model/Tests/File.test.ts b/Model/Tests/File.test.ts index 71fec69c02..9edadf0768 100644 --- a/Model/Tests/File.test.ts +++ b/Model/Tests/File.test.ts @@ -1,10 +1,10 @@ -import File from '../Models/File'; -import { describe, expect, it } from '@jest/globals'; -import BaseModel from 'Common/Models/BaseModel'; +import File from "../Models/File"; +import { describe, expect, it } from "@jest/globals"; +import BaseModel from "Common/Models/BaseModel"; -describe('File', () => { - it('should be an instance of BaseModel', () => { - const file: File = new File(); - expect(file).toBeInstanceOf(BaseModel); - }); +describe("File", () => { + it("should be an instance of BaseModel", () => { + const file: File = new File(); + expect(file).toBeInstanceOf(BaseModel); + }); }); diff --git a/Nginx/Index.ts b/Nginx/Index.ts index 85fa38a086..b6f15255e5 100644 --- a/Nginx/Index.ts +++ b/Nginx/Index.ts @@ -1,53 +1,53 @@ -import AcmeWriteCertificatesJob from './Jobs/AcmeWriteCertificates'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import { PostgresAppInstance } from 'CommonServer/Infrastructure/PostgresDatabase'; -import InfrastructureStatus from 'CommonServer/Infrastructure/Status'; -import logger from 'CommonServer/Utils/Logger'; -import App from 'CommonServer/Utils/StartServer'; +import AcmeWriteCertificatesJob from "./Jobs/AcmeWriteCertificates"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import { PostgresAppInstance } from "CommonServer/Infrastructure/PostgresDatabase"; +import InfrastructureStatus from "CommonServer/Infrastructure/Status"; +import logger from "CommonServer/Utils/Logger"; +import App from "CommonServer/Utils/StartServer"; -process.env['SERVICE_NAME'] = 'ingress'; +process.env["SERVICE_NAME"] = "ingress"; -const APP_NAME: string = process.env['SERVICE_NAME']; +const APP_NAME: string = process.env["SERVICE_NAME"]; const init: PromiseVoidFunction = async (): Promise<void> => { - try { - const statusCheck: PromiseVoidFunction = async (): Promise<void> => { - return await InfrastructureStatus.checkStatus({ - checkClickhouseStatus: false, - checkPostgresStatus: true, - checkRedisStatus: false, - }); - }; + try { + const statusCheck: PromiseVoidFunction = async (): Promise<void> => { + return await InfrastructureStatus.checkStatus({ + checkClickhouseStatus: false, + checkPostgresStatus: true, + checkRedisStatus: false, + }); + }; - // init the app - await App.init({ - appName: APP_NAME, - port: undefined, - isFrontendApp: false, - statusOptions: { - liveCheck: statusCheck, - readyCheck: statusCheck, - }, - }); + // init the app + await App.init({ + appName: APP_NAME, + port: undefined, + isFrontendApp: false, + statusOptions: { + liveCheck: statusCheck, + readyCheck: statusCheck, + }, + }); - // connect to the database. - await PostgresAppInstance.connect( - PostgresAppInstance.getDatasourceOptions() - ); + // connect to the database. + await PostgresAppInstance.connect( + PostgresAppInstance.getDatasourceOptions(), + ); - AcmeWriteCertificatesJob.init(); + AcmeWriteCertificatesJob.init(); - // add default routes - await App.addDefaultRoutes(); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); diff --git a/Nginx/Jobs/AcmeWriteCertificates.ts b/Nginx/Jobs/AcmeWriteCertificates.ts index baa5726f97..526a533ee2 100644 --- a/Nginx/Jobs/AcmeWriteCertificates.ts +++ b/Nginx/Jobs/AcmeWriteCertificates.ts @@ -1,62 +1,60 @@ -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import { EVERY_FIFTEEN_MINUTE, EVERY_MINUTE } from 'Common/Utils/CronTime'; -import { IsDevelopment } from 'CommonServer/EnvironmentConfig'; -import AcmeCertificateService from 'CommonServer/Services/AcmeCertificateService'; -import BasicCron from 'CommonServer/Utils/BasicCron'; -import LocalFile from 'CommonServer/Utils/LocalFile'; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import { EVERY_FIFTEEN_MINUTE, EVERY_MINUTE } from "Common/Utils/CronTime"; +import { IsDevelopment } from "CommonServer/EnvironmentConfig"; +import AcmeCertificateService from "CommonServer/Services/AcmeCertificateService"; +import BasicCron from "CommonServer/Utils/BasicCron"; +import LocalFile from "CommonServer/Utils/LocalFile"; // @ts-ignore -import logger from 'CommonServer/Utils/Logger'; -import AcmeCertificate from 'Model/Models/AcmeCertificate'; +import logger from "CommonServer/Utils/Logger"; +import AcmeCertificate from "Model/Models/AcmeCertificate"; export default class Jobs { - public static init(): void { - BasicCron({ - jobName: 'StatusPageCerts:WriteAcmeCertsToDisk', - options: { - schedule: IsDevelopment ? EVERY_MINUTE : EVERY_FIFTEEN_MINUTE, - runOnStartup: true, + public static init(): void { + BasicCron({ + jobName: "StatusPageCerts:WriteAcmeCertsToDisk", + options: { + schedule: IsDevelopment ? EVERY_MINUTE : EVERY_FIFTEEN_MINUTE, + runOnStartup: true, + }, + runFunction: async () => { + // Fetch all domains where certs are added to greenlock. + + const certs: Array<AcmeCertificate> = + await AcmeCertificateService.findBy({ + query: {}, + select: { + domain: true, + certificate: true, + certificateKey: true, }, - runFunction: async () => { - // Fetch all domains where certs are added to greenlock. - - const certs: Array<AcmeCertificate> = - await AcmeCertificateService.findBy({ - query: {}, - select: { - domain: true, - certificate: true, - certificateKey: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const cert of certs) { - try { - await LocalFile.makeDirectory( - '/etc/nginx/certs/StatusPageCerts' - ); - } catch (err) { - // directory already exists, ignore. - logger.error('Create directory err'); - logger.error(err); - } - - // Write to disk. - await LocalFile.write( - `/etc/nginx/certs/StatusPageCerts/${cert.domain}.crt`, - cert.certificate?.toString() || '' - ); - - await LocalFile.write( - `/etc/nginx/certs/StatusPageCerts/${cert.domain}.key`, - cert.certificateKey?.toString() || '' - ); - } + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, }, - }); - } + }); + + for (const cert of certs) { + try { + await LocalFile.makeDirectory("/etc/nginx/certs/StatusPageCerts"); + } catch (err) { + // directory already exists, ignore. + logger.error("Create directory err"); + logger.error(err); + } + + // Write to disk. + await LocalFile.write( + `/etc/nginx/certs/StatusPageCerts/${cert.domain}.crt`, + cert.certificate?.toString() || "", + ); + + await LocalFile.write( + `/etc/nginx/certs/StatusPageCerts/${cert.domain}.key`, + cert.certificateKey?.toString() || "", + ); + } + }, + }); + } } diff --git a/Nginx/Jobs/FetchCertificates.ts b/Nginx/Jobs/FetchCertificates.ts index db8927eb45..1f0f249724 100644 --- a/Nginx/Jobs/FetchCertificates.ts +++ b/Nginx/Jobs/FetchCertificates.ts @@ -1,97 +1,96 @@ -import LIMIT_MAX from 'Common/Types/Database/LimitMax'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import { EVERY_FIFTEEN_MINUTE, EVERY_MINUTE } from 'Common/Utils/CronTime'; -import { IsDevelopment } from 'CommonServer/EnvironmentConfig'; -import GreenlockCertificateService from 'CommonServer/Services/GreenlockCertificateService'; -import BasicCron from 'CommonServer/Utils/BasicCron'; -import LocalFile from 'CommonServer/Utils/LocalFile'; +import LIMIT_MAX from "Common/Types/Database/LimitMax"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import { EVERY_FIFTEEN_MINUTE, EVERY_MINUTE } from "Common/Utils/CronTime"; +import { IsDevelopment } from "CommonServer/EnvironmentConfig"; +import GreenlockCertificateService from "CommonServer/Services/GreenlockCertificateService"; +import BasicCron from "CommonServer/Utils/BasicCron"; +import LocalFile from "CommonServer/Utils/LocalFile"; // @ts-ignore -import logger from 'CommonServer/Utils/Logger'; -import GreenlockCertificate from 'Model/Models/GreenlockCertificate'; +import logger from "CommonServer/Utils/Logger"; +import GreenlockCertificate from "Model/Models/GreenlockCertificate"; export default class Jobs { - public static init(): void { - BasicCron({ - jobName: 'StatusPageCerts:WriteGreelockCertsToDisk', - options: { - schedule: IsDevelopment ? EVERY_MINUTE : EVERY_FIFTEEN_MINUTE, - runOnStartup: true, + public static init(): void { + BasicCron({ + jobName: "StatusPageCerts:WriteGreelockCertsToDisk", + options: { + schedule: IsDevelopment ? EVERY_MINUTE : EVERY_FIFTEEN_MINUTE, + runOnStartup: true, + }, + runFunction: async () => { + // Fetch all domains where certs are added to greenlock. + + const certs: Array<GreenlockCertificate> = + await GreenlockCertificateService.findBy({ + query: {}, + select: { + isKeyPair: true, + key: true, + blob: true, }, - runFunction: async () => { - // Fetch all domains where certs are added to greenlock. - - const certs: Array<GreenlockCertificate> = - await GreenlockCertificateService.findBy({ - query: {}, - select: { - isKeyPair: true, - key: true, - blob: true, - }, - limit: LIMIT_MAX, - skip: 0, - props: { - isRoot: true, - }, - }); - - for (const cert of certs) { - if (!cert.isKeyPair) { - continue; - } - - const certBlob: GreenlockCertificate | undefined = - certs.find((i: GreenlockCertificate) => { - return i.key === cert.key && !i.isKeyPair; - }); - - if (!certBlob) { - continue; - } - - const key: string = JSONFunctions.parseJSONObject( - cert.blob || '{}' - )['privateKeyPem'] as string; - let crt: string = JSONFunctions.parseJSONObject( - certBlob.blob || '{}' - )['cert'] as string; - - if ( - JSONFunctions.parseJSONObject(certBlob.blob || '{}')[ - 'chain' - ] as string - ) { - crt += ('\n' + - '\n' + - JSONFunctions.parseJSONObject( - certBlob.blob || '{}' - )['chain']) as string; - } - - // Need to make sure StatusPageCerts dir exists. - - try { - await LocalFile.makeDirectory( - '/etc/nginx/certs/StatusPageCerts' - ); - } catch (err) { - // directory already exists, ignore. - logger.error('Create directory err'); - logger.error(err); - } - - // Write to disk. - await LocalFile.write( - `/etc/nginx/certs/StatusPageCerts/${cert.key}.crt`, - crt - ); - - await LocalFile.write( - `/etc/nginx/certs/StatusPageCerts/${cert.key}.key`, - key - ); - } + limit: LIMIT_MAX, + skip: 0, + props: { + isRoot: true, }, - }); - } + }); + + for (const cert of certs) { + if (!cert.isKeyPair) { + continue; + } + + const certBlob: GreenlockCertificate | undefined = certs.find( + (i: GreenlockCertificate) => { + return i.key === cert.key && !i.isKeyPair; + }, + ); + + if (!certBlob) { + continue; + } + + const key: string = JSONFunctions.parseJSONObject(cert.blob || "{}")[ + "privateKeyPem" + ] as string; + let crt: string = JSONFunctions.parseJSONObject( + certBlob.blob || "{}", + )["cert"] as string; + + if ( + JSONFunctions.parseJSONObject(certBlob.blob || "{}")[ + "chain" + ] as string + ) { + crt += ("\n" + + "\n" + + JSONFunctions.parseJSONObject(certBlob.blob || "{}")[ + "chain" + ]) as string; + } + + // Need to make sure StatusPageCerts dir exists. + + try { + await LocalFile.makeDirectory("/etc/nginx/certs/StatusPageCerts"); + } catch (err) { + // directory already exists, ignore. + logger.error("Create directory err"); + logger.error(err); + } + + // Write to disk. + await LocalFile.write( + `/etc/nginx/certs/StatusPageCerts/${cert.key}.crt`, + crt, + ); + + await LocalFile.write( + `/etc/nginx/certs/StatusPageCerts/${cert.key}.key`, + key, + ); + } + }, + }); + } } diff --git a/Nginx/Utils/SelfSignedSSL.ts b/Nginx/Utils/SelfSignedSSL.ts index 8070943882..170e3c3792 100644 --- a/Nginx/Utils/SelfSignedSSL.ts +++ b/Nginx/Utils/SelfSignedSSL.ts @@ -1,9 +1,9 @@ -import Exec from 'CommonServer/Utils/Execute'; +import Exec from "CommonServer/Utils/Execute"; export default class SelfSignedSSL { - public static async generate(path: string, host: string): Promise<void> { - await Exec.executeCommand( - `mkdir -p ${path} && openssl req -new -x509 -nodes -subj "/C=US/ST=NY/L=NYC/O=Global Security/OU=IT Department/CN=example.com" -out ${path}/${host}.crt -keyout ${path}/${host}.key -days 99999 && chmod -R 777 ${path}` - ); - } + public static async generate(path: string, host: string): Promise<void> { + await Exec.executeCommand( + `mkdir -p ${path} && openssl req -new -x509 -nodes -subj "/C=US/ST=NY/L=NYC/O=Global Security/OU=IT Department/CN=example.com" -out ${path}/${host}.crt -keyout ${path}/${host}.key -days 99999 && chmod -R 777 ${path}`, + ); + } } diff --git a/Probe/Config.ts b/Probe/Config.ts index 0b3ff33eb1..44a051ef99 100644 --- a/Probe/Config.ts +++ b/Probe/Config.ts @@ -1,77 +1,73 @@ -import URL from 'Common/Types/API/URL'; -import ObjectID from 'Common/Types/ObjectID'; -import logger from 'CommonServer/Utils/Logger'; +import URL from "Common/Types/API/URL"; +import ObjectID from "Common/Types/ObjectID"; +import logger from "CommonServer/Utils/Logger"; -if (!process.env['INGESTOR_URL'] && !process.env['ONEUPTIME_URL']) { - logger.error('INGESTOR_URL or ONEUPTIME_URL is not set'); - process.exit(); +if (!process.env["INGESTOR_URL"] && !process.env["ONEUPTIME_URL"]) { + logger.error("INGESTOR_URL or ONEUPTIME_URL is not set"); + process.exit(); } export let INGESTOR_URL: URL = URL.fromString( - process.env['ONEUPTIME_URL'] || - process.env['INGESTOR_URL'] || - 'https://oneuptime.com' + process.env["ONEUPTIME_URL"] || + process.env["INGESTOR_URL"] || + "https://oneuptime.com", ); // If probe api does not have the path. Add it. if ( - !INGESTOR_URL.toString().endsWith('ingestor') && - !INGESTOR_URL.toString().endsWith('ingestor/') + !INGESTOR_URL.toString().endsWith("ingestor") && + !INGESTOR_URL.toString().endsWith("ingestor/") ) { - INGESTOR_URL = URL.fromString( - INGESTOR_URL.addRoute('/ingestor').toString() - ); + INGESTOR_URL = URL.fromString(INGESTOR_URL.addRoute("/ingestor").toString()); } -export const PROBE_NAME: string | null = process.env['PROBE_NAME'] || null; +export const PROBE_NAME: string | null = process.env["PROBE_NAME"] || null; export const PROBE_DESCRIPTION: string | null = - process.env['PROBE_DESCRIPTION'] || null; + process.env["PROBE_DESCRIPTION"] || null; -export const PROBE_ID: ObjectID | null = process.env['PROBE_ID'] - ? new ObjectID(process.env['PROBE_ID']) - : null; +export const PROBE_ID: ObjectID | null = process.env["PROBE_ID"] + ? new ObjectID(process.env["PROBE_ID"]) + : null; -if (!process.env['PROBE_KEY']) { - logger.error('PROBE_KEY is not set'); - process.exit(); +if (!process.env["PROBE_KEY"]) { + logger.error("PROBE_KEY is not set"); + process.exit(); } -export const PROBE_KEY: string = process.env['PROBE_KEY']; +export const PROBE_KEY: string = process.env["PROBE_KEY"]; let probeMonitoringWorkers: string | number = - process.env['PROBE_MONITORING_WORKERS'] || 1; + process.env["PROBE_MONITORING_WORKERS"] || 1; -if (typeof probeMonitoringWorkers === 'string') { - probeMonitoringWorkers = parseInt(probeMonitoringWorkers); +if (typeof probeMonitoringWorkers === "string") { + probeMonitoringWorkers = parseInt(probeMonitoringWorkers); } export const PROBE_MONITORING_WORKERS: number = probeMonitoringWorkers; let monitorFetchLimit: string | number = - process.env['PROBE_MONITOR_FETCH_LIMIT'] || 1; + process.env["PROBE_MONITOR_FETCH_LIMIT"] || 1; -if (typeof monitorFetchLimit === 'string') { - monitorFetchLimit = parseInt(monitorFetchLimit); +if (typeof monitorFetchLimit === "string") { + monitorFetchLimit = parseInt(monitorFetchLimit); } export const PROBE_MONITOR_FETCH_LIMIT: number = monitorFetchLimit; -export const HOSTNAME: string = process.env['HOSTNAME'] || 'localhost'; +export const HOSTNAME: string = process.env["HOSTNAME"] || "localhost"; export const PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS: number = process.env[ - 'PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS' + "PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS" ] - ? parseInt( - process.env['PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS'].toString() - ) - : 60000; + ? parseInt( + process.env["PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS"].toString(), + ) + : 60000; export const PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS: number = process - .env['PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS'] - ? parseInt( - process.env[ - 'PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS' - ].toString() - ) - : 60000; + .env["PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS"] + ? parseInt( + process.env["PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS"].toString(), + ) + : 60000; diff --git a/Probe/Index.ts b/Probe/Index.ts index d7c310e7ec..adea02cc27 100644 --- a/Probe/Index.ts +++ b/Probe/Index.ts @@ -1,76 +1,76 @@ -import { PROBE_MONITORING_WORKERS } from './Config'; -import './Jobs/Alive'; -import FetchListAndProbe from './Jobs/Monitor/FetchList'; -import Register from './Services/Register'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Sleep from 'Common/Types/Sleep'; -import logger from 'CommonServer/Utils/Logger'; -import App from 'CommonServer/Utils/StartServer'; -import 'ejs'; +import { PROBE_MONITORING_WORKERS } from "./Config"; +import "./Jobs/Alive"; +import FetchListAndProbe from "./Jobs/Monitor/FetchList"; +import Register from "./Services/Register"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Sleep from "Common/Types/Sleep"; +import logger from "CommonServer/Utils/Logger"; +import App from "CommonServer/Utils/StartServer"; +import "ejs"; -const APP_NAME: string = 'probe'; +const APP_NAME: string = "probe"; const init: PromiseVoidFunction = async (): Promise<void> => { + try { + // init the app + await App.init({ + appName: APP_NAME, + port: undefined, + isFrontendApp: false, + statusOptions: { + liveCheck: async () => {}, + readyCheck: async () => {}, + }, + }); + + // add default routes + await App.addDefaultRoutes(); + try { - // init the app - await App.init({ - appName: APP_NAME, - port: undefined, - isFrontendApp: false, - statusOptions: { - liveCheck: async () => {}, - readyCheck: async () => {}, - }, - }); + // Register this probe. + await Register.registerProbe(); - // add default routes - await App.addDefaultRoutes(); + logger.debug("Probe registered"); - try { - // Register this probe. - await Register.registerProbe(); - - logger.debug('Probe registered'); - - await Register.reportIfOffline(); - } catch (err) { - logger.error('Register probe failed'); - logger.error(err); - throw err; - } - - try { - let workers: number = 0; - - while (workers < PROBE_MONITORING_WORKERS) { - workers++; - - const currentWorker: number = workers; - - logger.debug(`Starting worker ${currentWorker}`); - - new FetchListAndProbe('Worker ' + currentWorker) - .run() - .catch((err: any) => { - logger.error(`Worker ${currentWorker} failed: `); - logger.error(err); - }); - - await Sleep.sleep(1000); - } - } catch (err) { - logger.error('Starting workers failed'); - logger.error(err); - } + await Register.reportIfOffline(); } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; + logger.error("Register probe failed"); + logger.error(err); + throw err; } + + try { + let workers: number = 0; + + while (workers < PROBE_MONITORING_WORKERS) { + workers++; + + const currentWorker: number = workers; + + logger.debug(`Starting worker ${currentWorker}`); + + new FetchListAndProbe("Worker " + currentWorker) + .run() + .catch((err: any) => { + logger.error(`Worker ${currentWorker} failed: `); + logger.error(err); + }); + + await Sleep.sleep(1000); + } + } catch (err) { + logger.error("Starting workers failed"); + logger.error(err); + } + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); diff --git a/Probe/Jobs/Alive.ts b/Probe/Jobs/Alive.ts index f85f2f4cac..0b5fef2eed 100644 --- a/Probe/Jobs/Alive.ts +++ b/Probe/Jobs/Alive.ts @@ -1,40 +1,40 @@ -import { INGESTOR_URL } from '../Config'; -import Register from '../Services/Register'; -import ProbeAPIRequest from '../Utils/ProbeAPIRequest'; -import URL from 'Common/Types/API/URL'; -import API from 'Common/Utils/API'; -import { EVERY_MINUTE } from 'Common/Utils/CronTime'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; -import BasicCron from 'CommonServer/Utils/BasicCron'; -import logger from 'CommonServer/Utils/Logger'; +import { INGESTOR_URL } from "../Config"; +import Register from "../Services/Register"; +import ProbeAPIRequest from "../Utils/ProbeAPIRequest"; +import URL from "Common/Types/API/URL"; +import API from "Common/Utils/API"; +import { EVERY_MINUTE } from "Common/Utils/CronTime"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; +import BasicCron from "CommonServer/Utils/BasicCron"; +import logger from "CommonServer/Utils/Logger"; BasicCron({ - jobName: 'Basic:Alive', - options: { - schedule: EVERY_MINUTE, - runOnStartup: false, - }, - runFunction: async () => { - logger.debug('Checking if probe is alive...'); + jobName: "Basic:Alive", + options: { + schedule: EVERY_MINUTE, + runOnStartup: false, + }, + runFunction: async () => { + logger.debug("Checking if probe is alive..."); - const probeId: string | undefined = LocalCache.getString( - 'PROBE', - 'PROBE_ID' - ); + const probeId: string | undefined = LocalCache.getString( + "PROBE", + "PROBE_ID", + ); - if (!probeId) { - logger.warn( - 'Probe is not registered yet. Skipping alive check. Trying to register probe again...' - ); - await Register.registerProbe(); - return; - } + if (!probeId) { + logger.warn( + "Probe is not registered yet. Skipping alive check. Trying to register probe again...", + ); + await Register.registerProbe(); + return; + } - logger.debug('Probe ID: ' + probeId.toString()); + logger.debug("Probe ID: " + probeId.toString()); - await API.post( - URL.fromString(INGESTOR_URL.toString()).addRoute('/alive'), - ProbeAPIRequest.getDefaultRequestBody() - ); - }, + await API.post( + URL.fromString(INGESTOR_URL.toString()).addRoute("/alive"), + ProbeAPIRequest.getDefaultRequestBody(), + ); + }, }); diff --git a/Probe/Jobs/Monitor/FetchList.ts b/Probe/Jobs/Monitor/FetchList.ts index c6a2bce5c3..0aa84905fb 100644 --- a/Probe/Jobs/Monitor/FetchList.ts +++ b/Probe/Jobs/Monitor/FetchList.ts @@ -1,118 +1,112 @@ -import { INGESTOR_URL, PROBE_MONITOR_FETCH_LIMIT } from '../../Config'; -import MonitorUtil from '../../Utils/Monitors/Monitor'; -import ProbeAPIRequest from '../../Utils/ProbeAPIRequest'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPMethod from 'Common/Types/API/HTTPMethod'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import { JSONArray } from 'Common/Types/JSON'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import Sleep from 'Common/Types/Sleep'; -import API from 'Common/Utils/API'; -import logger from 'CommonServer/Utils/Logger'; -import Monitor from 'Model/Models/Monitor'; +import { INGESTOR_URL, PROBE_MONITOR_FETCH_LIMIT } from "../../Config"; +import MonitorUtil from "../../Utils/Monitors/Monitor"; +import ProbeAPIRequest from "../../Utils/ProbeAPIRequest"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPMethod from "Common/Types/API/HTTPMethod"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import { JSONArray } from "Common/Types/JSON"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import Sleep from "Common/Types/Sleep"; +import API from "Common/Utils/API"; +import logger from "CommonServer/Utils/Logger"; +import Monitor from "Model/Models/Monitor"; export default class FetchListAndProbe { - private workerName: string = ''; + private workerName: string = ""; - public constructor(workerName: string) { - this.workerName = workerName; + public constructor(workerName: string) { + this.workerName = workerName; + } + + public async run(): Promise<void> { + logger.debug(`Running worker ${this.workerName}`); + + // eslint-disable-next-line no-constant-condition + while (true) { + const runTime: Date = OneUptimeDate.getCurrentDate(); + + logger.debug(`Probing monitors ${this.workerName}`); + + await this.fetchListAndProbe(); + + logger.debug(`Probing monitors ${this.workerName} complete`); + + // if rumTime + 5 seconds is in the future, then this fetchLst either errored out or had no monitors in the list. Either way, wait for 5 seconds and proceed. + + const twoSecondsAdded: Date = OneUptimeDate.addRemoveSeconds(runTime, 2); + + if (OneUptimeDate.isInTheFuture(twoSecondsAdded)) { + logger.debug(`Worker ${this.workerName} is waiting for 2 seconds`); + await Sleep.sleep(2000); + } } + } - public async run(): Promise<void> { - logger.debug(`Running worker ${this.workerName}`); + private async fetchListAndProbe(): Promise<void> { + try { + logger.debug("Fetching monitor list"); - // eslint-disable-next-line no-constant-condition - while (true) { - const runTime: Date = OneUptimeDate.getCurrentDate(); + const monitorListUrl: URL = URL.fromString( + INGESTOR_URL.toString(), + ).addRoute("/monitor/list"); - logger.debug(`Probing monitors ${this.workerName}`); + const result: HTTPResponse<JSONArray> | HTTPErrorResponse = + await API.fetch<JSONArray>( + HTTPMethod.POST, + monitorListUrl, + { + ...ProbeAPIRequest.getDefaultRequestBody(), + limit: PROBE_MONITOR_FETCH_LIMIT || 100, + }, + {}, + {}, + ); - await this.fetchListAndProbe(); + logger.debug("Fetched monitor list"); + logger.debug(result); - logger.debug(`Probing monitors ${this.workerName} complete`); + const monitors: Array<Monitor> = BaseModel.fromJSONArray( + result.data as JSONArray, + Monitor, + ); - // if rumTime + 5 seconds is in the future, then this fetchLst either errored out or had no monitors in the list. Either way, wait for 5 seconds and proceed. + const probeMonitorPromises: Array< + Promise<Array<ProbeMonitorResponse | null>> + > = []; // Array of promises to probe monitors - const twoSecondsAdded: Date = OneUptimeDate.addRemoveSeconds( - runTime, - 2 - ); + for (const monitor of monitors) { + probeMonitorPromises.push(MonitorUtil.probeMonitor(monitor)); + } - if (OneUptimeDate.isInTheFuture(twoSecondsAdded)) { - logger.debug( - `Worker ${this.workerName} is waiting for 2 seconds` - ); - await Sleep.sleep(2000); - } + // all settled + // eslint-disable-next-line no-undef + const results: PromiseSettledResult<(ProbeMonitorResponse | null)[]>[] = + await Promise.allSettled(probeMonitorPromises); + + let resultIndex: number = 0; + + for (const result of results) { + if (monitors && monitors[resultIndex]) { + logger.debug("Monitor:"); + logger.debug(monitors[resultIndex]); } - } - private async fetchListAndProbe(): Promise<void> { - try { - logger.debug('Fetching monitor list'); - - const monitorListUrl: URL = URL.fromString( - INGESTOR_URL.toString() - ).addRoute('/monitor/list'); - - const result: HTTPResponse<JSONArray> | HTTPErrorResponse = - await API.fetch<JSONArray>( - HTTPMethod.POST, - monitorListUrl, - { - ...ProbeAPIRequest.getDefaultRequestBody(), - limit: PROBE_MONITOR_FETCH_LIMIT || 100, - }, - {}, - {} - ); - - logger.debug('Fetched monitor list'); - logger.debug(result); - - const monitors: Array<Monitor> = BaseModel.fromJSONArray( - result.data as JSONArray, - Monitor - ); - - const probeMonitorPromises: Array< - Promise<Array<ProbeMonitorResponse | null>> - > = []; // Array of promises to probe monitors - - for (const monitor of monitors) { - probeMonitorPromises.push(MonitorUtil.probeMonitor(monitor)); - } - - // all settled - // eslint-disable-next-line no-undef - const results: PromiseSettledResult< - (ProbeMonitorResponse | null)[] - >[] = await Promise.allSettled(probeMonitorPromises); - - let resultIndex: number = 0; - - for (const result of results) { - if (monitors && monitors[resultIndex]) { - logger.debug('Monitor:'); - logger.debug(monitors[resultIndex]); - } - - if (result.status === 'rejected') { - logger.error('Error in probing monitor:'); - logger.error(result.reason); - } else { - logger.debug('Probed monitor: '); - logger.debug(result.value); - } - - resultIndex++; - } - } catch (err) { - logger.error('Error in fetching monitor list'); - logger.error(err); + if (result.status === "rejected") { + logger.error("Error in probing monitor:"); + logger.error(result.reason); + } else { + logger.debug("Probed monitor: "); + logger.debug(result.value); } + + resultIndex++; + } + } catch (err) { + logger.error("Error in fetching monitor list"); + logger.error(err); } + } } diff --git a/Probe/Services/Register.ts b/Probe/Services/Register.ts index d00c89b427..0ee8c3ade8 100644 --- a/Probe/Services/Register.ts +++ b/Probe/Services/Register.ts @@ -1,156 +1,148 @@ import { - HOSTNAME, - INGESTOR_URL, - PROBE_DESCRIPTION, - PROBE_ID, - PROBE_KEY, - PROBE_NAME, -} from '../Config'; -import OnlineCheck from '../Utils/OnlineCheck'; -import ProbeAPIRequest from '../Utils/ProbeAPIRequest'; -import HTTPMethod from 'Common/Types/API/HTTPMethod'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import URL from 'Common/Types/API/URL'; -import { JSONObject } from 'Common/Types/JSON'; -import ProbeStatusReport from 'Common/Types/Probe/ProbeStatusReport'; -import Sleep from 'Common/Types/Sleep'; -import API from 'Common/Utils/API'; -import { ClusterKey, HasClusterKey } from 'CommonServer/EnvironmentConfig'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; -import logger from 'CommonServer/Utils/Logger'; + HOSTNAME, + INGESTOR_URL, + PROBE_DESCRIPTION, + PROBE_ID, + PROBE_KEY, + PROBE_NAME, +} from "../Config"; +import OnlineCheck from "../Utils/OnlineCheck"; +import ProbeAPIRequest from "../Utils/ProbeAPIRequest"; +import HTTPMethod from "Common/Types/API/HTTPMethod"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import URL from "Common/Types/API/URL"; +import { JSONObject } from "Common/Types/JSON"; +import ProbeStatusReport from "Common/Types/Probe/ProbeStatusReport"; +import Sleep from "Common/Types/Sleep"; +import API from "Common/Utils/API"; +import { ClusterKey, HasClusterKey } from "CommonServer/EnvironmentConfig"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; +import logger from "CommonServer/Utils/Logger"; export default class Register { - public static async reportIfOffline(): Promise<void> { - const pingMonitoringCheck: boolean = - await OnlineCheck.canProbeMonitorPingMonitors(); - const websiteMonitoringCheck: boolean = - await OnlineCheck.canProbeMonitorWebsiteMonitors(); - const portMonitoringCheck: boolean = - await OnlineCheck.canProbeMonitorPortMonitors(); + public static async reportIfOffline(): Promise<void> { + const pingMonitoringCheck: boolean = + await OnlineCheck.canProbeMonitorPingMonitors(); + const websiteMonitoringCheck: boolean = + await OnlineCheck.canProbeMonitorWebsiteMonitors(); + const portMonitoringCheck: boolean = + await OnlineCheck.canProbeMonitorPortMonitors(); - if (!pingMonitoringCheck && websiteMonitoringCheck) { - // probe is online but ping monitoring is blocked by the cloud provider. Fallback to port monitoring. - logger.warn( - 'Ping monitoring is on this machine. Fallback to port monitoring' - ); - LocalCache.setString('PROBE', 'PING_MONITORING', 'PORT'); - } - - if (!pingMonitoringCheck || !websiteMonitoringCheck) { - // Send an email to the admin. - - if (!pingMonitoringCheck) { - logger.error('Ping monitoring is disabled'); - } - - if (!websiteMonitoringCheck) { - logger.error('Website monitoring is disabled'); - } - - // Send an email to the admin. - - const stausReport: ProbeStatusReport = { - isPingCheckOffline: !pingMonitoringCheck, - isWebsiteCheckOffline: !websiteMonitoringCheck, - isPortCheckOffline: !portMonitoringCheck, - hostname: HOSTNAME, - }; - - await API.fetch<JSONObject>( - HTTPMethod.POST, - URL.fromString(INGESTOR_URL.toString()).addRoute( - '/probe/status-report/offline' - ), - { - ...ProbeAPIRequest.getDefaultRequestBody(), - statusReport: stausReport as any, - }, - {}, - {} - ); - } + if (!pingMonitoringCheck && websiteMonitoringCheck) { + // probe is online but ping monitoring is blocked by the cloud provider. Fallback to port monitoring. + logger.warn( + "Ping monitoring is on this machine. Fallback to port monitoring", + ); + LocalCache.setString("PROBE", "PING_MONITORING", "PORT"); } - public static async registerProbe(): Promise<void> { - // register probe with 5 retry and 15 seocnd interval between each retry. + if (!pingMonitoringCheck || !websiteMonitoringCheck) { + // Send an email to the admin. - let currentRetry: number = 0; + if (!pingMonitoringCheck) { + logger.error("Ping monitoring is disabled"); + } - const maxRetry: number = 10; + if (!websiteMonitoringCheck) { + logger.error("Website monitoring is disabled"); + } - const retryIntervalInSeconds: number = 30; + // Send an email to the admin. - while (currentRetry < maxRetry) { - try { - logger.debug(`Registering probe. Attempt: ${currentRetry + 1}`); - await Register._registerProbe(); - logger.debug(`Probe registered successfully.`); - break; - } catch (error) { - logger.error( - `Failed to register probe. Retrying after ${retryIntervalInSeconds} seconds...` - ); - logger.error(error); - currentRetry++; - await Sleep.sleep(retryIntervalInSeconds * 1000); - } - } + const stausReport: ProbeStatusReport = { + isPingCheckOffline: !pingMonitoringCheck, + isWebsiteCheckOffline: !websiteMonitoringCheck, + isPortCheckOffline: !portMonitoringCheck, + hostname: HOSTNAME, + }; + + await API.fetch<JSONObject>( + HTTPMethod.POST, + URL.fromString(INGESTOR_URL.toString()).addRoute( + "/probe/status-report/offline", + ), + { + ...ProbeAPIRequest.getDefaultRequestBody(), + statusReport: stausReport as any, + }, + {}, + {}, + ); } + } - private static async _registerProbe(): Promise<void> { - if (HasClusterKey) { - const probeRegistrationUrl: URL = URL.fromString( - INGESTOR_URL.toString() - ).addRoute('/register'); + public static async registerProbe(): Promise<void> { + // register probe with 5 retry and 15 seocnd interval between each retry. - logger.debug('Registering Probe...'); - logger.debug( - 'Sending request to: ' + probeRegistrationUrl.toString() - ); + let currentRetry: number = 0; - const result: HTTPResponse<JSONObject> = await API.post( - probeRegistrationUrl, - { - probeKey: PROBE_KEY, - probeName: PROBE_NAME, - probeDescription: PROBE_DESCRIPTION, - clusterKey: ClusterKey.toString(), - } - ); + const maxRetry: number = 10; - if (result.isSuccess()) { - logger.debug('Probe Registered'); + const retryIntervalInSeconds: number = 30; - const probeId: string = result.data['_id'] as string; - - LocalCache.setString('PROBE', 'PROBE_ID', probeId as string); - } - } else { - // validate probe. - if (!PROBE_ID) { - logger.error('PROBE_ID or ONEUPTIME_SECRET should be set'); - return process.exit(); - } - - await API.post( - URL.fromString(INGESTOR_URL.toString()).addRoute('/alive'), - { - probeKey: PROBE_KEY.toString(), - probeId: PROBE_ID.toString(), - } - ); - - LocalCache.setString( - 'PROBE', - 'PROBE_ID', - PROBE_ID.toString() as string - ); - } - - logger.debug( - `Probe ID: ${ - LocalCache.getString('PROBE', 'PROBE_ID') || 'Unknown' - }` + while (currentRetry < maxRetry) { + try { + logger.debug(`Registering probe. Attempt: ${currentRetry + 1}`); + await Register._registerProbe(); + logger.debug(`Probe registered successfully.`); + break; + } catch (error) { + logger.error( + `Failed to register probe. Retrying after ${retryIntervalInSeconds} seconds...`, ); + logger.error(error); + currentRetry++; + await Sleep.sleep(retryIntervalInSeconds * 1000); + } } + } + + private static async _registerProbe(): Promise<void> { + if (HasClusterKey) { + const probeRegistrationUrl: URL = URL.fromString( + INGESTOR_URL.toString(), + ).addRoute("/register"); + + logger.debug("Registering Probe..."); + logger.debug("Sending request to: " + probeRegistrationUrl.toString()); + + const result: HTTPResponse<JSONObject> = await API.post( + probeRegistrationUrl, + { + probeKey: PROBE_KEY, + probeName: PROBE_NAME, + probeDescription: PROBE_DESCRIPTION, + clusterKey: ClusterKey.toString(), + }, + ); + + if (result.isSuccess()) { + logger.debug("Probe Registered"); + + const probeId: string = result.data["_id"] as string; + + LocalCache.setString("PROBE", "PROBE_ID", probeId as string); + } + } else { + // validate probe. + if (!PROBE_ID) { + logger.error("PROBE_ID or ONEUPTIME_SECRET should be set"); + return process.exit(); + } + + await API.post( + URL.fromString(INGESTOR_URL.toString()).addRoute("/alive"), + { + probeKey: PROBE_KEY.toString(), + probeId: PROBE_ID.toString(), + }, + ); + + LocalCache.setString("PROBE", "PROBE_ID", PROBE_ID.toString() as string); + } + + logger.debug( + `Probe ID: ${LocalCache.getString("PROBE", "PROBE_ID") || "Unknown"}`, + ); + } } diff --git a/Probe/Tests/Utils/PingMonitor.test.skip.ts b/Probe/Tests/Utils/PingMonitor.test.skip.ts index f706260d38..cccaf6256b 100644 --- a/Probe/Tests/Utils/PingMonitor.test.skip.ts +++ b/Probe/Tests/Utils/PingMonitor.test.skip.ts @@ -1,67 +1,67 @@ import Ping, { - PingResponse, -} from '../../Utils/Monitors/MonitorTypes/PingMonitor'; -import Hostname from 'Common/Types/API/Hostname'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IPv4 from 'Common/Types/IP/IPv4'; -import PositiveNumber from 'Common/Types/PositiveNumber'; + PingResponse, +} from "../../Utils/Monitors/MonitorTypes/PingMonitor"; +import Hostname from "Common/Types/API/Hostname"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IPv4 from "Common/Types/IP/IPv4"; +import PositiveNumber from "Common/Types/PositiveNumber"; -describe('Ping', () => { - jest.setTimeout(240000); - test('Ping.ping should return appropriate object if the valid hostname is given', async () => { - let result: PingResponse | null = await Ping.ping( - new Hostname('apple.com') - ); +describe("Ping", () => { + jest.setTimeout(240000); + test("Ping.ping should return appropriate object if the valid hostname is given", async () => { + let result: PingResponse | null = await Ping.ping( + new Hostname("apple.com"), + ); - expect(result).not.toBeNull(); - expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0); - expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000); - expect(result!.isOnline).toBe(true); - result = await Ping.ping(new Hostname('www.apple.com'), { - timeout: new PositiveNumber(5000), - }); - - expect(result).not.toBeNull(); - expect(result!.isOnline).toBe(true); - expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0); - expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000); - - try { - await Ping.ping(new Hostname('www.apple.com', 65000), { - timeout: new PositiveNumber(5000), - }); - } catch (err) { - expect(err).toBeInstanceOf(BadDataException); - } - - try { - await Ping.ping(new Hostname('www.a.com', 65000), { - timeout: new PositiveNumber(5000), - }); - } catch (err) { - expect(err).toBeInstanceOf(BadDataException); - } + expect(result).not.toBeNull(); + expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0); + expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000); + expect(result!.isOnline).toBe(true); + result = await Ping.ping(new Hostname("www.apple.com"), { + timeout: new PositiveNumber(5000), }); - test('Ping.ping should return appropriate object if the valid IPV4 or IPV6 is given', async () => { - // change test timeout to 2 minutes - let result: PingResponse | null = null; - result = await Ping.ping(new IPv4('1.1.1.1'), { - timeout: new PositiveNumber(5000), - }); - expect(result).not.toBeNull(); - expect(result!.isOnline).toBe(true); - expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0); - expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000); + expect(result).not.toBeNull(); + expect(result!.isOnline).toBe(true); + expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0); + expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000); - result = await Ping.ping(new IPv4('192.0.2.200')); // - expect(result).not.toBeNull(); - expect(result!.isOnline).toBe(false); - expect(result!.responseTimeInMS).toBeUndefined(); + try { + await Ping.ping(new Hostname("www.apple.com", 65000), { + timeout: new PositiveNumber(5000), + }); + } catch (err) { + expect(err).toBeInstanceOf(BadDataException); + } - result = await Ping.ping(new IPv4('0.42.52.42')); // ip can't start 0 - expect(result).not.toBeNull(); - expect(result!.responseTimeInMS).toBeUndefined(); - expect(result!.isOnline).toBe(false); + try { + await Ping.ping(new Hostname("www.a.com", 65000), { + timeout: new PositiveNumber(5000), + }); + } catch (err) { + expect(err).toBeInstanceOf(BadDataException); + } + }); + test("Ping.ping should return appropriate object if the valid IPV4 or IPV6 is given", async () => { + // change test timeout to 2 minutes + let result: PingResponse | null = null; + + result = await Ping.ping(new IPv4("1.1.1.1"), { + timeout: new PositiveNumber(5000), }); + expect(result).not.toBeNull(); + expect(result!.isOnline).toBe(true); + expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0); + expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000); + + result = await Ping.ping(new IPv4("192.0.2.200")); // + expect(result).not.toBeNull(); + expect(result!.isOnline).toBe(false); + expect(result!.responseTimeInMS).toBeUndefined(); + + result = await Ping.ping(new IPv4("0.42.52.42")); // ip can't start 0 + expect(result).not.toBeNull(); + expect(result!.responseTimeInMS).toBeUndefined(); + expect(result!.isOnline).toBe(false); + }); }); diff --git a/Probe/Utils/Monitors/Monitor.ts b/Probe/Utils/Monitors/Monitor.ts index 66b16201c1..3cba8be650 100644 --- a/Probe/Utils/Monitors/Monitor.ts +++ b/Probe/Utils/Monitors/Monitor.ts @@ -1,374 +1,365 @@ -import { INGESTOR_URL } from '../../Config'; -import ProbeUtil from '../Probe'; -import ProbeAPIRequest from '../ProbeAPIRequest'; -import ApiMonitor, { APIResponse } from './MonitorTypes/ApiMonitor'; -import CustomCodeMonitor from './MonitorTypes/CustomCodeMonitor'; -import PingMonitor, { PingResponse } from './MonitorTypes/PingMonitor'; -import PortMonitor, { PortMonitorResponse } from './MonitorTypes/PortMonitor'; -import SSLMonitor, { SslResponse } from './MonitorTypes/SslMonitor'; -import SyntheticMonitor from './MonitorTypes/SyntheticMonitor'; +import { INGESTOR_URL } from "../../Config"; +import ProbeUtil from "../Probe"; +import ProbeAPIRequest from "../ProbeAPIRequest"; +import ApiMonitor, { APIResponse } from "./MonitorTypes/ApiMonitor"; +import CustomCodeMonitor from "./MonitorTypes/CustomCodeMonitor"; +import PingMonitor, { PingResponse } from "./MonitorTypes/PingMonitor"; +import PortMonitor, { PortMonitorResponse } from "./MonitorTypes/PortMonitor"; +import SSLMonitor, { SslResponse } from "./MonitorTypes/SslMonitor"; +import SyntheticMonitor from "./MonitorTypes/SyntheticMonitor"; import WebsiteMonitor, { - ProbeWebsiteResponse, -} from './MonitorTypes/WebsiteMonitor'; -import HTTPMethod from 'Common/Types/API/HTTPMethod'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import { CheckOn, CriteriaFilter } from 'Common/Types/Monitor/CriteriaFilter'; -import CustomCodeMonitorResponse from 'Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse'; -import MonitorCriteriaInstance from 'Common/Types/Monitor/MonitorCriteriaInstance'; -import MonitorStep from 'Common/Types/Monitor/MonitorStep'; -import MonitorType from 'Common/Types/Monitor/MonitorType'; -import BrowserType from 'Common/Types/Monitor/SyntheticMonitors/BrowserType'; -import SyntheticMonitorResponse from 'Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse'; -import Port from 'Common/Types/Port'; -import ProbeMonitorResponse from 'Common/Types/Probe/ProbeMonitorResponse'; -import ScreenSizeType from 'Common/Types/ScreenSizeType'; -import API from 'Common/Utils/API'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; -import logger from 'CommonServer/Utils/Logger'; -import Monitor from 'Model/Models/Monitor'; + ProbeWebsiteResponse, +} from "./MonitorTypes/WebsiteMonitor"; +import HTTPMethod from "Common/Types/API/HTTPMethod"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import { CheckOn, CriteriaFilter } from "Common/Types/Monitor/CriteriaFilter"; +import CustomCodeMonitorResponse from "Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse"; +import MonitorCriteriaInstance from "Common/Types/Monitor/MonitorCriteriaInstance"; +import MonitorStep from "Common/Types/Monitor/MonitorStep"; +import MonitorType from "Common/Types/Monitor/MonitorType"; +import BrowserType from "Common/Types/Monitor/SyntheticMonitors/BrowserType"; +import SyntheticMonitorResponse from "Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse"; +import Port from "Common/Types/Port"; +import ProbeMonitorResponse from "Common/Types/Probe/ProbeMonitorResponse"; +import ScreenSizeType from "Common/Types/ScreenSizeType"; +import API from "Common/Utils/API"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; +import logger from "CommonServer/Utils/Logger"; +import Monitor from "Model/Models/Monitor"; export default class MonitorUtil { - public static async probeMonitor( - monitor: Monitor - ): Promise<Array<ProbeMonitorResponse | null>> { - const results: Array<ProbeMonitorResponse | null> = []; + public static async probeMonitor( + monitor: Monitor, + ): Promise<Array<ProbeMonitorResponse | null>> { + const results: Array<ProbeMonitorResponse | null> = []; - if ( - !monitor.monitorSteps || - monitor.monitorSteps.data?.monitorStepsInstanceArray.length === 0 - ) { - logger.debug('No monitor steps found'); - return []; - } - - for (const monitorStep of monitor.monitorSteps.data - ?.monitorStepsInstanceArray || []) { - if (!monitorStep) { - continue; - } - - const result: ProbeMonitorResponse | null = - await this.probeMonitorStep(monitorStep, monitor); - - if (result) { - // report this back to Probe API. - - await API.fetch<JSONObject>( - HTTPMethod.POST, - URL.fromString(INGESTOR_URL.toString()).addRoute( - '/probe/response/ingest' - ), - { - ...ProbeAPIRequest.getDefaultRequestBody(), - probeMonitorResponse: result as any, - }, - {}, - {} - ); - } - - results.push(result); - } - - return results; + if ( + !monitor.monitorSteps || + monitor.monitorSteps.data?.monitorStepsInstanceArray.length === 0 + ) { + logger.debug("No monitor steps found"); + return []; } - public static isHeadRequest(monitorStep: MonitorStep): boolean { - // If its not GET requestm it cannot be a head request - if ( - monitorStep.data?.requestType && - monitorStep.data?.requestType !== HTTPMethod.GET - ) { - return false; - } + for (const monitorStep of monitor.monitorSteps.data + ?.monitorStepsInstanceArray || []) { + if (!monitorStep) { + continue; + } - // check if monitor step has any criteria with needs request body. If no, then return true otherwise return false. + const result: ProbeMonitorResponse | null = await this.probeMonitorStep( + monitorStep, + monitor, + ); - if ( - monitorStep.data?.monitorCriteria.data - ?.monitorCriteriaInstanceArray && - monitorStep.data?.monitorCriteria.data?.monitorCriteriaInstanceArray - .length > 0 - ) { - const criteriaArray: Array<MonitorCriteriaInstance> = - monitorStep.data?.monitorCriteria.data - ?.monitorCriteriaInstanceArray; + if (result) { + // report this back to Probe API. - for (const criteria of criteriaArray) { - if ( - criteria.data?.filters && - criteria.data?.filters.length > 0 - ) { - const filters: Array<CriteriaFilter> = - criteria.data?.filters; + await API.fetch<JSONObject>( + HTTPMethod.POST, + URL.fromString(INGESTOR_URL.toString()).addRoute( + "/probe/response/ingest", + ), + { + ...ProbeAPIRequest.getDefaultRequestBody(), + probeMonitorResponse: result as any, + }, + {}, + {}, + ); + } - for (const filter of filters) { - if ( - filter.checkOn === CheckOn.ResponseBody || - filter.checkOn === CheckOn.JavaScriptExpression - ) { - return false; - } - } - } - } - } - - return true; + results.push(result); } - public static async probeMonitorStep( - monitorStep: MonitorStep, - monitor: Monitor - ): Promise<ProbeMonitorResponse | null> { - const result: ProbeMonitorResponse = { - monitorStepId: monitorStep.id, - monitorId: monitor.id!, - probeId: ProbeUtil.getProbeId(), - failureCause: '', - monitoredAt: OneUptimeDate.getCurrentDate(), - }; + return results; + } - if (!monitorStep.data) { - return result; - } + public static isHeadRequest(monitorStep: MonitorStep): boolean { + // If its not GET requestm it cannot be a head request + if ( + monitorStep.data?.requestType && + monitorStep.data?.requestType !== HTTPMethod.GET + ) { + return false; + } - if ( - monitor.monitorType === MonitorType.Ping || - monitor.monitorType === MonitorType.IP - ) { - if (!monitorStep.data?.monitorDestination) { - return result; - } + // check if monitor step has any criteria with needs request body. If no, then return true otherwise return false. - result.monitorDestination = monitorStep.data.monitorDestination; + if ( + monitorStep.data?.monitorCriteria.data?.monitorCriteriaInstanceArray && + monitorStep.data?.monitorCriteria.data?.monitorCriteriaInstanceArray + .length > 0 + ) { + const criteriaArray: Array<MonitorCriteriaInstance> = + monitorStep.data?.monitorCriteria.data?.monitorCriteriaInstanceArray; - if (LocalCache.getString('PROBE', 'PING_MONITORING') === 'PORT') { - // probe is online but ping monitoring is blocked by the cloud provider. Fallback to port monitoring. + for (const criteria of criteriaArray) { + if (criteria.data?.filters && criteria.data?.filters.length > 0) { + const filters: Array<CriteriaFilter> = criteria.data?.filters; - const response: PortMonitorResponse | null = - await PortMonitor.ping( - monitorStep.data?.monitorDestination, - new Port(80), // use port 80 by default. - { - retry: 5, - monitorId: monitor.id!, - } - ); - - if (!response) { - return null; - } - - result.isOnline = response.isOnline; - result.responseTimeInMs = response.responseTimeInMS?.toNumber(); - result.failureCause = response.failureCause; - } else { - const response: PingResponse | null = await PingMonitor.ping( - monitorStep.data?.monitorDestination, - { - retry: 5, - monitorId: monitor.id!, - } - ); - - if (!response) { - return null; - } - - result.isOnline = response.isOnline; - result.responseTimeInMs = response.responseTimeInMS?.toNumber(); - result.failureCause = response.failureCause; - } - } - - if (monitor.monitorType === MonitorType.Port) { - if (!monitorStep.data?.monitorDestination) { - return result; - } - - result.monitorDestination = monitorStep.data.monitorDestination; - - if (!monitorStep.data?.monitorDestinationPort) { - result.isOnline = false; - result.responseTimeInMs = 0; - result.failureCause = 'Port is not specified'; - - return result; - } - - result.monitorDestinationPort = - monitorStep.data.monitorDestinationPort; - - const response: PortMonitorResponse | null = await PortMonitor.ping( - monitorStep.data?.monitorDestination, - monitorStep.data.monitorDestinationPort, - { - retry: 5, - monitorId: monitor.id!, - } - ); - - if (!response) { - return null; - } - - result.isOnline = response.isOnline; - result.responseTimeInMs = response.responseTimeInMS?.toNumber(); - result.failureCause = response.failureCause; - } - - if (monitor.monitorType === MonitorType.SyntheticMonitor) { - if (!monitorStep.data?.customCode) { - result.failureCause = - 'Code not specified. Please add playwright script.'; - return result; - } - - const response: Array<SyntheticMonitorResponse> | null = - await SyntheticMonitor.execute({ - script: monitorStep.data.customCode, - monitorId: monitor.id!, - screenSizeTypes: monitorStep.data - .screenSizeTypes as Array<ScreenSizeType>, - browserTypes: monitorStep.data - .browserTypes as Array<BrowserType>, - }); - - if (!response) { - return null; - } - - result.syntheticMonitorResponse = response; - } - - if (monitor.monitorType === MonitorType.CustomJavaScriptCode) { - if (!monitorStep.data?.customCode) { - result.failureCause = - 'Code not specified. Please add playwright script.'; - return result; - } - - const response: CustomCodeMonitorResponse | null = - await CustomCodeMonitor.execute({ - script: monitorStep.data.customCode, - monitorId: monitor.id!, - }); - - if (!response) { - return null; - } - - result.customCodeMonitorResponse = response; - } - - if (monitor.monitorType === MonitorType.SSLCertificate) { - if (!monitorStep.data?.monitorDestination) { - return result; - } - - result.monitorDestination = monitorStep.data.monitorDestination; - - if (!monitorStep.data?.monitorDestination) { - result.isOnline = false; - result.responseTimeInMs = 0; - result.failureCause = 'Port is not specified'; - - return result; - } - - const response: SslResponse | null = await SSLMonitor.ping( - monitorStep.data?.monitorDestination as URL, - { - retry: 5, - monitorId: monitor.id!, - } - ); - - if (!response) { - return null; - } - - result.isOnline = response.isOnline; - result.failureCause = response.failureCause; - result.sslResponse = { - ...response, - }; - } - - if (monitor.monitorType === MonitorType.Website) { - if (!monitorStep.data?.monitorDestination) { - return result; - } - - result.monitorDestination = monitorStep.data.monitorDestination; - - const response: ProbeWebsiteResponse | null = - await WebsiteMonitor.ping( - monitorStep.data?.monitorDestination as URL, - { - isHeadRequest: MonitorUtil.isHeadRequest(monitorStep), - monitorId: monitor.id!, - retry: 5, - } - ); - - if (!response) { - return null; - } - - result.isOnline = response.isOnline; - result.responseTimeInMs = response.responseTimeInMS?.toNumber(); - result.responseBody = response.responseBody?.toString(); - result.responseHeaders = response.responseHeaders; - result.responseCode = response.statusCode; - result.failureCause = response.failureCause; - } - - if (monitor.monitorType === MonitorType.API) { - if (!monitorStep.data?.monitorDestination) { - return result; - } - - result.monitorDestination = monitorStep.data.monitorDestination; - - let requestBody: JSONObject | undefined = undefined; + for (const filter of filters) { if ( - monitorStep.data?.requestBody && - typeof monitorStep.data?.requestBody === 'string' + filter.checkOn === CheckOn.ResponseBody || + filter.checkOn === CheckOn.JavaScriptExpression ) { - requestBody = JSONFunctions.parseJSONObject( - monitorStep.data?.requestBody - ); + return false; } - - const response: APIResponse | null = await ApiMonitor.ping( - monitorStep.data?.monitorDestination as URL, - { - requestHeaders: monitorStep.data?.requestHeaders || {}, - requestBody: requestBody || undefined, - monitorId: monitor.id!, - requestType: - monitorStep.data?.requestType || HTTPMethod.GET, - retry: 5, - } - ); - - if (!response) { - return null; - } - - result.isOnline = response.isOnline; - result.responseTimeInMs = response.responseTimeInMS?.toNumber(); - result.responseBody = response.responseBody; - result.responseHeaders = response.responseHeaders; - result.responseCode = response.statusCode; - result.failureCause = response.failureCause; + } } + } + } + + return true; + } + + public static async probeMonitorStep( + monitorStep: MonitorStep, + monitor: Monitor, + ): Promise<ProbeMonitorResponse | null> { + const result: ProbeMonitorResponse = { + monitorStepId: monitorStep.id, + monitorId: monitor.id!, + probeId: ProbeUtil.getProbeId(), + failureCause: "", + monitoredAt: OneUptimeDate.getCurrentDate(), + }; + + if (!monitorStep.data) { + return result; + } + + if ( + monitor.monitorType === MonitorType.Ping || + monitor.monitorType === MonitorType.IP + ) { + if (!monitorStep.data?.monitorDestination) { + return result; + } + + result.monitorDestination = monitorStep.data.monitorDestination; + + if (LocalCache.getString("PROBE", "PING_MONITORING") === "PORT") { + // probe is online but ping monitoring is blocked by the cloud provider. Fallback to port monitoring. + + const response: PortMonitorResponse | null = await PortMonitor.ping( + monitorStep.data?.monitorDestination, + new Port(80), // use port 80 by default. + { + retry: 5, + monitorId: monitor.id!, + }, + ); + + if (!response) { + return null; + } + + result.isOnline = response.isOnline; + result.responseTimeInMs = response.responseTimeInMS?.toNumber(); + result.failureCause = response.failureCause; + } else { + const response: PingResponse | null = await PingMonitor.ping( + monitorStep.data?.monitorDestination, + { + retry: 5, + monitorId: monitor.id!, + }, + ); + + if (!response) { + return null; + } + + result.isOnline = response.isOnline; + result.responseTimeInMs = response.responseTimeInMS?.toNumber(); + result.failureCause = response.failureCause; + } + } + + if (monitor.monitorType === MonitorType.Port) { + if (!monitorStep.data?.monitorDestination) { + return result; + } + + result.monitorDestination = monitorStep.data.monitorDestination; + + if (!monitorStep.data?.monitorDestinationPort) { + result.isOnline = false; + result.responseTimeInMs = 0; + result.failureCause = "Port is not specified"; return result; + } + + result.monitorDestinationPort = monitorStep.data.monitorDestinationPort; + + const response: PortMonitorResponse | null = await PortMonitor.ping( + monitorStep.data?.monitorDestination, + monitorStep.data.monitorDestinationPort, + { + retry: 5, + monitorId: monitor.id!, + }, + ); + + if (!response) { + return null; + } + + result.isOnline = response.isOnline; + result.responseTimeInMs = response.responseTimeInMS?.toNumber(); + result.failureCause = response.failureCause; } + + if (monitor.monitorType === MonitorType.SyntheticMonitor) { + if (!monitorStep.data?.customCode) { + result.failureCause = + "Code not specified. Please add playwright script."; + return result; + } + + const response: Array<SyntheticMonitorResponse> | null = + await SyntheticMonitor.execute({ + script: monitorStep.data.customCode, + monitorId: monitor.id!, + screenSizeTypes: monitorStep.data + .screenSizeTypes as Array<ScreenSizeType>, + browserTypes: monitorStep.data.browserTypes as Array<BrowserType>, + }); + + if (!response) { + return null; + } + + result.syntheticMonitorResponse = response; + } + + if (monitor.monitorType === MonitorType.CustomJavaScriptCode) { + if (!monitorStep.data?.customCode) { + result.failureCause = + "Code not specified. Please add playwright script."; + return result; + } + + const response: CustomCodeMonitorResponse | null = + await CustomCodeMonitor.execute({ + script: monitorStep.data.customCode, + monitorId: monitor.id!, + }); + + if (!response) { + return null; + } + + result.customCodeMonitorResponse = response; + } + + if (monitor.monitorType === MonitorType.SSLCertificate) { + if (!monitorStep.data?.monitorDestination) { + return result; + } + + result.monitorDestination = monitorStep.data.monitorDestination; + + if (!monitorStep.data?.monitorDestination) { + result.isOnline = false; + result.responseTimeInMs = 0; + result.failureCause = "Port is not specified"; + + return result; + } + + const response: SslResponse | null = await SSLMonitor.ping( + monitorStep.data?.monitorDestination as URL, + { + retry: 5, + monitorId: monitor.id!, + }, + ); + + if (!response) { + return null; + } + + result.isOnline = response.isOnline; + result.failureCause = response.failureCause; + result.sslResponse = { + ...response, + }; + } + + if (monitor.monitorType === MonitorType.Website) { + if (!monitorStep.data?.monitorDestination) { + return result; + } + + result.monitorDestination = monitorStep.data.monitorDestination; + + const response: ProbeWebsiteResponse | null = await WebsiteMonitor.ping( + monitorStep.data?.monitorDestination as URL, + { + isHeadRequest: MonitorUtil.isHeadRequest(monitorStep), + monitorId: monitor.id!, + retry: 5, + }, + ); + + if (!response) { + return null; + } + + result.isOnline = response.isOnline; + result.responseTimeInMs = response.responseTimeInMS?.toNumber(); + result.responseBody = response.responseBody?.toString(); + result.responseHeaders = response.responseHeaders; + result.responseCode = response.statusCode; + result.failureCause = response.failureCause; + } + + if (monitor.monitorType === MonitorType.API) { + if (!monitorStep.data?.monitorDestination) { + return result; + } + + result.monitorDestination = monitorStep.data.monitorDestination; + + let requestBody: JSONObject | undefined = undefined; + if ( + monitorStep.data?.requestBody && + typeof monitorStep.data?.requestBody === "string" + ) { + requestBody = JSONFunctions.parseJSONObject( + monitorStep.data?.requestBody, + ); + } + + const response: APIResponse | null = await ApiMonitor.ping( + monitorStep.data?.monitorDestination as URL, + { + requestHeaders: monitorStep.data?.requestHeaders || {}, + requestBody: requestBody || undefined, + monitorId: monitor.id!, + requestType: monitorStep.data?.requestType || HTTPMethod.GET, + retry: 5, + }, + ); + + if (!response) { + return null; + } + + result.isOnline = response.isOnline; + result.responseTimeInMs = response.responseTimeInMS?.toNumber(); + result.responseBody = response.responseBody; + result.responseHeaders = response.responseHeaders; + result.responseCode = response.statusCode; + result.failureCause = response.failureCause; + } + + return result; + } } diff --git a/Probe/Utils/Monitors/MonitorTypes/ApiMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/ApiMonitor.ts index 56d0da161a..3cacf42408 100644 --- a/Probe/Utils/Monitors/MonitorTypes/ApiMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/ApiMonitor.ts @@ -1,194 +1,194 @@ -import OnlineCheck from '../../OnlineCheck'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPMethod from 'Common/Types/API/HTTPMethod'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Headers from 'Common/Types/API/Headers'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Sleep from 'Common/Types/Sleep'; -import API from 'Common/Utils/API'; -import logger from 'CommonServer/Utils/Logger'; +import OnlineCheck from "../../OnlineCheck"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPMethod from "Common/Types/API/HTTPMethod"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Headers from "Common/Types/API/Headers"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Sleep from "Common/Types/Sleep"; +import API from "Common/Utils/API"; +import logger from "CommonServer/Utils/Logger"; export interface APIResponse { - url: URL; - requestHeaders: Headers; - requestBody: JSONObject; - isSecure: boolean; - responseTimeInMS: PositiveNumber; - statusCode: number; - responseBody: string; - responseHeaders: Headers; - isOnline: boolean; - failureCause: string; + url: URL; + requestHeaders: Headers; + requestBody: JSONObject; + isSecure: boolean; + responseTimeInMS: PositiveNumber; + statusCode: number; + responseBody: string; + responseHeaders: Headers; + isOnline: boolean; + failureCause: string; } export default class ApiMonitor { - public static async ping( - url: URL, - options: { - requestHeaders?: Headers | undefined; - requestBody?: JSONObject | undefined; - requestType?: HTTPMethod | undefined; - retry?: number | undefined; - currentRetryCount?: number | undefined; - monitorId?: ObjectID | undefined; - isOnlineCheckRequest?: boolean | undefined; - } - ): Promise<APIResponse | null> { - if (!options) { - options = {}; - } - - if (options?.currentRetryCount === undefined) { - options.currentRetryCount = 1; - } - - const requestType: HTTPMethod = options.requestType || HTTPMethod.GET; - - try { - logger.debug( - `API Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} - Retry: ${ - options.currentRetryCount - }` - ); - - let startTime: [number, number] = process.hrtime(); - let result: HTTPResponse<JSONObject> | HTTPErrorResponse = - await API.fetch( - requestType, - url, - options.requestBody || undefined, - options.requestHeaders || undefined - ); - - if ( - result.statusCode >= 400 && - result.statusCode < 600 && - requestType === HTTPMethod.HEAD - ) { - startTime = process.hrtime(); - result = await API.fetch( - HTTPMethod.GET, - url, - options.requestBody || undefined, - options.requestHeaders || undefined - ); - } - - if (result.statusCode >= 500 && result.statusCode < 600) { - // implement retry, just to be sure server is down. - if (!options) { - options = {}; - } - - if (!options.currentRetryCount) { - options.currentRetryCount = 0; // default value - } - - if (options.currentRetryCount < (options.retry || 5)) { - options.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(url, options); - } - } - - const endTime: [number, number] = process.hrtime(startTime); - const responseTimeInMS: PositiveNumber = new PositiveNumber( - (endTime[0] * 1000000000 + endTime[1]) / 1000000 - ); - - // if response time is greater than 10 seconds then give it one more try - - if ( - responseTimeInMS.toNumber() > 10000 && - options.currentRetryCount < (options.retry || 5) - ) { - options.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(url, options); - } - - const apiResponse: APIResponse = { - url: url, - requestHeaders: options.requestHeaders || {}, - // if server is responding, it is online. - isOnline: true, - isSecure: url.protocol === Protocol.HTTPS, - responseTimeInMS: responseTimeInMS, - statusCode: result.statusCode, - responseBody: JSON.stringify(result.data || {}), - responseHeaders: result.headers, - requestBody: options.requestBody || {}, - failureCause: '', - }; - - logger.debug( - `API Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} Success - Response: ${JSON.stringify( - apiResponse - )}` - ); - - return apiResponse; - } catch (err: unknown) { - if (!options) { - options = {}; - } - - if (!options.currentRetryCount) { - options.currentRetryCount = 0; // default value - } - - if (options.currentRetryCount < (options.retry || 5)) { - options.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(url, options); - } - - if (!options.isOnlineCheckRequest) { - if (!(await OnlineCheck.canProbeMonitorWebsiteMonitors())) { - logger.error( - `API Monitor - Probe is not online. Cannot ping ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err}` - ); - return null; - } - } - - const apiResponse: APIResponse = { - url: url, - isOnline: false, - requestBody: options.requestBody || {}, - requestHeaders: options.requestHeaders || {}, - isSecure: url.protocol === Protocol.HTTPS, - responseTimeInMS: new PositiveNumber(0), - statusCode: 0, - responseBody: '', - responseHeaders: {}, - failureCause: API.getFriendlyErrorMessage(err as Error), - }; - - // check if timeout exceeded and if yes, return null - if ( - (err as any).toString().includes('timeout') && - (err as any).toString().includes('exceeded') - ) { - logger.debug( - `API Monitor - Timeout exceeded ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err}` - ); - - apiResponse.failureCause = 'Timeout exceeded'; - apiResponse.isOnline = false; - } - - logger.error( - `API Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err} Response: ${JSON.stringify( - apiResponse - )}` - ); - - return apiResponse; - } + public static async ping( + url: URL, + options: { + requestHeaders?: Headers | undefined; + requestBody?: JSONObject | undefined; + requestType?: HTTPMethod | undefined; + retry?: number | undefined; + currentRetryCount?: number | undefined; + monitorId?: ObjectID | undefined; + isOnlineCheckRequest?: boolean | undefined; + }, + ): Promise<APIResponse | null> { + if (!options) { + options = {}; } + + if (options?.currentRetryCount === undefined) { + options.currentRetryCount = 1; + } + + const requestType: HTTPMethod = options.requestType || HTTPMethod.GET; + + try { + logger.debug( + `API Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} - Retry: ${ + options.currentRetryCount + }`, + ); + + let startTime: [number, number] = process.hrtime(); + let result: HTTPResponse<JSONObject> | HTTPErrorResponse = + await API.fetch( + requestType, + url, + options.requestBody || undefined, + options.requestHeaders || undefined, + ); + + if ( + result.statusCode >= 400 && + result.statusCode < 600 && + requestType === HTTPMethod.HEAD + ) { + startTime = process.hrtime(); + result = await API.fetch( + HTTPMethod.GET, + url, + options.requestBody || undefined, + options.requestHeaders || undefined, + ); + } + + if (result.statusCode >= 500 && result.statusCode < 600) { + // implement retry, just to be sure server is down. + if (!options) { + options = {}; + } + + if (!options.currentRetryCount) { + options.currentRetryCount = 0; // default value + } + + if (options.currentRetryCount < (options.retry || 5)) { + options.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(url, options); + } + } + + const endTime: [number, number] = process.hrtime(startTime); + const responseTimeInMS: PositiveNumber = new PositiveNumber( + (endTime[0] * 1000000000 + endTime[1]) / 1000000, + ); + + // if response time is greater than 10 seconds then give it one more try + + if ( + responseTimeInMS.toNumber() > 10000 && + options.currentRetryCount < (options.retry || 5) + ) { + options.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(url, options); + } + + const apiResponse: APIResponse = { + url: url, + requestHeaders: options.requestHeaders || {}, + // if server is responding, it is online. + isOnline: true, + isSecure: url.protocol === Protocol.HTTPS, + responseTimeInMS: responseTimeInMS, + statusCode: result.statusCode, + responseBody: JSON.stringify(result.data || {}), + responseHeaders: result.headers, + requestBody: options.requestBody || {}, + failureCause: "", + }; + + logger.debug( + `API Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} Success - Response: ${JSON.stringify( + apiResponse, + )}`, + ); + + return apiResponse; + } catch (err: unknown) { + if (!options) { + options = {}; + } + + if (!options.currentRetryCount) { + options.currentRetryCount = 0; // default value + } + + if (options.currentRetryCount < (options.retry || 5)) { + options.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(url, options); + } + + if (!options.isOnlineCheckRequest) { + if (!(await OnlineCheck.canProbeMonitorWebsiteMonitors())) { + logger.error( + `API Monitor - Probe is not online. Cannot ping ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err}`, + ); + return null; + } + } + + const apiResponse: APIResponse = { + url: url, + isOnline: false, + requestBody: options.requestBody || {}, + requestHeaders: options.requestHeaders || {}, + isSecure: url.protocol === Protocol.HTTPS, + responseTimeInMS: new PositiveNumber(0), + statusCode: 0, + responseBody: "", + responseHeaders: {}, + failureCause: API.getFriendlyErrorMessage(err as Error), + }; + + // check if timeout exceeded and if yes, return null + if ( + (err as any).toString().includes("timeout") && + (err as any).toString().includes("exceeded") + ) { + logger.debug( + `API Monitor - Timeout exceeded ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err}`, + ); + + apiResponse.failureCause = "Timeout exceeded"; + apiResponse.isOnline = false; + } + + logger.error( + `API Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err} Response: ${JSON.stringify( + apiResponse, + )}`, + ); + + return apiResponse; + } + } } diff --git a/Probe/Utils/Monitors/MonitorTypes/CustomCodeMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/CustomCodeMonitor.ts index deab4f85eb..dcb8a928fe 100644 --- a/Probe/Utils/Monitors/MonitorTypes/CustomCodeMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/CustomCodeMonitor.ts @@ -1,71 +1,71 @@ -import { PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS } from '../../../Config'; -import ReturnResult from 'Common/Types/IsolatedVM/ReturnResult'; -import CustomCodeMonitorResponse from 'Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse'; -import ObjectID from 'Common/Types/ObjectID'; -import logger from 'CommonServer/Utils/Logger'; -import VMRunner from 'CommonServer/Utils/VM/VMRunner'; +import { PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS } from "../../../Config"; +import ReturnResult from "Common/Types/IsolatedVM/ReturnResult"; +import CustomCodeMonitorResponse from "Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse"; +import ObjectID from "Common/Types/ObjectID"; +import logger from "CommonServer/Utils/Logger"; +import VMRunner from "CommonServer/Utils/VM/VMRunner"; export interface CustomCodeMonitorOptions { - monitorId?: ObjectID | undefined; - script: string; + monitorId?: ObjectID | undefined; + script: string; } export default class CustomCodeMonitor { - public static async execute( - options: CustomCodeMonitorOptions - ): Promise<CustomCodeMonitorResponse | null> { - if (!options) { - // this should never happen - options = { - script: '', - }; - } - - const scriptResult: CustomCodeMonitorResponse = { - logMessages: [], - scriptError: undefined, - result: undefined, - - executionTimeInMS: 0, - }; - - try { - let result: ReturnResult | null = null; - - try { - const startTime: [number, number] = process.hrtime(); - - result = await VMRunner.runCodeInSandbox({ - code: options.script, - options: { - timeout: PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS, - args: {}, - }, - }); - - const endTime: [number, number] = process.hrtime(startTime); - - const executionTimeInMS: number = - (endTime[0] * 1000000000 + endTime[1]) / 1000000; - - scriptResult.executionTimeInMS = executionTimeInMS; - - scriptResult.logMessages = result.logMessages; - - scriptResult.result = result?.returnValue?.data; - } catch (err) { - logger.error(err); - scriptResult.scriptError = - (err as Error)?.message || (err as Error).toString(); - } - - return scriptResult; - } catch (err: unknown) { - logger.error(err); - scriptResult.scriptError = - (err as Error)?.message || (err as Error).toString(); - } - - return scriptResult; + public static async execute( + options: CustomCodeMonitorOptions, + ): Promise<CustomCodeMonitorResponse | null> { + if (!options) { + // this should never happen + options = { + script: "", + }; } + + const scriptResult: CustomCodeMonitorResponse = { + logMessages: [], + scriptError: undefined, + result: undefined, + + executionTimeInMS: 0, + }; + + try { + let result: ReturnResult | null = null; + + try { + const startTime: [number, number] = process.hrtime(); + + result = await VMRunner.runCodeInSandbox({ + code: options.script, + options: { + timeout: PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS, + args: {}, + }, + }); + + const endTime: [number, number] = process.hrtime(startTime); + + const executionTimeInMS: number = + (endTime[0] * 1000000000 + endTime[1]) / 1000000; + + scriptResult.executionTimeInMS = executionTimeInMS; + + scriptResult.logMessages = result.logMessages; + + scriptResult.result = result?.returnValue?.data; + } catch (err) { + logger.error(err); + scriptResult.scriptError = + (err as Error)?.message || (err as Error).toString(); + } + + return scriptResult; + } catch (err: unknown) { + logger.error(err); + scriptResult.scriptError = + (err as Error)?.message || (err as Error).toString(); + } + + return scriptResult; + } } diff --git a/Probe/Utils/Monitors/MonitorTypes/PingMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/PingMonitor.ts index aeffb36c50..982df45f9d 100644 --- a/Probe/Utils/Monitors/MonitorTypes/PingMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/PingMonitor.ts @@ -1,156 +1,149 @@ -import OnlineCheck from '../../OnlineCheck'; -import Hostname from 'Common/Types/API/Hostname'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import UnableToReachServer from 'Common/Types/Exception/UnableToReachServer'; -import IPv4 from 'Common/Types/IP/IPv4'; -import IPv6 from 'Common/Types/IP/IPv6'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Sleep from 'Common/Types/Sleep'; -import logger from 'CommonServer/Utils/Logger'; -import ping from 'ping'; +import OnlineCheck from "../../OnlineCheck"; +import Hostname from "Common/Types/API/Hostname"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import UnableToReachServer from "Common/Types/Exception/UnableToReachServer"; +import IPv4 from "Common/Types/IP/IPv4"; +import IPv6 from "Common/Types/IP/IPv6"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Sleep from "Common/Types/Sleep"; +import logger from "CommonServer/Utils/Logger"; +import ping from "ping"; // TODO - make sure it work for the IPV6 export interface PingResponse { - isOnline: boolean; - responseTimeInMS?: PositiveNumber | undefined; - failureCause: string; + isOnline: boolean; + responseTimeInMS?: PositiveNumber | undefined; + failureCause: string; } export interface PingOptions { - timeout?: PositiveNumber; - retry?: number | undefined; - currentRetryCount?: number | undefined; - monitorId?: ObjectID | undefined; - isOnlineCheckRequest?: boolean | undefined; + timeout?: PositiveNumber; + retry?: number | undefined; + currentRetryCount?: number | undefined; + monitorId?: ObjectID | undefined; + isOnlineCheckRequest?: boolean | undefined; } export default class PingMonitor { - public static async ping( - host: Hostname | IPv4 | IPv6 | URL, - pingOptions?: PingOptions - ): Promise<PingResponse | null> { - if (!pingOptions) { - pingOptions = {}; - } + public static async ping( + host: Hostname | IPv4 | IPv6 | URL, + pingOptions?: PingOptions, + ): Promise<PingResponse | null> { + if (!pingOptions) { + pingOptions = {}; + } - if (pingOptions?.currentRetryCount === undefined) { - pingOptions.currentRetryCount = 1; - } + if (pingOptions?.currentRetryCount === undefined) { + pingOptions.currentRetryCount = 1; + } - let hostAddress: string = ''; - if (host instanceof Hostname) { - hostAddress = host.hostname; + let hostAddress: string = ""; + if (host instanceof Hostname) { + hostAddress = host.hostname; - if (host.port) { - throw new BadDataException( - 'Port is not supported for ping monitor' - ); - } - } else if (host instanceof URL) { - hostAddress = host.hostname.hostname; - } else { - hostAddress = host.toString(); - } + if (host.port) { + throw new BadDataException("Port is not supported for ping monitor"); + } + } else if (host instanceof URL) { + hostAddress = host.hostname.hostname; + } else { + hostAddress = host.toString(); + } + logger.debug( + `Pinging host: ${pingOptions?.monitorId?.toString()} ${hostAddress} - Retry: ${ + pingOptions?.currentRetryCount + }`, + ); + + try { + const res: ping.PingResponse = await ping.promise.probe(hostAddress, { + timeout: Math.ceil((pingOptions?.timeout?.toNumber() || 5000) / 1000), + }); + + logger.debug( + `Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress} success: `, + ); + logger.debug(res); + + if (!res.alive) { + throw new UnableToReachServer( + `Unable to reach host ${hostAddress}. Monitor ID: ${pingOptions?.monitorId?.toString()}`, + ); + } + + const responseTime: PositiveNumber | undefined = res.time + ? new PositiveNumber(Math.ceil(res.time as any)) + : undefined; + + // if response time is greater than 10 seconds then give it one more try + + if ( + responseTime?.toNumber() && + responseTime.toNumber() > 10000 && + pingOptions.currentRetryCount < (pingOptions.retry || 5) + ) { + pingOptions.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(host, pingOptions); + } + + return { + isOnline: res.alive, + responseTimeInMS: responseTime, + failureCause: "", + }; + } catch (err: unknown) { + logger.debug( + `Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress} error: `, + ); + logger.debug(err); + + if (!pingOptions) { + pingOptions = {}; + } + + if (!pingOptions.currentRetryCount) { + pingOptions.currentRetryCount = 0; + } + + if (pingOptions.currentRetryCount < (pingOptions.retry || 5)) { + pingOptions.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(host, pingOptions); + } + + // check if timeout exceeded and if yes, return null + if ( + (err as any).toString().includes("timeout") && + (err as any).toString().includes("exceeded") + ) { logger.debug( - `Pinging host: ${pingOptions?.monitorId?.toString()} ${hostAddress} - Retry: ${ - pingOptions?.currentRetryCount - }` + `Ping Monitor - Timeout exceeded ${pingOptions.monitorId?.toString()} ${host.toString()} - ERROR: ${err}`, ); - try { - const res: ping.PingResponse = await ping.promise.probe( - hostAddress, - { - timeout: Math.ceil( - (pingOptions?.timeout?.toNumber() || 5000) / 1000 - ), - } - ); + return { + isOnline: false, + failureCause: "Timeout exceeded", + }; + } - logger.debug( - `Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress} success: ` - ); - logger.debug(res); - - if (!res.alive) { - throw new UnableToReachServer( - `Unable to reach host ${hostAddress}. Monitor ID: ${pingOptions?.monitorId?.toString()}` - ); - } - - const responseTime: PositiveNumber | undefined = res.time - ? new PositiveNumber(Math.ceil(res.time as any)) - : undefined; - - // if response time is greater than 10 seconds then give it one more try - - if ( - responseTime?.toNumber() && - responseTime.toNumber() > 10000 && - pingOptions.currentRetryCount < (pingOptions.retry || 5) - ) { - pingOptions.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(host, pingOptions); - } - - return { - isOnline: res.alive, - responseTimeInMS: responseTime, - failureCause: '', - }; - } catch (err: unknown) { - logger.debug( - `Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress} error: ` - ); - logger.debug(err); - - if (!pingOptions) { - pingOptions = {}; - } - - if (!pingOptions.currentRetryCount) { - pingOptions.currentRetryCount = 0; - } - - if (pingOptions.currentRetryCount < (pingOptions.retry || 5)) { - pingOptions.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(host, pingOptions); - } - - // check if timeout exceeded and if yes, return null - if ( - (err as any).toString().includes('timeout') && - (err as any).toString().includes('exceeded') - ) { - logger.debug( - `Ping Monitor - Timeout exceeded ${pingOptions.monitorId?.toString()} ${host.toString()} - ERROR: ${err}` - ); - - return { - isOnline: false, - failureCause: 'Timeout exceeded', - }; - } - - // check if the probe is online. - if (!pingOptions.isOnlineCheckRequest) { - if (!(await OnlineCheck.canProbeMonitorPingMonitors())) { - logger.error( - `PingMonitor Monitor - Probe is not online. Cannot ping ${pingOptions?.monitorId?.toString()} ${host.toString()} - ERROR: ${err}` - ); - return null; - } - } - - return { - isOnline: false, - failureCause: (err as any).toString(), - }; + // check if the probe is online. + if (!pingOptions.isOnlineCheckRequest) { + if (!(await OnlineCheck.canProbeMonitorPingMonitors())) { + logger.error( + `PingMonitor Monitor - Probe is not online. Cannot ping ${pingOptions?.monitorId?.toString()} ${host.toString()} - ERROR: ${err}`, + ); + return null; } + } + + return { + isOnline: false, + failureCause: (err as any).toString(), + }; } + } } diff --git a/Probe/Utils/Monitors/MonitorTypes/PortMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/PortMonitor.ts index a94124ec76..5cbb17a76c 100644 --- a/Probe/Utils/Monitors/MonitorTypes/PortMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/PortMonitor.ts @@ -1,200 +1,195 @@ -import OnlineCheck from '../../OnlineCheck'; -import Hostname from 'Common/Types/API/Hostname'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import UnableToReachServer from 'Common/Types/Exception/UnableToReachServer'; -import { PromiseRejectErrorFunction } from 'Common/Types/FunctionTypes'; -import IPv4 from 'Common/Types/IP/IPv4'; -import IPv6 from 'Common/Types/IP/IPv6'; -import ObjectID from 'Common/Types/ObjectID'; -import Port from 'Common/Types/Port'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Sleep from 'Common/Types/Sleep'; -import logger from 'CommonServer/Utils/Logger'; -import net from 'net'; +import OnlineCheck from "../../OnlineCheck"; +import Hostname from "Common/Types/API/Hostname"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import UnableToReachServer from "Common/Types/Exception/UnableToReachServer"; +import { PromiseRejectErrorFunction } from "Common/Types/FunctionTypes"; +import IPv4 from "Common/Types/IP/IPv4"; +import IPv6 from "Common/Types/IP/IPv6"; +import ObjectID from "Common/Types/ObjectID"; +import Port from "Common/Types/Port"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Sleep from "Common/Types/Sleep"; +import logger from "CommonServer/Utils/Logger"; +import net from "net"; // TODO - make sure it work for the IPV6 export interface PortMonitorResponse { - isOnline: boolean; - responseTimeInMS?: PositiveNumber | undefined; - failureCause: string; + isOnline: boolean; + responseTimeInMS?: PositiveNumber | undefined; + failureCause: string; } export interface PingOptions { - timeout?: PositiveNumber; - retry?: number | undefined; - currentRetryCount?: number | undefined; - monitorId?: ObjectID | undefined; - isOnlineCheckRequest?: boolean | undefined; + timeout?: PositiveNumber; + retry?: number | undefined; + currentRetryCount?: number | undefined; + monitorId?: ObjectID | undefined; + isOnlineCheckRequest?: boolean | undefined; } export default class PortMonitor { - public static async ping( - host: Hostname | IPv4 | IPv6 | URL, - port: Port, - pingOptions?: PingOptions - ): Promise<PortMonitorResponse | null> { - if (!pingOptions) { - pingOptions = {}; - } - - if (pingOptions?.currentRetryCount === undefined) { - pingOptions.currentRetryCount = 1; - } - - let hostAddress: string = ''; - if (host instanceof Hostname) { - hostAddress = host.hostname; - - if (host.port) { - port = host.port; - } - } else if (host instanceof URL) { - hostAddress = host.hostname.hostname; - - if (host.hostname.port) { - port = host.hostname.port; - } - } else { - hostAddress = host.toString(); - } - - if (!port) { - throw new BadDataException('Port is not specified'); - } - - logger.debug( - `Pinging host: ${pingOptions?.monitorId?.toString()} ${hostAddress}:${port.toString()} - Retry: ${ - pingOptions?.currentRetryCount - }` - ); - - try { - // Ping a host with port - - const promiseResult: Promise<PositiveNumber> = new Promise( - ( - resolve: (responseTimeInMS: PositiveNumber) => void, - reject: PromiseRejectErrorFunction - ) => { - const startTime: [number, number] = process.hrtime(); - - const socket: net.Socket = new net.Socket(); - - const timeout: number = - pingOptions?.timeout?.toNumber() || 5000; - - socket.setTimeout(timeout); - - if (!port) { - throw new BadDataException('Port is not specified'); - } - - let hasPromiseResolved: boolean = false; - - socket.connect(port.toNumber(), hostAddress, () => { - const endTime: [number, number] = - process.hrtime(startTime); - const responseTimeInMS: PositiveNumber = - new PositiveNumber( - (endTime[0] * 1000000000 + endTime[1]) / 1000000 - ); - - logger.debug( - `Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress}:${port!.toString()} success: Response Time ${responseTimeInMS} ms` - ); - - socket.destroy(); // Close the connection after success - if (!hasPromiseResolved) { - resolve(responseTimeInMS); - } - - hasPromiseResolved = true; - return; - }); - - socket.on('timeout', () => { - socket.destroy(); - logger.debug('Ping timeout'); - - if (!hasPromiseResolved) { - reject(new UnableToReachServer('Ping timeout')); - } - - hasPromiseResolved = true; - return; - }); - - socket.on('error', (error: Error) => { - socket.destroy(); - logger.debug( - 'Could not connect to: ' + host + ':' + port - ); - - if (!hasPromiseResolved) { - reject(error); - } - - hasPromiseResolved = true; - return; - }); - } - ); - - const responseTimeInMS: PositiveNumber = - (await promiseResult) as PositiveNumber; - - // if response time is greater than 10 seconds then give it one more try - - if ( - responseTimeInMS.toNumber() > 10000 && - pingOptions.currentRetryCount < (pingOptions.retry || 5) - ) { - pingOptions.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(host, port, pingOptions); - } - - return { - isOnline: true, - responseTimeInMS: responseTimeInMS, - failureCause: '', - }; - } catch (err: unknown) { - logger.debug( - `Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress}:${port.toString()} error: ` - ); - - logger.debug(err); - - if (!pingOptions) { - pingOptions = {}; - } - - if (!pingOptions.currentRetryCount) { - pingOptions.currentRetryCount = 0; - } - - if (pingOptions.currentRetryCount < (pingOptions.retry || 5)) { - pingOptions.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(host, port, pingOptions); - } - - // check if the probe is online. - if (!pingOptions.isOnlineCheckRequest) { - if (!(await OnlineCheck.canProbeMonitorPortMonitors())) { - logger.error( - `PortMonitor Monitor - Probe is not online. Cannot ping ${pingOptions?.monitorId?.toString()} ${host.toString()} - ERROR: ${err}` - ); - return null; - } - } - - return { - isOnline: false, - failureCause: (err as any).toString(), - }; - } + public static async ping( + host: Hostname | IPv4 | IPv6 | URL, + port: Port, + pingOptions?: PingOptions, + ): Promise<PortMonitorResponse | null> { + if (!pingOptions) { + pingOptions = {}; } + + if (pingOptions?.currentRetryCount === undefined) { + pingOptions.currentRetryCount = 1; + } + + let hostAddress: string = ""; + if (host instanceof Hostname) { + hostAddress = host.hostname; + + if (host.port) { + port = host.port; + } + } else if (host instanceof URL) { + hostAddress = host.hostname.hostname; + + if (host.hostname.port) { + port = host.hostname.port; + } + } else { + hostAddress = host.toString(); + } + + if (!port) { + throw new BadDataException("Port is not specified"); + } + + logger.debug( + `Pinging host: ${pingOptions?.monitorId?.toString()} ${hostAddress}:${port.toString()} - Retry: ${ + pingOptions?.currentRetryCount + }`, + ); + + try { + // Ping a host with port + + const promiseResult: Promise<PositiveNumber> = new Promise( + ( + resolve: (responseTimeInMS: PositiveNumber) => void, + reject: PromiseRejectErrorFunction, + ) => { + const startTime: [number, number] = process.hrtime(); + + const socket: net.Socket = new net.Socket(); + + const timeout: number = pingOptions?.timeout?.toNumber() || 5000; + + socket.setTimeout(timeout); + + if (!port) { + throw new BadDataException("Port is not specified"); + } + + let hasPromiseResolved: boolean = false; + + socket.connect(port.toNumber(), hostAddress, () => { + const endTime: [number, number] = process.hrtime(startTime); + const responseTimeInMS: PositiveNumber = new PositiveNumber( + (endTime[0] * 1000000000 + endTime[1]) / 1000000, + ); + + logger.debug( + `Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress}:${port!.toString()} success: Response Time ${responseTimeInMS} ms`, + ); + + socket.destroy(); // Close the connection after success + if (!hasPromiseResolved) { + resolve(responseTimeInMS); + } + + hasPromiseResolved = true; + return; + }); + + socket.on("timeout", () => { + socket.destroy(); + logger.debug("Ping timeout"); + + if (!hasPromiseResolved) { + reject(new UnableToReachServer("Ping timeout")); + } + + hasPromiseResolved = true; + return; + }); + + socket.on("error", (error: Error) => { + socket.destroy(); + logger.debug("Could not connect to: " + host + ":" + port); + + if (!hasPromiseResolved) { + reject(error); + } + + hasPromiseResolved = true; + return; + }); + }, + ); + + const responseTimeInMS: PositiveNumber = + (await promiseResult) as PositiveNumber; + + // if response time is greater than 10 seconds then give it one more try + + if ( + responseTimeInMS.toNumber() > 10000 && + pingOptions.currentRetryCount < (pingOptions.retry || 5) + ) { + pingOptions.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(host, port, pingOptions); + } + + return { + isOnline: true, + responseTimeInMS: responseTimeInMS, + failureCause: "", + }; + } catch (err: unknown) { + logger.debug( + `Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress}:${port.toString()} error: `, + ); + + logger.debug(err); + + if (!pingOptions) { + pingOptions = {}; + } + + if (!pingOptions.currentRetryCount) { + pingOptions.currentRetryCount = 0; + } + + if (pingOptions.currentRetryCount < (pingOptions.retry || 5)) { + pingOptions.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(host, port, pingOptions); + } + + // check if the probe is online. + if (!pingOptions.isOnlineCheckRequest) { + if (!(await OnlineCheck.canProbeMonitorPortMonitors())) { + logger.error( + `PortMonitor Monitor - Probe is not online. Cannot ping ${pingOptions?.monitorId?.toString()} ${host.toString()} - ERROR: ${err}`, + ); + return null; + } + } + + return { + isOnline: false, + failureCause: (err as any).toString(), + }; + } + } } diff --git a/Probe/Utils/Monitors/MonitorTypes/SslMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/SslMonitor.ts index 0aec3d3f14..745c436b0a 100644 --- a/Probe/Utils/Monitors/MonitorTypes/SslMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/SslMonitor.ts @@ -1,247 +1,242 @@ -import OnlineCheck from '../../OnlineCheck'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import SSLMonitorReponse from 'Common/Types/Monitor/SSLMonitor/SslMonitorResponse'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Sleep from 'Common/Types/Sleep'; -import API from 'Common/Utils/API'; -import ObjectUtil from 'Common/Utils/ObjectUtil'; -import logger from 'CommonServer/Utils/Logger'; -import { ClientRequest, IncomingMessage } from 'http'; -import https, { RequestOptions } from 'https'; -import tls, { TLSSocket } from 'tls'; +import OnlineCheck from "../../OnlineCheck"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import SSLMonitorReponse from "Common/Types/Monitor/SSLMonitor/SslMonitorResponse"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Sleep from "Common/Types/Sleep"; +import API from "Common/Utils/API"; +import ObjectUtil from "Common/Utils/ObjectUtil"; +import logger from "CommonServer/Utils/Logger"; +import { ClientRequest, IncomingMessage } from "http"; +import https, { RequestOptions } from "https"; +import tls, { TLSSocket } from "tls"; export interface SslResponse extends SSLMonitorReponse { - isOnline: boolean; - failureCause: string; + isOnline: boolean; + failureCause: string; } export interface SSLMonitorOptions { - timeout?: PositiveNumber; - retry?: number | undefined; - currentRetryCount?: number | undefined; - monitorId?: ObjectID | undefined; - isOnlineCheckRequest?: boolean | undefined; + timeout?: PositiveNumber; + retry?: number | undefined; + currentRetryCount?: number | undefined; + monitorId?: ObjectID | undefined; + isOnlineCheckRequest?: boolean | undefined; } export default class SSLMonitor { - // burn domain names into the code to see if this probe is online. + // burn domain names into the code to see if this probe is online. - public static async ping( - url: URL, - pingOptions?: SSLMonitorOptions - ): Promise<SslResponse | null> { - if (!pingOptions) { - pingOptions = {}; - } + public static async ping( + url: URL, + pingOptions?: SSLMonitorOptions, + ): Promise<SslResponse | null> { + if (!pingOptions) { + pingOptions = {}; + } - if (pingOptions?.currentRetryCount === undefined) { - pingOptions.currentRetryCount = 1; - } + if (pingOptions?.currentRetryCount === undefined) { + pingOptions.currentRetryCount = 1; + } + logger.debug( + `Pinging host: ${pingOptions?.monitorId?.toString()} ${url.toString()} - Retry: ${ + pingOptions?.currentRetryCount + }`, + ); + + try { + const res: SslResponse = await this.getSslMonitorResponse( + url.hostname.hostname, + url.hostname.port?.toNumber() || 443, + ); + + logger.debug( + `Pinging host ${pingOptions?.monitorId?.toString()} ${url.toString()} success: `, + ); + logger.debug(res); + + return res; + } catch (err: unknown) { + logger.debug( + `Pinging host ${pingOptions?.monitorId?.toString()} ${url.toString()} error: `, + ); + logger.debug(err); + + if (!pingOptions) { + pingOptions = {}; + } + + if (!pingOptions.currentRetryCount) { + pingOptions.currentRetryCount = 0; + } + + if (pingOptions.currentRetryCount < (pingOptions.retry || 5)) { + pingOptions.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(url, pingOptions); + } + + // check if timeout exceeded and if yes, return null + if ( + (err as any).toString().includes("timeout") && + (err as any).toString().includes("exceeded") + ) { logger.debug( - `Pinging host: ${pingOptions?.monitorId?.toString()} ${url.toString()} - Retry: ${ - pingOptions?.currentRetryCount - }` + `Ping Monitor - Timeout exceeded ${pingOptions.monitorId?.toString()} ${url.toString()} - ERROR: ${err}`, ); - try { - const res: SslResponse = await this.getSslMonitorResponse( - url.hostname.hostname, - url.hostname.port?.toNumber() || 443 - ); - - logger.debug( - `Pinging host ${pingOptions?.monitorId?.toString()} ${url.toString()} success: ` - ); - logger.debug(res); - - return res; - } catch (err: unknown) { - logger.debug( - `Pinging host ${pingOptions?.monitorId?.toString()} ${url.toString()} error: ` - ); - logger.debug(err); - - if (!pingOptions) { - pingOptions = {}; - } - - if (!pingOptions.currentRetryCount) { - pingOptions.currentRetryCount = 0; - } - - if (pingOptions.currentRetryCount < (pingOptions.retry || 5)) { - pingOptions.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(url, pingOptions); - } - - // check if timeout exceeded and if yes, return null - if ( - (err as any).toString().includes('timeout') && - (err as any).toString().includes('exceeded') - ) { - logger.debug( - `Ping Monitor - Timeout exceeded ${pingOptions.monitorId?.toString()} ${url.toString()} - ERROR: ${err}` - ); - - return { - isOnline: false, - failureCause: 'Timeout exceeded', - }; - } - - // check if the probe is online. - if (!pingOptions.isOnlineCheckRequest) { - if (!(await OnlineCheck.canProbeMonitorWebsiteMonitors())) { - logger.error( - `PingMonitor Monitor - Probe is not online. Cannot ping ${pingOptions?.monitorId?.toString()} ${url.toString()} - ERROR: ${err}` - ); - return null; - } - } - - return { - isOnline: false, - failureCause: API.getFriendlyErrorMessage(err as Error), - }; - } - } - - public static async getSslMonitorResponse( - host: string, - port = 443 - ): Promise<SslResponse> { - let isSelfSigned: boolean = false; - let certificate: tls.PeerCertificate | null = null; - - try { - certificate = await this.getCertificate({ - host, - port, - rejectUnauthorized: true, - }); - } catch (err) { - try { - certificate = await this.getCertificate({ - host, - port, - rejectUnauthorized: false, - }); - - isSelfSigned = true; - } catch (err) { - return { - isOnline: false, - failureCause: API.getFriendlyErrorMessage(err as Error), - }; - } - } - - if (!certificate) { - return { - isOnline: false, - failureCause: 'No certificate found', - }; - } - - const res: SslResponse = { - isOnline: true, - isSelfSigned: isSelfSigned, - createdAt: OneUptimeDate.fromString(certificate.valid_from), - expiresAt: OneUptimeDate.fromString(certificate.valid_to), - commonName: certificate.subject.CN, - organizationalUnit: certificate.subject.OU, - organization: certificate.subject.O, - locality: certificate.subject.L, - state: certificate.subject.ST, - country: certificate.subject.C, - serialNumber: certificate.serialNumber, - fingerprint: certificate.fingerprint, - fingerprint256: certificate.fingerprint256, - failureCause: '', - }; - - return res; - } - - public static async getCertificate(data: { - host: string; - port: number; - rejectUnauthorized: boolean; - }): Promise<tls.PeerCertificate> { - const { host, rejectUnauthorized } = data; - - let { port } = data; - - if (!port) { - port = 443; - } - - const sslPromise: Promise<tls.PeerCertificate> = new Promise( - ( - resolve: (value: tls.PeerCertificate) => void, - reject: (err: Error) => void - ) => { - const requestOptions: https.RequestOptions = this.getOptions( - host, - port, - rejectUnauthorized - ); - - let isResolvedOrRejected: boolean = false; - - const req: ClientRequest = https.get( - requestOptions, - (res: IncomingMessage) => { - const certificate: tls.PeerCertificate = ( - res.socket as TLSSocket - ).getPeerCertificate(); - if ( - ObjectUtil.isEmpty(certificate) || - certificate === null - ) { - isResolvedOrRejected = true; - return reject( - new BadDataException('No certificate found') - ); - } - isResolvedOrRejected = true; - return resolve(certificate); - } - ); - - req.end(); - - req.on('error', (err: Error) => { - if (!isResolvedOrRejected) { - isResolvedOrRejected = true; - return reject(err); - } - }); - } - ); - - const certificate: tls.PeerCertificate = await sslPromise; - - return certificate; - } - - private static getOptions( - url: string, - port: number, - rejectUnauthorized: boolean - ): RequestOptions { return { - hostname: url, - agent: false, - rejectUnauthorized: rejectUnauthorized, - ciphers: 'ALL', - port, - protocol: 'https:', + isOnline: false, + failureCause: "Timeout exceeded", }; + } + + // check if the probe is online. + if (!pingOptions.isOnlineCheckRequest) { + if (!(await OnlineCheck.canProbeMonitorWebsiteMonitors())) { + logger.error( + `PingMonitor Monitor - Probe is not online. Cannot ping ${pingOptions?.monitorId?.toString()} ${url.toString()} - ERROR: ${err}`, + ); + return null; + } + } + + return { + isOnline: false, + failureCause: API.getFriendlyErrorMessage(err as Error), + }; } + } + + public static async getSslMonitorResponse( + host: string, + port = 443, + ): Promise<SslResponse> { + let isSelfSigned: boolean = false; + let certificate: tls.PeerCertificate | null = null; + + try { + certificate = await this.getCertificate({ + host, + port, + rejectUnauthorized: true, + }); + } catch (err) { + try { + certificate = await this.getCertificate({ + host, + port, + rejectUnauthorized: false, + }); + + isSelfSigned = true; + } catch (err) { + return { + isOnline: false, + failureCause: API.getFriendlyErrorMessage(err as Error), + }; + } + } + + if (!certificate) { + return { + isOnline: false, + failureCause: "No certificate found", + }; + } + + const res: SslResponse = { + isOnline: true, + isSelfSigned: isSelfSigned, + createdAt: OneUptimeDate.fromString(certificate.valid_from), + expiresAt: OneUptimeDate.fromString(certificate.valid_to), + commonName: certificate.subject.CN, + organizationalUnit: certificate.subject.OU, + organization: certificate.subject.O, + locality: certificate.subject.L, + state: certificate.subject.ST, + country: certificate.subject.C, + serialNumber: certificate.serialNumber, + fingerprint: certificate.fingerprint, + fingerprint256: certificate.fingerprint256, + failureCause: "", + }; + + return res; + } + + public static async getCertificate(data: { + host: string; + port: number; + rejectUnauthorized: boolean; + }): Promise<tls.PeerCertificate> { + const { host, rejectUnauthorized } = data; + + let { port } = data; + + if (!port) { + port = 443; + } + + const sslPromise: Promise<tls.PeerCertificate> = new Promise( + ( + resolve: (value: tls.PeerCertificate) => void, + reject: (err: Error) => void, + ) => { + const requestOptions: https.RequestOptions = this.getOptions( + host, + port, + rejectUnauthorized, + ); + + let isResolvedOrRejected: boolean = false; + + const req: ClientRequest = https.get( + requestOptions, + (res: IncomingMessage) => { + const certificate: tls.PeerCertificate = ( + res.socket as TLSSocket + ).getPeerCertificate(); + if (ObjectUtil.isEmpty(certificate) || certificate === null) { + isResolvedOrRejected = true; + return reject(new BadDataException("No certificate found")); + } + isResolvedOrRejected = true; + return resolve(certificate); + }, + ); + + req.end(); + + req.on("error", (err: Error) => { + if (!isResolvedOrRejected) { + isResolvedOrRejected = true; + return reject(err); + } + }); + }, + ); + + const certificate: tls.PeerCertificate = await sslPromise; + + return certificate; + } + + private static getOptions( + url: string, + port: number, + rejectUnauthorized: boolean, + ): RequestOptions { + return { + hostname: url, + agent: false, + rejectUnauthorized: rejectUnauthorized, + ciphers: "ALL", + port, + protocol: "https:", + }; + } } diff --git a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts index afec944104..cfc6a0d452 100644 --- a/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/SyntheticMonitor.ts @@ -1,246 +1,241 @@ -import { PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS } from '../../../Config'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ReturnResult from 'Common/Types/IsolatedVM/ReturnResult'; -import BrowserType from 'Common/Types/Monitor/SyntheticMonitors/BrowserType'; -import ScreenSizeType from 'Common/Types/Monitor/SyntheticMonitors/ScreenSizeType'; -import SyntheticMonitorResponse from 'Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse'; -import ObjectID from 'Common/Types/ObjectID'; -import logger from 'CommonServer/Utils/Logger'; -import VMRunner from 'CommonServer/Utils/VM/VMRunner'; -import { Browser, Page, chromium, firefox } from 'playwright'; +import { PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS } from "../../../Config"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ReturnResult from "Common/Types/IsolatedVM/ReturnResult"; +import BrowserType from "Common/Types/Monitor/SyntheticMonitors/BrowserType"; +import ScreenSizeType from "Common/Types/Monitor/SyntheticMonitors/ScreenSizeType"; +import SyntheticMonitorResponse from "Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse"; +import ObjectID from "Common/Types/ObjectID"; +import logger from "CommonServer/Utils/Logger"; +import VMRunner from "CommonServer/Utils/VM/VMRunner"; +import { Browser, Page, chromium, firefox } from "playwright"; export interface SyntheticMonitorOptions { - monitorId?: ObjectID | undefined; - screenSizeTypes?: Array<ScreenSizeType> | undefined; - browserTypes?: Array<BrowserType> | undefined; - script: string; + monitorId?: ObjectID | undefined; + screenSizeTypes?: Array<ScreenSizeType> | undefined; + browserTypes?: Array<BrowserType> | undefined; + script: string; } export default class SyntheticMonitor { - public static async execute( - options: SyntheticMonitorOptions - ): Promise<Array<SyntheticMonitorResponse> | null> { - const results: Array<SyntheticMonitorResponse> = []; + public static async execute( + options: SyntheticMonitorOptions, + ): Promise<Array<SyntheticMonitorResponse> | null> { + const results: Array<SyntheticMonitorResponse> = []; - for (const browserType of options.browserTypes || []) { - for (const screenSizeType of options.screenSizeTypes || []) { - logger.debug( - `Running Synthetic Monitor: ${options?.monitorId?.toString()}, Screen Size: ${screenSizeType}, Browser: ${browserType}` - ); + for (const browserType of options.browserTypes || []) { + for (const screenSizeType of options.screenSizeTypes || []) { + logger.debug( + `Running Synthetic Monitor: ${options?.monitorId?.toString()}, Screen Size: ${screenSizeType}, Browser: ${browserType}`, + ); - const result: SyntheticMonitorResponse | null = - await this.executeByBrowserAndScreenSize({ - script: options.script, - browserType: browserType, - screenSizeType: screenSizeType, - }); + const result: SyntheticMonitorResponse | null = + await this.executeByBrowserAndScreenSize({ + script: options.script, + browserType: browserType, + screenSizeType: screenSizeType, + }); - if (result) { - result.browserType = browserType; - result.screenSizeType = screenSizeType; - results.push(result); - } - } + if (result) { + result.browserType = browserType; + result.screenSizeType = screenSizeType; + results.push(result); } - - return results; + } } - private static async executeByBrowserAndScreenSize(options: { - script: string; - browserType: BrowserType; - screenSizeType: ScreenSizeType; - }): Promise<SyntheticMonitorResponse | null> { - if (!options) { - // this should never happen - options = { - script: '', - browserType: BrowserType.Chromium, - screenSizeType: ScreenSizeType.Desktop, - }; - } + return results; + } - const scriptResult: SyntheticMonitorResponse = { - logMessages: [], - scriptError: undefined, - result: undefined, - screenshots: {}, - executionTimeInMS: 0, - browserType: options.browserType, - screenSizeType: options.screenSizeType, - }; - - try { - let result: ReturnResult | null = null; - - let pageAndBrowser: { - page: Page; - browser: Browser; - } | null = null; - - try { - const startTime: [number, number] = process.hrtime(); - - pageAndBrowser = await SyntheticMonitor.getPageByBrowserType({ - browserType: options.browserType, - screenSizeType: options.screenSizeType, - }); - - result = await VMRunner.runCodeInSandbox({ - code: options.script, - options: { - timeout: PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS, - args: {}, - context: { - page: pageAndBrowser.page, - screenSizeType: options.screenSizeType, - browserType: options.browserType, - }, - }, - }); - - const endTime: [number, number] = process.hrtime(startTime); - - const executionTimeInMS: number = - (endTime[0] * 1000000000 + endTime[1]) / 1000000; - - scriptResult.executionTimeInMS = executionTimeInMS; - - scriptResult.logMessages = result.logMessages; - - if (result.returnValue?.screenshots) { - if (!scriptResult.screenshots) { - scriptResult.screenshots = {}; - } - - for (const screenshotName in result.returnValue - .screenshots) { - if (!result.returnValue.screenshots[screenshotName]) { - continue; - } - - // check if this is of type Buffer. If it is not, continue. - - if ( - !( - result.returnValue.screenshots[ - screenshotName - ] instanceof Buffer - ) - ) { - continue; - } - - scriptResult.screenshots[screenshotName] = ( - result.returnValue.screenshots[ - screenshotName - ] as any - ).toString('base64'); // convert screennshots to base 64 - } - } - - scriptResult.result = result?.returnValue?.data; - } catch (err) { - logger.error(err); - scriptResult.scriptError = - (err as Error)?.message || (err as Error).toString(); - } - - if (pageAndBrowser?.browser) { - await pageAndBrowser.browser.close(); - } - - return scriptResult; - } catch (err: unknown) { - logger.error(err); - scriptResult.scriptError = - (err as Error)?.message || (err as Error).toString(); - } - - return scriptResult; + private static async executeByBrowserAndScreenSize(options: { + script: string; + browserType: BrowserType; + screenSizeType: ScreenSizeType; + }): Promise<SyntheticMonitorResponse | null> { + if (!options) { + // this should never happen + options = { + script: "", + browserType: BrowserType.Chromium, + screenSizeType: ScreenSizeType.Desktop, + }; } - private static getViewportHeightAndWidth(options: { - screenSizeType: ScreenSizeType; - }): { - height: number; - width: number; - } { - let viewPortHeight: number = 0; - let viewPortWidth: number = 0; + const scriptResult: SyntheticMonitorResponse = { + logMessages: [], + scriptError: undefined, + result: undefined, + screenshots: {}, + executionTimeInMS: 0, + browserType: options.browserType, + screenSizeType: options.screenSizeType, + }; - switch (options.screenSizeType) { - case ScreenSizeType.Desktop: - viewPortHeight = 1080; - viewPortWidth = 1920; - break; - case ScreenSizeType.Mobile: - viewPortHeight = 640; - viewPortWidth = 360; - break; - case ScreenSizeType.Tablet: - viewPortHeight = 768; - viewPortWidth = 1024; - break; - default: - viewPortHeight = 1080; - viewPortWidth = 1920; - break; - } + try { + let result: ReturnResult | null = null; - return { height: viewPortHeight, width: viewPortWidth }; - } - - private static async getPageByBrowserType(data: { - browserType: BrowserType; - screenSizeType: ScreenSizeType; - }): Promise<{ + let pageAndBrowser: { page: Page; browser: Browser; - }> { - const viewport: { - height: number; - width: number; - } = SyntheticMonitor.getViewportHeightAndWidth({ - screenSizeType: data.screenSizeType, + } | null = null; + + try { + const startTime: [number, number] = process.hrtime(); + + pageAndBrowser = await SyntheticMonitor.getPageByBrowserType({ + browserType: options.browserType, + screenSizeType: options.screenSizeType, }); - let page: Page | null = null; - let browser: Browser | null = null; - - if (data.browserType === BrowserType.Chromium) { - browser = await chromium.launch(); - page = await browser.newPage(); - } - - if (data.browserType === BrowserType.Firefox) { - browser = await firefox.launch(); - page = await browser.newPage(); - } - - // if (data.browserType === BrowserType.Webkit) { - // browser = await webkit.launch(); - // page = await browser.newPage(); - // } - - await page?.setViewportSize({ - width: viewport.width, - height: viewport.height, + result = await VMRunner.runCodeInSandbox({ + code: options.script, + options: { + timeout: PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS, + args: {}, + context: { + page: pageAndBrowser.page, + screenSizeType: options.screenSizeType, + browserType: options.browserType, + }, + }, }); - if (!browser) { - throw new BadDataException('Invalid Browser Type.'); + const endTime: [number, number] = process.hrtime(startTime); + + const executionTimeInMS: number = + (endTime[0] * 1000000000 + endTime[1]) / 1000000; + + scriptResult.executionTimeInMS = executionTimeInMS; + + scriptResult.logMessages = result.logMessages; + + if (result.returnValue?.screenshots) { + if (!scriptResult.screenshots) { + scriptResult.screenshots = {}; + } + + for (const screenshotName in result.returnValue.screenshots) { + if (!result.returnValue.screenshots[screenshotName]) { + continue; + } + + // check if this is of type Buffer. If it is not, continue. + + if ( + !( + result.returnValue.screenshots[screenshotName] instanceof Buffer + ) + ) { + continue; + } + + scriptResult.screenshots[screenshotName] = ( + result.returnValue.screenshots[screenshotName] as any + ).toString("base64"); // convert screennshots to base 64 + } } - if (!page) { - // close the browser if page is not created - await browser.close(); - throw new BadDataException('Invalid Browser Type.'); - } + scriptResult.result = result?.returnValue?.data; + } catch (err) { + logger.error(err); + scriptResult.scriptError = + (err as Error)?.message || (err as Error).toString(); + } - return { - page: page, - browser: browser, - }; + if (pageAndBrowser?.browser) { + await pageAndBrowser.browser.close(); + } + + return scriptResult; + } catch (err: unknown) { + logger.error(err); + scriptResult.scriptError = + (err as Error)?.message || (err as Error).toString(); } + + return scriptResult; + } + + private static getViewportHeightAndWidth(options: { + screenSizeType: ScreenSizeType; + }): { + height: number; + width: number; + } { + let viewPortHeight: number = 0; + let viewPortWidth: number = 0; + + switch (options.screenSizeType) { + case ScreenSizeType.Desktop: + viewPortHeight = 1080; + viewPortWidth = 1920; + break; + case ScreenSizeType.Mobile: + viewPortHeight = 640; + viewPortWidth = 360; + break; + case ScreenSizeType.Tablet: + viewPortHeight = 768; + viewPortWidth = 1024; + break; + default: + viewPortHeight = 1080; + viewPortWidth = 1920; + break; + } + + return { height: viewPortHeight, width: viewPortWidth }; + } + + private static async getPageByBrowserType(data: { + browserType: BrowserType; + screenSizeType: ScreenSizeType; + }): Promise<{ + page: Page; + browser: Browser; + }> { + const viewport: { + height: number; + width: number; + } = SyntheticMonitor.getViewportHeightAndWidth({ + screenSizeType: data.screenSizeType, + }); + + let page: Page | null = null; + let browser: Browser | null = null; + + if (data.browserType === BrowserType.Chromium) { + browser = await chromium.launch(); + page = await browser.newPage(); + } + + if (data.browserType === BrowserType.Firefox) { + browser = await firefox.launch(); + page = await browser.newPage(); + } + + // if (data.browserType === BrowserType.Webkit) { + // browser = await webkit.launch(); + // page = await browser.newPage(); + // } + + await page?.setViewportSize({ + width: viewport.width, + height: viewport.height, + }); + + if (!browser) { + throw new BadDataException("Invalid Browser Type."); + } + + if (!page) { + // close the browser if page is not created + await browser.close(); + throw new BadDataException("Invalid Browser Type."); + } + + return { + page: page, + browser: browser, + }; + } } diff --git a/Probe/Utils/Monitors/MonitorTypes/WebsiteMonitor.ts b/Probe/Utils/Monitors/MonitorTypes/WebsiteMonitor.ts index fe8b0e427d..a24dc98788 100644 --- a/Probe/Utils/Monitors/MonitorTypes/WebsiteMonitor.ts +++ b/Probe/Utils/Monitors/MonitorTypes/WebsiteMonitor.ts @@ -1,188 +1,187 @@ -import OnlineCheck from '../../OnlineCheck'; -import HTTPMethod from 'Common/Types/API/HTTPMethod'; -import Headers from 'Common/Types/API/Headers'; -import Protocol from 'Common/Types/API/Protocol'; -import URL from 'Common/Types/API/URL'; -import HTML from 'Common/Types/Html'; -import ObjectID from 'Common/Types/ObjectID'; -import PositiveNumber from 'Common/Types/PositiveNumber'; -import Sleep from 'Common/Types/Sleep'; -import WebsiteRequest, { WebsiteResponse } from 'Common/Types/WebsiteRequest'; -import API from 'Common/Utils/API'; -import logger from 'CommonServer/Utils/Logger'; -import { AxiosError } from 'axios'; +import OnlineCheck from "../../OnlineCheck"; +import HTTPMethod from "Common/Types/API/HTTPMethod"; +import Headers from "Common/Types/API/Headers"; +import Protocol from "Common/Types/API/Protocol"; +import URL from "Common/Types/API/URL"; +import HTML from "Common/Types/Html"; +import ObjectID from "Common/Types/ObjectID"; +import PositiveNumber from "Common/Types/PositiveNumber"; +import Sleep from "Common/Types/Sleep"; +import WebsiteRequest, { WebsiteResponse } from "Common/Types/WebsiteRequest"; +import API from "Common/Utils/API"; +import logger from "CommonServer/Utils/Logger"; +import { AxiosError } from "axios"; export interface ProbeWebsiteResponse { - url: URL; - requestHeaders: Headers; - isSecure: boolean; - responseTimeInMS: PositiveNumber; - statusCode: number | undefined; - responseBody: HTML | undefined; - responseHeaders: Headers | undefined; - isOnline: boolean; - failureCause: string; + url: URL; + requestHeaders: Headers; + isSecure: boolean; + responseTimeInMS: PositiveNumber; + statusCode: number | undefined; + responseBody: HTML | undefined; + responseHeaders: Headers | undefined; + isOnline: boolean; + failureCause: string; } export default class WebsiteMonitor { - public static async ping( - url: URL, - options: { - retry?: number | undefined; - isHeadRequest?: boolean | undefined; - currentRetryCount?: number | undefined; - monitorId?: ObjectID | undefined; - isOnlineCheckRequest?: boolean | undefined; - } - ): Promise<ProbeWebsiteResponse | null> { - if (!options) { - options = {}; - } - - if (options?.currentRetryCount === undefined) { - options.currentRetryCount = 1; - } - - let requestType: HTTPMethod = HTTPMethod.GET; - - if (options.isHeadRequest) { - requestType = HTTPMethod.HEAD; - } - - try { - logger.debug( - `Website Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} - Retry: ${ - options.currentRetryCount - }` - ); - - let startTime: [number, number] = process.hrtime(); - let result: WebsiteResponse = await WebsiteRequest.fetch(url, { - isHeadRequest: options.isHeadRequest, - timeout: 30000, - }); - - if ( - result.responseStatusCode >= 400 && - result.responseStatusCode < 600 && - requestType === HTTPMethod.HEAD - ) { - startTime = process.hrtime(); - result = await WebsiteRequest.fetch(url, { - isHeadRequest: false, - timeout: 30000, - }); - } - - const endTime: [number, number] = process.hrtime(startTime); - const responseTimeInMS: PositiveNumber = new PositiveNumber( - (endTime[0] * 1000000000 + endTime[1]) / 1000000 - ); - - // if response time is greater than 10 seconds then give it one more try - - if ( - responseTimeInMS.toNumber() > 10000 && - options.currentRetryCount < (options.retry || 5) - ) { - options.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(url, options); - } - - const probeWebsiteResponse: ProbeWebsiteResponse = { - url: url, - requestHeaders: {}, - isOnline: true, - isSecure: url.protocol === Protocol.HTTPS, - responseTimeInMS: responseTimeInMS, - statusCode: result.responseStatusCode, - responseBody: result.responseBody, - responseHeaders: result.responseHeaders, - failureCause: '', - }; - - logger.debug( - `Website Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} Success - Response: ${JSON.stringify( - probeWebsiteResponse - )}` - ); - - return probeWebsiteResponse; - } catch (err: unknown) { - if (!options) { - options = {}; - } - - if (!options.currentRetryCount) { - options.currentRetryCount = 0; // default value - } - - if (options.currentRetryCount < (options.retry || 5)) { - options.currentRetryCount++; - await Sleep.sleep(1000); - return await this.ping(url, options); - } - - let probeWebsiteResponse: ProbeWebsiteResponse | undefined = - undefined; - - if (err instanceof AxiosError) { - probeWebsiteResponse = { - url: url, - isOnline: Boolean(err.response), - requestHeaders: {}, - isSecure: url.protocol === Protocol.HTTPS, - responseTimeInMS: new PositiveNumber(0), - statusCode: err.response?.status, - responseBody: err.response?.data, - responseHeaders: (err.response?.headers as Headers) || {}, - failureCause: API.getFriendlyErrorMessage(err), - }; - } else { - probeWebsiteResponse = { - url: url, - isOnline: false, - requestHeaders: {}, - isSecure: url.protocol === Protocol.HTTPS, - responseTimeInMS: new PositiveNumber(0), - statusCode: undefined, - responseBody: undefined, - responseHeaders: undefined, - failureCause: API.getFriendlyErrorMessage(err as Error), - }; - } - - // check if timeout exceeded and if yes, return null - if ( - (err as any).toString().includes('timeout') && - (err as any).toString().includes('exceeded') - ) { - logger.debug( - `Website Monitor - Timeout exceeded ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err}` - ); - probeWebsiteResponse.failureCause = 'Timeout exceeded'; - probeWebsiteResponse.isOnline = false; - - return probeWebsiteResponse; - } - - if (!options.isOnlineCheckRequest) { - if (!(await OnlineCheck.canProbeMonitorWebsiteMonitors())) { - logger.error( - `Website Monitor - Probe is not online. Cannot ping ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err}` - ); - return null; - } - } - - logger.error( - `Website Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err} Response: ${JSON.stringify( - probeWebsiteResponse - )}` - ); - - return probeWebsiteResponse; - } + public static async ping( + url: URL, + options: { + retry?: number | undefined; + isHeadRequest?: boolean | undefined; + currentRetryCount?: number | undefined; + monitorId?: ObjectID | undefined; + isOnlineCheckRequest?: boolean | undefined; + }, + ): Promise<ProbeWebsiteResponse | null> { + if (!options) { + options = {}; } + + if (options?.currentRetryCount === undefined) { + options.currentRetryCount = 1; + } + + let requestType: HTTPMethod = HTTPMethod.GET; + + if (options.isHeadRequest) { + requestType = HTTPMethod.HEAD; + } + + try { + logger.debug( + `Website Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} - Retry: ${ + options.currentRetryCount + }`, + ); + + let startTime: [number, number] = process.hrtime(); + let result: WebsiteResponse = await WebsiteRequest.fetch(url, { + isHeadRequest: options.isHeadRequest, + timeout: 30000, + }); + + if ( + result.responseStatusCode >= 400 && + result.responseStatusCode < 600 && + requestType === HTTPMethod.HEAD + ) { + startTime = process.hrtime(); + result = await WebsiteRequest.fetch(url, { + isHeadRequest: false, + timeout: 30000, + }); + } + + const endTime: [number, number] = process.hrtime(startTime); + const responseTimeInMS: PositiveNumber = new PositiveNumber( + (endTime[0] * 1000000000 + endTime[1]) / 1000000, + ); + + // if response time is greater than 10 seconds then give it one more try + + if ( + responseTimeInMS.toNumber() > 10000 && + options.currentRetryCount < (options.retry || 5) + ) { + options.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(url, options); + } + + const probeWebsiteResponse: ProbeWebsiteResponse = { + url: url, + requestHeaders: {}, + isOnline: true, + isSecure: url.protocol === Protocol.HTTPS, + responseTimeInMS: responseTimeInMS, + statusCode: result.responseStatusCode, + responseBody: result.responseBody, + responseHeaders: result.responseHeaders, + failureCause: "", + }; + + logger.debug( + `Website Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} Success - Response: ${JSON.stringify( + probeWebsiteResponse, + )}`, + ); + + return probeWebsiteResponse; + } catch (err: unknown) { + if (!options) { + options = {}; + } + + if (!options.currentRetryCount) { + options.currentRetryCount = 0; // default value + } + + if (options.currentRetryCount < (options.retry || 5)) { + options.currentRetryCount++; + await Sleep.sleep(1000); + return await this.ping(url, options); + } + + let probeWebsiteResponse: ProbeWebsiteResponse | undefined = undefined; + + if (err instanceof AxiosError) { + probeWebsiteResponse = { + url: url, + isOnline: Boolean(err.response), + requestHeaders: {}, + isSecure: url.protocol === Protocol.HTTPS, + responseTimeInMS: new PositiveNumber(0), + statusCode: err.response?.status, + responseBody: err.response?.data, + responseHeaders: (err.response?.headers as Headers) || {}, + failureCause: API.getFriendlyErrorMessage(err), + }; + } else { + probeWebsiteResponse = { + url: url, + isOnline: false, + requestHeaders: {}, + isSecure: url.protocol === Protocol.HTTPS, + responseTimeInMS: new PositiveNumber(0), + statusCode: undefined, + responseBody: undefined, + responseHeaders: undefined, + failureCause: API.getFriendlyErrorMessage(err as Error), + }; + } + + // check if timeout exceeded and if yes, return null + if ( + (err as any).toString().includes("timeout") && + (err as any).toString().includes("exceeded") + ) { + logger.debug( + `Website Monitor - Timeout exceeded ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err}`, + ); + probeWebsiteResponse.failureCause = "Timeout exceeded"; + probeWebsiteResponse.isOnline = false; + + return probeWebsiteResponse; + } + + if (!options.isOnlineCheckRequest) { + if (!(await OnlineCheck.canProbeMonitorWebsiteMonitors())) { + logger.error( + `Website Monitor - Probe is not online. Cannot ping ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err}`, + ); + return null; + } + } + + logger.error( + `Website Monitor - Pinging ${options.monitorId?.toString()} ${requestType} ${url.toString()} - ERROR: ${err} Response: ${JSON.stringify( + probeWebsiteResponse, + )}`, + ); + + return probeWebsiteResponse; + } + } } diff --git a/Probe/Utils/OnlineCheck.ts b/Probe/Utils/OnlineCheck.ts index 582ba77ae1..9936e4adc6 100644 --- a/Probe/Utils/OnlineCheck.ts +++ b/Probe/Utils/OnlineCheck.ts @@ -1,81 +1,81 @@ -import PingMonitor from './Monitors/MonitorTypes/PingMonitor'; -import PortMonitor from './Monitors/MonitorTypes/PortMonitor'; -import WebsiteMonitor from './Monitors/MonitorTypes/WebsiteMonitor'; -import Hostname from 'Common/Types/API/Hostname'; -import URL from 'Common/Types/API/URL'; -import Port from 'Common/Types/Port'; +import PingMonitor from "./Monitors/MonitorTypes/PingMonitor"; +import PortMonitor from "./Monitors/MonitorTypes/PortMonitor"; +import WebsiteMonitor from "./Monitors/MonitorTypes/WebsiteMonitor"; +import Hostname from "Common/Types/API/Hostname"; +import URL from "Common/Types/API/URL"; +import Port from "Common/Types/Port"; export default class OnlineCheck { - // burn domain names into the code to see if this probe is online. - public static async canProbeMonitorWebsiteMonitors(): Promise<boolean> { - const websiteNames: Array<string> = [ - 'https://google.com', - 'https://facebook.com', - 'https://microsoft.com', - 'https://youtube.com', - 'https://apple.com', - ]; + // burn domain names into the code to see if this probe is online. + public static async canProbeMonitorWebsiteMonitors(): Promise<boolean> { + const websiteNames: Array<string> = [ + "https://google.com", + "https://facebook.com", + "https://microsoft.com", + "https://youtube.com", + "https://apple.com", + ]; - for (const websiteName of websiteNames) { - if ( - ( - await WebsiteMonitor.ping(URL.fromString(websiteName), { - isOnlineCheckRequest: true, - }) - )?.isOnline - ) { - return true; - } - } - - return false; + for (const websiteName of websiteNames) { + if ( + ( + await WebsiteMonitor.ping(URL.fromString(websiteName), { + isOnlineCheckRequest: true, + }) + )?.isOnline + ) { + return true; + } } - public static async canProbeMonitorPingMonitors(): Promise<boolean> { - const domains: Array<string> = [ - 'google.com', - 'facebook.com', - 'microsoft.com', - 'youtube.com', - 'apple.com', - ]; + return false; + } - for (const domain of domains) { - if ( - ( - await PingMonitor.ping(new Hostname(domain), { - isOnlineCheckRequest: true, - }) - )?.isOnline - ) { - return true; - } - } + public static async canProbeMonitorPingMonitors(): Promise<boolean> { + const domains: Array<string> = [ + "google.com", + "facebook.com", + "microsoft.com", + "youtube.com", + "apple.com", + ]; - return false; + for (const domain of domains) { + if ( + ( + await PingMonitor.ping(new Hostname(domain), { + isOnlineCheckRequest: true, + }) + )?.isOnline + ) { + return true; + } } - public static async canProbeMonitorPortMonitors(): Promise<boolean> { - const domains: Array<string> = [ - 'google.com', - 'facebook.com', - 'microsoft.com', - 'youtube.com', - 'apple.com', - ]; + return false; + } - for (const domain of domains) { - if ( - ( - await PortMonitor.ping(new Hostname(domain), new Port(80), { - isOnlineCheckRequest: true, - }) - )?.isOnline - ) { - return true; - } - } + public static async canProbeMonitorPortMonitors(): Promise<boolean> { + const domains: Array<string> = [ + "google.com", + "facebook.com", + "microsoft.com", + "youtube.com", + "apple.com", + ]; - return false; + for (const domain of domains) { + if ( + ( + await PortMonitor.ping(new Hostname(domain), new Port(80), { + isOnlineCheckRequest: true, + }) + )?.isOnline + ) { + return true; + } } + + return false; + } } diff --git a/Probe/Utils/Probe.ts b/Probe/Utils/Probe.ts index 1319cbbbd1..34d7644dbc 100644 --- a/Probe/Utils/Probe.ts +++ b/Probe/Utils/Probe.ts @@ -1,17 +1,16 @@ -import BadDataException from 'Common/Types/Exception/BadDataException'; -import ObjectID from 'Common/Types/ObjectID'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; +import BadDataException from "Common/Types/Exception/BadDataException"; +import ObjectID from "Common/Types/ObjectID"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; export default class ProbeUtil { - public static getProbeId(): ObjectID { - const id: string | undefined = - LocalCache.getString('PROBE', 'PROBE_ID') || - process.env['PROBE_ID']; + public static getProbeId(): ObjectID { + const id: string | undefined = + LocalCache.getString("PROBE", "PROBE_ID") || process.env["PROBE_ID"]; - if (!id) { - throw new BadDataException('Probe ID not found'); - } - - return new ObjectID(id); + if (!id) { + throw new BadDataException("Probe ID not found"); } + + return new ObjectID(id); + } } diff --git a/Probe/Utils/ProbeAPIRequest.ts b/Probe/Utils/ProbeAPIRequest.ts index 617046269d..e62eb3404d 100644 --- a/Probe/Utils/ProbeAPIRequest.ts +++ b/Probe/Utils/ProbeAPIRequest.ts @@ -1,12 +1,12 @@ -import { PROBE_KEY } from '../Config'; -import ProbeUtil from './Probe'; -import { JSONObject } from 'Common/Types/JSON'; +import { PROBE_KEY } from "../Config"; +import ProbeUtil from "./Probe"; +import { JSONObject } from "Common/Types/JSON"; export default class ProbeAPIRequest { - public static getDefaultRequestBody(): JSONObject { - return { - probeKey: PROBE_KEY, - probeId: ProbeUtil.getProbeId().toString(), - }; - } + public static getDefaultRequestBody(): JSONObject { + return { + probeKey: PROBE_KEY, + probeId: ProbeUtil.getProbeId().toString(), + }; + } } diff --git a/Scripts/Install/MergeEnvTemplate.js b/Scripts/Install/MergeEnvTemplate.js index 887400e98f..d16d21d015 100644 --- a/Scripts/Install/MergeEnvTemplate.js +++ b/Scripts/Install/MergeEnvTemplate.js @@ -1,42 +1,42 @@ // This script merges config.env.tpl to config.env -const fs = require('fs'); +const fs = require("fs"); const init = () => { - const tempate = fs.readFileSync('./config.example.env', 'utf8'); - const env = fs.readFileSync('./config.env', 'utf8'); + const tempate = fs.readFileSync("./config.example.env", "utf8"); + const env = fs.readFileSync("./config.env", "utf8"); - const linesInTemplate = tempate.split('\n'); - const linesInEnv= env.split('\n'); + const linesInTemplate = tempate.split("\n"); + const linesInEnv = env.split("\n"); - for (const line of linesInTemplate) { - // this is a comment, ignore. - if (line.startsWith('//')) { - continue; - } - - // comment. Ignore. - if (line.startsWith('#')) { - continue; - } - - // if the line is present in template but is not present in env file then add it to the env file. We assume, values in template file are default values. - if (line.split('=').length > 0) { - if ( - linesInEnv.filter((envLine) => { - return ( - envLine.split('=').length > 0 && - envLine.split('=')[0] === line.split('=')[0] - ); - }).length === 0 - ) { - linesInEnv.push(line); - } - } + for (const line of linesInTemplate) { + // this is a comment, ignore. + if (line.startsWith("//")) { + continue; } - // write the file back to disk and exit. - fs.writeFileSync('./config.env', linesInEnv.join('\n')); + // comment. Ignore. + if (line.startsWith("#")) { + continue; + } + + // if the line is present in template but is not present in env file then add it to the env file. We assume, values in template file are default values. + if (line.split("=").length > 0) { + if ( + linesInEnv.filter((envLine) => { + return ( + envLine.split("=").length > 0 && + envLine.split("=")[0] === line.split("=")[0] + ); + }).length === 0 + ) { + linesInEnv.push(line); + } + } + } + + // write the file back to disk and exit. + fs.writeFileSync("./config.env", linesInEnv.join("\n")); }; init(); diff --git a/Scripts/Install/ReplaceValueInConfig.js b/Scripts/Install/ReplaceValueInConfig.js index c3aef517e1..fd54be5834 100644 --- a/Scripts/Install/ReplaceValueInConfig.js +++ b/Scripts/Install/ReplaceValueInConfig.js @@ -1,53 +1,53 @@ // This script merges config.env.tpl to config.env -const fs = require('fs'); +const fs = require("fs"); const init = () => { - let env = ''; - try { - env = fs.readFileSync('./config.env', 'utf8'); - } catch (err) { - // do nothing. - } - const envValToReplace = process.argv[2]; + let env = ""; + try { + env = fs.readFileSync("./config.env", "utf8"); + } catch (err) { + // do nothing. + } + const envValToReplace = process.argv[2]; - if (!envValToReplace) { - // eslint-disable-next-line + if (!envValToReplace) { + // eslint-disable-next-line console.log('Please have env var to replace'); - return; - } + return; + } - const envValToReplaceWith= process.argv[3]; + const envValToReplaceWith = process.argv[3]; - if (!envValToReplaceWith) { - // eslint-disable-next-line + if (!envValToReplaceWith) { + // eslint-disable-next-line console.log('Please have env var to replace with'); - return; + return; + } + + const linesInEnv = env.split("\n"); + const linesToRender = []; + let found = false; + + for (let line of linesInEnv) { + // this is a comment, ignore. + if (!line.startsWith(envValToReplace)) { + linesToRender.push(line); + } else { + found = true; + const items = line.split("="); + items[1] = envValToReplaceWith; + line = items.join("="); + linesToRender.push(line); } + } - const linesInEnv = env.split('\n'); - const linesToRender = []; - let found = false; + if (!found) { + linesToRender.push(envValToReplace + "=" + envValToReplaceWith); + } - for (let line of linesInEnv) { - // this is a comment, ignore. - if (!line.startsWith(envValToReplace)) { - linesToRender.push(line); - } else { - found = true; - const items = line.split('='); - items[1] = envValToReplaceWith; - line = items.join('='); - linesToRender.push(line); - } - } - - if (!found) { - linesToRender.push(envValToReplace + '=' + envValToReplaceWith); - } - - // write the file back to disk and exit. - fs.writeFileSync('./config.env', linesToRender.join('\n')); + // write the file back to disk and exit. + fs.writeFileSync("./config.env", linesToRender.join("\n")); }; init(); diff --git a/Scripts/PaymentProvider/CouponCodeGenerator.ts b/Scripts/PaymentProvider/CouponCodeGenerator.ts index dfdd215ebc..67cfa04a2f 100644 --- a/Scripts/PaymentProvider/CouponCodeGenerator.ts +++ b/Scripts/PaymentProvider/CouponCodeGenerator.ts @@ -1,24 +1,24 @@ // To run this script: // export $(grep -v '^#' config.env | xargs) && ts-node ./Scripts/PaymentProvider/CouponCodeGenerator.ts > coupons.csv -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Sleep from 'Common/Types/Sleep'; -import BillingService from 'CommonServer/Services/BillingService'; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Sleep from "Common/Types/Sleep"; +import BillingService from "CommonServer/Services/BillingService"; const main: PromiseVoidFunction = async (): Promise<void> => { - for (let i: number = 0; i < 2000; i++) { - const code: string = await BillingService.generateCouponCode({ - name: 'Name', - percentOff: 100, - durationInMonths: 12, - maxRedemptions: 1, - }); - //eslint-disable-next-line no-console - console.log(code); - await Sleep.sleep(50); - } + for (let i: number = 0; i < 2000; i++) { + const code: string = await BillingService.generateCouponCode({ + name: "Name", + percentOff: 100, + durationInMonths: 12, + maxRedemptions: 1, + }); + //eslint-disable-next-line no-console + console.log(code); + await Sleep.sleep(50); + } }; main().catch((err: Error) => { - //eslint-disable-next-line no-console - console.error(err); + //eslint-disable-next-line no-console + console.error(err); }); diff --git a/StatusPage/Serve.ts b/StatusPage/Serve.ts index 7f434119c8..cbfda06531 100644 --- a/StatusPage/Serve.ts +++ b/StatusPage/Serve.ts @@ -1,38 +1,38 @@ -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import App from 'CommonServer/Utils/StartServer'; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import App from "CommonServer/Utils/StartServer"; -export const APP_NAME: string = 'status-page'; +export const APP_NAME: string = "status-page"; const app: ExpressApplication = Express.getExpressApp(); const init: PromiseVoidFunction = async (): Promise<void> => { - try { - // init the app - await App.init({ - appName: APP_NAME, - port: undefined, - isFrontendApp: true, - statusOptions: { - liveCheck: async () => {}, - readyCheck: async () => {}, - }, - }); + try { + // init the app + await App.init({ + appName: APP_NAME, + port: undefined, + isFrontendApp: true, + statusOptions: { + liveCheck: async () => {}, + readyCheck: async () => {}, + }, + }); - // add default routes - await App.addDefaultRoutes(); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); export default app; diff --git a/StatusPage/index.d.ts b/StatusPage/index.d.ts index cf14fde98c..a0ad0eb5c4 100644 --- a/StatusPage/index.d.ts +++ b/StatusPage/index.d.ts @@ -1,4 +1,4 @@ -declare module '*.png'; -declare module '*.svg'; -declare module '*.jpg'; -declare module '*.gif'; +declare module "*.png"; +declare module "*.svg"; +declare module "*.jpg"; +declare module "*.gif"; diff --git a/StatusPage/src/App.tsx b/StatusPage/src/App.tsx index c4252f457c..9b84e42c54 100644 --- a/StatusPage/src/App.tsx +++ b/StatusPage/src/App.tsx @@ -1,670 +1,573 @@ -import MasterPage from './Components/MasterPage/MasterPage'; -import ForgotPassword from './Pages/Accounts/ForgotPassword'; +import MasterPage from "./Components/MasterPage/MasterPage"; +import ForgotPassword from "./Pages/Accounts/ForgotPassword"; // Accounts. -import Login from './Pages/Accounts/Login'; +import Login from "./Pages/Accounts/Login"; // Logout. -import Logout from './Pages/Accounts/Logout'; -import ResetPassword from './Pages/Accounts/ResetPassword'; -import Sso from './Pages/Accounts/SSO'; -import AnnouncementDetail from './Pages/Announcement/Detail'; -import AnnouncementList from './Pages/Announcement/List'; -import IncidentDetail from './Pages/Incidents/Detail'; -import IncidentList from './Pages/Incidents/List'; -import PageNotFound from './Pages/NotFound/PageNotFound'; -import Overview from './Pages/Overview/Overview'; -import ScheduledEventDetail from './Pages/ScheduledEvent/Detail'; -import ScheduledEventList from './Pages/ScheduledEvent/List'; -import EmailSubscribe from './Pages/Subscribe/EmailSubscribe'; -import SMSSubscribe from './Pages/Subscribe/SmsSubscribe'; -import UpdateSubscription from './Pages/Subscribe/UpdateSubscription'; -import PageMap from './Utils/PageMap'; -import RouteMap from './Utils/RouteMap'; -import StatusPageUtil from './Utils/StatusPage'; -import Route from 'Common/Types/API/Route'; -import { VoidFunction } from 'Common/Types/FunctionTypes'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { useEffect, useState } from 'react'; +import Logout from "./Pages/Accounts/Logout"; +import ResetPassword from "./Pages/Accounts/ResetPassword"; +import Sso from "./Pages/Accounts/SSO"; +import AnnouncementDetail from "./Pages/Announcement/Detail"; +import AnnouncementList from "./Pages/Announcement/List"; +import IncidentDetail from "./Pages/Incidents/Detail"; +import IncidentList from "./Pages/Incidents/List"; +import PageNotFound from "./Pages/NotFound/PageNotFound"; +import Overview from "./Pages/Overview/Overview"; +import ScheduledEventDetail from "./Pages/ScheduledEvent/Detail"; +import ScheduledEventList from "./Pages/ScheduledEvent/List"; +import EmailSubscribe from "./Pages/Subscribe/EmailSubscribe"; +import SMSSubscribe from "./Pages/Subscribe/SmsSubscribe"; +import UpdateSubscription from "./Pages/Subscribe/UpdateSubscription"; +import PageMap from "./Utils/PageMap"; +import RouteMap from "./Utils/RouteMap"; +import StatusPageUtil from "./Utils/StatusPage"; +import Route from "Common/Types/API/Route"; +import { VoidFunction } from "Common/Types/FunctionTypes"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { useEffect, useState } from "react"; import { - Route as PageRoute, - Routes, - useLocation, - useNavigate, - useParams, -} from 'react-router-dom'; + Route as PageRoute, + Routes, + useLocation, + useNavigate, + useParams, +} from "react-router-dom"; const App: () => JSX.Element = () => { - Navigation.setNavigateHook(useNavigate()); - Navigation.setLocation(useLocation()); - Navigation.setParams(useParams()); + Navigation.setNavigateHook(useNavigate()); + Navigation.setLocation(useLocation()); + Navigation.setParams(useParams()); - const [isPreview, setIsPreview] = useState<boolean>(false); - const [enableEmailSubscribers, setenableEmailSubscribers] = - useState<boolean>(true); - const [ - allowSubscribersToChooseResources, - setAllowSubscribersToChooseResources, - ] = useState<boolean>(false); - const [enableSMSSubscribers, setenableSMSSubscribers] = - useState<boolean>(false); - const [statusPageName, setStatusPageName] = useState<string>(''); - const [statusPageLogoFileId, setStatusPageLogoFileId] = - useState<string>(''); - const [isPrivateStatusPage, setIsPrivateStatusPage] = - useState<boolean>(false); + const [isPreview, setIsPreview] = useState<boolean>(false); + const [enableEmailSubscribers, setenableEmailSubscribers] = + useState<boolean>(true); + const [ + allowSubscribersToChooseResources, + setAllowSubscribersToChooseResources, + ] = useState<boolean>(false); + const [enableSMSSubscribers, setenableSMSSubscribers] = + useState<boolean>(false); + const [statusPageName, setStatusPageName] = useState<string>(""); + const [statusPageLogoFileId, setStatusPageLogoFileId] = useState<string>(""); + const [isPrivateStatusPage, setIsPrivateStatusPage] = + useState<boolean>(false); - const [hasEnabledSSO, setHasEnabledSSO] = useState<boolean>(false); - const [forceSSO, setForceSSO] = useState<boolean>(false); + const [hasEnabledSSO, setHasEnabledSSO] = useState<boolean>(false); + const [forceSSO, setForceSSO] = useState<boolean>(false); - useEffect(() => { - const preview: boolean = StatusPageUtil.isPreviewPage(); - setIsPreview(preview); - }, []); + useEffect(() => { + const preview: boolean = StatusPageUtil.isPreviewPage(); + setIsPreview(preview); + }, []); - // js. - const [javascript, setJavaScript] = useState<string | null>(null); + // js. + const [javascript, setJavaScript] = useState<string | null>(null); - const onPageLoadComplete: VoidFunction = (): void => { + const onPageLoadComplete: VoidFunction = (): void => { + if (javascript) { + // run custom javascipt. + new Function(javascript)(); + } + }; + + return ( + <MasterPage + isPreview={isPreview} + enableSMSSubscribers={enableSMSSubscribers} + enableEmailSubscribers={enableEmailSubscribers} + isPrivateStatusPage={isPrivateStatusPage} + onLoadComplete={(masterpage: JSONObject) => { + document.title = + (JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.pageTitle", + ) as string | null) || "Status Page"; + + document + .querySelector('meta[name="description"]') + ?.setAttribute( + "content", + (JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.pageDescription", + ) as string | null) || "", + ); + + const javascript: string | null = JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.customJavaScript", + ) as string | null; if (javascript) { - // run custom javascipt. - new Function(javascript)(); + setJavaScript(javascript); } - }; - return ( - <MasterPage - isPreview={isPreview} - enableSMSSubscribers={enableSMSSubscribers} - enableEmailSubscribers={enableEmailSubscribers} - isPrivateStatusPage={isPrivateStatusPage} - onLoadComplete={(masterpage: JSONObject) => { - document.title = - (JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.pageTitle' - ) as string | null) || 'Status Page'; + const statusPageName: string | null = JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.pageTitle", + ) as string | null; - document - .querySelector('meta[name="description"]') - ?.setAttribute( - 'content', - (JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.pageDescription' - ) as string | null) || '' - ); + const isPrivateStatusPage: boolean = !JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.isPublicStatusPage", + ) as boolean; - const javascript: string | null = - JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.customJavaScript' - ) as string | null; - if (javascript) { - setJavaScript(javascript); - } + const enableEmailSubscribers: boolean = + JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.enableEmailSubscribers", + ) as boolean; - const statusPageName: string | null = - JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.pageTitle' - ) as string | null; + const enableSMSSubscribers: boolean = JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.enableSmsSubscribers", + ) as boolean; - const isPrivateStatusPage: boolean = - !JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.isPublicStatusPage' - ) as boolean; + const allowSubscribersToChooseResources: boolean = + JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.allowSubscribersToChooseResources", + ) as boolean; - const enableEmailSubscribers: boolean = - JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.enableEmailSubscribers' - ) as boolean; + setAllowSubscribersToChooseResources(allowSubscribersToChooseResources); - const enableSMSSubscribers: boolean = - JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.enableSmsSubscribers' - ) as boolean; + setenableSMSSubscribers(enableSMSSubscribers); + setenableEmailSubscribers(enableEmailSubscribers); - const allowSubscribersToChooseResources: boolean = - JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.allowSubscribersToChooseResources' - ) as boolean; + StatusPageUtil.setIsPrivateStatusPage(isPrivateStatusPage); + setIsPrivateStatusPage(isPrivateStatusPage); - setAllowSubscribersToChooseResources( - allowSubscribersToChooseResources - ); + const statusPageId: string | null = JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage._id", + ) as string | null; - setenableSMSSubscribers(enableSMSSubscribers); - setenableEmailSubscribers(enableEmailSubscribers); + StatusPageUtil.setStatusPageId( + statusPageId ? new ObjectID(statusPageId.toString()) : null, + ); - StatusPageUtil.setIsPrivateStatusPage(isPrivateStatusPage); - setIsPrivateStatusPage(isPrivateStatusPage); + setStatusPageName(statusPageName || "Status Page"); - const statusPageId: string | null = - JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage._id' - ) as string | null; + const fileId: string | null = JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.logoFileId", + ) as string | null; - StatusPageUtil.setStatusPageId( - statusPageId ? new ObjectID(statusPageId.toString()) : null - ); + setStatusPageLogoFileId(fileId || ""); - setStatusPageName(statusPageName || 'Status Page'); + setHasEnabledSSO( + JSONFunctions.getJSONValueInPath( + masterpage || {}, + "hasEnabledSSO", + ) as boolean, + ); - const fileId: string | null = JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.logoFileId' - ) as string | null; + setForceSSO( + JSONFunctions.getJSONValueInPath( + masterpage || {}, + "statusPage.requireSsoForLogin", + ) as boolean, + ); + }} + > + <Routes> + {/* Live */} - setStatusPageLogoFileId(fileId || ''); + <PageRoute + path={RouteMap[PageMap.OVERVIEW]?.toString() || ""} + element={ + <Overview + pageRoute={RouteMap[PageMap.OVERVIEW] as Route} + onLoadComplete={() => { + onPageLoadComplete(); + }} + /> + } + /> - setHasEnabledSSO( - JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'hasEnabledSSO' - ) as boolean - ); + <PageRoute + path={RouteMap[PageMap.LOGIN]?.toString() || ""} + element={ + <Login + statusPageName={statusPageName} + logoFileId={new ObjectID(statusPageLogoFileId)} + forceSSO={forceSSO} + hasEnabledSSOConfig={hasEnabledSSO} + /> + } + /> - setForceSSO( - JSONFunctions.getJSONValueInPath( - masterpage || {}, - 'statusPage.requireSsoForLogin' - ) as boolean - ); - }} - > - <Routes> - {/* Live */} + <PageRoute + path={RouteMap[PageMap.SSO]?.toString() || ""} + element={ + <Sso + statusPageName={statusPageName} + logoFileId={new ObjectID(statusPageLogoFileId)} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.OVERVIEW]?.toString() || ''} - element={ - <Overview - pageRoute={RouteMap[PageMap.OVERVIEW] as Route} - onLoadComplete={() => { - onPageLoadComplete(); - }} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.RESET_PASSWORD]?.toString() || ""} + element={ + <ResetPassword + statusPageName={statusPageName} + logoFileId={new ObjectID(statusPageLogoFileId)} + forceSSO={forceSSO} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.LOGIN]?.toString() || ''} - element={ - <Login - statusPageName={statusPageName} - logoFileId={new ObjectID(statusPageLogoFileId)} - forceSSO={forceSSO} - hasEnabledSSOConfig={hasEnabledSSO} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.FORGOT_PASSWORD]?.toString() || ""} + element={ + <ForgotPassword + statusPageName={statusPageName} + logoFileId={new ObjectID(statusPageLogoFileId)} + forceSSO={forceSSO} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.SSO]?.toString() || ''} - element={ - <Sso - statusPageName={statusPageName} - logoFileId={new ObjectID(statusPageLogoFileId)} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.SCHEDULED_EVENT_DETAIL]?.toString() || ""} + element={ + <ScheduledEventDetail + pageRoute={RouteMap[PageMap.SCHEDULED_EVENT_DETAIL] as Route} + onLoadComplete={() => { + onPageLoadComplete(); + }} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.RESET_PASSWORD]?.toString() || ''} - element={ - <ResetPassword - statusPageName={statusPageName} - logoFileId={new ObjectID(statusPageLogoFileId)} - forceSSO={forceSSO} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.SCHEDULED_EVENT_LIST]?.toString() || ""} + element={ + <ScheduledEventList + pageRoute={RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route} + onLoadComplete={() => { + onPageLoadComplete(); + }} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.FORGOT_PASSWORD]?.toString() || ''} - element={ - <ForgotPassword - statusPageName={statusPageName} - logoFileId={new ObjectID(statusPageLogoFileId)} - forceSSO={forceSSO} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.INCIDENT_DETAIL]?.toString() || ""} + element={ + <IncidentDetail + pageRoute={RouteMap[PageMap.INCIDENT_DETAIL] as Route} + onLoadComplete={() => { + onPageLoadComplete(); + }} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.SCHEDULED_EVENT_DETAIL]?.toString() || - '' - } - element={ - <ScheduledEventDetail - pageRoute={ - RouteMap[ - PageMap.SCHEDULED_EVENT_DETAIL - ] as Route - } - onLoadComplete={() => { - onPageLoadComplete(); - }} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.INCIDENT_LIST]?.toString() || ""} + element={ + <IncidentList + pageRoute={RouteMap[PageMap.INCIDENT_LIST] as Route} + onLoadComplete={() => { + onPageLoadComplete(); + }} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.SCHEDULED_EVENT_LIST]?.toString() || '' - } - element={ - <ScheduledEventList - pageRoute={ - RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route - } - onLoadComplete={() => { - onPageLoadComplete(); - }} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.ANNOUNCEMENT_DETAIL]?.toString() || ""} + element={ + <AnnouncementDetail + pageRoute={RouteMap[PageMap.ANNOUNCEMENT_DETAIL] as Route} + onLoadComplete={() => { + onPageLoadComplete(); + }} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.INCIDENT_DETAIL]?.toString() || ''} - element={ - <IncidentDetail - pageRoute={ - RouteMap[PageMap.INCIDENT_DETAIL] as Route - } - onLoadComplete={() => { - onPageLoadComplete(); - }} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.ANNOUNCEMENT_LIST]?.toString() || ""} + element={ + <AnnouncementList + pageRoute={RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route} + onLoadComplete={() => { + onPageLoadComplete(); + }} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.INCIDENT_LIST]?.toString() || ''} - element={ - <IncidentList - pageRoute={RouteMap[PageMap.INCIDENT_LIST] as Route} - onLoadComplete={() => { - onPageLoadComplete(); - }} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.SUBSCRIBE_EMAIL]?.toString() || ""} + element={ + <EmailSubscribe + pageRoute={RouteMap[PageMap.SUBSCRIBE_EMAIL] as Route} + allowSubscribersToChooseResources={ + allowSubscribersToChooseResources + } + onLoadComplete={() => { + onPageLoadComplete(); + }} + enableEmailSubscribers={enableEmailSubscribers} + enableSMSSubscribers={enableSMSSubscribers} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.ANNOUNCEMENT_DETAIL]?.toString() || '' - } - element={ - <AnnouncementDetail - pageRoute={ - RouteMap[PageMap.ANNOUNCEMENT_DETAIL] as Route - } - onLoadComplete={() => { - onPageLoadComplete(); - }} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.SUBSCRIBE_SMS]?.toString() || ""} + element={ + <SMSSubscribe + pageRoute={RouteMap[PageMap.SUBSCRIBE_SMS] as Route} + onLoadComplete={() => { + onPageLoadComplete(); + }} + allowSubscribersToChooseResources={ + allowSubscribersToChooseResources + } + enableEmailSubscribers={enableEmailSubscribers} + enableSMSSubscribers={enableSMSSubscribers} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.ANNOUNCEMENT_LIST]?.toString() || ''} - element={ - <AnnouncementList - pageRoute={ - RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route - } - onLoadComplete={() => { - onPageLoadComplete(); - }} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.UPDATE_SUBSCRIPTION]?.toString() || ""} + element={ + <UpdateSubscription + pageRoute={RouteMap[PageMap.UPDATE_SUBSCRIPTION] as Route} + onLoadComplete={() => { + onPageLoadComplete(); + }} + allowSubscribersToChooseResources={ + allowSubscribersToChooseResources + } + enableEmailSubscribers={enableEmailSubscribers} + enableSMSSubscribers={enableSMSSubscribers} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.SUBSCRIBE_EMAIL]?.toString() || ''} - element={ - <EmailSubscribe - pageRoute={ - RouteMap[PageMap.SUBSCRIBE_EMAIL] as Route - } - allowSubscribersToChooseResources={ - allowSubscribersToChooseResources - } - onLoadComplete={() => { - onPageLoadComplete(); - }} - enableEmailSubscribers={enableEmailSubscribers} - enableSMSSubscribers={enableSMSSubscribers} - /> - } - /> + {/* Preview */} - <PageRoute - path={RouteMap[PageMap.SUBSCRIBE_SMS]?.toString() || ''} - element={ - <SMSSubscribe - pageRoute={RouteMap[PageMap.SUBSCRIBE_SMS] as Route} - onLoadComplete={() => { - onPageLoadComplete(); - }} - allowSubscribersToChooseResources={ - allowSubscribersToChooseResources - } - enableEmailSubscribers={enableEmailSubscribers} - enableSMSSubscribers={enableSMSSubscribers} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_OVERVIEW]?.toString() || ""} + element={ + <Overview + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={RouteMap[PageMap.PREVIEW_OVERVIEW] as Route} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.UPDATE_SUBSCRIPTION]?.toString() || '' - } - element={ - <UpdateSubscription - pageRoute={ - RouteMap[PageMap.UPDATE_SUBSCRIPTION] as Route - } - onLoadComplete={() => { - onPageLoadComplete(); - }} - allowSubscribersToChooseResources={ - allowSubscribersToChooseResources - } - enableEmailSubscribers={enableEmailSubscribers} - enableSMSSubscribers={enableSMSSubscribers} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_SUBSCRIBE_EMAIL]?.toString() || ""} + element={ + <EmailSubscribe + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={RouteMap[PageMap.PREVIEW_SUBSCRIBE_EMAIL] as Route} + allowSubscribersToChooseResources={ + allowSubscribersToChooseResources + } + enableEmailSubscribers={enableEmailSubscribers} + enableSMSSubscribers={enableSMSSubscribers} + /> + } + /> - {/* Preview */} + <PageRoute + path={RouteMap[PageMap.PREVIEW_UPDATE_SUBSCRIPTION]?.toString() || ""} + element={ + <UpdateSubscription + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={RouteMap[PageMap.PREVIEW_UPDATE_SUBSCRIPTION] as Route} + allowSubscribersToChooseResources={ + allowSubscribersToChooseResources + } + enableEmailSubscribers={enableEmailSubscribers} + enableSMSSubscribers={enableSMSSubscribers} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.PREVIEW_OVERVIEW]?.toString() || ''} - element={ - <Overview - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[PageMap.PREVIEW_OVERVIEW] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_SUBSCRIBE_SMS]?.toString() || ""} + element={ + <SMSSubscribe + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={RouteMap[PageMap.PREVIEW_SUBSCRIBE_SMS] as Route} + allowSubscribersToChooseResources={ + allowSubscribersToChooseResources + } + enableEmailSubscribers={enableEmailSubscribers} + enableSMSSubscribers={enableSMSSubscribers} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.PREVIEW_SUBSCRIBE_EMAIL]?.toString() || - '' - } - element={ - <EmailSubscribe - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[ - PageMap.PREVIEW_SUBSCRIBE_EMAIL - ] as Route - } - allowSubscribersToChooseResources={ - allowSubscribersToChooseResources - } - enableEmailSubscribers={enableEmailSubscribers} - enableSMSSubscribers={enableSMSSubscribers} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_LOGOUT]?.toString() || ""} + element={<Logout />} + /> - <PageRoute - path={ - RouteMap[ - PageMap.PREVIEW_UPDATE_SUBSCRIPTION - ]?.toString() || '' - } - element={ - <UpdateSubscription - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[ - PageMap.PREVIEW_UPDATE_SUBSCRIPTION - ] as Route - } - allowSubscribersToChooseResources={ - allowSubscribersToChooseResources - } - enableEmailSubscribers={enableEmailSubscribers} - enableSMSSubscribers={enableSMSSubscribers} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.LOGOUT]?.toString() || ""} + element={<Logout />} + /> - <PageRoute - path={ - RouteMap[PageMap.PREVIEW_SUBSCRIBE_SMS]?.toString() || - '' - } - element={ - <SMSSubscribe - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[PageMap.PREVIEW_SUBSCRIBE_SMS] as Route - } - allowSubscribersToChooseResources={ - allowSubscribersToChooseResources - } - enableEmailSubscribers={enableEmailSubscribers} - enableSMSSubscribers={enableSMSSubscribers} - /> - } - /> + <PageRoute + path={ + RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL]?.toString() || "" + } + element={ + <ScheduledEventDetail + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={ + RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL] as Route + } + /> + } + /> - <PageRoute - path={RouteMap[PageMap.PREVIEW_LOGOUT]?.toString() || ''} - element={<Logout />} - /> + <PageRoute + path={ + RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_LIST]?.toString() || "" + } + element={ + <ScheduledEventList + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={ + RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_LIST] as Route + } + /> + } + /> - <PageRoute - path={RouteMap[PageMap.LOGOUT]?.toString() || ''} - element={<Logout />} - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_INCIDENT_DETAIL]?.toString() || ""} + element={ + <IncidentDetail + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={RouteMap[PageMap.PREVIEW_INCIDENT_DETAIL] as Route} + /> + } + /> - <PageRoute - path={ - RouteMap[ - PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL - ]?.toString() || '' - } - element={ - <ScheduledEventDetail - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[ - PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL - ] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_INCIDENT_LIST]?.toString() || ""} + element={ + <IncidentList + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route} + /> + } + /> - <PageRoute - path={ - RouteMap[ - PageMap.PREVIEW_SCHEDULED_EVENT_LIST - ]?.toString() || '' - } - element={ - <ScheduledEventList - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[ - PageMap.PREVIEW_SCHEDULED_EVENT_LIST - ] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_DETAIL]?.toString() || ""} + element={ + <AnnouncementDetail + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_DETAIL] as Route} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.PREVIEW_INCIDENT_DETAIL]?.toString() || - '' - } - element={ - <IncidentDetail - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[ - PageMap.PREVIEW_INCIDENT_DETAIL - ] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_LIST]?.toString() || ""} + element={ + <AnnouncementList + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_LIST] as Route} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.PREVIEW_INCIDENT_LIST]?.toString() || - '' - } - element={ - <IncidentList - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_LOGIN]?.toString() || ""} + element={ + <Login + statusPageName={statusPageName} + logoFileId={new ObjectID(statusPageLogoFileId)} + forceSSO={forceSSO} + hasEnabledSSOConfig={hasEnabledSSO} + /> + } + /> - <PageRoute - path={ - RouteMap[ - PageMap.PREVIEW_ANNOUNCEMENT_DETAIL - ]?.toString() || '' - } - element={ - <AnnouncementDetail - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[ - PageMap.PREVIEW_ANNOUNCEMENT_DETAIL - ] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_RESET_PASSWORD]?.toString() || ""} + element={ + <ResetPassword + statusPageName={statusPageName} + logoFileId={new ObjectID(statusPageLogoFileId)} + forceSSO={forceSSO} + /> + } + /> - <PageRoute - path={ - RouteMap[ - PageMap.PREVIEW_ANNOUNCEMENT_LIST - ]?.toString() || '' - } - element={ - <AnnouncementList - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={ - RouteMap[ - PageMap.PREVIEW_ANNOUNCEMENT_LIST - ] as Route - } - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_FORGOT_PASSWORD]?.toString() || ""} + element={ + <ForgotPassword + statusPageName={statusPageName} + logoFileId={new ObjectID(statusPageLogoFileId)} + forceSSO={forceSSO} + /> + } + /> - <PageRoute - path={RouteMap[PageMap.PREVIEW_LOGIN]?.toString() || ''} - element={ - <Login - statusPageName={statusPageName} - logoFileId={new ObjectID(statusPageLogoFileId)} - forceSSO={forceSSO} - hasEnabledSSOConfig={hasEnabledSSO} - /> - } - /> + <PageRoute + path={RouteMap[PageMap.PREVIEW_SSO]?.toString() || ""} + element={ + <Sso + statusPageName={statusPageName} + logoFileId={new ObjectID(statusPageLogoFileId)} + /> + } + /> - <PageRoute - path={ - RouteMap[PageMap.PREVIEW_RESET_PASSWORD]?.toString() || - '' - } - element={ - <ResetPassword - statusPageName={statusPageName} - logoFileId={new ObjectID(statusPageLogoFileId)} - forceSSO={forceSSO} - /> - } - /> + {/* 👇️ only match this when no other routes match */} - <PageRoute - path={ - RouteMap[PageMap.PREVIEW_FORGOT_PASSWORD]?.toString() || - '' - } - element={ - <ForgotPassword - statusPageName={statusPageName} - logoFileId={new ObjectID(statusPageLogoFileId)} - forceSSO={forceSSO} - /> - } - /> - - <PageRoute - path={RouteMap[PageMap.PREVIEW_SSO]?.toString() || ''} - element={ - <Sso - statusPageName={statusPageName} - logoFileId={new ObjectID(statusPageLogoFileId)} - /> - } - /> - - {/* 👇️ only match this when no other routes match */} - - <PageRoute - path="*" - element={ - <PageNotFound - onLoadComplete={() => { - onPageLoadComplete(); - }} - pageRoute={RouteMap[PageMap.NOT_FOUND] as Route} - /> - } - /> - </Routes> - </MasterPage> - ); + <PageRoute + path="*" + element={ + <PageNotFound + onLoadComplete={() => { + onPageLoadComplete(); + }} + pageRoute={RouteMap[PageMap.NOT_FOUND] as Route} + /> + } + /> + </Routes> + </MasterPage> + ); }; export default App; diff --git a/StatusPage/src/Components/Banner/Banner.tsx b/StatusPage/src/Components/Banner/Banner.tsx index 303432b813..1668fa229a 100644 --- a/StatusPage/src/Components/Banner/Banner.tsx +++ b/StatusPage/src/Components/Banner/Banner.tsx @@ -1,30 +1,30 @@ -import Image from 'CommonUI/src/Components/Image/Image'; -import File from 'Model/Models/File'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Image from "CommonUI/src/Components/Image/Image"; +import File from "Model/Models/File"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onClick?: () => void | undefined; - file?: File | undefined; + onClick?: () => void | undefined; + file?: File | undefined; } const Banner: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.file) { - return <></>; - } + if (!props.file) { + return <></>; + } - return ( - <div> - <Image - onClick={() => { - props.onClick && props.onClick(); - }} - className="rounded-xl w-full mt-5 mb-5" - file={props.file} - /> - </div> - ); + return ( + <div> + <Image + onClick={() => { + props.onClick && props.onClick(); + }} + className="rounded-xl w-full mt-5 mb-5" + file={props.file} + /> + </div> + ); }; export default Banner; diff --git a/StatusPage/src/Components/Footer/Footer.tsx b/StatusPage/src/Components/Footer/Footer.tsx index 84f88b9754..f460ccd2ea 100644 --- a/StatusPage/src/Components/Footer/Footer.tsx +++ b/StatusPage/src/Components/Footer/Footer.tsx @@ -1,35 +1,35 @@ -import URL from 'Common/Types/API/URL'; -import Link from 'Common/Types/Link'; -import Footer from 'CommonUI/src/Components/Footer/Footer'; -import React, { FunctionComponent, ReactElement } from 'react'; +import URL from "Common/Types/API/URL"; +import Link from "Common/Types/Link"; +import Footer from "CommonUI/src/Components/Footer/Footer"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - copyright?: string | undefined; - links: Array<Link>; - className?: string | undefined; - hidePoweredByOneUptimeBranding?: boolean | undefined; + copyright?: string | undefined; + links: Array<Link>; + className?: string | undefined; + hidePoweredByOneUptimeBranding?: boolean | undefined; } const StatusPageFooter: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const links: Array<Link> = [...props.links]; + const links: Array<Link> = [...props.links]; - if (!props.hidePoweredByOneUptimeBranding) { - links.push({ - title: 'Powered by OneUptime', - to: URL.fromString('https://oneuptime.com'), - openInNewTab: true, - }); - } + if (!props.hidePoweredByOneUptimeBranding) { + links.push({ + title: "Powered by OneUptime", + to: URL.fromString("https://oneuptime.com"), + openInNewTab: true, + }); + } - return ( - <Footer - className={props.className} - copyright={props.copyright} - links={links} - /> - ); + return ( + <Footer + className={props.className} + copyright={props.copyright} + links={links} + /> + ); }; export default StatusPageFooter; diff --git a/StatusPage/src/Components/Header/Header.tsx b/StatusPage/src/Components/Header/Header.tsx index 615535b8a4..75fa2a0c7a 100644 --- a/StatusPage/src/Components/Header/Header.tsx +++ b/StatusPage/src/Components/Header/Header.tsx @@ -1,84 +1,74 @@ -import Logo from '../Logo/Logo'; -import Link from 'Common/Types/Link'; -import Header from 'CommonUI/src/Components/Header/Header'; -import UILink from 'CommonUI/src/Components/Link/Link'; -import File from 'Model/Models/File'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Logo from "../Logo/Logo"; +import Link from "Common/Types/Link"; +import Header from "CommonUI/src/Components/Header/Header"; +import UILink from "CommonUI/src/Components/Link/Link"; +import File from "Model/Models/File"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - links: Array<Link>; - logo?: File | undefined; - onLogoClicked: () => void; + links: Array<Link>; + logo?: File | undefined; + onLogoClicked: () => void; } const StatusPageHeader: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.logo && props.links.length === 0) { - return <></>; - } + if (!props.logo && props.links.length === 0) { + return <></>; + } - return ( - <div> - {(props.logo || props.links?.length > 0) && ( - <Header - className="bg-transparent flex justify-between mt-5" - leftComponents={ - <> - {props.logo && ( - <div - id="status-page-logo" - className="flex h-12 mt-2" - > - <Logo - file={props.logo} - onClick={() => { - props.onLogoClicked(); - }} - style={{ - maxWidth: '200px', - maxHeight: '50px', - }} - /> - </div> - )} - </> - } - rightComponents={ - <> - {props.links && props.links.length > 0 && ( - <div key={'links'}> - <div className="flex space-x-4 "> - {props.links && - props.links.map( - (link: Link, i: number) => { - return ( - <div - key={i} - className="flex items-center " - > - <UILink - className="flex w-full flex-col items-center text-gray-400 hover:text-gray-600 font-medium font-mono" - to={link.to} - openInNewTab={ - link.openInNewTab - } - > - {link.title} - </UILink> - </div> - ); - } - )} - </div> - </div> - )} - </> - } - /> - )} - </div> - ); + return ( + <div> + {(props.logo || props.links?.length > 0) && ( + <Header + className="bg-transparent flex justify-between mt-5" + leftComponents={ + <> + {props.logo && ( + <div id="status-page-logo" className="flex h-12 mt-2"> + <Logo + file={props.logo} + onClick={() => { + props.onLogoClicked(); + }} + style={{ + maxWidth: "200px", + maxHeight: "50px", + }} + /> + </div> + )} + </> + } + rightComponents={ + <> + {props.links && props.links.length > 0 && ( + <div key={"links"}> + <div className="flex space-x-4 "> + {props.links && + props.links.map((link: Link, i: number) => { + return ( + <div key={i} className="flex items-center "> + <UILink + className="flex w-full flex-col items-center text-gray-400 hover:text-gray-600 font-medium font-mono" + to={link.to} + openInNewTab={link.openInNewTab} + > + {link.title} + </UILink> + </div> + ); + })} + </div> + </div> + )} + </> + } + /> + )} + </div> + ); }; export default StatusPageHeader; diff --git a/StatusPage/src/Components/Logo/Logo.tsx b/StatusPage/src/Components/Logo/Logo.tsx index 0beb64738d..a5650fa0e4 100644 --- a/StatusPage/src/Components/Logo/Logo.tsx +++ b/StatusPage/src/Components/Logo/Logo.tsx @@ -1,26 +1,26 @@ -import Image from 'CommonUI/src/Components/Image/Image'; -import File from 'Model/Models/File'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Image from "CommonUI/src/Components/Image/Image"; +import File from "Model/Models/File"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - onClick: () => void; - file: File; - style?: React.CSSProperties | undefined; + onClick: () => void; + file: File; + style?: React.CSSProperties | undefined; } const Logo: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="flex items-center"> - <Image - file={props.file} - onClick={props.onClick} - height={50} - style={props.style} - /> - </div> - ); + return ( + <div className="flex items-center"> + <Image + file={props.file} + onClick={props.onClick} + height={50} + style={props.style} + /> + </div> + ); }; export default Logo; diff --git a/StatusPage/src/Components/MasterPage/MasterPage.tsx b/StatusPage/src/Components/MasterPage/MasterPage.tsx index a8e2bdf3c4..d4871d2e98 100644 --- a/StatusPage/src/Components/MasterPage/MasterPage.tsx +++ b/StatusPage/src/Components/MasterPage/MasterPage.tsx @@ -1,364 +1,355 @@ -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import LoginUtil from '../../Utils/Login'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import RouteParams from '../../Utils/RouteParams'; -import StatusPageUtil from '../../Utils/StatusPage'; -import Banner from '../Banner/Banner'; -import Footer from '../Footer/Footer'; -import Header from '../Header/Header'; -import NavBar from '../NavBar/NavBar'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import JSONWebTokenData from 'Common/Types/JsonWebTokenData'; -import Link from 'Common/Types/Link'; -import ObjectID from 'Common/Types/ObjectID'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { ImageFunctions } from 'CommonUI/src/Components/Image/Image'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import MasterPage from 'CommonUI/src/Components/MasterPage/MasterPage'; -import JSONWebToken from 'CommonUI/src/Utils/JsonWebToken'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import File from 'Model/Models/File'; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import LoginUtil from "../../Utils/Login"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import RouteParams from "../../Utils/RouteParams"; +import StatusPageUtil from "../../Utils/StatusPage"; +import Banner from "../Banner/Banner"; +import Footer from "../Footer/Footer"; +import Header from "../Header/Header"; +import NavBar from "../NavBar/NavBar"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import JSONWebTokenData from "Common/Types/JsonWebTokenData"; +import Link from "Common/Types/Link"; +import ObjectID from "Common/Types/ObjectID"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { ImageFunctions } from "CommonUI/src/Components/Image/Image"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import MasterPage from "CommonUI/src/Components/MasterPage/MasterPage"; +import JSONWebToken from "CommonUI/src/Utils/JsonWebToken"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import File from "Model/Models/File"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; export interface ComponentProps { - children: ReactElement | Array<ReactElement>; - isLoading?: boolean | undefined; - error?: string | undefined; - onLoadComplete: (masterPage: JSONObject) => void; - isPreview: boolean; - isPrivateStatusPage: boolean; - enableEmailSubscribers: boolean; - enableSMSSubscribers: boolean; + children: ReactElement | Array<ReactElement>; + isLoading?: boolean | undefined; + error?: string | undefined; + onLoadComplete: (masterPage: JSONObject) => void; + isPreview: boolean; + isPrivateStatusPage: boolean; + enableEmailSubscribers: boolean; + enableSMSSubscribers: boolean; } const DashboardMasterPage: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - const [masterPageData, setMasterPageData] = useState<JSONObject | null>( - null - ); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string | null>(null); + const [masterPageData, setMasterPageData] = useState<JSONObject | null>(null); - const [statusPageId, setStatusPageId] = useState<ObjectID | null>(null); + const [statusPageId, setStatusPageId] = useState<ObjectID | null>(null); - const [headerHtml, setHeaderHtml] = useState<null | string>(null); - const [footerHtml, setFooterHTML] = useState<null | string>(null); + const [headerHtml, setHeaderHtml] = useState<null | string>(null); + const [footerHtml, setFooterHTML] = useState<null | string>(null); - const [hidePoweredByOneUptimeBranding, setHidePoweredByOneUptimeBranding] = - useState<boolean>(false); + const [hidePoweredByOneUptimeBranding, setHidePoweredByOneUptimeBranding] = + useState<boolean>(false); - useEffect(() => { - // if there is an SSO token. We need to save that to localstorage. + useEffect(() => { + // if there is an SSO token. We need to save that to localstorage. - const token: string | null = Navigation.getQueryStringByName('token'); + const token: string | null = Navigation.getQueryStringByName("token"); - if (token && statusPageId) { - // set token. + if (token && statusPageId) { + // set token. - const logoutRoute: Route = props.isPreview - ? RouteUtil.populateRouteParams( - RouteMap[PageMap.PREVIEW_LOGOUT]!, - statusPageId - ) - : RouteUtil.populateRouteParams( - RouteMap[PageMap.LOGOUT]!, - statusPageId - ); + const logoutRoute: Route = props.isPreview + ? RouteUtil.populateRouteParams( + RouteMap[PageMap.PREVIEW_LOGOUT]!, + statusPageId, + ) + : RouteUtil.populateRouteParams( + RouteMap[PageMap.LOGOUT]!, + statusPageId, + ); - const decodedtoken: JSONWebTokenData | null = JSONWebToken.decode( - token - ) as JSONWebTokenData; + const decodedtoken: JSONWebTokenData | null = JSONWebToken.decode( + token, + ) as JSONWebTokenData; - if (!decodedtoken) { - alert('Invalid Token. Please log in again.'); - return Navigation.navigate(logoutRoute); - } + if (!decodedtoken) { + alert("Invalid Token. Please log in again."); + return Navigation.navigate(logoutRoute); + } - if (!decodedtoken.userId.toString()) { - alert('User ID not found in Token. Logging out.'); - return Navigation.navigate(logoutRoute); - } + if (!decodedtoken.userId.toString()) { + alert("User ID not found in Token. Logging out."); + return Navigation.navigate(logoutRoute); + } - LoginUtil.login({ - user: { ...decodedtoken, _id: decodedtoken.userId }, - token: token, - }); + LoginUtil.login({ + user: { ...decodedtoken, _id: decodedtoken.userId }, + token: token, + }); - if (!decodedtoken.statusPageId) { - alert('Status Page ID not found in the token. Logging out.'); - return Navigation.navigate(logoutRoute); - } + if (!decodedtoken.statusPageId) { + alert("Status Page ID not found in the token. Logging out."); + return Navigation.navigate(logoutRoute); + } - if (Navigation.getQueryStringByName('redirectUrl')) { - Navigation.navigate( - new Route(Navigation.getQueryStringByName('redirectUrl')!), - { forceNavigate: true } - ); - } else { - Navigation.navigate( - !props.isPreview - ? RouteUtil.populateRouteParams( - RouteMap[PageMap.OVERVIEW]!, - statusPageId - ) - : RouteUtil.populateRouteParams( - RouteMap[PageMap.PREVIEW_OVERVIEW]!, - statusPageId - ), - { forceNavigate: true } - ); - } - } - }, [statusPageId]); - - type GetIdFunction = () => Promise<ObjectID>; - - const getId: GetIdFunction = async (): Promise<ObjectID> => { - if (StatusPageUtil.isPreviewPage()) { - const id: string | null = Navigation.getParamByName( - RouteParams.StatusPageId, - RouteMap[PageMap.PREVIEW_OVERVIEW]! - ); - if (id) { - return new ObjectID(id); - } - } - // get status page id by hostname. - const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute(`/domain`), - { - domain: Navigation.getHostname().toString(), - }, - {} + if (Navigation.getQueryStringByName("redirectUrl")) { + Navigation.navigate( + new Route(Navigation.getQueryStringByName("redirectUrl")!), + { forceNavigate: true }, ); - - if (response.data && response.data['statusPageId']) { - return new ObjectID(response.data['statusPageId'] as string); - } - - throw new BadDataException('Status Page ID not found'); - }; - - useAsyncEffect(async () => { - try { - setIsLoading(true); - const id: ObjectID = await getId(); - - setStatusPageId(id); - - LocalStorage.setItem('statusPageId', id); - const response: HTTPResponse<JSONObject> = - await API.post<JSONObject>( - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/master-page/${id.toString()}` - ), - {}, - {} - ); - setMasterPageData(response.data); - - // setfavicon. - const favIcon: File | null = JSONFunctions.getJSONValueInPath( - response.data || {}, - 'statusPage.faviconFile' - ) as File | null; - if (favIcon && favIcon.file) { - const link: any = document.createElement('link'); - link.rel = 'icon'; - (document as any) - .getElementsByTagName('head')[0] - .appendChild(link); - link.href = ImageFunctions.getImageURL(favIcon); - } - - // setcss. - const css: string | null = JSONFunctions.getJSONValueInPath( - response.data || {}, - 'statusPage.customCSS' - ) as string | null; - - if (css) { - const style: any = document.createElement('style'); - style.innerText = css; - (document as any) - .getElementsByTagName('head')[0] - .appendChild(style); - } - - const headHtml: string | null = JSONFunctions.getJSONValueInPath( - response.data || {}, - 'statusPage.headerHTML' - ) as string | null; - - const hidePoweredByOneUptimeBranding: boolean | null = - JSONFunctions.getJSONValueInPath( - response.data || {}, - 'statusPage.hidePoweredByOneUptimeBranding' - ) as boolean | null; - - setHidePoweredByOneUptimeBranding( - Boolean(hidePoweredByOneUptimeBranding) - ); - - const footHTML: string | null = JSONFunctions.getJSONValueInPath( - response.data || {}, - 'statusPage.footerHTML' - ) as string | null; - - if (headHtml) { - setHeaderHtml(headHtml); - } - - if (footHTML) { - setFooterHTML(footHTML); - } - - props.onLoadComplete(response.data); - - // check SSO token. - - setIsLoading(false); - } catch (err) { - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }, []); - - if (isLoading) { - return <PageLoader isVisible={true} />; + } else { + Navigation.navigate( + !props.isPreview + ? RouteUtil.populateRouteParams( + RouteMap[PageMap.OVERVIEW]!, + statusPageId, + ) + : RouteUtil.populateRouteParams( + RouteMap[PageMap.PREVIEW_OVERVIEW]!, + statusPageId, + ), + { forceNavigate: true }, + ); + } } + }, [statusPageId]); - if (error) { - return <ErrorMessage error={error} />; + type GetIdFunction = () => Promise<ObjectID>; + + const getId: GetIdFunction = async (): Promise<ObjectID> => { + if (StatusPageUtil.isPreviewPage()) { + const id: string | null = Navigation.getParamByName( + RouteParams.StatusPageId, + RouteMap[PageMap.PREVIEW_OVERVIEW]!, + ); + if (id) { + return new ObjectID(id); + } } - - if ( - Navigation.getCurrentRoute().toString().includes('login') || - Navigation.getCurrentRoute().toString().includes('forgot-password') || - Navigation.getCurrentRoute().toString().includes('reset-password') || - Navigation.getCurrentRoute().toString().includes('sso') - ) { - return <>{props.children}</>; - } - - const logo: BaseModel = - (JSONFunctions.getJSONValueInPath( - masterPageData || {}, - 'statusPage.logoFile' - ) as BaseModel) || undefined; - - const links: Array<Link> = ( - (JSONFunctions.getJSONValueInPath( - masterPageData || {}, - 'headerLinks' - ) as Array<JSONObject>) || [] - ).map((link: JSONObject) => { - return { - title: link['title'] as string, - to: link['link'] as URL, - openInNewTab: true, - }; - }); - - return ( - <div className="max-w-5xl m-auto px-5"> - { - <div> - <Banner - file={ - (JSONFunctions.getJSONValueInPath( - masterPageData || {}, - 'statusPage.coverImageFile' - ) as BaseModel) || undefined - } - /> - </div> - } - <MasterPage - makeTopSectionUnstick={true} - isLoading={props.isLoading || false} - error={props.error || ''} - > - <> - {!headerHtml ? ( - <Header - logo={logo} - links={links} - onLogoClicked={() => { - Navigation.navigate( - props.isPreview - ? RouteMap[PageMap.PREVIEW_OVERVIEW]! - : RouteMap[PageMap.OVERVIEW]! - ); - }} - /> - ) : ( - <div - dangerouslySetInnerHTML={{ - __html: headerHtml as string, - }} - /> - )} - <NavBar - isPrivateStatusPage={props.isPrivateStatusPage} - show={true} - isPreview={true} - enableEmailSubscribers={props.enableEmailSubscribers} - enableSMSSubscribers={props.enableSMSSubscribers} - /> - {props.children} - {!footerHtml ? ( - <Footer - hidePoweredByOneUptimeBranding={ - hidePoweredByOneUptimeBranding - } - className="mx-auto w-full py-5 px-0 md:flex md:items-center md:justify-between lg:px-0" - copyright={ - (JSONFunctions.getJSONValueInPath( - masterPageData || {}, - 'statusPage.copyrightText' - ) as string) || '' - } - links={( - (JSONFunctions.getJSONValueInPath( - masterPageData || {}, - 'footerLinks' - ) as Array<JSONObject>) || [] - ).map((link: JSONObject) => { - return { - title: link['title'] as string, - to: link['link'] as URL, - openInNewTab: true, - }; - })} - /> - ) : ( - <div - dangerouslySetInnerHTML={{ - __html: footerHtml as string, - }} - /> - )} - </> - </MasterPage> - </div> + // get status page id by hostname. + const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute(`/domain`), + { + domain: Navigation.getHostname().toString(), + }, + {}, ); + + if (response.data && response.data["statusPageId"]) { + return new ObjectID(response.data["statusPageId"] as string); + } + + throw new BadDataException("Status Page ID not found"); + }; + + useAsyncEffect(async () => { + try { + setIsLoading(true); + const id: ObjectID = await getId(); + + setStatusPageId(id); + + LocalStorage.setItem("statusPageId", id); + const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/master-page/${id.toString()}`, + ), + {}, + {}, + ); + setMasterPageData(response.data); + + // setfavicon. + const favIcon: File | null = JSONFunctions.getJSONValueInPath( + response.data || {}, + "statusPage.faviconFile", + ) as File | null; + if (favIcon && favIcon.file) { + const link: any = document.createElement("link"); + link.rel = "icon"; + (document as any).getElementsByTagName("head")[0].appendChild(link); + link.href = ImageFunctions.getImageURL(favIcon); + } + + // setcss. + const css: string | null = JSONFunctions.getJSONValueInPath( + response.data || {}, + "statusPage.customCSS", + ) as string | null; + + if (css) { + const style: any = document.createElement("style"); + style.innerText = css; + (document as any).getElementsByTagName("head")[0].appendChild(style); + } + + const headHtml: string | null = JSONFunctions.getJSONValueInPath( + response.data || {}, + "statusPage.headerHTML", + ) as string | null; + + const hidePoweredByOneUptimeBranding: boolean | null = + JSONFunctions.getJSONValueInPath( + response.data || {}, + "statusPage.hidePoweredByOneUptimeBranding", + ) as boolean | null; + + setHidePoweredByOneUptimeBranding( + Boolean(hidePoweredByOneUptimeBranding), + ); + + const footHTML: string | null = JSONFunctions.getJSONValueInPath( + response.data || {}, + "statusPage.footerHTML", + ) as string | null; + + if (headHtml) { + setHeaderHtml(headHtml); + } + + if (footHTML) { + setFooterHTML(footHTML); + } + + props.onLoadComplete(response.data); + + // check SSO token. + + setIsLoading(false); + } catch (err) { + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }, []); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + if ( + Navigation.getCurrentRoute().toString().includes("login") || + Navigation.getCurrentRoute().toString().includes("forgot-password") || + Navigation.getCurrentRoute().toString().includes("reset-password") || + Navigation.getCurrentRoute().toString().includes("sso") + ) { + return <>{props.children}</>; + } + + const logo: BaseModel = + (JSONFunctions.getJSONValueInPath( + masterPageData || {}, + "statusPage.logoFile", + ) as BaseModel) || undefined; + + const links: Array<Link> = ( + (JSONFunctions.getJSONValueInPath( + masterPageData || {}, + "headerLinks", + ) as Array<JSONObject>) || [] + ).map((link: JSONObject) => { + return { + title: link["title"] as string, + to: link["link"] as URL, + openInNewTab: true, + }; + }); + + return ( + <div className="max-w-5xl m-auto px-5"> + { + <div> + <Banner + file={ + (JSONFunctions.getJSONValueInPath( + masterPageData || {}, + "statusPage.coverImageFile", + ) as BaseModel) || undefined + } + /> + </div> + } + <MasterPage + makeTopSectionUnstick={true} + isLoading={props.isLoading || false} + error={props.error || ""} + > + <> + {!headerHtml ? ( + <Header + logo={logo} + links={links} + onLogoClicked={() => { + Navigation.navigate( + props.isPreview + ? RouteMap[PageMap.PREVIEW_OVERVIEW]! + : RouteMap[PageMap.OVERVIEW]!, + ); + }} + /> + ) : ( + <div + dangerouslySetInnerHTML={{ + __html: headerHtml as string, + }} + /> + )} + <NavBar + isPrivateStatusPage={props.isPrivateStatusPage} + show={true} + isPreview={true} + enableEmailSubscribers={props.enableEmailSubscribers} + enableSMSSubscribers={props.enableSMSSubscribers} + /> + {props.children} + {!footerHtml ? ( + <Footer + hidePoweredByOneUptimeBranding={hidePoweredByOneUptimeBranding} + className="mx-auto w-full py-5 px-0 md:flex md:items-center md:justify-between lg:px-0" + copyright={ + (JSONFunctions.getJSONValueInPath( + masterPageData || {}, + "statusPage.copyrightText", + ) as string) || "" + } + links={( + (JSONFunctions.getJSONValueInPath( + masterPageData || {}, + "footerLinks", + ) as Array<JSONObject>) || [] + ).map((link: JSONObject) => { + return { + title: link["title"] as string, + to: link["link"] as URL, + openInNewTab: true, + }; + })} + /> + ) : ( + <div + dangerouslySetInnerHTML={{ + __html: footerHtml as string, + }} + /> + )} + </> + </MasterPage> + </div> + ); }; export default DashboardMasterPage; diff --git a/StatusPage/src/Components/Monitor/MonitorOverview.tsx b/StatusPage/src/Components/Monitor/MonitorOverview.tsx index a4fde9f6bf..9914b2e835 100644 --- a/StatusPage/src/Components/Monitor/MonitorOverview.tsx +++ b/StatusPage/src/Components/Monitor/MonitorOverview.tsx @@ -1,154 +1,139 @@ -import { Green } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import IconProp from 'Common/Types/Icon/IconProp'; -import Icon from 'CommonUI/src/Components/Icon/Icon'; -import MarkdownViewer from 'CommonUI/src/Components/Markdown.tsx/LazyMarkdownViewer'; -import MonitorUptimeGraph from 'CommonUI/src/Components/MonitorGraphs/Uptime'; -import UptimeUtil from 'CommonUI/src/Components/MonitorGraphs/UptimeUtil'; -import Tooltip from 'CommonUI/src/Components/Tooltip/Tooltip'; -import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimelne from 'Model/Models/MonitorStatusTimeline'; -import StatusPageHistoryChartBarColorRule from 'Model/Models/StatusPageHistoryChartBarColorRule'; -import { UptimePrecision } from 'Model/Models/StatusPageResource'; -import React, { FunctionComponent, ReactElement } from 'react'; +import { Green } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import IconProp from "Common/Types/Icon/IconProp"; +import Icon from "CommonUI/src/Components/Icon/Icon"; +import MarkdownViewer from "CommonUI/src/Components/Markdown.tsx/LazyMarkdownViewer"; +import MonitorUptimeGraph from "CommonUI/src/Components/MonitorGraphs/Uptime"; +import UptimeUtil from "CommonUI/src/Components/MonitorGraphs/UptimeUtil"; +import Tooltip from "CommonUI/src/Components/Tooltip/Tooltip"; +import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimelne from "Model/Models/MonitorStatusTimeline"; +import StatusPageHistoryChartBarColorRule from "Model/Models/StatusPageHistoryChartBarColorRule"; +import { UptimePrecision } from "Model/Models/StatusPageResource"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - monitorName: string; - description?: string | undefined; - tooltip?: string | undefined; - currentStatus: MonitorStatus; - monitorStatusTimeline: Array<MonitorStatusTimelne>; - startDate: Date; - endDate: Date; - showHistoryChart?: boolean | undefined; - showCurrentStatus?: boolean | undefined; - uptimeGraphHeight?: number | undefined; - className?: string | undefined; - showUptimePercent: boolean; - uptimePrecision?: UptimePrecision | undefined; - statusPageHistoryChartBarColorRules: Array<StatusPageHistoryChartBarColorRule>; - downtimeMonitorStatuses: Array<MonitorStatus>; - defaultBarColor: Color; + monitorName: string; + description?: string | undefined; + tooltip?: string | undefined; + currentStatus: MonitorStatus; + monitorStatusTimeline: Array<MonitorStatusTimelne>; + startDate: Date; + endDate: Date; + showHistoryChart?: boolean | undefined; + showCurrentStatus?: boolean | undefined; + uptimeGraphHeight?: number | undefined; + className?: string | undefined; + showUptimePercent: boolean; + uptimePrecision?: UptimePrecision | undefined; + statusPageHistoryChartBarColorRules: Array<StatusPageHistoryChartBarColorRule>; + downtimeMonitorStatuses: Array<MonitorStatus>; + defaultBarColor: Color; } const MonitorOverview: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - const getCurrentStatus: GetReactElementFunction = (): ReactElement => { - // if the current status is operational then show uptime Percent. + const getCurrentStatus: GetReactElementFunction = (): ReactElement => { + // if the current status is operational then show uptime Percent. - let precision: UptimePrecision = UptimePrecision.ONE_DECIMAL; + let precision: UptimePrecision = UptimePrecision.ONE_DECIMAL; - if (props.uptimePrecision) { - precision = props.uptimePrecision; - } + if (props.uptimePrecision) { + precision = props.uptimePrecision; + } - if ( - !props.downtimeMonitorStatuses.find( - (downtimeStatus: MonitorStatus) => { - return ( - props.currentStatus.id?.toString() === - downtimeStatus.id?.toString() - ); - } - ) && - props.showUptimePercent - ) { - const uptimePercent: number = UptimeUtil.calculateUptimePercentage( - props.monitorStatusTimeline, - precision, - props.downtimeMonitorStatuses - ); + if ( + !props.downtimeMonitorStatuses.find((downtimeStatus: MonitorStatus) => { + return ( + props.currentStatus.id?.toString() === downtimeStatus.id?.toString() + ); + }) && + props.showUptimePercent + ) { + const uptimePercent: number = UptimeUtil.calculateUptimePercentage( + props.monitorStatusTimeline, + precision, + props.downtimeMonitorStatuses, + ); - return ( - <div - className="font-medium" - style={{ - color: - props.currentStatus?.color?.toString() || - Green.toString(), - }} - > - {uptimePercent}% uptime - </div> - ); - } - - if (props.showCurrentStatus) { - return ( - <div - className="" - style={{ - color: - props.currentStatus?.color?.toString() || - Green.toString(), - }} - > - {props.currentStatus?.name || 'Operational'} - </div> - ); - } - - return <></>; - }; - - return ( - <div className={props.className}> - <div> - <div - className="flex justify-between" - style={{ marginBottom: '3px' }} - > - <div className="flex"> - <div className="">{props.monitorName}</div> - {props.tooltip && ( - <Tooltip - key={1} - text={props.tooltip || 'Not available'} - > - <div className="ml-1"> - <Icon - className="cursor-pointer w-4 h-4 mt-1 text-gray-400" - icon={IconProp.Help} - /> - </div> - </Tooltip> - )} - </div> - {getCurrentStatus()} - </div> - <div className="mb-2 text-sm"> - {props.description && ( - <MarkdownViewer text={props.description || ''} /> - )} - </div> - </div> - {props.showHistoryChart && ( - <div> - <MonitorUptimeGraph - error={undefined} - barColorRules={ - props.statusPageHistoryChartBarColorRules - } - defaultBarColor={props.defaultBarColor} - downtimeMonitorStatuses={props.downtimeMonitorStatuses} - items={props.monitorStatusTimeline || []} - startDate={props.startDate} - endDate={props.endDate} - isLoading={false} - height={props.uptimeGraphHeight} - /> - </div> - )} - {props.showHistoryChart && ( - <div className="text-sm text-gray-400 mt-1 flex justify-between"> - <div>90 days ago</div> - <div>Today</div> - </div> - )} + return ( + <div + className="font-medium" + style={{ + color: props.currentStatus?.color?.toString() || Green.toString(), + }} + > + {uptimePercent}% uptime </div> - ); + ); + } + + if (props.showCurrentStatus) { + return ( + <div + className="" + style={{ + color: props.currentStatus?.color?.toString() || Green.toString(), + }} + > + {props.currentStatus?.name || "Operational"} + </div> + ); + } + + return <></>; + }; + + return ( + <div className={props.className}> + <div> + <div className="flex justify-between" style={{ marginBottom: "3px" }}> + <div className="flex"> + <div className="">{props.monitorName}</div> + {props.tooltip && ( + <Tooltip key={1} text={props.tooltip || "Not available"}> + <div className="ml-1"> + <Icon + className="cursor-pointer w-4 h-4 mt-1 text-gray-400" + icon={IconProp.Help} + /> + </div> + </Tooltip> + )} + </div> + {getCurrentStatus()} + </div> + <div className="mb-2 text-sm"> + {props.description && ( + <MarkdownViewer text={props.description || ""} /> + )} + </div> + </div> + {props.showHistoryChart && ( + <div> + <MonitorUptimeGraph + error={undefined} + barColorRules={props.statusPageHistoryChartBarColorRules} + defaultBarColor={props.defaultBarColor} + downtimeMonitorStatuses={props.downtimeMonitorStatuses} + items={props.monitorStatusTimeline || []} + startDate={props.startDate} + endDate={props.endDate} + isLoading={false} + height={props.uptimeGraphHeight} + /> + </div> + )} + {props.showHistoryChart && ( + <div className="text-sm text-gray-400 mt-1 flex justify-between"> + <div>90 days ago</div> + <div>Today</div> + </div> + )} + </div> + ); }; export default MonitorOverview; diff --git a/StatusPage/src/Components/NavBar/NavBar.tsx b/StatusPage/src/Components/NavBar/NavBar.tsx index 283c7a582d..3a07de1ede 100644 --- a/StatusPage/src/Components/NavBar/NavBar.tsx +++ b/StatusPage/src/Components/NavBar/NavBar.tsx @@ -1,113 +1,109 @@ -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 from 'CommonUI/src/Components/Navbar/NavBar'; -import NavBarItem from 'CommonUI/src/Components/Navbar/NavBarItem'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 from "CommonUI/src/Components/Navbar/NavBar"; +import NavBarItem from "CommonUI/src/Components/Navbar/NavBarItem"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - show: boolean; - isPreview: boolean; - isPrivateStatusPage: boolean; - enableEmailSubscribers: boolean; - enableSMSSubscribers: boolean; + show: boolean; + isPreview: boolean; + isPrivateStatusPage: boolean; + enableEmailSubscribers: boolean; + enableSMSSubscribers: boolean; } const DashboardNavbar: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - if (!props.show) { - return <></>; - } + if (!props.show) { + return <></>; + } - return ( - <NavBar className="bg-white lg:flex text-center justify-between py-2 mt-5 rounded-lg shadow px-5"> - <NavBarItem - id="overview-nav-bar-item" - title="Overview" - icon={IconProp.CheckCircle} - exact={true} - route={RouteUtil.populateRouteParams( - props.isPreview - ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) - : (RouteMap[PageMap.OVERVIEW] as Route) - )} - ></NavBarItem> + return ( + <NavBar className="bg-white lg:flex text-center justify-between py-2 mt-5 rounded-lg shadow px-5"> + <NavBarItem + id="overview-nav-bar-item" + title="Overview" + icon={IconProp.CheckCircle} + exact={true} + route={RouteUtil.populateRouteParams( + props.isPreview + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + )} + ></NavBarItem> - <NavBarItem - id="incidents-nav-bar-item" - title="Incidents" - icon={IconProp.Alert} - exact={true} - route={RouteUtil.populateRouteParams( - props.isPreview - ? (RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route) - : (RouteMap[PageMap.INCIDENT_LIST] as Route) - )} - ></NavBarItem> + <NavBarItem + id="incidents-nav-bar-item" + title="Incidents" + icon={IconProp.Alert} + exact={true} + route={RouteUtil.populateRouteParams( + props.isPreview + ? (RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route) + : (RouteMap[PageMap.INCIDENT_LIST] as Route), + )} + ></NavBarItem> - <NavBarItem - id="announcements-nav-bar-item" - title="Announcements" - icon={IconProp.Announcement} - exact={true} - route={RouteUtil.populateRouteParams( - props.isPreview - ? (RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_LIST] as Route) - : (RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route) - )} - ></NavBarItem> + <NavBarItem + id="announcements-nav-bar-item" + title="Announcements" + icon={IconProp.Announcement} + exact={true} + route={RouteUtil.populateRouteParams( + props.isPreview + ? (RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_LIST] as Route) + : (RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route), + )} + ></NavBarItem> - <NavBarItem - id="scheduled-events-nav-bar-item" - title="Scheduled Events" - icon={IconProp.Clock} - exact={true} - route={RouteUtil.populateRouteParams( - props.isPreview - ? (RouteMap[ - PageMap.PREVIEW_SCHEDULED_EVENT_LIST - ] as Route) - : (RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route) - )} - ></NavBarItem> + <NavBarItem + id="scheduled-events-nav-bar-item" + title="Scheduled Events" + icon={IconProp.Clock} + exact={true} + route={RouteUtil.populateRouteParams( + props.isPreview + ? (RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_LIST] as Route) + : (RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route), + )} + ></NavBarItem> - {props.enableEmailSubscribers || props.enableSMSSubscribers ? ( - <NavBarItem - id="subscribe-nav-bar-item" - title="Subscribe" - icon={IconProp.Email} - exact={true} - route={RouteUtil.populateRouteParams( - props.isPreview - ? (RouteMap[ - PageMap.PREVIEW_SUBSCRIBE_EMAIL - ] as Route) - : (RouteMap[PageMap.SUBSCRIBE_SMS] as Route) - )} - ></NavBarItem> - ) : ( - <></> - )} + {props.enableEmailSubscribers || props.enableSMSSubscribers ? ( + <NavBarItem + id="subscribe-nav-bar-item" + title="Subscribe" + icon={IconProp.Email} + exact={true} + route={RouteUtil.populateRouteParams( + props.isPreview + ? (RouteMap[PageMap.PREVIEW_SUBSCRIBE_EMAIL] as Route) + : (RouteMap[PageMap.SUBSCRIBE_SMS] as Route), + )} + ></NavBarItem> + ) : ( + <></> + )} - {props.isPrivateStatusPage ? ( - <NavBarItem - id="logout-nav-bar-item" - title="Logout" - icon={IconProp.Logout} - exact={true} - route={RouteUtil.populateRouteParams( - props.isPreview - ? (RouteMap[PageMap.PREVIEW_LOGOUT] as Route) - : (RouteMap[PageMap.LOGOUT] as Route) - )} - ></NavBarItem> - ) : ( - <></> - )} - </NavBar> - ); + {props.isPrivateStatusPage ? ( + <NavBarItem + id="logout-nav-bar-item" + title="Logout" + icon={IconProp.Logout} + exact={true} + route={RouteUtil.populateRouteParams( + props.isPreview + ? (RouteMap[PageMap.PREVIEW_LOGOUT] as Route) + : (RouteMap[PageMap.LOGOUT] as Route), + )} + ></NavBarItem> + ) : ( + <></> + )} + </NavBar> + ); }; export default DashboardNavbar; diff --git a/StatusPage/src/Components/Page/Page.tsx b/StatusPage/src/Components/Page/Page.tsx index 6144244f0d..3e8ecd22d8 100644 --- a/StatusPage/src/Components/Page/Page.tsx +++ b/StatusPage/src/Components/Page/Page.tsx @@ -1,17 +1,14 @@ import Page, { - ComponentProps as PageComponentProps, -} from 'CommonUI/src/Components/Page/Page'; -import React, { FunctionComponent, ReactElement } from 'react'; + ComponentProps as PageComponentProps, +} from "CommonUI/src/Components/Page/Page"; +import React, { FunctionComponent, ReactElement } from "react"; const StatusPagePage: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - return ( - <Page - {...props} - className="w-full mt-5 mb-20 h-fullmb-auto h-full p-0" - /> - ); + return ( + <Page {...props} className="w-full mt-5 mb-20 h-fullmb-auto h-full p-0" /> + ); }; export default StatusPagePage; diff --git a/StatusPage/src/Components/Section/Section.tsx b/StatusPage/src/Components/Section/Section.tsx index b9d08fada2..613968d2c7 100644 --- a/StatusPage/src/Components/Section/Section.tsx +++ b/StatusPage/src/Components/Section/Section.tsx @@ -1,18 +1,18 @@ -import React, { FunctionComponent, ReactElement } from 'react'; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - title: string; + title: string; } const Section: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <div className="section-label text-gray-500 mt-2 text-md font-medium flex"> - <div className="section-title">{props.title}</div> - <div className="section-border flex-grow border-t border-gray-300 ml-3 mt-3"></div> - </div> - ); + return ( + <div className="section-label text-gray-500 mt-2 text-md font-medium flex"> + <div className="section-title">{props.title}</div> + <div className="section-border flex-grow border-t border-gray-300 ml-3 mt-3"></div> + </div> + ); }; export default Section; diff --git a/StatusPage/src/Index.tsx b/StatusPage/src/Index.tsx index 6d21c17cf8..1779d619e1 100644 --- a/StatusPage/src/Index.tsx +++ b/StatusPage/src/Index.tsx @@ -1,19 +1,19 @@ -import App from './App'; -import Telemetry from 'CommonUI/src/Utils/Telemetry'; -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { BrowserRouter } from 'react-router-dom'; +import App from "./App"; +import Telemetry from "CommonUI/src/Utils/Telemetry"; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; Telemetry.init({ - serviceName: 'StatusPage', + serviceName: "StatusPage", }); const root: any = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById("root") as HTMLElement, ); root.render( - <BrowserRouter> - <App /> - </BrowserRouter> + <BrowserRouter> + <App /> + </BrowserRouter>, ); diff --git a/StatusPage/src/Pages/Accounts/ForgotPassword.tsx b/StatusPage/src/Pages/Accounts/ForgotPassword.tsx index 593b527b93..6e9fa3556b 100644 --- a/StatusPage/src/Pages/Accounts/ForgotPassword.tsx +++ b/StatusPage/src/Pages/Accounts/ForgotPassword.tsx @@ -1,157 +1,156 @@ -import { FORGOT_PASSWORD_API_URL } from '../../Utils/ApiPaths'; -import PageMap from '../../Utils/PageMap'; -import RouteMap from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import UserUtil from '../../Utils/User'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Link from 'CommonUI/src/Components/Link/Link'; -import { FILE_URL } from 'CommonUI/src/Config'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import React, { FunctionComponent, useEffect, useState } from 'react'; +import { FORGOT_PASSWORD_API_URL } from "../../Utils/ApiPaths"; +import PageMap from "../../Utils/PageMap"; +import RouteMap from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import UserUtil from "../../Utils/User"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ObjectID from "Common/Types/ObjectID"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Link from "CommonUI/src/Components/Link/Link"; +import { FILE_URL } from "CommonUI/src/Config"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import React, { FunctionComponent, useEffect, useState } from "react"; export interface ComponentProps { - statusPageName: string; - logoFileId: ObjectID; - forceSSO: boolean; + statusPageName: string; + logoFileId: ObjectID; + forceSSO: boolean; } const ForgotPassword: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ) => { - useEffect(() => { - if (props.forceSSO) { - Navigation.navigate( - !StatusPageUtil.isPreviewPage() - ? RouteMap[PageMap.SSO]! - : RouteMap[PageMap.PREVIEW_SSO]! - ); - } - }, [props.forceSSO]); - - const apiUrl: URL = FORGOT_PASSWORD_API_URL; - - const [isSuccess, setIsSuccess] = useState<boolean>(false); - - if (!StatusPageUtil.getStatusPageId()) { - return <></>; + useEffect(() => { + if (props.forceSSO) { + Navigation.navigate( + !StatusPageUtil.isPreviewPage() + ? RouteMap[PageMap.SSO]! + : RouteMap[PageMap.PREVIEW_SSO]!, + ); } + }, [props.forceSSO]); - if (!StatusPageUtil.isPrivateStatusPage()) { - Navigation.navigate( - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` - : '/' - ) - ); - } + const apiUrl: URL = FORGOT_PASSWORD_API_URL; - if ( - StatusPageUtil.getStatusPageId() && - UserUtil.isLoggedIn(StatusPageUtil.getStatusPageId()!) - ) { - Navigation.navigate( - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` - : '/' - ) - ); - } + const [isSuccess, setIsSuccess] = useState<boolean>(false); - return ( - <div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8"> - <div className="sm:mx-auto sm:w-full sm:max-w-md"> - {props.logoFileId && props.logoFileId.toString() ? ( - <img - style={{ height: '70px', margin: 'auto' }} - src={`${URL.fromString(FILE_URL.toString()).addRoute( - '/image/' + props.logoFileId.toString() - )}`} - /> - ) : ( - <></> - )} - <h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900"> - Forgot Password - </h2> - {!isSuccess && ( - <p className="mt-2 text-center text-sm text-gray-600"> - If you have forgotten your password for{' '} - {props.statusPageName}, please enter your email and the - password reset link will be sent to you.{' '} - </p> - )} + if (!StatusPageUtil.getStatusPageId()) { + return <></>; + } - {isSuccess && ( - <p className="mt-2 text-center text-sm text-gray-600"> - We have emailed you the password reset link. Please do - not forget to check spam.{' '} - </p> - )} - </div> - - <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> - {!isSuccess && ( - <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> - <ModelForm<StatusPagePrivateUser> - modelType={StatusPagePrivateUser} - id="login-form" - name="Status Page > Forgot Password" - createOrUpdateApiUrl={apiUrl} - onBeforeCreate={( - item: StatusPagePrivateUser - ): Promise<StatusPagePrivateUser> => { - item.statusPageId = - StatusPageUtil.getStatusPageId()!; - return Promise.resolve(item); - }} - fields={[ - { - field: { - email: true, - }, - title: 'Email', - showEvenIfPermissionDoesNotExist: true, - fieldType: FormFieldSchemaType.Email, - required: true, - }, - ]} - onSuccess={() => { - setIsSuccess(true); - }} - submitButtonText={'Send Password Reset Link'} - formType={FormType.Create} - maxPrimaryButtonWidth={true} - /> - </div> - )} - - <div className="mt-10 text-center"> - <p className="text-muted mb-0 text-gray-500"> - Remember your password?{' '} - <Link - to={ - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/login` - : '/login' - ) - } - className="text-indigo-500 hover:text-indigo-900 cursor-pointer" - > - Login. - </Link> - </p> - </div> - </div> - </div> + if (!StatusPageUtil.isPrivateStatusPage()) { + Navigation.navigate( + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` + : "/", + ), ); + } + + if ( + StatusPageUtil.getStatusPageId() && + UserUtil.isLoggedIn(StatusPageUtil.getStatusPageId()!) + ) { + Navigation.navigate( + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` + : "/", + ), + ); + } + + return ( + <div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8"> + <div className="sm:mx-auto sm:w-full sm:max-w-md"> + {props.logoFileId && props.logoFileId.toString() ? ( + <img + style={{ height: "70px", margin: "auto" }} + src={`${URL.fromString(FILE_URL.toString()).addRoute( + "/image/" + props.logoFileId.toString(), + )}`} + /> + ) : ( + <></> + )} + <h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900"> + Forgot Password + </h2> + {!isSuccess && ( + <p className="mt-2 text-center text-sm text-gray-600"> + If you have forgotten your password for {props.statusPageName}, + please enter your email and the password reset link will be sent to + you.{" "} + </p> + )} + + {isSuccess && ( + <p className="mt-2 text-center text-sm text-gray-600"> + We have emailed you the password reset link. Please do not forget to + check spam.{" "} + </p> + )} + </div> + + <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> + {!isSuccess && ( + <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> + <ModelForm<StatusPagePrivateUser> + modelType={StatusPagePrivateUser} + id="login-form" + name="Status Page > Forgot Password" + createOrUpdateApiUrl={apiUrl} + onBeforeCreate={( + item: StatusPagePrivateUser, + ): Promise<StatusPagePrivateUser> => { + item.statusPageId = StatusPageUtil.getStatusPageId()!; + return Promise.resolve(item); + }} + fields={[ + { + field: { + email: true, + }, + title: "Email", + showEvenIfPermissionDoesNotExist: true, + fieldType: FormFieldSchemaType.Email, + required: true, + }, + ]} + onSuccess={() => { + setIsSuccess(true); + }} + submitButtonText={"Send Password Reset Link"} + formType={FormType.Create} + maxPrimaryButtonWidth={true} + /> + </div> + )} + + <div className="mt-10 text-center"> + <p className="text-muted mb-0 text-gray-500"> + Remember your password?{" "} + <Link + to={ + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/login` + : "/login", + ) + } + className="text-indigo-500 hover:text-indigo-900 cursor-pointer" + > + Login. + </Link> + </p> + </div> + </div> + </div> + ); }; export default ForgotPassword; diff --git a/StatusPage/src/Pages/Accounts/Login.tsx b/StatusPage/src/Pages/Accounts/Login.tsx index 119a94dda7..0623253864 100644 --- a/StatusPage/src/Pages/Accounts/Login.tsx +++ b/StatusPage/src/Pages/Accounts/Login.tsx @@ -1,237 +1,226 @@ -import { LOGIN_API_URL } from '../../Utils/ApiPaths'; -import LoginUtil from '../../Utils/Login'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import UserUtil from '../../Utils/User'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Link from 'CommonUI/src/Components/Link/Link'; -import { FILE_URL } from 'CommonUI/src/Config'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import React, { FunctionComponent, useEffect } from 'react'; +import { LOGIN_API_URL } from "../../Utils/ApiPaths"; +import LoginUtil from "../../Utils/Login"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import UserUtil from "../../Utils/User"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Link from "CommonUI/src/Components/Link/Link"; +import { FILE_URL } from "CommonUI/src/Config"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import React, { FunctionComponent, useEffect } from "react"; export interface ComponentProps { - statusPageName: string; - logoFileId: ObjectID; - forceSSO: boolean; - hasEnabledSSOConfig: boolean; + statusPageName: string; + logoFileId: ObjectID; + forceSSO: boolean; + hasEnabledSSOConfig: boolean; } const LoginPage: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ) => { - useEffect(() => { - if (props.forceSSO && StatusPageUtil.getStatusPageId()) { - if (Navigation.getQueryStringByName('redirectUrl')) { - // forward redirect url to sso page + useEffect(() => { + if (props.forceSSO && StatusPageUtil.getStatusPageId()) { + if (Navigation.getQueryStringByName("redirectUrl")) { + // forward redirect url to sso page - const navRoute: Route = new Route( - (!StatusPageUtil.isPreviewPage() - ? RouteUtil.populateRouteParams( - RouteMap[PageMap.SSO]!, - StatusPageUtil.getStatusPageId()! - ) - : RouteUtil.populateRouteParams( - RouteMap[PageMap.PREVIEW_SSO]!, - StatusPageUtil.getStatusPageId()! - ) - ).toString() + - `?redirectUrl=${Navigation.getQueryStringByName( - 'redirectUrl' - )}` - ); - - Navigation.navigate(navRoute); - } else { - const navRoute: Route = !StatusPageUtil.isPreviewPage() - ? RouteUtil.populateRouteParams( - RouteMap[PageMap.SSO]!, - StatusPageUtil.getStatusPageId()! - ) - : RouteUtil.populateRouteParams( - RouteMap[PageMap.PREVIEW_SSO]!, - StatusPageUtil.getStatusPageId()! - ); - - Navigation.navigate(navRoute); - } - } - }, [props.forceSSO, StatusPageUtil.getStatusPageId()]); - - const apiUrl: URL = LOGIN_API_URL; - - if (!StatusPageUtil.getStatusPageId()) { - return <></>; - } - - if (!StatusPageUtil.isPrivateStatusPage()) { const navRoute: Route = new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` - : '/' + (!StatusPageUtil.isPreviewPage() + ? RouteUtil.populateRouteParams( + RouteMap[PageMap.SSO]!, + StatusPageUtil.getStatusPageId()!, + ) + : RouteUtil.populateRouteParams( + RouteMap[PageMap.PREVIEW_SSO]!, + StatusPageUtil.getStatusPageId()!, + ) + ).toString() + + `?redirectUrl=${Navigation.getQueryStringByName("redirectUrl")}`, ); Navigation.navigate(navRoute); - } - - if ( - StatusPageUtil.getStatusPageId() && - UserUtil.isLoggedIn(StatusPageUtil.getStatusPageId()!) - ) { - if (Navigation.getQueryStringByName('redirectUrl')) { - Navigation.navigate( - new Route(Navigation.getQueryStringByName('redirectUrl')!) - ); - } else { - const navRoute: Route = new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` - : '/' + } else { + const navRoute: Route = !StatusPageUtil.isPreviewPage() + ? RouteUtil.populateRouteParams( + RouteMap[PageMap.SSO]!, + StatusPageUtil.getStatusPageId()!, + ) + : RouteUtil.populateRouteParams( + RouteMap[PageMap.PREVIEW_SSO]!, + StatusPageUtil.getStatusPageId()!, ); - Navigation.navigate(navRoute); - } + Navigation.navigate(navRoute); + } } + }, [props.forceSSO, StatusPageUtil.getStatusPageId()]); - return ( - <div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8"> - <div className="sm:mx-auto sm:w-full sm:max-w-md"> - {props.logoFileId && props.logoFileId.toString() ? ( - <img - style={{ height: '70px', margin: 'auto' }} - src={`${URL.fromString(FILE_URL.toString()).addRoute( - '/image/' + props.logoFileId.toString() - )}`} - /> - ) : ( - <></> - )} - <h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900"> - Welcome back! - </h2> - <p className="mt-2 text-center text-sm text-gray-600"> - Please login to view {props.statusPageName || 'Status Page'} - </p> - </div> + const apiUrl: URL = LOGIN_API_URL; - <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> - <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> - <ModelForm<StatusPagePrivateUser> - modelType={StatusPagePrivateUser} - id="login-form" - name="Status Page Login" - fields={[ - { - field: { - email: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Email', - fieldType: FormFieldSchemaType.Email, - required: true, - }, - { - field: { - password: true, - }, - title: 'Password', - required: true, - showEvenIfPermissionDoesNotExist: true, - validation: { - minLength: 6, - }, - fieldType: FormFieldSchemaType.Password, - sideLink: { - text: 'Forgot password?', - url: new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/forgot-password` - : '/forgot-password' - ), - openLinkInNewTab: false, - }, - }, - ]} - createOrUpdateApiUrl={apiUrl} - formType={FormType.Create} - submitButtonText={'Login'} - onSuccess={( - value: StatusPagePrivateUser, - miscData: JSONObject | undefined - ) => { - LoginUtil.login({ - user: value, - token: miscData ? miscData['token'] : undefined, - }); + if (!StatusPageUtil.getStatusPageId()) { + return <></>; + } - if ( - Navigation.getQueryStringByName('redirectUrl') - ) { - Navigation.navigate( - new Route( - Navigation.getQueryStringByName( - 'redirectUrl' - )! - ) - ); - } else { - Navigation.navigate( - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/` - : '/' - ) - ); - } - }} - onBeforeCreate={( - item: StatusPagePrivateUser - ): Promise<StatusPagePrivateUser> => { - if (!StatusPageUtil.getStatusPageId()) { - throw new BadDataException( - 'Status Page ID not found' - ); - } - - item.statusPageId = - StatusPageUtil.getStatusPageId()!; - return Promise.resolve(item); - }} - maxPrimaryButtonWidth={true} - footer={ - <div className="actions pointer text-center mt-4 hover:underline fw-semibold"> - {props.hasEnabledSSOConfig ? ( - <p> - <Link - to={ - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/sso` - : '/sso' - ) - } - className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm" - > - Use single sign-on (SSO) instead - </Link> - </p> - ) : ( - <></> - )} - </div> - } - /> - </div> - </div> - </div> + if (!StatusPageUtil.isPrivateStatusPage()) { + const navRoute: Route = new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` + : "/", ); + + Navigation.navigate(navRoute); + } + + if ( + StatusPageUtil.getStatusPageId() && + UserUtil.isLoggedIn(StatusPageUtil.getStatusPageId()!) + ) { + if (Navigation.getQueryStringByName("redirectUrl")) { + Navigation.navigate( + new Route(Navigation.getQueryStringByName("redirectUrl")!), + ); + } else { + const navRoute: Route = new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` + : "/", + ); + + Navigation.navigate(navRoute); + } + } + + return ( + <div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8"> + <div className="sm:mx-auto sm:w-full sm:max-w-md"> + {props.logoFileId && props.logoFileId.toString() ? ( + <img + style={{ height: "70px", margin: "auto" }} + src={`${URL.fromString(FILE_URL.toString()).addRoute( + "/image/" + props.logoFileId.toString(), + )}`} + /> + ) : ( + <></> + )} + <h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900"> + Welcome back! + </h2> + <p className="mt-2 text-center text-sm text-gray-600"> + Please login to view {props.statusPageName || "Status Page"} + </p> + </div> + + <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> + <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> + <ModelForm<StatusPagePrivateUser> + modelType={StatusPagePrivateUser} + id="login-form" + name="Status Page Login" + fields={[ + { + field: { + email: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Email", + fieldType: FormFieldSchemaType.Email, + required: true, + }, + { + field: { + password: true, + }, + title: "Password", + required: true, + showEvenIfPermissionDoesNotExist: true, + validation: { + minLength: 6, + }, + fieldType: FormFieldSchemaType.Password, + sideLink: { + text: "Forgot password?", + url: new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/forgot-password` + : "/forgot-password", + ), + openLinkInNewTab: false, + }, + }, + ]} + createOrUpdateApiUrl={apiUrl} + formType={FormType.Create} + submitButtonText={"Login"} + onSuccess={( + value: StatusPagePrivateUser, + miscData: JSONObject | undefined, + ) => { + LoginUtil.login({ + user: value, + token: miscData ? miscData["token"] : undefined, + }); + + if (Navigation.getQueryStringByName("redirectUrl")) { + Navigation.navigate( + new Route(Navigation.getQueryStringByName("redirectUrl")!), + ); + } else { + Navigation.navigate( + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/` + : "/", + ), + ); + } + }} + onBeforeCreate={( + item: StatusPagePrivateUser, + ): Promise<StatusPagePrivateUser> => { + if (!StatusPageUtil.getStatusPageId()) { + throw new BadDataException("Status Page ID not found"); + } + + item.statusPageId = StatusPageUtil.getStatusPageId()!; + return Promise.resolve(item); + }} + maxPrimaryButtonWidth={true} + footer={ + <div className="actions pointer text-center mt-4 hover:underline fw-semibold"> + {props.hasEnabledSSOConfig ? ( + <p> + <Link + to={ + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/sso` + : "/sso", + ) + } + className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm" + > + Use single sign-on (SSO) instead + </Link> + </p> + ) : ( + <></> + )} + </div> + } + /> + </div> + </div> + </div> + ); }; export default LoginPage; diff --git a/StatusPage/src/Pages/Accounts/Logout.tsx b/StatusPage/src/Pages/Accounts/Logout.tsx index b186bd6abf..d8b402ab6f 100644 --- a/StatusPage/src/Pages/Accounts/Logout.tsx +++ b/StatusPage/src/Pages/Accounts/Logout.tsx @@ -1,46 +1,46 @@ -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import UserUtil from '../../Utils/User'; -import Route from 'Common/Types/API/Route'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import React, { useEffect } from 'react'; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import UserUtil from "../../Utils/User"; +import Route from "Common/Types/API/Route"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import React, { useEffect } from "react"; const Logout: () => JSX.Element = () => { - const [error, setError] = React.useState<string | null>(null); + const [error, setError] = React.useState<string | null>(null); - const logout: PromiseVoidFunction = async (): Promise<void> => { - if (StatusPageUtil.getStatusPageId()) { - await UserUtil.logout(StatusPageUtil.getStatusPageId()!); - const navRoute: Route = StatusPageUtil.isPreviewPage() - ? RouteUtil.populateRouteParams( - RouteMap[PageMap.PREVIEW_LOGIN]!, - StatusPageUtil.getStatusPageId()! - ) - : RouteUtil.populateRouteParams( - RouteMap[PageMap.LOGIN]!, - StatusPageUtil.getStatusPageId()! - ); - Navigation.navigate(navRoute, { - forceNavigate: true, - }); - } - }; - - useEffect(() => { - logout().catch((error: Error) => { - setError(error.message || error.toString()); - }); - }, [StatusPageUtil.getStatusPageId()]); - - if (error) { - return <ErrorMessage error={error} />; + const logout: PromiseVoidFunction = async (): Promise<void> => { + if (StatusPageUtil.getStatusPageId()) { + await UserUtil.logout(StatusPageUtil.getStatusPageId()!); + const navRoute: Route = StatusPageUtil.isPreviewPage() + ? RouteUtil.populateRouteParams( + RouteMap[PageMap.PREVIEW_LOGIN]!, + StatusPageUtil.getStatusPageId()!, + ) + : RouteUtil.populateRouteParams( + RouteMap[PageMap.LOGIN]!, + StatusPageUtil.getStatusPageId()!, + ); + Navigation.navigate(navRoute, { + forceNavigate: true, + }); } + }; - return <PageLoader isVisible={true} />; + useEffect(() => { + logout().catch((error: Error) => { + setError(error.message || error.toString()); + }); + }, [StatusPageUtil.getStatusPageId()]); + + if (error) { + return <ErrorMessage error={error} />; + } + + return <PageLoader isVisible={true} />; }; export default Logout; diff --git a/StatusPage/src/Pages/Accounts/ResetPassword.tsx b/StatusPage/src/Pages/Accounts/ResetPassword.tsx index 553a3b7224..54a41fa55a 100644 --- a/StatusPage/src/Pages/Accounts/ResetPassword.tsx +++ b/StatusPage/src/Pages/Accounts/ResetPassword.tsx @@ -1,181 +1,178 @@ -import { RESET_PASSWORD_API_URL } from '../../Utils/ApiPaths'; -import PageMap from '../../Utils/PageMap'; -import RouteMap from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import UserUtil from '../../Utils/User'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ObjectID from 'Common/Types/ObjectID'; -import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import Link from 'CommonUI/src/Components/Link/Link'; -import { FILE_URL } from 'CommonUI/src/Config'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import React, { FunctionComponent, useEffect, useState } from 'react'; +import { RESET_PASSWORD_API_URL } from "../../Utils/ApiPaths"; +import PageMap from "../../Utils/PageMap"; +import RouteMap from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import UserUtil from "../../Utils/User"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ObjectID from "Common/Types/ObjectID"; +import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import Link from "CommonUI/src/Components/Link/Link"; +import { FILE_URL } from "CommonUI/src/Config"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import React, { FunctionComponent, useEffect, useState } from "react"; export interface ComponentProps { - statusPageName: string; - logoFileId: ObjectID; - forceSSO: boolean; + statusPageName: string; + logoFileId: ObjectID; + forceSSO: boolean; } const ResetPassword: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ) => { - const apiUrl: URL = RESET_PASSWORD_API_URL; - const [isSuccess, setIsSuccess] = useState<boolean>(false); + const apiUrl: URL = RESET_PASSWORD_API_URL; + const [isSuccess, setIsSuccess] = useState<boolean>(false); - useEffect(() => { - if (props.forceSSO) { - Navigation.navigate( - !StatusPageUtil.isPreviewPage() - ? RouteMap[PageMap.SSO]! - : RouteMap[PageMap.PREVIEW_SSO]! - ); - } - }, [props.forceSSO]); - - if (!StatusPageUtil.getStatusPageId()) { - return <></>; + useEffect(() => { + if (props.forceSSO) { + Navigation.navigate( + !StatusPageUtil.isPreviewPage() + ? RouteMap[PageMap.SSO]! + : RouteMap[PageMap.PREVIEW_SSO]!, + ); } + }, [props.forceSSO]); - if ( - StatusPageUtil.getStatusPageId() && - UserUtil.isLoggedIn(StatusPageUtil.getStatusPageId()!) - ) { - Navigation.navigate( - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` - : '/' - ) - ); - } + if (!StatusPageUtil.getStatusPageId()) { + return <></>; + } - if (!StatusPageUtil.isPrivateStatusPage()) { - Navigation.navigate( - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` - : '/' - ) - ); - } - - return ( - <div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8"> - <div className="sm:mx-auto sm:w-full sm:max-w-md"> - {props.logoFileId && props.logoFileId.toString() ? ( - <img - style={{ height: '70px', margin: 'auto' }} - src={`${URL.fromString(FILE_URL.toString()).addRoute( - '/image/' + props.logoFileId.toString() - )}`} - /> - ) : ( - <></> - )} - <h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900"> - Create a new password for your {props.statusPageName}{' '} - account. - </h2> - - {!isSuccess && ( - <p className="mt-2 text-center text-sm text-gray-600"> - Please enter your new password and we will have it - updated.{' '} - </p> - )} - - {isSuccess && ( - <p className="mt-2 text-center text-sm text-gray-600"> - Your password has been updated. Please log in. - </p> - )} - </div> - - <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> - {!isSuccess && ( - <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> - <ModelForm<StatusPagePrivateUser> - modelType={StatusPagePrivateUser} - id="register-form" - name="Status Page > Reset Password" - onBeforeCreate={( - item: StatusPagePrivateUser - ): Promise<StatusPagePrivateUser> => { - item.resetPasswordToken = - Navigation.getLastParam() - ?.toString() - .replace('/', '') - .toString() || ''; - - item.statusPageId = - StatusPageUtil.getStatusPageId()!; - return Promise.resolve(item); - }} - showAsColumns={1} - maxPrimaryButtonWidth={true} - fields={[ - { - field: { - password: true, - }, - showEvenIfPermissionDoesNotExist: true, - fieldType: FormFieldSchemaType.Password, - validation: { - minLength: 6, - }, - placeholder: 'New Password', - title: 'New Password', - required: true, - }, - { - field: { - confirmPassword: true, - } as any, - validation: { - minLength: 6, - toMatchField: 'password', - }, - showEvenIfPermissionDoesNotExist: true, - fieldType: FormFieldSchemaType.Password, - placeholder: 'Confirm Password', - title: 'Confirm Password', - overrideFieldKey: 'confirmPassword', - required: true, - }, - ]} - createOrUpdateApiUrl={apiUrl} - formType={FormType.Create} - submitButtonText={'Reset Password'} - onSuccess={() => { - setIsSuccess(true); - }} - /> - </div> - )} - <div className="mt-10 text-center"> - <p className="text-muted mb-0 text-gray-500"> - Know your password?{' '} - <Link - to={ - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/login` - : '/login' - ) - } - className="text-indigo-500 hover:text-indigo-900 cursor-pointer" - > - Log in. - </Link> - </p> - </div> - </div> - </div> + if ( + StatusPageUtil.getStatusPageId() && + UserUtil.isLoggedIn(StatusPageUtil.getStatusPageId()!) + ) { + Navigation.navigate( + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` + : "/", + ), ); + } + + if (!StatusPageUtil.isPrivateStatusPage()) { + Navigation.navigate( + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` + : "/", + ), + ); + } + + return ( + <div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8"> + <div className="sm:mx-auto sm:w-full sm:max-w-md"> + {props.logoFileId && props.logoFileId.toString() ? ( + <img + style={{ height: "70px", margin: "auto" }} + src={`${URL.fromString(FILE_URL.toString()).addRoute( + "/image/" + props.logoFileId.toString(), + )}`} + /> + ) : ( + <></> + )} + <h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900"> + Create a new password for your {props.statusPageName} account. + </h2> + + {!isSuccess && ( + <p className="mt-2 text-center text-sm text-gray-600"> + Please enter your new password and we will have it updated.{" "} + </p> + )} + + {isSuccess && ( + <p className="mt-2 text-center text-sm text-gray-600"> + Your password has been updated. Please log in. + </p> + )} + </div> + + <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> + {!isSuccess && ( + <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> + <ModelForm<StatusPagePrivateUser> + modelType={StatusPagePrivateUser} + id="register-form" + name="Status Page > Reset Password" + onBeforeCreate={( + item: StatusPagePrivateUser, + ): Promise<StatusPagePrivateUser> => { + item.resetPasswordToken = + Navigation.getLastParam() + ?.toString() + .replace("/", "") + .toString() || ""; + + item.statusPageId = StatusPageUtil.getStatusPageId()!; + return Promise.resolve(item); + }} + showAsColumns={1} + maxPrimaryButtonWidth={true} + fields={[ + { + field: { + password: true, + }, + showEvenIfPermissionDoesNotExist: true, + fieldType: FormFieldSchemaType.Password, + validation: { + minLength: 6, + }, + placeholder: "New Password", + title: "New Password", + required: true, + }, + { + field: { + confirmPassword: true, + } as any, + validation: { + minLength: 6, + toMatchField: "password", + }, + showEvenIfPermissionDoesNotExist: true, + fieldType: FormFieldSchemaType.Password, + placeholder: "Confirm Password", + title: "Confirm Password", + overrideFieldKey: "confirmPassword", + required: true, + }, + ]} + createOrUpdateApiUrl={apiUrl} + formType={FormType.Create} + submitButtonText={"Reset Password"} + onSuccess={() => { + setIsSuccess(true); + }} + /> + </div> + )} + <div className="mt-10 text-center"> + <p className="text-muted mb-0 text-gray-500"> + Know your password?{" "} + <Link + to={ + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/login` + : "/login", + ) + } + className="text-indigo-500 hover:text-indigo-900 cursor-pointer" + > + Log in. + </Link> + </p> + </div> + </div> + </div> + ); }; export default ResetPassword; diff --git a/StatusPage/src/Pages/Accounts/SSO.tsx b/StatusPage/src/Pages/Accounts/SSO.tsx index 161b89fd80..a7080a854f 100644 --- a/StatusPage/src/Pages/Accounts/SSO.tsx +++ b/StatusPage/src/Pages/Accounts/SSO.tsx @@ -1,136 +1,131 @@ import { - STATUS_PAGE_API_URL, - STATUS_PAGE_SSO_API_URL, -} from '../../Utils/Config'; -import StatusPageUtil from '../../Utils/StatusPage'; -import UserUtil from '../../Utils/User'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import ObjectID from 'Common/Types/ObjectID'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import ModelList from 'CommonUI/src/Components/ModelList/ModelList'; -import { FILE_URL } from 'CommonUI/src/Config'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPageSSO from 'Model/Models/StatusPageSso'; -import React, { FunctionComponent, useState } from 'react'; + STATUS_PAGE_API_URL, + STATUS_PAGE_SSO_API_URL, +} from "../../Utils/Config"; +import StatusPageUtil from "../../Utils/StatusPage"; +import UserUtil from "../../Utils/User"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import ObjectID from "Common/Types/ObjectID"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import ModelList from "CommonUI/src/Components/ModelList/ModelList"; +import { FILE_URL } from "CommonUI/src/Config"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPageSSO from "Model/Models/StatusPageSso"; +import React, { FunctionComponent, useState } from "react"; export interface ComponentProps { - statusPageName: string; - logoFileId: ObjectID; + statusPageName: string; + logoFileId: ObjectID; } const LoginPage: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ) => { - const [isLoading, setIsLoading] = useState<boolean>(false); + const [isLoading, setIsLoading] = useState<boolean>(false); - if (!StatusPageUtil.getStatusPageId()) { - return <></>; - } + if (!StatusPageUtil.getStatusPageId()) { + return <></>; + } - if (!StatusPageUtil.isPrivateStatusPage()) { - Navigation.navigate( - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` - : '/' - ) - ); - } - - if ( - StatusPageUtil.getStatusPageId() && - UserUtil.isLoggedIn(StatusPageUtil.getStatusPageId()!) - ) { - if (Navigation.getQueryStringByName('redirectUrl')) { - Navigation.navigate( - new Route(Navigation.getQueryStringByName('redirectUrl')!) - ); - } else { - Navigation.navigate( - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` - : '/' - ) - ); - } - } - - if (Navigation.getQueryStringByName('redirectUrl')) { - // save this to local storage, so in the overview page. We can redirect to this page. - LocalStorage.setItem( - 'redirectUrl', - Navigation.getQueryStringByName('redirectUrl') - ); - } - - if (isLoading) { - return <PageLoader isVisible={true} />; - } - - return ( - <div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8"> - <div className="sm:mx-auto sm:w-full sm:max-w-md"> - {props.logoFileId && props.logoFileId.toString() ? ( - <img - style={{ height: '70px', margin: 'auto' }} - src={`${URL.fromString(FILE_URL.toString()).addRoute( - '/image/' + props.logoFileId.toString() - )}`} - /> - ) : ( - <></> - )} - <h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900"> - Log in with SSO - </h2> - <p className="mt-2 text-center text-sm text-gray-600"> - Please login to view {props.statusPageName || 'Status Page'} - </p> - </div> - - <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> - <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> - <ModelList<StatusPageSSO> - id="sso-list" - overrideFetchApiUrl={URL.fromString( - STATUS_PAGE_API_URL.toString() - ).addRoute( - '/sso/' + - StatusPageUtil.getStatusPageId()?.toString() - )} - modelType={StatusPageSSO} - titleField="name" - descriptionField="description" - select={{ - name: true, - description: true, - _id: true, - }} - noItemsMessage="No SSO Providers Configured or Enabled" - onSelectChange={(list: Array<StatusPageSSO>) => { - if (list && list.length > 0) { - setIsLoading(true); - Navigation.navigate( - URL.fromURL( - STATUS_PAGE_SSO_API_URL - ).addRoute( - new Route( - `/${StatusPageUtil.getStatusPageId()?.toString()}/${ - list[0]?._id - }` - ) - ) - ); - } - }} - /> - </div> - </div> - </div> + if (!StatusPageUtil.isPrivateStatusPage()) { + Navigation.navigate( + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` + : "/", + ), ); + } + + if ( + StatusPageUtil.getStatusPageId() && + UserUtil.isLoggedIn(StatusPageUtil.getStatusPageId()!) + ) { + if (Navigation.getQueryStringByName("redirectUrl")) { + Navigation.navigate( + new Route(Navigation.getQueryStringByName("redirectUrl")!), + ); + } else { + Navigation.navigate( + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}` + : "/", + ), + ); + } + } + + if (Navigation.getQueryStringByName("redirectUrl")) { + // save this to local storage, so in the overview page. We can redirect to this page. + LocalStorage.setItem( + "redirectUrl", + Navigation.getQueryStringByName("redirectUrl"), + ); + } + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + return ( + <div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8"> + <div className="sm:mx-auto sm:w-full sm:max-w-md"> + {props.logoFileId && props.logoFileId.toString() ? ( + <img + style={{ height: "70px", margin: "auto" }} + src={`${URL.fromString(FILE_URL.toString()).addRoute( + "/image/" + props.logoFileId.toString(), + )}`} + /> + ) : ( + <></> + )} + <h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900"> + Log in with SSO + </h2> + <p className="mt-2 text-center text-sm text-gray-600"> + Please login to view {props.statusPageName || "Status Page"} + </p> + </div> + + <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> + <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> + <ModelList<StatusPageSSO> + id="sso-list" + overrideFetchApiUrl={URL.fromString( + STATUS_PAGE_API_URL.toString(), + ).addRoute("/sso/" + StatusPageUtil.getStatusPageId()?.toString())} + modelType={StatusPageSSO} + titleField="name" + descriptionField="description" + select={{ + name: true, + description: true, + _id: true, + }} + noItemsMessage="No SSO Providers Configured or Enabled" + onSelectChange={(list: Array<StatusPageSSO>) => { + if (list && list.length > 0) { + setIsLoading(true); + Navigation.navigate( + URL.fromURL(STATUS_PAGE_SSO_API_URL).addRoute( + new Route( + `/${StatusPageUtil.getStatusPageId()?.toString()}/${ + list[0]?._id + }`, + ), + ), + ); + } + }} + /> + </div> + </div> + </div> + ); }; export default LoginPage; diff --git a/StatusPage/src/Pages/Announcement/Detail.tsx b/StatusPage/src/Pages/Announcement/Detail.tsx index 7a6aaedd87..6d75b19bcd 100644 --- a/StatusPage/src/Pages/Announcement/Detail.tsx +++ b/StatusPage/src/Pages/Announcement/Detail.tsx @@ -1,226 +1,219 @@ -import Page from '../../Components/Page/Page'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import PageComponentProps from '../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { Blue500 } from 'Common/Types/BrandColors'; -import OneUptimeDate from 'Common/Types/Date'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; +import Page from "../../Components/Page/Page"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import PageComponentProps from "../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { Blue500 } from "Common/Types/BrandColors"; +import OneUptimeDate from "Common/Types/Date"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; import EventItem, { - ComponentProps as EventItemComponentProps, -} from 'CommonUI/src/Components/EventItem/EventItem'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import StatusPageAnnouncement from 'Model/Models/StatusPageAnnouncement'; + ComponentProps as EventItemComponentProps, +} from "CommonUI/src/Components/EventItem/EventItem"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import StatusPageAnnouncement from "Model/Models/StatusPageAnnouncement"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; type GetAnnouncementEventItemFunctionProps = { - announcement: StatusPageAnnouncement; - isPreviewPage: boolean; - isSummary: boolean; + announcement: StatusPageAnnouncement; + isPreviewPage: boolean; + isSummary: boolean; }; type GetAnnouncementEventItemFunction = ( - data: GetAnnouncementEventItemFunctionProps + data: GetAnnouncementEventItemFunctionProps, ) => EventItemComponentProps; export const getAnnouncementEventItem: GetAnnouncementEventItemFunction = ( - data: GetAnnouncementEventItemFunctionProps + data: GetAnnouncementEventItemFunctionProps, ): EventItemComponentProps => { - const { announcement, isPreviewPage, isSummary } = data; + const { announcement, isPreviewPage, isSummary } = data; - return { - eventTitle: announcement.title || '', - eventDescription: announcement.description, - eventTimeline: [], - eventType: 'Announcement', - eventViewRoute: !isSummary - ? undefined - : RouteUtil.populateRouteParams( - isPreviewPage - ? (RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_DETAIL] as Route) - : (RouteMap[PageMap.ANNOUNCEMENT_DETAIL] as Route), - announcement.id! - ), - isDetailItem: !isSummary, - eventTypeColor: Blue500, - eventSecondDescription: announcement.showAnnouncementAt! - ? 'Announced at ' + - OneUptimeDate.getDateAsLocalFormattedString( - announcement.showAnnouncementAt! - ) - : '', - }; + return { + eventTitle: announcement.title || "", + eventDescription: announcement.description, + eventTimeline: [], + eventType: "Announcement", + eventViewRoute: !isSummary + ? undefined + : RouteUtil.populateRouteParams( + isPreviewPage + ? (RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_DETAIL] as Route) + : (RouteMap[PageMap.ANNOUNCEMENT_DETAIL] as Route), + announcement.id!, + ), + isDetailItem: !isSummary, + eventTypeColor: Blue500, + eventSecondDescription: announcement.showAnnouncementAt! + ? "Announced at " + + OneUptimeDate.getDateAsLocalFormattedString( + announcement.showAnnouncementAt!, + ) + : "", + }; }; const Overview: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - const [announcement, setAnnouncement] = - useState<StatusPageAnnouncement | null>(null); - const [parsedData, setParsedData] = - useState<EventItemComponentProps | null>(null); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string | null>(null); + const [announcement, setAnnouncement] = + useState<StatusPageAnnouncement | null>(null); + const [parsedData, setParsedData] = useState<EventItemComponentProps | null>( + null, + ); - StatusPageUtil.checkIfUserHasLoggedIn(); + StatusPageUtil.checkIfUserHasLoggedIn(); - useAsyncEffect(async () => { - try { - if (!StatusPageUtil.getStatusPageId()) { - return; - } + useAsyncEffect(async () => { + try { + if (!StatusPageUtil.getStatusPageId()) { + return; + } - setIsLoading(true); + setIsLoading(true); - const id: ObjectID = LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; - if (!id) { - throw new BadDataException('Status Page ID is required'); - } + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; + if (!id) { + throw new BadDataException("Status Page ID is required"); + } - const announcementId: string | undefined = - Navigation.getLastParamAsObjectID().toString(); + const announcementId: string | undefined = + Navigation.getLastParamAsObjectID().toString(); - const response: HTTPResponse<JSONObject> = - await API.post<JSONObject>( - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/announcements/${id.toString()}/${announcementId}` - ), - {}, - API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!) - ); + const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/announcements/${id.toString()}/${announcementId}`, + ), + {}, + API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!), + ); - if (!response.isSuccess()) { - throw response; - } - const data: JSONObject = response.data; + if (!response.isSuccess()) { + throw response; + } + const data: JSONObject = response.data; - const rawAnnouncements: JSONArray = - (data['announcements'] as JSONArray) || []; + const rawAnnouncements: JSONArray = + (data["announcements"] as JSONArray) || []; - const announcement: StatusPageAnnouncement = - BaseModel.fromJSONObject( - (rawAnnouncements[0] as JSONObject) || {}, - StatusPageAnnouncement - ); + const announcement: StatusPageAnnouncement = BaseModel.fromJSONObject( + (rawAnnouncements[0] as JSONObject) || {}, + StatusPageAnnouncement, + ); - // save data. set() + // save data. set() - setAnnouncement(announcement); + setAnnouncement(announcement); - setIsLoading(false); - props.onLoadComplete(); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - await StatusPageUtil.checkIfTheUserIsAuthenticated(err); - } + setIsLoading(false); + props.onLoadComplete(); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + await StatusPageUtil.checkIfTheUserIsAuthenticated(err); + } - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }, []); - - useEffect(() => { - if (isLoading) { - // parse data; - setParsedData(null); - return; - } - - if (!announcement) { - return; - } - - setParsedData( - getAnnouncementEventItem({ - announcement, - isPreviewPage: Boolean(StatusPageUtil.isPreviewPage()), - isSummary: false, - }) - ); - }, [isLoading]); + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }, []); + useEffect(() => { if (isLoading) { - return <PageLoader isVisible={true} />; + // parse data; + setParsedData(null); + return; } - if (error) { - return <ErrorMessage error={error} />; + if (!announcement) { + return; } - if (!parsedData) { - return <PageLoader isVisible={true} />; - } - - return ( - <Page - title="Announcement" - breadcrumbLinks={[ - { - title: 'Overview', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) - : (RouteMap[PageMap.OVERVIEW] as Route) - ), - }, - { - title: 'Announcements', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[ - PageMap.PREVIEW_ANNOUNCEMENT_LIST - ] as Route) - : (RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route) - ), - }, - { - title: 'Announcement', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[ - PageMap.PREVIEW_ANNOUNCEMENT_DETAIL - ] as Route) - : (RouteMap[PageMap.ANNOUNCEMENT_DETAIL] as Route), - Navigation.getLastParamAsObjectID() - ), - }, - ]} - > - {announcement ? <EventItem {...parsedData} /> : <></>} - {!announcement ? ( - <EmptyState - id="announcement-empty-state" - title={'No Announcement'} - description={'Announcement not found on this status page.'} - icon={IconProp.Announcement} - /> - ) : ( - <></> - )} - </Page> + setParsedData( + getAnnouncementEventItem({ + announcement, + isPreviewPage: Boolean(StatusPageUtil.isPreviewPage()), + isSummary: false, + }), ); + }, [isLoading]); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + if (!parsedData) { + return <PageLoader isVisible={true} />; + } + + return ( + <Page + title="Announcement" + breadcrumbLinks={[ + { + title: "Overview", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + ), + }, + { + title: "Announcements", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_LIST] as Route) + : (RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route), + ), + }, + { + title: "Announcement", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_DETAIL] as Route) + : (RouteMap[PageMap.ANNOUNCEMENT_DETAIL] as Route), + Navigation.getLastParamAsObjectID(), + ), + }, + ]} + > + {announcement ? <EventItem {...parsedData} /> : <></>} + {!announcement ? ( + <EmptyState + id="announcement-empty-state" + title={"No Announcement"} + description={"Announcement not found on this status page."} + icon={IconProp.Announcement} + /> + ) : ( + <></> + )} + </Page> + ); }; export default Overview; diff --git a/StatusPage/src/Pages/Announcement/List.tsx b/StatusPage/src/Pages/Announcement/List.tsx index fc97e42c14..4a420f8956 100644 --- a/StatusPage/src/Pages/Announcement/List.tsx +++ b/StatusPage/src/Pages/Announcement/List.tsx @@ -1,247 +1,236 @@ -import Page from '../../Components/Page/Page'; -import Section from '../../Components/Section/Section'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import PageComponentProps from '../PageComponentProps'; -import { getAnnouncementEventItem } from './Detail'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { ComponentProps as EventHistoryDayListComponentProps } from 'CommonUI/src/Components/EventHistoryList/EventHistoryDayList'; +import Page from "../../Components/Page/Page"; +import Section from "../../Components/Section/Section"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import PageComponentProps from "../PageComponentProps"; +import { getAnnouncementEventItem } from "./Detail"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { ComponentProps as EventHistoryDayListComponentProps } from "CommonUI/src/Components/EventHistoryList/EventHistoryDayList"; import EventHistoryList, { - ComponentProps as EventHistoryListComponentProps, -} from 'CommonUI/src/Components/EventHistoryList/EventHistoryList'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import StatusPageAnnouncement from 'Model/Models/StatusPageAnnouncement'; + ComponentProps as EventHistoryListComponentProps, +} from "CommonUI/src/Components/EventHistoryList/EventHistoryList"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import StatusPageAnnouncement from "Model/Models/StatusPageAnnouncement"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; const Overview: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - const [announcements, setAnnouncements] = useState< - Array<StatusPageAnnouncement> - >([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string | null>(null); + const [announcements, setAnnouncements] = useState< + Array<StatusPageAnnouncement> + >([]); - const [activeAnnounementsParsedData, setActiveAnnouncementsParsedData] = - useState<EventHistoryListComponentProps | null>(null); - const [pastAnnouncementsParsedData, setPastAnnouncementsParsedData] = - useState<EventHistoryListComponentProps | null>(null); + const [activeAnnounementsParsedData, setActiveAnnouncementsParsedData] = + useState<EventHistoryListComponentProps | null>(null); + const [pastAnnouncementsParsedData, setPastAnnouncementsParsedData] = + useState<EventHistoryListComponentProps | null>(null); - StatusPageUtil.checkIfUserHasLoggedIn(); + StatusPageUtil.checkIfUserHasLoggedIn(); - useAsyncEffect(async () => { - try { - if (!StatusPageUtil.getStatusPageId()) { - return; - } - setIsLoading(true); + useAsyncEffect(async () => { + try { + if (!StatusPageUtil.getStatusPageId()) { + return; + } + setIsLoading(true); - const id: ObjectID = LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; - if (!id) { - throw new BadDataException('Status Page ID is required'); - } - const response: HTTPResponse<JSONObject> = - await API.post<JSONObject>( - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/announcements/${id.toString()}` - ), - {}, - API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!) - ); + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; + if (!id) { + throw new BadDataException("Status Page ID is required"); + } + const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/announcements/${id.toString()}`, + ), + {}, + API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!), + ); - if (!response.isSuccess()) { - throw response; - } - const data: JSONObject = response.data; + if (!response.isSuccess()) { + throw response; + } + const data: JSONObject = response.data; - const announcements: Array<StatusPageAnnouncement> = - BaseModel.fromJSONArray( - (data['announcements'] as JSONArray) || [], - StatusPageAnnouncement - ); + const announcements: Array<StatusPageAnnouncement> = + BaseModel.fromJSONArray( + (data["announcements"] as JSONArray) || [], + StatusPageAnnouncement, + ); - // save data. set() + // save data. set() - setAnnouncements(announcements); + setAnnouncements(announcements); - setIsLoading(false); - props.onLoadComplete(); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - await StatusPageUtil.checkIfTheUserIsAuthenticated(err); - } - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }, []); + setIsLoading(false); + props.onLoadComplete(); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + await StatusPageUtil.checkIfTheUserIsAuthenticated(err); + } + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }, []); - type GetAnnouncementsParsedDataFunction = ( - announcements: Array<StatusPageAnnouncement> - ) => EventHistoryListComponentProps; + type GetAnnouncementsParsedDataFunction = ( + announcements: Array<StatusPageAnnouncement>, + ) => EventHistoryListComponentProps; - const getAnouncementsParsedData: GetAnnouncementsParsedDataFunction = ( - announcements: Array<StatusPageAnnouncement> - ): EventHistoryListComponentProps => { - const eventHistoryListComponentProps: EventHistoryListComponentProps = { - items: [], - }; - - const days: Dictionary<EventHistoryDayListComponentProps> = {}; - - for (const announcement of announcements) { - const dayString: string = OneUptimeDate.getDateString( - announcement.showAnnouncementAt! - ); - - if (!days[dayString]) { - days[dayString] = { - date: announcement.showAnnouncementAt!, - items: [], - }; - } - - days[dayString]?.items.push( - getAnnouncementEventItem({ - announcement, - isPreviewPage: Boolean(StatusPageUtil.isPreviewPage()), - isSummary: true, - }) - ); - } - - for (const key in days) { - eventHistoryListComponentProps.items.push( - days[key] as EventHistoryDayListComponentProps - ); - } - return eventHistoryListComponentProps; + const getAnouncementsParsedData: GetAnnouncementsParsedDataFunction = ( + announcements: Array<StatusPageAnnouncement>, + ): EventHistoryListComponentProps => { + const eventHistoryListComponentProps: EventHistoryListComponentProps = { + items: [], }; - useEffect(() => { - if (isLoading) { - // parse data; - setActiveAnnouncementsParsedData(null); - setPastAnnouncementsParsedData(null); - return; - } + const days: Dictionary<EventHistoryDayListComponentProps> = {}; - const activeAnnouncement: Array<StatusPageAnnouncement> = - announcements.filter((announcement: StatusPageAnnouncement) => { - return OneUptimeDate.isBefore( - OneUptimeDate.getCurrentDate(), - announcement.endAnnouncementAt! - ); - }); + for (const announcement of announcements) { + const dayString: string = OneUptimeDate.getDateString( + announcement.showAnnouncementAt!, + ); - const pastAnnouncement: Array<StatusPageAnnouncement> = - announcements.filter((announcement: StatusPageAnnouncement) => { - return OneUptimeDate.isAfter( - OneUptimeDate.getCurrentDate(), - announcement.endAnnouncementAt! - ); - }); + if (!days[dayString]) { + days[dayString] = { + date: announcement.showAnnouncementAt!, + items: [], + }; + } - setActiveAnnouncementsParsedData( - getAnouncementsParsedData(activeAnnouncement) - ); - setPastAnnouncementsParsedData( - getAnouncementsParsedData(pastAnnouncement) - ); - }, [isLoading]); + days[dayString]?.items.push( + getAnnouncementEventItem({ + announcement, + isPreviewPage: Boolean(StatusPageUtil.isPreviewPage()), + isSummary: true, + }), + ); + } + for (const key in days) { + eventHistoryListComponentProps.items.push( + days[key] as EventHistoryDayListComponentProps, + ); + } + return eventHistoryListComponentProps; + }; + + useEffect(() => { if (isLoading) { - return <PageLoader isVisible={true} />; + // parse data; + setActiveAnnouncementsParsedData(null); + setPastAnnouncementsParsedData(null); + return; } - if (error) { - return <ErrorMessage error={error} />; - } + const activeAnnouncement: Array<StatusPageAnnouncement> = + announcements.filter((announcement: StatusPageAnnouncement) => { + return OneUptimeDate.isBefore( + OneUptimeDate.getCurrentDate(), + announcement.endAnnouncementAt!, + ); + }); - return ( - <Page - title="Announcements" - breadcrumbLinks={[ - { - title: 'Overview', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) - : (RouteMap[PageMap.OVERVIEW] as Route) - ), - }, - { - title: 'Announcements', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[ - PageMap.PREVIEW_ANNOUNCEMENT_LIST - ] as Route) - : (RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route) - ), - }, - ]} - > - {activeAnnounementsParsedData?.items && - activeAnnounementsParsedData?.items.length > 0 ? ( - <div> - <Section title="Active Announcements" /> + const pastAnnouncement: Array<StatusPageAnnouncement> = + announcements.filter((announcement: StatusPageAnnouncement) => { + return OneUptimeDate.isAfter( + OneUptimeDate.getCurrentDate(), + announcement.endAnnouncementAt!, + ); + }); - <EventHistoryList - items={activeAnnounementsParsedData?.items || []} - /> - </div> - ) : ( - <></> - )} - - {pastAnnouncementsParsedData?.items && - pastAnnouncementsParsedData?.items.length > 0 ? ( - <div> - <Section title="Past Announcements" /> - <EventHistoryList - items={pastAnnouncementsParsedData?.items || []} - /> - </div> - ) : ( - <></> - )} - - {announcements.length === 0 ? ( - <EmptyState - id="announcements-empty-state" - title={'No Announcements'} - description={'No announcements posted so far on this page.'} - icon={IconProp.Announcement} - /> - ) : ( - <></> - )} - </Page> + setActiveAnnouncementsParsedData( + getAnouncementsParsedData(activeAnnouncement), ); + setPastAnnouncementsParsedData(getAnouncementsParsedData(pastAnnouncement)); + }, [isLoading]); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <Page + title="Announcements" + breadcrumbLinks={[ + { + title: "Overview", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + ), + }, + { + title: "Announcements", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_ANNOUNCEMENT_LIST] as Route) + : (RouteMap[PageMap.ANNOUNCEMENT_LIST] as Route), + ), + }, + ]} + > + {activeAnnounementsParsedData?.items && + activeAnnounementsParsedData?.items.length > 0 ? ( + <div> + <Section title="Active Announcements" /> + + <EventHistoryList items={activeAnnounementsParsedData?.items || []} /> + </div> + ) : ( + <></> + )} + + {pastAnnouncementsParsedData?.items && + pastAnnouncementsParsedData?.items.length > 0 ? ( + <div> + <Section title="Past Announcements" /> + <EventHistoryList items={pastAnnouncementsParsedData?.items || []} /> + </div> + ) : ( + <></> + )} + + {announcements.length === 0 ? ( + <EmptyState + id="announcements-empty-state" + title={"No Announcements"} + description={"No announcements posted so far on this page."} + icon={IconProp.Announcement} + /> + ) : ( + <></> + )} + </Page> + ); }; export default Overview; diff --git a/StatusPage/src/Pages/Incidents/Detail.tsx b/StatusPage/src/Pages/Incidents/Detail.tsx index c8bece70e1..e83c25b03a 100644 --- a/StatusPage/src/Pages/Incidents/Detail.tsx +++ b/StatusPage/src/Pages/Incidents/Detail.tsx @@ -1,422 +1,407 @@ -import Page from '../../Components/Page/Page'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import PageComponentProps from '../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { Gray500, Green, Red } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; +import Page from "../../Components/Page/Page"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import PageComponentProps from "../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { Gray500, Green, Red } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; import EventItem, { - ComponentProps as EventItemComponentProps, - TimelineItem, - TimelineItemType, -} from 'CommonUI/src/Components/EventItem/EventItem'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Incident from 'Model/Models/Incident'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; -import StatusPageResource from 'Model/Models/StatusPageResource'; + ComponentProps as EventItemComponentProps, + TimelineItem, + TimelineItemType, +} from "CommonUI/src/Components/EventItem/EventItem"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Incident from "Model/Models/Incident"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; +import StatusPageResource from "Model/Models/StatusPageResource"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; type GetIncidentEventItemFunctionProps = { - incident: Incident; - incidentPublicNotes: Array<IncidentPublicNote>; - incidentStateTimelines: Array<IncidentStateTimeline>; - statusPageResources: Array<StatusPageResource>; - monitorsInGroup: Dictionary<Array<ObjectID>>; - isPreviewPage: boolean; - isSummary: boolean; + incident: Incident; + incidentPublicNotes: Array<IncidentPublicNote>; + incidentStateTimelines: Array<IncidentStateTimeline>; + statusPageResources: Array<StatusPageResource>; + monitorsInGroup: Dictionary<Array<ObjectID>>; + isPreviewPage: boolean; + isSummary: boolean; }; type GetIncidentEventItemFunction = ( - props: GetIncidentEventItemFunctionProps + props: GetIncidentEventItemFunctionProps, ) => EventItemComponentProps; export const getIncidentEventItem: GetIncidentEventItemFunction = ( - props: GetIncidentEventItemFunctionProps + props: GetIncidentEventItemFunctionProps, ): EventItemComponentProps => { - const { + const { + incident, + incidentPublicNotes, + incidentStateTimelines, + statusPageResources, + monitorsInGroup, + isPreviewPage, + isSummary, + } = props; + + const timeline: Array<TimelineItem> = []; + + let currentStateStatus: string = ""; + let currentStatusColor: Color = Green; + + if (isSummary) { + // If this is summary then reverse the order so we show the latest first + incidentPublicNotes.sort((a: IncidentPublicNote, b: IncidentPublicNote) => { + return OneUptimeDate.isAfter(a.postedAt!, b.postedAt!) === false ? 1 : -1; + }); + + incidentStateTimelines.sort( + (a: IncidentStateTimeline, b: IncidentStateTimeline) => { + return OneUptimeDate.isAfter(a.createdAt!, b.createdAt!) === false + ? 1 + : -1; + }, + ); + } + + for (const incidentPublicNote of incidentPublicNotes) { + if ( + incidentPublicNote.incidentId?.toString() === incident.id?.toString() && + incidentPublicNote?.note + ) { + timeline.push({ + note: incidentPublicNote?.note, + date: incidentPublicNote?.postedAt as Date, + type: TimelineItemType.Note, + icon: IconProp.Chat, + iconColor: Gray500, + }); + + // If this incident is a sumamry then don't include all the notes . + if (isSummary) { + break; + } + } + } + + for (const incidentStateTimeline of incidentStateTimelines) { + if ( + incidentStateTimeline.incidentId?.toString() === + incident.id?.toString() && + incidentStateTimeline.incidentState + ) { + timeline.push({ + state: incidentStateTimeline.incidentState, + date: incidentStateTimeline?.createdAt as Date, + type: TimelineItemType.StateChange, + icon: incidentStateTimeline.incidentState.isCreatedState + ? IconProp.Alert + : incidentStateTimeline.incidentState.isAcknowledgedState + ? IconProp.TransparentCube + : incidentStateTimeline.incidentState.isResolvedState + ? IconProp.CheckCircle + : IconProp.ArrowCircleRight, + iconColor: incidentStateTimeline.incidentState.color || Gray500, + }); + + if (!currentStateStatus) { + currentStateStatus = incidentStateTimeline.incidentState?.name || ""; + currentStatusColor = + incidentStateTimeline.incidentState?.color || Green; + } + + // If this incident is a sumamry then don't include all the notes . + if (isSummary) { + break; + } + } + } + + timeline.sort((a: TimelineItem, b: TimelineItem) => { + return OneUptimeDate.isAfter(a.date, b.date) === true ? 1 : -1; + }); + + const monitorIdsInThisIncident: Array<string | undefined> = + incident.monitors?.map((monitor: Monitor) => { + return monitor._id; + }) || []; + + let namesOfResources: Array<StatusPageResource> = statusPageResources.filter( + (resource: StatusPageResource) => { + return monitorIdsInThisIncident.includes(resource.monitorId?.toString()); + }, + ); + + // add names of the groups as well. + namesOfResources = namesOfResources.concat( + statusPageResources.filter((resource: StatusPageResource) => { + if (!resource.monitorGroupId) { + return false; + } + + const monitorGroupId: string = resource.monitorGroupId.toString(); + + const monitorIdsInThisGroup: Array<ObjectID> = + monitorsInGroup[monitorGroupId]! || []; + + for (const monitorId of monitorIdsInThisGroup) { + if ( + monitorIdsInThisIncident.find((id: string | undefined) => { + return id?.toString() === monitorId.toString(); + }) + ) { + return true; + } + } + + return false; + }), + ); + + const data: EventItemComponentProps = { + eventTitle: incident.title || "", + eventDescription: incident.description, + eventResourcesAffected: namesOfResources.map((i: StatusPageResource) => { + return i.displayName || ""; + }), + eventTimeline: timeline, + eventType: "Incident", + eventViewRoute: !isSummary + ? undefined + : RouteUtil.populateRouteParams( + isPreviewPage + ? (RouteMap[PageMap.PREVIEW_INCIDENT_DETAIL] as Route) + : (RouteMap[PageMap.INCIDENT_DETAIL] as Route), + incident.id!, + ), + isDetailItem: !isSummary, + currentStatus: currentStateStatus, + currentStatusColor: currentStatusColor, + anotherStatusColor: incident.incidentSeverity?.color || undefined, + anotherStatus: incident.incidentSeverity?.name, + eventSecondDescription: incident.createdAt + ? "Created at " + + OneUptimeDate.getDateAsLocalFormattedString(incident.createdAt!) + : "", + eventTypeColor: Red, + labels: + incident.labels?.map((label: Label) => { + return { + name: label.name!, + color: label.color!, + }; + }) || [], + }; + + return data; +}; + +const Detail: FunctionComponent<PageComponentProps> = ( + props: PageComponentProps, +): ReactElement => { + StatusPageUtil.checkIfUserHasLoggedIn(); + + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string | null>(null); + const [statusPageResources, setStatusPageResources] = useState< + Array<StatusPageResource> + >([]); + const [incidentPublicNotes, setIncidentPublicNotes] = useState< + Array<IncidentPublicNote> + >([]); + const [incident, setIncident] = useState<Incident | null>(null); + const [incidentStateTimelines, setIncidentStateTimelines] = useState< + Array<IncidentStateTimeline> + >([]); + const [parsedData, setParsedData] = useState<EventItemComponentProps | null>( + null, + ); + + const [monitorsInGroup, setMonitorsInGroup] = useState< + Dictionary<Array<ObjectID>> + >({}); + + useAsyncEffect(async () => { + try { + if (!StatusPageUtil.getStatusPageId()) { + return; + } + setIsLoading(true); + + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; + + const incidentId: string | undefined = + Navigation.getLastParamAsObjectID().toString(); + + if (!id) { + throw new BadDataException("Status Page ID is required"); + } + const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/incidents/${id.toString()}/${incidentId?.toString()}`, + ), + {}, + API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!), + ); + + if (!response.isSuccess()) { + throw response; + } + const data: JSONObject = response.data; + + const incidentPublicNotes: Array<IncidentPublicNote> = + BaseModel.fromJSONArray( + (data["incidentPublicNotes"] as JSONArray) || [], + IncidentPublicNote, + ); + + const rawIncidents: JSONArray = (data["incidents"] as JSONArray) || []; + const incident: Incident = BaseModel.fromJSONObject( + (rawIncidents[0] as JSONObject) || {}, + Incident, + ); + const statusPageResources: Array<StatusPageResource> = + BaseModel.fromJSONArray( + (data["statusPageResources"] as JSONArray) || [], + StatusPageResource, + ); + + const incidentStateTimelines: Array<IncidentStateTimeline> = + BaseModel.fromJSONArray( + (data["incidentStateTimelines"] as JSONArray) || [], + IncidentStateTimeline, + ); + + const monitorsInGroup: Dictionary<Array<ObjectID>> = + JSONFunctions.deserialize( + (data["monitorsInGroup"] as JSONObject) || {}, + ) as Dictionary<Array<ObjectID>>; + + setMonitorsInGroup(monitorsInGroup); + + // save data. set() + setIncidentPublicNotes(incidentPublicNotes); + setIncident(incident); + setStatusPageResources(statusPageResources); + setIncidentStateTimelines(incidentStateTimelines); + + setIsLoading(false); + props.onLoadComplete(); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + await StatusPageUtil.checkIfTheUserIsAuthenticated(err); + } + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }, []); + + useEffect(() => { + if (isLoading) { + // parse data; + setParsedData(null); + return; + } + + if (!incident) { + return; + } + + setParsedData( + getIncidentEventItem({ incident, incidentPublicNotes, incidentStateTimelines, statusPageResources, monitorsInGroup, - isPreviewPage, - isSummary, - } = props; - - const timeline: Array<TimelineItem> = []; - - let currentStateStatus: string = ''; - let currentStatusColor: Color = Green; - - if (isSummary) { - // If this is summary then reverse the order so we show the latest first - incidentPublicNotes.sort( - (a: IncidentPublicNote, b: IncidentPublicNote) => { - return OneUptimeDate.isAfter(a.postedAt!, b.postedAt!) === false - ? 1 - : -1; - } - ); - - incidentStateTimelines.sort( - (a: IncidentStateTimeline, b: IncidentStateTimeline) => { - return OneUptimeDate.isAfter(a.createdAt!, b.createdAt!) === - false - ? 1 - : -1; - } - ); - } - - for (const incidentPublicNote of incidentPublicNotes) { - if ( - incidentPublicNote.incidentId?.toString() === - incident.id?.toString() && - incidentPublicNote?.note - ) { - timeline.push({ - note: incidentPublicNote?.note, - date: incidentPublicNote?.postedAt as Date, - type: TimelineItemType.Note, - icon: IconProp.Chat, - iconColor: Gray500, - }); - - // If this incident is a sumamry then don't include all the notes . - if (isSummary) { - break; - } - } - } - - for (const incidentStateTimeline of incidentStateTimelines) { - if ( - incidentStateTimeline.incidentId?.toString() === - incident.id?.toString() && - incidentStateTimeline.incidentState - ) { - timeline.push({ - state: incidentStateTimeline.incidentState, - date: incidentStateTimeline?.createdAt as Date, - type: TimelineItemType.StateChange, - icon: incidentStateTimeline.incidentState.isCreatedState - ? IconProp.Alert - : incidentStateTimeline.incidentState.isAcknowledgedState - ? IconProp.TransparentCube - : incidentStateTimeline.incidentState.isResolvedState - ? IconProp.CheckCircle - : IconProp.ArrowCircleRight, - iconColor: incidentStateTimeline.incidentState.color || Gray500, - }); - - if (!currentStateStatus) { - currentStateStatus = - incidentStateTimeline.incidentState?.name || ''; - currentStatusColor = - incidentStateTimeline.incidentState?.color || Green; - } - - // If this incident is a sumamry then don't include all the notes . - if (isSummary) { - break; - } - } - } - - timeline.sort((a: TimelineItem, b: TimelineItem) => { - return OneUptimeDate.isAfter(a.date, b.date) === true ? 1 : -1; - }); - - const monitorIdsInThisIncident: Array<string | undefined> = - incident.monitors?.map((monitor: Monitor) => { - return monitor._id; - }) || []; - - let namesOfResources: Array<StatusPageResource> = - statusPageResources.filter((resource: StatusPageResource) => { - return monitorIdsInThisIncident.includes( - resource.monitorId?.toString() - ); - }); - - // add names of the groups as well. - namesOfResources = namesOfResources.concat( - statusPageResources.filter((resource: StatusPageResource) => { - if (!resource.monitorGroupId) { - return false; - } - - const monitorGroupId: string = resource.monitorGroupId.toString(); - - const monitorIdsInThisGroup: Array<ObjectID> = - monitorsInGroup[monitorGroupId]! || []; - - for (const monitorId of monitorIdsInThisGroup) { - if ( - monitorIdsInThisIncident.find((id: string | undefined) => { - return id?.toString() === monitorId.toString(); - }) - ) { - return true; - } - } - - return false; - }) + isPreviewPage: StatusPageUtil.isPreviewPage(), + isSummary: false, + }), ); + }, [isLoading, incident]); - const data: EventItemComponentProps = { - eventTitle: incident.title || '', - eventDescription: incident.description, - eventResourcesAffected: namesOfResources.map( - (i: StatusPageResource) => { - return i.displayName || ''; - } - ), - eventTimeline: timeline, - eventType: 'Incident', - eventViewRoute: !isSummary - ? undefined - : RouteUtil.populateRouteParams( - isPreviewPage - ? (RouteMap[PageMap.PREVIEW_INCIDENT_DETAIL] as Route) - : (RouteMap[PageMap.INCIDENT_DETAIL] as Route), - incident.id! - ), - isDetailItem: !isSummary, - currentStatus: currentStateStatus, - currentStatusColor: currentStatusColor, - anotherStatusColor: incident.incidentSeverity?.color || undefined, - anotherStatus: incident.incidentSeverity?.name, - eventSecondDescription: incident.createdAt - ? 'Created at ' + - OneUptimeDate.getDateAsLocalFormattedString(incident.createdAt!) - : '', - eventTypeColor: Red, - labels: - incident.labels?.map((label: Label) => { - return { - name: label.name!, - color: label.color!, - }; - }) || [], - }; + if (isLoading) { + return <PageLoader isVisible={true} />; + } - return data; -}; + if (error) { + return <ErrorMessage error={error} />; + } -const Detail: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps -): ReactElement => { - StatusPageUtil.checkIfUserHasLoggedIn(); + if (!parsedData) { + return <PageLoader isVisible={true} />; + } - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - const [statusPageResources, setStatusPageResources] = useState< - Array<StatusPageResource> - >([]); - const [incidentPublicNotes, setIncidentPublicNotes] = useState< - Array<IncidentPublicNote> - >([]); - const [incident, setIncident] = useState<Incident | null>(null); - const [incidentStateTimelines, setIncidentStateTimelines] = useState< - Array<IncidentStateTimeline> - >([]); - const [parsedData, setParsedData] = - useState<EventItemComponentProps | null>(null); - - const [monitorsInGroup, setMonitorsInGroup] = useState< - Dictionary<Array<ObjectID>> - >({}); - - useAsyncEffect(async () => { - try { - if (!StatusPageUtil.getStatusPageId()) { - return; - } - setIsLoading(true); - - const id: ObjectID = LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; - - const incidentId: string | undefined = - Navigation.getLastParamAsObjectID().toString(); - - if (!id) { - throw new BadDataException('Status Page ID is required'); - } - const response: HTTPResponse<JSONObject> = - await API.post<JSONObject>( - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/incidents/${id.toString()}/${incidentId?.toString()}` - ), - {}, - API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!) - ); - - if (!response.isSuccess()) { - throw response; - } - const data: JSONObject = response.data; - - const incidentPublicNotes: Array<IncidentPublicNote> = - BaseModel.fromJSONArray( - (data['incidentPublicNotes'] as JSONArray) || [], - IncidentPublicNote - ); - - const rawIncidents: JSONArray = - (data['incidents'] as JSONArray) || []; - const incident: Incident = BaseModel.fromJSONObject( - (rawIncidents[0] as JSONObject) || {}, - Incident - ); - const statusPageResources: Array<StatusPageResource> = - BaseModel.fromJSONArray( - (data['statusPageResources'] as JSONArray) || [], - StatusPageResource - ); - - const incidentStateTimelines: Array<IncidentStateTimeline> = - BaseModel.fromJSONArray( - (data['incidentStateTimelines'] as JSONArray) || [], - IncidentStateTimeline - ); - - const monitorsInGroup: Dictionary<Array<ObjectID>> = - JSONFunctions.deserialize( - (data['monitorsInGroup'] as JSONObject) || {} - ) as Dictionary<Array<ObjectID>>; - - setMonitorsInGroup(monitorsInGroup); - - // save data. set() - setIncidentPublicNotes(incidentPublicNotes); - setIncident(incident); - setStatusPageResources(statusPageResources); - setIncidentStateTimelines(incidentStateTimelines); - - setIsLoading(false); - props.onLoadComplete(); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - await StatusPageUtil.checkIfTheUserIsAuthenticated(err); - } - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }, []); - - useEffect(() => { - if (isLoading) { - // parse data; - setParsedData(null); - return; - } - - if (!incident) { - return; - } - - setParsedData( - getIncidentEventItem({ - incident, - incidentPublicNotes, - incidentStateTimelines, - statusPageResources, - monitorsInGroup, - isPreviewPage: StatusPageUtil.isPreviewPage(), - isSummary: false, - }) - ); - }, [isLoading, incident]); - - if (isLoading) { - return <PageLoader isVisible={true} />; - } - - if (error) { - return <ErrorMessage error={error} />; - } - - if (!parsedData) { - return <PageLoader isVisible={true} />; - } - - return ( - <Page - title="Incident Report" - breadcrumbLinks={[ - { - title: 'Overview', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) - : (RouteMap[PageMap.OVERVIEW] as Route) - ), - }, - { - title: 'Incidents', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route) - : (RouteMap[PageMap.INCIDENT_LIST] as Route) - ), - }, - { - title: 'Incident Report', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[ - PageMap.PREVIEW_INCIDENT_DETAIL - ] as Route) - : (RouteMap[PageMap.INCIDENT_DETAIL] as Route), - Navigation.getLastParamAsObjectID() - ), - }, - ]} - > - {incident ? <EventItem {...parsedData} /> : <></>} - {!incident ? ( - <EmptyState - id="incident-empty-state" - title={'No Incident'} - description={'Incident not found on this status page.'} - icon={IconProp.Alert} - /> - ) : ( - <></> - )} - </Page> - ); + return ( + <Page + title="Incident Report" + breadcrumbLinks={[ + { + title: "Overview", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + ), + }, + { + title: "Incidents", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route) + : (RouteMap[PageMap.INCIDENT_LIST] as Route), + ), + }, + { + title: "Incident Report", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_INCIDENT_DETAIL] as Route) + : (RouteMap[PageMap.INCIDENT_DETAIL] as Route), + Navigation.getLastParamAsObjectID(), + ), + }, + ]} + > + {incident ? <EventItem {...parsedData} /> : <></>} + {!incident ? ( + <EmptyState + id="incident-empty-state" + title={"No Incident"} + description={"Incident not found on this status page."} + icon={IconProp.Alert} + /> + ) : ( + <></> + )} + </Page> + ); }; export default Detail; diff --git a/StatusPage/src/Pages/Incidents/List.tsx b/StatusPage/src/Pages/Incidents/List.tsx index c26becd597..fa4bfb2013 100644 --- a/StatusPage/src/Pages/Incidents/List.tsx +++ b/StatusPage/src/Pages/Incidents/List.tsx @@ -1,309 +1,301 @@ -import Page from '../../Components/Page/Page'; -import Section from '../../Components/Section/Section'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import PageComponentProps from '../PageComponentProps'; -import { getIncidentEventItem } from './Detail'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { ComponentProps as EventHistoryDayListComponentProps } from 'CommonUI/src/Components/EventHistoryList/EventHistoryDayList'; +import Page from "../../Components/Page/Page"; +import Section from "../../Components/Section/Section"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import PageComponentProps from "../PageComponentProps"; +import { getIncidentEventItem } from "./Detail"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { ComponentProps as EventHistoryDayListComponentProps } from "CommonUI/src/Components/EventHistoryList/EventHistoryDayList"; import EventHistoryList, { - ComponentProps as EventHistoryListComponentProps, -} from 'CommonUI/src/Components/EventHistoryList/EventHistoryList'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import Incident from 'Model/Models/Incident'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import StatusPageResource from 'Model/Models/StatusPageResource'; + ComponentProps as EventHistoryListComponentProps, +} from "CommonUI/src/Components/EventHistoryList/EventHistoryList"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import Incident from "Model/Models/Incident"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import StatusPageResource from "Model/Models/StatusPageResource"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; const Overview: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - const [statusPageResources, setStatusPageResources] = useState< - Array<StatusPageResource> - >([]); - const [incidentPublicNotes, setIncidentPublicNotes] = useState< - Array<IncidentPublicNote> - >([]); - const [incidents, setIncidents] = useState<Array<Incident>>([]); - const [incidentStateTimelines, setIncidentStateTimelines] = useState< - Array<IncidentStateTimeline> - >([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string | null>(null); + const [statusPageResources, setStatusPageResources] = useState< + Array<StatusPageResource> + >([]); + const [incidentPublicNotes, setIncidentPublicNotes] = useState< + Array<IncidentPublicNote> + >([]); + const [incidents, setIncidents] = useState<Array<Incident>>([]); + const [incidentStateTimelines, setIncidentStateTimelines] = useState< + Array<IncidentStateTimeline> + >([]); - const [parsedActiveIncidentsData, setParsedActiveIncidentsData] = - useState<EventHistoryListComponentProps | null>(null); + const [parsedActiveIncidentsData, setParsedActiveIncidentsData] = + useState<EventHistoryListComponentProps | null>(null); - const [parsedResolvedIncidentsData, setParsedResolvedIncidentsData] = - useState<EventHistoryListComponentProps | null>(null); + const [parsedResolvedIncidentsData, setParsedResolvedIncidentsData] = + useState<EventHistoryListComponentProps | null>(null); - const [monitorsInGroup, setMonitorsInGroup] = useState< - Dictionary<Array<ObjectID>> - >({}); + const [monitorsInGroup, setMonitorsInGroup] = useState< + Dictionary<Array<ObjectID>> + >({}); - const [incidentStates, setIncidentStates] = useState<Array<IncidentState>>( - [] - ); + const [incidentStates, setIncidentStates] = useState<Array<IncidentState>>( + [], + ); - StatusPageUtil.checkIfUserHasLoggedIn(); + StatusPageUtil.checkIfUserHasLoggedIn(); - useAsyncEffect(async () => { - try { - if (!StatusPageUtil.getStatusPageId()) { - return; - } - setIsLoading(true); + useAsyncEffect(async () => { + try { + if (!StatusPageUtil.getStatusPageId()) { + return; + } + setIsLoading(true); - const id: ObjectID = LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; - if (!id) { - throw new BadDataException('Status Page ID is required'); - } - const response: HTTPResponse<JSONObject> = - await API.post<JSONObject>( - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/incidents/${id.toString()}` - ), - {}, - API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!) - ); + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; + if (!id) { + throw new BadDataException("Status Page ID is required"); + } + const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/incidents/${id.toString()}`, + ), + {}, + API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!), + ); - if (!response.isSuccess()) { - throw response; - } + if (!response.isSuccess()) { + throw response; + } - const data: JSONObject = response.data; + const data: JSONObject = response.data; - const incidentPublicNotes: Array<IncidentPublicNote> = - BaseModel.fromJSONArray( - (data['incidentPublicNotes'] as JSONArray) || [], - IncidentPublicNote - ); - const incidents: Array<Incident> = BaseModel.fromJSONArray( - (data['incidents'] as JSONArray) || [], - Incident - ); - const statusPageResources: Array<StatusPageResource> = - BaseModel.fromJSONArray( - (data['statusPageResources'] as JSONArray) || [], - StatusPageResource - ); - const incidentStateTimelines: Array<IncidentStateTimeline> = - BaseModel.fromJSONArray( - (data['incidentStateTimelines'] as JSONArray) || [], - IncidentStateTimeline - ); + const incidentPublicNotes: Array<IncidentPublicNote> = + BaseModel.fromJSONArray( + (data["incidentPublicNotes"] as JSONArray) || [], + IncidentPublicNote, + ); + const incidents: Array<Incident> = BaseModel.fromJSONArray( + (data["incidents"] as JSONArray) || [], + Incident, + ); + const statusPageResources: Array<StatusPageResource> = + BaseModel.fromJSONArray( + (data["statusPageResources"] as JSONArray) || [], + StatusPageResource, + ); + const incidentStateTimelines: Array<IncidentStateTimeline> = + BaseModel.fromJSONArray( + (data["incidentStateTimelines"] as JSONArray) || [], + IncidentStateTimeline, + ); - const monitorsInGroup: Dictionary<Array<ObjectID>> = - JSONFunctions.deserialize( - (data['monitorsInGroup'] as JSONObject) || {} - ) as Dictionary<Array<ObjectID>>; + const monitorsInGroup: Dictionary<Array<ObjectID>> = + JSONFunctions.deserialize( + (data["monitorsInGroup"] as JSONObject) || {}, + ) as Dictionary<Array<ObjectID>>; - const incidentStates: Array<IncidentState> = - BaseModel.fromJSONArray( - (data['incidentStates'] as JSONArray) || [], - IncidentState - ); + const incidentStates: Array<IncidentState> = BaseModel.fromJSONArray( + (data["incidentStates"] as JSONArray) || [], + IncidentState, + ); - setMonitorsInGroup(monitorsInGroup); - setIncidentStates(incidentStates); + setMonitorsInGroup(monitorsInGroup); + setIncidentStates(incidentStates); - // save data. set() - setIncidentPublicNotes(incidentPublicNotes); - setIncidents(incidents); - setStatusPageResources(statusPageResources); - setIncidentStateTimelines(incidentStateTimelines); + // save data. set() + setIncidentPublicNotes(incidentPublicNotes); + setIncidents(incidents); + setStatusPageResources(statusPageResources); + setIncidentStateTimelines(incidentStateTimelines); - setIsLoading(false); - props.onLoadComplete(); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - await StatusPageUtil.checkIfTheUserIsAuthenticated(err); - } - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }, []); + setIsLoading(false); + props.onLoadComplete(); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + await StatusPageUtil.checkIfTheUserIsAuthenticated(err); + } + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }, []); - type GetEventHistoryListComponentProps = ( - incidents: Array<Incident> - ) => EventHistoryListComponentProps; + type GetEventHistoryListComponentProps = ( + incidents: Array<Incident>, + ) => EventHistoryListComponentProps; - const getEventHistoryListComponentProps: GetEventHistoryListComponentProps = - (incidents: Array<Incident>): EventHistoryListComponentProps => { - const eventHistoryListComponentProps: EventHistoryListComponentProps = - { - items: [], - }; + const getEventHistoryListComponentProps: GetEventHistoryListComponentProps = ( + incidents: Array<Incident>, + ): EventHistoryListComponentProps => { + const eventHistoryListComponentProps: EventHistoryListComponentProps = { + items: [], + }; - const days: Dictionary<EventHistoryDayListComponentProps> = {}; + const days: Dictionary<EventHistoryDayListComponentProps> = {}; - for (const incident of incidents) { - const dayString: string = OneUptimeDate.getDateString( - incident.createdAt! - ); + for (const incident of incidents) { + const dayString: string = OneUptimeDate.getDateString( + incident.createdAt!, + ); - if (!days[dayString]) { - days[dayString] = { - date: incident.createdAt!, - items: [], - }; - } - - days[dayString]?.items.push( - getIncidentEventItem({ - incident, - incidentPublicNotes, - incidentStateTimelines, - statusPageResources, - monitorsInGroup, - isPreviewPage: StatusPageUtil.isPreviewPage(), - isSummary: true, - }) - ); - } - - for (const key in days) { - eventHistoryListComponentProps.items.push( - days[key] as EventHistoryDayListComponentProps - ); - } - - return eventHistoryListComponentProps; + if (!days[dayString]) { + days[dayString] = { + date: incident.createdAt!, + items: [], }; + } - useEffect(() => { - if (isLoading) { - // parse data; - setParsedActiveIncidentsData(null); - setParsedResolvedIncidentsData(null); - return; - } + days[dayString]?.items.push( + getIncidentEventItem({ + incident, + incidentPublicNotes, + incidentStateTimelines, + statusPageResources, + monitorsInGroup, + isPreviewPage: StatusPageUtil.isPreviewPage(), + isSummary: true, + }), + ); + } - const resolvedIncidentStateOrder: number = - incidentStates.find((state: IncidentState) => { - return state.isResolvedState; - })?.order || 0; + for (const key in days) { + eventHistoryListComponentProps.items.push( + days[key] as EventHistoryDayListComponentProps, + ); + } - const activeIncidents: Array<Incident> = incidents.filter( - (incident: Incident) => { - return ( - (incident.currentIncidentState?.order || 0) < - resolvedIncidentStateOrder - ); - } - ); - - const resolvedIncidents: Array<Incident> = incidents.filter( - (incident: Incident) => { - return !( - (incident.currentIncidentState?.order || 0) < - resolvedIncidentStateOrder - ); - } - ); - - setParsedActiveIncidentsData( - getEventHistoryListComponentProps(activeIncidents) - ); - setParsedResolvedIncidentsData( - getEventHistoryListComponentProps(resolvedIncidents) - ); - }, [isLoading]); + return eventHistoryListComponentProps; + }; + useEffect(() => { if (isLoading) { - return <PageLoader isVisible={true} />; + // parse data; + setParsedActiveIncidentsData(null); + setParsedResolvedIncidentsData(null); + return; } - if (error) { - return <ErrorMessage error={error} />; - } + const resolvedIncidentStateOrder: number = + incidentStates.find((state: IncidentState) => { + return state.isResolvedState; + })?.order || 0; - return ( - <Page - title={'Incidents'} - breadcrumbLinks={[ - { - title: 'Overview', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) - : (RouteMap[PageMap.OVERVIEW] as Route) - ), - }, - { - title: 'Incidents', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route) - : (RouteMap[PageMap.INCIDENT_LIST] as Route) - ), - }, - ]} - > - {parsedActiveIncidentsData?.items && - parsedActiveIncidentsData?.items.length > 0 ? ( - <div> - <Section title="Active Incidents" /> - - <EventHistoryList - items={parsedActiveIncidentsData?.items || []} - /> - </div> - ) : ( - <></> - )} - - {parsedResolvedIncidentsData?.items && - parsedResolvedIncidentsData?.items.length > 0 ? ( - <div> - <Section title="Resolved Incidents" /> - - <EventHistoryList - items={parsedResolvedIncidentsData?.items || []} - /> - </div> - ) : ( - <></> - )} - {incidents.length === 0 ? ( - <EmptyState - id={'incidents-empty-state'} - title={'No Incident'} - description={'No incidents posted on this status page.'} - icon={IconProp.Alert} - /> - ) : ( - <></> - )} - </Page> + const activeIncidents: Array<Incident> = incidents.filter( + (incident: Incident) => { + return ( + (incident.currentIncidentState?.order || 0) < + resolvedIncidentStateOrder + ); + }, ); + + const resolvedIncidents: Array<Incident> = incidents.filter( + (incident: Incident) => { + return !( + (incident.currentIncidentState?.order || 0) < + resolvedIncidentStateOrder + ); + }, + ); + + setParsedActiveIncidentsData( + getEventHistoryListComponentProps(activeIncidents), + ); + setParsedResolvedIncidentsData( + getEventHistoryListComponentProps(resolvedIncidents), + ); + }, [isLoading]); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <Page + title={"Incidents"} + breadcrumbLinks={[ + { + title: "Overview", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + ), + }, + { + title: "Incidents", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_INCIDENT_LIST] as Route) + : (RouteMap[PageMap.INCIDENT_LIST] as Route), + ), + }, + ]} + > + {parsedActiveIncidentsData?.items && + parsedActiveIncidentsData?.items.length > 0 ? ( + <div> + <Section title="Active Incidents" /> + + <EventHistoryList items={parsedActiveIncidentsData?.items || []} /> + </div> + ) : ( + <></> + )} + + {parsedResolvedIncidentsData?.items && + parsedResolvedIncidentsData?.items.length > 0 ? ( + <div> + <Section title="Resolved Incidents" /> + + <EventHistoryList items={parsedResolvedIncidentsData?.items || []} /> + </div> + ) : ( + <></> + )} + {incidents.length === 0 ? ( + <EmptyState + id={"incidents-empty-state"} + title={"No Incident"} + description={"No incidents posted on this status page."} + icon={IconProp.Alert} + /> + ) : ( + <></> + )} + </Page> + ); }; export default Overview; diff --git a/StatusPage/src/Pages/NotFound/PageNotFound.tsx b/StatusPage/src/Pages/NotFound/PageNotFound.tsx index ba202b5a77..b7bc249b5a 100644 --- a/StatusPage/src/Pages/NotFound/PageNotFound.tsx +++ b/StatusPage/src/Pages/NotFound/PageNotFound.tsx @@ -1,22 +1,22 @@ -import Page from '../../Components/Page/Page'; -import PageComponentProps from '../PageComponentProps'; -import IconProp from 'Common/Types/Icon/IconProp'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import React, { FunctionComponent, ReactElement } from 'react'; +import Page from "../../Components/Page/Page"; +import PageComponentProps from "../PageComponentProps"; +import IconProp from "Common/Types/Icon/IconProp"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import React, { FunctionComponent, ReactElement } from "react"; const PageNotFound: FunctionComponent<PageComponentProps> = ( - _props: PageComponentProps + _props: PageComponentProps, ): ReactElement => { - return ( - <Page> - <EmptyState - id="empty-state-page-not-found" - title={'Page not found.'} - description={'Page you are looking for does not exist.'} - icon={IconProp.AltGlobe} - /> - </Page> - ); + return ( + <Page> + <EmptyState + id="empty-state-page-not-found" + title={"Page not found."} + description={"Page you are looking for does not exist."} + icon={IconProp.AltGlobe} + /> + </Page> + ); }; export default PageNotFound; diff --git a/StatusPage/src/Pages/Overview/Overview.tsx b/StatusPage/src/Pages/Overview/Overview.tsx index 6adc004c27..d20d0e8592 100644 --- a/StatusPage/src/Pages/Overview/Overview.tsx +++ b/StatusPage/src/Pages/Overview/Overview.tsx @@ -1,947 +1,849 @@ -import MonitorOverview from '../../Components/Monitor/MonitorOverview'; -import Page from '../../Components/Page/Page'; -import Section from '../../Components/Section/Section'; -import IncidentGroup from '../../Types/IncidentGroup'; -import ScheduledMaintenanceGroup from '../../Types/ScheduledMaintenanceGroup'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import StatusPageUtil from '../../Utils/StatusPage'; -import { getAnnouncementEventItem } from '../Announcement/Detail'; -import { getIncidentEventItem } from '../Incidents/Detail'; -import PageComponentProps from '../PageComponentProps'; -import { getScheduledEventEventItem } from '../ScheduledEvent/Detail'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { Green } from 'Common/Types/BrandColors'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import Accordion from 'CommonUI/src/Components/Accordion/Accordion'; -import AccordionGroup from 'CommonUI/src/Components/Accordion/AccordionGroup'; -import Alert from 'CommonUI/src/Components/Alerts/Alert'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import EventItem from 'CommonUI/src/Components/EventItem/EventItem'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import MarkdownViewer from 'CommonUI/src/Components/Markdown.tsx/LazyMarkdownViewer'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Incident from 'Model/Models/Incident'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import MonitorStatus from 'Model/Models/MonitorStatus'; -import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import StatusPage from 'Model/Models/StatusPage'; -import StatusPageAnnouncement from 'Model/Models/StatusPageAnnouncement'; -import StatusPageGroup from 'Model/Models/StatusPageGroup'; -import StatusPageHistoryChartBarColorRule from 'Model/Models/StatusPageHistoryChartBarColorRule'; +import MonitorOverview from "../../Components/Monitor/MonitorOverview"; +import Page from "../../Components/Page/Page"; +import Section from "../../Components/Section/Section"; +import IncidentGroup from "../../Types/IncidentGroup"; +import ScheduledMaintenanceGroup from "../../Types/ScheduledMaintenanceGroup"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import StatusPageUtil from "../../Utils/StatusPage"; +import { getAnnouncementEventItem } from "../Announcement/Detail"; +import { getIncidentEventItem } from "../Incidents/Detail"; +import PageComponentProps from "../PageComponentProps"; +import { getScheduledEventEventItem } from "../ScheduledEvent/Detail"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { Green } from "Common/Types/BrandColors"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import Accordion from "CommonUI/src/Components/Accordion/Accordion"; +import AccordionGroup from "CommonUI/src/Components/Accordion/AccordionGroup"; +import Alert from "CommonUI/src/Components/Alerts/Alert"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import EventItem from "CommonUI/src/Components/EventItem/EventItem"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import MarkdownViewer from "CommonUI/src/Components/Markdown.tsx/LazyMarkdownViewer"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Incident from "Model/Models/Incident"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import MonitorStatus from "Model/Models/MonitorStatus"; +import MonitorStatusTimeline from "Model/Models/MonitorStatusTimeline"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import StatusPage from "Model/Models/StatusPage"; +import StatusPageAnnouncement from "Model/Models/StatusPageAnnouncement"; +import StatusPageGroup from "Model/Models/StatusPageGroup"; +import StatusPageHistoryChartBarColorRule from "Model/Models/StatusPageHistoryChartBarColorRule"; import StatusPageResource, { - UptimePrecision, -} from 'Model/Models/StatusPageResource'; + UptimePrecision, +} from "Model/Models/StatusPageResource"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const Overview: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - if (LocalStorage.getItem('redirectUrl')) { - // const get item + if (LocalStorage.getItem("redirectUrl")) { + // const get item - const redirectUrl: string = LocalStorage.getItem( - 'redirectUrl' - ) as string; + const redirectUrl: string = LocalStorage.getItem("redirectUrl") as string; - // clear local storage. - LocalStorage.removeItem('redirectUrl'); + // clear local storage. + LocalStorage.removeItem("redirectUrl"); - Navigation.navigate(new Route(redirectUrl)); - } + Navigation.navigate(new Route(redirectUrl)); + } - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - const [ - scheduledMaintenanceEventsPublicNotes, - setScheduledMaintenanceEventsPublicNotes, - ] = useState<Array<ScheduledMaintenancePublicNote>>([]); - const [statusPage, setStatusPage] = useState<StatusPage | null>(null); - const [ - activeScheduledMaintenanceEvents, - setActiveScheduledMaintenanceEvents, - ] = useState<Array<ScheduledMaintenance>>([]); - const [activeAnnouncements, setActiveAnnouncements] = useState< - Array<StatusPageAnnouncement> - >([]); - const [incidentPublicNotes, setIncidentPublicNotes] = useState< - Array<IncidentPublicNote> - >([]); - const [activeIncidents, setActiveIncidents] = useState<Array<Incident>>([]); - const [monitorStatusTimelines, setMonitorStatusTimelines] = useState< - Array<MonitorStatusTimeline> - >([]); - const [resourceGroups, setResourceGroups] = useState< - Array<StatusPageGroup> - >([]); - const [monitorStatuses, setMonitorStatuses] = useState< - Array<MonitorStatus> - >([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string | null>(null); + const [ + scheduledMaintenanceEventsPublicNotes, + setScheduledMaintenanceEventsPublicNotes, + ] = useState<Array<ScheduledMaintenancePublicNote>>([]); + const [statusPage, setStatusPage] = useState<StatusPage | null>(null); + const [ + activeScheduledMaintenanceEvents, + setActiveScheduledMaintenanceEvents, + ] = useState<Array<ScheduledMaintenance>>([]); + const [activeAnnouncements, setActiveAnnouncements] = useState< + Array<StatusPageAnnouncement> + >([]); + const [incidentPublicNotes, setIncidentPublicNotes] = useState< + Array<IncidentPublicNote> + >([]); + const [activeIncidents, setActiveIncidents] = useState<Array<Incident>>([]); + const [monitorStatusTimelines, setMonitorStatusTimelines] = useState< + Array<MonitorStatusTimeline> + >([]); + const [resourceGroups, setResourceGroups] = useState<Array<StatusPageGroup>>( + [], + ); + const [monitorStatuses, setMonitorStatuses] = useState<Array<MonitorStatus>>( + [], + ); - const [ + const [ + statusPageHistoryChartBarColorRules, + setStatusPageHistoryChartBarColorRules, + ] = useState<Array<StatusPageHistoryChartBarColorRule>>([]); + + const [statusPageResources, setStatusPageResources] = useState< + Array<StatusPageResource> + >([]); + const [incidentStateTimelines, setIncidentStateTimelines] = useState< + Array<IncidentStateTimeline> + >([]); + const [ + scheduledMaintenanceStateTimelines, + setScheduledMaintenanceStateTimelines, + ] = useState<Array<ScheduledMaintenanceStateTimeline>>([]); + const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); + const endDate: Date = OneUptimeDate.getCurrentDate(); + const [currentStatus, setCurrentStatus] = useState<MonitorStatus | null>( + null, + ); + + const [monitorsInGroup, setMonitorsInGroup] = useState< + Dictionary<Array<ObjectID>> + >({}); + + const [monitorGroupCurrentStatuses, setMonitorGroupCurrentStatuses] = + useState<Dictionary<ObjectID>>({}); + + StatusPageUtil.checkIfUserHasLoggedIn(); + + const loadPage: PromiseVoidFunction = async (): Promise<void> => { + try { + if (!StatusPageUtil.getStatusPageId()) { + return; + } + setIsLoading(true); + + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; + if (!id) { + throw new BadDataException("Status Page ID is required"); + } + const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/overview/${id.toString()}`, + ), + {}, + API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!), + ); + + if (!response.isSuccess()) { + throw response; + } + + if (!response.isSuccess()) { + throw response; + } + + const data: JSONObject = response.data; + + const scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = + BaseModel.fromJSONArray( + (data["scheduledMaintenanceEventsPublicNotes"] as JSONArray) || [], + ScheduledMaintenancePublicNote, + ); + const activeScheduledMaintenanceEvents: Array<ScheduledMaintenance> = + BaseModel.fromJSONArray( + (data["scheduledMaintenanceEvents"] as JSONArray) || [], + ScheduledMaintenance, + ); + const activeAnnouncements: Array<StatusPageAnnouncement> = + BaseModel.fromJSONArray( + (data["activeAnnouncements"] as JSONArray) || [], + StatusPageAnnouncement, + ); + const incidentPublicNotes: Array<IncidentPublicNote> = + BaseModel.fromJSONArray( + (data["incidentPublicNotes"] as JSONArray) || [], + IncidentPublicNote, + ); + + const statusPageHistoryChartBarColorRules: Array<StatusPageHistoryChartBarColorRule> = + BaseModel.fromJSONArray( + (data["statusPageHistoryChartBarColorRules"] as JSONArray) || [], + StatusPageHistoryChartBarColorRule, + ); + + const activeIncidents: Array<Incident> = BaseModel.fromJSONArray( + (data["activeIncidents"] as JSONArray) || [], + Incident, + ); + const monitorStatusTimelines: Array<MonitorStatusTimeline> = + BaseModel.fromJSONArray( + (data["monitorStatusTimelines"] as JSONArray) || [], + MonitorStatusTimeline, + ); + const resourceGroups: Array<StatusPageGroup> = BaseModel.fromJSONArray( + (data["resourceGroups"] as JSONArray) || [], + StatusPageGroup, + ); + const monitorStatuses: Array<MonitorStatus> = BaseModel.fromJSONArray( + (data["monitorStatuses"] as JSONArray) || [], + MonitorStatus, + ); + const statusPageResources: Array<StatusPageResource> = + BaseModel.fromJSONArray( + (data["statusPageResources"] as JSONArray) || [], + StatusPageResource, + ); + const incidentStateTimelines: Array<IncidentStateTimeline> = + BaseModel.fromJSONArray( + (data["incidentStateTimelines"] as JSONArray) || [], + IncidentStateTimeline, + ); + + const statusPage: StatusPage = BaseModel.fromJSONObject( + (data["statusPage"] as JSONObject) || [], + StatusPage, + ); + + const scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = + BaseModel.fromJSONArray( + (data["scheduledMaintenanceStateTimelines"] as JSONArray) || [], + ScheduledMaintenanceStateTimeline, + ); + + const monitorsInGroup: Dictionary<Array<ObjectID>> = + JSONFunctions.deserialize( + (data["monitorsInGroup"] as JSONObject) || {}, + ) as Dictionary<Array<ObjectID>>; + + const monitorGroupCurrentStatuses: Dictionary<ObjectID> = + JSONFunctions.deserialize( + (data["monitorGroupCurrentStatuses"] as JSONObject) || {}, + ) as Dictionary<ObjectID>; + + setMonitorsInGroup(monitorsInGroup); + setMonitorGroupCurrentStatuses(monitorGroupCurrentStatuses); + + setStatusPageHistoryChartBarColorRules( statusPageHistoryChartBarColorRules, - setStatusPageHistoryChartBarColorRules, - ] = useState<Array<StatusPageHistoryChartBarColorRule>>([]); + ); - const [statusPageResources, setStatusPageResources] = useState< - Array<StatusPageResource> - >([]); - const [incidentStateTimelines, setIncidentStateTimelines] = useState< - Array<IncidentStateTimeline> - >([]); - const [ - scheduledMaintenanceStateTimelines, - setScheduledMaintenanceStateTimelines, - ] = useState<Array<ScheduledMaintenanceStateTimeline>>([]); - const startDate: Date = OneUptimeDate.getSomeDaysAgo(90); - const endDate: Date = OneUptimeDate.getCurrentDate(); - const [currentStatus, setCurrentStatus] = useState<MonitorStatus | null>( - null - ); + // save data. set() + setScheduledMaintenanceEventsPublicNotes( + scheduledMaintenanceEventsPublicNotes, + ); + setActiveScheduledMaintenanceEvents(activeScheduledMaintenanceEvents); + setActiveAnnouncements(activeAnnouncements); + setIncidentPublicNotes(incidentPublicNotes); + setActiveIncidents(activeIncidents); + setMonitorStatusTimelines(monitorStatusTimelines); + setResourceGroups(resourceGroups); + setMonitorStatuses(monitorStatuses); + setStatusPage(statusPage); + setStatusPageResources(statusPageResources); + setIncidentStateTimelines(incidentStateTimelines); + setScheduledMaintenanceStateTimelines(scheduledMaintenanceStateTimelines); - const [monitorsInGroup, setMonitorsInGroup] = useState< - Dictionary<Array<ObjectID>> - >({}); + // Parse Data. + setCurrentStatus( + getOverallMonitorStatus( + statusPageResources, + monitorStatuses, + monitorGroupCurrentStatuses, + ), + ); - const [monitorGroupCurrentStatuses, setMonitorGroupCurrentStatuses] = - useState<Dictionary<ObjectID>>({}); + setIsLoading(false); + props.onLoadComplete(); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + await StatusPageUtil.checkIfTheUserIsAuthenticated(err); + } - StatusPageUtil.checkIfUserHasLoggedIn(); + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }; - const loadPage: PromiseVoidFunction = async (): Promise<void> => { - try { - if (!StatusPageUtil.getStatusPageId()) { - return; - } - setIsLoading(true); + useEffect(() => { + loadPage().catch((err: Error) => { + setError(err.message); + }); + }, []); - const id: ObjectID = LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; - if (!id) { - throw new BadDataException('Status Page ID is required'); - } - const response: HTTPResponse<JSONObject> = - await API.post<JSONObject>( - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/overview/${id.toString()}` - ), - {}, - API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!) - ); + useEffect(() => { + loadPage().catch((err: Error) => { + setError(err.message); + }); + }, [ + StatusPageUtil.getStatusPageId()?.toString() || "", + StatusPageUtil.isPreviewPage(), + StatusPageUtil.isPrivateStatusPage(), + ]); - if (!response.isSuccess()) { - throw response; - } + type GetOverallMonitorStatusFunction = ( + statusPageResources: Array<StatusPageResource>, + monitorStatuses: Array<MonitorStatus>, + monitorGroupCurrentStatuses: Dictionary<ObjectID>, + ) => MonitorStatus | null; - if (!response.isSuccess()) { - throw response; - } + const getOverallMonitorStatus: GetOverallMonitorStatusFunction = ( + statusPageResources: Array<StatusPageResource>, + monitorStatuses: Array<MonitorStatus>, + monitorGroupCurrentStatuses: Dictionary<ObjectID>, + ): MonitorStatus | null => { + let currentStatus: MonitorStatus | null = + monitorStatuses.length > 0 && monitorStatuses[0] + ? monitorStatuses[0] + : null; - const data: JSONObject = response.data; + const dict: Dictionary<number> = {}; - const scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = - BaseModel.fromJSONArray( - (data[ - 'scheduledMaintenanceEventsPublicNotes' - ] as JSONArray) || [], - ScheduledMaintenancePublicNote - ); - const activeScheduledMaintenanceEvents: Array<ScheduledMaintenance> = - BaseModel.fromJSONArray( - (data['scheduledMaintenanceEvents'] as JSONArray) || [], - ScheduledMaintenance - ); - const activeAnnouncements: Array<StatusPageAnnouncement> = - BaseModel.fromJSONArray( - (data['activeAnnouncements'] as JSONArray) || [], - StatusPageAnnouncement - ); - const incidentPublicNotes: Array<IncidentPublicNote> = - BaseModel.fromJSONArray( - (data['incidentPublicNotes'] as JSONArray) || [], - IncidentPublicNote - ); + for (const resource of statusPageResources) { + if (resource.monitor?.currentMonitorStatusId) { + if ( + !Object.keys(dict).includes( + resource.monitor?.currentMonitorStatusId.toString() || "", + ) + ) { + dict[resource.monitor?.currentMonitorStatusId.toString()] = 1; + } else { + dict[resource.monitor?.currentMonitorStatusId.toString()]++; + } + } + } - const statusPageHistoryChartBarColorRules: Array<StatusPageHistoryChartBarColorRule> = - BaseModel.fromJSONArray( - (data[ - 'statusPageHistoryChartBarColorRules' - ] as JSONArray) || [], - StatusPageHistoryChartBarColorRule - ); + // check status of monitor groups. - const activeIncidents: Array<Incident> = BaseModel.fromJSONArray( - (data['activeIncidents'] as JSONArray) || [], - Incident - ); - const monitorStatusTimelines: Array<MonitorStatusTimeline> = - BaseModel.fromJSONArray( - (data['monitorStatusTimelines'] as JSONArray) || [], - MonitorStatusTimeline - ); - const resourceGroups: Array<StatusPageGroup> = - BaseModel.fromJSONArray( - (data['resourceGroups'] as JSONArray) || [], - StatusPageGroup - ); - const monitorStatuses: Array<MonitorStatus> = - BaseModel.fromJSONArray( - (data['monitorStatuses'] as JSONArray) || [], - MonitorStatus - ); - const statusPageResources: Array<StatusPageResource> = - BaseModel.fromJSONArray( - (data['statusPageResources'] as JSONArray) || [], - StatusPageResource - ); - const incidentStateTimelines: Array<IncidentStateTimeline> = - BaseModel.fromJSONArray( - (data['incidentStateTimelines'] as JSONArray) || [], - IncidentStateTimeline - ); + for (const groupId in monitorGroupCurrentStatuses) { + const statusId: ObjectID | undefined = + monitorGroupCurrentStatuses[groupId]; - const statusPage: StatusPage = BaseModel.fromJSONObject( - (data['statusPage'] as JSONObject) || [], - StatusPage - ); + if (statusId) { + if (!Object.keys(dict).includes(statusId.toString() || "")) { + dict[statusId.toString()] = 1; + } else { + dict[statusId.toString()]++; + } + } + } - const scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = - BaseModel.fromJSONArray( - (data['scheduledMaintenanceStateTimelines'] as JSONArray) || - [], - ScheduledMaintenanceStateTimeline - ); + for (const monitorStatus of monitorStatuses) { + if (monitorStatus._id && dict[monitorStatus._id]) { + currentStatus = monitorStatus; + } + } - const monitorsInGroup: Dictionary<Array<ObjectID>> = - JSONFunctions.deserialize( - (data['monitorsInGroup'] as JSONObject) || {} - ) as Dictionary<Array<ObjectID>>; + return currentStatus; + }; - const monitorGroupCurrentStatuses: Dictionary<ObjectID> = - JSONFunctions.deserialize( - (data['monitorGroupCurrentStatuses'] as JSONObject) || {} - ) as Dictionary<ObjectID>; + if (isLoading) { + return <PageLoader isVisible={true} />; + } - setMonitorsInGroup(monitorsInGroup); - setMonitorGroupCurrentStatuses(monitorGroupCurrentStatuses); + if (error) { + return <ErrorMessage error={error} />; + } - setStatusPageHistoryChartBarColorRules( + type GetMonitorOverviewListInGroupFunction = ( + group: StatusPageGroup | null, + ) => Array<ReactElement>; + + const getMonitorOverviewListInGroup: GetMonitorOverviewListInGroupFunction = ( + group: StatusPageGroup | null, + ): Array<ReactElement> => { + const elements: Array<ReactElement> = []; + + for (const resource of statusPageResources) { + if ( + (resource.statusPageGroupId && + resource.statusPageGroupId.toString() && + group && + group._id?.toString() && + group._id?.toString() === resource.statusPageGroupId.toString()) || + (!resource.statusPageGroupId && !group) + ) { + // if its not a monitor or a monitor group, then continue. This should ideally not happen. + + if (!resource.monitor && !resource.monitorGroupId) { + continue; + } + + // if its a monitor + + if (resource.monitor) { + let currentStatus: MonitorStatus | undefined = monitorStatuses.find( + (status: MonitorStatus) => { + return ( + status._id?.toString() === + resource.monitor?.currentMonitorStatusId?.toString() + ); + }, + ); + + if (!currentStatus) { + currentStatus = new MonitorStatus(); + currentStatus.name = "Operational"; + currentStatus.color = Green; + } + + elements.push( + <MonitorOverview + key={Math.random()} + monitorName={resource.displayName || resource.monitor?.name || ""} + statusPageHistoryChartBarColorRules={ statusPageHistoryChartBarColorRules - ); - - // save data. set() - setScheduledMaintenanceEventsPublicNotes( - scheduledMaintenanceEventsPublicNotes - ); - setActiveScheduledMaintenanceEvents( - activeScheduledMaintenanceEvents - ); - setActiveAnnouncements(activeAnnouncements); - setIncidentPublicNotes(incidentPublicNotes); - setActiveIncidents(activeIncidents); - setMonitorStatusTimelines(monitorStatusTimelines); - setResourceGroups(resourceGroups); - setMonitorStatuses(monitorStatuses); - setStatusPage(statusPage); - setStatusPageResources(statusPageResources); - setIncidentStateTimelines(incidentStateTimelines); - setScheduledMaintenanceStateTimelines( - scheduledMaintenanceStateTimelines - ); - - // Parse Data. - setCurrentStatus( - getOverallMonitorStatus( - statusPageResources, - monitorStatuses, - monitorGroupCurrentStatuses - ) - ); - - setIsLoading(false); - props.onLoadComplete(); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - await StatusPageUtil.checkIfTheUserIsAuthenticated(err); - } - - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }; - - useEffect(() => { - loadPage().catch((err: Error) => { - setError(err.message); - }); - }, []); - - useEffect(() => { - loadPage().catch((err: Error) => { - setError(err.message); - }); - }, [ - StatusPageUtil.getStatusPageId()?.toString() || '', - StatusPageUtil.isPreviewPage(), - StatusPageUtil.isPrivateStatusPage(), - ]); - - type GetOverallMonitorStatusFunction = ( - statusPageResources: Array<StatusPageResource>, - monitorStatuses: Array<MonitorStatus>, - monitorGroupCurrentStatuses: Dictionary<ObjectID> - ) => MonitorStatus | null; - - const getOverallMonitorStatus: GetOverallMonitorStatusFunction = ( - statusPageResources: Array<StatusPageResource>, - monitorStatuses: Array<MonitorStatus>, - monitorGroupCurrentStatuses: Dictionary<ObjectID> - ): MonitorStatus | null => { - let currentStatus: MonitorStatus | null = - monitorStatuses.length > 0 && monitorStatuses[0] - ? monitorStatuses[0] - : null; - - const dict: Dictionary<number> = {}; - - for (const resource of statusPageResources) { - if (resource.monitor?.currentMonitorStatusId) { - if ( - !Object.keys(dict).includes( - resource.monitor?.currentMonitorStatusId.toString() || - '' - ) - ) { - dict[ - resource.monitor?.currentMonitorStatusId.toString() - ] = 1; - } else { - dict[resource.monitor?.currentMonitorStatusId.toString()]++; - } - } + } + downtimeMonitorStatuses={ + statusPage?.downtimeMonitorStatuses || [] + } + description={resource.displayDescription || ""} + tooltip={resource.displayTooltip || ""} + currentStatus={currentStatus} + showUptimePercent={Boolean(resource.showUptimePercent)} + uptimePrecision={ + resource.uptimePercentPrecision || UptimePrecision.ONE_DECIMAL + } + monitorStatusTimeline={[...monitorStatusTimelines].filter( + (timeline: MonitorStatusTimeline) => { + return ( + timeline.monitorId?.toString() === + resource.monitorId?.toString() + ); + }, + )} + startDate={startDate} + endDate={endDate} + showHistoryChart={resource.showStatusHistoryChart} + showCurrentStatus={resource.showCurrentStatus} + uptimeGraphHeight={10} + defaultBarColor={statusPage?.defaultBarColor || Green} + />, + ); } - // check status of monitor groups. + // if its a monitor group, then... - for (const groupId in monitorGroupCurrentStatuses) { - const statusId: ObjectID | undefined = - monitorGroupCurrentStatuses[groupId]; + if (resource.monitorGroupId) { + let currentStatus: MonitorStatus | undefined = monitorStatuses.find( + (status: MonitorStatus) => { + return ( + status._id?.toString() === + monitorGroupCurrentStatuses[ + resource.monitorGroupId?.toString() || "" + ]?.toString() + ); + }, + ); - if (statusId) { - if (!Object.keys(dict).includes(statusId.toString() || '')) { - dict[statusId.toString()] = 1; - } else { - dict[statusId.toString()]++; - } - } + if (!currentStatus) { + currentStatus = new MonitorStatus(); + currentStatus.name = "Operational"; + currentStatus.color = Green; + } + + elements.push( + <MonitorOverview + key={Math.random()} + monitorName={resource.displayName || resource.monitor?.name || ""} + showUptimePercent={Boolean(resource.showUptimePercent)} + uptimePrecision={ + resource.uptimePercentPrecision || UptimePrecision.ONE_DECIMAL + } + statusPageHistoryChartBarColorRules={ + statusPageHistoryChartBarColorRules + } + description={resource.displayDescription || ""} + tooltip={resource.displayTooltip || ""} + currentStatus={currentStatus} + monitorStatusTimeline={[...monitorStatusTimelines].filter( + (timeline: MonitorStatusTimeline) => { + const monitorsInThisGroup: Array<ObjectID> | undefined = + monitorsInGroup[resource.monitorGroupId?.toString() || ""]; + + if (!monitorsInThisGroup) { + return false; + } + + return monitorsInThisGroup.find((monitorId: ObjectID) => { + return ( + monitorId.toString() === timeline.monitorId?.toString() + ); + }); + }, + )} + downtimeMonitorStatuses={ + statusPage?.downtimeMonitorStatuses || [] + } + startDate={startDate} + endDate={endDate} + showHistoryChart={resource.showStatusHistoryChart} + showCurrentStatus={resource.showCurrentStatus} + uptimeGraphHeight={10} + defaultBarColor={statusPage?.defaultBarColor || Green} + />, + ); } - - for (const monitorStatus of monitorStatuses) { - if (monitorStatus._id && dict[monitorStatus._id]) { - currentStatus = monitorStatus; - } - } - - return currentStatus; - }; - - if (isLoading) { - return <PageLoader isVisible={true} />; + } } - if (error) { - return <ErrorMessage error={error} />; + if (elements.length === 0) { + elements.push( + <div key={1} className="mb-20"> + <ErrorMessage error="No resources added to this group." /> + </div>, + ); } - type GetMonitorOverviewListInGroupFunction = ( - group: StatusPageGroup | null - ) => Array<ReactElement>; + return elements; + }; - const getMonitorOverviewListInGroup: GetMonitorOverviewListInGroupFunction = - (group: StatusPageGroup | null): Array<ReactElement> => { - const elements: Array<ReactElement> = []; + type GetActiveIncidentsFunction = () => Array<IncidentGroup>; - for (const resource of statusPageResources) { - if ( - (resource.statusPageGroupId && - resource.statusPageGroupId.toString() && - group && - group._id?.toString() && - group._id?.toString() === - resource.statusPageGroupId.toString()) || - (!resource.statusPageGroupId && !group) - ) { - // if its not a monitor or a monitor group, then continue. This should ideally not happen. + const getActiveIncidents: GetActiveIncidentsFunction = + (): Array<IncidentGroup> => { + const groups: Array<IncidentGroup> = []; - if (!resource.monitor && !resource.monitorGroupId) { - continue; - } - - // if its a monitor - - if (resource.monitor) { - let currentStatus: MonitorStatus | undefined = - monitorStatuses.find((status: MonitorStatus) => { - return ( - status._id?.toString() === - resource.monitor?.currentMonitorStatusId?.toString() - ); - }); - - if (!currentStatus) { - currentStatus = new MonitorStatus(); - currentStatus.name = 'Operational'; - currentStatus.color = Green; - } - - elements.push( - <MonitorOverview - key={Math.random()} - monitorName={ - resource.displayName || - resource.monitor?.name || - '' - } - statusPageHistoryChartBarColorRules={ - statusPageHistoryChartBarColorRules - } - downtimeMonitorStatuses={ - statusPage?.downtimeMonitorStatuses || [] - } - description={resource.displayDescription || ''} - tooltip={resource.displayTooltip || ''} - currentStatus={currentStatus} - showUptimePercent={Boolean( - resource.showUptimePercent - )} - uptimePrecision={ - resource.uptimePercentPrecision || - UptimePrecision.ONE_DECIMAL - } - monitorStatusTimeline={[ - ...monitorStatusTimelines, - ].filter((timeline: MonitorStatusTimeline) => { - return ( - timeline.monitorId?.toString() === - resource.monitorId?.toString() - ); - })} - startDate={startDate} - endDate={endDate} - showHistoryChart={ - resource.showStatusHistoryChart - } - showCurrentStatus={resource.showCurrentStatus} - uptimeGraphHeight={10} - defaultBarColor={ - statusPage?.defaultBarColor || Green - } - /> - ); - } - - // if its a monitor group, then... - - if (resource.monitorGroupId) { - let currentStatus: MonitorStatus | undefined = - monitorStatuses.find((status: MonitorStatus) => { - return ( - status._id?.toString() === - monitorGroupCurrentStatuses[ - resource.monitorGroupId?.toString() || - '' - ]?.toString() - ); - }); - - if (!currentStatus) { - currentStatus = new MonitorStatus(); - currentStatus.name = 'Operational'; - currentStatus.color = Green; - } - - elements.push( - <MonitorOverview - key={Math.random()} - monitorName={ - resource.displayName || - resource.monitor?.name || - '' - } - showUptimePercent={Boolean( - resource.showUptimePercent - )} - uptimePrecision={ - resource.uptimePercentPrecision || - UptimePrecision.ONE_DECIMAL - } - statusPageHistoryChartBarColorRules={ - statusPageHistoryChartBarColorRules - } - description={resource.displayDescription || ''} - tooltip={resource.displayTooltip || ''} - currentStatus={currentStatus} - monitorStatusTimeline={[ - ...monitorStatusTimelines, - ].filter((timeline: MonitorStatusTimeline) => { - const monitorsInThisGroup: - | Array<ObjectID> - | undefined = - monitorsInGroup[ - resource.monitorGroupId?.toString() || - '' - ]; - - if (!monitorsInThisGroup) { - return false; - } - - return monitorsInThisGroup.find( - (monitorId: ObjectID) => { - return ( - monitorId.toString() === - timeline.monitorId?.toString() - ); - } - ); - })} - downtimeMonitorStatuses={ - statusPage?.downtimeMonitorStatuses || [] - } - startDate={startDate} - endDate={endDate} - showHistoryChart={ - resource.showStatusHistoryChart - } - showCurrentStatus={resource.showCurrentStatus} - uptimeGraphHeight={10} - defaultBarColor={ - statusPage?.defaultBarColor || Green - } - /> - ); - } - } - } - - if (elements.length === 0) { - elements.push( - <div key={1} className="mb-20"> - <ErrorMessage error="No resources added to this group." /> - </div> - ); - } - - return elements; - }; - - type GetActiveIncidentsFunction = () => Array<IncidentGroup>; - - const getActiveIncidents: GetActiveIncidentsFunction = - (): Array<IncidentGroup> => { - const groups: Array<IncidentGroup> = []; - - for (const activeIncident of activeIncidents) { - if (!activeIncident.currentIncidentState) { - throw new BadDataException('Incident State not found.'); - } - - const timeline: IncidentStateTimeline | undefined = - incidentStateTimelines.find( - (timeline: IncidentStateTimeline) => { - return ( - timeline.incidentId?.toString() === - activeIncident._id - ); - } - ); - - if (!timeline) { - throw new BadDataException('Incident Timeline not found.'); - } - - const group: IncidentGroup = { - incident: activeIncident, - incidentState: activeIncident.currentIncidentState, - incidentResources: statusPageResources, - publicNotes: incidentPublicNotes.filter( - (publicNote: IncidentPublicNote) => { - return ( - publicNote.incidentId?.toString() === - activeIncident._id - ); - } - ), - incidentSeverity: activeIncident.incidentSeverity!, - incidentStateTimelines: [timeline], - monitorsInGroup: monitorsInGroup, - }; - - groups.push(group); - } - - return groups; - }; - - type GetOngoingScheduledEventsFunction = - () => Array<ScheduledMaintenanceGroup>; - - const getOngoingScheduledEvents: GetOngoingScheduledEventsFunction = - (): Array<ScheduledMaintenanceGroup> => { - const groups: Array<ScheduledMaintenanceGroup> = []; - - for (const activeEvent of activeScheduledMaintenanceEvents) { - if (!activeEvent.currentScheduledMaintenanceState) { - throw new BadDataException( - 'Scheduled Maintenance State not found.' - ); - } - - const timeline: ScheduledMaintenanceStateTimeline | undefined = - scheduledMaintenanceStateTimelines.find( - (timeline: ScheduledMaintenanceStateTimeline) => { - return ( - timeline.scheduledMaintenanceId?.toString() === - activeEvent._id - ); - } - ); - - if (!timeline) { - throw new BadDataException('Incident Timeline not found.'); - } - - const group: ScheduledMaintenanceGroup = { - scheduledMaintenance: activeEvent, - scheduledMaintenanceState: - activeEvent.currentScheduledMaintenanceState, - scheduledEventResources: statusPageResources, - publicNotes: scheduledMaintenanceEventsPublicNotes.filter( - (publicNote: ScheduledMaintenancePublicNote) => { - return ( - publicNote.scheduledMaintenanceId?.toString() === - activeEvent._id - ); - } - ), - scheduledMaintenanceStateTimelines: [timeline], - monitorsInGroup: monitorsInGroup, - }; - - groups.push(group); - } - - return groups; - }; - - type GetRightAccordionElementFunction = ( - group: StatusPageGroup - ) => ReactElement; - - const getRightAccordionElement: GetRightAccordionElementFunction = ( - group: StatusPageGroup - ): ReactElement => { - let currentStatus: MonitorStatus = new MonitorStatus(); - currentStatus.name = 'Operational'; - currentStatus.color = Green; - let hasResource: boolean = false; - - for (const resource of statusPageResources) { - if ( - (resource.statusPageGroupId && - resource.statusPageGroupId.toString() && - group && - group._id?.toString() && - group._id?.toString() === - resource.statusPageGroupId.toString()) || - (!resource.statusPageGroupId && !group) - ) { - hasResource = true; - const currentMonitorStatus: MonitorStatus | undefined = - monitorStatuses.find((status: MonitorStatus) => { - return ( - status._id?.toString() === - resource.monitor?.currentMonitorStatusId?.toString() - ); - }); - - if ( - (currentStatus && - currentStatus.priority && - currentMonitorStatus?.priority && - currentMonitorStatus?.priority > - currentStatus.priority) || - !currentStatus.priority - ) { - currentStatus = currentMonitorStatus!; - } - } + for (const activeIncident of activeIncidents) { + if (!activeIncident.currentIncidentState) { + throw new BadDataException("Incident State not found."); } - if (hasResource) { + const timeline: IncidentStateTimeline | undefined = + incidentStateTimelines.find((timeline: IncidentStateTimeline) => { + return timeline.incidentId?.toString() === activeIncident._id; + }); + + if (!timeline) { + throw new BadDataException("Incident Timeline not found."); + } + + const group: IncidentGroup = { + incident: activeIncident, + incidentState: activeIncident.currentIncidentState, + incidentResources: statusPageResources, + publicNotes: incidentPublicNotes.filter( + (publicNote: IncidentPublicNote) => { + return publicNote.incidentId?.toString() === activeIncident._id; + }, + ), + incidentSeverity: activeIncident.incidentSeverity!, + incidentStateTimelines: [timeline], + monitorsInGroup: monitorsInGroup, + }; + + groups.push(group); + } + + return groups; + }; + + type GetOngoingScheduledEventsFunction = + () => Array<ScheduledMaintenanceGroup>; + + const getOngoingScheduledEvents: GetOngoingScheduledEventsFunction = + (): Array<ScheduledMaintenanceGroup> => { + const groups: Array<ScheduledMaintenanceGroup> = []; + + for (const activeEvent of activeScheduledMaintenanceEvents) { + if (!activeEvent.currentScheduledMaintenanceState) { + throw new BadDataException("Scheduled Maintenance State not found."); + } + + const timeline: ScheduledMaintenanceStateTimeline | undefined = + scheduledMaintenanceStateTimelines.find( + (timeline: ScheduledMaintenanceStateTimeline) => { + return ( + timeline.scheduledMaintenanceId?.toString() === activeEvent._id + ); + }, + ); + + if (!timeline) { + throw new BadDataException("Incident Timeline not found."); + } + + const group: ScheduledMaintenanceGroup = { + scheduledMaintenance: activeEvent, + scheduledMaintenanceState: + activeEvent.currentScheduledMaintenanceState, + scheduledEventResources: statusPageResources, + publicNotes: scheduledMaintenanceEventsPublicNotes.filter( + (publicNote: ScheduledMaintenancePublicNote) => { + return ( + publicNote.scheduledMaintenanceId?.toString() === + activeEvent._id + ); + }, + ), + scheduledMaintenanceStateTimelines: [timeline], + monitorsInGroup: monitorsInGroup, + }; + + groups.push(group); + } + + return groups; + }; + + type GetRightAccordionElementFunction = ( + group: StatusPageGroup, + ) => ReactElement; + + const getRightAccordionElement: GetRightAccordionElementFunction = ( + group: StatusPageGroup, + ): ReactElement => { + let currentStatus: MonitorStatus = new MonitorStatus(); + currentStatus.name = "Operational"; + currentStatus.color = Green; + let hasResource: boolean = false; + + for (const resource of statusPageResources) { + if ( + (resource.statusPageGroupId && + resource.statusPageGroupId.toString() && + group && + group._id?.toString() && + group._id?.toString() === resource.statusPageGroupId.toString()) || + (!resource.statusPageGroupId && !group) + ) { + hasResource = true; + const currentMonitorStatus: MonitorStatus | undefined = + monitorStatuses.find((status: MonitorStatus) => { return ( - <div - className="bold font16" - style={{ - color: - currentStatus?.color?.toString() || - Green.toString(), - }} - > - {currentStatus?.name || 'Operational'} - </div> + status._id?.toString() === + resource.monitor?.currentMonitorStatusId?.toString() ); + }); + + if ( + (currentStatus && + currentStatus.priority && + currentMonitorStatus?.priority && + currentMonitorStatus?.priority > currentStatus.priority) || + !currentStatus.priority + ) { + currentStatus = currentMonitorStatus!; } - return <></>; - }; + } + } - const activeIncidentsInIncidentGroup: Array<IncidentGroup> = - getActiveIncidents(); - const activeScheduledMaintenanceEventsInScheduledMaintenanceGroup: Array<ScheduledMaintenanceGroup> = - getOngoingScheduledEvents(); + if (hasResource) { + return ( + <div + className="bold font16" + style={{ + color: currentStatus?.color?.toString() || Green.toString(), + }} + > + {currentStatus?.name || "Operational"} + </div> + ); + } + return <></>; + }; - return ( - <Page> - {isLoading ? <PageLoader isVisible={true} /> : <></>} - {error ? <ErrorMessage error={error} /> : <></>} + const activeIncidentsInIncidentGroup: Array<IncidentGroup> = + getActiveIncidents(); + const activeScheduledMaintenanceEventsInScheduledMaintenanceGroup: Array<ScheduledMaintenanceGroup> = + getOngoingScheduledEvents(); - {!isLoading && !error ? ( - <div data-testid="status-page-overview"> - {/* Overview Page Description */} - {statusPage && statusPage.overviewPageDescription && ( - <div - id="status-page-description" - className="bg-white p-5 my-5 rounded-xl shadow" - > - <MarkdownViewer - text={statusPage.overviewPageDescription} - /> - </div> - )} + return ( + <Page> + {isLoading ? <PageLoader isVisible={true} /> : <></>} + {error ? <ErrorMessage error={error} /> : <></>} - {/* Load Active Announcement */} - <div id="announcements-list"> - {activeAnnouncements.map( - ( - announcement: StatusPageAnnouncement, - i: number - ) => { - return ( - <EventItem - {...getAnnouncementEventItem({ - announcement, - isPreviewPage: - StatusPageUtil.isPreviewPage(), - isSummary: true, - })} - isDetailItem={false} - key={i} - /> - ); - } - )} - </div> + {!isLoading && !error ? ( + <div data-testid="status-page-overview"> + {/* Overview Page Description */} + {statusPage && statusPage.overviewPageDescription && ( + <div + id="status-page-description" + className="bg-white p-5 my-5 rounded-xl shadow" + > + <MarkdownViewer text={statusPage.overviewPageDescription} /> + </div> + )} - <div> - {currentStatus && statusPageResources.length > 0 && ( - <Alert - title={`${ - currentStatus.isOperationalState - ? `All` - : 'Some' - } Resources are ${ - currentStatus.name?.toLowerCase() === - 'maintenance' - ? 'under' - : '' - } ${currentStatus.name}`} - color={currentStatus.color} - doNotShowIcon={true} - textClassName="text-white text-lg" - id="overview-alert" - /> - )} - </div> - - {statusPageResources.length > 0 && ( - <div className="bg-white pl-5 pr-5 mt-5 rounded-xl shadow space-y-5 mb-6"> - <AccordionGroup> - {statusPageResources.filter( - (resources: StatusPageResource) => { - return !resources.statusPageGroupId; - } - ).length > 0 ? ( - <Accordion - key={Math.random()} - title={undefined} - isLastElement={ - resourceGroups.length === 0 - } - > - {getMonitorOverviewListInGroup(null)} - </Accordion> - ) : ( - <></> - )} - <div - key={Math.random()} - style={{ - padding: '0px', - }} - > - {resourceGroups.length > 0 && - resourceGroups.map( - ( - resourceGroup: StatusPageGroup, - i: number - ) => { - return ( - <Accordion - key={i} - rightElement={getRightAccordionElement( - resourceGroup - )} - isInitiallyExpanded={ - resourceGroup.isExpandedByDefault - } - isLastElement={ - resourceGroups.length - - 1 === - i - } - title={ - resourceGroup.name! - } - description={ - resourceGroup.description! - } - > - {getMonitorOverviewListInGroup( - resourceGroup - )} - </Accordion> - ); - } - )} - </div> - </AccordionGroup> - </div> - )} - - {/* Load Active Incident */} - {activeIncidentsInIncidentGroup.length > 0 && ( - <div id="incidents-list mt-2"> - <Section title="Active Incidents" /> - {activeIncidentsInIncidentGroup.map( - (incidentGroup: IncidentGroup, i: number) => { - return ( - <EventItem - {...getIncidentEventItem({ - incident: - incidentGroup.incident, - incidentPublicNotes: - incidentGroup.publicNotes || - [], - incidentStateTimelines: - incidentGroup.incidentStateTimelines, - statusPageResources: - incidentGroup.incidentResources, - monitorsInGroup: - incidentGroup.monitorsInGroup, - isPreviewPage: - StatusPageUtil.isPreviewPage(), - isSummary: true, - })} - isDetailItem={false} - key={i} - /> - ); - } - )} - </div> - )} - - {/* Load Active ScheduledEvent */} - {activeScheduledMaintenanceEventsInScheduledMaintenanceGroup && - activeScheduledMaintenanceEventsInScheduledMaintenanceGroup.length > - 0 && ( - <div id="scheduled-events-list mt-2"> - <Section title="Scheduled Maintenance Events" /> - {activeScheduledMaintenanceEventsInScheduledMaintenanceGroup.map( - ( - scheduledEventGroup: ScheduledMaintenanceGroup, - i: number - ) => { - return ( - <EventItem - key={i} - {...getScheduledEventEventItem({ - scheduledMaintenance: - scheduledEventGroup.scheduledMaintenance, - scheduledMaintenanceEventsPublicNotes: - scheduledEventGroup.publicNotes || - [], - scheduledMaintenanceStateTimelines: - scheduledEventGroup.scheduledMaintenanceStateTimelines, - statusPageResources: - scheduledEventGroup.scheduledEventResources, - monitorsInGroup: - scheduledEventGroup.monitorsInGroup, - isPreviewPage: - StatusPageUtil.isPreviewPage(), - isSummary: true, - })} - isDetailItem={false} - /> - ); - } - )} - </div> - )} - - {activeIncidentsInIncidentGroup.length === 0 && - activeScheduledMaintenanceEventsInScheduledMaintenanceGroup.length === - 0 && - statusPageResources.length === 0 && - activeAnnouncements.length === 0 && - !isLoading && - !error && ( - <EmptyState - id="overview-empty-state" - icon={IconProp.CheckCircle} - title={'Everything looks great'} - description="No resources added to this status page yet. Please add some resources from the dashboard." - /> - )} - </div> - ) : ( - <></> + {/* Load Active Announcement */} + <div id="announcements-list"> + {activeAnnouncements.map( + (announcement: StatusPageAnnouncement, i: number) => { + return ( + <EventItem + {...getAnnouncementEventItem({ + announcement, + isPreviewPage: StatusPageUtil.isPreviewPage(), + isSummary: true, + })} + isDetailItem={false} + key={i} + /> + ); + }, )} - </Page> - ); + </div> + + <div> + {currentStatus && statusPageResources.length > 0 && ( + <Alert + title={`${ + currentStatus.isOperationalState ? `All` : "Some" + } Resources are ${ + currentStatus.name?.toLowerCase() === "maintenance" + ? "under" + : "" + } ${currentStatus.name}`} + color={currentStatus.color} + doNotShowIcon={true} + textClassName="text-white text-lg" + id="overview-alert" + /> + )} + </div> + + {statusPageResources.length > 0 && ( + <div className="bg-white pl-5 pr-5 mt-5 rounded-xl shadow space-y-5 mb-6"> + <AccordionGroup> + {statusPageResources.filter((resources: StatusPageResource) => { + return !resources.statusPageGroupId; + }).length > 0 ? ( + <Accordion + key={Math.random()} + title={undefined} + isLastElement={resourceGroups.length === 0} + > + {getMonitorOverviewListInGroup(null)} + </Accordion> + ) : ( + <></> + )} + <div + key={Math.random()} + style={{ + padding: "0px", + }} + > + {resourceGroups.length > 0 && + resourceGroups.map( + (resourceGroup: StatusPageGroup, i: number) => { + return ( + <Accordion + key={i} + rightElement={getRightAccordionElement( + resourceGroup, + )} + isInitiallyExpanded={ + resourceGroup.isExpandedByDefault + } + isLastElement={resourceGroups.length - 1 === i} + title={resourceGroup.name!} + description={resourceGroup.description!} + > + {getMonitorOverviewListInGroup(resourceGroup)} + </Accordion> + ); + }, + )} + </div> + </AccordionGroup> + </div> + )} + + {/* Load Active Incident */} + {activeIncidentsInIncidentGroup.length > 0 && ( + <div id="incidents-list mt-2"> + <Section title="Active Incidents" /> + {activeIncidentsInIncidentGroup.map( + (incidentGroup: IncidentGroup, i: number) => { + return ( + <EventItem + {...getIncidentEventItem({ + incident: incidentGroup.incident, + incidentPublicNotes: incidentGroup.publicNotes || [], + incidentStateTimelines: + incidentGroup.incidentStateTimelines, + statusPageResources: incidentGroup.incidentResources, + monitorsInGroup: incidentGroup.monitorsInGroup, + isPreviewPage: StatusPageUtil.isPreviewPage(), + isSummary: true, + })} + isDetailItem={false} + key={i} + /> + ); + }, + )} + </div> + )} + + {/* Load Active ScheduledEvent */} + {activeScheduledMaintenanceEventsInScheduledMaintenanceGroup && + activeScheduledMaintenanceEventsInScheduledMaintenanceGroup.length > + 0 && ( + <div id="scheduled-events-list mt-2"> + <Section title="Scheduled Maintenance Events" /> + {activeScheduledMaintenanceEventsInScheduledMaintenanceGroup.map( + ( + scheduledEventGroup: ScheduledMaintenanceGroup, + i: number, + ) => { + return ( + <EventItem + key={i} + {...getScheduledEventEventItem({ + scheduledMaintenance: + scheduledEventGroup.scheduledMaintenance, + scheduledMaintenanceEventsPublicNotes: + scheduledEventGroup.publicNotes || [], + scheduledMaintenanceStateTimelines: + scheduledEventGroup.scheduledMaintenanceStateTimelines, + statusPageResources: + scheduledEventGroup.scheduledEventResources, + monitorsInGroup: scheduledEventGroup.monitorsInGroup, + isPreviewPage: StatusPageUtil.isPreviewPage(), + isSummary: true, + })} + isDetailItem={false} + /> + ); + }, + )} + </div> + )} + + {activeIncidentsInIncidentGroup.length === 0 && + activeScheduledMaintenanceEventsInScheduledMaintenanceGroup.length === + 0 && + statusPageResources.length === 0 && + activeAnnouncements.length === 0 && + !isLoading && + !error && ( + <EmptyState + id="overview-empty-state" + icon={IconProp.CheckCircle} + title={"Everything looks great"} + description="No resources added to this status page yet. Please add some resources from the dashboard." + /> + )} + </div> + ) : ( + <></> + )} + </Page> + ); }; export default Overview; diff --git a/StatusPage/src/Pages/PageComponentProps.ts b/StatusPage/src/Pages/PageComponentProps.ts index b0d24b9f29..fa90f81dd2 100644 --- a/StatusPage/src/Pages/PageComponentProps.ts +++ b/StatusPage/src/Pages/PageComponentProps.ts @@ -1,6 +1,6 @@ -import Route from 'Common/Types/API/Route'; +import Route from "Common/Types/API/Route"; export default interface ComponentProps { - pageRoute: Route; - onLoadComplete: () => void; + pageRoute: Route; + onLoadComplete: () => void; } diff --git a/StatusPage/src/Pages/ScheduledEvent/Detail.tsx b/StatusPage/src/Pages/ScheduledEvent/Detail.tsx index 63983d6f6b..d90ec3fe36 100644 --- a/StatusPage/src/Pages/ScheduledEvent/Detail.tsx +++ b/StatusPage/src/Pages/ScheduledEvent/Detail.tsx @@ -1,471 +1,450 @@ -import Page from '../../Components/Page/Page'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import PageComponentProps from '../PageComponentProps'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import { Gray500, Green, Yellow } from 'Common/Types/BrandColors'; -import Color from 'Common/Types/Color'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; +import Page from "../../Components/Page/Page"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import PageComponentProps from "../PageComponentProps"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import { Gray500, Green, Yellow } from "Common/Types/BrandColors"; +import Color from "Common/Types/Color"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; import EventItem, { - ComponentProps as EventItemComponentProps, - TimelineItem, - TimelineItemType, -} from 'CommonUI/src/Components/EventItem/EventItem'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import Label from 'Model/Models/Label'; -import Monitor from 'Model/Models/Monitor'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import StatusPageResource from 'Model/Models/StatusPageResource'; + ComponentProps as EventItemComponentProps, + TimelineItem, + TimelineItemType, +} from "CommonUI/src/Components/EventItem/EventItem"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import Label from "Model/Models/Label"; +import Monitor from "Model/Models/Monitor"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import StatusPageResource from "Model/Models/StatusPageResource"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; export type GetScheduledEventEventItemFunctionProps = { - scheduledMaintenance: ScheduledMaintenance; - scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote>; - scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline>; - statusPageResources: Array<StatusPageResource>; - monitorsInGroup: Dictionary<Array<ObjectID>>; - isPreviewPage: boolean; - isSummary: boolean; + scheduledMaintenance: ScheduledMaintenance; + scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote>; + scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline>; + statusPageResources: Array<StatusPageResource>; + monitorsInGroup: Dictionary<Array<ObjectID>>; + isPreviewPage: boolean; + isSummary: boolean; }; export type GetScheduledEventEventItemFunction = ( - props: GetScheduledEventEventItemFunctionProps + props: GetScheduledEventEventItemFunctionProps, ) => EventItemComponentProps; export const getScheduledEventEventItem: GetScheduledEventEventItemFunction = ( - props: GetScheduledEventEventItemFunctionProps + props: GetScheduledEventEventItemFunctionProps, ): EventItemComponentProps => { - const { - scheduledMaintenance, + const { + scheduledMaintenance, + scheduledMaintenanceEventsPublicNotes, + scheduledMaintenanceStateTimelines, + statusPageResources, + monitorsInGroup, + isPreviewPage, + isSummary, + } = props; + /// get timeline. + + let currentStateStatus: string = ""; + let currentStatusColor: Color = Green; + + const timeline: Array<TimelineItem> = []; + + if (isSummary) { + // If this is summary then reverse the order so we show the latest first + scheduledMaintenanceEventsPublicNotes.sort( + ( + a: ScheduledMaintenancePublicNote, + b: ScheduledMaintenancePublicNote, + ) => { + return OneUptimeDate.isAfter(a.postedAt!, b.postedAt!) === false + ? 1 + : -1; + }, + ); + + scheduledMaintenanceStateTimelines.sort( + ( + a: ScheduledMaintenanceStateTimeline, + b: ScheduledMaintenanceStateTimeline, + ) => { + return OneUptimeDate.isAfter(a.createdAt!, b.createdAt!) === false + ? 1 + : -1; + }, + ); + } + + for (const scheduledMaintenancePublicNote of scheduledMaintenanceEventsPublicNotes) { + if ( + scheduledMaintenancePublicNote.scheduledMaintenanceId?.toString() === + scheduledMaintenance.id?.toString() && + scheduledMaintenancePublicNote?.note + ) { + timeline.push({ + note: scheduledMaintenancePublicNote?.note || "", + date: scheduledMaintenancePublicNote?.postedAt as Date, + type: TimelineItemType.Note, + icon: IconProp.Chat, + iconColor: Gray500, + }); + + if (isSummary) { + break; + } + } + } + + for (const scheduledMaintenanceEventstateTimeline of scheduledMaintenanceStateTimelines) { + if ( + scheduledMaintenanceEventstateTimeline.scheduledMaintenanceId?.toString() === + scheduledMaintenance.id?.toString() && + scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState + ) { + timeline.push({ + state: scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState, + date: scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState + ?.isScheduledState + ? scheduledMaintenance.startsAt! + : (scheduledMaintenanceEventstateTimeline?.createdAt as Date), + type: TimelineItemType.StateChange, + icon: scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState + .isScheduledState + ? IconProp.Clock + : scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState + .isOngoingState + ? IconProp.Settings + : scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState + .isResolvedState + ? IconProp.CheckCircle + : IconProp.ArrowCircleRight, + iconColor: + scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState + .color || Gray500, + }); + + if (!currentStateStatus) { + currentStateStatus = + scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState + ?.name || ""; + currentStatusColor = + scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState + ?.color || Green; + } + + if (isSummary) { + break; + } + } + } + + timeline.sort((a: TimelineItem, b: TimelineItem) => { + return OneUptimeDate.isAfter(a.date, b.date) === true ? 1 : -1; + }); + + let namesOfResources: Array<StatusPageResource> = []; + + if (scheduledMaintenance.monitors) { + const monitorIdsInThisScheduledMaintenance: Array<string> = + scheduledMaintenance.monitors + .map((monitor: Monitor) => { + return monitor.id!.toString(); + }) + .filter((id: string) => { + return Boolean(id); + }); + + namesOfResources = statusPageResources.filter( + (resource: StatusPageResource) => { + return ( + resource.monitorId && + monitorIdsInThisScheduledMaintenance.includes( + resource.monitorId.toString(), + ) + ); + }, + ); + + // add names of the groups as well. + namesOfResources = namesOfResources.concat( + statusPageResources.filter((resource: StatusPageResource) => { + if (!resource.monitorGroupId) { + return false; + } + + const monitorGroupId: string = resource.monitorGroupId.toString(); + + const monitorIdsInThisGroup: Array<ObjectID> = + monitorsInGroup[monitorGroupId]!; + + for (const monitorId of monitorIdsInThisGroup) { + if ( + monitorIdsInThisScheduledMaintenance.find( + (id: string | undefined) => { + return id?.toString() === monitorId.toString(); + }, + ) + ) { + return true; + } + } + + return false; + }), + ); + } + + return { + eventTitle: scheduledMaintenance.title || "", + eventDescription: scheduledMaintenance.description, + eventTimeline: timeline, + eventType: "Scheduled Maintenance", + eventResourcesAffected: namesOfResources.map((i: StatusPageResource) => { + return i.displayName || ""; + }), + eventViewRoute: !isSummary + ? undefined + : RouteUtil.populateRouteParams( + isPreviewPage + ? (RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL] as Route) + : (RouteMap[PageMap.SCHEDULED_EVENT_DETAIL] as Route), + scheduledMaintenance.id!, + ), + isDetailItem: !isSummary, + currentStatus: currentStateStatus, + currentStatusColor: currentStatusColor, + eventTypeColor: Yellow, + eventSecondDescription: scheduledMaintenance.startsAt + ? "Scheduled at " + + OneUptimeDate.getDateAsLocalFormattedString( + scheduledMaintenance.startsAt!, + ) + : "", + labels: + scheduledMaintenance.labels?.map((label: Label) => { + return { + name: label.name!, + color: label.color!, + }; + }) || [], + }; +}; + +const Overview: FunctionComponent<PageComponentProps> = ( + props: PageComponentProps, +): ReactElement => { + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string | null>(null); + const [ + scheduledMaintenanceEventsPublicNotes, + setscheduledMaintenanceEventsPublicNotes, + ] = useState<Array<ScheduledMaintenancePublicNote>>([]); + const [scheduledMaintenanceEvent, setscheduledMaintenanceEvent] = + useState<ScheduledMaintenance | null>(null); + const [ + scheduledMaintenanceStateTimelines, + setscheduledMaintenanceStateTimelines, + ] = useState<Array<ScheduledMaintenanceStateTimeline>>([]); + const [parsedData, setParsedData] = useState<EventItemComponentProps | null>( + null, + ); + + const [monitorsInGroup, setMonitorsInGroup] = useState< + Dictionary<Array<ObjectID>> + >({}); + + const [statusPageResources, setStatusPageResources] = useState< + Array<StatusPageResource> + >([]); + + StatusPageUtil.checkIfUserHasLoggedIn(); + + useAsyncEffect(async () => { + try { + if (!StatusPageUtil.getStatusPageId()) { + return; + } + setIsLoading(true); + + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; + if (!id) { + throw new BadDataException("Status Page ID is required"); + } + + const eventId: string | undefined = + Navigation.getLastParamAsObjectID().toString(); + + const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/scheduled-maintenance-events/${id.toString()}/${eventId}`, + ), + {}, + API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!), + ); + + if (!response.isSuccess()) { + throw response; + } + const data: JSONObject = response.data; + + const scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = + BaseModel.fromJSONArray( + (data["scheduledMaintenanceEventsPublicNotes"] as JSONArray) || [], + ScheduledMaintenancePublicNote, + ); + + const rawAnnouncements: JSONArray = + (data["scheduledMaintenanceEvents"] as JSONArray) || []; + + const scheduledMaintenanceEvent: ScheduledMaintenance = + BaseModel.fromJSONObject( + (rawAnnouncements[0] as JSONObject) || {}, + ScheduledMaintenance, + ); + const scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = + BaseModel.fromJSONArray( + (data["scheduledMaintenanceStateTimelines"] as JSONArray) || [], + ScheduledMaintenanceStateTimeline, + ); + + const statusPageResources: Array<StatusPageResource> = + BaseModel.fromJSONArray( + (data["statusPageResources"] as JSONArray) || [], + StatusPageResource, + ); + + const monitorsInGroup: Dictionary<Array<ObjectID>> = + JSONFunctions.deserialize( + (data["monitorsInGroup"] as JSONObject) || {}, + ) as Dictionary<Array<ObjectID>>; + + // save data. set() + setscheduledMaintenanceEventsPublicNotes( + scheduledMaintenanceEventsPublicNotes, + ); + setscheduledMaintenanceEvent(scheduledMaintenanceEvent); + setStatusPageResources(statusPageResources); + + setscheduledMaintenanceStateTimelines(scheduledMaintenanceStateTimelines); + + setMonitorsInGroup(monitorsInGroup); + + setIsLoading(false); + props.onLoadComplete(); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + await StatusPageUtil.checkIfTheUserIsAuthenticated(err); + } + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }, []); + + useEffect(() => { + if (isLoading) { + // parse data; + setParsedData(null); + return; + } + + if (!scheduledMaintenanceEvent) { + return; + } + setParsedData( + getScheduledEventEventItem({ + scheduledMaintenance: scheduledMaintenanceEvent, scheduledMaintenanceEventsPublicNotes, scheduledMaintenanceStateTimelines, statusPageResources, monitorsInGroup, - isPreviewPage, - isSummary, - } = props; - /// get timeline. - - let currentStateStatus: string = ''; - let currentStatusColor: Color = Green; - - const timeline: Array<TimelineItem> = []; - - if (isSummary) { - // If this is summary then reverse the order so we show the latest first - scheduledMaintenanceEventsPublicNotes.sort( - ( - a: ScheduledMaintenancePublicNote, - b: ScheduledMaintenancePublicNote - ) => { - return OneUptimeDate.isAfter(a.postedAt!, b.postedAt!) === false - ? 1 - : -1; - } - ); - - scheduledMaintenanceStateTimelines.sort( - ( - a: ScheduledMaintenanceStateTimeline, - b: ScheduledMaintenanceStateTimeline - ) => { - return OneUptimeDate.isAfter(a.createdAt!, b.createdAt!) === - false - ? 1 - : -1; - } - ); - } - - for (const scheduledMaintenancePublicNote of scheduledMaintenanceEventsPublicNotes) { - if ( - scheduledMaintenancePublicNote.scheduledMaintenanceId?.toString() === - scheduledMaintenance.id?.toString() && - scheduledMaintenancePublicNote?.note - ) { - timeline.push({ - note: scheduledMaintenancePublicNote?.note || '', - date: scheduledMaintenancePublicNote?.postedAt as Date, - type: TimelineItemType.Note, - icon: IconProp.Chat, - iconColor: Gray500, - }); - - if (isSummary) { - break; - } - } - } - - for (const scheduledMaintenanceEventstateTimeline of scheduledMaintenanceStateTimelines) { - if ( - scheduledMaintenanceEventstateTimeline.scheduledMaintenanceId?.toString() === - scheduledMaintenance.id?.toString() && - scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState - ) { - timeline.push({ - state: scheduledMaintenanceEventstateTimeline.scheduledMaintenanceState, - date: scheduledMaintenanceEventstateTimeline - .scheduledMaintenanceState?.isScheduledState - ? scheduledMaintenance.startsAt! - : (scheduledMaintenanceEventstateTimeline?.createdAt as Date), - type: TimelineItemType.StateChange, - icon: scheduledMaintenanceEventstateTimeline - .scheduledMaintenanceState.isScheduledState - ? IconProp.Clock - : scheduledMaintenanceEventstateTimeline - .scheduledMaintenanceState.isOngoingState - ? IconProp.Settings - : scheduledMaintenanceEventstateTimeline - .scheduledMaintenanceState.isResolvedState - ? IconProp.CheckCircle - : IconProp.ArrowCircleRight, - iconColor: - scheduledMaintenanceEventstateTimeline - .scheduledMaintenanceState.color || Gray500, - }); - - if (!currentStateStatus) { - currentStateStatus = - scheduledMaintenanceEventstateTimeline - .scheduledMaintenanceState?.name || ''; - currentStatusColor = - scheduledMaintenanceEventstateTimeline - .scheduledMaintenanceState?.color || Green; - } - - if (isSummary) { - break; - } - } - } - - timeline.sort((a: TimelineItem, b: TimelineItem) => { - return OneUptimeDate.isAfter(a.date, b.date) === true ? 1 : -1; - }); - - let namesOfResources: Array<StatusPageResource> = []; - - if (scheduledMaintenance.monitors) { - const monitorIdsInThisScheduledMaintenance: Array<string> = - scheduledMaintenance.monitors - .map((monitor: Monitor) => { - return monitor.id!.toString(); - }) - .filter((id: string) => { - return Boolean(id); - }); - - namesOfResources = statusPageResources.filter( - (resource: StatusPageResource) => { - return ( - resource.monitorId && - monitorIdsInThisScheduledMaintenance.includes( - resource.monitorId.toString() - ) - ); - } - ); - - // add names of the groups as well. - namesOfResources = namesOfResources.concat( - statusPageResources.filter((resource: StatusPageResource) => { - if (!resource.monitorGroupId) { - return false; - } - - const monitorGroupId: string = - resource.monitorGroupId.toString(); - - const monitorIdsInThisGroup: Array<ObjectID> = - monitorsInGroup[monitorGroupId]!; - - for (const monitorId of monitorIdsInThisGroup) { - if ( - monitorIdsInThisScheduledMaintenance.find( - (id: string | undefined) => { - return id?.toString() === monitorId.toString(); - } - ) - ) { - return true; - } - } - - return false; - }) - ); - } - - return { - eventTitle: scheduledMaintenance.title || '', - eventDescription: scheduledMaintenance.description, - eventTimeline: timeline, - eventType: 'Scheduled Maintenance', - eventResourcesAffected: namesOfResources.map( - (i: StatusPageResource) => { - return i.displayName || ''; - } - ), - eventViewRoute: !isSummary - ? undefined - : RouteUtil.populateRouteParams( - isPreviewPage - ? (RouteMap[ - PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL - ] as Route) - : (RouteMap[PageMap.SCHEDULED_EVENT_DETAIL] as Route), - scheduledMaintenance.id! - ), - isDetailItem: !isSummary, - currentStatus: currentStateStatus, - currentStatusColor: currentStatusColor, - eventTypeColor: Yellow, - eventSecondDescription: scheduledMaintenance.startsAt - ? 'Scheduled at ' + - OneUptimeDate.getDateAsLocalFormattedString( - scheduledMaintenance.startsAt! - ) - : '', - labels: - scheduledMaintenance.labels?.map((label: Label) => { - return { - name: label.name!, - color: label.color!, - }; - }) || [], - }; -}; - -const Overview: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps -): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - const [ - scheduledMaintenanceEventsPublicNotes, - setscheduledMaintenanceEventsPublicNotes, - ] = useState<Array<ScheduledMaintenancePublicNote>>([]); - const [scheduledMaintenanceEvent, setscheduledMaintenanceEvent] = - useState<ScheduledMaintenance | null>(null); - const [ - scheduledMaintenanceStateTimelines, - setscheduledMaintenanceStateTimelines, - ] = useState<Array<ScheduledMaintenanceStateTimeline>>([]); - const [parsedData, setParsedData] = - useState<EventItemComponentProps | null>(null); - - const [monitorsInGroup, setMonitorsInGroup] = useState< - Dictionary<Array<ObjectID>> - >({}); - - const [statusPageResources, setStatusPageResources] = useState< - Array<StatusPageResource> - >([]); - - StatusPageUtil.checkIfUserHasLoggedIn(); - - useAsyncEffect(async () => { - try { - if (!StatusPageUtil.getStatusPageId()) { - return; - } - setIsLoading(true); - - const id: ObjectID = LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; - if (!id) { - throw new BadDataException('Status Page ID is required'); - } - - const eventId: string | undefined = - Navigation.getLastParamAsObjectID().toString(); - - const response: HTTPResponse<JSONObject> = - await API.post<JSONObject>( - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/scheduled-maintenance-events/${id.toString()}/${eventId}` - ), - {}, - API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!) - ); - - if (!response.isSuccess()) { - throw response; - } - const data: JSONObject = response.data; - - const scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = - BaseModel.fromJSONArray( - (data[ - 'scheduledMaintenanceEventsPublicNotes' - ] as JSONArray) || [], - ScheduledMaintenancePublicNote - ); - - const rawAnnouncements: JSONArray = - (data['scheduledMaintenanceEvents'] as JSONArray) || []; - - const scheduledMaintenanceEvent: ScheduledMaintenance = - BaseModel.fromJSONObject( - (rawAnnouncements[0] as JSONObject) || {}, - ScheduledMaintenance - ); - const scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = - BaseModel.fromJSONArray( - (data['scheduledMaintenanceStateTimelines'] as JSONArray) || - [], - ScheduledMaintenanceStateTimeline - ); - - const statusPageResources: Array<StatusPageResource> = - BaseModel.fromJSONArray( - (data['statusPageResources'] as JSONArray) || [], - StatusPageResource - ); - - const monitorsInGroup: Dictionary<Array<ObjectID>> = - JSONFunctions.deserialize( - (data['monitorsInGroup'] as JSONObject) || {} - ) as Dictionary<Array<ObjectID>>; - - // save data. set() - setscheduledMaintenanceEventsPublicNotes( - scheduledMaintenanceEventsPublicNotes - ); - setscheduledMaintenanceEvent(scheduledMaintenanceEvent); - setStatusPageResources(statusPageResources); - - setscheduledMaintenanceStateTimelines( - scheduledMaintenanceStateTimelines - ); - - setMonitorsInGroup(monitorsInGroup); - - setIsLoading(false); - props.onLoadComplete(); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - await StatusPageUtil.checkIfTheUserIsAuthenticated(err); - } - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }, []); - - useEffect(() => { - if (isLoading) { - // parse data; - setParsedData(null); - return; - } - - if (!scheduledMaintenanceEvent) { - return; - } - setParsedData( - getScheduledEventEventItem({ - scheduledMaintenance: scheduledMaintenanceEvent, - scheduledMaintenanceEventsPublicNotes, - scheduledMaintenanceStateTimelines, - statusPageResources, - monitorsInGroup, - isPreviewPage: Boolean(StatusPageUtil.isPreviewPage()), - isSummary: false, - }) - ); - }, [isLoading]); - - if (isLoading) { - return <PageLoader isVisible={true} />; - } - - if (error) { - return <ErrorMessage error={error} />; - } - - if (!parsedData) { - return <PageLoader isVisible={true} />; - } - - return ( - <Page - title="Scheduled Event Report" - breadcrumbLinks={[ - { - title: 'Overview', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) - : (RouteMap[PageMap.OVERVIEW] as Route) - ), - }, - { - title: 'Scheduled Events', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[ - PageMap.PREVIEW_SCHEDULED_EVENT_LIST - ] as Route) - : (RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route) - ), - }, - { - title: 'Scheduled Event', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[ - PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL - ] as Route) - : (RouteMap[ - PageMap.SCHEDULED_EVENT_DETAIL - ] as Route), - Navigation.getLastParamAsObjectID() - ), - }, - ]} - > - {scheduledMaintenanceEvent ? <EventItem {...parsedData} /> : <></>} - {!scheduledMaintenanceEvent ? ( - <EmptyState - id="scheduled-event-empty-state" - title={'No Scheduled Event'} - description={ - 'No scheduled event found for this status page.' - } - icon={IconProp.Clock} - /> - ) : ( - <></> - )} - </Page> + isPreviewPage: Boolean(StatusPageUtil.isPreviewPage()), + isSummary: false, + }), ); + }, [isLoading]); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + if (!parsedData) { + return <PageLoader isVisible={true} />; + } + + return ( + <Page + title="Scheduled Event Report" + breadcrumbLinks={[ + { + title: "Overview", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + ), + }, + { + title: "Scheduled Events", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_LIST] as Route) + : (RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route), + ), + }, + { + title: "Scheduled Event", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL] as Route) + : (RouteMap[PageMap.SCHEDULED_EVENT_DETAIL] as Route), + Navigation.getLastParamAsObjectID(), + ), + }, + ]} + > + {scheduledMaintenanceEvent ? <EventItem {...parsedData} /> : <></>} + {!scheduledMaintenanceEvent ? ( + <EmptyState + id="scheduled-event-empty-state" + title={"No Scheduled Event"} + description={"No scheduled event found for this status page."} + icon={IconProp.Clock} + /> + ) : ( + <></> + )} + </Page> + ); }; export default Overview; diff --git a/StatusPage/src/Pages/ScheduledEvent/List.tsx b/StatusPage/src/Pages/ScheduledEvent/List.tsx index c1f7d15697..1c698da4c3 100644 --- a/StatusPage/src/Pages/ScheduledEvent/List.tsx +++ b/StatusPage/src/Pages/ScheduledEvent/List.tsx @@ -1,400 +1,374 @@ -import Page from '../../Components/Page/Page'; -import Section from '../../Components/Section/Section'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import PageComponentProps from '../PageComponentProps'; -import { getScheduledEventEventItem } from './Detail'; -import BaseModel from 'Common/Models/BaseModel'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import HTTPResponse from 'Common/Types/API/HTTPResponse'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import OneUptimeDate from 'Common/Types/Date'; -import Dictionary from 'Common/Types/Dictionary'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import IconProp from 'Common/Types/Icon/IconProp'; -import { JSONArray, JSONObject } from 'Common/Types/JSON'; -import JSONFunctions from 'Common/Types/JSONFunctions'; -import ObjectID from 'Common/Types/ObjectID'; -import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; -import { ComponentProps as EventHistoryDayListComponentProps } from 'CommonUI/src/Components/EventHistoryList/EventHistoryDayList'; +import Page from "../../Components/Page/Page"; +import Section from "../../Components/Section/Section"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import PageComponentProps from "../PageComponentProps"; +import { getScheduledEventEventItem } from "./Detail"; +import BaseModel from "Common/Models/BaseModel"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import HTTPResponse from "Common/Types/API/HTTPResponse"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import OneUptimeDate from "Common/Types/Date"; +import Dictionary from "Common/Types/Dictionary"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import IconProp from "Common/Types/Icon/IconProp"; +import { JSONArray, JSONObject } from "Common/Types/JSON"; +import JSONFunctions from "Common/Types/JSONFunctions"; +import ObjectID from "Common/Types/ObjectID"; +import EmptyState from "CommonUI/src/Components/EmptyState/EmptyState"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; +import { ComponentProps as EventHistoryDayListComponentProps } from "CommonUI/src/Components/EventHistoryList/EventHistoryDayList"; import EventHistoryList, { - ComponentProps as EventHistoryListComponentProps, -} from 'CommonUI/src/Components/EventHistoryList/EventHistoryList'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import StatusPageResource from 'Model/Models/StatusPageResource'; + ComponentProps as EventHistoryListComponentProps, +} from "CommonUI/src/Components/EventHistoryList/EventHistoryList"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import StatusPageResource from "Model/Models/StatusPageResource"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; -import useAsyncEffect from 'use-async-effect'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import useAsyncEffect from "use-async-effect"; const Overview: FunctionComponent<PageComponentProps> = ( - props: PageComponentProps + props: PageComponentProps, ): ReactElement => { - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - const [ - scheduledMaintenanceEventsPublicNotes, - setscheduledMaintenanceEventsPublicNotes, - ] = useState<Array<ScheduledMaintenancePublicNote>>([]); - const [scheduledMaintenanceEvents, setscheduledMaintenanceEvents] = - useState<Array<ScheduledMaintenance>>([]); - const [ - scheduledMaintenanceStateTimelines, - setscheduledMaintenanceStateTimelines, - ] = useState<Array<ScheduledMaintenanceStateTimeline>>([]); + const [isLoading, setIsLoading] = useState<boolean>(true); + const [error, setError] = useState<string | null>(null); + const [ + scheduledMaintenanceEventsPublicNotes, + setscheduledMaintenanceEventsPublicNotes, + ] = useState<Array<ScheduledMaintenancePublicNote>>([]); + const [scheduledMaintenanceEvents, setscheduledMaintenanceEvents] = useState< + Array<ScheduledMaintenance> + >([]); + const [ + scheduledMaintenanceStateTimelines, + setscheduledMaintenanceStateTimelines, + ] = useState<Array<ScheduledMaintenanceStateTimeline>>([]); - const [ongoingEventsParsedData, setOngoingEventsParsedData] = - useState<EventHistoryListComponentProps | null>(null); - const [scheduledEventsParsedData, setScheduledEventsParsedData] = - useState<EventHistoryListComponentProps | null>(null); - const [endedEventsParsedData, setEndedEventsParsedData] = - useState<EventHistoryListComponentProps | null>(null); + const [ongoingEventsParsedData, setOngoingEventsParsedData] = + useState<EventHistoryListComponentProps | null>(null); + const [scheduledEventsParsedData, setScheduledEventsParsedData] = + useState<EventHistoryListComponentProps | null>(null); + const [endedEventsParsedData, setEndedEventsParsedData] = + useState<EventHistoryListComponentProps | null>(null); - const [statusPageResources, setStatusPageResources] = useState< - Array<StatusPageResource> - >([]); + const [statusPageResources, setStatusPageResources] = useState< + Array<StatusPageResource> + >([]); - const [monitorsInGroup, setMonitorsInGroup] = useState< - Dictionary<Array<ObjectID>> - >({}); + const [monitorsInGroup, setMonitorsInGroup] = useState< + Dictionary<Array<ObjectID>> + >({}); - const [scheduledMaintenanceStates, setScheduledMaintenanceStates] = - useState<Array<ScheduledMaintenanceState>>([]); + const [scheduledMaintenanceStates, setScheduledMaintenanceStates] = useState< + Array<ScheduledMaintenanceState> + >([]); - StatusPageUtil.checkIfUserHasLoggedIn(); + StatusPageUtil.checkIfUserHasLoggedIn(); - type GetEventHistoryFunctionProps = { - scheduledMaintenanceEvents: ScheduledMaintenance[]; - scheduledMaintenanceEventsPublicNotes: ScheduledMaintenancePublicNote[]; - scheduledMaintenanceStateTimelines: ScheduledMaintenanceStateTimeline[]; - statusPageResources: StatusPageResource[]; - monitorsInGroup: Dictionary<ObjectID[]>; + type GetEventHistoryFunctionProps = { + scheduledMaintenanceEvents: ScheduledMaintenance[]; + scheduledMaintenanceEventsPublicNotes: ScheduledMaintenancePublicNote[]; + scheduledMaintenanceStateTimelines: ScheduledMaintenanceStateTimeline[]; + statusPageResources: StatusPageResource[]; + monitorsInGroup: Dictionary<ObjectID[]>; + }; + + type GetEventHistoryFunction = ( + data: GetEventHistoryFunctionProps, + ) => EventHistoryListComponentProps; + + const getEventHistoryListComponentProps: GetEventHistoryFunction = ( + data: GetEventHistoryFunctionProps, + ): EventHistoryListComponentProps => { + const { + scheduledMaintenanceEvents, + scheduledMaintenanceEventsPublicNotes, + scheduledMaintenanceStateTimelines, + statusPageResources, + monitorsInGroup, + } = data; + + const eventHistoryListComponentProps: EventHistoryListComponentProps = { + items: [], }; - type GetEventHistoryFunction = ( - data: GetEventHistoryFunctionProps - ) => EventHistoryListComponentProps; + const days: Dictionary<EventHistoryDayListComponentProps> = {}; - const getEventHistoryListComponentProps: GetEventHistoryFunction = ( - data: GetEventHistoryFunctionProps - ): EventHistoryListComponentProps => { - const { - scheduledMaintenanceEvents, - scheduledMaintenanceEventsPublicNotes, - scheduledMaintenanceStateTimelines, - statusPageResources, - monitorsInGroup, - } = data; + for (const scheduledMaintenance of scheduledMaintenanceEvents) { + const dayString: string = OneUptimeDate.getDateString( + scheduledMaintenance.startsAt!, + ); - const eventHistoryListComponentProps: EventHistoryListComponentProps = { - items: [], + if (!days[dayString]) { + days[dayString] = { + date: scheduledMaintenance.startsAt!, + items: [], }; + } - const days: Dictionary<EventHistoryDayListComponentProps> = {}; + days[dayString]?.items.push( + getScheduledEventEventItem({ + scheduledMaintenance: scheduledMaintenance, + scheduledMaintenanceEventsPublicNotes, + scheduledMaintenanceStateTimelines, + statusPageResources, + monitorsInGroup, + isPreviewPage: Boolean(StatusPageUtil.isPreviewPage()), + isSummary: true, + }), + ); + } - for (const scheduledMaintenance of scheduledMaintenanceEvents) { - const dayString: string = OneUptimeDate.getDateString( - scheduledMaintenance.startsAt! - ); + for (const key in days) { + eventHistoryListComponentProps.items.push( + days[key] as EventHistoryDayListComponentProps, + ); + } + return eventHistoryListComponentProps; + }; - if (!days[dayString]) { - days[dayString] = { - date: scheduledMaintenance.startsAt!, - items: [], - }; - } + useAsyncEffect(async () => { + try { + if (!StatusPageUtil.getStatusPageId()) { + return; + } + setIsLoading(true); - days[dayString]?.items.push( - getScheduledEventEventItem({ - scheduledMaintenance: scheduledMaintenance, - scheduledMaintenanceEventsPublicNotes, - scheduledMaintenanceStateTimelines, - statusPageResources, - monitorsInGroup, - isPreviewPage: Boolean(StatusPageUtil.isPreviewPage()), - isSummary: true, - }) - ); - } + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; + if (!id) { + throw new BadDataException("Status Page ID is required"); + } + const response: HTTPResponse<JSONObject> = await API.post<JSONObject>( + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/scheduled-maintenance-events/${id.toString()}`, + ), + {}, + API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!), + ); - for (const key in days) { - eventHistoryListComponentProps.items.push( - days[key] as EventHistoryDayListComponentProps - ); - } - return eventHistoryListComponentProps; - }; + if (!response.isSuccess()) { + throw response; + } + const data: JSONObject = response.data; - useAsyncEffect(async () => { - try { - if (!StatusPageUtil.getStatusPageId()) { - return; - } - setIsLoading(true); + const scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = + BaseModel.fromJSONArray( + (data["scheduledMaintenanceEventsPublicNotes"] as JSONArray) || [], + ScheduledMaintenancePublicNote, + ); + const scheduledMaintenanceEvents: Array<ScheduledMaintenance> = + BaseModel.fromJSONArray( + (data["scheduledMaintenanceEvents"] as JSONArray) || [], + ScheduledMaintenance, + ); + const scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = + BaseModel.fromJSONArray( + (data["scheduledMaintenanceStateTimelines"] as JSONArray) || [], + ScheduledMaintenanceStateTimeline, + ); - const id: ObjectID = LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; - if (!id) { - throw new BadDataException('Status Page ID is required'); - } - const response: HTTPResponse<JSONObject> = - await API.post<JSONObject>( - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/scheduled-maintenance-events/${id.toString()}` - ), - {}, - API.getDefaultHeaders(StatusPageUtil.getStatusPageId()!) - ); + const statusPageResources: Array<StatusPageResource> = + BaseModel.fromJSONArray( + (data["statusPageResources"] as JSONArray) || [], + StatusPageResource, + ); - if (!response.isSuccess()) { - throw response; - } - const data: JSONObject = response.data; + const monitorsInGroup: Dictionary<Array<ObjectID>> = + JSONFunctions.deserialize( + (data["monitorsInGroup"] as JSONObject) || {}, + ) as Dictionary<Array<ObjectID>>; - const scheduledMaintenanceEventsPublicNotes: Array<ScheduledMaintenancePublicNote> = - BaseModel.fromJSONArray( - (data[ - 'scheduledMaintenanceEventsPublicNotes' - ] as JSONArray) || [], - ScheduledMaintenancePublicNote - ); - const scheduledMaintenanceEvents: Array<ScheduledMaintenance> = - BaseModel.fromJSONArray( - (data['scheduledMaintenanceEvents'] as JSONArray) || [], - ScheduledMaintenance - ); - const scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline> = - BaseModel.fromJSONArray( - (data['scheduledMaintenanceStateTimelines'] as JSONArray) || - [], - ScheduledMaintenanceStateTimeline - ); + const scheduledMaintenanceStates: Array<ScheduledMaintenanceState> = + BaseModel.fromJSONArray( + (data["scheduledMaintenanceStates"] as JSONArray) || [], + ScheduledMaintenanceState, + ); - const statusPageResources: Array<StatusPageResource> = - BaseModel.fromJSONArray( - (data['statusPageResources'] as JSONArray) || [], - StatusPageResource - ); + setScheduledMaintenanceStates(scheduledMaintenanceStates); + setStatusPageResources(statusPageResources); + setMonitorsInGroup(monitorsInGroup); - const monitorsInGroup: Dictionary<Array<ObjectID>> = - JSONFunctions.deserialize( - (data['monitorsInGroup'] as JSONObject) || {} - ) as Dictionary<Array<ObjectID>>; + // save data. set() + setscheduledMaintenanceEventsPublicNotes( + scheduledMaintenanceEventsPublicNotes, + ); + setscheduledMaintenanceEvents(scheduledMaintenanceEvents); + setscheduledMaintenanceStateTimelines(scheduledMaintenanceStateTimelines); - const scheduledMaintenanceStates: Array<ScheduledMaintenanceState> = - BaseModel.fromJSONArray( - (data['scheduledMaintenanceStates'] as JSONArray) || [], - ScheduledMaintenanceState - ); - - setScheduledMaintenanceStates(scheduledMaintenanceStates); - setStatusPageResources(statusPageResources); - setMonitorsInGroup(monitorsInGroup); - - // save data. set() - setscheduledMaintenanceEventsPublicNotes( - scheduledMaintenanceEventsPublicNotes - ); - setscheduledMaintenanceEvents(scheduledMaintenanceEvents); - setscheduledMaintenanceStateTimelines( - scheduledMaintenanceStateTimelines - ); - - setIsLoading(false); - props.onLoadComplete(); - } catch (err) { - if (err instanceof HTTPErrorResponse) { - await StatusPageUtil.checkIfTheUserIsAuthenticated(err); - } - setError(API.getFriendlyMessage(err)); - setIsLoading(false); - } - }, []); - - useEffect(() => { - if (isLoading) { - // parse data; - setOngoingEventsParsedData(null); - setScheduledEventsParsedData(null); - setEndedEventsParsedData(null); - return; - } - - const ongoingOrder: number = - scheduledMaintenanceStates.find( - (state: ScheduledMaintenanceState) => { - return state.isOngoingState; - } - )?.order || 0; - - const endedEventOrder: number = - scheduledMaintenanceStates.find( - (state: ScheduledMaintenanceState) => { - return state.isEndedState; - } - )?.order || 0; - - // get ongoing events - anything after ongoing state but before ended state - - const ongoingEvents: ScheduledMaintenance[] = - scheduledMaintenanceEvents.filter((event: ScheduledMaintenance) => { - return ( - event.currentScheduledMaintenanceState!.order! >= - ongoingOrder && - event.currentScheduledMaintenanceState!.order! < - endedEventOrder - ); - }); - - // get scheduled events - anything before ongoing state - - const scheduledEvents: ScheduledMaintenance[] = - scheduledMaintenanceEvents.filter((event: ScheduledMaintenance) => { - return ( - event.currentScheduledMaintenanceState!.order! < - ongoingOrder - ); - }); - - // get ended events - anythign equalTo or after ended state - - const endedEvents: ScheduledMaintenance[] = - scheduledMaintenanceEvents.filter((event: ScheduledMaintenance) => { - return ( - event.currentScheduledMaintenanceState!.order! >= - endedEventOrder - ); - }); - - const endedEventProps: EventHistoryListComponentProps = - getEventHistoryListComponentProps({ - scheduledMaintenanceEvents: endedEvents, - scheduledMaintenanceEventsPublicNotes, - scheduledMaintenanceStateTimelines, - statusPageResources, - monitorsInGroup, - }); - const scheduledEventProps: EventHistoryListComponentProps = - getEventHistoryListComponentProps({ - scheduledMaintenanceEvents: scheduledEvents, - scheduledMaintenanceEventsPublicNotes, - scheduledMaintenanceStateTimelines, - statusPageResources, - monitorsInGroup, - }); - const ongoingEventProps: EventHistoryListComponentProps = - getEventHistoryListComponentProps({ - scheduledMaintenanceEvents: ongoingEvents, - scheduledMaintenanceEventsPublicNotes, - scheduledMaintenanceStateTimelines, - statusPageResources, - monitorsInGroup, - }); - - setOngoingEventsParsedData(ongoingEventProps); - setScheduledEventsParsedData(scheduledEventProps); - setEndedEventsParsedData(endedEventProps); - }, [isLoading]); + setIsLoading(false); + props.onLoadComplete(); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + await StatusPageUtil.checkIfTheUserIsAuthenticated(err); + } + setError(API.getFriendlyMessage(err)); + setIsLoading(false); + } + }, []); + useEffect(() => { if (isLoading) { - return <PageLoader isVisible={true} />; + // parse data; + setOngoingEventsParsedData(null); + setScheduledEventsParsedData(null); + setEndedEventsParsedData(null); + return; } - if (error) { - return <ErrorMessage error={error} />; - } + const ongoingOrder: number = + scheduledMaintenanceStates.find((state: ScheduledMaintenanceState) => { + return state.isOngoingState; + })?.order || 0; - return ( - <Page - title="Scheduled Events" - breadcrumbLinks={[ - { - title: 'Overview', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) - : (RouteMap[PageMap.OVERVIEW] as Route) - ), - }, - { - title: 'Scheduled Events', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[ - PageMap.PREVIEW_SCHEDULED_EVENT_LIST - ] as Route) - : (RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route) - ), - }, - ]} - > - {ongoingEventsParsedData?.items && - ongoingEventsParsedData?.items.length > 0 ? ( - <div> - <Section title="Ongoing Events" /> + const endedEventOrder: number = + scheduledMaintenanceStates.find((state: ScheduledMaintenanceState) => { + return state.isEndedState; + })?.order || 0; - <EventHistoryList - items={ongoingEventsParsedData?.items || []} - /> - </div> - ) : ( - <></> - )} + // get ongoing events - anything after ongoing state but before ended state - {scheduledEventsParsedData?.items && - scheduledEventsParsedData?.items.length > 0 ? ( - <div> - <Section title="Scheduled Events" /> + const ongoingEvents: ScheduledMaintenance[] = + scheduledMaintenanceEvents.filter((event: ScheduledMaintenance) => { + return ( + event.currentScheduledMaintenanceState!.order! >= ongoingOrder && + event.currentScheduledMaintenanceState!.order! < endedEventOrder + ); + }); - <EventHistoryList - items={scheduledEventsParsedData?.items || []} - /> - </div> - ) : ( - <></> - )} + // get scheduled events - anything before ongoing state - {endedEventsParsedData?.items && - endedEventsParsedData?.items.length > 0 ? ( - <div> - <Section title="Completed Events" /> + const scheduledEvents: ScheduledMaintenance[] = + scheduledMaintenanceEvents.filter((event: ScheduledMaintenance) => { + return event.currentScheduledMaintenanceState!.order! < ongoingOrder; + }); - <EventHistoryList - items={endedEventsParsedData?.items || []} - /> - </div> - ) : ( - <></> - )} + // get ended events - anythign equalTo or after ended state - {scheduledMaintenanceEvents.length === 0 ? ( - <EmptyState - id="scheduled-events-empty-state" - title={'No Scheduled Events'} - description={ - 'No scheduled events posted for this status page.' - } - icon={IconProp.Clock} - /> - ) : ( - <></> - )} - </Page> - ); + const endedEvents: ScheduledMaintenance[] = + scheduledMaintenanceEvents.filter((event: ScheduledMaintenance) => { + return ( + event.currentScheduledMaintenanceState!.order! >= endedEventOrder + ); + }); + + const endedEventProps: EventHistoryListComponentProps = + getEventHistoryListComponentProps({ + scheduledMaintenanceEvents: endedEvents, + scheduledMaintenanceEventsPublicNotes, + scheduledMaintenanceStateTimelines, + statusPageResources, + monitorsInGroup, + }); + const scheduledEventProps: EventHistoryListComponentProps = + getEventHistoryListComponentProps({ + scheduledMaintenanceEvents: scheduledEvents, + scheduledMaintenanceEventsPublicNotes, + scheduledMaintenanceStateTimelines, + statusPageResources, + monitorsInGroup, + }); + const ongoingEventProps: EventHistoryListComponentProps = + getEventHistoryListComponentProps({ + scheduledMaintenanceEvents: ongoingEvents, + scheduledMaintenanceEventsPublicNotes, + scheduledMaintenanceStateTimelines, + statusPageResources, + monitorsInGroup, + }); + + setOngoingEventsParsedData(ongoingEventProps); + setScheduledEventsParsedData(scheduledEventProps); + setEndedEventsParsedData(endedEventProps); + }, [isLoading]); + + if (isLoading) { + return <PageLoader isVisible={true} />; + } + + if (error) { + return <ErrorMessage error={error} />; + } + + return ( + <Page + title="Scheduled Events" + breadcrumbLinks={[ + { + title: "Overview", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + ), + }, + { + title: "Scheduled Events", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_SCHEDULED_EVENT_LIST] as Route) + : (RouteMap[PageMap.SCHEDULED_EVENT_LIST] as Route), + ), + }, + ]} + > + {ongoingEventsParsedData?.items && + ongoingEventsParsedData?.items.length > 0 ? ( + <div> + <Section title="Ongoing Events" /> + + <EventHistoryList items={ongoingEventsParsedData?.items || []} /> + </div> + ) : ( + <></> + )} + + {scheduledEventsParsedData?.items && + scheduledEventsParsedData?.items.length > 0 ? ( + <div> + <Section title="Scheduled Events" /> + + <EventHistoryList items={scheduledEventsParsedData?.items || []} /> + </div> + ) : ( + <></> + )} + + {endedEventsParsedData?.items && + endedEventsParsedData?.items.length > 0 ? ( + <div> + <Section title="Completed Events" /> + + <EventHistoryList items={endedEventsParsedData?.items || []} /> + </div> + ) : ( + <></> + )} + + {scheduledMaintenanceEvents.length === 0 ? ( + <EmptyState + id="scheduled-events-empty-state" + title={"No Scheduled Events"} + description={"No scheduled events posted for this status page."} + icon={IconProp.Clock} + /> + ) : ( + <></> + )} + </Page> + ); }; export default Overview; diff --git a/StatusPage/src/Pages/Subscribe/EmailSubscribe.tsx b/StatusPage/src/Pages/Subscribe/EmailSubscribe.tsx index 0a2ef6071c..3ac42e98a0 100644 --- a/StatusPage/src/Pages/Subscribe/EmailSubscribe.tsx +++ b/StatusPage/src/Pages/Subscribe/EmailSubscribe.tsx @@ -1,233 +1,221 @@ -import Page from '../../Components/Page/Page'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import SubscribeSideMenu from './SideMenu'; -import { SubscribePageProps } from './SubscribePageUtils'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import Card from 'CommonUI/src/Components/Card/Card'; -import { CategoryCheckboxOptionsAndCategories } from 'CommonUI/src/Components/CategoryCheckbox/Index'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; +import Page from "../../Components/Page/Page"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import SubscribeSideMenu from "./SideMenu"; +import { SubscribePageProps } from "./SubscribePageUtils"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import Card from "CommonUI/src/Components/Card/Card"; +import { CategoryCheckboxOptionsAndCategories } from "CommonUI/src/Components/CategoryCheckbox/Index"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; import ModelForm, { - FormType, - ModelField, -} from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import SubscriberUtil from 'CommonUI/src/Utils/StatusPage'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; + FormType, + ModelField, +} from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import SubscriberUtil from "CommonUI/src/Utils/StatusPage"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const SubscribePage: FunctionComponent<SubscribePageProps> = ( - props: SubscribePageProps + props: SubscribePageProps, ): ReactElement => { - const [isSuccess, setIsSuccess] = useState<boolean>(false); + const [isSuccess, setIsSuccess] = useState<boolean>(false); - const id: ObjectID = LocalStorage.getItem('statusPageId') as ObjectID; + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; - const [ - categoryCheckboxOptionsAndCategories, - setCategoryCheckboxOptionsAndCategories, - ] = useState<CategoryCheckboxOptionsAndCategories>({ - categories: [], - options: [], + const [ + categoryCheckboxOptionsAndCategories, + setCategoryCheckboxOptionsAndCategories, + ] = useState<CategoryCheckboxOptionsAndCategories>({ + categories: [], + options: [], + }); + const [isLaoding, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string | undefined>(undefined); + + const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = + async (): Promise<void> => { + try { + setIsLoading(true); + + const result: CategoryCheckboxOptionsAndCategories = + await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources( + id, + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/resources/${id.toString()}`, + ), + ); + + setCategoryCheckboxOptionsAndCategories(result); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchCheckboxOptionsAndCategories().catch((error: Error) => { + setError(error.message); }); - const [isLaoding, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string | undefined>(undefined); + }, []); - const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = - async (): Promise<void> => { - try { - setIsLoading(true); + if (!id) { + throw new BadDataException("Status Page ID is required"); + } - const result: CategoryCheckboxOptionsAndCategories = - await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources( - id, - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/resources/${id.toString()}` - ) - ); + StatusPageUtil.checkIfUserHasLoggedIn(); - setCategoryCheckboxOptionsAndCategories(result); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + const fields: Array<ModelField<StatusPageSubscriber>> = [ + { + field: { + subscriberEmail: true, + }, + title: "Your Email", + fieldType: FormFieldSchemaType.Email, + required: true, + placeholder: "subscriber@company.com", + }, + ]; - setIsLoading(false); - }; + if (props.allowSubscribersToChooseResources) { + fields.push({ + field: { + isSubscribedToAllResources: true, + }, + title: "Subscribe to All Resources", + description: + "Select this option if you want to subscribe to all resources.", + fieldType: FormFieldSchemaType.Checkbox, + required: false, + defaultValue: true, + }); - useEffect(() => { - fetchCheckboxOptionsAndCategories().catch((error: Error) => { - setError(error.message); - }); - }, []); + fields.push({ + field: { + statusPageResources: true, + }, + title: "Select Resources to Subscribe", + description: "Please select the resources you want to subscribe to.", + fieldType: FormFieldSchemaType.CategoryCheckbox, + required: false, + categoryCheckboxProps: categoryCheckboxOptionsAndCategories, + showIf: (model: FormValues<StatusPageSubscriber>) => { + return !model || !model.isSubscribedToAllResources; + }, + }); + } - if (!id) { - throw new BadDataException('Status Page ID is required'); - } - - StatusPageUtil.checkIfUserHasLoggedIn(); - - const fields: Array<ModelField<StatusPageSubscriber>> = [ + return ( + <Page + title={"Subscribe"} + breadcrumbLinks={[ { - field: { - subscriberEmail: true, - }, - title: 'Your Email', - fieldType: FormFieldSchemaType.Email, - required: true, - placeholder: 'subscriber@company.com', + title: "Overview", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + ), }, - ]; + { + title: "Subscribe", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_SUBSCRIBE_EMAIL] as Route) + : (RouteMap[PageMap.SUBSCRIBE_EMAIL] as Route), + ), + }, + ]} + sideMenu={ + <SubscribeSideMenu + isPreviewStatusPage={Boolean(StatusPageUtil.isPreviewPage())} + enableEmailSubscribers={props.enableEmailSubscribers} + enableSMSSubscribers={props.enableSMSSubscribers} + /> + } + > + {isLaoding ? <PageLoader isVisible={isLaoding} /> : <></>} - if (props.allowSubscribersToChooseResources) { - fields.push({ - field: { - isSubscribedToAllResources: true, - }, - title: 'Subscribe to All Resources', - description: - 'Select this option if you want to subscribe to all resources.', - fieldType: FormFieldSchemaType.Checkbox, - required: false, - defaultValue: true, - }); + {error ? <ErrorMessage error={error} /> : <></>} - fields.push({ - field: { - statusPageResources: true, - }, - title: 'Select Resources to Subscribe', - description: - 'Please select the resources you want to subscribe to.', - fieldType: FormFieldSchemaType.CategoryCheckbox, - required: false, - categoryCheckboxProps: categoryCheckboxOptionsAndCategories, - showIf: (model: FormValues<StatusPageSubscriber>) => { - return !model || !model.isSubscribedToAllResources; - }, - }); - } - - return ( - <Page - title={'Subscribe'} - breadcrumbLinks={[ - { - title: 'Overview', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) - : (RouteMap[PageMap.OVERVIEW] as Route) - ), - }, - { - title: 'Subscribe', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[ - PageMap.PREVIEW_SUBSCRIBE_EMAIL - ] as Route) - : (RouteMap[PageMap.SUBSCRIBE_EMAIL] as Route) - ), - }, - ]} - sideMenu={ - <SubscribeSideMenu - isPreviewStatusPage={Boolean( - StatusPageUtil.isPreviewPage() - )} - enableEmailSubscribers={props.enableEmailSubscribers} - enableSMSSubscribers={props.enableSMSSubscribers} - /> - } - > - {isLaoding ? <PageLoader isVisible={isLaoding} /> : <></>} - - {error ? <ErrorMessage error={error} /> : <></>} - - {!isLaoding && !error ? ( - <div className="justify-center"> - <div> - {isSuccess && ( - <p className="text-center text-gray-400 mb-20 mt-20"> - {' '} - You have been subscribed successfully. - </p> - )} - - {!isSuccess ? ( - <div className=""> - <Card - title="Subscribe by Email" - description={ - 'All of our updates will be sent to this email address.' - } - > - <ModelForm<StatusPageSubscriber> - modelType={StatusPageSubscriber} - id="email-form" - name="Status Page > Email Subscribe" - fields={fields} - createOrUpdateApiUrl={URL.fromString( - STATUS_PAGE_API_URL.toString() - ).addRoute( - `/subscribe/${id.toString()}` - )} - requestHeaders={API.getDefaultHeaders( - StatusPageUtil.getStatusPageId()! - )} - formType={FormType.Create} - submitButtonText={'Subscribe'} - onBeforeCreate={async ( - item: StatusPageSubscriber - ) => { - const id: ObjectID = - LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; - if (!id) { - throw new BadDataException( - 'Status Page ID is required' - ); - } - - item.statusPageId = id; - return item; - }} - onSuccess={( - _value: StatusPagePrivateUser - ) => { - setIsSuccess(true); - }} - maxPrimaryButtonWidth={true} - /> - </Card> - </div> - ) : ( - <></> - )} - </div> - </div> - ) : ( - <></> + {!isLaoding && !error ? ( + <div className="justify-center"> + <div> + {isSuccess && ( + <p className="text-center text-gray-400 mb-20 mt-20"> + {" "} + You have been subscribed successfully. + </p> )} - </Page> - ); + + {!isSuccess ? ( + <div className=""> + <Card + title="Subscribe by Email" + description={ + "All of our updates will be sent to this email address." + } + > + <ModelForm<StatusPageSubscriber> + modelType={StatusPageSubscriber} + id="email-form" + name="Status Page > Email Subscribe" + fields={fields} + createOrUpdateApiUrl={URL.fromString( + STATUS_PAGE_API_URL.toString(), + ).addRoute(`/subscribe/${id.toString()}`)} + requestHeaders={API.getDefaultHeaders( + StatusPageUtil.getStatusPageId()!, + )} + formType={FormType.Create} + submitButtonText={"Subscribe"} + onBeforeCreate={async (item: StatusPageSubscriber) => { + const id: ObjectID = LocalStorage.getItem( + "statusPageId", + ) as ObjectID; + if (!id) { + throw new BadDataException( + "Status Page ID is required", + ); + } + + item.statusPageId = id; + return item; + }} + onSuccess={(_value: StatusPagePrivateUser) => { + setIsSuccess(true); + }} + maxPrimaryButtonWidth={true} + /> + </Card> + </div> + ) : ( + <></> + )} + </div> + </div> + ) : ( + <></> + )} + </Page> + ); }; export default SubscribePage; diff --git a/StatusPage/src/Pages/Subscribe/SideMenu.tsx b/StatusPage/src/Pages/Subscribe/SideMenu.tsx index cc23c70846..30a7aeea08 100644 --- a/StatusPage/src/Pages/Subscribe/SideMenu.tsx +++ b/StatusPage/src/Pages/Subscribe/SideMenu.tsx @@ -1,57 +1,53 @@ -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 'CommonUI/src/Components/SideMenu/SideMenu'; -import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem'; -import React, { FunctionComponent, ReactElement } from 'react'; +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 "CommonUI/src/Components/SideMenu/SideMenu"; +import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem"; +import React, { FunctionComponent, ReactElement } from "react"; export interface ComponentProps { - isPreviewStatusPage: boolean; - enableEmailSubscribers: boolean; - enableSMSSubscribers: boolean; + isPreviewStatusPage: boolean; + enableEmailSubscribers: boolean; + enableSMSSubscribers: boolean; } const SubscribeSideMenu: FunctionComponent<ComponentProps> = ( - props: ComponentProps + props: ComponentProps, ): ReactElement => { - return ( - <SideMenu> - {props.enableEmailSubscribers ? ( - <SideMenuItem - link={{ - title: 'Email', - to: RouteUtil.populateRouteParams( - props.isPreviewStatusPage - ? (RouteMap[ - PageMap.PREVIEW_SUBSCRIBE_EMAIL - ] as Route) - : (RouteMap[PageMap.SUBSCRIBE_EMAIL] as Route) - ), - }} - icon={IconProp.Email} - /> - ) : ( - <></> - )} - {props.enableSMSSubscribers ? ( - <SideMenuItem - link={{ - title: 'SMS', - to: RouteUtil.populateRouteParams( - props.isPreviewStatusPage - ? (RouteMap[ - PageMap.PREVIEW_SUBSCRIBE_SMS - ] as Route) - : (RouteMap[PageMap.SUBSCRIBE_SMS] as Route) - ), - }} - icon={IconProp.SMS} - /> - ) : ( - <></> - )} - {/* <SideMenuItem + return ( + <SideMenu> + {props.enableEmailSubscribers ? ( + <SideMenuItem + link={{ + title: "Email", + to: RouteUtil.populateRouteParams( + props.isPreviewStatusPage + ? (RouteMap[PageMap.PREVIEW_SUBSCRIBE_EMAIL] as Route) + : (RouteMap[PageMap.SUBSCRIBE_EMAIL] as Route), + ), + }} + icon={IconProp.Email} + /> + ) : ( + <></> + )} + {props.enableSMSSubscribers ? ( + <SideMenuItem + link={{ + title: "SMS", + to: RouteUtil.populateRouteParams( + props.isPreviewStatusPage + ? (RouteMap[PageMap.PREVIEW_SUBSCRIBE_SMS] as Route) + : (RouteMap[PageMap.SUBSCRIBE_SMS] as Route), + ), + }} + icon={IconProp.SMS} + /> + ) : ( + <></> + )} + {/* <SideMenuItem link={{ title: 'Webhooks', to: RouteUtil.populateRouteParams( @@ -61,8 +57,8 @@ const SubscribeSideMenu: FunctionComponent<ComponentProps> = ( icon={IconProp.Globe} /> */} - </SideMenu> - ); + </SideMenu> + ); }; export default SubscribeSideMenu; diff --git a/StatusPage/src/Pages/Subscribe/SmsSubscribe.tsx b/StatusPage/src/Pages/Subscribe/SmsSubscribe.tsx index e023cb07b2..5017127466 100644 --- a/StatusPage/src/Pages/Subscribe/SmsSubscribe.tsx +++ b/StatusPage/src/Pages/Subscribe/SmsSubscribe.tsx @@ -1,231 +1,221 @@ -import Page from '../../Components/Page/Page'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import PageMap from '../../Utils/PageMap'; -import RouteMap, { RouteUtil } from '../../Utils/RouteMap'; -import StatusPageUtil from '../../Utils/StatusPage'; -import SubscribeSideMenu from './SideMenu'; -import { SubscribePageProps } from './SubscribePageUtils'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import Card from 'CommonUI/src/Components/Card/Card'; -import { CategoryCheckboxOptionsAndCategories } from 'CommonUI/src/Components/CategoryCheckbox/Index'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; +import Page from "../../Components/Page/Page"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import PageMap from "../../Utils/PageMap"; +import RouteMap, { RouteUtil } from "../../Utils/RouteMap"; +import StatusPageUtil from "../../Utils/StatusPage"; +import SubscribeSideMenu from "./SideMenu"; +import { SubscribePageProps } from "./SubscribePageUtils"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import Card from "CommonUI/src/Components/Card/Card"; +import { CategoryCheckboxOptionsAndCategories } from "CommonUI/src/Components/CategoryCheckbox/Index"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; import ModelForm, { - FormType, - ModelField, -} from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import SubscriberUtil from 'CommonUI/src/Utils/StatusPage'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; + FormType, + ModelField, +} from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import SubscriberUtil from "CommonUI/src/Utils/StatusPage"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const SubscribePage: FunctionComponent<SubscribePageProps> = ( - props: SubscribePageProps + props: SubscribePageProps, ): ReactElement => { - const [isSuccess, setIsSuccess] = useState<boolean>(false); + const [isSuccess, setIsSuccess] = useState<boolean>(false); - const id: ObjectID = LocalStorage.getItem('statusPageId') as ObjectID; + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; - const [ - categoryCheckboxOptionsAndCategories, - setCategoryCheckboxOptionsAndCategories, - ] = useState<CategoryCheckboxOptionsAndCategories>({ - categories: [], - options: [], + const [ + categoryCheckboxOptionsAndCategories, + setCategoryCheckboxOptionsAndCategories, + ] = useState<CategoryCheckboxOptionsAndCategories>({ + categories: [], + options: [], + }); + const [isLaoding, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string | undefined>(undefined); + + const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = + async (): Promise<void> => { + try { + setIsLoading(true); + + const result: CategoryCheckboxOptionsAndCategories = + await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources( + id, + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/resources/${id.toString()}`, + ), + ); + + setCategoryCheckboxOptionsAndCategories(result); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchCheckboxOptionsAndCategories().catch((error: Error) => { + setError(error.message); }); - const [isLaoding, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string | undefined>(undefined); + }, []); - const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = - async (): Promise<void> => { - try { - setIsLoading(true); + if (!id) { + throw new BadDataException("Status Page ID is required"); + } - const result: CategoryCheckboxOptionsAndCategories = - await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources( - id, - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/resources/${id.toString()}` - ) - ); + StatusPageUtil.checkIfUserHasLoggedIn(); - setCategoryCheckboxOptionsAndCategories(result); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + const fields: Array<ModelField<StatusPageSubscriber>> = [ + { + field: { + subscriberPhone: true, + }, + title: "Your Phone Number", + fieldType: FormFieldSchemaType.Phone, + required: true, + placeholder: "+11234567890", + }, + ]; - setIsLoading(false); - }; + if (props.allowSubscribersToChooseResources) { + fields.push({ + field: { + isSubscribedToAllResources: true, + }, + title: "Subscribe to All Resources", + description: + "Select this option if you want to subscribe to all resources.", + fieldType: FormFieldSchemaType.Checkbox, + required: false, + defaultValue: true, + }); - useEffect(() => { - fetchCheckboxOptionsAndCategories().catch((error: Error) => { - setError(error.message); - }); - }, []); + fields.push({ + field: { + statusPageResources: true, + }, + title: "Select Resources to Subscribe", + description: "Please select the resources you want to subscribe to.", + fieldType: FormFieldSchemaType.CategoryCheckbox, + required: false, + categoryCheckboxProps: categoryCheckboxOptionsAndCategories, + showIf: (model: FormValues<StatusPageSubscriber>) => { + return !model || !model.isSubscribedToAllResources; + }, + }); + } - if (!id) { - throw new BadDataException('Status Page ID is required'); - } - - StatusPageUtil.checkIfUserHasLoggedIn(); - - const fields: Array<ModelField<StatusPageSubscriber>> = [ + return ( + <Page + title={"Subscribe"} + breadcrumbLinks={[ { - field: { - subscriberPhone: true, - }, - title: 'Your Phone Number', - fieldType: FormFieldSchemaType.Phone, - required: true, - placeholder: '+11234567890', + title: "Overview", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) + : (RouteMap[PageMap.OVERVIEW] as Route), + ), }, - ]; + { + title: "Subscribe", + to: RouteUtil.populateRouteParams( + StatusPageUtil.isPreviewPage() + ? (RouteMap[PageMap.PREVIEW_SUBSCRIBE_SMS] as Route) + : (RouteMap[PageMap.SUBSCRIBE_SMS] as Route), + ), + }, + ]} + sideMenu={ + <SubscribeSideMenu + isPreviewStatusPage={Boolean(StatusPageUtil.isPreviewPage())} + enableSMSSubscribers={props.enableSMSSubscribers} + enableEmailSubscribers={props.enableEmailSubscribers} + /> + } + > + {isLaoding ? <PageLoader isVisible={isLaoding} /> : <></>} - if (props.allowSubscribersToChooseResources) { - fields.push({ - field: { - isSubscribedToAllResources: true, - }, - title: 'Subscribe to All Resources', - description: - 'Select this option if you want to subscribe to all resources.', - fieldType: FormFieldSchemaType.Checkbox, - required: false, - defaultValue: true, - }); + {error ? <ErrorMessage error={error} /> : <></>} - fields.push({ - field: { - statusPageResources: true, - }, - title: 'Select Resources to Subscribe', - description: - 'Please select the resources you want to subscribe to.', - fieldType: FormFieldSchemaType.CategoryCheckbox, - required: false, - categoryCheckboxProps: categoryCheckboxOptionsAndCategories, - showIf: (model: FormValues<StatusPageSubscriber>) => { - return !model || !model.isSubscribedToAllResources; - }, - }); - } - - return ( - <Page - title={'Subscribe'} - breadcrumbLinks={[ - { - title: 'Overview', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_OVERVIEW] as Route) - : (RouteMap[PageMap.OVERVIEW] as Route) - ), - }, - { - title: 'Subscribe', - to: RouteUtil.populateRouteParams( - StatusPageUtil.isPreviewPage() - ? (RouteMap[PageMap.PREVIEW_SUBSCRIBE_SMS] as Route) - : (RouteMap[PageMap.SUBSCRIBE_SMS] as Route) - ), - }, - ]} - sideMenu={ - <SubscribeSideMenu - isPreviewStatusPage={Boolean( - StatusPageUtil.isPreviewPage() - )} - enableSMSSubscribers={props.enableSMSSubscribers} - enableEmailSubscribers={props.enableEmailSubscribers} - /> - } - > - {isLaoding ? <PageLoader isVisible={isLaoding} /> : <></>} - - {error ? <ErrorMessage error={error} /> : <></>} - - {!isLaoding && !error ? ( - <div className="justify-center"> - <div> - {isSuccess && ( - <p className="text-center text-gray-400 mb-20 mt-20"> - {' '} - You have been subscribed successfully. - </p> - )} - - {!isSuccess ? ( - <div className=""> - <Card - title="Subscribe by SMS" - description={ - 'All of our updates will be sent to this phone number.' - } - > - <ModelForm<StatusPageSubscriber> - modelType={StatusPageSubscriber} - id="SMS-form" - name="Status Page > SMS Subscribe" - fields={fields} - createOrUpdateApiUrl={URL.fromString( - STATUS_PAGE_API_URL.toString() - ).addRoute( - `/subscribe/${id.toString()}` - )} - requestHeaders={API.getDefaultHeaders( - StatusPageUtil.getStatusPageId()! - )} - formType={FormType.Create} - submitButtonText={'Subscribe'} - onBeforeCreate={async ( - item: StatusPageSubscriber - ) => { - const id: ObjectID = - LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; - if (!id) { - throw new BadDataException( - 'Status Page ID is required' - ); - } - - item.statusPageId = id; - return item; - }} - onSuccess={( - _value: StatusPagePrivateUser - ) => { - setIsSuccess(true); - }} - maxPrimaryButtonWidth={true} - /> - </Card> - </div> - ) : ( - <></> - )} - </div> - </div> - ) : ( - <></> + {!isLaoding && !error ? ( + <div className="justify-center"> + <div> + {isSuccess && ( + <p className="text-center text-gray-400 mb-20 mt-20"> + {" "} + You have been subscribed successfully. + </p> )} - </Page> - ); + + {!isSuccess ? ( + <div className=""> + <Card + title="Subscribe by SMS" + description={ + "All of our updates will be sent to this phone number." + } + > + <ModelForm<StatusPageSubscriber> + modelType={StatusPageSubscriber} + id="SMS-form" + name="Status Page > SMS Subscribe" + fields={fields} + createOrUpdateApiUrl={URL.fromString( + STATUS_PAGE_API_URL.toString(), + ).addRoute(`/subscribe/${id.toString()}`)} + requestHeaders={API.getDefaultHeaders( + StatusPageUtil.getStatusPageId()!, + )} + formType={FormType.Create} + submitButtonText={"Subscribe"} + onBeforeCreate={async (item: StatusPageSubscriber) => { + const id: ObjectID = LocalStorage.getItem( + "statusPageId", + ) as ObjectID; + if (!id) { + throw new BadDataException( + "Status Page ID is required", + ); + } + + item.statusPageId = id; + return item; + }} + onSuccess={(_value: StatusPagePrivateUser) => { + setIsSuccess(true); + }} + maxPrimaryButtonWidth={true} + /> + </Card> + </div> + ) : ( + <></> + )} + </div> + </div> + ) : ( + <></> + )} + </Page> + ); }; export default SubscribePage; diff --git a/StatusPage/src/Pages/Subscribe/SubscribePageUtils.ts b/StatusPage/src/Pages/Subscribe/SubscribePageUtils.ts index 8d52b7f8b7..f6f3ed02d1 100644 --- a/StatusPage/src/Pages/Subscribe/SubscribePageUtils.ts +++ b/StatusPage/src/Pages/Subscribe/SubscribePageUtils.ts @@ -1,7 +1,7 @@ -import PageComponentProps from '../PageComponentProps'; +import PageComponentProps from "../PageComponentProps"; export interface SubscribePageProps extends PageComponentProps { - enableEmailSubscribers: boolean; - enableSMSSubscribers: boolean; - allowSubscribersToChooseResources: boolean; + enableEmailSubscribers: boolean; + enableSMSSubscribers: boolean; + allowSubscribersToChooseResources: boolean; } diff --git a/StatusPage/src/Pages/Subscribe/UpdateSubscription.tsx b/StatusPage/src/Pages/Subscribe/UpdateSubscription.tsx index f249c78400..18342dc539 100644 --- a/StatusPage/src/Pages/Subscribe/UpdateSubscription.tsx +++ b/StatusPage/src/Pages/Subscribe/UpdateSubscription.tsx @@ -1,244 +1,239 @@ -import Page from '../../Components/Page/Page'; -import API from '../../Utils/API'; -import { STATUS_PAGE_API_URL } from '../../Utils/Config'; -import StatusPageUtil from '../../Utils/StatusPage'; -import { SubscribePageProps } from './SubscribePageUtils'; -import URL from 'Common/Types/API/URL'; -import BadDataException from 'Common/Types/Exception/BadDataException'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import ObjectID from 'Common/Types/ObjectID'; -import Card from 'CommonUI/src/Components/Card/Card'; -import { CategoryCheckboxOptionsAndCategories } from 'CommonUI/src/Components/CategoryCheckbox/Index'; -import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage'; +import Page from "../../Components/Page/Page"; +import API from "../../Utils/API"; +import { STATUS_PAGE_API_URL } from "../../Utils/Config"; +import StatusPageUtil from "../../Utils/StatusPage"; +import { SubscribePageProps } from "./SubscribePageUtils"; +import URL from "Common/Types/API/URL"; +import BadDataException from "Common/Types/Exception/BadDataException"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import ObjectID from "Common/Types/ObjectID"; +import Card from "CommonUI/src/Components/Card/Card"; +import { CategoryCheckboxOptionsAndCategories } from "CommonUI/src/Components/CategoryCheckbox/Index"; +import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage"; import ModelForm, { - FormType, - ModelField, -} from 'CommonUI/src/Components/Forms/ModelForm'; -import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; -import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; -import PageLoader from 'CommonUI/src/Components/Loader/PageLoader'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; -import SubscriberUtil from 'CommonUI/src/Utils/StatusPage'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; -import StatusPageSubscriber from 'Model/Models/StatusPageSubscriber'; + FormType, + ModelField, +} from "CommonUI/src/Components/Forms/ModelForm"; +import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType"; +import FormValues from "CommonUI/src/Components/Forms/Types/FormValues"; +import PageLoader from "CommonUI/src/Components/Loader/PageLoader"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import Navigation from "CommonUI/src/Utils/Navigation"; +import SubscriberUtil from "CommonUI/src/Utils/StatusPage"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; +import StatusPageSubscriber from "Model/Models/StatusPageSubscriber"; import React, { - FunctionComponent, - ReactElement, - useEffect, - useState, -} from 'react'; + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; const SubscribePage: FunctionComponent<SubscribePageProps> = ( - props: SubscribePageProps + props: SubscribePageProps, ): ReactElement => { - const statusPageSubscriberId: string | undefined = - Navigation.getLastParamAsObjectID().toString(); + const statusPageSubscriberId: string | undefined = + Navigation.getLastParamAsObjectID().toString(); - const [isSuccess, setIsSuccess] = useState<boolean>(false); + const [isSuccess, setIsSuccess] = useState<boolean>(false); - const statusPageId: ObjectID = LocalStorage.getItem( - 'statusPageId' - ) as ObjectID; + const statusPageId: ObjectID = LocalStorage.getItem( + "statusPageId", + ) as ObjectID; - const updateApiUrl: URL = URL.fromString( - URL.fromString(STATUS_PAGE_API_URL.toString()) - .addRoute(`/update-subscription/${statusPageId.toString()}`) - .addRoute('/' + statusPageSubscriberId.toString()) - .toString() - ); + const updateApiUrl: URL = URL.fromString( + URL.fromString(STATUS_PAGE_API_URL.toString()) + .addRoute(`/update-subscription/${statusPageId.toString()}`) + .addRoute("/" + statusPageSubscriberId.toString()) + .toString(), + ); - const getSubscriptionUrl: URL = URL.fromString( - URL.fromString(STATUS_PAGE_API_URL.toString()) - .addRoute(`/get-subscription/${statusPageId.toString()}`) - .addRoute('/' + statusPageSubscriberId.toString()) - .toString() - ); + const getSubscriptionUrl: URL = URL.fromString( + URL.fromString(STATUS_PAGE_API_URL.toString()) + .addRoute(`/get-subscription/${statusPageId.toString()}`) + .addRoute("/" + statusPageSubscriberId.toString()) + .toString(), + ); - const [ - categoryCheckboxOptionsAndCategories, - setCategoryCheckboxOptionsAndCategories, - ] = useState<CategoryCheckboxOptionsAndCategories>({ - categories: [], - options: [], + const [ + categoryCheckboxOptionsAndCategories, + setCategoryCheckboxOptionsAndCategories, + ] = useState<CategoryCheckboxOptionsAndCategories>({ + categories: [], + options: [], + }); + + const [isLaoding, setIsLoading] = useState<boolean>(false); + const [error, setError] = useState<string | undefined>(undefined); + + const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = + async (): Promise<void> => { + try { + setIsLoading(true); + + const result: CategoryCheckboxOptionsAndCategories = + await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources( + statusPageId, + URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( + `/resources/${statusPageId.toString()}`, + ), + ); + + setCategoryCheckboxOptionsAndCategories(result); + } catch (err) { + setError(API.getFriendlyMessage(err)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchCheckboxOptionsAndCategories().catch((error: Error) => { + setError(error.message); }); + }, []); - const [isLaoding, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string | undefined>(undefined); + if (!statusPageId) { + throw new BadDataException("Status Page ID is required"); + } - const fetchCheckboxOptionsAndCategories: PromiseVoidFunction = - async (): Promise<void> => { - try { - setIsLoading(true); + if (!statusPageSubscriberId) { + throw new BadDataException("Status Page Subscriber ID is required"); + } - const result: CategoryCheckboxOptionsAndCategories = - await SubscriberUtil.getCategoryCheckboxPropsBasedOnResources( - statusPageId, - URL.fromString(STATUS_PAGE_API_URL.toString()).addRoute( - `/resources/${statusPageId.toString()}` - ) - ); + StatusPageUtil.checkIfUserHasLoggedIn(); - setCategoryCheckboxOptionsAndCategories(result); - } catch (err) { - setError(API.getFriendlyMessage(err)); - } + const fields: Array<ModelField<StatusPageSubscriber>> = [ + { + field: { + subscriberEmail: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Your Email", + fieldType: FormFieldSchemaType.Email, + required: (model: FormValues<StatusPageSubscriber>) => { + return model && Boolean(model.subscriberEmail); + }, + disabled: true, + placeholder: "subscriber@company.com", + showIf: (model: FormValues<StatusPageSubscriber>) => { + return model && Boolean(model.subscriberEmail); + }, + }, + { + field: { + subscriberPhone: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Your Phone Number", + fieldType: FormFieldSchemaType.Email, + required: (model: FormValues<StatusPageSubscriber>) => { + return model && Boolean(model.subscriberPhone); + }, + placeholder: "+15853641376", + disabled: true, + showIf: (model: FormValues<StatusPageSubscriber>) => { + return model && Boolean(model.subscriberPhone); + }, + }, + ]; - setIsLoading(false); - }; - - useEffect(() => { - fetchCheckboxOptionsAndCategories().catch((error: Error) => { - setError(error.message); - }); - }, []); - - if (!statusPageId) { - throw new BadDataException('Status Page ID is required'); - } - - if (!statusPageSubscriberId) { - throw new BadDataException('Status Page Subscriber ID is required'); - } - - StatusPageUtil.checkIfUserHasLoggedIn(); - - const fields: Array<ModelField<StatusPageSubscriber>> = [ - { - field: { - subscriberEmail: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Your Email', - fieldType: FormFieldSchemaType.Email, - required: (model: FormValues<StatusPageSubscriber>) => { - return model && Boolean(model.subscriberEmail); - }, - disabled: true, - placeholder: 'subscriber@company.com', - showIf: (model: FormValues<StatusPageSubscriber>) => { - return model && Boolean(model.subscriberEmail); - }, - }, - { - field: { - subscriberPhone: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Your Phone Number', - fieldType: FormFieldSchemaType.Email, - required: (model: FormValues<StatusPageSubscriber>) => { - return model && Boolean(model.subscriberPhone); - }, - placeholder: '+15853641376', - disabled: true, - showIf: (model: FormValues<StatusPageSubscriber>) => { - return model && Boolean(model.subscriberPhone); - }, - }, - ]; - - if (props.allowSubscribersToChooseResources) { - fields.push({ - field: { - isSubscribedToAllResources: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Subscribe to All Resources', - description: - 'Select this option if you want to subscribe to all resources.', - fieldType: FormFieldSchemaType.Checkbox, - required: false, - defaultValue: true, - }); - - fields.push({ - field: { - statusPageResources: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Select Resources to Subscribe', - description: - 'Please select the resources you want to subscribe to.', - fieldType: FormFieldSchemaType.CategoryCheckbox, - required: false, - categoryCheckboxProps: categoryCheckboxOptionsAndCategories, - showIf: (model: FormValues<StatusPageSubscriber>) => { - return !model || !model.isSubscribedToAllResources; - }, - }); - } + if (props.allowSubscribersToChooseResources) { + fields.push({ + field: { + isSubscribedToAllResources: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Subscribe to All Resources", + description: + "Select this option if you want to subscribe to all resources.", + fieldType: FormFieldSchemaType.Checkbox, + required: false, + defaultValue: true, + }); fields.push({ - field: { - isUnsubscribed: true, - }, - showEvenIfPermissionDoesNotExist: true, - title: 'Unsubscribe', - description: - 'Please select this if you would like to unsubscribe from all resources.', - fieldType: FormFieldSchemaType.Toggle, - required: false, + field: { + statusPageResources: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Select Resources to Subscribe", + description: "Please select the resources you want to subscribe to.", + fieldType: FormFieldSchemaType.CategoryCheckbox, + required: false, + categoryCheckboxProps: categoryCheckboxOptionsAndCategories, + showIf: (model: FormValues<StatusPageSubscriber>) => { + return !model || !model.isSubscribedToAllResources; + }, }); + } - return ( - <Page> - {isLaoding ? <PageLoader isVisible={isLaoding} /> : <></>} + fields.push({ + field: { + isUnsubscribed: true, + }, + showEvenIfPermissionDoesNotExist: true, + title: "Unsubscribe", + description: + "Please select this if you would like to unsubscribe from all resources.", + fieldType: FormFieldSchemaType.Toggle, + required: false, + }); - {error ? <ErrorMessage error={error} /> : <></>} + return ( + <Page> + {isLaoding ? <PageLoader isVisible={isLaoding} /> : <></>} - {!isLaoding && !error ? ( - <div className="justify-center"> - <div> - {isSuccess && ( - <p className="text-center text-gray-400 mb-20 mt-20"> - {' '} - Your changes have been saved.{' '} - </p> - )} + {error ? <ErrorMessage error={error} /> : <></>} - {!isSuccess ? ( - <div className=""> - <Card - title="Update Subscription" - description={ - 'You can update your subscription preferences or unsubscribe here.' - } - > - <ModelForm<StatusPageSubscriber> - modelType={StatusPageSubscriber} - id="email-form" - name="Status Page > Update Subscription" - fields={fields} - createOrUpdateApiUrl={updateApiUrl} - requestHeaders={API.getDefaultHeaders( - StatusPageUtil.getStatusPageId()! - )} - fetchItemApiUrl={getSubscriptionUrl} - formType={FormType.Update} - modelIdToEdit={ - new ObjectID(statusPageSubscriberId) - } - submitButtonText={'Update Subscription'} - onSuccess={( - _value: StatusPagePrivateUser - ) => { - setIsSuccess(true); - }} - maxPrimaryButtonWidth={true} - /> - </Card> - </div> - ) : ( - <></> - )} - </div> - </div> - ) : ( - <></> + {!isLaoding && !error ? ( + <div className="justify-center"> + <div> + {isSuccess && ( + <p className="text-center text-gray-400 mb-20 mt-20"> + {" "} + Your changes have been saved.{" "} + </p> )} - </Page> - ); + + {!isSuccess ? ( + <div className=""> + <Card + title="Update Subscription" + description={ + "You can update your subscription preferences or unsubscribe here." + } + > + <ModelForm<StatusPageSubscriber> + modelType={StatusPageSubscriber} + id="email-form" + name="Status Page > Update Subscription" + fields={fields} + createOrUpdateApiUrl={updateApiUrl} + requestHeaders={API.getDefaultHeaders( + StatusPageUtil.getStatusPageId()!, + )} + fetchItemApiUrl={getSubscriptionUrl} + formType={FormType.Update} + modelIdToEdit={new ObjectID(statusPageSubscriberId)} + submitButtonText={"Update Subscription"} + onSuccess={(_value: StatusPagePrivateUser) => { + setIsSuccess(true); + }} + maxPrimaryButtonWidth={true} + /> + </Card> + </div> + ) : ( + <></> + )} + </div> + </div> + ) : ( + <></> + )} + </Page> + ); }; export default SubscribePage; diff --git a/StatusPage/src/Types/IncidentGroup.ts b/StatusPage/src/Types/IncidentGroup.ts index b4ea1e3f3b..bad660b019 100644 --- a/StatusPage/src/Types/IncidentGroup.ts +++ b/StatusPage/src/Types/IncidentGroup.ts @@ -1,18 +1,18 @@ -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; -import Incident from 'Model/Models/Incident'; -import IncidentPublicNote from 'Model/Models/IncidentPublicNote'; -import IncidentSeverity from 'Model/Models/IncidentSeverity'; -import IncidentState from 'Model/Models/IncidentState'; -import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline'; -import StatusPageResource from 'Model/Models/StatusPageResource'; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; +import Incident from "Model/Models/Incident"; +import IncidentPublicNote from "Model/Models/IncidentPublicNote"; +import IncidentSeverity from "Model/Models/IncidentSeverity"; +import IncidentState from "Model/Models/IncidentState"; +import IncidentStateTimeline from "Model/Models/IncidentStateTimeline"; +import StatusPageResource from "Model/Models/StatusPageResource"; export default interface IncidentGroup { - incident: Incident; - incidentSeverity: IncidentSeverity; - publicNotes?: Array<IncidentPublicNote>; - incidentState: IncidentState; - incidentStateTimelines: Array<IncidentStateTimeline>; - incidentResources: Array<StatusPageResource>; - monitorsInGroup: Dictionary<Array<ObjectID>>; + incident: Incident; + incidentSeverity: IncidentSeverity; + publicNotes?: Array<IncidentPublicNote>; + incidentState: IncidentState; + incidentStateTimelines: Array<IncidentStateTimeline>; + incidentResources: Array<StatusPageResource>; + monitorsInGroup: Dictionary<Array<ObjectID>>; } diff --git a/StatusPage/src/Types/ScheduledMaintenanceGroup.ts b/StatusPage/src/Types/ScheduledMaintenanceGroup.ts index 61933d6182..a105d6ccc8 100644 --- a/StatusPage/src/Types/ScheduledMaintenanceGroup.ts +++ b/StatusPage/src/Types/ScheduledMaintenanceGroup.ts @@ -1,16 +1,16 @@ -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; -import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance'; -import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote'; -import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState'; -import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline'; -import StatusPageResource from 'Model/Models/StatusPageResource'; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; +import ScheduledMaintenance from "Model/Models/ScheduledMaintenance"; +import ScheduledMaintenancePublicNote from "Model/Models/ScheduledMaintenancePublicNote"; +import ScheduledMaintenanceState from "Model/Models/ScheduledMaintenanceState"; +import ScheduledMaintenanceStateTimeline from "Model/Models/ScheduledMaintenanceStateTimeline"; +import StatusPageResource from "Model/Models/StatusPageResource"; export default interface ScheduledMaintenanceGroup { - scheduledMaintenance: ScheduledMaintenance; - publicNotes?: Array<ScheduledMaintenancePublicNote>; - scheduledMaintenanceState: ScheduledMaintenanceState; - scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline>; - scheduledEventResources: Array<StatusPageResource>; - monitorsInGroup: Dictionary<Array<ObjectID>>; + scheduledMaintenance: ScheduledMaintenance; + publicNotes?: Array<ScheduledMaintenancePublicNote>; + scheduledMaintenanceState: ScheduledMaintenanceState; + scheduledMaintenanceStateTimelines: Array<ScheduledMaintenanceStateTimeline>; + scheduledEventResources: Array<StatusPageResource>; + monitorsInGroup: Dictionary<Array<ObjectID>>; } diff --git a/StatusPage/src/Utils/API.ts b/StatusPage/src/Utils/API.ts index 6b7ad37841..75178e556c 100644 --- a/StatusPage/src/Utils/API.ts +++ b/StatusPage/src/Utils/API.ts @@ -1,25 +1,25 @@ -import StatusPageUtil from './StatusPage'; -import Headers from 'Common/Types/API/Headers'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import BaseAPI from 'CommonUI/src/Utils/API/API'; +import StatusPageUtil from "./StatusPage"; +import Headers from "Common/Types/API/Headers"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import BaseAPI from "CommonUI/src/Utils/API/API"; export default class API extends BaseAPI { - public static override getDefaultHeaders(statusPageId: ObjectID): Headers { - if (!statusPageId) { - return {}; - } - - return { - 'status-page-id': statusPageId.toString(), - }; + public static override getDefaultHeaders(statusPageId: ObjectID): Headers { + if (!statusPageId) { + return {}; } - public static override getLoginRoute(): Route { - return new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/login` - : '/login' - ); - } + return { + "status-page-id": statusPageId.toString(), + }; + } + + public static override getLoginRoute(): Route { + return new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/login` + : "/login", + ); + } } diff --git a/StatusPage/src/Utils/ApiPaths.ts b/StatusPage/src/Utils/ApiPaths.ts index 99fc9bed1b..2c33b3347d 100644 --- a/StatusPage/src/Utils/ApiPaths.ts +++ b/StatusPage/src/Utils/ApiPaths.ts @@ -1,15 +1,15 @@ -import { STATUS_PAGE_IDENTITY_API_URL } from './Config'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; +import { STATUS_PAGE_IDENTITY_API_URL } from "./Config"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; export const LOGIN_API_URL: URL = URL.fromURL( - STATUS_PAGE_IDENTITY_API_URL -).addRoute(new Route('/login')); + STATUS_PAGE_IDENTITY_API_URL, +).addRoute(new Route("/login")); export const FORGOT_PASSWORD_API_URL: URL = URL.fromURL( - STATUS_PAGE_IDENTITY_API_URL -).addRoute(new Route('/forgot-password')); + STATUS_PAGE_IDENTITY_API_URL, +).addRoute(new Route("/forgot-password")); export const RESET_PASSWORD_API_URL: URL = URL.fromURL( - STATUS_PAGE_IDENTITY_API_URL -).addRoute(new Route('/reset-password')); + STATUS_PAGE_IDENTITY_API_URL, +).addRoute(new Route("/reset-password")); diff --git a/StatusPage/src/Utils/Config.ts b/StatusPage/src/Utils/Config.ts index 61d9254d1e..b6bb270f6a 100644 --- a/StatusPage/src/Utils/Config.ts +++ b/StatusPage/src/Utils/Config.ts @@ -1,25 +1,25 @@ -import Protocol from 'Common/Types/API/Protocol'; -import Route from 'Common/Types/API/Route'; -import URL from 'Common/Types/API/URL'; +import Protocol from "Common/Types/API/Protocol"; +import Route from "Common/Types/API/Route"; +import URL from "Common/Types/API/URL"; -const PROTOCOL: Protocol = window.location.protocol.includes('https') - ? Protocol.HTTPS - : Protocol.HTTP; +const PROTOCOL: Protocol = window.location.protocol.includes("https") + ? Protocol.HTTPS + : Protocol.HTTP; export const STATUS_PAGE_API_URL: URL = new URL( - PROTOCOL, - window.location.host, - new Route('/status-page-api') + PROTOCOL, + window.location.host, + new Route("/status-page-api"), ); export const STATUS_PAGE_SSO_API_URL: URL = new URL( - PROTOCOL, - window.location.host, - new Route('/status-page-sso-api') + PROTOCOL, + window.location.host, + new Route("/status-page-sso-api"), ); export const STATUS_PAGE_IDENTITY_API_URL: URL = new URL( - PROTOCOL, - window.location.host, - new Route('/status-page-identity-api') + PROTOCOL, + window.location.host, + new Route("/status-page-identity-api"), ); diff --git a/StatusPage/src/Utils/Login.ts b/StatusPage/src/Utils/Login.ts index 6c0a912629..73a0b790f4 100644 --- a/StatusPage/src/Utils/Login.ts +++ b/StatusPage/src/Utils/Login.ts @@ -1,33 +1,29 @@ -import UserUtil from './User'; -import BaseModel from 'Common/Models/BaseModel'; -import Route from 'Common/Types/API/Route'; -import Email from 'Common/Types/Email'; -import { JSONObject } from 'Common/Types/JSON'; -import ObjectID from 'Common/Types/ObjectID'; -import Cookie from 'CommonUI/src/Utils/Cookie'; -import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser'; +import UserUtil from "./User"; +import BaseModel from "Common/Models/BaseModel"; +import Route from "Common/Types/API/Route"; +import Email from "Common/Types/Email"; +import { JSONObject } from "Common/Types/JSON"; +import ObjectID from "Common/Types/ObjectID"; +import Cookie from "CommonUI/src/Utils/Cookie"; +import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser"; export default abstract class LoginUtil { - public static login(value: JSONObject): void { - const user: StatusPagePrivateUser = BaseModel.fromJSON( - value['user'] as JSONObject, - StatusPagePrivateUser - ) as StatusPagePrivateUser; + public static login(value: JSONObject): void { + const user: StatusPagePrivateUser = BaseModel.fromJSON( + value["user"] as JSONObject, + StatusPagePrivateUser, + ) as StatusPagePrivateUser; - const statusPageId: ObjectID = user.statusPageId!; + const statusPageId: ObjectID = user.statusPageId!; - UserUtil.setEmail(statusPageId, user.email as Email); - UserUtil.setUserId(statusPageId, user.id as ObjectID); - if (value && value['token']) { - // set token as cookie. - Cookie.setItem( - 'user-token-' + statusPageId.toString(), - value['token'], - { - path: new Route('/'), - maxAgeInDays: 30, - } - ); - } + UserUtil.setEmail(statusPageId, user.email as Email); + UserUtil.setUserId(statusPageId, user.id as ObjectID); + if (value && value["token"]) { + // set token as cookie. + Cookie.setItem("user-token-" + statusPageId.toString(), value["token"], { + path: new Route("/"), + maxAgeInDays: 30, + }); } + } } diff --git a/StatusPage/src/Utils/PageMap.ts b/StatusPage/src/Utils/PageMap.ts index a95ed3b6ab..977dbae60a 100644 --- a/StatusPage/src/Utils/PageMap.ts +++ b/StatusPage/src/Utils/PageMap.ts @@ -1,47 +1,47 @@ enum PageMap { - OVERVIEW = 'OVERVIEW', - INCIDENT_LIST = 'INCIDENT_LIST', - INCIDENT_DETAIL = 'INCIDENT_DETAIL', - ANNOUNCEMENT_DETAIL = 'ANNOUNCEMENT_DETAIL', - ANNOUNCEMENT_LIST = 'ANNOUNCEMENT_LIST', - SCHEDULED_EVENT_LIST = 'SCHEDULED_EVENT_LIST', - SCHEDULED_EVENT_DETAIL = 'SCHEDULED_EVENT_DETAIL', - RSS = 'RSS', + OVERVIEW = "OVERVIEW", + INCIDENT_LIST = "INCIDENT_LIST", + INCIDENT_DETAIL = "INCIDENT_DETAIL", + ANNOUNCEMENT_DETAIL = "ANNOUNCEMENT_DETAIL", + ANNOUNCEMENT_LIST = "ANNOUNCEMENT_LIST", + SCHEDULED_EVENT_LIST = "SCHEDULED_EVENT_LIST", + SCHEDULED_EVENT_DETAIL = "SCHEDULED_EVENT_DETAIL", + RSS = "RSS", - SUBSCRIBE_EMAIL = 'SUBSCRIBE_EMAIL', - SUBSCRIBE_SMS = 'SUBSCRIBE_SMS', - SUBSCRIBE_WEBHOOKS = 'SUBSCRIBE_WEBHOOKS', - UPDATE_SUBSCRIPTION = 'UPDATE_SUBSCRIPTION', + SUBSCRIBE_EMAIL = "SUBSCRIBE_EMAIL", + SUBSCRIBE_SMS = "SUBSCRIBE_SMS", + SUBSCRIBE_WEBHOOKS = "SUBSCRIBE_WEBHOOKS", + UPDATE_SUBSCRIPTION = "UPDATE_SUBSCRIPTION", - NOT_FOUND = 'NOT_FOUND', + NOT_FOUND = "NOT_FOUND", - PREVIEW_OVERVIEW = 'PREVIEW_OVERVIEW', - PREVIEW_INCIDENT_LIST = 'PREVIEW_INCIDENT_LIST', - PREVIEW_INCIDENT_DETAIL = 'PREVIEW_INCIDENT_DETAIL', - PREVIEW_ANNOUNCEMENT_DETAIL = 'PREVIEW_ANNOUNCEMENT_DETAIL', - PREVIEW_ANNOUNCEMENT_LIST = 'PREVIEW_ANNOUNCEMENT_LIST', - PREVIEW_SCHEDULED_EVENT_LIST = 'PREVIEW_SCHEDULED_EVENT_LIST', - PREVIEW_SCHEDULED_EVENT_DETAIL = 'PREVIEW_SCHEDULED_EVENT_DETAIL', - PREVIEW_RSS = 'PREVIEW_RSS', + PREVIEW_OVERVIEW = "PREVIEW_OVERVIEW", + PREVIEW_INCIDENT_LIST = "PREVIEW_INCIDENT_LIST", + PREVIEW_INCIDENT_DETAIL = "PREVIEW_INCIDENT_DETAIL", + PREVIEW_ANNOUNCEMENT_DETAIL = "PREVIEW_ANNOUNCEMENT_DETAIL", + PREVIEW_ANNOUNCEMENT_LIST = "PREVIEW_ANNOUNCEMENT_LIST", + PREVIEW_SCHEDULED_EVENT_LIST = "PREVIEW_SCHEDULED_EVENT_LIST", + PREVIEW_SCHEDULED_EVENT_DETAIL = "PREVIEW_SCHEDULED_EVENT_DETAIL", + PREVIEW_RSS = "PREVIEW_RSS", - PREVIEW_SUBSCRIBE_EMAIL = 'PREVIEW_SUBSCRIBE_EMAIL', - PREVIEW_SUBSCRIBE_SMS = 'PREVIEW_SUBSCRIBE_SMS', - PREVIEW_SUBSCRIBE_WEBHOOKS = 'PREVIEW_SUBSCRIBE_WEBHOOKS', - PREVIEW_UPDATE_SUBSCRIPTION = 'PREVIEW_UPDATE_SUBSCRIPTION', + PREVIEW_SUBSCRIBE_EMAIL = "PREVIEW_SUBSCRIBE_EMAIL", + PREVIEW_SUBSCRIBE_SMS = "PREVIEW_SUBSCRIBE_SMS", + PREVIEW_SUBSCRIBE_WEBHOOKS = "PREVIEW_SUBSCRIBE_WEBHOOKS", + PREVIEW_UPDATE_SUBSCRIPTION = "PREVIEW_UPDATE_SUBSCRIPTION", - // Accounts. - LOGIN = 'LOGIN', - LOGOUT = 'LOGOUT', - RESET_PASSWORD = 'RESET_PASSWORD', - FORGOT_PASSWORD = 'FORGOT_PASSWORD', - SSO = 'SSO', + // Accounts. + LOGIN = "LOGIN", + LOGOUT = "LOGOUT", + RESET_PASSWORD = "RESET_PASSWORD", + FORGOT_PASSWORD = "FORGOT_PASSWORD", + SSO = "SSO", - // Accounts. - PREVIEW_LOGIN = 'PREVIEW_LOGIN', - PREVIEW_RESET_PASSWORD = 'PREVIEW_RESET_PASSWORD', - PREVIEW_FORGOT_PASSWORD = 'PREVIEW_FORGOT_PASSWORD', - PREVIEW_SSO = 'PREVIEW_SSO', - PREVIEW_LOGOUT = 'PREVIEW_LOGOUT', + // Accounts. + PREVIEW_LOGIN = "PREVIEW_LOGIN", + PREVIEW_RESET_PASSWORD = "PREVIEW_RESET_PASSWORD", + PREVIEW_FORGOT_PASSWORD = "PREVIEW_FORGOT_PASSWORD", + PREVIEW_SSO = "PREVIEW_SSO", + PREVIEW_LOGOUT = "PREVIEW_LOGOUT", } export default PageMap; diff --git a/StatusPage/src/Utils/RouteMap.ts b/StatusPage/src/Utils/RouteMap.ts index 234425266f..3a7a859b38 100644 --- a/StatusPage/src/Utils/RouteMap.ts +++ b/StatusPage/src/Utils/RouteMap.ts @@ -1,114 +1,108 @@ -import PageMap from './PageMap'; -import RouteParams from './RouteParams'; -import Route from 'Common/Types/API/Route'; -import Dictionary from 'Common/Types/Dictionary'; -import ObjectID from 'Common/Types/ObjectID'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; +import PageMap from "./PageMap"; +import RouteParams from "./RouteParams"; +import Route from "Common/Types/API/Route"; +import Dictionary from "Common/Types/Dictionary"; +import ObjectID from "Common/Types/ObjectID"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; const RouteMap: Dictionary<Route> = { - [PageMap.OVERVIEW]: new Route(`/`), - [PageMap.INCIDENT_LIST]: new Route(`/incidents`), - [PageMap.INCIDENT_DETAIL]: new Route(`/incidents/:id`), - [PageMap.ANNOUNCEMENT_DETAIL]: new Route(`/announcements/:id`), - [PageMap.ANNOUNCEMENT_LIST]: new Route(`/announcements`), - [PageMap.SCHEDULED_EVENT_LIST]: new Route(`/scheduled-events`), - [PageMap.SCHEDULED_EVENT_DETAIL]: new Route(`/scheduled-events/:id`), - [PageMap.RSS]: new Route(`/rss`), - [PageMap.SUBSCRIBE_EMAIL]: new Route(`/subscribe/email`), - [PageMap.SUBSCRIBE_SMS]: new Route(`/subscribe/sms`), - [PageMap.SUBSCRIBE_WEBHOOKS]: new Route(`/subscribe/webhooks`), - [PageMap.UPDATE_SUBSCRIPTION]: new Route(`/update-subscription/:id`), + [PageMap.OVERVIEW]: new Route(`/`), + [PageMap.INCIDENT_LIST]: new Route(`/incidents`), + [PageMap.INCIDENT_DETAIL]: new Route(`/incidents/:id`), + [PageMap.ANNOUNCEMENT_DETAIL]: new Route(`/announcements/:id`), + [PageMap.ANNOUNCEMENT_LIST]: new Route(`/announcements`), + [PageMap.SCHEDULED_EVENT_LIST]: new Route(`/scheduled-events`), + [PageMap.SCHEDULED_EVENT_DETAIL]: new Route(`/scheduled-events/:id`), + [PageMap.RSS]: new Route(`/rss`), + [PageMap.SUBSCRIBE_EMAIL]: new Route(`/subscribe/email`), + [PageMap.SUBSCRIBE_SMS]: new Route(`/subscribe/sms`), + [PageMap.SUBSCRIBE_WEBHOOKS]: new Route(`/subscribe/webhooks`), + [PageMap.UPDATE_SUBSCRIPTION]: new Route(`/update-subscription/:id`), - [PageMap.LOGIN]: new Route(`/login`), - [PageMap.SSO]: new Route(`/sso`), - [PageMap.LOGOUT]: new Route(`/logout`), - [PageMap.FORGOT_PASSWORD]: new Route(`/forgot-password`), - [PageMap.RESET_PASSWORD]: new Route(`/reset-password/:token`), + [PageMap.LOGIN]: new Route(`/login`), + [PageMap.SSO]: new Route(`/sso`), + [PageMap.LOGOUT]: new Route(`/logout`), + [PageMap.FORGOT_PASSWORD]: new Route(`/forgot-password`), + [PageMap.RESET_PASSWORD]: new Route(`/reset-password/:token`), - [PageMap.PREVIEW_OVERVIEW]: new Route( - `/status-page/${RouteParams.StatusPageId}` - ), - [PageMap.PREVIEW_INCIDENT_LIST]: new Route( - `/status-page/${RouteParams.StatusPageId}/incidents` - ), - [PageMap.PREVIEW_INCIDENT_DETAIL]: new Route( - `/status-page/${RouteParams.StatusPageId}/incidents/:id` - ), - [PageMap.PREVIEW_ANNOUNCEMENT_DETAIL]: new Route( - `/status-page/${RouteParams.StatusPageId}/announcements/:id` - ), - [PageMap.PREVIEW_ANNOUNCEMENT_LIST]: new Route( - `/status-page/${RouteParams.StatusPageId}/announcements` - ), - [PageMap.PREVIEW_SCHEDULED_EVENT_LIST]: new Route( - `/status-page/${RouteParams.StatusPageId}/scheduled-events` - ), - [PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL]: new Route( - `/status-page/${RouteParams.StatusPageId}/scheduled-events/:id` - ), - [PageMap.PREVIEW_RSS]: new Route( - `/status-page/${RouteParams.StatusPageId}/rss` - ), + [PageMap.PREVIEW_OVERVIEW]: new Route( + `/status-page/${RouteParams.StatusPageId}`, + ), + [PageMap.PREVIEW_INCIDENT_LIST]: new Route( + `/status-page/${RouteParams.StatusPageId}/incidents`, + ), + [PageMap.PREVIEW_INCIDENT_DETAIL]: new Route( + `/status-page/${RouteParams.StatusPageId}/incidents/:id`, + ), + [PageMap.PREVIEW_ANNOUNCEMENT_DETAIL]: new Route( + `/status-page/${RouteParams.StatusPageId}/announcements/:id`, + ), + [PageMap.PREVIEW_ANNOUNCEMENT_LIST]: new Route( + `/status-page/${RouteParams.StatusPageId}/announcements`, + ), + [PageMap.PREVIEW_SCHEDULED_EVENT_LIST]: new Route( + `/status-page/${RouteParams.StatusPageId}/scheduled-events`, + ), + [PageMap.PREVIEW_SCHEDULED_EVENT_DETAIL]: new Route( + `/status-page/${RouteParams.StatusPageId}/scheduled-events/:id`, + ), + [PageMap.PREVIEW_RSS]: new Route( + `/status-page/${RouteParams.StatusPageId}/rss`, + ), - [PageMap.PREVIEW_SUBSCRIBE_EMAIL]: new Route( - `/status-page/${RouteParams.StatusPageId}/subscribe/email` - ), + [PageMap.PREVIEW_SUBSCRIBE_EMAIL]: new Route( + `/status-page/${RouteParams.StatusPageId}/subscribe/email`, + ), - [PageMap.PREVIEW_SUBSCRIBE_SMS]: new Route( - `/status-page/${RouteParams.StatusPageId}/subscribe/sms` - ), + [PageMap.PREVIEW_SUBSCRIBE_SMS]: new Route( + `/status-page/${RouteParams.StatusPageId}/subscribe/sms`, + ), - [PageMap.PREVIEW_SUBSCRIBE_WEBHOOKS]: new Route( - `/status-page/${RouteParams.StatusPageId}/subscribe/webhooks` - ), + [PageMap.PREVIEW_SUBSCRIBE_WEBHOOKS]: new Route( + `/status-page/${RouteParams.StatusPageId}/subscribe/webhooks`, + ), - [PageMap.NOT_FOUND]: new Route(`status-page/not-found`), + [PageMap.NOT_FOUND]: new Route(`status-page/not-found`), - [PageMap.PREVIEW_LOGIN]: new Route( - `/status-page/${RouteParams.StatusPageId}/login` - ), - [PageMap.PREVIEW_FORGOT_PASSWORD]: new Route( - `/status-page/${RouteParams.StatusPageId}/forgot-password` - ), - [PageMap.PREVIEW_RESET_PASSWORD]: new Route( - `/status-page/${RouteParams.StatusPageId}/reset-password/:token` - ), + [PageMap.PREVIEW_LOGIN]: new Route( + `/status-page/${RouteParams.StatusPageId}/login`, + ), + [PageMap.PREVIEW_FORGOT_PASSWORD]: new Route( + `/status-page/${RouteParams.StatusPageId}/forgot-password`, + ), + [PageMap.PREVIEW_RESET_PASSWORD]: new Route( + `/status-page/${RouteParams.StatusPageId}/reset-password/:token`, + ), - [PageMap.PREVIEW_SSO]: new Route( - `/status-page/${RouteParams.StatusPageId}/sso` - ), + [PageMap.PREVIEW_SSO]: new Route( + `/status-page/${RouteParams.StatusPageId}/sso`, + ), - [PageMap.PREVIEW_LOGOUT]: new Route( - `/status-page/${RouteParams.StatusPageId}/logout` - ), + [PageMap.PREVIEW_LOGOUT]: new Route( + `/status-page/${RouteParams.StatusPageId}/logout`, + ), - [PageMap.PREVIEW_UPDATE_SUBSCRIPTION]: new Route( - `/status-page/${RouteParams.StatusPageId}/update-subscription/:id` - ), + [PageMap.PREVIEW_UPDATE_SUBSCRIPTION]: new Route( + `/status-page/${RouteParams.StatusPageId}/update-subscription/:id`, + ), }; export class RouteUtil { - public static populateRouteParams(route: Route, modelId?: ObjectID): Route { - const tempRoute: Route = new Route(route.toString()); + public static populateRouteParams(route: Route, modelId?: ObjectID): Route { + const tempRoute: Route = new Route(route.toString()); - if (modelId) { - route = tempRoute.addRouteParam( - RouteParams.ModelID, - modelId.toString() - ); - } - - const id: ObjectID = LocalStorage.getItem('statusPageId') as ObjectID; - - if (id) { - route = tempRoute.addRouteParam( - RouteParams.StatusPageId, - id.toString() - ); - } - - return tempRoute; + if (modelId) { + route = tempRoute.addRouteParam(RouteParams.ModelID, modelId.toString()); } + + const id: ObjectID = LocalStorage.getItem("statusPageId") as ObjectID; + + if (id) { + route = tempRoute.addRouteParam(RouteParams.StatusPageId, id.toString()); + } + + return tempRoute; + } } export default RouteMap; diff --git a/StatusPage/src/Utils/RouteParams.ts b/StatusPage/src/Utils/RouteParams.ts index e5544fb915..0c64ef274d 100644 --- a/StatusPage/src/Utils/RouteParams.ts +++ b/StatusPage/src/Utils/RouteParams.ts @@ -1,6 +1,6 @@ enum RouteParams { - StatusPageId = ':statusPageId', - ModelID = ':id', + StatusPageId = ":statusPageId", + ModelID = ":id", } export default RouteParams; diff --git a/StatusPage/src/Utils/StatusPage.ts b/StatusPage/src/Utils/StatusPage.ts index 7b4df30044..f7e1c5d0cf 100644 --- a/StatusPage/src/Utils/StatusPage.ts +++ b/StatusPage/src/Utils/StatusPage.ts @@ -1,73 +1,72 @@ -import UserUtil from './User'; -import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; -import Route from 'Common/Types/API/Route'; -import ObjectID from 'Common/Types/ObjectID'; -import Typeof from 'Common/Types/Typeof'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; -import Navigation from 'CommonUI/src/Utils/Navigation'; +import UserUtil from "./User"; +import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import Route from "Common/Types/API/Route"; +import ObjectID from "Common/Types/ObjectID"; +import Typeof from "Common/Types/Typeof"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; +import Navigation from "CommonUI/src/Utils/Navigation"; export default class StatusPageUtil { - public static getStatusPageId(): ObjectID | null { - const value: ObjectID | null = LocalStorage.getItem( - 'statusPageId' - ) as ObjectID | null; + public static getStatusPageId(): ObjectID | null { + const value: ObjectID | null = LocalStorage.getItem( + "statusPageId", + ) as ObjectID | null; - if (value && typeof value === Typeof.String) { - return new ObjectID(value.toString()); - } - - return value; + if (value && typeof value === Typeof.String) { + return new ObjectID(value.toString()); } - public static setStatusPageId(id: ObjectID | null): void { - LocalStorage.setItem('statusPageId', id); + return value; + } + + public static setStatusPageId(id: ObjectID | null): void { + LocalStorage.setItem("statusPageId", id); + } + + public static setIsPrivateStatusPage(isPrivate: boolean): void { + LocalStorage.setItem("isPrivateStatusPage", isPrivate); + } + + public static isPrivateStatusPage(): boolean { + return Boolean(LocalStorage.getItem("isPrivateStatusPage")); + } + + public static isPreviewPage(): boolean { + return Navigation.containsInPath("/status-page/"); + } + + public static navigateToLoginPage(): void { + Navigation.navigate( + new Route( + StatusPageUtil.isPreviewPage() + ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/login?redirectUrl=${Navigation.getCurrentPath()}` + : `/login?redirectUrl=${Navigation.getCurrentPath()}`, + ), + { forceNavigate: true }, + ); + } + + public static checkIfUserHasLoggedIn(): void { + const statusPageId: ObjectID | null = StatusPageUtil.getStatusPageId(); + + if ( + statusPageId && + StatusPageUtil.isPrivateStatusPage() && + !UserUtil.isLoggedIn(statusPageId) + ) { + StatusPageUtil.navigateToLoginPage(); } + } - public static setIsPrivateStatusPage(isPrivate: boolean): void { - LocalStorage.setItem('isPrivateStatusPage', isPrivate); - } - - public static isPrivateStatusPage(): boolean { - return Boolean(LocalStorage.getItem('isPrivateStatusPage')); - } - - public static isPreviewPage(): boolean { - return Navigation.containsInPath('/status-page/'); - } - - public static navigateToLoginPage(): void { - Navigation.navigate( - new Route( - StatusPageUtil.isPreviewPage() - ? `/status-page/${StatusPageUtil.getStatusPageId()?.toString()}/login?redirectUrl=${Navigation.getCurrentPath()}` - : `/login?redirectUrl=${Navigation.getCurrentPath()}` - ), - { forceNavigate: true } - ); - } - - public static checkIfUserHasLoggedIn(): void { - - const statusPageId: ObjectID | null = StatusPageUtil.getStatusPageId(); - - if ( - statusPageId && - StatusPageUtil.isPrivateStatusPage() && - !UserUtil.isLoggedIn(statusPageId) - ) { - StatusPageUtil.navigateToLoginPage(); - } - } - - public static async checkIfTheUserIsAuthenticated( - errorResponse: HTTPErrorResponse - ): Promise<void> { - if ( - errorResponse instanceof HTTPErrorResponse && - errorResponse.statusCode === 401 - ) { - await UserUtil.logout(StatusPageUtil.getStatusPageId()!); - StatusPageUtil.navigateToLoginPage(); - } + public static async checkIfTheUserIsAuthenticated( + errorResponse: HTTPErrorResponse, + ): Promise<void> { + if ( + errorResponse instanceof HTTPErrorResponse && + errorResponse.statusCode === 401 + ) { + await UserUtil.logout(StatusPageUtil.getStatusPageId()!); + StatusPageUtil.navigateToLoginPage(); } + } } diff --git a/StatusPage/src/Utils/User.ts b/StatusPage/src/Utils/User.ts index 9538f64f71..03b0ac4b63 100644 --- a/StatusPage/src/Utils/User.ts +++ b/StatusPage/src/Utils/User.ts @@ -1,92 +1,86 @@ -import URL from 'Common/Types/API/URL'; -import Email from 'Common/Types/Email'; -import Name from 'Common/Types/Name'; -import ObjectID from 'Common/Types/ObjectID'; -import API from 'Common/Utils/API'; -import { IDENTITY_URL } from 'CommonUI/src/Config'; -import LocalStorage from 'CommonUI/src/Utils/LocalStorage'; +import URL from "Common/Types/API/URL"; +import Email from "Common/Types/Email"; +import Name from "Common/Types/Name"; +import ObjectID from "Common/Types/ObjectID"; +import API from "Common/Utils/API"; +import { IDENTITY_URL } from "CommonUI/src/Config"; +import LocalStorage from "CommonUI/src/Utils/LocalStorage"; export default class User { - public static setUserId(statusPageId: ObjectID, userId: ObjectID): void { - LocalStorage.setItem( - statusPageId.toString() + 'user_id', - userId.toString() - ); + public static setUserId(statusPageId: ObjectID, userId: ObjectID): void { + LocalStorage.setItem( + statusPageId.toString() + "user_id", + userId.toString(), + ); + } + + public static getUserId(statusPageId: ObjectID): ObjectID { + return new ObjectID( + (LocalStorage.getItem(statusPageId.toString() + "user_id") as string) || + "", + ); + } + + public static getName(statusPageId: ObjectID): Name { + return new Name( + (LocalStorage.getItem(statusPageId.toString() + "user_name") as string) || + "", + ); + } + + public static setName(statusPageId: ObjectID, name: Name): void { + LocalStorage.setItem( + statusPageId.toString() + "user_name", + name.toString(), + ); + } + + public static removeName(statusPageId: ObjectID): void { + LocalStorage.removeItem(statusPageId.toString() + "user_name"); + } + + public static removeUser(statusPageId: ObjectID): void { + this.removeUserId(statusPageId); + this.removeUserEmail(statusPageId); + this.removeName(statusPageId); + } + + public static getEmail(statusPageId: ObjectID): Email | null { + if (!LocalStorage.getItem(statusPageId.toString() + "user_email")) { + return null; } - public static getUserId(statusPageId: ObjectID): ObjectID { - return new ObjectID( - (LocalStorage.getItem( - statusPageId.toString() + 'user_id' - ) as string) || '' - ); - } + return new Email( + LocalStorage.getItem(statusPageId.toString() + "user_email") as string, + ); + } - public static getName(statusPageId: ObjectID): Name { - return new Name( - (LocalStorage.getItem( - statusPageId.toString() + 'user_name' - ) as string) || '' - ); - } + public static setEmail(statusPageId: ObjectID, email: Email): void { + LocalStorage.setItem(statusPageId.toString() + "user_email", email); + } - public static setName(statusPageId: ObjectID, name: Name): void { - LocalStorage.setItem( - statusPageId.toString() + 'user_name', - name.toString() - ); - } + public static removeUserId(statusPageId: ObjectID): void { + LocalStorage.removeItem(statusPageId.toString() + "user_id"); + } - public static removeName(statusPageId: ObjectID): void { - LocalStorage.removeItem(statusPageId.toString() + 'user_name'); - } + public static removeUserEmail(statusPageId: ObjectID): void { + LocalStorage.removeItem(statusPageId.toString() + "user_email"); + } - public static removeUser(statusPageId: ObjectID): void { - this.removeUserId(statusPageId); - this.removeUserEmail(statusPageId); - this.removeName(statusPageId); - } + public static removeInitialUrl(statusPageId: ObjectID): void { + return sessionStorage.removeItem(statusPageId.toString() + "initialUrl"); + } - public static getEmail(statusPageId: ObjectID): Email | null { - if (!LocalStorage.getItem(statusPageId.toString() + 'user_email')) { - return null; - } + public static isLoggedIn(statusPageId: ObjectID): boolean { + return Boolean(this.getEmail(statusPageId)); + } - return new Email( - LocalStorage.getItem( - statusPageId.toString() + 'user_email' - ) as string - ); - } - - public static setEmail(statusPageId: ObjectID, email: Email): void { - LocalStorage.setItem(statusPageId.toString() + 'user_email', email); - } - - public static removeUserId(statusPageId: ObjectID): void { - LocalStorage.removeItem(statusPageId.toString() + 'user_id'); - } - - public static removeUserEmail(statusPageId: ObjectID): void { - LocalStorage.removeItem(statusPageId.toString() + 'user_email'); - } - - public static removeInitialUrl(statusPageId: ObjectID): void { - return sessionStorage.removeItem( - statusPageId.toString() + 'initialUrl' - ); - } - - public static isLoggedIn(statusPageId: ObjectID): boolean { - return Boolean(this.getEmail(statusPageId)); - } - - public static async logout(statusPageId: ObjectID): Promise<void> { - await API.post( - URL.fromString(IDENTITY_URL.toString()) - .addRoute('/status-page/logout') - .addRoute('/' + statusPageId.toString()) - ); - this.removeUser(statusPageId); - } + public static async logout(statusPageId: ObjectID): Promise<void> { + await API.post( + URL.fromString(IDENTITY_URL.toString()) + .addRoute("/status-page/logout") + .addRoute("/" + statusPageId.toString()), + ); + this.removeUser(statusPageId); + } } diff --git a/StatusPage/webpack.config.js b/StatusPage/webpack.config.js index 0b43b868a1..5fbe53d62c 100644 --- a/StatusPage/webpack.config.js +++ b/StatusPage/webpack.config.js @@ -1,84 +1,87 @@ -require('ts-loader'); -require('file-loader'); -require('style-loader'); -require('css-loader'); -require('sass-loader'); +require("ts-loader"); +require("file-loader"); +require("style-loader"); +require("css-loader"); +require("sass-loader"); const path = require("path"); const webpack = require("webpack"); -const dotenv = require('dotenv'); -const express = require('express'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const dotenv = require("dotenv"); +const express = require("express"); +const BundleAnalyzerPlugin = + require("webpack-bundle-analyzer").BundleAnalyzerPlugin; const readEnvFile = (pathToFile) => { + const parsed = dotenv.config({ path: pathToFile }).parsed; - const parsed = dotenv.config({ path: pathToFile }).parsed; + const env = {}; - const env = {}; + for (const key in parsed) { + env[key] = JSON.stringify(parsed[key]); + } - for (const key in parsed) { - env[key] = JSON.stringify(parsed[key]); - } - - return env; -} + return env; +}; module.exports = { - entry: "./src/Index.tsx", - mode: "development", - output: { - filename: "bundle.js", - path: path.resolve(__dirname, "public", "dist"), - publicPath: "/status-page/dist/", + entry: "./src/Index.tsx", + mode: "development", + output: { + filename: "bundle.js", + path: path.resolve(__dirname, "public", "dist"), + publicPath: "/status-page/dist/", + }, + resolve: { + extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"], + alias: { + react: path.resolve("./node_modules/react"), }, - resolve: { - extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'], - alias: { - react: path.resolve('./node_modules/react'), - } - }, - externals: { - 'react-native-sqlite-storage': 'react-native-sqlite-storage' - }, - plugins: [ - new webpack.DefinePlugin({ - 'process': { - 'env': { - ...readEnvFile('/usr/src/app/dev-env/.env') - } - } - }), - process.env.analyze === 'true' ? new BundleAnalyzerPlugin() : () => { } - ], - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - loader: 'ts-loader', - }, - { - test: /\.s[ac]ss$/i, - use: ['style-loader', 'css-loader', "sass-loader"] - }, - { - test: /\.css$/i, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.(jpe?g|png|gif|svg)$/i, - loader: 'file-loader' - } - ], - }, - devServer: { - historyApiFallback: true, - devMiddleware: { - writeToDisk: true, + }, + externals: { + "react-native-sqlite-storage": "react-native-sqlite-storage", + }, + plugins: [ + new webpack.DefinePlugin({ + process: { + env: { + ...readEnvFile("/usr/src/app/dev-env/.env"), }, - allowedHosts: "all", - setupMiddlewares: (middlewares, devServer) => { - devServer.app.use('/status-page/assets', express.static(path.resolve(__dirname, 'public', 'assets'))); - return middlewares; - } + }, + }), + process.env.analyze === "true" ? new BundleAnalyzerPlugin() : () => {}, + ], + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + loader: "ts-loader", + }, + { + test: /\.s[ac]ss$/i, + use: ["style-loader", "css-loader", "sass-loader"], + }, + { + test: /\.css$/i, + use: ["style-loader", "css-loader"], + }, + { + test: /\.(jpe?g|png|gif|svg)$/i, + loader: "file-loader", + }, + ], + }, + devServer: { + historyApiFallback: true, + devMiddleware: { + writeToDisk: true, }, - devtool: 'eval-source-map', -} \ No newline at end of file + allowedHosts: "all", + setupMiddlewares: (middlewares, devServer) => { + devServer.app.use( + "/status-page/assets", + express.static(path.resolve(__dirname, "public", "assets")), + ); + return middlewares; + }, + }, + devtool: "eval-source-map", +}; diff --git a/TestServer/API/Main.ts b/TestServer/API/Main.ts index 88f58e6a29..05b5536893 100644 --- a/TestServer/API/Main.ts +++ b/TestServer/API/Main.ts @@ -1,88 +1,88 @@ -import { JSONValue } from 'Common/Types/JSON'; -import Sleep from 'Common/Types/Sleep'; -import Typeof from 'Common/Types/Typeof'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; +import { JSONValue } from "Common/Types/JSON"; +import Sleep from "Common/Types/Sleep"; +import Typeof from "Common/Types/Typeof"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, - RequestHandler, -} from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import Response from 'CommonServer/Utils/Response'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, + RequestHandler, +} from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import Response from "CommonServer/Utils/Response"; const router: ExpressRouter = Express.getRouter(); router.get( - '/', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - returnResponse(req, res, next); - } + "/", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + returnResponse(req, res, next); + }, ); router.post( - '/', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - returnResponse(req, res, next); - } + "/", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + returnResponse(req, res, next); + }, ); const returnResponse: RequestHandler = async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, ): Promise<void> => { - try { - logger.debug('Request Headers: '); - logger.debug(req.headers); - logger.debug('Request Body: '); - logger.debug(req.body); + try { + logger.debug("Request Headers: "); + logger.debug(req.headers); + logger.debug("Request Body: "); + logger.debug(req.body); - const responseCode: number | undefined = - LocalCache.getNumber('TestServer', 'responseCode') || 200; - const responseTime: number | undefined = - LocalCache.getNumber('TestServer', 'responseTime') || 0; - const responseBody: string | undefined = - LocalCache.getString('TestServer', 'responseBody') || ''; - let responseHeaders: JSONValue | undefined = - LocalCache.getJSON('TestServer', 'responseHeaders') || {}; + const responseCode: number | undefined = + LocalCache.getNumber("TestServer", "responseCode") || 200; + const responseTime: number | undefined = + LocalCache.getNumber("TestServer", "responseTime") || 0; + const responseBody: string | undefined = + LocalCache.getString("TestServer", "responseBody") || ""; + let responseHeaders: JSONValue | undefined = + LocalCache.getJSON("TestServer", "responseHeaders") || {}; - logger.debug('Response Code: ' + responseCode); - logger.debug('Response Time: ' + responseTime); - logger.debug('Response Body: '); - logger.debug(responseBody); - logger.debug('Response Headers: '); - logger.debug(responseHeaders); + logger.debug("Response Code: " + responseCode); + logger.debug("Response Time: " + responseTime); + logger.debug("Response Body: "); + logger.debug(responseBody); + logger.debug("Response Headers: "); + logger.debug(responseHeaders); - if (responseHeaders && typeof responseHeaders === Typeof.String) { - responseHeaders = JSON.parse(responseHeaders.toString()); - } - - if (responseTime > 0) { - await Sleep.sleep(responseTime); - } - - // middleware marks the probe as alive. - // so we don't need to do anything here. - return Response.sendCustomResponse( - req, - res, - responseCode, - responseBody, - responseHeaders ? (responseHeaders as any) : {} - ); - } catch (err) { - return next(err); + if (responseHeaders && typeof responseHeaders === Typeof.String) { + responseHeaders = JSON.parse(responseHeaders.toString()); } + + if (responseTime > 0) { + await Sleep.sleep(responseTime); + } + + // middleware marks the probe as alive. + // so we don't need to do anything here. + return Response.sendCustomResponse( + req, + res, + responseCode, + responseBody, + responseHeaders ? (responseHeaders as any) : {}, + ); + } catch (err) { + return next(err); + } }; export default router; diff --git a/TestServer/API/Settings.ts b/TestServer/API/Settings.ts index 8d40daae12..25e7c18e45 100644 --- a/TestServer/API/Settings.ts +++ b/TestServer/API/Settings.ts @@ -1,75 +1,63 @@ -import { JSONObject } from 'Common/Types/JSON'; -import LocalCache from 'CommonServer/Infrastructure/LocalCache'; +import { JSONObject } from "Common/Types/JSON"; +import LocalCache from "CommonServer/Infrastructure/LocalCache"; import Express, { - ExpressRequest, - ExpressResponse, - ExpressRouter, - NextFunction, -} from 'CommonServer/Utils/Express'; -import Response from 'CommonServer/Utils/Response'; + ExpressRequest, + ExpressResponse, + ExpressRouter, + NextFunction, +} from "CommonServer/Utils/Express"; +import Response from "CommonServer/Utils/Response"; const router: ExpressRouter = Express.getRouter(); router.post( - '/settings', - async ( - req: ExpressRequest, - res: ExpressResponse, - next: NextFunction - ): Promise<void> => { - try { - const data: JSONObject = req.body; + "/settings", + async ( + req: ExpressRequest, + res: ExpressResponse, + next: NextFunction, + ): Promise<void> => { + try { + const data: JSONObject = req.body; - const responseType: string | undefined = data['responseType'] as - | string - | undefined; - const responseCode: number | undefined = data['responseCode'] as - | number - | undefined; - const responseTime: number | undefined = data['responseTime'] as - | number - | undefined; - const responseBody: string | undefined = data['responseBody'] as - | string - | undefined; + const responseType: string | undefined = data["responseType"] as + | string + | undefined; + const responseCode: number | undefined = data["responseCode"] as + | number + | undefined; + const responseTime: number | undefined = data["responseTime"] as + | number + | undefined; + const responseBody: string | undefined = data["responseBody"] as + | string + | undefined; - const responseHeaders: JSONObject | undefined = data[ - 'responseHeaders' - ] as JSONObject | undefined; + const responseHeaders: JSONObject | undefined = data[ + "responseHeaders" + ] as JSONObject | undefined; - LocalCache.setJSON( - 'TestServer', - 'responseHeaders', - responseHeaders || '' - ); - LocalCache.setString( - 'TestServer', - 'responseType', - responseType || 'JSON' - ); - LocalCache.setNumber( - 'TestServer', - 'responseCode', - responseCode || 200 - ); - LocalCache.setNumber( - 'TestServer', - 'responseTime', - responseTime || 0 - ); - LocalCache.setString( - 'TestServer', - 'responseBody', - responseBody || '' - ); + LocalCache.setJSON( + "TestServer", + "responseHeaders", + responseHeaders || "", + ); + LocalCache.setString( + "TestServer", + "responseType", + responseType || "JSON", + ); + LocalCache.setNumber("TestServer", "responseCode", responseCode || 200); + LocalCache.setNumber("TestServer", "responseTime", responseTime || 0); + LocalCache.setString("TestServer", "responseBody", responseBody || ""); - // middleware marks the probe as alive. - // so we don't need to do anything here. - return Response.sendEmptySuccessResponse(req, res); - } catch (err) { - return next(err); - } + // middleware marks the probe as alive. + // so we don't need to do anything here. + return Response.sendEmptySuccessResponse(req, res); + } catch (err) { + return next(err); } + }, ); export default router; diff --git a/TestServer/Index.ts b/TestServer/Index.ts index 989ad56ae6..2f392a2eec 100644 --- a/TestServer/Index.ts +++ b/TestServer/Index.ts @@ -1,42 +1,42 @@ -import MainAPI from './API/Main'; -import SettingsAPI from './API/Settings'; -import { PromiseVoidFunction } from 'Common/Types/FunctionTypes'; -import Express, { ExpressApplication } from 'CommonServer/Utils/Express'; -import logger from 'CommonServer/Utils/Logger'; -import App from 'CommonServer/Utils/StartServer'; -import 'ejs'; +import MainAPI from "./API/Main"; +import SettingsAPI from "./API/Settings"; +import { PromiseVoidFunction } from "Common/Types/FunctionTypes"; +import Express, { ExpressApplication } from "CommonServer/Utils/Express"; +import logger from "CommonServer/Utils/Logger"; +import App from "CommonServer/Utils/StartServer"; +import "ejs"; const app: ExpressApplication = Express.getExpressApp(); -const APP_NAME: string = 'test-server'; +const APP_NAME: string = "test-server"; -app.use([`/${APP_NAME}`, '/'], MainAPI); -app.use([`/${APP_NAME}`, '/'], SettingsAPI); +app.use([`/${APP_NAME}`, "/"], MainAPI); +app.use([`/${APP_NAME}`, "/"], SettingsAPI); const init: PromiseVoidFunction = async (): Promise<void> => { - try { - // init the app - await App.init({ - appName: APP_NAME, - port: undefined, - isFrontendApp: false, - statusOptions: { - liveCheck: async () => {}, - readyCheck: async () => {}, - }, - }); + try { + // init the app + await App.init({ + appName: APP_NAME, + port: undefined, + isFrontendApp: false, + statusOptions: { + liveCheck: async () => {}, + readyCheck: async () => {}, + }, + }); - // add default routes - await App.addDefaultRoutes(); - } catch (err) { - logger.error('App Init Failed:'); - logger.error(err); - throw err; - } + // add default routes + await App.addDefaultRoutes(); + } catch (err) { + logger.error("App Init Failed:"); + logger.error(err); + throw err; + } }; init().catch((err: Error) => { - logger.error(err); - logger.error('Exiting node process'); - process.exit(1); + logger.error(err); + logger.error("Exiting node process"); + process.exit(1); }); diff --git a/babel.config.ts b/babel.config.ts index 4eba1e3cde..ab5062c8f3 100644 --- a/babel.config.ts +++ b/babel.config.ts @@ -1,6 +1,6 @@ export default { - plugins: [ - '@babel/plugin-proposal-private-methods', - '@babel/plugin-proposal-class-properties', - ], + plugins: [ + "@babel/plugin-proposal-private-methods", + "@babel/plugin-proposal-class-properties", + ], }; diff --git a/eslint.config.js b/eslint.config.js index 6f4d780064..32ab63ef4d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,13 +1,26 @@ // @ts-check -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; export default tseslint.config( - { - ignores: ['**/node_modules/', '**/dist/', '**/build/', '**/out/', '**/coverage/', '**/lib/', '**/esm/', '**/cjs/', '**/playwright-report/', '**/playwright-coverage/', '**/playwright-screenshots/', '**/playwright-videos'], - }, - eslint.configs.recommended, - ...tseslint.configs.strict, - eslintPluginPrettierRecommended + { + ignores: [ + "**/node_modules/", + "**/dist/", + "**/build/", + "**/out/", + "**/coverage/", + "**/lib/", + "**/esm/", + "**/cjs/", + "**/playwright-report/", + "**/playwright-coverage/", + "**/playwright-screenshots/", + "**/playwright-videos", + ], + }, + eslint.configs.recommended, + ...tseslint.configs.strict, + eslintPluginPrettierRecommended, );